Skip to content

Commit cd1dfbb

Browse files
committed
Rewritten on LFS API
1 parent 7c744c1 commit cd1dfbb

File tree

12 files changed

+276
-115
lines changed

12 files changed

+276
-115
lines changed

models/lfs.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ type LFSMetaObject struct {
2727
CreatedUnix timeutil.TimeStamp `xorm:"created"`
2828
}
2929

30+
// LFSMetaObjectBasic represents basic LFS metadata.
31+
type LFSMetaObjectBasic struct {
32+
Oid string `xorm:"UNIQUE(s) INDEX NOT NULL"`
33+
Size int64 `xorm:"NOT NULL"`
34+
}
35+
3036
// RelativePath returns the relative path of the lfs object
3137
func (m *LFSMetaObject) RelativePath() string {
3238
if len(m.Oid) < 5 {

modules/forms/repo_form.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ type MigrateRepoForm struct {
7474
RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"`
7575
Mirror bool `json:"mirror"`
7676
LFS bool `json:"lfs"`
77+
LFSServer string `json:"lfs_server"`
78+
LFSFetchOlder bool `json:"lfs_fetch_older"`
7779
Private bool `json:"private"`
7880
Description string `json:"description" binding:"MaxSize(255)"`
7981
Uncyclo bool `json:"wiki"`

modules/lfs/client.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package lfs
6+
7+
import (
8+
"encoding/json"
9+
"bytes"
10+
"fmt"
11+
"io"
12+
"net/http"
13+
"context"
14+
15+
"code.gitea.io/gitea/models"
16+
"code.gitea.io/gitea/modules/log"
17+
"code.gitea.io/gitea/modules/storage"
18+
)
19+
20+
type BatchRequest struct {
21+
Operation string `json:"operation"`
22+
Transfers []string `json:"transfers,omitempty"`
23+
Ref *Reference `json:"ref,omitempty"`
24+
Objects []*models.LFSMetaObjectBasic `json:"objects"`
25+
}
26+
27+
type Reference struct {
28+
Name string `json:"name"`
29+
}
30+
31+
func packbatch(operation string, transfers []string, ref *Reference, metaObjects []*models.LFSMetaObject) (string, error) {
32+
metaObjectsBasic := []*models.LFSMetaObjectBasic{}
33+
for _, meta := range metaObjects {
34+
metaBasic := &LFSMetaObjectBasic{meta.oid, meta.size}
35+
metaObjectsBasic = append(metaObjectsBasic, metaBasic)
36+
}
37+
38+
reqobj := &BatchRequest{operation, transfers, ref, metaObjectsBasic}
39+
40+
buf := &bytes.Buffer{}
41+
if err := json.NewEncoder(buf).Encode(reqobj); err != nil {
42+
return nil, fmt.Errorf("Failed to encode BatchRequest as json. Error: %v", err)
43+
}
44+
return buf, nil
45+
}
46+
47+
func BasicTransferAdapter(href string, size int64) (io.ReadCloser, error) {
48+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, href, nil)
49+
if err != nil {
50+
return err
51+
}
52+
req.Header.Set("Content-type", "application/octet-stream")
53+
req.Header.Set("Content-Length", size)
54+
55+
resp, err := client.Do(req)
56+
if err != nil {
57+
select {
58+
case <-ctx.Done():
59+
return ctx.Err()
60+
default:
61+
}
62+
return err
63+
}
64+
defer resp.Body.Close()
65+
66+
if resp.StatusCode != http.StatusOK {
67+
return decodeJSONError(resp).Err
68+
}
69+
return resp.Body
70+
}
71+
72+
func FetchLFSFilesToContentStore(ctx *context.Context, metaObjects []*models.LFSMetaObject, userName string, repo *models.Repository, LFSServer string) error {
73+
client := http.DefaultClient
74+
75+
rv, err := packbatch("download", nil, nil, metaObjects)
76+
if err != nil {
77+
return err
78+
}
79+
batchAPIURL := LFSServer + "/objects/batch"
80+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, batchAPIURL, rv)
81+
if err != nil {
82+
return err
83+
}
84+
req.Header.Set("Content-type", metaMediaType)
85+
req.Header.Set("Accept", metaMediaType)
86+
87+
resp, err := client.Do(req)
88+
if err != nil {
89+
select {
90+
case <-ctx.Done():
91+
return ctx.Err()
92+
default:
93+
}
94+
return err
95+
}
96+
defer resp.Body.Close()
97+
98+
if resp.StatusCode != http.StatusOK {
99+
return decodeJSONError(resp).Err
100+
}
101+
102+
var respBatch BatchResponse
103+
err = json.NewDecoder(resp.Body).Decode(&respBatch)
104+
if err != nil {
105+
return err
106+
}
107+
108+
if respBatch.Transfer == nil {
109+
respBatch.Transfer = "basic"
110+
}
111+
112+
contentStore := &lfs.ContentStore{ObjectStorage: storage.LFS}
113+
114+
for _, rep := range respBatch.Objects {
115+
rc := BasicTransferAdapter(rep.Actions["download"].Href, rep.Size)
116+
meta, err := GetLFSMetaObjectByOid(rep.Oid)
117+
if err != nil {
118+
log.Error("Unable to get LFS OID[%s] Error: %v", rep.Oid, err)
119+
return err
120+
}
121+
122+
// put LFS file to contentStore
123+
exist, err := contentStore.Exists(meta)
124+
if err != nil {
125+
log.Error("Unable to check if LFS OID[%s] exist on %s/%s. Error: %v", meta.Oid, userName, repo.Name, err)
126+
return err
127+
}
128+
129+
if exist {
130+
// remove collision
131+
if _, err := repo.RemoveLFSMetaObjectByOid(meta.Oid); err != nil {
132+
return fmt.Errorf("Error whilst removing matched LFS object %s: %v", meta.Oid, err)
133+
}
134+
}
135+
136+
if err := contentStore.Put(meta, rc); err != nil {
137+
if _, err2 := repo.RemoveLFSMetaObjectByOid(meta.Oid); err2 != nil {
138+
return fmt.Errorf("Error whilst removing failed inserted LFS object %s: %v (Prev Error: %v)", meta.Oid, err2, err)
139+
}
140+
return err
141+
}
142+
}
143+
}

modules/migrations/base/options.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ type MigrateOptions struct {
2121
RepoName string `json:"repo_name" binding:"Required"`
2222
Mirror bool `json:"mirror"`
2323
LFS bool `json:"lfs"`
24+
LFSServer string `json:"lfs_server"`
25+
LFSFetchOlder bool `json:"lfs_fetch_older"`
2426
Private bool `json:"private"`
2527
Description string `json:"description"`
2628
OriginalURL string

modules/migrations/gitea_uploader.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate
118118
GitServiceType: opts.GitServiceType,
119119
Mirror: repo.IsMirror,
120120
LFS: opts.LFS,
121+
LFSServer: opts.LFSServer,
122+
LFSFetchOlder: opts.LFSFetchOlder,
121123
CloneAddr: repo.CloneURL,
122124
Private: repo.IsPrivate,
123125
Uncyclo: opts.Uncyclo,

0 commit comments

Comments
 (0)