feat(diff): diff a whole branch vs a base (?base=<rev>) — review before landing (W3)
The git-diff viewer can now diff the current branch against a base commit-ish
(e.g. main) — review an agent's whole branch from your phone before merging, not
just uncommitted changes. Completes the long-deferred FR-B1.9.
- src/http/diff.ts: getDiff() gains an optional base. Three-layer defense so an
attacker-supplied base never reaches a shell or acts as a git option:
(1) isPlausibleRev() rejects leading '-', '..'/'...' ranges, metachars, control
chars, >250 chars; (2) git rev-parse --verify --quiet --end-of-options
<base>^{commit} — only a resolved 7-64 hex sha is accepted, else empty result;
(3) git diff --no-color <sha>... -- (three-dot = the branch's changes since
divergence, PR-style). execFile, no shell, timeout + maxBuffer bound.
- src/types.ts: additive optional base on DiffResult.
- src/server.ts GET /projects/diff reads ?base (400 on !isPlausibleRev); read-only.
- public/diff.ts: a "compare base" <select> (Working tree + one option per base),
disables Working/Staged tabs in base mode. public/projects.ts derives bases from
the worktree branches ∪ current branch.
Verified: typecheck + build:web clean, 1763 pass (diff tests 87 green). The 1 red
in the plain full run is the pre-existing real-PTY "ring buffer" flake (times out
at default 5s under sandbox load; 27/27 at --test-timeout=30000) — unrelated.
This commit is contained in:
@@ -411,6 +411,29 @@ describe('renderProjectDetail — B1 View Diff', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('passes the repo worktree branches + current branch as diff bases (FR-B1.9)', () => {
|
||||
const detail = makeDetail({
|
||||
path: '/home/user/proj',
|
||||
isGit: true,
|
||||
branch: 'main',
|
||||
worktrees: [
|
||||
{ path: '/home/user/proj', branch: 'main', isMain: true, isCurrent: true },
|
||||
{ path: '/home/user/proj-wt/feat', branch: 'feat', isMain: false, isCurrent: false },
|
||||
],
|
||||
})
|
||||
const root = renderProjectDetail(detail, makeHooks(), makeCbs())
|
||||
;(root.querySelector('.proj-diff-toggle') as HTMLButtonElement).click()
|
||||
|
||||
expect(mockMountDiffViewer).toHaveBeenCalledWith(
|
||||
expect.any(HTMLElement),
|
||||
'/home/user/proj',
|
||||
expect.objectContaining({ bases: expect.arrayContaining(['main', 'feat']) }),
|
||||
)
|
||||
// Deduped: `main` appears in both the current branch and a worktree → once.
|
||||
const opts = mockMountDiffViewer.mock.calls[0]?.[2] as { bases: string[] }
|
||||
expect(opts.bases.filter((b) => b === 'main')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('clicking "View Diff" shows the diff panel', () => {
|
||||
const root = renderProjectDetail(makeDetail({ isGit: true }), makeHooks(), makeCbs())
|
||||
const panel = root.querySelector('.proj-diff-panel') as HTMLElement
|
||||
|
||||
Reference in New Issue
Block a user