You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

gitea_uploader_test.go 15KB

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