From a2268498b6b5c1ad571c3c13ffc628478b550067 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 27 Sep 2025 17:08:17 -0700 Subject: [PATCH 01/14] Move some functions to gitrepo package --- cmd/admin.go | 4 +- modules/gitrepo/blame.go | 2 +- modules/gitrepo/branch.go | 12 +++--- modules/gitrepo/command.go | 21 ++++++++++- modules/gitrepo/config.go | 6 +-- modules/gitrepo/diff.go | 2 +- modules/gitrepo/remote.go | 4 +- modules/gitrepo/size.go | 37 +++++++++++++++++++ modules/indexer/code/bleve/bleve.go | 3 +- .../code/elasticsearch/elasticsearch.go | 3 +- modules/indexer/code/git.go | 7 ++-- modules/repository/create.go | 33 +---------------- modules/repository/create_test.go | 3 +- routers/private/hook_pre_receive.go | 6 ++- services/actions/notifier_helper.go | 16 ++++---- services/agit/agit.go | 8 ++-- services/context/repo.go | 2 +- services/convert/pull.go | 6 +-- services/doctor/heads.go | 7 +++- services/doctor/mergebase.go | 10 ++--- services/migrations/gitea_uploader.go | 4 +- services/mirror/mirror_pull.go | 2 +- services/pull/check.go | 12 +++--- services/release/release.go | 5 ++- services/repository/avatar.go | 2 +- services/repository/check.go | 17 ++++----- .../repository/commitstatus/commitstatus.go | 4 +- services/repository/create.go | 6 +-- services/repository/migrate.go | 6 +-- tests/integration/api_repo_git_tags_test.go | 5 +-- tests/integration/editor_test.go | 3 +- tests/integration/pull_merge_test.go | 21 ++++------- tests/integration/repo_branch_test.go | 6 +-- 33 files changed, 157 insertions(+), 128 deletions(-) create mode 100644 modules/gitrepo/size.go diff --git a/cmd/admin.go b/cmd/admin.go index 5c58a40ca27de..a01274b90e932 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -121,7 +121,7 @@ func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error { } log.Trace("Processing next %d repos of %d", len(repos), count) for _, repo := range repos { - log.Trace("Synchronizing repo %s with path %s", repo.FullName(), repo.RepoPath()) + log.Trace("Synchronizing repo %s with path %s", repo.FullName(), repo.RelativePath()) gitRepo, err := gitrepo.OpenRepository(ctx, repo) if err != nil { log.Warn("OpenRepository: %v", err) @@ -147,7 +147,7 @@ func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error { continue } - log.Trace(" repo %s releases synchronized to tags: from %d to %d", + log.Trace("repo %s releases synchronized to tags: from %d to %d", repo.FullName(), oldnum, count) gitRepo.Close() } diff --git a/modules/gitrepo/blame.go b/modules/gitrepo/blame.go index 02ada5813010d..3ce808d9b3cdb 100644 --- a/modules/gitrepo/blame.go +++ b/modules/gitrepo/blame.go @@ -10,7 +10,7 @@ import ( ) func LineBlame(ctx context.Context, repo Repository, revision, file string, line uint) (string, error) { - return runCmdString(ctx, repo, + return RunCmdString(ctx, repo, gitcmd.NewCommand("blame"). AddOptionFormat("-L %d,%d", line, line). AddOptionValues("-p", revision). diff --git a/modules/gitrepo/branch.go b/modules/gitrepo/branch.go index b857b2ad4773e..e05d75caf8393 100644 --- a/modules/gitrepo/branch.go +++ b/modules/gitrepo/branch.go @@ -36,14 +36,14 @@ func GetBranchCommitID(ctx context.Context, repo Repository, branch string) (str // SetDefaultBranch sets default branch of repository. func SetDefaultBranch(ctx context.Context, repo Repository, name string) error { - _, err := runCmdString(ctx, repo, gitcmd.NewCommand("symbolic-ref", "HEAD"). + _, err := RunCmdString(ctx, repo, gitcmd.NewCommand("symbolic-ref", "HEAD"). AddDynamicArguments(git.BranchPrefix+name)) return err } // GetDefaultBranch gets default branch of repository. func GetDefaultBranch(ctx context.Context, repo Repository) (string, error) { - stdout, err := runCmdString(ctx, repo, gitcmd.NewCommand("symbolic-ref", "HEAD")) + stdout, err := RunCmdString(ctx, repo, gitcmd.NewCommand("symbolic-ref", "HEAD")) if err != nil { return "", err } @@ -56,7 +56,7 @@ func GetDefaultBranch(ctx context.Context, repo Repository) (string, error) { // IsReferenceExist returns true if given reference exists in the repository. func IsReferenceExist(ctx context.Context, repo Repository, name string) bool { - _, err := runCmdString(ctx, repo, gitcmd.NewCommand("show-ref", "--verify").AddDashesAndList(name)) + _, err := RunCmdString(ctx, repo, gitcmd.NewCommand("show-ref", "--verify").AddDashesAndList(name)) return err == nil } @@ -76,7 +76,7 @@ func DeleteBranch(ctx context.Context, repo Repository, name string, force bool) } cmd.AddDashesAndList(name) - _, err := runCmdString(ctx, repo, cmd) + _, err := RunCmdString(ctx, repo, cmd) return err } @@ -85,12 +85,12 @@ func CreateBranch(ctx context.Context, repo Repository, branch, oldbranchOrCommi cmd := gitcmd.NewCommand("branch") cmd.AddDashesAndList(branch, oldbranchOrCommit) - _, err := runCmdString(ctx, repo, cmd) + _, err := RunCmdString(ctx, repo, cmd) return err } // RenameBranch rename a branch func RenameBranch(ctx context.Context, repo Repository, from, to string) error { - _, err := runCmdString(ctx, repo, gitcmd.NewCommand("branch", "-m").AddDynamicArguments(from, to)) + _, err := RunCmdString(ctx, repo, gitcmd.NewCommand("branch", "-m").AddDynamicArguments(from, to)) return err } diff --git a/modules/gitrepo/command.go b/modules/gitrepo/command.go index 58dee2aef0fa6..c260079aad6c6 100644 --- a/modules/gitrepo/command.go +++ b/modules/gitrepo/command.go @@ -9,7 +9,24 @@ import ( "code.gitea.io/gitea/modules/git/gitcmd" ) -func runCmdString(ctx context.Context, repo Repository, cmd *gitcmd.Command) (string, error) { - res, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)}) +type CmdOption struct { + Env []string +} + +func WithEnv(env []string) func(opt *CmdOption) { + return func(opt *CmdOption) { + opt.Env = env + } +} + +func RunCmdString(ctx context.Context, repo Repository, cmd *gitcmd.Command, opts ...func(opt *CmdOption)) (string, error) { + var opt CmdOption + for _, o := range opts { + o(&opt) + } + res, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{ + Dir: repoPath(repo), + Env: opt.Env, + }) return res, err } diff --git a/modules/gitrepo/config.go b/modules/gitrepo/config.go index 5dfdb02b94fd6..bc1746fc3fbe1 100644 --- a/modules/gitrepo/config.go +++ b/modules/gitrepo/config.go @@ -12,7 +12,7 @@ import ( ) func GitConfigGet(ctx context.Context, repo Repository, key string) (string, error) { - result, err := runCmdString(ctx, repo, gitcmd.NewCommand("config", "--get"). + result, err := RunCmdString(ctx, repo, gitcmd.NewCommand("config", "--get"). AddDynamicArguments(key)) if err != nil { return "", err @@ -27,7 +27,7 @@ func getRepoConfigLockKey(repoStoragePath string) string { // GitConfigAdd add a git configuration key to a specific value for the given repository. func GitConfigAdd(ctx context.Context, repo Repository, key, value string) error { return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error { - _, err := runCmdString(ctx, repo, gitcmd.NewCommand("config", "--add"). + _, err := RunCmdString(ctx, repo, gitcmd.NewCommand("config", "--add"). AddDynamicArguments(key, value)) return err }) @@ -38,7 +38,7 @@ func GitConfigAdd(ctx context.Context, repo Repository, key, value string) error // If the key exists, it will be updated to the new value. func GitConfigSet(ctx context.Context, repo Repository, key, value string) error { return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error { - _, err := runCmdString(ctx, repo, gitcmd.NewCommand("config"). + _, err := RunCmdString(ctx, repo, gitcmd.NewCommand("config"). AddDynamicArguments(key, value)) return err }) diff --git a/modules/gitrepo/diff.go b/modules/gitrepo/diff.go index 31a7c153b7c5a..c98c3ffcfe4c9 100644 --- a/modules/gitrepo/diff.go +++ b/modules/gitrepo/diff.go @@ -20,7 +20,7 @@ func GetDiffShortStatByCmdArgs(ctx context.Context, repo Repository, trustedArgs // we get: // " 9902 files changed, 2034198 insertions(+), 298800 deletions(-)\n" cmd := gitcmd.NewCommand("diff", "--shortstat").AddArguments(trustedArgs...).AddDynamicArguments(dynamicArgs...) - stdout, err := runCmdString(ctx, repo, cmd) + stdout, err := RunCmdString(ctx, repo, cmd) if err != nil { return 0, 0, 0, err } diff --git a/modules/gitrepo/remote.go b/modules/gitrepo/remote.go index f56f6d4702c43..22b3a03ce2778 100644 --- a/modules/gitrepo/remote.go +++ b/modules/gitrepo/remote.go @@ -36,7 +36,7 @@ func GitRemoteAdd(ctx context.Context, repo Repository, remoteName, remoteURL st return errors.New("unknown remote option: " + string(options[0])) } } - _, err := runCmdString(ctx, repo, cmd.AddDynamicArguments(remoteName, remoteURL)) + _, err := RunCmdString(ctx, repo, cmd.AddDynamicArguments(remoteName, remoteURL)) return err }) } @@ -44,7 +44,7 @@ func GitRemoteAdd(ctx context.Context, repo Repository, remoteName, remoteURL st func GitRemoteRemove(ctx context.Context, repo Repository, remoteName string) error { return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error { cmd := gitcmd.NewCommand("remote", "rm").AddDynamicArguments(remoteName) - _, err := runCmdString(ctx, repo, cmd) + _, err := RunCmdString(ctx, repo, cmd) return err }) } diff --git a/modules/gitrepo/size.go b/modules/gitrepo/size.go new file mode 100644 index 0000000000000..7524bb2542272 --- /dev/null +++ b/modules/gitrepo/size.go @@ -0,0 +1,37 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package gitrepo + +import ( + "os" + "path/filepath" +) + +const notRegularFileMode = os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular + +// CalcRepositorySize returns the disk consumption for a given path +func CalcRepositorySize(repo Repository) (int64, error) { + var size int64 + err := filepath.WalkDir(repoPath(repo), func(_ string, entry os.DirEntry, err error) error { + if os.IsNotExist(err) { // ignore the error because some files (like temp/lock file) may be deleted during traversing. + return nil + } else if err != nil { + return err + } + if entry.IsDir() { + return nil + } + info, err := entry.Info() + if os.IsNotExist(err) { // ignore the error as above + return nil + } else if err != nil { + return err + } + if (info.Mode() & notRegularFileMode) == 0 { + size += info.Size() + } + return nil + }) + return size, err +} diff --git a/modules/indexer/code/bleve/bleve.go b/modules/indexer/code/bleve/bleve.go index c233f491e3e56..0e2d0f879a5ea 100644 --- a/modules/indexer/code/bleve/bleve.go +++ b/modules/indexer/code/bleve/bleve.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git/gitcmd" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/indexer" path_filter "code.gitea.io/gitea/modules/indexer/code/bleve/token/path" "code.gitea.io/gitea/modules/indexer/code/internal" @@ -163,7 +164,7 @@ func (b *Indexer) addUpdate(ctx context.Context, batchWriter git.WriteCloserErro var err error if !update.Sized { var stdout string - stdout, _, err = gitcmd.NewCommand("cat-file", "-s").AddDynamicArguments(update.BlobSha).RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()}) + stdout, err = gitrepo.RunCmdString(ctx, repo, gitcmd.NewCommand("cat-file", "-s").AddDynamicArguments(update.BlobSha)) if err != nil { return err } diff --git a/modules/indexer/code/elasticsearch/elasticsearch.go b/modules/indexer/code/elasticsearch/elasticsearch.go index b08d837a2a944..012c57da291ab 100644 --- a/modules/indexer/code/elasticsearch/elasticsearch.go +++ b/modules/indexer/code/elasticsearch/elasticsearch.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git/gitcmd" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/indexer" "code.gitea.io/gitea/modules/indexer/code/internal" indexer_internal "code.gitea.io/gitea/modules/indexer/internal" @@ -148,7 +149,7 @@ func (b *Indexer) addUpdate(ctx context.Context, batchWriter git.WriteCloserErro var err error if !update.Sized { var stdout string - stdout, _, err = gitcmd.NewCommand("cat-file", "-s").AddDynamicArguments(update.BlobSha).RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()}) + stdout, err = gitrepo.RunCmdString(ctx, repo, gitcmd.NewCommand("cat-file", "-s").AddDynamicArguments(update.BlobSha)) if err != nil { return nil, err } diff --git a/modules/indexer/code/git.go b/modules/indexer/code/git.go index f1513d66b0383..93fa9e2256820 100644 --- a/modules/indexer/code/git.go +++ b/modules/indexer/code/git.go @@ -11,13 +11,14 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git/gitcmd" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/indexer/code/internal" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" ) func getDefaultBranchSha(ctx context.Context, repo *repo_model.Repository) (string, error) { - stdout, _, err := gitcmd.NewCommand("show-ref", "-s").AddDynamicArguments(git.BranchPrefix+repo.DefaultBranch).RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()}) + stdout, err := gitrepo.RunCmdString(ctx, repo, gitcmd.NewCommand("show-ref", "-s").AddDynamicArguments(git.BranchPrefix+repo.DefaultBranch)) if err != nil { return "", err } @@ -34,7 +35,7 @@ func getRepoChanges(ctx context.Context, repo *repo_model.Repository, revision s needGenesis := len(status.CommitSha) == 0 if !needGenesis { hasAncestorCmd := gitcmd.NewCommand("merge-base").AddDynamicArguments(status.CommitSha, revision) - stdout, _, _ := hasAncestorCmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()}) + stdout, _ := gitrepo.RunCmdString(ctx, repo, hasAncestorCmd) needGenesis = len(stdout) == 0 } @@ -100,7 +101,7 @@ func genesisChanges(ctx context.Context, repo *repo_model.Repository, revision s // nonGenesisChanges get changes since the previous indexer update func nonGenesisChanges(ctx context.Context, repo *repo_model.Repository, revision string) (*internal.RepoChanges, error) { diffCmd := gitcmd.NewCommand("diff", "--name-status").AddDynamicArguments(repo.CodeIndexerStatus.CommitSha, revision) - stdout, _, runErr := diffCmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()}) + stdout, runErr := gitrepo.RunCmdString(ctx, repo, diffCmd) if runErr != nil { // previous commit sha may have been removed by a force push, so // try rebuilding from scratch diff --git a/modules/repository/create.go b/modules/repository/create.go index a75598a84b214..f5fb92df407f4 100644 --- a/modules/repository/create.go +++ b/modules/repository/create.go @@ -6,44 +6,15 @@ package repository import ( "context" "fmt" - "os" - "path/filepath" git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/gitrepo" ) -const notRegularFileMode = os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular - -// getDirectorySize returns the disk consumption for a given path -func getDirectorySize(path string) (int64, error) { - var size int64 - err := filepath.WalkDir(path, func(_ string, entry os.DirEntry, err error) error { - if os.IsNotExist(err) { // ignore the error because some files (like temp/lock file) may be deleted during traversing. - return nil - } else if err != nil { - return err - } - if entry.IsDir() { - return nil - } - info, err := entry.Info() - if os.IsNotExist(err) { // ignore the error as above - return nil - } else if err != nil { - return err - } - if (info.Mode() & notRegularFileMode) == 0 { - size += info.Size() - } - return nil - }) - return size, err -} - // UpdateRepoSize updates the repository size, calculating it using getDirectorySize func UpdateRepoSize(ctx context.Context, repo *repo_model.Repository) error { - size, err := getDirectorySize(repo.RepoPath()) + size, err := gitrepo.CalcRepositorySize(repo) if err != nil { return fmt.Errorf("updateSize: %w", err) } diff --git a/modules/repository/create_test.go b/modules/repository/create_test.go index 68b0f4dea1404..a3fca02c2510d 100644 --- a/modules/repository/create_test.go +++ b/modules/repository/create_test.go @@ -8,6 +8,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/gitrepo" "github.com/stretchr/testify/assert" ) @@ -16,7 +17,7 @@ func TestGetDirectorySize(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) repo, err := repo_model.GetRepositoryByID(t.Context(), 1) assert.NoError(t, err) - size, err := getDirectorySize(repo.RepoPath()) + size, err := gitrepo.CalcRepositorySize(repo) assert.NoError(t, err) repo.Size = 8165 // real size on the disk assert.Equal(t, repo.Size, size) diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index 99a0b450d701f..5efb83738021e 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -189,7 +189,11 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r // 2. Disallow force pushes to protected branches if oldCommitID != objectFormat.EmptyObjectID().String() { - output, _, err := gitcmd.NewCommand("rev-list", "--max-count=1").AddDynamicArguments(oldCommitID, "^"+newCommitID).RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath(), Env: ctx.env}) + output, err := gitrepo.RunCmdString(ctx, + repo, + gitcmd.NewCommand("rev-list", "--max-count=1").AddDynamicArguments(oldCommitID, "^"+newCommitID), + gitrepo.WithEnv(ctx.env), + ) if err != nil { log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err) ctx.JSON(http.StatusInternalServerError, private.Response{ diff --git a/services/actions/notifier_helper.go b/services/actions/notifier_helper.go index 66916e9301742..d0d2572b0beac 100644 --- a/services/actions/notifier_helper.go +++ b/services/actions/notifier_helper.go @@ -195,7 +195,7 @@ func notify(ctx context.Context, input *notifyInput) error { } log.Trace("repo %s with commit %s event %s find %d workflows and %d schedules", - input.Repo.RepoPath(), + input.Repo.RelativePath(), commit.ID, input.Event, len(workflows), @@ -204,7 +204,7 @@ func notify(ctx context.Context, input *notifyInput) error { for _, wf := range workflows { if actionsConfig.IsWorkflowDisabled(wf.EntryName) { - log.Trace("repo %s has disable workflows %s", input.Repo.RepoPath(), wf.EntryName) + log.Trace("repo %s has disable workflows %s", input.Repo.RelativePath(), wf.EntryName) continue } @@ -225,7 +225,7 @@ func notify(ctx context.Context, input *notifyInput) error { return fmt.Errorf("DetectWorkflows: %w", err) } if len(baseWorkflows) == 0 { - log.Trace("repo %s with commit %s couldn't find pull_request_target workflows", input.Repo.RepoPath(), baseCommit.ID) + log.Trace("repo %s with commit %s couldn't find pull_request_target workflows", input.Repo.RelativePath(), baseCommit.ID) } else { for _, wf := range baseWorkflows { if wf.TriggerEvent.Name == actions_module.GithubEventPullRequestTarget { @@ -255,11 +255,11 @@ func skipWorkflows(ctx context.Context, input *notifyInput, commit *git.Commit) if slices.Contains(skipWorkflowEvents, input.Event) { for _, s := range setting.Actions.SkipWorkflowStrings { if input.PullRequest != nil && strings.Contains(input.PullRequest.Issue.Title, s) { - log.Debug("repo %s: skipped run for pr %v because of %s string", input.Repo.RepoPath(), input.PullRequest.Issue.ID, s) + log.Debug("repo %s: skipped run for pr %v because of %s string", input.Repo.RelativePath(), input.PullRequest.Issue.ID, s) return true } if strings.Contains(commit.CommitMessage, s) { - log.Debug("repo %s with commit %s: skipped run because of %s string", input.Repo.RepoPath(), commit.ID, s) + log.Debug("repo %s with commit %s: skipped run because of %s string", input.Repo.RelativePath(), commit.ID, s) return true } } @@ -282,7 +282,7 @@ func skipWorkflows(ctx context.Context, input *notifyInput, commit *git.Commit) } } // skip workflow runs events exceeding the maximum of 5 recursive events - log.Debug("repo %s: skipped workflow_run because of recursive event of 5", input.Repo.RepoPath()) + log.Debug("repo %s: skipped workflow_run because of recursive event of 5", input.Repo.RelativePath()) return true } return false @@ -296,7 +296,7 @@ func handleWorkflows( ref string, ) error { if len(detectedWorkflows) == 0 { - log.Trace("repo %s with commit %s couldn't find workflows", input.Repo.RepoPath(), commit.ID) + log.Trace("repo %s with commit %s couldn't find workflows", input.Repo.RelativePath(), commit.ID) return nil } @@ -521,7 +521,7 @@ func handleSchedules( } if len(detectedWorkflows) == 0 { - log.Trace("repo %s with commit %s couldn't find schedules", input.Repo.RepoPath(), commit.ID) + log.Trace("repo %s with commit %s couldn't find schedules", input.Repo.RelativePath(), commit.ID) return nil } diff --git a/services/agit/agit.go b/services/agit/agit.go index 4ed867b358323..8ba14f9b22632 100644 --- a/services/agit/agit.go +++ b/services/agit/agit.go @@ -7,7 +7,6 @@ import ( "context" "encoding/base64" "fmt" - "os" "strings" git_model "code.gitea.io/gitea/models/git" @@ -199,9 +198,10 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git. } if !forcePush.Value() { - output, _, err := gitcmd.NewCommand("rev-list", "--max-count=1"). - AddDynamicArguments(oldCommitID, "^"+opts.NewCommitIDs[i]). - RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath(), Env: os.Environ()}) + output, err := gitrepo.RunCmdString(ctx, repo, + gitcmd.NewCommand("rev-list", "--max-count=1"). + AddDynamicArguments(oldCommitID, "^"+opts.NewCommitIDs[i]), + ) if err != nil { return nil, fmt.Errorf("failed to detect force push: %w", err) } else if len(output) > 0 { diff --git a/services/context/repo.go b/services/context/repo.go index 04ddba70fe07b..0ff1c7ea0337c 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -621,7 +621,7 @@ func RepoAssignment(ctx *Context) { ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, repo) if err != nil { if strings.Contains(err.Error(), "repository does not exist") || strings.Contains(err.Error(), "no such file or directory") { - log.Error("Repository %-v has a broken repository on the file system: %s Error: %v", ctx.Repo.Repository, ctx.Repo.Repository.RepoPath(), err) + log.Error("Repository %-v has a broken repository on the file system: %s Error: %v", ctx.Repo.Repository, ctx.Repo.Repository.RelativePath(), err) ctx.Repo.Repository.MarkAsBrokenEmpty() // Only allow access to base of repo or settings if !isHomeOrSettings { diff --git a/services/convert/pull.go b/services/convert/pull.go index 50237767e2f75..8b783d396aee8 100644 --- a/services/convert/pull.go +++ b/services/convert/pull.go @@ -146,7 +146,7 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u gitRepo, err := gitrepo.OpenRepository(ctx, pr.BaseRepo) if err != nil { - log.Error("OpenRepository[%s]: %v", pr.BaseRepo.RepoPath(), err) + log.Error("OpenRepository[%s]: %v", pr.BaseRepo.RelativePath(), err) return nil } defer gitRepo.Close() @@ -192,7 +192,7 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u headGitRepo, err := gitrepo.OpenRepository(ctx, pr.HeadRepo) if err != nil { - log.Error("OpenRepository[%s]: %v", pr.HeadRepo.RepoPath(), err) + log.Error("OpenRepository[%s]: %v", pr.HeadRepo.RelativePath(), err) return nil } defer headGitRepo.Close() @@ -248,7 +248,7 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u if len(apiPullRequest.Head.Sha) == 0 && len(apiPullRequest.Head.Ref) != 0 { baseGitRepo, err := gitrepo.OpenRepository(ctx, pr.BaseRepo) if err != nil { - log.Error("OpenRepository[%s]: %v", pr.BaseRepo.RepoPath(), err) + log.Error("OpenRepository[%s]: %v", pr.BaseRepo.RelativePath(), err) return nil } defer baseGitRepo.Close() diff --git a/services/doctor/heads.go b/services/doctor/heads.go index 4354505806944..5c2893ce88772 100644 --- a/services/doctor/heads.go +++ b/services/doctor/heads.go @@ -9,6 +9,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git/gitcmd" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" ) @@ -19,9 +20,11 @@ func synchronizeRepoHeads(ctx context.Context, logger log.Logger, autofix bool) numReposUpdated := 0 err := iterateRepositories(ctx, func(repo *repo_model.Repository) error { numRepos++ - _, _, defaultBranchErr := gitcmd.NewCommand("rev-parse").AddDashesAndList(repo.DefaultBranch).RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()}) + _, defaultBranchErr := gitrepo.RunCmdString(ctx, repo, + gitcmd.NewCommand("rev-parse").AddDashesAndList(repo.DefaultBranch)) - head, _, headErr := gitcmd.NewCommand("symbolic-ref", "--short", "HEAD").RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()}) + head, headErr := gitrepo.RunCmdString(ctx, repo, + gitcmd.NewCommand("symbolic-ref", "--short", "HEAD")) // what we expect: default branch is valid, and HEAD points to it if headErr == nil && defaultBranchErr == nil && head == repo.DefaultBranch { diff --git a/services/doctor/mergebase.go b/services/doctor/mergebase.go index a078d54dad69c..852e37f4157ed 100644 --- a/services/doctor/mergebase.go +++ b/services/doctor/mergebase.go @@ -13,6 +13,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git/gitcmd" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" "xorm.io/builder" @@ -37,23 +38,22 @@ func checkPRMergeBase(ctx context.Context, logger log.Logger, autofix bool) erro return iteratePRs(ctx, repo, func(repo *repo_model.Repository, pr *issues_model.PullRequest) error { numPRs++ pr.BaseRepo = repo - repoPath := repo.RepoPath() oldMergeBase := pr.MergeBase if !pr.HasMerged { var err error - pr.MergeBase, _, err = gitcmd.NewCommand("merge-base").AddDashesAndList(pr.BaseBranch, pr.GetGitHeadRefName()).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) + pr.MergeBase, err = gitrepo.RunCmdString(ctx, repo, gitcmd.NewCommand("merge-base").AddDashesAndList(pr.BaseBranch, pr.GetGitHeadRefName())) if err != nil { var err2 error - pr.MergeBase, _, err2 = gitcmd.NewCommand("rev-parse").AddDynamicArguments(git.BranchPrefix+pr.BaseBranch).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) + pr.MergeBase, err2 = gitrepo.RunCmdString(ctx, repo, gitcmd.NewCommand("rev-parse").AddDynamicArguments(git.BranchPrefix+pr.BaseBranch)) if err2 != nil { logger.Warn("Unable to get merge base for PR ID %d, #%d onto %s in %s/%s. Error: %v & %v", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err, err2) return nil } } } else { - parentsString, _, err := gitcmd.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) + parentsString, err := gitrepo.RunCmdString(ctx, repo, gitcmd.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID)) if err != nil { logger.Warn("Unable to get parents for merged PR ID %d, #%d onto %s in %s/%s. Error: %v", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err) return nil @@ -66,7 +66,7 @@ func checkPRMergeBase(ctx context.Context, logger log.Logger, autofix bool) erro refs := append([]string{}, parents[1:]...) refs = append(refs, pr.GetGitHeadRefName()) cmd := gitcmd.NewCommand("merge-base").AddDashesAndList(refs...) - pr.MergeBase, _, err = cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) + pr.MergeBase, err = gitrepo.RunCmdString(ctx, repo, cmd) if err != nil { logger.Warn("Unable to get merge base for merged PR ID %d, #%d onto %s in %s/%s. Error: %v", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err) return nil diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go index 02c61bd0f16c1..4d23060661d9a 100644 --- a/services/migrations/gitea_uploader.go +++ b/services/migrations/gitea_uploader.go @@ -663,7 +663,7 @@ func (g *GiteaLocalUploader) updateGitForPullRequest(ctx context.Context, pr *ba fetchArg = git.BranchPrefix + fetchArg } - _, _, err = gitcmd.NewCommand("fetch", "--no-tags").AddDashesAndList(remote, fetchArg).RunStdString(ctx, &gitcmd.RunOpts{Dir: g.repo.RepoPath()}) + _, err = gitrepo.RunCmdString(ctx, g.repo, gitcmd.NewCommand("fetch", "--no-tags").AddDashesAndList(remote, fetchArg)) if err != nil { log.Error("Fetch branch from %s failed: %v", pr.Head.CloneURL, err) return head, nil @@ -698,7 +698,7 @@ func (g *GiteaLocalUploader) updateGitForPullRequest(ctx context.Context, pr *ba // The SHA is empty log.Warn("Empty reference, no pull head for PR #%d in %s/%s", pr.Number, g.repoOwner, g.repoName) } else { - _, _, err = gitcmd.NewCommand("rev-list", "--quiet", "-1").AddDynamicArguments(pr.Head.SHA).RunStdString(ctx, &gitcmd.RunOpts{Dir: g.repo.RepoPath()}) + _, err = gitrepo.RunCmdString(ctx, g.repo, gitcmd.NewCommand("rev-list", "--quiet", "-1").AddDynamicArguments(pr.Head.SHA)) if err != nil { // Git update-ref remove bad references with a relative path log.Warn("Deprecated local head %s for PR #%d in %s/%s, removing %s", pr.Head.SHA, pr.Number, g.repoOwner, g.repoName, pr.GetGitHeadRefName()) diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index 6a87aa8a2745e..0013e60f6c737 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -640,7 +640,7 @@ func checkAndUpdateEmptyRepository(ctx context.Context, m *repo_model.Mirror, re // Update the is empty and default_branch columns if err := repo_model.UpdateRepositoryColsWithAutoTime(ctx, m.Repo, "default_branch", "is_empty"); err != nil { log.Error("Failed to update default branch of repository %-v. Error: %v", m.Repo, err) - desc := fmt.Sprintf("Failed to update default branch of repository '%s': %v", m.Repo.RepoPath(), err) + desc := fmt.Sprintf("Failed to update default branch of repository '%s': %v", m.Repo.RelativePath(), err) if err = system_model.CreateRepositoryNotice(desc); err != nil { log.Error("CreateRepositoryNotice: %v", err) } diff --git a/services/pull/check.go b/services/pull/check.go index 088fd3702c643..5b28ec9658d58 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -281,9 +281,9 @@ func getMergeCommit(ctx context.Context, pr *issues_model.PullRequest) (*git.Com prHeadRef := pr.GetGitHeadRefName() // Check if the pull request is merged into BaseBranch - if _, _, err := gitcmd.NewCommand("merge-base", "--is-ancestor"). - AddDynamicArguments(prHeadRef, pr.BaseBranch). - RunStdString(ctx, &gitcmd.RunOpts{Dir: pr.BaseRepo.RepoPath()}); err != nil { + if _, err := gitrepo.RunCmdString(ctx, pr.BaseRepo, + gitcmd.NewCommand("merge-base", "--is-ancestor"). + AddDynamicArguments(prHeadRef, pr.BaseBranch)); err != nil { if strings.Contains(err.Error(), "exit status 1") { // prHeadRef is not an ancestor of the base branch return nil, nil @@ -309,9 +309,9 @@ func getMergeCommit(ctx context.Context, pr *issues_model.PullRequest) (*git.Com objectFormat := git.ObjectFormatFromName(pr.BaseRepo.ObjectFormatName) // Get the commit from BaseBranch where the pull request got merged - mergeCommit, _, err := gitcmd.NewCommand("rev-list", "--ancestry-path", "--merges", "--reverse"). - AddDynamicArguments(prHeadCommitID+".."+pr.BaseBranch). - RunStdString(ctx, &gitcmd.RunOpts{Dir: pr.BaseRepo.RepoPath()}) + mergeCommit, err := gitrepo.RunCmdString(ctx, pr.BaseRepo, + gitcmd.NewCommand("rev-list", "--ancestry-path", "--merges", "--reverse"). + AddDynamicArguments(prHeadCommitID+".."+pr.BaseBranch)) if err != nil { return nil, fmt.Errorf("git rev-list --ancestry-path --merges --reverse: %w", err) } else if len(mergeCommit) < objectFormat.FullLength() { diff --git a/services/release/release.go b/services/release/release.go index 3c5392c993897..28061ae8b180c 100644 --- a/services/release/release.go +++ b/services/release/release.go @@ -371,8 +371,9 @@ func DeleteReleaseByID(ctx context.Context, repo *repo_model.Repository, rel *re } } - if stdout, _, err := gitcmd.NewCommand("tag", "-d").AddDashesAndList(rel.TagName). - RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()}); err != nil && !strings.Contains(err.Error(), "not found") { + if stdout, err := gitrepo.RunCmdString(ctx, repo, + gitcmd.NewCommand("tag", "-d").AddDashesAndList(rel.TagName), + ); err != nil && !strings.Contains(err.Error(), "not found") { log.Error("DeleteReleaseByID (git tag -d): %d in %v Failed:\nStdout: %s\nError: %v", rel.ID, repo, stdout, err) return fmt.Errorf("git tag -d: %w", err) } diff --git a/services/repository/avatar.go b/services/repository/avatar.go index 998ac42230c6c..79da629aa6630 100644 --- a/services/repository/avatar.go +++ b/services/repository/avatar.go @@ -43,7 +43,7 @@ func UploadAvatar(ctx context.Context, repo *repo_model.Repository, data []byte) _, err := w.Write(avatarData) return err }); err != nil { - return fmt.Errorf("UploadAvatar %s failed: Failed to remove old repo avatar %s: %w", repo.RepoPath(), newAvatar, err) + return fmt.Errorf("UploadAvatar %s failed: Failed to remove old repo avatar %s: %w", repo.RelativePath(), newAvatar, err) } if len(oldAvatarPath) > 0 { diff --git a/services/repository/check.go b/services/repository/check.go index 46394ba48e43f..57d627c63d2ea 100644 --- a/services/repository/check.go +++ b/services/repository/check.go @@ -17,7 +17,6 @@ import ( "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" - "code.gitea.io/gitea/modules/util" "xorm.io/builder" ) @@ -89,24 +88,24 @@ func GitGcRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Du command := gitcmd.NewCommand("gc").AddArguments(args...) var stdout string var err error - stdout, _, err = command.RunStdString(ctx, &gitcmd.RunOpts{Timeout: timeout, Dir: repo.RepoPath()}) + stdout, err = gitrepo.RunCmdString(ctx, repo, command) if err != nil { log.Error("Repository garbage collection failed for %-v. Stdout: %s\nError: %v", repo, stdout, err) - desc := fmt.Sprintf("Repository garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err) + desc := fmt.Sprintf("Repository garbage collection failed for %s. Stdout: %s\nError: %v", repo.RelativePath(), stdout, err) if err := system_model.CreateRepositoryNotice(desc); err != nil { log.Error("CreateRepositoryNotice: %v", err) } - return fmt.Errorf("Repository garbage collection failed in repo: %s: Error: %w", repo.FullName(), err) + return fmt.Errorf("Repository garbage collection failed in repo: %s: Error: %w", repo.RelativePath(), err) } // Now update the size of the repository if err := repo_module.UpdateRepoSize(ctx, repo); err != nil { log.Error("Updating size as part of garbage collection failed for %-v. Stdout: %s\nError: %v", repo, stdout, err) - desc := fmt.Sprintf("Updating size as part of garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err) + desc := fmt.Sprintf("Updating size as part of garbage collection failed for %s. Stdout: %s\nError: %v", repo.RelativePath(), stdout, err) if err := system_model.CreateRepositoryNotice(desc); err != nil { log.Error("CreateRepositoryNotice: %v", err) } - return fmt.Errorf("Updating size as part of garbage collection failed in repo: %s: Error: %w", repo.FullName(), err) + return fmt.Errorf("Updating size as part of garbage collection failed in repo: %s: Error: %w", repo.RelativePath(), err) } return nil @@ -123,11 +122,11 @@ func gatherMissingRepoRecords(ctx context.Context) (repo_model.RepositoryList, e return db.ErrCancelledf("during gathering missing repo records before checking %s", repo.FullName()) default: } - isDir, err := util.IsDir(repo.RepoPath()) + exist, err := gitrepo.IsRepositoryExist(ctx, repo) if err != nil { return fmt.Errorf("Unable to check dir for %s. %w", repo.FullName(), err) } - if !isDir { + if !exist { repos = append(repos, repo) } return nil @@ -191,7 +190,7 @@ func ReinitMissingRepositories(ctx context.Context) error { } log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID) if err := gitrepo.InitRepository(ctx, repo, repo.ObjectFormatName); err != nil { - log.Error("Unable (re)initialize repository %d at %s. Error: %v", repo.ID, repo.RepoPath(), err) + log.Error("Unable (re)initialize repository %d at %s. Error: %v", repo.ID, repo.RelativePath(), err) if err2 := system_model.CreateRepositoryNotice("InitRepository [%d]: %v", repo.ID, err); err2 != nil { log.Error("CreateRepositoryNotice: %v", err2) } diff --git a/services/repository/commitstatus/commitstatus.go b/services/repository/commitstatus/commitstatus.go index c0a8b8e824330..a1534223c3b9d 100644 --- a/services/repository/commitstatus/commitstatus.go +++ b/services/repository/commitstatus/commitstatus.go @@ -69,12 +69,10 @@ func deleteCommitStatusCache(repoID int64, branchName string) error { // NOTE: All text-values will be trimmed from whitespaces. // Requires: Repo, Creator, SHA func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creator *user_model.User, sha string, status *git_model.CommitStatus) error { - repoPath := repo.RepoPath() - // confirm that commit is exist gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo) if err != nil { - return fmt.Errorf("OpenRepository[%s]: %w", repoPath, err) + return fmt.Errorf("OpenRepository[%s]: %w", repo.RelativePath(), err) } defer closer.Close() diff --git a/services/repository/create.go b/services/repository/create.go index 3006bfaeaf078..7ea8f9c132c46 100644 --- a/services/repository/create.go +++ b/services/repository/create.go @@ -314,7 +314,7 @@ func CreateRepositoryDirectly(ctx context.Context, doer, owner *user_model.User, licenses = append(licenses, opts.License) var stdout string - stdout, _, err = gitcmd.NewCommand("rev-parse", "HEAD").RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()}) + stdout, err = gitrepo.RunCmdString(ctx, repo, gitcmd.NewCommand("rev-parse", "HEAD")) if err != nil { log.Error("CreateRepository(git rev-parse HEAD) in %v: Stdout: %s\nError: %v", repo, stdout, err) return nil, fmt.Errorf("CreateRepository(git rev-parse HEAD): %w", err) @@ -475,8 +475,8 @@ func updateGitRepoAfterCreate(ctx context.Context, repo *repo_model.Repository) return fmt.Errorf("checkDaemonExportOK: %w", err) } - if stdout, _, err := gitcmd.NewCommand("update-server-info"). - RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()}); err != nil { + if stdout, err := gitrepo.RunCmdString(ctx, repo, + gitcmd.NewCommand("update-server-info")); err != nil { log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) return fmt.Errorf("CreateRepository(git update-server-info): %w", err) } diff --git a/services/repository/migrate.go b/services/repository/migrate.go index 88d83fada57f8..b55b4d18d40a4 100644 --- a/services/repository/migrate.go +++ b/services/repository/migrate.go @@ -87,8 +87,8 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, migrateTimeout := time.Duration(setting.Git.Timeout.Migrate) * time.Second - if err := util.RemoveAll(repoPath); err != nil { - return repo, fmt.Errorf("failed to remove existing repo dir %q, err: %w", repoPath, err) + if err := gitrepo.DeleteRepository(ctx, repo); err != nil { + return repo, fmt.Errorf("failed to remove existing repo dir %q, err: %w", repo.FullName(), err) } if err := git.Clone(ctx, opts.CloneAddr, repoPath, git.CloneRepoOptions{ @@ -123,7 +123,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err) } - gitRepo, err := git.OpenRepository(ctx, repoPath) + gitRepo, err := gitrepo.OpenRepository(ctx, repo) if err != nil { return repo, fmt.Errorf("OpenRepository: %w", err) } diff --git a/tests/integration/api_repo_git_tags_test.go b/tests/integration/api_repo_git_tags_test.go index 6bdb40f786e02..a0445bb80011a 100644 --- a/tests/integration/api_repo_git_tags_test.go +++ b/tests/integration/api_repo_git_tags_test.go @@ -12,7 +12,6 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/git/gitcmd" "code.gitea.io/gitea/modules/gitrepo" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -30,8 +29,8 @@ func TestAPIGitTags(t *testing.T) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) // Set up git config for the tagger - _ = gitcmd.NewCommand("config", "user.name").AddDynamicArguments(user.Name).Run(t.Context(), &gitcmd.RunOpts{Dir: repo.RepoPath()}) - _ = gitcmd.NewCommand("config", "user.email").AddDynamicArguments(user.Email).Run(t.Context(), &gitcmd.RunOpts{Dir: repo.RepoPath()}) + _ = gitrepo.GitConfigSet(t.Context(), repo, "user.name", user.Name) + _ = gitrepo.GitConfigSet(t.Context(), repo, "user.email", user.Email) gitRepo, _ := gitrepo.OpenRepository(t.Context(), repo) defer gitRepo.Close() diff --git a/tests/integration/editor_test.go b/tests/integration/editor_test.go index 5dd2aabcdfbe3..05c7b272abcd4 100644 --- a/tests/integration/editor_test.go +++ b/tests/integration/editor_test.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/util" @@ -187,7 +188,7 @@ func testEditorWebGitCommitEmail(t *testing.T) { require.True(t, user.KeepEmailPrivate) repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - gitRepo, _ := git.OpenRepository(t.Context(), repo1.RepoPath()) + gitRepo, _ := gitrepo.OpenRepository(t.Context(), repo1) defer gitRepo.Close() getLastCommit := func(t *testing.T) *git.Commit { c, err := gitRepo.GetBranchCommit("master") diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go index 72bf8ab4b032d..ee039dab8ad6e 100644 --- a/tests/integration/pull_merge_test.go +++ b/tests/integration/pull_merge_test.go @@ -11,7 +11,6 @@ import ( "net/url" "os" "path" - "path/filepath" "strconv" "strings" "testing" @@ -689,17 +688,13 @@ func TestPullMergeIndexerNotifier(t *testing.T) { }) } -func testResetRepo(t *testing.T, repoPath, branch, commitID string) { - f, err := os.OpenFile(filepath.Join(repoPath, "refs", "heads", branch), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644) - assert.NoError(t, err) - _, err = f.WriteString(commitID + "\n") - assert.NoError(t, err) - f.Close() +func testResetRepo(t *testing.T, repo *repo_model.Repository, branch, commitID string) { + assert.NoError(t, gitrepo.UpdateRef(t.Context(), repo, git.BranchPrefix+branch, commitID)) - repo, err := git.OpenRepository(t.Context(), repoPath) + gitRepo, err := gitrepo.OpenRepository(t.Context(), repo) assert.NoError(t, err) - defer repo.Close() - id, err := repo.GetBranchCommitID(branch) + defer gitRepo.Close() + id, err := gitRepo.GetBranchCommitID(branch) assert.NoError(t, err) assert.Equal(t, commitID, id) } @@ -778,7 +773,7 @@ func TestPullAutoMergeAfterCommitStatusSucceed(t *testing.T) { assert.ElementsMatch(t, []string{"sub-home-md-img-check", "home-md-img-check", "pr-to-update", "branch2", "DefaultBranch", "develop", "feature/1", "master"}, branches) baseGitRepo.Close() defer func() { - testResetRepo(t, baseRepo.RepoPath(), "master", masterCommitID) + testResetRepo(t, baseRepo, "master", masterCommitID) }() err = commitstatus_service.CreateCommitStatus(t.Context(), baseRepo, user1, sha, &git_model.CommitStatus{ @@ -856,7 +851,7 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApproval(t *testing.T) { assert.NoError(t, err) baseGitRepo.Close() defer func() { - testResetRepo(t, baseRepo.RepoPath(), "master", masterCommitID) + testResetRepo(t, baseRepo, "master", masterCommitID) }() err = commitstatus_service.CreateCommitStatus(t.Context(), baseRepo, user1, sha, &git_model.CommitStatus{ @@ -985,7 +980,7 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApprovalForAgitFlow(t *testing. assert.NoError(t, err) baseGitRepo.Close() defer func() { - testResetRepo(t, baseRepo.RepoPath(), "master", masterCommitID) + testResetRepo(t, baseRepo, "master", masterCommitID) }() err = commitstatus_service.CreateCommitStatus(t.Context(), baseRepo, user1, sha, &git_model.CommitStatus{ diff --git a/tests/integration/repo_branch_test.go b/tests/integration/repo_branch_test.go index 50ceb6533080f..379cf568028cb 100644 --- a/tests/integration/repo_branch_test.go +++ b/tests/integration/repo_branch_test.go @@ -154,7 +154,7 @@ func prepareRecentlyPushedBranchTest(t *testing.T, headSession *TestSession, bas // only `new-commit` branch has commits ahead the base branch checkRecentlyPushedNewBranches(t, headSession, headRepoPath, []string{"new-commit"}) - if baseRepo.RepoPath() != headRepo.RepoPath() { + if baseRepo.ID != headRepo.ID { checkRecentlyPushedNewBranches(t, headSession, baseRepoPath, []string{fmt.Sprintf("%v:new-commit", headRepo.FullName())}) } @@ -162,7 +162,7 @@ func prepareRecentlyPushedBranchTest(t *testing.T, headSession *TestSession, bas testCreatePullToDefaultBranch(t, headSession, baseRepo, headRepo, "new-commit", "merge new-commit to default branch") // No push message show because of active PR checkRecentlyPushedNewBranches(t, headSession, headRepoPath, []string{}) - if baseRepo.RepoPath() != headRepo.RepoPath() { + if baseRepo.ID != headRepo.ID { checkRecentlyPushedNewBranches(t, headSession, baseRepoPath, []string{}) } } @@ -179,7 +179,7 @@ func prepareRecentlyPushedBranchSpecialTest(t *testing.T, session *TestSession, // Though we have new `no-commit` branch, but the headBranch is not newer or commits ahead baseBranch. No message show. checkRecentlyPushedNewBranches(t, session, headRepoPath, []string{}) - if baseRepo.RepoPath() != headRepo.RepoPath() { + if baseRepo.ID != headRepo.ID { checkRecentlyPushedNewBranches(t, session, baseRepoPath, []string{}) } } From 53c868c69c9abf01c4d2f4d85d1554aa6d5bc66f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 29 Sep 2025 10:45:10 -0700 Subject: [PATCH 02/14] Adjust log depth of git command --- modules/git/gitcmd/command.go | 26 ++++++++++++++------------ modules/gitrepo/command.go | 5 +++-- modules/log/logger_global.go | 4 ++++ 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/modules/git/gitcmd/command.go b/modules/git/gitcmd/command.go index ed2f6fb647edf..631636842fc82 100644 --- a/modules/git/gitcmd/command.go +++ b/modules/git/gitcmd/command.go @@ -221,6 +221,9 @@ type RunOpts struct { Stdin io.Reader PipelineFunc func(context.Context, context.CancelFunc) error + + // for debugging purpose only, it means current function call depth, every caller should +1 + LogDepth int } func commonBaseEnvs() []string { @@ -265,10 +268,6 @@ var ErrBrokenCommand = errors.New("git command is broken") // Run runs the command with the RunOpts func (c *Command) Run(ctx context.Context, opts *RunOpts) error { - return c.run(ctx, 1, opts) -} - -func (c *Command) run(ctx context.Context, skip int, opts *RunOpts) error { if len(c.brokenArgs) != 0 { log.Error("git command is broken: %s, broken args: %s", c.LogString(), strings.Join(c.brokenArgs, " ")) return ErrBrokenCommand @@ -284,13 +283,14 @@ func (c *Command) run(ctx context.Context, skip int, opts *RunOpts) error { } cmdLogString := c.LogString() - callerInfo := util.CallerFuncName(1 /* util */ + 1 /* this */ + skip /* parent */) + depth := 1 /* util */ + 1 /* this */ + opts.LogDepth /* parent */ + callerInfo := util.CallerFuncName(depth) if pos := strings.LastIndex(callerInfo, "/"); pos >= 0 { callerInfo = callerInfo[pos+1:] } // these logs are for debugging purposes only, so no guarantee of correctness or stability desc := fmt.Sprintf("git.Run(by:%s, repo:%s): %s", callerInfo, logArgSanitize(opts.Dir), cmdLogString) - log.Debug("git.Command: %s", desc) + log.DebugWithSkip(depth-1, "git.Command: %s", desc) _, span := gtprof.GetTracer().Start(ctx, gtprof.TraceSpanGitRun) defer span.End() @@ -399,7 +399,11 @@ func IsErrorExitCode(err error, code int) bool { // RunStdString runs the command with options and returns stdout/stderr as string. and store stderr to returned error (err combined with stderr). func (c *Command) RunStdString(ctx context.Context, opts *RunOpts) (stdout, stderr string, runErr RunStdError) { - stdoutBytes, stderrBytes, err := c.runStdBytes(ctx, opts) + if opts == nil { + opts = &RunOpts{} + } + opts.LogDepth += 1 + stdoutBytes, stderrBytes, err := c.RunStdBytes(ctx, opts) stdout = util.UnsafeBytesToString(stdoutBytes) stderr = util.UnsafeBytesToString(stderrBytes) if err != nil { @@ -411,13 +415,10 @@ func (c *Command) RunStdString(ctx context.Context, opts *RunOpts) (stdout, stde // RunStdBytes runs the command with options and returns stdout/stderr as bytes. and store stderr to returned error (err combined with stderr). func (c *Command) RunStdBytes(ctx context.Context, opts *RunOpts) (stdout, stderr []byte, runErr RunStdError) { - return c.runStdBytes(ctx, opts) -} - -func (c *Command) runStdBytes(ctx context.Context, opts *RunOpts) (stdout, stderr []byte, runErr RunStdError) { if opts == nil { opts = &RunOpts{} } + opts.LogDepth += 1 if opts.Stdout != nil || opts.Stderr != nil { // we must panic here, otherwise there would be bugs if developers set Stdin/Stderr by mistake, and it would be very difficult to debug panic("stdout and stderr field must be nil when using RunStdBytes") @@ -435,9 +436,10 @@ func (c *Command) runStdBytes(ctx context.Context, opts *RunOpts) (stdout, stder Stderr: stderrBuf, Stdin: opts.Stdin, PipelineFunc: opts.PipelineFunc, + LogDepth: opts.LogDepth, } - err := c.run(ctx, 2, newOpts) + err := c.Run(ctx, newOpts) stderr = stderrBuf.Bytes() if err != nil { return nil, stderr, &runStdError{err: err, stderr: util.UnsafeBytesToString(stderr)} diff --git a/modules/gitrepo/command.go b/modules/gitrepo/command.go index c260079aad6c6..d0fa5ab69a413 100644 --- a/modules/gitrepo/command.go +++ b/modules/gitrepo/command.go @@ -25,8 +25,9 @@ func RunCmdString(ctx context.Context, repo Repository, cmd *gitcmd.Command, opt o(&opt) } res, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{ - Dir: repoPath(repo), - Env: opt.Env, + Dir: repoPath(repo), + Env: opt.Env, + LogDepth: 1, }) return res, err } diff --git a/modules/log/logger_global.go b/modules/log/logger_global.go index 07c25cd62f6f4..e9f9546499fae 100644 --- a/modules/log/logger_global.go +++ b/modules/log/logger_global.go @@ -33,6 +33,10 @@ func Debug(format string, v ...any) { Log(1, DEBUG, format, v...) } +func DebugWithSkip(skip int, format string, v ...any) { + Log(skip+1, DEBUG, format, v...) +} + func IsDebug() bool { return GetLevel() <= DEBUG } From 3e97124f744e125fb94671c6b7db2dae53bddb23 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 29 Sep 2025 11:15:37 -0700 Subject: [PATCH 03/14] improvements --- modules/git/gitcmd/command.go | 18 ++++++++++-------- modules/gitrepo/command.go | 6 +++--- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/modules/git/gitcmd/command.go b/modules/git/gitcmd/command.go index 631636842fc82..26197fbd784a2 100644 --- a/modules/git/gitcmd/command.go +++ b/modules/git/gitcmd/command.go @@ -222,8 +222,10 @@ type RunOpts struct { PipelineFunc func(context.Context, context.CancelFunc) error - // for debugging purpose only, it means current function call depth, every caller should +1 - LogDepth int + // for debugging purpose only, it indicates how many stack frames to skip to find the caller info + // it's mainly used to find the caller function name for logging + // it should be 0 when the caller is the direct caller of Run/RunStdString/RunStdBytes + LogSkip int } func commonBaseEnvs() []string { @@ -283,14 +285,14 @@ func (c *Command) Run(ctx context.Context, opts *RunOpts) error { } cmdLogString := c.LogString() - depth := 1 /* util */ + 1 /* this */ + opts.LogDepth /* parent */ - callerInfo := util.CallerFuncName(depth) + skip := 1 /* util */ + 1 /* this */ + opts.LogSkip /* parent */ + callerInfo := util.CallerFuncName(skip) if pos := strings.LastIndex(callerInfo, "/"); pos >= 0 { callerInfo = callerInfo[pos+1:] } // these logs are for debugging purposes only, so no guarantee of correctness or stability desc := fmt.Sprintf("git.Run(by:%s, repo:%s): %s", callerInfo, logArgSanitize(opts.Dir), cmdLogString) - log.DebugWithSkip(depth-1, "git.Command: %s", desc) + log.DebugWithSkip(opts.LogSkip+1, "git.Command: %s", desc) _, span := gtprof.GetTracer().Start(ctx, gtprof.TraceSpanGitRun) defer span.End() @@ -402,7 +404,7 @@ func (c *Command) RunStdString(ctx context.Context, opts *RunOpts) (stdout, stde if opts == nil { opts = &RunOpts{} } - opts.LogDepth += 1 + opts.LogSkip++ stdoutBytes, stderrBytes, err := c.RunStdBytes(ctx, opts) stdout = util.UnsafeBytesToString(stdoutBytes) stderr = util.UnsafeBytesToString(stderrBytes) @@ -418,7 +420,7 @@ func (c *Command) RunStdBytes(ctx context.Context, opts *RunOpts) (stdout, stder if opts == nil { opts = &RunOpts{} } - opts.LogDepth += 1 + opts.LogSkip++ if opts.Stdout != nil || opts.Stderr != nil { // we must panic here, otherwise there would be bugs if developers set Stdin/Stderr by mistake, and it would be very difficult to debug panic("stdout and stderr field must be nil when using RunStdBytes") @@ -436,7 +438,7 @@ func (c *Command) RunStdBytes(ctx context.Context, opts *RunOpts) (stdout, stder Stderr: stderrBuf, Stdin: opts.Stdin, PipelineFunc: opts.PipelineFunc, - LogDepth: opts.LogDepth, + LogSkip: opts.LogSkip, } err := c.Run(ctx, newOpts) diff --git a/modules/gitrepo/command.go b/modules/gitrepo/command.go index d0fa5ab69a413..855645acf2b06 100644 --- a/modules/gitrepo/command.go +++ b/modules/gitrepo/command.go @@ -25,9 +25,9 @@ func RunCmdString(ctx context.Context, repo Repository, cmd *gitcmd.Command, opt o(&opt) } res, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{ - Dir: repoPath(repo), - Env: opt.Env, - LogDepth: 1, + Dir: repoPath(repo), + Env: opt.Env, + LogSkip: 1, }) return res, err } From f7fb1a6d6126a13e1292c8724dc2d5b760b28214 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 29 Sep 2025 11:37:40 -0700 Subject: [PATCH 04/14] improvements --- modules/git/gitcmd/command.go | 3 +-- routers/web/repo/pull.go | 3 ++- services/repository/migrate.go | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/git/gitcmd/command.go b/modules/git/gitcmd/command.go index 26197fbd784a2..e2c688c5881ef 100644 --- a/modules/git/gitcmd/command.go +++ b/modules/git/gitcmd/command.go @@ -285,8 +285,7 @@ func (c *Command) Run(ctx context.Context, opts *RunOpts) error { } cmdLogString := c.LogString() - skip := 1 /* util */ + 1 /* this */ + opts.LogSkip /* parent */ - callerInfo := util.CallerFuncName(skip) + callerInfo := util.CallerFuncName(1 /* util */ + 1 /* this */ + opts.LogSkip /* parent */) if pos := strings.LastIndex(callerInfo, "/"); pos >= 0 { callerInfo = callerInfo[pos+1:] } diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 7970c92ebb52f..9fd01717a48c4 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -234,7 +234,8 @@ func GetMergedBaseCommitID(ctx *context.Context, issue *issues_model.Issue) stri } if commitSHA != "" { // Get immediate parent of the first commit in the patch, grab history back - parentCommit, _, err = gitcmd.NewCommand("rev-list", "-1", "--skip=1").AddDynamicArguments(commitSHA).RunStdString(ctx, &gitcmd.RunOpts{Dir: ctx.Repo.GitRepo.Path}) + parentCommit, err = gitrepo.RunCmdString(ctx, ctx.Repo.Repository, + gitcmd.NewCommand("rev-list", "-1", "--skip=1").AddDynamicArguments(commitSHA)) if err == nil { parentCommit = strings.TrimSpace(parentCommit) } diff --git a/services/repository/migrate.go b/services/repository/migrate.go index b55b4d18d40a4..9873d5deeb4f8 100644 --- a/services/repository/migrate.go +++ b/services/repository/migrate.go @@ -226,9 +226,9 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, // this is necessary for sync local tags from remote configName := fmt.Sprintf("remote.%s.fetch", mirrorModel.GetRemoteName()) - if stdout, _, err := gitcmd.NewCommand("config"). - AddOptionValues("--add", configName, `+refs/tags/*:refs/tags/*`). - RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}); err != nil { + if stdout, err := gitrepo.RunCmdString(ctx, repo, + gitcmd.NewCommand("config"). + AddOptionValues("--add", configName, `+refs/tags/*:refs/tags/*`)); err != nil { log.Error("MigrateRepositoryGitData(git config --add +refs/tags/*:refs/tags/*) in %v: Stdout: %s\nError: %v", repo, stdout, err) return repo, fmt.Errorf("error in MigrateRepositoryGitData(git config --add +refs/tags/*:refs/tags/*): %w", err) } From c5fa93b19626a74bd3e8856455f48e86060b42f8 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 4 Oct 2025 16:40:34 -0700 Subject: [PATCH 05/14] refactor git command --- cmd/hook.go | 2 +- models/migrations/v1_12/v128.go | 8 +- models/migrations/v1_12/v134.go | 4 +- modules/git/attribute/batch.go | 13 +- modules/git/attribute/checker.go | 11 +- modules/git/batch_reader.go | 35 ++- modules/git/blame.go | 11 +- modules/git/commit.go | 32 ++- modules/git/config.go | 21 +- modules/git/diff.go | 64 +++--- modules/git/git.go | 2 +- modules/git/gitcmd/command.go | 210 +++++++++++++----- modules/git/gitcmd/command_test.go | 15 +- modules/git/grep.go | 13 +- modules/git/log_name_status.go | 9 +- modules/git/pipeline/catfile.go | 32 ++- modules/git/pipeline/lfs_nogogit.go | 10 +- modules/git/pipeline/namerev.go | 12 +- modules/git/pipeline/revlist.go | 18 +- modules/git/remote.go | 2 +- modules/git/repo.go | 45 ++-- modules/git/repo_archive.go | 9 +- modules/git/repo_branch.go | 6 +- modules/git/repo_branch_nogogit.go | 10 +- modules/git/repo_commit.go | 117 +++++++--- modules/git/repo_commit_nogogit.go | 10 +- modules/git/repo_commitgraph.go | 2 +- modules/git/repo_compare.go | 65 +++--- modules/git/repo_compare_test.go | 8 +- modules/git/repo_gpg.go | 10 +- modules/git/repo_index.go | 34 +-- modules/git/repo_object.go | 12 +- modules/git/repo_ref.go | 3 +- modules/git/repo_ref_nogogit.go | 10 +- modules/git/repo_stats.go | 18 +- modules/git/repo_tag.go | 19 +- modules/git/repo_tree.go | 12 +- modules/git/submodule.go | 16 +- modules/git/submodule_test.go | 6 +- modules/git/tree.go | 5 +- modules/git/tree_nogogit.go | 5 +- modules/gitrepo/command.go | 26 +-- modules/gitrepo/compare.go | 2 +- modules/gitrepo/fsck.go | 2 +- modules/gitrepo/ref.go | 8 +- modules/gitrepo/remote.go | 24 +- modules/indexer/code/git.go | 4 +- routers/private/hook_pre_receive.go | 5 +- routers/private/hook_verification.go | 50 ++--- routers/web/repo/githttp.go | 29 ++- services/asymkey/sign.go | 10 +- services/doctor/heads.go | 2 +- services/gitdiff/git_diff_tree.go | 2 +- services/gitdiff/gitdiff.go | 11 +- services/migrations/dump.go | 8 +- services/migrations/gitea_uploader_test.go | 6 +- services/mirror/mirror_pull.go | 20 +- services/pull/merge.go | 6 +- services/pull/merge_prepare.go | 81 ++++--- services/pull/merge_rebase.go | 11 +- services/pull/merge_squash.go | 2 +- services/pull/patch.go | 32 +-- services/pull/patch_unmerged.go | 73 +++--- services/pull/pull.go | 13 +- services/pull/temp_repo.go | 30 ++- services/pull/update_rebase.go | 17 +- services/repository/contributors_graph.go | 13 +- services/repository/create.go | 2 +- services/repository/files/patch.go | 11 +- services/repository/files/temp_repo.go | 92 ++++---- services/repository/fork.go | 3 +- services/repository/generate.go | 7 +- services/repository/gitgraph/graph.go | 14 +- services/repository/init.go | 13 +- tests/integration/git_general_test.go | 37 ++- .../git_helper_for_declarative_test.go | 32 ++- tests/integration/git_lfs_ssh_test.go | 4 +- tests/integration/git_misc_test.go | 23 +- tests/integration/git_push_test.go | 4 +- tests/integration/pull_create_test.go | 5 +- tests/integration/pull_merge_test.go | 39 ++-- tests/integration/repo_tag_test.go | 24 +- 82 files changed, 974 insertions(+), 769 deletions(-) diff --git a/cmd/hook.go b/cmd/hook.go index 2f866dd396b6b..c8d0d2078405e 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -313,7 +313,7 @@ func runHookPostReceive(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) // First of all run update-server-info no matter what - if _, _, err := gitcmd.NewCommand("update-server-info").RunStdString(ctx, nil); err != nil { + if _, _, err := gitcmd.NewCommand("update-server-info").RunStdString(ctx); err != nil { return fmt.Errorf("failed to call 'git update-server-info': %w", err) } diff --git a/models/migrations/v1_12/v128.go b/models/migrations/v1_12/v128.go index 34746dcdc42fe..ff5b12af18f1c 100644 --- a/models/migrations/v1_12/v128.go +++ b/models/migrations/v1_12/v128.go @@ -84,17 +84,17 @@ func FixMergeBase(ctx context.Context, x *xorm.Engine) error { if !pr.HasMerged { var err error - pr.MergeBase, _, err = gitcmd.NewCommand("merge-base").AddDashesAndList(pr.BaseBranch, gitRefName).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) + pr.MergeBase, _, err = gitcmd.NewCommand("merge-base").AddDashesAndList(pr.BaseBranch, gitRefName).WithDir(repoPath).RunStdString(ctx) if err != nil { var err2 error - pr.MergeBase, _, err2 = gitcmd.NewCommand("rev-parse").AddDynamicArguments(git.BranchPrefix+pr.BaseBranch).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) + pr.MergeBase, _, err2 = gitcmd.NewCommand("rev-parse").AddDynamicArguments(git.BranchPrefix + pr.BaseBranch).WithDir(repoPath).RunStdString(ctx) if err2 != nil { log.Error("Unable to get merge base for PR ID %d, Index %d in %s/%s. Error: %v & %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err, err2) continue } } } else { - parentsString, _, err := gitcmd.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) + parentsString, _, err := gitcmd.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).WithDir(repoPath).RunStdString(ctx) if err != nil { log.Error("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) continue @@ -108,7 +108,7 @@ func FixMergeBase(ctx context.Context, x *xorm.Engine) error { refs = append(refs, gitRefName) cmd := gitcmd.NewCommand("merge-base").AddDashesAndList(refs...) - pr.MergeBase, _, err = cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) + pr.MergeBase, _, err = cmd.WithDir(repoPath).RunStdString(ctx) if err != nil { log.Error("Unable to get merge base for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) continue diff --git a/models/migrations/v1_12/v134.go b/models/migrations/v1_12/v134.go index d31cc3abdb3b5..98bb8dbda72fe 100644 --- a/models/migrations/v1_12/v134.go +++ b/models/migrations/v1_12/v134.go @@ -80,7 +80,7 @@ func RefixMergeBase(ctx context.Context, x *xorm.Engine) error { gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index) - parentsString, _, err := gitcmd.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) + parentsString, _, err := gitcmd.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).WithDir(repoPath).RunStdString(ctx) if err != nil { log.Error("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) continue @@ -95,7 +95,7 @@ func RefixMergeBase(ctx context.Context, x *xorm.Engine) error { refs = append(refs, gitRefName) cmd := gitcmd.NewCommand("merge-base").AddDashesAndList(refs...) - pr.MergeBase, _, err = cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) + pr.MergeBase, _, err = cmd.WithDir(repoPath).RunStdString(ctx) if err != nil { log.Error("Unable to get merge base for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) continue diff --git a/modules/git/attribute/batch.go b/modules/git/attribute/batch.go index 9f805d55c5140..27befdfa25af1 100644 --- a/modules/git/attribute/batch.go +++ b/modules/git/attribute/batch.go @@ -77,13 +77,12 @@ func NewBatchChecker(repo *git.Repository, treeish string, attributes []string) _ = lw.Close() }() stdErr := new(bytes.Buffer) - err := cmd.Run(ctx, &gitcmd.RunOpts{ - Env: envs, - Dir: repo.Path, - Stdin: stdinReader, - Stdout: lw, - Stderr: stdErr, - }) + err := cmd.WithEnv(envs). + WithDir(repo.Path). + WithStdin(stdinReader). + WithStdout(lw). + WithStderr(stdErr). + Run(ctx) if err != nil && !git.IsErrCanceledOrKilled(err) { log.Error("Attribute checker for commit %s exits with error: %v", treeish, err) diff --git a/modules/git/attribute/checker.go b/modules/git/attribute/checker.go index 4b313adf377d6..49c0eb90ef26f 100644 --- a/modules/git/attribute/checker.go +++ b/modules/git/attribute/checker.go @@ -71,12 +71,11 @@ func CheckAttributes(ctx context.Context, gitRepo *git.Repository, treeish strin stdOut := new(bytes.Buffer) stdErr := new(bytes.Buffer) - if err := cmd.Run(ctx, &gitcmd.RunOpts{ - Env: append(os.Environ(), envs...), - Dir: gitRepo.Path, - Stdout: stdOut, - Stderr: stdErr, - }); err != nil { + if err := cmd.WithEnv(append(os.Environ(), envs...)). + WithDir(gitRepo.Path). + WithStdout(stdOut). + WithStderr(stdErr). + Run(ctx); err != nil { return nil, fmt.Errorf("failed to run check-attr: %w\n%s\n%s", err, stdOut.String(), stdErr.String()) } diff --git a/modules/git/batch_reader.go b/modules/git/batch_reader.go index f09f4144c8968..b5cec130d5e3a 100644 --- a/modules/git/batch_reader.go +++ b/modules/git/batch_reader.go @@ -31,10 +31,9 @@ type WriteCloserError interface { func ensureValidGitRepository(ctx context.Context, repoPath string) error { stderr := strings.Builder{} err := gitcmd.NewCommand("rev-parse"). - Run(ctx, &gitcmd.RunOpts{ - Dir: repoPath, - Stderr: &stderr, - }) + WithDir(repoPath). + WithStderr(&stderr). + Run(ctx) if err != nil { return gitcmd.ConcatenateError(err, (&stderr).String()) } @@ -63,14 +62,12 @@ func catFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError, go func() { stderr := strings.Builder{} err := gitcmd.NewCommand("cat-file", "--batch-check"). - Run(ctx, &gitcmd.RunOpts{ - Dir: repoPath, - Stdin: batchStdinReader, - Stdout: batchStdoutWriter, - Stderr: &stderr, - - UseContextTimeout: true, - }) + WithDir(repoPath). + WithStdin(batchStdinReader). + WithStdout(batchStdoutWriter). + WithStderr(&stderr). + WithUseContextTimeout(true). + Run(ctx) if err != nil { _ = batchStdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String())) _ = batchStdinReader.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String())) @@ -111,14 +108,12 @@ func catFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufi go func() { stderr := strings.Builder{} err := gitcmd.NewCommand("cat-file", "--batch"). - Run(ctx, &gitcmd.RunOpts{ - Dir: repoPath, - Stdin: batchStdinReader, - Stdout: batchStdoutWriter, - Stderr: &stderr, - - UseContextTimeout: true, - }) + WithDir(repoPath). + WithStdin(batchStdinReader). + WithStdout(batchStdoutWriter). + WithStderr(&stderr). + WithUseContextTimeout(true). + Run(ctx) if err != nil { _ = batchStdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String())) _ = batchStdinReader.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String())) diff --git a/modules/git/blame.go b/modules/git/blame.go index 50cadc41c2238..601be96f05efb 100644 --- a/modules/git/blame.go +++ b/modules/git/blame.go @@ -166,12 +166,11 @@ func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath go func() { stderr := bytes.Buffer{} // TODO: it doesn't work for directories (the directories shouldn't be "blamed"), and the "err" should be returned by "Read" but not by "Close" - err := cmd.Run(ctx, &gitcmd.RunOpts{ - UseContextTimeout: true, - Dir: repoPath, - Stdout: stdout, - Stderr: &stderr, - }) + err := cmd.WithDir(repoPath). + WithUseContextTimeout(true). + WithStdout(stdout). + WithStderr(&stderr). + Run(ctx) done <- err _ = stdout.Close() if err != nil { diff --git a/modules/git/commit.go b/modules/git/commit.go index a0c5955ae8b6d..5eef1439d0699 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -93,7 +93,7 @@ func AddChanges(ctx context.Context, repoPath string, all bool, files ...string) cmd.AddArguments("--all") } cmd.AddDashesAndList(files...) - _, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) + _, _, err := cmd.WithDir(repoPath).RunStdString(ctx) return err } @@ -122,7 +122,7 @@ func CommitChanges(ctx context.Context, repoPath string, opts CommitChangesOptio } cmd.AddOptionFormat("--message=%s", opts.Message) - _, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) + _, _, err := cmd.WithDir(repoPath).RunStdString(ctx) // No stderr but exit status 1 means nothing to commit. if err != nil && err.Error() == "exit status 1" { return nil @@ -141,7 +141,7 @@ func AllCommitsCount(ctx context.Context, repoPath string, hidePRRefs bool, file cmd.AddDashesAndList(files...) } - stdout, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) + stdout, _, err := cmd.WithDir(repoPath).RunStdString(ctx) if err != nil { return 0, err } @@ -173,7 +173,8 @@ func CommitsCount(ctx context.Context, opts CommitsCountOptions) (int64, error) cmd.AddDashesAndList(opts.RelPath...) } - stdout, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: opts.RepoPath}) + var stdout string + err := cmd.WithDir(opts.RepoPath).WithStringOutput(&stdout).Run(ctx) if err != nil { return 0, err } @@ -208,7 +209,10 @@ func (c *Commit) HasPreviousCommit(objectID ObjectID) (bool, error) { return false, nil } - _, _, err := gitcmd.NewCommand("merge-base", "--is-ancestor").AddDynamicArguments(that, this).RunStdString(c.repo.Ctx, &gitcmd.RunOpts{Dir: c.repo.Path}) + _, _, err := gitcmd.NewCommand("merge-base", "--is-ancestor"). + AddDynamicArguments(that, this). + WithDir(c.repo.Path). + RunStdString(c.repo.Ctx) if err == nil { return true, nil } @@ -354,7 +358,7 @@ func (c *Commit) GetBranchName() (string, error) { cmd.AddArguments("--exclude", "refs/tags/*") } cmd.AddArguments("--name-only", "--no-undefined").AddDynamicArguments(c.ID.String()) - data, _, err := cmd.RunStdString(c.repo.Ctx, &gitcmd.RunOpts{Dir: c.repo.Path}) + data, _, err := cmd.WithDir(c.repo.Path).RunStdString(c.repo.Ctx) if err != nil { // handle special case where git can not describe commit if strings.Contains(err.Error(), "cannot describe") { @@ -432,11 +436,12 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi }() stderr := new(bytes.Buffer) - err := gitcmd.NewCommand("log", "--name-status", "-m", "--pretty=format:", "--first-parent", "--no-renames", "-z", "-1").AddDynamicArguments(commitID).Run(ctx, &gitcmd.RunOpts{ - Dir: repoPath, - Stdout: w, - Stderr: stderr, - }) + err := gitcmd.NewCommand("log", "--name-status", "-m", "--pretty=format:", "--first-parent", "--no-renames", "-z", "-1"). + AddDynamicArguments(commitID). + WithDir(repoPath). + WithStdout(w). + WithStderr(stderr). + Run(ctx) w.Close() // Close writer to exit parsing goroutine if err != nil { return nil, gitcmd.ConcatenateError(err, stderr.String()) @@ -448,7 +453,10 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi // GetFullCommitID returns full length (40) of commit ID by given short SHA in a repository. func GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error) { - commitID, _, err := gitcmd.NewCommand("rev-parse").AddDynamicArguments(shortID).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) + commitID, _, err := gitcmd.NewCommand("rev-parse"). + AddDynamicArguments(shortID). + WithDir(repoPath). + RunStdString(ctx) if err != nil { if strings.Contains(err.Error(), "exit status 128") { return "", ErrNotExist{shortID, ""} diff --git a/modules/git/config.go b/modules/git/config.go index 2eafe971b392b..79aa0535e4c34 100644 --- a/modules/git/config.go +++ b/modules/git/config.go @@ -118,7 +118,9 @@ func syncGitConfig(ctx context.Context) (err error) { } func configSet(ctx context.Context, key, value string) error { - stdout, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(ctx, nil) + stdout, _, err := gitcmd.NewCommand("config", "--global", "--get"). + AddDynamicArguments(key). + RunStdString(ctx) if err != nil && !gitcmd.IsErrorExitCode(err, 1) { return fmt.Errorf("failed to get git config %s, err: %w", key, err) } @@ -128,8 +130,9 @@ func configSet(ctx context.Context, key, value string) error { return nil } - _, _, err = gitcmd.NewCommand("config", "--global").AddDynamicArguments(key, value).RunStdString(ctx, nil) - if err != nil { + if _, _, err = gitcmd.NewCommand("config", "--global"). + AddDynamicArguments(key, value). + RunStdString(ctx); err != nil { return fmt.Errorf("failed to set git global config %s, err: %w", key, err) } @@ -137,14 +140,14 @@ func configSet(ctx context.Context, key, value string) error { } func configSetNonExist(ctx context.Context, key, value string) error { - _, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(ctx, nil) + _, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(ctx) if err == nil { // already exist return nil } if gitcmd.IsErrorExitCode(err, 1) { // not exist, set new config - _, _, err = gitcmd.NewCommand("config", "--global").AddDynamicArguments(key, value).RunStdString(ctx, nil) + _, _, err = gitcmd.NewCommand("config", "--global").AddDynamicArguments(key, value).RunStdString(ctx) if err != nil { return fmt.Errorf("failed to set git global config %s, err: %w", key, err) } @@ -155,14 +158,14 @@ func configSetNonExist(ctx context.Context, key, value string) error { } func configAddNonExist(ctx context.Context, key, value string) error { - _, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(ctx, nil) + _, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(ctx) if err == nil { // already exist return nil } if gitcmd.IsErrorExitCode(err, 1) { // not exist, add new config - _, _, err = gitcmd.NewCommand("config", "--global", "--add").AddDynamicArguments(key, value).RunStdString(ctx, nil) + _, _, err = gitcmd.NewCommand("config", "--global", "--add").AddDynamicArguments(key, value).RunStdString(ctx) if err != nil { return fmt.Errorf("failed to add git global config %s, err: %w", key, err) } @@ -172,10 +175,10 @@ func configAddNonExist(ctx context.Context, key, value string) error { } func configUnsetAll(ctx context.Context, key, value string) error { - _, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(ctx, nil) + _, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(ctx) if err == nil { // exist, need to remove - _, _, err = gitcmd.NewCommand("config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(ctx, nil) + _, _, err = gitcmd.NewCommand("config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(ctx) if err != nil { return fmt.Errorf("failed to unset git global config %s, err: %w", key, err) } diff --git a/modules/git/diff.go b/modules/git/diff.go index d185cc9277650..437b26eb05158 100644 --- a/modules/git/diff.go +++ b/modules/git/diff.go @@ -35,12 +35,12 @@ func GetRawDiff(repo *Repository, commitID string, diffType RawDiffType, writer // GetReverseRawDiff dumps the reverse diff results of repository in given commit ID to io.Writer. func GetReverseRawDiff(ctx context.Context, repoPath, commitID string, writer io.Writer) error { stderr := new(bytes.Buffer) - cmd := gitcmd.NewCommand("show", "--pretty=format:revert %H%n", "-R").AddDynamicArguments(commitID) - if err := cmd.Run(ctx, &gitcmd.RunOpts{ - Dir: repoPath, - Stdout: writer, - Stderr: stderr, - }); err != nil { + if err := gitcmd.NewCommand("show", "--pretty=format:revert %H%n", "-R"). + AddDynamicArguments(commitID). + WithDir(repoPath). + WithStdout(writer). + WithStderr(stderr). + Run(ctx); err != nil { return fmt.Errorf("Run: %w - %s", err, stderr) } return nil @@ -90,11 +90,10 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff } stderr := new(bytes.Buffer) - if err = cmd.Run(repo.Ctx, &gitcmd.RunOpts{ - Dir: repo.Path, - Stdout: writer, - Stderr: stderr, - }); err != nil { + if err = cmd.WithDir(repo.Path). + WithStdout(writer). + WithStderr(stderr). + Run(repo.Ctx); err != nil { return fmt.Errorf("Run: %w - %s", err, stderr) } return nil @@ -314,29 +313,28 @@ func GetAffectedFiles(repo *Repository, branchName, oldCommitID, newCommitID str // Run `git diff --name-only` to get the names of the changed files err = gitcmd.NewCommand("diff", "--name-only").AddDynamicArguments(oldCommitID, newCommitID). - Run(repo.Ctx, &gitcmd.RunOpts{ - Env: env, - Dir: repo.Path, - Stdout: stdoutWriter, - PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { - // Close the writer end of the pipe to begin processing - _ = stdoutWriter.Close() - defer func() { - // Close the reader on return to terminate the git command if necessary - _ = stdoutReader.Close() - }() - // Now scan the output from the command - scanner := bufio.NewScanner(stdoutReader) - for scanner.Scan() { - path := strings.TrimSpace(scanner.Text()) - if len(path) == 0 { - continue - } - affectedFiles = append(affectedFiles, path) + WithEnv(env). + WithDir(repo.Path). + WithStdout(stdoutWriter). + WithPipelineFunc(func(ctx context.Context, cancel context.CancelFunc) error { + // Close the writer end of the pipe to begin processing + _ = stdoutWriter.Close() + defer func() { + // Close the reader on return to terminate the git command if necessary + _ = stdoutReader.Close() + }() + // Now scan the output from the command + scanner := bufio.NewScanner(stdoutReader) + for scanner.Scan() { + path := strings.TrimSpace(scanner.Text()) + if len(path) == 0 { + continue } - return scanner.Err() - }, - }) + affectedFiles = append(affectedFiles, path) + } + return scanner.Err() + }). + Run(repo.Ctx) if err != nil { log.Error("Unable to get affected files for commits from %s to %s in %s: %v", oldCommitID, newCommitID, repo.Path, err) } diff --git a/modules/git/git.go b/modules/git/git.go index 161fa42196a1d..6d2c643b33802 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -57,7 +57,7 @@ func DefaultFeatures() *Features { } func loadGitVersionFeatures() (*Features, error) { - stdout, _, runErr := gitcmd.NewCommand("version").RunStdString(context.Background(), nil) + stdout, _, runErr := gitcmd.NewCommand("version").RunStdString(context.Background()) if runErr != nil { return nil, runErr } diff --git a/modules/git/gitcmd/command.go b/modules/git/gitcmd/command.go index e2c688c5881ef..ffea3513d00ec 100644 --- a/modules/git/gitcmd/command.go +++ b/modules/git/gitcmd/command.go @@ -46,6 +46,7 @@ type Command struct { brokenArgs []string cmd *exec.Cmd // for debug purpose only configArgs []string + opts *runOpts } func logArgSanitize(arg string) string { @@ -194,8 +195,8 @@ func ToTrustedCmdArgs(args []string) TrustedCmdArgs { return ret } -// RunOpts represents parameters to run the command. If UseContextTimeout is specified, then Timeout is ignored. -type RunOpts struct { +// runOpts represents parameters to run the command. If UseContextTimeout is specified, then Timeout is ignored. +type runOpts struct { Env []string Timeout time.Duration UseContextTimeout bool @@ -268,30 +269,164 @@ func CommonCmdServEnvs() []string { var ErrBrokenCommand = errors.New("git command is broken") +type stringWriter struct { + str *string +} + +func (w *stringWriter) Write(p []byte) (n int, err error) { + *w.str += util.UnsafeBytesToString(p) + return len(p), nil +} + +func (c *Command) WithStringOutput(output *string) *Command { + if output != nil { + if c.opts == nil { + c.opts = &runOpts{} + } + c.opts.Stdout = &stringWriter{str: output} + } + return c +} + +func (c *Command) WithStringErr(stderr *string) *Command { + if stderr != nil { + if c.opts == nil { + c.opts = &runOpts{} + } + c.opts.Stderr = &stringWriter{str: stderr} + } + return c +} + +type bytesWriter struct { + buf *[]byte +} + +func (w *bytesWriter) Write(p []byte) (n int, err error) { + *w.buf = append(*w.buf, p...) + return len(p), nil +} + +func (c *Command) WithBytesOutput(output *[]byte) *Command { + if output != nil { + if c.opts == nil { + c.opts = &runOpts{} + } + c.opts.Stdout = &bytesWriter{buf: output} + } + return c +} + +func (c *Command) WithDir(dir string) *Command { + if dir != "" { + if c.opts == nil { + c.opts = &runOpts{} + } + c.opts.Dir = dir + } + return c +} + +func (c *Command) WithEnv(env []string) *Command { + if env != nil { + if c.opts == nil { + c.opts = &runOpts{} + } + c.opts.Env = env + } + return c +} + +func (c *Command) WithTimeout(timeout time.Duration) *Command { + if timeout > 0 { + if c.opts == nil { + c.opts = &runOpts{} + } + c.opts.Timeout = timeout + } + return c +} + +func (c *Command) WithStdout(stdout io.Writer) *Command { + if stdout != nil { + if c.opts == nil { + c.opts = &runOpts{} + } + c.opts.Stdout = stdout + } + return c +} + +func (c *Command) WithStderr(stderr io.Writer) *Command { + if stderr != nil { + if c.opts == nil { + c.opts = &runOpts{} + } + c.opts.Stderr = stderr + } + return c +} + +func (c *Command) WithStdin(stdin io.Reader) *Command { + if stdin != nil { + if c.opts == nil { + c.opts = &runOpts{} + } + c.opts.Stdin = stdin + } + return c +} + +func (c *Command) WithPipelineFunc(f func(context.Context, context.CancelFunc) error) *Command { + if f != nil { + if c.opts == nil { + c.opts = &runOpts{} + } + c.opts.PipelineFunc = f + } + return c +} + +func (c *Command) WithUseContextTimeout(useContextTimeout bool) *Command { + if c.opts == nil { + c.opts = &runOpts{} + } + c.opts.UseContextTimeout = useContextTimeout + return c +} + +func (c *Command) WithLogSkipStep(stepSkip int) *Command { + if c.opts == nil { + c.opts = &runOpts{} + } + c.opts.LogSkip += stepSkip + return c +} + // Run runs the command with the RunOpts -func (c *Command) Run(ctx context.Context, opts *RunOpts) error { +func (c *Command) Run(ctx context.Context) error { if len(c.brokenArgs) != 0 { log.Error("git command is broken: %s, broken args: %s", c.LogString(), strings.Join(c.brokenArgs, " ")) return ErrBrokenCommand } - if opts == nil { - opts = &RunOpts{} + if c.opts == nil { + c.opts = &runOpts{} } // We must not change the provided options - timeout := opts.Timeout + timeout := c.opts.Timeout if timeout <= 0 { timeout = defaultCommandExecutionTimeout } cmdLogString := c.LogString() - callerInfo := util.CallerFuncName(1 /* util */ + 1 /* this */ + opts.LogSkip /* parent */) + callerInfo := util.CallerFuncName(1 /* util */ + 1 /* this */ + c.opts.LogSkip /* parent */) if pos := strings.LastIndex(callerInfo, "/"); pos >= 0 { callerInfo = callerInfo[pos+1:] } // these logs are for debugging purposes only, so no guarantee of correctness or stability - desc := fmt.Sprintf("git.Run(by:%s, repo:%s): %s", callerInfo, logArgSanitize(opts.Dir), cmdLogString) - log.DebugWithSkip(opts.LogSkip+1, "git.Command: %s", desc) + desc := fmt.Sprintf("git.Run(by:%s, repo:%s): %s", callerInfo, logArgSanitize(c.opts.Dir), cmdLogString) + log.Debug("git.Command: %s", desc) _, span := gtprof.GetTracer().Start(ctx, gtprof.TraceSpanGitRun) defer span.End() @@ -301,7 +436,7 @@ func (c *Command) Run(ctx context.Context, opts *RunOpts) error { var cancel context.CancelFunc var finished context.CancelFunc - if opts.UseContextTimeout { + if c.opts.UseContextTimeout { ctx, cancel, finished = process.GetManager().AddContext(ctx, desc) } else { ctx, cancel, finished = process.GetManager().AddContextTimeout(ctx, timeout, desc) @@ -312,24 +447,24 @@ func (c *Command) Run(ctx context.Context, opts *RunOpts) error { cmd := exec.CommandContext(ctx, c.prog, append(c.configArgs, c.args...)...) c.cmd = cmd // for debug purpose only - if opts.Env == nil { + if c.opts.Env == nil { cmd.Env = os.Environ() } else { - cmd.Env = opts.Env + cmd.Env = c.opts.Env } process.SetSysProcAttribute(cmd) cmd.Env = append(cmd.Env, CommonGitCmdEnvs()...) - cmd.Dir = opts.Dir - cmd.Stdout = opts.Stdout - cmd.Stderr = opts.Stderr - cmd.Stdin = opts.Stdin + cmd.Dir = c.opts.Dir + cmd.Stdout = c.opts.Stdout + cmd.Stderr = c.opts.Stderr + cmd.Stdin = c.opts.Stdin if err := cmd.Start(); err != nil { return err } - if opts.PipelineFunc != nil { - err := opts.PipelineFunc(ctx, cancel) + if c.opts.PipelineFunc != nil { + err := c.opts.PipelineFunc(ctx, cancel) if err != nil { cancel() _ = cmd.Wait() @@ -398,13 +533,8 @@ func IsErrorExitCode(err error, code int) bool { return false } -// RunStdString runs the command with options and returns stdout/stderr as string. and store stderr to returned error (err combined with stderr). -func (c *Command) RunStdString(ctx context.Context, opts *RunOpts) (stdout, stderr string, runErr RunStdError) { - if opts == nil { - opts = &RunOpts{} - } - opts.LogSkip++ - stdoutBytes, stderrBytes, err := c.RunStdBytes(ctx, opts) +func (c *Command) RunStdString(ctx context.Context) (stdout, stderr string, runErr RunStdError) { + stdoutBytes, stderrBytes, err := c.WithLogSkipStep(1).RunStdBytes(ctx) stdout = util.UnsafeBytesToString(stdoutBytes) stderr = util.UnsafeBytesToString(stderrBytes) if err != nil { @@ -414,34 +544,14 @@ func (c *Command) RunStdString(ctx context.Context, opts *RunOpts) (stdout, stde return stdout, stderr, nil } -// RunStdBytes runs the command with options and returns stdout/stderr as bytes. and store stderr to returned error (err combined with stderr). -func (c *Command) RunStdBytes(ctx context.Context, opts *RunOpts) (stdout, stderr []byte, runErr RunStdError) { - if opts == nil { - opts = &RunOpts{} - } - opts.LogSkip++ - if opts.Stdout != nil || opts.Stderr != nil { - // we must panic here, otherwise there would be bugs if developers set Stdin/Stderr by mistake, and it would be very difficult to debug - panic("stdout and stderr field must be nil when using RunStdBytes") - } +func (c *Command) RunStdBytes(ctx context.Context) (stdout, stderr []byte, runErr RunStdError) { stdoutBuf := &bytes.Buffer{} stderrBuf := &bytes.Buffer{} - // We must not change the provided options as it could break future calls - therefore make a copy. - newOpts := &RunOpts{ - Env: opts.Env, - Timeout: opts.Timeout, - UseContextTimeout: opts.UseContextTimeout, - Dir: opts.Dir, - Stdout: stdoutBuf, - Stderr: stderrBuf, - Stdin: opts.Stdin, - PipelineFunc: opts.PipelineFunc, - LogSkip: opts.LogSkip, - } - - err := c.Run(ctx, newOpts) - stderr = stderrBuf.Bytes() + err := c.WithLogSkipStep(1). + WithStdout(stdoutBuf). + WithStderr(stderrBuf). + Run(ctx) if err != nil { return nil, stderr, &runStdError{err: err, stderr: util.UnsafeBytesToString(stderr)} } diff --git a/modules/git/gitcmd/command_test.go b/modules/git/gitcmd/command_test.go index 544a97f64c102..05ef66b98c87b 100644 --- a/modules/git/gitcmd/command_test.go +++ b/modules/git/gitcmd/command_test.go @@ -27,31 +27,30 @@ func TestMain(m *testing.M) { func TestRunWithContextStd(t *testing.T) { cmd := NewCommand("--version") - stdout, stderr, err := cmd.RunStdString(t.Context(), &RunOpts{}) + stdout, stderr, err := cmd.RunStdString(t.Context()) assert.NoError(t, err) assert.Empty(t, stderr) assert.Contains(t, stdout, "git version") cmd = NewCommand("--no-such-arg") - stdout, stderr, err = cmd.RunStdString(t.Context(), &RunOpts{}) + stdout, stderr, err = cmd.RunStdString(t.Context()) if assert.Error(t, err) { - assert.Equal(t, stderr, err.Stderr()) - assert.Contains(t, err.Stderr(), "unknown option:") - assert.Contains(t, err.Error(), "exit status 129 - unknown option:") + assert.Contains(t, stderr, "unknown option:") + assert.Contains(t, err.Error(), "exit status 129") assert.Empty(t, stdout) } cmd = NewCommand() cmd.AddDynamicArguments("-test") - assert.ErrorIs(t, cmd.Run(t.Context(), &RunOpts{}), ErrBrokenCommand) + assert.ErrorIs(t, cmd.Run(t.Context()), ErrBrokenCommand) cmd = NewCommand() cmd.AddDynamicArguments("--test") - assert.ErrorIs(t, cmd.Run(t.Context(), &RunOpts{}), ErrBrokenCommand) + assert.ErrorIs(t, cmd.Run(t.Context()), ErrBrokenCommand) subCmd := "version" cmd = NewCommand().AddDynamicArguments(subCmd) // for test purpose only, the sub-command should never be dynamic for production - stdout, stderr, err = cmd.RunStdString(t.Context(), &RunOpts{}) + stdout, stderr, err = cmd.RunStdString(t.Context()) assert.NoError(t, err) assert.Empty(t, stderr) assert.Contains(t, stdout, "git version") diff --git a/modules/git/grep.go b/modules/git/grep.go index f5f6f120416b3..ed69a788a4aea 100644 --- a/modules/git/grep.go +++ b/modules/git/grep.go @@ -84,11 +84,10 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO cmd.AddDashesAndList(opts.PathspecList...) opts.MaxResultLimit = util.IfZero(opts.MaxResultLimit, 50) stderr := bytes.Buffer{} - err = cmd.Run(ctx, &gitcmd.RunOpts{ - Dir: repo.Path, - Stdout: stdoutWriter, - Stderr: &stderr, - PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { + err = cmd.WithDir(repo.Path). + WithStdout(stdoutWriter). + WithStderr(&stderr). + WithPipelineFunc(func(ctx context.Context, cancel context.CancelFunc) error { _ = stdoutWriter.Close() defer stdoutReader.Close() @@ -133,8 +132,8 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO } } return nil - }, - }) + }). + Run(ctx) // git grep exits by cancel (killed), usually it is caused by the limit of results if gitcmd.IsErrorExitCode(err, -1) && stderr.Len() == 0 { return results, nil diff --git a/modules/git/log_name_status.go b/modules/git/log_name_status.go index 7a5192f58b7c2..72e513000b396 100644 --- a/modules/git/log_name_status.go +++ b/modules/git/log_name_status.go @@ -65,11 +65,10 @@ func LogNameStatusRepo(ctx context.Context, repository, head, treepath string, p go func() { stderr := strings.Builder{} - err := cmd.Run(ctx, &gitcmd.RunOpts{ - Dir: repository, - Stdout: stdoutWriter, - Stderr: &stderr, - }) + err := cmd.WithDir(repository). + WithStdout(stdoutWriter). + WithStderr(&stderr). + Run(ctx) if err != nil { _ = stdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String())) return diff --git a/modules/git/pipeline/catfile.go b/modules/git/pipeline/catfile.go index ced8532e6d9da..a4d1ff64cf3a7 100644 --- a/modules/git/pipeline/catfile.go +++ b/modules/git/pipeline/catfile.go @@ -26,12 +26,11 @@ func CatFileBatchCheck(ctx context.Context, shasToCheckReader *io.PipeReader, ca stderr := new(bytes.Buffer) var errbuf strings.Builder cmd := gitcmd.NewCommand("cat-file", "--batch-check") - if err := cmd.Run(ctx, &gitcmd.RunOpts{ - Dir: tmpBasePath, - Stdin: shasToCheckReader, - Stdout: catFileCheckWriter, - Stderr: stderr, - }); err != nil { + if err := cmd.WithDir(tmpBasePath). + WithStdin(shasToCheckReader). + WithStdout(catFileCheckWriter). + WithStderr(stderr). + Run(ctx); err != nil { _ = catFileCheckWriter.CloseWithError(fmt.Errorf("git cat-file --batch-check [%s]: %w - %s", tmpBasePath, err, errbuf.String())) } } @@ -44,11 +43,10 @@ func CatFileBatchCheckAllObjects(ctx context.Context, catFileCheckWriter *io.Pip stderr := new(bytes.Buffer) var errbuf strings.Builder cmd := gitcmd.NewCommand("cat-file", "--batch-check", "--batch-all-objects") - if err := cmd.Run(ctx, &gitcmd.RunOpts{ - Dir: tmpBasePath, - Stdout: catFileCheckWriter, - Stderr: stderr, - }); err != nil { + if err := cmd.WithDir(tmpBasePath). + WithStdout(catFileCheckWriter). + WithStderr(stderr). + Run(ctx); err != nil { log.Error("git cat-file --batch-check --batch-all-object [%s]: %v - %s", tmpBasePath, err, errbuf.String()) err = fmt.Errorf("git cat-file --batch-check --batch-all-object [%s]: %w - %s", tmpBasePath, err, errbuf.String()) _ = catFileCheckWriter.CloseWithError(err) @@ -64,12 +62,12 @@ func CatFileBatch(ctx context.Context, shasToBatchReader *io.PipeReader, catFile stderr := new(bytes.Buffer) var errbuf strings.Builder - if err := gitcmd.NewCommand("cat-file", "--batch").Run(ctx, &gitcmd.RunOpts{ - Dir: tmpBasePath, - Stdout: catFileBatchWriter, - Stdin: shasToBatchReader, - Stderr: stderr, - }); err != nil { + if err := gitcmd.NewCommand("cat-file", "--batch"). + WithDir(tmpBasePath). + WithStdin(shasToBatchReader). + WithStdout(catFileBatchWriter). + WithStderr(stderr). + Run(ctx); err != nil { _ = shasToBatchReader.CloseWithError(fmt.Errorf("git rev-list [%s]: %w - %s", tmpBasePath, err, errbuf.String())) } } diff --git a/modules/git/pipeline/lfs_nogogit.go b/modules/git/pipeline/lfs_nogogit.go index d2f147854d91b..4881a2be64870 100644 --- a/modules/git/pipeline/lfs_nogogit.go +++ b/modules/git/pipeline/lfs_nogogit.go @@ -33,11 +33,11 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err go func() { stderr := strings.Builder{} - err := gitcmd.NewCommand("rev-list", "--all").Run(repo.Ctx, &gitcmd.RunOpts{ - Dir: repo.Path, - Stdout: revListWriter, - Stderr: &stderr, - }) + err := gitcmd.NewCommand("rev-list", "--all"). + WithDir(repo.Path). + WithStdout(revListWriter). + WithStderr(&stderr). + Run(repo.Ctx) if err != nil { _ = revListWriter.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String())) } else { diff --git a/modules/git/pipeline/namerev.go b/modules/git/pipeline/namerev.go index 0081f7a26db70..782b5f0531f44 100644 --- a/modules/git/pipeline/namerev.go +++ b/modules/git/pipeline/namerev.go @@ -22,12 +22,12 @@ func NameRevStdin(ctx context.Context, shasToNameReader *io.PipeReader, nameRevS stderr := new(bytes.Buffer) var errbuf strings.Builder - if err := gitcmd.NewCommand("name-rev", "--stdin", "--name-only", "--always").Run(ctx, &gitcmd.RunOpts{ - Dir: tmpBasePath, - Stdout: nameRevStdinWriter, - Stdin: shasToNameReader, - Stderr: stderr, - }); err != nil { + if err := gitcmd.NewCommand("name-rev", "--stdin", "--name-only", "--always"). + WithDir(tmpBasePath). + WithStdin(shasToNameReader). + WithStdout(nameRevStdinWriter). + WithStderr(stderr). + Run(ctx); err != nil { _ = shasToNameReader.CloseWithError(fmt.Errorf("git name-rev [%s]: %w - %s", tmpBasePath, err, errbuf.String())) } } diff --git a/modules/git/pipeline/revlist.go b/modules/git/pipeline/revlist.go index 9d4ff7543413f..755b165a650d0 100644 --- a/modules/git/pipeline/revlist.go +++ b/modules/git/pipeline/revlist.go @@ -24,11 +24,10 @@ func RevListAllObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sy stderr := new(bytes.Buffer) var errbuf strings.Builder cmd := gitcmd.NewCommand("rev-list", "--objects", "--all") - if err := cmd.Run(ctx, &gitcmd.RunOpts{ - Dir: basePath, - Stdout: revListWriter, - Stderr: stderr, - }); err != nil { + if err := cmd.WithDir(basePath). + WithStdout(revListWriter). + WithStderr(stderr). + Run(ctx); err != nil { log.Error("git rev-list --objects --all [%s]: %v - %s", basePath, err, errbuf.String()) err = fmt.Errorf("git rev-list --objects --all [%s]: %w - %s", basePath, err, errbuf.String()) _ = revListWriter.CloseWithError(err) @@ -46,11 +45,10 @@ func RevListObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sync. if baseSHA != "" { cmd = cmd.AddArguments("--not").AddDynamicArguments(baseSHA) } - if err := cmd.Run(ctx, &gitcmd.RunOpts{ - Dir: tmpBasePath, - Stdout: revListWriter, - Stderr: stderr, - }); err != nil { + if err := cmd.WithDir(tmpBasePath). + WithStdout(revListWriter). + WithStderr(stderr). + Run(ctx); err != nil { log.Error("git rev-list [%s]: %v - %s", tmpBasePath, err, errbuf.String()) errChan <- fmt.Errorf("git rev-list [%s]: %w - %s", tmpBasePath, err, errbuf.String()) } diff --git a/modules/git/remote.go b/modules/git/remote.go index 9f12142f916ea..1999ad4b9444e 100644 --- a/modules/git/remote.go +++ b/modules/git/remote.go @@ -22,7 +22,7 @@ func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, cmd = gitcmd.NewCommand("config", "--get").AddDynamicArguments("remote." + remoteName + ".url") } - result, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) + result, _, err := cmd.WithDir(repoPath).RunStdString(ctx) if err != nil { return "", err } diff --git a/modules/git/repo.go b/modules/git/repo.go index 9f8b6225c8f05..27ef6e265a9d3 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -42,8 +42,8 @@ func (repo *Repository) GetAllCommitsCount() (int64, error) { func (repo *Repository) ShowPrettyFormatLogToList(ctx context.Context, revisionRange string) ([]*Commit, error) { // avoid: ambiguous argument 'refs/a...refs/b': unknown revision or path not in the working tree. Use '--': 'git [...] -- [...]' logs, _, err := gitcmd.NewCommand("log").AddArguments(prettyLogFormat). - AddDynamicArguments(revisionRange).AddArguments("--"). - RunStdBytes(ctx, &gitcmd.RunOpts{Dir: repo.Path}) + AddDynamicArguments(revisionRange).AddArguments("--").WithDir(repo.Path). + RunStdBytes(ctx) if err != nil { return nil, err } @@ -71,7 +71,7 @@ func (repo *Repository) parsePrettyFormatLogToList(logs []byte) ([]*Commit, erro // IsRepoURLAccessible checks if given repository URL is accessible. func IsRepoURLAccessible(ctx context.Context, url string) bool { - _, _, err := gitcmd.NewCommand("ls-remote", "-q", "-h").AddDynamicArguments(url, "HEAD").RunStdString(ctx, nil) + _, _, err := gitcmd.NewCommand("ls-remote", "-q", "-h").AddDynamicArguments(url, "HEAD").RunStdString(ctx) return err == nil } @@ -94,19 +94,20 @@ func InitRepository(ctx context.Context, repoPath string, bare bool, objectForma if bare { cmd.AddArguments("--bare") } - _, _, err = cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) + _, _, err = cmd.WithDir(repoPath).RunStdString(ctx) return err } // IsEmpty Check if repository is empty. func (repo *Repository) IsEmpty() (bool, error) { var errbuf, output strings.Builder - if err := gitcmd.NewCommand().AddOptionFormat("--git-dir=%s", repo.Path).AddArguments("rev-list", "-n", "1", "--all"). - Run(repo.Ctx, &gitcmd.RunOpts{ - Dir: repo.Path, - Stdout: &output, - Stderr: &errbuf, - }); err != nil { + if err := gitcmd.NewCommand(). + AddOptionFormat("--git-dir=%s", repo.Path). + AddArguments("rev-list", "-n", "1", "--all"). + WithDir(repo.Path). + WithStdout(&output). + WithStderr(&errbuf). + Run(repo.Ctx); err != nil { if (err.Error() == "exit status 1" && strings.TrimSpace(errbuf.String()) == "") || err.Error() == "exit status 129" { // git 2.11 exits with 129 if the repo is empty return true, nil @@ -179,12 +180,12 @@ func Clone(ctx context.Context, from, to string, opts CloneRepoOptions) error { } stderr := new(bytes.Buffer) - if err = cmd.Run(ctx, &gitcmd.RunOpts{ - Timeout: opts.Timeout, - Env: envs, - Stdout: io.Discard, - Stderr: stderr, - }); err != nil { + if err = cmd. + WithTimeout(opts.Timeout). + WithEnv(envs). + WithStdout(io.Discard). + WithStderr(stderr). + Run(ctx); err != nil { return gitcmd.ConcatenateError(err, stderr.String()) } return nil @@ -215,7 +216,7 @@ func Push(ctx context.Context, repoPath string, opts PushOptions) error { } cmd.AddDashesAndList(remoteBranchArgs...) - stdout, stderr, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Env: opts.Env, Timeout: opts.Timeout, Dir: repoPath}) + stdout, stderr, err := cmd.WithEnv(opts.Env).WithTimeout(opts.Timeout).WithDir(repoPath).RunStdString(ctx) if err != nil { if strings.Contains(stderr, "non-fast-forward") { return &ErrPushOutOfDate{StdOut: stdout, StdErr: stderr, Err: err} @@ -235,7 +236,7 @@ func Push(ctx context.Context, repoPath string, opts PushOptions) error { // GetLatestCommitTime returns time for latest commit in repository (across all branches) func GetLatestCommitTime(ctx context.Context, repoPath string) (time.Time, error) { cmd := gitcmd.NewCommand("for-each-ref", "--sort=-committerdate", BranchPrefix, "--count", "1", "--format=%(committerdate)") - stdout, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) + stdout, _, err := cmd.WithDir(repoPath).RunStdString(ctx) if err != nil { return time.Time{}, err } @@ -252,23 +253,23 @@ func (repo *Repository) CreateBundle(ctx context.Context, commit string, out io. defer cleanup() env := append(os.Environ(), "GIT_OBJECT_DIRECTORY="+filepath.Join(repo.Path, "objects")) - _, _, err = gitcmd.NewCommand("init", "--bare").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmp, Env: env}) + _, _, err = gitcmd.NewCommand("init", "--bare").WithEnv(env).WithDir(tmp).RunStdString(ctx) if err != nil { return err } - _, _, err = gitcmd.NewCommand("reset", "--soft").AddDynamicArguments(commit).RunStdString(ctx, &gitcmd.RunOpts{Dir: tmp, Env: env}) + _, _, err = gitcmd.NewCommand("reset", "--soft").AddDynamicArguments(commit).WithEnv(env).WithDir(tmp).RunStdString(ctx) if err != nil { return err } - _, _, err = gitcmd.NewCommand("branch", "-m", "bundle").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmp, Env: env}) + _, _, err = gitcmd.NewCommand("branch", "-m", "bundle").WithEnv(env).WithDir(tmp).RunStdString(ctx) if err != nil { return err } tmpFile := filepath.Join(tmp, "bundle") - _, _, err = gitcmd.NewCommand("bundle", "create").AddDynamicArguments(tmpFile, "bundle", "HEAD").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmp, Env: env}) + _, _, err = gitcmd.NewCommand("bundle", "create").AddDynamicArguments(tmpFile, "bundle", "HEAD").WithEnv(env).WithDir(tmp).RunStdString(ctx) if err != nil { return err } diff --git a/modules/git/repo_archive.go b/modules/git/repo_archive.go index e12300345f16c..8a9eec9e6aa78 100644 --- a/modules/git/repo_archive.go +++ b/modules/git/repo_archive.go @@ -63,11 +63,10 @@ func (repo *Repository) CreateArchive(ctx context.Context, format ArchiveType, t cmd.AddDynamicArguments(commitID) var stderr strings.Builder - err := cmd.Run(ctx, &gitcmd.RunOpts{ - Dir: repo.Path, - Stdout: target, - Stderr: &stderr, - }) + err := cmd.WithDir(repo.Path). + WithStdout(target). + WithStderr(&stderr). + Run(ctx) if err != nil { return gitcmd.ConcatenateError(err, stderr.String()) } diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go index ef0f9a1e1334a..1eebc72158bb6 100644 --- a/modules/git/repo_branch.go +++ b/modules/git/repo_branch.go @@ -17,8 +17,8 @@ func (repo *Repository) AddRemote(name, url string, fetch bool) error { if fetch { cmd.AddArguments("-f") } - cmd.AddDynamicArguments(name, url) - - _, _, err := cmd.RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + _, _, err := cmd.AddDynamicArguments(name, url). + WithDir(repo.Path). + RunStdString(repo.Ctx) return err } diff --git a/modules/git/repo_branch_nogogit.go b/modules/git/repo_branch_nogogit.go index 255c2974e9b0e..f1b26b06ab610 100644 --- a/modules/git/repo_branch_nogogit.go +++ b/modules/git/repo_branch_nogogit.go @@ -110,11 +110,11 @@ func WalkShowRef(ctx context.Context, repoPath string, extraArgs gitcmd.TrustedC stderrBuilder := &strings.Builder{} args := gitcmd.TrustedCmdArgs{"for-each-ref", "--format=%(objectname) %(refname)"} args = append(args, extraArgs...) - err := gitcmd.NewCommand(args...).Run(ctx, &gitcmd.RunOpts{ - Dir: repoPath, - Stdout: stdoutWriter, - Stderr: stderrBuilder, - }) + err := gitcmd.NewCommand(args...). + WithDir(repoPath). + WithStdout(stdoutWriter). + WithStderr(stderrBuilder). + Run(ctx) if err != nil { if stderrBuilder.Len() == 0 { _ = stdoutWriter.Close() diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 6e5911f1dddf5..5f4487ce7e24b 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -60,7 +60,11 @@ func (repo *Repository) getCommitByPathWithID(id ObjectID, relpath string) (*Com relpath = `\` + relpath } - stdout, _, runErr := gitcmd.NewCommand("log", "-1", prettyLogFormat).AddDynamicArguments(id.String()).AddDashesAndList(relpath).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, runErr := gitcmd.NewCommand("log", "-1", prettyLogFormat). + AddDynamicArguments(id.String()). + AddDashesAndList(relpath). + WithDir(repo.Path). + RunStdString(repo.Ctx) if runErr != nil { return nil, runErr } @@ -75,7 +79,10 @@ func (repo *Repository) getCommitByPathWithID(id ObjectID, relpath string) (*Com // GetCommitByPath returns the last commit of relative path. func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) { - stdout, _, runErr := gitcmd.NewCommand("log", "-1", prettyLogFormat).AddDashesAndList(relpath).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, runErr := gitcmd.NewCommand("log", "-1", prettyLogFormat). + AddDashesAndList(relpath). + WithDir(repo.Path). + RunStdBytes(repo.Ctx) if runErr != nil { return nil, runErr } @@ -108,7 +115,7 @@ func (repo *Repository) commitsByRangeWithTime(id ObjectID, page, pageSize int, cmd.AddOptionFormat("--until=%s", until) } - stdout, _, err := cmd.RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, err := cmd.WithDir(repo.Path).RunStdBytes(repo.Ctx) if err != nil { return nil, err } @@ -162,7 +169,7 @@ func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([ // search for commits matching given constraints and keywords in commit msg addCommonSearchArgs(cmd) - stdout, _, err := cmd.RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, err := cmd.WithDir(repo.Path).RunStdBytes(repo.Ctx) if err != nil { return nil, err } @@ -183,7 +190,7 @@ func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([ hashCmd.AddDynamicArguments(v) // search with given constraints for commit matching sha hash of v - hashMatching, _, err := hashCmd.RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + hashMatching, _, err := hashCmd.WithDir(repo.Path).RunStdBytes(repo.Ctx) if err != nil || bytes.Contains(stdout, hashMatching) { continue } @@ -198,7 +205,11 @@ func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([ // FileChangedBetweenCommits Returns true if the file changed between commit IDs id1 and id2 // You must ensure that id1 and id2 are valid commit ids. func (repo *Repository) FileChangedBetweenCommits(filename, id1, id2 string) (bool, error) { - stdout, _, err := gitcmd.NewCommand("diff", "--name-only", "-z").AddDynamicArguments(id1, id2).AddDashesAndList(filename).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, err := gitcmd.NewCommand("diff", "--name-only", "-z"). + AddDynamicArguments(id1, id2). + AddDashesAndList(filename). + WithDir(repo.Path). + RunStdBytes(repo.Ctx) if err != nil { return false, err } @@ -249,11 +260,10 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) } gitCmd.AddDashesAndList(opts.File) - err := gitCmd.Run(repo.Ctx, &gitcmd.RunOpts{ - Dir: repo.Path, - Stdout: stdoutWriter, - Stderr: &stderr, - }) + err := gitCmd.WithDir(repo.Path). + WithStdout(stdoutWriter). + WithStderr(&stderr). + Run(repo.Ctx) if err != nil { _ = stdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String())) } else { @@ -291,11 +301,17 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) // FilesCountBetween return the number of files changed between two commits func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (int, error) { - stdout, _, err := gitcmd.NewCommand("diff", "--name-only").AddDynamicArguments(startCommitID+"..."+endCommitID).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, err := gitcmd.NewCommand("diff", "--name-only"). + AddDynamicArguments(startCommitID + "..." + endCommitID). + WithDir(repo.Path). + RunStdString(repo.Ctx) if err != nil && strings.Contains(err.Error(), "no merge base") { // git >= 2.28 now returns an error if startCommitID and endCommitID have become unrelated. // previously it would return the results of git diff --name-only startCommitID endCommitID so let's try that... - stdout, _, err = gitcmd.NewCommand("diff", "--name-only").AddDynamicArguments(startCommitID, endCommitID).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, err = gitcmd.NewCommand("diff", "--name-only"). + AddDynamicArguments(startCommitID, endCommitID). + WithDir(repo.Path). + RunStdString(repo.Ctx) } if err != nil { return 0, err @@ -309,13 +325,22 @@ func (repo *Repository) CommitsBetween(last, before *Commit) ([]*Commit, error) var stdout []byte var err error if before == nil { - stdout, _, err = gitcmd.NewCommand("rev-list").AddDynamicArguments(last.ID.String()).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, err = gitcmd.NewCommand("rev-list"). + AddDynamicArguments(last.ID.String()). + WithDir(repo.Path). + RunStdBytes(repo.Ctx) } else { - stdout, _, err = gitcmd.NewCommand("rev-list").AddDynamicArguments(before.ID.String()+".."+last.ID.String()).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, err = gitcmd.NewCommand("rev-list"). + AddDynamicArguments(before.ID.String() + ".." + last.ID.String()). + WithDir(repo.Path). + RunStdBytes(repo.Ctx) if err != nil && strings.Contains(err.Error(), "no merge base") { // future versions of git >= 2.28 are likely to return an error if before and last have become unrelated. // previously it would return the results of git rev-list before last so let's try that... - stdout, _, err = gitcmd.NewCommand("rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, err = gitcmd.NewCommand("rev-list"). + AddDynamicArguments(before.ID.String(), last.ID.String()). + WithDir(repo.Path). + RunStdBytes(repo.Ctx) } } if err != nil { @@ -332,19 +357,25 @@ func (repo *Repository) CommitsBetweenLimit(last, before *Commit, limit, skip in stdout, _, err = gitcmd.NewCommand("rev-list"). AddOptionValues("--max-count", strconv.Itoa(limit)). AddOptionValues("--skip", strconv.Itoa(skip)). - AddDynamicArguments(last.ID.String()).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + AddDynamicArguments(last.ID.String()). + WithDir(repo.Path). + RunStdBytes(repo.Ctx) } else { stdout, _, err = gitcmd.NewCommand("rev-list"). AddOptionValues("--max-count", strconv.Itoa(limit)). AddOptionValues("--skip", strconv.Itoa(skip)). - AddDynamicArguments(before.ID.String()+".."+last.ID.String()).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + AddDynamicArguments(before.ID.String() + ".." + last.ID.String()). + WithDir(repo.Path). + RunStdBytes(repo.Ctx) if err != nil && strings.Contains(err.Error(), "no merge base") { // future versions of git >= 2.28 are likely to return an error if before and last have become unrelated. // previously it would return the results of git rev-list --max-count n before last so let's try that... stdout, _, err = gitcmd.NewCommand("rev-list"). AddOptionValues("--max-count", strconv.Itoa(limit)). AddOptionValues("--skip", strconv.Itoa(skip)). - AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + AddDynamicArguments(before.ID.String(), last.ID.String()). + WithDir(repo.Path). + RunStdBytes(repo.Ctx) } } if err != nil { @@ -359,13 +390,25 @@ func (repo *Repository) CommitsBetweenNotBase(last, before *Commit, baseBranch s var stdout []byte var err error if before == nil { - stdout, _, err = gitcmd.NewCommand("rev-list").AddDynamicArguments(last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, err = gitcmd.NewCommand("rev-list"). + AddDynamicArguments(last.ID.String()). + AddOptionValues("--not", baseBranch). + WithDir(repo.Path). + RunStdBytes(repo.Ctx) } else { - stdout, _, err = gitcmd.NewCommand("rev-list").AddDynamicArguments(before.ID.String()+".."+last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, err = gitcmd.NewCommand("rev-list"). + AddDynamicArguments(before.ID.String()+".."+last.ID.String()). + AddOptionValues("--not", baseBranch). + WithDir(repo.Path). + RunStdBytes(repo.Ctx) if err != nil && strings.Contains(err.Error(), "no merge base") { // future versions of git >= 2.28 are likely to return an error if before and last have become unrelated. // previously it would return the results of git rev-list before last so let's try that... - stdout, _, err = gitcmd.NewCommand("rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, err = gitcmd.NewCommand("rev-list"). + AddDynamicArguments(before.ID.String(), last.ID.String()). + AddOptionValues("--not", baseBranch). + WithDir(repo.Path). + RunStdBytes(repo.Ctx) } } if err != nil { @@ -417,7 +460,7 @@ func (repo *Repository) commitsBefore(id ObjectID, limit int) ([]*Commit, error) } cmd.AddDynamicArguments(id.String()) - stdout, _, runErr := cmd.RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, runErr := cmd.WithDir(repo.Path).RunStdBytes(repo.Ctx) if runErr != nil { return nil, runErr } @@ -457,10 +500,9 @@ func (repo *Repository) getBranches(env []string, commitID string, limit int) ([ stdout, _, err := gitcmd.NewCommand("for-each-ref", "--format=%(refname:strip=2)"). AddOptionFormat("--count=%d", limit). AddOptionValues("--contains", commitID, BranchPrefix). - RunStdString(repo.Ctx, &gitcmd.RunOpts{ - Dir: repo.Path, - Env: env, - }) + WithDir(repo.Path). + WithEnv(env). + RunStdString(repo.Ctx) if err != nil { return nil, err } @@ -469,10 +511,11 @@ func (repo *Repository) getBranches(env []string, commitID string, limit int) ([ return branches, nil } - stdout, _, err := gitcmd.NewCommand("branch").AddOptionValues("--contains", commitID).RunStdString(repo.Ctx, &gitcmd.RunOpts{ - Dir: repo.Path, - Env: env, - }) + stdout, _, err := gitcmd.NewCommand("branch"). + AddOptionValues("--contains", commitID). + WithDir(repo.Path). + WithEnv(env). + RunStdString(repo.Ctx) if err != nil { return nil, err } @@ -511,7 +554,10 @@ func (repo *Repository) GetCommitsFromIDs(commitIDs []string) []*Commit { // IsCommitInBranch check if the commit is on the branch func (repo *Repository) IsCommitInBranch(commitID, branch string) (r bool, err error) { - stdout, _, err := gitcmd.NewCommand("branch", "--contains").AddDynamicArguments(commitID, branch).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, err := gitcmd.NewCommand("branch", "--contains"). + AddDynamicArguments(commitID, branch). + WithDir(repo.Path). + RunStdString(repo.Ctx) if err != nil { return false, err } @@ -540,10 +586,9 @@ func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID s cmd := gitcmd.NewCommand("log", prettyLogFormat) cmd.AddDynamicArguments(endCommitID) - stdout, _, runErr := cmd.RunStdBytes(repo.Ctx, &gitcmd.RunOpts{ - Dir: repo.Path, - Env: env, - }) + stdout, _, runErr := cmd.WithDir(repo.Path). + WithEnv(env). + RunStdBytes(repo.Ctx) if runErr != nil { return "", runErr } diff --git a/modules/git/repo_commit_nogogit.go b/modules/git/repo_commit_nogogit.go index d2c66a541b7d5..3f27833fa6c62 100644 --- a/modules/git/repo_commit_nogogit.go +++ b/modules/git/repo_commit_nogogit.go @@ -17,7 +17,10 @@ import ( // ResolveReference resolves a name to a reference func (repo *Repository) ResolveReference(name string) (string, error) { - stdout, _, err := gitcmd.NewCommand("show-ref", "--hash").AddDynamicArguments(name).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, err := gitcmd.NewCommand("show-ref", "--hash"). + AddDynamicArguments(name). + WithDir(repo.Path). + RunStdString(repo.Ctx) if err != nil { if strings.Contains(err.Error(), "not a valid ref") { return "", ErrNotExist{name, ""} @@ -57,7 +60,10 @@ func (repo *Repository) IsCommitExist(name string) bool { log.Error("IsCommitExist: %v", err) return false } - _, _, err := gitcmd.NewCommand("cat-file", "-e").AddDynamicArguments(name).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + _, _, err := gitcmd.NewCommand("cat-file", "-e"). + AddDynamicArguments(name). + WithDir(repo.Path). + RunStdString(repo.Ctx) return err == nil } diff --git a/modules/git/repo_commitgraph.go b/modules/git/repo_commitgraph.go index 331c799b33433..3dac74304c151 100644 --- a/modules/git/repo_commitgraph.go +++ b/modules/git/repo_commitgraph.go @@ -14,7 +14,7 @@ import ( // this requires git v2.18 to be installed func WriteCommitGraph(ctx context.Context, repoPath string) error { if DefaultFeatures().CheckVersionAtLeast("2.18") { - if _, _, err := gitcmd.NewCommand("commit-graph", "write").RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}); err != nil { + if _, _, err := gitcmd.NewCommand("commit-graph", "write").WithDir(repoPath).RunStdString(ctx); err != nil { return fmt.Errorf("unable to write commit-graph for '%s' : %w", repoPath, err) } } diff --git a/modules/git/repo_compare.go b/modules/git/repo_compare.go index 69835521ec0ef..f60696a76378d 100644 --- a/modules/git/repo_compare.go +++ b/modules/git/repo_compare.go @@ -27,13 +27,20 @@ func (repo *Repository) GetMergeBase(tmpRemote, base, head string) (string, stri if tmpRemote != "origin" { tmpBaseName := RemotePrefix + tmpRemote + "/tmp_" + base // Fetch commit into a temporary branch in order to be able to handle commits and tags - _, _, err := gitcmd.NewCommand("fetch", "--no-tags").AddDynamicArguments(tmpRemote).AddDashesAndList(base+":"+tmpBaseName).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + _, _, err := gitcmd.NewCommand("fetch", "--no-tags"). + AddDynamicArguments(tmpRemote). + AddDashesAndList(base + ":" + tmpBaseName). + WithDir(repo.Path). + RunStdString(repo.Ctx) if err == nil { base = tmpBaseName } } - stdout, _, err := gitcmd.NewCommand("merge-base").AddDashesAndList(base, head).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, err := gitcmd.NewCommand("merge-base"). + AddDashesAndList(base, head). + WithDir(repo.Path). + RunStdString(repo.Ctx) return strings.TrimSpace(stdout), base, err } @@ -61,22 +68,25 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis } // avoid: ambiguous argument 'refs/a...refs/b': unknown revision or path not in the working tree. Use '--': 'git [...] -- [...]' - if err := gitcmd.NewCommand("diff", "-z", "--name-only").AddDynamicArguments(base+separator+head).AddArguments("--"). - Run(repo.Ctx, &gitcmd.RunOpts{ - Dir: repo.Path, - Stdout: w, - Stderr: stderr, - }); err != nil { + if err := gitcmd.NewCommand("diff", "-z", "--name-only"). + AddDynamicArguments(base + separator + head). + AddArguments("--"). + WithDir(repo.Path). + WithStdout(w). + WithStderr(stderr). + Run(repo.Ctx); err != nil { if strings.Contains(stderr.String(), "no merge base") { // git >= 2.28 now returns an error if base and head have become unrelated. // previously it would return the results of git diff -z --name-only base head so let's try that... w = &lineCountWriter{} stderr.Reset() - if err = gitcmd.NewCommand("diff", "-z", "--name-only").AddDynamicArguments(base, head).AddArguments("--").Run(repo.Ctx, &gitcmd.RunOpts{ - Dir: repo.Path, - Stdout: w, - Stderr: stderr, - }); err == nil { + if err = gitcmd.NewCommand("diff", "-z", "--name-only"). + AddDynamicArguments(base, head). + AddArguments("--"). + WithDir(repo.Path). + WithStdout(w). + WithStderr(stderr). + Run(repo.Ctx); err == nil { return w.numLines, nil } } @@ -91,30 +101,29 @@ var patchCommits = regexp.MustCompile(`^From\s(\w+)\s`) func (repo *Repository) GetDiff(compareArg string, w io.Writer) error { stderr := new(bytes.Buffer) return gitcmd.NewCommand("diff", "-p").AddDynamicArguments(compareArg). - Run(repo.Ctx, &gitcmd.RunOpts{ - Dir: repo.Path, - Stdout: w, - Stderr: stderr, - }) + WithDir(repo.Path). + WithStdout(w). + WithStderr(stderr). + Run(repo.Ctx) } // GetDiffBinary generates and returns patch data between given revisions, including binary diffs. func (repo *Repository) GetDiffBinary(compareArg string, w io.Writer) error { - return gitcmd.NewCommand("diff", "-p", "--binary", "--histogram").AddDynamicArguments(compareArg).Run(repo.Ctx, &gitcmd.RunOpts{ - Dir: repo.Path, - Stdout: w, - }) + return gitcmd.NewCommand("diff", "-p", "--binary", "--histogram"). + AddDynamicArguments(compareArg). + WithDir(repo.Path). + WithStdout(w). + Run(repo.Ctx) } // GetPatch generates and returns format-patch data between given revisions, able to be used with `git apply` func (repo *Repository) GetPatch(compareArg string, w io.Writer) error { stderr := new(bytes.Buffer) return gitcmd.NewCommand("format-patch", "--binary", "--stdout").AddDynamicArguments(compareArg). - Run(repo.Ctx, &gitcmd.RunOpts{ - Dir: repo.Path, - Stdout: w, - Stderr: stderr, - }) + WithDir(repo.Path). + WithStdout(w). + WithStderr(stderr). + Run(repo.Ctx) } // GetFilesChangedBetween returns a list of all files that have been changed between the given commits @@ -131,7 +140,7 @@ func (repo *Repository) GetFilesChangedBetween(base, head string) ([]string, err } else { cmd.AddDynamicArguments(base, head) } - stdout, _, err := cmd.RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, err := cmd.WithDir(repo.Path).RunStdString(repo.Ctx) if err != nil { return nil, err } diff --git a/modules/git/repo_compare_test.go b/modules/git/repo_compare_test.go index 47fd2ca102f20..bf16b7cfceb61 100644 --- a/modules/git/repo_compare_test.go +++ b/modules/git/repo_compare_test.go @@ -103,7 +103,8 @@ func TestReadWritePullHead(t *testing.T) { newCommit := "feaf4ba6bc635fec442f46ddd4512416ec43c2c2" _, _, err = gitcmd.NewCommand("update-ref"). AddDynamicArguments(PullPrefix+"1/head", newCommit). - RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repo.Path}) + WithDir(repo.Path). + RunStdString(t.Context()) if err != nil { assert.NoError(t, err) return @@ -121,8 +122,9 @@ func TestReadWritePullHead(t *testing.T) { // Remove file after the test _, _, err = gitcmd.NewCommand("update-ref", "--no-deref", "-d"). - AddDynamicArguments(PullPrefix+"1/head"). - RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repo.Path}) + AddDynamicArguments(PullPrefix + "1/head"). + WithDir(repo.Path). + RunStdString(t.Context()) assert.NoError(t, err) } diff --git a/modules/git/repo_gpg.go b/modules/git/repo_gpg.go index a999d2dbc6080..eb1e71e30a5bb 100644 --- a/modules/git/repo_gpg.go +++ b/modules/git/repo_gpg.go @@ -43,7 +43,7 @@ func (repo *Repository) GetDefaultPublicGPGKey(forceUpdate bool) (*GPGSettings, Sign: true, } - value, _, _ := gitcmd.NewCommand("config", "--get", "commit.gpgsign").RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + value, _, _ := gitcmd.NewCommand("config", "--get", "commit.gpgsign").WithDir(repo.Path).RunStdString(repo.Ctx) sign, valid := ParseBool(strings.TrimSpace(value)) if !sign || !valid { gpgSettings.Sign = false @@ -51,16 +51,16 @@ func (repo *Repository) GetDefaultPublicGPGKey(forceUpdate bool) (*GPGSettings, return gpgSettings, nil } - signingKey, _, _ := gitcmd.NewCommand("config", "--get", "user.signingkey").RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + signingKey, _, _ := gitcmd.NewCommand("config", "--get", "user.signingkey").WithDir(repo.Path).RunStdString(repo.Ctx) gpgSettings.KeyID = strings.TrimSpace(signingKey) - format, _, _ := gitcmd.NewCommand("config", "--default", SigningKeyFormatOpenPGP, "--get", "gpg.format").RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + format, _, _ := gitcmd.NewCommand("config", "--default", SigningKeyFormatOpenPGP, "--get", "gpg.format").WithDir(repo.Path).RunStdString(repo.Ctx) gpgSettings.Format = strings.TrimSpace(format) - defaultEmail, _, _ := gitcmd.NewCommand("config", "--get", "user.email").RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + defaultEmail, _, _ := gitcmd.NewCommand("config", "--get", "user.email").WithDir(repo.Path).RunStdString(repo.Ctx) gpgSettings.Email = strings.TrimSpace(defaultEmail) - defaultName, _, _ := gitcmd.NewCommand("config", "--get", "user.name").RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + defaultName, _, _ := gitcmd.NewCommand("config", "--get", "user.name").WithDir(repo.Path).RunStdString(repo.Ctx) gpgSettings.Name = strings.TrimSpace(defaultName) if err := gpgSettings.LoadPublicKeyContent(); err != nil { diff --git a/modules/git/repo_index.go b/modules/git/repo_index.go index e7b3792d95a2c..4068f86bb25f1 100644 --- a/modules/git/repo_index.go +++ b/modules/git/repo_index.go @@ -22,7 +22,7 @@ func (repo *Repository) ReadTreeToIndex(treeish string, indexFilename ...string) } if len(treeish) != objectFormat.FullLength() { - res, _, err := gitcmd.NewCommand("rev-parse", "--verify").AddDynamicArguments(treeish).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + res, _, err := gitcmd.NewCommand("rev-parse", "--verify").AddDynamicArguments(treeish).WithDir(repo.Path).RunStdString(repo.Ctx) if err != nil { return err } @@ -42,7 +42,7 @@ func (repo *Repository) readTreeToIndex(id ObjectID, indexFilename ...string) er if len(indexFilename) > 0 { env = append(os.Environ(), "GIT_INDEX_FILE="+indexFilename[0]) } - _, _, err := gitcmd.NewCommand("read-tree").AddDynamicArguments(id.String()).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path, Env: env}) + _, _, err := gitcmd.NewCommand("read-tree").AddDynamicArguments(id.String()).WithDir(repo.Path).WithEnv(env).RunStdString(repo.Ctx) if err != nil { return err } @@ -75,14 +75,14 @@ func (repo *Repository) ReadTreeToTemporaryIndex(treeish string) (tmpIndexFilena // EmptyIndex empties the index func (repo *Repository) EmptyIndex() error { - _, _, err := gitcmd.NewCommand("read-tree", "--empty").RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + _, _, err := gitcmd.NewCommand("read-tree", "--empty").WithDir(repo.Path).RunStdString(repo.Ctx) return err } // LsFiles checks if the given filenames are in the index func (repo *Repository) LsFiles(filenames ...string) ([]string, error) { cmd := gitcmd.NewCommand("ls-files", "-z").AddDashesAndList(filenames...) - res, _, err := cmd.RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + res, _, err := cmd.WithDir(repo.Path).RunStdBytes(repo.Ctx) if err != nil { return nil, err } @@ -110,12 +110,12 @@ func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error { buffer.WriteString("0 blob " + objectFormat.EmptyObjectID().String() + "\t" + file + "\000") } } - return cmd.Run(repo.Ctx, &gitcmd.RunOpts{ - Dir: repo.Path, - Stdin: bytes.NewReader(buffer.Bytes()), - Stdout: stdout, - Stderr: stderr, - }) + return cmd. + WithDir(repo.Path). + WithStdin(bytes.NewReader(buffer.Bytes())). + WithStdout(stdout). + WithStderr(stderr). + Run(repo.Ctx) } type IndexObjectInfo struct { @@ -134,12 +134,12 @@ func (repo *Repository) AddObjectsToIndex(objects ...IndexObjectInfo) error { // using format: mode SP type SP sha1 TAB path buffer.WriteString(object.Mode + " blob " + object.Object.String() + "\t" + object.Filename + "\000") } - return cmd.Run(repo.Ctx, &gitcmd.RunOpts{ - Dir: repo.Path, - Stdin: bytes.NewReader(buffer.Bytes()), - Stdout: stdout, - Stderr: stderr, - }) + return cmd. + WithDir(repo.Path). + WithStdin(bytes.NewReader(buffer.Bytes())). + WithStdout(stdout). + WithStderr(stderr). + Run(repo.Ctx) } // AddObjectToIndex adds the provided object hash to the index at the provided filename @@ -149,7 +149,7 @@ func (repo *Repository) AddObjectToIndex(mode string, object ObjectID, filename // WriteTree writes the current index as a tree to the object db and returns its hash func (repo *Repository) WriteTree() (*Tree, error) { - stdout, _, runErr := gitcmd.NewCommand("write-tree").RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, runErr := gitcmd.NewCommand("write-tree").WithDir(repo.Path).RunStdString(repo.Ctx) if runErr != nil { return nil, runErr } diff --git a/modules/git/repo_object.go b/modules/git/repo_object.go index e8f6510c237f9..2a39a3c4d813f 100644 --- a/modules/git/repo_object.go +++ b/modules/git/repo_object.go @@ -76,12 +76,12 @@ func (repo *Repository) hashObject(reader io.Reader, save bool) (string, error) } stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) - err := cmd.Run(repo.Ctx, &gitcmd.RunOpts{ - Dir: repo.Path, - Stdin: reader, - Stdout: stdout, - Stderr: stderr, - }) + err := cmd. + WithDir(repo.Path). + WithStdin(reader). + WithStdout(stdout). + WithStderr(stderr). + Run(repo.Ctx) if err != nil { return "", err } diff --git a/modules/git/repo_ref.go b/modules/git/repo_ref.go index 577e17c45df68..8859a93a578d9 100644 --- a/modules/git/repo_ref.go +++ b/modules/git/repo_ref.go @@ -28,7 +28,8 @@ func (repo *Repository) ListOccurrences(ctx context.Context, refType, commitSHA default: return nil, util.NewInvalidArgumentErrorf(`can only use "branch" or "tag" for refType, but got %q`, refType) } - stdout, _, err := cmd.AddArguments("--no-color", "--sort=-creatordate", "--contains").AddDynamicArguments(commitSHA).RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, err := cmd.AddArguments("--no-color", "--sort=-creatordate", "--contains"). + AddDynamicArguments(commitSHA).WithDir(repo.Path).RunStdString(ctx) if err != nil { return nil, err } diff --git a/modules/git/repo_ref_nogogit.go b/modules/git/repo_ref_nogogit.go index 784efecc65453..09bb0df7b80e1 100644 --- a/modules/git/repo_ref_nogogit.go +++ b/modules/git/repo_ref_nogogit.go @@ -23,11 +23,11 @@ func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) { go func() { stderrBuilder := &strings.Builder{} - err := gitcmd.NewCommand("for-each-ref").Run(repo.Ctx, &gitcmd.RunOpts{ - Dir: repo.Path, - Stdout: stdoutWriter, - Stderr: stderrBuilder, - }) + err := gitcmd.NewCommand("for-each-ref"). + WithDir(repo.Path). + WithStdout(stdoutWriter). + WithStderr(stderrBuilder). + Run(repo.Ctx) if err != nil { _ = stdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, stderrBuilder.String())) } else { diff --git a/modules/git/repo_stats.go b/modules/git/repo_stats.go index 22082325efb80..cfb35288fe819 100644 --- a/modules/git/repo_stats.go +++ b/modules/git/repo_stats.go @@ -43,7 +43,8 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string) stdout, _, runErr := gitcmd.NewCommand("rev-list", "--count", "--no-merges", "--branches=*", "--date=iso"). AddOptionFormat("--since=%s", since). - RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + WithDir(repo.Path). + RunStdString(repo.Ctx) if runErr != nil { return nil, runErr } @@ -72,12 +73,11 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string) } stderr := new(strings.Builder) - err = gitCmd.Run(repo.Ctx, &gitcmd.RunOpts{ - Env: []string{}, - Dir: repo.Path, - Stdout: stdoutWriter, - Stderr: stderr, - PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { + err = gitCmd. + WithDir(repo.Path). + WithStdout(stdoutWriter). + WithStderr(stderr). + WithPipelineFunc(func(ctx context.Context, cancel context.CancelFunc) error { _ = stdoutWriter.Close() scanner := bufio.NewScanner(stdoutReader) scanner.Split(bufio.ScanLines) @@ -145,8 +145,8 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string) stats.Authors = a _ = stdoutReader.Close() return nil - }, - }) + }). + Run(repo.Ctx) if err != nil { return nil, fmt.Errorf("Failed to get GetCodeActivityStats for repository.\nError: %w\nStderr: %s", err, stderr) } diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go index 0cb0932459022..4ad0c6e5abc9b 100644 --- a/modules/git/repo_tag.go +++ b/modules/git/repo_tag.go @@ -19,13 +19,17 @@ const TagPrefix = "refs/tags/" // CreateTag create one tag in the repository func (repo *Repository) CreateTag(name, revision string) error { - _, _, err := gitcmd.NewCommand("tag").AddDashesAndList(name, revision).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + _, _, err := gitcmd.NewCommand("tag").AddDashesAndList(name, revision).WithDir(repo.Path).RunStdString(repo.Ctx) return err } // CreateAnnotatedTag create one annotated tag in the repository func (repo *Repository) CreateAnnotatedTag(name, message, revision string) error { - _, _, err := gitcmd.NewCommand("tag", "-a", "-m").AddDynamicArguments(message).AddDashesAndList(name, revision).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + _, _, err := gitcmd.NewCommand("tag", "-a", "-m"). + AddDynamicArguments(message). + AddDashesAndList(name, revision). + WithDir(repo.Path). + RunStdString(repo.Ctx) return err } @@ -35,7 +39,7 @@ func (repo *Repository) GetTagNameBySHA(sha string) (string, error) { return "", fmt.Errorf("SHA is too short: %s", sha) } - stdout, _, err := gitcmd.NewCommand("show-ref", "--tags", "-d").RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, err := gitcmd.NewCommand("show-ref", "--tags", "-d").WithDir(repo.Path).RunStdString(repo.Ctx) if err != nil { return "", err } @@ -58,7 +62,7 @@ func (repo *Repository) GetTagNameBySHA(sha string) (string, error) { // GetTagID returns the object ID for a tag (annotated tags have both an object SHA AND a commit SHA) func (repo *Repository) GetTagID(name string) (string, error) { - stdout, _, err := gitcmd.NewCommand("show-ref", "--tags").AddDashesAndList(name).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + stdout, _, err := gitcmd.NewCommand("show-ref", "--tags").AddDashesAndList(name).WithDir(repo.Path).RunStdString(repo.Ctx) if err != nil { return "", err } @@ -115,12 +119,15 @@ func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) { defer stdoutReader.Close() defer stdoutWriter.Close() stderr := strings.Builder{} - rc := &gitcmd.RunOpts{Dir: repo.Path, Stdout: stdoutWriter, Stderr: &stderr} go func() { err := gitcmd.NewCommand("for-each-ref"). AddOptionFormat("--format=%s", forEachRefFmt.Flag()). - AddArguments("--sort", "-*creatordate", "refs/tags").Run(repo.Ctx, rc) + AddArguments("--sort", "-*creatordate", "refs/tags"). + WithDir(repo.Path). + WithStdout(stdoutWriter). + WithStderr(&stderr). + Run(repo.Ctx) if err != nil { _ = stdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, stderr.String())) } else { diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go index 1d8c9409518cf..669ca459ed1b0 100644 --- a/modules/git/repo_tree.go +++ b/modules/git/repo_tree.go @@ -60,13 +60,11 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) - err := cmd.Run(repo.Ctx, &gitcmd.RunOpts{ - Env: env, - Dir: repo.Path, - Stdin: messageBytes, - Stdout: stdout, - Stderr: stderr, - }) + err := cmd.WithEnv(env). + WithDir(repo.Path). + WithStdin(messageBytes). + WithStdout(stdout). + Run(repo.Ctx) if err != nil { return nil, gitcmd.ConcatenateError(err, stderr.String()) } diff --git a/modules/git/submodule.go b/modules/git/submodule.go index 58824adc82537..45059eae77695 100644 --- a/modules/git/submodule.go +++ b/modules/git/submodule.go @@ -25,10 +25,11 @@ func GetTemplateSubmoduleCommits(ctx context.Context, repoPath string) (submodul if err != nil { return nil, err } - opts := &gitcmd.RunOpts{ - Dir: repoPath, - Stdout: stdoutWriter, - PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { + + err = gitcmd.NewCommand("ls-tree", "-r", "--", "HEAD"). + WithDir(repoPath). + WithStdout(stdoutWriter). + WithPipelineFunc(func(ctx context.Context, cancel context.CancelFunc) error { _ = stdoutWriter.Close() defer stdoutReader.Close() @@ -44,9 +45,8 @@ func GetTemplateSubmoduleCommits(ctx context.Context, repoPath string) (submodul } } return scanner.Err() - }, - } - err = gitcmd.NewCommand("ls-tree", "-r", "--", "HEAD").Run(ctx, opts) + }). + Run(ctx) if err != nil { return nil, fmt.Errorf("GetTemplateSubmoduleCommits: error running git ls-tree: %v", err) } @@ -58,7 +58,7 @@ func GetTemplateSubmoduleCommits(ctx context.Context, repoPath string) (submodul func AddTemplateSubmoduleIndexes(ctx context.Context, repoPath string, submodules []TemplateSubmoduleCommit) error { for _, submodule := range submodules { cmd := gitcmd.NewCommand("update-index", "--add", "--cacheinfo", "160000").AddDynamicArguments(submodule.Commit, submodule.Path) - if stdout, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}); err != nil { + if stdout, _, err := cmd.WithDir(repoPath).RunStdString(ctx); err != nil { log.Error("Unable to add %s as submodule to repo %s: stdout %s\nError: %v", submodule.Path, repoPath, stdout, err) return err } diff --git a/modules/git/submodule_test.go b/modules/git/submodule_test.go index d2df8b2a91135..22bd5bf71e5f9 100644 --- a/modules/git/submodule_test.go +++ b/modules/git/submodule_test.go @@ -32,14 +32,14 @@ func TestAddTemplateSubmoduleIndexes(t *testing.T) { ctx := t.Context() tmpDir := t.TempDir() var err error - _, _, err = gitcmd.NewCommand("init").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmpDir}) + _, _, err = gitcmd.NewCommand("init").WithDir(tmpDir).RunStdString(ctx) require.NoError(t, err) _ = os.Mkdir(filepath.Join(tmpDir, "new-dir"), 0o755) err = AddTemplateSubmoduleIndexes(ctx, tmpDir, []TemplateSubmoduleCommit{{Path: "new-dir", Commit: "1234567890123456789012345678901234567890"}}) require.NoError(t, err) - _, _, err = gitcmd.NewCommand("add", "--all").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmpDir}) + _, _, err = gitcmd.NewCommand("add", "--all").WithDir(tmpDir).RunStdString(ctx) require.NoError(t, err) - _, _, err = gitcmd.NewCommand("-c", "user.name=a", "-c", "user.email=b", "commit", "-m=test").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmpDir}) + _, _, err = gitcmd.NewCommand("-c", "user.name=a", "-c", "user.email=b", "commit", "-m=test").WithDir(tmpDir).RunStdString(ctx) require.NoError(t, err) submodules, err := GetTemplateSubmoduleCommits(t.Context(), tmpDir) require.NoError(t, err) diff --git a/modules/git/tree.go b/modules/git/tree.go index a8c4929c7c9c3..9c73aec735e2a 100644 --- a/modules/git/tree.go +++ b/modules/git/tree.go @@ -53,7 +53,7 @@ func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error cmd := gitcmd.NewCommand("ls-tree", "-z", "--name-only"). AddDashesAndList(append([]string{ref}, filenames...)...) - res, _, err := cmd.RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + res, _, err := cmd.WithDir(repo.Path).RunStdBytes(repo.Ctx) if err != nil { return nil, err } @@ -69,7 +69,8 @@ func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error func (repo *Repository) GetTreePathLatestCommit(refName, treePath string) (*Commit, error) { stdout, _, err := gitcmd.NewCommand("rev-list", "-1"). AddDynamicArguments(refName).AddDashesAndList(treePath). - RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + WithDir(repo.Path). + RunStdString(repo.Ctx) if err != nil { return nil, err } diff --git a/modules/git/tree_nogogit.go b/modules/git/tree_nogogit.go index 045d78c42c64d..956a5938f0f27 100644 --- a/modules/git/tree_nogogit.go +++ b/modules/git/tree_nogogit.go @@ -72,7 +72,7 @@ func (t *Tree) ListEntries() (Entries, error) { } } - stdout, _, runErr := gitcmd.NewCommand("ls-tree", "-l").AddDynamicArguments(t.ID.String()).RunStdBytes(t.repo.Ctx, &gitcmd.RunOpts{Dir: t.repo.Path}) + stdout, _, runErr := gitcmd.NewCommand("ls-tree", "-l").AddDynamicArguments(t.ID.String()).WithDir(t.repo.Path).RunStdBytes(t.repo.Ctx) if runErr != nil { if strings.Contains(runErr.Error(), "fatal: Not a valid object name") || strings.Contains(runErr.Error(), "fatal: not a tree object") { return nil, ErrNotExist{ @@ -101,7 +101,8 @@ func (t *Tree) listEntriesRecursive(extraArgs gitcmd.TrustedCmdArgs) (Entries, e stdout, _, runErr := gitcmd.NewCommand("ls-tree", "-t", "-r"). AddArguments(extraArgs...). AddDynamicArguments(t.ID.String()). - RunStdBytes(t.repo.Ctx, &gitcmd.RunOpts{Dir: t.repo.Path}) + WithDir(t.repo.Path). + RunStdBytes(t.repo.Ctx) if runErr != nil { return nil, runErr } diff --git a/modules/gitrepo/command.go b/modules/gitrepo/command.go index 855645acf2b06..9dc111fb7ff68 100644 --- a/modules/gitrepo/command.go +++ b/modules/gitrepo/command.go @@ -9,25 +9,17 @@ import ( "code.gitea.io/gitea/modules/git/gitcmd" ) -type CmdOption struct { - Env []string +func RunCmd(ctx context.Context, repo Repository, cmd *gitcmd.Command) error { + return cmd.WithDir(repoPath(repo)). + WithLogSkipStep(1). + Run(ctx) } -func WithEnv(env []string) func(opt *CmdOption) { - return func(opt *CmdOption) { - opt.Env = env - } +func RunCmdString(ctx context.Context, repo Repository, cmd *gitcmd.Command) (string, error) { + res, _, err := cmd.WithDir(repoPath(repo)).WithLogSkipStep(1).RunStdString(ctx) + return res, err } -func RunCmdString(ctx context.Context, repo Repository, cmd *gitcmd.Command, opts ...func(opt *CmdOption)) (string, error) { - var opt CmdOption - for _, o := range opts { - o(&opt) - } - res, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{ - Dir: repoPath(repo), - Env: opt.Env, - LogSkip: 1, - }) - return res, err +func RunCmdBytes(ctx context.Context, repo Repository, cmd *gitcmd.Command) ([]byte, []byte, error) { + return cmd.WithDir(repoPath(repo)).WithLogSkipStep(1).RunStdBytes(ctx) } diff --git a/modules/gitrepo/compare.go b/modules/gitrepo/compare.go index 1c8f5421fa7bf..b8e4c30d6cfa3 100644 --- a/modules/gitrepo/compare.go +++ b/modules/gitrepo/compare.go @@ -22,7 +22,7 @@ type DivergeObject struct { func GetDivergingCommits(ctx context.Context, repo Repository, baseBranch, targetBranch string) (*DivergeObject, error) { cmd := gitcmd.NewCommand("rev-list", "--count", "--left-right"). AddDynamicArguments(baseBranch + "..." + targetBranch).AddArguments("--") - stdout, _, err1 := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)}) + stdout, err1 := RunCmdString(ctx, repo, cmd) if err1 != nil { return nil, err1 } diff --git a/modules/gitrepo/fsck.go b/modules/gitrepo/fsck.go index ffccff28a9dd1..f74ca3b46a42f 100644 --- a/modules/gitrepo/fsck.go +++ b/modules/gitrepo/fsck.go @@ -12,5 +12,5 @@ import ( // Fsck verifies the connectivity and validity of the objects in the database func Fsck(ctx context.Context, repo Repository, timeout time.Duration, args gitcmd.TrustedCmdArgs) error { - return gitcmd.NewCommand("fsck").AddArguments(args...).Run(ctx, &gitcmd.RunOpts{Timeout: timeout, Dir: repoPath(repo)}) + return RunCmd(ctx, repo, gitcmd.NewCommand("fsck").AddArguments(args...).WithTimeout(timeout)) } diff --git a/modules/gitrepo/ref.go b/modules/gitrepo/ref.go index babef8b65f047..5212528326875 100644 --- a/modules/gitrepo/ref.go +++ b/modules/gitrepo/ref.go @@ -10,12 +10,10 @@ import ( ) func UpdateRef(ctx context.Context, repo Repository, refName, newCommitID string) error { - _, _, err := gitcmd.NewCommand("update-ref").AddDynamicArguments(refName, newCommitID).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)}) - return err + return RunCmd(ctx, repo, gitcmd.NewCommand("update-ref").AddDynamicArguments(refName, newCommitID)) } func RemoveRef(ctx context.Context, repo Repository, refName string) error { - _, _, err := gitcmd.NewCommand("update-ref", "--no-deref", "-d"). - AddDynamicArguments(refName).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath(repo)}) - return err + return RunCmd(ctx, repo, gitcmd.NewCommand("update-ref", "--no-deref", "-d"). + AddDynamicArguments(refName)) } diff --git a/modules/gitrepo/remote.go b/modules/gitrepo/remote.go index 22b3a03ce2778..ce43988461637 100644 --- a/modules/gitrepo/remote.go +++ b/modules/gitrepo/remote.go @@ -63,22 +63,18 @@ func GitRemoteGetURL(ctx context.Context, repo Repository, remoteName string) (* // GitRemotePrune prunes the remote branches that no longer exist in the remote repository. func GitRemotePrune(ctx context.Context, repo Repository, remoteName string, timeout time.Duration, stdout, stderr io.Writer) error { - return gitcmd.NewCommand("remote", "prune").AddDynamicArguments(remoteName). - Run(ctx, &gitcmd.RunOpts{ - Timeout: timeout, - Dir: repoPath(repo), - Stdout: stdout, - Stderr: stderr, - }) + return RunCmd(ctx, repo, gitcmd.NewCommand("remote", "prune"). + AddDynamicArguments(remoteName). + WithTimeout(timeout). + WithStdout(stdout). + WithStderr(stderr)) } // GitRemoteUpdatePrune updates the remote branches and prunes the ones that no longer exist in the remote repository. func GitRemoteUpdatePrune(ctx context.Context, repo Repository, remoteName string, timeout time.Duration, stdout, stderr io.Writer) error { - return gitcmd.NewCommand("remote", "update", "--prune").AddDynamicArguments(remoteName). - Run(ctx, &gitcmd.RunOpts{ - Timeout: timeout, - Dir: repoPath(repo), - Stdout: stdout, - Stderr: stderr, - }) + return RunCmd(ctx, repo, gitcmd.NewCommand("remote", "update", "--prune"). + AddDynamicArguments(remoteName). + WithTimeout(timeout). + WithStdout(stdout). + WithStderr(stderr)) } diff --git a/modules/indexer/code/git.go b/modules/indexer/code/git.go index 93fa9e2256820..ca9c6a2974885 100644 --- a/modules/indexer/code/git.go +++ b/modules/indexer/code/git.go @@ -88,7 +88,7 @@ func parseGitLsTreeOutput(stdout []byte) ([]internal.FileUpdate, error) { // genesisChanges get changes to add repo to the indexer for the first time func genesisChanges(ctx context.Context, repo *repo_model.Repository, revision string) (*internal.RepoChanges, error) { var changes internal.RepoChanges - stdout, _, runErr := gitcmd.NewCommand("ls-tree", "--full-tree", "-l", "-r").AddDynamicArguments(revision).RunStdBytes(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()}) + stdout, _, runErr := gitrepo.RunCmdBytes(ctx, repo, gitcmd.NewCommand("ls-tree", "--full-tree", "-l", "-r").AddDynamicArguments(revision)) if runErr != nil { return nil, runErr } @@ -119,7 +119,7 @@ func nonGenesisChanges(ctx context.Context, repo *repo_model.Repository, revisio updateChanges := func() error { cmd := gitcmd.NewCommand("ls-tree", "--full-tree", "-l").AddDynamicArguments(revision). AddDashesAndList(updatedFilenames...) - lsTreeStdout, _, err := cmd.RunStdBytes(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()}) + lsTreeStdout, _, err := gitrepo.RunCmdBytes(ctx, repo, cmd) if err != nil { return err } diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index 5efb83738021e..9d69bbcedfaba 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -191,8 +191,9 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r if oldCommitID != objectFormat.EmptyObjectID().String() { output, err := gitrepo.RunCmdString(ctx, repo, - gitcmd.NewCommand("rev-list", "--max-count=1").AddDynamicArguments(oldCommitID, "^"+newCommitID), - gitrepo.WithEnv(ctx.env), + gitcmd.NewCommand("rev-list", "--max-count=1"). + AddDynamicArguments(oldCommitID, "^"+newCommitID). + WithEnv(ctx.env), ) if err != nil { log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err) diff --git a/routers/private/hook_verification.go b/routers/private/hook_verification.go index a69c3b04ebd90..9c357f4b41c6f 100644 --- a/routers/private/hook_verification.go +++ b/routers/private/hook_verification.go @@ -39,11 +39,10 @@ func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env [] command = gitcmd.NewCommand("rev-list").AddDynamicArguments(oldCommitID + "..." + newCommitID) } // This is safe as force pushes are already forbidden - err = command.Run(repo.Ctx, &gitcmd.RunOpts{ - Env: env, - Dir: repo.Path, - Stdout: stdoutWriter, - PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { + err = command.WithEnv(env). + WithDir(repo.Path). + WithStdout(stdoutWriter). + WithPipelineFunc(func(ctx context.Context, cancel context.CancelFunc) error { _ = stdoutWriter.Close() err := readAndVerifyCommitsFromShaReader(stdoutReader, repo, env) if err != nil { @@ -52,8 +51,8 @@ func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env [] } _ = stdoutReader.Close() return err - }, - }) + }). + Run(repo.Ctx) if err != nil && !isErrUnverifiedCommit(err) { log.Error("Unable to check commits from %s to %s in %s: %v", oldCommitID, newCommitID, repo.Path, err) } @@ -86,26 +85,25 @@ func readAndVerifyCommit(sha string, repo *git.Repository, env []string) error { commitID := git.MustIDFromString(sha) return gitcmd.NewCommand("cat-file", "commit").AddDynamicArguments(sha). - Run(repo.Ctx, &gitcmd.RunOpts{ - Env: env, - Dir: repo.Path, - Stdout: stdoutWriter, - PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { - _ = stdoutWriter.Close() - commit, err := git.CommitFromReader(repo, commitID, stdoutReader) - if err != nil { - return err - } - verification := asymkey_service.ParseCommitWithSignature(ctx, commit) - if !verification.Verified { - cancel() - return &errUnverifiedCommit{ - commit.ID.String(), - } + WithEnv(env). + WithDir(repo.Path). + WithStdout(stdoutWriter). + WithPipelineFunc(func(ctx context.Context, cancel context.CancelFunc) error { + _ = stdoutWriter.Close() + commit, err := git.CommitFromReader(repo, commitID, stdoutReader) + if err != nil { + return err + } + verification := asymkey_service.ParseCommitWithSignature(ctx, commit) + if !verification.Verified { + cancel() + return &errUnverifiedCommit{ + commit.ID.String(), } - return nil - }, - }) + } + return nil + }). + Run(repo.Ctx) } type errUnverifiedCommit struct { diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index ab649b6f15d86..40fef291d0c0a 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -316,7 +316,9 @@ func dummyInfoRefs(ctx *context.Context) { return } - refs, _, err := gitcmd.NewCommand("receive-pack", "--stateless-rpc", "--advertise-refs", ".").RunStdBytes(ctx, &gitcmd.RunOpts{Dir: tmpDir}) + refs, _, err := gitcmd.NewCommand("receive-pack", "--stateless-rpc", "--advertise-refs", "."). + WithDir(tmpDir). + RunStdBytes(ctx) if err != nil { log.Error(fmt.Sprintf("%v - %s", err, string(refs))) } @@ -448,15 +450,15 @@ func serviceRPC(ctx *context.Context, h *serviceHandler, service string) { } var stderr bytes.Buffer - cmd.AddArguments("--stateless-rpc").AddDynamicArguments(h.getRepoDir()) - if err := cmd.Run(ctx, &gitcmd.RunOpts{ - Dir: h.getRepoDir(), - Env: append(os.Environ(), h.environ...), - Stdout: ctx.Resp, - Stdin: reqBody, - Stderr: &stderr, - UseContextTimeout: true, - }); err != nil { + if err := cmd.AddArguments("--stateless-rpc"). + AddDynamicArguments(h.getRepoDir()). + WithDir(h.getRepoDir()). + WithEnv(append(os.Environ(), h.environ...)). + WithStderr(&stderr). + WithStdin(reqBody). + WithStdout(ctx.Resp). + WithUseContextTimeout(true). + Run(ctx); err != nil { if !git.IsErrCanceledOrKilled(err) { log.Error("Fail to serve RPC(%s) in %s: %v - %s", service, h.getRepoDir(), err, stderr.String()) } @@ -489,7 +491,7 @@ func getServiceType(ctx *context.Context) string { } func updateServerInfo(ctx gocontext.Context, dir string) []byte { - out, _, err := gitcmd.NewCommand("update-server-info").RunStdBytes(ctx, &gitcmd.RunOpts{Dir: dir}) + out, _, err := gitcmd.NewCommand("update-server-info").WithDir(dir).RunStdBytes(ctx) if err != nil { log.Error(fmt.Sprintf("%v - %s", err, string(out))) } @@ -519,7 +521,10 @@ func GetInfoRefs(ctx *context.Context) { } h.environ = append(os.Environ(), h.environ...) - refs, _, err := cmd.AddArguments("--stateless-rpc", "--advertise-refs", ".").RunStdBytes(ctx, &gitcmd.RunOpts{Env: h.environ, Dir: h.getRepoDir()}) + refs, _, err := cmd.AddArguments("--stateless-rpc", "--advertise-refs", "."). + WithEnv(h.environ). + WithDir(h.getRepoDir()). + RunStdBytes(ctx) if err != nil { log.Error(fmt.Sprintf("%v - %s", err, string(refs))) } diff --git a/services/asymkey/sign.go b/services/asymkey/sign.go index 4fde342bea5b2..61b9e56d954a2 100644 --- a/services/asymkey/sign.go +++ b/services/asymkey/sign.go @@ -117,16 +117,16 @@ func SigningKey(ctx context.Context, repoPath string) (*git.SigningKey, *git.Sig if setting.Repository.Signing.SigningKey == "default" || setting.Repository.Signing.SigningKey == "" { // Can ignore the error here as it means that commit.gpgsign is not set - value, _, _ := gitcmd.NewCommand("config", "--get", "commit.gpgsign").RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) + value, _, _ := gitcmd.NewCommand("config", "--get", "commit.gpgsign").WithDir(repoPath).RunStdString(ctx) sign, valid := git.ParseBool(strings.TrimSpace(value)) if !sign || !valid { return nil, nil } - format, _, _ := gitcmd.NewCommand("config", "--default", git.SigningKeyFormatOpenPGP, "--get", "gpg.format").RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) - signingKey, _, _ := gitcmd.NewCommand("config", "--get", "user.signingkey").RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) - signingName, _, _ := gitcmd.NewCommand("config", "--get", "user.name").RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) - signingEmail, _, _ := gitcmd.NewCommand("config", "--get", "user.email").RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}) + format, _, _ := gitcmd.NewCommand("config", "--default", git.SigningKeyFormatOpenPGP, "--get", "gpg.format").WithDir(repoPath).RunStdString(ctx) + signingKey, _, _ := gitcmd.NewCommand("config", "--get", "user.signingkey").WithDir(repoPath).RunStdString(ctx) + signingName, _, _ := gitcmd.NewCommand("config", "--get", "user.name").WithDir(repoPath).RunStdString(ctx) + signingEmail, _, _ := gitcmd.NewCommand("config", "--get", "user.email").WithDir(repoPath).RunStdString(ctx) if strings.TrimSpace(signingKey) == "" { return nil, nil diff --git a/services/doctor/heads.go b/services/doctor/heads.go index 5c2893ce88772..bdadfa674c191 100644 --- a/services/doctor/heads.go +++ b/services/doctor/heads.go @@ -50,7 +50,7 @@ func synchronizeRepoHeads(ctx context.Context, logger log.Logger, autofix bool) } // otherwise, let's try fixing HEAD - err := gitcmd.NewCommand("symbolic-ref").AddDashesAndList("HEAD", git.BranchPrefix+repo.DefaultBranch).Run(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()}) + err := gitrepo.RunCmd(ctx, repo, gitcmd.NewCommand("symbolic-ref").AddDashesAndList("HEAD", git.BranchPrefix+repo.DefaultBranch)) if err != nil { logger.Warn("Failed to fix HEAD for %s/%s: %v", repo.OwnerName, repo.Name, err) return nil diff --git a/services/gitdiff/git_diff_tree.go b/services/gitdiff/git_diff_tree.go index eeb354c1b6edb..4649c24af311b 100644 --- a/services/gitdiff/git_diff_tree.go +++ b/services/gitdiff/git_diff_tree.go @@ -61,7 +61,7 @@ func runGitDiffTree(ctx context.Context, gitRepo *git.Repository, useMergeBase b cmd.AddArguments("--merge-base") } cmd.AddDynamicArguments(baseCommitID, headCommitID) - stdout, _, runErr := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: gitRepo.Path}) + stdout, _, runErr := cmd.WithDir(gitRepo.Path).RunStdString(ctx) if runErr != nil { log.Warn("git diff-tree: %v", runErr) return nil, runErr diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index 1cc5ce66f838f..dfe071f0cfdc4 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -1161,12 +1161,11 @@ func getDiffBasic(ctx context.Context, gitRepo *git.Repository, opts *DiffOption go func() { stderr := &bytes.Buffer{} - if err := cmdDiff.Run(cmdCtx, &gitcmd.RunOpts{ - Timeout: time.Duration(setting.Git.Timeout.Default) * time.Second, - Dir: repoPath, - Stdout: writer, - Stderr: stderr, - }); err != nil && !git.IsErrCanceledOrKilled(err) { + if err := cmdDiff.WithTimeout(time.Duration(setting.Git.Timeout.Default) * time.Second). + WithDir(repoPath). + WithStdout(writer). + WithStderr(stderr). + Run(cmdCtx); err != nil && !git.IsErrCanceledOrKilled(err) { log.Error("error during GetDiff(git diff dir: %s): %v, stderr: %s", repoPath, err, stderr.String()) } diff --git a/services/migrations/dump.go b/services/migrations/dump.go index 385759800e45b..f9309e5e6a2ea 100644 --- a/services/migrations/dump.go +++ b/services/migrations/dump.go @@ -489,7 +489,7 @@ func (g *RepositoryDumper) handlePullRequest(ctx context.Context, pr *base.PullR if pr.Head.CloneURL == "" || pr.Head.Ref == "" { // Set head information if pr.Head.SHA is available if pr.Head.SHA != "" { - _, _, err = gitcmd.NewCommand("update-ref", "--no-deref").AddDynamicArguments(pr.GetGitHeadRefName(), pr.Head.SHA).RunStdString(ctx, &gitcmd.RunOpts{Dir: g.gitPath()}) + _, _, err = gitcmd.NewCommand("update-ref", "--no-deref").AddDynamicArguments(pr.GetGitHeadRefName(), pr.Head.SHA).WithDir(g.gitPath()).RunStdString(ctx) if err != nil { log.Error("PR #%d in %s/%s unable to update-ref for pr HEAD: %v", pr.Number, g.repoOwner, g.repoName, err) } @@ -519,7 +519,7 @@ func (g *RepositoryDumper) handlePullRequest(ctx context.Context, pr *base.PullR if !ok { // Set head information if pr.Head.SHA is available if pr.Head.SHA != "" { - _, _, err = gitcmd.NewCommand("update-ref", "--no-deref").AddDynamicArguments(pr.GetGitHeadRefName(), pr.Head.SHA).RunStdString(ctx, &gitcmd.RunOpts{Dir: g.gitPath()}) + _, _, err = gitcmd.NewCommand("update-ref", "--no-deref").AddDynamicArguments(pr.GetGitHeadRefName(), pr.Head.SHA).WithDir(g.gitPath()).RunStdString(ctx) if err != nil { log.Error("PR #%d in %s/%s unable to update-ref for pr HEAD: %v", pr.Number, g.repoOwner, g.repoName, err) } @@ -554,7 +554,7 @@ func (g *RepositoryDumper) handlePullRequest(ctx context.Context, pr *base.PullR fetchArg = git.BranchPrefix + fetchArg } - _, _, err = gitcmd.NewCommand("fetch", "--no-tags").AddDashesAndList(remote, fetchArg).RunStdString(ctx, &gitcmd.RunOpts{Dir: g.gitPath()}) + _, _, err = gitcmd.NewCommand("fetch", "--no-tags").AddDashesAndList(remote, fetchArg).WithDir(g.gitPath()).RunStdString(ctx) if err != nil { log.Error("Fetch branch from %s failed: %v", pr.Head.CloneURL, err) // We need to continue here so that the Head.Ref is reset and we attempt to set the gitref for the PR @@ -578,7 +578,7 @@ func (g *RepositoryDumper) handlePullRequest(ctx context.Context, pr *base.PullR pr.Head.SHA = headSha } if pr.Head.SHA != "" { - _, _, err = gitcmd.NewCommand("update-ref", "--no-deref").AddDynamicArguments(pr.GetGitHeadRefName(), pr.Head.SHA).RunStdString(ctx, &gitcmd.RunOpts{Dir: g.gitPath()}) + _, _, err = gitcmd.NewCommand("update-ref", "--no-deref").AddDynamicArguments(pr.GetGitHeadRefName(), pr.Head.SHA).WithDir(g.gitPath()).RunStdString(ctx) if err != nil { log.Error("unable to set %s as the local head for PR #%d from %s in %s/%s. Error: %v", pr.Head.SHA, pr.Number, pr.Head.Ref, g.repoOwner, g.repoName, err) } diff --git a/services/migrations/gitea_uploader_test.go b/services/migrations/gitea_uploader_test.go index 08efef5de6d28..477b68e37e089 100644 --- a/services/migrations/gitea_uploader_test.go +++ b/services/migrations/gitea_uploader_test.go @@ -239,7 +239,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { baseRef := "master" // this is very different from the real situation. It should be a bare repository for all the Gitea managed repositories assert.NoError(t, git.InitRepository(t.Context(), fromRepo.RepoPath(), false, fromRepo.ObjectFormatName)) - err := gitcmd.NewCommand("symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseRef).Run(t.Context(), &gitcmd.RunOpts{Dir: fromRepo.RepoPath()}) + err := gitrepo.RunCmd(t.Context(), fromRepo, gitcmd.NewCommand("symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseRef)) assert.NoError(t, err) assert.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte("# Testing Repository\n\nOriginally created in: "+fromRepo.RepoPath()), 0o644)) assert.NoError(t, git.AddChanges(t.Context(), fromRepo.RepoPath(), true)) @@ -263,7 +263,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { // fromRepo branch1 // headRef := "branch1" - _, _, err = gitcmd.NewCommand("checkout", "-b").AddDynamicArguments(headRef).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: fromRepo.RepoPath()}) + _, err = gitrepo.RunCmdString(t.Context(), fromRepo, gitcmd.NewCommand("checkout", "-b").AddDynamicArguments(headRef)) assert.NoError(t, err) assert.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte("SOMETHING"), 0o644)) assert.NoError(t, git.AddChanges(t.Context(), fromRepo.RepoPath(), true)) @@ -287,7 +287,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { assert.NoError(t, git.Clone(t.Context(), fromRepo.RepoPath(), forkRepo.RepoPath(), git.CloneRepoOptions{ Branch: headRef, })) - _, _, err = gitcmd.NewCommand("checkout", "-b").AddDynamicArguments(forkHeadRef).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: forkRepo.RepoPath()}) + _, err = gitrepo.RunCmdString(t.Context(), forkRepo, gitcmd.NewCommand("checkout", "-b").AddDynamicArguments(forkHeadRef)) assert.NoError(t, err) assert.NoError(t, os.WriteFile(filepath.Join(forkRepo.RepoPath(), "README.md"), []byte("# branch2 "+forkRepo.RepoPath()), 0o644)) assert.NoError(t, git.AddChanges(t.Context(), forkRepo.RepoPath(), true)) diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index 0013e60f6c737..4b19112d3c794 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -272,13 +272,10 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo stdoutBuilder := strings.Builder{} stderrBuilder := strings.Builder{} - if err := cmd.Run(ctx, &gitcmd.RunOpts{ - Timeout: timeout, - Dir: repoPath, - Env: envs, - Stdout: &stdoutBuilder, - Stderr: &stderrBuilder, - }); err != nil { + if err := gitrepo.RunCmd(ctx, m.Repo, cmd.WithTimeout(timeout). + WithEnv(envs). + WithStdout(&stdoutBuilder). + WithStderr(&stderrBuilder)); err != nil { stdout := stdoutBuilder.String() stderr := stderrBuilder.String() @@ -297,12 +294,9 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo // Successful prune - reattempt mirror stderrBuilder.Reset() stdoutBuilder.Reset() - if err = cmd.Run(ctx, &gitcmd.RunOpts{ - Timeout: timeout, - Dir: repoPath, - Stdout: &stdoutBuilder, - Stderr: &stderrBuilder, - }); err != nil { + if err = gitrepo.RunCmd(ctx, m.Repo, cmd.WithTimeout(timeout). + WithStdout(&stdoutBuilder). + WithStderr(&stderrBuilder)); err != nil { stdout := stdoutBuilder.String() stderr := stderrBuilder.String() diff --git a/services/pull/merge.go b/services/pull/merge.go index 4d6afb8d3385e..8acafe476096a 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -406,7 +406,7 @@ func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *use // Push back to upstream. // This cause an api call to "/api/internal/hook/post-receive/...", // If it's merge, all db transaction and operations should be there but not here to prevent deadlock. - if err := pushCmd.Run(ctx, mergeCtx.RunOpts()); err != nil { + if err := mergeCtx.WithCmd(pushCmd).Run(ctx); err != nil { if strings.Contains(mergeCtx.errbuf.String(), "non-fast-forward") { return "", &git.ErrPushOutOfDate{ StdOut: mergeCtx.outbuf.String(), @@ -440,7 +440,7 @@ func commitAndSignNoAuthor(ctx *mergeContext, message string) error { } cmdCommit.AddOptionFormat("-S%s", ctx.signKey.KeyID) } - if err := cmdCommit.Run(ctx, ctx.RunOpts()); err != nil { + if err := ctx.WithCmd(cmdCommit).Run(ctx); err != nil { log.Error("git commit %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) return fmt.Errorf("git commit %v: %w\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) } @@ -501,7 +501,7 @@ func (err ErrMergeDivergingFastForwardOnly) Error() string { } func runMergeCommand(ctx *mergeContext, mergeStyle repo_model.MergeStyle, cmd *gitcmd.Command) error { - if err := cmd.Run(ctx, ctx.RunOpts()); err != nil { + if err := ctx.WithCmd(cmd).Run(ctx); err != nil { // Merge will leave a MERGE_HEAD file in the .git folder if there is a conflict if _, statErr := os.Stat(filepath.Join(ctx.tmpBasePath, ".git", "MERGE_HEAD")); statErr == nil { // We have a merge conflict error diff --git a/services/pull/merge_prepare.go b/services/pull/merge_prepare.go index d7152560d21c9..9785bc91eaefd 100644 --- a/services/pull/merge_prepare.go +++ b/services/pull/merge_prepare.go @@ -32,15 +32,14 @@ type mergeContext struct { env []string } -func (ctx *mergeContext) RunOpts() *gitcmd.RunOpts { +func (ctx *mergeContext) WithCmd(cmd *gitcmd.Command) *gitcmd.Command { ctx.outbuf.Reset() ctx.errbuf.Reset() - return &gitcmd.RunOpts{ - Env: ctx.env, - Dir: ctx.tmpBasePath, - Stdout: ctx.outbuf, - Stderr: ctx.errbuf, - } + return cmd.WithEnv(ctx.env). + WithDir(ctx.tmpBasePath). + WithLogSkipStep(1). + WithStdout(ctx.outbuf). + WithStderr(ctx.errbuf) } // ErrSHADoesNotMatch represents a "SHADoesNotMatch" kind of error. @@ -74,7 +73,7 @@ func createTemporaryRepoForMerge(ctx context.Context, pr *issues_model.PullReque } if expectedHeadCommitID != "" { - trackingCommitID, _, err := gitcmd.NewCommand("show-ref", "--hash").AddDynamicArguments(git.BranchPrefix+trackingBranch).RunStdString(ctx, &gitcmd.RunOpts{Dir: mergeCtx.tmpBasePath}) + trackingCommitID, _, err := mergeCtx.WithCmd(gitcmd.NewCommand("show-ref", "--hash").AddDynamicArguments(git.BranchPrefix + trackingBranch)).RunStdString(ctx) if err != nil { defer cancel() log.Error("failed to get sha of head branch in %-v: show-ref[%s] --hash refs/heads/tracking: %v", mergeCtx.pr, mergeCtx.tmpBasePath, err) @@ -152,8 +151,8 @@ func prepareTemporaryRepoForMerge(ctx *mergeContext) error { } setConfig := func(key, value string) error { - if err := gitcmd.NewCommand("config", "--local").AddDynamicArguments(key, value). - Run(ctx, ctx.RunOpts()); err != nil { + if err := ctx.WithCmd(gitcmd.NewCommand("config", "--local").AddDynamicArguments(key, value)). + Run(ctx); err != nil { log.Error("git config [%s -> %q]: %v\n%s\n%s", key, value, err, ctx.outbuf.String(), ctx.errbuf.String()) return fmt.Errorf("git config [%s -> %q]: %w\n%s\n%s", key, value, err, ctx.outbuf.String(), ctx.errbuf.String()) } @@ -185,8 +184,8 @@ func prepareTemporaryRepoForMerge(ctx *mergeContext) error { } // Read base branch index - if err := gitcmd.NewCommand("read-tree", "HEAD"). - Run(ctx, ctx.RunOpts()); err != nil { + if err := ctx.WithCmd(gitcmd.NewCommand("read-tree", "HEAD")). + Run(ctx); err != nil { log.Error("git read-tree HEAD: %v\n%s\n%s", err, ctx.outbuf.String(), ctx.errbuf.String()) return fmt.Errorf("Unable to read base branch in to the index: %w\n%s\n%s", err, ctx.outbuf.String(), ctx.errbuf.String()) } @@ -222,31 +221,31 @@ func getDiffTree(ctx context.Context, repoPath, baseBranch, headBranch string, o return 0, nil, nil } - err = gitcmd.NewCommand("diff-tree", "--no-commit-id", "--name-only", "-r", "-r", "-z", "--root").AddDynamicArguments(baseBranch, headBranch). - Run(ctx, &gitcmd.RunOpts{ - Dir: repoPath, - Stdout: diffOutWriter, - PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { - // Close the writer end of the pipe to begin processing - _ = diffOutWriter.Close() - defer func() { - // Close the reader on return to terminate the git command if necessary - _ = diffOutReader.Close() - }() - - // Now scan the output from the command - scanner := bufio.NewScanner(diffOutReader) - scanner.Split(scanNullTerminatedStrings) - for scanner.Scan() { - filepath := scanner.Text() - // escape '*', '?', '[', spaces and '!' prefix - filepath = escapedSymbols.ReplaceAllString(filepath, `\$1`) - // no necessary to escape the first '#' symbol because the first symbol is '/' - fmt.Fprintf(out, "/%s\n", filepath) - } - return scanner.Err() - }, - }) + err = gitcmd.NewCommand("diff-tree", "--no-commit-id", "--name-only", "-r", "-r", "-z", "--root"). + AddDynamicArguments(baseBranch, headBranch). + WithDir(repoPath). + WithStdout(diffOutWriter). + WithPipelineFunc(func(ctx context.Context, cancel context.CancelFunc) error { + // Close the writer end of the pipe to begin processing + _ = diffOutWriter.Close() + defer func() { + // Close the reader on return to terminate the git command if necessary + _ = diffOutReader.Close() + }() + + // Now scan the output from the command + scanner := bufio.NewScanner(diffOutReader) + scanner.Split(scanNullTerminatedStrings) + for scanner.Scan() { + filepath := scanner.Text() + // escape '*', '?', '[', spaces and '!' prefix + filepath = escapedSymbols.ReplaceAllString(filepath, `\$1`) + // no necessary to escape the first '#' symbol because the first symbol is '/' + fmt.Fprintf(out, "/%s\n", filepath) + } + return scanner.Err() + }). + Run(ctx) return err } @@ -273,16 +272,16 @@ func (err ErrRebaseConflicts) Error() string { // if there is a conflict it will return an ErrRebaseConflicts func rebaseTrackingOnToBase(ctx *mergeContext, mergeStyle repo_model.MergeStyle) error { // Checkout head branch - if err := gitcmd.NewCommand("checkout", "-b").AddDynamicArguments(stagingBranch, trackingBranch). - Run(ctx, ctx.RunOpts()); err != nil { + if err := ctx.WithCmd(gitcmd.NewCommand("checkout", "-b").AddDynamicArguments(stagingBranch, trackingBranch)). + Run(ctx); err != nil { return fmt.Errorf("unable to git checkout tracking as staging in temp repo for %v: %w\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) } ctx.outbuf.Reset() ctx.errbuf.Reset() // Rebase before merging - if err := gitcmd.NewCommand("rebase").AddDynamicArguments(baseBranch). - Run(ctx, ctx.RunOpts()); err != nil { + if err := ctx.WithCmd(gitcmd.NewCommand("rebase").AddDynamicArguments(baseBranch)). + Run(ctx); err != nil { // Rebase will leave a REBASE_HEAD file in .git if there is a conflict if _, statErr := os.Stat(filepath.Join(ctx.tmpBasePath, ".git", "REBASE_HEAD")); statErr == nil { var commitSha string diff --git a/services/pull/merge_rebase.go b/services/pull/merge_rebase.go index 1d7395ebca166..7e0b74c93b033 100644 --- a/services/pull/merge_rebase.go +++ b/services/pull/merge_rebase.go @@ -17,7 +17,7 @@ import ( // getRebaseAmendMessage composes the message to amend commits in rebase merge of a pull request. func getRebaseAmendMessage(ctx *mergeContext, baseGitRepo *git.Repository) (message string, err error) { // Get existing commit message. - commitMessage, _, err := gitcmd.NewCommand("show", "--format=%B", "-s").RunStdString(ctx, &gitcmd.RunOpts{Dir: ctx.tmpBasePath}) + commitMessage, _, err := gitcmd.NewCommand("show", "--format=%B", "-s").WithDir(ctx.tmpBasePath).RunStdString(ctx) if err != nil { return "", err } @@ -74,7 +74,10 @@ func doMergeRebaseFastForward(ctx *mergeContext) error { } if newMessage != "" { - if err := gitcmd.NewCommand("commit", "--amend").AddOptionFormat("--message=%s", newMessage).Run(ctx, &gitcmd.RunOpts{Dir: ctx.tmpBasePath}); err != nil { + if err := gitcmd.NewCommand("commit", "--amend"). + AddOptionFormat("--message=%s", newMessage). + WithDir(ctx.tmpBasePath). + Run(ctx); err != nil { log.Error("Unable to amend commit message: %v", err) return err } @@ -106,8 +109,8 @@ func doMergeStyleRebase(ctx *mergeContext, mergeStyle repo_model.MergeStyle, mes } // Checkout base branch again - if err := gitcmd.NewCommand("checkout").AddDynamicArguments(baseBranch). - Run(ctx, ctx.RunOpts()); err != nil { + if err := ctx.WithCmd(gitcmd.NewCommand("checkout").AddDynamicArguments(baseBranch)). + Run(ctx); err != nil { log.Error("git checkout base prior to merge post staging rebase %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) return fmt.Errorf("git checkout base prior to merge post staging rebase %v: %w\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) } diff --git a/services/pull/merge_squash.go b/services/pull/merge_squash.go index 99c4311e045e4..426d7e1f39511 100644 --- a/services/pull/merge_squash.go +++ b/services/pull/merge_squash.go @@ -80,7 +80,7 @@ func doMergeStyleSquash(ctx *mergeContext, message string) error { } cmdCommit.AddOptionFormat("-S%s", ctx.signKey.KeyID) } - if err := cmdCommit.Run(ctx, ctx.RunOpts()); err != nil { + if err := ctx.WithCmd(cmdCommit).Run(ctx); err != nil { log.Error("git commit %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) return fmt.Errorf("git commit [%s:%s -> %s:%s]: %w\n%s\n%s", ctx.pr.HeadRepo.FullName(), ctx.pr.HeadBranch, ctx.pr.BaseRepo.FullName(), ctx.pr.BaseBranch, err, ctx.outbuf.String(), ctx.errbuf.String()) } diff --git a/services/pull/patch.go b/services/pull/patch.go index 674ae01253505..d82fe3e2251cd 100644 --- a/services/pull/patch.go +++ b/services/pull/patch.go @@ -91,7 +91,7 @@ func testPullRequestTmpRepoBranchMergeable(ctx context.Context, prCtx *prTmpRepo defer gitRepo.Close() // 1. update merge base - pr.MergeBase, _, err = gitcmd.NewCommand("merge-base", "--", "base", "tracking").RunStdString(ctx, &gitcmd.RunOpts{Dir: prCtx.tmpBasePath}) + pr.MergeBase, _, err = gitcmd.NewCommand("merge-base", "--", "base", "tracking").WithDir(prCtx.tmpBasePath).RunStdString(ctx) if err != nil { var err2 error pr.MergeBase, err2 = gitRepo.GetRefCommitID(git.BranchPrefix + "base") @@ -191,7 +191,7 @@ func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, f } // Need to get the objects from the object db to attempt to merge - root, _, err := gitcmd.NewCommand("unpack-file").AddDynamicArguments(file.stage1.sha).RunStdString(ctx, &gitcmd.RunOpts{Dir: tmpBasePath}) + root, _, err := gitcmd.NewCommand("unpack-file").AddDynamicArguments(file.stage1.sha).WithDir(tmpBasePath).RunStdString(ctx) if err != nil { return fmt.Errorf("unable to get root object: %s at path: %s for merging. Error: %w", file.stage1.sha, file.stage1.path, err) } @@ -200,7 +200,7 @@ func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, f _ = util.Remove(filepath.Join(tmpBasePath, root)) }() - base, _, err := gitcmd.NewCommand("unpack-file").AddDynamicArguments(file.stage2.sha).RunStdString(ctx, &gitcmd.RunOpts{Dir: tmpBasePath}) + base, _, err := gitcmd.NewCommand("unpack-file").AddDynamicArguments(file.stage2.sha).WithDir(tmpBasePath).RunStdString(ctx) if err != nil { return fmt.Errorf("unable to get base object: %s at path: %s for merging. Error: %w", file.stage2.sha, file.stage2.path, err) } @@ -208,7 +208,7 @@ func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, f defer func() { _ = util.Remove(base) }() - head, _, err := gitcmd.NewCommand("unpack-file").AddDynamicArguments(file.stage3.sha).RunStdString(ctx, &gitcmd.RunOpts{Dir: tmpBasePath}) + head, _, err := gitcmd.NewCommand("unpack-file").AddDynamicArguments(file.stage3.sha).WithDir(tmpBasePath).RunStdString(ctx) if err != nil { return fmt.Errorf("unable to get head object:%s at path: %s for merging. Error: %w", file.stage3.sha, file.stage3.path, err) } @@ -218,13 +218,13 @@ func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, f }() // now git merge-file annoyingly takes a different order to the merge-tree ... - _, _, conflictErr := gitcmd.NewCommand("merge-file").AddDynamicArguments(base, root, head).RunStdString(ctx, &gitcmd.RunOpts{Dir: tmpBasePath}) + _, _, conflictErr := gitcmd.NewCommand("merge-file").AddDynamicArguments(base, root, head).WithDir(tmpBasePath).RunStdString(ctx) if conflictErr != nil { return &errMergeConflict{file.stage2.path} } // base now contains the merged data - hash, _, err := gitcmd.NewCommand("hash-object", "-w", "--path").AddDynamicArguments(file.stage2.path, base).RunStdString(ctx, &gitcmd.RunOpts{Dir: tmpBasePath}) + hash, _, err := gitcmd.NewCommand("hash-object", "-w", "--path").AddDynamicArguments(file.stage2.path, base).WithDir(tmpBasePath).RunStdString(ctx) if err != nil { return err } @@ -249,7 +249,7 @@ func AttemptThreeWayMerge(ctx context.Context, gitPath string, gitRepo *git.Repo defer cancel() // First we use read-tree to do a simple three-way merge - if _, _, err := gitcmd.NewCommand("read-tree", "-m").AddDynamicArguments(base, ours, theirs).RunStdString(ctx, &gitcmd.RunOpts{Dir: gitPath}); err != nil { + if _, _, err := gitcmd.NewCommand("read-tree", "-m").AddDynamicArguments(base, ours, theirs).WithDir(gitPath).RunStdString(ctx); err != nil { log.Error("Unable to run read-tree -m! Error: %v", err) return false, nil, fmt.Errorf("unable to run read-tree -m! Error: %w", err) } @@ -323,9 +323,9 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * // No conflicts detected so we need to check if the patch is empty... // a. Write the newly merged tree and check the new tree-hash var treeHash string - treeHash, _, err = gitcmd.NewCommand("write-tree").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmpBasePath}) + treeHash, _, err = gitcmd.NewCommand("write-tree").WithDir(tmpBasePath).RunStdString(ctx) if err != nil { - lsfiles, _, _ := gitcmd.NewCommand("ls-files", "-u").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmpBasePath}) + lsfiles, _, _ := gitcmd.NewCommand("ls-files", "-u").WithDir(tmpBasePath).RunStdString(ctx) return false, fmt.Errorf("unable to write unconflicted tree: %w\n`git ls-files -u`:\n%s", err, lsfiles) } treeHash = strings.TrimSpace(treeHash) @@ -382,7 +382,7 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * log.Trace("PullRequest[%d].testPullRequestTmpRepoBranchMergeable (patchPath): %s", pr.ID, patchPath) // 4. Read the base branch in to the index of the temporary repository - _, _, err = gitcmd.NewCommand("read-tree", "base").RunStdString(gitRepo.Ctx, &gitcmd.RunOpts{Dir: tmpBasePath}) + _, _, err = gitcmd.NewCommand("read-tree", "base").WithDir(tmpBasePath).RunStdString(ctx) if err != nil { return false, fmt.Errorf("git read-tree %s: %w", pr.BaseBranch, err) } @@ -426,10 +426,10 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * // 8. Run the check command conflict = false - err = cmdApply.Run(gitRepo.Ctx, &gitcmd.RunOpts{ - Dir: tmpBasePath, - Stderr: stderrWriter, - PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { + err = cmdApply. + WithDir(tmpBasePath). + WithStderr(stderrWriter). + WithPipelineFunc(func(ctx context.Context, cancel context.CancelFunc) error { // Close the writer end of the pipe to begin processing _ = stderrWriter.Close() defer func() { @@ -488,8 +488,8 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * } return nil - }, - }) + }). + Run(gitRepo.Ctx) // 9. Check if the found conflictedfiles is non-zero, "err" could be non-nil, so we should ignore it if we found conflicts. // Note: `"err" could be non-nil` is due that if enable 3-way merge, it doesn't return any error on found conflicts. diff --git a/services/pull/patch_unmerged.go b/services/pull/patch_unmerged.go index 91640354807fa..049168031398f 100644 --- a/services/pull/patch_unmerged.go +++ b/services/pull/patch_unmerged.go @@ -73,48 +73,47 @@ func readUnmergedLsFileLines(ctx context.Context, tmpBasePath string, outputChan stderr := &strings.Builder{} err = gitcmd.NewCommand("ls-files", "-u", "-z"). - Run(ctx, &gitcmd.RunOpts{ - Dir: tmpBasePath, - Stdout: lsFilesWriter, - Stderr: stderr, - PipelineFunc: func(_ context.Context, _ context.CancelFunc) error { - _ = lsFilesWriter.Close() - defer func() { - _ = lsFilesReader.Close() - }() - bufferedReader := bufio.NewReader(lsFilesReader) - - for { - line, err := bufferedReader.ReadString('\000') - if err != nil { - if err == io.EOF { - return nil - } - return err + WithDir(tmpBasePath). + WithStdout(lsFilesWriter). + WithStderr(stderr). + WithPipelineFunc(func(_ context.Context, _ context.CancelFunc) error { + _ = lsFilesWriter.Close() + defer func() { + _ = lsFilesReader.Close() + }() + bufferedReader := bufio.NewReader(lsFilesReader) + + for { + line, err := bufferedReader.ReadString('\000') + if err != nil { + if err == io.EOF { + return nil } - toemit := &lsFileLine{} - - split := strings.SplitN(line, " ", 3) - if len(split) < 3 { - return fmt.Errorf("malformed line: %s", line) - } - toemit.mode = split[0] - toemit.sha = split[1] + return err + } + toemit := &lsFileLine{} - if len(split[2]) < 4 { - return fmt.Errorf("malformed line: %s", line) - } + split := strings.SplitN(line, " ", 3) + if len(split) < 3 { + return fmt.Errorf("malformed line: %s", line) + } + toemit.mode = split[0] + toemit.sha = split[1] - toemit.stage, err = strconv.Atoi(split[2][0:1]) - if err != nil { - return fmt.Errorf("malformed line: %s", line) - } + if len(split[2]) < 4 { + return fmt.Errorf("malformed line: %s", line) + } - toemit.path = split[2][2 : len(split[2])-1] - outputChan <- toemit + toemit.stage, err = strconv.Atoi(split[2][0:1]) + if err != nil { + return fmt.Errorf("malformed line: %s", line) } - }, - }) + + toemit.path = split[2][2 : len(split[2])-1] + outputChan <- toemit + } + }). + Run(ctx) if err != nil { outputChan <- &lsFileLine{err: fmt.Errorf("git ls-files -u -z: %w", gitcmd.ConcatenateError(err, stderr.String()))} } diff --git a/services/pull/pull.go b/services/pull/pull.go index 7bf13733b2727..bbd0cd7ecd654 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -505,18 +505,17 @@ func checkIfPRContentChanged(ctx context.Context, pr *issues_model.PullRequest, } stderr := new(bytes.Buffer) - if err := cmd.Run(ctx, &gitcmd.RunOpts{ - Dir: prCtx.tmpBasePath, - Stdout: stdoutWriter, - Stderr: stderr, - PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { + if err := cmd.WithDir(prCtx.tmpBasePath). + WithStdout(stdoutWriter). + WithStderr(stderr). + WithPipelineFunc(func(ctx context.Context, cancel context.CancelFunc) error { _ = stdoutWriter.Close() defer func() { _ = stdoutReader.Close() }() return util.IsEmptyReader(stdoutReader) - }, - }); err != nil { + }). + Run(ctx); err != nil { if err == util.ErrNotEmpty { return true, nil } diff --git a/services/pull/temp_repo.go b/services/pull/temp_repo.go index 8750b4288a429..04a4a817905d7 100644 --- a/services/pull/temp_repo.go +++ b/services/pull/temp_repo.go @@ -37,14 +37,12 @@ type prTmpRepoContext struct { errbuf *strings.Builder // any use should be preceded by a Reset and preferably after use } -func (ctx *prTmpRepoContext) RunOpts() *gitcmd.RunOpts { +func (ctx *prTmpRepoContext) WithCmd(cmd *gitcmd.Command) *gitcmd.Command { ctx.outbuf.Reset() ctx.errbuf.Reset() - return &gitcmd.RunOpts{ - Dir: ctx.tmpBasePath, - Stdout: ctx.outbuf, - Stderr: ctx.errbuf, - } + return cmd.WithDir(ctx.tmpBasePath). + WithStdout(ctx.outbuf). + WithStderr(ctx.errbuf) } // createTemporaryRepoForPR creates a temporary repo with "base" for pr.BaseBranch and "tracking" for pr.HeadBranch @@ -132,22 +130,22 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) return nil, nil, fmt.Errorf("Unable to add base repository to temporary repo [%s -> tmpBasePath]: %w", pr.BaseRepo.FullName(), err) } - if err := gitcmd.NewCommand("remote", "add", "-t").AddDynamicArguments(pr.BaseBranch).AddArguments("-m").AddDynamicArguments(pr.BaseBranch).AddDynamicArguments("origin", baseRepoPath). - Run(ctx, prCtx.RunOpts()); err != nil { + if err := prCtx.WithCmd(gitcmd.NewCommand("remote", "add", "-t").AddDynamicArguments(pr.BaseBranch).AddArguments("-m").AddDynamicArguments(pr.BaseBranch).AddDynamicArguments("origin", baseRepoPath)). + Run(ctx); err != nil { 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()) cancel() 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()) } - if err := gitcmd.NewCommand("fetch", "origin").AddArguments(fetchArgs...).AddDashesAndList(pr.BaseBranch+":"+baseBranch, pr.BaseBranch+":original_"+baseBranch). - Run(ctx, prCtx.RunOpts()); err != nil { + if err := prCtx.WithCmd(gitcmd.NewCommand("fetch", "origin").AddArguments(fetchArgs...).AddDashesAndList(pr.BaseBranch+":"+baseBranch, pr.BaseBranch+":original_"+baseBranch)). + Run(ctx); err != nil { 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()) cancel() 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()) } - if err := gitcmd.NewCommand("symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseBranch). - Run(ctx, prCtx.RunOpts()); err != nil { + if err := prCtx.WithCmd(gitcmd.NewCommand("symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseBranch)). + Run(ctx); err != nil { 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()) cancel() 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()) @@ -159,8 +157,8 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) return nil, nil, fmt.Errorf("Unable to add head base repository to temporary repo [%s -> tmpBasePath]: %w", pr.HeadRepo.FullName(), err) } - if err := gitcmd.NewCommand("remote", "add").AddDynamicArguments(remoteRepoName, headRepoPath). - Run(ctx, prCtx.RunOpts()); err != nil { + if err := prCtx.WithCmd(gitcmd.NewCommand("remote", "add").AddDynamicArguments(remoteRepoName, headRepoPath)). + Run(ctx); err != nil { 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()) cancel() 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()) @@ -177,8 +175,8 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) } else { headBranch = pr.GetGitHeadRefName() } - if err := gitcmd.NewCommand("fetch").AddArguments(fetchArgs...).AddDynamicArguments(remoteRepoName, headBranch+":"+trackingBranch). - Run(ctx, prCtx.RunOpts()); err != nil { + if err := prCtx.WithCmd(gitcmd.NewCommand("fetch").AddArguments(fetchArgs...).AddDynamicArguments(remoteRepoName, headBranch+":"+trackingBranch)). + Run(ctx); err != nil { cancel() if !gitrepo.IsBranchExist(ctx, pr.HeadRepo, pr.HeadBranch) { return nil, nil, git_model.ErrBranchNotExist{ diff --git a/services/pull/update_rebase.go b/services/pull/update_rebase.go index 3b5d3afba5b4b..e6845f6b143b6 100644 --- a/services/pull/update_rebase.go +++ b/services/pull/update_rebase.go @@ -28,7 +28,8 @@ func updateHeadByRebaseOnToBase(ctx context.Context, pr *issues_model.PullReques defer cancel() // Determine the old merge-base before the rebase - we use this for LFS push later on - oldMergeBase, _, _ := gitcmd.NewCommand("merge-base").AddDashesAndList(baseBranch, trackingBranch).RunStdString(ctx, &gitcmd.RunOpts{Dir: mergeCtx.tmpBasePath}) + oldMergeBase, _, _ := gitcmd.NewCommand("merge-base").AddDashesAndList(baseBranch, trackingBranch). + WithDir(mergeCtx.tmpBasePath).RunStdString(ctx) oldMergeBase = strings.TrimSpace(oldMergeBase) // Rebase the tracking branch on to the base as the staging branch @@ -72,18 +73,18 @@ func updateHeadByRebaseOnToBase(ctx context.Context, pr *issues_model.PullReques mergeCtx.outbuf.Reset() mergeCtx.errbuf.Reset() - if err := pushCmd.Run(ctx, &gitcmd.RunOpts{ - Env: repo_module.FullPushingEnvironment( + if err := pushCmd. + WithEnv(repo_module.FullPushingEnvironment( headUser, doer, pr.HeadRepo, pr.HeadRepo.Name, pr.ID, - ), - Dir: mergeCtx.tmpBasePath, - Stdout: mergeCtx.outbuf, - Stderr: mergeCtx.errbuf, - }); err != nil { + )). + WithDir(mergeCtx.tmpBasePath). + WithStdout(mergeCtx.outbuf). + WithStderr(mergeCtx.errbuf). + Run(ctx); err != nil { if strings.Contains(mergeCtx.errbuf.String(), "non-fast-forward") { return &git.ErrPushOutOfDate{ StdOut: mergeCtx.outbuf.String(), diff --git a/services/repository/contributors_graph.go b/services/repository/contributors_graph.go index a7fb130cb57d9..2c5c7c604fe80 100644 --- a/services/repository/contributors_graph.go +++ b/services/repository/contributors_graph.go @@ -132,11 +132,10 @@ func getExtendedCommitStats(repo *git.Repository, revision string /*, limit int var extendedCommitStats []*ExtendedCommitStats stderr := new(strings.Builder) - err = gitCmd.Run(repo.Ctx, &gitcmd.RunOpts{ - Dir: repo.Path, - Stdout: stdoutWriter, - Stderr: stderr, - PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { + err = gitCmd.WithDir(repo.Path). + WithStdout(stdoutWriter). + WithStderr(stderr). + WithPipelineFunc(func(ctx context.Context, cancel context.CancelFunc) error { _ = stdoutWriter.Close() scanner := bufio.NewScanner(stdoutReader) @@ -191,8 +190,8 @@ func getExtendedCommitStats(repo *git.Repository, revision string /*, limit int } _ = stdoutReader.Close() return nil - }, - }) + }). + Run(repo.Ctx) if err != nil { return nil, fmt.Errorf("Failed to get ContributorsCommitStats for repository.\nError: %w\nStderr: %s", err, stderr) } diff --git a/services/repository/create.go b/services/repository/create.go index 7ea8f9c132c46..0b57db988b73b 100644 --- a/services/repository/create.go +++ b/services/repository/create.go @@ -70,7 +70,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir // Clone to temporary path and do the init commit. if stdout, _, err := gitcmd.NewCommand("clone").AddDynamicArguments(repo.RepoPath(), tmpDir). - RunStdString(ctx, &gitcmd.RunOpts{Dir: "", Env: env}); err != nil { + WithEnv(env).RunStdString(ctx); err != nil { log.Error("Failed to clone from %v into %s: stdout: %s\nError: %v", repo, tmpDir, stdout, err) return fmt.Errorf("git clone: %w", err) } diff --git a/services/repository/files/patch.go b/services/repository/files/patch.go index 093072dff20b8..610650ad9032b 100644 --- a/services/repository/files/patch.go +++ b/services/repository/files/patch.go @@ -165,12 +165,11 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user cmdApply.AddArguments("-3") } - if err := cmdApply.Run(ctx, &gitcmd.RunOpts{ - Dir: t.basePath, - Stdout: stdout, - Stderr: stderr, - Stdin: strings.NewReader(opts.Content), - }); err != nil { + if err := cmdApply.WithDir(t.basePath). + WithStdout(stdout). + WithStderr(stderr). + WithStdin(strings.NewReader(opts.Content)). + Run(ctx); err != nil { return nil, fmt.Errorf("Error: Stdout: %s\nStderr: %s\nErr: %w", stdout.String(), stderr.String(), err) } diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go index dcbe368357b77..f21a343b442c2 100644 --- a/services/repository/files/temp_repo.go +++ b/services/repository/files/temp_repo.go @@ -59,7 +59,7 @@ func (t *TemporaryUploadRepository) Clone(ctx context.Context, branch string, ba cmd.AddArguments("--bare") } - if _, _, err := cmd.RunStdString(ctx, nil); err != nil { + if _, _, err := cmd.RunStdString(ctx); err != nil { stderr := err.Error() if matched, _ := regexp.MatchString(".*Remote branch .* not found in upstream origin.*", stderr); matched { return git.ErrBranchNotExist{ @@ -98,7 +98,7 @@ func (t *TemporaryUploadRepository) Init(ctx context.Context, objectFormatName s // SetDefaultIndex sets the git index to our HEAD func (t *TemporaryUploadRepository) SetDefaultIndex(ctx context.Context) error { - if _, _, err := gitcmd.NewCommand("read-tree", "HEAD").RunStdString(ctx, &gitcmd.RunOpts{Dir: t.basePath}); err != nil { + if _, _, err := gitcmd.NewCommand("read-tree", "HEAD").WithDir(t.basePath).RunStdString(ctx); err != nil { return fmt.Errorf("SetDefaultIndex: %w", err) } return nil @@ -106,7 +106,7 @@ func (t *TemporaryUploadRepository) SetDefaultIndex(ctx context.Context) error { // RefreshIndex looks at the current index and checks to see if merges or updates are needed by checking stat() information. func (t *TemporaryUploadRepository) RefreshIndex(ctx context.Context) error { - if _, _, err := gitcmd.NewCommand("update-index", "--refresh").RunStdString(ctx, &gitcmd.RunOpts{Dir: t.basePath}); err != nil { + if _, _, err := gitcmd.NewCommand("update-index", "--refresh").WithDir(t.basePath).RunStdString(ctx); err != nil { return fmt.Errorf("RefreshIndex: %w", err) } return nil @@ -118,11 +118,10 @@ func (t *TemporaryUploadRepository) LsFiles(ctx context.Context, filenames ...st stdErr := new(bytes.Buffer) if err := gitcmd.NewCommand("ls-files", "-z").AddDashesAndList(filenames...). - Run(ctx, &gitcmd.RunOpts{ - Dir: t.basePath, - Stdout: stdOut, - Stderr: stdErr, - }); err != nil { + WithDir(t.basePath). + WithStdout(stdOut). + WithStderr(stdErr). + Run(ctx); err != nil { log.Error("Unable to run git ls-files for temporary repo: %s (%s) Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), t.basePath, err, stdOut.String(), stdErr.String()) err = fmt.Errorf("Unable to run git ls-files for temporary repo of: %s Error: %w\nstdout: %s\nstderr: %s", t.repo.FullName(), err, stdOut.String(), stdErr.String()) return nil, err @@ -154,12 +153,11 @@ func (t *TemporaryUploadRepository) RemoveFilesFromIndex(ctx context.Context, fi } if err := gitcmd.NewCommand("update-index", "--remove", "-z", "--index-info"). - Run(ctx, &gitcmd.RunOpts{ - Dir: t.basePath, - Stdin: stdIn, - Stdout: stdOut, - Stderr: stdErr, - }); err != nil { + WithDir(t.basePath). + WithStdout(stdOut). + WithStderr(stdErr). + WithStdin(stdIn). + Run(ctx); err != nil { return fmt.Errorf("unable to update-index for temporary repo: %q, error: %w\nstdout: %s\nstderr: %s", t.repo.FullName(), err, stdOut.String(), stdErr.String()) } return nil @@ -171,12 +169,11 @@ func (t *TemporaryUploadRepository) HashObjectAndWrite(ctx context.Context, cont stdErr := new(bytes.Buffer) if err := gitcmd.NewCommand("hash-object", "-w", "--stdin"). - Run(ctx, &gitcmd.RunOpts{ - Dir: t.basePath, - Stdin: content, - Stdout: stdOut, - Stderr: stdErr, - }); err != nil { + WithDir(t.basePath). + WithStdout(stdOut). + WithStderr(stdErr). + WithStdin(content). + Run(ctx); err != nil { log.Error("Unable to hash-object to temporary repo: %s (%s) Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), t.basePath, err, stdOut.String(), stdErr.String()) return "", fmt.Errorf("Unable to hash-object to temporary repo: %s Error: %w\nstdout: %s\nstderr: %s", t.repo.FullName(), err, stdOut.String(), stdErr.String()) } @@ -186,7 +183,8 @@ func (t *TemporaryUploadRepository) HashObjectAndWrite(ctx context.Context, cont // AddObjectToIndex adds the provided object hash to the index with the provided mode and path func (t *TemporaryUploadRepository) AddObjectToIndex(ctx context.Context, mode, objectHash, objectPath string) error { - if _, _, err := gitcmd.NewCommand("update-index", "--add", "--replace", "--cacheinfo").AddDynamicArguments(mode, objectHash, objectPath).RunStdString(ctx, &gitcmd.RunOpts{Dir: t.basePath}); err != nil { + if _, _, err := gitcmd.NewCommand("update-index", "--add", "--replace", "--cacheinfo"). + AddDynamicArguments(mode, objectHash, objectPath).WithDir(t.basePath).RunStdString(ctx); err != nil { stderr := err.Error() if matched, _ := regexp.MatchString(".*Invalid path '.*", stderr); matched { return ErrFilePathInvalid{ @@ -202,7 +200,7 @@ func (t *TemporaryUploadRepository) AddObjectToIndex(ctx context.Context, mode, // WriteTree writes the current index as a tree to the object db and returns its hash func (t *TemporaryUploadRepository) WriteTree(ctx context.Context) (string, error) { - stdout, _, err := gitcmd.NewCommand("write-tree").RunStdString(ctx, &gitcmd.RunOpts{Dir: t.basePath}) + stdout, _, err := gitcmd.NewCommand("write-tree").WithDir(t.basePath).RunStdString(ctx) if err != nil { log.Error("Unable to write tree in temporary repo: %s(%s): Error: %v", t.repo.FullName(), t.basePath, err) return "", fmt.Errorf("Unable to write-tree in temporary repo for: %s Error: %w", t.repo.FullName(), err) @@ -220,7 +218,7 @@ func (t *TemporaryUploadRepository) GetLastCommitByRef(ctx context.Context, ref if ref == "" { ref = "HEAD" } - stdout, _, err := gitcmd.NewCommand("rev-parse").AddDynamicArguments(ref).RunStdString(ctx, &gitcmd.RunOpts{Dir: t.basePath}) + stdout, _, err := gitcmd.NewCommand("rev-parse").AddDynamicArguments(ref).WithDir(t.basePath).RunStdString(ctx) if err != nil { log.Error("Unable to get last ref for %s in temporary repo: %s(%s): Error: %v", ref, t.repo.FullName(), t.basePath, err) return "", fmt.Errorf("Unable to rev-parse %s in temporary repo for: %s Error: %w", ref, t.repo.FullName(), err) @@ -338,13 +336,12 @@ func (t *TemporaryUploadRepository) CommitTree(ctx context.Context, opts *Commit stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) if err := cmdCommitTree. - Run(ctx, &gitcmd.RunOpts{ - Env: env, - Dir: t.basePath, - Stdin: messageBytes, - Stdout: stdout, - Stderr: stderr, - }); err != nil { + WithEnv(env). + WithDir(t.basePath). + WithStdout(stdout). + WithStderr(stderr). + WithStdin(messageBytes). + Run(ctx); err != nil { log.Error("Unable to commit-tree in temporary repo: %s (%s) Error: %v\nStdout: %s\nStderr: %s", t.repo.FullName(), t.basePath, err, stdout, stderr) return "", fmt.Errorf("Unable to commit-tree in temporary repo: %s Error: %w\nStdout: %s\nStderr: %s", @@ -391,24 +388,23 @@ func (t *TemporaryUploadRepository) DiffIndex(ctx context.Context) (*gitdiff.Dif stderr := new(bytes.Buffer) var diff *gitdiff.Diff err = gitcmd.NewCommand("diff-index", "--src-prefix=\\a/", "--dst-prefix=\\b/", "--cached", "-p", "HEAD"). - Run(ctx, &gitcmd.RunOpts{ - Timeout: 30 * time.Second, - Dir: t.basePath, - Stdout: stdoutWriter, - Stderr: stderr, - PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { - _ = stdoutWriter.Close() - defer cancel() - var diffErr error - diff, diffErr = gitdiff.ParsePatch(ctx, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, stdoutReader, "") - _ = stdoutReader.Close() - if diffErr != nil { - // if the diffErr is not nil, it will be returned as the error of "Run()" - return fmt.Errorf("ParsePatch: %w", diffErr) - } - return nil - }, - }) + WithTimeout(30 * time.Second). + WithDir(t.basePath). + WithStdout(stdoutWriter). + WithStderr(stderr). + WithPipelineFunc(func(ctx context.Context, cancel context.CancelFunc) error { + _ = stdoutWriter.Close() + defer cancel() + var diffErr error + diff, diffErr = gitdiff.ParsePatch(ctx, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, stdoutReader, "") + _ = stdoutReader.Close() + if diffErr != nil { + // if the diffErr is not nil, it will be returned as the error of "Run()" + return fmt.Errorf("ParsePatch: %w", diffErr) + } + return nil + }). + Run(ctx) if err != nil && !git.IsErrCanceledOrKilled(err) { log.Error("Unable to diff-index in temporary repo %s (%s). Error: %v\nStderr: %s", t.repo.FullName(), t.basePath, err, stderr) return nil, fmt.Errorf("unable to run diff-index pipeline in temporary repo: %w", err) diff --git a/services/repository/fork.go b/services/repository/fork.go index 05909f0e0d546..2380666afbfe6 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -153,7 +153,8 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork } var stdout []byte if stdout, _, err = cloneCmd.AddDynamicArguments(opts.BaseRepo.RepoPath(), repo.RepoPath()). - RunStdBytes(ctx, &gitcmd.RunOpts{Timeout: 10 * time.Minute}); err != nil { + WithTimeout(10 * time.Minute). + RunStdBytes(ctx); err != nil { log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, opts.BaseRepo, stdout, err) return nil, fmt.Errorf("git clone: %w", err) } diff --git a/services/repository/generate.go b/services/repository/generate.go index c03d9b51a7933..8e7f9a45b0dfa 100644 --- a/services/repository/generate.go +++ b/services/repository/generate.go @@ -235,8 +235,11 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r return err } - if stdout, _, err := gitcmd.NewCommand("remote", "add", "origin").AddDynamicArguments(repo.RepoPath()). - RunStdString(ctx, &gitcmd.RunOpts{Dir: tmpDir, Env: env}); err != nil { + if stdout, _, err := gitcmd.NewCommand("remote", "add", "origin"). + AddDynamicArguments(repo.RepoPath()). + WithDir(tmpDir). + WithEnv(env). + RunStdString(ctx); err != nil { log.Error("Unable to add %v as remote origin to temporary repo to %s: stdout %s\nError: %v", repo, tmpDir, stdout, err) return fmt.Errorf("git remote add: %w", err) } diff --git a/services/repository/gitgraph/graph.go b/services/repository/gitgraph/graph.go index 3bc97b7bbc7de..f89d9a095a125 100644 --- a/services/repository/gitgraph/graph.go +++ b/services/repository/gitgraph/graph.go @@ -54,11 +54,11 @@ func GetCommitGraph(r *git.Repository, page, maxAllowedColors int, hidePRRefs bo scanner := bufio.NewScanner(stdoutReader) - if err := graphCmd.Run(r.Ctx, &gitcmd.RunOpts{ - Dir: r.Path, - Stdout: stdoutWriter, - Stderr: stderr, - PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { + if err := graphCmd. + WithDir(r.Path). + WithStdout(stdoutWriter). + WithStderr(stderr). + WithPipelineFunc(func(ctx context.Context, cancel context.CancelFunc) error { _ = stdoutWriter.Close() defer stdoutReader.Close() parser := &Parser{} @@ -109,8 +109,8 @@ func GetCommitGraph(r *git.Repository, page, maxAllowedColors int, hidePRRefs bo } } return scanner.Err() - }, - }); err != nil { + }). + Run(r.Ctx); err != nil { return graph, err } return graph, nil diff --git a/services/repository/init.go b/services/repository/init.go index c905d1f6a30f6..8d9decf811b2e 100644 --- a/services/repository/init.go +++ b/services/repository/init.go @@ -33,8 +33,7 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi committerName := sig.Name committerEmail := sig.Email - if stdout, _, err := gitcmd.NewCommand("add", "--all"). - RunStdString(ctx, &gitcmd.RunOpts{Dir: tmpPath}); err != nil { + if stdout, _, err := gitcmd.NewCommand("add", "--all").WithDir(tmpPath).RunStdString(ctx); err != nil { log.Error("git add --all failed: Stdout: %s\nError: %v", stdout, err) return fmt.Errorf("git add --all: %w", err) } @@ -63,8 +62,7 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi "GIT_COMMITTER_EMAIL="+committerEmail, ) - if stdout, _, err := cmd. - RunStdString(ctx, &gitcmd.RunOpts{Dir: tmpPath, Env: env}); err != nil { + if stdout, _, err := cmd.WithDir(tmpPath).WithEnv(env).RunStdString(ctx); err != nil { log.Error("Failed to commit: %v: Stdout: %s\nError: %v", cmd.LogString(), stdout, err) return fmt.Errorf("git commit: %w", err) } @@ -73,8 +71,11 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi defaultBranch = setting.Repository.DefaultBranch } - if stdout, _, err := gitcmd.NewCommand("push", "origin").AddDynamicArguments("HEAD:"+defaultBranch). - RunStdString(ctx, &gitcmd.RunOpts{Dir: tmpPath, Env: repo_module.InternalPushingEnvironment(u, repo)}); err != nil { + if stdout, _, err := gitcmd.NewCommand("push", "origin"). + AddDynamicArguments("HEAD:" + defaultBranch). + WithDir(tmpPath). + WithEnv(repo_module.InternalPushingEnvironment(u, repo)). + RunStdString(ctx); err != nil { log.Error("Failed to push back to HEAD: Stdout: %s\nError: %v", stdout, err) return fmt.Errorf("git push: %w", err) } diff --git a/tests/integration/git_general_test.go b/tests/integration/git_general_test.go index d243f0f4afd6d..a726f0349f5cb 100644 --- a/tests/integration/git_general_test.go +++ b/tests/integration/git_general_test.go @@ -194,9 +194,10 @@ func lfsCommitAndPushTest(t *testing.T, dstPath string, sizes ...int) (pushedFil t.Run("CommitAndPushLFS", func(t *testing.T) { defer tests.PrintCurrentTest(t)() prefix := "lfs-data-file-" - err := gitcmd.NewCommand("lfs").AddArguments("install").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + err := gitcmd.NewCommand("lfs").AddArguments("install").WithDir(dstPath).Run(t.Context()) assert.NoError(t, err) - _, _, err = gitcmd.NewCommand("lfs").AddArguments("track").AddDynamicArguments(prefix+"*").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err = gitcmd.NewCommand("lfs").AddArguments("track").AddDynamicArguments(prefix + "*"). + WithDir(dstPath).RunStdString(t.Context()) assert.NoError(t, err) err = git.AddChanges(t.Context(), dstPath, false, ".gitattributes") assert.NoError(t, err) @@ -312,20 +313,20 @@ func lockTest(t *testing.T, repoPath string) { } func lockFileTest(t *testing.T, filename, repoPath string) { - _, _, err := gitcmd.NewCommand("lfs").AddArguments("locks").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repoPath}) + _, _, err := gitcmd.NewCommand("lfs").AddArguments("locks").WithDir(repoPath).RunStdString(t.Context()) assert.NoError(t, err) - _, _, err = gitcmd.NewCommand("lfs").AddArguments("lock").AddDynamicArguments(filename).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repoPath}) + _, _, err = gitcmd.NewCommand("lfs").AddArguments("lock").AddDynamicArguments(filename).WithDir(repoPath).RunStdString(t.Context()) assert.NoError(t, err) - _, _, err = gitcmd.NewCommand("lfs").AddArguments("locks").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repoPath}) + _, _, err = gitcmd.NewCommand("lfs").AddArguments("locks").WithDir(repoPath).RunStdString(t.Context()) assert.NoError(t, err) - _, _, err = gitcmd.NewCommand("lfs").AddArguments("unlock").AddDynamicArguments(filename).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repoPath}) + _, _, err = gitcmd.NewCommand("lfs").AddArguments("unlock").AddDynamicArguments(filename).WithDir(repoPath).RunStdString(t.Context()) assert.NoError(t, err) } func doCommitAndPush(t *testing.T, size int, repoPath, prefix string) string { name, err := generateCommitWithNewData(t.Context(), size, repoPath, "user2@example.com", "User Two", prefix) assert.NoError(t, err) - _, _, err = gitcmd.NewCommand("push", "origin", "master").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repoPath}) // Push + _, _, err = gitcmd.NewCommand("push", "origin", "master").WithDir(repoPath).RunStdString(t.Context()) assert.NoError(t, err) return name } @@ -425,7 +426,7 @@ func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *tes // Try to force push without force push permissions, which should fail t.Run("ForcePushWithoutForcePermissions", func(t *testing.T) { t.Run("CreateDivergentHistory", func(t *testing.T) { - gitcmd.NewCommand("reset", "--hard", "HEAD~1").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + gitcmd.NewCommand("reset", "--hard", "HEAD~1").WithDir(dstPath).Run(t.Context()) _, err := generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-new") assert.NoError(t, err) }) @@ -848,7 +849,10 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string }) t.Run("Push", func(t *testing.T) { - err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic="+headBranch).Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master", "-o"). + AddDynamicArguments("topic=" + headBranch). + WithDir(dstPath). + Run(t.Context()) require.NoError(t, err) unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+1) @@ -866,7 +870,10 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string assert.Contains(t, "Testing commit 1", prMsg.Body) assert.Equal(t, commit, prMsg.Head.Sha) - _, _, err = gitcmd.NewCommand("push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/"+headBranch).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err = gitcmd.NewCommand("push", "origin"). + AddDynamicArguments("HEAD:refs/for/master/test/" + headBranch). + WithDir(dstPath). + RunStdString(t.Context()) require.NoError(t, err) unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) @@ -914,7 +921,10 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string }) t.Run("Push2", func(t *testing.T) { - err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic="+headBranch).Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master", "-o"). + AddDynamicArguments("topic=" + headBranch). + WithDir(dstPath). + Run(t.Context()) require.NoError(t, err) unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) @@ -924,7 +934,10 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string assert.False(t, prMsg.HasMerged) assert.Equal(t, commit, prMsg.Head.Sha) - _, _, err = gitcmd.NewCommand("push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/"+headBranch).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err = gitcmd.NewCommand("push", "origin"). + AddDynamicArguments("HEAD:refs/for/master/test/" + headBranch). + WithDir(dstPath). + RunStdString(t.Context()) require.NoError(t, err) unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) diff --git a/tests/integration/git_helper_for_declarative_test.go b/tests/integration/git_helper_for_declarative_test.go index 91c30f7278a3b..b59fc34c214d9 100644 --- a/tests/integration/git_helper_for_declarative_test.go +++ b/tests/integration/git_helper_for_declarative_test.go @@ -122,7 +122,9 @@ func doGitInitTestRepository(dstPath string) func(*testing.T) { // Init repository in dstPath assert.NoError(t, git.InitRepository(t.Context(), dstPath, false, git.Sha1ObjectFormat.Name())) // forcibly set default branch to master - _, _, err := gitcmd.NewCommand("symbolic-ref", "HEAD", git.BranchPrefix+"master").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err := gitcmd.NewCommand("symbolic-ref", "HEAD", git.BranchPrefix+"master"). + WithDir(dstPath). + RunStdString(t.Context()) assert.NoError(t, err) assert.NoError(t, os.WriteFile(filepath.Join(dstPath, "README.md"), []byte("# Testing Repository\n\nOriginally created in: "+dstPath), 0o644)) assert.NoError(t, git.AddChanges(t.Context(), dstPath, true)) @@ -141,21 +143,27 @@ func doGitInitTestRepository(dstPath string) func(*testing.T) { func doGitAddRemote(dstPath, remoteName string, u *url.URL) func(*testing.T) { return func(t *testing.T) { - _, _, err := gitcmd.NewCommand("remote", "add").AddDynamicArguments(remoteName, u.String()).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err := gitcmd.NewCommand("remote", "add").AddDynamicArguments(remoteName, u.String()). + WithDir(dstPath). + RunStdString(t.Context()) assert.NoError(t, err) } } func doGitPushTestRepository(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { - _, _, err := gitcmd.NewCommand("push", "-u").AddArguments(gitcmd.ToTrustedCmdArgs(args)...).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err := gitcmd.NewCommand("push", "-u").AddArguments(gitcmd.ToTrustedCmdArgs(args)...). + WithDir(dstPath). + RunStdString(t.Context()) assert.NoError(t, err) } } func doGitPushTestRepositoryFail(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { - _, _, err := gitcmd.NewCommand("push").AddArguments(gitcmd.ToTrustedCmdArgs(args)...).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err := gitcmd.NewCommand("push").AddArguments(gitcmd.ToTrustedCmdArgs(args)...). + WithDir(dstPath). + RunStdString(t.Context()) assert.Error(t, err) } } @@ -180,28 +188,36 @@ func doGitAddSomeCommits(dstPath, branch string) func(*testing.T) { func doGitCreateBranch(dstPath, branch string) func(*testing.T) { return func(t *testing.T) { - _, _, err := gitcmd.NewCommand("checkout", "-b").AddDynamicArguments(branch).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err := gitcmd.NewCommand("checkout", "-b").AddDynamicArguments(branch). + WithDir(dstPath).RunStdString(t.Context()) assert.NoError(t, err) } } func doGitCheckoutBranch(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { - _, _, err := gitcmd.NewCommand().AddArguments("checkout").AddArguments(gitcmd.ToTrustedCmdArgs(args)...).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err := gitcmd.NewCommand().AddArguments("checkout"). + AddArguments(gitcmd.ToTrustedCmdArgs(args)...). + WithDir(dstPath). + RunStdString(t.Context()) assert.NoError(t, err) } } func doGitMerge(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { - _, _, err := gitcmd.NewCommand("merge").AddArguments(gitcmd.ToTrustedCmdArgs(args)...).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err := gitcmd.NewCommand("merge").AddArguments(gitcmd.ToTrustedCmdArgs(args)...). + WithDir(dstPath). + RunStdString(t.Context()) assert.NoError(t, err) } } func doGitPull(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { - _, _, err := gitcmd.NewCommand().AddArguments("pull").AddArguments(gitcmd.ToTrustedCmdArgs(args)...).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err := gitcmd.NewCommand().AddArguments("pull").AddArguments(gitcmd.ToTrustedCmdArgs(args)...). + WithDir(dstPath). + RunStdString(t.Context()) assert.NoError(t, err) } } diff --git a/tests/integration/git_lfs_ssh_test.go b/tests/integration/git_lfs_ssh_test.go index 6bb9a748b4eb2..4ca1ffece5eb1 100644 --- a/tests/integration/git_lfs_ssh_test.go +++ b/tests/integration/git_lfs_ssh_test.go @@ -45,7 +45,9 @@ func TestGitLFSSSH(t *testing.T) { setting.LFS.AllowPureSSH = true require.NoError(t, cfg.Save()) - _, _, cmdErr := gitcmd.NewCommand("config", "lfs.sshtransfer", "always").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, cmdErr := gitcmd.NewCommand("config", "lfs.sshtransfer", "always"). + WithDir(dstPath). + RunStdString(t.Context()) assert.NoError(t, cmdErr) lfsCommitAndPushTest(t, dstPath, 10) }) diff --git a/tests/integration/git_misc_test.go b/tests/integration/git_misc_test.go index 2510bc648f5f6..c830086e3fa11 100644 --- a/tests/integration/git_misc_test.go +++ b/tests/integration/git_misc_test.go @@ -104,7 +104,8 @@ func TestAgitPullPush(t *testing.T) { err = gitcmd.NewCommand("push", "origin", "-o", "title=test-title", "-o", "description=test-description", "HEAD:refs/for/master/test-agit-push", - ).Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + ).WithDir(dstPath). + Run(t.Context()) assert.NoError(t, err) // check pull request exist @@ -118,20 +119,26 @@ func TestAgitPullPush(t *testing.T) { assert.NoError(t, err) // push 2 - err = gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + err = gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push"). + WithDir(dstPath). + Run(t.Context()) assert.NoError(t, err) // reset to first commit - err = gitcmd.NewCommand("reset", "--hard", "HEAD~1").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + err = gitcmd.NewCommand("reset", "--hard", "HEAD~1").WithDir(dstPath).Run(t.Context()) assert.NoError(t, err) // test force push without confirm - _, stderr, err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, stderr, err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push"). + WithDir(dstPath). + RunStdString(t.Context()) assert.Error(t, err) assert.Contains(t, stderr, "[remote rejected] HEAD -> refs/for/master/test-agit-push (request `force-push` push option)") // test force push with confirm - err = gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push", "-o", "force-push").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + err = gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push", "-o", "force-push"). + WithDir(dstPath). + Run(t.Context()) assert.NoError(t, err) }) } @@ -160,7 +167,7 @@ func TestAgitReviewStaleness(t *testing.T) { err = gitcmd.NewCommand("push", "origin", "-o", "title=Test agit Review Staleness", "-o", "description=Testing review staleness", "HEAD:refs/for/master/test-agit-review", - ).Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + ).WithDir(dstPath).Run(t.Context()) assert.NoError(t, err) pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ @@ -200,7 +207,9 @@ func TestAgitReviewStaleness(t *testing.T) { _, err = generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "updated-") assert.NoError(t, err) - err = gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-review").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + err = gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-review"). + WithDir(dstPath). + Run(t.Context()) assert.NoError(t, err) // Reload PR to get updated commit ID diff --git a/tests/integration/git_push_test.go b/tests/integration/git_push_test.go index b02387c3b98e9..0efa85468193e 100644 --- a/tests/integration/git_push_test.go +++ b/tests/integration/git_push_test.go @@ -206,9 +206,7 @@ func TestPushPullRefs(t *testing.T) { doGitClone(dstPath, u)(t) cmd := gitcmd.NewCommand("push", "--delete", "origin", "refs/pull/2/head") - stdout, stderr, err := cmd.RunStdString(t.Context(), &gitcmd.RunOpts{ - Dir: dstPath, - }) + stdout, stderr, err := cmd.WithDir(dstPath).RunStdString(t.Context()) assert.Error(t, err) assert.Empty(t, stdout) assert.NotContains(t, stderr, "[deleted]", "stderr: %s", stderr) diff --git a/tests/integration/pull_create_test.go b/tests/integration/pull_create_test.go index 18f7e80e8276f..726d898d33002 100644 --- a/tests/integration/pull_create_test.go +++ b/tests/integration/pull_create_test.go @@ -265,7 +265,10 @@ func TestCreateAgitPullWithReadPermission(t *testing.T) { t.Run("add commit", doGitAddSomeCommits(dstPath, "master")) t.Run("do agit pull create", func(t *testing.T) { - err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic="+"test-topic").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master", "-o"). + AddDynamicArguments("topic=" + "test-topic"). + WithDir(dstPath). + Run(t.Context()) assert.NoError(t, err) }) }) diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go index ee039dab8ad6e..7670aebab5349 100644 --- a/tests/integration/pull_merge_test.go +++ b/tests/integration/pull_merge_test.go @@ -294,24 +294,27 @@ func TestCantMergeUnrelated(t *testing.T) { }) path := repo_model.RepoPath(user1.Name, repo1.Name) - err := gitcmd.NewCommand("read-tree", "--empty").Run(t.Context(), &gitcmd.RunOpts{Dir: path}) + err := gitcmd.NewCommand("read-tree", "--empty").WithDir(path).Run(t.Context()) assert.NoError(t, err) stdin := strings.NewReader("Unrelated File") var stdout strings.Builder - err = gitcmd.NewCommand("hash-object", "-w", "--stdin").Run(t.Context(), &gitcmd.RunOpts{ - Dir: path, - Stdin: stdin, - Stdout: &stdout, - }) + err = gitcmd.NewCommand("hash-object", "-w", "--stdin"). + WithDir(path). + WithStdin(stdin). + WithStdout(&stdout). + Run(t.Context()) assert.NoError(t, err) sha := strings.TrimSpace(stdout.String()) - _, _, err = gitcmd.NewCommand("update-index", "--add", "--replace", "--cacheinfo").AddDynamicArguments("100644", sha, "somewher-over-the-rainbow").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: path}) + _, _, err = gitcmd.NewCommand("update-index", "--add", "--replace", "--cacheinfo"). + AddDynamicArguments("100644", sha, "somewher-over-the-rainbow"). + WithDir(path). + RunStdString(t.Context()) assert.NoError(t, err) - treeSha, _, err := gitcmd.NewCommand("write-tree").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: path}) + treeSha, _, err := gitcmd.NewCommand("write-tree").WithDir(path).RunStdString(t.Context()) assert.NoError(t, err) treeSha = strings.TrimSpace(treeSha) @@ -332,16 +335,18 @@ func TestCantMergeUnrelated(t *testing.T) { stdout.Reset() err = gitcmd.NewCommand("commit-tree").AddDynamicArguments(treeSha). - Run(t.Context(), &gitcmd.RunOpts{ - Env: env, - Dir: path, - Stdin: messageBytes, - Stdout: &stdout, - }) + WithEnv(env). + WithDir(path). + WithStdin(messageBytes). + WithStdout(&stdout). + Run(t.Context()) assert.NoError(t, err) commitSha := strings.TrimSpace(stdout.String()) - _, _, err = gitcmd.NewCommand("branch", "unrelated").AddDynamicArguments(commitSha).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: path}) + _, _, err = gitcmd.NewCommand("branch", "unrelated"). + AddDynamicArguments(commitSha). + WithDir(path). + RunStdString(t.Context()) assert.NoError(t, err) testEditFileToNewBranch(t, session, "user1", "repo1", "master", "conflict", "README.md", "Hello, World (Edited Once)\n") @@ -927,7 +932,9 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApprovalForAgitFlow(t *testing. AddDynamicArguments(`title="create a test pull request with agit"`). AddArguments("-o"). AddDynamicArguments(`description="This PR is a test pull request which created with agit"`). - Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath, Stderr: stderrBuf}) + WithDir(dstPath). + WithStderr(stderrBuf). + Run(t.Context()) assert.NoError(t, err) assert.Contains(t, stderrBuf.String(), setting.AppURL+"user2/repo1/pulls/6") diff --git a/tests/integration/repo_tag_test.go b/tests/integration/repo_tag_test.go index 98581cb6da6b5..b31d9bf4a21d7 100644 --- a/tests/integration/repo_tag_test.go +++ b/tests/integration/repo_tag_test.go @@ -55,10 +55,10 @@ func TestCreateNewTagProtected(t *testing.T) { doGitClone(dstPath, u)(t) - _, _, err := gitcmd.NewCommand("tag", "v-2").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err := gitcmd.NewCommand("tag", "v-2").WithDir(dstPath).RunStdString(t.Context()) assert.NoError(t, err) - _, _, err = gitcmd.NewCommand("push", "--tags").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err = gitcmd.NewCommand("push", "--tags").WithDir(dstPath).RunStdString(t.Context()) assert.Error(t, err) assert.Contains(t, err.Error(), "Tag v-2 is protected") }) @@ -75,20 +75,22 @@ func TestCreateNewTagProtected(t *testing.T) { doGitClone(dstPath, u)(t) - _, _, err := gitcmd.NewCommand("tag", "v-1.1", "-m", "force update", "--force").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err := gitcmd.NewCommand("tag", "v-1.1", "-m", "force update", "--force"). + WithDir(dstPath). + RunStdString(t.Context()) require.NoError(t, err) - _, _, err = gitcmd.NewCommand("push", "--tags").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err = gitcmd.NewCommand("push", "--tags").WithDir(dstPath).RunStdString(t.Context()) require.NoError(t, err) - _, _, err = gitcmd.NewCommand("tag", "v-1.1", "-m", "force update v2", "--force").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err = gitcmd.NewCommand("tag", "v-1.1", "-m", "force update v2", "--force").WithDir(dstPath).RunStdString(t.Context()) require.NoError(t, err) - _, _, err = gitcmd.NewCommand("push", "--tags").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err = gitcmd.NewCommand("push", "--tags").WithDir(dstPath).RunStdString(t.Context()) require.Error(t, err) assert.Contains(t, err.Error(), "the tag already exists in the remote") - _, _, err = gitcmd.NewCommand("push", "--tags", "--force").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err = gitcmd.NewCommand("push", "--tags", "--force").WithDir(dstPath).RunStdString(t.Context()) require.NoError(t, err) req := NewRequestf(t, "GET", "/%s/releases/tag/v-1.1", repo.FullName()) resp := MakeRequest(t, req, http.StatusOK) @@ -137,15 +139,15 @@ func TestRepushTag(t *testing.T) { doGitClone(dstPath, u)(t) // create and push a tag - _, _, err := gitcmd.NewCommand("tag", "v2.0").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err := gitcmd.NewCommand("tag", "v2.0").WithDir(dstPath).RunStdString(t.Context()) assert.NoError(t, err) - _, _, err = gitcmd.NewCommand("push", "origin", "--tags", "v2.0").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err = gitcmd.NewCommand("push", "origin", "--tags", "v2.0").WithDir(dstPath).RunStdString(t.Context()) assert.NoError(t, err) // create a release for the tag createdRelease := createNewReleaseUsingAPI(t, token, owner, repo, "v2.0", "", "Release of v2.0", "desc") assert.False(t, createdRelease.IsDraft) // delete the tag - _, _, err = gitcmd.NewCommand("push", "origin", "--delete", "v2.0").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err = gitcmd.NewCommand("push", "origin", "--delete", "v2.0").WithDir(dstPath).RunStdString(t.Context()) assert.NoError(t, err) // query the release by API and it should be a draft req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, "v2.0")) @@ -154,7 +156,7 @@ func TestRepushTag(t *testing.T) { DecodeJSON(t, resp, &respRelease) assert.True(t, respRelease.IsDraft) // re-push the tag - _, _, err = gitcmd.NewCommand("push", "origin", "--tags", "v2.0").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath}) + _, _, err = gitcmd.NewCommand("push", "origin", "--tags", "v2.0").WithDir(dstPath).RunStdString(t.Context()) assert.NoError(t, err) // query the release by API and it should not be a draft req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, "v2.0")) From acf8026a4b777d48b1089bce16b71b62c2e83e32 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 4 Oct 2025 19:38:06 -0700 Subject: [PATCH 06/14] remove unnecessary change --- modules/log/logger_global.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/log/logger_global.go b/modules/log/logger_global.go index 70e698592ab1a..2bc8c4f44975d 100644 --- a/modules/log/logger_global.go +++ b/modules/log/logger_global.go @@ -34,10 +34,6 @@ func Debug(format string, v ...any) { Log(1, DEBUG, format, v...) } -func DebugWithSkip(skip int, format string, v ...any) { - Log(skip+1, DEBUG, format, v...) -} - func IsDebug() bool { return GetLevel() <= DEBUG } From cda6a37b2fb0564cf66e2fe7891795c4a2a24db3 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 4 Oct 2025 19:51:59 -0700 Subject: [PATCH 07/14] revert unnecessary change --- modules/git/commit.go | 3 +-- modules/git/gitcmd/command.go | 13 ++++++++++++- modules/git/gitcmd/command_test.go | 3 ++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/modules/git/commit.go b/modules/git/commit.go index 5eef1439d0699..260b81b59022e 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -173,8 +173,7 @@ func CommitsCount(ctx context.Context, opts CommitsCountOptions) (int64, error) cmd.AddDashesAndList(opts.RelPath...) } - var stdout string - err := cmd.WithDir(opts.RepoPath).WithStringOutput(&stdout).Run(ctx) + stdout, _, err := cmd.WithDir(opts.RepoPath).RunStdString(ctx) if err != nil { return 0, err } diff --git a/modules/git/gitcmd/command.go b/modules/git/gitcmd/command.go index ffea3513d00ec..8614803780b8d 100644 --- a/modules/git/gitcmd/command.go +++ b/modules/git/gitcmd/command.go @@ -403,7 +403,7 @@ func (c *Command) WithLogSkipStep(stepSkip int) *Command { return c } -// Run runs the command with the RunOpts +// Run runs the command func (c *Command) Run(ctx context.Context) error { if len(c.brokenArgs) != 0 { log.Error("git command is broken: %s, broken args: %s", c.LogString(), strings.Join(c.brokenArgs, " ")) @@ -533,6 +533,7 @@ func IsErrorExitCode(err error, code int) bool { return false } +// RunStdString runs the command and returns stdout/stderr as string. and store stderr to returned error (err combined with stderr). func (c *Command) RunStdString(ctx context.Context) (stdout, stderr string, runErr RunStdError) { stdoutBytes, stderrBytes, err := c.WithLogSkipStep(1).RunStdBytes(ctx) stdout = util.UnsafeBytesToString(stdoutBytes) @@ -544,7 +545,17 @@ func (c *Command) RunStdString(ctx context.Context) (stdout, stderr string, runE return stdout, stderr, nil } +// RunStdBytes runs the command and returns stdout/stderr as bytes. and store stderr to returned error (err combined with stderr). func (c *Command) RunStdBytes(ctx context.Context) (stdout, stderr []byte, runErr RunStdError) { + if c.opts == nil { + c.opts = &runOpts{} + } + + if c.opts.Stdout != nil || c.opts.Stderr != nil { + // we must panic here, otherwise there would be bugs if developers set Stdin/Stderr by mistake, and it would be very difficult to debug + panic("stdout and stderr field must be nil when using RunStdBytes") + } + stdoutBuf := &bytes.Buffer{} stderrBuf := &bytes.Buffer{} diff --git a/modules/git/gitcmd/command_test.go b/modules/git/gitcmd/command_test.go index 05ef66b98c87b..5b0f34146c844 100644 --- a/modules/git/gitcmd/command_test.go +++ b/modules/git/gitcmd/command_test.go @@ -35,8 +35,9 @@ func TestRunWithContextStd(t *testing.T) { cmd = NewCommand("--no-such-arg") stdout, stderr, err = cmd.RunStdString(t.Context()) if assert.Error(t, err) { + assert.Equal(t, stderr, err.Stderr()) assert.Contains(t, stderr, "unknown option:") - assert.Contains(t, err.Error(), "exit status 129") + assert.Contains(t, err.Error(), "exit status 129 - unknown option:") assert.Empty(t, stdout) } From e69169f23ebd7f81e29e8c9881b1af02ed26998f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 4 Oct 2025 21:01:12 -0700 Subject: [PATCH 08/14] Fix --- modules/git/gitcmd/command_test.go | 2 +- modules/git/repo_tree.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/git/gitcmd/command_test.go b/modules/git/gitcmd/command_test.go index 5b0f34146c844..d61ef1a4fd083 100644 --- a/modules/git/gitcmd/command_test.go +++ b/modules/git/gitcmd/command_test.go @@ -36,7 +36,7 @@ func TestRunWithContextStd(t *testing.T) { stdout, stderr, err = cmd.RunStdString(t.Context()) if assert.Error(t, err) { assert.Equal(t, stderr, err.Stderr()) - assert.Contains(t, stderr, "unknown option:") + assert.Contains(t, err.Stderr(), "unknown option:") assert.Contains(t, err.Error(), "exit status 129 - unknown option:") assert.Empty(t, stdout) } diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go index 669ca459ed1b0..964342ba001f7 100644 --- a/modules/git/repo_tree.go +++ b/modules/git/repo_tree.go @@ -64,6 +64,7 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt WithDir(repo.Path). WithStdin(messageBytes). WithStdout(stdout). + WithStderr(stderr). Run(repo.Ctx) if err != nil { return nil, gitcmd.ConcatenateError(err, stderr.String()) From 0f8b0bd9ef5607699cc8b2b1b7ca5d0a62b14e8c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 4 Oct 2025 21:13:25 -0700 Subject: [PATCH 09/14] Fix gogit build --- modules/git/gitcmd/command_race_test.go | 4 ++-- modules/git/repo_commit_gogit.go | 5 ++++- modules/git/repo_tree_gogit.go | 5 ++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/modules/git/gitcmd/command_race_test.go b/modules/git/gitcmd/command_race_test.go index aee2272808b5a..c2f0b124a2607 100644 --- a/modules/git/gitcmd/command_race_test.go +++ b/modules/git/gitcmd/command_race_test.go @@ -17,7 +17,7 @@ func TestRunWithContextNoTimeout(t *testing.T) { // 'git --version' does not block so it must be finished before the timeout triggered. cmd := NewCommand("--version") for i := 0; i < maxLoops; i++ { - if err := cmd.Run(t.Context(), &RunOpts{}); err != nil { + if err := cmd.Run(t.Context()); err != nil { t.Fatal(err) } } @@ -29,7 +29,7 @@ func TestRunWithContextTimeout(t *testing.T) { // 'git hash-object --stdin' blocks on stdin so we can have the timeout triggered. cmd := NewCommand("hash-object", "--stdin") for i := 0; i < maxLoops; i++ { - if err := cmd.Run(t.Context(), &RunOpts{Timeout: 1 * time.Millisecond}); err != nil { + if err := cmd.WithTimeout(1 * time.Millisecond).Run(t.Context()); err != nil { if err != context.DeadlineExceeded { t.Fatalf("Testing %d/%d: %v", i, maxLoops, err) } diff --git a/modules/git/repo_commit_gogit.go b/modules/git/repo_commit_gogit.go index fc653714adb2a..896d65603971d 100644 --- a/modules/git/repo_commit_gogit.go +++ b/modules/git/repo_commit_gogit.go @@ -51,7 +51,10 @@ func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) { } } - actualCommitID, _, err := gitcmd.NewCommand("rev-parse", "--verify").AddDynamicArguments(commitID).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + actualCommitID, _, err := gitcmd.NewCommand("rev-parse", "--verify"). + AddDynamicArguments(commitID). + WithDir(repo.Path). + RunStdString(repo.Ctx) actualCommitID = strings.TrimSpace(actualCommitID) if err != nil { if strings.Contains(err.Error(), "unknown revision or path") || diff --git a/modules/git/repo_tree_gogit.go b/modules/git/repo_tree_gogit.go index 40524d0c344aa..e15663a32a7ac 100644 --- a/modules/git/repo_tree_gogit.go +++ b/modules/git/repo_tree_gogit.go @@ -38,7 +38,10 @@ func (repo *Repository) GetTree(idStr string) (*Tree, error) { } if len(idStr) != objectFormat.FullLength() { - res, _, err := gitcmd.NewCommand("rev-parse", "--verify").AddDynamicArguments(idStr).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path}) + res, _, err := gitcmd.NewCommand("rev-parse", "--verify"). + AddDynamicArguments(idStr). + WithDir(repo.Path). + RunStdString(repo.Ctx) if err != nil { return nil, err } From bfa7f946d54f98ae904324f2c7d161c16a4f5b7a Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 5 Oct 2025 12:25:17 +0800 Subject: [PATCH 10/14] fix --- modules/git/gitcmd/command.go | 84 +---------------------------------- 1 file changed, 1 insertion(+), 83 deletions(-) diff --git a/modules/git/gitcmd/command.go b/modules/git/gitcmd/command.go index 8614803780b8d..f060368eb8c31 100644 --- a/modules/git/gitcmd/command.go +++ b/modules/git/gitcmd/command.go @@ -46,7 +46,7 @@ type Command struct { brokenArgs []string cmd *exec.Cmd // for debug purpose only configArgs []string - opts *runOpts + opts runOpts } func logArgSanitize(arg string) string { @@ -269,59 +269,8 @@ func CommonCmdServEnvs() []string { var ErrBrokenCommand = errors.New("git command is broken") -type stringWriter struct { - str *string -} - -func (w *stringWriter) Write(p []byte) (n int, err error) { - *w.str += util.UnsafeBytesToString(p) - return len(p), nil -} - -func (c *Command) WithStringOutput(output *string) *Command { - if output != nil { - if c.opts == nil { - c.opts = &runOpts{} - } - c.opts.Stdout = &stringWriter{str: output} - } - return c -} - -func (c *Command) WithStringErr(stderr *string) *Command { - if stderr != nil { - if c.opts == nil { - c.opts = &runOpts{} - } - c.opts.Stderr = &stringWriter{str: stderr} - } - return c -} - -type bytesWriter struct { - buf *[]byte -} - -func (w *bytesWriter) Write(p []byte) (n int, err error) { - *w.buf = append(*w.buf, p...) - return len(p), nil -} - -func (c *Command) WithBytesOutput(output *[]byte) *Command { - if output != nil { - if c.opts == nil { - c.opts = &runOpts{} - } - c.opts.Stdout = &bytesWriter{buf: output} - } - return c -} - func (c *Command) WithDir(dir string) *Command { if dir != "" { - if c.opts == nil { - c.opts = &runOpts{} - } c.opts.Dir = dir } return c @@ -329,9 +278,6 @@ func (c *Command) WithDir(dir string) *Command { func (c *Command) WithEnv(env []string) *Command { if env != nil { - if c.opts == nil { - c.opts = &runOpts{} - } c.opts.Env = env } return c @@ -339,9 +285,6 @@ func (c *Command) WithEnv(env []string) *Command { func (c *Command) WithTimeout(timeout time.Duration) *Command { if timeout > 0 { - if c.opts == nil { - c.opts = &runOpts{} - } c.opts.Timeout = timeout } return c @@ -349,9 +292,6 @@ func (c *Command) WithTimeout(timeout time.Duration) *Command { func (c *Command) WithStdout(stdout io.Writer) *Command { if stdout != nil { - if c.opts == nil { - c.opts = &runOpts{} - } c.opts.Stdout = stdout } return c @@ -359,9 +299,6 @@ func (c *Command) WithStdout(stdout io.Writer) *Command { func (c *Command) WithStderr(stderr io.Writer) *Command { if stderr != nil { - if c.opts == nil { - c.opts = &runOpts{} - } c.opts.Stderr = stderr } return c @@ -369,9 +306,6 @@ func (c *Command) WithStderr(stderr io.Writer) *Command { func (c *Command) WithStdin(stdin io.Reader) *Command { if stdin != nil { - if c.opts == nil { - c.opts = &runOpts{} - } c.opts.Stdin = stdin } return c @@ -379,26 +313,17 @@ func (c *Command) WithStdin(stdin io.Reader) *Command { func (c *Command) WithPipelineFunc(f func(context.Context, context.CancelFunc) error) *Command { if f != nil { - if c.opts == nil { - c.opts = &runOpts{} - } c.opts.PipelineFunc = f } return c } func (c *Command) WithUseContextTimeout(useContextTimeout bool) *Command { - if c.opts == nil { - c.opts = &runOpts{} - } c.opts.UseContextTimeout = useContextTimeout return c } func (c *Command) WithLogSkipStep(stepSkip int) *Command { - if c.opts == nil { - c.opts = &runOpts{} - } c.opts.LogSkip += stepSkip return c } @@ -409,9 +334,6 @@ func (c *Command) Run(ctx context.Context) error { log.Error("git command is broken: %s, broken args: %s", c.LogString(), strings.Join(c.brokenArgs, " ")) return ErrBrokenCommand } - if c.opts == nil { - c.opts = &runOpts{} - } // We must not change the provided options timeout := c.opts.Timeout @@ -547,10 +469,6 @@ func (c *Command) RunStdString(ctx context.Context) (stdout, stderr string, runE // RunStdBytes runs the command and returns stdout/stderr as bytes. and store stderr to returned error (err combined with stderr). func (c *Command) RunStdBytes(ctx context.Context) (stdout, stderr []byte, runErr RunStdError) { - if c.opts == nil { - c.opts = &runOpts{} - } - if c.opts.Stdout != nil || c.opts.Stderr != nil { // we must panic here, otherwise there would be bugs if developers set Stdin/Stderr by mistake, and it would be very difficult to debug panic("stdout and stderr field must be nil when using RunStdBytes") From 85681d6e8b280cae5ca59e9b5db632917f67a9b8 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 5 Oct 2025 13:12:44 +0800 Subject: [PATCH 11/14] fix caller info --- modules/git/gitcmd/command.go | 64 ++++++++++++++++------------------ modules/gitrepo/command.go | 8 ++--- modules/util/runtime.go | 8 +++-- modules/util/runtime_test.go | 4 +-- services/pull/merge_prepare.go | 2 +- 5 files changed, 42 insertions(+), 44 deletions(-) diff --git a/modules/git/gitcmd/command.go b/modules/git/gitcmd/command.go index f060368eb8c31..87f5e4d8c3374 100644 --- a/modules/git/gitcmd/command.go +++ b/modules/git/gitcmd/command.go @@ -223,10 +223,7 @@ type runOpts struct { PipelineFunc func(context.Context, context.CancelFunc) error - // for debugging purpose only, it indicates how many stack frames to skip to find the caller info - // it's mainly used to find the caller function name for logging - // it should be 0 when the caller is the direct caller of Run/RunStdString/RunStdBytes - LogSkip int + callerInfo string } func commonBaseEnvs() []string { @@ -270,51 +267,37 @@ func CommonCmdServEnvs() []string { var ErrBrokenCommand = errors.New("git command is broken") func (c *Command) WithDir(dir string) *Command { - if dir != "" { - c.opts.Dir = dir - } + c.opts.Dir = dir return c } func (c *Command) WithEnv(env []string) *Command { - if env != nil { - c.opts.Env = env - } + c.opts.Env = env return c } func (c *Command) WithTimeout(timeout time.Duration) *Command { - if timeout > 0 { - c.opts.Timeout = timeout - } + c.opts.Timeout = timeout return c } func (c *Command) WithStdout(stdout io.Writer) *Command { - if stdout != nil { - c.opts.Stdout = stdout - } + c.opts.Stdout = stdout return c } func (c *Command) WithStderr(stderr io.Writer) *Command { - if stderr != nil { - c.opts.Stderr = stderr - } + c.opts.Stderr = stderr return c } func (c *Command) WithStdin(stdin io.Reader) *Command { - if stdin != nil { - c.opts.Stdin = stdin - } + c.opts.Stdin = stdin return c } func (c *Command) WithPipelineFunc(f func(context.Context, context.CancelFunc) error) *Command { - if f != nil { - c.opts.PipelineFunc = f - } + c.opts.PipelineFunc = f return c } @@ -323,8 +306,22 @@ func (c *Command) WithUseContextTimeout(useContextTimeout bool) *Command { return c } -func (c *Command) WithLogSkipStep(stepSkip int) *Command { - c.opts.LogSkip += stepSkip +// WithParentCallerInfo can be used to set the caller info (usually function name) of the parent function of the caller. +// For most cases, "Run" family functions can get its caller info automatically +// But if you need to call "Run" family functions in a wrapper function: "FeatureFunc -> GeneralWrapperFunc -> RunXxx", +// then you can to call this function in GeneralWrapperFunc to set the caller info of FeatureFunc. +func (c *Command) WithParentCallerInfo(optInfo ...string) *Command { + if len(optInfo) > 0 { + c.opts.callerInfo = optInfo[0] + return c + } + skip := 1 /*parent "wrap/run" functions*/ + 1 /*this function*/ + callerFuncName := util.CallerFuncName(skip) + callerInfo := callerFuncName + if pos := strings.LastIndex(callerInfo, "/"); pos >= 0 { + callerInfo = callerInfo[pos+1:] + } + c.opts.callerInfo = callerInfo return c } @@ -342,17 +339,16 @@ func (c *Command) Run(ctx context.Context) error { } cmdLogString := c.LogString() - callerInfo := util.CallerFuncName(1 /* util */ + 1 /* this */ + c.opts.LogSkip /* parent */) - if pos := strings.LastIndex(callerInfo, "/"); pos >= 0 { - callerInfo = callerInfo[pos+1:] + if c.opts.callerInfo == "" { + c.WithParentCallerInfo() } // these logs are for debugging purposes only, so no guarantee of correctness or stability - desc := fmt.Sprintf("git.Run(by:%s, repo:%s): %s", callerInfo, logArgSanitize(c.opts.Dir), cmdLogString) + desc := fmt.Sprintf("git.Run(by:%s, repo:%s): %s", c.opts.callerInfo, logArgSanitize(c.opts.Dir), cmdLogString) log.Debug("git.Command: %s", desc) _, span := gtprof.GetTracer().Start(ctx, gtprof.TraceSpanGitRun) defer span.End() - span.SetAttributeString(gtprof.TraceAttrFuncCaller, callerInfo) + span.SetAttributeString(gtprof.TraceAttrFuncCaller, c.opts.callerInfo) span.SetAttributeString(gtprof.TraceAttrGitCommand, cmdLogString) var cancel context.CancelFunc @@ -457,7 +453,7 @@ func IsErrorExitCode(err error, code int) bool { // RunStdString runs the command and returns stdout/stderr as string. and store stderr to returned error (err combined with stderr). func (c *Command) RunStdString(ctx context.Context) (stdout, stderr string, runErr RunStdError) { - stdoutBytes, stderrBytes, err := c.WithLogSkipStep(1).RunStdBytes(ctx) + stdoutBytes, stderrBytes, err := c.WithParentCallerInfo().RunStdBytes(ctx) stdout = util.UnsafeBytesToString(stdoutBytes) stderr = util.UnsafeBytesToString(stderrBytes) if err != nil { @@ -477,7 +473,7 @@ func (c *Command) RunStdBytes(ctx context.Context) (stdout, stderr []byte, runEr stdoutBuf := &bytes.Buffer{} stderrBuf := &bytes.Buffer{} - err := c.WithLogSkipStep(1). + err := c.WithParentCallerInfo(). WithStdout(stdoutBuf). WithStderr(stderrBuf). Run(ctx) diff --git a/modules/gitrepo/command.go b/modules/gitrepo/command.go index 9dc111fb7ff68..d4cb6093fcb14 100644 --- a/modules/gitrepo/command.go +++ b/modules/gitrepo/command.go @@ -10,16 +10,14 @@ import ( ) func RunCmd(ctx context.Context, repo Repository, cmd *gitcmd.Command) error { - return cmd.WithDir(repoPath(repo)). - WithLogSkipStep(1). - Run(ctx) + return cmd.WithDir(repoPath(repo)).WithParentCallerInfo().Run(ctx) } func RunCmdString(ctx context.Context, repo Repository, cmd *gitcmd.Command) (string, error) { - res, _, err := cmd.WithDir(repoPath(repo)).WithLogSkipStep(1).RunStdString(ctx) + res, _, err := cmd.WithDir(repoPath(repo)).WithParentCallerInfo().RunStdString(ctx) return res, err } func RunCmdBytes(ctx context.Context, repo Repository, cmd *gitcmd.Command) ([]byte, []byte, error) { - return cmd.WithDir(repoPath(repo)).WithLogSkipStep(1).RunStdBytes(ctx) + return cmd.WithDir(repoPath(repo)).WithParentCallerInfo().RunStdBytes(ctx) } diff --git a/modules/util/runtime.go b/modules/util/runtime.go index 91ec3c869c35f..2acdbeeb63524 100644 --- a/modules/util/runtime.go +++ b/modules/util/runtime.go @@ -5,9 +5,13 @@ package util import "runtime" -func CallerFuncName(skip int) string { +func CallerFuncName(optSkipParent ...int) string { pc := make([]uintptr, 1) - runtime.Callers(skip+1, pc) + skipParent := 0 + if len(optSkipParent) > 0 { + skipParent = optSkipParent[0] + } + runtime.Callers(skipParent+1 /*this*/ +1 /*runtime*/, pc) funcName := runtime.FuncForPC(pc[0]).Name() return funcName } diff --git a/modules/util/runtime_test.go b/modules/util/runtime_test.go index 01dd034ceabc9..ea6eb639d1be6 100644 --- a/modules/util/runtime_test.go +++ b/modules/util/runtime_test.go @@ -11,7 +11,7 @@ import ( ) func TestCallerFuncName(t *testing.T) { - s := CallerFuncName(1) + s := CallerFuncName() assert.Equal(t, "code.gitea.io/gitea/modules/util.TestCallerFuncName", s) } @@ -26,7 +26,7 @@ func BenchmarkCallerFuncName(b *testing.B) { // It is almost as fast as fmt.Sprintf b.Run("caller", func(b *testing.B) { for b.Loop() { - CallerFuncName(1) + CallerFuncName() } }) } diff --git a/services/pull/merge_prepare.go b/services/pull/merge_prepare.go index 9785bc91eaefd..375cab386e639 100644 --- a/services/pull/merge_prepare.go +++ b/services/pull/merge_prepare.go @@ -37,7 +37,7 @@ func (ctx *mergeContext) WithCmd(cmd *gitcmd.Command) *gitcmd.Command { ctx.errbuf.Reset() return cmd.WithEnv(ctx.env). WithDir(ctx.tmpBasePath). - WithLogSkipStep(1). + WithParentCallerInfo(). WithStdout(ctx.outbuf). WithStderr(ctx.errbuf) } From 7ff3463e6941738c9c48c49f3936d2d15f08486f Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 5 Oct 2025 13:27:43 +0800 Subject: [PATCH 12/14] fix --- modules/git/gitcmd/command.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/git/gitcmd/command.go b/modules/git/gitcmd/command.go index 87f5e4d8c3374..3f581eb6eeb58 100644 --- a/modules/git/gitcmd/command.go +++ b/modules/git/gitcmd/command.go @@ -472,14 +472,13 @@ func (c *Command) RunStdBytes(ctx context.Context) (stdout, stderr []byte, runEr stdoutBuf := &bytes.Buffer{} stderrBuf := &bytes.Buffer{} - err := c.WithParentCallerInfo(). WithStdout(stdoutBuf). WithStderr(stderrBuf). Run(ctx) if err != nil { - return nil, stderr, &runStdError{err: err, stderr: util.UnsafeBytesToString(stderr)} + return nil, stderrBuf.Bytes(), &runStdError{err: err, stderr: util.UnsafeBytesToString(stderrBuf.Bytes())} } // even if there is no err, there could still be some stderr output - return stdoutBuf.Bytes(), stderr, nil + return stdoutBuf.Bytes(), stderrBuf.Bytes(), nil } From 2de26564eb347ff70bb48aa979488e4ff8043821 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 5 Oct 2025 15:02:48 +0800 Subject: [PATCH 13/14] fix --- modules/git/gitcmd/command.go | 16 +++---- modules/git/gitcmd/command_test.go | 70 +++++++++++++++++++----------- 2 files changed, 52 insertions(+), 34 deletions(-) diff --git a/modules/git/gitcmd/command.go b/modules/git/gitcmd/command.go index 3f581eb6eeb58..5740b49d29c70 100644 --- a/modules/git/gitcmd/command.go +++ b/modules/git/gitcmd/command.go @@ -453,18 +453,16 @@ func IsErrorExitCode(err error, code int) bool { // RunStdString runs the command and returns stdout/stderr as string. and store stderr to returned error (err combined with stderr). func (c *Command) RunStdString(ctx context.Context) (stdout, stderr string, runErr RunStdError) { - stdoutBytes, stderrBytes, err := c.WithParentCallerInfo().RunStdBytes(ctx) - stdout = util.UnsafeBytesToString(stdoutBytes) - stderr = util.UnsafeBytesToString(stderrBytes) - if err != nil { - return stdout, stderr, &runStdError{err: err, stderr: stderr} - } - // even if there is no err, there could still be some stderr output, so we just return stdout/stderr as they are - return stdout, stderr, nil + stdoutBytes, stderrBytes, runErr := c.WithParentCallerInfo().runStdBytes(ctx) + return util.UnsafeBytesToString(stdoutBytes), util.UnsafeBytesToString(stderrBytes), runErr } // RunStdBytes runs the command and returns stdout/stderr as bytes. and store stderr to returned error (err combined with stderr). -func (c *Command) RunStdBytes(ctx context.Context) (stdout, stderr []byte, runErr RunStdError) { +func (c *Command) RunStdBytes(ctx context.Context) ( /*stdout*/ []byte /*stderr*/, []byte /*runErr*/, RunStdError) { + return c.WithParentCallerInfo().runStdBytes(ctx) +} + +func (c *Command) runStdBytes(ctx context.Context) ( /*stdout*/ []byte /*stderr*/, []byte /*runErr*/, RunStdError) { if c.opts.Stdout != nil || c.opts.Stderr != nil { // we must panic here, otherwise there would be bugs if developers set Stdin/Stderr by mistake, and it would be very difficult to debug panic("stdout and stderr field must be nil when using RunStdBytes") diff --git a/modules/git/gitcmd/command_test.go b/modules/git/gitcmd/command_test.go index d61ef1a4fd083..b5d5163b629a3 100644 --- a/modules/git/gitcmd/command_test.go +++ b/modules/git/gitcmd/command_test.go @@ -23,38 +23,58 @@ func TestMain(m *testing.M) { defer cleanup() setting.Git.HomePath = gitHomePath + os.Exit(m.Run()) } func TestRunWithContextStd(t *testing.T) { - cmd := NewCommand("--version") - stdout, stderr, err := cmd.RunStdString(t.Context()) - assert.NoError(t, err) - assert.Empty(t, stderr) - assert.Contains(t, stdout, "git version") - - cmd = NewCommand("--no-such-arg") - stdout, stderr, err = cmd.RunStdString(t.Context()) - if assert.Error(t, err) { - assert.Equal(t, stderr, err.Stderr()) - assert.Contains(t, err.Stderr(), "unknown option:") - assert.Contains(t, err.Error(), "exit status 129 - unknown option:") - assert.Empty(t, stdout) + { + cmd := NewCommand("--version") + stdout, stderr, err := cmd.RunStdString(t.Context()) + assert.NoError(t, err) + assert.Empty(t, stderr) + assert.Contains(t, stdout, "git version") } - cmd = NewCommand() - cmd.AddDynamicArguments("-test") - assert.ErrorIs(t, cmd.Run(t.Context()), ErrBrokenCommand) + { + cmd := NewCommand("ls-tree", "no-such") + stdout, stderr, err := cmd.RunStdString(t.Context()) + if assert.Error(t, err) { + assert.Equal(t, stderr, err.Stderr()) + assert.Equal(t, "fatal: Not a valid object name no-such\n", err.Stderr()) + assert.Equal(t, "exit status 128 - fatal: Not a valid object name no-such\n", err.Error()) + assert.Empty(t, stdout) + } + } + + { + cmd := NewCommand("ls-tree", "no-such") + stdout, stderr, err := cmd.RunStdBytes(t.Context()) + if assert.Error(t, err) { + assert.Equal(t, string(stderr), err.Stderr()) + assert.Equal(t, "fatal: Not a valid object name no-such\n", err.Stderr()) + assert.Equal(t, "exit status 128 - fatal: Not a valid object name no-such\n", err.Error()) + assert.Empty(t, stdout) + } + } - cmd = NewCommand() - cmd.AddDynamicArguments("--test") - assert.ErrorIs(t, cmd.Run(t.Context()), ErrBrokenCommand) + { + cmd := NewCommand() + cmd.AddDynamicArguments("-test") + assert.ErrorIs(t, cmd.Run(t.Context()), ErrBrokenCommand) - subCmd := "version" - cmd = NewCommand().AddDynamicArguments(subCmd) // for test purpose only, the sub-command should never be dynamic for production - stdout, stderr, err = cmd.RunStdString(t.Context()) - assert.NoError(t, err) - assert.Empty(t, stderr) - assert.Contains(t, stdout, "git version") + cmd = NewCommand() + cmd.AddDynamicArguments("--test") + assert.ErrorIs(t, cmd.Run(t.Context()), ErrBrokenCommand) + } + + { + subCmd := "version" + cmd := NewCommand().AddDynamicArguments(subCmd) // for test purpose only, the sub-command should never be dynamic for production + stdout, stderr, err := cmd.RunStdString(t.Context()) + assert.NoError(t, err) + assert.Empty(t, stderr) + assert.Contains(t, stdout, "git version") + } } func TestGitArgument(t *testing.T) { From 0e834b0a9001b5a90b90a23548d99ce562b7f784 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 5 Oct 2025 15:34:03 +0800 Subject: [PATCH 14/14] fix --- modules/git/gitcmd/command.go | 12 +++++++++--- modules/git/gitcmd/command_test.go | 2 ++ services/pull/merge.go | 6 +++--- services/pull/merge_prepare.go | 12 ++++++------ services/pull/merge_rebase.go | 2 +- services/pull/merge_squash.go | 2 +- services/pull/temp_repo.go | 12 ++++++------ 7 files changed, 28 insertions(+), 20 deletions(-) diff --git a/modules/git/gitcmd/command.go b/modules/git/gitcmd/command.go index 5740b49d29c70..ff2827bd6c47d 100644 --- a/modules/git/gitcmd/command.go +++ b/modules/git/gitcmd/command.go @@ -310,7 +310,11 @@ func (c *Command) WithUseContextTimeout(useContextTimeout bool) *Command { // For most cases, "Run" family functions can get its caller info automatically // But if you need to call "Run" family functions in a wrapper function: "FeatureFunc -> GeneralWrapperFunc -> RunXxx", // then you can to call this function in GeneralWrapperFunc to set the caller info of FeatureFunc. +// The caller info can only be set once. func (c *Command) WithParentCallerInfo(optInfo ...string) *Command { + if c.opts.callerInfo != "" { + return c + } if len(optInfo) > 0 { c.opts.callerInfo = optInfo[0] return c @@ -428,7 +432,8 @@ type runStdError struct { } func (r *runStdError) Error() string { - // the stderr must be in the returned error text, some code only checks `strings.Contains(err.Error(), "git error")` + // FIXME: GIT-CMD-STDERR: it is a bad design, the stderr should not be put in the error message + // But a lof of code only checks `strings.Contains(err.Error(), "git error")` if r.errMsg == "" { r.errMsg = ConcatenateError(r.err, r.stderr).Error() } @@ -458,7 +463,7 @@ func (c *Command) RunStdString(ctx context.Context) (stdout, stderr string, runE } // RunStdBytes runs the command and returns stdout/stderr as bytes. and store stderr to returned error (err combined with stderr). -func (c *Command) RunStdBytes(ctx context.Context) ( /*stdout*/ []byte /*stderr*/, []byte /*runErr*/, RunStdError) { +func (c *Command) RunStdBytes(ctx context.Context) (stdout, stderr []byte, runErr RunStdError) { return c.WithParentCallerInfo().runStdBytes(ctx) } @@ -467,7 +472,6 @@ func (c *Command) runStdBytes(ctx context.Context) ( /*stdout*/ []byte /*stderr* // we must panic here, otherwise there would be bugs if developers set Stdin/Stderr by mistake, and it would be very difficult to debug panic("stdout and stderr field must be nil when using RunStdBytes") } - stdoutBuf := &bytes.Buffer{} stderrBuf := &bytes.Buffer{} err := c.WithParentCallerInfo(). @@ -475,6 +479,8 @@ func (c *Command) runStdBytes(ctx context.Context) ( /*stdout*/ []byte /*stderr* WithStderr(stderrBuf). Run(ctx) if err != nil { + // FIXME: GIT-CMD-STDERR: it is a bad design, the stderr should not be put in the error message + // But a lot of code depends on it, so we have to keep this behavior return nil, stderrBuf.Bytes(), &runStdError{err: err, stderr: util.UnsafeBytesToString(stderrBuf.Bytes())} } // even if there is no err, there could still be some stderr output diff --git a/modules/git/gitcmd/command_test.go b/modules/git/gitcmd/command_test.go index b5d5163b629a3..1ba8b2e3e4ab6 100644 --- a/modules/git/gitcmd/command_test.go +++ b/modules/git/gitcmd/command_test.go @@ -41,6 +41,7 @@ func TestRunWithContextStd(t *testing.T) { if assert.Error(t, err) { assert.Equal(t, stderr, err.Stderr()) assert.Equal(t, "fatal: Not a valid object name no-such\n", err.Stderr()) + // FIXME: GIT-CMD-STDERR: it is a bad design, the stderr should not be put in the error message assert.Equal(t, "exit status 128 - fatal: Not a valid object name no-such\n", err.Error()) assert.Empty(t, stdout) } @@ -52,6 +53,7 @@ func TestRunWithContextStd(t *testing.T) { if assert.Error(t, err) { assert.Equal(t, string(stderr), err.Stderr()) assert.Equal(t, "fatal: Not a valid object name no-such\n", err.Stderr()) + // FIXME: GIT-CMD-STDERR: it is a bad design, the stderr should not be put in the error message assert.Equal(t, "exit status 128 - fatal: Not a valid object name no-such\n", err.Error()) assert.Empty(t, stdout) } diff --git a/services/pull/merge.go b/services/pull/merge.go index 8acafe476096a..f1ad8fa17d9cc 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -406,7 +406,7 @@ func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *use // Push back to upstream. // This cause an api call to "/api/internal/hook/post-receive/...", // If it's merge, all db transaction and operations should be there but not here to prevent deadlock. - if err := mergeCtx.WithCmd(pushCmd).Run(ctx); err != nil { + if err := mergeCtx.PrepareGitCmd(pushCmd).Run(ctx); err != nil { if strings.Contains(mergeCtx.errbuf.String(), "non-fast-forward") { return "", &git.ErrPushOutOfDate{ StdOut: mergeCtx.outbuf.String(), @@ -440,7 +440,7 @@ func commitAndSignNoAuthor(ctx *mergeContext, message string) error { } cmdCommit.AddOptionFormat("-S%s", ctx.signKey.KeyID) } - if err := ctx.WithCmd(cmdCommit).Run(ctx); err != nil { + if err := ctx.PrepareGitCmd(cmdCommit).Run(ctx); err != nil { log.Error("git commit %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) return fmt.Errorf("git commit %v: %w\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) } @@ -501,7 +501,7 @@ func (err ErrMergeDivergingFastForwardOnly) Error() string { } func runMergeCommand(ctx *mergeContext, mergeStyle repo_model.MergeStyle, cmd *gitcmd.Command) error { - if err := ctx.WithCmd(cmd).Run(ctx); err != nil { + if err := ctx.PrepareGitCmd(cmd).Run(ctx); err != nil { // Merge will leave a MERGE_HEAD file in the .git folder if there is a conflict if _, statErr := os.Stat(filepath.Join(ctx.tmpBasePath, ".git", "MERGE_HEAD")); statErr == nil { // We have a merge conflict error diff --git a/services/pull/merge_prepare.go b/services/pull/merge_prepare.go index 375cab386e639..7dedf0d2a017a 100644 --- a/services/pull/merge_prepare.go +++ b/services/pull/merge_prepare.go @@ -32,7 +32,7 @@ type mergeContext struct { env []string } -func (ctx *mergeContext) WithCmd(cmd *gitcmd.Command) *gitcmd.Command { +func (ctx *mergeContext) PrepareGitCmd(cmd *gitcmd.Command) *gitcmd.Command { ctx.outbuf.Reset() ctx.errbuf.Reset() return cmd.WithEnv(ctx.env). @@ -73,7 +73,7 @@ func createTemporaryRepoForMerge(ctx context.Context, pr *issues_model.PullReque } if expectedHeadCommitID != "" { - trackingCommitID, _, err := mergeCtx.WithCmd(gitcmd.NewCommand("show-ref", "--hash").AddDynamicArguments(git.BranchPrefix + trackingBranch)).RunStdString(ctx) + trackingCommitID, _, err := mergeCtx.PrepareGitCmd(gitcmd.NewCommand("show-ref", "--hash").AddDynamicArguments(git.BranchPrefix + trackingBranch)).RunStdString(ctx) if err != nil { defer cancel() log.Error("failed to get sha of head branch in %-v: show-ref[%s] --hash refs/heads/tracking: %v", mergeCtx.pr, mergeCtx.tmpBasePath, err) @@ -151,7 +151,7 @@ func prepareTemporaryRepoForMerge(ctx *mergeContext) error { } setConfig := func(key, value string) error { - if err := ctx.WithCmd(gitcmd.NewCommand("config", "--local").AddDynamicArguments(key, value)). + if err := ctx.PrepareGitCmd(gitcmd.NewCommand("config", "--local").AddDynamicArguments(key, value)). Run(ctx); err != nil { log.Error("git config [%s -> %q]: %v\n%s\n%s", key, value, err, ctx.outbuf.String(), ctx.errbuf.String()) return fmt.Errorf("git config [%s -> %q]: %w\n%s\n%s", key, value, err, ctx.outbuf.String(), ctx.errbuf.String()) @@ -184,7 +184,7 @@ func prepareTemporaryRepoForMerge(ctx *mergeContext) error { } // Read base branch index - if err := ctx.WithCmd(gitcmd.NewCommand("read-tree", "HEAD")). + if err := ctx.PrepareGitCmd(gitcmd.NewCommand("read-tree", "HEAD")). Run(ctx); err != nil { log.Error("git read-tree HEAD: %v\n%s\n%s", err, ctx.outbuf.String(), ctx.errbuf.String()) return fmt.Errorf("Unable to read base branch in to the index: %w\n%s\n%s", err, ctx.outbuf.String(), ctx.errbuf.String()) @@ -272,7 +272,7 @@ func (err ErrRebaseConflicts) Error() string { // if there is a conflict it will return an ErrRebaseConflicts func rebaseTrackingOnToBase(ctx *mergeContext, mergeStyle repo_model.MergeStyle) error { // Checkout head branch - if err := ctx.WithCmd(gitcmd.NewCommand("checkout", "-b").AddDynamicArguments(stagingBranch, trackingBranch)). + if err := ctx.PrepareGitCmd(gitcmd.NewCommand("checkout", "-b").AddDynamicArguments(stagingBranch, trackingBranch)). Run(ctx); err != nil { return fmt.Errorf("unable to git checkout tracking as staging in temp repo for %v: %w\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) } @@ -280,7 +280,7 @@ func rebaseTrackingOnToBase(ctx *mergeContext, mergeStyle repo_model.MergeStyle) ctx.errbuf.Reset() // Rebase before merging - if err := ctx.WithCmd(gitcmd.NewCommand("rebase").AddDynamicArguments(baseBranch)). + if err := ctx.PrepareGitCmd(gitcmd.NewCommand("rebase").AddDynamicArguments(baseBranch)). Run(ctx); err != nil { // Rebase will leave a REBASE_HEAD file in .git if there is a conflict if _, statErr := os.Stat(filepath.Join(ctx.tmpBasePath, ".git", "REBASE_HEAD")); statErr == nil { diff --git a/services/pull/merge_rebase.go b/services/pull/merge_rebase.go index 7e0b74c93b033..0fa4fd00f6e57 100644 --- a/services/pull/merge_rebase.go +++ b/services/pull/merge_rebase.go @@ -109,7 +109,7 @@ func doMergeStyleRebase(ctx *mergeContext, mergeStyle repo_model.MergeStyle, mes } // Checkout base branch again - if err := ctx.WithCmd(gitcmd.NewCommand("checkout").AddDynamicArguments(baseBranch)). + if err := ctx.PrepareGitCmd(gitcmd.NewCommand("checkout").AddDynamicArguments(baseBranch)). Run(ctx); err != nil { log.Error("git checkout base prior to merge post staging rebase %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) return fmt.Errorf("git checkout base prior to merge post staging rebase %v: %w\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) diff --git a/services/pull/merge_squash.go b/services/pull/merge_squash.go index 426d7e1f39511..84bd67c44503b 100644 --- a/services/pull/merge_squash.go +++ b/services/pull/merge_squash.go @@ -80,7 +80,7 @@ func doMergeStyleSquash(ctx *mergeContext, message string) error { } cmdCommit.AddOptionFormat("-S%s", ctx.signKey.KeyID) } - if err := ctx.WithCmd(cmdCommit).Run(ctx); err != nil { + if err := ctx.PrepareGitCmd(cmdCommit).Run(ctx); err != nil { log.Error("git commit %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) return fmt.Errorf("git commit [%s:%s -> %s:%s]: %w\n%s\n%s", ctx.pr.HeadRepo.FullName(), ctx.pr.HeadBranch, ctx.pr.BaseRepo.FullName(), ctx.pr.BaseBranch, err, ctx.outbuf.String(), ctx.errbuf.String()) } diff --git a/services/pull/temp_repo.go b/services/pull/temp_repo.go index 82eb9e2ce7a5a..597a4aa48cf5a 100644 --- a/services/pull/temp_repo.go +++ b/services/pull/temp_repo.go @@ -37,7 +37,7 @@ type prTmpRepoContext struct { errbuf *strings.Builder // any use should be preceded by a Reset and preferably after use } -func (ctx *prTmpRepoContext) WithCmd(cmd *gitcmd.Command) *gitcmd.Command { +func (ctx *prTmpRepoContext) PrepareGitCmd(cmd *gitcmd.Command) *gitcmd.Command { ctx.outbuf.Reset() ctx.errbuf.Reset() return cmd.WithDir(ctx.tmpBasePath). @@ -130,14 +130,14 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) return nil, nil, fmt.Errorf("Unable to add base repository to temporary repo [%s -> tmpBasePath]: %w", pr.BaseRepo.FullName(), err) } - if err := prCtx.WithCmd(gitcmd.NewCommand("remote", "add", "-t").AddDynamicArguments(pr.BaseBranch).AddArguments("-m").AddDynamicArguments(pr.BaseBranch).AddDynamicArguments("origin", baseRepoPath)). + if err := prCtx.PrepareGitCmd(gitcmd.NewCommand("remote", "add", "-t").AddDynamicArguments(pr.BaseBranch).AddArguments("-m").AddDynamicArguments(pr.BaseBranch).AddDynamicArguments("origin", baseRepoPath)). Run(ctx); err != nil { 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()) cancel() 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()) } - if err := prCtx.WithCmd(gitcmd.NewCommand("fetch", "origin").AddArguments(fetchArgs...). + if err := prCtx.PrepareGitCmd(gitcmd.NewCommand("fetch", "origin").AddArguments(fetchArgs...). AddDashesAndList(git.BranchPrefix+pr.BaseBranch+":"+git.BranchPrefix+baseBranch, git.BranchPrefix+pr.BaseBranch+":"+git.BranchPrefix+"original_"+baseBranch)). Run(ctx); err != nil { 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()) @@ -145,7 +145,7 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) 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()) } - if err := prCtx.WithCmd(gitcmd.NewCommand("symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseBranch)). + if err := prCtx.PrepareGitCmd(gitcmd.NewCommand("symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseBranch)). Run(ctx); err != nil { 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()) cancel() @@ -158,7 +158,7 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) return nil, nil, fmt.Errorf("Unable to add head base repository to temporary repo [%s -> tmpBasePath]: %w", pr.HeadRepo.FullName(), err) } - if err := prCtx.WithCmd(gitcmd.NewCommand("remote", "add").AddDynamicArguments(remoteRepoName, headRepoPath)). + if err := prCtx.PrepareGitCmd(gitcmd.NewCommand("remote", "add").AddDynamicArguments(remoteRepoName, headRepoPath)). Run(ctx); err != nil { 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()) cancel() @@ -176,7 +176,7 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) } else { headBranch = pr.GetGitHeadRefName() } - if err := prCtx.WithCmd(gitcmd.NewCommand("fetch").AddArguments(fetchArgs...).AddDynamicArguments(remoteRepoName, headBranch+":"+trackingBranch)). + if err := prCtx.PrepareGitCmd(gitcmd.NewCommand("fetch").AddArguments(fetchArgs...).AddDynamicArguments(remoteRepoName, headBranch+":"+trackingBranch)). Run(ctx); err != nil { cancel() if !gitrepo.IsBranchExist(ctx, pr.HeadRepo, pr.HeadBranch) {