github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/git/command.go (about)

     1  package git
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"io"
     8  	"os/exec"
     9  
    10  	"github.com/ungtb10d/cli/v2/internal/run"
    11  )
    12  
    13  type commandCtx = func(ctx context.Context, name string, args ...string) *exec.Cmd
    14  
    15  type gitCommand struct {
    16  	*exec.Cmd
    17  }
    18  
    19  func (gc *gitCommand) Run() error {
    20  	stderr := &bytes.Buffer{}
    21  	if gc.Cmd.Stderr == nil {
    22  		gc.Cmd.Stderr = stderr
    23  	}
    24  	// This is a hack in order to not break the hundreds of
    25  	// existing tests that rely on `run.PrepareCmd` to be invoked.
    26  	err := run.PrepareCmd(gc.Cmd).Run()
    27  	if err != nil {
    28  		ge := GitError{err: err, Stderr: stderr.String()}
    29  		var exitError *exec.ExitError
    30  		if errors.As(err, &exitError) {
    31  			ge.ExitCode = exitError.ExitCode()
    32  		}
    33  		return &ge
    34  	}
    35  	return nil
    36  }
    37  
    38  func (gc *gitCommand) Output() ([]byte, error) {
    39  	gc.Stdout = nil
    40  	gc.Stderr = nil
    41  	// This is a hack in order to not break the hundreds of
    42  	// existing tests that rely on `run.PrepareCmd` to be invoked.
    43  	out, err := run.PrepareCmd(gc.Cmd).Output()
    44  	if err != nil {
    45  		ge := GitError{err: err}
    46  		var exitError *exec.ExitError
    47  		if errors.As(err, &exitError) {
    48  			ge.Stderr = string(exitError.Stderr)
    49  			ge.ExitCode = exitError.ExitCode()
    50  		}
    51  		err = &ge
    52  	}
    53  	return out, err
    54  }
    55  
    56  func (gc *gitCommand) setRepoDir(repoDir string) {
    57  	for i, arg := range gc.Args {
    58  		if arg == "-C" {
    59  			gc.Args[i+1] = repoDir
    60  			return
    61  		}
    62  	}
    63  	// Handle "--" invocations for testing purposes.
    64  	var index int
    65  	for i, arg := range gc.Args {
    66  		if arg == "--" {
    67  			index = i + 1
    68  		}
    69  	}
    70  	gc.Args = append(gc.Args[:index+3], gc.Args[index+1:]...)
    71  	gc.Args[index+1] = "-C"
    72  	gc.Args[index+2] = repoDir
    73  }
    74  
    75  // Allow individual commands to be modified from the default client options.
    76  type CommandModifier func(*gitCommand)
    77  
    78  func WithStderr(stderr io.Writer) CommandModifier {
    79  	return func(gc *gitCommand) {
    80  		gc.Stderr = stderr
    81  	}
    82  }
    83  
    84  func WithStdout(stdout io.Writer) CommandModifier {
    85  	return func(gc *gitCommand) {
    86  		gc.Stdout = stdout
    87  	}
    88  }
    89  
    90  func WithStdin(stdin io.Reader) CommandModifier {
    91  	return func(gc *gitCommand) {
    92  		gc.Stdin = stdin
    93  	}
    94  }
    95  
    96  func WithRepoDir(repoDir string) CommandModifier {
    97  	return func(gc *gitCommand) {
    98  		gc.setRepoDir(repoDir)
    99  	}
   100  }