diff options
author | Patrick Steinhardt <psteinhardt@gitlab.com> | 2020-12-15 12:20:57 +0300 |
---|---|---|
committer | Patrick Steinhardt <psteinhardt@gitlab.com> | 2020-12-15 12:20:57 +0300 |
commit | d52ea41f12a33a5213bf0067f0bc0beabb949556 (patch) | |
tree | babbecd901b0d133af03ef632fe4afe13b0ccebf | |
parent | 1296ac533238b39a382a77ced57db6994e28d032 (diff) | |
parent | d28dc57cdd449c5cd4c2e0d2f75c3ef2aaa2a565 (diff) |
Merge branch 'pks-git-safecmd-refactorings' into 'master'
git: Improve SafeCmd interface
See merge request gitlab-org/gitaly!2891
47 files changed, 403 insertions, 329 deletions
diff --git a/cmd/gitaly-ssh/upload_pack_test.go b/cmd/gitaly-ssh/upload_pack_test.go index 618e98e33..8529ac980 100644 --- a/cmd/gitaly-ssh/upload_pack_test.go +++ b/cmd/gitaly-ssh/upload_pack_test.go @@ -86,13 +86,13 @@ func TestVisibilityOfHiddenRefs(t *testing.T) { } stdout := &bytes.Buffer{} - cmd, err := git.SafeBareCmd(ctx, git.CmdStream{Out: stdout}, env, nil, git.SubCmd{ + cmd, err := git.SafeBareCmd(ctx, env, nil, git.SubCmd{ Name: "ls-remote", Args: []string{ fmt.Sprintf("%s:%s", "git@localhost", testRepoPath), keepAroundRef, }, - }) + }, git.WithStdout(stdout)) require.NoError(t, err) err = cmd.Wait() diff --git a/internal/git/catfile/batch.go b/internal/git/catfile/batch.go index 358ba9ade..58bf98c30 100644 --- a/internal/git/catfile/batch.go +++ b/internal/git/catfile/batch.go @@ -51,9 +51,18 @@ func newBatchProcess(ctx context.Context, repo repository.GitRepo) (*batchProces ctx = correlation.ContextWithCorrelation(ctx, "") ctx = opentracing.ContextWithSpan(ctx, nil) - batchCmd, err := git.SafeBareCmd(ctx, git.CmdStream{In: stdinReader}, env, - []git.Option{git.ValueFlag{Name: "--git-dir", Value: repoPath}}, - git.SubCmd{Name: "cat-file", Flags: []git.Option{git.Flag{Name: "--batch"}}}) + batchCmd, err := git.SafeBareCmd(ctx, env, + []git.GlobalOption{ + git.ValueFlag{Name: "--git-dir", Value: repoPath}, + }, + git.SubCmd{ + Name: "cat-file", + Flags: []git.Option{ + git.Flag{Name: "--batch"}, + }, + }, + git.WithStdin(stdinReader), + ) if err != nil { return nil, err } diff --git a/internal/git/catfile/batchcheck.go b/internal/git/catfile/batchcheck.go index 9a283f86a..28afdd9b7 100644 --- a/internal/git/catfile/batchcheck.go +++ b/internal/git/catfile/batchcheck.go @@ -37,9 +37,18 @@ func newBatchCheck(ctx context.Context, repo repository.GitRepo) (*batchCheck, e ctx = correlation.ContextWithCorrelation(ctx, "") ctx = opentracing.ContextWithSpan(ctx, nil) - batchCmd, err := git.SafeBareCmd(ctx, git.CmdStream{In: stdinReader}, env, - []git.Option{git.ValueFlag{Name: "--git-dir", Value: repoPath}}, - git.SubCmd{Name: "cat-file", Flags: []git.Option{git.Flag{Name: "--batch-check"}}}) + batchCmd, err := git.SafeBareCmd(ctx, env, + []git.GlobalOption{ + git.ValueFlag{Name: "--git-dir", Value: repoPath}, + }, + git.SubCmd{ + Name: "cat-file", + Flags: []git.Option{ + git.Flag{Name: "--batch-check"}, + }, + }, + git.WithStdin(stdinReader), + ) if err != nil { return nil, err } diff --git a/internal/git/command.go b/internal/git/command.go index d4c7e883a..72a7f8516 100644 --- a/internal/git/command.go +++ b/internal/git/command.go @@ -35,7 +35,7 @@ func (cf *CommandFactory) gitPath() string { } // unsafeCmdWithEnv creates a git.unsafeCmd with the given args, environment, and Repository -func (cf *CommandFactory) unsafeCmdWithEnv(ctx context.Context, extraEnv []string, stream CmdStream, repo repository.GitRepo, args ...string) (*command.Command, error) { +func (cf *CommandFactory) unsafeCmdWithEnv(ctx context.Context, extraEnv []string, stream cmdStream, repo repository.GitRepo, args ...string) (*command.Command, error) { args, env, err := cf.argsAndEnv(repo, args...) if err != nil { return nil, err @@ -56,7 +56,7 @@ func (cf *CommandFactory) unsafeStdinCmd(ctx context.Context, extraEnv []string, env = append(env, extraEnv...) - return cf.unsafeBareCmd(ctx, CmdStream{In: command.SetupStdin}, env, args...) + return cf.unsafeBareCmd(ctx, cmdStream{In: command.SetupStdin}, env, args...) } func (cf *CommandFactory) argsAndEnv(repo repository.GitRepo, args ...string) ([]string, []string, error) { @@ -72,7 +72,7 @@ func (cf *CommandFactory) argsAndEnv(repo repository.GitRepo, args ...string) ([ } // unsafeBareCmd creates a git.Command with the given args, stdin/stdout/stderr, and env -func (cf *CommandFactory) unsafeBareCmd(ctx context.Context, stream CmdStream, env []string, args ...string) (*command.Command, error) { +func (cf *CommandFactory) unsafeBareCmd(ctx context.Context, stream cmdStream, env []string, args ...string) (*command.Command, error) { env = append(env, command.GitEnv...) cmd, err := command.New(ctx, exec.Command(cf.gitPath(), args...), stream.In, stream.Out, stream.Err, env...) @@ -88,7 +88,7 @@ func (cf *CommandFactory) unsafeBareCmd(ctx context.Context, stream CmdStream, e } // unsafeBareCmdInDir calls unsafeBareCmd in dir. -func (cf *CommandFactory) unsafeBareCmdInDir(ctx context.Context, dir string, stream CmdStream, env []string, args ...string) (*command.Command, error) { +func (cf *CommandFactory) unsafeBareCmdInDir(ctx context.Context, dir string, stream cmdStream, env []string, args ...string) (*command.Command, error) { env = append(env, command.GitEnv...) cmd1 := exec.Command(cf.gitPath(), args...) diff --git a/internal/git/command_test.go b/internal/git/command_test.go index 1215fbbb1..55032a8de 100644 --- a/internal/git/command_test.go +++ b/internal/git/command_test.go @@ -29,7 +29,7 @@ func TestGitCommandProxy(t *testing.T) { dir, cleanup := testhelper.TempDir(t) defer cleanup() - cmd, err := NewCommandFactory().unsafeBareCmd(ctx, CmdStream{}, nil, "clone", "http://gitlab.com/bogus-repo", dir) + cmd, err := NewCommandFactory().unsafeBareCmd(ctx, cmdStream{}, nil, "clone", "http://gitlab.com/bogus-repo", dir) require.NoError(t, err) err = cmd.Wait() diff --git a/internal/git/objectpool/clone.go b/internal/git/objectpool/clone.go index af9b00688..f9330610d 100644 --- a/internal/git/objectpool/clone.go +++ b/internal/git/objectpool/clone.go @@ -17,7 +17,7 @@ func (o *ObjectPool) clone(ctx context.Context, repo *gitalypb.Repository) error return err } - cmd, err := git.SafeCmdWithoutRepo(ctx, git.CmdStream{}, nil, + cmd, err := git.SafeCmdWithoutRepo(ctx, nil, git.SubCmd{ Name: "clone", Flags: []git.Option{ diff --git a/internal/git/objectpool/fetch.go b/internal/git/objectpool/fetch.go index ddbfc1e23..995f124a1 100644 --- a/internal/git/objectpool/fetch.go +++ b/internal/git/objectpool/fetch.go @@ -186,7 +186,7 @@ func rescueDanglingObjects(ctx context.Context, repo repository.GitRepo) error { } func repackPool(ctx context.Context, pool repository.GitRepo) error { - repackArgs := []git.Option{ + repackArgs := []git.GlobalOption{ git.ValueFlag{"-c", "pack.island=" + sourceRefNamespace + "/he(a)ds"}, git.ValueFlag{"-c", "pack.island=" + sourceRefNamespace + "/t(a)gs"}, git.ValueFlag{"-c", "pack.islandCore=a"}, diff --git a/internal/git/objectpool/pool.go b/internal/git/objectpool/pool.go index bf73f2929..df9e7c285 100644 --- a/internal/git/objectpool/pool.go +++ b/internal/git/objectpool/pool.go @@ -124,7 +124,7 @@ func (o *ObjectPool) Init(ctx context.Context) (err error) { return nil } - cmd, err := git.SafeCmdWithoutRepo(ctx, git.CmdStream{}, nil, + cmd, err := git.SafeCmdWithoutRepo(ctx, nil, git.SubCmd{ Name: "init", Flags: []git.Option{ diff --git a/internal/git/proto.go b/internal/git/proto.go index cb8982712..97dac733b 100644 --- a/internal/git/proto.go +++ b/internal/git/proto.go @@ -40,16 +40,3 @@ func ValidateRevisionAllowEmpty(revision []byte) error { func ValidateRevision(revision []byte) error { return validateRevision(revision, false) } - -// BuildGitOptions helps to generate options to the git command. -// If gitOpts is not empty then its values are passed as part of -// the "-c" option of the git command, the other values are passed along with the subcommand. -func BuildGitOptions(gitOpts []string, otherOpts ...string) []string { - args := []string{} - - for _, opt := range gitOpts { - args = append(args, "-c", opt) - } - - return append(args, otherOpts...) -} diff --git a/internal/git/receivepack.go b/internal/git/receivepack.go index a253469a1..97fbc98f4 100644 --- a/internal/git/receivepack.go +++ b/internal/git/receivepack.go @@ -2,8 +2,8 @@ package git // ReceivePackConfig contains config options we want to enforce when // receiving a push with git-receive-pack. -func ReceivePackConfig() []Option { - return []Option{ +func ReceivePackConfig() []GlobalOption { + return []GlobalOption{ // In case the repository belongs to an object pool, we want to prevent // Git from including the pool's refs in the ref advertisement. We do // this by rigging core.alternateRefsCommand to produce no output. diff --git a/internal/git/remote.go b/internal/git/remote.go index b6b29dcfb..4026a3a01 100644 --- a/internal/git/remote.go +++ b/internal/git/remote.go @@ -120,10 +120,11 @@ func (repo RepositoryRemote) Add(ctx context.Context, name, url string, opts Rem stderr := bytes.Buffer{} cmd, err := SafeCmd(ctx, repo.repo, nil, - SubCmd{ - Name: "remote", - Flags: append([]Option{SubSubCmd{Name: "add"}}, opts.buildFlags()...), - Args: []string{name, url}, + SubSubCmd{ + Name: "remote", + Action: "add", + Flags: opts.buildFlags(), + Args: []string{name, url}, }, WithStderr(&stderr), WithRefTxHook(ctx, helper.ProtoRepoFromRepo(repo.repo), config.Config), @@ -158,10 +159,10 @@ func (repo RepositoryRemote) Remove(ctx context.Context, name string) error { var stderr bytes.Buffer cmd, err := SafeCmd(ctx, repo.repo, nil, - SubCmd{ - Name: "remote", - Flags: []Option{SubSubCmd{Name: "remove"}}, - Args: []string{name}, + SubSubCmd{ + Name: "remote", + Action: "remove", + Args: []string{name}, }, WithStderr(&stderr), WithRefTxHook(ctx, helper.ProtoRepoFromRepo(repo.repo), config.Config), @@ -214,10 +215,11 @@ func (repo RepositoryRemote) SetURL(ctx context.Context, name, url string, opts var stderr bytes.Buffer cmd, err := SafeCmd(ctx, repo.repo, nil, - SubCmd{ - Name: "remote", - Flags: append([]Option{SubSubCmd{Name: "set-url"}}, opts.buildFlags()...), - Args: []string{name, url}, + SubSubCmd{ + Name: "remote", + Action: "set-url", + Flags: opts.buildFlags(), + Args: []string{name, url}, }, WithStderr(&stderr), WithRefTxHook(ctx, helper.ProtoRepoFromRepo(repo.repo), config.Config), diff --git a/internal/git/remote/remote.go b/internal/git/remote/remote.go index a21bb5af7..defb0f5e4 100644 --- a/internal/git/remote/remote.go +++ b/internal/git/remote/remote.go @@ -12,10 +12,10 @@ import ( //Remove removes the remote from repository func Remove(ctx context.Context, cfg config.Cfg, repo repository.GitRepo, name string) error { - cmd, err := git.SafeCmd(ctx, repo, nil, git.SubCmd{ - Name: "remote", - Flags: []git.Option{git.SubSubCmd{Name: "remove"}}, - Args: []string{name}, + cmd, err := git.SafeCmd(ctx, repo, nil, git.SubSubCmd{ + Name: "remote", + Action: "remove", + Args: []string{name}, }, git.WithRefTxHook(ctx, helper.ProtoRepoFromRepo(repo), cfg)) if err != nil { return err diff --git a/internal/git/repository.go b/internal/git/repository.go index e72f2e69d..5d15c9858 100644 --- a/internal/git/repository.go +++ b/internal/git/repository.go @@ -56,7 +56,7 @@ type FetchOpts struct { // Env is a list of env vars to pass to the cmd. Env []string // Global is a list of global flags to use with 'git' command. - Global []Option + Global []GlobalOption // Prune if set fetch removes any remote-tracking references that no longer exist on the remote. // https://git-scm.com/docs/git-fetch#Documentation/git-fetch.txt---prune Prune bool @@ -117,7 +117,7 @@ func NewRepository(repo repository.GitRepo) *LocalRepository { // command creates a Git Command with the given args and Repository, executed // in the Repository. It validates the arguments in the command before // executing. -func (repo *LocalRepository) command(ctx context.Context, globals []Option, cmd SubCmd, opts ...CmdOpt) (*command.Command, error) { +func (repo *LocalRepository) command(ctx context.Context, globals []GlobalOption, cmd SubCmd, opts ...CmdOpt) (*command.Command, error) { return SafeCmd(ctx, repo.repo, globals, cmd, opts...) } diff --git a/internal/git/repository_test.go b/internal/git/repository_test.go index 0720c0af9..d371f3dfa 100644 --- a/internal/git/repository_test.go +++ b/internal/git/repository_test.go @@ -571,7 +571,7 @@ func TestLocalRepository_FetchRemote(t *testing.T) { ctx, "source", FetchOpts{ - Global: []Option{ValueFlag{Name: "-c", Value: "fetch.prune=true"}}, + Global: []GlobalOption{ValueFlag{Name: "-c", Value: "fetch.prune=true"}}, }), ) diff --git a/internal/git/safecmd.go b/internal/git/safecmd.go index 24638d4e7..82a90658e 100644 --- a/internal/git/safecmd.go +++ b/internal/git/safecmd.go @@ -37,8 +37,7 @@ func incrInvalidArg(subcmdName string) { // Cmd is an interface for safe git commands type Cmd interface { - ValidateArgs() ([]string, error) - IsCmd() + CommandArgs() ([]string, error) Subcommand() string } @@ -50,22 +49,17 @@ type SubCmd struct { PostSepArgs []string // post separator (i.e. "--") positional args } -// CmdStream represents standard input/output streams for a command -type CmdStream struct { +// cmdStream represents standard input/output streams for a command +type cmdStream struct { In io.Reader // standard input Out, Err io.Writer // standard output and error } -var subCmdNameRegex = regexp.MustCompile(`^[[:alnum:]]+(-[[:alnum:]]+)*$`) - -// IsCmd allows SubCmd to satisfy the Cmd interface -func (sc SubCmd) IsCmd() {} - // Subcommand returns the subcommand name func (sc SubCmd) Subcommand() string { return sc.Name } -// ValidateArgs checks all arguments in the sub command and validates them -func (sc SubCmd) ValidateArgs() ([]string, error) { +// CommandArgs checks all arguments in the sub command and validates them +func (sc SubCmd) CommandArgs() ([]string, error) { var safeArgs []string if _, ok := subcommands[sc.Name]; !ok { @@ -73,57 +67,100 @@ func (sc SubCmd) ValidateArgs() ([]string, error) { } safeArgs = append(safeArgs, sc.Name) - for _, o := range sc.Flags { - args, err := o.ValidateArgs() + commandArgs, err := assembleCommandArgs(sc.Name, sc.Flags, sc.Args, sc.PostSepArgs) + if err != nil { + return nil, err + } + safeArgs = append(safeArgs, commandArgs...) + + return safeArgs, nil +} + +func assembleCommandArgs(command string, flags []Option, args []string, postSepArgs []string) ([]string, error) { + var commandArgs []string + + for _, o := range flags { + args, err := o.OptionArgs() if err != nil { return nil, err } - safeArgs = append(safeArgs, args...) + commandArgs = append(commandArgs, args...) } - for _, a := range sc.Args { + for _, a := range args { if err := validatePositionalArg(a); err != nil { return nil, err } - safeArgs = append(safeArgs, a) + commandArgs = append(commandArgs, a) } - if supportsEndOfOptions(sc.Name) { - safeArgs = append(safeArgs, "--end-of-options") + if supportsEndOfOptions(command) { + commandArgs = append(commandArgs, "--end-of-options") } - if len(sc.PostSepArgs) > 0 { - safeArgs = append(safeArgs, "--") + if len(postSepArgs) > 0 { + commandArgs = append(commandArgs, "--") } // post separator args do not need any validation - safeArgs = append(safeArgs, sc.PostSepArgs...) + commandArgs = append(commandArgs, postSepArgs...) - return safeArgs, nil + return commandArgs, nil +} + +// GlobalOption is an interface for all options which can be globally applied +// to git commands. This is the command-inspecific part before the actual +// command that's being run, e.g. the `-c` part in `git -c foo.bar=value +// command`. +type GlobalOption interface { + GlobalArgs() ([]string, error) } // Option is a git command line flag with validation logic type Option interface { - IsOption() - ValidateArgs() ([]string, error) + OptionArgs() ([]string, error) } // SubSubCmd is a positional argument that appears in the list of options for // a subcommand. type SubSubCmd struct { + // Name is the name of the subcommand, e.g. "remote" in `git remote set-url` Name string + // Action is the action of the subcommand, e.g. "set-url" in `git remote set-url` + Action string + + // Flags are optional flags before the positional args + Flags []Option + // Args are positional arguments after all flags + Args []string + // PostSepArgs are positional args after the "--" separator + PostSepArgs []string } -// IsOption is a method present on all Flag interface implementations -func (SubSubCmd) IsOption() {} +func (sc SubSubCmd) Subcommand() string { return sc.Name } + +var actionRegex = regexp.MustCompile(`^[[:alnum:]]+[-[:alnum:]]*$`) + +func (sc SubSubCmd) CommandArgs() ([]string, error) { + var safeArgs []string -// ValidateArgs returns an error if the command name or options are not -// sanitary -func (sc SubSubCmd) ValidateArgs() ([]string, error) { - if !subCmdNameRegex.MatchString(sc.Name) { - return nil, fmt.Errorf("invalid sub-sub command name %q: %w", sc.Name, ErrInvalidArg) + if _, ok := subcommands[sc.Name]; !ok { + return nil, fmt.Errorf("invalid sub command name %q: %w", sc.Name, ErrInvalidArg) } - return []string{sc.Name}, nil + safeArgs = append(safeArgs, sc.Name) + + if !actionRegex.MatchString(sc.Action) { + return nil, fmt.Errorf("invalid sub command action %q: %w", sc.Action, ErrInvalidArg) + } + safeArgs = append(safeArgs, sc.Action) + + commandArgs, err := assembleCommandArgs(sc.Name, sc.Flags, sc.Args, sc.PostSepArgs) + if err != nil { + return nil, err + } + safeArgs = append(safeArgs, commandArgs...) + + return safeArgs, nil } // ConfigPair is a sub-command option for use with commands like "git config" @@ -138,13 +175,10 @@ type ConfigPair struct { Scope string } -// IsOption is a method present on all Flag interface implementations -func (ConfigPair) IsOption() {} - var configKeyRegex = regexp.MustCompile(`^[[:alnum:]]+[-[:alnum:]]*\.(.+\.)*[[:alnum:]]+[-[:alnum:]]*$`) -// ValidateArgs validates the config pair args -func (cp ConfigPair) ValidateArgs() ([]string, error) { +// OptionArgs validates the config pair args +func (cp ConfigPair) OptionArgs() ([]string, error) { if !configKeyRegex.MatchString(cp.Key) { return nil, fmt.Errorf("config key %q failed regexp validation: %w", cp.Key, ErrInvalidArg) } @@ -157,11 +191,12 @@ type Flag struct { Name string } -// IsOption is a method present on all Flag interface implementations -func (Flag) IsOption() {} +func (f Flag) GlobalArgs() ([]string, error) { + return f.OptionArgs() +} -// ValidateArgs returns an error if the flag is not sanitary -func (f Flag) ValidateArgs() ([]string, error) { +// OptionArgs returns an error if the flag is not sanitary +func (f Flag) OptionArgs() ([]string, error) { if !flagRegex.MatchString(f.Name) { return nil, fmt.Errorf("flag %q failed regex validation: %w", f.Name, ErrInvalidArg) } @@ -175,11 +210,12 @@ type ValueFlag struct { Value string } -// IsOption is a method present on all Flag interface implementations -func (ValueFlag) IsOption() {} +func (vf ValueFlag) GlobalArgs() ([]string, error) { + return vf.OptionArgs() +} -// ValidateArgs returns an error if the flag is not sanitary -func (vf ValueFlag) ValidateArgs() ([]string, error) { +// OptionArgs returns an error if the flag is not sanitary +func (vf ValueFlag) OptionArgs() ([]string, error) { if !flagRegex.MatchString(vf.Name) { return nil, fmt.Errorf("value flag %q failed regex validation: %w", vf.Name, ErrInvalidArg) } @@ -201,8 +237,8 @@ func validatePositionalArg(arg string) error { } // ConvertGlobalOptions converts a protobuf message to command-line flags -func ConvertGlobalOptions(options *gitalypb.GlobalOptions) []Option { - var globals []Option +func ConvertGlobalOptions(options *gitalypb.GlobalOptions) []GlobalOption { + var globals []GlobalOption if options == nil { return globals @@ -217,7 +253,7 @@ func ConvertGlobalOptions(options *gitalypb.GlobalOptions) []Option { type cmdCfg struct { env []string - globals []Option + globals []GlobalOption stdin io.Reader stdout io.Writer stderr io.Writer @@ -284,13 +320,13 @@ func handleOpts(ctx context.Context, sc Cmd, cc *cmdCfg, opts []CmdOpt) error { // SafeCmd creates a command.Command with the given args and Repository. It // validates the arguments in the command before executing. -func SafeCmd(ctx context.Context, repo repository.GitRepo, globals []Option, sc Cmd, opts ...CmdOpt) (*command.Command, error) { +func SafeCmd(ctx context.Context, repo repository.GitRepo, globals []GlobalOption, sc Cmd, opts ...CmdOpt) (*command.Command, error) { return SafeCmdWithEnv(ctx, nil, repo, globals, sc, opts...) } // SafeCmdWithEnv creates a command.Command with the given args, environment, and Repository. It // validates the arguments in the command before executing. -func SafeCmdWithEnv(ctx context.Context, env []string, repo repository.GitRepo, globals []Option, sc Cmd, opts ...CmdOpt) (*command.Command, error) { +func SafeCmdWithEnv(ctx context.Context, env []string, repo repository.GitRepo, globals []GlobalOption, sc Cmd, opts ...CmdOpt) (*command.Command, error) { cc := &cmdCfg{} if err := handleOpts(ctx, sc, cc, opts); err != nil { @@ -302,16 +338,16 @@ func SafeCmdWithEnv(ctx context.Context, env []string, repo repository.GitRepo, return nil, err } - return NewCommandFactory().unsafeCmdWithEnv(ctx, append(env, cc.env...), CmdStream{ + return NewCommandFactory().unsafeCmdWithEnv(ctx, append(env, cc.env...), cmdStream{ In: cc.stdin, Out: cc.stdout, Err: cc.stderr, }, repo, args...) } -// SafeBareCmd creates a git.Command with the given args, stream, and env. It +// SafeBareCmd creates a git.Command with the given args and env. It // validates the arguments in the command before executing. -func SafeBareCmd(ctx context.Context, stream CmdStream, env []string, globals []Option, sc Cmd, opts ...CmdOpt) (*command.Command, error) { +func SafeBareCmd(ctx context.Context, env []string, globals []GlobalOption, sc Cmd, opts ...CmdOpt) (*command.Command, error) { cc := &cmdCfg{} if err := handleOpts(ctx, sc, cc, opts); err != nil { @@ -323,11 +359,15 @@ func SafeBareCmd(ctx context.Context, stream CmdStream, env []string, globals [] return nil, err } - return NewCommandFactory().unsafeBareCmd(ctx, stream, append(env, cc.env...), args...) + return NewCommandFactory().unsafeBareCmd(ctx, cmdStream{ + In: cc.stdin, + Out: cc.stdout, + Err: cc.stderr, + }, append(env, cc.env...), args...) } // SafeBareCmdInDir runs SafeBareCmd in the dir. -func SafeBareCmdInDir(ctx context.Context, dir string, stream CmdStream, env []string, globals []Option, sc Cmd, opts ...CmdOpt) (*command.Command, error) { +func SafeBareCmdInDir(ctx context.Context, dir string, env []string, globals []GlobalOption, sc Cmd, opts ...CmdOpt) (*command.Command, error) { if dir == "" { return nil, errors.New("no 'dir' provided") } @@ -343,13 +383,17 @@ func SafeBareCmdInDir(ctx context.Context, dir string, stream CmdStream, env []s return nil, err } - return NewCommandFactory().unsafeBareCmdInDir(ctx, dir, stream, append(env, cc.env...), args...) + return NewCommandFactory().unsafeBareCmdInDir(ctx, dir, cmdStream{ + In: cc.stdin, + Out: cc.stdout, + Err: cc.stderr, + }, append(env, cc.env...), args...) } // SafeStdinCmd creates a git.Command with the given args and Repository that is // suitable for Write()ing to. It validates the arguments in the command before // executing. -func SafeStdinCmd(ctx context.Context, repo repository.GitRepo, globals []Option, sc SubCmd, opts ...CmdOpt) (*command.Command, error) { +func SafeStdinCmd(ctx context.Context, repo repository.GitRepo, globals []GlobalOption, sc Cmd, opts ...CmdOpt) (*command.Command, error) { cc := &cmdCfg{} if err := handleOpts(ctx, sc, cc, opts); err != nil { @@ -366,7 +410,7 @@ func SafeStdinCmd(ctx context.Context, repo repository.GitRepo, globals []Option // SafeCmdWithoutRepo works like Command but without a git repository. It // validates the arguments in the command before executing. -func SafeCmdWithoutRepo(ctx context.Context, stream CmdStream, globals []Option, sc SubCmd, opts ...CmdOpt) (*command.Command, error) { +func SafeCmdWithoutRepo(ctx context.Context, globals []GlobalOption, sc Cmd, opts ...CmdOpt) (*command.Command, error) { cc := &cmdCfg{} if err := handleOpts(ctx, sc, cc, opts); err != nil { @@ -378,10 +422,14 @@ func SafeCmdWithoutRepo(ctx context.Context, stream CmdStream, globals []Option, return nil, err } - return NewCommandFactory().unsafeBareCmd(ctx, stream, cc.env, args...) + return NewCommandFactory().unsafeBareCmd(ctx, cmdStream{ + In: cc.stdin, + Out: cc.stdout, + Err: cc.stderr, + }, cc.env, args...) } -func combineArgs(globals []Option, sc Cmd, cc *cmdCfg) (_ []string, err error) { +func combineArgs(globals []GlobalOption, sc Cmd, cc *cmdCfg) (_ []string, err error) { var args []string defer func() { @@ -390,15 +438,15 @@ func combineArgs(globals []Option, sc Cmd, cc *cmdCfg) (_ []string, err error) { } }() - for _, g := range append(globals, cc.globals...) { - gargs, err := g.ValidateArgs() + for _, global := range append(globals, cc.globals...) { + globalArgs, err := global.GlobalArgs() if err != nil { return nil, err } - args = append(args, gargs...) + args = append(args, globalArgs...) } - scArgs, err := sc.ValidateArgs() + scArgs, err := sc.CommandArgs() if err != nil { return nil, err } diff --git a/internal/git/safecmd_test.go b/internal/git/safecmd_test.go index 578d19585..f66eaed44 100644 --- a/internal/git/safecmd_test.go +++ b/internal/git/safecmd_test.go @@ -1,4 +1,4 @@ -package git_test +package git import ( "bytes" @@ -7,7 +7,6 @@ import ( "testing" "github.com/stretchr/testify/require" - "gitlab.com/gitlab-org/gitaly/internal/git" "gitlab.com/gitlab-org/gitaly/internal/git/hooks" "gitlab.com/gitlab-org/gitaly/internal/gitaly/config" "gitlab.com/gitlab-org/gitaly/internal/helper/text" @@ -17,106 +16,100 @@ import ( func TestFlagValidation(t *testing.T) { for _, tt := range []struct { - option git.Option + option Option valid bool }{ // valid Flag inputs - {option: git.Flag{Name: "-k"}, valid: true}, - {option: git.Flag{Name: "-K"}, valid: true}, - {option: git.Flag{Name: "--asdf"}, valid: true}, - {option: git.Flag{Name: "--asdf-qwer"}, valid: true}, - {option: git.Flag{Name: "--asdf=qwerty"}, valid: true}, - {option: git.Flag{Name: "-D=A"}, valid: true}, - {option: git.Flag{Name: "-D="}, valid: true}, + {option: Flag{Name: "-k"}, valid: true}, + {option: Flag{Name: "-K"}, valid: true}, + {option: Flag{Name: "--asdf"}, valid: true}, + {option: Flag{Name: "--asdf-qwer"}, valid: true}, + {option: Flag{Name: "--asdf=qwerty"}, valid: true}, + {option: Flag{Name: "-D=A"}, valid: true}, + {option: Flag{Name: "-D="}, valid: true}, // valid ValueFlag inputs - {option: git.ValueFlag{"-k", "adsf"}, valid: true}, - {option: git.ValueFlag{"-k", "--anything"}, valid: true}, - {option: git.ValueFlag{"-k", ""}, valid: true}, - - // valid SubSubCmd inputs - {option: git.SubSubCmd{"meow"}, valid: true}, + {option: ValueFlag{"-k", "adsf"}, valid: true}, + {option: ValueFlag{"-k", "--anything"}, valid: true}, + {option: ValueFlag{"-k", ""}, valid: true}, // valid ConfigPair inputs - {option: git.ConfigPair{Key: "a.b.c", Value: "d"}, valid: true}, - {option: git.ConfigPair{Key: "core.sound", Value: "meow"}, valid: true}, - {option: git.ConfigPair{Key: "asdf-qwer.1234-5678", Value: ""}, valid: true}, - {option: git.ConfigPair{Key: "http.https://user@example.com/repo.git.user", Value: "kitty"}, valid: true}, + {option: ConfigPair{Key: "a.b.c", Value: "d"}, valid: true}, + {option: ConfigPair{Key: "core.sound", Value: "meow"}, valid: true}, + {option: ConfigPair{Key: "asdf-qwer.1234-5678", Value: ""}, valid: true}, + {option: ConfigPair{Key: "http.https://user@example.com/repo.git.user", Value: "kitty"}, valid: true}, // invalid Flag inputs - {option: git.Flag{Name: "-*"}}, // invalid character - {option: git.Flag{Name: "a"}}, // missing dash - {option: git.Flag{Name: "[["}}, // suspicious characters - {option: git.Flag{Name: "||"}}, // suspicious characters - {option: git.Flag{Name: "asdf=qwerty"}}, // missing dash + {option: Flag{Name: "-*"}}, // invalid character + {option: Flag{Name: "a"}}, // missing dash + {option: Flag{Name: "[["}}, // suspicious characters + {option: Flag{Name: "||"}}, // suspicious characters + {option: Flag{Name: "asdf=qwerty"}}, // missing dash // invalid ValueFlag inputs - {option: git.ValueFlag{"k", "asdf"}}, // missing dash - - // invalid SubSubCmd inputs - {option: git.SubSubCmd{"--meow"}}, // cannot start with dash + {option: ValueFlag{"k", "asdf"}}, // missing dash // invalid ConfigPair inputs - {option: git.ConfigPair{Key: "", Value: ""}}, // key cannot be empty - {option: git.ConfigPair{Key: " ", Value: ""}}, // key cannot be whitespace - {option: git.ConfigPair{Key: "asdf", Value: ""}}, // two components required - {option: git.ConfigPair{Key: "asdf.", Value: ""}}, // 2nd component must be non-empty - {option: git.ConfigPair{Key: "--asdf.asdf", Value: ""}}, // key cannot start with dash - {option: git.ConfigPair{Key: "as[[df.asdf", Value: ""}}, // 1st component cannot contain non-alphanumeric - {option: git.ConfigPair{Key: "asdf.as]]df", Value: ""}}, // 2nd component cannot contain non-alphanumeric + {option: ConfigPair{Key: "", Value: ""}}, // key cannot be empty + {option: ConfigPair{Key: " ", Value: ""}}, // key cannot be whitespace + {option: ConfigPair{Key: "asdf", Value: ""}}, // two components required + {option: ConfigPair{Key: "asdf.", Value: ""}}, // 2nd component must be non-empty + {option: ConfigPair{Key: "--asdf.asdf", Value: ""}}, // key cannot start with dash + {option: ConfigPair{Key: "as[[df.asdf", Value: ""}}, // 1st component cannot contain non-alphanumeric + {option: ConfigPair{Key: "asdf.as]]df", Value: ""}}, // 2nd component cannot contain non-alphanumeric } { - args, err := tt.option.ValidateArgs() + args, err := tt.option.OptionArgs() if tt.valid { require.NoError(t, err) } else { require.Error(t, err, "expected error, but args %v passed validation", args) - require.True(t, git.IsInvalidArgErr(err)) + require.True(t, IsInvalidArgErr(err)) } } } func TestSafeCmdInvalidArg(t *testing.T) { for _, tt := range []struct { - globals []git.Option - subCmd git.SubCmd + globals []GlobalOption + subCmd Cmd errMsg string }{ { - subCmd: git.SubCmd{Name: "--meow"}, + subCmd: SubCmd{Name: "--meow"}, errMsg: `invalid sub command name "--meow": invalid argument`, }, { - subCmd: git.SubCmd{ + subCmd: SubCmd{ Name: "update-ref", - Flags: []git.Option{git.Flag{Name: "woof"}}, + Flags: []Option{Flag{Name: "woof"}}, }, errMsg: `flag "woof" failed regex validation: invalid argument`, }, { - subCmd: git.SubCmd{ + subCmd: SubCmd{ Name: "update-ref", Args: []string{"--tweet"}, }, errMsg: `positional arg "--tweet" cannot start with dash '-': invalid argument`, }, { - subCmd: git.SubCmd{ - Name: "update-ref", - Flags: []git.Option{git.SubSubCmd{"-invalid"}}, + subCmd: SubSubCmd{ + Name: "update-ref", + Action: "-invalid", }, - errMsg: `invalid sub-sub command name "-invalid": invalid argument`, + errMsg: `invalid sub command action "-invalid": invalid argument`, }, } { - _, err := git.SafeCmd( + _, err := SafeCmd( context.Background(), &gitalypb.Repository{}, tt.globals, tt.subCmd, - git.WithRefTxHook(context.Background(), &gitalypb.Repository{}, config.Config), + WithRefTxHook(context.Background(), &gitalypb.Repository{}, config.Config), ) require.EqualError(t, err, tt.errMsg) - require.True(t, git.IsInvalidArgErr(err)) + require.True(t, IsInvalidArgErr(err)) } } @@ -136,26 +129,26 @@ func TestSafeCmdValid(t *testing.T) { for _, tt := range []struct { desc string - globals []git.Option - subCmd git.SubCmd + globals []GlobalOption + subCmd Cmd expectArgs []string }{ { desc: "no args", - subCmd: git.SubCmd{Name: "update-ref"}, + subCmd: SubCmd{Name: "update-ref"}, expectArgs: []string{"-c", hooksPath, "update-ref", endOfOptions}, }, { desc: "single option", - globals: []git.Option{ - git.Flag{Name: "--aaaa-bbbb"}, + globals: []GlobalOption{ + Flag{Name: "--aaaa-bbbb"}, }, - subCmd: git.SubCmd{Name: "update-ref"}, + subCmd: SubCmd{Name: "update-ref"}, expectArgs: []string{"--aaaa-bbbb", "-c", hooksPath, "update-ref", endOfOptions}, }, { desc: "empty arg and postsep args", - subCmd: git.SubCmd{ + subCmd: SubCmd{ Name: "update-ref", Args: []string{""}, PostSepArgs: []string{"-woof", ""}, @@ -164,16 +157,16 @@ func TestSafeCmdValid(t *testing.T) { }, { desc: "full blown", - globals: []git.Option{ - git.Flag{Name: "-a"}, - git.ValueFlag{"-b", "c"}, + globals: []GlobalOption{ + Flag{Name: "-a"}, + ValueFlag{"-b", "c"}, }, - subCmd: git.SubCmd{ + subCmd: SubCmd{ Name: "update-ref", - Flags: []git.Option{ - git.Flag{Name: "-e"}, - git.ValueFlag{"-f", "g"}, - git.Flag{Name: "-h=i"}, + Flags: []Option{ + Flag{Name: "-e"}, + ValueFlag{"-f", "g"}, + Flag{Name: "-h=i"}, }, Args: []string{"1", "2"}, PostSepArgs: []string{"3", "4", "5"}, @@ -182,60 +175,60 @@ func TestSafeCmdValid(t *testing.T) { }, { desc: "output to stdout", - subCmd: git.SubCmd{ - Name: "update-ref", - Flags: []git.Option{ - git.SubSubCmd{"verb"}, - git.OutputToStdout, - git.Flag{Name: "--adjective"}, + subCmd: SubSubCmd{ + Name: "update-ref", + Action: "verb", + Flags: []Option{ + OutputToStdout, + Flag{Name: "--adjective"}, }, }, expectArgs: []string{"-c", hooksPath, "update-ref", "verb", "-", "--adjective", endOfOptions}, }, { desc: "multiple value flags", - globals: []git.Option{ - git.Flag{Name: "--contributing"}, - git.ValueFlag{"--author", "a-gopher"}, + globals: []GlobalOption{ + Flag{Name: "--contributing"}, + ValueFlag{"--author", "a-gopher"}, }, - subCmd: git.SubCmd{ + subCmd: SubCmd{ Name: "update-ref", Args: []string{"mr"}, - Flags: []git.Option{ - git.Flag{Name: "--is-important"}, - git.ValueFlag{"--why", "looking-for-first-contribution"}, + Flags: []Option{ + Flag{Name: "--is-important"}, + ValueFlag{"--why", "looking-for-first-contribution"}, }, }, expectArgs: []string{"--contributing", "--author", "a-gopher", "-c", hooksPath, "update-ref", "--is-important", "--why", "looking-for-first-contribution", "mr", endOfOptions}, }, } { t.Run(tt.desc, func(t *testing.T) { - opts := []git.CmdOpt{git.WithRefTxHook(ctx, &gitalypb.Repository{}, config.Config)} + opts := []CmdOpt{WithRefTxHook(ctx, &gitalypb.Repository{}, config.Config)} - cmd, err := git.SafeCmd(ctx, testRepo, tt.globals, tt.subCmd, opts...) + cmd, err := SafeCmd(ctx, testRepo, tt.globals, tt.subCmd, opts...) require.NoError(t, err) // ignore first 3 indeterministic args (executable path and repo args) require.Equal(t, tt.expectArgs, cmd.Args()[3:]) - cmd, err = git.SafeCmdWithEnv(ctx, nil, testRepo, tt.globals, tt.subCmd, opts...) + cmd, err = SafeCmdWithEnv(ctx, nil, testRepo, tt.globals, tt.subCmd, opts...) require.NoError(t, err) // ignore first 3 indeterministic args (executable path and repo args) require.Equal(t, tt.expectArgs, cmd.Args()[3:]) - cmd, err = git.SafeStdinCmd(ctx, testRepo, tt.globals, tt.subCmd, opts...) + cmd, err = SafeStdinCmd(ctx, testRepo, tt.globals, tt.subCmd, opts...) require.NoError(t, err) require.Equal(t, tt.expectArgs, cmd.Args()[3:]) - cmd, err = git.SafeBareCmd(ctx, git.CmdStream{}, nil, tt.globals, tt.subCmd, opts...) + cmd, err = SafeBareCmd(ctx, nil, tt.globals, tt.subCmd, opts...) require.NoError(t, err) // ignore first indeterministic arg (executable path) require.Equal(t, tt.expectArgs, cmd.Args()[1:]) - cmd, err = git.SafeCmdWithoutRepo(ctx, git.CmdStream{}, tt.globals, tt.subCmd, opts...) + cmd, err = SafeCmdWithoutRepo(ctx, tt.globals, tt.subCmd, opts...) require.NoError(t, err) require.Equal(t, tt.expectArgs, cmd.Args()[1:]) - cmd, err = git.SafeBareCmdInDir(ctx, testRepoPath, git.CmdStream{}, nil, tt.globals, tt.subCmd, opts...) + cmd, err = SafeBareCmdInDir(ctx, testRepoPath, nil, tt.globals, tt.subCmd, opts...) require.NoError(t, err) require.Equal(t, tt.expectArgs, cmd.Args()[1:]) }) @@ -252,18 +245,18 @@ func TestSafeCmdWithEnv(t *testing.T) { reenableGitCmd := disableGitCmd() defer reenableGitCmd() - globals := []git.Option{ - git.Flag{Name: "--aaaa-bbbb"}, + globals := []GlobalOption{ + Flag{Name: "--aaaa-bbbb"}, } - subCmd := git.SubCmd{Name: "update-ref"} + subCmd := SubCmd{Name: "update-ref"} endOfOptions := "--end-of-options" expectArgs := []string{"--aaaa-bbbb", "-c", "core.hooksPath=" + hooks.Path(config.Config), "update-ref", endOfOptions} env := []string{"TEST_VAR1=1", "TEST_VAR2=2"} - opts := []git.CmdOpt{git.WithRefTxHook(ctx, &gitalypb.Repository{}, config.Config)} - cmd, err := git.SafeCmdWithEnv(ctx, env, testRepo, globals, subCmd, opts...) + opts := []CmdOpt{WithRefTxHook(ctx, &gitalypb.Repository{}, config.Config)} + cmd, err := SafeCmdWithEnv(ctx, env, testRepo, globals, subCmd, opts...) require.NoError(t, err) // ignore first 3 indeterministic args (executable path and repo args) require.Equal(t, expectArgs, cmd.Args()[3:]) @@ -281,7 +274,7 @@ func TestSafeBareCmdInDir(t *testing.T) { ctx, cancel := testhelper.Context() defer cancel() - _, err := git.SafeBareCmdInDir(ctx, "", git.CmdStream{}, nil, nil, nil) + _, err := SafeBareCmdInDir(ctx, "", nil, nil, nil) require.Error(t, err) require.Contains(t, err.Error(), "no 'dir' provided") }) @@ -294,10 +287,10 @@ func TestSafeBareCmdInDir(t *testing.T) { defer cancel() var stderr bytes.Buffer - cmd, err := git.SafeBareCmdInDir(ctx, repoPath, git.CmdStream{Err: &stderr}, nil, nil, git.SubCmd{ + cmd, err := SafeBareCmdInDir(ctx, repoPath, nil, nil, SubCmd{ Name: "rev-parse", Args: []string{"master"}, - }) + }, WithStderr(&stderr)) require.NoError(t, err) revData, err := ioutil.ReadAll(cmd) @@ -313,10 +306,10 @@ func TestSafeBareCmdInDir(t *testing.T) { defer cancel() var stderr bytes.Buffer - _, err := git.SafeBareCmdInDir(ctx, "non-existing-dir", git.CmdStream{Err: &stderr}, nil, nil, git.SubCmd{ + _, err := SafeBareCmdInDir(ctx, "non-existing-dir", nil, nil, SubCmd{ Name: "rev-parse", Args: []string{"master"}, - }) + }, WithStderr(&stderr)) require.Error(t, err) require.Contains(t, err.Error(), "no such file or directory") }) @@ -331,12 +324,12 @@ func TestSafeCmd(t *testing.T) { defer cancel() var stderr bytes.Buffer - cmd, err := git.SafeCmd(ctx, repo, nil, - git.SubCmd{ + cmd, err := SafeCmd(ctx, repo, nil, + SubCmd{ Name: "rev-parse", Args: []string{"master"}, }, - git.WithStderr(&stderr), + WithStderr(&stderr), ) require.NoError(t, err) @@ -357,11 +350,11 @@ func TestSafeCmd(t *testing.T) { defer cancel() var stderr bytes.Buffer - cmd, err := git.SafeCmd(ctx, repo, nil, git.SubCmd{ + cmd, err := SafeCmd(ctx, repo, nil, SubCmd{ Name: "rev-parse", Args: []string{"invalid-ref"}, }, - git.WithStderr(&stderr), + WithStderr(&stderr), ) require.NoError(t, err) diff --git a/internal/git/staticargs.go b/internal/git/staticargs.go index 7caaf9475..c56083805 100644 --- a/internal/git/staticargs.go +++ b/internal/git/staticargs.go @@ -5,12 +5,9 @@ type StaticOption struct { value string } -// IsOption is a method present on all Flag interface implementations -func (sa StaticOption) IsOption() {} - // ValidateArgs just passes through the already trusted value. This never // returns an error. -func (sa StaticOption) ValidateArgs() ([]string, error) { return []string{sa.value}, nil } +func (sa StaticOption) OptionArgs() ([]string, error) { return []string{sa.value}, nil } var ( // OutputToStdout is used indicate the output should be sent to STDOUT diff --git a/internal/git/uploadpack.go b/internal/git/uploadpack.go index 7eab25d3f..c5c7d64c2 100644 --- a/internal/git/uploadpack.go +++ b/internal/git/uploadpack.go @@ -1,12 +1,12 @@ package git -import "gitlab.com/gitlab-org/gitaly/internal/gitalyssh" - // UploadPackFilterConfig confines config options that are required to allow // partial-clone filters. -func UploadPackFilterConfig() []Option { - return []Option{ +func UploadPackFilterConfig() []GlobalOption { + return []GlobalOption{ ValueFlag{"-c", "uploadpack.allowFilter=true"}, - ValueFlag{"-c", gitalyssh.EnvVarUploadPackAllowAnySHA1InWant}, + // Enables the capability to request individual SHA1's from the + // remote repo. + ValueFlag{"-c", "uploadpack.allowAnySHA1InWant=true"}, } } diff --git a/internal/git/version.go b/internal/git/version.go index 87cf01126..6f6f4e4f3 100644 --- a/internal/git/version.go +++ b/internal/git/version.go @@ -21,7 +21,7 @@ type version struct { // Version returns the used git version. func Version(ctx context.Context) (string, error) { var buf bytes.Buffer - cmd, err := NewCommandFactory().unsafeBareCmd(ctx, CmdStream{Out: &buf}, nil, "version") + cmd, err := NewCommandFactory().unsafeBareCmd(ctx, cmdStream{Out: &buf}, nil, "version") if err != nil { return "", err } diff --git a/internal/gitaly/service/commit/languages.go b/internal/gitaly/service/commit/languages.go index 9344f0739..ebb94de23 100644 --- a/internal/gitaly/service/commit/languages.go +++ b/internal/gitaly/service/commit/languages.go @@ -117,11 +117,13 @@ func (s *server) lookupRevision(ctx context.Context, repo *gitalypb.Repository, } func checkRevision(ctx context.Context, repoPath string, env []string, revision string) (string, error) { - opts := []git.Option{git.ValueFlag{"-C", repoPath}} + opts := []git.GlobalOption{git.ValueFlag{"-C", repoPath}} var stdout, stderr bytes.Buffer - revParse, err := git.SafeBareCmd(ctx, git.CmdStream{Out: &stdout, Err: &stderr}, env, opts, + revParse, err := git.SafeBareCmd(ctx, env, opts, git.SubCmd{Name: "rev-parse", Args: []string{revision}}, + git.WithStdout(&stdout), + git.WithStderr(&stderr), ) if err != nil { diff --git a/internal/gitaly/service/conflicts/resolve_conflicts.go b/internal/gitaly/service/conflicts/resolve_conflicts.go index 390ae99db..2bf6b9f23 100644 --- a/internal/gitaly/service/conflicts/resolve_conflicts.go +++ b/internal/gitaly/service/conflicts/resolve_conflicts.go @@ -283,16 +283,14 @@ func (s *server) repoWithBranchCommit(ctx context.Context, srcRepo, targetRepo * if err != nil { return err } - // to enable fetching a specific SHA: - env = append(env, gitalyssh.EnvVarUploadPackAllowAnySHA1InWant) srcRepoPath, err := s.locator.GetRepoPath(srcRepo) if err != nil { return err } - cmd, err := git.SafeBareCmd(ctx, git.CmdStream{}, env, - []git.Option{git.ValueFlag{"--git-dir", srcRepoPath}}, + cmd, err := git.SafeBareCmd(ctx, env, + []git.GlobalOption{git.ValueFlag{"--git-dir", srcRepoPath}}, git.SubCmd{ Name: "fetch", Flags: []git.Option{git.Flag{Name: "--no-tags"}}, diff --git a/internal/gitaly/service/diff/commit.go b/internal/gitaly/service/diff/commit.go index 590177d89..ac137617d 100644 --- a/internal/gitaly/service/diff/commit.go +++ b/internal/gitaly/service/diff/commit.go @@ -206,7 +206,7 @@ func validateRequest(in requestWithLeftRightCommitIds) error { } func eachDiff(ctx context.Context, rpc string, repo *gitalypb.Repository, subCmd git.Cmd, limits diff.Limits, callback func(*diff.Diff) error) error { - diffArgs := []git.Option{ + diffArgs := []git.GlobalOption{ git.ValueFlag{"-c", "diff.noprefix=false"}, } diff --git a/internal/gitaly/service/operations/squash.go b/internal/gitaly/service/operations/squash.go index f621165d7..1ab1c68dc 100644 --- a/internal/gitaly/service/operations/squash.go +++ b/internal/gitaly/service/operations/squash.go @@ -167,13 +167,15 @@ func (s *Server) runUserSquashGo(ctx context.Context, req *gitalypb.UserSquashRe func (s *Server) diffFiles(ctx context.Context, env []string, repoPath string, req *gitalypb.UserSquashRequest) ([]byte, error) { var stdout, stderr bytes.Buffer - cmd, err := git.SafeBareCmd(ctx, git.CmdStream{Out: &stdout, Err: &stderr}, env, - []git.Option{git.ValueFlag{Name: "--git-dir", Value: repoPath}}, + cmd, err := git.SafeBareCmd(ctx, env, + []git.GlobalOption{git.ValueFlag{Name: "--git-dir", Value: repoPath}}, git.SubCmd{ Name: "diff", Flags: []git.Option{git.Flag{Name: "--name-only"}, git.Flag{Name: "--diff-filter=ar"}, git.Flag{Name: "--binary"}}, Args: []string{diffRange(req)}, }, + git.WithStdout(&stdout), + git.WithStderr(&stderr), ) if err != nil { return nil, fmt.Errorf("create 'git diff': %w", gitError{ErrMsg: stderr.String(), Err: err}) @@ -243,12 +245,13 @@ func (s *Server) userSquashWithDiffInFiles(ctx context.Context, req *gitalypb.Us func (s *Server) checkout(ctx context.Context, repo *gitalypb.Repository, worktreePath string, req *gitalypb.UserSquashRequest) error { var stderr bytes.Buffer - checkoutCmd, err := git.SafeBareCmdInDir(ctx, worktreePath, git.CmdStream{Err: &stderr}, nil, nil, + checkoutCmd, err := git.SafeBareCmdInDir(ctx, worktreePath, nil, nil, git.SubCmd{ Name: "checkout", Flags: []git.Option{git.Flag{Name: "--detach"}}, Args: []string{req.GetStartSha()}, }, + git.WithStderr(&stderr), git.WithRefTxHook(ctx, repo, s.cfg), ) if err != nil { @@ -268,10 +271,14 @@ func (s *Server) checkout(ctx context.Context, repo *gitalypb.Repository, worktr func (s *Server) revParseGitDir(ctx context.Context, worktreePath string) (string, error) { var stdout, stderr bytes.Buffer - cmd, err := git.SafeBareCmdInDir(ctx, worktreePath, git.CmdStream{Out: &stdout, Err: &stderr}, nil, nil, git.SubCmd{ - Name: "rev-parse", - Flags: []git.Option{git.Flag{Name: "--git-dir"}}, - }) + cmd, err := git.SafeBareCmdInDir(ctx, worktreePath, nil, nil, + git.SubCmd{ + Name: "rev-parse", + Flags: []git.Option{git.Flag{Name: "--git-dir"}}, + }, + git.WithStdout(&stdout), + git.WithStderr(&stderr), + ) if err != nil { return "", fmt.Errorf("creation of 'git rev-parse --git-dir': %w", gitError{ErrMsg: stderr.String(), Err: err}) } @@ -314,7 +321,7 @@ func (s *Server) addWorktree(ctx context.Context, repo *gitalypb.Repository, wor } args := []string{worktreePath} - flags := []git.Option{git.SubSubCmd{Name: "add"}, git.Flag{Name: "--detach"}} + flags := []git.Option{git.Flag{Name: "--detach"}} if committish != "" { args = append(args, committish) } else { @@ -323,7 +330,12 @@ func (s *Server) addWorktree(ctx context.Context, repo *gitalypb.Repository, wor var stderr bytes.Buffer cmd, err := git.SafeCmd(ctx, repo, nil, - git.SubCmd{Name: "worktree", Flags: flags, Args: args}, + git.SubSubCmd{ + Name: "worktree", + Action: "add", + Flags: flags, + Args: args, + }, git.WithStderr(&stderr), git.WithRefTxHook(ctx, repo, s.cfg), ) @@ -340,10 +352,11 @@ func (s *Server) addWorktree(ctx context.Context, repo *gitalypb.Repository, wor func (s *Server) removeWorktree(ctx context.Context, repo *gitalypb.Repository, worktreeName string) error { cmd, err := git.SafeCmd(ctx, repo, nil, - git.SubCmd{ - Name: "worktree", - Flags: []git.Option{git.SubSubCmd{Name: "remove"}, git.Flag{Name: "--force"}}, - Args: []string{worktreeName}, + git.SubSubCmd{ + Name: "worktree", + Action: "remove", + Flags: []git.Option{git.Flag{Name: "--force"}}, + Args: []string{worktreeName}, }, git.WithRefTxHook(ctx, repo, s.cfg), ) @@ -377,14 +390,18 @@ func (s *Server) applyDiff(ctx context.Context, repo *gitalypb.Repository, req * } var applyStderr bytes.Buffer - cmdApply, err := git.SafeBareCmdInDir(ctx, worktreePath, git.CmdStream{In: command.SetupStdin, Err: &applyStderr}, env, nil, git.SubCmd{ - Name: "apply", - Flags: []git.Option{ - git.Flag{Name: "--index"}, - git.Flag{Name: "--3way"}, - git.Flag{Name: "--whitespace=nowarn"}, + cmdApply, err := git.SafeBareCmdInDir(ctx, worktreePath, env, nil, + git.SubCmd{ + Name: "apply", + Flags: []git.Option{ + git.Flag{Name: "--index"}, + git.Flag{Name: "--3way"}, + git.Flag{Name: "--whitespace=nowarn"}, + }, }, - }) + git.WithStdin(command.SetupStdin), + git.WithStderr(&applyStderr), + ) if err != nil { return "", fmt.Errorf("creation of 'git apply' for range %q: %w", diffRange, gitError{ErrMsg: applyStderr.String(), Err: err}) } @@ -409,14 +426,14 @@ func (s *Server) applyDiff(ctx context.Context, repo *gitalypb.Repository, req * ) var commitStderr bytes.Buffer - cmdCommit, err := git.SafeBareCmdInDir(ctx, worktreePath, git.CmdStream{Err: &commitStderr}, commitEnv, nil, git.SubCmd{ + cmdCommit, err := git.SafeBareCmdInDir(ctx, worktreePath, commitEnv, nil, git.SubCmd{ Name: "commit", Flags: []git.Option{ git.Flag{Name: "--no-verify"}, git.Flag{Name: "--quiet"}, git.ValueFlag{Name: "--message", Value: string(req.GetCommitMessage())}, }, - }, git.WithRefTxHook(ctx, repo, s.cfg)) + }, git.WithStderr(&commitStderr), git.WithRefTxHook(ctx, repo, s.cfg)) if err != nil { return "", fmt.Errorf("creation of 'git commit': %w", gitError{ErrMsg: commitStderr.String(), Err: err}) } @@ -426,14 +443,14 @@ func (s *Server) applyDiff(ctx context.Context, repo *gitalypb.Repository, req * } var revParseStdout, revParseStderr bytes.Buffer - revParseCmd, err := git.SafeBareCmdInDir(ctx, worktreePath, git.CmdStream{Out: &revParseStdout, Err: &revParseStderr}, env, nil, git.SubCmd{ + revParseCmd, err := git.SafeBareCmdInDir(ctx, worktreePath, env, nil, git.SubCmd{ Name: "rev-parse", Flags: []git.Option{ git.Flag{Name: "--quiet"}, git.Flag{Name: "--verify"}, }, Args: []string{"HEAD^{commit}"}, - }) + }, git.WithStdout(&revParseStdout), git.WithStderr(&revParseStderr)) if err != nil { return "", fmt.Errorf("creation of 'git rev-parse': %w", gitError{ErrMsg: revParseStderr.String(), Err: err}) } diff --git a/internal/gitaly/service/ref/refname.go b/internal/gitaly/service/ref/refname.go index fdd8a4c2c..0e8928bc2 100644 --- a/internal/gitaly/service/ref/refname.go +++ b/internal/gitaly/service/ref/refname.go @@ -72,9 +72,6 @@ type ForEachRefCmd struct { PostArgFlags []git.Option } -// IsCmd is to satisfy the git.Cmd interface -func (f ForEachRefCmd) IsCmd() {} - var ( // ErrOnlyForEachRefAllowed indicates a command other than for-each-ref is being used with ForEachRefCmd ErrOnlyForEachRefAllowed = errors.New("only for-each-ref allowed") @@ -83,13 +80,13 @@ var ( ErrNoPostSeparatorArgsAllowed = errors.New("post separator args not allowed") ) -// ValidateArgs validates and returns the flags and arguments for the for-each-ref command -func (f ForEachRefCmd) ValidateArgs() ([]string, error) { +// CommandArgs validates and returns the flags and arguments for the for-each-ref command +func (f ForEachRefCmd) CommandArgs() ([]string, error) { if f.Name != "for-each-ref" { return nil, ErrOnlyForEachRefAllowed } - args, err := f.SubCmd.ValidateArgs() + args, err := f.SubCmd.CommandArgs() if err != nil { return nil, err } @@ -97,7 +94,7 @@ func (f ForEachRefCmd) ValidateArgs() ([]string, error) { var postArgFlags []string for _, o := range f.PostArgFlags { - args, err := o.ValidateArgs() + args, err := o.OptionArgs() if err != nil { return nil, err } diff --git a/internal/gitaly/service/ref/refname_test.go b/internal/gitaly/service/ref/refname_test.go index 0b67a0b9b..c078e92a7 100644 --- a/internal/gitaly/service/ref/refname_test.go +++ b/internal/gitaly/service/ref/refname_test.go @@ -210,7 +210,7 @@ func TestFindRefCmd(t *testing.T) { } for _, tc := range testCases { - args, err := tc.cmd.ValidateArgs() + args, err := tc.cmd.CommandArgs() require.Equal(t, tc.expectedErr, err) require.Equal(t, tc.expectedArgs, args) } diff --git a/internal/gitaly/service/remote/find_remote_root_ref.go b/internal/gitaly/service/remote/find_remote_root_ref.go index c3ef6a978..0eaea33e3 100644 --- a/internal/gitaly/service/remote/find_remote_root_ref.go +++ b/internal/gitaly/service/remote/find_remote_root_ref.go @@ -16,7 +16,11 @@ const headPrefix = "HEAD branch: " func findRemoteRootRef(ctx context.Context, repo *gitalypb.Repository, remote string) (string, error) { cmd, err := git.SafeCmd(ctx, repo, nil, - git.SubCmd{Name: "remote", Flags: []git.Option{git.SubSubCmd{Name: "show"}}, Args: []string{remote}}, + git.SubSubCmd{ + Name: "remote", + Action: "show", + Args: []string{remote}, + }, git.WithRefTxHook(ctx, repo, config.Config), ) if err != nil { diff --git a/internal/gitaly/service/remote/remotes.go b/internal/gitaly/service/remote/remotes.go index 074969d08..7afdec9d2 100644 --- a/internal/gitaly/service/remote/remotes.go +++ b/internal/gitaly/service/remote/remotes.go @@ -75,7 +75,7 @@ func (s *server) FindRemoteRepository(ctx context.Context, req *gitalypb.FindRem return nil, status.Error(codes.InvalidArgument, "FindRemoteRepository: empty remote can't be checked.") } - cmd, err := git.SafeCmdWithoutRepo(ctx, git.CmdStream{}, nil, + cmd, err := git.SafeCmdWithoutRepo(ctx, nil, git.SubCmd{ Name: "ls-remote", Args: []string{ diff --git a/internal/gitaly/service/repository/archive.go b/internal/gitaly/service/repository/archive.go index f046d7752..4cbc96273 100644 --- a/internal/gitaly/service/repository/archive.go +++ b/internal/gitaly/service/repository/archive.go @@ -200,7 +200,7 @@ func handleArchive(p archiveParams) error { fmt.Sprintf("%s=%s", log.GitalyLogDirEnvKey, p.loggingDir), } - var globals []git.Option + var globals []git.GlobalOption if p.in.GetIncludeLfsBlobs() { binary := filepath.Join(p.binDir, "gitaly-lfs-smudge") diff --git a/internal/gitaly/service/repository/calculate_checksum.go b/internal/gitaly/service/repository/calculate_checksum.go index 1bdf570a6..2b7a26f12 100644 --- a/internal/gitaly/service/repository/calculate_checksum.go +++ b/internal/gitaly/service/repository/calculate_checksum.go @@ -85,9 +85,16 @@ func (s *server) isValidRepo(ctx context.Context, repo *gitalypb.Repository) boo env := alternates.Env(repoPath, repo.GetGitObjectDirectory(), repo.GetGitAlternateObjectDirectories()) stdout := &bytes.Buffer{} - opts := []git.Option{git.ValueFlag{"-C", repoPath}} - cmd, err := git.SafeBareCmd(ctx, git.CmdStream{Out: stdout}, env, opts, - git.SubCmd{Name: "rev-parse", Flags: []git.Option{git.Flag{Name: "--is-inside-git-dir"}}}) + globals := []git.GlobalOption{git.ValueFlag{"-C", repoPath}} + cmd, err := git.SafeBareCmd(ctx, env, globals, + git.SubCmd{ + Name: "rev-parse", + Flags: []git.Option{ + git.Flag{Name: "--is-inside-git-dir"}, + }, + }, + git.WithStdout(stdout), + ) if err != nil { return false } diff --git a/internal/gitaly/service/repository/cleanup.go b/internal/gitaly/service/repository/cleanup.go index 752b89c8e..48c774563 100644 --- a/internal/gitaly/service/repository/cleanup.go +++ b/internal/gitaly/service/repository/cleanup.go @@ -127,9 +127,11 @@ func (s *server) cleanStaleWorktrees(ctx context.Context, repo *gitalypb.Reposit if info.ModTime().Before(threshold) { cmd, err := git.SafeCmd(ctx, repo, nil, - git.SubCmd{ - Name: "worktree", - Flags: []git.Option{git.SubSubCmd{"remove"}, git.Flag{Name: "--force"}, git.SubSubCmd{info.Name()}}, + git.SubSubCmd{ + Name: "worktree", + Action: "remove", + Flags: []git.Option{git.Flag{Name: "--force"}}, + Args: []string{info.Name()}, }, git.WithRefTxHook(ctx, repo, s.cfg), ) @@ -148,9 +150,9 @@ func (s *server) cleanStaleWorktrees(ctx context.Context, repo *gitalypb.Reposit func (s *server) cleanDisconnectedWorktrees(ctx context.Context, repo *gitalypb.Repository) error { cmd, err := git.SafeCmd(ctx, repo, nil, - git.SubCmd{ - Name: "worktree", - Flags: []git.Option{git.SubSubCmd{"prune"}}, + git.SubSubCmd{ + Name: "worktree", + Action: "prune", }, git.WithRefTxHook(ctx, repo, s.cfg), ) diff --git a/internal/gitaly/service/repository/clone_from_pool_internal.go b/internal/gitaly/service/repository/clone_from_pool_internal.go index 7034568b3..47d94be4f 100644 --- a/internal/gitaly/service/repository/clone_from_pool_internal.go +++ b/internal/gitaly/service/repository/clone_from_pool_internal.go @@ -125,7 +125,7 @@ func (s *server) cloneFromPool(ctx context.Context, objectPoolRepo *gitalypb.Obj return fmt.Errorf("expected *gitlaypb.Repository but got %T", repo) } - cmd, err := git.SafeBareCmd(ctx, git.CmdStream{}, nil, nil, + cmd, err := git.SafeBareCmd(ctx, nil, nil, git.SubCmd{ Name: "clone", Flags: []git.Option{git.Flag{Name: "--bare"}, git.Flag{Name: "--shared"}}, diff --git a/internal/gitaly/service/repository/commit_graph.go b/internal/gitaly/service/repository/commit_graph.go index 20459e498..21d6e326e 100644 --- a/internal/gitaly/service/repository/commit_graph.go +++ b/internal/gitaly/service/repository/commit_graph.go @@ -24,10 +24,10 @@ func (s *server) WriteCommitGraph(ctx context.Context, in *gitalypb.WriteCommitG func (s *server) writeCommitGraph(ctx context.Context, in *gitalypb.WriteCommitGraphRequest) error { cmd, err := git.SafeCmd(ctx, in.GetRepository(), nil, - git.SubCmd{ - Name: "commit-graph", + git.SubSubCmd{ + Name: "commit-graph", + Action: "write", Flags: []git.Option{ - git.SubSubCmd{Name: "write"}, git.Flag{Name: "--reachable"}, }, }, diff --git a/internal/gitaly/service/repository/create.go b/internal/gitaly/service/repository/create.go index b9fc42d3a..861d6aafe 100644 --- a/internal/gitaly/service/repository/create.go +++ b/internal/gitaly/service/repository/create.go @@ -21,7 +21,7 @@ func (s *server) CreateRepository(ctx context.Context, req *gitalypb.CreateRepos } stderr := &bytes.Buffer{} - cmd, err := git.SafeCmdWithoutRepo(ctx, git.CmdStream{Err: stderr}, nil, + cmd, err := git.SafeCmdWithoutRepo(ctx, nil, git.SubCmd{ Name: "init", Flags: []git.Option{ @@ -30,6 +30,7 @@ func (s *server) CreateRepository(ctx context.Context, req *gitalypb.CreateRepos }, Args: []string{diskPath}, }, + git.WithStderr(stderr), ) if err != nil { return nil, helper.ErrInternalf("create git init: %w", err) diff --git a/internal/gitaly/service/repository/create_bundle.go b/internal/gitaly/service/repository/create_bundle.go index 4f2d631c5..b2e1962fc 100644 --- a/internal/gitaly/service/repository/create_bundle.go +++ b/internal/gitaly/service/repository/create_bundle.go @@ -23,9 +23,10 @@ func (s *server) CreateBundle(req *gitalypb.CreateBundleRequest, stream gitalypb return helper.ErrInternalf("running Cleanup on repository: %w", err) } - cmd, err := git.SafeCmd(ctx, repo, nil, git.SubCmd{ - Name: "bundle", - Flags: []git.Option{git.SubSubCmd{"create"}, git.OutputToStdout, git.Flag{Name: "--all"}}, + cmd, err := git.SafeCmd(ctx, repo, nil, git.SubSubCmd{ + Name: "bundle", + Action: "create", + Flags: []git.Option{git.OutputToStdout, git.Flag{Name: "--all"}}, }) if err != nil { return status.Errorf(codes.Internal, "CreateBundle: cmd start failed: %v", err) diff --git a/internal/gitaly/service/repository/create_from_bundle.go b/internal/gitaly/service/repository/create_from_bundle.go index a92486fdc..7414b7041 100644 --- a/internal/gitaly/service/repository/create_from_bundle.go +++ b/internal/gitaly/service/repository/create_from_bundle.go @@ -71,7 +71,7 @@ func (s *server) CreateRepositoryFromBundle(stream gitalypb.RepositoryService_Cr } stderr := bytes.Buffer{} - cmd, err := git.SafeCmdWithoutRepo(ctx, git.CmdStream{Err: &stderr}, nil, + cmd, err := git.SafeCmdWithoutRepo(ctx, nil, git.SubCmd{ Name: "clone", Flags: []git.Option{ @@ -80,6 +80,7 @@ func (s *server) CreateRepositoryFromBundle(stream gitalypb.RepositoryService_Cr }, PostSepArgs: []string{bundlePath, repoPath}, }, + git.WithStderr(&stderr), git.WithRefTxHook(ctx, repo, s.cfg), ) if err != nil { @@ -93,13 +94,14 @@ func (s *server) CreateRepositoryFromBundle(stream gitalypb.RepositoryService_Cr // We do a fetch to get all refs including keep-around refs stderr.Reset() - cmd, err = git.SafeCmdWithoutRepo(ctx, git.CmdStream{Err: &stderr}, - []git.Option{git.ValueFlag{Name: "-C", Value: repoPath}}, + cmd, err = git.SafeCmdWithoutRepo(ctx, + []git.GlobalOption{git.ValueFlag{Name: "-C", Value: repoPath}}, git.SubCmd{ Name: "fetch", Flags: []git.Option{git.Flag{Name: "--quiet"}}, Args: []string{bundlePath, "refs/*:refs/*"}, }, + git.WithStderr(&stderr), git.WithRefTxHook(ctx, repo, s.cfg), ) if err != nil { diff --git a/internal/gitaly/service/repository/create_from_url.go b/internal/gitaly/service/repository/create_from_url.go index b430b9565..15a28a36b 100644 --- a/internal/gitaly/service/repository/create_from_url.go +++ b/internal/gitaly/service/repository/create_from_url.go @@ -23,7 +23,7 @@ func (s *server) cloneFromURLCommand(ctx context.Context, repo *gitalypb.Reposit return nil, helper.ErrInternal(err) } - globalFlags := []git.Option{ + globalFlags := []git.GlobalOption{ git.ValueFlag{Name: "-c", Value: "http.followRedirects=false"}, } @@ -47,12 +47,13 @@ func (s *server) cloneFromURLCommand(ctx context.Context, repo *gitalypb.Reposit globalFlags = append(globalFlags, git.ValueFlag{Name: "-c", Value: fmt.Sprintf("http.extraHeader=%s", authHeader)}) } - return git.SafeBareCmd(ctx, git.CmdStream{Err: stderr}, nil, globalFlags, + return git.SafeBareCmd(ctx, nil, globalFlags, git.SubCmd{ Name: "clone", Flags: cloneFlags, PostSepArgs: []string{u.String(), repositoryFullPath}, }, + git.WithStderr(stderr), git.WithRefTxHook(ctx, repo, s.cfg), ) } diff --git a/internal/gitaly/service/repository/fetch.go b/internal/gitaly/service/repository/fetch.go index 9bdc059df..16198e581 100644 --- a/internal/gitaly/service/repository/fetch.go +++ b/internal/gitaly/service/repository/fetch.go @@ -45,8 +45,8 @@ func (s *server) FetchSourceBranch(ctx context.Context, req *gitalypb.FetchSourc } } - cmd, err := git.SafeBareCmd(ctx, git.CmdStream{}, env, - []git.Option{git.ValueFlag{"--git-dir", repoPath}}, + cmd, err := git.SafeBareCmd(ctx, env, + []git.GlobalOption{git.ValueFlag{"--git-dir", repoPath}}, git.SubCmd{ Name: "fetch", Flags: []git.Option{git.Flag{Name: "--prune"}}, diff --git a/internal/gitaly/service/repository/fork.go b/internal/gitaly/service/repository/fork.go index 8ebd735e3..53c487ef6 100644 --- a/internal/gitaly/service/repository/fork.go +++ b/internal/gitaly/service/repository/fork.go @@ -53,7 +53,7 @@ func (s *server) CreateFork(ctx context.Context, req *gitalypb.CreateForkRequest return nil, err } - cmd, err := git.SafeBareCmd(ctx, git.CmdStream{}, env, nil, + cmd, err := git.SafeBareCmd(ctx, env, nil, git.SubCmd{ Name: "clone", Flags: []git.Option{ diff --git a/internal/gitaly/service/repository/fsck.go b/internal/gitaly/service/repository/fsck.go index 209e2875a..da07c0557 100644 --- a/internal/gitaly/service/repository/fsck.go +++ b/internal/gitaly/service/repository/fsck.go @@ -20,9 +20,11 @@ func (s *server) Fsck(ctx context.Context, req *gitalypb.FsckRequest) (*gitalypb env := alternates.Env(repoPath, repo.GetGitObjectDirectory(), repo.GetGitAlternateObjectDirectories()) - cmd, err := git.SafeBareCmd(ctx, git.CmdStream{Out: &stdout, Err: &stderr}, env, - []git.Option{git.ValueFlag{"--git-dir", repoPath}}, + cmd, err := git.SafeBareCmd(ctx, env, + []git.GlobalOption{git.ValueFlag{"--git-dir", repoPath}}, git.SubCmd{Name: "fsck"}, + git.WithStdout(&stdout), + git.WithStderr(&stderr), ) if err != nil { return nil, err diff --git a/internal/gitaly/service/repository/midx.go b/internal/gitaly/service/repository/midx.go index ec04e3dc6..c1e0e1d03 100644 --- a/internal/gitaly/service/repository/midx.go +++ b/internal/gitaly/service/repository/midx.go @@ -76,11 +76,9 @@ func midxSetConfig(ctx context.Context, repo repository.GitRepo) error { func midxWrite(ctx context.Context, repo repository.GitRepo) error { cmd, err := git.SafeCmd(ctx, repo, nil, - git.SubCmd{ - Name: "multi-pack-index", - Flags: []git.Option{ - git.SubSubCmd{Name: "write"}, - }, + git.SubSubCmd{ + Name: "multi-pack-index", + Action: "write", }, ) @@ -114,11 +112,9 @@ func midxVerify(ctx context.Context, repo repository.GitRepo) error { ctxlogger := ctxlogrus.Extract(ctx) cmd, err := git.SafeCmd(ctx, repo, nil, - git.SubCmd{ - Name: "multi-pack-index", - Flags: []git.Option{ - git.SubSubCmd{Name: "verify"}, - }, + git.SubSubCmd{ + Name: "multi-pack-index", + Action: "verify", }, ) if err != nil { @@ -151,12 +147,10 @@ func (s *server) midxRewrite(ctx context.Context, repo repository.GitRepo) error } func midxExpire(ctx context.Context, repo repository.GitRepo) error { - cmd, err := git.SafeCmd(ctx, repo, []git.Option{}, - git.SubCmd{ - Name: "multi-pack-index", - Flags: []git.Option{ - git.SubSubCmd{Name: "expire"}, - }, + cmd, err := git.SafeCmd(ctx, repo, nil, + git.SubSubCmd{ + Name: "multi-pack-index", + Action: "expire", }, ) if err != nil { @@ -196,10 +190,10 @@ func (s *server) midxRepack(ctx context.Context, repo repository.GitRepo) error // Bitmap index 'repack.writeBitmaps' is not yet supported. cmd, err := git.SafeCmd(ctx, repo, repackConfig(ctx, false), - git.SubCmd{ - Name: "multi-pack-index", + git.SubSubCmd{ + Name: "multi-pack-index", + Action: "repack", Flags: []git.Option{ - git.SubSubCmd{Name: "repack"}, git.ValueFlag{Name: "--batch-size", Value: strconv.FormatInt(batchSize, 10)}, }, }, diff --git a/internal/gitaly/service/repository/repack.go b/internal/gitaly/service/repository/repack.go index c4aa38b3a..214cf482f 100644 --- a/internal/gitaly/service/repository/repack.go +++ b/internal/gitaly/service/repository/repack.go @@ -70,8 +70,8 @@ func repackCommand(ctx context.Context, repo repository.GitRepo, bitmap bool, ar return nil } -func repackConfig(ctx context.Context, bitmap bool) []git.Option { - args := []git.Option{ +func repackConfig(ctx context.Context, bitmap bool) []git.GlobalOption { + args := []git.GlobalOption{ git.ValueFlag{"-c", "pack.island=r(e)fs/heads"}, git.ValueFlag{"-c", "pack.island=r(e)fs/tags"}, git.ValueFlag{"-c", "pack.islandCore=e"}, diff --git a/internal/gitaly/service/smarthttp/inforefs.go b/internal/gitaly/service/smarthttp/inforefs.go index 71034daaa..65b3afbca 100644 --- a/internal/gitaly/service/smarthttp/inforefs.go +++ b/internal/gitaly/service/smarthttp/inforefs.go @@ -48,7 +48,7 @@ func (s *server) handleInfoRefs(ctx context.Context, service string, req *gitaly return err } - var globalOpts []git.Option + var globalOpts []git.GlobalOption cmdOpts := []git.CmdOpt{git.WithGitProtocol(ctx, req)} if service == "receive-pack" { globalOpts = append(globalOpts, git.ReceivePackConfig()...) @@ -63,7 +63,7 @@ func (s *server) handleInfoRefs(ctx context.Context, service string, req *gitaly globalOpts = append(globalOpts, git.ValueFlag{"-c", o}) } - cmd, err := git.SafeBareCmd(ctx, git.CmdStream{}, nil, globalOpts, git.SubCmd{ + cmd, err := git.SafeBareCmd(ctx, nil, globalOpts, git.SubCmd{ Name: service, Flags: []git.Option{git.Flag{Name: "--stateless-rpc"}, git.Flag{Name: "--advertise-refs"}}, Args: []string{repoPath}, diff --git a/internal/gitaly/service/smarthttp/receive_pack.go b/internal/gitaly/service/smarthttp/receive_pack.go index 29b11cb65..abb9f4453 100644 --- a/internal/gitaly/service/smarthttp/receive_pack.go +++ b/internal/gitaly/service/smarthttp/receive_pack.go @@ -48,12 +48,14 @@ func (s *server) PostReceivePack(stream gitalypb.SmartHTTPService_PostReceivePac globalOpts = append(globalOpts, git.ValueFlag{"-c", o}) } - cmd, err := git.SafeBareCmd(ctx, git.CmdStream{In: stdin, Out: stdout}, nil, globalOpts, + cmd, err := git.SafeBareCmd(ctx, nil, globalOpts, git.SubCmd{ Name: "receive-pack", Flags: []git.Option{git.Flag{Name: "--stateless-rpc"}}, Args: []string{repoPath}, }, + git.WithStdin(stdin), + git.WithStdout(stdout), git.WithReceivePackHooks(ctx, config.Config, req, "http"), git.WithGitProtocol(ctx, req), ) diff --git a/internal/gitaly/service/smarthttp/upload_pack.go b/internal/gitaly/service/smarthttp/upload_pack.go index ceac032c0..cec97e4fe 100644 --- a/internal/gitaly/service/smarthttp/upload_pack.go +++ b/internal/gitaly/service/smarthttp/upload_pack.go @@ -77,11 +77,11 @@ func (s *server) PostUploadPack(stream gitalypb.SmartHTTPService_PostUploadPackS globalOpts = append(globalOpts, git.ValueFlag{"-c", o}) } - cmd, err := git.SafeBareCmd(ctx, git.CmdStream{In: stdin, Out: stdout}, nil, globalOpts, git.SubCmd{ + cmd, err := git.SafeBareCmd(ctx, nil, globalOpts, git.SubCmd{ Name: "upload-pack", Flags: []git.Option{git.Flag{Name: "--stateless-rpc"}}, Args: []string{repoPath}, - }, git.WithGitProtocol(ctx, req)) + }, git.WithStdin(stdin), git.WithStdout(stdout), git.WithGitProtocol(ctx, req)) if err != nil { return status.Errorf(codes.Unavailable, "PostUploadPack: cmd: %v", err) diff --git a/internal/gitaly/service/ssh/monitor_stdin_command.go b/internal/gitaly/service/ssh/monitor_stdin_command.go index 658602a70..aa08e250f 100644 --- a/internal/gitaly/service/ssh/monitor_stdin_command.go +++ b/internal/gitaly/service/ssh/monitor_stdin_command.go @@ -10,13 +10,15 @@ import ( "gitlab.com/gitlab-org/gitaly/internal/git/pktline" ) -func monitorStdinCommand(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, env []string, globals []git.Option, sc git.SubCmd, opts ...git.CmdOpt) (*command.Command, *pktline.ReadMonitor, error) { +func monitorStdinCommand(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, env []string, globals []git.GlobalOption, sc git.SubCmd, opts ...git.CmdOpt) (*command.Command, *pktline.ReadMonitor, error) { stdinPipe, monitor, err := pktline.NewReadMonitor(ctx, stdin) if err != nil { return nil, nil, fmt.Errorf("create monitor: %v", err) } - cmd, err := git.SafeBareCmd(ctx, git.CmdStream{In: stdinPipe, Out: stdout, Err: stderr}, env, globals, sc, opts...) + cmd, err := git.SafeBareCmd(ctx, env, globals, sc, append([]git.CmdOpt{ + git.WithStdin(stdinPipe), git.WithStdout(stdout), git.WithStderr(stderr), + }, opts...)...) stdinPipe.Close() // this now belongs to cmd if err != nil { return nil, nil, fmt.Errorf("start cmd: %v", err) diff --git a/internal/gitaly/service/ssh/receive_pack.go b/internal/gitaly/service/ssh/receive_pack.go index 9e8ee81f0..cf6a5d2e4 100644 --- a/internal/gitaly/service/ssh/receive_pack.go +++ b/internal/gitaly/service/ssh/receive_pack.go @@ -66,11 +66,14 @@ func (s *server) sshReceivePack(stream gitalypb.SSHService_SSHReceivePackServer, globalOpts = append(globalOpts, git.ValueFlag{"-c", o}) } - cmd, err := git.SafeBareCmd(ctx, git.CmdStream{In: stdin, Out: stdout, Err: stderr}, nil, globalOpts, + cmd, err := git.SafeBareCmd(ctx, nil, globalOpts, git.SubCmd{ Name: "receive-pack", Args: []string{repoPath}, }, + git.WithStdin(stdin), + git.WithStdout(stdout), + git.WithStderr(stderr), git.WithReceivePackHooks(ctx, config.Config, req, "ssh"), git.WithGitProtocol(ctx, req), ) diff --git a/internal/gitalyssh/gitalyssh.go b/internal/gitalyssh/gitalyssh.go index 253073934..4b57f6168 100644 --- a/internal/gitalyssh/gitalyssh.go +++ b/internal/gitalyssh/gitalyssh.go @@ -26,12 +26,6 @@ const ( GitalyInternalURL = "ssh://gitaly/internal.git" ) -const ( - // EnvVarUploadPackAllowAnySHA1InWant enables the capability to request - // individual SHA1's from the remote repo - EnvVarUploadPackAllowAnySHA1InWant = "uploadpack.allowAnySHA1InWant=true" -) - var ( envInjector = tracing.NewEnvInjector() ) |