You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

pull.go 21 kB

Change target branch for pull request (#6488) * Adds functionality to change target branch of created pull requests Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use const instead of var in JavaScript additions Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Check if branches are equal and if PR already exists before changing target branch Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Make sure to check all commits Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Print error messages for user as error flash message Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Disallow changing target branch of closed or merged pull requests Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Resolve conflicts after merge of upstream/master Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Change order of branch select fields Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes duplicate check Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use ctx.Tr for translations Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Recompile JS Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use correct translation namespace Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Remove redundant if condition Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Moves most change branch logic into pull service Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Completes comment Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Add Ref to ChangesPayload for logging changed target branches instead of creating a new struct Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Revert changes to go.mod Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Directly use createComment method Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return 404 if pull request is not found. Move written check up Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Remove variable declaration Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return client errors on change pull request target errors Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return error in commit.HasPreviousCommit Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds blank line Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Test patch before persisting new target branch Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update patch before testing (not working) Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes patch calls when changeing pull request target Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes unneeded check for base name Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Moves ChangeTargetBranch completely to pull service. Update patch status. Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Set webhook mode after errors were validated Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update PR in one transaction Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Move logic for check if head is equal with branch to pull model Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds missing comment and simplify return Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adjust CreateComment method call Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>
5 years ago
Change target branch for pull request (#6488) * Adds functionality to change target branch of created pull requests Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use const instead of var in JavaScript additions Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Check if branches are equal and if PR already exists before changing target branch Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Make sure to check all commits Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Print error messages for user as error flash message Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Disallow changing target branch of closed or merged pull requests Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Resolve conflicts after merge of upstream/master Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Change order of branch select fields Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes duplicate check Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use ctx.Tr for translations Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Recompile JS Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use correct translation namespace Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Remove redundant if condition Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Moves most change branch logic into pull service Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Completes comment Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Add Ref to ChangesPayload for logging changed target branches instead of creating a new struct Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Revert changes to go.mod Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Directly use createComment method Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return 404 if pull request is not found. Move written check up Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Remove variable declaration Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return client errors on change pull request target errors Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return error in commit.HasPreviousCommit Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds blank line Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Test patch before persisting new target branch Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update patch before testing (not working) Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes patch calls when changeing pull request target Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes unneeded check for base name Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Moves ChangeTargetBranch completely to pull service. Update patch status. Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Set webhook mode after errors were validated Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update PR in one transaction Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Move logic for check if head is equal with branch to pull model Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds missing comment and simplify return Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adjust CreateComment method call Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>
5 years ago
[Enhancement] Allow admin to merge pr with protected file changes (#12078) * [Enhancement] Allow admin to merge pr with protected file changes As tilte, show protected message in diff page and merge box. Signed-off-by: a1012112796 <1012112796@qq.com> * remove unused ver * Update options/locale/locale_en-US.ini Co-authored-by: Cirno the Strongest <1447794+CirnoT@users.noreply.github.com> * Add TrN * Apply suggestions from code review * fix lint * Update options/locale/locale_en-US.ini Co-authored-by: zeripath <art27@cantab.net> * Apply suggestions from code review * move pr proteced files check to TestPatch * Call TestPatch when protected branches settings changed * Apply review suggestion @CirnoT * move to service @lunny * slightly restructure routers/private/hook.go Adds a lot of comments and simplifies the logic Signed-off-by: Andrew Thornton <art27@cantab.net> * placate lint Signed-off-by: Andrew Thornton <art27@cantab.net> * skip duplicate protected files check * fix check logic * slight refactor of TestPatch Signed-off-by: Andrew Thornton <art27@cantab.net> * When checking for protected files changes in TestPatch use the temporary repository Signed-off-by: Andrew Thornton <art27@cantab.net> * fix introduced issue with hook Signed-off-by: Andrew Thornton <art27@cantab.net> * Remove the check on PR index being greater than 0 as it unnecessary Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: techknowlogick <matti@mdranta.net> Co-authored-by: Cirno the Strongest <1447794+CirnoT@users.noreply.github.com> Co-authored-by: zeripath <art27@cantab.net> Co-authored-by: techknowlogick <techknowlogick@gitea.io>
5 years ago
Change target branch for pull request (#6488) * Adds functionality to change target branch of created pull requests Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use const instead of var in JavaScript additions Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Check if branches are equal and if PR already exists before changing target branch Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Make sure to check all commits Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Print error messages for user as error flash message Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Disallow changing target branch of closed or merged pull requests Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Resolve conflicts after merge of upstream/master Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Change order of branch select fields Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes duplicate check Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use ctx.Tr for translations Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Recompile JS Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use correct translation namespace Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Remove redundant if condition Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Moves most change branch logic into pull service Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Completes comment Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Add Ref to ChangesPayload for logging changed target branches instead of creating a new struct Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Revert changes to go.mod Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Directly use createComment method Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return 404 if pull request is not found. Move written check up Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Remove variable declaration Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return client errors on change pull request target errors Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Return error in commit.HasPreviousCommit Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds blank line Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Test patch before persisting new target branch Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update patch before testing (not working) Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes patch calls when changeing pull request target Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes unneeded check for base name Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Moves ChangeTargetBranch completely to pull service. Update patch status. Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Set webhook mode after errors were validated Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Update PR in one transaction Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Move logic for check if head is equal with branch to pull model Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds missing comment and simplify return Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adjust CreateComment method call Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>
5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694
  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. "encoding/json"
  10. "errors"
  11. "fmt"
  12. "strings"
  13. "time"
  14. "code.gitea.io/gitea/models"
  15. "code.gitea.io/gitea/modules/git"
  16. "code.gitea.io/gitea/modules/graceful"
  17. "code.gitea.io/gitea/modules/log"
  18. "code.gitea.io/gitea/modules/notification"
  19. "code.gitea.io/gitea/modules/setting"
  20. issue_service "code.gitea.io/gitea/services/issue"
  21. )
  22. // NewPullRequest creates new pull request with labels for repository.
  23. func NewPullRequest(repo *models.Repository, pull *models.Issue, labelIDs []int64, uuids []string, pr *models.PullRequest, assigneeIDs []int64) error {
  24. if err := TestPatch(pr); err != nil {
  25. return err
  26. }
  27. divergence, err := GetDiverging(pr)
  28. if err != nil {
  29. return err
  30. }
  31. pr.CommitsAhead = divergence.Ahead
  32. pr.CommitsBehind = divergence.Behind
  33. if err := models.NewPullRequest(repo, pull, labelIDs, uuids, pr); err != nil {
  34. return err
  35. }
  36. for _, assigneeID := range assigneeIDs {
  37. if err := issue_service.AddAssigneeIfNotAssigned(pull, pull.Poster, assigneeID); err != nil {
  38. return err
  39. }
  40. }
  41. pr.Issue = pull
  42. pull.PullRequest = pr
  43. if err := PushToBaseRepo(pr); err != nil {
  44. return err
  45. }
  46. mentions, err := pull.FindAndUpdateIssueMentions(models.DefaultDBContext(), pull.Poster, pull.Content)
  47. if err != nil {
  48. return err
  49. }
  50. notification.NotifyNewPullRequest(pr, mentions)
  51. // add first push codes comment
  52. baseGitRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
  53. if err != nil {
  54. return err
  55. }
  56. defer baseGitRepo.Close()
  57. compareInfo, err := baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(),
  58. git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName())
  59. if err != nil {
  60. return err
  61. }
  62. if compareInfo.Commits.Len() > 0 {
  63. data := models.PushActionContent{IsForcePush: false}
  64. data.CommitIDs = make([]string, 0, compareInfo.Commits.Len())
  65. for e := compareInfo.Commits.Back(); e != nil; e = e.Prev() {
  66. data.CommitIDs = append(data.CommitIDs, e.Value.(*git.Commit).ID.String())
  67. }
  68. dataJSON, err := json.Marshal(data)
  69. if err != nil {
  70. return err
  71. }
  72. ops := &models.CreateCommentOptions{
  73. Type: models.CommentTypePullPush,
  74. Doer: pull.Poster,
  75. Repo: repo,
  76. Issue: pr.Issue,
  77. IsForcePush: false,
  78. Content: string(dataJSON),
  79. }
  80. _, _ = models.CreateComment(ops)
  81. }
  82. return nil
  83. }
  84. // ChangeTargetBranch changes the target branch of this pull request, as the given user.
  85. func ChangeTargetBranch(pr *models.PullRequest, doer *models.User, targetBranch string) (err error) {
  86. // Current target branch is already the same
  87. if pr.BaseBranch == targetBranch {
  88. return nil
  89. }
  90. if pr.Issue.IsClosed {
  91. return models.ErrIssueIsClosed{
  92. ID: pr.Issue.ID,
  93. RepoID: pr.Issue.RepoID,
  94. Index: pr.Issue.Index,
  95. }
  96. }
  97. if pr.HasMerged {
  98. return models.ErrPullRequestHasMerged{
  99. ID: pr.ID,
  100. IssueID: pr.Index,
  101. HeadRepoID: pr.HeadRepoID,
  102. BaseRepoID: pr.BaseRepoID,
  103. HeadBranch: pr.HeadBranch,
  104. BaseBranch: pr.BaseBranch,
  105. }
  106. }
  107. // Check if branches are equal
  108. branchesEqual, err := IsHeadEqualWithBranch(pr, targetBranch)
  109. if err != nil {
  110. return err
  111. }
  112. if branchesEqual {
  113. return models.ErrBranchesEqual{
  114. HeadBranchName: pr.HeadBranch,
  115. BaseBranchName: targetBranch,
  116. }
  117. }
  118. // Check if pull request for the new target branch already exists
  119. existingPr, err := models.GetUnmergedPullRequest(pr.HeadRepoID, pr.BaseRepoID, pr.HeadBranch, targetBranch)
  120. if existingPr != nil {
  121. return models.ErrPullRequestAlreadyExists{
  122. ID: existingPr.ID,
  123. IssueID: existingPr.Index,
  124. HeadRepoID: existingPr.HeadRepoID,
  125. BaseRepoID: existingPr.BaseRepoID,
  126. HeadBranch: existingPr.HeadBranch,
  127. BaseBranch: existingPr.BaseBranch,
  128. }
  129. }
  130. if err != nil && !models.IsErrPullRequestNotExist(err) {
  131. return err
  132. }
  133. // Set new target branch
  134. oldBranch := pr.BaseBranch
  135. pr.BaseBranch = targetBranch
  136. // Refresh patch
  137. if err := TestPatch(pr); err != nil {
  138. return err
  139. }
  140. // Update target branch, PR diff and status
  141. // This is the same as checkAndUpdateStatus in check service, but also updates base_branch
  142. if pr.Status == models.PullRequestStatusChecking {
  143. pr.Status = models.PullRequestStatusMergeable
  144. }
  145. // Update Commit Divergence
  146. divergence, err := GetDiverging(pr)
  147. if err != nil {
  148. return err
  149. }
  150. pr.CommitsAhead = divergence.Ahead
  151. pr.CommitsBehind = divergence.Behind
  152. if err := pr.UpdateColsIfNotMerged("merge_base", "status", "conflicted_files", "changed_protected_files", "base_branch", "commits_ahead", "commits_behind"); err != nil {
  153. return err
  154. }
  155. // Create comment
  156. options := &models.CreateCommentOptions{
  157. Type: models.CommentTypeChangeTargetBranch,
  158. Doer: doer,
  159. Repo: pr.Issue.Repo,
  160. Issue: pr.Issue,
  161. OldRef: oldBranch,
  162. NewRef: targetBranch,
  163. }
  164. if _, err = models.CreateComment(options); err != nil {
  165. return fmt.Errorf("CreateChangeTargetBranchComment: %v", err)
  166. }
  167. return nil
  168. }
  169. func checkForInvalidation(requests models.PullRequestList, repoID int64, doer *models.User, branch string) error {
  170. repo, err := models.GetRepositoryByID(repoID)
  171. if err != nil {
  172. return fmt.Errorf("GetRepositoryByID: %v", err)
  173. }
  174. gitRepo, err := git.OpenRepository(repo.RepoPath())
  175. if err != nil {
  176. return fmt.Errorf("git.OpenRepository: %v", err)
  177. }
  178. go func() {
  179. // FIXME: graceful: We need to tell the manager we're doing something...
  180. err := requests.InvalidateCodeComments(doer, gitRepo, branch)
  181. if err != nil {
  182. log.Error("PullRequestList.InvalidateCodeComments: %v", err)
  183. }
  184. gitRepo.Close()
  185. }()
  186. return nil
  187. }
  188. // AddTestPullRequestTask adds new test tasks by given head/base repository and head/base branch,
  189. // and generate new patch for testing as needed.
  190. func AddTestPullRequestTask(doer *models.User, repoID int64, branch string, isSync bool, oldCommitID, newCommitID string) {
  191. log.Trace("AddTestPullRequestTask [head_repo_id: %d, head_branch: %s]: finding pull requests", repoID, branch)
  192. graceful.GetManager().RunWithShutdownContext(func(ctx context.Context) {
  193. // There is no sensible way to shut this down ":-("
  194. // If you don't let it run all the way then you will lose data
  195. // FIXME: graceful: AddTestPullRequestTask needs to become a queue!
  196. prs, err := models.GetUnmergedPullRequestsByHeadInfo(repoID, branch)
  197. if err != nil {
  198. log.Error("Find pull requests [head_repo_id: %d, head_branch: %s]: %v", repoID, branch, err)
  199. return
  200. }
  201. if isSync {
  202. requests := models.PullRequestList(prs)
  203. if err = requests.LoadAttributes(); err != nil {
  204. log.Error("PullRequestList.LoadAttributes: %v", err)
  205. }
  206. if invalidationErr := checkForInvalidation(requests, repoID, doer, branch); invalidationErr != nil {
  207. log.Error("checkForInvalidation: %v", invalidationErr)
  208. }
  209. if err == nil {
  210. for _, pr := range prs {
  211. if newCommitID != "" && newCommitID != git.EmptySHA {
  212. changed, err := checkIfPRContentChanged(pr, oldCommitID, newCommitID)
  213. if err != nil {
  214. log.Error("checkIfPRContentChanged: %v", err)
  215. }
  216. if changed {
  217. // Mark old reviews as stale if diff to mergebase has changed
  218. if err := models.MarkReviewsAsStale(pr.IssueID); err != nil {
  219. log.Error("MarkReviewsAsStale: %v", err)
  220. }
  221. }
  222. if err := models.MarkReviewsAsNotStale(pr.IssueID, newCommitID); err != nil {
  223. log.Error("MarkReviewsAsNotStale: %v", err)
  224. }
  225. divergence, err := GetDiverging(pr)
  226. if err != nil {
  227. log.Error("GetDiverging: %v", err)
  228. } else {
  229. err = pr.UpdateCommitDivergence(divergence.Ahead, divergence.Behind)
  230. if err != nil {
  231. log.Error("UpdateCommitDivergence: %v", err)
  232. }
  233. }
  234. }
  235. pr.Issue.PullRequest = pr
  236. notification.NotifyPullRequestSynchronized(doer, pr)
  237. }
  238. }
  239. }
  240. for _, pr := range prs {
  241. log.Trace("Updating PR[%d]: composing new test task", pr.ID)
  242. if err := PushToBaseRepo(pr); err != nil {
  243. log.Error("PushToBaseRepo: %v", err)
  244. continue
  245. }
  246. AddToTaskQueue(pr)
  247. comment, err := models.CreatePushPullComment(doer, pr, oldCommitID, newCommitID)
  248. if err == nil && comment != nil {
  249. notification.NotifyPullRequestPushCommits(doer, pr, comment)
  250. }
  251. }
  252. log.Trace("AddTestPullRequestTask [base_repo_id: %d, base_branch: %s]: finding pull requests", repoID, branch)
  253. prs, err = models.GetUnmergedPullRequestsByBaseInfo(repoID, branch)
  254. if err != nil {
  255. log.Error("Find pull requests [base_repo_id: %d, base_branch: %s]: %v", repoID, branch, err)
  256. return
  257. }
  258. for _, pr := range prs {
  259. divergence, err := GetDiverging(pr)
  260. if err != nil {
  261. log.Error("GetDiverging: %v", err)
  262. } else {
  263. err = pr.UpdateCommitDivergence(divergence.Ahead, divergence.Behind)
  264. if err != nil {
  265. log.Error("UpdateCommitDivergence: %v", err)
  266. }
  267. }
  268. AddToTaskQueue(pr)
  269. }
  270. })
  271. }
  272. // checkIfPRContentChanged checks if diff to target branch has changed by push
  273. // A commit can be considered to leave the PR untouched if the patch/diff with its merge base is unchanged
  274. func checkIfPRContentChanged(pr *models.PullRequest, oldCommitID, newCommitID string) (hasChanged bool, err error) {
  275. if err = pr.LoadHeadRepo(); err != nil {
  276. return false, fmt.Errorf("LoadHeadRepo: %v", err)
  277. } else if pr.HeadRepo == nil {
  278. // corrupt data assumed changed
  279. return true, nil
  280. }
  281. if err = pr.LoadBaseRepo(); err != nil {
  282. return false, fmt.Errorf("LoadBaseRepo: %v", err)
  283. }
  284. headGitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath())
  285. if err != nil {
  286. return false, fmt.Errorf("OpenRepository: %v", err)
  287. }
  288. defer headGitRepo.Close()
  289. // Add a temporary remote.
  290. tmpRemote := "checkIfPRContentChanged-" + fmt.Sprint(time.Now().UnixNano())
  291. if err = headGitRepo.AddRemote(tmpRemote, pr.BaseRepo.RepoPath(), true); err != nil {
  292. return false, fmt.Errorf("AddRemote: %s/%s-%s: %v", pr.HeadRepo.OwnerName, pr.HeadRepo.Name, tmpRemote, err)
  293. }
  294. defer func() {
  295. if err := headGitRepo.RemoveRemote(tmpRemote); err != nil {
  296. log.Error("checkIfPRContentChanged: RemoveRemote: %s/%s-%s: %v", pr.HeadRepo.OwnerName, pr.HeadRepo.Name, tmpRemote, err)
  297. }
  298. }()
  299. // To synchronize repo and get a base ref
  300. _, base, err := headGitRepo.GetMergeBase(tmpRemote, pr.BaseBranch, pr.HeadBranch)
  301. if err != nil {
  302. return false, fmt.Errorf("GetMergeBase: %v", err)
  303. }
  304. diffBefore := &bytes.Buffer{}
  305. diffAfter := &bytes.Buffer{}
  306. if err := headGitRepo.GetDiffFromMergeBase(base, oldCommitID, diffBefore); err != nil {
  307. // If old commit not found, assume changed.
  308. log.Debug("GetDiffFromMergeBase: %v", err)
  309. return true, nil
  310. }
  311. if err := headGitRepo.GetDiffFromMergeBase(base, newCommitID, diffAfter); err != nil {
  312. // New commit should be found
  313. return false, fmt.Errorf("GetDiffFromMergeBase: %v", err)
  314. }
  315. diffBeforeLines := bufio.NewScanner(diffBefore)
  316. diffAfterLines := bufio.NewScanner(diffAfter)
  317. for diffBeforeLines.Scan() && diffAfterLines.Scan() {
  318. if strings.HasPrefix(diffBeforeLines.Text(), "index") && strings.HasPrefix(diffAfterLines.Text(), "index") {
  319. // file hashes can change without the diff changing
  320. continue
  321. } else if strings.HasPrefix(diffBeforeLines.Text(), "@@") && strings.HasPrefix(diffAfterLines.Text(), "@@") {
  322. // the location of the difference may change
  323. continue
  324. } else if !bytes.Equal(diffBeforeLines.Bytes(), diffAfterLines.Bytes()) {
  325. return true, nil
  326. }
  327. }
  328. if diffBeforeLines.Scan() || diffAfterLines.Scan() {
  329. // Diffs not of equal length
  330. return true, nil
  331. }
  332. return false, nil
  333. }
  334. // PushToBaseRepo pushes commits from branches of head repository to
  335. // corresponding branches of base repository.
  336. // FIXME: Only push branches that are actually updates?
  337. func PushToBaseRepo(pr *models.PullRequest) (err error) {
  338. log.Trace("PushToBaseRepo[%d]: pushing commits to base repo '%s'", pr.BaseRepoID, pr.GetGitRefName())
  339. if err := pr.LoadHeadRepo(); err != nil {
  340. log.Error("Unable to load head repository for PR[%d] Error: %v", pr.ID, err)
  341. return err
  342. }
  343. headRepoPath := pr.HeadRepo.RepoPath()
  344. if err := pr.LoadBaseRepo(); err != nil {
  345. log.Error("Unable to load base repository for PR[%d] Error: %v", pr.ID, err)
  346. return err
  347. }
  348. baseRepoPath := pr.BaseRepo.RepoPath()
  349. if err = pr.LoadIssue(); err != nil {
  350. return fmt.Errorf("unable to load issue %d for pr %d: %v", pr.IssueID, pr.ID, err)
  351. }
  352. if err = pr.Issue.LoadPoster(); err != nil {
  353. return fmt.Errorf("unable to load poster %d for pr %d: %v", pr.Issue.PosterID, pr.ID, err)
  354. }
  355. gitRefName := pr.GetGitRefName()
  356. if err := git.Push(headRepoPath, git.PushOptions{
  357. Remote: baseRepoPath,
  358. Branch: pr.HeadBranch + ":" + gitRefName,
  359. Force: true,
  360. // Use InternalPushingEnvironment here because we know that pre-receive and post-receive do not run on a refs/pulls/...
  361. Env: models.InternalPushingEnvironment(pr.Issue.Poster, pr.BaseRepo),
  362. }); err != nil {
  363. if git.IsErrPushOutOfDate(err) {
  364. // This should not happen as we're using force!
  365. 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)
  366. return err
  367. } else if git.IsErrPushRejected(err) {
  368. rejectErr := err.(*git.ErrPushRejected)
  369. 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)
  370. return err
  371. }
  372. 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)
  373. return fmt.Errorf("Push: %s:%s %s:%s %v", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), gitRefName, err)
  374. }
  375. return nil
  376. }
  377. type errlist []error
  378. func (errs errlist) Error() string {
  379. if len(errs) > 0 {
  380. var buf strings.Builder
  381. for i, err := range errs {
  382. if i > 0 {
  383. buf.WriteString(", ")
  384. }
  385. buf.WriteString(err.Error())
  386. }
  387. return buf.String()
  388. }
  389. return ""
  390. }
  391. // CloseBranchPulls close all the pull requests who's head branch is the branch
  392. func CloseBranchPulls(doer *models.User, repoID int64, branch string) error {
  393. prs, err := models.GetUnmergedPullRequestsByHeadInfo(repoID, branch)
  394. if err != nil {
  395. return err
  396. }
  397. prs2, err := models.GetUnmergedPullRequestsByBaseInfo(repoID, branch)
  398. if err != nil {
  399. return err
  400. }
  401. prs = append(prs, prs2...)
  402. if err := models.PullRequestList(prs).LoadAttributes(); err != nil {
  403. return err
  404. }
  405. var errs errlist
  406. for _, pr := range prs {
  407. if err = issue_service.ChangeStatus(pr.Issue, doer, true); err != nil && !models.IsErrPullWasClosed(err) {
  408. errs = append(errs, err)
  409. }
  410. }
  411. if len(errs) > 0 {
  412. return errs
  413. }
  414. return nil
  415. }
  416. // CloseRepoBranchesPulls close all pull requests which head branches are in the given repository
  417. func CloseRepoBranchesPulls(doer *models.User, repo *models.Repository) error {
  418. branches, err := git.GetBranchesByPath(repo.RepoPath())
  419. if err != nil {
  420. return err
  421. }
  422. var errs errlist
  423. for _, branch := range branches {
  424. prs, err := models.GetUnmergedPullRequestsByHeadInfo(repo.ID, branch.Name)
  425. if err != nil {
  426. return err
  427. }
  428. if err = models.PullRequestList(prs).LoadAttributes(); err != nil {
  429. return err
  430. }
  431. for _, pr := range prs {
  432. if err = issue_service.ChangeStatus(pr.Issue, doer, true); err != nil && !models.IsErrPullWasClosed(err) {
  433. errs = append(errs, err)
  434. }
  435. }
  436. }
  437. if len(errs) > 0 {
  438. return errs
  439. }
  440. return nil
  441. }
  442. // GetSquashMergeCommitMessages returns the commit messages between head and merge base (if there is one)
  443. func GetSquashMergeCommitMessages(pr *models.PullRequest) string {
  444. if err := pr.LoadIssue(); err != nil {
  445. log.Error("Cannot load issue %d for PR id %d: Error: %v", pr.IssueID, pr.ID, err)
  446. return ""
  447. }
  448. if err := pr.Issue.LoadPoster(); err != nil {
  449. log.Error("Cannot load poster %d for pr id %d, index %d Error: %v", pr.Issue.PosterID, pr.ID, pr.Index, err)
  450. return ""
  451. }
  452. if pr.HeadRepo == nil {
  453. var err error
  454. pr.HeadRepo, err = models.GetRepositoryByID(pr.HeadRepoID)
  455. if err != nil {
  456. log.Error("GetRepositoryById[%d]: %v", pr.HeadRepoID, err)
  457. return ""
  458. }
  459. }
  460. gitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath())
  461. if err != nil {
  462. log.Error("Unable to open head repository: Error: %v", err)
  463. return ""
  464. }
  465. defer gitRepo.Close()
  466. headCommit, err := gitRepo.GetBranchCommit(pr.HeadBranch)
  467. if err != nil {
  468. log.Error("Unable to get head commit: %s Error: %v", pr.HeadBranch, err)
  469. return ""
  470. }
  471. mergeBase, err := gitRepo.GetCommit(pr.MergeBase)
  472. if err != nil {
  473. log.Error("Unable to get merge base commit: %s Error: %v", pr.MergeBase, err)
  474. return ""
  475. }
  476. limit := setting.Repository.PullRequest.DefaultMergeMessageCommitsLimit
  477. list, err := gitRepo.CommitsBetweenLimit(headCommit, mergeBase, limit, 0)
  478. if err != nil {
  479. log.Error("Unable to get commits between: %s %s Error: %v", pr.HeadBranch, pr.MergeBase, err)
  480. return ""
  481. }
  482. posterSig := pr.Issue.Poster.NewGitSig().String()
  483. authorsMap := map[string]bool{}
  484. authors := make([]string, 0, list.Len())
  485. stringBuilder := strings.Builder{}
  486. stringBuilder.WriteString(pr.Issue.Content)
  487. if stringBuilder.Len() > 0 {
  488. stringBuilder.WriteRune('\n')
  489. stringBuilder.WriteRune('\n')
  490. }
  491. // commits list is in reverse chronological order
  492. element := list.Back()
  493. for element != nil {
  494. commit := element.Value.(*git.Commit)
  495. authorString := commit.Author.String()
  496. if !authorsMap[authorString] && authorString != posterSig {
  497. authors = append(authors, authorString)
  498. authorsMap[authorString] = true
  499. }
  500. element = element.Prev()
  501. }
  502. // Consider collecting the remaining authors
  503. if limit >= 0 && setting.Repository.PullRequest.DefaultMergeMessageAllAuthors {
  504. skip := limit
  505. limit = 30
  506. for {
  507. list, err := gitRepo.CommitsBetweenLimit(headCommit, mergeBase, limit, skip)
  508. if err != nil {
  509. log.Error("Unable to get commits between: %s %s Error: %v", pr.HeadBranch, pr.MergeBase, err)
  510. return ""
  511. }
  512. if list.Len() == 0 {
  513. break
  514. }
  515. element := list.Front()
  516. for element != nil {
  517. commit := element.Value.(*git.Commit)
  518. authorString := commit.Author.String()
  519. if !authorsMap[authorString] && authorString != posterSig {
  520. authors = append(authors, authorString)
  521. authorsMap[authorString] = true
  522. }
  523. element = element.Next()
  524. }
  525. skip += limit
  526. }
  527. }
  528. if len(authors) > 0 {
  529. if _, err := stringBuilder.WriteRune('\n'); err != nil {
  530. log.Error("Unable to write to string builder Error: %v", err)
  531. return ""
  532. }
  533. }
  534. for _, author := range authors {
  535. if _, err := stringBuilder.Write([]byte("Co-authored-by: ")); err != nil {
  536. log.Error("Unable to write to string builder Error: %v", err)
  537. return ""
  538. }
  539. if _, err := stringBuilder.Write([]byte(author)); err != nil {
  540. log.Error("Unable to write to string builder Error: %v", err)
  541. return ""
  542. }
  543. if _, err := stringBuilder.WriteRune('\n'); err != nil {
  544. log.Error("Unable to write to string builder Error: %v", err)
  545. return ""
  546. }
  547. }
  548. return stringBuilder.String()
  549. }
  550. // GetLastCommitStatus returns list of commit statuses for latest commit on this pull request.
  551. func GetLastCommitStatus(pr *models.PullRequest) (status []*models.CommitStatus, err error) {
  552. if err = pr.LoadBaseRepo(); err != nil {
  553. return nil, err
  554. }
  555. gitRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
  556. if err != nil {
  557. return nil, err
  558. }
  559. defer gitRepo.Close()
  560. compareInfo, err := gitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.MergeBase, pr.GetGitRefName())
  561. if err != nil {
  562. return nil, err
  563. }
  564. if compareInfo.Commits.Len() == 0 {
  565. return nil, errors.New("pull request has no commits")
  566. }
  567. sha := compareInfo.Commits.Front().Value.(*git.Commit).ID.String()
  568. statusList, err := models.GetLatestCommitStatus(pr.BaseRepo.ID, sha, models.ListOptions{})
  569. if err != nil {
  570. return nil, err
  571. }
  572. return statusList, nil
  573. }
  574. // IsHeadEqualWithBranch returns if the commits of branchName are available in pull request head
  575. func IsHeadEqualWithBranch(pr *models.PullRequest, branchName string) (bool, error) {
  576. var err error
  577. if err = pr.LoadBaseRepo(); err != nil {
  578. return false, err
  579. }
  580. baseGitRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
  581. if err != nil {
  582. return false, err
  583. }
  584. defer baseGitRepo.Close()
  585. baseCommit, err := baseGitRepo.GetBranchCommit(branchName)
  586. if err != nil {
  587. return false, err
  588. }
  589. if err = pr.LoadHeadRepo(); err != nil {
  590. return false, err
  591. }
  592. headGitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath())
  593. if err != nil {
  594. return false, err
  595. }
  596. defer headGitRepo.Close()
  597. headCommit, err := headGitRepo.GetBranchCommit(pr.HeadBranch)
  598. if err != nil {
  599. return false, err
  600. }
  601. return baseCommit.HasPreviousCommit(headCommit.ID)
  602. }