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.

pull.go 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package pull
  5. import (
  6. "bufio"
  7. "bytes"
  8. "context"
  9. "fmt"
  10. "io"
  11. "regexp"
  12. "strings"
  13. "time"
  14. "code.gitea.io/gitea/models"
  15. "code.gitea.io/gitea/models/db"
  16. repo_model "code.gitea.io/gitea/models/repo"
  17. user_model "code.gitea.io/gitea/models/user"
  18. "code.gitea.io/gitea/modules/git"
  19. "code.gitea.io/gitea/modules/graceful"
  20. "code.gitea.io/gitea/modules/json"
  21. "code.gitea.io/gitea/modules/log"
  22. "code.gitea.io/gitea/modules/notification"
  23. "code.gitea.io/gitea/modules/process"
  24. repo_module "code.gitea.io/gitea/modules/repository"
  25. "code.gitea.io/gitea/modules/setting"
  26. "code.gitea.io/gitea/modules/sync"
  27. issue_service "code.gitea.io/gitea/services/issue"
  28. )
  29. // TODO: use clustered lock (unique queue? or *abuse* cache)
  30. var pullWorkingPool = sync.NewExclusivePool()
  31. // NewPullRequest creates new pull request with labels for repository.
  32. func NewPullRequest(ctx context.Context, repo *repo_model.Repository, pull *models.Issue, labelIDs []int64, uuids []string, pr *models.PullRequest, assigneeIDs []int64) error {
  33. if err := TestPatch(pr); err != nil {
  34. return err
  35. }
  36. divergence, err := GetDiverging(ctx, pr)
  37. if err != nil {
  38. return err
  39. }
  40. pr.CommitsAhead = divergence.Ahead
  41. pr.CommitsBehind = divergence.Behind
  42. if err := models.NewPullRequest(ctx, repo, pull, labelIDs, uuids, pr); err != nil {
  43. return err
  44. }
  45. for _, assigneeID := range assigneeIDs {
  46. if err := issue_service.AddAssigneeIfNotAssigned(pull, pull.Poster, assigneeID); err != nil {
  47. return err
  48. }
  49. }
  50. pr.Issue = pull
  51. pull.PullRequest = pr
  52. // Now - even if the request context has been cancelled as the PR has been created
  53. // in the db and there is no way to cancel that transaction we have to proceed - therefore
  54. // create new context and work from there
  55. prCtx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("NewPullRequest: %s:%d", repo.FullName(), pr.Index))
  56. defer finished()
  57. if pr.Flow == models.PullRequestFlowGithub {
  58. err = PushToBaseRepo(prCtx, pr)
  59. } else {
  60. err = UpdateRef(prCtx, pr)
  61. }
  62. if err != nil {
  63. return err
  64. }
  65. mentions, err := models.FindAndUpdateIssueMentions(ctx, pull, pull.Poster, pull.Content)
  66. if err != nil {
  67. return err
  68. }
  69. notification.NotifyNewPullRequest(pr, mentions)
  70. if len(pull.Labels) > 0 {
  71. notification.NotifyIssueChangeLabels(pull.Poster, pull, pull.Labels, nil)
  72. }
  73. if pull.Milestone != nil {
  74. notification.NotifyIssueChangeMilestone(pull.Poster, pull, 0)
  75. }
  76. // add first push codes comment
  77. baseGitRepo, err := git.OpenRepository(prCtx, pr.BaseRepo.RepoPath())
  78. if err != nil {
  79. return err
  80. }
  81. defer baseGitRepo.Close()
  82. compareInfo, err := baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(),
  83. git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName(), false, false)
  84. if err != nil {
  85. return err
  86. }
  87. if len(compareInfo.Commits) > 0 {
  88. data := models.PushActionContent{IsForcePush: false}
  89. data.CommitIDs = make([]string, 0, len(compareInfo.Commits))
  90. for i := len(compareInfo.Commits) - 1; i >= 0; i-- {
  91. data.CommitIDs = append(data.CommitIDs, compareInfo.Commits[i].ID.String())
  92. }
  93. dataJSON, err := json.Marshal(data)
  94. if err != nil {
  95. return err
  96. }
  97. ops := &models.CreateCommentOptions{
  98. Type: models.CommentTypePullRequestPush,
  99. Doer: pull.Poster,
  100. Repo: repo,
  101. Issue: pr.Issue,
  102. IsForcePush: false,
  103. Content: string(dataJSON),
  104. }
  105. _, _ = models.CreateComment(ops)
  106. }
  107. return nil
  108. }
  109. // ChangeTargetBranch changes the target branch of this pull request, as the given user.
  110. func ChangeTargetBranch(ctx context.Context, pr *models.PullRequest, doer *user_model.User, targetBranch string) (err error) {
  111. pullWorkingPool.CheckIn(fmt.Sprint(pr.ID))
  112. defer pullWorkingPool.CheckOut(fmt.Sprint(pr.ID))
  113. // Current target branch is already the same
  114. if pr.BaseBranch == targetBranch {
  115. return nil
  116. }
  117. if pr.Issue.IsClosed {
  118. return models.ErrIssueIsClosed{
  119. ID: pr.Issue.ID,
  120. RepoID: pr.Issue.RepoID,
  121. Index: pr.Issue.Index,
  122. }
  123. }
  124. if pr.HasMerged {
  125. return models.ErrPullRequestHasMerged{
  126. ID: pr.ID,
  127. IssueID: pr.Index,
  128. HeadRepoID: pr.HeadRepoID,
  129. BaseRepoID: pr.BaseRepoID,
  130. HeadBranch: pr.HeadBranch,
  131. BaseBranch: pr.BaseBranch,
  132. }
  133. }
  134. // Check if branches are equal
  135. branchesEqual, err := IsHeadEqualWithBranch(ctx, pr, targetBranch)
  136. if err != nil {
  137. return err
  138. }
  139. if branchesEqual {
  140. return models.ErrBranchesEqual{
  141. HeadBranchName: pr.HeadBranch,
  142. BaseBranchName: targetBranch,
  143. }
  144. }
  145. // Check if pull request for the new target branch already exists
  146. existingPr, err := models.GetUnmergedPullRequest(pr.HeadRepoID, pr.BaseRepoID, pr.HeadBranch, targetBranch, models.PullRequestFlowGithub)
  147. if existingPr != nil {
  148. return models.ErrPullRequestAlreadyExists{
  149. ID: existingPr.ID,
  150. IssueID: existingPr.Index,
  151. HeadRepoID: existingPr.HeadRepoID,
  152. BaseRepoID: existingPr.BaseRepoID,
  153. HeadBranch: existingPr.HeadBranch,
  154. BaseBranch: existingPr.BaseBranch,
  155. }
  156. }
  157. if err != nil && !models.IsErrPullRequestNotExist(err) {
  158. return err
  159. }
  160. // Set new target branch
  161. oldBranch := pr.BaseBranch
  162. pr.BaseBranch = targetBranch
  163. // Refresh patch
  164. if err := TestPatch(pr); err != nil {
  165. return err
  166. }
  167. // Update target branch, PR diff and status
  168. // This is the same as checkAndUpdateStatus in check service, but also updates base_branch
  169. if pr.Status == models.PullRequestStatusChecking {
  170. pr.Status = models.PullRequestStatusMergeable
  171. }
  172. // Update Commit Divergence
  173. divergence, err := GetDiverging(ctx, pr)
  174. if err != nil {
  175. return err
  176. }
  177. pr.CommitsAhead = divergence.Ahead
  178. pr.CommitsBehind = divergence.Behind
  179. if err := pr.UpdateColsIfNotMerged("merge_base", "status", "conflicted_files", "changed_protected_files", "base_branch", "commits_ahead", "commits_behind"); err != nil {
  180. return err
  181. }
  182. // Create comment
  183. options := &models.CreateCommentOptions{
  184. Type: models.CommentTypeChangeTargetBranch,
  185. Doer: doer,
  186. Repo: pr.Issue.Repo,
  187. Issue: pr.Issue,
  188. OldRef: oldBranch,
  189. NewRef: targetBranch,
  190. }
  191. if _, err = models.CreateComment(options); err != nil {
  192. return fmt.Errorf("CreateChangeTargetBranchComment: %v", err)
  193. }
  194. return nil
  195. }
  196. func checkForInvalidation(ctx context.Context, requests models.PullRequestList, repoID int64, doer *user_model.User, branch string) error {
  197. repo, err := repo_model.GetRepositoryByID(repoID)
  198. if err != nil {
  199. return fmt.Errorf("GetRepositoryByID: %v", err)
  200. }
  201. gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
  202. if err != nil {
  203. return fmt.Errorf("git.OpenRepository: %v", err)
  204. }
  205. go func() {
  206. // FIXME: graceful: We need to tell the manager we're doing something...
  207. err := requests.InvalidateCodeComments(ctx, doer, gitRepo, branch)
  208. if err != nil {
  209. log.Error("PullRequestList.InvalidateCodeComments: %v", err)
  210. }
  211. gitRepo.Close()
  212. }()
  213. return nil
  214. }
  215. // AddTestPullRequestTask adds new test tasks by given head/base repository and head/base branch,
  216. // and generate new patch for testing as needed.
  217. func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string, isSync bool, oldCommitID, newCommitID string) {
  218. log.Trace("AddTestPullRequestTask [head_repo_id: %d, head_branch: %s]: finding pull requests", repoID, branch)
  219. graceful.GetManager().RunWithShutdownContext(func(ctx context.Context) {
  220. // There is no sensible way to shut this down ":-("
  221. // If you don't let it run all the way then you will lose data
  222. // TODO: graceful: AddTestPullRequestTask needs to become a queue!
  223. prs, err := models.GetUnmergedPullRequestsByHeadInfo(repoID, branch)
  224. if err != nil {
  225. log.Error("Find pull requests [head_repo_id: %d, head_branch: %s]: %v", repoID, branch, err)
  226. return
  227. }
  228. if isSync {
  229. requests := models.PullRequestList(prs)
  230. if err = requests.LoadAttributes(); err != nil {
  231. log.Error("PullRequestList.LoadAttributes: %v", err)
  232. }
  233. if invalidationErr := checkForInvalidation(ctx, requests, repoID, doer, branch); invalidationErr != nil {
  234. log.Error("checkForInvalidation: %v", invalidationErr)
  235. }
  236. if err == nil {
  237. for _, pr := range prs {
  238. if newCommitID != "" && newCommitID != git.EmptySHA {
  239. changed, err := checkIfPRContentChanged(ctx, pr, oldCommitID, newCommitID)
  240. if err != nil {
  241. log.Error("checkIfPRContentChanged: %v", err)
  242. }
  243. if changed {
  244. // Mark old reviews as stale if diff to mergebase has changed
  245. if err := models.MarkReviewsAsStale(pr.IssueID); err != nil {
  246. log.Error("MarkReviewsAsStale: %v", err)
  247. }
  248. }
  249. if err := models.MarkReviewsAsNotStale(pr.IssueID, newCommitID); err != nil {
  250. log.Error("MarkReviewsAsNotStale: %v", err)
  251. }
  252. divergence, err := GetDiverging(ctx, pr)
  253. if err != nil {
  254. log.Error("GetDiverging: %v", err)
  255. } else {
  256. err = pr.UpdateCommitDivergence(divergence.Ahead, divergence.Behind)
  257. if err != nil {
  258. log.Error("UpdateCommitDivergence: %v", err)
  259. }
  260. }
  261. }
  262. pr.Issue.PullRequest = pr
  263. notification.NotifyPullRequestSynchronized(doer, pr)
  264. }
  265. }
  266. }
  267. for _, pr := range prs {
  268. log.Trace("Updating PR[%d]: composing new test task", pr.ID)
  269. if pr.Flow == models.PullRequestFlowGithub {
  270. if err := PushToBaseRepo(ctx, pr); err != nil {
  271. log.Error("PushToBaseRepo: %v", err)
  272. continue
  273. }
  274. } else {
  275. continue
  276. }
  277. AddToTaskQueue(pr)
  278. comment, err := models.CreatePushPullComment(ctx, doer, pr, oldCommitID, newCommitID)
  279. if err == nil && comment != nil {
  280. notification.NotifyPullRequestPushCommits(doer, pr, comment)
  281. }
  282. }
  283. log.Trace("AddTestPullRequestTask [base_repo_id: %d, base_branch: %s]: finding pull requests", repoID, branch)
  284. prs, err = models.GetUnmergedPullRequestsByBaseInfo(repoID, branch)
  285. if err != nil {
  286. log.Error("Find pull requests [base_repo_id: %d, base_branch: %s]: %v", repoID, branch, err)
  287. return
  288. }
  289. for _, pr := range prs {
  290. divergence, err := GetDiverging(ctx, pr)
  291. if err != nil {
  292. if models.IsErrBranchDoesNotExist(err) && !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) {
  293. log.Warn("Cannot test PR %s/%d: head_branch %s no longer exists", pr.BaseRepo.Name, pr.IssueID, pr.HeadBranch)
  294. } else {
  295. log.Error("GetDiverging: %v", err)
  296. }
  297. } else {
  298. err = pr.UpdateCommitDivergence(divergence.Ahead, divergence.Behind)
  299. if err != nil {
  300. log.Error("UpdateCommitDivergence: %v", err)
  301. }
  302. }
  303. AddToTaskQueue(pr)
  304. }
  305. })
  306. }
  307. // checkIfPRContentChanged checks if diff to target branch has changed by push
  308. // A commit can be considered to leave the PR untouched if the patch/diff with its merge base is unchanged
  309. func checkIfPRContentChanged(ctx context.Context, pr *models.PullRequest, oldCommitID, newCommitID string) (hasChanged bool, err error) {
  310. if err = pr.LoadHeadRepoCtx(ctx); err != nil {
  311. return false, fmt.Errorf("LoadHeadRepo: %v", err)
  312. } else if pr.HeadRepo == nil {
  313. // corrupt data assumed changed
  314. return true, nil
  315. }
  316. if err = pr.LoadBaseRepoCtx(ctx); err != nil {
  317. return false, fmt.Errorf("LoadBaseRepo: %v", err)
  318. }
  319. headGitRepo, err := git.OpenRepository(ctx, pr.HeadRepo.RepoPath())
  320. if err != nil {
  321. return false, fmt.Errorf("OpenRepository: %v", err)
  322. }
  323. defer headGitRepo.Close()
  324. // Add a temporary remote.
  325. tmpRemote := "checkIfPRContentChanged-" + fmt.Sprint(time.Now().UnixNano())
  326. if err = headGitRepo.AddRemote(tmpRemote, pr.BaseRepo.RepoPath(), true); err != nil {
  327. return false, fmt.Errorf("AddRemote: %s/%s-%s: %v", pr.HeadRepo.OwnerName, pr.HeadRepo.Name, tmpRemote, err)
  328. }
  329. defer func() {
  330. if err := headGitRepo.RemoveRemote(tmpRemote); err != nil {
  331. log.Error("checkIfPRContentChanged: RemoveRemote: %s/%s-%s: %v", pr.HeadRepo.OwnerName, pr.HeadRepo.Name, tmpRemote, err)
  332. }
  333. }()
  334. // To synchronize repo and get a base ref
  335. _, base, err := headGitRepo.GetMergeBase(tmpRemote, pr.BaseBranch, pr.HeadBranch)
  336. if err != nil {
  337. return false, fmt.Errorf("GetMergeBase: %v", err)
  338. }
  339. diffBefore := &bytes.Buffer{}
  340. diffAfter := &bytes.Buffer{}
  341. if err := headGitRepo.GetDiffFromMergeBase(base, oldCommitID, diffBefore); err != nil {
  342. // If old commit not found, assume changed.
  343. log.Debug("GetDiffFromMergeBase: %v", err)
  344. return true, nil
  345. }
  346. if err := headGitRepo.GetDiffFromMergeBase(base, newCommitID, diffAfter); err != nil {
  347. // New commit should be found
  348. return false, fmt.Errorf("GetDiffFromMergeBase: %v", err)
  349. }
  350. diffBeforeLines := bufio.NewScanner(diffBefore)
  351. diffAfterLines := bufio.NewScanner(diffAfter)
  352. for diffBeforeLines.Scan() && diffAfterLines.Scan() {
  353. if strings.HasPrefix(diffBeforeLines.Text(), "index") && strings.HasPrefix(diffAfterLines.Text(), "index") {
  354. // file hashes can change without the diff changing
  355. continue
  356. } else if strings.HasPrefix(diffBeforeLines.Text(), "@@") && strings.HasPrefix(diffAfterLines.Text(), "@@") {
  357. // the location of the difference may change
  358. continue
  359. } else if !bytes.Equal(diffBeforeLines.Bytes(), diffAfterLines.Bytes()) {
  360. return true, nil
  361. }
  362. }
  363. if diffBeforeLines.Scan() || diffAfterLines.Scan() {
  364. // Diffs not of equal length
  365. return true, nil
  366. }
  367. return false, nil
  368. }
  369. // PushToBaseRepo pushes commits from branches of head repository to
  370. // corresponding branches of base repository.
  371. // FIXME: Only push branches that are actually updates?
  372. func PushToBaseRepo(ctx context.Context, pr *models.PullRequest) (err error) {
  373. return pushToBaseRepoHelper(ctx, pr, "")
  374. }
  375. func pushToBaseRepoHelper(ctx context.Context, pr *models.PullRequest, prefixHeadBranch string) (err error) {
  376. log.Trace("PushToBaseRepo[%d]: pushing commits to base repo '%s'", pr.BaseRepoID, pr.GetGitRefName())
  377. if err := pr.LoadHeadRepoCtx(ctx); err != nil {
  378. log.Error("Unable to load head repository for PR[%d] Error: %v", pr.ID, err)
  379. return err
  380. }
  381. headRepoPath := pr.HeadRepo.RepoPath()
  382. if err := pr.LoadBaseRepoCtx(ctx); err != nil {
  383. log.Error("Unable to load base repository for PR[%d] Error: %v", pr.ID, err)
  384. return err
  385. }
  386. baseRepoPath := pr.BaseRepo.RepoPath()
  387. if err = pr.LoadIssue(); err != nil {
  388. return fmt.Errorf("unable to load issue %d for pr %d: %v", pr.IssueID, pr.ID, err)
  389. }
  390. if err = pr.Issue.LoadPoster(); err != nil {
  391. return fmt.Errorf("unable to load poster %d for pr %d: %v", pr.Issue.PosterID, pr.ID, err)
  392. }
  393. gitRefName := pr.GetGitRefName()
  394. if err := git.Push(ctx, headRepoPath, git.PushOptions{
  395. Remote: baseRepoPath,
  396. Branch: prefixHeadBranch + pr.HeadBranch + ":" + gitRefName,
  397. Force: true,
  398. // Use InternalPushingEnvironment here because we know that pre-receive and post-receive do not run on a refs/pulls/...
  399. Env: repo_module.InternalPushingEnvironment(pr.Issue.Poster, pr.BaseRepo),
  400. }); err != nil {
  401. if git.IsErrPushOutOfDate(err) {
  402. // This should not happen as we're using force!
  403. log.Error("Unable to push PR head for %s#%d (%-v:%s) due to ErrPushOfDate: %v", pr.BaseRepo.FullName(), pr.Index, pr.BaseRepo, gitRefName, err)
  404. return err
  405. } else if git.IsErrPushRejected(err) {
  406. rejectErr := err.(*git.ErrPushRejected)
  407. log.Info("Unable to push PR head for %s#%d (%-v:%s) due to rejection:\nStdout: %s\nStderr: %s\nError: %v", pr.BaseRepo.FullName(), pr.Index, pr.BaseRepo, gitRefName, rejectErr.StdOut, rejectErr.StdErr, rejectErr.Err)
  408. return err
  409. } else if git.IsErrMoreThanOne(err) {
  410. if prefixHeadBranch != "" {
  411. log.Info("Can't push with %s%s", prefixHeadBranch, pr.HeadBranch)
  412. return err
  413. }
  414. log.Info("Retrying to push with %s%s", git.BranchPrefix, pr.HeadBranch)
  415. err = pushToBaseRepoHelper(ctx, pr, git.BranchPrefix)
  416. return err
  417. }
  418. log.Error("Unable to push PR head for %s#%d (%-v:%s) due to Error: %v", pr.BaseRepo.FullName(), pr.Index, pr.BaseRepo, gitRefName, err)
  419. return fmt.Errorf("Push: %s:%s %s:%s %v", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), gitRefName, err)
  420. }
  421. return nil
  422. }
  423. // UpdateRef update refs/pull/id/head directly for agit flow pull request
  424. func UpdateRef(ctx context.Context, pr *models.PullRequest) (err error) {
  425. log.Trace("UpdateRef[%d]: upgate pull request ref in base repo '%s'", pr.ID, pr.GetGitRefName())
  426. if err := pr.LoadBaseRepoCtx(ctx); err != nil {
  427. log.Error("Unable to load base repository for PR[%d] Error: %v", pr.ID, err)
  428. return err
  429. }
  430. _, _, err = git.NewCommand(ctx, "update-ref", pr.GetGitRefName(), pr.HeadCommitID).RunStdString(&git.RunOpts{Dir: pr.BaseRepo.RepoPath()})
  431. if err != nil {
  432. log.Error("Unable to update ref in base repository for PR[%d] Error: %v", pr.ID, err)
  433. }
  434. return err
  435. }
  436. type errlist []error
  437. func (errs errlist) Error() string {
  438. if len(errs) > 0 {
  439. var buf strings.Builder
  440. for i, err := range errs {
  441. if i > 0 {
  442. buf.WriteString(", ")
  443. }
  444. buf.WriteString(err.Error())
  445. }
  446. return buf.String()
  447. }
  448. return ""
  449. }
  450. // CloseBranchPulls close all the pull requests who's head branch is the branch
  451. func CloseBranchPulls(doer *user_model.User, repoID int64, branch string) error {
  452. prs, err := models.GetUnmergedPullRequestsByHeadInfo(repoID, branch)
  453. if err != nil {
  454. return err
  455. }
  456. prs2, err := models.GetUnmergedPullRequestsByBaseInfo(repoID, branch)
  457. if err != nil {
  458. return err
  459. }
  460. prs = append(prs, prs2...)
  461. if err := models.PullRequestList(prs).LoadAttributes(); err != nil {
  462. return err
  463. }
  464. var errs errlist
  465. for _, pr := range prs {
  466. if err = issue_service.ChangeStatus(pr.Issue, doer, true); err != nil && !models.IsErrPullWasClosed(err) && !models.IsErrDependenciesLeft(err) {
  467. errs = append(errs, err)
  468. }
  469. }
  470. if len(errs) > 0 {
  471. return errs
  472. }
  473. return nil
  474. }
  475. // CloseRepoBranchesPulls close all pull requests which head branches are in the given repository, but only whose base repo is not in the given repository
  476. func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *repo_model.Repository) error {
  477. branches, _, err := git.GetBranchesByPath(ctx, repo.RepoPath(), 0, 0)
  478. if err != nil {
  479. return err
  480. }
  481. var errs errlist
  482. for _, branch := range branches {
  483. prs, err := models.GetUnmergedPullRequestsByHeadInfo(repo.ID, branch.Name)
  484. if err != nil {
  485. return err
  486. }
  487. if err = models.PullRequestList(prs).LoadAttributes(); err != nil {
  488. return err
  489. }
  490. for _, pr := range prs {
  491. // If the base repository for this pr is this repository there is no need to close it
  492. // as it is going to be deleted anyway
  493. if pr.BaseRepoID == repo.ID {
  494. continue
  495. }
  496. if err = issue_service.ChangeStatus(pr.Issue, doer, true); err != nil && !models.IsErrPullWasClosed(err) {
  497. errs = append(errs, err)
  498. }
  499. }
  500. }
  501. if len(errs) > 0 {
  502. return errs
  503. }
  504. return nil
  505. }
  506. var commitMessageTrailersPattern = regexp.MustCompile(`(?:^|\n\n)(?:[\w-]+[ \t]*:[^\n]+\n*(?:[ \t]+[^\n]+\n*)*)+$`)
  507. // GetSquashMergeCommitMessages returns the commit messages between head and merge base (if there is one)
  508. func GetSquashMergeCommitMessages(ctx context.Context, pr *models.PullRequest) string {
  509. if err := pr.LoadIssue(); err != nil {
  510. log.Error("Cannot load issue %d for PR id %d: Error: %v", pr.IssueID, pr.ID, err)
  511. return ""
  512. }
  513. if err := pr.Issue.LoadPoster(); err != nil {
  514. log.Error("Cannot load poster %d for pr id %d, index %d Error: %v", pr.Issue.PosterID, pr.ID, pr.Index, err)
  515. return ""
  516. }
  517. if pr.HeadRepo == nil {
  518. var err error
  519. pr.HeadRepo, err = repo_model.GetRepositoryByID(pr.HeadRepoID)
  520. if err != nil {
  521. log.Error("GetRepositoryById[%d]: %v", pr.HeadRepoID, err)
  522. return ""
  523. }
  524. }
  525. gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, pr.HeadRepo.RepoPath())
  526. if err != nil {
  527. log.Error("Unable to open head repository: Error: %v", err)
  528. return ""
  529. }
  530. defer closer.Close()
  531. var headCommit *git.Commit
  532. if pr.Flow == models.PullRequestFlowGithub {
  533. headCommit, err = gitRepo.GetBranchCommit(pr.HeadBranch)
  534. } else {
  535. pr.HeadCommitID, err = gitRepo.GetRefCommitID(pr.GetGitRefName())
  536. if err != nil {
  537. log.Error("Unable to get head commit: %s Error: %v", pr.GetGitRefName(), err)
  538. return ""
  539. }
  540. headCommit, err = gitRepo.GetCommit(pr.HeadCommitID)
  541. }
  542. if err != nil {
  543. log.Error("Unable to get head commit: %s Error: %v", pr.HeadBranch, err)
  544. return ""
  545. }
  546. mergeBase, err := gitRepo.GetCommit(pr.MergeBase)
  547. if err != nil {
  548. log.Error("Unable to get merge base commit: %s Error: %v", pr.MergeBase, err)
  549. return ""
  550. }
  551. limit := setting.Repository.PullRequest.DefaultMergeMessageCommitsLimit
  552. commits, err := gitRepo.CommitsBetweenLimit(headCommit, mergeBase, limit, 0)
  553. if err != nil {
  554. log.Error("Unable to get commits between: %s %s Error: %v", pr.HeadBranch, pr.MergeBase, err)
  555. return ""
  556. }
  557. posterSig := pr.Issue.Poster.NewGitSig().String()
  558. authorsMap := map[string]bool{}
  559. authors := make([]string, 0, len(commits))
  560. stringBuilder := strings.Builder{}
  561. if !setting.Repository.PullRequest.PopulateSquashCommentWithCommitMessages {
  562. message := strings.TrimSpace(pr.Issue.Content)
  563. stringBuilder.WriteString(message)
  564. if stringBuilder.Len() > 0 {
  565. stringBuilder.WriteRune('\n')
  566. if !commitMessageTrailersPattern.MatchString(message) {
  567. stringBuilder.WriteRune('\n')
  568. }
  569. }
  570. }
  571. // commits list is in reverse chronological order
  572. first := true
  573. for i := len(commits) - 1; i >= 0; i-- {
  574. commit := commits[i]
  575. if setting.Repository.PullRequest.PopulateSquashCommentWithCommitMessages {
  576. maxSize := setting.Repository.PullRequest.DefaultMergeMessageSize
  577. if maxSize < 0 || stringBuilder.Len() < maxSize {
  578. var toWrite []byte
  579. if first {
  580. first = false
  581. toWrite = []byte(strings.TrimPrefix(commit.CommitMessage, pr.Issue.Title))
  582. } else {
  583. toWrite = []byte(commit.CommitMessage)
  584. }
  585. if len(toWrite) > maxSize-stringBuilder.Len() && maxSize > -1 {
  586. toWrite = append(toWrite[:maxSize-stringBuilder.Len()], "..."...)
  587. }
  588. if _, err := stringBuilder.Write(toWrite); err != nil {
  589. log.Error("Unable to write commit message Error: %v", err)
  590. return ""
  591. }
  592. if _, err := stringBuilder.WriteRune('\n'); err != nil {
  593. log.Error("Unable to write commit message Error: %v", err)
  594. return ""
  595. }
  596. }
  597. }
  598. authorString := commit.Author.String()
  599. if !authorsMap[authorString] && authorString != posterSig {
  600. authors = append(authors, authorString)
  601. authorsMap[authorString] = true
  602. }
  603. }
  604. // Consider collecting the remaining authors
  605. if limit >= 0 && setting.Repository.PullRequest.DefaultMergeMessageAllAuthors {
  606. skip := limit
  607. limit = 30
  608. for {
  609. commits, err := gitRepo.CommitsBetweenLimit(headCommit, mergeBase, limit, skip)
  610. if err != nil {
  611. log.Error("Unable to get commits between: %s %s Error: %v", pr.HeadBranch, pr.MergeBase, err)
  612. return ""
  613. }
  614. if len(commits) == 0 {
  615. break
  616. }
  617. for _, commit := range commits {
  618. authorString := commit.Author.String()
  619. if !authorsMap[authorString] && authorString != posterSig {
  620. authors = append(authors, authorString)
  621. authorsMap[authorString] = true
  622. }
  623. }
  624. skip += limit
  625. }
  626. }
  627. for _, author := range authors {
  628. if _, err := stringBuilder.Write([]byte("Co-authored-by: ")); err != nil {
  629. log.Error("Unable to write to string builder Error: %v", err)
  630. return ""
  631. }
  632. if _, err := stringBuilder.Write([]byte(author)); err != nil {
  633. log.Error("Unable to write to string builder Error: %v", err)
  634. return ""
  635. }
  636. if _, err := stringBuilder.WriteRune('\n'); err != nil {
  637. log.Error("Unable to write to string builder Error: %v", err)
  638. return ""
  639. }
  640. }
  641. return stringBuilder.String()
  642. }
  643. // GetIssuesLastCommitStatus returns a map of issue ID to the most recent commit's latest status
  644. func GetIssuesLastCommitStatus(ctx context.Context, issues models.IssueList) (map[int64]*models.CommitStatus, error) {
  645. _, lastStatus, err := GetIssuesAllCommitStatus(ctx, issues)
  646. return lastStatus, err
  647. }
  648. // GetIssuesAllCommitStatus returns a map of issue ID to a list of all statuses for the most recent commit as well as a map of issue ID to only the commit's latest status
  649. func GetIssuesAllCommitStatus(ctx context.Context, issues models.IssueList) (map[int64][]*models.CommitStatus, map[int64]*models.CommitStatus, error) {
  650. if err := issues.LoadPullRequests(); err != nil {
  651. return nil, nil, err
  652. }
  653. if _, err := issues.LoadRepositories(); err != nil {
  654. return nil, nil, err
  655. }
  656. var (
  657. gitRepos = make(map[int64]*git.Repository)
  658. res = make(map[int64][]*models.CommitStatus)
  659. lastRes = make(map[int64]*models.CommitStatus)
  660. err error
  661. )
  662. defer func() {
  663. for _, gitRepo := range gitRepos {
  664. gitRepo.Close()
  665. }
  666. }()
  667. for _, issue := range issues {
  668. if !issue.IsPull {
  669. continue
  670. }
  671. gitRepo, ok := gitRepos[issue.RepoID]
  672. if !ok {
  673. gitRepo, err = git.OpenRepository(ctx, issue.Repo.RepoPath())
  674. if err != nil {
  675. log.Error("Cannot open git repository %-v for issue #%d[%d]. Error: %v", issue.Repo, issue.Index, issue.ID, err)
  676. continue
  677. }
  678. gitRepos[issue.RepoID] = gitRepo
  679. }
  680. statuses, lastStatus, err := getAllCommitStatus(gitRepo, issue.PullRequest)
  681. if err != nil {
  682. log.Error("getAllCommitStatus: cant get commit statuses of pull [%d]: %v", issue.PullRequest.ID, err)
  683. continue
  684. }
  685. res[issue.PullRequest.ID] = statuses
  686. lastRes[issue.PullRequest.ID] = lastStatus
  687. }
  688. return res, lastRes, nil
  689. }
  690. // getAllCommitStatus get pr's commit statuses.
  691. func getAllCommitStatus(gitRepo *git.Repository, pr *models.PullRequest) (statuses []*models.CommitStatus, lastStatus *models.CommitStatus, err error) {
  692. sha, shaErr := gitRepo.GetRefCommitID(pr.GetGitRefName())
  693. if shaErr != nil {
  694. return nil, nil, shaErr
  695. }
  696. statuses, _, err = models.GetLatestCommitStatus(pr.BaseRepo.ID, sha, db.ListOptions{})
  697. lastStatus = models.CalcCommitStatus(statuses)
  698. return statuses, lastStatus, err
  699. }
  700. // IsHeadEqualWithBranch returns if the commits of branchName are available in pull request head
  701. func IsHeadEqualWithBranch(ctx context.Context, pr *models.PullRequest, branchName string) (bool, error) {
  702. var err error
  703. if err = pr.LoadBaseRepoCtx(ctx); err != nil {
  704. return false, err
  705. }
  706. baseGitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, pr.BaseRepo.RepoPath())
  707. if err != nil {
  708. return false, err
  709. }
  710. defer closer.Close()
  711. baseCommit, err := baseGitRepo.GetBranchCommit(branchName)
  712. if err != nil {
  713. return false, err
  714. }
  715. if err = pr.LoadHeadRepoCtx(ctx); err != nil {
  716. return false, err
  717. }
  718. var headGitRepo *git.Repository
  719. if pr.HeadRepoID == pr.BaseRepoID {
  720. headGitRepo = baseGitRepo
  721. } else {
  722. var closer io.Closer
  723. headGitRepo, closer, err = git.RepositoryFromContextOrOpen(ctx, pr.HeadRepo.RepoPath())
  724. if err != nil {
  725. return false, err
  726. }
  727. defer closer.Close()
  728. }
  729. var headCommit *git.Commit
  730. if pr.Flow == models.PullRequestFlowGithub {
  731. headCommit, err = headGitRepo.GetBranchCommit(pr.HeadBranch)
  732. if err != nil {
  733. return false, err
  734. }
  735. } else {
  736. pr.HeadCommitID, err = baseGitRepo.GetRefCommitID(pr.GetGitRefName())
  737. if err != nil {
  738. return false, err
  739. }
  740. if headCommit, err = baseGitRepo.GetCommit(pr.HeadCommitID); err != nil {
  741. return false, err
  742. }
  743. }
  744. return baseCommit.HasPreviousCommit(headCommit.ID)
  745. }