Post-merge follow-ups for #328 (GET /api/system/repo-status):
- Event-loop blocking: every git invocation in repo-status.ts is now async
(promisified execFile), never execFileSync — the per-remote ls-remote +
fetch could hold the event loop (SSE, PTY streaming) for up to ~60s per
request. The whole computation is single-flight with a 45s TTL cache
(createSingleFlightCache): concurrent requests share one in-flight
promise, a fresh result is served without spawning git, and a rejected
compute is never cached. Route handler shape and response fields
unchanged; remotes still processed sequentially (concurrent fetches in
one repo contend on ref locks).
- Credential disclosure: the redaction from git-clone.ts is extracted as
exported redactGitCredentials() (sanitizeGitOutput now uses it) and
applied via redactRemoteStatus() to every remote card's url and error
string, so a scheme://user:token@host remote URL (or git stderr echoing
it) never reaches a client.
- Non-interactive env: runGit() now uses the shared gitNonInteractiveEnv()
instead of a partial GIT_TERMINAL_PROMPT/BatchMode env, also closing the
GIT_ASKPASS/SSH_ASKPASS/SSH_ASKPASS_REQUIRE/DISPLAY/GCM_INTERACTIVE
prompt paths.
- Upstream parse bug: a local-branch upstream (@{upstream} with no slash,
e.g. after `git branch -u otherbranch`) made slice(0, indexOf('/')) into
slice(0, -1) and yielded garbage like "maste". parseTrackingRemote()
(pure, unit-tested) returns null for it, and the bare ref is dropped so
it cannot be mistaken for a remote-tracking ref downstream.
Tests extended in test/repo-status.test.ts (parseTrackingRemote,
redactGitCredentials/redactRemoteStatus, createSingleFlightCache
single-flight/TTL/rejection semantics).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`GET /api/system/update/check` answers "is there a newer published release
tag?", which is the right question for an npm install but not for a git clone
that tracks a branch. Such an install can be many commits behind its own remote
while the latest tag says it is current, and nothing surfaces that.
Adds `GET /api/system/repo-status`: an informational companion that reports what
this CHECKOUT looks like against its own remotes — current branch and commit,
ahead/behind counts per remote, the remote's role (tracking / upstream / other),
and a bounded list of incoming commits.
Read-only and defensive: every git invocation is `execFileSync` with an argv
array and a timeout, a non-git or remote-less install reports a structured
`error` rather than throwing, and nothing here mutates the working tree or
touches the updater's own state.
Tests: 24 cases in test/repo-status.test.ts.