Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

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