feat: update an open PR with its target branch (u) #121

Merged
deadstyle merged 20 commits from feature/118-update-pr into develop 2026-10-10 08:11:07 -05:00
Owner

Summary

u on an open PR you can write to opens a small dialog: merge the latest target branch in, or rebase onto it. GitHub offers merge only. Confirming updates the PR on the forge, the status bar says Updated #N (merge) / Updated #N (rebase) or shows the forge's reason, and the PR's row refreshes its head and CI. Works in the PR box, its details, and ★ Renovate PR rows.

Refs #118

Changes

  • forge: new optional BranchUpdater capability (UpdateStyles, UpdateBranch), ActUpdateBranch gated on write access like merge; Fake and shared contract cases.
  • gitea (Forgejo/Gitea): POST /repos/{o}/{r}/pulls/{n}/update?style=merge|rebase.
  • github: PUT /repos/{o}/{r}/pulls/{n}/update-branch, merge only; any other style fails as unsupported without a request.
  • core: Service.UpdateBranch runs the update, re-fetches the PR into the cache (drops it if no longer open); a refresh failure after a successful update is reported as such.
  • ui: u key, update dialog (reuses the existing dialog, j/k pick, y/enter confirm, esc cancel); u is off where the forge advertises no styles, on non-PR rows, and without write access.
  • docs: design.md keymap gains u; architecture.md records the capability and API mapping.

Decisions

  • Decided: u update is last in the action hints so o open stays visible at 120 columns; u is always in ? help.
  • Decided: no in-flight guard on repeated u, matching approve and close.
  • Decided: shared HTTP error mapping unchanged (409/422 → refused with the forge's message, 403 → unauthorized).
  • Assumed: GitHub's update finishes asynchronously, so the new head may appear only on the next refresh (r or the 5-minute timer).
  • Assumed: Forgejo may answer "already up to date" with a bare 500 for non-admins; the status shows what the forge sent.

Test plan

  • New tests across forge (contract cases on both adapters, Can gating), gitea and github (update_test.go with recorded handlers), core (update_branch_test.go), ui (update_branch_test.go, update_branch_edges_test.go); every new test mutation-checked by its writer.
  • Final verify: make build, go test -race ./... all packages pass, golangci-lint run 0 issues, golangci-lint fmt --diff clean, Windows go vet clean. shellcheck isn't installed locally; this MR touches no shell, CI covers it.
  • Reviews: spec, standards, regression PASS (after one fix round: removed a test pinning internal values; u disabled when no styles are advertised); test-quality PASS; comments PASS after one round.

🤖 Generated with Claude Code

https://claude.ai/code/session_011V1QHxdTA8cs6mwLNYyVNf

## Summary `u` on an open PR you can write to opens a small dialog: **merge** the latest target branch in, or **rebase** onto it. GitHub offers merge only. Confirming updates the PR on the forge, the status bar says `Updated #N (merge)` / `Updated #N (rebase)` or shows the forge's reason, and the PR's row refreshes its head and CI. Works in the PR box, its details, and ★ Renovate PR rows. Refs #118 ## Changes - **forge:** new optional `BranchUpdater` capability (`UpdateStyles`, `UpdateBranch`), `ActUpdateBranch` gated on write access like merge; Fake and shared contract cases. - **gitea (Forgejo/Gitea):** `POST /repos/{o}/{r}/pulls/{n}/update?style=merge|rebase`. - **github:** `PUT /repos/{o}/{r}/pulls/{n}/update-branch`, merge only; any other style fails as unsupported without a request. - **core:** `Service.UpdateBranch` runs the update, re-fetches the PR into the cache (drops it if no longer open); a refresh failure after a successful update is reported as such. - **ui:** `u` key, update dialog (reuses the existing dialog, `j`/`k` pick, `y`/`enter` confirm, `esc` cancel); `u` is off where the forge advertises no styles, on non-PR rows, and without write access. - **docs:** design.md keymap gains `u`; architecture.md records the capability and API mapping. ## Decisions - Decided: `u update` is last in the action hints so `o open` stays visible at 120 columns; `u` is always in `?` help. - Decided: no in-flight guard on repeated `u`, matching approve and close. - Decided: shared HTTP error mapping unchanged (409/422 → refused with the forge's message, 403 → unauthorized). - Assumed: GitHub's update finishes asynchronously, so the new head may appear only on the next refresh (`r` or the 5-minute timer). - Assumed: Forgejo may answer "already up to date" with a bare 500 for non-admins; the status shows what the forge sent. ## Test plan - New tests across forge (contract cases on both adapters, `Can` gating), gitea and github (`update_test.go` with recorded handlers), core (`update_branch_test.go`), ui (`update_branch_test.go`, `update_branch_edges_test.go`); every new test mutation-checked by its writer. - Final verify: `make build`, `go test -race ./...` all packages pass, `golangci-lint run` 0 issues, `golangci-lint fmt --diff` clean, Windows `go vet` clean. `shellcheck` isn't installed locally; this MR touches no shell, CI covers it. - Reviews: spec, standards, regression PASS (after one fix round: removed a test pinning internal values; `u` disabled when no styles are advertised); test-quality PASS; comments PASS after one round. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_011V1QHxdTA8cs6mwLNYyVNf
Adds forge.BranchUpdater (UpdateStyles, UpdateBranch) and the ActUpdateBranch action, which needs write access like merge. The Fake implements the capability and contract cases run for every BranchUpdater adapter.

Refs #118

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011V1QHxdTA8cs6mwLNYyVNf
Refs #118
Gitea posts the style to pulls/{n}/update and offers merge and rebase.
GitHub puts update-branch and offers merge only; any other style returns
ErrUnsupported without a request.

Refs #118

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011V1QHxdTA8cs6mwLNYyVNf
Refs #118

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011V1QHxdTA8cs6mwLNYyVNf
Service.UpdateBranch runs the forge update and refreshes the cached change request; Recheck shares the refresh step.

Refs #118
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011V1QHxdTA8cs6mwLNYyVNf
The u key opens a dialog that picks merge or rebase; the result arrives as the status bar's Updated line.

Refs #118
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011V1QHxdTA8cs6mwLNYyVNf
Refs #118
Refs #118
Refs #118

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011V1QHxdTA8cs6mwLNYyVNf
Its help already reads y/enter confirm, so the hint() override was redundant.

Refs #118

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011V1QHxdTA8cs6mwLNYyVNf
Drops TestActionValuesStable, which pinned iota values nothing persists.

Refs #118

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011V1QHxdTA8cs6mwLNYyVNf
Refs #118

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011V1QHxdTA8cs6mwLNYyVNf
Refs #118

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011V1QHxdTA8cs6mwLNYyVNf
Refs #118

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011V1QHxdTA8cs6mwLNYyVNf
Refs #118

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011V1QHxdTA8cs6mwLNYyVNf
Refs #118

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011V1QHxdTA8cs6mwLNYyVNf
Refs #118
Refs #118
Refs #118
docs(forge): tighten update-branch comments
All checks were successful
ci / check (pull_request) Successful in 1m45s
17b44ab52e
Refs #118

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011V1QHxdTA8cs6mwLNYyVNf
deadstyle merged commit 44f6b00c99 into develop 2026-10-10 08:11:07 -05:00
deadstyle deleted branch feature/118-update-pr 2026-10-10 08:11:07 -05:00
Sign in to join this conversation.
No description provided.