diff --git a/packages/core/src/git.ts b/packages/core/src/git.ts index b633eed780e2..87fab04a98a3 100644 --- a/packages/core/src/git.ts +++ b/packages/core/src/git.ts @@ -367,6 +367,22 @@ const layer = Layer.effect( }) }) + /** + * On Windows, Git exits non-zero instead of reusing a loose object that another process is finalizing at the + * same moment. Objects are content-addressed, so one of at most two retries finds that copy in place. Only a Git + * exit is retried, never a failure to start Git, and other platforms fail on the first attempt. + */ + const retryConcurrentObjectWrite = (effect: Effect.Effect) => + process.platform === "win32" + ? effect.pipe( + Effect.retry({ + times: 2, + schedule: Schedule.spaced("50 millis"), + while: (error) => error.cause === undefined, + }), + ) + : effect + /** * A new store is initialized in a temporary sibling directory and renamed into * place, so processes racing to create the same store never observe or write a @@ -505,11 +521,13 @@ const layer = Layer.effect( { concurrency: 8 }, )).filter((item): item is RelativePath => item !== undefined) if (added.length) - yield* repositoryOperation( - "refresh", - input.repository, - ["update-index", "--add", "--remove", "--replace", "-z", "--stdin"], - { stdin: [...added, ...embedded].join("\0") + "\0", index: input.index }, + yield* retryConcurrentObjectWrite( + repositoryOperation( + "refresh", + input.repository, + ["update-index", "--add", "--remove", "--replace", "-z", "--stdin"], + { stdin: [...added, ...embedded].join("\0") + "\0", index: input.index }, + ), ) return { skipped } }) @@ -561,7 +579,9 @@ const layer = Layer.effect( }) const writeTree = Effect.fn("Git.tree.write")(function* (repository: Repository, index?: string) { - const tree = (yield* repositoryOperation("write_tree", repository, ["write-tree"], { index })).text.trim() + const tree = (yield* retryConcurrentObjectWrite( + repositoryOperation("write_tree", repository, ["write-tree"], { index }), + )).text.trim() if (/^[0-9a-f]{40,64}$/.test(tree)) return TreeID.make(tree) return yield* new OperationError({ operation: "write_tree",