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

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