gitea源码

temp_repo.go 8.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. // Copyright 2019 The Gitea Authors.
  2. // All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package pull
  5. import (
  6. "context"
  7. "fmt"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. git_model "code.gitea.io/gitea/models/git"
  12. issues_model "code.gitea.io/gitea/models/issues"
  13. repo_model "code.gitea.io/gitea/models/repo"
  14. "code.gitea.io/gitea/modules/git"
  15. "code.gitea.io/gitea/modules/git/gitcmd"
  16. "code.gitea.io/gitea/modules/gitrepo"
  17. "code.gitea.io/gitea/modules/log"
  18. repo_module "code.gitea.io/gitea/modules/repository"
  19. )
  20. // Temporary repos created here use standard branch names to help simplify
  21. // merging code
  22. const (
  23. baseBranch = "base" // equivalent to pr.BaseBranch
  24. trackingBranch = "tracking" // equivalent to pr.HeadBranch
  25. stagingBranch = "staging" // this is used for a working branch
  26. )
  27. type prTmpRepoContext struct {
  28. context.Context
  29. tmpBasePath string
  30. pr *issues_model.PullRequest
  31. outbuf *strings.Builder // we keep these around to help reduce needless buffer recreation,
  32. errbuf *strings.Builder // any use should be preceded by a Reset and preferably after use
  33. }
  34. func (ctx *prTmpRepoContext) RunOpts() *gitcmd.RunOpts {
  35. ctx.outbuf.Reset()
  36. ctx.errbuf.Reset()
  37. return &gitcmd.RunOpts{
  38. Dir: ctx.tmpBasePath,
  39. Stdout: ctx.outbuf,
  40. Stderr: ctx.errbuf,
  41. }
  42. }
  43. // createTemporaryRepoForPR creates a temporary repo with "base" for pr.BaseBranch and "tracking" for pr.HeadBranch
  44. // it also create a second base branch called "original_base"
  45. func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) (prCtx *prTmpRepoContext, cancel context.CancelFunc, err error) {
  46. if err := pr.LoadHeadRepo(ctx); err != nil {
  47. log.Error("%-v LoadHeadRepo: %v", pr, err)
  48. return nil, nil, fmt.Errorf("%v LoadHeadRepo: %w", pr, err)
  49. } else if pr.HeadRepo == nil {
  50. log.Error("%-v HeadRepo %d does not exist", pr, pr.HeadRepoID)
  51. return nil, nil, &repo_model.ErrRepoNotExist{
  52. ID: pr.HeadRepoID,
  53. }
  54. } else if err := pr.LoadBaseRepo(ctx); err != nil {
  55. log.Error("%-v LoadBaseRepo: %v", pr, err)
  56. return nil, nil, fmt.Errorf("%v LoadBaseRepo: %w", pr, err)
  57. } else if pr.BaseRepo == nil {
  58. log.Error("%-v BaseRepo %d does not exist", pr, pr.BaseRepoID)
  59. return nil, nil, &repo_model.ErrRepoNotExist{
  60. ID: pr.BaseRepoID,
  61. }
  62. } else if err := pr.HeadRepo.LoadOwner(ctx); err != nil {
  63. log.Error("%-v HeadRepo.LoadOwner: %v", pr, err)
  64. return nil, nil, fmt.Errorf("%v HeadRepo.LoadOwner: %w", pr, err)
  65. } else if err := pr.BaseRepo.LoadOwner(ctx); err != nil {
  66. log.Error("%-v BaseRepo.LoadOwner: %v", pr, err)
  67. return nil, nil, fmt.Errorf("%v BaseRepo.LoadOwner: %w", pr, err)
  68. }
  69. // Clone base repo.
  70. tmpBasePath, cleanup, err := repo_module.CreateTemporaryPath("pull")
  71. if err != nil {
  72. log.Error("CreateTemporaryPath[%-v]: %v", pr, err)
  73. return nil, nil, err
  74. }
  75. cancel = cleanup
  76. prCtx = &prTmpRepoContext{
  77. Context: ctx,
  78. tmpBasePath: tmpBasePath,
  79. pr: pr,
  80. outbuf: &strings.Builder{},
  81. errbuf: &strings.Builder{},
  82. }
  83. baseRepoPath := pr.BaseRepo.RepoPath()
  84. headRepoPath := pr.HeadRepo.RepoPath()
  85. if err := git.InitRepository(ctx, tmpBasePath, false, pr.BaseRepo.ObjectFormatName); err != nil {
  86. log.Error("Unable to init tmpBasePath for %-v: %v", pr, err)
  87. cancel()
  88. return nil, nil, err
  89. }
  90. remoteRepoName := "head_repo"
  91. baseBranch := "base"
  92. fetchArgs := gitcmd.TrustedCmdArgs{"--no-tags"}
  93. if git.DefaultFeatures().CheckVersionAtLeast("2.25.0") {
  94. // Writing the commit graph can be slow and is not needed here
  95. fetchArgs = append(fetchArgs, "--no-write-commit-graph")
  96. }
  97. // addCacheRepo adds git alternatives for the cacheRepoPath in the repoPath
  98. addCacheRepo := func(repoPath, cacheRepoPath string) error {
  99. p := filepath.Join(repoPath, ".git", "objects", "info", "alternates")
  100. f, err := os.OpenFile(p, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)
  101. if err != nil {
  102. log.Error("Could not create .git/objects/info/alternates file in %s: %v", repoPath, err)
  103. return err
  104. }
  105. defer f.Close()
  106. data := filepath.Join(cacheRepoPath, "objects")
  107. if _, err := fmt.Fprintln(f, data); err != nil {
  108. log.Error("Could not write to .git/objects/info/alternates file in %s: %v", repoPath, err)
  109. return err
  110. }
  111. return nil
  112. }
  113. // Add head repo remote.
  114. if err := addCacheRepo(tmpBasePath, baseRepoPath); err != nil {
  115. log.Error("%-v Unable to add base repository to temporary repo [%s -> %s]: %v", pr, pr.BaseRepo.FullName(), tmpBasePath, err)
  116. cancel()
  117. return nil, nil, fmt.Errorf("Unable to add base repository to temporary repo [%s -> tmpBasePath]: %w", pr.BaseRepo.FullName(), err)
  118. }
  119. if err := gitcmd.NewCommand("remote", "add", "-t").AddDynamicArguments(pr.BaseBranch).AddArguments("-m").AddDynamicArguments(pr.BaseBranch).AddDynamicArguments("origin", baseRepoPath).
  120. Run(ctx, prCtx.RunOpts()); err != nil {
  121. log.Error("%-v Unable to add base repository as origin [%s -> %s]: %v\n%s\n%s", pr, pr.BaseRepo.FullName(), tmpBasePath, err, prCtx.outbuf.String(), prCtx.errbuf.String())
  122. cancel()
  123. return nil, nil, fmt.Errorf("Unable to add base repository as origin [%s -> tmpBasePath]: %w\n%s\n%s", pr.BaseRepo.FullName(), err, prCtx.outbuf.String(), prCtx.errbuf.String())
  124. }
  125. if err := gitcmd.NewCommand("fetch", "origin").AddArguments(fetchArgs...).AddDashesAndList(pr.BaseBranch+":"+baseBranch, pr.BaseBranch+":original_"+baseBranch).
  126. Run(ctx, prCtx.RunOpts()); err != nil {
  127. log.Error("%-v Unable to fetch origin base branch [%s:%s -> base, original_base in %s]: %v:\n%s\n%s", pr, pr.BaseRepo.FullName(), pr.BaseBranch, tmpBasePath, err, prCtx.outbuf.String(), prCtx.errbuf.String())
  128. cancel()
  129. return nil, nil, fmt.Errorf("Unable to fetch origin base branch [%s:%s -> base, original_base in tmpBasePath]: %w\n%s\n%s", pr.BaseRepo.FullName(), pr.BaseBranch, err, prCtx.outbuf.String(), prCtx.errbuf.String())
  130. }
  131. if err := gitcmd.NewCommand("symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseBranch).
  132. Run(ctx, prCtx.RunOpts()); err != nil {
  133. log.Error("%-v Unable to set HEAD as base branch in [%s]: %v\n%s\n%s", pr, tmpBasePath, err, prCtx.outbuf.String(), prCtx.errbuf.String())
  134. cancel()
  135. return nil, nil, fmt.Errorf("Unable to set HEAD as base branch in tmpBasePath: %w\n%s\n%s", err, prCtx.outbuf.String(), prCtx.errbuf.String())
  136. }
  137. if err := addCacheRepo(tmpBasePath, headRepoPath); err != nil {
  138. log.Error("%-v Unable to add head repository to temporary repo [%s -> %s]: %v", pr, pr.HeadRepo.FullName(), tmpBasePath, err)
  139. cancel()
  140. return nil, nil, fmt.Errorf("Unable to add head base repository to temporary repo [%s -> tmpBasePath]: %w", pr.HeadRepo.FullName(), err)
  141. }
  142. if err := gitcmd.NewCommand("remote", "add").AddDynamicArguments(remoteRepoName, headRepoPath).
  143. Run(ctx, prCtx.RunOpts()); err != nil {
  144. log.Error("%-v Unable to add head repository as head_repo [%s -> %s]: %v\n%s\n%s", pr, pr.HeadRepo.FullName(), tmpBasePath, err, prCtx.outbuf.String(), prCtx.errbuf.String())
  145. cancel()
  146. return nil, nil, fmt.Errorf("Unable to add head repository as head_repo [%s -> tmpBasePath]: %w\n%s\n%s", pr.HeadRepo.FullName(), err, prCtx.outbuf.String(), prCtx.errbuf.String())
  147. }
  148. trackingBranch := "tracking"
  149. objectFormat := git.ObjectFormatFromName(pr.BaseRepo.ObjectFormatName)
  150. // Fetch head branch
  151. var headBranch string
  152. if pr.Flow == issues_model.PullRequestFlowGithub {
  153. headBranch = git.BranchPrefix + pr.HeadBranch
  154. } else if len(pr.HeadCommitID) == objectFormat.FullLength() { // for not created pull request
  155. headBranch = pr.HeadCommitID
  156. } else {
  157. headBranch = pr.GetGitHeadRefName()
  158. }
  159. if err := gitcmd.NewCommand("fetch").AddArguments(fetchArgs...).AddDynamicArguments(remoteRepoName, headBranch+":"+trackingBranch).
  160. Run(ctx, prCtx.RunOpts()); err != nil {
  161. cancel()
  162. if !gitrepo.IsBranchExist(ctx, pr.HeadRepo, pr.HeadBranch) {
  163. return nil, nil, git_model.ErrBranchNotExist{
  164. BranchName: pr.HeadBranch,
  165. }
  166. }
  167. log.Error("%-v Unable to fetch head_repo head branch [%s:%s -> tracking in %s]: %v:\n%s\n%s", pr, pr.HeadRepo.FullName(), pr.HeadBranch, tmpBasePath, err, prCtx.outbuf.String(), prCtx.errbuf.String())
  168. return nil, nil, fmt.Errorf("Unable to fetch head_repo head branch [%s:%s -> tracking in tmpBasePath]: %w\n%s\n%s", pr.HeadRepo.FullName(), headBranch, err, prCtx.outbuf.String(), prCtx.errbuf.String())
  169. }
  170. prCtx.outbuf.Reset()
  171. prCtx.errbuf.Reset()
  172. return prCtx, cancel, nil
  173. }