fix(refresh): improve auto-refresh robustness against repo switches

The automatic data refreshing mechanism has been updated to handle asynchronous state changes more reliably. It now incorporates checks to determine if the active repository path has changed during background operations. This prevents stale or incorrect data from being displayed if a user navigates away from or switches repositories while a refresh cycle is in progress.

- Added path validation checks after status fetching and bundle opening
- Ensured all subsequent refresh calls use the validated current repository path
This commit is contained in:
Christoph Brandau
2026-07-10 11:15:47 +02:00
parent 55b4c9fc50
commit 44a5b776d7
+15 -9
View File
@@ -631,26 +631,32 @@
async function autoRefreshTick() { async function autoRefreshTick() {
if (!autoRefreshEnabled || activeView !== "repository" || !activeRepoPath || isBusy || autoRefreshInFlight || resolveDialogOpen || compareDialogOpen || compareSelectOpen || newBranchCommit || globalSearchOpen) return; if (!autoRefreshEnabled || activeView !== "repository" || !activeRepoPath || isBusy || autoRefreshInFlight || resolveDialogOpen || compareDialogOpen || compareSelectOpen || newBranchCommit || globalSearchOpen) return;
const path = activeRepoPath;
autoRefreshInFlight = true; autoRefreshInFlight = true;
try { try {
// Cheap fast path: only fetch status; skip the heavy reload if nothing changed. // Cheap fast path: only fetch status; skip the heavy reload if nothing changed.
const nextStatus = await getStatus(activeRepoPath); const nextStatus = await getStatus(path);
// The user may have switched repos (or closed this one) while the status
// call was in flight — applying a stale result would flash/overwrite the
// now-active repo's name and data with this one's.
if (!sameRepoPath(path, activeRepoPath)) return;
if (statusFingerprint(nextStatus) === lastStatusFingerprint) return; if (statusFingerprint(nextStatus) === lastStatusFingerprint) return;
applyStatus(nextStatus); applyStatus(nextStatus);
// Something changed — reload branches, commits and files in one bundled call. // Something changed — reload branches, commits and files in one bundled call.
const bundle = await openRepositoryBundle(activeRepoPath, 100); const bundle = await openRepositoryBundle(path, 100);
if (!sameRepoPath(path, activeRepoPath)) return;
const previousHeadHash = lastFileHistoryHeadHash; const previousHeadHash = lastFileHistoryHeadHash;
await refreshBranchList(activeRepoPath, bundle.branches); await refreshBranchList(path, bundle.branches);
await refreshTags(activeRepoPath, bundle.tags); await refreshTags(path, bundle.tags);
await refreshStashes(activeRepoPath, bundle.stashes); await refreshStashes(path, bundle.stashes);
await refreshCommitHistory(activeRepoPath, bundle.commits); await refreshCommitHistory(path, bundle.commits);
await refreshExplorerFiles(activeRepoPath, bundle.files); await refreshExplorerFiles(path, bundle.files);
// File history reflects `git log`, which only changes when HEAD actually moves // File history reflects `git log`, which only changes when HEAD actually moves
// (new commit, checkout, merge, ...) — skip the reload otherwise so a plain // (new commit, checkout, merge, ...) — skip the reload otherwise so a plain
// working-tree/status change (staging, edits) doesn't keep re-fetching and // working-tree/status change (staging, edits) doesn't keep re-fetching and
// flickering the currently viewed file's history. // flickering the currently viewed file's history.
if (lastFileHistoryHeadHash !== previousHeadHash) { if (lastFileHistoryHeadHash !== previousHeadHash && sameRepoPath(path, activeRepoPath)) {
await refreshFileHistory(activeRepoPath); await refreshFileHistory(path);
} }
} catch { /* ignore transient errors */ } finally { } catch { /* ignore transient errors */ } finally {
autoRefreshInFlight = false; autoRefreshInFlight = false;