Retry lock errors reported only through the command's error

Have isRetryableError also inspect the returned error, not just the
captured output. Streamed commands (amend, commit, and other operations
run through the gpg helper) don't capture output, so their index.lock
failures were slipping past the retry loop and surfacing to the user as
a hard "Git command failed". Now they retry like every other command.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller 2026-07-10 08:56:56 +02:00
parent 0902c5c058
commit c1cd500fa7
2 changed files with 12 additions and 10 deletions

View file

@ -20,11 +20,17 @@ type gitCmdObjRunner struct {
innerRunner oscommands.ICmdObjRunner
}
// isRetryableError returns true if the error output indicates a transient
// lock-related error that may succeed on retry
func isRetryableError(output string) bool {
return strings.Contains(output, ".git/index.lock") ||
strings.Contains(output, "cannot lock ref")
// isRetryableError returns true if a failed command hit a transient
// lock-related condition that may succeed on retry. The lock message can reach
// us either in the command's captured output or, for streamed commands whose
// output we don't capture, only in the returned error, so we check both.
func isRetryableError(output string, err error) bool {
text := output
if err != nil {
text += "\n" + err.Error()
}
return strings.Contains(text, ".git/index.lock") ||
strings.Contains(text, "cannot lock ref")
}
func (self *gitCmdObjRunner) Run(cmdObj *oscommands.CmdObj) error {
@ -58,7 +64,7 @@ func (self *gitCmdObjRunner) retryOnLockError(run func() (string, error)) (strin
for range RetryCount {
output, err = run()
if err == nil || !isRetryableError(output) {
if err == nil || !isRetryableError(output, err) {
return output, err
}

View file

@ -102,10 +102,6 @@ func TestRunWithOutputRetriesWhenLockErrorIsOnlyInError(t *testing.T) {
_, err := newTestRunner(inner).RunWithOutput(dummyCmdObj())
/* EXPECTED:
assert.NoError(t, err)
assert.Equal(t, 2, inner.calls)
ACTUAL: */
assert.Error(t, err)
assert.Equal(t, 1, inner.calls)
}