Fix branch refresh.

This commit is contained in:
Greyson Parrelli
2026-08-27 12:10:32 -04:00
parent 11ebe07e4f
commit 2e3af19d00
3 changed files with 44 additions and 6 deletions
+10 -1
View File
@@ -783,7 +783,16 @@ fn handleRevision(
else => "git could not produce that diff — check the base ref",
},
);
return writeJson(a, request, .ok, .{ .revision = rev });
// The branch rides along with the digest so the page can notice that the
// work tree moved to another branch — which the digest alone cannot always
// say. `digest` hashes HEAD's *sha*, so branching off the commit you are
// already on (`git switch -c`) leaves it identical while the name in the
// review's header is now wrong. One `rev-parse` next to the `git diff` this
// endpoint already runs is not a cost worth avoiding.
return writeJson(a, request, .ok, .{
.revision = rev,
.branch = git.currentBranch(repo, a, self.io),
});
}
fn handleFile(
+27 -4
View File
@@ -205,6 +205,9 @@ export default function App() {
const [toast, setToast] = useState<string | null>(null);
const path = repo?.path ?? null;
// What the header currently says the work tree is on. The poll below compares
// against it to notice a branch switch made outside this pane.
const branch = repo?.branch ?? null;
// ctxByRepo holds only the repos whose base ref you've actually changed;
// anything else falls back to HEAD. Deriving rather than seeding state avoids a
@@ -338,6 +341,18 @@ export default function App() {
if (path) loadDiff(ctx);
}, [path, ctx, loadDiff]);
// What the refresh button and the stale banner do. Deliberately more than
// reloading the patch: the reason to press either is that the world moved, and
// everything the header shows — the branch, the ref list the base picker offers,
// the draft counts — is otherwise fetched once when the page loads and never
// again. The poll below catches a branch switch on its own, but it stops once
// the diff is known to be stale, and it can't see a ref that was merely
// created; pressing refresh is the answer to both.
const reload = useCallback(() => {
loadRepo();
loadDiff(ctx);
}, [loadRepo, loadDiff, ctx]);
// Tell the server which diff is on screen, so an agent asked to review it lands
// its comments on the lines you're actually looking at. Best-effort — nothing on
// screen depends on it.
@@ -404,10 +419,18 @@ export default function App() {
busy = true;
const seq = reqRef.current;
try {
const { revision: now } = await api.revision(ctx, { ignoreWhitespace: ignoreWs });
const { revision: now, branch: on } = await api.revision(ctx, {
ignoreWhitespace: ignoreWs,
});
// A load that started while this was in flight has already answered the
// question, with a revision this closure doesn't know about.
if (canceled || seq !== reqRef.current) return;
// A branch switch is news about the repository rather than about the
// diff, and the two don't always arrive together — so it is acted on
// rather than announced. The header is describing the work tree, which
// really is on another branch now; the diff below it is still the one
// you were reading, and the banner is what offers to move that.
if (on && on !== branch) loadRepo();
if (now && now !== revision) setStale(now);
} catch {
// A failed poll says nothing about the diff — the next one will.
@@ -428,7 +451,7 @@ export default function App() {
document.removeEventListener('visibilitychange', onVisible);
window.removeEventListener('focus', onVisible);
};
}, [path, ctx, ignoreWs, revision, stale]);
}, [path, ctx, ignoreWs, revision, stale, branch, loadRepo]);
// dismissStale keeps the diff you are reading and stops pointing at it, but
// takes the newer state as the baseline — so the *next* change says so too,
@@ -723,7 +746,7 @@ export default function App() {
<button
className={`icon-btn${stale ? ' is-attention' : ''}`}
onClick={() => loadDiff(ctx)}
onClick={reload}
title={stale ? 'Reload the diff — the work tree has moved on' : 'Refresh diff'}
aria-label="Refresh diff"
>
@@ -779,7 +802,7 @@ export default function App() {
<span className="banner-text">
This diff is out of date the work tree has changed since it loaded.
</span>
<button className="btn-ghost" onClick={() => loadDiff(ctx)}>
<button className="btn-ghost" onClick={reload}>
<Icon name="sync" size={14} />
Refresh
</button>
+7 -1
View File
@@ -108,13 +108,19 @@ export const api = {
// `revision` that came with the diff on screen. One hash, so it's cheap to ask
// for repeatedly — which is what lets the page notice the diff is out of date
// without pulling it out from under whoever is reading it.
//
// `branch` is the work tree's current branch, answered here because this is the
// one thing the page asks about on a timer. The header names the branch, and
// nothing else would tell the page it had changed: the digest covers HEAD's
// sha, so branching off the commit you're on doesn't move it (see
// Server.handleRevision), and repo info is otherwise fetched once at load.
revision: (ctx: DiffContext, opts: { ignoreWhitespace?: boolean } = {}) =>
fetch(
`${apiBase}/diff/revision${q(
...ctxParams(ctx),
...(opts.ignoreWhitespace ? ['ignoreWhitespace=1'] : []),
)}`,
).then(json<{ revision: string }>),
).then(json<{ revision: string; branch?: string }>),
// Full contents of a file at a ref, for expanding collapsed context. Null when
// the file doesn't exist at that ref (e.g. a newly added file).