Skip to content

Commit 20acbb7

Browse files
fix(server): reset a published PR branch even after its upstream was fetched
An idle local PR branch at an old, published head stayed behind when a fetch had already moved its remote-tracking ref to the rewritten head. Treat a head reachable from a remote-tracking ref, or in the upstream's reflog, as safe to reset. AI-assisted; verified with the tests and tools listed above.
1 parent b15a216 commit 20acbb7

2 files changed

Lines changed: 32 additions & 7 deletions

File tree

‎apps/server/src/git/GitManager.test.ts‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5391,6 +5391,7 @@ it.layer(layerGitManagerTest)("GitManager", (it) => {
53915391
{ localBranch: "ahead", expectedHead: "local", isOnPullRequestHead: false },
53925392
{ localBranch: "behind", expectedHead: "pull request", isOnPullRequestHead: true },
53935393
{ localBranch: "rewritten", expectedHead: "pull request", isOnPullRequestHead: true },
5394+
{ localBranch: "rewritten-fetched", expectedHead: "pull request", isOnPullRequestHead: true },
53945395
{ localBranch: "wrong-ref", expectedHead: "pull request", isOnPullRequestHead: true },
53955396
{ localBranch: "stale-head", expectedHead: "local", isOnPullRequestHead: false },
53965397
{ localBranch: "checkout-race", expectedHead: "pull request", isOnPullRequestHead: true },
@@ -5412,7 +5413,7 @@ it.layer(layerGitManagerTest)("GitManager", (it) => {
54125413
NodeFS.writeFileSync(NodePath.join(repoDir, "mine.txt"), "mine\n");
54135414
yield* runGit(repoDir, ["add", "mine.txt"]);
54145415
yield* runGit(repoDir, ["commit", "-m", "My unpublished commit"]);
5415-
} else if (localBranch !== "rewritten") {
5416+
} else if (!localBranch.startsWith("rewritten")) {
54165417
yield* runGit(repoDir, ["checkout", "-b", "author-push"]);
54175418
NodeFS.writeFileSync(NodePath.join(repoDir, "authored.txt"), "authored\n");
54185419
yield* runGit(repoDir, ["add", "authored.txt"]);
@@ -5430,11 +5431,13 @@ it.layer(layerGitManagerTest)("GitManager", (it) => {
54305431
"origin",
54315432
"author-rewrite:feature/pr-existing",
54325433
]);
5433-
yield* runGit(repoDir, [
5434-
"update-ref",
5435-
"refs/remotes/origin/feature/pr-existing",
5436-
staleHead,
5437-
]);
5434+
if (localBranch === "rewritten") {
5435+
yield* runGit(repoDir, [
5436+
"update-ref",
5437+
"refs/remotes/origin/feature/pr-existing",
5438+
staleHead,
5439+
]);
5440+
}
54385441
}
54395442
const localHead = (yield* runGit(repoDir, [
54405443
"rev-parse",

‎apps/server/src/git/GitManager.ts‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3134,11 +3134,33 @@ export const make = Effect.gen(function* () {
31343134
),
31353135
),
31363136
);
3137+
const { commitSha: headCommit } = yield* gitCore.resolveCommit({ cwd, revision: "HEAD" });
3138+
// A head that was ever published loses nothing when reset, even if a fetch already moved its upstream.
3139+
const gitLines = (args: ReadonlyArray<string>) =>
3140+
gitCore
3141+
.execute({
3142+
operation: "GitManager.preparePullRequestThread.publishedHead",
3143+
cwd,
3144+
args,
3145+
allowNonZeroExit: true,
3146+
timeoutMs: 5_000,
3147+
})
3148+
.pipe(
3149+
Effect.map((result) =>
3150+
result.exitCode === 0 ? result.stdout.split("\n").filter(Boolean) : [],
3151+
),
3152+
);
3153+
const publishedHead =
3154+
(yield* gitLines(["for-each-ref", "--contains", headCommit, "refs/remotes"])).length >
3155+
0 ||
3156+
(yield* gitLines(["log", "--walk-reflogs", "--format=%H", "@{upstream}"])).includes(
3157+
headCommit,
3158+
);
31373159
return yield* gitCore
31383160
.refreshCheckedOutBranch({
31393161
cwd,
31403162
targetCommit: target.commitSha,
3141-
resetWhenHeadCommit: upstreamCommitBeforeFetch,
3163+
resetWhenHeadCommit: publishedHead ? headCommit : upstreamCommitBeforeFetch,
31423164
})
31433165
.pipe(Effect.map((result) => result.onTarget));
31443166
});

0 commit comments

Comments
 (0)