Skip to content

Commit f5d803d

Browse files
committed
Add PR/MR review feature with URL resolution, cross-fork support, and TUI integration
1 parent 37b7cc6 commit f5d803d

4 files changed

Lines changed: 119 additions & 7 deletions

File tree

specs/20260329-205124-branch-compare/data-model.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,28 @@ The complete result of comparing two remote tracking branches. Passed from `_Git
2828
| `unique_commits` | `list[CommitInfo]` | Commits in target but not base (reuses existing `CommitInfo` type) |
2929
| `conflicts` | `list[ConflictFile]` | Conflicting files; empty list = clean merge |
3030
| `shallow_warning` | `bool` | True if the clone is shallow and results may be incomplete |
31+
| `full_diff` | `str` | Full `git diff --no-color` output for export |
32+
| `full_log` | `str` | Full `git log --stat --no-color --date=iso` output for export |
33+
34+
### `PRMetadata`
35+
36+
Metadata resolved from a GitHub PR or GitLab MR URL via the provider REST API.
37+
38+
| Field | Type | Description |
39+
|-------|------|-----------|
40+
| `provider` | `str` | `"github"` or `"gitlab"` |
41+
| `owner` | `str` | Base repo owner |
42+
| `repo` | `str` | Base repo name |
43+
| `number` | `int` | PR/MR number |
44+
| `title` | `str` | PR/MR title |
45+
| `state` | `str` | `open`, `closed`, or `merged` |
46+
| `author` | `str` | PR/MR author username |
47+
| `base` | `str` | Base branch name |
48+
| `head` | `str` | Head branch name |
49+
| `url` | `str` | Original PR/MR URL |
50+
| `head_clone_url` | `str` | Clone URL for the head repo (may be a fork) |
51+
| `head_owner` | `str` | Owner of the head repo (may differ from base for cross-fork PRs) |
52+
| `description` | `str` | PR/MR body text |
3153

3254
## Reused Existing Types
3355

@@ -54,4 +76,15 @@ BranchComparison result displayed
5476
5577
├─ user presses Export button → .txt written, notification shown
5678
└─ user presses Escape → screen dismissed, return to main view
79+
80+
For PR/MR flow (TUI):
81+
82+
```
83+
User enters PR number → _run_pr_resolve()
84+
85+
▼ API call resolves base + head branch names + metadata
86+
├─ cross-fork: add pr-head remote, fetch branch
87+
▼ base/target inputs auto-filled → _run_comparison() triggered
88+
▼ BranchComparison result displayed with PR title + description header
89+
```
5790
```

specs/20260329-205124-branch-compare/quickstart.md

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Quickstart: Branch Comparison View
22

3-
## Using the Compare Screen
3+
## TUI — Compare Two Branches
44

55
1. Load a repository as normal (`owner/repo` + Load).
66
2. Press `c` to open the compare screen.
@@ -10,15 +10,42 @@
1010
6. The screen fetches remote refs and displays a scrollable result panel:
1111
- **Diff Summary** — total files/lines changed, per-file breakdown
1212
- **Commits** — commits in target not in base
13-
- **Conflicts** — conflicting files with hunk detail, or "Clean merge"
14-
7. Press **Export** to save the result to a `.txt` file in your working directory.
13+
- **Conflicts** — conflicting files listed, or "Clean merge"
14+
7. Press **Export** to save a detailed `.txt` report to your working directory.
1515
8. Press `Escape` to return to the main commit view.
1616

17+
## TUI — Review a PR/MR
18+
19+
1. Load the repository the PR targets.
20+
2. Press `c` to open the compare screen.
21+
3. In the **PR/MR number** input, type the PR number (e.g., `123`) — or paste a full URL.
22+
4. Press Enter or click **Fill branches** — base and target inputs auto-fill from the API.
23+
5. Comparison runs automatically; PR title and description appear at the top of results.
24+
6. Press **Export** to save the report (filename includes the PR number).
25+
26+
## CLI — Compare Branches
27+
28+
```bash
29+
uv run cex owner/repo --compare main feature/foo
30+
```
31+
32+
Prints a summary to stdout and writes `compare-main-feature-foo-YYYYMMDD.txt`.
33+
34+
## CLI — Review a PR/MR
35+
36+
```bash
37+
uv run cex --pr https://github.com/owner/repo/pull/123
38+
```
39+
40+
Resolves base/head from the GitHub/GitLab API, clones, compares, and writes
41+
`compare-owner-repo-pr123-YYYYMMDD.txt` with a PR metadata header.
42+
1743
## Notes
1844

19-
- If the repo was loaded with `--depth N`, a shallow-clone warning may appear and some results may be incomplete.
20-
- Branch names with `/` (e.g., `feature/foo`) are supported — type them as-is without the `origin/` prefix.
21-
- The compare screen reuses the existing clone; no re-download occurs.
45+
- Branch names with `/` (e.g., `feature/foo`) are supported — type as-is without `origin/` prefix.
46+
- Cross-fork PRs are handled: the fork is added as a `pr-head` remote automatically.
47+
- Export includes full `git diff` patch and `git log --stat` output.
48+
- Set `GITHUB_TOKEN` / `GITLAB_TOKEN` env vars to avoid API rate limits.
2249

2350
## Development Setup
2451

specs/20260329-205124-branch-compare/tasks.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,11 @@
8080

