This PR follows #21535 (and replace #22592) ## Review without space diff https://github.com/go-gitea/gitea/pull/22678/files?diff=split&w=1 ## Purpose of this PR 1. Make git module command completely safe (risky user inputs won't be passed as argument option anymore) 2. Avoid low-level mistakes like https://github.com/go-gitea/gitea/pull/22098#discussion_r1045234918 3. Remove deprecated and dirty `CmdArgCheck` function, hide the `CmdArg` type 4. Simplify code when using git command ## The main idea of this PR * Move the `git.CmdArg` to the `internal` package, then no other package except `git` could use it. Then developers could never do `AddArguments(git.CmdArg(userInput))` any more. * Introduce `git.ToTrustedCmdArgs`, it's for user-provided and already trusted arguments. It's only used in a few cases, for example: use git arguments from config file, help unit test with some arguments. * Introduce `AddOptionValues` and `AddOptionFormat`, they make code more clear and simple: * Before: `AddArguments("-m").AddDynamicArguments(message)` * After: `AddOptionValues("-m", message)` * - * Before: `AddArguments(git.CmdArg(fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email)))` * After: `AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)` ## FAQ ### Why these changes were not done in #21535 ? #21535 is mainly a search&replace, it did its best to not change too much logic. Making the framework better needs a lot of changes, so this separate PR is needed as the second step. ### The naming of `AddOptionXxx` According to git's manual, the `--xxx` part is called `option`. ### How can it guarantee that `internal.CmdArg` won't be not misused? Go's specification guarantees that. Trying to access other package's internal package causes compilation error. And, `golangci-lint` also denies the git/internal package. Only the `git/command.go` can use it carefully. ### There is still a `ToTrustedCmdArgs`, will it still allow developers to make mistakes and pass untrusted arguments? Generally speaking, no. Because when using `ToTrustedCmdArgs`, the code will be very complex (see the changes for examples). Then developers and reviewers can know that something might be unreasonable. ### Why there was a `CmdArgCheck` and why it's removed? At the moment of #21535, to reduce unnecessary changes, `CmdArgCheck` was introduced as a hacky patch. Now, almost all code could be written as `cmd := NewCommand(); cmd.AddXxx(...)`, then there is no need for `CmdArgCheck` anymore. ### Why many codes for `signArg == ""` is deleted? Because in the old code, `signArg` could never be empty string, it's either `-S[key-id]` or `--no-gpg-sign`. So the `signArg == ""` is just dead code. --------- Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>tags/v1.19.0-rc0
@@ -84,6 +84,7 @@ linters-settings: | |||
- github.com/unknwon/com: "use gitea's util and replacements" | |||
- io/ioutil: "use os or io instead" | |||
- golang.org/x/exp: "it's experimental and unreliable." | |||
- code.gitea.io/gitea/modules/git/internal: "do not use the internal package, use AddXxx function instead" | |||
issues: | |||
max-issues-per-linter: 0 |
@@ -16,14 +16,20 @@ import ( | |||
"time" | |||
"unsafe" | |||
"code.gitea.io/gitea/modules/git/internal" //nolint:depguard // only this file can use the internal type CmdArg, other files and packages should use AddXxx functions | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/process" | |||
"code.gitea.io/gitea/modules/util" | |||
) | |||
// TrustedCmdArgs returns the trusted arguments for git command. | |||
// It's mainly for passing user-provided and trusted arguments to git command | |||
// In most cases, it shouldn't be used. Use AddXxx function instead | |||
type TrustedCmdArgs []internal.CmdArg | |||
var ( | |||
// globalCommandArgs global command args for external package setting | |||
globalCommandArgs []CmdArg | |||
globalCommandArgs TrustedCmdArgs | |||
// defaultCommandExecutionTimeout default command execution timeout duration | |||
defaultCommandExecutionTimeout = 360 * time.Second | |||
@@ -42,8 +48,6 @@ type Command struct { | |||
brokenArgs []string | |||
} | |||
type CmdArg string | |||
func (c *Command) String() string { | |||
if len(c.args) == 0 { | |||
return c.name | |||
@@ -53,7 +57,7 @@ func (c *Command) String() string { | |||
// NewCommand creates and returns a new Git Command based on given command and arguments. | |||
// Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead. | |||
func NewCommand(ctx context.Context, args ...CmdArg) *Command { | |||
func NewCommand(ctx context.Context, args ...internal.CmdArg) *Command { | |||
// Make an explicit copy of globalCommandArgs, otherwise append might overwrite it | |||
cargs := make([]string, 0, len(globalCommandArgs)+len(args)) | |||
for _, arg := range globalCommandArgs { | |||
@@ -70,15 +74,9 @@ func NewCommand(ctx context.Context, args ...CmdArg) *Command { | |||
} | |||
} | |||
// NewCommandNoGlobals creates and returns a new Git Command based on given command and arguments only with the specify args and don't care global command args | |||
// Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead. | |||
func NewCommandNoGlobals(args ...CmdArg) *Command { | |||
return NewCommandContextNoGlobals(DefaultContext, args...) | |||
} | |||
// NewCommandContextNoGlobals creates and returns a new Git Command based on given command and arguments only with the specify args and don't care global command args | |||
// Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead. | |||
func NewCommandContextNoGlobals(ctx context.Context, args ...CmdArg) *Command { | |||
func NewCommandContextNoGlobals(ctx context.Context, args ...internal.CmdArg) *Command { | |||
cargs := make([]string, 0, len(args)) | |||
for _, arg := range args { | |||
cargs = append(cargs, string(arg)) | |||
@@ -96,27 +94,70 @@ func (c *Command) SetParentContext(ctx context.Context) *Command { | |||
return c | |||
} | |||
// SetDescription sets the description for this command which be returned on | |||
// c.String() | |||
// SetDescription sets the description for this command which be returned on c.String() | |||
func (c *Command) SetDescription(desc string) *Command { | |||
c.desc = desc | |||
return c | |||
} | |||
// AddArguments adds new git argument(s) to the command. Each argument must be safe to be trusted. | |||
// User-provided arguments should be passed to AddDynamicArguments instead. | |||
func (c *Command) AddArguments(args ...CmdArg) *Command { | |||
// isSafeArgumentValue checks if the argument is safe to be used as a value (not an option) | |||
func isSafeArgumentValue(s string) bool { | |||
return s == "" || s[0] != '-' | |||
} | |||
// isValidArgumentOption checks if the argument is a valid option (starting with '-'). | |||
// It doesn't check whether the option is supported or not | |||
func isValidArgumentOption(s string) bool { | |||
return s != "" && s[0] == '-' | |||
} | |||
// AddArguments adds new git arguments (option/value) to the command. It only accepts string literals, or trusted CmdArg. | |||
// Type CmdArg is in the internal package, so it can not be used outside of this package directly, | |||
// it makes sure that user-provided arguments won't cause RCE risks. | |||
// User-provided arguments should be passed by other AddXxx functions | |||
func (c *Command) AddArguments(args ...internal.CmdArg) *Command { | |||
for _, arg := range args { | |||
c.args = append(c.args, string(arg)) | |||
} | |||
return c | |||
} | |||
// AddDynamicArguments adds new dynamic argument(s) to the command. | |||
// The arguments may come from user input and can not be trusted, so no leading '-' is allowed to avoid passing options | |||
// AddOptionValues adds a new option with a list of non-option values | |||
// For example: AddOptionValues("--opt", val) means 2 arguments: {"--opt", val}. | |||
// The values are treated as dynamic argument values. It equals to: AddArguments("--opt") then AddDynamicArguments(val). | |||
func (c *Command) AddOptionValues(opt internal.CmdArg, args ...string) *Command { | |||
if !isValidArgumentOption(string(opt)) { | |||
c.brokenArgs = append(c.brokenArgs, string(opt)) | |||
return c | |||
} | |||
c.args = append(c.args, string(opt)) | |||
c.AddDynamicArguments(args...) | |||
return c | |||
} | |||
// AddOptionFormat adds a new option with a format string and arguments | |||
// For example: AddOptionFormat("--opt=%s %s", val1, val2) means 1 argument: {"--opt=val1 val2"}. | |||
func (c *Command) AddOptionFormat(opt string, args ...any) *Command { | |||
if !isValidArgumentOption(opt) { | |||
c.brokenArgs = append(c.brokenArgs, opt) | |||
return c | |||
} | |||
// a quick check to make sure the format string matches the number of arguments, to find low-level mistakes ASAP | |||
if strings.Count(strings.ReplaceAll(opt, "%%", ""), "%") != len(args) { | |||
c.brokenArgs = append(c.brokenArgs, opt) | |||
return c | |||
} | |||
s := fmt.Sprintf(opt, args...) | |||
c.args = append(c.args, s) | |||
return c | |||
} | |||
// AddDynamicArguments adds new dynamic argument values to the command. | |||
// The arguments may come from user input and can not be trusted, so no leading '-' is allowed to avoid passing options. | |||
// TODO: in the future, this function can be renamed to AddArgumentValues | |||
func (c *Command) AddDynamicArguments(args ...string) *Command { | |||
for _, arg := range args { | |||
if arg != "" && arg[0] == '-' { | |||
if !isSafeArgumentValue(arg) { | |||
c.brokenArgs = append(c.brokenArgs, arg) | |||
} | |||
} | |||
@@ -137,14 +178,14 @@ func (c *Command) AddDashesAndList(list ...string) *Command { | |||
return c | |||
} | |||
// CmdArgCheck checks whether the string is safe to be used as a dynamic argument. | |||
// It panics if the check fails. Usually it should not be used, it's just for refactoring purpose | |||
// deprecated | |||
func CmdArgCheck(s string) CmdArg { | |||
if s != "" && s[0] == '-' { | |||
panic("invalid git cmd argument: " + s) | |||
// ToTrustedCmdArgs converts a list of strings (trusted as argument) to TrustedCmdArgs | |||
// In most cases, it shouldn't be used. Use AddXxx function instead | |||
func ToTrustedCmdArgs(args []string) TrustedCmdArgs { | |||
ret := make(TrustedCmdArgs, len(args)) | |||
for i, arg := range args { | |||
ret[i] = internal.CmdArg(arg) | |||
} | |||
return CmdArg(s) | |||
return ret | |||
} | |||
// RunOpts represents parameters to run the command. If UseContextTimeout is specified, then Timeout is ignored. | |||
@@ -364,9 +405,9 @@ func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunS | |||
} | |||
// AllowLFSFiltersArgs return globalCommandArgs with lfs filter, it should only be used for tests | |||
func AllowLFSFiltersArgs() []CmdArg { | |||
func AllowLFSFiltersArgs() TrustedCmdArgs { | |||
// Now here we should explicitly allow lfs filters to run | |||
filteredLFSGlobalArgs := make([]CmdArg, len(globalCommandArgs)) | |||
filteredLFSGlobalArgs := make(TrustedCmdArgs, len(globalCommandArgs)) | |||
j := 0 | |||
for _, arg := range globalCommandArgs { | |||
if strings.Contains(string(arg), "lfs") { |
@@ -41,3 +41,14 @@ func TestRunWithContextStd(t *testing.T) { | |||
assert.Empty(t, stderr) | |||
assert.Contains(t, stdout, "git version") | |||
} | |||
func TestGitArgument(t *testing.T) { | |||
assert.True(t, isValidArgumentOption("-x")) | |||
assert.True(t, isValidArgumentOption("--xx")) | |||
assert.False(t, isValidArgumentOption("")) | |||
assert.False(t, isValidArgumentOption("x")) | |||
assert.True(t, isSafeArgumentValue("")) | |||
assert.True(t, isSafeArgumentValue("x")) | |||
assert.False(t, isSafeArgumentValue("-x")) | |||
} |
@@ -9,7 +9,6 @@ import ( | |||
"bytes" | |||
"context" | |||
"errors" | |||
"fmt" | |||
"io" | |||
"os/exec" | |||
"strconv" | |||
@@ -91,8 +90,8 @@ func AddChanges(repoPath string, all bool, files ...string) error { | |||
} | |||
// AddChangesWithArgs marks local changes to be ready for commit. | |||
func AddChangesWithArgs(repoPath string, globalArgs []CmdArg, all bool, files ...string) error { | |||
cmd := NewCommandNoGlobals(append(globalArgs, "add")...) | |||
func AddChangesWithArgs(repoPath string, globalArgs TrustedCmdArgs, all bool, files ...string) error { | |||
cmd := NewCommandContextNoGlobals(DefaultContext, globalArgs...).AddArguments("add") | |||
if all { | |||
cmd.AddArguments("--all") | |||
} | |||
@@ -111,17 +110,18 @@ type CommitChangesOptions struct { | |||
// CommitChanges commits local changes with given committer, author and message. | |||
// If author is nil, it will be the same as committer. | |||
func CommitChanges(repoPath string, opts CommitChangesOptions) error { | |||
cargs := make([]CmdArg, len(globalCommandArgs)) | |||
cargs := make(TrustedCmdArgs, len(globalCommandArgs)) | |||
copy(cargs, globalCommandArgs) | |||
return CommitChangesWithArgs(repoPath, cargs, opts) | |||
} | |||
// CommitChangesWithArgs commits local changes with given committer, author and message. | |||
// If author is nil, it will be the same as committer. | |||
func CommitChangesWithArgs(repoPath string, args []CmdArg, opts CommitChangesOptions) error { | |||
cmd := NewCommandNoGlobals(args...) | |||
func CommitChangesWithArgs(repoPath string, args TrustedCmdArgs, opts CommitChangesOptions) error { | |||
cmd := NewCommandContextNoGlobals(DefaultContext, args...) | |||
if opts.Committer != nil { | |||
cmd.AddArguments("-c", CmdArg("user.name="+opts.Committer.Name), "-c", CmdArg("user.email="+opts.Committer.Email)) | |||
cmd.AddOptionValues("-c", "user.name="+opts.Committer.Name) | |||
cmd.AddOptionValues("-c", "user.email="+opts.Committer.Email) | |||
} | |||
cmd.AddArguments("commit") | |||
@@ -129,9 +129,9 @@ func CommitChangesWithArgs(repoPath string, args []CmdArg, opts CommitChangesOpt | |||
opts.Author = opts.Committer | |||
} | |||
if opts.Author != nil { | |||
cmd.AddArguments(CmdArg(fmt.Sprintf("--author='%s <%s>'", opts.Author.Name, opts.Author.Email))) | |||
cmd.AddOptionFormat("--author='%s <%s>'", opts.Author.Name, opts.Author.Email) | |||
} | |||
cmd.AddArguments("-m").AddDynamicArguments(opts.Message) | |||
cmd.AddOptionValues("-m", opts.Message) | |||
_, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) | |||
// No stderr but exit status 1 means nothing to commit. |
@@ -383,6 +383,6 @@ func configUnsetAll(key, value string) error { | |||
} | |||
// Fsck verifies the connectivity and validity of the objects in the database | |||
func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args ...CmdArg) error { | |||
func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args TrustedCmdArgs) error { | |||
return NewCommand(ctx, "fsck").AddArguments(args...).Run(&RunOpts{Timeout: timeout, Dir: repoPath}) | |||
} |
@@ -0,0 +1,9 @@ | |||
// Copyright 2023 The Gitea Authors. All rights reserved. | |||
// SPDX-License-Identifier: MIT | |||
package internal | |||
// CmdArg represents a command argument for git command, and it will be used for the git command directly without any further processing. | |||
// In most cases, you should use the "AddXxx" functions to add arguments, but not use this type directly. | |||
// Casting a risky (user-provided) string to CmdArg would cause security issues if it's injected with a "--xxx" argument. | |||
type CmdArg string |
@@ -115,7 +115,7 @@ func Clone(ctx context.Context, from, to string, opts CloneRepoOptions) error { | |||
} | |||
// CloneWithArgs original repository to target path. | |||
func CloneWithArgs(ctx context.Context, args []CmdArg, from, to string, opts CloneRepoOptions) (err error) { | |||
func CloneWithArgs(ctx context.Context, args TrustedCmdArgs, from, to string, opts CloneRepoOptions) (err error) { | |||
toDir := path.Dir(to) | |||
if err = os.MkdirAll(toDir, os.ModePerm); err != nil { | |||
return err |
@@ -57,9 +57,9 @@ func (repo *Repository) CreateArchive(ctx context.Context, format ArchiveType, t | |||
cmd := NewCommand(ctx, "archive") | |||
if usePrefix { | |||
cmd.AddArguments(CmdArg("--prefix=" + filepath.Base(strings.TrimSuffix(repo.Path, ".git")) + "/")) | |||
cmd.AddOptionFormat("--prefix=%s", filepath.Base(strings.TrimSuffix(repo.Path, ".git"))+"/") | |||
} | |||
cmd.AddArguments(CmdArg("--format=" + format.String())) | |||
cmd.AddOptionFormat("--format=%s", format.String()) | |||
cmd.AddDynamicArguments(commitID) | |||
var stderr strings.Builder |
@@ -17,7 +17,7 @@ import ( | |||
type CheckAttributeOpts struct { | |||
CachedOnly bool | |||
AllAttributes bool | |||
Attributes []CmdArg | |||
Attributes []string | |||
Filenames []string | |||
IndexFile string | |||
WorkTree string | |||
@@ -48,7 +48,7 @@ func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[ | |||
} else { | |||
for _, attribute := range opts.Attributes { | |||
if attribute != "" { | |||
cmd.AddArguments(attribute) | |||
cmd.AddDynamicArguments(attribute) | |||
} | |||
} | |||
} | |||
@@ -95,7 +95,7 @@ func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[ | |||
// CheckAttributeReader provides a reader for check-attribute content that can be long running | |||
type CheckAttributeReader struct { | |||
// params | |||
Attributes []CmdArg | |||
Attributes []string | |||
Repo *Repository | |||
IndexFile string | |||
WorkTree string | |||
@@ -111,19 +111,6 @@ type CheckAttributeReader struct { | |||
// Init initializes the CheckAttributeReader | |||
func (c *CheckAttributeReader) Init(ctx context.Context) error { | |||
cmdArgs := []CmdArg{"check-attr", "--stdin", "-z"} | |||
if len(c.IndexFile) > 0 { | |||
cmdArgs = append(cmdArgs, "--cached") | |||
c.env = append(c.env, "GIT_INDEX_FILE="+c.IndexFile) | |||
} | |||
if len(c.WorkTree) > 0 { | |||
c.env = append(c.env, "GIT_WORK_TREE="+c.WorkTree) | |||
} | |||
c.env = append(c.env, "GIT_FLUSH=1") | |||
if len(c.Attributes) == 0 { | |||
lw := new(nulSeparatedAttributeWriter) | |||
lw.attributes = make(chan attributeTriple) | |||
@@ -134,11 +121,22 @@ func (c *CheckAttributeReader) Init(ctx context.Context) error { | |||
return fmt.Errorf("no provided Attributes to check") | |||
} | |||
cmdArgs = append(cmdArgs, c.Attributes...) | |||
cmdArgs = append(cmdArgs, "--") | |||
c.ctx, c.cancel = context.WithCancel(ctx) | |||
c.cmd = NewCommand(c.ctx, cmdArgs...) | |||
c.cmd = NewCommand(c.ctx, "check-attr", "--stdin", "-z") | |||
if len(c.IndexFile) > 0 { | |||
c.cmd.AddArguments("--cached") | |||
c.env = append(c.env, "GIT_INDEX_FILE="+c.IndexFile) | |||
} | |||
if len(c.WorkTree) > 0 { | |||
c.env = append(c.env, "GIT_WORK_TREE="+c.WorkTree) | |||
} | |||
c.env = append(c.env, "GIT_FLUSH=1") | |||
// The empty "--" comes from #16773 , and it seems unnecessary because nothing else would be added later. | |||
c.cmd.AddDynamicArguments(c.Attributes...).AddArguments("--") | |||
var err error | |||
@@ -294,7 +292,7 @@ func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeRe | |||
} | |||
checker := &CheckAttributeReader{ | |||
Attributes: []CmdArg{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language"}, | |||
Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language"}, | |||
Repo: repo, | |||
IndexFile: indexFilename, | |||
WorkTree: worktree, |
@@ -3,7 +3,9 @@ | |||
package git | |||
import "fmt" | |||
import ( | |||
"fmt" | |||
) | |||
// FileBlame return the Blame object of file | |||
func (repo *Repository) FileBlame(revision, path, file string) ([]byte, error) { | |||
@@ -14,8 +16,8 @@ func (repo *Repository) FileBlame(revision, path, file string) ([]byte, error) { | |||
// LineBlame returns the latest commit at the given line | |||
func (repo *Repository) LineBlame(revision, path, file string, line uint) (*Commit, error) { | |||
res, _, err := NewCommand(repo.Ctx, "blame"). | |||
AddArguments(CmdArg(fmt.Sprintf("-L %d,%d", line, line))). | |||
AddArguments("-p").AddDynamicArguments(revision). | |||
AddOptionFormat("-L %d,%d", line, line). | |||
AddOptionValues("-p", revision). | |||
AddDashesAndList(file).RunStdString(&RunOpts{Dir: path}) | |||
if err != nil { | |||
return nil, err |
@@ -50,8 +50,8 @@ func (repo *Repository) IsBranchExist(name string) bool { | |||
return reference.Type() != plumbing.InvalidReference | |||
} | |||
// GetBranches returns branches from the repository, skipping skip initial branches and | |||
// returning at most limit branches, or all branches if limit is 0. | |||
// GetBranches returns branches from the repository, skipping "skip" initial branches and | |||
// returning at most "limit" branches, or all branches if "limit" is 0. | |||
func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) { | |||
var branchNames []string | |||
@@ -59,10 +59,10 @@ func (repo *Repository) IsBranchExist(name string) bool { | |||
return repo.IsReferenceExist(BranchPrefix + name) | |||
} | |||
// GetBranchNames returns branches from the repository, skipping skip initial branches and | |||
// returning at most limit branches, or all branches if limit is 0. | |||
// GetBranchNames returns branches from the repository, skipping "skip" initial branches and | |||
// returning at most "limit" branches, or all branches if "limit" is 0. | |||
func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) { | |||
return callShowRef(repo.Ctx, repo.Path, BranchPrefix, []CmdArg{BranchPrefix, "--sort=-committerdate"}, skip, limit) | |||
return callShowRef(repo.Ctx, repo.Path, BranchPrefix, TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"}, skip, limit) | |||
} | |||
// WalkReferences walks all the references from the repository | |||
@@ -73,19 +73,19 @@ func WalkReferences(ctx context.Context, repoPath string, walkfn func(sha1, refn | |||
// WalkReferences walks all the references from the repository | |||
// refType should be empty, ObjectTag or ObjectBranch. All other values are equivalent to empty. | |||
func (repo *Repository) WalkReferences(refType ObjectType, skip, limit int, walkfn func(sha1, refname string) error) (int, error) { | |||
var args []CmdArg | |||
var args TrustedCmdArgs | |||
switch refType { | |||
case ObjectTag: | |||
args = []CmdArg{TagPrefix, "--sort=-taggerdate"} | |||
args = TrustedCmdArgs{TagPrefix, "--sort=-taggerdate"} | |||
case ObjectBranch: | |||
args = []CmdArg{BranchPrefix, "--sort=-committerdate"} | |||
args = TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"} | |||
} | |||
return walkShowRef(repo.Ctx, repo.Path, args, skip, limit, walkfn) | |||
} | |||
// callShowRef return refs, if limit = 0 it will not limit | |||
func callShowRef(ctx context.Context, repoPath, trimPrefix string, extraArgs []CmdArg, skip, limit int) (branchNames []string, countAll int, err error) { | |||
func callShowRef(ctx context.Context, repoPath, trimPrefix string, extraArgs TrustedCmdArgs, skip, limit int) (branchNames []string, countAll int, err error) { | |||
countAll, err = walkShowRef(ctx, repoPath, extraArgs, skip, limit, func(_, branchName string) error { | |||
branchName = strings.TrimPrefix(branchName, trimPrefix) | |||
branchNames = append(branchNames, branchName) | |||
@@ -95,7 +95,7 @@ func callShowRef(ctx context.Context, repoPath, trimPrefix string, extraArgs []C | |||
return branchNames, countAll, err | |||
} | |||
func walkShowRef(ctx context.Context, repoPath string, extraArgs []CmdArg, skip, limit int, walkfn func(sha1, refname string) error) (countAll int, err error) { | |||
func walkShowRef(ctx context.Context, repoPath string, extraArgs TrustedCmdArgs, skip, limit int, walkfn func(sha1, refname string) error) (countAll int, err error) { | |||
stdoutReader, stdoutWriter := io.Pipe() | |||
defer func() { | |||
_ = stdoutReader.Close() | |||
@@ -104,7 +104,7 @@ func walkShowRef(ctx context.Context, repoPath string, extraArgs []CmdArg, skip, | |||
go func() { | |||
stderrBuilder := &strings.Builder{} | |||
args := []CmdArg{"for-each-ref", "--format=%(objectname) %(refname)"} | |||
args := TrustedCmdArgs{"for-each-ref", "--format=%(objectname) %(refname)"} | |||
args = append(args, extraArgs...) | |||
err := NewCommand(ctx, args...).Run(&RunOpts{ | |||
Dir: repoPath, |
@@ -89,7 +89,7 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) { | |||
func (repo *Repository) commitsByRange(id SHA1, page, pageSize int) ([]*Commit, error) { | |||
stdout, _, err := NewCommand(repo.Ctx, "log"). | |||
AddArguments(CmdArg("--skip="+strconv.Itoa((page-1)*pageSize)), CmdArg("--max-count="+strconv.Itoa(pageSize)), prettyLogFormat). | |||
AddOptionFormat("--skip=%d", (page-1)*pageSize).AddOptionFormat("--max-count=%d", pageSize).AddArguments(prettyLogFormat). | |||
AddDynamicArguments(id.String()). | |||
RunStdBytes(&RunOpts{Dir: repo.Path}) | |||
if err != nil { | |||
@@ -99,32 +99,36 @@ func (repo *Repository) commitsByRange(id SHA1, page, pageSize int) ([]*Commit, | |||
} | |||
func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Commit, error) { | |||
// create new git log command with limit of 100 commis | |||
cmd := NewCommand(repo.Ctx, "log", "-100", prettyLogFormat).AddDynamicArguments(id.String()) | |||
// ignore case | |||
args := []CmdArg{"-i"} | |||
// add common arguments to git command | |||
addCommonSearchArgs := func(c *Command) { | |||
// ignore case | |||
c.AddArguments("-i") | |||
// add authors if present in search query | |||
if len(opts.Authors) > 0 { | |||
for _, v := range opts.Authors { | |||
c.AddOptionFormat("--author=%s", v) | |||
} | |||
} | |||
// add authors if present in search query | |||
if len(opts.Authors) > 0 { | |||
for _, v := range opts.Authors { | |||
args = append(args, CmdArg("--author="+v)) | |||
// add committers if present in search query | |||
if len(opts.Committers) > 0 { | |||
for _, v := range opts.Committers { | |||
c.AddOptionFormat("--committer=%s", v) | |||
} | |||
} | |||
} | |||
// add committers if present in search query | |||
if len(opts.Committers) > 0 { | |||
for _, v := range opts.Committers { | |||
args = append(args, CmdArg("--committer="+v)) | |||
// add time constraints if present in search query | |||
if len(opts.After) > 0 { | |||
c.AddOptionFormat("--after=%s", opts.After) | |||
} | |||
if len(opts.Before) > 0 { | |||
c.AddOptionFormat("--before=%s", opts.Before) | |||
} | |||
} | |||
// add time constraints if present in search query | |||
if len(opts.After) > 0 { | |||
args = append(args, CmdArg("--after="+opts.After)) | |||
} | |||
if len(opts.Before) > 0 { | |||
args = append(args, CmdArg("--before="+opts.Before)) | |||
} | |||
// create new git log command with limit of 100 commits | |||
cmd := NewCommand(repo.Ctx, "log", "-100", prettyLogFormat).AddDynamicArguments(id.String()) | |||
// pretend that all refs along with HEAD were listed on command line as <commis> | |||
// https://git-scm.com/docs/git-log#Documentation/git-log.txt---all | |||
@@ -137,12 +141,12 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co | |||
// note this is done only for command created above | |||
if len(opts.Keywords) > 0 { | |||
for _, v := range opts.Keywords { | |||
cmd.AddArguments(CmdArg("--grep=" + v)) | |||
cmd.AddOptionFormat("--grep=%s", v) | |||
} | |||
} | |||
// search for commits matching given constraints and keywords in commit msg | |||
cmd.AddArguments(args...) | |||
addCommonSearchArgs(cmd) | |||
stdout, _, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path}) | |||
if err != nil { | |||
return nil, err | |||
@@ -160,7 +164,7 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co | |||
// create new git log command with 1 commit limit | |||
hashCmd := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat) | |||
// add previous arguments except for --grep and --all | |||
hashCmd.AddArguments(args...) | |||
addCommonSearchArgs(hashCmd) | |||
// add keyword as <commit> | |||
hashCmd.AddDynamicArguments(v) | |||
@@ -213,8 +217,8 @@ func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) ( | |||
go func() { | |||
stderr := strings.Builder{} | |||
gitCmd := NewCommand(repo.Ctx, "rev-list"). | |||
AddArguments(CmdArg("--max-count=" + strconv.Itoa(setting.Git.CommitsRangeSize*page))). | |||
AddArguments(CmdArg("--skip=" + strconv.Itoa(skip))) | |||
AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize*page). | |||
AddOptionFormat("--skip=%d", skip) | |||
gitCmd.AddDynamicArguments(revision) | |||
gitCmd.AddDashesAndList(file) | |||
err := gitCmd.Run(&RunOpts{ | |||
@@ -295,21 +299,21 @@ func (repo *Repository) CommitsBetweenLimit(last, before *Commit, limit, skip in | |||
var stdout []byte | |||
var err error | |||
if before == nil { | |||
stdout, _, err = NewCommand(repo.Ctx, "rev-list", | |||
"--max-count", CmdArg(strconv.Itoa(limit)), | |||
"--skip", CmdArg(strconv.Itoa(skip))). | |||
stdout, _, err = NewCommand(repo.Ctx, "rev-list"). | |||
AddOptionValues("--max-count", strconv.Itoa(limit)). | |||
AddOptionValues("--skip", strconv.Itoa(skip)). | |||
AddDynamicArguments(last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path}) | |||
} else { | |||
stdout, _, err = NewCommand(repo.Ctx, "rev-list", | |||
"--max-count", CmdArg(strconv.Itoa(limit)), | |||
"--skip", CmdArg(strconv.Itoa(skip))). | |||
stdout, _, err = NewCommand(repo.Ctx, "rev-list"). | |||
AddOptionValues("--max-count", strconv.Itoa(limit)). | |||
AddOptionValues("--skip", strconv.Itoa(skip)). | |||
AddDynamicArguments(before.ID.String() + ".." + last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path}) | |||
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 = NewCommand(repo.Ctx, "rev-list", | |||
"--max-count", CmdArg(strconv.Itoa(limit)), | |||
"--skip", CmdArg(strconv.Itoa(skip))). | |||
stdout, _, err = NewCommand(repo.Ctx, "rev-list"). | |||
AddOptionValues("--max-count", strconv.Itoa(limit)). | |||
AddOptionValues("--skip", strconv.Itoa(skip)). | |||
AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path}) | |||
} | |||
} | |||
@@ -349,12 +353,11 @@ func (repo *Repository) CommitsCountBetween(start, end string) (int64, error) { | |||
// commitsBefore the limit is depth, not total number of returned commits. | |||
func (repo *Repository) commitsBefore(id SHA1, limit int) ([]*Commit, error) { | |||
cmd := NewCommand(repo.Ctx, "log") | |||
cmd := NewCommand(repo.Ctx, "log", prettyLogFormat) | |||
if limit > 0 { | |||
cmd.AddArguments(CmdArg("-"+strconv.Itoa(limit)), prettyLogFormat).AddDynamicArguments(id.String()) | |||
} else { | |||
cmd.AddArguments(prettyLogFormat).AddDynamicArguments(id.String()) | |||
cmd.AddOptionFormat("-%d", limit) | |||
} | |||
cmd.AddDynamicArguments(id.String()) | |||
stdout, _, runErr := cmd.RunStdBytes(&RunOpts{Dir: repo.Path}) | |||
if runErr != nil { | |||
@@ -393,10 +396,9 @@ func (repo *Repository) getCommitsBeforeLimit(id SHA1, num int) ([]*Commit, erro | |||
func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) { | |||
if CheckGitVersionAtLeast("2.7.0") == nil { | |||
stdout, _, err := NewCommand(repo.Ctx, "for-each-ref", | |||
CmdArg("--count="+strconv.Itoa(limit)), | |||
"--format=%(refname:strip=2)", "--contains"). | |||
AddDynamicArguments(commit.ID.String(), BranchPrefix). | |||
stdout, _, err := NewCommand(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)"). | |||
AddOptionFormat("--count=%d", limit). | |||
AddOptionValues("--contains", commit.ID.String(), BranchPrefix). | |||
RunStdString(&RunOpts{Dir: repo.Path}) | |||
if err != nil { | |||
return nil, err | |||
@@ -406,7 +408,7 @@ func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) | |||
return branches, nil | |||
} | |||
stdout, _, err := NewCommand(repo.Ctx, "branch", "--contains").AddDynamicArguments(commit.ID.String()).RunStdString(&RunOpts{Dir: repo.Path}) | |||
stdout, _, err := NewCommand(repo.Ctx, "branch").AddOptionValues("--contains", commit.ID.String()).RunStdString(&RunOpts{Dir: repo.Path}) | |||
if err != nil { | |||
return nil, err | |||
} |
@@ -172,25 +172,21 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis | |||
// GetDiffShortStat counts number of changed files, number of additions and deletions | |||
func (repo *Repository) GetDiffShortStat(base, head string) (numFiles, totalAdditions, totalDeletions int, err error) { | |||
numFiles, totalAdditions, totalDeletions, err = GetDiffShortStat(repo.Ctx, repo.Path, CmdArgCheck(base+"..."+head)) | |||
numFiles, totalAdditions, totalDeletions, err = GetDiffShortStat(repo.Ctx, repo.Path, nil, base+"..."+head) | |||
if err != nil && strings.Contains(err.Error(), "no merge base") { | |||
return GetDiffShortStat(repo.Ctx, repo.Path, CmdArgCheck(base), CmdArgCheck(head)) | |||
return GetDiffShortStat(repo.Ctx, repo.Path, nil, base, head) | |||
} | |||
return numFiles, totalAdditions, totalDeletions, err | |||
} | |||
// GetDiffShortStat counts number of changed files, number of additions and deletions | |||
func GetDiffShortStat(ctx context.Context, repoPath string, args ...CmdArg) (numFiles, totalAdditions, totalDeletions int, err error) { | |||
func GetDiffShortStat(ctx context.Context, repoPath string, trustedArgs TrustedCmdArgs, dynamicArgs ...string) (numFiles, totalAdditions, totalDeletions int, err error) { | |||
// Now if we call: | |||
// $ git diff --shortstat 1ebb35b98889ff77299f24d82da426b434b0cca0...788b8b1440462d477f45b0088875 | |||
// we get: | |||
// " 9902 files changed, 2034198 insertions(+), 298800 deletions(-)\n" | |||
args = append([]CmdArg{ | |||
"diff", | |||
"--shortstat", | |||
}, args...) | |||
stdout, _, err := NewCommand(ctx, args...).RunStdString(&RunOpts{Dir: repoPath}) | |||
cmd := NewCommand(ctx, "diff", "--shortstat").AddArguments(trustedArgs...).AddDynamicArguments(dynamicArgs...) | |||
stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) | |||
if err != nil { | |||
return 0, 0, 0, err | |||
} |
@@ -40,7 +40,7 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string) | |||
since := fromTime.Format(time.RFC3339) | |||
stdout, _, runErr := NewCommand(repo.Ctx, "rev-list", "--count", "--no-merges", "--branches=*", "--date=iso", CmdArg(fmt.Sprintf("--since='%s'", since))).RunStdString(&RunOpts{Dir: repo.Path}) | |||
stdout, _, runErr := NewCommand(repo.Ctx, "rev-list", "--count", "--no-merges", "--branches=*", "--date=iso").AddOptionFormat("--since='%s'", since).RunStdString(&RunOpts{Dir: repo.Path}) | |||
if runErr != nil { | |||
return nil, runErr | |||
} | |||
@@ -60,7 +60,7 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string) | |||
_ = stdoutWriter.Close() | |||
}() | |||
gitCmd := NewCommand(repo.Ctx, "log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso", CmdArg(fmt.Sprintf("--since='%s'", since))) | |||
gitCmd := NewCommand(repo.Ctx, "log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso").AddOptionFormat("--since='%s'", since) | |||
if len(branch) == 0 { | |||
gitCmd.AddArguments("--branches=*") | |||
} else { |
@@ -121,7 +121,9 @@ func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) { | |||
rc := &RunOpts{Dir: repo.Path, Stdout: stdoutWriter, Stderr: &stderr} | |||
go func() { | |||
err := NewCommand(repo.Ctx, "for-each-ref", CmdArg("--format="+forEachRefFmt.Flag()), "--sort", "-*creatordate", "refs/tags").Run(rc) | |||
err := NewCommand(repo.Ctx, "for-each-ref"). | |||
AddOptionFormat("--format=%s", forEachRefFmt.Flag()). | |||
AddArguments("--sort", "-*creatordate", "refs/tags").Run(rc) | |||
if err != nil { | |||
_ = stdoutWriter.CloseWithError(ConcatenateError(err, stderr.String())) | |||
} else { |
@@ -25,7 +25,7 @@ func (repo *Repository) IsTagExist(name string) bool { | |||
// GetTags returns all tags of the repository. | |||
// returning at most limit tags, or all if limit is 0. | |||
func (repo *Repository) GetTags(skip, limit int) (tags []string, err error) { | |||
tags, _, err = callShowRef(repo.Ctx, repo.Path, TagPrefix, []CmdArg{TagPrefix, "--sort=-taggerdate"}, skip, limit) | |||
tags, _, err = callShowRef(repo.Ctx, repo.Path, TagPrefix, TrustedCmdArgs{TagPrefix, "--sort=-taggerdate"}, skip, limit) | |||
return tags, err | |||
} | |||
@@ -6,7 +6,6 @@ package git | |||
import ( | |||
"bytes" | |||
"fmt" | |||
"os" | |||
"strings" | |||
"time" | |||
@@ -45,7 +44,7 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt | |||
_, _ = messageBytes.WriteString("\n") | |||
if opts.KeyID != "" || opts.AlwaysSign { | |||
cmd.AddArguments(CmdArg(fmt.Sprintf("-S%s", opts.KeyID))) | |||
cmd.AddOptionFormat("-S%s", opts.KeyID) | |||
} | |||
if opts.NoGPGSign { |
@@ -100,14 +100,15 @@ func (t *Tree) ListEntries() (Entries, error) { | |||
// listEntriesRecursive returns all entries of current tree recursively including all subtrees | |||
// extraArgs could be "-l" to get the size, which is slower | |||
func (t *Tree) listEntriesRecursive(extraArgs ...CmdArg) (Entries, error) { | |||
func (t *Tree) listEntriesRecursive(extraArgs TrustedCmdArgs) (Entries, error) { | |||
if t.entriesRecursiveParsed { | |||
return t.entriesRecursive, nil | |||
} | |||
args := append([]CmdArg{"ls-tree", "-t", "-r"}, extraArgs...) | |||
args = append(args, CmdArg(t.ID.String())) | |||
stdout, _, runErr := NewCommand(t.repo.Ctx, args...).RunStdBytes(&RunOpts{Dir: t.repo.Path}) | |||
stdout, _, runErr := NewCommand(t.repo.Ctx, "ls-tree", "-t", "-r"). | |||
AddArguments(extraArgs...). | |||
AddDynamicArguments(t.ID.String()). | |||
RunStdBytes(&RunOpts{Dir: t.repo.Path}) | |||
if runErr != nil { | |||
return nil, runErr | |||
} | |||
@@ -123,10 +124,10 @@ func (t *Tree) listEntriesRecursive(extraArgs ...CmdArg) (Entries, error) { | |||
// ListEntriesRecursiveFast returns all entries of current tree recursively including all subtrees, no size | |||
func (t *Tree) ListEntriesRecursiveFast() (Entries, error) { | |||
return t.listEntriesRecursive() | |||
return t.listEntriesRecursive(nil) | |||
} | |||
// ListEntriesRecursiveWithSize returns all entries of current tree recursively including all subtrees, with size | |||
func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) { | |||
return t.listEntriesRecursive("--long") | |||
return t.listEntriesRecursive(TrustedCmdArgs{"--long"}) | |||
} |
@@ -7,7 +7,6 @@ import ( | |||
"bufio" | |||
"bytes" | |||
"context" | |||
"fmt" | |||
"os" | |||
"strings" | |||
@@ -33,12 +32,9 @@ func GetCommitGraph(r *git.Repository, page, maxAllowedColors int, hidePRRefs bo | |||
graphCmd.AddArguments("--all") | |||
} | |||
graphCmd.AddArguments( | |||
"-C", | |||
"-M", | |||
git.CmdArg(fmt.Sprintf("-n %d", setting.UI.GraphMaxCommitNum*page)), | |||
"--date=iso", | |||
git.CmdArg(fmt.Sprintf("--pretty=format:%s", format))) | |||
graphCmd.AddArguments("-C", "-M", "--date=iso"). | |||
AddOptionFormat("-n %d", setting.UI.GraphMaxCommitNum*page). | |||
AddOptionFormat("--pretty=format:%s", format) | |||
if len(branches) > 0 { | |||
graphCmd.AddDynamicArguments(branches...) |
@@ -316,14 +316,13 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi | |||
return fmt.Errorf("git add --all: %w", err) | |||
} | |||
cmd := git.NewCommand(ctx, | |||
"commit", git.CmdArg(fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email)), | |||
"-m", "Initial commit", | |||
) | |||
cmd := git.NewCommand(ctx, "commit"). | |||
AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email). | |||
AddOptionValues("-m", "Initial commit") | |||
sign, keyID, signer, _ := asymkey_service.SignInitialCommit(ctx, tmpPath, u) | |||
if sign { | |||
cmd.AddArguments(git.CmdArg("-S" + keyID)) | |||
cmd.AddOptionFormat("-S%s", keyID) | |||
if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel { | |||
// need to set the committer to the KeyID owner |
@@ -217,7 +217,7 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m | |||
filename2attribute2info, err := ctx.Repo.GitRepo.CheckAttribute(git.CheckAttributeOpts{ | |||
CachedOnly: true, | |||
Attributes: []git.CmdArg{"linguist-language", "gitlab-language"}, | |||
Attributes: []string{"linguist-language", "gitlab-language"}, | |||
Filenames: []string{ctx.Repo.TreePath}, | |||
IndexFile: indexFilename, | |||
WorkTree: worktree, |
@@ -560,7 +560,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { | |||
func PrepareCompareDiff( | |||
ctx *context.Context, | |||
ci *CompareInfo, | |||
whitespaceBehavior git.CmdArg, | |||
whitespaceBehavior git.TrustedCmdArgs, | |||
) bool { | |||
var ( | |||
repo = ctx.Repo.Repository |
@@ -498,7 +498,8 @@ func serviceRPC(ctx gocontext.Context, h serviceHandler, service string) { | |||
} | |||
var stderr bytes.Buffer | |||
cmd := git.NewCommand(h.r.Context(), git.CmdArgCheck(service), "--stateless-rpc").AddDynamicArguments(h.dir) | |||
// the service is generated by ourselves, so it's safe to trust it | |||
cmd := git.NewCommand(h.r.Context(), git.ToTrustedCmdArgs([]string{service})...).AddArguments("--stateless-rpc").AddDynamicArguments(h.dir) | |||
cmd.SetDescription(fmt.Sprintf("%s %s %s [repo_path: %s]", git.GitExecutable, service, "--stateless-rpc", h.dir)) | |||
if err := cmd.Run(&git.RunOpts{ | |||
Dir: h.dir, | |||
@@ -570,7 +571,8 @@ func GetInfoRefs(ctx *context.Context) { | |||
} | |||
h.environ = append(os.Environ(), h.environ...) | |||
refs, _, err := git.NewCommand(ctx, git.CmdArgCheck(service), "--stateless-rpc", "--advertise-refs", ".").RunStdBytes(&git.RunOpts{Env: h.environ, Dir: h.dir}) | |||
// the service is generated by ourselves, so we can trust it | |||
refs, _, err := git.NewCommand(ctx, git.ToTrustedCmdArgs([]string{service})...).AddArguments("--stateless-rpc", "--advertise-refs", ".").RunStdBytes(&git.RunOpts{Env: h.environ, Dir: h.dir}) | |||
if err != nil { | |||
log.Error(fmt.Sprintf("%v - %s", err, string(refs))) | |||
} |
@@ -146,7 +146,7 @@ func LFSLocks(ctx *context.Context) { | |||
} | |||
name2attribute2info, err := gitRepo.CheckAttribute(git.CheckAttributeOpts{ | |||
Attributes: []git.CmdArg{"lockable"}, | |||
Attributes: []string{"lockable"}, | |||
Filenames: filenames, | |||
CachedOnly: true, | |||
}) |
@@ -509,7 +509,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st | |||
filename2attribute2info, err := ctx.Repo.GitRepo.CheckAttribute(git.CheckAttributeOpts{ | |||
CachedOnly: true, | |||
Attributes: []git.CmdArg{"linguist-language", "gitlab-language"}, | |||
Attributes: []string{"linguist-language", "gitlab-language"}, | |||
Filenames: []string{ctx.Repo.TreePath}, | |||
IndexFile: indexFilename, | |||
WorkTree: worktree, |
@@ -59,11 +59,7 @@ func registerRepoHealthCheck() { | |||
}, func(ctx context.Context, _ *user_model.User, config Config) error { | |||
rhcConfig := config.(*RepoHealthCheckConfig) | |||
// the git args are set by config, they can be safe to be trusted | |||
args := make([]git.CmdArg, 0, len(rhcConfig.Args)) | |||
for _, arg := range rhcConfig.Args { | |||
args = append(args, git.CmdArg(arg)) | |||
} | |||
return repo_service.GitFsckRepos(ctx, rhcConfig.Timeout, args) | |||
return repo_service.GitFsckRepos(ctx, rhcConfig.Timeout, git.ToTrustedCmdArgs(rhcConfig.Args)) | |||
}) | |||
} | |||
@@ -61,11 +61,7 @@ func registerGarbageCollectRepositories() { | |||
}, func(ctx context.Context, _ *user_model.User, config Config) error { | |||
rhcConfig := config.(*RepoHealthCheckConfig) | |||
// the git args are set by config, they can be safe to be trusted | |||
args := make([]git.CmdArg, 0, len(rhcConfig.Args)) | |||
for _, arg := range rhcConfig.Args { | |||
args = append(args, git.CmdArg(arg)) | |||
} | |||
return repo_service.GitGcRepos(ctx, rhcConfig.Timeout, args...) | |||
return repo_service.GitGcRepos(ctx, rhcConfig.Timeout, git.ToTrustedCmdArgs(rhcConfig.Args)) | |||
}) | |||
} | |||
@@ -1056,7 +1056,7 @@ type DiffOptions struct { | |||
MaxLines int | |||
MaxLineCharacters int | |||
MaxFiles int | |||
WhitespaceBehavior git.CmdArg | |||
WhitespaceBehavior git.TrustedCmdArgs | |||
DirectComparison bool | |||
} | |||
@@ -1071,38 +1071,22 @@ func GetDiff(gitRepo *git.Repository, opts *DiffOptions, files ...string) (*Diff | |||
return nil, err | |||
} | |||
argsLength := 6 | |||
if len(opts.WhitespaceBehavior) > 0 { | |||
argsLength++ | |||
} | |||
if len(opts.SkipTo) > 0 { | |||
argsLength++ | |||
} | |||
if len(files) > 0 { | |||
argsLength += len(files) + 1 | |||
} | |||
diffArgs := make([]git.CmdArg, 0, argsLength) | |||
cmdDiff := git.NewCommand(gitRepo.Ctx) | |||
if (len(opts.BeforeCommitID) == 0 || opts.BeforeCommitID == git.EmptySHA) && commit.ParentCount() == 0 { | |||
diffArgs = append(diffArgs, "diff", "--src-prefix=\\a/", "--dst-prefix=\\b/", "-M") | |||
if len(opts.WhitespaceBehavior) != 0 { | |||
diffArgs = append(diffArgs, opts.WhitespaceBehavior) | |||
} | |||
// append empty tree ref | |||
diffArgs = append(diffArgs, "4b825dc642cb6eb9a060e54bf8d69288fbee4904") | |||
diffArgs = append(diffArgs, git.CmdArgCheck(opts.AfterCommitID)) | |||
cmdDiff.AddArguments("diff", "--src-prefix=\\a/", "--dst-prefix=\\b/", "-M"). | |||
AddArguments(opts.WhitespaceBehavior...). | |||
AddArguments("4b825dc642cb6eb9a060e54bf8d69288fbee4904"). // append empty tree ref | |||
AddDynamicArguments(opts.AfterCommitID) | |||
} else { | |||
actualBeforeCommitID := opts.BeforeCommitID | |||
if len(actualBeforeCommitID) == 0 { | |||
parentCommit, _ := commit.Parent(0) | |||
actualBeforeCommitID = parentCommit.ID.String() | |||
} | |||
diffArgs = append(diffArgs, "diff", "--src-prefix=\\a/", "--dst-prefix=\\b/", "-M") | |||
if len(opts.WhitespaceBehavior) != 0 { | |||
diffArgs = append(diffArgs, opts.WhitespaceBehavior) | |||
} | |||
diffArgs = append(diffArgs, git.CmdArgCheck(actualBeforeCommitID)) | |||
diffArgs = append(diffArgs, git.CmdArgCheck(opts.AfterCommitID)) | |||
cmdDiff.AddArguments("diff", "--src-prefix=\\a/", "--dst-prefix=\\b/", "-M"). | |||
AddArguments(opts.WhitespaceBehavior...). | |||
AddDynamicArguments(actualBeforeCommitID, opts.AfterCommitID) | |||
opts.BeforeCommitID = actualBeforeCommitID | |||
} | |||
@@ -1111,16 +1095,11 @@ func GetDiff(gitRepo *git.Repository, opts *DiffOptions, files ...string) (*Diff | |||
// the skipping for us | |||
parsePatchSkipToFile := opts.SkipTo | |||
if opts.SkipTo != "" && git.CheckGitVersionAtLeast("2.31") == nil { | |||
diffArgs = append(diffArgs, git.CmdArg("--skip-to="+opts.SkipTo)) | |||
cmdDiff.AddOptionFormat("--skip-to=%s", opts.SkipTo) | |||
parsePatchSkipToFile = "" | |||
} | |||
if len(files) > 0 { | |||
diffArgs = append(diffArgs, "--") | |||
for _, file := range files { | |||
diffArgs = append(diffArgs, git.CmdArg(file)) // it's safe to cast it to CmdArg because there is a "--" before | |||
} | |||
} | |||
cmdDiff.AddDashesAndList(files...) | |||
reader, writer := io.Pipe() | |||
defer func() { | |||
@@ -1128,10 +1107,9 @@ func GetDiff(gitRepo *git.Repository, opts *DiffOptions, files ...string) (*Diff | |||
_ = writer.Close() | |||
}() | |||
go func(ctx context.Context, diffArgs []git.CmdArg, repoPath string, writer *io.PipeWriter) { | |||
cmd := git.NewCommand(ctx, diffArgs...) | |||
cmd.SetDescription(fmt.Sprintf("GetDiffRange [repo_path: %s]", repoPath)) | |||
if err := cmd.Run(&git.RunOpts{ | |||
go func() { | |||
cmdDiff.SetDescription(fmt.Sprintf("GetDiffRange [repo_path: %s]", repoPath)) | |||
if err := cmdDiff.Run(&git.RunOpts{ | |||
Timeout: time.Duration(setting.Git.Timeout.Default) * time.Second, | |||
Dir: repoPath, | |||
Stderr: os.Stderr, | |||
@@ -1141,7 +1119,7 @@ func GetDiff(gitRepo *git.Repository, opts *DiffOptions, files ...string) (*Diff | |||
} | |||
_ = writer.Close() | |||
}(gitRepo.Ctx, diffArgs, repoPath, writer) | |||
}() | |||
diff, err := ParsePatch(opts.MaxLines, opts.MaxLineCharacters, opts.MaxFiles, reader, parsePatchSkipToFile) | |||
if err != nil { | |||
@@ -1201,16 +1179,16 @@ func GetDiff(gitRepo *git.Repository, opts *DiffOptions, files ...string) (*Diff | |||
separator = ".." | |||
} | |||
shortstatArgs := []git.CmdArg{git.CmdArgCheck(opts.BeforeCommitID + separator + opts.AfterCommitID)} | |||
diffPaths := []string{opts.BeforeCommitID + separator + opts.AfterCommitID} | |||
if len(opts.BeforeCommitID) == 0 || opts.BeforeCommitID == git.EmptySHA { | |||
shortstatArgs = []git.CmdArg{git.EmptyTreeSHA, git.CmdArgCheck(opts.AfterCommitID)} | |||
diffPaths = []string{git.EmptyTreeSHA, opts.AfterCommitID} | |||
} | |||
diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, shortstatArgs...) | |||
diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...) | |||
if err != nil && strings.Contains(err.Error(), "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 --shortstat base head so let's try that... | |||
shortstatArgs = []git.CmdArg{git.CmdArgCheck(opts.BeforeCommitID), git.CmdArgCheck(opts.AfterCommitID)} | |||
diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, shortstatArgs...) | |||
diffPaths = []string{opts.BeforeCommitID, opts.AfterCommitID} | |||
diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...) | |||
} | |||
if err != nil { | |||
return nil, err | |||
@@ -1324,17 +1302,17 @@ func CommentMustAsDiff(c *issues_model.Comment) *Diff { | |||
} | |||
// GetWhitespaceFlag returns git diff flag for treating whitespaces | |||
func GetWhitespaceFlag(whitespaceBehavior string) git.CmdArg { | |||
whitespaceFlags := map[string]string{ | |||
"ignore-all": "-w", | |||
"ignore-change": "-b", | |||
"ignore-eol": "--ignore-space-at-eol", | |||
"show-all": "", | |||
func GetWhitespaceFlag(whitespaceBehavior string) git.TrustedCmdArgs { | |||
whitespaceFlags := map[string]git.TrustedCmdArgs{ | |||
"ignore-all": {"-w"}, | |||
"ignore-change": {"-b"}, | |||
"ignore-eol": {"--ignore-space-at-eol"}, | |||
"show-all": nil, | |||
} | |||
if flag, ok := whitespaceFlags[whitespaceBehavior]; ok { | |||
return git.CmdArg(flag) | |||
return flag | |||
} | |||
log.Warn("unknown whitespace behavior: %q, default to 'show-all'", whitespaceBehavior) | |||
return "" | |||
return nil | |||
} |
@@ -626,7 +626,7 @@ func TestGetDiffRangeWithWhitespaceBehavior(t *testing.T) { | |||
return | |||
} | |||
defer gitRepo.Close() | |||
for _, behavior := range []git.CmdArg{"-w", "--ignore-space-at-eol", "-b", ""} { | |||
for _, behavior := range []git.TrustedCmdArgs{{"-w"}, {"--ignore-space-at-eol"}, {"-b"}, nil} { | |||
diffs, err := GetDiff(gitRepo, | |||
&DiffOptions{ | |||
AfterCommitID: "bd7063cc7c04689c4d082183d32a604ed27a24f9", |
@@ -203,11 +203,11 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo | |||
log.Trace("SyncMirrors [repo: %-v]: running git remote update...", m.Repo) | |||
gitArgs := []git.CmdArg{"remote", "update"} | |||
cmd := git.NewCommand(ctx, "remote", "update") | |||
if m.EnablePrune { | |||
gitArgs = append(gitArgs, "--prune") | |||
cmd.AddArguments("--prune") | |||
} | |||
gitArgs = append(gitArgs, git.CmdArgCheck(m.GetRemoteName())) | |||
cmd.AddDynamicArguments(m.GetRemoteName()) | |||
remoteURL, remoteErr := git.GetRemoteURL(ctx, repoPath, m.GetRemoteName()) | |||
if remoteErr != nil { | |||
@@ -217,7 +217,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo | |||
stdoutBuilder := strings.Builder{} | |||
stderrBuilder := strings.Builder{} | |||
if err := git.NewCommand(ctx, gitArgs...). | |||
if err := cmd. | |||
SetDescription(fmt.Sprintf("Mirror.runSync: %s", m.Repo.FullName())). | |||
Run(&git.RunOpts{ | |||
Timeout: timeout, | |||
@@ -243,7 +243,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo | |||
// Successful prune - reattempt mirror | |||
stderrBuilder.Reset() | |||
stdoutBuilder.Reset() | |||
if err = git.NewCommand(ctx, gitArgs...). | |||
if err = cmd. | |||
SetDescription(fmt.Sprintf("Mirror.runSync: %s", m.Repo.FullName())). | |||
Run(&git.RunOpts{ | |||
Timeout: timeout, |
@@ -37,10 +37,10 @@ func AddPushMirrorRemote(ctx context.Context, m *repo_model.PushMirror, addr str | |||
if _, _, err := cmd.RunStdString(&git.RunOpts{Dir: path}); err != nil { | |||
return err | |||
} | |||
if _, _, err := git.NewCommand(ctx, "config", "--add", git.CmdArg("remote."+m.RemoteName+".push"), "+refs/heads/*:refs/heads/*").RunStdString(&git.RunOpts{Dir: path}); err != nil { | |||
if _, _, err := git.NewCommand(ctx, "config", "--add").AddDynamicArguments("remote."+m.RemoteName+".push", "+refs/heads/*:refs/heads/*").RunStdString(&git.RunOpts{Dir: path}); err != nil { | |||
return err | |||
} | |||
if _, _, err := git.NewCommand(ctx, "config", "--add", git.CmdArg("remote."+m.RemoteName+".push"), "+refs/tags/*:refs/tags/*").RunStdString(&git.RunOpts{Dir: path}); err != nil { | |||
if _, _, err := git.NewCommand(ctx, "config", "--add").AddDynamicArguments("remote."+m.RemoteName+".push", "+refs/tags/*:refs/tags/*").RunStdString(&git.RunOpts{Dir: path}); err != nil { | |||
return err | |||
} | |||
return nil |
@@ -370,16 +370,16 @@ func rawMerge(ctx context.Context, pr *issues_model.PullRequest, doer *user_mode | |||
sig := doer.NewGitSig() | |||
committer := sig | |||
// Determine if we should sign | |||
var signArg git.CmdArg | |||
sign, keyID, signer, _ := asymkey_service.SignMerge(ctx, pr, doer, tmpBasePath, "HEAD", trackingBranch) | |||
// Determine if we should sign. If no signKeyID, use --no-gpg-sign to countermand the sign config (from gitconfig) | |||
var signArgs git.TrustedCmdArgs | |||
sign, signKeyID, signer, _ := asymkey_service.SignMerge(ctx, pr, doer, tmpBasePath, "HEAD", trackingBranch) | |||
if sign { | |||
signArg = git.CmdArg("-S" + keyID) | |||
if pr.BaseRepo.GetTrustModel() == repo_model.CommitterTrustModel || pr.BaseRepo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel { | |||
committer = signer | |||
} | |||
signArgs = git.ToTrustedCmdArgs([]string{"-S" + signKeyID}) | |||
} else { | |||
signArg = git.CmdArg("--no-gpg-sign") | |||
signArgs = append(signArgs, "--no-gpg-sign") | |||
} | |||
commitTimeStr := time.Now().Format(time.RFC3339) | |||
@@ -403,7 +403,7 @@ func rawMerge(ctx context.Context, pr *issues_model.PullRequest, doer *user_mode | |||
return "", err | |||
} | |||
if err := commitAndSignNoAuthor(ctx, pr, message, signArg, tmpBasePath, env); err != nil { | |||
if err := commitAndSignNoAuthor(ctx, pr, message, signArgs, tmpBasePath, env); err != nil { | |||
log.Error("Unable to make final commit: %v", err) | |||
return "", err | |||
} | |||
@@ -505,7 +505,7 @@ func rawMerge(ctx context.Context, pr *issues_model.PullRequest, doer *user_mode | |||
return "", err | |||
} | |||
if mergeStyle == repo_model.MergeStyleRebaseMerge { | |||
if err := commitAndSignNoAuthor(ctx, pr, message, signArg, tmpBasePath, env); err != nil { | |||
if err := commitAndSignNoAuthor(ctx, pr, message, signArgs, tmpBasePath, env); err != nil { | |||
log.Error("Unable to make final commit: %v", err) | |||
return "", err | |||
} | |||
@@ -523,35 +523,22 @@ func rawMerge(ctx context.Context, pr *issues_model.PullRequest, doer *user_mode | |||
return "", fmt.Errorf("LoadPoster: %w", err) | |||
} | |||
sig := pr.Issue.Poster.NewGitSig() | |||
if signArg == "" { | |||
if err := git.NewCommand(ctx, "commit", git.CmdArg(fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email)), "-m").AddDynamicArguments(message). | |||
Run(&git.RunOpts{ | |||
Env: env, | |||
Dir: tmpBasePath, | |||
Stdout: &outbuf, | |||
Stderr: &errbuf, | |||
}); err != nil { | |||
log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) | |||
return "", fmt.Errorf("git commit [%s:%s -> %s:%s]: %w\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) | |||
} | |||
} else { | |||
if setting.Repository.PullRequest.AddCoCommitterTrailers && committer.String() != sig.String() { | |||
// add trailer | |||
message += fmt.Sprintf("\nCo-authored-by: %s\nCo-committed-by: %s\n", sig.String(), sig.String()) | |||
} | |||
if err := git.NewCommand(ctx, "commit"). | |||
AddArguments(signArg). | |||
AddArguments(git.CmdArg(fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email))). | |||
AddArguments("-m").AddDynamicArguments(message). | |||
Run(&git.RunOpts{ | |||
Env: env, | |||
Dir: tmpBasePath, | |||
Stdout: &outbuf, | |||
Stderr: &errbuf, | |||
}); err != nil { | |||
log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) | |||
return "", fmt.Errorf("git commit [%s:%s -> %s:%s]: %w\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) | |||
} | |||
if setting.Repository.PullRequest.AddCoCommitterTrailers && committer.String() != sig.String() { | |||
// add trailer | |||
message += fmt.Sprintf("\nCo-authored-by: %s\nCo-committed-by: %s\n", sig.String(), sig.String()) | |||
} | |||
if err := git.NewCommand(ctx, "commit"). | |||
AddArguments(signArgs...). | |||
AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email). | |||
AddOptionValues("-m", message). | |||
Run(&git.RunOpts{ | |||
Env: env, | |||
Dir: tmpBasePath, | |||
Stdout: &outbuf, | |||
Stderr: &errbuf, | |||
}); err != nil { | |||
log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) | |||
return "", fmt.Errorf("git commit [%s:%s -> %s:%s]: %w\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) | |||
} | |||
outbuf.Reset() | |||
errbuf.Reset() | |||
@@ -649,30 +636,17 @@ func rawMerge(ctx context.Context, pr *issues_model.PullRequest, doer *user_mode | |||
return mergeCommitID, nil | |||
} | |||
func commitAndSignNoAuthor(ctx context.Context, pr *issues_model.PullRequest, message string, signArg git.CmdArg, tmpBasePath string, env []string) error { | |||
func commitAndSignNoAuthor(ctx context.Context, pr *issues_model.PullRequest, message string, signArgs git.TrustedCmdArgs, tmpBasePath string, env []string) error { | |||
var outbuf, errbuf strings.Builder | |||
if signArg == "" { | |||
if err := git.NewCommand(ctx, "commit", "-m").AddDynamicArguments(message). | |||
Run(&git.RunOpts{ | |||
Env: env, | |||
Dir: tmpBasePath, | |||
Stdout: &outbuf, | |||
Stderr: &errbuf, | |||
}); err != nil { | |||
log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) | |||
return fmt.Errorf("git commit [%s:%s -> %s:%s]: %w\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) | |||
} | |||
} else { | |||
if err := git.NewCommand(ctx, "commit").AddArguments(signArg).AddArguments("-m").AddDynamicArguments(message). | |||
Run(&git.RunOpts{ | |||
Env: env, | |||
Dir: tmpBasePath, | |||
Stdout: &outbuf, | |||
Stderr: &errbuf, | |||
}); err != nil { | |||
log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) | |||
return fmt.Errorf("git commit [%s:%s -> %s:%s]: %w\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) | |||
} | |||
if err := git.NewCommand(ctx, "commit").AddArguments(signArgs...).AddOptionValues("-m", message). | |||
Run(&git.RunOpts{ | |||
Env: env, | |||
Dir: tmpBasePath, | |||
Stdout: &outbuf, | |||
Stderr: &errbuf, | |||
}); err != nil { | |||
log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) | |||
return fmt.Errorf("git commit [%s:%s -> %s:%s]: %w\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) | |||
} | |||
return nil | |||
} |
@@ -376,16 +376,16 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * | |||
prConfig := prUnit.PullRequestsConfig() | |||
// 6. Prepare the arguments to apply the patch against the index | |||
args := []git.CmdArg{"apply", "--check", "--cached"} | |||
cmdApply := git.NewCommand(gitRepo.Ctx, "apply", "--check", "--cached") | |||
if prConfig.IgnoreWhitespaceConflicts { | |||
args = append(args, "--ignore-whitespace") | |||
cmdApply.AddArguments("--ignore-whitespace") | |||
} | |||
is3way := false | |||
if git.CheckGitVersionAtLeast("2.32.0") == nil { | |||
args = append(args, "--3way") | |||
cmdApply.AddArguments("--3way") | |||
is3way = true | |||
} | |||
args = append(args, git.CmdArgCheck(patchPath)) | |||
cmdApply.AddDynamicArguments(patchPath) | |||
// 7. Prep the pipe: | |||
// - Here we could do the equivalent of: | |||
@@ -407,71 +407,70 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * | |||
// 8. Run the check command | |||
conflict = false | |||
err = git.NewCommand(gitRepo.Ctx, args...). | |||
Run(&git.RunOpts{ | |||
Dir: tmpBasePath, | |||
Stderr: stderrWriter, | |||
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { | |||
// Close the writer end of the pipe to begin processing | |||
_ = stderrWriter.Close() | |||
defer func() { | |||
// Close the reader on return to terminate the git command if necessary | |||
_ = stderrReader.Close() | |||
}() | |||
const prefix = "error: patch failed:" | |||
const errorPrefix = "error: " | |||
const threewayFailed = "Failed to perform three-way merge..." | |||
const appliedPatchPrefix = "Applied patch to '" | |||
const withConflicts = "' with conflicts." | |||
conflicts := make(container.Set[string]) | |||
// Now scan the output from the command | |||
scanner := bufio.NewScanner(stderrReader) | |||
for scanner.Scan() { | |||
line := scanner.Text() | |||
log.Trace("PullRequest[%d].testPatch: stderr: %s", pr.ID, line) | |||
if strings.HasPrefix(line, prefix) { | |||
conflict = true | |||
filepath := strings.TrimSpace(strings.Split(line[len(prefix):], ":")[0]) | |||
conflicts.Add(filepath) | |||
} else if is3way && line == threewayFailed { | |||
conflict = true | |||
} else if strings.HasPrefix(line, errorPrefix) { | |||
conflict = true | |||
for _, suffix := range patchErrorSuffices { | |||
if strings.HasSuffix(line, suffix) { | |||
filepath := strings.TrimSpace(strings.TrimSuffix(line[len(errorPrefix):], suffix)) | |||
if filepath != "" { | |||
conflicts.Add(filepath) | |||
} | |||
break | |||
err = cmdApply.Run(&git.RunOpts{ | |||
Dir: tmpBasePath, | |||
Stderr: stderrWriter, | |||
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { | |||
// Close the writer end of the pipe to begin processing | |||
_ = stderrWriter.Close() | |||
defer func() { | |||
// Close the reader on return to terminate the git command if necessary | |||
_ = stderrReader.Close() | |||
}() | |||
const prefix = "error: patch failed:" | |||
const errorPrefix = "error: " | |||
const threewayFailed = "Failed to perform three-way merge..." | |||
const appliedPatchPrefix = "Applied patch to '" | |||
const withConflicts = "' with conflicts." | |||
conflicts := make(container.Set[string]) | |||
// Now scan the output from the command | |||
scanner := bufio.NewScanner(stderrReader) | |||
for scanner.Scan() { | |||
line := scanner.Text() | |||
log.Trace("PullRequest[%d].testPatch: stderr: %s", pr.ID, line) | |||
if strings.HasPrefix(line, prefix) { | |||
conflict = true | |||
filepath := strings.TrimSpace(strings.Split(line[len(prefix):], ":")[0]) | |||
conflicts.Add(filepath) | |||
} else if is3way && line == threewayFailed { | |||
conflict = true | |||
} else if strings.HasPrefix(line, errorPrefix) { | |||
conflict = true | |||
for _, suffix := range patchErrorSuffices { | |||
if strings.HasSuffix(line, suffix) { | |||
filepath := strings.TrimSpace(strings.TrimSuffix(line[len(errorPrefix):], suffix)) | |||
if filepath != "" { | |||
conflicts.Add(filepath) | |||
} | |||
} | |||
} else if is3way && strings.HasPrefix(line, appliedPatchPrefix) && strings.HasSuffix(line, withConflicts) { | |||
conflict = true | |||
filepath := strings.TrimPrefix(strings.TrimSuffix(line, withConflicts), appliedPatchPrefix) | |||
if filepath != "" { | |||
conflicts.Add(filepath) | |||
break | |||
} | |||
} | |||
// only list 10 conflicted files | |||
if len(conflicts) >= 10 { | |||
break | |||
} else if is3way && strings.HasPrefix(line, appliedPatchPrefix) && strings.HasSuffix(line, withConflicts) { | |||
conflict = true | |||
filepath := strings.TrimPrefix(strings.TrimSuffix(line, withConflicts), appliedPatchPrefix) | |||
if filepath != "" { | |||
conflicts.Add(filepath) | |||
} | |||
} | |||
// only list 10 conflicted files | |||
if len(conflicts) >= 10 { | |||
break | |||
} | |||
} | |||
if len(conflicts) > 0 { | |||
pr.ConflictedFiles = make([]string, 0, len(conflicts)) | |||
for key := range conflicts { | |||
pr.ConflictedFiles = append(pr.ConflictedFiles, key) | |||
} | |||
if len(conflicts) > 0 { | |||
pr.ConflictedFiles = make([]string, 0, len(conflicts)) | |||
for key := range conflicts { | |||
pr.ConflictedFiles = append(pr.ConflictedFiles, key) | |||
} | |||
} | |||
return nil | |||
}, | |||
}) | |||
return nil | |||
}, | |||
}) | |||
// 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. |
@@ -23,7 +23,7 @@ import ( | |||
) | |||
// GitFsckRepos calls 'git fsck' to check repository health. | |||
func GitFsckRepos(ctx context.Context, timeout time.Duration, args []git.CmdArg) error { | |||
func GitFsckRepos(ctx context.Context, timeout time.Duration, args git.TrustedCmdArgs) error { | |||
log.Trace("Doing: GitFsck") | |||
if err := db.Iterate( | |||
@@ -47,10 +47,10 @@ func GitFsckRepos(ctx context.Context, timeout time.Duration, args []git.CmdArg) | |||
} | |||
// GitFsckRepo calls 'git fsck' to check an individual repository's health. | |||
func GitFsckRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Duration, args []git.CmdArg) error { | |||
func GitFsckRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Duration, args git.TrustedCmdArgs) error { | |||
log.Trace("Running health check on repository %-v", repo) | |||
repoPath := repo.RepoPath() | |||
if err := git.Fsck(ctx, repoPath, timeout, args...); err != nil { | |||
if err := git.Fsck(ctx, repoPath, timeout, args); err != nil { | |||
log.Warn("Failed to health check repository (%-v): %v", repo, err) | |||
if err = system_model.CreateRepositoryNotice("Failed to health check repository (%s): %v", repo.FullName(), err); err != nil { | |||
log.Error("CreateRepositoryNotice: %v", err) | |||
@@ -60,9 +60,8 @@ func GitFsckRepo(ctx context.Context, repo *repo_model.Repository, timeout time. | |||
} | |||
// GitGcRepos calls 'git gc' to remove unnecessary files and optimize the local repository | |||
func GitGcRepos(ctx context.Context, timeout time.Duration, args ...git.CmdArg) error { | |||
func GitGcRepos(ctx context.Context, timeout time.Duration, args git.TrustedCmdArgs) error { | |||
log.Trace("Doing: GitGcRepos") | |||
args = append([]git.CmdArg{"gc"}, args...) | |||
if err := db.Iterate( | |||
ctx, | |||
@@ -86,9 +85,9 @@ func GitGcRepos(ctx context.Context, timeout time.Duration, args ...git.CmdArg) | |||
} | |||
// GitGcRepo calls 'git gc' to remove unnecessary files and optimize the local repository | |||
func GitGcRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Duration, args []git.CmdArg) error { | |||
func GitGcRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Duration, args git.TrustedCmdArgs) error { | |||
log.Trace("Running git gc on %-v", repo) | |||
command := git.NewCommand(ctx, args...). | |||
command := git.NewCommand(ctx, "gc").AddArguments(args...). | |||
SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName())) | |||
var stdout string | |||
var err error |
@@ -141,14 +141,12 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user | |||
stdout := &strings.Builder{} | |||
stderr := &strings.Builder{} | |||
args := []git.CmdArg{"apply", "--index", "--recount", "--cached", "--ignore-whitespace", "--whitespace=fix", "--binary"} | |||
cmdApply := git.NewCommand(ctx, "apply", "--index", "--recount", "--cached", "--ignore-whitespace", "--whitespace=fix", "--binary") | |||
if git.CheckGitVersionAtLeast("2.32") == nil { | |||
args = append(args, "-3") | |||
cmdApply.AddArguments("-3") | |||
} | |||
cmd := git.NewCommand(ctx, args...) | |||
if err := cmd.Run(&git.RunOpts{ | |||
if err := cmdApply.Run(&git.RunOpts{ | |||
Dir: t.basePath, | |||
Stdout: stdout, | |||
Stderr: stderr, |
@@ -233,11 +233,9 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(parent string, author, co | |||
_, _ = messageBytes.WriteString(message) | |||
_, _ = messageBytes.WriteString("\n") | |||
var args []git.CmdArg | |||
cmdCommitTree := git.NewCommand(t.ctx, "commit-tree").AddDynamicArguments(treeHash) | |||
if parent != "" { | |||
args = []git.CmdArg{"commit-tree", git.CmdArgCheck(treeHash), "-p", git.CmdArgCheck(parent)} | |||
} else { | |||
args = []git.CmdArg{"commit-tree", git.CmdArgCheck(treeHash)} | |||
cmdCommitTree.AddOptionValues("-p", parent) | |||
} | |||
var sign bool | |||
@@ -249,7 +247,7 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(parent string, author, co | |||
sign, keyID, signer, _ = asymkey_service.SignInitialCommit(t.ctx, t.repo.RepoPath(), author) | |||
} | |||
if sign { | |||
args = append(args, git.CmdArg("-S"+keyID)) | |||
cmdCommitTree.AddOptionFormat("-S%s", keyID) | |||
if t.repo.GetTrustModel() == repo_model.CommitterTrustModel || t.repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel { | |||
if committerSig.Name != authorSig.Name || committerSig.Email != authorSig.Email { | |||
// Add trailers | |||
@@ -264,7 +262,7 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(parent string, author, co | |||
committerSig = signer | |||
} | |||
} else { | |||
args = append(args, "--no-gpg-sign") | |||
cmdCommitTree.AddArguments("--no-gpg-sign") | |||
} | |||
if signoff { | |||
@@ -281,7 +279,7 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(parent string, author, co | |||
stdout := new(bytes.Buffer) | |||
stderr := new(bytes.Buffer) | |||
if err := git.NewCommand(t.ctx, args...). | |||
if err := cmdCommitTree. | |||
Run(&git.RunOpts{ | |||
Env: env, | |||
Dir: t.basePath, | |||
@@ -364,7 +362,7 @@ func (t *TemporaryUploadRepository) DiffIndex() (*gitdiff.Diff, error) { | |||
t.repo.FullName(), err, stderr) | |||
} | |||
diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(t.ctx, t.basePath, "--cached", "HEAD") | |||
diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(t.ctx, t.basePath, git.TrustedCmdArgs{"--cached"}, "HEAD") | |||
if err != nil { | |||
return nil, err | |||
} |
@@ -370,7 +370,7 @@ func CreateOrUpdateRepoFile(ctx context.Context, repo *repo_model.Repository, do | |||
if setting.LFS.StartServer && hasOldBranch { | |||
// Check there is no way this can return multiple infos | |||
filename2attribute2info, err := t.gitRepo.CheckAttribute(git.CheckAttributeOpts{ | |||
Attributes: []git.CmdArg{"filter"}, | |||
Attributes: []string{"filter"}, | |||
Filenames: []string{treePath}, | |||
CachedOnly: true, | |||
}) |
@@ -96,7 +96,7 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use | |||
var filename2attribute2info map[string]map[string]string | |||
if setting.LFS.StartServer { | |||
filename2attribute2info, err = t.gitRepo.CheckAttribute(git.CheckAttributeOpts{ | |||
Attributes: []git.CmdArg{"filter"}, | |||
Attributes: []string{"filter"}, | |||
Filenames: names, | |||
CachedOnly: true, | |||
}) |
@@ -154,16 +154,16 @@ func doGitAddRemote(dstPath, remoteName string, u *url.URL) func(*testing.T) { | |||
} | |||
} | |||
func doGitPushTestRepository(dstPath string, args ...git.CmdArg) func(*testing.T) { | |||
func doGitPushTestRepository(dstPath string, args ...string) func(*testing.T) { | |||
return func(t *testing.T) { | |||
_, _, err := git.NewCommand(git.DefaultContext, append([]git.CmdArg{"push", "-u"}, args...)...).RunStdString(&git.RunOpts{Dir: dstPath}) | |||
_, _, err := git.NewCommand(git.DefaultContext, "push", "-u").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(&git.RunOpts{Dir: dstPath}) | |||
assert.NoError(t, err) | |||
} | |||
} | |||
func doGitPushTestRepositoryFail(dstPath string, args ...git.CmdArg) func(*testing.T) { | |||
func doGitPushTestRepositoryFail(dstPath string, args ...string) func(*testing.T) { | |||
return func(t *testing.T) { | |||
_, _, err := git.NewCommand(git.DefaultContext, append([]git.CmdArg{"push"}, args...)...).RunStdString(&git.RunOpts{Dir: dstPath}) | |||
_, _, err := git.NewCommand(git.DefaultContext, "push").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(&git.RunOpts{Dir: dstPath}) | |||
assert.Error(t, err) | |||
} | |||
} | |||
@@ -175,23 +175,23 @@ func doGitCreateBranch(dstPath, branch string) func(*testing.T) { | |||
} | |||
} | |||
func doGitCheckoutBranch(dstPath string, args ...git.CmdArg) func(*testing.T) { | |||
func doGitCheckoutBranch(dstPath string, args ...string) func(*testing.T) { | |||
return func(t *testing.T) { | |||
_, _, err := git.NewCommandNoGlobals(append(append(git.AllowLFSFiltersArgs(), "checkout"), args...)...).RunStdString(&git.RunOpts{Dir: dstPath}) | |||
_, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, git.AllowLFSFiltersArgs()...).AddArguments("checkout").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(&git.RunOpts{Dir: dstPath}) | |||
assert.NoError(t, err) | |||
} | |||
} | |||
func doGitMerge(dstPath string, args ...git.CmdArg) func(*testing.T) { | |||
func doGitMerge(dstPath string, args ...string) func(*testing.T) { | |||
return func(t *testing.T) { | |||
_, _, err := git.NewCommand(git.DefaultContext, append([]git.CmdArg{"merge"}, args...)...).RunStdString(&git.RunOpts{Dir: dstPath}) | |||
_, _, err := git.NewCommand(git.DefaultContext, "merge").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(&git.RunOpts{Dir: dstPath}) | |||
assert.NoError(t, err) | |||
} | |||
} | |||
func doGitPull(dstPath string, args ...git.CmdArg) func(*testing.T) { | |||
func doGitPull(dstPath string, args ...string) func(*testing.T) { | |||
return func(t *testing.T) { | |||
_, _, err := git.NewCommandNoGlobals(append(append(git.AllowLFSFiltersArgs(), "pull"), args...)...).RunStdString(&git.RunOpts{Dir: dstPath}) | |||
_, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, git.AllowLFSFiltersArgs()...).AddArguments("pull").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(&git.RunOpts{Dir: dstPath}) | |||
assert.NoError(t, err) | |||
} | |||
} |
@@ -509,7 +509,7 @@ func doCreatePRAndSetManuallyMerged(ctx, baseCtx APITestContext, dstPath, baseBr | |||
})) | |||
t.Run("CreateHeadBranch", doGitCreateBranch(dstPath, headBranch)) | |||
t.Run("PushToHeadBranch", doGitPushTestRepository(dstPath, "origin", git.CmdArgCheck(headBranch))) | |||
t.Run("PushToHeadBranch", doGitPushTestRepository(dstPath, "origin", headBranch)) | |||
t.Run("CreateEmptyPullRequest", func(t *testing.T) { | |||
pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, baseBranch, headBranch)(t) | |||
assert.NoError(t, err) |
@@ -287,7 +287,7 @@ func TestCantMergeUnrelated(t *testing.T) { | |||
assert.NoError(t, err) | |||
sha := strings.TrimSpace(stdout.String()) | |||
_, _, err = git.NewCommand(git.DefaultContext, "update-index", "--add", "--replace", "--cacheinfo", "100644", git.CmdArgCheck(sha), "somewher-over-the-rainbow").RunStdString(&git.RunOpts{Dir: path}) | |||
_, _, err = git.NewCommand(git.DefaultContext, "update-index", "--add", "--replace", "--cacheinfo").AddDynamicArguments("100644", sha, "somewher-over-the-rainbow").RunStdString(&git.RunOpts{Dir: path}) | |||
assert.NoError(t, err) | |||
treeSha, _, err := git.NewCommand(git.DefaultContext, "write-tree").RunStdString(&git.RunOpts{Dir: path}) |