Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

pull.go 19KB

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