Skip to content

Derive PR associations from the forge review cache#14709

Closed
estib-vega wants to merge 11 commits into
masterfrom
derive-pr-association-plan
Closed

Derive PR associations from the forge review cache#14709
estib-vega wants to merge 11 commits into
masterfrom
derive-pr-association-plan

Conversation

@estib-vega

@estib-vega estib-vega commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Superseded

This aggregate draft has been replaced by the following reviewable draft stack.

Layer Pull request
1 #14733
2 #14734
3 #14735
4 #14736
5 #14737

The original branch remains available as a safety copy; review should continue on the stacked PRs above.

estib-vega and others added 11 commits July 14, 2026 09:19
Add `review_for_head_ref` and `reviews_by_head` in a new `association`
module. Both match a branch's remote/pushed head short name against the
cached `ForgeReview.source_branch`, deriving the PR association from the
forge cache instead of stored branch metadata.

Reads are cache-only (no network, safe inside a projection); a `None`
result means the branch isn't published, not an error. When several
cached reviews share a `source_branch`, prefer an open review over a
merged/closed one, then a base-repo head over a fork.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switch the primary branch projection to resolve its PR number from the
forge review cache instead of stored branch metadata. At the but-api
boundary we already have the branch's resolved remote-tracking ref; take
its short name (remote stripped) and look it up against the cached
reviews via `review_for_head_ref`.

`review_id` is no longer used and is always cleared. Stored `pr_number`
metadata is still written by other paths but is now unused by this
projection, keeping the migration incremental.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve each stack segment's `pr_or_mr` in `list_branches` from the
forge review cache, matching the segment's remote-tracking short name
against the cached reviews, instead of reading `review.pull_request`
off segment metadata. The map is built once per listing pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add `RefInfo::apply_forge_pr_associations`, a projection-time pass that
resolves each segment's PR association from a cache-derived map keyed by
the segment's remote/pushed short name, overwriting the stored
`review.pull_request`. A stale number is cleared when nothing matches,
and a match on an otherwise metadata-less segment synthesizes metadata
so single-branch (ad-hoc) mode surfaces the association too. `review_id`
is projection-unused and always cleared.

Wire it into the `head_info` command that backs the desktop's workspace
stack view, mirroring how gerrit enrichment is applied only on that read
path (not on mutation-response workspace state). The crate stays free of
DB/forge wiring: the caller builds the `{short name -> PR number}` map
from the forge review cache.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Lite app writes a mutation's returned `head_info` straight into its
head_info query cache instead of refetching, so mutation responses need
the same cache-derived PR associations the `head_info` read command
applies. Otherwise Lite would revert to stored-metadata PRs (and, once
the write side is removed, show none) after every mutation.

Thread a `{ pushed short name -> PR number }` map through the single
`WorkspaceState::from_workspace` choke point (and the rebase-based
constructors that funnel into it), applying `apply_forge_pr_associations`
there. The map is built once per endpoint via the shared
`forge_prs_by_head`, before any exclusive borrow of the context. Making
the parameter required means the compiler enforces enrichment at every
mutation site. The graph-workspace projection is left untouched (out of
scope).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Creating a PR returned the review but never cached it, so a cache-only
projection would not show the new PR until the next full review-list
sync. Insert it optimistically: `publish_review` now caches the created
review (via the new `but_forge::cache_review`, a single upsert that never
deletes other rows) right after the forge call, keyed by the forge's own
`source_branch` — exactly what the projection resolver matches.

To stop a background list sync from immediately deleting that fresh row
(the forge's list endpoint is eventually consistent and may omit a
brand-new PR for a few seconds), `reconcile_listed` now spares open
reviews synced within a grace window: absent-open deletion is gated on
`last_sync_at <= absent_open_cutoff`, and callers pass `now - 60s`.

While here, clarify the reconcile code, which had grown into one dense
method: split it into named steps (`delete_open_reviews_absent_from_list`,
`prune_merged_before`) with docs explaining why each exception exists.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`publishReview` now invalidates Stacks/StackDetails so the head_info
workspace view refetches and surfaces the optimistically-cached PR
association right after creation, instead of relying on a metadata write.

Drop the `updateBranchPrNumber` call from the review-creation flow: the
association is derived from the forge review cache now, so there is no PR
number to persist onto branch metadata.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Migrate the remaining metadata readers off `review.pull_request` so the
stored field can be retired without regressing these surfaces.

- but-forge: add `pr_numbers_by_head`, the `{pushed short name -> PR
  number}` map both projection boundaries need; reuse it in but-api.
- but (CLI): apply `apply_forge_pr_associations` in the crate's own
  `head_info`, mirroring the desktop command, so `HeadInfoBranch` and any
  `applied_stacks` consumer see cache-derived associations. The `status`
  command's segments are `but_graph::StackSegment` (a different layer), so
  migrate its `pr_number()` reads to the `review_map` it already builds
  from the cache (keyed by branch name); the CI gate now keys on a cached
  review existing. Remove the now-unused `SegmentWithId::pr_number()`.
- but-agentlog: enrich the observation's `head_info`, and resolve the
  single-branch fallback via the cache (read-only db open, empty when
  absent). Drop the now-unused `meta` param.
- but-transaction: its `WorkspaceState` is always discarded by the CLI, so
  pass an empty map rather than couple this layer to forge/db.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
With every reader deriving the PR association from the forge review
cache, retire the code that wrote a PR number into branch metadata.

Backend:
- gitbutler-stack: delete `Stack::set_pr_number`.
- gitbutler-branch-actions: delete `update_branch_pr_number`; drop the
  upstream-integration "dissociate closed reviews" write (staleness is now
  reflected by the cache).
- but-api: delete the `update_branch_pr_number` command and stop writing
  `review.pull_request` when applying a review (the applied review is
  already cached).
- but-server / gitbutler-tauri: drop the removed command's route and
  handler registration.
- but (CLI): `publish_review` relies on the optimistic cache insert, so
  drop its `update_branch_pr_number` call.

Desktop:
- Delete `PrNumberSync` and the `updateBranchPrNumber` endpoint/accessor
  and its test mock.

The `UpdateDependentBranchPrNumber` oplog kind is kept so existing oplog
entries still deserialize. The legacy `set_branch` review persistence and
rename carry-over are intentionally left for the schema/rename discussion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@estib-vega estib-vega force-pushed the derive-pr-association-plan branch from 1c77b35 to b983fb1 Compare July 14, 2026 07:19
@github-actions github-actions Bot added rust Pull requests that update Rust code @gitbutler/desktop CLI The command-line program `but` labels Jul 14, 2026
@estib-vega

Copy link
Copy Markdown
Contributor Author

Superseded by the five linked draft PRs #14733 through #14737.

@estib-vega estib-vega closed this Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLI The command-line program `but` @gitbutler/desktop rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant