本站源代码
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.

617 satır
15KB

  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. // CreateTopics creates topics
  121. func (g *GiteaLocalUploader) CreateTopics(topics ...string) error {
  122. return models.SaveTopics(g.repo.ID, topics...)
  123. }
  124. // CreateMilestones creates milestones
  125. func (g *GiteaLocalUploader) CreateMilestones(milestones ...*base.Milestone) error {
  126. var mss = make([]*models.Milestone, 0, len(milestones))
  127. for _, milestone := range milestones {
  128. var deadline timeutil.TimeStamp
  129. if milestone.Deadline != nil {
  130. deadline = timeutil.TimeStamp(milestone.Deadline.Unix())
  131. }
  132. if deadline == 0 {
  133. deadline = timeutil.TimeStamp(time.Date(9999, 1, 1, 0, 0, 0, 0, setting.DefaultUILocation).Unix())
  134. }
  135. var ms = models.Milestone{
  136. RepoID: g.repo.ID,
  137. Name: milestone.Title,
  138. Content: milestone.Description,
  139. IsClosed: milestone.State == "closed",
  140. DeadlineUnix: deadline,
  141. }
  142. if ms.IsClosed && milestone.Closed != nil {
  143. ms.ClosedDateUnix = timeutil.TimeStamp(milestone.Closed.Unix())
  144. }
  145. mss = append(mss, &ms)
  146. }
  147. err := models.InsertMilestones(mss...)
  148. if err != nil {
  149. return err
  150. }
  151. for _, ms := range mss {
  152. g.milestones.Store(ms.Name, ms.ID)
  153. }
  154. return nil
  155. }
  156. // CreateLabels creates labels
  157. func (g *GiteaLocalUploader) CreateLabels(labels ...*base.Label) error {
  158. var lbs = make([]*models.Label, 0, len(labels))
  159. for _, label := range labels {
  160. lbs = append(lbs, &models.Label{
  161. RepoID: g.repo.ID,
  162. Name: label.Name,
  163. Description: label.Description,
  164. Color: fmt.Sprintf("#%s", label.Color),
  165. })
  166. }
  167. err := models.NewLabels(lbs...)
  168. if err != nil {
  169. return err
  170. }
  171. for _, lb := range lbs {
  172. g.labels.Store(lb.Name, lb)
  173. }
  174. return nil
  175. }
  176. // CreateReleases creates releases
  177. func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
  178. var rels = make([]*models.Release, 0, len(releases))
  179. for _, release := range releases {
  180. var rel = models.Release{
  181. RepoID: g.repo.ID,
  182. TagName: release.TagName,
  183. LowerTagName: strings.ToLower(release.TagName),
  184. Target: release.TargetCommitish,
  185. Title: release.Name,
  186. Sha1: release.TargetCommitish,
  187. Note: release.Body,
  188. IsDraft: release.Draft,
  189. IsPrerelease: release.Prerelease,
  190. IsTag: false,
  191. CreatedUnix: timeutil.TimeStamp(release.Created.Unix()),
  192. }
  193. userid, ok := g.userMap[release.PublisherID]
  194. tp := g.gitServiceType.Name()
  195. if !ok && tp != "" {
  196. var err error
  197. userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", release.PublisherID))
  198. if err != nil {
  199. log.Error("GetUserIDByExternalUserID: %v", err)
  200. }
  201. if userid > 0 {
  202. g.userMap[release.PublisherID] = userid
  203. }
  204. }
  205. if userid > 0 {
  206. rel.PublisherID = userid
  207. } else {
  208. rel.PublisherID = g.doer.ID
  209. rel.OriginalAuthor = release.PublisherName
  210. rel.OriginalAuthorID = release.PublisherID
  211. }
  212. // calc NumCommits
  213. commit, err := g.gitRepo.GetCommit(rel.TagName)
  214. if err != nil {
  215. return fmt.Errorf("GetCommit: %v", err)
  216. }
  217. rel.NumCommits, err = commit.CommitsCount()
  218. if err != nil {
  219. return fmt.Errorf("CommitsCount: %v", err)
  220. }
  221. for _, asset := range release.Assets {
  222. var attach = models.Attachment{
  223. UUID: gouuid.NewV4().String(),
  224. Name: asset.Name,
  225. DownloadCount: int64(*asset.DownloadCount),
  226. Size: int64(*asset.Size),
  227. CreatedUnix: timeutil.TimeStamp(asset.Created.Unix()),
  228. }
  229. // download attachment
  230. resp, err := http.Get(asset.URL)
  231. if err != nil {
  232. return err
  233. }
  234. defer resp.Body.Close()
  235. localPath := attach.LocalPath()
  236. if err = os.MkdirAll(path.Dir(localPath), os.ModePerm); err != nil {
  237. return fmt.Errorf("MkdirAll: %v", err)
  238. }
  239. fw, err := os.Create(localPath)
  240. if err != nil {
  241. return fmt.Errorf("Create: %v", err)
  242. }
  243. defer fw.Close()
  244. if _, err := io.Copy(fw, resp.Body); err != nil {
  245. return err
  246. }
  247. rel.Attachments = append(rel.Attachments, &attach)
  248. }
  249. rels = append(rels, &rel)
  250. }
  251. if err := models.InsertReleases(rels...); err != nil {
  252. return err
  253. }
  254. // sync tags to releases in database
  255. return models.SyncReleasesWithTags(g.repo, g.gitRepo)
  256. }
  257. // CreateIssues creates issues
  258. func (g *GiteaLocalUploader) CreateIssues(issues ...*base.Issue) error {
  259. var iss = make([]*models.Issue, 0, len(issues))
  260. for _, issue := range issues {
  261. var labels []*models.Label
  262. for _, label := range issue.Labels {
  263. lb, ok := g.labels.Load(label.Name)
  264. if ok {
  265. labels = append(labels, lb.(*models.Label))
  266. }
  267. }
  268. var milestoneID int64
  269. if issue.Milestone != "" {
  270. milestone, ok := g.milestones.Load(issue.Milestone)
  271. if ok {
  272. milestoneID = milestone.(int64)
  273. }
  274. }
  275. var is = models.Issue{
  276. RepoID: g.repo.ID,
  277. Repo: g.repo,
  278. Index: issue.Number,
  279. Title: issue.Title,
  280. Content: issue.Content,
  281. IsClosed: issue.State == "closed",
  282. IsLocked: issue.IsLocked,
  283. MilestoneID: milestoneID,
  284. Labels: labels,
  285. CreatedUnix: timeutil.TimeStamp(issue.Created.Unix()),
  286. }
  287. userid, ok := g.userMap[issue.PosterID]
  288. tp := g.gitServiceType.Name()
  289. if !ok && tp != "" {
  290. var err error
  291. userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", issue.PosterID))
  292. if err != nil {
  293. log.Error("GetUserIDByExternalUserID: %v", err)
  294. }
  295. if userid > 0 {
  296. g.userMap[issue.PosterID] = userid
  297. }
  298. }
  299. if userid > 0 {
  300. is.PosterID = userid
  301. } else {
  302. is.PosterID = g.doer.ID
  303. is.OriginalAuthor = issue.PosterName
  304. is.OriginalAuthorID = issue.PosterID
  305. }
  306. if issue.Closed != nil {
  307. is.ClosedUnix = timeutil.TimeStamp(issue.Closed.Unix())
  308. }
  309. // TODO: add reactions
  310. iss = append(iss, &is)
  311. }
  312. err := models.InsertIssues(iss...)
  313. if err != nil {
  314. return err
  315. }
  316. for _, is := range iss {
  317. g.issues.Store(is.Index, is.ID)
  318. }
  319. return nil
  320. }
  321. // CreateComments creates comments of issues
  322. func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error {
  323. var cms = make([]*models.Comment, 0, len(comments))
  324. for _, comment := range comments {
  325. var issueID int64
  326. if issueIDStr, ok := g.issues.Load(comment.IssueIndex); !ok {
  327. issue, err := models.GetIssueByIndex(g.repo.ID, comment.IssueIndex)
  328. if err != nil {
  329. return err
  330. }
  331. issueID = issue.ID
  332. g.issues.Store(comment.IssueIndex, issueID)
  333. } else {
  334. issueID = issueIDStr.(int64)
  335. }
  336. userid, ok := g.userMap[comment.PosterID]
  337. tp := g.gitServiceType.Name()
  338. if !ok && tp != "" {
  339. var err error
  340. userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", comment.PosterID))
  341. if err != nil {
  342. log.Error("GetUserIDByExternalUserID: %v", err)
  343. }
  344. if userid > 0 {
  345. g.userMap[comment.PosterID] = userid
  346. }
  347. }
  348. cm := models.Comment{
  349. IssueID: issueID,
  350. Type: models.CommentTypeComment,
  351. Content: comment.Content,
  352. CreatedUnix: timeutil.TimeStamp(comment.Created.Unix()),
  353. }
  354. if userid > 0 {
  355. cm.PosterID = userid
  356. } else {
  357. cm.PosterID = g.doer.ID
  358. cm.OriginalAuthor = comment.PosterName
  359. cm.OriginalAuthorID = comment.PosterID
  360. }
  361. cms = append(cms, &cm)
  362. // TODO: Reactions
  363. }
  364. return models.InsertIssueComments(cms)
  365. }
  366. // CreatePullRequests creates pull requests
  367. func (g *GiteaLocalUploader) CreatePullRequests(prs ...*base.PullRequest) error {
  368. var gprs = make([]*models.PullRequest, 0, len(prs))
  369. for _, pr := range prs {
  370. gpr, err := g.newPullRequest(pr)
  371. if err != nil {
  372. return err
  373. }
  374. userid, ok := g.userMap[pr.PosterID]
  375. tp := g.gitServiceType.Name()
  376. if !ok && tp != "" {
  377. var err error
  378. userid, err = models.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", pr.PosterID))
  379. if err != nil {
  380. log.Error("GetUserIDByExternalUserID: %v", err)
  381. }
  382. if userid > 0 {
  383. g.userMap[pr.PosterID] = userid
  384. }
  385. }
  386. if userid > 0 {
  387. gpr.Issue.PosterID = userid
  388. } else {
  389. gpr.Issue.PosterID = g.doer.ID
  390. gpr.Issue.OriginalAuthor = pr.PosterName
  391. gpr.Issue.OriginalAuthorID = pr.PosterID
  392. }
  393. gprs = append(gprs, gpr)
  394. }
  395. if err := models.InsertPullRequests(gprs...); err != nil {
  396. return err
  397. }
  398. for _, pr := range gprs {
  399. g.issues.Store(pr.Issue.Index, pr.Issue.ID)
  400. }
  401. return nil
  402. }
  403. func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullRequest, error) {
  404. var labels []*models.Label
  405. for _, label := range pr.Labels {
  406. lb, ok := g.labels.Load(label.Name)
  407. if ok {
  408. labels = append(labels, lb.(*models.Label))
  409. }
  410. }
  411. var milestoneID int64
  412. if pr.Milestone != "" {
  413. milestone, ok := g.milestones.Load(pr.Milestone)
  414. if ok {
  415. milestoneID = milestone.(int64)
  416. }
  417. }
  418. // download patch file
  419. resp, err := http.Get(pr.PatchURL)
  420. if err != nil {
  421. return nil, err
  422. }
  423. defer resp.Body.Close()
  424. pullDir := filepath.Join(g.repo.RepoPath(), "pulls")
  425. if err = os.MkdirAll(pullDir, os.ModePerm); err != nil {
  426. return nil, err
  427. }
  428. f, err := os.Create(filepath.Join(pullDir, fmt.Sprintf("%d.patch", pr.Number)))
  429. if err != nil {
  430. return nil, err
  431. }
  432. defer f.Close()
  433. _, err = io.Copy(f, resp.Body)
  434. if err != nil {
  435. return nil, err
  436. }
  437. // set head information
  438. pullHead := filepath.Join(g.repo.RepoPath(), "refs", "pull", fmt.Sprintf("%d", pr.Number))
  439. if err := os.MkdirAll(pullHead, os.ModePerm); err != nil {
  440. return nil, err
  441. }
  442. p, err := os.Create(filepath.Join(pullHead, "head"))
  443. if err != nil {
  444. return nil, err
  445. }
  446. defer p.Close()
  447. _, err = p.WriteString(pr.Head.SHA)
  448. if err != nil {
  449. return nil, err
  450. }
  451. var head = "unknown repository"
  452. if pr.IsForkPullRequest() && pr.State != "closed" {
  453. if pr.Head.OwnerName != "" {
  454. remote := pr.Head.OwnerName
  455. _, ok := g.prHeadCache[remote]
  456. if !ok {
  457. // git remote add
  458. err := g.gitRepo.AddRemote(remote, pr.Head.CloneURL, true)
  459. if err != nil {
  460. log.Error("AddRemote failed: %s", err)
  461. } else {
  462. g.prHeadCache[remote] = struct{}{}
  463. ok = true
  464. }
  465. }
  466. if ok {
  467. _, err = git.NewCommand("fetch", remote, pr.Head.Ref).RunInDir(g.repo.RepoPath())
  468. if err != nil {
  469. log.Error("Fetch branch from %s failed: %v", pr.Head.CloneURL, err)
  470. } else {
  471. headBranch := filepath.Join(g.repo.RepoPath(), "refs", "heads", pr.Head.OwnerName, pr.Head.Ref)
  472. if err := os.MkdirAll(filepath.Dir(headBranch), os.ModePerm); err != nil {
  473. return nil, err
  474. }
  475. b, err := os.Create(headBranch)
  476. if err != nil {
  477. return nil, err
  478. }
  479. defer b.Close()
  480. _, err = b.WriteString(pr.Head.SHA)
  481. if err != nil {
  482. return nil, err
  483. }
  484. head = pr.Head.OwnerName + "/" + pr.Head.Ref
  485. }
  486. }
  487. }
  488. } else {
  489. head = pr.Head.Ref
  490. }
  491. var issue = models.Issue{
  492. RepoID: g.repo.ID,
  493. Repo: g.repo,
  494. Title: pr.Title,
  495. Index: pr.Number,
  496. Content: pr.Content,
  497. MilestoneID: milestoneID,
  498. IsPull: true,
  499. IsClosed: pr.State == "closed",
  500. IsLocked: pr.IsLocked,
  501. Labels: labels,
  502. CreatedUnix: timeutil.TimeStamp(pr.Created.Unix()),
  503. }
  504. userid, ok := g.userMap[pr.PosterID]
  505. if !ok {
  506. var err error
  507. userid, err = models.GetUserIDByExternalUserID("github", fmt.Sprintf("%v", pr.PosterID))
  508. if err != nil {
  509. log.Error("GetUserIDByExternalUserID: %v", err)
  510. }
  511. if userid > 0 {
  512. g.userMap[pr.PosterID] = userid
  513. }
  514. }
  515. if userid > 0 {
  516. issue.PosterID = userid
  517. } else {
  518. issue.PosterID = g.doer.ID
  519. issue.OriginalAuthor = pr.PosterName
  520. issue.OriginalAuthorID = pr.PosterID
  521. }
  522. var pullRequest = models.PullRequest{
  523. HeadRepoID: g.repo.ID,
  524. HeadBranch: head,
  525. BaseRepoID: g.repo.ID,
  526. BaseBranch: pr.Base.Ref,
  527. MergeBase: pr.Base.SHA,
  528. Index: pr.Number,
  529. HasMerged: pr.Merged,
  530. Issue: &issue,
  531. }
  532. if pullRequest.Issue.IsClosed && pr.Closed != nil {
  533. pullRequest.Issue.ClosedUnix = timeutil.TimeStamp(pr.Closed.Unix())
  534. }
  535. if pullRequest.HasMerged && pr.MergedTime != nil {
  536. pullRequest.MergedUnix = timeutil.TimeStamp(pr.MergedTime.Unix())
  537. pullRequest.MergedCommitID = pr.MergeCommitSHA
  538. pullRequest.MergerID = g.doer.ID
  539. }
  540. // TODO: reactions
  541. // TODO: assignees
  542. return &pullRequest, nil
  543. }
  544. // Rollback when migrating failed, this will rollback all the changes.
  545. func (g *GiteaLocalUploader) Rollback() error {
  546. if g.repo != nil && g.repo.ID > 0 {
  547. if err := models.DeleteRepository(g.doer, g.repo.OwnerID, g.repo.ID); err != nil {
  548. return err
  549. }
  550. }
  551. return nil
  552. }
上海开阖软件有限公司 沪ICP备12045867号-1