Skip to content

Commit 2048363

Browse files
GiteaBotlunny
andauthored
Don't remove all mirror repository's releases when mirroring (go-gitea#28817) (go-gitea#28939)
Backport go-gitea#28817 by @lunny Fix go-gitea#22066 # Purpose This PR fix the releases will be deleted when mirror repository sync the tags. # The problem In the previous implementation of go-gitea#19125. All releases record in databases of one mirror repository will be deleted before sync. Ref: https://github.com/go-gitea/gitea/pull/19125/files#diff-2aa04998a791c30e5a02b49a97c07fcd93d50e8b31640ce2ddb1afeebf605d02R481 # The Pros This PR introduced a new method which will load all releases from databases and all tags on git data into memory. And detect which tags needs to be inserted, which tags need to be updated or deleted. Only tags releases(IsTag=true) which are not included in git data will be deleted, only tags which sha1 changed will be updated. So it will not delete any real releases include drafts. # The Cons The drawback is the memory usage will be higher than before if there are many tags on this repository. This PR defined a special release struct to reduce columns loaded from database to memory. --------- Co-authored-by: Lunny Xiao <[email protected]>
1 parent b8e6cff commit 2048363

File tree

3 files changed

+155
-7
lines changed

3 files changed

+155
-7
lines changed

models/repo/release.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -230,12 +230,18 @@ type FindReleasesOptions struct {
230230
IsPreRelease util.OptionalBool
231231
IsDraft util.OptionalBool
232232
TagNames []string
233+
RepoID int64
233234
HasSha1 util.OptionalBool // useful to find draft releases which are created with existing tags
234235
}
235236

236237
func (opts *FindReleasesOptions) toConds(repoID int64) builder.Cond {
238+
opts.RepoID = repoID
239+
return opts.ToConds()
240+
}
241+
242+
func (opts *FindReleasesOptions) ToConds() builder.Cond {
237243
cond := builder.NewCond()
238-
cond = cond.And(builder.Eq{"repo_id": repoID})
244+
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
239245

240246
if !opts.IncludeDrafts {
241247
cond = cond.And(builder.Eq{"is_draft": false})

modules/repository/repo.go

+72-6
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,18 @@ func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Re
491491
return nil
492492
}
493493

494+
// shortRelease to reduce load memory, this struct can replace repo_model.Release
495+
type shortRelease struct {
496+
ID int64
497+
TagName string
498+
Sha1 string
499+
IsTag bool
500+
}
501+
502+
func (shortRelease) TableName() string {
503+
return "release"
504+
}
505+
494506
// pullMirrorReleaseSync is a pull-mirror specific tag<->release table
495507
// synchronization which overwrites all Releases from the repository tags. This
496508
// can be relied on since a pull-mirror is always identical to its
@@ -504,16 +516,22 @@ func pullMirrorReleaseSync(ctx context.Context, repo *repo_model.Repository, git
504516
return fmt.Errorf("unable to GetTagInfos in pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
505517
}
506518
err = db.WithTx(ctx, func(ctx context.Context) error {
507-
//
508-
// clear out existing releases
509-
//
510-
if _, err := db.DeleteByBean(ctx, &repo_model.Release{RepoID: repo.ID}); err != nil {
511-
return fmt.Errorf("unable to clear releases for pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
519+
dbReleases := make([]*shortRelease, 0, len(tags))
520+
err := db.Find(ctx, &repo_model.FindReleasesOptions{
521+
ListOptions: db.ListOptions{ListAll: true},
522+
RepoID: repo.ID,
523+
IncludeDrafts: true,
524+
IncludeTags: true,
525+
}, &dbReleases)
526+
if err != nil {
527+
return fmt.Errorf("unable to FindReleases in pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
512528
}
529+
530+
inserts, deletes, updates := calcSync(tags, dbReleases)
513531
//
514532
// make release set identical to upstream tags
515533
//
516-
for _, tag := range tags {
534+
for _, tag := range inserts {
517535
release := repo_model.Release{
518536
RepoID: repo.ID,
519537
TagName: tag.Name,
@@ -530,6 +548,25 @@ func pullMirrorReleaseSync(ctx context.Context, repo *repo_model.Repository, git
530548
return fmt.Errorf("unable insert tag %s for pull-mirror Repo[%d:%s/%s]: %w", tag.Name, repo.ID, repo.OwnerName, repo.Name, err)
531549
}
532550
}
551+
552+
// only delete tags releases
553+
if len(deletes) > 0 {
554+
if _, err := db.GetEngine(ctx).Where("repo_id=?", repo.ID).
555+
In("id", deletes).
556+
Delete(&repo_model.Release{}); err != nil {
557+
return fmt.Errorf("unable to delete tags for pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
558+
}
559+
}
560+
561+
for _, tag := range updates {
562+
if _, err := db.GetEngine(ctx).Where("repo_id = ? AND lower_tag_name = ?", repo.ID, strings.ToLower(tag.Name)).
563+
Cols("sha1").
564+
Update(&repo_model.Release{
565+
Sha1: tag.Object.String(),
566+
}); err != nil {
567+
return fmt.Errorf("unable to update tag %s for pull-mirror Repo[%d:%s/%s]: %w", tag.Name, repo.ID, repo.OwnerName, repo.Name, err)
568+
}
569+
}
533570
return nil
534571
})
535572
if err != nil {
@@ -539,3 +576,32 @@ func pullMirrorReleaseSync(ctx context.Context, repo *repo_model.Repository, git
539576
log.Trace("pullMirrorReleaseSync: done rebuilding %d releases", numTags)
540577
return nil
541578
}
579+
580+
func calcSync(destTags []*git.Tag, dbTags []*shortRelease) ([]*git.Tag, []int64, []*git.Tag) {
581+
destTagMap := make(map[string]*git.Tag)
582+
for _, tag := range destTags {
583+
destTagMap[tag.Name] = tag
584+
}
585+
dbTagMap := make(map[string]*shortRelease)
586+
for _, rel := range dbTags {
587+
dbTagMap[rel.TagName] = rel
588+
}
589+
590+
inserted := make([]*git.Tag, 0, 10)
591+
updated := make([]*git.Tag, 0, 10)
592+
for _, tag := range destTags {
593+
rel := dbTagMap[tag.Name]
594+
if rel == nil {
595+
inserted = append(inserted, tag)
596+
} else if rel.Sha1 != tag.Object.String() {
597+
updated = append(updated, tag)
598+
}
599+
}
600+
deleted := make([]int64, 0, 10)
601+
for _, tag := range dbTags {
602+
if destTagMap[tag.TagName] == nil && tag.IsTag {
603+
deleted = append(deleted, tag.ID)
604+
}
605+
}
606+
return inserted, deleted, updated
607+
}

modules/repository/repo_test.go

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package repository
5+
6+
import (
7+
"testing"
8+
9+
"code.gitea.io/gitea/modules/git"
10+
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func Test_calcSync(t *testing.T) {
15+
gitTags := []*git.Tag{
16+
/*{
17+
Name: "v0.1.0-beta", //deleted tag
18+
Object: git.MustIDFromString(""),
19+
},
20+
{
21+
Name: "v0.1.1-beta", //deleted tag but release should not be deleted because it's a release
22+
Object: git.MustIDFromString(""),
23+
},
24+
*/
25+
{
26+
Name: "v1.0.0", // keep as before
27+
Object: git.MustIDFromString("1006e6e13c73ad3d9e2d5682ad266b5016523485"),
28+
},
29+
{
30+
Name: "v1.1.0", // retagged with new commit id
31+
Object: git.MustIDFromString("bbdb7df30248e7d4a26a909c8d2598a152e13868"),
32+
},
33+
{
34+
Name: "v1.2.0", // new tag
35+
Object: git.MustIDFromString("a5147145e2f24d89fd6d2a87826384cc1d253267"),
36+
},
37+
}
38+
39+
dbReleases := []*shortRelease{
40+
{
41+
ID: 1,
42+
TagName: "v0.1.0-beta",
43+
Sha1: "244758d7da8dd1d9e0727e8cb7704ed4ba9a17c3",
44+
IsTag: true,
45+
},
46+
{
47+
ID: 2,
48+
TagName: "v0.1.1-beta",
49+
Sha1: "244758d7da8dd1d9e0727e8cb7704ed4ba9a17c3",
50+
IsTag: false,
51+
},
52+
{
53+
ID: 3,
54+
TagName: "v1.0.0",
55+
Sha1: "1006e6e13c73ad3d9e2d5682ad266b5016523485",
56+
},
57+
{
58+
ID: 4,
59+
TagName: "v1.1.0",
60+
Sha1: "53ab18dcecf4152b58328d1f47429510eb414d50",
61+
},
62+
}
63+
64+
inserts, deletes, updates := calcSync(gitTags, dbReleases)
65+
if assert.EqualValues(t, 1, len(inserts), "inserts") {
66+
assert.EqualValues(t, *gitTags[2], *inserts[0], "inserts equal")
67+
}
68+
69+
if assert.EqualValues(t, 1, len(deletes), "deletes") {
70+
assert.EqualValues(t, 1, deletes[0], "deletes equal")
71+
}
72+
73+
if assert.EqualValues(t, 1, len(updates), "updates") {
74+
assert.EqualValues(t, *gitTags[1], *updates[0], "updates equal")
75+
}
76+
}

0 commit comments

Comments
 (0)