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.go 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  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. "os"
  11. "path"
  12. "path/filepath"
  13. "strings"
  14. "sync"
  15. "time"
  16. "code.gitea.io/gitea/models"
  17. "code.gitea.io/gitea/modules/git"
  18. "code.gitea.io/gitea/modules/log"
  19. "code.gitea.io/gitea/modules/migrations/base"
  20. "code.gitea.io/gitea/modules/setting"
  21. "code.gitea.io/gitea/modules/util"
  22. gouuid "github.com/satori/go.uuid"
  23. )
  24. var (
  25. _ base.Uploader = &GiteaLocalUploader{}
  26. )
  27. // GiteaLocalUploader implements an Uploader to gitea sites
  28. type GiteaLocalUploader struct {
  29. doer *models.User
  30. repoOwner string
  31. repoName string
  32. repo *models.Repository
  33. labels sync.Map
  34. milestones sync.Map
  35. issues sync.Map
  36. gitRepo *git.Repository
  37. prHeadCache map[string]struct{}
  38. }
  39. // NewGiteaLocalUploader creates an gitea Uploader via gitea API v1
  40. func NewGiteaLocalUploader(doer *models.User, repoOwner, repoName string) *GiteaLocalUploader {
  41. return &GiteaLocalUploader{
  42. doer: doer,
  43. repoOwner: repoOwner,
  44. repoName: repoName,
  45. prHeadCache: make(map[string]struct{}),
  46. }
  47. }
  48. // MaxBatchInsertSize returns the table's max batch insert size
  49. func (g *GiteaLocalUploader) MaxBatchInsertSize(tp string) int {
  50. switch tp {
  51. case "issue":
  52. return models.MaxBatchInsertSize(new(models.Issue))
  53. case "comment":
  54. return models.MaxBatchInsertSize(new(models.Comment))
  55. case "milestone":
  56. return models.MaxBatchInsertSize(new(models.Milestone))
  57. case "label":
  58. return models.MaxBatchInsertSize(new(models.Label))
  59. case "release":
  60. return models.MaxBatchInsertSize(new(models.Release))
  61. case "pullrequest":
  62. return models.MaxBatchInsertSize(new(models.PullRequest))
  63. }
  64. return 10
  65. }
  66. // CreateRepo creates a repository
  67. func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.MigrateOptions) error {
  68. owner, err := models.GetUserByName(g.repoOwner)
  69. if err != nil {
  70. return err
  71. }
  72. r, err := models.MigrateRepository(g.doer, owner, models.MigrateRepoOptions{
  73. Name: g.repoName,
  74. Description: repo.Description,
  75. OriginalURL: repo.OriginalURL,
  76. IsMirror: repo.IsMirror,
  77. RemoteAddr: repo.CloneURL,
  78. IsPrivate: repo.IsPrivate,
  79. Wiki: opts.Wiki,
  80. SyncReleasesWithTags: !opts.Releases, // if didn't get releases, then sync them from tags
  81. })
  82. g.repo = r
  83. if err != nil {
  84. return err
  85. }
  86. g.gitRepo, err = git.OpenRepository(r.RepoPath())
  87. return err
  88. }
  89. // CreateMilestones creates milestones
  90. func (g *GiteaLocalUploader) CreateMilestones(milestones ...*base.Milestone) error {
  91. var mss = make([]*models.Milestone, 0, len(milestones))
  92. for _, milestone := range milestones {
  93. var deadline util.TimeStamp
  94. if milestone.Deadline != nil {
  95. deadline = util.TimeStamp(milestone.Deadline.Unix())
  96. }
  97. if deadline == 0 {
  98. deadline = util.TimeStamp(time.Date(9999, 1, 1, 0, 0, 0, 0, setting.UILocation).Unix())
  99. }
  100. var ms = models.Milestone{
  101. RepoID: g.repo.ID,
  102. Name: milestone.Title,
  103. Content: milestone.Description,
  104. IsClosed: milestone.State == "close",
  105. DeadlineUnix: deadline,
  106. }
  107. if ms.IsClosed && milestone.Closed != nil {
  108. ms.ClosedDateUnix = util.TimeStamp(milestone.Closed.Unix())
  109. }
  110. mss = append(mss, &ms)
  111. }
  112. err := models.InsertMilestones(mss...)
  113. if err != nil {
  114. return err
  115. }
  116. for _, ms := range mss {
  117. g.milestones.Store(ms.Name, ms.ID)
  118. }
  119. return nil
  120. }
  121. // CreateLabels creates labels
  122. func (g *GiteaLocalUploader) CreateLabels(labels ...*base.Label) error {
  123. var lbs = make([]*models.Label, 0, len(labels))
  124. for _, label := range labels {
  125. lbs = append(lbs, &models.Label{
  126. RepoID: g.repo.ID,
  127. Name: label.Name,
  128. Description: label.Description,
  129. Color: fmt.Sprintf("#%s", label.Color),
  130. })
  131. }
  132. err := models.NewLabels(lbs...)
  133. if err != nil {
  134. return err
  135. }
  136. for _, lb := range lbs {
  137. g.labels.Store(lb.Name, lb)
  138. }
  139. return nil
  140. }
  141. // CreateReleases creates releases
  142. func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
  143. var rels = make([]*models.Release, 0, len(releases))
  144. for _, release := range releases {
  145. var rel = models.Release{
  146. RepoID: g.repo.ID,
  147. PublisherID: g.doer.ID,
  148. TagName: release.TagName,
  149. LowerTagName: strings.ToLower(release.TagName),
  150. Target: release.TargetCommitish,
  151. Title: release.Name,
  152. Sha1: release.TargetCommitish,
  153. Note: release.Body,
  154. IsDraft: release.Draft,
  155. IsPrerelease: release.Prerelease,
  156. IsTag: false,
  157. CreatedUnix: util.TimeStamp(release.Created.Unix()),
  158. }
  159. // calc NumCommits
  160. commit, err := g.gitRepo.GetCommit(rel.TagName)
  161. if err != nil {
  162. return fmt.Errorf("GetCommit: %v", err)
  163. }
  164. rel.NumCommits, err = commit.CommitsCount()
  165. if err != nil {
  166. return fmt.Errorf("CommitsCount: %v", err)
  167. }
  168. for _, asset := range release.Assets {
  169. var attach = models.Attachment{
  170. UUID: gouuid.NewV4().String(),
  171. Name: asset.Name,
  172. DownloadCount: int64(*asset.DownloadCount),
  173. Size: int64(*asset.Size),
  174. CreatedUnix: util.TimeStamp(asset.Created.Unix()),
  175. }
  176. // download attachment
  177. resp, err := http.Get(asset.URL)
  178. if err != nil {
  179. return err
  180. }
  181. defer resp.Body.Close()
  182. localPath := attach.LocalPath()
  183. if err = os.MkdirAll(path.Dir(localPath), os.ModePerm); err != nil {
  184. return fmt.Errorf("MkdirAll: %v", err)
  185. }
  186. fw, err := os.Create(localPath)
  187. if err != nil {
  188. return fmt.Errorf("Create: %v", err)
  189. }
  190. defer fw.Close()
  191. if _, err := io.Copy(fw, resp.Body); err != nil {
  192. return err
  193. }
  194. rel.Attachments = append(rel.Attachments, &attach)
  195. }
  196. rels = append(rels, &rel)
  197. }
  198. if err := models.InsertReleases(rels...); err != nil {
  199. return err
  200. }
  201. // sync tags to releases in database
  202. return models.SyncReleasesWithTags(g.repo, g.gitRepo)
  203. }
  204. // CreateIssues creates issues
  205. func (g *GiteaLocalUploader) CreateIssues(issues ...*base.Issue) error {
  206. var iss = make([]*models.Issue, 0, len(issues))
  207. for _, issue := range issues {
  208. var labels []*models.Label
  209. for _, label := range issue.Labels {
  210. lb, ok := g.labels.Load(label.Name)
  211. if ok {
  212. labels = append(labels, lb.(*models.Label))
  213. }
  214. }
  215. var milestoneID int64
  216. if issue.Milestone != "" {
  217. milestone, ok := g.milestones.Load(issue.Milestone)
  218. if ok {
  219. milestoneID = milestone.(int64)
  220. }
  221. }
  222. var is = models.Issue{
  223. RepoID: g.repo.ID,
  224. Repo: g.repo,
  225. Index: issue.Number,
  226. PosterID: g.doer.ID,
  227. OriginalAuthor: issue.PosterName,
  228. OriginalAuthorID: issue.PosterID,
  229. Title: issue.Title,
  230. Content: issue.Content,
  231. IsClosed: issue.State == "closed",
  232. IsLocked: issue.IsLocked,
  233. MilestoneID: milestoneID,
  234. Labels: labels,
  235. CreatedUnix: util.TimeStamp(issue.Created.Unix()),
  236. }
  237. if issue.Closed != nil {
  238. is.ClosedUnix = util.TimeStamp(issue.Closed.Unix())
  239. }
  240. // TODO: add reactions
  241. iss = append(iss, &is)
  242. }
  243. err := models.InsertIssues(iss...)
  244. if err != nil {
  245. return err
  246. }
  247. for _, is := range iss {
  248. g.issues.Store(is.Index, is.ID)
  249. }
  250. return nil
  251. }
  252. // CreateComments creates comments of issues
  253. func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error {
  254. var cms = make([]*models.Comment, 0, len(comments))
  255. for _, comment := range comments {
  256. var issueID int64
  257. if issueIDStr, ok := g.issues.Load(comment.IssueIndex); !ok {
  258. issue, err := models.GetIssueByIndex(g.repo.ID, comment.IssueIndex)
  259. if err != nil {
  260. return err
  261. }
  262. issueID = issue.ID
  263. g.issues.Store(comment.IssueIndex, issueID)
  264. } else {
  265. issueID = issueIDStr.(int64)
  266. }
  267. cms = append(cms, &models.Comment{
  268. IssueID: issueID,
  269. Type: models.CommentTypeComment,
  270. PosterID: g.doer.ID,
  271. OriginalAuthor: comment.PosterName,
  272. OriginalAuthorID: comment.PosterID,
  273. Content: comment.Content,
  274. CreatedUnix: util.TimeStamp(comment.Created.Unix()),
  275. })
  276. // TODO: Reactions
  277. }
  278. return models.InsertIssueComments(cms)
  279. }
  280. // CreatePullRequests creates pull requests
  281. func (g *GiteaLocalUploader) CreatePullRequests(prs ...*base.PullRequest) error {
  282. var gprs = make([]*models.PullRequest, 0, len(prs))
  283. for _, pr := range prs {
  284. gpr, err := g.newPullRequest(pr)
  285. if err != nil {
  286. return err
  287. }
  288. gprs = append(gprs, gpr)
  289. }
  290. if err := models.InsertPullRequests(gprs...); err != nil {
  291. return err
  292. }
  293. for _, pr := range gprs {
  294. g.issues.Store(pr.Issue.Index, pr.Issue.ID)
  295. }
  296. return nil
  297. }
  298. func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullRequest, error) {
  299. var labels []*models.Label
  300. for _, label := range pr.Labels {
  301. lb, ok := g.labels.Load(label.Name)
  302. if ok {
  303. labels = append(labels, lb.(*models.Label))
  304. }
  305. }
  306. var milestoneID int64
  307. if pr.Milestone != "" {
  308. milestone, ok := g.milestones.Load(pr.Milestone)
  309. if ok {
  310. milestoneID = milestone.(int64)
  311. }
  312. }
  313. // download patch file
  314. resp, err := http.Get(pr.PatchURL)
  315. if err != nil {
  316. return nil, err
  317. }
  318. defer resp.Body.Close()
  319. pullDir := filepath.Join(g.repo.RepoPath(), "pulls")
  320. if err = os.MkdirAll(pullDir, os.ModePerm); err != nil {
  321. return nil, err
  322. }
  323. f, err := os.Create(filepath.Join(pullDir, fmt.Sprintf("%d.patch", pr.Number)))
  324. if err != nil {
  325. return nil, err
  326. }
  327. defer f.Close()
  328. _, err = io.Copy(f, resp.Body)
  329. if err != nil {
  330. return nil, err
  331. }
  332. // set head information
  333. pullHead := filepath.Join(g.repo.RepoPath(), "refs", "pull", fmt.Sprintf("%d", pr.Number))
  334. if err := os.MkdirAll(pullHead, os.ModePerm); err != nil {
  335. return nil, err
  336. }
  337. p, err := os.Create(filepath.Join(pullHead, "head"))
  338. if err != nil {
  339. return nil, err
  340. }
  341. defer p.Close()
  342. _, err = p.WriteString(pr.Head.SHA)
  343. if err != nil {
  344. return nil, err
  345. }
  346. var head = "unknown repository"
  347. if pr.IsForkPullRequest() && pr.State != "closed" {
  348. if pr.Head.OwnerName != "" {
  349. remote := pr.Head.OwnerName
  350. _, ok := g.prHeadCache[remote]
  351. if !ok {
  352. // git remote add
  353. err := g.gitRepo.AddRemote(remote, pr.Head.CloneURL, true)
  354. if err != nil {
  355. log.Error("AddRemote failed: %s", err)
  356. } else {
  357. g.prHeadCache[remote] = struct{}{}
  358. ok = true
  359. }
  360. }
  361. if ok {
  362. _, err = git.NewCommand("fetch", remote, pr.Head.Ref).RunInDir(g.repo.RepoPath())
  363. if err != nil {
  364. log.Error("Fetch branch from %s failed: %v", pr.Head.CloneURL, err)
  365. } else {
  366. headBranch := filepath.Join(g.repo.RepoPath(), "refs", "heads", pr.Head.OwnerName, pr.Head.Ref)
  367. if err := os.MkdirAll(filepath.Dir(headBranch), os.ModePerm); err != nil {
  368. return nil, err
  369. }
  370. b, err := os.Create(headBranch)
  371. if err != nil {
  372. return nil, err
  373. }
  374. defer b.Close()
  375. _, err = b.WriteString(pr.Head.SHA)
  376. if err != nil {
  377. return nil, err
  378. }
  379. head = pr.Head.OwnerName + "/" + pr.Head.Ref
  380. }
  381. }
  382. }
  383. } else {
  384. head = pr.Head.Ref
  385. }
  386. var pullRequest = models.PullRequest{
  387. HeadRepoID: g.repo.ID,
  388. HeadBranch: head,
  389. HeadUserName: g.repoOwner,
  390. BaseRepoID: g.repo.ID,
  391. BaseBranch: pr.Base.Ref,
  392. MergeBase: pr.Base.SHA,
  393. Index: pr.Number,
  394. HasMerged: pr.Merged,
  395. Issue: &models.Issue{
  396. RepoID: g.repo.ID,
  397. Repo: g.repo,
  398. Title: pr.Title,
  399. Index: pr.Number,
  400. PosterID: g.doer.ID,
  401. OriginalAuthor: pr.PosterName,
  402. OriginalAuthorID: pr.PosterID,
  403. Content: pr.Content,
  404. MilestoneID: milestoneID,
  405. IsPull: true,
  406. IsClosed: pr.State == "closed",
  407. IsLocked: pr.IsLocked,
  408. Labels: labels,
  409. CreatedUnix: util.TimeStamp(pr.Created.Unix()),
  410. },
  411. }
  412. if pullRequest.Issue.IsClosed && pr.Closed != nil {
  413. pullRequest.Issue.ClosedUnix = util.TimeStamp(pr.Closed.Unix())
  414. }
  415. if pullRequest.HasMerged && pr.MergedTime != nil {
  416. pullRequest.MergedUnix = util.TimeStamp(pr.MergedTime.Unix())
  417. pullRequest.MergedCommitID = pr.MergeCommitSHA
  418. pullRequest.MergerID = g.doer.ID
  419. }
  420. // TODO: reactions
  421. // TODO: assignees
  422. return &pullRequest, nil
  423. }
  424. // Rollback when migrating failed, this will rollback all the changes.
  425. func (g *GiteaLocalUploader) Rollback() error {
  426. if g.repo != nil && g.repo.ID > 0 {
  427. if err := models.DeleteRepository(g.doer, g.repo.OwnerID, g.repo.ID); err != nil {
  428. return err
  429. }
  430. }
  431. return nil
  432. }