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 19 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>
6 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>
6 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>
6 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  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. "os"
  11. "path"
  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. "github.com/unknwon/com"
  22. )
  23. // NewPullRequest creates new pull request with labels for repository.
  24. func NewPullRequest(repo *models.Repository, pull *models.Issue, labelIDs []int64, uuids []string, pr *models.PullRequest, assigneeIDs []int64) error {
  25. if err := TestPatch(pr); err != nil {
  26. return err
  27. }
  28. if err := models.NewPullRequest(repo, pull, labelIDs, uuids, pr); err != nil {
  29. return err
  30. }
  31. for _, assigneeID := range assigneeIDs {
  32. if err := issue_service.AddAssigneeIfNotAssigned(pull, pull.Poster, assigneeID); err != nil {
  33. return err
  34. }
  35. }
  36. pr.Issue = pull
  37. pull.PullRequest = pr
  38. if err := PushToBaseRepo(pr); err != nil {
  39. return err
  40. }
  41. notification.NotifyNewPullRequest(pr)
  42. return nil
  43. }
  44. // ChangeTargetBranch changes the target branch of this pull request, as the given user.
  45. func ChangeTargetBranch(pr *models.PullRequest, doer *models.User, targetBranch string) (err error) {
  46. // Current target branch is already the same
  47. if pr.BaseBranch == targetBranch {
  48. return nil
  49. }
  50. if pr.Issue.IsClosed {
  51. return models.ErrIssueIsClosed{
  52. ID: pr.Issue.ID,
  53. RepoID: pr.Issue.RepoID,
  54. Index: pr.Issue.Index,
  55. }
  56. }
  57. if pr.HasMerged {
  58. return models.ErrPullRequestHasMerged{
  59. ID: pr.ID,
  60. IssueID: pr.Index,
  61. HeadRepoID: pr.HeadRepoID,
  62. BaseRepoID: pr.BaseRepoID,
  63. HeadBranch: pr.HeadBranch,
  64. BaseBranch: pr.BaseBranch,
  65. }
  66. }
  67. // Check if branches are equal
  68. branchesEqual, err := IsHeadEqualWithBranch(pr, targetBranch)
  69. if err != nil {
  70. return err
  71. }
  72. if branchesEqual {
  73. return models.ErrBranchesEqual{
  74. HeadBranchName: pr.HeadBranch,
  75. BaseBranchName: targetBranch,
  76. }
  77. }
  78. // Check if pull request for the new target branch already exists
  79. existingPr, err := models.GetUnmergedPullRequest(pr.HeadRepoID, pr.BaseRepoID, pr.HeadBranch, targetBranch)
  80. if existingPr != nil {
  81. return models.ErrPullRequestAlreadyExists{
  82. ID: existingPr.ID,
  83. IssueID: existingPr.Index,
  84. HeadRepoID: existingPr.HeadRepoID,
  85. BaseRepoID: existingPr.BaseRepoID,
  86. HeadBranch: existingPr.HeadBranch,
  87. BaseBranch: existingPr.BaseBranch,
  88. }
  89. }
  90. if err != nil && !models.IsErrPullRequestNotExist(err) {
  91. return err
  92. }
  93. // Set new target branch
  94. oldBranch := pr.BaseBranch
  95. pr.BaseBranch = targetBranch
  96. // Refresh patch
  97. if err := TestPatch(pr); err != nil {
  98. return err
  99. }
  100. // Update target branch, PR diff and status
  101. // This is the same as checkAndUpdateStatus in check service, but also updates base_branch
  102. if pr.Status == models.PullRequestStatusChecking {
  103. pr.Status = models.PullRequestStatusMergeable
  104. }
  105. if err := pr.UpdateColsIfNotMerged("merge_base", "status", "conflicted_files", "base_branch"); err != nil {
  106. return err
  107. }
  108. // Create comment
  109. options := &models.CreateCommentOptions{
  110. Type: models.CommentTypeChangeTargetBranch,
  111. Doer: doer,
  112. Repo: pr.Issue.Repo,
  113. Issue: pr.Issue,
  114. OldRef: oldBranch,
  115. NewRef: targetBranch,
  116. }
  117. if _, err = models.CreateComment(options); err != nil {
  118. return fmt.Errorf("CreateChangeTargetBranchComment: %v", err)
  119. }
  120. return nil
  121. }
  122. func checkForInvalidation(requests models.PullRequestList, repoID int64, doer *models.User, branch string) error {
  123. repo, err := models.GetRepositoryByID(repoID)
  124. if err != nil {
  125. return fmt.Errorf("GetRepositoryByID: %v", err)
  126. }
  127. gitRepo, err := git.OpenRepository(repo.RepoPath())
  128. if err != nil {
  129. return fmt.Errorf("git.OpenRepository: %v", err)
  130. }
  131. go func() {
  132. // FIXME: graceful: We need to tell the manager we're doing something...
  133. err := requests.InvalidateCodeComments(doer, gitRepo, branch)
  134. if err != nil {
  135. log.Error("PullRequestList.InvalidateCodeComments: %v", err)
  136. }
  137. gitRepo.Close()
  138. }()
  139. return nil
  140. }
  141. func addHeadRepoTasks(prs []*models.PullRequest) {
  142. for _, pr := range prs {
  143. log.Trace("addHeadRepoTasks[%d]: composing new test task", pr.ID)
  144. if err := PushToBaseRepo(pr); err != nil {
  145. log.Error("PushToBaseRepo: %v", err)
  146. continue
  147. }
  148. AddToTaskQueue(pr)
  149. }
  150. }
  151. // AddTestPullRequestTask adds new test tasks by given head/base repository and head/base branch,
  152. // and generate new patch for testing as needed.
  153. func AddTestPullRequestTask(doer *models.User, repoID int64, branch string, isSync bool, oldCommitID, newCommitID string) {
  154. log.Trace("AddTestPullRequestTask [head_repo_id: %d, head_branch: %s]: finding pull requests", repoID, branch)
  155. graceful.GetManager().RunWithShutdownContext(func(ctx context.Context) {
  156. // There is no sensible way to shut this down ":-("
  157. // If you don't let it run all the way then you will lose data
  158. // FIXME: graceful: AddTestPullRequestTask needs to become a queue!
  159. prs, err := models.GetUnmergedPullRequestsByHeadInfo(repoID, branch)
  160. if err != nil {
  161. log.Error("Find pull requests [head_repo_id: %d, head_branch: %s]: %v", repoID, branch, err)
  162. return
  163. }
  164. if isSync {
  165. requests := models.PullRequestList(prs)
  166. if err = requests.LoadAttributes(); err != nil {
  167. log.Error("PullRequestList.LoadAttributes: %v", err)
  168. }
  169. if invalidationErr := checkForInvalidation(requests, repoID, doer, branch); invalidationErr != nil {
  170. log.Error("checkForInvalidation: %v", invalidationErr)
  171. }
  172. if err == nil {
  173. for _, pr := range prs {
  174. if newCommitID != "" && newCommitID != git.EmptySHA {
  175. changed, err := checkIfPRContentChanged(pr, oldCommitID, newCommitID)
  176. if err != nil {
  177. log.Error("checkIfPRContentChanged: %v", err)
  178. }
  179. if changed {
  180. // Mark old reviews as stale if diff to mergebase has changed
  181. if err := models.MarkReviewsAsStale(pr.IssueID); err != nil {
  182. log.Error("MarkReviewsAsStale: %v", err)
  183. }
  184. }
  185. if err := models.MarkReviewsAsNotStale(pr.IssueID, newCommitID); err != nil {
  186. log.Error("MarkReviewsAsNotStale: %v", err)
  187. }
  188. }
  189. pr.Issue.PullRequest = pr
  190. notification.NotifyPullRequestSynchronized(doer, pr)
  191. }
  192. }
  193. }
  194. addHeadRepoTasks(prs)
  195. log.Trace("AddTestPullRequestTask [base_repo_id: %d, base_branch: %s]: finding pull requests", repoID, branch)
  196. prs, err = models.GetUnmergedPullRequestsByBaseInfo(repoID, branch)
  197. if err != nil {
  198. log.Error("Find pull requests [base_repo_id: %d, base_branch: %s]: %v", repoID, branch, err)
  199. return
  200. }
  201. for _, pr := range prs {
  202. AddToTaskQueue(pr)
  203. }
  204. })
  205. }
  206. // checkIfPRContentChanged checks if diff to target branch has changed by push
  207. // A commit can be considered to leave the PR untouched if the patch/diff with its merge base is unchanged
  208. func checkIfPRContentChanged(pr *models.PullRequest, oldCommitID, newCommitID string) (hasChanged bool, err error) {
  209. if err = pr.LoadHeadRepo(); err != nil {
  210. return false, fmt.Errorf("LoadHeadRepo: %v", err)
  211. } else if pr.HeadRepo == nil {
  212. // corrupt data assumed changed
  213. return true, nil
  214. }
  215. if err = pr.LoadBaseRepo(); err != nil {
  216. return false, fmt.Errorf("LoadBaseRepo: %v", err)
  217. }
  218. headGitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath())
  219. if err != nil {
  220. return false, fmt.Errorf("OpenRepository: %v", err)
  221. }
  222. defer headGitRepo.Close()
  223. // Add a temporary remote.
  224. tmpRemote := "checkIfPRContentChanged-" + com.ToStr(time.Now().UnixNano())
  225. if err = headGitRepo.AddRemote(tmpRemote, pr.BaseRepo.RepoPath(), true); err != nil {
  226. return false, fmt.Errorf("AddRemote: %s/%s-%s: %v", pr.HeadRepo.OwnerName, pr.HeadRepo.Name, tmpRemote, err)
  227. }
  228. defer func() {
  229. if err := headGitRepo.RemoveRemote(tmpRemote); err != nil {
  230. log.Error("checkIfPRContentChanged: RemoveRemote: %s/%s-%s: %v", pr.HeadRepo.OwnerName, pr.HeadRepo.Name, tmpRemote, err)
  231. }
  232. }()
  233. // To synchronize repo and get a base ref
  234. _, base, err := headGitRepo.GetMergeBase(tmpRemote, pr.BaseBranch, pr.HeadBranch)
  235. if err != nil {
  236. return false, fmt.Errorf("GetMergeBase: %v", err)
  237. }
  238. diffBefore := &bytes.Buffer{}
  239. diffAfter := &bytes.Buffer{}
  240. if err := headGitRepo.GetDiffFromMergeBase(base, oldCommitID, diffBefore); err != nil {
  241. // If old commit not found, assume changed.
  242. log.Debug("GetDiffFromMergeBase: %v", err)
  243. return true, nil
  244. }
  245. if err := headGitRepo.GetDiffFromMergeBase(base, newCommitID, diffAfter); err != nil {
  246. // New commit should be found
  247. return false, fmt.Errorf("GetDiffFromMergeBase: %v", err)
  248. }
  249. diffBeforeLines := bufio.NewScanner(diffBefore)
  250. diffAfterLines := bufio.NewScanner(diffAfter)
  251. for diffBeforeLines.Scan() && diffAfterLines.Scan() {
  252. if strings.HasPrefix(diffBeforeLines.Text(), "index") && strings.HasPrefix(diffAfterLines.Text(), "index") {
  253. // file hashes can change without the diff changing
  254. continue
  255. } else if strings.HasPrefix(diffBeforeLines.Text(), "@@") && strings.HasPrefix(diffAfterLines.Text(), "@@") {
  256. // the location of the difference may change
  257. continue
  258. } else if !bytes.Equal(diffBeforeLines.Bytes(), diffAfterLines.Bytes()) {
  259. return true, nil
  260. }
  261. }
  262. if diffBeforeLines.Scan() || diffAfterLines.Scan() {
  263. // Diffs not of equal length
  264. return true, nil
  265. }
  266. return false, nil
  267. }
  268. // PushToBaseRepo pushes commits from branches of head repository to
  269. // corresponding branches of base repository.
  270. // FIXME: Only push branches that are actually updates?
  271. func PushToBaseRepo(pr *models.PullRequest) (err error) {
  272. log.Trace("PushToBaseRepo[%d]: pushing commits to base repo '%s'", pr.BaseRepoID, pr.GetGitRefName())
  273. // Clone base repo.
  274. tmpBasePath, err := models.CreateTemporaryPath("pull")
  275. if err != nil {
  276. log.Error("CreateTemporaryPath: %v", err)
  277. return err
  278. }
  279. defer func() {
  280. err := models.RemoveTemporaryPath(tmpBasePath)
  281. if err != nil {
  282. log.Error("Error whilst removing temporary path: %s Error: %v", tmpBasePath, err)
  283. }
  284. }()
  285. if err := pr.LoadHeadRepo(); err != nil {
  286. log.Error("Unable to load head repository for PR[%d] Error: %v", pr.ID, err)
  287. return err
  288. }
  289. headRepoPath := pr.HeadRepo.RepoPath()
  290. if err := git.Clone(headRepoPath, tmpBasePath, git.CloneRepoOptions{
  291. Bare: true,
  292. Shared: true,
  293. Branch: pr.HeadBranch,
  294. Quiet: true,
  295. }); err != nil {
  296. log.Error("git clone tmpBasePath: %v", err)
  297. return err
  298. }
  299. gitRepo, err := git.OpenRepository(tmpBasePath)
  300. if err != nil {
  301. return fmt.Errorf("OpenRepository: %v", err)
  302. }
  303. if err := pr.LoadBaseRepo(); err != nil {
  304. log.Error("Unable to load base repository for PR[%d] Error: %v", pr.ID, err)
  305. return err
  306. }
  307. if err := gitRepo.AddRemote("base", pr.BaseRepo.RepoPath(), false); err != nil {
  308. return fmt.Errorf("tmpGitRepo.AddRemote: %v", err)
  309. }
  310. defer gitRepo.Close()
  311. headFile := pr.GetGitRefName()
  312. // Remove head in case there is a conflict.
  313. file := path.Join(pr.BaseRepo.RepoPath(), headFile)
  314. _ = os.Remove(file)
  315. if err = pr.LoadIssue(); err != nil {
  316. return fmt.Errorf("unable to load issue %d for pr %d: %v", pr.IssueID, pr.ID, err)
  317. }
  318. if err = pr.Issue.LoadPoster(); err != nil {
  319. return fmt.Errorf("unable to load poster %d for pr %d: %v", pr.Issue.PosterID, pr.ID, err)
  320. }
  321. if err = git.Push(tmpBasePath, git.PushOptions{
  322. Remote: "base",
  323. Branch: fmt.Sprintf("%s:%s", pr.HeadBranch, headFile),
  324. Force: true,
  325. // Use InternalPushingEnvironment here because we know that pre-receive and post-receive do not run on a refs/pulls/...
  326. Env: models.InternalPushingEnvironment(pr.Issue.Poster, pr.BaseRepo),
  327. }); err != nil {
  328. return fmt.Errorf("Push: %s:%s %s:%s %v", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), headFile, err)
  329. }
  330. return nil
  331. }
  332. type errlist []error
  333. func (errs errlist) Error() string {
  334. if len(errs) > 0 {
  335. var buf strings.Builder
  336. for i, err := range errs {
  337. if i > 0 {
  338. buf.WriteString(", ")
  339. }
  340. buf.WriteString(err.Error())
  341. }
  342. return buf.String()
  343. }
  344. return ""
  345. }
  346. // CloseBranchPulls close all the pull requests who's head branch is the branch
  347. func CloseBranchPulls(doer *models.User, repoID int64, branch string) error {
  348. prs, err := models.GetUnmergedPullRequestsByHeadInfo(repoID, branch)
  349. if err != nil {
  350. return err
  351. }
  352. prs2, err := models.GetUnmergedPullRequestsByBaseInfo(repoID, branch)
  353. if err != nil {
  354. return err
  355. }
  356. prs = append(prs, prs2...)
  357. if err := models.PullRequestList(prs).LoadAttributes(); err != nil {
  358. return err
  359. }
  360. var errs errlist
  361. for _, pr := range prs {
  362. if err = issue_service.ChangeStatus(pr.Issue, doer, true); err != nil && !models.IsErrPullWasClosed(err) {
  363. errs = append(errs, err)
  364. }
  365. }
  366. if len(errs) > 0 {
  367. return errs
  368. }
  369. return nil
  370. }
  371. // CloseRepoBranchesPulls close all pull requests which head branches are in the given repository
  372. func CloseRepoBranchesPulls(doer *models.User, repo *models.Repository) error {
  373. branches, err := git.GetBranchesByPath(repo.RepoPath())
  374. if err != nil {
  375. return err
  376. }
  377. var errs errlist
  378. for _, branch := range branches {
  379. prs, err := models.GetUnmergedPullRequestsByHeadInfo(repo.ID, branch.Name)
  380. if err != nil {
  381. return err
  382. }
  383. if err = models.PullRequestList(prs).LoadAttributes(); err != nil {
  384. return err
  385. }
  386. for _, pr := range prs {
  387. if err = issue_service.ChangeStatus(pr.Issue, doer, true); err != nil && !models.IsErrPullWasClosed(err) {
  388. errs = append(errs, err)
  389. }
  390. }
  391. }
  392. if len(errs) > 0 {
  393. return errs
  394. }
  395. return nil
  396. }
  397. // GetCommitMessages returns the commit messages between head and merge base (if there is one)
  398. func GetCommitMessages(pr *models.PullRequest) string {
  399. if err := pr.LoadIssue(); err != nil {
  400. log.Error("Cannot load issue %d for PR id %d: Error: %v", pr.IssueID, pr.ID, err)
  401. return ""
  402. }
  403. if err := pr.Issue.LoadPoster(); err != nil {
  404. log.Error("Cannot load poster %d for pr id %d, index %d Error: %v", pr.Issue.PosterID, pr.ID, pr.Index, err)
  405. return ""
  406. }
  407. if pr.HeadRepo == nil {
  408. var err error
  409. pr.HeadRepo, err = models.GetRepositoryByID(pr.HeadRepoID)
  410. if err != nil {
  411. log.Error("GetRepositoryById[%d]: %v", pr.HeadRepoID, err)
  412. return ""
  413. }
  414. }
  415. gitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath())
  416. if err != nil {
  417. log.Error("Unable to open head repository: Error: %v", err)
  418. return ""
  419. }
  420. defer gitRepo.Close()
  421. headCommit, err := gitRepo.GetBranchCommit(pr.HeadBranch)
  422. if err != nil {
  423. log.Error("Unable to get head commit: %s Error: %v", pr.HeadBranch, err)
  424. return ""
  425. }
  426. mergeBase, err := gitRepo.GetCommit(pr.MergeBase)
  427. if err != nil {
  428. log.Error("Unable to get merge base commit: %s Error: %v", pr.MergeBase, err)
  429. return ""
  430. }
  431. limit := setting.Repository.PullRequest.DefaultMergeMessageCommitsLimit
  432. list, err := gitRepo.CommitsBetweenLimit(headCommit, mergeBase, limit, 0)
  433. if err != nil {
  434. log.Error("Unable to get commits between: %s %s Error: %v", pr.HeadBranch, pr.MergeBase, err)
  435. return ""
  436. }
  437. maxSize := setting.Repository.PullRequest.DefaultMergeMessageSize
  438. posterSig := pr.Issue.Poster.NewGitSig().String()
  439. authorsMap := map[string]bool{}
  440. authors := make([]string, 0, list.Len())
  441. stringBuilder := strings.Builder{}
  442. element := list.Front()
  443. for element != nil {
  444. commit := element.Value.(*git.Commit)
  445. if maxSize < 0 || stringBuilder.Len() < maxSize {
  446. toWrite := []byte(commit.CommitMessage)
  447. if len(toWrite) > maxSize-stringBuilder.Len() && maxSize > -1 {
  448. toWrite = append(toWrite[:maxSize-stringBuilder.Len()], "..."...)
  449. }
  450. if _, err := stringBuilder.Write(toWrite); err != nil {
  451. log.Error("Unable to write commit message Error: %v", err)
  452. return ""
  453. }
  454. if _, err := stringBuilder.WriteRune('\n'); err != nil {
  455. log.Error("Unable to write commit message Error: %v", err)
  456. return ""
  457. }
  458. }
  459. authorString := commit.Author.String()
  460. if !authorsMap[authorString] && authorString != posterSig {
  461. authors = append(authors, authorString)
  462. authorsMap[authorString] = true
  463. }
  464. element = element.Next()
  465. }
  466. // Consider collecting the remaining authors
  467. if limit >= 0 && setting.Repository.PullRequest.DefaultMergeMessageAllAuthors {
  468. skip := limit
  469. limit = 30
  470. for {
  471. list, err := gitRepo.CommitsBetweenLimit(headCommit, mergeBase, limit, skip)
  472. if err != nil {
  473. log.Error("Unable to get commits between: %s %s Error: %v", pr.HeadBranch, pr.MergeBase, err)
  474. return ""
  475. }
  476. if list.Len() == 0 {
  477. break
  478. }
  479. element := list.Front()
  480. for element != nil {
  481. commit := element.Value.(*git.Commit)
  482. authorString := commit.Author.String()
  483. if !authorsMap[authorString] && authorString != posterSig {
  484. authors = append(authors, authorString)
  485. authorsMap[authorString] = true
  486. }
  487. element = element.Next()
  488. }
  489. }
  490. }
  491. if len(authors) > 0 {
  492. if _, err := stringBuilder.WriteRune('\n'); err != nil {
  493. log.Error("Unable to write to string builder Error: %v", err)
  494. return ""
  495. }
  496. }
  497. for _, author := range authors {
  498. if _, err := stringBuilder.Write([]byte("Co-authored-by: ")); err != nil {
  499. log.Error("Unable to write to string builder Error: %v", err)
  500. return ""
  501. }
  502. if _, err := stringBuilder.Write([]byte(author)); err != nil {
  503. log.Error("Unable to write to string builder Error: %v", err)
  504. return ""
  505. }
  506. if _, err := stringBuilder.WriteRune('\n'); err != nil {
  507. log.Error("Unable to write to string builder Error: %v", err)
  508. return ""
  509. }
  510. }
  511. return stringBuilder.String()
  512. }
  513. // GetLastCommitStatus returns the last commit status for this pull request.
  514. func GetLastCommitStatus(pr *models.PullRequest) (status *models.CommitStatus, err error) {
  515. if err = pr.LoadHeadRepo(); err != nil {
  516. return nil, err
  517. }
  518. if pr.HeadRepo == nil {
  519. return nil, models.ErrPullRequestHeadRepoMissing{ID: pr.ID, HeadRepoID: pr.HeadRepoID}
  520. }
  521. headGitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath())
  522. if err != nil {
  523. return nil, err
  524. }
  525. defer headGitRepo.Close()
  526. lastCommitID, err := headGitRepo.GetBranchCommitID(pr.HeadBranch)
  527. if err != nil {
  528. return nil, err
  529. }
  530. err = pr.LoadBaseRepo()
  531. if err != nil {
  532. return nil, err
  533. }
  534. statusList, err := models.GetLatestCommitStatus(pr.BaseRepo, lastCommitID, 0)
  535. if err != nil {
  536. return nil, err
  537. }
  538. return models.CalcCommitStatus(statusList), nil
  539. }
  540. // IsHeadEqualWithBranch returns if the commits of branchName are available in pull request head
  541. func IsHeadEqualWithBranch(pr *models.PullRequest, branchName string) (bool, error) {
  542. var err error
  543. if err = pr.LoadBaseRepo(); err != nil {
  544. return false, err
  545. }
  546. baseGitRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
  547. if err != nil {
  548. return false, err
  549. }
  550. baseCommit, err := baseGitRepo.GetBranchCommit(branchName)
  551. if err != nil {
  552. return false, err
  553. }
  554. if err = pr.LoadHeadRepo(); err != nil {
  555. return false, err
  556. }
  557. headGitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath())
  558. if err != nil {
  559. return false, err
  560. }
  561. headCommit, err := headGitRepo.GetBranchCommit(pr.HeadBranch)
  562. if err != nil {
  563. return false, err
  564. }
  565. return baseCommit.HasPreviousCommit(headCommit.ID)
  566. }