code.gitea.io/gitea@v1.22.3/services/migrations/gitea_uploader_test.go (about) 1 // Copyright 2019 The Gitea Authors. All rights reserved. 2 // Copyright 2018 Jonas Franz. All rights reserved. 3 // SPDX-License-Identifier: MIT 4 5 package migrations 6 7 import ( 8 "context" 9 "fmt" 10 "os" 11 "path/filepath" 12 "strconv" 13 "testing" 14 "time" 15 16 "code.gitea.io/gitea/models/db" 17 issues_model "code.gitea.io/gitea/models/issues" 18 repo_model "code.gitea.io/gitea/models/repo" 19 "code.gitea.io/gitea/models/unittest" 20 user_model "code.gitea.io/gitea/models/user" 21 "code.gitea.io/gitea/modules/git" 22 "code.gitea.io/gitea/modules/gitrepo" 23 "code.gitea.io/gitea/modules/graceful" 24 "code.gitea.io/gitea/modules/log" 25 base "code.gitea.io/gitea/modules/migration" 26 "code.gitea.io/gitea/modules/optional" 27 "code.gitea.io/gitea/modules/structs" 28 "code.gitea.io/gitea/modules/test" 29 30 "github.com/stretchr/testify/assert" 31 ) 32 33 func TestGiteaUploadRepo(t *testing.T) { 34 // FIXME: Since no accesskey or user/password will trigger rate limit of github, just skip 35 t.Skip() 36 37 unittest.PrepareTestEnv(t) 38 39 user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) 40 41 var ( 42 ctx = context.Background() 43 downloader = NewGithubDownloaderV3(ctx, "https://github.com", "", "", "", "go-xorm", "builder") 44 repoName = "builder-" + time.Now().Format("2006-01-02-15-04-05") 45 uploader = NewGiteaLocalUploader(graceful.GetManager().HammerContext(), user, user.Name, repoName) 46 ) 47 48 err := migrateRepository(db.DefaultContext, user, downloader, uploader, base.MigrateOptions{ 49 CloneAddr: "https://github.com/go-xorm/builder", 50 RepoName: repoName, 51 AuthUsername: "", 52 53 Wiki: true, 54 Issues: true, 55 Milestones: true, 56 Labels: true, 57 Releases: true, 58 Comments: true, 59 PullRequests: true, 60 Private: true, 61 Mirror: false, 62 }, nil) 63 assert.NoError(t, err) 64 65 repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID, Name: repoName}) 66 assert.True(t, repo.HasWiki()) 67 assert.EqualValues(t, repo_model.RepositoryReady, repo.Status) 68 69 milestones, err := db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{ 70 RepoID: repo.ID, 71 IsClosed: optional.Some(false), 72 }) 73 assert.NoError(t, err) 74 assert.Len(t, milestones, 1) 75 76 milestones, err = db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{ 77 RepoID: repo.ID, 78 IsClosed: optional.Some(true), 79 }) 80 assert.NoError(t, err) 81 assert.Empty(t, milestones) 82 83 labels, err := issues_model.GetLabelsByRepoID(ctx, repo.ID, "", db.ListOptions{}) 84 assert.NoError(t, err) 85 assert.Len(t, labels, 12) 86 87 releases, err := db.Find[repo_model.Release](db.DefaultContext, repo_model.FindReleasesOptions{ 88 ListOptions: db.ListOptions{ 89 PageSize: 10, 90 Page: 0, 91 }, 92 IncludeTags: true, 93 RepoID: repo.ID, 94 }) 95 assert.NoError(t, err) 96 assert.Len(t, releases, 8) 97 98 releases, err = db.Find[repo_model.Release](db.DefaultContext, repo_model.FindReleasesOptions{ 99 ListOptions: db.ListOptions{ 100 PageSize: 10, 101 Page: 0, 102 }, 103 IncludeTags: false, 104 RepoID: repo.ID, 105 }) 106 assert.NoError(t, err) 107 assert.Len(t, releases, 1) 108 109 issues, err := issues_model.Issues(db.DefaultContext, &issues_model.IssuesOptions{ 110 RepoIDs: []int64{repo.ID}, 111 IsPull: optional.Some(false), 112 SortType: "oldest", 113 }) 114 assert.NoError(t, err) 115 assert.Len(t, issues, 15) 116 assert.NoError(t, issues[0].LoadDiscussComments(db.DefaultContext)) 117 assert.Empty(t, issues[0].Comments) 118 119 pulls, _, err := issues_model.PullRequests(db.DefaultContext, repo.ID, &issues_model.PullRequestsOptions{ 120 SortType: "oldest", 121 }) 122 assert.NoError(t, err) 123 assert.Len(t, pulls, 30) 124 assert.NoError(t, pulls[0].LoadIssue(db.DefaultContext)) 125 assert.NoError(t, pulls[0].Issue.LoadDiscussComments(db.DefaultContext)) 126 assert.Len(t, pulls[0].Issue.Comments, 2) 127 } 128 129 func TestGiteaUploadRemapLocalUser(t *testing.T) { 130 unittest.PrepareTestEnv(t) 131 doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) 132 user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) 133 134 repoName := "migrated" 135 uploader := NewGiteaLocalUploader(context.Background(), doer, doer.Name, repoName) 136 // call remapLocalUser 137 uploader.sameApp = true 138 139 externalID := int64(1234567) 140 externalName := "username" 141 source := base.Release{ 142 PublisherID: externalID, 143 PublisherName: externalName, 144 } 145 146 // 147 // The externalID does not match any existing user, everything 148 // belongs to the doer 149 // 150 target := repo_model.Release{} 151 uploader.userMap = make(map[int64]int64) 152 err := uploader.remapUser(&source, &target) 153 assert.NoError(t, err) 154 assert.EqualValues(t, doer.ID, target.GetUserID()) 155 156 // 157 // The externalID matches a known user but the name does not match, 158 // everything belongs to the doer 159 // 160 source.PublisherID = user.ID 161 target = repo_model.Release{} 162 uploader.userMap = make(map[int64]int64) 163 err = uploader.remapUser(&source, &target) 164 assert.NoError(t, err) 165 assert.EqualValues(t, doer.ID, target.GetUserID()) 166 167 // 168 // The externalID and externalName match an existing user, everything 169 // belongs to the existing user 170 // 171 source.PublisherName = user.Name 172 target = repo_model.Release{} 173 uploader.userMap = make(map[int64]int64) 174 err = uploader.remapUser(&source, &target) 175 assert.NoError(t, err) 176 assert.EqualValues(t, user.ID, target.GetUserID()) 177 } 178 179 func TestGiteaUploadRemapExternalUser(t *testing.T) { 180 unittest.PrepareTestEnv(t) 181 doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) 182 183 repoName := "migrated" 184 uploader := NewGiteaLocalUploader(context.Background(), doer, doer.Name, repoName) 185 uploader.gitServiceType = structs.GiteaService 186 // call remapExternalUser 187 uploader.sameApp = false 188 189 externalID := int64(1234567) 190 externalName := "username" 191 source := base.Release{ 192 PublisherID: externalID, 193 PublisherName: externalName, 194 } 195 196 // 197 // When there is no user linked to the external ID, the migrated data is authored 198 // by the doer 199 // 200 uploader.userMap = make(map[int64]int64) 201 target := repo_model.Release{} 202 err := uploader.remapUser(&source, &target) 203 assert.NoError(t, err) 204 assert.EqualValues(t, doer.ID, target.GetUserID()) 205 206 // 207 // Link the external ID to an existing user 208 // 209 linkedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) 210 externalLoginUser := &user_model.ExternalLoginUser{ 211 ExternalID: strconv.FormatInt(externalID, 10), 212 UserID: linkedUser.ID, 213 LoginSourceID: 0, 214 Provider: structs.GiteaService.Name(), 215 } 216 err = user_model.LinkExternalToUser(db.DefaultContext, linkedUser, externalLoginUser) 217 assert.NoError(t, err) 218 219 // 220 // When a user is linked to the external ID, it becomes the author of 221 // the migrated data 222 // 223 uploader.userMap = make(map[int64]int64) 224 target = repo_model.Release{} 225 err = uploader.remapUser(&source, &target) 226 assert.NoError(t, err) 227 assert.EqualValues(t, linkedUser.ID, target.GetUserID()) 228 } 229 230 func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { 231 unittest.PrepareTestEnv(t) 232 233 // 234 // fromRepo master 235 // 236 fromRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 237 baseRef := "master" 238 assert.NoError(t, git.InitRepository(git.DefaultContext, fromRepo.RepoPath(), false, fromRepo.ObjectFormatName)) 239 err := git.NewCommand(git.DefaultContext, "symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseRef).Run(&git.RunOpts{Dir: fromRepo.RepoPath()}) 240 assert.NoError(t, err) 241 assert.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s", fromRepo.RepoPath())), 0o644)) 242 assert.NoError(t, git.AddChanges(fromRepo.RepoPath(), true)) 243 signature := git.Signature{ 244 Email: "test@example.com", 245 Name: "test", 246 When: time.Now(), 247 } 248 assert.NoError(t, git.CommitChanges(fromRepo.RepoPath(), git.CommitChangesOptions{ 249 Committer: &signature, 250 Author: &signature, 251 Message: "Initial Commit", 252 })) 253 fromGitRepo, err := gitrepo.OpenRepository(git.DefaultContext, fromRepo) 254 assert.NoError(t, err) 255 defer fromGitRepo.Close() 256 baseSHA, err := fromGitRepo.GetBranchCommitID(baseRef) 257 assert.NoError(t, err) 258 259 // 260 // fromRepo branch1 261 // 262 headRef := "branch1" 263 _, _, err = git.NewCommand(git.DefaultContext, "checkout", "-b").AddDynamicArguments(headRef).RunStdString(&git.RunOpts{Dir: fromRepo.RepoPath()}) 264 assert.NoError(t, err) 265 assert.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte("SOMETHING"), 0o644)) 266 assert.NoError(t, git.AddChanges(fromRepo.RepoPath(), true)) 267 signature.When = time.Now() 268 assert.NoError(t, git.CommitChanges(fromRepo.RepoPath(), git.CommitChangesOptions{ 269 Committer: &signature, 270 Author: &signature, 271 Message: "Pull request", 272 })) 273 assert.NoError(t, err) 274 headSHA, err := fromGitRepo.GetBranchCommitID(headRef) 275 assert.NoError(t, err) 276 277 fromRepoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: fromRepo.OwnerID}) 278 279 // 280 // forkRepo branch2 281 // 282 forkHeadRef := "branch2" 283 forkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 8}) 284 assert.NoError(t, git.CloneWithArgs(git.DefaultContext, nil, fromRepo.RepoPath(), forkRepo.RepoPath(), git.CloneRepoOptions{ 285 Branch: headRef, 286 })) 287 _, _, err = git.NewCommand(git.DefaultContext, "checkout", "-b").AddDynamicArguments(forkHeadRef).RunStdString(&git.RunOpts{Dir: forkRepo.RepoPath()}) 288 assert.NoError(t, err) 289 assert.NoError(t, os.WriteFile(filepath.Join(forkRepo.RepoPath(), "README.md"), []byte(fmt.Sprintf("# branch2 %s", forkRepo.RepoPath())), 0o644)) 290 assert.NoError(t, git.AddChanges(forkRepo.RepoPath(), true)) 291 assert.NoError(t, git.CommitChanges(forkRepo.RepoPath(), git.CommitChangesOptions{ 292 Committer: &signature, 293 Author: &signature, 294 Message: "branch2 commit", 295 })) 296 forkGitRepo, err := gitrepo.OpenRepository(git.DefaultContext, forkRepo) 297 assert.NoError(t, err) 298 defer forkGitRepo.Close() 299 forkHeadSHA, err := forkGitRepo.GetBranchCommitID(forkHeadRef) 300 assert.NoError(t, err) 301 302 toRepoName := "migrated" 303 uploader := NewGiteaLocalUploader(context.Background(), fromRepoOwner, fromRepoOwner.Name, toRepoName) 304 uploader.gitServiceType = structs.GiteaService 305 assert.NoError(t, uploader.CreateRepo(&base.Repository{ 306 Description: "description", 307 OriginalURL: fromRepo.RepoPath(), 308 CloneURL: fromRepo.RepoPath(), 309 IsPrivate: false, 310 IsMirror: true, 311 }, base.MigrateOptions{ 312 GitServiceType: structs.GiteaService, 313 Private: false, 314 Mirror: true, 315 })) 316 317 for _, testCase := range []struct { 318 name string 319 head string 320 logFilter []string 321 logFiltered []bool 322 pr base.PullRequest 323 }{ 324 { 325 name: "fork, good Head.SHA", 326 head: fmt.Sprintf("%s/%s", forkRepo.OwnerName, forkHeadRef), 327 pr: base.PullRequest{ 328 PatchURL: "", 329 Number: 1, 330 State: "open", 331 Base: base.PullRequestBranch{ 332 CloneURL: fromRepo.RepoPath(), 333 Ref: baseRef, 334 SHA: baseSHA, 335 RepoName: fromRepo.Name, 336 OwnerName: fromRepo.OwnerName, 337 }, 338 Head: base.PullRequestBranch{ 339 CloneURL: forkRepo.RepoPath(), 340 Ref: forkHeadRef, 341 SHA: forkHeadSHA, 342 RepoName: forkRepo.Name, 343 OwnerName: forkRepo.OwnerName, 344 }, 345 }, 346 }, 347 { 348 name: "fork, invalid Head.Ref", 349 head: "unknown repository", 350 pr: base.PullRequest{ 351 PatchURL: "", 352 Number: 1, 353 State: "open", 354 Base: base.PullRequestBranch{ 355 CloneURL: fromRepo.RepoPath(), 356 Ref: baseRef, 357 SHA: baseSHA, 358 RepoName: fromRepo.Name, 359 OwnerName: fromRepo.OwnerName, 360 }, 361 Head: base.PullRequestBranch{ 362 CloneURL: forkRepo.RepoPath(), 363 Ref: "INVALID", 364 SHA: forkHeadSHA, 365 RepoName: forkRepo.Name, 366 OwnerName: forkRepo.OwnerName, 367 }, 368 }, 369 logFilter: []string{"Fetch branch from"}, 370 logFiltered: []bool{true}, 371 }, 372 { 373 name: "invalid fork CloneURL", 374 head: "unknown repository", 375 pr: base.PullRequest{ 376 PatchURL: "", 377 Number: 1, 378 State: "open", 379 Base: base.PullRequestBranch{ 380 CloneURL: fromRepo.RepoPath(), 381 Ref: baseRef, 382 SHA: baseSHA, 383 RepoName: fromRepo.Name, 384 OwnerName: fromRepo.OwnerName, 385 }, 386 Head: base.PullRequestBranch{ 387 CloneURL: "UNLIKELY", 388 Ref: forkHeadRef, 389 SHA: forkHeadSHA, 390 RepoName: forkRepo.Name, 391 OwnerName: "WRONG", 392 }, 393 }, 394 logFilter: []string{"AddRemote"}, 395 logFiltered: []bool{true}, 396 }, 397 { 398 name: "no fork, good Head.SHA", 399 head: headRef, 400 pr: base.PullRequest{ 401 PatchURL: "", 402 Number: 1, 403 State: "open", 404 Base: base.PullRequestBranch{ 405 CloneURL: fromRepo.RepoPath(), 406 Ref: baseRef, 407 SHA: baseSHA, 408 RepoName: fromRepo.Name, 409 OwnerName: fromRepo.OwnerName, 410 }, 411 Head: base.PullRequestBranch{ 412 CloneURL: fromRepo.RepoPath(), 413 Ref: headRef, 414 SHA: headSHA, 415 RepoName: fromRepo.Name, 416 OwnerName: fromRepo.OwnerName, 417 }, 418 }, 419 }, 420 { 421 name: "no fork, empty Head.SHA", 422 head: headRef, 423 pr: base.PullRequest{ 424 PatchURL: "", 425 Number: 1, 426 State: "open", 427 Base: base.PullRequestBranch{ 428 CloneURL: fromRepo.RepoPath(), 429 Ref: baseRef, 430 SHA: baseSHA, 431 RepoName: fromRepo.Name, 432 OwnerName: fromRepo.OwnerName, 433 }, 434 Head: base.PullRequestBranch{ 435 CloneURL: fromRepo.RepoPath(), 436 Ref: headRef, 437 SHA: "", 438 RepoName: fromRepo.Name, 439 OwnerName: fromRepo.OwnerName, 440 }, 441 }, 442 logFilter: []string{"Empty reference", "Cannot remove local head"}, 443 logFiltered: []bool{true, false}, 444 }, 445 { 446 name: "no fork, invalid Head.SHA", 447 head: headRef, 448 pr: base.PullRequest{ 449 PatchURL: "", 450 Number: 1, 451 State: "open", 452 Base: base.PullRequestBranch{ 453 CloneURL: fromRepo.RepoPath(), 454 Ref: baseRef, 455 SHA: baseSHA, 456 RepoName: fromRepo.Name, 457 OwnerName: fromRepo.OwnerName, 458 }, 459 Head: base.PullRequestBranch{ 460 CloneURL: fromRepo.RepoPath(), 461 Ref: headRef, 462 SHA: "brokenSHA", 463 RepoName: fromRepo.Name, 464 OwnerName: fromRepo.OwnerName, 465 }, 466 }, 467 logFilter: []string{"Deprecated local head"}, 468 logFiltered: []bool{true}, 469 }, 470 { 471 name: "no fork, not found Head.SHA", 472 head: headRef, 473 pr: base.PullRequest{ 474 PatchURL: "", 475 Number: 1, 476 State: "open", 477 Base: base.PullRequestBranch{ 478 CloneURL: fromRepo.RepoPath(), 479 Ref: baseRef, 480 SHA: baseSHA, 481 RepoName: fromRepo.Name, 482 OwnerName: fromRepo.OwnerName, 483 }, 484 Head: base.PullRequestBranch{ 485 CloneURL: fromRepo.RepoPath(), 486 Ref: headRef, 487 SHA: "2697b352310fcd01cbd1f3dbd43b894080027f68", 488 RepoName: fromRepo.Name, 489 OwnerName: fromRepo.OwnerName, 490 }, 491 }, 492 logFilter: []string{"Deprecated local head", "Cannot remove local head"}, 493 logFiltered: []bool{true, false}, 494 }, 495 } { 496 t.Run(testCase.name, func(t *testing.T) { 497 stopMark := fmt.Sprintf(">>>>>>>>>>>>>STOP: %s<<<<<<<<<<<<<<<", testCase.name) 498 499 logChecker, cleanup := test.NewLogChecker(log.DEFAULT) 500 logChecker.Filter(testCase.logFilter...).StopMark(stopMark) 501 defer cleanup() 502 503 testCase.pr.EnsuredSafe = true 504 505 head, err := uploader.updateGitForPullRequest(&testCase.pr) 506 assert.NoError(t, err) 507 assert.EqualValues(t, testCase.head, head) 508 509 log.Info(stopMark) 510 511 logFiltered, logStopped := logChecker.Check(5 * time.Second) 512 assert.True(t, logStopped) 513 if len(testCase.logFilter) > 0 { 514 assert.EqualValues(t, testCase.logFiltered, logFiltered, "for log message filters: %v", testCase.logFilter) 515 } 516 }) 517 } 518 }