diff --git a/integrations/lfs_getobject_test.go b/integrations/lfs_getobject_test.go
index f364349ef140a..477fc4a61ae19 100644
--- a/integrations/lfs_getobject_test.go
+++ b/integrations/lfs_getobject_test.go
@@ -16,7 +16,6 @@ import (
"testing"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/routers/routes"
@@ -50,7 +49,7 @@ func storeObjectInRepo(t *testing.T, repositoryID int64, content *[]byte) string
lfsID++
lfsMetaObject, err = models.NewLFSMetaObject(lfsMetaObject)
assert.NoError(t, err)
- contentStore := &lfs.ContentStore{ObjectStorage: storage.LFS}
+ contentStore := &models.ContentStore{ObjectStorage: storage.LFS}
exist, err := contentStore.Exists(lfsMetaObject)
assert.NoError(t, err)
if !exist {
diff --git a/models/error.go b/models/error.go
index 84b7ebbfa35b0..8ce3db4e6132c 100644
--- a/models/error.go
+++ b/models/error.go
@@ -56,6 +56,20 @@ func (err ErrNamePatternNotAllowed) Error() string {
return fmt.Sprintf("name pattern is not allowed [pattern: %s]", err.Pattern)
}
+// ErrMirrorLFSServerNotValid represents an "LFS Server not valid" error.
+type ErrMirrorLFSServerNotValid struct {
+}
+
+// IsMirrorLFSServerValid checks if an error is an ErrMirrorLFSServerNotValid.
+func IsMirrorLFSServerValid(err error) bool {
+ _, ok := err.(ErrMirrorLFSServerNotValid)
+ return ok
+}
+
+func (err ErrMirrorLFSServerNotValid) Error() string {
+ return "LFS Server not valid"
+}
+
// ErrNameCharsNotAllowed represents a "character not allowed in name" error.
type ErrNameCharsNotAllowed struct {
Name string
diff --git a/models/lfs.go b/models/lfs.go
index 274b32a736758..1f52678df78c3 100644
--- a/models/lfs.go
+++ b/models/lfs.go
@@ -11,7 +11,12 @@ import (
"fmt"
"io"
"path"
+ "strconv"
+ "strings"
+ "time"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/builder"
@@ -27,6 +32,70 @@ type LFSMetaObject struct {
CreatedUnix timeutil.TimeStamp `xorm:"created"`
}
+// LFSMetaObjectBasic represents basic LFS metadata.
+type LFSMetaObjectBasic struct {
+ Oid string `json:"oid"`
+ Size int64 `json:"size"`
+}
+
+// IsPointerFileAndStored will return a partially filled LFSMetaObject if the provided byte slice is a pointer file and stored in contentStore
+func IsPointerFileAndStored(buf *[]byte) *LFSMetaObject {
+ if !setting.LFS.StartServer {
+ return nil
+ }
+
+ headString := string(*buf)
+ if !strings.HasPrefix(headString, LFSMetaFileIdentifier) {
+ return nil
+ }
+
+ splitLines := strings.Split(headString, "\n")
+ if len(splitLines) < 3 {
+ return nil
+ }
+
+ oid := strings.TrimPrefix(splitLines[1], LFSMetaFileOidPrefix)
+ size, err := strconv.ParseInt(strings.TrimPrefix(splitLines[2], "size "), 10, 64)
+ if len(oid) != 64 || err != nil {
+ return nil
+ }
+
+ contentStore := &ContentStore{ObjectStorage: storage.LFS}
+ meta := &LFSMetaObject{Oid: oid, Size: size}
+ exist, err := contentStore.Exists(meta)
+ if err != nil || !exist {
+ return nil
+ }
+
+ return meta
+}
+
+// IsPointerFile will return a partially filled LFSMetaObject if the provided byte slice is a pointer file
+func IsPointerFile(buf *[]byte) *LFSMetaObjectBasic {
+ if !setting.LFS.StartServer {
+ return nil
+ }
+
+ headString := string(*buf)
+ if !strings.HasPrefix(headString, LFSMetaFileIdentifier) {
+ return nil
+ }
+
+ splitLines := strings.Split(headString, "\n")
+ if len(splitLines) < 3 {
+ return nil
+ }
+
+ oid := strings.TrimPrefix(splitLines[1], LFSMetaFileOidPrefix)
+ size, err := strconv.ParseInt(strings.TrimPrefix(splitLines[2], "size "), 10, 64)
+ if len(oid) != 64 || err != nil {
+ return nil
+ }
+ meta := &LFSMetaObjectBasic{Oid: oid, Size: size}
+
+ return meta
+}
+
// RelativePath returns the relative path of the lfs object
func (m *LFSMetaObject) RelativePath() string {
if len(m.Oid) < 5 {
@@ -234,3 +303,31 @@ func IterateLFS(f func(mo *LFSMetaObject) error) error {
}
}
}
+
+// BatchResponse contains multiple object metadata Representation structures
+// for use with the batch API.
+type BatchResponse struct {
+ Transfer string `json:"transfer,omitempty"`
+ Objects []*Representation `json:"objects"`
+}
+
+// Representation is object metadata as seen by clients of the lfs server.
+type Representation struct {
+ Oid string `json:"oid"`
+ Size int64 `json:"size"`
+ Actions map[string]*Link `json:"actions"`
+ Error *ObjectError `json:"error,omitempty"`
+}
+
+// ObjectError defines the JSON structure returned to the client in case of an error
+type ObjectError struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+}
+
+// Link provides a structure used to build a hypermedia representation of an HTTP link.
+type Link struct {
+ Href string `json:"href"`
+ Header map[string]string `json:"header,omitempty"`
+ ExpiresAt time.Time `json:"expires_at,omitempty"`
+}
diff --git a/modules/lfs/content_store.go b/models/lfs_content_store.go
similarity index 91%
rename from modules/lfs/content_store.go
rename to models/lfs_content_store.go
index 788ef5b9a6950..8122b0b49deb7 100644
--- a/modules/lfs/content_store.go
+++ b/models/lfs_content_store.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
-package lfs
+package models
import (
"crypto/sha256"
@@ -13,7 +13,6 @@ import (
"io"
"os"
- "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/storage"
)
@@ -45,7 +44,7 @@ type ContentStore struct {
// Get takes a Meta object and retrieves the content from the store, returning
// it as an io.Reader. If fromByte > 0, the reader starts from that byte
-func (s *ContentStore) Get(meta *models.LFSMetaObject, fromByte int64) (io.ReadCloser, error) {
+func (s *ContentStore) Get(meta *LFSMetaObject, fromByte int64) (io.ReadCloser, error) {
f, err := s.Open(meta.RelativePath())
if err != nil {
log.Error("Whilst trying to read LFS OID[%s]: Unable to open Error: %v", meta.Oid, err)
@@ -66,7 +65,7 @@ func (s *ContentStore) Get(meta *models.LFSMetaObject, fromByte int64) (io.ReadC
}
// Put takes a Meta object and an io.Reader and writes the content to the store.
-func (s *ContentStore) Put(meta *models.LFSMetaObject, r io.Reader) error {
+func (s *ContentStore) Put(meta *LFSMetaObject, r io.Reader) error {
p := meta.RelativePath()
// Wrap the provided reader with an inline hashing and size checker
@@ -92,7 +91,7 @@ func (s *ContentStore) Put(meta *models.LFSMetaObject, r io.Reader) error {
}
// Exists returns true if the object exists in the content store.
-func (s *ContentStore) Exists(meta *models.LFSMetaObject) (bool, error) {
+func (s *ContentStore) Exists(meta *LFSMetaObject) (bool, error) {
_, err := s.ObjectStorage.Stat(meta.RelativePath())
if err != nil {
if os.IsNotExist(err) {
@@ -104,7 +103,7 @@ func (s *ContentStore) Exists(meta *models.LFSMetaObject) (bool, error) {
}
// Verify returns true if the object exists in the content store and size is correct.
-func (s *ContentStore) Verify(meta *models.LFSMetaObject) (bool, error) {
+func (s *ContentStore) Verify(meta *LFSMetaObject) (bool, error) {
p := meta.RelativePath()
fi, err := s.ObjectStorage.Stat(p)
if os.IsNotExist(err) || (err == nil && fi.Size() != meta.Size) {
diff --git a/models/repo.go b/models/repo.go
index 2c71fc3e1eede..874951ea6f5d8 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -215,10 +215,12 @@ type Repository struct {
NumClosedProjects int `xorm:"NOT NULL DEFAULT 0"`
NumOpenProjects int `xorm:"-"`
- IsPrivate bool `xorm:"INDEX"`
- IsEmpty bool `xorm:"INDEX"`
- IsArchived bool `xorm:"INDEX"`
- IsMirror bool `xorm:"INDEX"`
+ IsPrivate bool `xorm:"INDEX"`
+ IsEmpty bool `xorm:"INDEX"`
+ IsArchived bool `xorm:"INDEX"`
+ IsMirror bool `xorm:"INDEX"`
+ LFS bool `xorm:"INDEX"`
+ LFSServer string `xorm:"TEXT"`
*Mirror `xorm:"-"`
Status RepositoryStatus `xorm:"NOT NULL DEFAULT 0"`
@@ -945,7 +947,7 @@ func (repo *Repository) CloneLink() (cl *CloneLink) {
}
// CheckCreateRepository check if could created a repository
-func CheckCreateRepository(doer, u *User, name string, overwriteOrAdopt bool) error {
+func CheckCreateRepository(doer, u *User, name string, lfs bool, lfsServer string, overwriteOrAdopt bool) error {
if !doer.CanCreateRepo() {
return ErrReachLimitOfRepo{u.MaxRepoCreation}
}
@@ -969,6 +971,13 @@ func CheckCreateRepository(doer, u *User, name string, overwriteOrAdopt bool) er
if !overwriteOrAdopt && isExist {
return ErrRepoFilesAlreadyExist{u.Name, name}
}
+
+ if lfs {
+ _, err := url.ParseRequestURI(lfsServer)
+ if err != nil {
+ return ErrMirrorLFSServerNotValid{}
+ }
+ }
return nil
}
diff --git a/modules/forms/repo_form.go b/modules/forms/repo_form.go
index ab88aef571f0b..775f4956e16d1 100644
--- a/modules/forms/repo_form.go
+++ b/modules/forms/repo_form.go
@@ -73,6 +73,9 @@ type MigrateRepoForm struct {
// required: true
RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"`
Mirror bool `json:"mirror"`
+ LFS bool `json:"lfs"`
+ LFSServer string `json:"lfs_server"`
+ LFSFetchOlder bool `json:"lfs_fetch_older"`
Private bool `json:"private"`
Description string `json:"description" binding:"MaxSize(255)"`
Wiki bool `json:"wiki"`
diff --git a/modules/git/blob.go b/modules/git/blob.go
index 674a6a9592778..e3fa0f2fdd672 100644
--- a/modules/git/blob.go
+++ b/modules/git/blob.go
@@ -32,6 +32,19 @@ func (b *Blob) GetBlobContent() (string, error) {
return string(buf), nil
}
+// GetBlobFirstBytes gets limited content of the blob as bytes
+func (b *Blob) GetBlobFirstBytes(limit int) ([]byte, error) {
+ dataRc, err := b.DataAsync()
+ buf := make([]byte, limit)
+ if err != nil {
+ return buf, err
+ }
+ defer dataRc.Close()
+ n, _ := dataRc.Read(buf)
+ buf = buf[:n]
+ return buf, nil
+}
+
// GetBlobLineCount gets line count of lob as raw text
func (b *Blob) GetBlobLineCount() (int, error) {
reader, err := b.DataAsync()
diff --git a/modules/lfs/pointers.go b/modules/lfs/pointers.go
index c6fbf090e5164..30850145a347c 100644
--- a/modules/lfs/pointers.go
+++ b/modules/lfs/pointers.go
@@ -6,8 +6,6 @@ package lfs
import (
"io"
- "strconv"
- "strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
@@ -29,43 +27,11 @@ func ReadPointerFile(reader io.Reader) (*models.LFSMetaObject, *[]byte) {
return nil, nil
}
- return IsPointerFile(&buf), &buf
-}
-
-// IsPointerFile will return a partially filled LFSMetaObject if the provided byte slice is a pointer file
-func IsPointerFile(buf *[]byte) *models.LFSMetaObject {
- if !setting.LFS.StartServer {
- return nil
- }
-
- headString := string(*buf)
- if !strings.HasPrefix(headString, models.LFSMetaFileIdentifier) {
- return nil
- }
-
- splitLines := strings.Split(headString, "\n")
- if len(splitLines) < 3 {
- return nil
- }
-
- oid := strings.TrimPrefix(splitLines[1], models.LFSMetaFileOidPrefix)
- size, err := strconv.ParseInt(strings.TrimPrefix(splitLines[2], "size "), 10, 64)
- if len(oid) != 64 || err != nil {
- return nil
- }
-
- contentStore := &ContentStore{ObjectStorage: storage.LFS}
- meta := &models.LFSMetaObject{Oid: oid, Size: size}
- exist, err := contentStore.Exists(meta)
- if err != nil || !exist {
- return nil
- }
-
- return meta
+ return models.IsPointerFileAndStored(&buf), &buf
}
// ReadMetaObject will read a models.LFSMetaObject and return a reader
func ReadMetaObject(meta *models.LFSMetaObject) (io.ReadCloser, error) {
- contentStore := &ContentStore{ObjectStorage: storage.LFS}
+ contentStore := &models.ContentStore{ObjectStorage: storage.LFS}
return contentStore.Get(meta, 0)
}
diff --git a/modules/lfs/server.go b/modules/lfs/server.go
index 45cba9d9b7512..a40ae3bfb7eb7 100644
--- a/modules/lfs/server.go
+++ b/modules/lfs/server.go
@@ -6,6 +6,8 @@ package lfs
import (
"encoding/base64"
+
+ "errors"
"fmt"
"io"
"net/http"
@@ -13,7 +15,6 @@ import (
"regexp"
"strconv"
"strings"
- "time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
@@ -29,6 +30,11 @@ const (
metaMediaType = "application/vnd.git-lfs+json"
)
+var (
+ errHashMismatch = errors.New("Content hash does not match OID")
+ errSizeMismatch = errors.New("Content size does not match")
+)
+
// RequestVars contain variables from the HTTP request. Variables from routing, json body decoding, and
// some headers are stored.
type RequestVars struct {
@@ -48,27 +54,6 @@ type BatchVars struct {
Objects []*RequestVars `json:"objects"`
}
-// BatchResponse contains multiple object metadata Representation structures
-// for use with the batch API.
-type BatchResponse struct {
- Transfer string `json:"transfer,omitempty"`
- Objects []*Representation `json:"objects"`
-}
-
-// Representation is object metadata as seen by clients of the lfs server.
-type Representation struct {
- Oid string `json:"oid"`
- Size int64 `json:"size"`
- Actions map[string]*link `json:"actions"`
- Error *ObjectError `json:"error,omitempty"`
-}
-
-// ObjectError defines the JSON structure returned to the client in case of an error
-type ObjectError struct {
- Code int `json:"code"`
- Message string `json:"message"`
-}
-
// Claims is a JWT Token Claims
type Claims struct {
RepoID int64
@@ -87,13 +72,6 @@ func (v *RequestVars) VerifyLink() string {
return setting.AppURL + path.Join(v.User, v.Repo+".git", "info/lfs/verify")
}
-// link provides a structure used to build a hypermedia representation of an HTTP link.
-type link struct {
- Href string `json:"href"`
- Header map[string]string `json:"header,omitempty"`
- ExpiresAt time.Time `json:"expires_at,omitempty"`
-}
-
var oidRegExp = regexp.MustCompile(`^[A-Fa-f0-9]+$`)
func isOidValid(oid string) bool {
@@ -187,10 +165,10 @@ func getContentHandler(ctx *context.Context) {
}
}
- contentStore := &ContentStore{ObjectStorage: storage.LFS}
+ contentStore := &models.ContentStore{ObjectStorage: storage.LFS}
content, err := contentStore.Get(meta, fromByte)
if err != nil {
- if IsErrRangeNotSatisfiable(err) {
+ if models.IsErrRangeNotSatisfiable(err) {
writeStatus(ctx, http.StatusRequestedRangeNotSatisfiable)
} else {
// Errors are logged in contentStore.Get
@@ -293,7 +271,7 @@ func PostHandler(ctx *context.Context) {
ctx.Resp.Header().Set("Content-Type", metaMediaType)
sentStatus := 202
- contentStore := &ContentStore{ObjectStorage: storage.LFS}
+ contentStore := &models.ContentStore{ObjectStorage: storage.LFS}
exist, err := contentStore.Exists(meta)
if err != nil {
log.Error("Unable to check if LFS OID[%s] exist on %s / %s. Error: %v", rv.Oid, rv.User, rv.Repo, err)
@@ -329,7 +307,7 @@ func BatchHandler(ctx *context.Context) {
bv := unpackbatch(ctx)
- var responseObjects []*Representation
+ var responseObjects []*models.Representation
// Create a response object
for _, object := range bv.Objects {
@@ -355,7 +333,7 @@ func BatchHandler(ctx *context.Context) {
return
}
- contentStore := &ContentStore{ObjectStorage: storage.LFS}
+ contentStore := &models.ContentStore{ObjectStorage: storage.LFS}
meta, err := repository.GetLFSMetaObjectByOid(object.Oid)
if err == nil { // Object is found and exists
@@ -394,7 +372,7 @@ func BatchHandler(ctx *context.Context) {
ctx.Resp.Header().Set("Content-Type", metaMediaType)
- respobj := &BatchResponse{Objects: responseObjects}
+ respobj := &models.BatchResponse{Objects: responseObjects}
json := jsoniter.ConfigCompatibleWithStandardLibrary
enc := json.NewEncoder(ctx.Resp)
@@ -414,7 +392,7 @@ func PutHandler(ctx *context.Context) {
return
}
- contentStore := &ContentStore{ObjectStorage: storage.LFS}
+ contentStore := &models.ContentStore{ObjectStorage: storage.LFS}
defer ctx.Req.Body.Close()
if err := contentStore.Put(meta, ctx.Req.Body); err != nil {
// Put will log the error itself
@@ -455,7 +433,7 @@ func VerifyHandler(ctx *context.Context) {
return
}
- contentStore := &ContentStore{ObjectStorage: storage.LFS}
+ contentStore := &models.ContentStore{ObjectStorage: storage.LFS}
ok, err := contentStore.Verify(meta)
if err != nil {
// Error will be logged in Verify
@@ -473,11 +451,11 @@ func VerifyHandler(ctx *context.Context) {
// Represent takes a RequestVars and Meta and turns it into a Representation suitable
// for json encoding
-func Represent(rv *RequestVars, meta *models.LFSMetaObject, download, upload bool) *Representation {
- rep := &Representation{
+func Represent(rv *RequestVars, meta *models.LFSMetaObject, download, upload bool) *models.Representation {
+ rep := &models.Representation{
Oid: meta.Oid,
Size: meta.Size,
- Actions: make(map[string]*link),
+ Actions: make(map[string]*models.Link),
}
header := make(map[string]string)
@@ -490,11 +468,11 @@ func Represent(rv *RequestVars, meta *models.LFSMetaObject, download, upload boo
}
if download {
- rep.Actions["download"] = &link{Href: rv.ObjectLink(), Header: header}
+ rep.Actions["download"] = &models.Link{Href: rv.ObjectLink(), Header: header}
}
if upload {
- rep.Actions["upload"] = &link{Href: rv.ObjectLink(), Header: header}
+ rep.Actions["upload"] = &models.Link{Href: rv.ObjectLink(), Header: header}
}
if upload && !download {
@@ -507,7 +485,7 @@ func Represent(rv *RequestVars, meta *models.LFSMetaObject, download, upload boo
// This is only needed to workaround https://github.com/git-lfs/git-lfs/issues/3662
verifyHeader["Accept"] = metaMediaType
- rep.Actions["verify"] = &link{Href: rv.VerifyLink(), Header: verifyHeader}
+ rep.Actions["verify"] = &models.Link{Href: rv.VerifyLink(), Header: verifyHeader}
}
return rep
diff --git a/modules/lfsclient/client.go b/modules/lfsclient/client.go
new file mode 100644
index 0000000000000..417d7271340f2
--- /dev/null
+++ b/modules/lfsclient/client.go
@@ -0,0 +1,141 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package lfsclient
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "strconv"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/log"
+)
+
+const (
+ metaMediaType = "application/vnd.git-lfs+json"
+)
+
+// BatchRequest encodes json object using in a lfs batch api request
+type BatchRequest struct {
+ Operation string `json:"operation"`
+ Transfers []string `json:"transfers,omitempty"`
+ Ref *Reference `json:"ref,omitempty"`
+ Objects []*models.LFSMetaObjectBasic `json:"objects"`
+}
+
+// Reference is a reference field of BatchRequest
+type Reference struct {
+ Name string `json:"name"`
+}
+
+// packbatch packs lfs batch request to json encoded as bytes
+func packbatch(operation string, transfers []string, ref *Reference, metaObjects []*models.LFSMetaObject) (*bytes.Buffer, error) {
+ metaObjectsBasic := []*models.LFSMetaObjectBasic{}
+ for _, meta := range metaObjects {
+ metaBasic := &models.LFSMetaObjectBasic{Oid: meta.Oid, Size: meta.Size}
+ metaObjectsBasic = append(metaObjectsBasic, metaBasic)
+ }
+
+ reqobj := &BatchRequest{operation, transfers, ref, metaObjectsBasic}
+
+ buf := &bytes.Buffer{}
+ if err := json.NewEncoder(buf).Encode(reqobj); err != nil {
+ return buf, fmt.Errorf("Failed to encode BatchRequest as json. Error: %v", err)
+ }
+ return buf, nil
+}
+
+// BasicTransferAdapter makes request to lfs server and returns io.ReadCLoser
+func BasicTransferAdapter(ctx context.Context, client *http.Client, href string, size int64) (io.ReadCloser, error) {
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, href, nil)
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Content-type", "application/octet-stream")
+ req.Header.Set("Content-Length", strconv.Itoa(int(size)))
+
+ resp, err := client.Do(req)
+ if err != nil {
+ select {
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ default:
+ }
+ return nil, err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("Failed to query BasicTransferAdapter with response: %s", resp.Status)
+ }
+ return resp.Body, nil
+}
+
+// FetchLFSFilesToContentStore downloads []LFSMetaObject from lfsServer to ContentStore
+func FetchLFSFilesToContentStore(ctx context.Context, metaObjects []*models.LFSMetaObject, userName string, repo *models.Repository, lfsServer string, contentStore *models.ContentStore) error {
+ client := http.DefaultClient
+
+ rv, err := packbatch("download", []string{"basic"}, nil, metaObjects)
+ if err != nil {
+ return err
+ }
+ batchAPIURL := lfsServer + "/objects/batch"
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, batchAPIURL, rv)
+ if err != nil {
+ return err
+ }
+ req.Header.Set("Content-type", metaMediaType)
+ req.Header.Set("Accept", metaMediaType)
+
+ resp, err := client.Do(req)
+ if err != nil {
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ default:
+ }
+ return err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return fmt.Errorf("Failed to query Batch with response: %s", resp.Status)
+ }
+
+ var respBatch models.BatchResponse
+ err = json.NewDecoder(resp.Body).Decode(&respBatch)
+ if err != nil {
+ return err
+ }
+
+ if len(respBatch.Transfer) == 0 {
+ respBatch.Transfer = "basic"
+ }
+
+ for _, rep := range respBatch.Objects {
+ rc, err := BasicTransferAdapter(ctx, client, rep.Actions["download"].Href, rep.Size)
+ if err != nil {
+ log.Error("Unable to use BasicTransferAdapter. Error: %v", err)
+ return err
+ }
+ meta, err := repo.GetLFSMetaObjectByOid(rep.Oid)
+ if err != nil {
+ log.Error("Unable to get LFS OID[%s] Error: %v", rep.Oid, err)
+ return err
+ }
+
+ // put LFS file to contentStore
+ if err := contentStore.Put(meta, rc); err != nil {
+ if _, err2 := repo.RemoveLFSMetaObjectByOid(meta.Oid); err2 != nil {
+ return fmt.Errorf("Error whilst removing failed inserted LFS object %s: %v (Prev Error: %v)", meta.Oid, err2, err)
+ }
+ return err
+ }
+ }
+ return nil
+}
diff --git a/modules/migrations/base/options.go b/modules/migrations/base/options.go
index 168f9848c813d..2adb772572356 100644
--- a/modules/migrations/base/options.go
+++ b/modules/migrations/base/options.go
@@ -20,6 +20,9 @@ type MigrateOptions struct {
// required: true
RepoName string `json:"repo_name" binding:"Required"`
Mirror bool `json:"mirror"`
+ LFS bool `json:"lfs"`
+ LFSServer string `json:"lfs_server"`
+ LFSFetchOlder bool `json:"lfs_fetch_older"`
Private bool `json:"private"`
Description string `json:"description"`
OriginalURL string
diff --git a/modules/migrations/base/repo.go b/modules/migrations/base/repo.go
index 693a96314dc1f..1c8f91339028d 100644
--- a/modules/migrations/base/repo.go
+++ b/modules/migrations/base/repo.go
@@ -9,8 +9,10 @@ package base
type Repository struct {
Name string
Owner string
- IsPrivate bool `yaml:"is_private"`
- IsMirror bool `yaml:"is_mirror"`
+ IsPrivate bool `yaml:"is_private"`
+ IsMirror bool `yaml:"is_mirror"`
+ LFS bool `yaml:"lfs"`
+ LFSServer string `yaml:"lfs_server"`
Description string
CloneURL string `yaml:"clone_url"`
OriginalURL string `yaml:"original_url"`
diff --git a/modules/migrations/gitea_uploader.go b/modules/migrations/gitea_uploader.go
index aa1ea4bc09d04..6e16ce3ab18e3 100644
--- a/modules/migrations/gitea_uploader.go
+++ b/modules/migrations/gitea_uploader.go
@@ -116,6 +116,9 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate
OriginalURL: repo.OriginalURL,
GitServiceType: opts.GitServiceType,
Mirror: repo.IsMirror,
+ LFS: opts.LFS,
+ LFSServer: opts.LFSServer,
+ LFSFetchOlder: opts.LFSFetchOlder,
CloneAddr: repo.CloneURL,
Private: repo.IsPrivate,
Wiki: opts.Wiki,
diff --git a/modules/migrations/migrate.go b/modules/migrations/migrate.go
index 656b78a584895..71ded71b93a1f 100644
--- a/modules/migrations/migrate.go
+++ b/modules/migrations/migrate.go
@@ -147,6 +147,8 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
}
repo.IsPrivate = opts.Private
repo.IsMirror = opts.Mirror
+ repo.LFS = opts.LFS
+ repo.LFSServer = opts.LFSServer
if opts.Description != "" {
repo.Description = opts.Description
}
diff --git a/modules/repofiles/update.go b/modules/repofiles/update.go
index d25e109b29ede..affef0799fb20 100644
--- a/modules/repofiles/update.go
+++ b/modules/repofiles/update.go
@@ -70,7 +70,7 @@ func detectEncodingAndBOM(entry *git.TreeEntry, repo *models.Repository) (string
buf = buf[:n]
if setting.LFS.StartServer {
- meta := lfs.IsPointerFile(&buf)
+ meta := models.IsPointerFileAndStored(&buf)
if meta != nil {
meta, err = repo.GetLFSMetaObjectByOid(meta.Oid)
if err != nil && err != models.ErrLFSObjectNotExist {
@@ -435,7 +435,7 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up
if err != nil {
return nil, err
}
- contentStore := &lfs.ContentStore{ObjectStorage: storage.LFS}
+ contentStore := &models.ContentStore{ObjectStorage: storage.LFS}
exist, err := contentStore.Exists(lfsMetaObject)
if err != nil {
return nil, err
diff --git a/modules/repofiles/upload.go b/modules/repofiles/upload.go
index 2846e6c44b8c1..cadff5b3159a8 100644
--- a/modules/repofiles/upload.go
+++ b/modules/repofiles/upload.go
@@ -12,7 +12,6 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
)
@@ -169,7 +168,7 @@ func UploadRepoFiles(repo *models.Repository, doer *models.User, opts *UploadRep
// OK now we can insert the data into the store - there's no way to clean up the store
// once it's in there, it's in there.
- contentStore := &lfs.ContentStore{ObjectStorage: storage.LFS}
+ contentStore := &models.ContentStore{ObjectStorage: storage.LFS}
for _, uploadInfo := range infos {
if uploadInfo.lfsMetaObject == nil {
continue
diff --git a/modules/repository/repo.go b/modules/repository/repo.go
index ede714673ab16..4094d26889c5c 100644
--- a/modules/repository/repo.go
+++ b/modules/repository/repo.go
@@ -13,9 +13,11 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/lfsclient"
"code.gitea.io/gitea/modules/log"
migration "code.gitea.io/gitea/modules/migrations/base"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@@ -98,6 +100,13 @@ func MigrateRepositoryGitData(ctx context.Context, u *models.User, repo *models.
}
defer gitRepo.Close()
+ if opts.LFS {
+ err := FetchMissingLFSFilesToContentStore(ctx, repo, u.Name, gitRepo, opts.LFSServer, opts.LFSFetchOlder)
+ if err != nil {
+ return repo, fmt.Errorf("Failed to fetch LFS files: %v", err)
+ }
+ }
+
repo.IsEmpty, err = gitRepo.IsEmpty()
if err != nil {
return repo, fmt.Errorf("git.IsEmpty: %v", err)
@@ -166,6 +175,109 @@ func MigrateRepositoryGitData(ctx context.Context, u *models.User, repo *models.
return repo, err
}
+// FetchMissingLFSFilesToContentStore downloads lfs files to a ContentStore
+func FetchMissingLFSFilesToContentStore(ctx context.Context, repo *models.Repository, userName string, gitRepo *git.Repository, lfsServer string, lfsFetchOlder bool) error {
+ fetchingMetaObjectsSet := make(map[string]*models.LFSMetaObject)
+ var err error
+ contentStore := &models.ContentStore{ObjectStorage: storage.LFS}
+
+ // scan repo for pointer files
+ headBranch, _ := gitRepo.GetHEADBranch()
+ headCommit, _ := gitRepo.GetCommit(headBranch.Name)
+
+ err = FindLFSMetaObjectsBelowMaxFileSizeWithMissingFiles(headCommit, userName, repo, &fetchingMetaObjectsSet, contentStore)
+ if err != nil {
+ log.Error("Failed to access git LFS meta objects on commit %s: %v", headCommit.ID.String(), err)
+ return err
+ }
+
+ if lfsFetchOlder {
+ opts := git.NewSearchCommitsOptions("before:"+headCommit.ID.String(), true)
+ commitIDsList, _ := headCommit.SearchCommits(opts)
+ var commitIDs = []string{}
+ for e := commitIDsList.Front(); e != nil; e = e.Next() {
+ commitIDs = append(commitIDs, e.Value.(string))
+ }
+ commitsList := gitRepo.GetCommitsFromIDs(commitIDs)
+
+ for e := commitsList.Front(); e != nil; e = e.Next() {
+ commit := e.Value.(*git.Commit)
+ err = FindLFSMetaObjectsBelowMaxFileSizeWithMissingFiles(commit, userName, repo, &fetchingMetaObjectsSet, contentStore)
+ if err != nil {
+ log.Error("Failed to access git LFS meta objects on commit %s: %v", commit.ID.String(), err)
+ return err
+ }
+ }
+ }
+
+ fetchingMetaObjects := []*models.LFSMetaObject{}
+ for metaID := range fetchingMetaObjectsSet {
+ fetchingMetaObjects = append(fetchingMetaObjects, fetchingMetaObjectsSet[metaID])
+ }
+
+ // fetch LFS files from external server
+ err = lfsclient.FetchLFSFilesToContentStore(ctx, fetchingMetaObjects, userName, repo, lfsServer, contentStore)
+ if err != nil {
+ log.Error("Unable to fetch LFS files in %v/%v to content store. Error: %v", userName, repo.Name, err)
+ return err
+ }
+
+ return nil
+}
+
+// FindLFSMetaObjectsBelowMaxFileSizeWithMissingFiles finds lfs pointers in a repo and adds them to a passed fetchingMetaObjectsSet
+func FindLFSMetaObjectsBelowMaxFileSizeWithMissingFiles(commit *git.Commit, userName string, repo *models.Repository, fetchingMetaObjectsSet *map[string]*models.LFSMetaObject, contentStore *models.ContentStore) error {
+ entries, err := commit.Tree.ListEntriesRecursive()
+ if err != nil {
+ log.Error("Failed to access git commit %s tree: %v", commit.ID.String(), err)
+ return err
+ }
+
+ for _, entry := range entries {
+ buf, _ := entry.Blob().GetBlobFirstBytes(1024)
+ metaBasic := models.IsPointerFile(&buf)
+ if metaBasic == nil {
+ continue
+ }
+
+ if setting.LFS.MaxFileSize > 0 && metaBasic.Size > setting.LFS.MaxFileSize {
+ log.Info("Denied LFS oid[%s] download of size %d to %s/%s because of LFS_MAX_FILE_SIZE=%d", metaBasic.Oid, metaBasic.Size, userName, repo.Name, setting.LFS.MaxFileSize)
+ continue
+ }
+
+ meta, err := models.NewLFSMetaObject(&models.LFSMetaObject{Oid: metaBasic.Oid, Size: metaBasic.Size, RepositoryID: repo.ID})
+ if err != nil {
+ log.Error("Unable to write LFS OID[%s] size %d meta object in %v/%v to database. Error: %v", meta.Oid, meta.Size, userName, repo.Name, err)
+ return err
+ }
+
+ exist, err := contentStore.Exists(meta)
+ if err != nil {
+ log.Error("Unable to check if LFS OID[%s] exist on %s/%s. Error: %v", meta.Oid, userName, repo.Name, err)
+ return err
+ }
+
+ if exist {
+ fileSizeValid, err := contentStore.Verify(meta)
+ if err != nil {
+ log.Error("Unable to verify LFS OID[%s] size on %s/%s. Error: %v", meta.Oid, userName, repo.Name, err)
+ return err
+ }
+ // remove file collision if exists and size not matching
+ if !fileSizeValid {
+ if err := contentStore.Delete(meta.RelativePath()); err != nil {
+ return fmt.Errorf("Error whilst deleting contentStore file by LFS oid %s: %v", meta.Oid, err)
+ }
+ (*fetchingMetaObjectsSet)[meta.Oid] = meta
+ }
+ // if exists and size matching, do not fetch
+ } else {
+ (*fetchingMetaObjectsSet)[meta.Oid] = meta
+ }
+ }
+ return nil
+}
+
// cleanUpMigrateGitConfig removes mirror info which prevents "push --all".
// This also removes possible user credentials.
func cleanUpMigrateGitConfig(configPath string) error {
diff --git a/modules/structs/repo.go b/modules/structs/repo.go
index c47700cd00934..0ae3b4dba6ce6 100644
--- a/modules/structs/repo.go
+++ b/modules/structs/repo.go
@@ -257,6 +257,9 @@ type MigrateRepoOptions struct {
AuthToken string `json:"auth_token"`
Mirror bool `json:"mirror"`
+ LFS bool `json:"lfs"`
+ LFSServer string `json:"lfs_server"`
+ LFSFetchOlder bool `json:"lfs_fetch_older"`
Private bool `json:"private"`
Description string `json:"description" binding:"MaxSize(255)"`
Wiki bool `json:"wiki"`
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 08b202d192f84..4918a0fe59822 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -393,6 +393,7 @@ user_not_exist = The user does not exist.
team_not_exist = The team does not exist.
last_org_owner = You cannot remove the last user from the 'owners' team. There must be at least one owner for an organization.
cannot_add_org_to_team = An organization cannot be added as a team member.
+lfs_server_not_valid = LFS Server not valid.
invalid_ssh_key = Can not verify your SSH key: %s
invalid_gpg_key = Can not verify your GPG key: %s
@@ -775,7 +776,10 @@ need_auth = Clone Authorization
migrate_options = Migration Options
migrate_service = Migration Service
migrate_options_mirror_helper = This repository will be a mirror
+migrate_options_mirror_lfs = Mirror LFS data
migrate_options_mirror_disabled = Your site administrator has disabled new mirrors.
+migrate_options_lfs_server = LFS Server
+migrate_options_lfs_fetch_older = Fetch LFS files of older commits
migrate_items = Migration Items
migrate_items_wiki = Wiki
migrate_items_milestones = Milestones
diff --git a/routers/api/v1/repo/migrate.go b/routers/api/v1/repo/migrate.go
index 61cd12b991cb1..6a068f69f7ba5 100644
--- a/routers/api/v1/repo/migrate.go
+++ b/routers/api/v1/repo/migrate.go
@@ -134,6 +134,9 @@ func Migrate(ctx *context.APIContext) {
Description: form.Description,
Private: form.Private || setting.Repository.ForcePrivate,
Mirror: form.Mirror,
+ LFS: form.LFS,
+ LFSServer: form.LFSServer,
+ LFSFetchOlder: form.LFSFetchOlder,
AuthUsername: form.AuthUsername,
AuthPassword: form.AuthPassword,
AuthToken: form.AuthToken,
diff --git a/routers/repo/lfs.go b/routers/repo/lfs.go
index fb0e3b10eae9a..32a3910af70fa 100644
--- a/routers/repo/lfs.go
+++ b/routers/repo/lfs.go
@@ -493,7 +493,7 @@ type pointerResult struct {
func createPointerResultsFromCatFileBatch(catFileBatchReader *io.PipeReader, wg *sync.WaitGroup, pointerChan chan<- pointerResult, repo *models.Repository, user *models.User) {
defer wg.Done()
defer catFileBatchReader.Close()
- contentStore := lfs.ContentStore{ObjectStorage: storage.LFS}
+ contentStore := models.ContentStore{ObjectStorage: storage.LFS}
bufferedReader := bufio.NewReader(catFileBatchReader)
buf := make([]byte, 1025)
@@ -526,7 +526,7 @@ func createPointerResultsFromCatFileBatch(catFileBatchReader *io.PipeReader, wg
}
pointerBuf = pointerBuf[:size]
// Now we need to check if the pointerBuf is an LFS pointer
- pointer := lfs.IsPointerFile(&pointerBuf)
+ pointer := models.IsPointerFileAndStored(&pointerBuf)
if pointer == nil {
continue
}
diff --git a/routers/repo/migrate.go b/routers/repo/migrate.go
index e1ff8e13606ab..77995b04e76f7 100644
--- a/routers/repo/migrate.go
+++ b/routers/repo/migrate.go
@@ -46,6 +46,8 @@ func Migrate(ctx *context.Context) {
ctx.Data["private"] = getRepoPrivate(ctx)
ctx.Data["mirror"] = ctx.Query("mirror") == "1"
+ ctx.Data["LFS"] = ctx.Query("lfs") == "1"
+ ctx.Data["LFSFetchOlder"] = ctx.Query("lfs_fetch_older") == "1"
ctx.Data["wiki"] = ctx.Query("wiki") == "1"
ctx.Data["milestones"] = ctx.Query("milestones") == "1"
ctx.Data["labels"] = ctx.Query("labels") == "1"
@@ -96,6 +98,9 @@ func handleMigrateError(ctx *context.Context, owner *models.User, err error, nam
case models.IsErrNamePatternNotAllowed(err):
ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tpl, form)
+ case models.IsMirrorLFSServerValid(err):
+ ctx.Data["Err_LFSServerNotValid"] = true
+ ctx.RenderWithErr(ctx.Tr("form.lfs_server_not_valid"), tpl, form)
default:
remoteAddr, _ := auth.ParseRemoteAddr(form.CloneAddr, form.AuthUsername, form.AuthPassword, owner)
err = util.URLSanitizedError(err, remoteAddr)
@@ -167,6 +172,9 @@ func MigratePost(ctx *context.Context) {
Description: form.Description,
Private: form.Private || setting.Repository.ForcePrivate,
Mirror: form.Mirror && !setting.Repository.DisableMirrors,
+ LFS: form.LFS,
+ LFSServer: form.LFSServer,
+ LFSFetchOlder: form.LFSFetchOlder,
AuthUsername: form.AuthUsername,
AuthPassword: form.AuthPassword,
AuthToken: form.AuthToken,
@@ -187,7 +195,7 @@ func MigratePost(ctx *context.Context) {
opts.Releases = false
}
- err = models.CheckCreateRepository(ctx.User, ctxUser, opts.RepoName, false)
+ err = models.CheckCreateRepository(ctx.User, ctxUser, opts.RepoName, opts.LFS, opts.LFSServer, false)
if err != nil {
handleMigrateError(ctx, ctxUser, err, "MigratePost", tpl, form)
return
diff --git a/routers/repo/view.go b/routers/repo/view.go
index 39f16d183c3b1..91b46d9c8fc79 100644
--- a/routers/repo/view.go
+++ b/routers/repo/view.go
@@ -273,7 +273,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {
// FIXME: what happens when README file is an image?
if isTextFile && setting.LFS.StartServer {
- meta := lfs.IsPointerFile(&buf)
+ meta := models.IsPointerFileAndStored(&buf)
if meta != nil {
meta, err = ctx.Repo.Repository.GetLFSMetaObjectByOid(meta.Oid)
if err != nil && err != models.ErrLFSObjectNotExist {
@@ -399,7 +399,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
//Check for LFS meta file
if isTextFile && setting.LFS.StartServer {
- meta := lfs.IsPointerFile(&buf)
+ meta := models.IsPointerFileAndStored(&buf)
if meta != nil {
meta, err = ctx.Repo.Repository.GetLFSMetaObjectByOid(meta.Oid)
if err != nil && err != models.ErrLFSObjectNotExist {
diff --git a/services/mirror/mirror.go b/services/mirror/mirror.go
index e4981b8c00e64..7f3178be75081 100644
--- a/services/mirror/mirror.go
+++ b/services/mirror/mirror.go
@@ -206,7 +206,7 @@ func parseRemoteUpdateOutput(output string) []*mirrorSyncResult {
}
// runSync returns true if sync finished without error.
-func runSync(m *models.Mirror) ([]*mirrorSyncResult, bool) {
+func runSync(ctx context.Context, m *models.Mirror) ([]*mirrorSyncResult, bool) {
repoPath := m.Repo.RepoPath()
wikiPath := m.Repo.WikiPath()
timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Second
@@ -253,13 +253,20 @@ func runSync(m *models.Mirror) ([]*mirrorSyncResult, bool) {
log.Error("OpenRepository: %v", err)
return nil, false
}
+ defer gitRepo.Close()
+
+ if m.Repo.LFS {
+ log.Trace("SyncMirrors [repo: %-v]: fetching LFS files...", m.Repo)
+ err := repo_module.FetchMissingLFSFilesToContentStore(ctx, m.Repo, Username(m), gitRepo, m.Repo.LFSServer, false)
+ if err != nil {
+ log.Error("Failed to fetch LFS files %v:\nErr: %v", m.Repo, err)
+ }
+ }
log.Trace("SyncMirrors [repo: %-v]: syncing releases with tags...", m.Repo)
if err = repo_module.SyncReleasesWithTags(m.Repo, gitRepo); err != nil {
- gitRepo.Close()
log.Error("Failed to synchronize tags to releases for repository: %v", err)
}
- gitRepo.Close()
log.Trace("SyncMirrors [repo: %-v]: updating size of repository", m.Repo)
if err := m.Repo.UpdateSize(models.DefaultDBContext()); err != nil {
@@ -378,12 +385,12 @@ func SyncMirrors(ctx context.Context) {
mirrorQueue.Close()
return
case repoID := <-mirrorQueue.Queue():
- syncMirror(repoID)
+ syncMirror(ctx, repoID)
}
}
}
-func syncMirror(repoID string) {
+func syncMirror(ctx context.Context, repoID string) {
log.Trace("SyncMirrors [repo_id: %v]", repoID)
defer func() {
err := recover()
@@ -403,7 +410,7 @@ func syncMirror(repoID string) {
}
log.Trace("SyncMirrors [repo: %-v]: Running Sync", m.Repo)
- results, ok := runSync(m)
+ results, ok := runSync(ctx, m)
if !ok {
return
}
diff --git a/services/mirror/mirror_test.go b/services/mirror/mirror_test.go
index 57628aa68dec6..0fa8ca216e586 100644
--- a/services/mirror/mirror_test.go
+++ b/services/mirror/mirror_test.go
@@ -48,7 +48,8 @@ func TestRelease_MirrorDelete(t *testing.T) {
})
assert.NoError(t, err)
- mirror, err := repository.MigrateRepositoryGitData(context.Background(), user, mirrorRepo, opts)
+ ctx := context.Background()
+ mirror, err := repository.MigrateRepositoryGitData(ctx, user, mirrorRepo, opts)
assert.NoError(t, err)
gitRepo, err := git.OpenRepository(repoPath)
@@ -74,7 +75,7 @@ func TestRelease_MirrorDelete(t *testing.T) {
err = mirror.GetMirror()
assert.NoError(t, err)
- _, ok := runSync(mirror.Mirror)
+ _, ok := runSync(ctx, mirror.Mirror)
assert.True(t, ok)
count, err := models.GetReleaseCountByRepoID(mirror.ID, findOptions)
@@ -85,7 +86,7 @@ func TestRelease_MirrorDelete(t *testing.T) {
assert.NoError(t, err)
assert.NoError(t, release_service.DeleteReleaseByID(release.ID, user, true))
- _, ok = runSync(mirror.Mirror)
+ _, ok = runSync(ctx, mirror.Mirror)
assert.True(t, ok)
count, err = models.GetReleaseCountByRepoID(mirror.ID, findOptions)
diff --git a/services/pull/lfs.go b/services/pull/lfs.go
index a1981b8253690..965510937e1f1 100644
--- a/services/pull/lfs.go
+++ b/services/pull/lfs.go
@@ -13,7 +13,6 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git/pipeline"
- "code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
)
@@ -101,7 +100,7 @@ func createLFSMetaObjectsFromCatFileBatch(catFileBatchReader *io.PipeReader, wg
}
pointerBuf = pointerBuf[:size]
// Now we need to check if the pointerBuf is an LFS pointer
- pointer := lfs.IsPointerFile(&pointerBuf)
+ pointer := models.IsPointerFileAndStored(&pointerBuf)
if pointer == nil {
continue
}
diff --git a/templates/repo/migrate/git.tmpl b/templates/repo/migrate/git.tmpl
index 233a019435308..fb1c1c78d03fa 100644
--- a/templates/repo/migrate/git.tmpl
+++ b/templates/repo/migrate/git.tmpl
@@ -15,7 +15,6 @@
{{.i18n.Tr "repo.migrate.clone_address_desc"}}{{if .ContextUser.CanImportLocal}} {{.i18n.Tr "repo.migrate.clone_local_path"}}{{end}}
- {{if .LFSActive}}
{{.i18n.Tr "repo.migrate.lfs_mirror_unsupported"}}{{end}}