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_uploader.go 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984
  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. "context"
  8. "fmt"
  9. "io"
  10. "os"
  11. "path/filepath"
  12. "strings"
  13. "sync"
  14. "time"
  15. "code.gitea.io/gitea/models"
  16. "code.gitea.io/gitea/models/db"
  17. repo_model "code.gitea.io/gitea/models/repo"
  18. user_model "code.gitea.io/gitea/models/user"
  19. "code.gitea.io/gitea/modules/git"
  20. "code.gitea.io/gitea/modules/log"
  21. base "code.gitea.io/gitea/modules/migration"
  22. repo_module "code.gitea.io/gitea/modules/repository"
  23. "code.gitea.io/gitea/modules/setting"
  24. "code.gitea.io/gitea/modules/storage"
  25. "code.gitea.io/gitea/modules/structs"
  26. "code.gitea.io/gitea/modules/timeutil"
  27. "code.gitea.io/gitea/modules/uri"
  28. "code.gitea.io/gitea/services/pull"
  29. gouuid "github.com/google/uuid"
  30. )
  31. var (
  32. _ base.Uploader = &GiteaLocalUploader{}
  33. )
  34. // GiteaLocalUploader implements an Uploader to gitea sites
  35. type GiteaLocalUploader struct {
  36. ctx context.Context
  37. doer *user_model.User
  38. repoOwner string
  39. repoName string
  40. repo *models.Repository
  41. labels sync.Map
  42. milestones sync.Map
  43. issues sync.Map
  44. gitRepo *git.Repository
  45. prHeadCache map[string]struct{}
  46. userMap map[int64]int64 // external user id mapping to user id
  47. prCache map[int64]*models.PullRequest
  48. gitServiceType structs.GitServiceType
  49. }
  50. // NewGiteaLocalUploader creates an gitea Uploader via gitea API v1
  51. func NewGiteaLocalUploader(ctx context.Context, doer *user_model.User, repoOwner, repoName string) *GiteaLocalUploader {
  52. return &GiteaLocalUploader{
  53. ctx: ctx,
  54. doer: doer,
  55. repoOwner: repoOwner,
  56. repoName: repoName,
  57. prHeadCache: make(map[string]struct{}),
  58. userMap: make(map[int64]int64),
  59. prCache: make(map[int64]*models.PullRequest),
  60. }
  61. }
  62. // MaxBatchInsertSize returns the table's max batch insert size
  63. func (g *GiteaLocalUploader) MaxBatchInsertSize(tp string) int {
  64. switch tp {
  65. case "issue":
  66. return db.MaxBatchInsertSize(new(models.Issue))
  67. case "comment":
  68. return db.MaxBatchInsertSize(new(models.Comment))
  69. case "milestone":
  70. return db.MaxBatchInsertSize(new(models.Milestone))
  71. case "label":
  72. return db.MaxBatchInsertSize(new(models.Label))
  73. case "release":
  74. return db.MaxBatchInsertSize(new(models.Release))
  75. case "pullrequest":
  76. return db.MaxBatchInsertSize(new(models.PullRequest))
  77. }
  78. return 10
  79. }
  80. // CreateRepo creates a repository
  81. func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.MigrateOptions) error {
  82. owner, err := user_model.GetUserByName(g.repoOwner)
  83. if err != nil {
  84. return err
  85. }
  86. var r *models.Repository
  87. if opts.MigrateToRepoID <= 0 {
  88. r, err = repo_module.CreateRepository(g.doer, owner, models.CreateRepoOptions{
  89. Name: g.repoName,
  90. Description: repo.Description,
  91. OriginalURL: repo.OriginalURL,
  92. GitServiceType: opts.GitServiceType,
  93. IsPrivate: opts.Private,
  94. IsMirror: opts.Mirror,
  95. Status: models.RepositoryBeingMigrated,
  96. })
  97. } else {
  98. r, err = models.GetRepositoryByID(opts.MigrateToRepoID)
  99. }
  100. if err != nil {
  101. return err
  102. }
  103. r.DefaultBranch = repo.DefaultBranch
  104. r.Description = repo.Description
  105. r, err = repo_module.MigrateRepositoryGitData(g.ctx, owner, r, base.MigrateOptions{
  106. RepoName: g.repoName,
  107. Description: repo.Description,
  108. OriginalURL: repo.OriginalURL,
  109. GitServiceType: opts.GitServiceType,
  110. Mirror: repo.IsMirror,
  111. LFS: opts.LFS,
  112. LFSEndpoint: opts.LFSEndpoint,
  113. CloneAddr: repo.CloneURL,
  114. Private: repo.IsPrivate,
  115. Wiki: opts.Wiki,
  116. Releases: opts.Releases, // if didn't get releases, then sync them from tags
  117. MirrorInterval: opts.MirrorInterval,
  118. }, NewMigrationHTTPTransport())
  119. g.repo = r
  120. if err != nil {
  121. return err
  122. }
  123. g.gitRepo, err = git.OpenRepository(r.RepoPath())
  124. return err
  125. }
  126. // Close closes this uploader
  127. func (g *GiteaLocalUploader) Close() {
  128. if g.gitRepo != nil {
  129. g.gitRepo.Close()
  130. }
  131. }
  132. // CreateTopics creates topics
  133. func (g *GiteaLocalUploader) CreateTopics(topics ...string) error {
  134. // ignore topics to long for the db
  135. c := 0
  136. for i := range topics {
  137. if len(topics[i]) <= 50 {
  138. topics[c] = topics[i]
  139. c++
  140. }
  141. }
  142. topics = topics[:c]
  143. return models.SaveTopics(g.repo.ID, topics...)
  144. }
  145. // CreateMilestones creates milestones
  146. func (g *GiteaLocalUploader) CreateMilestones(milestones ...*base.Milestone) error {
  147. var mss = make([]*models.Milestone, 0, len(milestones))
  148. for _, milestone := range milestones {
  149. var deadline timeutil.TimeStamp
  150. if milestone.Deadline != nil {
  151. deadline = timeutil.TimeStamp(milestone.Deadline.Unix())
  152. }
  153. if deadline == 0 {
  154. deadline = timeutil.TimeStamp(time.Date(9999, 1, 1, 0, 0, 0, 0, setting.DefaultUILocation).Unix())
  155. }
  156. if milestone.Created.IsZero() {
  157. if milestone.Updated != nil {
  158. milestone.Created = *milestone.Updated
  159. } else if milestone.Deadline != nil {
  160. milestone.Created = *milestone.Deadline
  161. } else {
  162. milestone.Created = time.Now()
  163. }
  164. }
  165. if milestone.Updated == nil || milestone.Updated.IsZero() {
  166. milestone.Updated = &milestone.Created
  167. }
  168. var ms = models.Milestone{
  169. RepoID: g.repo.ID,
  170. Name: milestone.Title,
  171. Content: milestone.Description,
  172. IsClosed: milestone.State == "closed",
  173. CreatedUnix: timeutil.TimeStamp(milestone.Created.Unix()),
  174. UpdatedUnix: timeutil.TimeStamp(milestone.Updated.Unix()),
  175. DeadlineUnix: deadline,
  176. }
  177. if ms.IsClosed && milestone.Closed != nil {
  178. ms.ClosedDateUnix = timeutil.TimeStamp(milestone.Closed.Unix())
  179. }
  180. mss = append(mss, &ms)
  181. }
  182. err := models.InsertMilestones(mss...)
  183. if err != nil {
  184. return err
  185. }
  186. for _, ms := range mss {
  187. g.milestones.Store(ms.Name, ms.ID)
  188. }
  189. return nil
  190. }
  191. // CreateLabels creates labels
  192. func (g *GiteaLocalUploader) CreateLabels(labels ...*base.Label) error {
  193. var lbs = make([]*models.Label, 0, len(labels))
  194. for _, label := range labels {
  195. lbs = append(lbs, &models.Label{
  196. RepoID: g.repo.ID,
  197. Name: label.Name,
  198. Description: label.Description,
  199. Color: fmt.Sprintf("#%s", label.Color),
  200. })
  201. }
  202. err := models.NewLabels(lbs...)
  203. if err != nil {
  204. return err
  205. }
  206. for _, lb := range lbs {
  207. g.labels.Store(lb.Name, lb)
  208. }
  209. return nil
  210. }
  211. // CreateReleases creates releases
  212. func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
  213. var rels = make([]*models.Release, 0, len(releases))
  214. for _, release := range releases {
  215. if release.Created.IsZero() {
  216. if !release.Published.IsZero() {
  217. release.Created = release.Published
  218. } else {
  219. release.Created = time.Now()
  220. }
  221. }
  222. var rel = models.Release{
  223. RepoID: g.repo.ID,
  224. TagName: release.TagName,
  225. LowerTagName: strings.ToLower(release.TagName),
  226. Target: release.TargetCommitish,
  227. Title: release.Name,
  228. Sha1: release.TargetCommitish,
  229. Note: release.Body,
  230. IsDraft: release.Draft,
  231. IsPrerelease: release.Prerelease,
  232. IsTag: false,
  233. CreatedUnix: timeutil.TimeStamp(release.Created.Unix()),
  234. }
  235. userid, ok := g.userMap[release.PublisherID]
  236. tp := g.gitServiceType.Name()
  237. if !ok && tp != "" {
  238. var err error
  239. userid, err = user_model.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", release.PublisherID))
  240. if err != nil {
  241. log.Error("GetUserIDByExternalUserID: %v", err)
  242. }
  243. if userid > 0 {
  244. g.userMap[release.PublisherID] = userid
  245. }
  246. }
  247. if userid > 0 {
  248. rel.PublisherID = userid
  249. } else {
  250. rel.PublisherID = g.doer.ID
  251. rel.OriginalAuthor = release.PublisherName
  252. rel.OriginalAuthorID = release.PublisherID
  253. }
  254. // calc NumCommits if no draft
  255. if !release.Draft {
  256. commit, err := g.gitRepo.GetTagCommit(rel.TagName)
  257. if err != nil {
  258. return fmt.Errorf("GetTagCommit[%v]: %v", rel.TagName, err)
  259. }
  260. rel.NumCommits, err = commit.CommitsCount()
  261. if err != nil {
  262. return fmt.Errorf("CommitsCount: %v", err)
  263. }
  264. }
  265. for _, asset := range release.Assets {
  266. if asset.Created.IsZero() {
  267. if !asset.Updated.IsZero() {
  268. asset.Created = asset.Updated
  269. } else {
  270. asset.Created = release.Created
  271. }
  272. }
  273. var attach = repo_model.Attachment{
  274. UUID: gouuid.New().String(),
  275. Name: asset.Name,
  276. DownloadCount: int64(*asset.DownloadCount),
  277. Size: int64(*asset.Size),
  278. CreatedUnix: timeutil.TimeStamp(asset.Created.Unix()),
  279. }
  280. // download attachment
  281. err := func() error {
  282. // asset.DownloadURL maybe a local file
  283. var rc io.ReadCloser
  284. var err error
  285. if asset.DownloadFunc != nil {
  286. rc, err = asset.DownloadFunc()
  287. if err != nil {
  288. return err
  289. }
  290. } else if asset.DownloadURL != nil {
  291. rc, err = uri.Open(*asset.DownloadURL)
  292. if err != nil {
  293. return err
  294. }
  295. }
  296. if rc == nil {
  297. return nil
  298. }
  299. _, err = storage.Attachments.Save(attach.RelativePath(), rc, int64(*asset.Size))
  300. rc.Close()
  301. return err
  302. }()
  303. if err != nil {
  304. return err
  305. }
  306. rel.Attachments = append(rel.Attachments, &attach)
  307. }
  308. rels = append(rels, &rel)
  309. }
  310. return models.InsertReleases(rels...)
  311. }
  312. // SyncTags syncs releases with tags in the database
  313. func (g *GiteaLocalUploader) SyncTags() error {
  314. return repo_module.SyncReleasesWithTags(g.repo, g.gitRepo)
  315. }
  316. // CreateIssues creates issues
  317. func (g *GiteaLocalUploader) CreateIssues(issues ...*base.Issue) error {
  318. var iss = make([]*models.Issue, 0, len(issues))
  319. for _, issue := range issues {
  320. var labels []*models.Label
  321. for _, label := range issue.Labels {
  322. lb, ok := g.labels.Load(label.Name)
  323. if ok {
  324. labels = append(labels, lb.(*models.Label))
  325. }
  326. }
  327. var milestoneID int64
  328. if issue.Milestone != "" {
  329. milestone, ok := g.milestones.Load(issue.Milestone)
  330. if ok {
  331. milestoneID = milestone.(int64)
  332. }
  333. }
  334. if issue.Created.IsZero() {
  335. if issue.Closed != nil {
  336. issue.Created = *issue.Closed
  337. } else {
  338. issue.Created = time.Now()
  339. }
  340. }
  341. if issue.Updated.IsZero() {
  342. if issue.Closed != nil {
  343. issue.Updated = *issue.Closed
  344. } else {
  345. issue.Updated = time.Now()
  346. }
  347. }
  348. var is = models.Issue{
  349. RepoID: g.repo.ID,
  350. Repo: g.repo,
  351. Index: issue.Number,
  352. Title: issue.Title,
  353. Content: issue.Content,
  354. Ref: issue.Ref,
  355. IsClosed: issue.State == "closed",
  356. IsLocked: issue.IsLocked,
  357. MilestoneID: milestoneID,
  358. Labels: labels,
  359. CreatedUnix: timeutil.TimeStamp(issue.Created.Unix()),
  360. UpdatedUnix: timeutil.TimeStamp(issue.Updated.Unix()),
  361. }
  362. userid, ok := g.userMap[issue.PosterID]
  363. tp := g.gitServiceType.Name()
  364. if !ok && tp != "" {
  365. var err error
  366. userid, err = user_model.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", issue.PosterID))
  367. if err != nil {
  368. log.Error("GetUserIDByExternalUserID: %v", err)
  369. }
  370. if userid > 0 {
  371. g.userMap[issue.PosterID] = userid
  372. }
  373. }
  374. if userid > 0 {
  375. is.PosterID = userid
  376. } else {
  377. is.PosterID = g.doer.ID
  378. is.OriginalAuthor = issue.PosterName
  379. is.OriginalAuthorID = issue.PosterID
  380. }
  381. if issue.Closed != nil {
  382. is.ClosedUnix = timeutil.TimeStamp(issue.Closed.Unix())
  383. }
  384. // add reactions
  385. for _, reaction := range issue.Reactions {
  386. userid, ok := g.userMap[reaction.UserID]
  387. if !ok && tp != "" {
  388. var err error
  389. userid, err = user_model.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", reaction.UserID))
  390. if err != nil {
  391. log.Error("GetUserIDByExternalUserID: %v", err)
  392. }
  393. if userid > 0 {
  394. g.userMap[reaction.UserID] = userid
  395. }
  396. }
  397. var res = models.Reaction{
  398. Type: reaction.Content,
  399. CreatedUnix: timeutil.TimeStampNow(),
  400. }
  401. if userid > 0 {
  402. res.UserID = userid
  403. } else {
  404. res.UserID = g.doer.ID
  405. res.OriginalAuthorID = reaction.UserID
  406. res.OriginalAuthor = reaction.UserName
  407. }
  408. is.Reactions = append(is.Reactions, &res)
  409. }
  410. iss = append(iss, &is)
  411. }
  412. if len(iss) > 0 {
  413. if err := models.InsertIssues(iss...); err != nil {
  414. return err
  415. }
  416. for _, is := range iss {
  417. g.issues.Store(is.Index, is)
  418. }
  419. }
  420. return nil
  421. }
  422. // CreateComments creates comments of issues
  423. func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error {
  424. var cms = make([]*models.Comment, 0, len(comments))
  425. for _, comment := range comments {
  426. var issue *models.Issue
  427. issueInter, ok := g.issues.Load(comment.IssueIndex)
  428. if !ok {
  429. var err error
  430. issue, err = models.GetIssueByIndex(g.repo.ID, comment.IssueIndex)
  431. if err != nil {
  432. return err
  433. }
  434. g.issues.Store(comment.IssueIndex, issue)
  435. } else {
  436. issue = issueInter.(*models.Issue)
  437. }
  438. userid, ok := g.userMap[comment.PosterID]
  439. tp := g.gitServiceType.Name()
  440. if !ok && tp != "" {
  441. var err error
  442. userid, err = user_model.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", comment.PosterID))
  443. if err != nil {
  444. log.Error("GetUserIDByExternalUserID: %v", err)
  445. }
  446. if userid > 0 {
  447. g.userMap[comment.PosterID] = userid
  448. }
  449. }
  450. if comment.Created.IsZero() {
  451. comment.Created = time.Unix(int64(issue.CreatedUnix), 0)
  452. }
  453. if comment.Updated.IsZero() {
  454. comment.Updated = comment.Created
  455. }
  456. cm := models.Comment{
  457. IssueID: issue.ID,
  458. Type: models.CommentTypeComment,
  459. Content: comment.Content,
  460. CreatedUnix: timeutil.TimeStamp(comment.Created.Unix()),
  461. UpdatedUnix: timeutil.TimeStamp(comment.Updated.Unix()),
  462. }
  463. if userid > 0 {
  464. cm.PosterID = userid
  465. } else {
  466. cm.PosterID = g.doer.ID
  467. cm.OriginalAuthor = comment.PosterName
  468. cm.OriginalAuthorID = comment.PosterID
  469. }
  470. // add reactions
  471. for _, reaction := range comment.Reactions {
  472. userid, ok := g.userMap[reaction.UserID]
  473. if !ok && tp != "" {
  474. var err error
  475. userid, err = user_model.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", reaction.UserID))
  476. if err != nil {
  477. log.Error("GetUserIDByExternalUserID: %v", err)
  478. }
  479. if userid > 0 {
  480. g.userMap[reaction.UserID] = userid
  481. }
  482. }
  483. var res = models.Reaction{
  484. Type: reaction.Content,
  485. CreatedUnix: timeutil.TimeStampNow(),
  486. }
  487. if userid > 0 {
  488. res.UserID = userid
  489. } else {
  490. res.UserID = g.doer.ID
  491. res.OriginalAuthorID = reaction.UserID
  492. res.OriginalAuthor = reaction.UserName
  493. }
  494. cm.Reactions = append(cm.Reactions, &res)
  495. }
  496. cms = append(cms, &cm)
  497. }
  498. if len(cms) == 0 {
  499. return nil
  500. }
  501. return models.InsertIssueComments(cms)
  502. }
  503. // CreatePullRequests creates pull requests
  504. func (g *GiteaLocalUploader) CreatePullRequests(prs ...*base.PullRequest) error {
  505. var gprs = make([]*models.PullRequest, 0, len(prs))
  506. for _, pr := range prs {
  507. gpr, err := g.newPullRequest(pr)
  508. if err != nil {
  509. return err
  510. }
  511. userid, ok := g.userMap[pr.PosterID]
  512. tp := g.gitServiceType.Name()
  513. if !ok && tp != "" {
  514. var err error
  515. userid, err = user_model.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", pr.PosterID))
  516. if err != nil {
  517. log.Error("GetUserIDByExternalUserID: %v", err)
  518. }
  519. if userid > 0 {
  520. g.userMap[pr.PosterID] = userid
  521. }
  522. }
  523. if userid > 0 {
  524. gpr.Issue.PosterID = userid
  525. } else {
  526. gpr.Issue.PosterID = g.doer.ID
  527. gpr.Issue.OriginalAuthor = pr.PosterName
  528. gpr.Issue.OriginalAuthorID = pr.PosterID
  529. }
  530. gprs = append(gprs, gpr)
  531. }
  532. if err := models.InsertPullRequests(gprs...); err != nil {
  533. return err
  534. }
  535. for _, pr := range gprs {
  536. g.issues.Store(pr.Issue.Index, pr.Issue)
  537. pull.AddToTaskQueue(pr)
  538. }
  539. return nil
  540. }
  541. func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullRequest, error) {
  542. var labels []*models.Label
  543. for _, label := range pr.Labels {
  544. lb, ok := g.labels.Load(label.Name)
  545. if ok {
  546. labels = append(labels, lb.(*models.Label))
  547. }
  548. }
  549. var milestoneID int64
  550. if pr.Milestone != "" {
  551. milestone, ok := g.milestones.Load(pr.Milestone)
  552. if ok {
  553. milestoneID = milestone.(int64)
  554. }
  555. }
  556. // download patch file
  557. err := func() error {
  558. if pr.PatchURL == "" {
  559. return nil
  560. }
  561. // pr.PatchURL maybe a local file
  562. ret, err := uri.Open(pr.PatchURL)
  563. if err != nil {
  564. return err
  565. }
  566. defer ret.Close()
  567. pullDir := filepath.Join(g.repo.RepoPath(), "pulls")
  568. if err = os.MkdirAll(pullDir, os.ModePerm); err != nil {
  569. return err
  570. }
  571. f, err := os.Create(filepath.Join(pullDir, fmt.Sprintf("%d.patch", pr.Number)))
  572. if err != nil {
  573. return err
  574. }
  575. defer f.Close()
  576. _, err = io.Copy(f, ret)
  577. return err
  578. }()
  579. if err != nil {
  580. return nil, err
  581. }
  582. // set head information
  583. pullHead := filepath.Join(g.repo.RepoPath(), "refs", "pull", fmt.Sprintf("%d", pr.Number))
  584. if err := os.MkdirAll(pullHead, os.ModePerm); err != nil {
  585. return nil, err
  586. }
  587. p, err := os.Create(filepath.Join(pullHead, "head"))
  588. if err != nil {
  589. return nil, err
  590. }
  591. _, err = p.WriteString(pr.Head.SHA)
  592. p.Close()
  593. if err != nil {
  594. return nil, err
  595. }
  596. var head = "unknown repository"
  597. if pr.IsForkPullRequest() && pr.State != "closed" {
  598. if pr.Head.OwnerName != "" {
  599. remote := pr.Head.OwnerName
  600. _, ok := g.prHeadCache[remote]
  601. if !ok {
  602. // git remote add
  603. err := g.gitRepo.AddRemote(remote, pr.Head.CloneURL, true)
  604. if err != nil {
  605. log.Error("AddRemote failed: %s", err)
  606. } else {
  607. g.prHeadCache[remote] = struct{}{}
  608. ok = true
  609. }
  610. }
  611. if ok {
  612. _, err = git.NewCommand("fetch", remote, pr.Head.Ref).RunInDir(g.repo.RepoPath())
  613. if err != nil {
  614. log.Error("Fetch branch from %s failed: %v", pr.Head.CloneURL, err)
  615. } else {
  616. headBranch := filepath.Join(g.repo.RepoPath(), "refs", "heads", pr.Head.OwnerName, pr.Head.Ref)
  617. if err := os.MkdirAll(filepath.Dir(headBranch), os.ModePerm); err != nil {
  618. return nil, err
  619. }
  620. b, err := os.Create(headBranch)
  621. if err != nil {
  622. return nil, err
  623. }
  624. _, err = b.WriteString(pr.Head.SHA)
  625. b.Close()
  626. if err != nil {
  627. return nil, err
  628. }
  629. head = pr.Head.OwnerName + "/" + pr.Head.Ref
  630. }
  631. }
  632. }
  633. } else {
  634. head = pr.Head.Ref
  635. // Ensure the closed PR SHA still points to an existing ref
  636. _, err = git.NewCommand("rev-list", "--quiet", "-1", pr.Head.SHA).RunInDir(g.repo.RepoPath())
  637. if err != nil {
  638. if pr.Head.SHA != "" {
  639. // Git update-ref remove bad references with a relative path
  640. log.Warn("Deprecated local head, removing : %v", pr.Head.SHA)
  641. relPath := pr.GetGitRefName()
  642. _, err = git.NewCommand("update-ref", "--no-deref", "-d", relPath).RunInDir(g.repo.RepoPath())
  643. } else {
  644. // The SHA is empty, remove the head file
  645. log.Warn("Empty reference, removing : %v", pullHead)
  646. err = os.Remove(filepath.Join(pullHead, "head"))
  647. }
  648. if err != nil {
  649. log.Error("Cannot remove local head ref, %v", err)
  650. }
  651. }
  652. }
  653. if pr.Created.IsZero() {
  654. if pr.Closed != nil {
  655. pr.Created = *pr.Closed
  656. } else if pr.MergedTime != nil {
  657. pr.Created = *pr.MergedTime
  658. } else {
  659. pr.Created = time.Now()
  660. }
  661. }
  662. if pr.Updated.IsZero() {
  663. pr.Updated = pr.Created
  664. }
  665. var issue = models.Issue{
  666. RepoID: g.repo.ID,
  667. Repo: g.repo,
  668. Title: pr.Title,
  669. Index: pr.Number,
  670. Content: pr.Content,
  671. MilestoneID: milestoneID,
  672. IsPull: true,
  673. IsClosed: pr.State == "closed",
  674. IsLocked: pr.IsLocked,
  675. Labels: labels,
  676. CreatedUnix: timeutil.TimeStamp(pr.Created.Unix()),
  677. UpdatedUnix: timeutil.TimeStamp(pr.Updated.Unix()),
  678. }
  679. tp := g.gitServiceType.Name()
  680. userid, ok := g.userMap[pr.PosterID]
  681. if !ok && tp != "" {
  682. var err error
  683. userid, err = user_model.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", pr.PosterID))
  684. if err != nil {
  685. log.Error("GetUserIDByExternalUserID: %v", err)
  686. }
  687. if userid > 0 {
  688. g.userMap[pr.PosterID] = userid
  689. }
  690. }
  691. if userid > 0 {
  692. issue.PosterID = userid
  693. } else {
  694. issue.PosterID = g.doer.ID
  695. issue.OriginalAuthor = pr.PosterName
  696. issue.OriginalAuthorID = pr.PosterID
  697. }
  698. // add reactions
  699. for _, reaction := range pr.Reactions {
  700. userid, ok := g.userMap[reaction.UserID]
  701. if !ok && tp != "" {
  702. var err error
  703. userid, err = user_model.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", reaction.UserID))
  704. if err != nil {
  705. log.Error("GetUserIDByExternalUserID: %v", err)
  706. }
  707. if userid > 0 {
  708. g.userMap[reaction.UserID] = userid
  709. }
  710. }
  711. var res = models.Reaction{
  712. Type: reaction.Content,
  713. CreatedUnix: timeutil.TimeStampNow(),
  714. }
  715. if userid > 0 {
  716. res.UserID = userid
  717. } else {
  718. res.UserID = g.doer.ID
  719. res.OriginalAuthorID = reaction.UserID
  720. res.OriginalAuthor = reaction.UserName
  721. }
  722. issue.Reactions = append(issue.Reactions, &res)
  723. }
  724. var pullRequest = models.PullRequest{
  725. HeadRepoID: g.repo.ID,
  726. HeadBranch: head,
  727. BaseRepoID: g.repo.ID,
  728. BaseBranch: pr.Base.Ref,
  729. MergeBase: pr.Base.SHA,
  730. Index: pr.Number,
  731. HasMerged: pr.Merged,
  732. Issue: &issue,
  733. }
  734. if pullRequest.Issue.IsClosed && pr.Closed != nil {
  735. pullRequest.Issue.ClosedUnix = timeutil.TimeStamp(pr.Closed.Unix())
  736. }
  737. if pullRequest.HasMerged && pr.MergedTime != nil {
  738. pullRequest.MergedUnix = timeutil.TimeStamp(pr.MergedTime.Unix())
  739. pullRequest.MergedCommitID = pr.MergeCommitSHA
  740. pullRequest.MergerID = g.doer.ID
  741. }
  742. // TODO: assignees
  743. return &pullRequest, nil
  744. }
  745. func convertReviewState(state string) models.ReviewType {
  746. switch state {
  747. case base.ReviewStatePending:
  748. return models.ReviewTypePending
  749. case base.ReviewStateApproved:
  750. return models.ReviewTypeApprove
  751. case base.ReviewStateChangesRequested:
  752. return models.ReviewTypeReject
  753. case base.ReviewStateCommented:
  754. return models.ReviewTypeComment
  755. default:
  756. return models.ReviewTypePending
  757. }
  758. }
  759. // CreateReviews create pull request reviews
  760. func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
  761. var cms = make([]*models.Review, 0, len(reviews))
  762. for _, review := range reviews {
  763. var issue *models.Issue
  764. issueInter, ok := g.issues.Load(review.IssueIndex)
  765. if !ok {
  766. var err error
  767. issue, err = models.GetIssueByIndex(g.repo.ID, review.IssueIndex)
  768. if err != nil {
  769. return err
  770. }
  771. g.issues.Store(review.IssueIndex, issue)
  772. } else {
  773. issue = issueInter.(*models.Issue)
  774. }
  775. userid, ok := g.userMap[review.ReviewerID]
  776. tp := g.gitServiceType.Name()
  777. if !ok && tp != "" {
  778. var err error
  779. userid, err = user_model.GetUserIDByExternalUserID(tp, fmt.Sprintf("%v", review.ReviewerID))
  780. if err != nil {
  781. log.Error("GetUserIDByExternalUserID: %v", err)
  782. }
  783. if userid > 0 {
  784. g.userMap[review.ReviewerID] = userid
  785. }
  786. }
  787. if review.CreatedAt.IsZero() {
  788. review.CreatedAt = time.Unix(int64(issue.CreatedUnix), 0)
  789. }
  790. var cm = models.Review{
  791. Type: convertReviewState(review.State),
  792. IssueID: issue.ID,
  793. Content: review.Content,
  794. Official: review.Official,
  795. CreatedUnix: timeutil.TimeStamp(review.CreatedAt.Unix()),
  796. UpdatedUnix: timeutil.TimeStamp(review.CreatedAt.Unix()),
  797. }
  798. if userid > 0 {
  799. cm.ReviewerID = userid
  800. } else {
  801. cm.ReviewerID = g.doer.ID
  802. cm.OriginalAuthor = review.ReviewerName
  803. cm.OriginalAuthorID = review.ReviewerID
  804. }
  805. // get pr
  806. pr, ok := g.prCache[issue.ID]
  807. if !ok {
  808. var err error
  809. pr, err = models.GetPullRequestByIssueIDWithNoAttributes(issue.ID)
  810. if err != nil {
  811. return err
  812. }
  813. g.prCache[issue.ID] = pr
  814. }
  815. for _, comment := range review.Comments {
  816. line := comment.Line
  817. if line != 0 {
  818. comment.Position = 1
  819. } else {
  820. _, _, line, _ = git.ParseDiffHunkString(comment.DiffHunk)
  821. }
  822. headCommitID, err := g.gitRepo.GetRefCommitID(pr.GetGitRefName())
  823. if err != nil {
  824. log.Warn("GetRefCommitID[%s]: %v, the review comment will be ignored", pr.GetGitRefName(), err)
  825. continue
  826. }
  827. var patch string
  828. reader, writer := io.Pipe()
  829. defer func() {
  830. _ = reader.Close()
  831. _ = writer.Close()
  832. }()
  833. go func() {
  834. if err := git.GetRepoRawDiffForFile(g.gitRepo, pr.MergeBase, headCommitID, git.RawDiffNormal, comment.TreePath, writer); err != nil {
  835. // We should ignore the error since the commit maybe removed when force push to the pull request
  836. log.Warn("GetRepoRawDiffForFile failed when migrating [%s, %s, %s, %s]: %v", g.gitRepo.Path, pr.MergeBase, headCommitID, comment.TreePath, err)
  837. }
  838. _ = writer.Close()
  839. }()
  840. patch, _ = git.CutDiffAroundLine(reader, int64((&models.Comment{Line: int64(line + comment.Position - 1)}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines)
  841. if comment.CreatedAt.IsZero() {
  842. comment.CreatedAt = review.CreatedAt
  843. }
  844. if comment.UpdatedAt.IsZero() {
  845. comment.UpdatedAt = comment.CreatedAt
  846. }
  847. var c = models.Comment{
  848. Type: models.CommentTypeCode,
  849. PosterID: comment.PosterID,
  850. IssueID: issue.ID,
  851. Content: comment.Content,
  852. Line: int64(line + comment.Position - 1),
  853. TreePath: comment.TreePath,
  854. CommitSHA: comment.CommitID,
  855. Patch: patch,
  856. CreatedUnix: timeutil.TimeStamp(comment.CreatedAt.Unix()),
  857. UpdatedUnix: timeutil.TimeStamp(comment.UpdatedAt.Unix()),
  858. }
  859. if userid > 0 {
  860. c.PosterID = userid
  861. } else {
  862. c.PosterID = g.doer.ID
  863. c.OriginalAuthor = review.ReviewerName
  864. c.OriginalAuthorID = review.ReviewerID
  865. }
  866. cm.Comments = append(cm.Comments, &c)
  867. }
  868. cms = append(cms, &cm)
  869. }
  870. return models.InsertReviews(cms)
  871. }
  872. // Rollback when migrating failed, this will rollback all the changes.
  873. func (g *GiteaLocalUploader) Rollback() error {
  874. if g.repo != nil && g.repo.ID > 0 {
  875. g.gitRepo.Close()
  876. if err := models.DeleteRepository(g.doer, g.repo.OwnerID, g.repo.ID); err != nil {
  877. return err
  878. }
  879. }
  880. return nil
  881. }
  882. // Finish when migrating success, this will do some status update things.
  883. func (g *GiteaLocalUploader) Finish() error {
  884. if g.repo == nil || g.repo.ID <= 0 {
  885. return ErrRepoNotCreated
  886. }
  887. // update issue_index
  888. if err := models.RecalculateIssueIndexForRepo(g.repo.ID); err != nil {
  889. return err
  890. }
  891. g.repo.Status = models.RepositoryReady
  892. return models.UpdateRepositoryCols(g.repo, "status")
  893. }