Open pull requests from Pane, and follow them from the review panel - #387
Open pull requests from Pane, and follow them from the review panel#3870x92 wants to merge 10 commits into
Conversation
Reviewing a session meant reading one combined patch: which files a commit touched, and how much, was not visible anywhere. Two changes, both in the diff path: - Commit history rows expand to a per-file list with status and line counts, and clicking a file opens that file's diff directly - Reviewing a large working tree no longer blocks the UI The freeze was real work, not a hang: capturing the working-directory diff spawned one `cat` per untracked file and one `wc -l` per file, all synchronously on the main thread — 800+ spawns for a session with many untracked files, several seconds each pass. The path is now async and batched, with the file list gathered once instead of per consumer. The parsers for `--numstat -z`, `--name-status -z` and porcelain `-z` are pure functions with their own tests; the `-z` format packs `XY path` into a single token, which is easy to get wrong and impossible to notice by eye. Tests: commit file changes (23) and the unified diff parser (11).
Two things went wrong for WSL projects, both because the host is Windows while the shell that runs the command is bash inside the distro. Arguments were quoted with escapeShellArg(), which picks its style from process.platform and therefore handed bash double quotes. Inside those, $, backticks and \ stay live. Git only forbids space, ~^:?*[\ and control characters in a ref, so a branch named fix-`whoami` is legal and would have run as a command; the pull request title is free user text on the same path. quoteArg() now asks the CommandRunner which shell it targets and uses escapeForBash() from wslUtils for WSL, which exists for exactly this and says so in its comment. The pull request body was written to the host tmpdir() and its C:\ path passed to a gh running inside the distro, which cannot open it, so --body-file failed before a pull request was ever created. resolveBodyFile() now writes to the distro's /tmp through the UNC view Windows has of it and names the Linux path to gh. Host projects keep the behaviour they had.
Two defects, both in the path that inlines untracked files into a working-tree diff. The listing used `git ls-files --others --exclude-standard` split on newlines. Git delimits with newlines there, which a filename may contain, and quotes anything non-ASCII into a C-style escape — `täst.txt` arrived as the literal `"t\303\244st.txt"`, a name matching no file on disk, so the file vanished from the diff and from the stats without a word. The listing now uses `-z` and is split on NUL, and nothing is trimmed: a leading or trailing space is part of the name. Content and line counts were then read with `cat "<worktree>/<file>"` and `wc -l "<worktree>/<file>"`, built by interpolation. Git allows `$`, backticks and parentheses in a filename, and inside bash double quotes those are still syntax: a file named `back`whoami`.txt` in a repository was enough to run a command, with no interaction beyond opening the session. Both now go through `fs` — readFile for content, a streamed newline count for the totals — so a repository-controlled name never reaches a shell. untrackedFilePath() resolves the worktree-relative name git reports, going through the UNC mount for a WSL project the same way gitPlumbingCommands already does. The performance fix this PR made is kept and improved: capture spawns a fixed handful of commands whatever the file count, where before it was one `cat` and one batched `wc` per file. MAX_UNTRACKED_INLINE_FILES and MAX_UNTRACKED_INLINE_BYTES still bound what is inlined, and a per-file ceiling stands in for the 1 MB buffer `cat` used to have. chunkByCommandLength() goes with them — nothing builds a command line out of paths any more.
5ef4830 to
26d2f59
Compare
parsakhaz
left a comment
There was a problem hiding this comment.
Verdict: Request changes. Two creation-flow defects violate the PR intent.
Counts: Must Fix: 2 (security: 0) · Should Fix: 0 · pass 1/3
Must Fix
- MF-1 Existing cross-repository pull requests are searched in the checkout context, not the upstream target ·
main/src/services/pullRequestManager.ts:542,main/src/services/pullRequestManager.ts:1005· query each resolved target with--repoand useforkOwner:branchfor an upstream head · violates the stated existing-PR blocker.- Evidence:
findExistingrunsgh pr list --head <branch>without--repo; the focused test stubs anypr listcall and therefore does not prove the command targetsdcouple/Pane. - Failure scenario: from a fork checkout with an open PR into upstream,
ghsearches the fork, returns no row, and Pane offers Create Pull Request instead of the existing upstream link.
- Evidence:
- MF-2 A base branch the target repository does not have is described as invalid but still submit-enabled ·
frontend/src/components/git/CreatePullRequestDialog.tsx:145,frontend/src/components/git/CreatePullRequestDialog.tsx:150,main/src/services/pullRequestManager.ts:581· block known-invalid bases and validate the target base before pushing · violates the stated readable blocker for a missing target base.- Evidence:
baseIsUnknownonly renders warning copy;canSubmitignores it, and the backend pushes beforegh pr createperforms validation. - Failure scenario: the user selects a target where the typed base is absent, Pane pushes the branch, then fails through raw
ghrejection despite already knowing the base is invalid.
- Evidence:
Praise
- Shell arguments are quoted for the actual WSL or host target, PR bodies use a temporary file, and hostile branch/title coverage passed.
- Focused backend review tests passed, including PR manager, WSL quoting, commit-file parsing, untracked hostile filenames, and daemon channel registration.
parsakhaz
left a comment
There was a problem hiding this comment.
Verdict: Request changes. One additional Must-Fix surfaced during the full touched-file pass.
Counts: Must Fix: 1 (security: 0) · Should Fix: 0 · pass 2/3
Must Fix
- MF-3 The lazy patch request cancels itself when loading starts ·
frontend/src/components/git/PullRequestChanges.tsx:63· do not depend on the loading state that the effect itself mutates.- Evidence: the effect includes
diffLoadingin its dependency list, then callssetDiffLoading(true). That rerun invokes cleanup, sets the first request'scancelledflag, and the replacement run exits because loading is true. - Failure scenario: clicking Show diff leaves the panel on Reading the patch forever, even when
pr:get-diffsucceeds.
- Evidence: the effect includes
parsakhaz
left a comment
There was a problem hiding this comment.
Verdict: Request changes. Rebase verification found one additional blocking test-wiring defect.
Counts: Must Fix: 1 (security: 0) · Should Fix: 0 · pass 3/3
Must Fix
- MF-4 Pull request daemon registration executes outside a test with undefined fixtures ·
main/src/ipc/daemonRegistryBindings.test.ts:359· restore the named test block and remove stale imports from handlers no longer covered here.- Evidence: focused Vitest collection failed with
ReferenceError: ipcMain is not defined; after restoring the block, all 12 daemon registry tests pass. - Failure scenario: CI cannot collect this suite, so the new daemon-owned PR channel registration has no executable verification.
- Evidence: focused Vitest collection failed with
parsakhaz
left a comment
There was a problem hiding this comment.
All four review defects are fixed on the current head: target-aware existing PR lookup, pre-push target base validation, stable lazy diff loading, and restored daemon IPC coverage. Current-head unit, browser, lint, and typecheck evidence is recorded in the PR summary.
Final three-pass reportCurrent pushed head: 1. ReviewThe correctness and security review found four Must-Fix defects and posted each with file and line evidence:
Fixed in 2. SimplifyCommit
The current branch has zero blocking lint, Knip, boundary-conformance, or advisory anti-slop findings. 3. RefactorCommit Commit Verification
The branch is rebased on current main, pushed, MERGEABLE, and APPROVED. The currently reported GitHub checks are successful. Follow-ups
|
Description
Open a pull request for a session's branch without leaving Pane, and follow it from the review panel afterwards.
A session already is a branch in a worktree, so everything a pull request needs is on hand. Today the flow leaves the app: switch to a browser, find the fork, remember which repository the contribution is meant for. This adds a Create Pull Request entry to the session's git menu that gathers the branch, its commits, the repository's PR template and the possible targets in one round trip, pushes the branch and calls
gh pr create.Once the pull request exists, the same session's Review panel gains a status strip: state, review decision, mergeability, size and reviewers — the questions that otherwise send you back to the browser.
What it does
Creating
pull_request_template.mdis appended rather than replacing them.owner:branchwhen the pull request crosses repositories.<datalist>hides everything that does not match what is already typed.)+/-and totals, compared against the merge base the way GitHub counts it. "Show diff" loads the patch on demand and renders it in the existingDiffViewer.ghmissing or signed out, an existing pull request for this branch (shown as a link — there is nothing to create), a base branch the target repository does not have.Following
Open / Draft / Merged / Closed, number and title, review decision, comment count, and a conflict warning when GitHub reportsCONFLICTING.owner:head → repo:base, size, and each reviewer with their newest verdict — a "changes requested" that the same person later approved is not an open objection.How it works
PullRequestManager(main process) is the only place that runs commands; everything that decides what to send is a pure function next to it, tested on its own:deriveDraftText,resolveTargets,buildCreateArgs,normalizeBaseBranch,sortBaseBranches,parsePullRequestStatus,parseChecks, …ghis invoked through the session'sCommandRunner, so a WSL project uses theghinside the distro rather than the one on the Windows host. Whenghis not on the snapshotted PATH — for instance because it was installed while Pane was running — the usual install locations are tried before giving up.--body-file, so backticks, quotes and newlines in markdown survive.pr:*): the branch, its remote and theghbinary live on the machine that runs the agents, so a remote runtime answers these itself instead of having the laptop push a branch it does not have.Type of Change
Checklist
pnpm typecheckandpnpm lintlocallypnpm electron-devCritical Areas Modified
Additional Notes
Builds on the per-file commit details PR — it reuses
shared/types/git.ts, thenumstat/name-statusparsers ingitDiffManagerandparseUnifiedDiff. Please merge that one first.Tested against the real GitHub CLI and this repository: draft generation, the target list for a fork, base-branch listing across several API pages, the file comparison cross-checked against
git diff --name-only <base>...HEAD, and status for open pull requests. One real pull request was created end to end and closed again.PR test automation
Status: PASS on
2ab27e158ae6cbe230373bb4c8dd0b2d17158250