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 25KB

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