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

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