Add retry logic to checkout and submoduleUpdate for partial clone resilience

When using partial clones (filter=blob:none, which is automatically set
for sparse checkouts), `git checkout` lazily fetches missing blobs from
the promisor remote. If the remote is temporarily unavailable, this
network call fails and surfaces as a hard error with no retry.

The `fetch`, `getDefaultBranch`, and `lfsFetch` methods already use
retryHelper, but `checkout` and `submoduleUpdate` did not, despite both
performing network operations:

- `checkout`: fetches blobs on-demand from promisor remotes during
  partial clone checkouts
- `submoduleUpdate`: clones/fetches submodule repositories

This was observed in production when GitHub's git service had a brief
outage, causing the checkout step to fail with:

  fatal: unable to access '...': Failed to connect to github.com port
  443 after 135272 ms: Couldn't connect to server
  fatal: could not fetch <sha> from promisor remote

Wrapping both methods with the existing retryHelper (3 attempts with
10-20s jittered backoff) makes these operations resilient to transient
network failures, consistent with how fetch already behaves.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jason Barnett 2026-03-20 16:52:56 +00:00
parent 0c366fd6a8
commit b6a00b350f
2 changed files with 24 additions and 4 deletions

14
dist/index.js vendored
View File

@ -789,7 +789,14 @@ class GitCommandManager {
else {
args.push(ref);
}
yield this.execGit(args);
// Retry checkout because it can trigger network I/O when using partial
// clones (filter=blob:none). In that mode git lazily fetches missing
// blobs from the promisor remote during checkout, so a transient network
// failure would otherwise surface as a hard error here.
const that = this;
yield retryHelper.execute(() => __awaiter(this, void 0, void 0, function* () {
yield that.execGit(args);
}));
});
}
checkoutDetach() {
@ -990,7 +997,10 @@ class GitCommandManager {
if (recursive) {
args.push('--recursive');
}
yield this.execGit(args);
const that = this;
yield retryHelper.execute(() => __awaiter(this, void 0, void 0, function* () {
yield that.execGit(args);
}));
});
}
submoduleStatus() {

View File

@ -228,7 +228,14 @@ class GitCommandManager {
args.push(ref)
}
await this.execGit(args)
// Retry checkout because it can trigger network I/O when using partial
// clones (filter=blob:none). In that mode git lazily fetches missing
// blobs from the promisor remote during checkout, so a transient network
// failure would otherwise surface as a hard error here.
const that = this
await retryHelper.execute(async () => {
await that.execGit(args)
})
}
async checkoutDetach(): Promise<void> {
@ -457,7 +464,10 @@ class GitCommandManager {
args.push('--recursive')
}
await this.execGit(args)
const that = this
await retryHelper.execute(async () => {
await that.execGit(args)
})
}
async submoduleStatus(): Promise<boolean> {