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 13KB

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