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