Skip to content

Repository files navigation

jj-pr

A CLI tool that syncs Jujutsu (jj) bookmarks with GitHub Pull Requests.

Core philosophy:

  • No hidden state — everything is derived at runtime from jj, gh, and change description trailers like PR: #1234
  • jj owns structure — the DAG of changes
  • GitHub owns presentation — PR titles, descriptions, review state
  • Invalid states are first class — ambiguities and inconsistencies are clearly displayed to you and don't halt your work — fix them when you feel like it

Installation

cargo install --git https://github.com/hydro-project/jj-pr --locked
jj-pr util install-aliases

The second line installs jj pr as a subcommand alias into your user config.

Dependencies

Requires jj (of course) and gh (GitHub CLI) on your PATH.

To install jj:

cargo install jj-cli --bin jj --locked

To install gh, see here.

Example Workflow

1. Fetch latest changes

jj git fetch

Note: jj-pr does not fetch for you — fetch first to update the local repo to the latest remote state.

2. View the PR DAG

jj pr          # or: jj-pr show
  PR #2804  Ready  mingwei/fix-inline-tasksfix(dfir_rs): defer `_counter` task spawning via `Context::request_task` buffer  PR #2746  Ready  mingwei/sqs-rebase
├─╯  feat(hydro_test): add example of AWS + SQS support
│ ◆  trunk()
├─╯
│     PR #2812*  Draft  mingwei/delete-subgraph-id
│ ├─╮  refactor(dfir_rs): remove SubgraphId, simplify StateLifespan
│ │   PR #2811*  Draft  mingwei/delete-schedule-subgraph-argsPR #2810*  Draft  mingwei/delete-metrics-slotvec
│ ├─╯  refactor(dfir_rs): use slotmap SecondaryMap for metrics  PR #2801*  Draft  mingwei/delete-state-api-usage
╭─┤  refactor(dfir_lang): operators capture local state  PR #2798  Ready  mingwei/delete-intermediate-subgraph
├─╯  refactor(dfir_lang): remove intermediate subgraphs
~  (elided revisions)

PR numbers are clickable hyperlinks in supported terminals. A * after the PR number means there are pending actions for that PR.

If there are ambiguous commits, they show up as warnings with hints.

  PR #2798*  Ready  mingwei/delete-intermediate-subgraphrefactor(dfir_lang): double-buffer defer_tick handoffs, remove intermediate subgraphs [ci-bench]
│   PR #2801*  Draft  mingwei/delete-state-api-usage
├─╯  refactor(dfir_lang): operators capture local state instead of using the state API [ci-bench]
⚠  ambiguous branches: PR #2798, PR #2801, trailers: PR #2798
│  (restructure PRs to resolvestack one on the other)
◆  trunk()

3. Diagnose and fix issues

Use jj pr log to see individual commits and their PR associations.

jj pr log          # commits associated with PRs
jj pr log --all    # include commits not in any PR

This is mainly useful for diagnosing ambiguous commits.

  lqxnymrprnpl a6f5cee01ecb PR #2801* Draftrefactor(dfir_lang): add write_tick_end to OperatorWriteOutput, convert fold to inline state  wnkkzpnlkwzt ca43c533b6f6 mingwei/delete-intermediate-subgraph sandbox-d11a1381-9aa3-49f8-abf1-16585609c8a6 PR #2798* Ready
├─╯  Address PR #2798 review comments: extract helper methods, fix stale commentsprupozyltvpz cf49be1b8fc8 ambiguous [PR #2798, PR #2801]
│  refactor(dfir_lang): double-buffer defer_tick handoffs, remove intermediate subgraphsxqxnxvxoyltz 7a71c32b196c ambiguous [PR #2798, PR #2801]
│  test: add test_mutual_defer_tick reproducing as_code topo sort cycletrunk()

Fix ambiguities by restructuring with jj (e.g., jj rebase to stack one PR on the other, or jj describe to edit "PR: #1234" trailers).

4. Sync with GitHub

jj pr sync

This stamps missing trailers, rebases children of merged PRs onto trunk(), abandons merged commits, pushes affected bookmarks, and updates GitHub base branches. Bookmarks with content-conflicted commits are skipped automatically. You'll be prompted before any changes are applied.

  stamp #15 trailer on knynvqkoypsy
  stamp #14 trailer on olqtvyvtvzyq
  push: #15 (my-feature-a)
Apply 3 action(s)? [Y/n]
Stamping PR #15 on knynvqkoypsy
Stamping PR #14 on olqtvyvtvzyq
Pushing 1 bookmark(s): my-feature-a

5. Create a new PR

jj bookmark create my-feature-b  # first create the bookmark in jj
jj pr create my-feature-b        # then create a draft PR for it

The base branch is auto-detected by walking the parent graph.

Pushing my-feature-b
Creating PR: feat: my new features (hydro-project:main ← my-feature-b) [draft]
Created PR #16

How It Works

PR membership is determined from the jj DAG structure: each commit belongs to the PR whose bookmark is its nearest descendant. Commits shared between multiple PRs become ambiguous nodes. Trailers (PR: #1234) are appended to change descriptions as a safety net. See the decide() function in pr_dag.rs for the full membership algorithm.

See DESIGN.md for the full design rationale.

Fork Workflows

If you push to a personal fork instead of the upstream repo:

  1. Add your fork as a named remote:
jj git remote add fork https://github.com/YOUR_USERNAME/REPO.git
  1. Configure jj to push to your fork by default:
jj config set --repo git.push "fork"

This tells jj pr create where to push bookmarks that aren't yet tracked on any remote.

  1. Create bookmarks and PRs as normal:
jj bookmark create my-branch
jj pr create my-branch

jj pr create will automatically:

  • Detect the fork by comparing the remote's GitHub owner against the upstream owner
  • Push to the correct remote
  • Pass YOUR_USERNAME:branch as the head ref so GitHub creates a cross-repo PR

The display shows both owners:

create PR: "my feature" (upstream-org:main ← your-username:my-branch) [draft]

Known Limitations

  • No stacked PRs in fork workflows — GitHub requires the base branch to exist on the upstream repo. Since intermediate branches only exist on your fork, all fork PRs target the default branch (e.g. main). Local DAG structure is preserved in jj but not reflected in GitHub's PR chain.
  • Multi-parent PRs target the default branch — GitHub PRs can only have a single base branch. If a PR has multiple parents in the DAG, it targets the default branch instead of stacking.
  • One tracked remote per PR bookmark — if a bookmark tracks multiple non-git remotes, jj pr create will error. Untrack one with jj bookmark untrack.
  • Multiple PRs sharing the same bookmark — if you have e.g. an open and a closed/merged PR both using the same branch name, only one will be tracked.
  • Some other things, probably

About

DAG PRs are in, stacked PRs are out!

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages