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.

merge.go 31KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887
  1. // Copyright 2019 The Gitea Authors.
  2. // All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package pull
  6. import (
  7. "bufio"
  8. "bytes"
  9. "context"
  10. "fmt"
  11. "os"
  12. "path/filepath"
  13. "regexp"
  14. "strconv"
  15. "strings"
  16. "time"
  17. "code.gitea.io/gitea/models"
  18. "code.gitea.io/gitea/models/db"
  19. pull_model "code.gitea.io/gitea/models/pull"
  20. repo_model "code.gitea.io/gitea/models/repo"
  21. "code.gitea.io/gitea/models/unit"
  22. user_model "code.gitea.io/gitea/models/user"
  23. "code.gitea.io/gitea/modules/cache"
  24. "code.gitea.io/gitea/modules/git"
  25. "code.gitea.io/gitea/modules/log"
  26. "code.gitea.io/gitea/modules/notification"
  27. "code.gitea.io/gitea/modules/references"
  28. repo_module "code.gitea.io/gitea/modules/repository"
  29. "code.gitea.io/gitea/modules/setting"
  30. "code.gitea.io/gitea/modules/timeutil"
  31. asymkey_service "code.gitea.io/gitea/services/asymkey"
  32. issue_service "code.gitea.io/gitea/services/issue"
  33. )
  34. // GetDefaultMergeMessage returns default message used when merging pull request
  35. func GetDefaultMergeMessage(baseGitRepo *git.Repository, pr *models.PullRequest, mergeStyle repo_model.MergeStyle) (string, error) {
  36. if err := pr.LoadHeadRepo(); err != nil {
  37. return "", err
  38. }
  39. if err := pr.LoadBaseRepo(); err != nil {
  40. return "", err
  41. }
  42. if pr.BaseRepo == nil {
  43. return "", repo_model.ErrRepoNotExist{ID: pr.BaseRepoID}
  44. }
  45. if err := pr.LoadIssue(); err != nil {
  46. return "", err
  47. }
  48. isExternalTracker := pr.BaseRepo.UnitEnabled(unit.TypeExternalTracker)
  49. issueReference := "#"
  50. if isExternalTracker {
  51. issueReference = "!"
  52. }
  53. if mergeStyle != "" {
  54. templateFilepath := fmt.Sprintf(".gitea/default_merge_message/%s_TEMPLATE.md", strings.ToUpper(string(mergeStyle)))
  55. commit, err := baseGitRepo.GetBranchCommit(pr.BaseRepo.DefaultBranch)
  56. if err != nil {
  57. return "", err
  58. }
  59. templateContent, err := commit.GetFileContent(templateFilepath, setting.Repository.PullRequest.DefaultMergeMessageSize)
  60. if err != nil {
  61. if !git.IsErrNotExist(err) {
  62. return "", err
  63. }
  64. } else {
  65. vars := map[string]string{
  66. "BaseRepoOwnerName": pr.BaseRepo.OwnerName,
  67. "BaseRepoName": pr.BaseRepo.Name,
  68. "BaseBranch": pr.BaseBranch,
  69. "HeadRepoOwnerName": "",
  70. "HeadRepoName": "",
  71. "HeadBranch": pr.HeadBranch,
  72. "PullRequestTitle": pr.Issue.Title,
  73. "PullRequestDescription": pr.Issue.Content,
  74. "PullRequestPosterName": pr.Issue.Poster.Name,
  75. "PullRequestIndex": strconv.FormatInt(pr.Index, 10),
  76. "PullRequestReference": fmt.Sprintf("%s%d", issueReference, pr.Index),
  77. }
  78. if pr.HeadRepo != nil {
  79. vars["HeadRepoOwnerName"] = pr.HeadRepo.OwnerName
  80. vars["HeadRepoName"] = pr.HeadRepo.Name
  81. }
  82. refs, err := pr.ResolveCrossReferences(baseGitRepo.Ctx)
  83. if err == nil {
  84. closeIssueIndexes := make([]string, 0, len(refs))
  85. closeWord := "close"
  86. if len(setting.Repository.PullRequest.CloseKeywords) > 0 {
  87. closeWord = setting.Repository.PullRequest.CloseKeywords[0]
  88. }
  89. for _, ref := range refs {
  90. if ref.RefAction == references.XRefActionCloses {
  91. closeIssueIndexes = append(closeIssueIndexes, fmt.Sprintf("%s %s%d", closeWord, issueReference, ref.Issue.Index))
  92. }
  93. }
  94. if len(closeIssueIndexes) > 0 {
  95. vars["ClosingIssues"] = strings.Join(closeIssueIndexes, ", ")
  96. } else {
  97. vars["ClosingIssues"] = ""
  98. }
  99. }
  100. return os.Expand(templateContent, func(s string) string {
  101. return vars[s]
  102. }), nil
  103. }
  104. }
  105. // Squash merge has a different from other styles.
  106. if mergeStyle == repo_model.MergeStyleSquash {
  107. return fmt.Sprintf("%s (%s%d)", pr.Issue.Title, issueReference, pr.Issue.Index), nil
  108. }
  109. if pr.BaseRepoID == pr.HeadRepoID {
  110. return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadBranch, pr.BaseBranch), nil
  111. }
  112. if pr.HeadRepo == nil {
  113. 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), nil
  114. }
  115. 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), nil
  116. }
  117. // Merge merges pull request to base repository.
  118. // Caller should check PR is ready to be merged (review and status checks)
  119. func Merge(ctx context.Context, pr *models.PullRequest, doer *user_model.User, baseGitRepo *git.Repository, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string) error {
  120. if err := pr.LoadHeadRepo(); err != nil {
  121. log.Error("LoadHeadRepo: %v", err)
  122. return fmt.Errorf("LoadHeadRepo: %v", err)
  123. } else if err := pr.LoadBaseRepo(); err != nil {
  124. log.Error("LoadBaseRepo: %v", err)
  125. return fmt.Errorf("LoadBaseRepo: %v", err)
  126. }
  127. pullWorkingPool.CheckIn(fmt.Sprint(pr.ID))
  128. defer pullWorkingPool.CheckOut(fmt.Sprint(pr.ID))
  129. // Removing an auto merge pull and ignore if not exist
  130. if err := pull_model.DeleteScheduledAutoMerge(db.DefaultContext, pr.ID); err != nil && !db.IsErrNotExist(err) {
  131. return err
  132. }
  133. prUnit, err := pr.BaseRepo.GetUnit(unit.TypePullRequests)
  134. if err != nil {
  135. log.Error("pr.BaseRepo.GetUnit(unit.TypePullRequests): %v", err)
  136. return err
  137. }
  138. prConfig := prUnit.PullRequestsConfig()
  139. // Check if merge style is correct and allowed
  140. if !prConfig.IsMergeStyleAllowed(mergeStyle) {
  141. return models.ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: mergeStyle}
  142. }
  143. defer func() {
  144. go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false, "", "")
  145. }()
  146. // TODO: make it able to do this in a database session
  147. mergeCtx := context.Background()
  148. pr.MergedCommitID, err = rawMerge(mergeCtx, pr, doer, mergeStyle, expectedHeadCommitID, message)
  149. if err != nil {
  150. return err
  151. }
  152. pr.MergedUnix = timeutil.TimeStampNow()
  153. pr.Merger = doer
  154. pr.MergerID = doer.ID
  155. if _, err := pr.SetMerged(ctx); err != nil {
  156. log.Error("setMerged [%d]: %v", pr.ID, err)
  157. }
  158. if err := pr.LoadIssueCtx(ctx); err != nil {
  159. log.Error("loadIssue [%d]: %v", pr.ID, err)
  160. }
  161. if err := pr.Issue.LoadRepo(ctx); err != nil {
  162. log.Error("loadRepo for issue [%d]: %v", pr.ID, err)
  163. }
  164. if err := pr.Issue.Repo.GetOwner(ctx); err != nil {
  165. log.Error("GetOwner for issue repo [%d]: %v", pr.ID, err)
  166. }
  167. notification.NotifyMergePullRequest(pr, doer)
  168. // Reset cached commit count
  169. cache.Remove(pr.Issue.Repo.GetCommitsCountCacheKey(pr.BaseBranch, true))
  170. // Resolve cross references
  171. refs, err := pr.ResolveCrossReferences(ctx)
  172. if err != nil {
  173. log.Error("ResolveCrossReferences: %v", err)
  174. return nil
  175. }
  176. for _, ref := range refs {
  177. if err = ref.LoadIssueCtx(ctx); err != nil {
  178. return err
  179. }
  180. if err = ref.Issue.LoadRepo(ctx); err != nil {
  181. return err
  182. }
  183. close := ref.RefAction == references.XRefActionCloses
  184. if close != ref.Issue.IsClosed {
  185. if err = issue_service.ChangeStatus(ref.Issue, doer, close); err != nil {
  186. // Allow ErrDependenciesLeft
  187. if !models.IsErrDependenciesLeft(err) {
  188. return err
  189. }
  190. }
  191. }
  192. }
  193. return nil
  194. }
  195. // rawMerge perform the merge operation without changing any pull information in database
  196. func rawMerge(ctx context.Context, pr *models.PullRequest, doer *user_model.User, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string) (string, error) {
  197. err := git.LoadGitVersion()
  198. if err != nil {
  199. log.Error("git.LoadGitVersion: %v", err)
  200. return "", fmt.Errorf("Unable to get git version: %v", err)
  201. }
  202. // Clone base repo.
  203. tmpBasePath, err := createTemporaryRepo(ctx, pr)
  204. if err != nil {
  205. log.Error("CreateTemporaryPath: %v", err)
  206. return "", err
  207. }
  208. defer func() {
  209. if err := repo_module.RemoveTemporaryPath(tmpBasePath); err != nil {
  210. log.Error("Merge: RemoveTemporaryPath: %s", err)
  211. }
  212. }()
  213. baseBranch := "base"
  214. trackingBranch := "tracking"
  215. stagingBranch := "staging"
  216. if expectedHeadCommitID != "" {
  217. trackingCommitID, _, err := git.NewCommand(ctx, "show-ref", "--hash", git.BranchPrefix+trackingBranch).RunStdString(&git.RunOpts{Dir: tmpBasePath})
  218. if err != nil {
  219. log.Error("show-ref[%s] --hash refs/heads/trackingn: %v", tmpBasePath, git.BranchPrefix+trackingBranch, err)
  220. return "", fmt.Errorf("getDiffTree: %v", err)
  221. }
  222. if strings.TrimSpace(trackingCommitID) != expectedHeadCommitID {
  223. return "", models.ErrSHADoesNotMatch{
  224. GivenSHA: expectedHeadCommitID,
  225. CurrentSHA: trackingCommitID,
  226. }
  227. }
  228. }
  229. var outbuf, errbuf strings.Builder
  230. // Enable sparse-checkout
  231. sparseCheckoutList, err := getDiffTree(ctx, tmpBasePath, baseBranch, trackingBranch)
  232. if err != nil {
  233. log.Error("getDiffTree(%s, %s, %s): %v", tmpBasePath, baseBranch, trackingBranch, err)
  234. return "", fmt.Errorf("getDiffTree: %v", err)
  235. }
  236. infoPath := filepath.Join(tmpBasePath, ".git", "info")
  237. if err := os.MkdirAll(infoPath, 0o700); err != nil {
  238. log.Error("Unable to create .git/info in %s: %v", tmpBasePath, err)
  239. return "", fmt.Errorf("Unable to create .git/info in tmpBasePath: %v", err)
  240. }
  241. sparseCheckoutListPath := filepath.Join(infoPath, "sparse-checkout")
  242. if err := os.WriteFile(sparseCheckoutListPath, []byte(sparseCheckoutList), 0o600); err != nil {
  243. log.Error("Unable to write .git/info/sparse-checkout file in %s: %v", tmpBasePath, err)
  244. return "", fmt.Errorf("Unable to write .git/info/sparse-checkout file in tmpBasePath: %v", err)
  245. }
  246. var gitConfigCommand func() *git.Command
  247. if git.CheckGitVersionAtLeast("1.8.0") == nil {
  248. gitConfigCommand = func() *git.Command {
  249. return git.NewCommand(ctx, "config", "--local")
  250. }
  251. } else {
  252. gitConfigCommand = func() *git.Command {
  253. return git.NewCommand(ctx, "config")
  254. }
  255. }
  256. // Switch off LFS process (set required, clean and smudge here also)
  257. if err := gitConfigCommand().AddArguments("filter.lfs.process", "").
  258. Run(&git.RunOpts{
  259. Dir: tmpBasePath,
  260. Stdout: &outbuf,
  261. Stderr: &errbuf,
  262. }); err != nil {
  263. log.Error("git config [filter.lfs.process -> <> ]: %v\n%s\n%s", err, outbuf.String(), errbuf.String())
  264. return "", fmt.Errorf("git config [filter.lfs.process -> <> ]: %v\n%s\n%s", err, outbuf.String(), errbuf.String())
  265. }
  266. outbuf.Reset()
  267. errbuf.Reset()
  268. if err := gitConfigCommand().AddArguments("filter.lfs.required", "false").
  269. Run(&git.RunOpts{
  270. Dir: tmpBasePath,
  271. Stdout: &outbuf,
  272. Stderr: &errbuf,
  273. }); err != nil {
  274. log.Error("git config [filter.lfs.required -> <false> ]: %v\n%s\n%s", err, outbuf.String(), errbuf.String())
  275. return "", fmt.Errorf("git config [filter.lfs.required -> <false> ]: %v\n%s\n%s", err, outbuf.String(), errbuf.String())
  276. }
  277. outbuf.Reset()
  278. errbuf.Reset()
  279. if err := gitConfigCommand().AddArguments("filter.lfs.clean", "").
  280. Run(&git.RunOpts{
  281. Dir: tmpBasePath,
  282. Stdout: &outbuf,
  283. Stderr: &errbuf,
  284. }); err != nil {
  285. log.Error("git config [filter.lfs.clean -> <> ]: %v\n%s\n%s", err, outbuf.String(), errbuf.String())
  286. return "", fmt.Errorf("git config [filter.lfs.clean -> <> ]: %v\n%s\n%s", err, outbuf.String(), errbuf.String())
  287. }
  288. outbuf.Reset()
  289. errbuf.Reset()
  290. if err := gitConfigCommand().AddArguments("filter.lfs.smudge", "").
  291. Run(&git.RunOpts{
  292. Dir: tmpBasePath,
  293. Stdout: &outbuf,
  294. Stderr: &errbuf,
  295. }); err != nil {
  296. log.Error("git config [filter.lfs.smudge -> <> ]: %v\n%s\n%s", err, outbuf.String(), errbuf.String())
  297. return "", fmt.Errorf("git config [filter.lfs.smudge -> <> ]: %v\n%s\n%s", err, outbuf.String(), errbuf.String())
  298. }
  299. outbuf.Reset()
  300. errbuf.Reset()
  301. if err := gitConfigCommand().AddArguments("core.sparseCheckout", "true").
  302. Run(&git.RunOpts{
  303. Dir: tmpBasePath,
  304. Stdout: &outbuf,
  305. Stderr: &errbuf,
  306. }); err != nil {
  307. log.Error("git config [core.sparseCheckout -> true ]: %v\n%s\n%s", err, outbuf.String(), errbuf.String())
  308. return "", fmt.Errorf("git config [core.sparsecheckout -> true]: %v\n%s\n%s", err, outbuf.String(), errbuf.String())
  309. }
  310. outbuf.Reset()
  311. errbuf.Reset()
  312. // Read base branch index
  313. if err := git.NewCommand(ctx, "read-tree", "HEAD").
  314. Run(&git.RunOpts{
  315. Dir: tmpBasePath,
  316. Stdout: &outbuf,
  317. Stderr: &errbuf,
  318. }); err != nil {
  319. log.Error("git read-tree HEAD: %v\n%s\n%s", err, outbuf.String(), errbuf.String())
  320. return "", fmt.Errorf("Unable to read base branch in to the index: %v\n%s\n%s", err, outbuf.String(), errbuf.String())
  321. }
  322. outbuf.Reset()
  323. errbuf.Reset()
  324. sig := doer.NewGitSig()
  325. committer := sig
  326. // Determine if we should sign
  327. signArg := ""
  328. if git.CheckGitVersionAtLeast("1.7.9") == nil {
  329. sign, keyID, signer, _ := asymkey_service.SignMerge(ctx, pr, doer, tmpBasePath, "HEAD", trackingBranch)
  330. if sign {
  331. signArg = "-S" + keyID
  332. if pr.BaseRepo.GetTrustModel() == repo_model.CommitterTrustModel || pr.BaseRepo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
  333. committer = signer
  334. }
  335. } else if git.CheckGitVersionAtLeast("2.0.0") == nil {
  336. signArg = "--no-gpg-sign"
  337. }
  338. }
  339. commitTimeStr := time.Now().Format(time.RFC3339)
  340. // Because this may call hooks we should pass in the environment
  341. env := append(os.Environ(),
  342. "GIT_AUTHOR_NAME="+sig.Name,
  343. "GIT_AUTHOR_EMAIL="+sig.Email,
  344. "GIT_AUTHOR_DATE="+commitTimeStr,
  345. "GIT_COMMITTER_NAME="+committer.Name,
  346. "GIT_COMMITTER_EMAIL="+committer.Email,
  347. "GIT_COMMITTER_DATE="+commitTimeStr,
  348. )
  349. // Merge commits.
  350. switch mergeStyle {
  351. case repo_model.MergeStyleMerge:
  352. cmd := git.NewCommand(ctx, "merge", "--no-ff", "--no-commit", trackingBranch)
  353. if err := runMergeCommand(pr, mergeStyle, cmd, tmpBasePath); err != nil {
  354. log.Error("Unable to merge tracking into base: %v", err)
  355. return "", err
  356. }
  357. if err := commitAndSignNoAuthor(ctx, pr, message, signArg, tmpBasePath, env); err != nil {
  358. log.Error("Unable to make final commit: %v", err)
  359. return "", err
  360. }
  361. case repo_model.MergeStyleRebase:
  362. fallthrough
  363. case repo_model.MergeStyleRebaseUpdate:
  364. fallthrough
  365. case repo_model.MergeStyleRebaseMerge:
  366. // Checkout head branch
  367. if err := git.NewCommand(ctx, "checkout", "-b", stagingBranch, trackingBranch).
  368. Run(&git.RunOpts{
  369. Dir: tmpBasePath,
  370. Stdout: &outbuf,
  371. Stderr: &errbuf,
  372. }); err != nil {
  373. log.Error("git checkout base prior to merge post staging rebase [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  374. return "", fmt.Errorf("git checkout base prior to merge post staging rebase [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  375. }
  376. outbuf.Reset()
  377. errbuf.Reset()
  378. // Rebase before merging
  379. if err := git.NewCommand(ctx, "rebase", baseBranch).
  380. Run(&git.RunOpts{
  381. Dir: tmpBasePath,
  382. Stdout: &outbuf,
  383. Stderr: &errbuf,
  384. }); err != nil {
  385. // Rebase will leave a REBASE_HEAD file in .git if there is a conflict
  386. if _, statErr := os.Stat(filepath.Join(tmpBasePath, ".git", "REBASE_HEAD")); statErr == nil {
  387. var commitSha string
  388. ok := false
  389. failingCommitPaths := []string{
  390. filepath.Join(tmpBasePath, ".git", "rebase-apply", "original-commit"), // Git < 2.26
  391. filepath.Join(tmpBasePath, ".git", "rebase-merge", "stopped-sha"), // Git >= 2.26
  392. }
  393. for _, failingCommitPath := range failingCommitPaths {
  394. if _, statErr := os.Stat(failingCommitPath); statErr == nil {
  395. commitShaBytes, readErr := os.ReadFile(failingCommitPath)
  396. if readErr != nil {
  397. // Abandon this attempt to handle the error
  398. log.Error("git rebase staging on to base [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  399. return "", fmt.Errorf("git rebase staging on to base [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  400. }
  401. commitSha = strings.TrimSpace(string(commitShaBytes))
  402. ok = true
  403. break
  404. }
  405. }
  406. if !ok {
  407. log.Error("Unable to determine failing commit sha for this rebase message. Cannot cast as models.ErrRebaseConflicts.")
  408. log.Error("git rebase staging on to base [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  409. return "", fmt.Errorf("git rebase staging on to base [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  410. }
  411. log.Debug("RebaseConflict at %s [%s:%s -> %s:%s]: %v\n%s\n%s", commitSha, pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  412. return "", models.ErrRebaseConflicts{
  413. Style: mergeStyle,
  414. CommitSHA: commitSha,
  415. StdOut: outbuf.String(),
  416. StdErr: errbuf.String(),
  417. Err: err,
  418. }
  419. }
  420. log.Error("git rebase staging on to base [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  421. return "", fmt.Errorf("git rebase staging on to base [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  422. }
  423. outbuf.Reset()
  424. errbuf.Reset()
  425. // not need merge, just update by rebase. so skip
  426. if mergeStyle == repo_model.MergeStyleRebaseUpdate {
  427. break
  428. }
  429. // Checkout base branch again
  430. if err := git.NewCommand(ctx, "checkout", baseBranch).
  431. Run(&git.RunOpts{
  432. Dir: tmpBasePath,
  433. Stdout: &outbuf,
  434. Stderr: &errbuf,
  435. }); err != nil {
  436. log.Error("git checkout base prior to merge post staging rebase [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  437. return "", fmt.Errorf("git checkout base prior to merge post staging rebase [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  438. }
  439. outbuf.Reset()
  440. errbuf.Reset()
  441. cmd := git.NewCommand(ctx, "merge")
  442. if mergeStyle == repo_model.MergeStyleRebase {
  443. cmd.AddArguments("--ff-only")
  444. } else {
  445. cmd.AddArguments("--no-ff", "--no-commit")
  446. }
  447. cmd.AddArguments(stagingBranch)
  448. // Prepare merge with commit
  449. if err := runMergeCommand(pr, mergeStyle, cmd, tmpBasePath); err != nil {
  450. log.Error("Unable to merge staging into base: %v", err)
  451. return "", err
  452. }
  453. if mergeStyle == repo_model.MergeStyleRebaseMerge {
  454. if err := commitAndSignNoAuthor(ctx, pr, message, signArg, tmpBasePath, env); err != nil {
  455. log.Error("Unable to make final commit: %v", err)
  456. return "", err
  457. }
  458. }
  459. case repo_model.MergeStyleSquash:
  460. // Merge with squash
  461. cmd := git.NewCommand(ctx, "merge", "--squash", trackingBranch)
  462. if err := runMergeCommand(pr, mergeStyle, cmd, tmpBasePath); err != nil {
  463. log.Error("Unable to merge --squash tracking into base: %v", err)
  464. return "", err
  465. }
  466. if err = pr.Issue.LoadPoster(); err != nil {
  467. log.Error("LoadPoster: %v", err)
  468. return "", fmt.Errorf("LoadPoster: %v", err)
  469. }
  470. sig := pr.Issue.Poster.NewGitSig()
  471. if signArg == "" {
  472. if err := git.NewCommand(ctx, "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", message).
  473. Run(&git.RunOpts{
  474. Env: env,
  475. Dir: tmpBasePath,
  476. Stdout: &outbuf,
  477. Stderr: &errbuf,
  478. }); err != nil {
  479. log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  480. return "", fmt.Errorf("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  481. }
  482. } else {
  483. if setting.Repository.PullRequest.AddCoCommitterTrailers && committer.String() != sig.String() {
  484. // add trailer
  485. message += fmt.Sprintf("\nCo-authored-by: %s\nCo-committed-by: %s\n", sig.String(), sig.String())
  486. }
  487. if err := git.NewCommand(ctx, "commit", signArg, fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", message).
  488. Run(&git.RunOpts{
  489. Env: env,
  490. Dir: tmpBasePath,
  491. Stdout: &outbuf,
  492. Stderr: &errbuf,
  493. }); err != nil {
  494. log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  495. return "", fmt.Errorf("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  496. }
  497. }
  498. outbuf.Reset()
  499. errbuf.Reset()
  500. default:
  501. return "", models.ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: mergeStyle}
  502. }
  503. // OK we should cache our current head and origin/headbranch
  504. mergeHeadSHA, err := git.GetFullCommitID(ctx, tmpBasePath, "HEAD")
  505. if err != nil {
  506. return "", fmt.Errorf("Failed to get full commit id for HEAD: %v", err)
  507. }
  508. mergeBaseSHA, err := git.GetFullCommitID(ctx, tmpBasePath, "original_"+baseBranch)
  509. if err != nil {
  510. return "", fmt.Errorf("Failed to get full commit id for origin/%s: %v", pr.BaseBranch, err)
  511. }
  512. mergeCommitID, err := git.GetFullCommitID(ctx, tmpBasePath, baseBranch)
  513. if err != nil {
  514. return "", fmt.Errorf("Failed to get full commit id for the new merge: %v", err)
  515. }
  516. // Now it's questionable about where this should go - either after or before the push
  517. // I think in the interests of data safety - failures to push to the lfs should prevent
  518. // the merge as you can always remerge.
  519. if setting.LFS.StartServer {
  520. if err := LFSPush(ctx, tmpBasePath, mergeHeadSHA, mergeBaseSHA, pr); err != nil {
  521. return "", err
  522. }
  523. }
  524. var headUser *user_model.User
  525. err = pr.HeadRepo.GetOwner(ctx)
  526. if err != nil {
  527. if !user_model.IsErrUserNotExist(err) {
  528. log.Error("Can't find user: %d for head repository - %v", pr.HeadRepo.OwnerID, err)
  529. return "", err
  530. }
  531. log.Error("Can't find user: %d for head repository - defaulting to doer: %s - %v", pr.HeadRepo.OwnerID, doer.Name, err)
  532. headUser = doer
  533. } else {
  534. headUser = pr.HeadRepo.Owner
  535. }
  536. env = repo_module.FullPushingEnvironment(
  537. headUser,
  538. doer,
  539. pr.BaseRepo,
  540. pr.BaseRepo.Name,
  541. pr.ID,
  542. )
  543. var pushCmd *git.Command
  544. if mergeStyle == repo_model.MergeStyleRebaseUpdate {
  545. // force push the rebase result to head branch
  546. pushCmd = git.NewCommand(ctx, "push", "-f", "head_repo", stagingBranch+":"+git.BranchPrefix+pr.HeadBranch)
  547. } else {
  548. pushCmd = git.NewCommand(ctx, "push", "origin", baseBranch+":"+git.BranchPrefix+pr.BaseBranch)
  549. }
  550. // Push back to upstream.
  551. // TODO: this cause an api call to "/api/internal/hook/post-receive/...",
  552. // that prevents us from doint the whole merge in one db transaction
  553. if err := pushCmd.Run(&git.RunOpts{
  554. Env: env,
  555. Dir: tmpBasePath,
  556. Stdout: &outbuf,
  557. Stderr: &errbuf,
  558. }); err != nil {
  559. if strings.Contains(errbuf.String(), "non-fast-forward") {
  560. return "", &git.ErrPushOutOfDate{
  561. StdOut: outbuf.String(),
  562. StdErr: errbuf.String(),
  563. Err: err,
  564. }
  565. } else if strings.Contains(errbuf.String(), "! [remote rejected]") {
  566. err := &git.ErrPushRejected{
  567. StdOut: outbuf.String(),
  568. StdErr: errbuf.String(),
  569. Err: err,
  570. }
  571. err.GenerateMessage()
  572. return "", err
  573. }
  574. return "", fmt.Errorf("git push: %s", errbuf.String())
  575. }
  576. outbuf.Reset()
  577. errbuf.Reset()
  578. return mergeCommitID, nil
  579. }
  580. func commitAndSignNoAuthor(ctx context.Context, pr *models.PullRequest, message, signArg, tmpBasePath string, env []string) error {
  581. var outbuf, errbuf strings.Builder
  582. if signArg == "" {
  583. if err := git.NewCommand(ctx, "commit", "-m", message).
  584. Run(&git.RunOpts{
  585. Env: env,
  586. Dir: tmpBasePath,
  587. Stdout: &outbuf,
  588. Stderr: &errbuf,
  589. }); err != nil {
  590. log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  591. return fmt.Errorf("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  592. }
  593. } else {
  594. if err := git.NewCommand(ctx, "commit", signArg, "-m", message).
  595. Run(&git.RunOpts{
  596. Env: env,
  597. Dir: tmpBasePath,
  598. Stdout: &outbuf,
  599. Stderr: &errbuf,
  600. }); err != nil {
  601. log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  602. return fmt.Errorf("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  603. }
  604. }
  605. return nil
  606. }
  607. func runMergeCommand(pr *models.PullRequest, mergeStyle repo_model.MergeStyle, cmd *git.Command, tmpBasePath string) error {
  608. var outbuf, errbuf strings.Builder
  609. if err := cmd.Run(&git.RunOpts{
  610. Dir: tmpBasePath,
  611. Stdout: &outbuf,
  612. Stderr: &errbuf,
  613. }); err != nil {
  614. // Merge will leave a MERGE_HEAD file in the .git folder if there is a conflict
  615. if _, statErr := os.Stat(filepath.Join(tmpBasePath, ".git", "MERGE_HEAD")); statErr == nil {
  616. // We have a merge conflict error
  617. log.Debug("MergeConflict [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  618. return models.ErrMergeConflicts{
  619. Style: mergeStyle,
  620. StdOut: outbuf.String(),
  621. StdErr: errbuf.String(),
  622. Err: err,
  623. }
  624. } else if strings.Contains(errbuf.String(), "refusing to merge unrelated histories") {
  625. log.Debug("MergeUnrelatedHistories [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  626. return models.ErrMergeUnrelatedHistories{
  627. Style: mergeStyle,
  628. StdOut: outbuf.String(),
  629. StdErr: errbuf.String(),
  630. Err: err,
  631. }
  632. }
  633. log.Error("git merge [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  634. return fmt.Errorf("git merge [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
  635. }
  636. return nil
  637. }
  638. var escapedSymbols = regexp.MustCompile(`([*[?! \\])`)
  639. func getDiffTree(ctx context.Context, repoPath, baseBranch, headBranch string) (string, error) {
  640. getDiffTreeFromBranch := func(repoPath, baseBranch, headBranch string) (string, error) {
  641. var outbuf, errbuf strings.Builder
  642. // Compute the diff-tree for sparse-checkout
  643. if err := git.NewCommand(ctx, "diff-tree", "--no-commit-id", "--name-only", "-r", "-z", "--root", baseBranch, headBranch, "--").
  644. Run(&git.RunOpts{
  645. Dir: repoPath,
  646. Stdout: &outbuf,
  647. Stderr: &errbuf,
  648. }); err != nil {
  649. return "", fmt.Errorf("git diff-tree [%s base:%s head:%s]: %s", repoPath, baseBranch, headBranch, errbuf.String())
  650. }
  651. return outbuf.String(), nil
  652. }
  653. scanNullTerminatedStrings := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
  654. if atEOF && len(data) == 0 {
  655. return 0, nil, nil
  656. }
  657. if i := bytes.IndexByte(data, '\x00'); i >= 0 {
  658. return i + 1, data[0:i], nil
  659. }
  660. if atEOF {
  661. return len(data), data, nil
  662. }
  663. return 0, nil, nil
  664. }
  665. list, err := getDiffTreeFromBranch(repoPath, baseBranch, headBranch)
  666. if err != nil {
  667. return "", err
  668. }
  669. // Prefixing '/' for each entry, otherwise all files with the same name in subdirectories would be matched.
  670. out := bytes.Buffer{}
  671. scanner := bufio.NewScanner(strings.NewReader(list))
  672. scanner.Split(scanNullTerminatedStrings)
  673. for scanner.Scan() {
  674. filepath := scanner.Text()
  675. // escape '*', '?', '[', spaces and '!' prefix
  676. filepath = escapedSymbols.ReplaceAllString(filepath, `\$1`)
  677. // no necessary to escape the first '#' symbol because the first symbol is '/'
  678. fmt.Fprintf(&out, "/%s\n", filepath)
  679. }
  680. return out.String(), nil
  681. }
  682. // IsUserAllowedToMerge check if user is allowed to merge PR with given permissions and branch protections
  683. func IsUserAllowedToMerge(ctx context.Context, pr *models.PullRequest, p models.Permission, user *user_model.User) (bool, error) {
  684. if user == nil {
  685. return false, nil
  686. }
  687. err := pr.LoadProtectedBranchCtx(ctx)
  688. if err != nil {
  689. return false, err
  690. }
  691. if (p.CanWrite(unit.TypeCode) && pr.ProtectedBranch == nil) || (pr.ProtectedBranch != nil && models.IsUserMergeWhitelisted(ctx, pr.ProtectedBranch, user.ID, p)) {
  692. return true, nil
  693. }
  694. return false, nil
  695. }
  696. // CheckPullBranchProtections checks whether the PR is ready to be merged (reviews and status checks)
  697. func CheckPullBranchProtections(ctx context.Context, pr *models.PullRequest, skipProtectedFilesCheck bool) (err error) {
  698. if err = pr.LoadBaseRepoCtx(ctx); err != nil {
  699. return fmt.Errorf("LoadBaseRepo: %v", err)
  700. }
  701. if err = pr.LoadProtectedBranchCtx(ctx); err != nil {
  702. return fmt.Errorf("LoadProtectedBranch: %v", err)
  703. }
  704. if pr.ProtectedBranch == nil {
  705. return nil
  706. }
  707. isPass, err := IsPullCommitStatusPass(ctx, pr)
  708. if err != nil {
  709. return err
  710. }
  711. if !isPass {
  712. return models.ErrDisallowedToMerge{
  713. Reason: "Not all required status checks successful",
  714. }
  715. }
  716. if !pr.ProtectedBranch.HasEnoughApprovals(ctx, pr) {
  717. return models.ErrDisallowedToMerge{
  718. Reason: "Does not have enough approvals",
  719. }
  720. }
  721. if pr.ProtectedBranch.MergeBlockedByRejectedReview(ctx, pr) {
  722. return models.ErrDisallowedToMerge{
  723. Reason: "There are requested changes",
  724. }
  725. }
  726. if pr.ProtectedBranch.MergeBlockedByOfficialReviewRequests(ctx, pr) {
  727. return models.ErrDisallowedToMerge{
  728. Reason: "There are official review requests",
  729. }
  730. }
  731. if pr.ProtectedBranch.MergeBlockedByOutdatedBranch(pr) {
  732. return models.ErrDisallowedToMerge{
  733. Reason: "The head branch is behind the base branch",
  734. }
  735. }
  736. if skipProtectedFilesCheck {
  737. return nil
  738. }
  739. if pr.ProtectedBranch.MergeBlockedByProtectedFiles(pr) {
  740. return models.ErrDisallowedToMerge{
  741. Reason: "Changed protected files",
  742. }
  743. }
  744. return nil
  745. }
  746. // MergedManually mark pr as merged manually
  747. func MergedManually(pr *models.PullRequest, doer *user_model.User, baseGitRepo *git.Repository, commitID string) error {
  748. pullWorkingPool.CheckIn(fmt.Sprint(pr.ID))
  749. defer pullWorkingPool.CheckOut(fmt.Sprint(pr.ID))
  750. if err := db.WithTx(func(ctx context.Context) error {
  751. prUnit, err := pr.BaseRepo.GetUnitCtx(ctx, unit.TypePullRequests)
  752. if err != nil {
  753. return err
  754. }
  755. prConfig := prUnit.PullRequestsConfig()
  756. // Check if merge style is correct and allowed
  757. if !prConfig.IsMergeStyleAllowed(repo_model.MergeStyleManuallyMerged) {
  758. return models.ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: repo_model.MergeStyleManuallyMerged}
  759. }
  760. if len(commitID) < 40 {
  761. return fmt.Errorf("Wrong commit ID")
  762. }
  763. commit, err := baseGitRepo.GetCommit(commitID)
  764. if err != nil {
  765. if git.IsErrNotExist(err) {
  766. return fmt.Errorf("Wrong commit ID")
  767. }
  768. return err
  769. }
  770. commitID = commit.ID.String()
  771. ok, err := baseGitRepo.IsCommitInBranch(commitID, pr.BaseBranch)
  772. if err != nil {
  773. return err
  774. }
  775. if !ok {
  776. return fmt.Errorf("Wrong commit ID")
  777. }
  778. pr.MergedCommitID = commitID
  779. pr.MergedUnix = timeutil.TimeStamp(commit.Author.When.Unix())
  780. pr.Status = models.PullRequestStatusManuallyMerged
  781. pr.Merger = doer
  782. pr.MergerID = doer.ID
  783. merged := false
  784. if merged, err = pr.SetMerged(ctx); err != nil {
  785. return err
  786. } else if !merged {
  787. return fmt.Errorf("SetMerged failed")
  788. }
  789. return nil
  790. }); err != nil {
  791. return err
  792. }
  793. notification.NotifyMergePullRequest(pr, doer)
  794. log.Info("manuallyMerged[%d]: Marked as manually merged into %s/%s by commit id: %s", pr.ID, pr.BaseRepo.Name, pr.BaseBranch, commitID)
  795. return nil
  796. }