8181
- [X] T016 [P] Add input validation to `CompareScreen` in `app.py` — strip whitespace from branch names; show inline error if either field is empty when Compare is pressed; show `notify()` if `origin/<branch>` ref does not exist after fetch
8282
- [X] T017 [P] Add shallow-warning banner to `CompareScreen` results rendering in `app.py` — if `BranchComparison.shallow_warning` is True, prepend `"⚠ Shallow clone — commit log and conflict results may be incomplete"` at the top of the results panel
83-
- [ ] T018 Manual validation per `specs/20260329-205124-branch-compare/quickstart.md` against at least one real repository — confirm all three sections render correctly and export file matches screen content
83+
- [X] T018 Manual validation per `specs/20260329-205124-branch-compare/quickstart.md` against at least one real repository — confirmed all sections render correctly and export matches screen content
84+
- [X] T019 Enhanced `_write_export()` to produce detailed report: full `git diff` patch, full `git log --stat`, changed files with +/- counts, PR metadata header when available
85+
- [X] T020 Added `--compare BASE TARGET` CLI flag to `main()` — clones repo, compares branches, prints summary, writes `.txt` without launching TUI
86+
- [X] T021 Fixed `filter=blob:none` partial clone: switched to `git diff --name-status` for file list (tree-only), fetch blobs on demand before `--shortstat` and full diff
87+
- [X] T022 Added PR/MR review: `--pr <URL>` CLI flag + PR number input in TUI; resolves base/head via GitHub/GitLab API; handles cross-fork PRs via `pr-head` remote; export filename encodes PR number; PR title + description shown in TUI and export
8488

8589
---
8690

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Tasks: PR/MR Review from URL
2+
3+
**Input**: Design documents from `/specs/20260329-233618-pr-review/`
4+
**Prerequisites**: spec.md ✓
5+
6+
**Structure**: All code lives in `app.py` (single-file architecture per constitution).
7+
8+
---
9+
10+
## Phase 1: Core Types & URL Resolution
11+
12+
- [X] T001 Add `PRMetadata` NamedTuple to Types section of `app.py` — fields: `provider`, `owner`, `repo`, `number`, `title`, `state`, `author`, `base`, `head`, `url`, `head_clone_url`, `head_owner`, `description`
13+
- [X] T002 Implement `_resolve_pr_url(url) -> PRMetadata` in `app.py` — parses GitHub PR URLs (`github.com/.../pull/N`) and GitLab MR URLs (`gitlab.com/.../merge_requests/N`); calls provider REST API to fetch base/head branch names, title, state, author, description; raises `ValueError` for unsupported URL formats
14+
15+
---
16+
17+
## Phase 2: CLI `--pr` Flag
18+
19+
- [X] T003 Implement `_add_fork_remote(tmpdir, fork_url, branch)` helper in `app.py` — adds `pr-head` remote pointing at the fork and fetches the given branch with `--filter=blob:none`
20+
- [X] T004 Implement `_pr_review(url, provider_key, depth)` async function in `app.py` — resolves PR metadata, clones base repo, adds fork remote for cross-fork PRs, calls `compare_branches(base, head_ref)`, writes export via `_write_export(result, pr_meta=pr)`, prints summary to stdout
21+
- [X] T005 Add `--pr URL` argument to `main()` argparse in `app.py` — takes priority over `--compare`; `owner/repo` positional arg optional (inferred from URL)
22+
- [X] T006 Update `_write_export()` to accept optional `pr_meta` — when present: use `compare-{owner}-{repo}-pr{N}-{YYYYMMDD}.txt` filename; prepend PR metadata header (URL, title, author, state, description) before diff sections
23+
24+
---
25+
26+
## Phase 3: TUI Integration
27+
28+
- [X] T007 Pass `owner`, `repo`, `provider` to `CompareScreen.__init__()` from `action_compare()` in `CommitExplorer`
29+
- [X] T008 Add PR/MR number input row to `CompareScreen.compose()``Input` with placeholder "PR/MR number (e.g. 123)" + "Fill branches" `Button`; placed above existing branch inputs
30+
- [X] T009 Implement `_build_pr_url(number)` method on `CompareScreen` — constructs full GitHub/GitLab URL from loaded repo context; raises `ValueError` if repo not loaded or provider unsupported
31+
- [X] T010 Implement `_run_pr_resolve()` `@work` method on `CompareScreen` — resolves PR URL, adds fork remote if cross-fork, auto-fills base/target inputs, triggers `_run_comparison()`; stores `pr_meta` in `self._last_pr`
32+
- [X] T011 Update `_render_comparison()` to show PR header when `self._last_pr` is set — display title, author, state, and up to 20 lines of description (truncated with "… see export" notice)
33+
- [X] T012 Update `on_export_pressed()` to pass `pr_meta=self._last_pr` to `_write_export()` so export filename includes PR number
34+
35+
---
36+
37+
## Phase 4: Cross-Fork PR Support
38+
39+
- [X] T013 Detect cross-fork PRs in `_pr_review()` — compare `pr.head_owner` vs `pr.owner`; if different, call `_add_fork_remote()` and set `head_ref = "pr-head/<branch>"`
40+
- [X] T014 Detect cross-fork PRs in `CompareScreen._run_pr_resolve()` — same logic; set target input to `pr-head/<branch>` so `_run_comparison()` uses the correct ref
41+
- [X] T015 Update `compare_branches()` ref resolution — handle pre-qualified refs (`pr-head/...`) without adding `origin/` prefix; update blob fetch to use correct remote per ref
42+
43+
---
44+
45+
## Validation
46+
47+
- [X] T016 CLI: tested with `cex --pr https://github.com/anthropics/claude-code/pull/40594` (cross-fork PR) — 7 files changed, correct diff and commit log in export
48+
- [X] T017 TUI: tested with PR number input `40586` on `anthropics/claude-code` — base/target auto-filled, comparison runs, PR title shown in results

0 commit comments

Comments
 (0)