feat(git): add interactive push mode for SSH passphrase support

Introduces gitInteractive() that connects stdin/stdout to the terminal,
allowing SSH to prompt for passphrases. Also adds GitError type to
improve error messages by including stderr output.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Snider 2026-01-30 07:44:35 +00:00
parent 2252d6bda1
commit dcb0871b61

View file

@ -4,6 +4,8 @@ package git
import ( import (
"bytes" "bytes"
"context" "context"
"io"
"os"
"os/exec" "os/exec"
"strconv" "strconv"
"strings" "strings"
@ -139,9 +141,32 @@ func getAheadBehind(ctx context.Context, path string) (ahead, behind int) {
} }
// Push pushes commits for a single repository. // Push pushes commits for a single repository.
// Uses interactive mode to support SSH passphrase prompts.
func Push(ctx context.Context, path string) error { func Push(ctx context.Context, path string) error {
_, err := gitCommand(ctx, path, "push") return gitInteractive(ctx, path, "push")
return err }
// gitInteractive runs a git command with terminal attached for user interaction.
func gitInteractive(ctx context.Context, dir string, args ...string) error {
cmd := exec.CommandContext(ctx, "git", args...)
cmd.Dir = dir
// Connect to terminal for SSH passphrase prompts
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
// Capture stderr for error reporting while also showing it
var stderr bytes.Buffer
cmd.Stderr = io.MultiWriter(os.Stderr, &stderr)
if err := cmd.Run(); err != nil {
if stderr.Len() > 0 {
return &GitError{Err: err, Stderr: stderr.String()}
}
return err
}
return nil
} }
// PushResult represents the result of a push operation. // PushResult represents the result of a push operation.
@ -191,8 +216,31 @@ func gitCommand(ctx context.Context, dir string, args ...string) (string, error)
cmd.Stderr = &stderr cmd.Stderr = &stderr
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
// Include stderr in error message for better diagnostics
if stderr.Len() > 0 {
return "", &GitError{Err: err, Stderr: stderr.String()}
}
return "", err return "", err
} }
return stdout.String(), nil return stdout.String(), nil
} }
// GitError wraps a git command error with stderr output.
type GitError struct {
Err error
Stderr string
}
func (e *GitError) Error() string {
// Return just the stderr message, trimmed
msg := strings.TrimSpace(e.Stderr)
if msg != "" {
return msg
}
return e.Err.Error()
}
func (e *GitError) Unwrap() error {
return e.Err
}