Skip to content

feat: auto-update PR title/body for single-revision PRs during sync - #73

Draft
MingweiSamuel wants to merge 2 commits into
mainfrom
update-prs
Draft

feat: auto-update PR title/body for single-revision PRs during sync#73
MingweiSamuel wants to merge 2 commits into
mainfrom
update-prs

Conversation

@MingweiSamuel

Copy link
Copy Markdown
Member

Implements GitHub issue #72: when a PR has exactly one revision (commit)
associated with it, the sync command now automatically updates the PR's
title and body on GitHub to match the commit's description.

Changes:

  • src/gh.rs: Added body field to GhPr struct (with #[serde(default)]
    for backward compat with existing fixtures). Added body to the GraphQL
    query (PR_NODE_FIELDS). Added edit_title_body() function that calls
    gh pr edit --title --body.

  • src/pr_dag.rs: Added SyncAction::UpdateDescription variant with
    new_title and new_body fields. Added title_body_from_description()
    helper that derives title (first line) and body (remaining lines, excluding
    in plan_sync that checks single-commit open PRs and generates an
    UpdateDescription action when title/body don't match. Refactored
    plan_create to reuse title_body_from_description() instead of
    duplicating the logic.

  • src/tests.rs: Added three dedicated tests:

    • sync_updates_description_single_commit: verifies update is generated
    • sync_no_update_description_multi_commit: verifies no update for multi-commit PRs
    • sync_no_update_description_already_matches: verifies no update when already in sync
      Updated gh_pr helper to include body field.
  • Updated existing snapshots that now correctly include description update
    actions for single-commit PRs whose title differs from the commit description.

MingweiSamuel and others added 2 commits July 30, 2026 20:25
Implements GitHub issue #72: when a PR has exactly one revision (commit)
associated with it, the `sync` command now automatically updates the PR's
title and body on GitHub to match the commit's description.

Changes:

- src/gh.rs: Added `body` field to `GhPr` struct (with `#[serde(default)]`
  for backward compat with existing fixtures). Added `body` to the GraphQL
  query (`PR_NODE_FIELDS`). Added `edit_title_body()` function that calls
  `gh pr edit --title --body`.

- src/pr_dag.rs: Added `SyncAction::UpdateDescription` variant with
  `new_title` and `new_body` fields. Added `title_body_from_description()`
  helper that derives title (first line) and body (remaining lines, excluding
  PR: and Co-authored-by: trailers) from a commit description. Added step 6
  in `plan_sync` that checks single-commit open PRs and generates an
  `UpdateDescription` action when title/body don't match. Refactored
  `plan_create` to reuse `title_body_from_description()` instead of
  duplicating the logic.

- src/tests.rs: Added three dedicated tests:
  - `sync_updates_description_single_commit`: verifies update is generated
  - `sync_no_update_description_multi_commit`: verifies no update for multi-commit PRs
  - `sync_no_update_description_already_matches`: verifies no update when already in sync
  Updated `gh_pr` helper to include `body` field.

- Updated existing snapshots that now correctly include description update
  actions for single-commit PRs whose title differs from the commit description.

Co-authored-by: Infinity 🤖 <infinity@hydro.run>
PR: #73
…one actions

Reworks the auto-update-description feature to reduce code complexity.
Instead of fetching `body` from GitHub's GraphQL API and comparing it to
decide when to update, we now blindly update the title/body for
single-revision open PRs whenever they are being pushed.

Key changes:
- Removed `body` field from `GhPr` struct and GraphQL query (saves ~60KB
  payload on large repos like hydro-project/hydro)
- Removed standalone `SyncAction::UpdateDescription` variant
- Added `description_updates` field to `SyncAction::Push` — description
  updates are now bundled with the push action
- During push execution, after pushing bookmarks, each single-revision PR
  gets its title/body updated via `gh pr edit`
- Kept `title_body_from_description()` helper (shared with `plan_create`)
- Updated all tests and snapshots to reflect the new behavior

Co-authored-by: Infinity 🤖 <infinity@hydro.run>
PR: #73
@MingweiSamuel MingweiSamuel changed the title Auto-update PR title/body for single-revision PRs during sync feat: auto-update PR title/body for single-revision PRs during sync Jul 30, 2026
@MingweiSamuel
MingweiSamuel requested a review from Copilot July 30, 2026 20:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support to sync for updating GitHub PR descriptions (title/body) based on the commit description when a PR corresponds to a single revision, and updates tests/snapshots accordingly.

Changes:

  • Extend sync planning/execution so Push actions can also carry PR description (title/body) updates for single-revision open PRs.
  • Add title_body_from_description() helper and reuse it for default PR title/body derivation in plan_create.
  • Add dedicated sync tests and update/add snapshot fixtures to reflect the new planned actions.

Reviewed changes

Copilot reviewed 29 out of 29 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/pr_dag.rs Plans and executes PR title/body updates alongside pushes; adds commit-description parsing helper.
src/gh.rs Adds edit_title_body() wrapper around gh pr edit --title/--body.
src/tests.rs Adds tests covering single-commit vs multi-commit behavior and non-push behavior.
src/snapshots/jj_pr__tests__sync@stageleft-71.json.gz.snap Snapshot metadata update (assertion line) and sync output adjustments.
src/snapshots/jj_pr__tests__sync@remade-pr-old-branch-deleted.json.gz.snap Snapshot metadata update (assertion line).
src/snapshots/jj_pr__tests__sync@remade-pr-new-pr-created.json.gz.snap Snapshot metadata update (assertion line).
src/snapshots/jj_pr__tests__sync@remade-pr-both-branches-exist.json.gz.snap Snapshot metadata update (assertion line).
src/snapshots/jj_pr__tests__sync@jj-pr.json.gz.snap Snapshot metadata update (assertion line) and sync output includes title update lines.
src/snapshots/jj_pr__tests__sync@jj-pr-stale-trunk-1778002925.json.gz.snap Snapshot metadata update (assertion line).
src/snapshots/jj_pr__tests__sync@jj-pr-dump-1065390.json.gz.snap Snapshot metadata update (assertion line) and sync output includes title update lines.
src/snapshots/jj_pr__tests__sync@jj-pr-conflict-1778014006.json.gz.snap Snapshot metadata update (assertion line) and sync output includes title update lines.
src/snapshots/jj_pr__tests__sync@jj-pr-ambiguous.json.gz.snap Snapshot metadata update (assertion line) and sync output includes title update lines.
src/snapshots/jj_pr__tests__sync@hydro-prs-approved-1778006800.json.gz.snap Snapshot metadata update (assertion line).
src/snapshots/jj_pr__tests__sync@hydro-merged-many.json.gz.snap Snapshot metadata update (assertion line) and sync output includes title update lines.
src/snapshots/jj_pr__tests__sync@hydro-before.json.gz.snap Snapshot metadata update (assertion line) and sync output includes title update lines.
src/snapshots/jj_pr__tests__sync@hydro-after.json.gz.snap Snapshot metadata update (assertion line) and sync output includes title update lines.
src/snapshots/jj_pr__tests__sync@hydro-4.json.gz.snap Snapshot metadata update (assertion line) and sync output includes title update lines.
src/snapshots/jj_pr__tests__sync@hydro-3.json.gz.snap Snapshot metadata update (assertion line) and sync output includes title update lines.
src/snapshots/jj_pr__tests__sync@cargo-smart-release-73.json.gz.snap Snapshot metadata update (assertion line).
src/snapshots/jj_pr__tests__sync_updates_description_single_commit_on_push.snap New snapshot for single-commit PR push including a planned title update.
src/snapshots/jj_pr__tests__sync_no_description_update_multi_commit_on_push.snap New snapshot for multi-commit PR push with no planned description update.
src/snapshots/jj_pr__tests__sync_no_description_update_when_not_pushing.snap New snapshot for “no push” case with no planned description update.
src/snapshots/jj_pr__tests__stacked_prs_sync.snap Snapshot metadata update (assertion line).
src/snapshots/jj_pr__tests__nothing_to_sync.snap Snapshot metadata update (assertion line).
src/snapshots/jj_pr__tests__merged_parent_sync.snap Snapshot metadata update (assertion line) and sync output includes title update lines.
src/snapshots/jj_pr__tests__foreign_pr_collision_sync.snap Snapshot metadata update (assertion line).
src/snapshots/jj_pr__tests__create_intermediate_pr_sync_after.snap Snapshot assertion line update and sync output includes title update line.
src/snapshots/jj_pr__tests__closed_pr_base_mismatch_sync.snap Snapshot metadata update (assertion line).
src/snapshots/jj_pr__tests__base_mismatch_sync.snap Snapshot metadata update (assertion line) and sync output includes title update line.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/pr_dag.rs
Comment on lines +1344 to +1348
// For single-revision open PRs, also update the PR title/body.
if gh_pr.state == gh::PrState::Open {
let node_commit_count = jj_entries
.iter()
.filter(|e| state.commit_node.get(&*e.commit.commit_id) == Some(&nk))
Comment thread src/pr_dag.rs
Comment on lines +1192 to +1206
fn title_body_from_description(description: &str) -> (String, String) {
let title = description.lines().next().unwrap_or("untitled").to_owned();
let body = description
.lines()
.skip(1)
.filter(|line| {
let l = line.trim_start().to_ascii_lowercase();
!l.starts_with("co-authored-by:") && !l.starts_with("pr:")
})
.collect::<Vec<_>>()
.join("\n")
.trim()
.to_owned();
(title, body)
}
Comment thread src/pr_dag.rs
Comment on lines 1167 to +1171
let details: Vec<_> = bookmarks.iter().map(|(pr, bm)| format!("{pr} ({bm})")).collect();
write!(f, "push: {}", details.join(", "))
write!(f, "push: {}", details.join(", "))?;
for (pr, title, _body) in description_updates {
write!(f, "\n update {pr} title -> \"{title}\"")?;
}
Comment thread src/pr_dag.rs
Comment on lines +1472 to +1475
for (pr, title, body) in description_updates {
eprintln!("Updating {} title -> \"{}\"", crate::style::pr_num(*pr, None), title,);
gh::edit_title_body(pr.get(), title, body)?;
}
Comment on lines +2 to +4
source: src/tests.rs
assertion_line: 1085
expression: plan_sync(&f)
Comment on lines +2 to +4
source: src/tests.rs
assertion_line: 1104
expression: plan_sync(&f)
Comment on lines +2 to +4
source: src/tests.rs
assertion_line: 1121
expression: plan_sync(&f)
Comment thread src/tests.rs
Comment on lines +1069 to +1073
#[test]
fn sync_updates_description_single_commit_on_push() {
// Single-commit PR that needs pushing — description update should be included with push.
let f = fixture(
vec![
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants