Skip to content

Commit 8bd89ca

Browse files
singuliereLoïc Dacharylunny
authored
preserve users if restoring a repository on the same Gitea instance (#18604)
When calling DumpRepository and RestoreRepository on the same Gitea instance, the users are preserved: all labels, issues etc. belong to the external user who is, in this particular case, the local user. Dead code verifying g.gitServiceType.Name() == "" (i.e. plain git) is removed. The function is never called because the plain git downloader does not migrate anything that is associated to a user, by definition. Errors returned by GetUserIDByExternalUserID are no longer ignored. The userMap is used when the external user is not kown, which is the most common case. It was only used when the external user exists which happens less often and, as a result, every occurence of an unknown external user required a SQL query. Signed-off-by: Loïc Dachary <[email protected]> Co-authored-by: Loïc Dachary <[email protected]> Co-authored-by: Lunny Xiao <[email protected]>
1 parent 9419dd2 commit 8bd89ca

File tree

4 files changed

+125
-26
lines changed

4 files changed

+125
-26
lines changed

integrations/dump_restore_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ func TestDumpRestore(t *testing.T) {
129129
assert.EqualValues(t, before[i].Created, after[i].Created)
130130
assert.EqualValues(t, before[i].Updated, after[i].Updated)
131131
assert.EqualValues(t, before[i].Labels, after[i].Labels)
132+
assert.EqualValues(t, before[i].Reactions, after[i].Reactions)
132133
}
133134
}
134135
})

models/user/user.go

+13
Original file line numberDiff line numberDiff line change
@@ -1025,6 +1025,19 @@ func GetUserNamesByIDs(ids []int64) ([]string, error) {
10251025
return unames, err
10261026
}
10271027

1028+
// GetUserNameByID returns username for the id
1029+
func GetUserNameByID(ctx context.Context, id int64) (string, error) {
1030+
var name string
1031+
has, err := db.GetEngine(db.DefaultContext).Table("user").Where("id = ?", id).Cols("name").Get(&name)
1032+
if err != nil {
1033+
return "", err
1034+
}
1035+
if has {
1036+
return name, nil
1037+
}
1038+
return "", nil
1039+
}
1040+
10281041
// GetUserIDsByNames returns a slice of ids corresponds to names.
10291042
func GetUserIDsByNames(names []string, ignoreNonExistent bool) ([]int64, error) {
10301043
ids := make([]int64, 0, len(names))

services/migrations/gitea_uploader.go

+53-22
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ type GiteaLocalUploader struct {
4646
issues map[int64]*models.Issue
4747
gitRepo *git.Repository
4848
prHeadCache map[string]struct{}
49+
sameApp bool
4950
userMap map[int64]int64 // external user id mapping to user id
5051
prCache map[int64]*models.PullRequest
5152
gitServiceType structs.GitServiceType
@@ -128,6 +129,7 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate
128129
MirrorInterval: opts.MirrorInterval,
129130
}, NewMigrationHTTPTransport())
130131

132+
g.sameApp = strings.HasPrefix(repo.OriginalURL, setting.AppURL)
131133
g.repo = r
132134
if err != nil {
133135
return err
@@ -256,7 +258,7 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
256258
CreatedUnix: timeutil.TimeStamp(release.Created.Unix()),
257259
}
258260

259-
if err := g.remapExternalUser(release, &rel); err != nil {
261+
if err := g.remapUser(release, &rel); err != nil {
260262
return err
261263
}
262264

@@ -373,7 +375,7 @@ func (g *GiteaLocalUploader) CreateIssues(issues ...*base.Issue) error {
373375
UpdatedUnix: timeutil.TimeStamp(issue.Updated.Unix()),
374376
}
375377

376-
if err := g.remapExternalUser(issue, &is); err != nil {
378+
if err := g.remapUser(issue, &is); err != nil {
377379
return err
378380
}
379381

@@ -386,7 +388,7 @@ func (g *GiteaLocalUploader) CreateIssues(issues ...*base.Issue) error {
386388
Type: reaction.Content,
387389
CreatedUnix: timeutil.TimeStampNow(),
388390
}
389-
if err := g.remapExternalUser(reaction, &res); err != nil {
391+
if err := g.remapUser(reaction, &res); err != nil {
390392
return err
391393
}
392394
is.Reactions = append(is.Reactions, &res)
@@ -437,7 +439,7 @@ func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error {
437439
UpdatedUnix: timeutil.TimeStamp(comment.Updated.Unix()),
438440
}
439441

440-
if err := g.remapExternalUser(comment, &cm); err != nil {
442+
if err := g.remapUser(comment, &cm); err != nil {
441443
return err
442444
}
443445

@@ -447,7 +449,7 @@ func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error {
447449
Type: reaction.Content,
448450
CreatedUnix: timeutil.TimeStampNow(),
449451
}
450-
if err := g.remapExternalUser(reaction, &res); err != nil {
452+
if err := g.remapUser(reaction, &res); err != nil {
451453
return err
452454
}
453455
cm.Reactions = append(cm.Reactions, &res)
@@ -471,7 +473,7 @@ func (g *GiteaLocalUploader) CreatePullRequests(prs ...*base.PullRequest) error
471473
return err
472474
}
473475

474-
if err := g.remapExternalUser(pr, gpr.Issue); err != nil {
476+
if err := g.remapUser(pr, gpr.Issue); err != nil {
475477
return err
476478
}
477479

@@ -626,7 +628,7 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullR
626628
UpdatedUnix: timeutil.TimeStamp(pr.Updated.Unix()),
627629
}
628630

629-
if err := g.remapExternalUser(pr, &issue); err != nil {
631+
if err := g.remapUser(pr, &issue); err != nil {
630632
return nil, err
631633
}
632634

@@ -636,7 +638,7 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullR
636638
Type: reaction.Content,
637639
CreatedUnix: timeutil.TimeStampNow(),
638640
}
639-
if err := g.remapExternalUser(reaction, &res); err != nil {
641+
if err := g.remapUser(reaction, &res); err != nil {
640642
return nil, err
641643
}
642644
issue.Reactions = append(issue.Reactions, &res)
@@ -710,7 +712,7 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
710712
UpdatedUnix: timeutil.TimeStamp(review.CreatedAt.Unix()),
711713
}
712714

713-
if err := g.remapExternalUser(review, &cm); err != nil {
715+
if err := g.remapUser(review, &cm); err != nil {
714716
return err
715717
}
716718

@@ -773,7 +775,7 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
773775
UpdatedUnix: timeutil.TimeStamp(comment.UpdatedAt.Unix()),
774776
}
775777

776-
if err := g.remapExternalUser(review, &c); err != nil {
778+
if err := g.remapUser(review, &c); err != nil {
777779
return err
778780
}
779781

@@ -816,23 +818,52 @@ func (g *GiteaLocalUploader) Finish() error {
816818
return repo_model.UpdateRepositoryCols(g.repo, "status")
817819
}
818820

819-
func (g *GiteaLocalUploader) remapExternalUser(source user_model.ExternalUserMigrated, target user_model.ExternalUserRemappable) (err error) {
821+
func (g *GiteaLocalUploader) remapUser(source user_model.ExternalUserMigrated, target user_model.ExternalUserRemappable) error {
822+
var userid int64
823+
var err error
824+
if g.sameApp {
825+
userid, err = g.remapLocalUser(source, target)
826+
} else {
827+
userid, err = g.remapExternalUser(source, target)
828+
}
829+
830+
if err != nil {
831+
return err
832+
}
833+
834+
if userid > 0 {
835+
return target.RemapExternalUser("", 0, userid)
836+
}
837+
return target.RemapExternalUser(source.GetExternalName(), source.GetExternalID(), g.doer.ID)
838+
}
839+
840+
func (g *GiteaLocalUploader) remapLocalUser(source user_model.ExternalUserMigrated, target user_model.ExternalUserRemappable) (int64, error) {
820841
userid, ok := g.userMap[source.GetExternalID()]
821-
tp := g.gitServiceType.Name()
822-
if !ok && tp != "" {
823-
userid, err = user_model.GetUserIDByExternalUserID(tp, fmt.Sprintf("%d", source.GetExternalID()))
842+
if !ok {
843+
name, err := user_model.GetUserNameByID(g.ctx, source.GetExternalID())
824844
if err != nil {
825-
log.Error("GetUserIDByExternalUserID: %v", err)
845+
return 0, err
826846
}
827-
if userid > 0 {
828-
g.userMap[source.GetExternalID()] = userid
847+
// let's not reuse an ID when the user was deleted or has a different user name
848+
if name != source.GetExternalName() {
849+
userid = 0
850+
} else {
851+
userid = source.GetExternalID()
829852
}
853+
g.userMap[source.GetExternalID()] = userid
830854
}
855+
return userid, nil
856+
}
831857

832-
if userid > 0 {
833-
err = target.RemapExternalUser("", 0, userid)
834-
} else {
835-
err = target.RemapExternalUser(source.GetExternalName(), source.GetExternalID(), g.doer.ID)
858+
func (g *GiteaLocalUploader) remapExternalUser(source user_model.ExternalUserMigrated, target user_model.ExternalUserRemappable) (userid int64, err error) {
859+
userid, ok := g.userMap[source.GetExternalID()]
860+
if !ok {
861+
userid, err = user_model.GetUserIDByExternalUserID(g.gitServiceType.Name(), fmt.Sprintf("%d", source.GetExternalID()))
862+
if err != nil {
863+
log.Error("GetUserIDByExternalUserID: %v", err)
864+
return 0, err
865+
}
866+
g.userMap[source.GetExternalID()] = userid
836867
}
837-
return
868+
return userid, nil
838869
}

services/migrations/gitea_uploader_test.go

+58-4
Original file line numberDiff line numberDiff line change
@@ -117,16 +117,68 @@ func TestGiteaUploadRepo(t *testing.T) {
117117
assert.Len(t, pulls[0].Issue.Comments, 2)
118118
}
119119

120+
func TestGiteaUploadRemapLocalUser(t *testing.T) {
121+
unittest.PrepareTestEnv(t)
122+
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
123+
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
124+
125+
repoName := "migrated"
126+
uploader := NewGiteaLocalUploader(context.Background(), doer, doer.Name, repoName)
127+
// call remapLocalUser
128+
uploader.sameApp = true
129+
130+
externalID := int64(1234567)
131+
externalName := "username"
132+
source := base.Release{
133+
PublisherID: externalID,
134+
PublisherName: externalName,
135+
}
136+
137+
//
138+
// The externalID does not match any existing user, everything
139+
// belongs to the doer
140+
//
141+
target := models.Release{}
142+
uploader.userMap = make(map[int64]int64)
143+
err := uploader.remapUser(&source, &target)
144+
assert.NoError(t, err)
145+
assert.EqualValues(t, doer.ID, target.GetUserID())
146+
147+
//
148+
// The externalID matches a known user but the name does not match,
149+
// everything belongs to the doer
150+
//
151+
source.PublisherID = user.ID
152+
target = models.Release{}
153+
uploader.userMap = make(map[int64]int64)
154+
err = uploader.remapUser(&source, &target)
155+
assert.NoError(t, err)
156+
assert.EqualValues(t, doer.ID, target.GetUserID())
157+
158+
//
159+
// The externalID and externalName match an existing user, everything
160+
// belongs to the existing user
161+
//
162+
source.PublisherName = user.Name
163+
target = models.Release{}
164+
uploader.userMap = make(map[int64]int64)
165+
err = uploader.remapUser(&source, &target)
166+
assert.NoError(t, err)
167+
assert.EqualValues(t, user.ID, target.GetUserID())
168+
}
169+
120170
func TestGiteaUploadRemapExternalUser(t *testing.T) {
121171
unittest.PrepareTestEnv(t)
122172
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
123173

124174
repoName := "migrated"
125175
uploader := NewGiteaLocalUploader(context.Background(), doer, doer.Name, repoName)
126176
uploader.gitServiceType = structs.GiteaService
177+
// call remapExternalUser
178+
uploader.sameApp = false
127179

128180
externalID := int64(1234567)
129-
externalName := "url.or.something"
181+
externalName := "username"
130182
source := base.Release{
131183
PublisherID: externalID,
132184
PublisherName: externalName,
@@ -136,8 +188,9 @@ func TestGiteaUploadRemapExternalUser(t *testing.T) {
136188
// When there is no user linked to the external ID, the migrated data is authored
137189
// by the doer
138190
//
191+
uploader.userMap = make(map[int64]int64)
139192
target := models.Release{}
140-
err := uploader.remapExternalUser(&source, &target)
193+
err := uploader.remapUser(&source, &target)
141194
assert.NoError(t, err)
142195
assert.EqualValues(t, doer.ID, target.GetUserID())
143196

@@ -158,8 +211,9 @@ func TestGiteaUploadRemapExternalUser(t *testing.T) {
158211
// When a user is linked to the external ID, it becomes the author of
159212
// the migrated data
160213
//
214+
uploader.userMap = make(map[int64]int64)
161215
target = models.Release{}
162-
err = uploader.remapExternalUser(&source, &target)
216+
err = uploader.remapUser(&source, &target)
163217
assert.NoError(t, err)
164-
assert.EqualValues(t, target.GetUserID(), linkedUser.ID)
218+
assert.EqualValues(t, linkedUser.ID, target.GetUserID())
165219
}

0 commit comments

Comments
 (0)