gitea源码

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