您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // Copyright 2018 Jonas Franz. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package migrations
  6. import (
  7. "fmt"
  8. "io"
  9. "net/http"
  10. "net/url"
  11. "os"
  12. "path"
  13. "path/filepath"
  14. "strings"
  15. "sync"
  16. "time"
  17. "code.gitea.io/gitea/models"
  18. "code.gitea.io/gitea/modules/git"
  19. "code.gitea.io/gitea/modules/log"
  20. "code.gitea.io/gitea/modules/migrations/base"
  21. "code.gitea.io/gitea/modules/setting"
  22. "code.gitea.io/gitea/modules/structs"
  23. "code.gitea.io/gitea/modules/timeutil"
  24. gouuid "github.com/satori/go.uuid"
  25. )
  26. var (
  27. _ base.Uploader = &GiteaLocalUploader{}
  28. )
  29. // GiteaLocalUploader implements an Uploader to gitea sites
  30. type GiteaLocalUploader struct {
  31. doer *models.User
  32. repoOwner string
  33. repoName string
  34. repo *models.Repository
  35. labels sync.Map
  36. milestones sync.Map
  37. issues sync.Map
  38. gitRepo *git.Repository
  39. prHeadCache map[string]struct{}
  40. userMap map[int64]int64 // external user id mapping to user id
  41. gitServiceType structs.GitServiceType
  42. }
  43. // NewGiteaLocalUploader creates an gitea Uploader via gitea API v1
  44. func NewGiteaLocalUploader(doer *models.User, repoOwner, repoName string) *GiteaLocalUploader {
  45. return &GiteaLocalUploader{
  46. doer: doer,
  47. repoOwner: repoOwner,
  48. repoName: repoName,
  49. prHeadCache: make(map[string]struct{}),
  50. userMap: make(map[int64]int64),
  51. }
  52. }
  53. // MaxBatchInsertSize returns the table's max batch insert size
  54. func (g *GiteaLocalUploader) MaxBatchInsertSize(tp string) int {
  55. switch tp {
  56. case "issue":
  57. return models.MaxBatchInsertSize(new(models.Issue))
  58. case "comment":
  59. return models.MaxBatchInsertSize(new(models.Comment))
  60. case "milestone":
  61. return models.MaxBatchInsertSize(new(models.Milestone))
  62. case "label":
  63. return models.MaxBatchInsertSize(new(models.Label))
  64. case "release":
  65. return models.MaxBatchInsertSize(new(models.Release))
  66. case "pullrequest":
  67. return models.MaxBatchInsertSize(new(models.PullRequest))
  68. }
  69. return 10
  70. }
  71. // CreateRepo creates a repository
  72. func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.MigrateOptions) error {
  73. owner, err := models.GetUserByName(g.repoOwner)
  74. if err != nil {
  75. return err
  76. }
  77. var remoteAddr = repo.CloneURL
  78. if len(opts.AuthUsername) > 0 {
  79. u, err := url.Parse(repo.CloneURL)
  80. if err != nil {
  81. return err
  82. }
  83. u.User = url.UserPassword(opts.AuthUsername, opts.AuthPassword)
  84. remoteAddr = u.String()
  85. }
  86. var r *models.Repository
  87. if opts.MigrateToRepoID <= 0 {
  88. r, err = models.CreateRepository(g.doer, owner, models.CreateRepoOptions{
  89. Name: g.repoName,
  90. Description: repo.Description,
  91. OriginalURL: repo.OriginalURL,
  92. IsPrivate: opts.Private,
  93. IsMirror: opts.Mirror,
  94. Status: models.RepositoryBeingMigrated,
  95. })
  96. } else {
  97. r, err = models.GetRepositoryByID(opts.MigrateToRepoID)
  98. }
  99. if err != nil {
  100. return err
  101. }
  102. r, err = models.MigrateRepositoryGitData(g.doer, owner, r, structs.MigrateRepoOption{
  103. RepoName: g.repoName,
  104. Description: repo.Description,
  105. OriginalURL: repo.OriginalURL,
  106. GitServiceType: opts.GitServiceType,
  107. Mirror: repo.IsMirror,
  108. CloneAddr: remoteAddr,
  109. Private: repo.IsPrivate,
  110. Wiki: opts.Wiki,
  111. Releases: opts.Releases, // if didn't get releases, then sync them from tags
  112. })
  113. g.repo = r
  114. if err != nil {
  115. return err
  116. }
  117. g.gitRepo, err = git.OpenRepository(r.RepoPath())
  118. return err
  119. }
  120. // Close closes this uploader
  121. func (g *GiteaLocalUploader) Close() {
  122. if g.gitRepo != nil {
  123. g.gitRepo.Close()
  124. }
  125. }
  126. // CreateTopics creates topics
  127. func (g *GiteaLocalUploader) CreateTopics(topics ...string) error {
  128. return models.SaveTopics(g.repo.ID, topics...)
  129. }
  130. // CreateMilestones creates milestones
  131. func (g *GiteaLocalUploader) CreateMilestones(milestones ...*base.Milestone) error {
  132. var mss = make([]*models.Milestone, 0, len(milestones))
  133. for _, milestone := range milestones {
  134. var deadline timeutil.TimeStamp
  135. if milestone.Deadline != nil {
  136. deadline = timeutil.TimeStamp(milestone.Deadline.Unix())
  137. }
  138. if deadline == 0 {
  139. deadline = timeutil.TimeStamp(time.Date(9999, 1, 1, 0, 0, 0, 0, setting.DefaultUILocation).Unix())
  140. }
  141. var ms = models.Milestone{
  142. RepoID: g.repo.ID,
  143. Name: milestone.Title,
  144. Content: milestone.Description,
  145. IsClosed: milestone.State == "closed",
  146. DeadlineUnix: deadline,
  147. }
  148. if ms.IsClosed && milestone.Closed != nil {
  149. ms.ClosedDateUnix = timeutil.TimeStamp(milestone.Closed.Unix())
  150. }
  151. mss = append(mss, &ms)
  152. }
  153. err := models.InsertMilestones(mss...)
  154. if err != nil {
  155. return err
  156. }
  157. for _, ms := range mss {
  158. g.milestones.Store(ms.Name, ms.ID)
  159. }
  160. return nil
  161. }
  162. // CreateLabels creates labels
  163. func (g *GiteaLocalUploader) CreateLabels(labels ...*base.Label) error {
  164. var lbs = make([]*models.Label, 0, len(labels))
  165. for _, label := range labels {
  166. lbs = append(lbs, &models.Label{
  167. RepoID: g.repo.ID,
  168. Name: label.Name,
  169. Description: label.Description,
  170. Color: fmt.Sprintf("#%s", label.Color),
  171. })
  172. }
  173. err := models.NewLabels(lbs...)
  174. if err != nil {
  175. return err
  176. }
  177. for _, lb := range lbs {
  178. g.labels.Store(lb.Name, lb)
  179. }
  180. return nil
  181. }
  182. // CreateReleases creates releases
  183. func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
  184. var rels = make([]*models.Release, 0, len(releases))
  185. for _, release := range releases {
  186. var rel = models.Release{
  187. RepoID: g.repo.ID,
  188. TagName: release.TagName,
  189. LowerTagName: strings.ToLower(release.TagName),
  190. Target: release.TargetCommitish,
  191. Title: release.Name,
  192. Sha1: release.TargetCommitish,
  193. Note: release.Body,
  194. IsDraft: release.Draft,
  195. IsPrerelease: release.Prerelease,
  196. IsTag: false,
  197. CreatedUnix: timeutil.TimeStamp(release.Created.Unix()),
  198. }
  199. userid, ok := g.userMap[release.PublisherID]
  200. tp := g.gitServiceType.Name()
  201. if !ok && tp != "" {
  202. var err error
  203. userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", release.PublisherID))
  204. if err != nil {
  205. log.Error("GetUserIDByExternalUserID: %v", err)
  206. }
  207. if userid > 0 {
  208. g.userMap[release.PublisherID] = userid
  209. }
  210. }
  211. if userid > 0 {
  212. rel.PublisherID = userid
  213. } else {
  214. rel.PublisherID = g.doer.ID
  215. rel.OriginalAuthor = release.PublisherName
  216. rel.OriginalAuthorID = release.PublisherID
  217. }
  218. // calc NumCommits
  219. commit, err := g.gitRepo.GetCommit(rel.TagName)
  220. if err != nil {
  221. return fmt.Errorf("GetCommit: %v", err)
  222. }
  223. rel.NumCommits, err = commit.CommitsCount()
  224. if err != nil {
  225. return fmt.Errorf("CommitsCount: %v", err)
  226. }
  227. for _, asset := range release.Assets {
  228. var attach = models.Attachment{
  229. UUID: gouuid.NewV4().String(),
  230. Name: asset.Name,
  231. DownloadCount: int64(*asset.DownloadCount),
  232. Size: int64(*asset.Size),
  233. CreatedUnix: timeutil.TimeStamp(asset.Created.Unix()),
  234. }
  235. // download attachment
  236. err = func() error {
  237. resp, err := http.Get(asset.URL)
  238. if err != nil {
  239. return err
  240. }
  241. defer resp.Body.Close()
  242. localPath := attach.LocalPath()
  243. if err = os.MkdirAll(path.Dir(localPath), os.ModePerm); err != nil {
  244. return fmt.Errorf("MkdirAll: %v", err)
  245. }
  246. fw, err := os.Create(localPath)
  247. if err != nil {
  248. return fmt.Errorf("Create: %v", err)
  249. }
  250. defer fw.Close()
  251. _, err = io.Copy(fw, resp.Body)
  252. return err
  253. }()
  254. if err != nil {
  255. return err
  256. }
  257. rel.Attachments = append(rel.Attachments, &attach)
  258. }
  259. rels = append(rels, &rel)
  260. }
  261. if err := models.InsertReleases(rels...); err != nil {
  262. return err
  263. }
  264. // sync tags to releases in database
  265. return models.SyncReleasesWithTags(g.repo, g.gitRepo)
  266. }
  267. // CreateIssues creates issues
  268. func (g *GiteaLocalUploader) CreateIssues(issues ...*base.Issue) error {
  269. var iss = make([]*models.Issue, 0, len(issues))
  270. for _, issue := range issues {
  271. var labels []*models.Label
  272. for _, label := range issue.Labels {
  273. lb, ok := g.labels.Load(label.Name)
  274. if ok {
  275. labels = append(labels, lb.(*models.Label))
  276. }
  277. }
  278. var milestoneID int64
  279. if issue.Milestone != "" {
  280. milestone, ok := g.milestones.Load(issue.Milestone)
  281. if ok {
  282. milestoneID = milestone.(int64)
  283. }
  284. }
  285. var is = models.Issue{
  286. RepoID: g.repo.ID,
  287. Repo: g.repo,
  288. Index: issue.Number,
  289. Title: issue.Title,
  290. Content: issue.Content,
  291. IsClosed: issue.State == "closed",
  292. IsLocked: issue.IsLocked,
  293. MilestoneID: milestoneID,
  294. Labels: labels,
  295. CreatedUnix: timeutil.TimeStamp(issue.Created.Unix()),
  296. }
  297. userid, ok := g.userMap[issue.PosterID]
  298. tp := g.gitServiceType.Name()
  299. if !ok && tp != "" {
  300. var err error
  301. userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", issue.PosterID))
  302. if err != nil {
  303. log.Error("GetUserIDByExternalUserID: %v", err)
  304. }
  305. if userid > 0 {
  306. g.userMap[issue.PosterID] = userid
  307. }
  308. }
  309. if userid > 0 {
  310. is.PosterID = userid
  311. } else {
  312. is.PosterID = g.doer.ID
  313. is.OriginalAuthor = issue.PosterName
  314. is.OriginalAuthorID = issue.PosterID
  315. }
  316. if issue.Closed != nil {
  317. is.ClosedUnix = timeutil.TimeStamp(issue.Closed.Unix())
  318. }
  319. // TODO: add reactions
  320. iss = append(iss, &is)
  321. }
  322. err := models.InsertIssues(iss...)
  323. if err != nil {
  324. return err
  325. }
  326. for _, is := range iss {
  327. g.issues.Store(is.Index, is.ID)
  328. }
  329. return nil
  330. }
  331. // CreateComments creates comments of issues
  332. func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error {
  333. var cms = make([]*models.Comment, 0, len(comments))
  334. for _, comment := range comments {
  335. var issueID int64
  336. if issueIDStr, ok := g.issues.Load(comment.IssueIndex); !ok {
  337. issue, err := models.GetIssueByIndex(g.repo.ID, comment.IssueIndex)
  338. if err != nil {
  339. return err
  340. }
  341. issueID = issue.ID
  342. g.issues.Store(comment.IssueIndex, issueID)
  343. } else {
  344. issueID = issueIDStr.(int64)
  345. }
  346. userid, ok := g.userMap[comment.PosterID]
  347. tp := g.gitServiceType.Name()
  348. if !ok && tp != "" {
  349. var err error
  350. userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", comment.PosterID))
  351. if err != nil {
  352. log.Error("GetUserIDByExternalUserID: %v", err)
  353. }
  354. if userid > 0 {
  355. g.userMap[comment.PosterID] = userid
  356. }
  357. }
  358. cm := models.Comment{
  359. IssueID: issueID,
  360. Type: models.CommentTypeComment,
  361. Content: comment.Content,
  362. CreatedUnix: timeutil.TimeStamp(comment.Created.Unix()),
  363. }
  364. if userid > 0 {
  365. cm.PosterID = userid
  366. } else {
  367. cm.PosterID = g.doer.ID
  368. cm.OriginalAuthor = comment.PosterName
  369. cm.OriginalAuthorID = comment.PosterID
  370. }
  371. cms = append(cms, &cm)
  372. // TODO: Reactions
  373. }
  374. return models.InsertIssueComments(cms)
  375. }
  376. // CreatePullRequests creates pull requests
  377. func (g *GiteaLocalUploader) CreatePullRequests(prs ...*base.PullRequest) error {
  378. var gprs = make([]*models.PullRequest, 0, len(prs))
  379. for _, pr := range prs {
  380. gpr, err := g.newPullRequest(pr)
  381. if err != nil {
  382. return err
  383. }
  384. userid, ok := g.userMap[pr.PosterID]
  385. tp := g.gitServiceType.Name()
  386. if !ok && tp != "" {
  387. var err error
  388. userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", pr.PosterID))
  389. if err != nil {
  390. log.Error("GetUserIDByExternalUserID: %v", err)
  391. }
  392. if userid > 0 {
  393. g.userMap[pr.PosterID] = userid
  394. }
  395. }
  396. if userid > 0 {
  397. gpr.Issue.PosterID = userid
  398. } else {
  399. gpr.Issue.PosterID = g.doer.ID
  400. gpr.Issue.OriginalAuthor = pr.PosterName
  401. gpr.Issue.OriginalAuthorID = pr.PosterID
  402. }
  403. gprs = append(gprs, gpr)
  404. }
  405. if err := models.InsertPullRequests(gprs...); err != nil {
  406. return err
  407. }
  408. for _, pr := range gprs {
  409. g.issues.Store(pr.Issue.Index, pr.Issue.ID)
  410. }
  411. return nil
  412. }
  413. func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullRequest, error) {
  414. var labels []*models.Label
  415. for _, label := range pr.Labels {
  416. lb, ok := g.labels.Load(label.Name)
  417. if ok {
  418. labels = append(labels, lb.(*models.Label))
  419. }
  420. }
  421. var milestoneID int64
  422. if pr.Milestone != "" {
  423. milestone, ok := g.milestones.Load(pr.Milestone)
  424. if ok {
  425. milestoneID = milestone.(int64)
  426. }
  427. }
  428. // download patch file
  429. err := func() error {
  430. resp, err := http.Get(pr.PatchURL)
  431. if err != nil {
  432. return err
  433. }
  434. defer resp.Body.Close()
  435. pullDir := filepath.Join(g.repo.RepoPath(), "pulls")
  436. if err = os.MkdirAll(pullDir, os.ModePerm); err != nil {
  437. return err
  438. }
  439. f, err := os.Create(filepath.Join(pullDir, fmt.Sprintf("%d.patch", pr.Number)))
  440. if err != nil {
  441. return err
  442. }
  443. defer f.Close()
  444. _, err = io.Copy(f, resp.Body)
  445. return err
  446. }()
  447. if err != nil {
  448. return nil, err
  449. }
  450. // set head information
  451. pullHead := filepath.Join(g.repo.RepoPath(), "refs", "pull", fmt.Sprintf("%d", pr.Number))
  452. if err := os.MkdirAll(pullHead, os.ModePerm); err != nil {
  453. return nil, err
  454. }
  455. p, err := os.Create(filepath.Join(pullHead, "head"))
  456. if err != nil {
  457. return nil, err
  458. }
  459. _, err = p.WriteString(pr.Head.SHA)
  460. p.Close()
  461. if err != nil {
  462. return nil, err
  463. }
  464. var head = "unknown repository"
  465. if pr.IsForkPullRequest() && pr.State != "closed" {
  466. if pr.Head.OwnerName != "" {
  467. remote := pr.Head.OwnerName
  468. _, ok := g.prHeadCache[remote]
  469. if !ok {
  470. // git remote add
  471. err := g.gitRepo.AddRemote(remote, pr.Head.CloneURL, true)
  472. if err != nil {
  473. log.Error("AddRemote failed: %s", err)
  474. } else {
  475. g.prHeadCache[remote] = struct{}{}
  476. ok = true
  477. }
  478. }
  479. if ok {
  480. _, err = git.NewCommand("fetch", remote, pr.Head.Ref).RunInDir(g.repo.RepoPath())
  481. if err != nil {
  482. log.Error("Fetch branch from %s failed: %v", pr.Head.CloneURL, err)
  483. } else {
  484. headBranch := filepath.Join(g.repo.RepoPath(), "refs", "heads", pr.Head.OwnerName, pr.Head.Ref)
  485. if err := os.MkdirAll(filepath.Dir(headBranch), os.ModePerm); err != nil {
  486. return nil, err
  487. }
  488. b, err := os.Create(headBranch)
  489. if err != nil {
  490. return nil, err
  491. }
  492. _, err = b.WriteString(pr.Head.SHA)
  493. b.Close()
  494. if err != nil {
  495. return nil, err
  496. }
  497. head = pr.Head.OwnerName + "/" + pr.Head.Ref
  498. }
  499. }
  500. }
  501. } else {
  502. head = pr.Head.Ref
  503. }
  504. var issue = models.Issue{
  505. RepoID: g.repo.ID,
  506. Repo: g.repo,
  507. Title: pr.Title,
  508. Index: pr.Number,
  509. Content: pr.Content,
  510. MilestoneID: milestoneID,
  511. IsPull: true,
  512. IsClosed: pr.State == "closed",
  513. IsLocked: pr.IsLocked,
  514. Labels: labels,
  515. CreatedUnix: timeutil.TimeStamp(pr.Created.Unix()),
  516. }
  517. userid, ok := g.userMap[pr.PosterID]
  518. if !ok {
  519. var err error
  520. userid, err = models.GetUserIDByExternalUserID("github", fmt.Sprintf("%v", pr.PosterID))
  521. if err != nil {
  522. log.Error("GetUserIDByExternalUserID: %v", err)
  523. }
  524. if userid > 0 {
  525. g.userMap[pr.PosterID] = userid
  526. }
  527. }
  528. if userid > 0 {
  529. issue.PosterID = userid
  530. } else {
  531. issue.PosterID = g.doer.ID
  532. issue.OriginalAuthor = pr.PosterName
  533. issue.OriginalAuthorID = pr.PosterID
  534. }
  535. var pullRequest = models.PullRequest{
  536. HeadRepoID: g.repo.ID,
  537. HeadBranch: head,
  538. BaseRepoID: g.repo.ID,
  539. BaseBranch: pr.Base.Ref,
  540. MergeBase: pr.Base.SHA,
  541. Index: pr.Number,
  542. HasMerged: pr.Merged,
  543. Issue: &issue,
  544. }
  545. if pullRequest.Issue.IsClosed && pr.Closed != nil {
  546. pullRequest.Issue.ClosedUnix = timeutil.TimeStamp(pr.Closed.Unix())
  547. }
  548. if pullRequest.HasMerged && pr.MergedTime != nil {
  549. pullRequest.MergedUnix = timeutil.TimeStamp(pr.MergedTime.Unix())
  550. pullRequest.MergedCommitID = pr.MergeCommitSHA
  551. pullRequest.MergerID = g.doer.ID
  552. }
  553. // TODO: reactions
  554. // TODO: assignees
  555. return &pullRequest, nil
  556. }
  557. // Rollback when migrating failed, this will rollback all the changes.
  558. func (g *GiteaLocalUploader) Rollback() error {
  559. if g.repo != nil && g.repo.ID > 0 {
  560. if err := models.DeleteRepository(g.doer, g.repo.OwnerID, g.repo.ID); err != nil {
  561. return err
  562. }
  563. }
  564. return nil
  565. }