Skip to content

Commit fb8daae

Browse files
wxiaoguangStelios Malathouras
authored and
Stelios Malathouras
committed
Allow admin to associate missing LFS objects for repositories (go-gitea#18143)
This PR reworked the Find pointer files feature in Settings -> LFS page. When a LFS object is missing from database but exists in LFS content store, admin can associate it to the repository by clicking the Associate button. This PR is not perfect (because the LFS module itself should be improved too), it's just a nice-to-have feature to help users recover their LFS repositories (eg: database was lost / table was truncated)
1 parent 2febda3 commit fb8daae

File tree

3 files changed

+57
-26
lines changed

3 files changed

+57
-26
lines changed

models/lfs.go

+38-15
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ package models
77
import (
88
"context"
99
"errors"
10+
"fmt"
1011

1112
"code.gitea.io/gitea/models/db"
1213
repo_model "code.gitea.io/gitea/models/repo"
1314
user_model "code.gitea.io/gitea/models/user"
1415
"code.gitea.io/gitea/modules/lfs"
16+
"code.gitea.io/gitea/modules/log"
1517
"code.gitea.io/gitea/modules/timeutil"
1618

1719
"xorm.io/builder"
@@ -145,6 +147,11 @@ func LFSObjectAccessible(user *user_model.User, oid string) (bool, error) {
145147
return count > 0, err
146148
}
147149

150+
// LFSObjectIsAssociated checks if a provided Oid is associated
151+
func LFSObjectIsAssociated(oid string) (bool, error) {
152+
return db.GetEngine(db.DefaultContext).Exist(&LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}})
153+
}
154+
148155
// LFSAutoAssociate auto associates accessible LFSMetaObjects
149156
func LFSAutoAssociate(metas []*LFSMetaObject, user *user_model.User, repoID int64) error {
150157
ctx, committer, err := db.TxContext()
@@ -162,23 +169,39 @@ func LFSAutoAssociate(metas []*LFSMetaObject, user *user_model.User, repoID int6
162169
oidMap[meta.Oid] = meta
163170
}
164171

165-
cond := builder.NewCond()
166172
if !user.IsAdmin {
167-
cond = builder.In("`lfs_meta_object`.repository_id",
168-
builder.Select("`repository`.id").From("repository").Where(accessibleRepositoryCondition(user)))
169-
}
170-
newMetas := make([]*LFSMetaObject, 0, len(metas))
171-
if err := sess.Cols("oid").Where(cond).In("oid", oids...).GroupBy("oid").Find(&newMetas); err != nil {
172-
return err
173-
}
174-
for i := range newMetas {
175-
newMetas[i].Size = oidMap[newMetas[i].Oid].Size
176-
newMetas[i].RepositoryID = repoID
177-
}
178-
if err := db.Insert(ctx, newMetas); err != nil {
179-
return err
173+
newMetas := make([]*LFSMetaObject, 0, len(metas))
174+
cond := builder.In(
175+
"`lfs_meta_object`.repository_id",
176+
builder.Select("`repository`.id").From("repository").Where(accessibleRepositoryCondition(user)),
177+
)
178+
err = sess.Cols("oid").Where(cond).In("oid", oids...).GroupBy("oid").Find(&newMetas)
179+
if err != nil {
180+
return err
181+
}
182+
if len(newMetas) != len(oidMap) {
183+
return fmt.Errorf("unable collect all LFS objects from database, expected %d, actually %d", len(oidMap), len(newMetas))
184+
}
185+
for i := range newMetas {
186+
newMetas[i].Size = oidMap[newMetas[i].Oid].Size
187+
newMetas[i].RepositoryID = repoID
188+
}
189+
if err = db.Insert(ctx, newMetas); err != nil {
190+
return err
191+
}
192+
} else {
193+
// admin can associate any LFS object to any repository, and we do not care about errors (eg: duplicated unique key),
194+
// even if error occurs, it won't hurt users and won't make things worse
195+
for i := range metas {
196+
_, err = sess.Insert(&LFSMetaObject{
197+
Pointer: lfs.Pointer{Oid: metas[i].Oid, Size: metas[i].Size},
198+
RepositoryID: repoID,
199+
})
200+
if err != nil {
201+
log.Warn("failed to insert LFS meta object into database, err=%v", err)
202+
}
203+
}
180204
}
181-
182205
return committer.Commit()
183206
}
184207

routers/web/repo/lfs.go

+18-10
Original file line numberDiff line numberDiff line change
@@ -421,12 +421,13 @@ func LFSPointerFiles(ctx *context.Context) {
421421
var numAssociated, numNoExist, numAssociatable int
422422

423423
type pointerResult struct {
424-
SHA string
425-
Oid string
426-
Size int64
427-
InRepo bool
428-
Exists bool
429-
Accessible bool
424+
SHA string
425+
Oid string
426+
Size int64
427+
InRepo bool
428+
Exists bool
429+
Accessible bool
430+
Associatable bool
430431
}
431432

432433
results := []pointerResult{}
@@ -461,22 +462,29 @@ func LFSPointerFiles(ctx *context.Context) {
461462
// Can we fix?
462463
// OK well that's "simple"
463464
// - we need to check whether current user has access to a repo that has access to the file
464-
result.Accessible, err = models.LFSObjectAccessible(ctx.User, pointerBlob.Oid)
465+
result.Associatable, err = models.LFSObjectAccessible(ctx.User, pointerBlob.Oid)
465466
if err != nil {
466467
return err
467468
}
468-
} else {
469-
result.Accessible = true
469+
if !result.Associatable {
470+
associated, err := models.LFSObjectIsAssociated(pointerBlob.Oid)
471+
if err != nil {
472+
return err
473+
}
474+
result.Associatable = !associated
475+
}
470476
}
471477
}
472478

479+
result.Accessible = result.InRepo || result.Associatable
480+
473481
if result.InRepo {
474482
numAssociated++
475483
}
476484
if !result.Exists {
477485
numNoExist++
478486
}
479-
if !result.InRepo && result.Accessible {
487+
if result.Associatable {
480488
numAssociatable++
481489
}
482490

templates/repo/settings/lfs_pointers.tmpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<form class="ui form" method="post" action="{{$.Link}}/associate">
1212
{{.CsrfTokenHtml}}
1313
{{range .Pointers}}
14-
{{if and (not .InRepo) .Exists .Accessible}}
14+
{{if .Associatable}}
1515
<input type="hidden" name="oid" value="{{.Oid}} {{.Size}}"/>
1616
{{end}}
1717
{{end}}

0 commit comments

Comments
 (0)