code review cleanup · Entire
code review cleanup
ba637db→main· Soph·1mo ago·7 files·+216 added/-297 removed
- Drop internal basicAuth duplicate; use transporthttp.BasicAuth directly in the gitproto retry path (the package already pulls in go-git types via the AuthMethod interface contract). - Collapse GitCredentialHelper.Approve/Reject into a single signal() helper; they only differed in the op string. - Type the credential op as auth.CredentialOp with named constants (CredentialOpFill / Approve / Reject), removing magic strings from production code and the integration test switch. - Collapse fakeCredentialHelper's six counters/last-X fields into a single calls []credCall slice with count(op)/last(op) accessors. - Add a newTestConn(t, rt) helper to deduplicate the 8 new tests. - Trim narrating comments throughout (per code review). Kept the CredentialHelper interface contract, the GIT_TERMINAL_PROMPT=0 rationale, the 403-Cloudflare anecdote, and the explicit-auth-wins invariant — these encode non-obvious WHY. Trimmed the rest.
No behaviour change; full test suite + race detector + golangci-lint pass clean.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
Sessions
4f0fc9b99fdcView transcript
Changes
7
internal
auth
Mauth.go+42/-55
Mauth_test.go+17/-23
gitproto
Msmarthttp.go+12/-36
Msmarthttp_test.go+140/-174
syncer
Mauth_test.go+2/-2
Mintegration_test.go+3/-4
Msyncer.go-3
43 unmodified lines
return &transporthttp.BasicAuth{Username: username, Password: password}, nil } // Note: we deliberately do not consult the git credential helper here. // Doing so eagerly would leak stored credentials to public repos that // don't require auth, and previously caused interactive prompts when // no helper had credentials (issue #63). The credential helper is now // consulted on demand when an HTTP request returns 401 — see // GitCredentialHelper, wired up by the HTTP connection layer. return nil, nil //nolint:nilnil // nil signals no auth method found at this stage }
return nil }
// newGitCredentialCmd builds the git credential <op> invocation used by
// GitCredentialCommand. Extracted so tests can inspect the command's
// environment without exec'ing git.
func newGitCredentialCmd(ctx context.Context, op, input string) *exec.Cmd {
cmd := exec.CommandContext(ctx, "git", "credential", op)
// CredentialOp identifies a git credential subcommand.
type CredentialOp string
const ( CredentialOpFill CredentialOp = "fill" CredentialOpApprove CredentialOp = "approve" CredentialOpReject CredentialOp = "reject" )
// newGitCredentialCmd builds the git credential <op> invocation. Extracted
// so tests can inspect the command's environment without exec'ing git.
func newGitCredentialCmd(ctx context.Context, op CredentialOp, input string) *exec.Cmd {
cmd := exec.CommandContext(ctx, "git", "credential", string(op))
cmd.Stdin = strings.NewReader(input)
// Disable git's interactive terminal prompt fallback. When no credential
// helper has credentials for the host (e.g. a public repo on a server
// the user has never authenticated against), git would otherwise drop
// to an interactive username/password prompt on /dev/tty. git-sync is a
// non-interactive tool — failing here lets us cleanly surface a 401
// rather than block waiting for input. See issue #63.
// Suppress git's interactive username/password fallback. Without this,
// a host with no configured helper drops to a /dev/tty prompt and turns
// git-sync into an interactive command (issue #63).
cmd.Env = append(os.Environ(), "GIT_TERMINAL_PROMPT=0")
return cmd
}
// GitCredentialCommand invokes git credential <op> with the given input
// (in the git-credential text format). op is one of "fill", "approve", or
// "reject". Replaceable for testing.
var GitCredentialCommand = func(ctx context.Context, op, input string) ([]byte, error) {
return newGitCredentialCmd(ctx, op, input).Output()
}
// GitCredentialHelper bridges Git's credential helper protocol to HTTP auth. // It looks up credentials on demand (typically in response to a 401) and // signals back to the helper whether the credentials worked. // // Implementations are best-effort: a missing or misbehaving helper must not // fail the surrounding sync, only deny credentials. Errors from approve/reject // are silently swallowed since those steps are advisory. // Best-effort: a missing or misbehaving helper denies credentials rather // than failing the surrounding sync. type GitCredentialHelper struct{}
// Lookup queries the git credential helper for credentials for ep. Returns // ok=false if no credentials are available (so the caller can surface a // clean 401 rather than block). // ok=false if no credentials are available so the caller can surface a // clean 401 rather than block. //nolint:unparam // err is always nil today but kept for the CredentialHelper interface. func (GitCredentialHelper) Lookup(ctx context.Context, ep *url.URL) (username, password string, ok bool, err error) { if !isHTTPEndpoint(ep) { // git's credential helper protocol only knows about HTTP. return "", "", false, nil } input := credentialInput(ep, "", "") if input == "" { return "", "", false, nil } output, helperErr := GitCredentialCommand(ctx, "fill", input) if helperErr != nil { // Helper exited non-zero — typically means "no credentials found" // or "terminal prompts disabled" (when no helper has creds). Treat // both as "no credentials available" so the original 401 surfaces. return "", "", false, nil //nolint:nilerr // intentional: swallow helper failure as "no credentials" } values := parseCredentialOutput(output) password = values["password"] return username, password, true, nil }
// isHTTPEndpoint reports whether ep is a non-nil HTTP or HTTPS endpoint. // Approve tells the helper the credentials worked. func (h GitCredentialHelper) Approve(ctx context.Context, ep *url.URL, username, password string) { h.signal(ctx, CredentialOpApprove, ep, username, password) }
// Reject tells the helper the credentials failed. func (h GitCredentialHelper) Reject(ctx context.Context, ep *url.URL, username, password string) { h.signal(ctx, CredentialOpReject, ep, username, password) }
func (GitCredentialHelper) signal(ctx context.Context, op CredentialOp, ep *url.URL, username, password string) { input := credentialInput(ep, username, password) if input == "" { return } _, _ = GitCredentialCommand(ctx, op, input) //nolint:errcheck // advisory signal; helper failures swallowed }
func isHTTPEndpoint(ep *url.URL) bool { return ep != nil && (ep.Scheme == "http" || ep.Scheme == "https") }
// Approve tells the helper the credentials worked, so it can persist them. // Best-effort: helper failures are swallowed. func (GitCredentialHelper) Approve(ctx context.Context, ep *url.URL, username, password string) { input := credentialInput(ep, username, password) if input == "" { return } _, _ = GitCredentialCommand(ctx, "approve", input) //nolint:errcheck // best-effort signal }
// Reject tells the helper the credentials failed, so it can forget them. // Best-effort: helper failures are swallowed. func (GitCredentialHelper) Reject(ctx context.Context, ep *url.URL, username, password string) { input := credentialInput(ep, username, password) if input == "" { return } _, _ = GitCredentialCommand(ctx, "reject", input) //nolint:errcheck // best-effort signal }
// credentialInput builds a git-credential format request body for the given
// endpoint. When username/password are set, they are appended (for use with
// git credential approve/reject). When both are empty, the result is a