diff --git a/git/cmd/gitcmd.go b/git/cmd/gitcmd.go index d0d7b7b..c3e1f45 100644 --- a/git/cmd/gitcmd.go +++ b/git/cmd/gitcmd.go @@ -93,7 +93,7 @@ func (r Runner) Command(ctx context.Context, dir string, args ...string) *exec.C if r.basicAuth != nil { panic("gitcmd: Command cannot be used with WithBasicAuth; use Run or Output so credentials can be cleaned up") } - cmd := exec.CommandContext(ctx, "git", args...) + cmd := gitCommand(ctx, args...) cmd.Dir = dir cmd.Env, _ = r.commandEnv(ctx, dir) return cmd @@ -107,7 +107,7 @@ func (r Runner) Output(ctx context.Context, dir string, args ...string) ([]byte, // Run runs git and returns stdout, stderr, and a *GitError on failure. func (r Runner) Run(ctx context.Context, dir string, stdin io.Reader, args ...string) ([]byte, []byte, error) { - cmd := exec.CommandContext(ctx, "git", args...) + cmd := gitCommand(ctx, args...) cmd.Dir = dir var cleanup func() cmd.Env, cleanup = r.commandEnv(ctx, dir) @@ -131,6 +131,12 @@ func (r Runner) Run(ctx context.Context, dir string, stdin io.Reader, args ...st return stdout.Bytes(), stderr.Bytes(), nil } +func gitCommand(ctx context.Context, args ...string) *exec.Cmd { + cmd := exec.CommandContext(ctx, "git", args...) + prepareGitCommand(cmd) + return cmd +} + type basicAuth struct { username string password string @@ -231,7 +237,7 @@ func readSafeDirectories(ctx context.Context, env []string, dir string) []string for _, scope := range scopes { // --includes is required for explicit-scope reads to honor include.path // and includeIf directives the way git's default config sequence does. - cmd := exec.CommandContext(ctx, "git", "config", scope, "--includes", "-z", "--get-all", "safe.directory") + cmd := gitCommand(ctx, "config", scope, "--includes", "-z", "--get-all", "safe.directory") cmd.Dir = dir cmd.Env = env out, err := cmd.Output() diff --git a/git/cmd/gitcmd_other.go b/git/cmd/gitcmd_other.go new file mode 100644 index 0000000..2c85d29 --- /dev/null +++ b/git/cmd/gitcmd_other.go @@ -0,0 +1,7 @@ +//go:build !windows + +package gitcmd + +import "os/exec" + +func prepareGitCommand(cmd *exec.Cmd) {} diff --git a/git/cmd/gitcmd_windows.go b/git/cmd/gitcmd_windows.go new file mode 100644 index 0000000..2b326e4 --- /dev/null +++ b/git/cmd/gitcmd_windows.go @@ -0,0 +1,18 @@ +//go:build windows + +package gitcmd + +import ( + "os/exec" + "syscall" + + "golang.org/x/sys/windows" +) + +func prepareGitCommand(cmd *exec.Cmd) { + if cmd.SysProcAttr == nil { + cmd.SysProcAttr = &syscall.SysProcAttr{} + } + // Console-less callers otherwise cause git.exe to allocate a visible window. + cmd.SysProcAttr.CreationFlags |= windows.CREATE_NO_WINDOW +} diff --git a/git/cmd/gitcmd_windows_test.go b/git/cmd/gitcmd_windows_test.go new file mode 100644 index 0000000..1d7810b --- /dev/null +++ b/git/cmd/gitcmd_windows_test.go @@ -0,0 +1,21 @@ +//go:build windows + +package gitcmd + +import ( + "context" + "testing" + + "golang.org/x/sys/windows" +) + +func TestRunnerCommandHidesGitConsoleWindowOnWindows(t *testing.T) { + cmd := New().Command(context.Background(), "", "status") + + if cmd.SysProcAttr == nil { + t.Fatal("git command SysProcAttr is nil") + } + if cmd.SysProcAttr.CreationFlags&windows.CREATE_NO_WINDOW == 0 { + t.Fatalf("git command creation flags = %#x, want CREATE_NO_WINDOW", cmd.SysProcAttr.CreationFlags) + } +}