gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. // Copyright 2020 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package convert
  4. import (
  5. "context"
  6. "fmt"
  7. git_model "code.gitea.io/gitea/models/git"
  8. issues_model "code.gitea.io/gitea/models/issues"
  9. "code.gitea.io/gitea/models/perm"
  10. access_model "code.gitea.io/gitea/models/perm/access"
  11. repo_model "code.gitea.io/gitea/models/repo"
  12. user_model "code.gitea.io/gitea/models/user"
  13. "code.gitea.io/gitea/modules/cache"
  14. "code.gitea.io/gitea/modules/cachegroup"
  15. "code.gitea.io/gitea/modules/git"
  16. "code.gitea.io/gitea/modules/gitrepo"
  17. "code.gitea.io/gitea/modules/log"
  18. "code.gitea.io/gitea/modules/setting"
  19. api "code.gitea.io/gitea/modules/structs"
  20. "code.gitea.io/gitea/modules/util"
  21. "code.gitea.io/gitea/services/gitdiff"
  22. )
  23. // ToAPIPullRequest assumes following fields have been assigned with valid values:
  24. // Required - Issue
  25. // Optional - Merger
  26. func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User) *api.PullRequest {
  27. var (
  28. baseBranch string
  29. headBranch string
  30. baseCommit *git.Commit
  31. err error
  32. )
  33. if err = pr.LoadIssue(ctx); err != nil {
  34. log.Error("pr.LoadIssue[%d]: %v", pr.ID, err)
  35. return nil
  36. }
  37. if err = pr.Issue.LoadRepo(ctx); err != nil {
  38. log.Error("pr.Issue.LoadRepo[%d]: %v", pr.ID, err)
  39. return nil
  40. }
  41. apiIssue := ToAPIIssue(ctx, doer, pr.Issue)
  42. if err := pr.LoadBaseRepo(ctx); err != nil {
  43. log.Error("GetRepositoryById[%d]: %v", pr.ID, err)
  44. return nil
  45. }
  46. if err := pr.LoadHeadRepo(ctx); err != nil {
  47. log.Error("GetRepositoryById[%d]: %v", pr.ID, err)
  48. return nil
  49. }
  50. var doerID int64
  51. if doer != nil {
  52. doerID = doer.ID
  53. }
  54. repoUserPerm, err := cache.GetWithContextCache(ctx, cachegroup.RepoUserPermission, fmt.Sprintf("%d-%d", pr.BaseRepoID, doerID),
  55. func(ctx context.Context, _ string) (access_model.Permission, error) {
  56. return access_model.GetUserRepoPermission(ctx, pr.BaseRepo, doer)
  57. },
  58. )
  59. if err != nil {
  60. log.Error("GetUserRepoPermission[%d]: %v", pr.BaseRepoID, err)
  61. repoUserPerm.AccessMode = perm.AccessModeNone
  62. }
  63. apiPullRequest := &api.PullRequest{
  64. ID: pr.ID,
  65. URL: pr.Issue.HTMLURL(ctx),
  66. Index: pr.Index,
  67. Poster: apiIssue.Poster,
  68. Title: apiIssue.Title,
  69. Body: apiIssue.Body,
  70. Labels: apiIssue.Labels,
  71. Milestone: apiIssue.Milestone,
  72. Assignee: apiIssue.Assignee,
  73. Assignees: util.SliceNilAsEmpty(apiIssue.Assignees),
  74. State: apiIssue.State,
  75. Draft: pr.IsWorkInProgress(ctx),
  76. IsLocked: apiIssue.IsLocked,
  77. Comments: apiIssue.Comments,
  78. ReviewComments: pr.GetReviewCommentsCount(ctx),
  79. HTMLURL: pr.Issue.HTMLURL(ctx),
  80. DiffURL: pr.Issue.DiffURL(),
  81. PatchURL: pr.Issue.PatchURL(),
  82. HasMerged: pr.HasMerged,
  83. MergeBase: pr.MergeBase,
  84. Mergeable: pr.Mergeable(ctx),
  85. Deadline: apiIssue.Deadline,
  86. Created: pr.Issue.CreatedUnix.AsTimePtr(),
  87. Updated: pr.Issue.UpdatedUnix.AsTimePtr(),
  88. PinOrder: util.Iif(apiIssue.PinOrder == -1, 0, apiIssue.PinOrder),
  89. // output "[]" rather than null to align to github outputs
  90. RequestedReviewers: []*api.User{},
  91. RequestedReviewersTeams: []*api.Team{},
  92. AllowMaintainerEdit: pr.AllowMaintainerEdit,
  93. Base: &api.PRBranchInfo{
  94. Name: pr.BaseBranch,
  95. Ref: pr.BaseBranch,
  96. RepoID: pr.BaseRepoID,
  97. Repository: ToRepo(ctx, pr.BaseRepo, repoUserPerm),
  98. },
  99. Head: &api.PRBranchInfo{
  100. Name: pr.HeadBranch,
  101. Ref: pr.GetGitHeadRefName(),
  102. RepoID: -1,
  103. },
  104. }
  105. if err = pr.LoadRequestedReviewers(ctx); err != nil {
  106. log.Error("LoadRequestedReviewers[%d]: %v", pr.ID, err)
  107. return nil
  108. }
  109. if err = pr.LoadRequestedReviewersTeams(ctx); err != nil {
  110. log.Error("LoadRequestedReviewersTeams[%d]: %v", pr.ID, err)
  111. return nil
  112. }
  113. for _, reviewer := range pr.RequestedReviewers {
  114. apiPullRequest.RequestedReviewers = append(apiPullRequest.RequestedReviewers, ToUser(ctx, reviewer, nil))
  115. }
  116. for _, reviewerTeam := range pr.RequestedReviewersTeams {
  117. convertedTeam, err := ToTeam(ctx, reviewerTeam, true)
  118. if err != nil {
  119. log.Error("LoadRequestedReviewersTeams[%d]: %v", pr.ID, err)
  120. return nil
  121. }
  122. apiPullRequest.RequestedReviewersTeams = append(apiPullRequest.RequestedReviewersTeams, convertedTeam)
  123. }
  124. if pr.Issue.ClosedUnix != 0 {
  125. apiPullRequest.Closed = pr.Issue.ClosedUnix.AsTimePtr()
  126. }
  127. gitRepo, err := gitrepo.OpenRepository(ctx, pr.BaseRepo)
  128. if err != nil {
  129. log.Error("OpenRepository[%s]: %v", pr.BaseRepo.RepoPath(), err)
  130. return nil
  131. }
  132. defer gitRepo.Close()
  133. exist, err := git_model.IsBranchExist(ctx, pr.BaseRepoID, pr.BaseBranch)
  134. if err != nil {
  135. log.Error("GetBranch[%s]: %v", pr.BaseBranch, err)
  136. return nil
  137. }
  138. if exist {
  139. baseCommit, err = gitRepo.GetBranchCommit(pr.BaseBranch)
  140. if err != nil && !git.IsErrNotExist(err) {
  141. log.Error("GetCommit[%s]: %v", baseBranch, err)
  142. return nil
  143. }
  144. if err == nil {
  145. apiPullRequest.Base.Sha = baseCommit.ID.String()
  146. }
  147. }
  148. if pr.Flow == issues_model.PullRequestFlowAGit {
  149. apiPullRequest.Head.Sha, err = gitRepo.GetRefCommitID(pr.GetGitHeadRefName())
  150. if err != nil {
  151. log.Error("GetRefCommitID[%s]: %v", pr.GetGitHeadRefName(), err)
  152. return nil
  153. }
  154. apiPullRequest.Head.RepoID = pr.BaseRepoID
  155. apiPullRequest.Head.Repository = apiPullRequest.Base.Repository
  156. apiPullRequest.Head.Name = ""
  157. }
  158. if pr.HeadRepo != nil && pr.Flow == issues_model.PullRequestFlowGithub {
  159. p, err := access_model.GetUserRepoPermission(ctx, pr.HeadRepo, doer)
  160. if err != nil {
  161. log.Error("GetUserRepoPermission[%d]: %v", pr.HeadRepoID, err)
  162. p.AccessMode = perm.AccessModeNone
  163. }
  164. apiPullRequest.Head.RepoID = pr.HeadRepo.ID
  165. apiPullRequest.Head.Repository = ToRepo(ctx, pr.HeadRepo, p)
  166. headGitRepo, err := gitrepo.OpenRepository(ctx, pr.HeadRepo)
  167. if err != nil {
  168. log.Error("OpenRepository[%s]: %v", pr.HeadRepo.RepoPath(), err)
  169. return nil
  170. }
  171. defer headGitRepo.Close()
  172. exist, err = git_model.IsBranchExist(ctx, pr.HeadRepoID, pr.HeadBranch)
  173. if err != nil {
  174. log.Error("GetBranch[%s]: %v", pr.HeadBranch, err)
  175. return nil
  176. }
  177. // Outer scope variables to be used in diff calculation
  178. var (
  179. startCommitID string
  180. endCommitID string
  181. )
  182. if !exist {
  183. headCommitID, err := headGitRepo.GetRefCommitID(apiPullRequest.Head.Ref)
  184. if err != nil && !git.IsErrNotExist(err) {
  185. log.Error("GetCommit[%s]: %v", pr.HeadBranch, err)
  186. return nil
  187. }
  188. if err == nil {
  189. apiPullRequest.Head.Sha = headCommitID
  190. endCommitID = headCommitID
  191. }
  192. } else {
  193. commit, err := headGitRepo.GetBranchCommit(pr.HeadBranch)
  194. if err != nil && !git.IsErrNotExist(err) {
  195. log.Error("GetCommit[%s]: %v", headBranch, err)
  196. return nil
  197. }
  198. if err == nil {
  199. apiPullRequest.Head.Ref = pr.HeadBranch
  200. apiPullRequest.Head.Sha = commit.ID.String()
  201. endCommitID = commit.ID.String()
  202. }
  203. }
  204. // Calculate diff
  205. startCommitID = pr.MergeBase
  206. diffShortStats, err := gitdiff.GetDiffShortStat(gitRepo, startCommitID, endCommitID)
  207. if err != nil {
  208. log.Error("GetDiffShortStat: %v", err)
  209. } else {
  210. apiPullRequest.ChangedFiles = &diffShortStats.NumFiles
  211. apiPullRequest.Additions = &diffShortStats.TotalAddition
  212. apiPullRequest.Deletions = &diffShortStats.TotalDeletion
  213. }
  214. }
  215. if len(apiPullRequest.Head.Sha) == 0 && len(apiPullRequest.Head.Ref) != 0 {
  216. baseGitRepo, err := gitrepo.OpenRepository(ctx, pr.BaseRepo)
  217. if err != nil {
  218. log.Error("OpenRepository[%s]: %v", pr.BaseRepo.RepoPath(), err)
  219. return nil
  220. }
  221. defer baseGitRepo.Close()
  222. refs, err := baseGitRepo.GetRefsFiltered(apiPullRequest.Head.Ref)
  223. if err != nil {
  224. log.Error("GetRefsFiltered[%s]: %v", apiPullRequest.Head.Ref, err)
  225. return nil
  226. } else if len(refs) == 0 {
  227. log.Error("unable to resolve PR head ref")
  228. } else {
  229. apiPullRequest.Head.Sha = refs[0].Object.String()
  230. }
  231. }
  232. if pr.HasMerged {
  233. apiPullRequest.Merged = pr.MergedUnix.AsTimePtr()
  234. apiPullRequest.MergedCommitID = &pr.MergedCommitID
  235. apiPullRequest.MergedBy = ToUser(ctx, pr.Merger, nil)
  236. }
  237. return apiPullRequest
  238. }
  239. func ToAPIPullRequests(ctx context.Context, baseRepo *repo_model.Repository, prs issues_model.PullRequestList, doer *user_model.User) ([]*api.PullRequest, error) {
  240. for _, pr := range prs {
  241. pr.BaseRepo = baseRepo
  242. if pr.BaseRepoID == pr.HeadRepoID {
  243. pr.HeadRepo = baseRepo
  244. }
  245. }
  246. // NOTE: load head repositories
  247. if err := prs.LoadRepositories(ctx); err != nil {
  248. return nil, err
  249. }
  250. issueList, err := prs.LoadIssues(ctx)
  251. if err != nil {
  252. return nil, err
  253. }
  254. if err := issueList.LoadLabels(ctx); err != nil {
  255. return nil, err
  256. }
  257. if err := issueList.LoadPosters(ctx); err != nil {
  258. return nil, err
  259. }
  260. if err := issueList.LoadAttachments(ctx); err != nil {
  261. return nil, err
  262. }
  263. if err := issueList.LoadMilestones(ctx); err != nil {
  264. return nil, err
  265. }
  266. if err := issueList.LoadAssignees(ctx); err != nil {
  267. return nil, err
  268. }
  269. if err = issueList.LoadPinOrder(ctx); err != nil {
  270. return nil, err
  271. }
  272. reviews, err := prs.LoadReviews(ctx)
  273. if err != nil {
  274. return nil, err
  275. }
  276. if err = reviews.LoadReviewers(ctx); err != nil {
  277. return nil, err
  278. }
  279. reviewersMap := make(map[int64][]*user_model.User)
  280. for _, review := range reviews {
  281. if review.Reviewer != nil {
  282. reviewersMap[review.IssueID] = append(reviewersMap[review.IssueID], review.Reviewer)
  283. }
  284. }
  285. reviewCounts, err := prs.LoadReviewCommentsCounts(ctx)
  286. if err != nil {
  287. return nil, err
  288. }
  289. gitRepo, err := gitrepo.OpenRepository(ctx, baseRepo)
  290. if err != nil {
  291. return nil, err
  292. }
  293. defer gitRepo.Close()
  294. baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, baseRepo, doer)
  295. if err != nil {
  296. log.Error("GetUserRepoPermission[%d]: %v", baseRepo.ID, err)
  297. baseRepoPerm.AccessMode = perm.AccessModeNone
  298. }
  299. apiRepo := ToRepo(ctx, baseRepo, baseRepoPerm)
  300. baseBranchCache := make(map[string]*git_model.Branch)
  301. apiPullRequests := make([]*api.PullRequest, 0, len(prs))
  302. for _, pr := range prs {
  303. apiIssue := ToAPIIssue(ctx, doer, pr.Issue)
  304. apiPullRequest := &api.PullRequest{
  305. ID: pr.ID,
  306. URL: pr.Issue.HTMLURL(ctx),
  307. Index: pr.Index,
  308. Poster: apiIssue.Poster,
  309. Title: apiIssue.Title,
  310. Body: apiIssue.Body,
  311. Labels: apiIssue.Labels,
  312. Milestone: apiIssue.Milestone,
  313. Assignee: apiIssue.Assignee,
  314. Assignees: apiIssue.Assignees,
  315. State: apiIssue.State,
  316. Draft: pr.IsWorkInProgress(ctx),
  317. IsLocked: apiIssue.IsLocked,
  318. Comments: apiIssue.Comments,
  319. ReviewComments: reviewCounts[pr.IssueID],
  320. HTMLURL: pr.Issue.HTMLURL(ctx),
  321. DiffURL: pr.Issue.DiffURL(),
  322. PatchURL: pr.Issue.PatchURL(),
  323. HasMerged: pr.HasMerged,
  324. MergeBase: pr.MergeBase,
  325. Mergeable: pr.Mergeable(ctx),
  326. Deadline: apiIssue.Deadline,
  327. Created: pr.Issue.CreatedUnix.AsTimePtr(),
  328. Updated: pr.Issue.UpdatedUnix.AsTimePtr(),
  329. PinOrder: util.Iif(apiIssue.PinOrder == -1, 0, apiIssue.PinOrder),
  330. AllowMaintainerEdit: pr.AllowMaintainerEdit,
  331. Base: &api.PRBranchInfo{
  332. Name: pr.BaseBranch,
  333. Ref: pr.BaseBranch,
  334. RepoID: pr.BaseRepoID,
  335. Repository: apiRepo,
  336. },
  337. Head: &api.PRBranchInfo{
  338. Name: pr.HeadBranch,
  339. Ref: pr.GetGitHeadRefName(),
  340. RepoID: -1,
  341. },
  342. }
  343. pr.RequestedReviewers = reviewersMap[pr.IssueID]
  344. for _, reviewer := range pr.RequestedReviewers {
  345. apiPullRequest.RequestedReviewers = append(apiPullRequest.RequestedReviewers, ToUser(ctx, reviewer, nil))
  346. }
  347. for _, reviewerTeam := range pr.RequestedReviewersTeams {
  348. convertedTeam, err := ToTeam(ctx, reviewerTeam, true)
  349. if err != nil {
  350. log.Error("LoadRequestedReviewersTeams[%d]: %v", pr.ID, err)
  351. return nil, err
  352. }
  353. apiPullRequest.RequestedReviewersTeams = append(apiPullRequest.RequestedReviewersTeams, convertedTeam)
  354. }
  355. if pr.Issue.ClosedUnix != 0 {
  356. apiPullRequest.Closed = pr.Issue.ClosedUnix.AsTimePtr()
  357. }
  358. baseBranch, ok := baseBranchCache[pr.BaseBranch]
  359. if !ok {
  360. baseBranch, err = git_model.GetBranch(ctx, baseRepo.ID, pr.BaseBranch)
  361. if err == nil {
  362. baseBranchCache[pr.BaseBranch] = baseBranch
  363. } else if !git_model.IsErrBranchNotExist(err) {
  364. return nil, err
  365. }
  366. }
  367. if baseBranch != nil {
  368. apiPullRequest.Base.Sha = baseBranch.CommitID
  369. }
  370. if pr.HeadRepoID == pr.BaseRepoID {
  371. apiPullRequest.Head.Repository = apiPullRequest.Base.Repository
  372. }
  373. // pull request head branch, both repository and branch could not exist
  374. if pr.HeadRepo != nil {
  375. apiPullRequest.Head.RepoID = pr.HeadRepo.ID
  376. exist, err := git_model.IsBranchExist(ctx, pr.HeadRepo.ID, pr.HeadBranch)
  377. if err != nil {
  378. log.Error("IsBranchExist[%d]: %v", pr.HeadRepo.ID, err)
  379. return nil, err
  380. }
  381. if exist {
  382. apiPullRequest.Head.Ref = pr.HeadBranch
  383. }
  384. if pr.HeadRepoID != pr.BaseRepoID {
  385. p, err := access_model.GetUserRepoPermission(ctx, pr.HeadRepo, doer)
  386. if err != nil {
  387. log.Error("GetUserRepoPermission[%d]: %v", pr.HeadRepoID, err)
  388. p.AccessMode = perm.AccessModeNone
  389. }
  390. apiPullRequest.Head.Repository = ToRepo(ctx, pr.HeadRepo, p)
  391. }
  392. }
  393. if apiPullRequest.Head.Ref == "" {
  394. apiPullRequest.Head.Ref = pr.GetGitHeadRefName()
  395. }
  396. if pr.Flow == issues_model.PullRequestFlowAGit {
  397. apiPullRequest.Head.Name = ""
  398. }
  399. apiPullRequest.Head.Sha, err = gitRepo.GetRefCommitID(pr.GetGitHeadRefName())
  400. if err != nil {
  401. log.Error("GetRefCommitID[%s]: %v", pr.GetGitHeadRefName(), err)
  402. }
  403. if len(apiPullRequest.Head.Sha) == 0 && len(apiPullRequest.Head.Ref) != 0 {
  404. refs, err := gitRepo.GetRefsFiltered(apiPullRequest.Head.Ref)
  405. if err != nil {
  406. log.Error("GetRefsFiltered[%s]: %v", apiPullRequest.Head.Ref, err)
  407. return nil, err
  408. } else if len(refs) == 0 {
  409. log.Error("unable to resolve PR head ref")
  410. } else {
  411. apiPullRequest.Head.Sha = refs[0].Object.String()
  412. }
  413. }
  414. if pr.HasMerged {
  415. apiPullRequest.Merged = pr.MergedUnix.AsTimePtr()
  416. apiPullRequest.MergedCommitID = &pr.MergedCommitID
  417. apiPullRequest.MergedBy = ToUser(ctx, pr.Merger, nil)
  418. }
  419. // Do not provide "ChangeFiles/Additions/Deletions" for the PR list, because the "diff" is quite slow
  420. // If callers are interested in these values, they should do a separate request to get the PR details
  421. if apiPullRequest.ChangedFiles != nil || apiPullRequest.Additions != nil || apiPullRequest.Deletions != nil {
  422. setting.PanicInDevOrTesting("ChangedFiles/Additions/Deletions should not be set in PR list")
  423. }
  424. apiPullRequests = append(apiPullRequests, apiPullRequest)
  425. }
  426. return apiPullRequests, nil
  427. }