Files
yaak-mountain-loop/crates/yaak-git/polling.test.ts
T
Gregory SchierandClaude Opus 5 a939f2ac55 fix(git): stop polling a sync directory that is not a git repository
A workspace whose sync directory points at a plain folder still ran the whole
git polling loop. `useGitFetchAll` was unconditional, and `cmd_git_fetch_all`
shells out twice — once to probe `git --version`, once for the fetch itself —
so every window focus spawned two processes to be told

    fatal: not a git repository (or any of the parent directories): .git

`cmd_git_branch_info` failed alongside it, and neither backed off, so the
failures repeated for as long as the workspace stayed open. Reported by a user
whose console was full of both errors.

Branch info (or status, in the dialogs) is now the repository probe, and the
fetch only runs once that probe has succeeded. A non-repository sync directory
therefore costs one libgit2 lookup rather than a pair of subprocesses. Since
branch info is no longer keyed on the fetch timestamp, a completed fetch
invalidates what it invalidates instead — which also stops the branch-info cache
growing an entry per fetch.

Failed git queries now pace themselves: `shouldRetryAfterFailure` holds a query
back for 30s after its first failure and doubles that per consecutive failure up
to ten minutes. That predicate drives `refetchOnWindowFocus` and `retryOnMount`
both: a probe that has never succeeded has no data, so `refetchOnMount` never
gets consulted (`shouldLoadOnMount` in query-core short-circuits on
`state.data === undefined`), and without `retryOnMount` reopening the commit or
remotes dialog on a non-repository re-ran the failed probe immediately.

Any git mutation still invalidates the whole `["git"]` key, so an actual
`git init` takes effect straight away.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 17:01:52 -07:00

28 lines
1.0 KiB
TypeScript

import { describe, expect, test } from "vitest";
import { shouldRetryAfterFailure } from "./polling";
const NOW = 1_000_000_000;
describe("shouldRetryAfterFailure", () => {
test("a query that has not failed always runs", () => {
expect(shouldRetryAfterFailure(0, 0, NOW)).toBe(true);
});
test("waits 30s after the first failure", () => {
expect(shouldRetryAfterFailure(1, NOW - 29_999, NOW)).toBe(false);
expect(shouldRetryAfterFailure(1, NOW - 30_000, NOW)).toBe(true);
});
test("doubles the wait for each consecutive failure", () => {
expect(shouldRetryAfterFailure(2, NOW - 59_999, NOW)).toBe(false);
expect(shouldRetryAfterFailure(2, NOW - 60_000, NOW)).toBe(true);
expect(shouldRetryAfterFailure(3, NOW - 119_999, NOW)).toBe(false);
expect(shouldRetryAfterFailure(3, NOW - 120_000, NOW)).toBe(true);
});
test("caps the wait at ten minutes", () => {
expect(shouldRetryAfterFailure(50, NOW - (10 * 60_000 - 1), NOW)).toBe(false);
expect(shouldRetryAfterFailure(50, NOW - 10 * 60_000, NOW)).toBe(true);
});
});