gitea源码

merge.go 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728
  1. // Copyright 2019 The Gitea Authors.
  2. // All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package pull
  5. import (
  6. "context"
  7. "errors"
  8. "fmt"
  9. "maps"
  10. "os"
  11. "path/filepath"
  12. "regexp"
  13. "strconv"
  14. "strings"
  15. "unicode"
  16. "code.gitea.io/gitea/models/db"
  17. git_model "code.gitea.io/gitea/models/git"
  18. issues_model "code.gitea.io/gitea/models/issues"
  19. access_model "code.gitea.io/gitea/models/perm/access"
  20. pull_model "code.gitea.io/gitea/models/pull"
  21. repo_model "code.gitea.io/gitea/models/repo"
  22. "code.gitea.io/gitea/models/unit"
  23. user_model "code.gitea.io/gitea/models/user"
  24. "code.gitea.io/gitea/modules/cache"
  25. "code.gitea.io/gitea/modules/git"
  26. "code.gitea.io/gitea/modules/git/gitcmd"
  27. "code.gitea.io/gitea/modules/globallock"
  28. "code.gitea.io/gitea/modules/httplib"
  29. "code.gitea.io/gitea/modules/log"
  30. "code.gitea.io/gitea/modules/references"
  31. repo_module "code.gitea.io/gitea/modules/repository"
  32. "code.gitea.io/gitea/modules/setting"
  33. "code.gitea.io/gitea/modules/timeutil"
  34. "code.gitea.io/gitea/modules/util"
  35. issue_service "code.gitea.io/gitea/services/issue"
  36. notify_service "code.gitea.io/gitea/services/notify"
  37. )
  38. // getMergeMessage composes the message used when merging a pull request.
  39. func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issues_model.PullRequest, mergeStyle repo_model.MergeStyle, extraVars map[string]string) (message, body string, err error) {
  40. if err := pr.LoadBaseRepo(ctx); err != nil {
  41. return "", "", err
  42. }
  43. if err := pr.LoadHeadRepo(ctx); err != nil {
  44. return "", "", err
  45. }
  46. if err := pr.LoadIssue(ctx); err != nil {
  47. return "", "", err
  48. }
  49. if err := pr.Issue.LoadPoster(ctx); err != nil {
  50. return "", "", err
  51. }
  52. if err := pr.Issue.LoadRepo(ctx); err != nil {
  53. return "", "", err
  54. }
  55. isExternalTracker := pr.BaseRepo.UnitEnabled(ctx, unit.TypeExternalTracker)
  56. issueReference := "#"
  57. if isExternalTracker {
  58. issueReference = "!"
  59. }
  60. reviewedOn := "Reviewed-on: " + httplib.MakeAbsoluteURL(ctx, pr.Issue.Link())
  61. reviewedBy := pr.GetApprovers(ctx)
  62. if mergeStyle != "" {
  63. templateFilepath := fmt.Sprintf(".gitea/default_merge_message/%s_TEMPLATE.md", strings.ToUpper(string(mergeStyle)))
  64. commit, err := baseGitRepo.GetBranchCommit(pr.BaseRepo.DefaultBranch)
  65. if err != nil {
  66. return "", "", err
  67. }
  68. templateContent, err := commit.GetFileContent(templateFilepath, setting.Repository.PullRequest.DefaultMergeMessageSize)
  69. if err != nil {
  70. if !git.IsErrNotExist(err) {
  71. return "", "", err
  72. }
  73. } else {
  74. vars := map[string]string{
  75. "BaseRepoOwnerName": pr.BaseRepo.OwnerName,
  76. "BaseRepoName": pr.BaseRepo.Name,
  77. "BaseBranch": pr.BaseBranch,
  78. "HeadRepoOwnerName": "",
  79. "HeadRepoName": "",
  80. "HeadBranch": pr.HeadBranch,
  81. "PullRequestTitle": pr.Issue.Title,
  82. "PullRequestDescription": pr.Issue.Content,
  83. "PullRequestPosterName": pr.Issue.Poster.Name,
  84. "PullRequestIndex": strconv.FormatInt(pr.Index, 10),
  85. "PullRequestReference": fmt.Sprintf("%s%d", issueReference, pr.Index),
  86. "ReviewedOn": reviewedOn,
  87. "ReviewedBy": reviewedBy,
  88. }
  89. if pr.HeadRepo != nil {
  90. vars["HeadRepoOwnerName"] = pr.HeadRepo.OwnerName
  91. vars["HeadRepoName"] = pr.HeadRepo.Name
  92. }
  93. maps.Copy(vars, extraVars)
  94. refs, err := pr.ResolveCrossReferences(ctx)
  95. if err == nil {
  96. closeIssueIndexes := make([]string, 0, len(refs))
  97. closeWord := "close"
  98. if len(setting.Repository.PullRequest.CloseKeywords) > 0 {
  99. closeWord = setting.Repository.PullRequest.CloseKeywords[0]
  100. }
  101. for _, ref := range refs {
  102. if ref.RefAction == references.XRefActionCloses {
  103. if err := ref.LoadIssue(ctx); err != nil {
  104. return "", "", err
  105. }
  106. closeIssueIndexes = append(closeIssueIndexes, fmt.Sprintf("%s %s%d", closeWord, issueReference, ref.Issue.Index))
  107. }
  108. }
  109. if len(closeIssueIndexes) > 0 {
  110. vars["ClosingIssues"] = strings.Join(closeIssueIndexes, ", ")
  111. } else {
  112. vars["ClosingIssues"] = ""
  113. }
  114. }
  115. message, body = expandDefaultMergeMessage(templateContent, vars)
  116. return message, body, nil
  117. }
  118. }
  119. if mergeStyle == repo_model.MergeStyleRebase {
  120. // for fast-forward rebase, do not amend the last commit if there is no template
  121. return "", "", nil
  122. }
  123. body = fmt.Sprintf("%s\n%s", reviewedOn, reviewedBy)
  124. // Squash merge has a different from other styles.
  125. if mergeStyle == repo_model.MergeStyleSquash {
  126. return fmt.Sprintf("%s (%s%d)", pr.Issue.Title, issueReference, pr.Issue.Index), body, nil
  127. }
  128. if pr.BaseRepoID == pr.HeadRepoID {
  129. return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadBranch, pr.BaseBranch), body, nil
  130. }
  131. if pr.HeadRepo == nil {
  132. return fmt.Sprintf("Merge pull request '%s' (%s%d) from <deleted>:%s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadBranch, pr.BaseBranch), body, nil
  133. }
  134. return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s:%s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseBranch), body, nil
  135. }
  136. func expandDefaultMergeMessage(template string, vars map[string]string) (message, body string) {
  137. message = strings.TrimSpace(template)
  138. if splits := strings.SplitN(message, "\n", 2); len(splits) == 2 {
  139. message = splits[0]
  140. body = strings.TrimSpace(splits[1])
  141. }
  142. mapping := func(s string) string { return vars[s] }
  143. return os.Expand(message, mapping), os.Expand(body, mapping)
  144. }
  145. // GetDefaultMergeMessage returns default message used when merging pull request
  146. func GetDefaultMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issues_model.PullRequest, mergeStyle repo_model.MergeStyle) (message, body string, err error) {
  147. return getMergeMessage(ctx, baseGitRepo, pr, mergeStyle, nil)
  148. }
  149. func AddCommitMessageTailer(message, tailerKey, tailerValue string) string {
  150. tailerLine := tailerKey + ": " + tailerValue
  151. message = strings.ReplaceAll(message, "\r\n", "\n")
  152. message = strings.ReplaceAll(message, "\r", "\n")
  153. if strings.Contains(message, "\n"+tailerLine+"\n") || strings.HasSuffix(message, "\n"+tailerLine) {
  154. return message
  155. }
  156. if !strings.HasSuffix(message, "\n") {
  157. message += "\n"
  158. }
  159. pos1 := strings.LastIndexByte(message[:len(message)-1], '\n')
  160. pos2 := -1
  161. if pos1 != -1 {
  162. pos2 = strings.IndexByte(message[pos1:], ':')
  163. if pos2 != -1 {
  164. pos2 += pos1
  165. }
  166. }
  167. var lastLineKey string
  168. if pos1 != -1 && pos2 != -1 {
  169. lastLineKey = message[pos1+1 : pos2]
  170. }
  171. isLikelyTailerLine := lastLineKey != "" && unicode.IsUpper(rune(lastLineKey[0])) && strings.Contains(message, "-")
  172. for i := 0; isLikelyTailerLine && i < len(lastLineKey); i++ {
  173. r := rune(lastLineKey[i])
  174. isLikelyTailerLine = unicode.IsLetter(r) || unicode.IsDigit(r) || r == '-'
  175. }
  176. if !strings.HasSuffix(message, "\n\n") && !isLikelyTailerLine {
  177. message += "\n"
  178. }
  179. return message + tailerLine
  180. }
  181. // ErrInvalidMergeStyle represents an error if merging with disabled merge strategy
  182. type ErrInvalidMergeStyle struct {
  183. ID int64
  184. Style repo_model.MergeStyle
  185. }
  186. // IsErrInvalidMergeStyle checks if an error is a ErrInvalidMergeStyle.
  187. func IsErrInvalidMergeStyle(err error) bool {
  188. _, ok := err.(ErrInvalidMergeStyle)
  189. return ok
  190. }
  191. func (err ErrInvalidMergeStyle) Error() string {
  192. return fmt.Sprintf("merge strategy is not allowed or is invalid [repo_id: %d, strategy: %s]",
  193. err.ID, err.Style)
  194. }
  195. func (err ErrInvalidMergeStyle) Unwrap() error {
  196. return util.ErrInvalidArgument
  197. }
  198. // Merge merges pull request to base repository.
  199. // Caller should check PR is ready to be merged (review and status checks)
  200. func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, baseGitRepo *git.Repository, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string, wasAutoMerged bool) error {
  201. if err := pr.LoadBaseRepo(ctx); err != nil {
  202. log.Error("Unable to load base repo: %v", err)
  203. return fmt.Errorf("unable to load base repo: %w", err)
  204. } else if err := pr.LoadHeadRepo(ctx); err != nil {
  205. log.Error("Unable to load head repo: %v", err)
  206. return fmt.Errorf("unable to load head repo: %w", err)
  207. }
  208. prUnit, err := pr.BaseRepo.GetUnit(ctx, unit.TypePullRequests)
  209. if err != nil {
  210. log.Error("pr.BaseRepo.GetUnit(unit.TypePullRequests): %v", err)
  211. return err
  212. }
  213. prConfig := prUnit.PullRequestsConfig()
  214. // Check if merge style is correct and allowed
  215. if !prConfig.IsMergeStyleAllowed(mergeStyle) {
  216. return ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: mergeStyle}
  217. }
  218. releaser, err := globallock.Lock(ctx, getPullWorkingLockKey(pr.ID))
  219. if err != nil {
  220. log.Error("lock.Lock(): %v", err)
  221. return fmt.Errorf("lock.Lock: %w", err)
  222. }
  223. defer releaser()
  224. defer func() {
  225. go AddTestPullRequestTask(TestPullRequestOptions{
  226. RepoID: pr.BaseRepo.ID,
  227. Doer: doer,
  228. Branch: pr.BaseBranch,
  229. IsSync: false,
  230. IsForcePush: false,
  231. OldCommitID: "",
  232. NewCommitID: "",
  233. })
  234. }()
  235. _, err = doMergeAndPush(ctx, pr, doer, mergeStyle, expectedHeadCommitID, message, repo_module.PushTriggerPRMergeToBase)
  236. releaser()
  237. if err != nil {
  238. return err
  239. }
  240. // reload pull request because it has been updated by post receive hook
  241. pr, err = issues_model.GetPullRequestByID(ctx, pr.ID)
  242. if err != nil {
  243. return err
  244. }
  245. if err := pr.LoadIssue(ctx); err != nil {
  246. log.Error("LoadIssue %-v: %v", pr, err)
  247. }
  248. if err := pr.Issue.LoadRepo(ctx); err != nil {
  249. log.Error("pr.Issue.LoadRepo %-v: %v", pr, err)
  250. }
  251. if err := pr.Issue.Repo.LoadOwner(ctx); err != nil {
  252. log.Error("LoadOwner for %-v: %v", pr, err)
  253. }
  254. if wasAutoMerged {
  255. notify_service.AutoMergePullRequest(ctx, doer, pr)
  256. } else {
  257. notify_service.MergePullRequest(ctx, doer, pr)
  258. }
  259. // Reset cached commit count
  260. cache.Remove(pr.Issue.Repo.GetCommitsCountCacheKey(pr.BaseBranch, true))
  261. return handleCloseCrossReferences(ctx, pr, doer)
  262. }
  263. func handleCloseCrossReferences(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User) error {
  264. // Resolve cross references
  265. refs, err := pr.ResolveCrossReferences(ctx)
  266. if err != nil {
  267. log.Error("ResolveCrossReferences: %v", err)
  268. return nil
  269. }
  270. for _, ref := range refs {
  271. if err = ref.LoadIssue(ctx); err != nil {
  272. return err
  273. }
  274. if err = ref.Issue.LoadRepo(ctx); err != nil {
  275. return err
  276. }
  277. if ref.RefAction == references.XRefActionCloses && !ref.Issue.IsClosed {
  278. if err = issue_service.CloseIssue(ctx, ref.Issue, doer, pr.MergedCommitID); err != nil {
  279. // Allow ErrDependenciesLeft
  280. if !issues_model.IsErrDependenciesLeft(err) {
  281. return err
  282. }
  283. }
  284. } else if ref.RefAction == references.XRefActionReopens && ref.Issue.IsClosed {
  285. if err = issue_service.ReopenIssue(ctx, ref.Issue, doer, pr.MergedCommitID); err != nil {
  286. return err
  287. }
  288. }
  289. }
  290. return nil
  291. }
  292. // doMergeAndPush performs the merge operation without changing any pull information in database and pushes it up to the base repository
  293. func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string, pushTrigger repo_module.PushTrigger) (string, error) { //nolint:unparam // non-error result is never used
  294. // Clone base repo.
  295. mergeCtx, cancel, err := createTemporaryRepoForMerge(ctx, pr, doer, expectedHeadCommitID)
  296. if err != nil {
  297. return "", err
  298. }
  299. defer cancel()
  300. // Merge commits.
  301. switch mergeStyle {
  302. case repo_model.MergeStyleMerge:
  303. if err := doMergeStyleMerge(mergeCtx, message); err != nil {
  304. return "", err
  305. }
  306. case repo_model.MergeStyleRebase, repo_model.MergeStyleRebaseMerge:
  307. if err := doMergeStyleRebase(mergeCtx, mergeStyle, message); err != nil {
  308. return "", err
  309. }
  310. case repo_model.MergeStyleSquash:
  311. if err := doMergeStyleSquash(mergeCtx, message); err != nil {
  312. return "", err
  313. }
  314. case repo_model.MergeStyleFastForwardOnly:
  315. if err := doMergeStyleFastForwardOnly(mergeCtx); err != nil {
  316. return "", err
  317. }
  318. default:
  319. return "", ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: mergeStyle}
  320. }
  321. // OK we should cache our current head and origin/headbranch
  322. mergeHeadSHA, err := git.GetFullCommitID(ctx, mergeCtx.tmpBasePath, "HEAD")
  323. if err != nil {
  324. return "", fmt.Errorf("Failed to get full commit id for HEAD: %w", err)
  325. }
  326. mergeBaseSHA, err := git.GetFullCommitID(ctx, mergeCtx.tmpBasePath, "original_"+baseBranch)
  327. if err != nil {
  328. return "", fmt.Errorf("Failed to get full commit id for origin/%s: %w", pr.BaseBranch, err)
  329. }
  330. mergeCommitID, err := git.GetFullCommitID(ctx, mergeCtx.tmpBasePath, baseBranch)
  331. if err != nil {
  332. return "", fmt.Errorf("Failed to get full commit id for the new merge: %w", err)
  333. }
  334. // Now it's questionable about where this should go - either after or before the push
  335. // I think in the interests of data safety - failures to push to the lfs should prevent
  336. // the merge as you can always remerge.
  337. if setting.LFS.StartServer {
  338. if err := LFSPush(ctx, mergeCtx.tmpBasePath, mergeHeadSHA, mergeBaseSHA, pr); err != nil {
  339. return "", err
  340. }
  341. }
  342. var headUser *user_model.User
  343. err = pr.HeadRepo.LoadOwner(ctx)
  344. if err != nil {
  345. if !user_model.IsErrUserNotExist(err) {
  346. log.Error("Can't find user: %d for head repository in %-v: %v", pr.HeadRepo.OwnerID, pr, err)
  347. return "", err
  348. }
  349. log.Warn("Can't find user: %d for head repository in %-v - defaulting to doer: %s - %v", pr.HeadRepo.OwnerID, pr, doer.Name, err)
  350. headUser = doer
  351. } else {
  352. headUser = pr.HeadRepo.Owner
  353. }
  354. mergeCtx.env = repo_module.FullPushingEnvironment(
  355. headUser,
  356. doer,
  357. pr.BaseRepo,
  358. pr.BaseRepo.Name,
  359. pr.ID,
  360. )
  361. mergeCtx.env = append(mergeCtx.env, repo_module.EnvPushTrigger+"="+string(pushTrigger))
  362. pushCmd := gitcmd.NewCommand("push", "origin").AddDynamicArguments(baseBranch + ":" + git.BranchPrefix + pr.BaseBranch)
  363. // Push back to upstream.
  364. // This cause an api call to "/api/internal/hook/post-receive/...",
  365. // If it's merge, all db transaction and operations should be there but not here to prevent deadlock.
  366. if err := pushCmd.Run(ctx, mergeCtx.RunOpts()); err != nil {
  367. if strings.Contains(mergeCtx.errbuf.String(), "non-fast-forward") {
  368. return "", &git.ErrPushOutOfDate{
  369. StdOut: mergeCtx.outbuf.String(),
  370. StdErr: mergeCtx.errbuf.String(),
  371. Err: err,
  372. }
  373. } else if strings.Contains(mergeCtx.errbuf.String(), "! [remote rejected]") {
  374. err := &git.ErrPushRejected{
  375. StdOut: mergeCtx.outbuf.String(),
  376. StdErr: mergeCtx.errbuf.String(),
  377. Err: err,
  378. }
  379. err.GenerateMessage()
  380. return "", err
  381. }
  382. return "", fmt.Errorf("git push: %s", mergeCtx.errbuf.String())
  383. }
  384. mergeCtx.outbuf.Reset()
  385. mergeCtx.errbuf.Reset()
  386. return mergeCommitID, nil
  387. }
  388. func commitAndSignNoAuthor(ctx *mergeContext, message string) error {
  389. cmdCommit := gitcmd.NewCommand("commit").AddOptionFormat("--message=%s", message)
  390. if ctx.signKey == nil {
  391. cmdCommit.AddArguments("--no-gpg-sign")
  392. } else {
  393. if ctx.signKey.Format != "" {
  394. cmdCommit.AddConfig("gpg.format", ctx.signKey.Format)
  395. }
  396. cmdCommit.AddOptionFormat("-S%s", ctx.signKey.KeyID)
  397. }
  398. if err := cmdCommit.Run(ctx, ctx.RunOpts()); err != nil {
  399. log.Error("git commit %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String())
  400. return fmt.Errorf("git commit %v: %w\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String())
  401. }
  402. return nil
  403. }
  404. // ErrMergeConflicts represents an error if merging fails with a conflict
  405. type ErrMergeConflicts struct {
  406. Style repo_model.MergeStyle
  407. StdOut string
  408. StdErr string
  409. Err error
  410. }
  411. // IsErrMergeConflicts checks if an error is a ErrMergeConflicts.
  412. func IsErrMergeConflicts(err error) bool {
  413. _, ok := err.(ErrMergeConflicts)
  414. return ok
  415. }
  416. func (err ErrMergeConflicts) Error() string {
  417. return fmt.Sprintf("Merge Conflict Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
  418. }
  419. // ErrMergeUnrelatedHistories represents an error if merging fails due to unrelated histories
  420. type ErrMergeUnrelatedHistories struct {
  421. Style repo_model.MergeStyle
  422. StdOut string
  423. StdErr string
  424. Err error
  425. }
  426. // IsErrMergeUnrelatedHistories checks if an error is a ErrMergeUnrelatedHistories.
  427. func IsErrMergeUnrelatedHistories(err error) bool {
  428. _, ok := err.(ErrMergeUnrelatedHistories)
  429. return ok
  430. }
  431. func (err ErrMergeUnrelatedHistories) Error() string {
  432. return fmt.Sprintf("Merge UnrelatedHistories Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
  433. }
  434. // ErrMergeDivergingFastForwardOnly represents an error if a fast-forward-only merge fails because the branches diverge
  435. type ErrMergeDivergingFastForwardOnly struct {
  436. StdOut string
  437. StdErr string
  438. Err error
  439. }
  440. // IsErrMergeDivergingFastForwardOnly checks if an error is a ErrMergeDivergingFastForwardOnly.
  441. func IsErrMergeDivergingFastForwardOnly(err error) bool {
  442. _, ok := err.(ErrMergeDivergingFastForwardOnly)
  443. return ok
  444. }
  445. func (err ErrMergeDivergingFastForwardOnly) Error() string {
  446. return fmt.Sprintf("Merge DivergingFastForwardOnly Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
  447. }
  448. func runMergeCommand(ctx *mergeContext, mergeStyle repo_model.MergeStyle, cmd *gitcmd.Command) error {
  449. if err := cmd.Run(ctx, ctx.RunOpts()); err != nil {
  450. // Merge will leave a MERGE_HEAD file in the .git folder if there is a conflict
  451. if _, statErr := os.Stat(filepath.Join(ctx.tmpBasePath, ".git", "MERGE_HEAD")); statErr == nil {
  452. // We have a merge conflict error
  453. log.Debug("MergeConflict %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String())
  454. return ErrMergeConflicts{
  455. Style: mergeStyle,
  456. StdOut: ctx.outbuf.String(),
  457. StdErr: ctx.errbuf.String(),
  458. Err: err,
  459. }
  460. } else if strings.Contains(ctx.errbuf.String(), "refusing to merge unrelated histories") {
  461. log.Debug("MergeUnrelatedHistories %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String())
  462. return ErrMergeUnrelatedHistories{
  463. Style: mergeStyle,
  464. StdOut: ctx.outbuf.String(),
  465. StdErr: ctx.errbuf.String(),
  466. Err: err,
  467. }
  468. } else if mergeStyle == repo_model.MergeStyleFastForwardOnly && strings.Contains(ctx.errbuf.String(), "Not possible to fast-forward, aborting") {
  469. log.Debug("MergeDivergingFastForwardOnly %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String())
  470. return ErrMergeDivergingFastForwardOnly{
  471. StdOut: ctx.outbuf.String(),
  472. StdErr: ctx.errbuf.String(),
  473. Err: err,
  474. }
  475. }
  476. log.Error("git merge %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String())
  477. return fmt.Errorf("git merge %v: %w\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String())
  478. }
  479. ctx.outbuf.Reset()
  480. ctx.errbuf.Reset()
  481. return nil
  482. }
  483. var escapedSymbols = regexp.MustCompile(`([*[?! \\])`)
  484. // IsUserAllowedToMerge check if user is allowed to merge PR with given permissions and branch protections
  485. func IsUserAllowedToMerge(ctx context.Context, pr *issues_model.PullRequest, p access_model.Permission, user *user_model.User) (bool, error) {
  486. if user == nil {
  487. return false, nil
  488. }
  489. pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
  490. if err != nil {
  491. return false, err
  492. }
  493. if (p.CanWrite(unit.TypeCode) && pb == nil) || (pb != nil && git_model.IsUserMergeWhitelisted(ctx, pb, user.ID, p)) {
  494. return true, nil
  495. }
  496. return false, nil
  497. }
  498. // CheckPullBranchProtections checks whether the PR is ready to be merged (reviews and status checks)
  499. func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullRequest, skipProtectedFilesCheck bool) (err error) {
  500. if err = pr.LoadBaseRepo(ctx); err != nil {
  501. return fmt.Errorf("LoadBaseRepo: %w", err)
  502. }
  503. pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
  504. if err != nil {
  505. return fmt.Errorf("LoadProtectedBranch: %v", err)
  506. }
  507. if pb == nil {
  508. return nil
  509. }
  510. isPass, err := IsPullCommitStatusPass(ctx, pr)
  511. if err != nil {
  512. return err
  513. }
  514. if !isPass {
  515. return util.ErrorWrap(ErrNotReadyToMerge, "Not all required status checks successful")
  516. }
  517. if !issues_model.HasEnoughApprovals(ctx, pb, pr) {
  518. return util.ErrorWrap(ErrNotReadyToMerge, "Does not have enough approvals")
  519. }
  520. if issues_model.MergeBlockedByRejectedReview(ctx, pb, pr) {
  521. return util.ErrorWrap(ErrNotReadyToMerge, "There are requested changes")
  522. }
  523. if issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pr) {
  524. return util.ErrorWrap(ErrNotReadyToMerge, "There are official review requests")
  525. }
  526. if issues_model.MergeBlockedByOutdatedBranch(pb, pr) {
  527. return util.ErrorWrap(ErrNotReadyToMerge, "The head branch is behind the base branch")
  528. }
  529. if skipProtectedFilesCheck {
  530. return nil
  531. }
  532. if pb.MergeBlockedByProtectedFiles(pr.ChangedProtectedFiles) {
  533. return util.ErrorWrap(ErrNotReadyToMerge, "Changed protected files")
  534. }
  535. return nil
  536. }
  537. // MergedManually mark pr as merged manually
  538. func MergedManually(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, baseGitRepo *git.Repository, commitID string) error {
  539. releaser, err := globallock.Lock(ctx, getPullWorkingLockKey(pr.ID))
  540. if err != nil {
  541. log.Error("lock.Lock(): %v", err)
  542. return fmt.Errorf("lock.Lock: %w", err)
  543. }
  544. defer releaser()
  545. err = db.WithTx(ctx, func(ctx context.Context) error {
  546. if err := pr.LoadBaseRepo(ctx); err != nil {
  547. return err
  548. }
  549. prUnit, err := pr.BaseRepo.GetUnit(ctx, unit.TypePullRequests)
  550. if err != nil {
  551. return err
  552. }
  553. prConfig := prUnit.PullRequestsConfig()
  554. // Check if merge style is correct and allowed
  555. if !prConfig.IsMergeStyleAllowed(repo_model.MergeStyleManuallyMerged) {
  556. return ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: repo_model.MergeStyleManuallyMerged}
  557. }
  558. objectFormat := git.ObjectFormatFromName(pr.BaseRepo.ObjectFormatName)
  559. if len(commitID) != objectFormat.FullLength() {
  560. return errors.New("Wrong commit ID")
  561. }
  562. commit, err := baseGitRepo.GetCommit(commitID)
  563. if err != nil {
  564. if git.IsErrNotExist(err) {
  565. return errors.New("Wrong commit ID")
  566. }
  567. return err
  568. }
  569. commitID = commit.ID.String()
  570. ok, err := baseGitRepo.IsCommitInBranch(commitID, pr.BaseBranch)
  571. if err != nil {
  572. return err
  573. }
  574. if !ok {
  575. return errors.New("Wrong commit ID")
  576. }
  577. var merged bool
  578. if merged, err = SetMerged(ctx, pr, commitID, timeutil.TimeStamp(commit.Author.When.Unix()), doer, issues_model.PullRequestStatusManuallyMerged); err != nil {
  579. return err
  580. } else if !merged {
  581. return errors.New("SetMerged failed")
  582. }
  583. return nil
  584. })
  585. releaser()
  586. if err != nil {
  587. return err
  588. }
  589. notify_service.MergePullRequest(baseGitRepo.Ctx, doer, pr)
  590. log.Info("manuallyMerged[%d]: Marked as manually merged into %s/%s by commit id: %s", pr.ID, pr.BaseRepo.Name, pr.BaseBranch, commitID)
  591. return handleCloseCrossReferences(ctx, pr, doer)
  592. }
  593. // SetMerged sets a pull request to merged and closes the corresponding issue
  594. func SetMerged(ctx context.Context, pr *issues_model.PullRequest, mergedCommitID string, mergedTimeStamp timeutil.TimeStamp, merger *user_model.User, mergeStatus issues_model.PullRequestStatus) (bool, error) {
  595. if pr.HasMerged {
  596. return false, fmt.Errorf("PullRequest[%d] already merged", pr.Index)
  597. }
  598. pr.HasMerged = true
  599. pr.MergedCommitID = mergedCommitID
  600. pr.MergedUnix = mergedTimeStamp
  601. pr.Merger = merger
  602. pr.MergerID = merger.ID
  603. pr.Status = mergeStatus
  604. // reset the conflicted files as there cannot be any if we're merged
  605. pr.ConflictedFiles = []string{}
  606. if pr.MergedCommitID == "" || pr.MergedUnix == 0 || pr.Merger == nil {
  607. return false, fmt.Errorf("unable to merge PullRequest[%d], some required fields are empty", pr.Index)
  608. }
  609. return db.WithTx2(ctx, func(ctx context.Context) (bool, error) {
  610. pr.Issue = nil
  611. if err := pr.LoadIssue(ctx); err != nil {
  612. return false, err
  613. }
  614. if err := pr.Issue.LoadRepo(ctx); err != nil {
  615. return false, err
  616. }
  617. if err := pr.Issue.Repo.LoadOwner(ctx); err != nil {
  618. return false, err
  619. }
  620. // Removing an auto merge pull and ignore if not exist
  621. if err := pull_model.DeleteScheduledAutoMerge(ctx, pr.ID); err != nil && !db.IsErrNotExist(err) {
  622. return false, fmt.Errorf("DeleteScheduledAutoMerge[%d]: %v", pr.ID, err)
  623. }
  624. // Set issue as closed
  625. if _, err := issues_model.SetIssueAsClosed(ctx, pr.Issue, pr.Merger, true); err != nil {
  626. return false, fmt.Errorf("ChangeIssueStatus: %w", err)
  627. }
  628. // We need to save all of the data used to compute this merge as it may have already been changed by testPullRequestBranchMergeable. FIXME: need to set some state to prevent testPullRequestBranchMergeable from running whilst we are merging.
  629. if cnt, err := db.GetEngine(ctx).Where("id = ?", pr.ID).
  630. And("has_merged = ?", false).
  631. Cols("has_merged, status, merge_base, merged_commit_id, merger_id, merged_unix, conflicted_files").
  632. Update(pr); err != nil {
  633. return false, fmt.Errorf("failed to update pr[%d]: %w", pr.ID, err)
  634. } else if cnt != 1 {
  635. return false, issues_model.ErrIssueAlreadyChanged
  636. }
  637. return true, nil
  638. })
  639. }