msuozzo/jj-forge
{ "createdAt": "2026-01-04T18:50:27Z", "defaultBranch": "main", "description": "jj-forge bridges the gap between the stacked world of jj and the branch-based world of git forges.", "fullName": "msuozzo/jj-forge", "homepage": "", "language": "Go", "name": "jj-forge", "pushedAt": "2026-03-19T04:30:45Z", "stargazersCount": 8, "topics": [], "updatedAt": "2026-03-19T04:30:50Z", "url": "https://github.com/msuozzo/jj-forge"}jj-forge
Section titled “jj-forge”A translation layer between Jujutsu (jj) and code forges like GitHub.
Highlights
Section titled “Highlights”-
Reviews with clickable links — Review references like GitHub PRs can be added directly to the
jj logoutput and include clickable terminal hyperlinks. -
Local, parallel checks — Run a configurable check command against multiple changes in parallel, with result caching and
jj logintegration. Supports--detachfor asynchronous background execution. Checks run automatically before review operations likeopenandmerge. -
Stacked merge protection — Configures ruleset to prevent accidental merges of stacked reviews.
-
Forge-independent model — Commands and APIs not specific to a forge. While GitHub is the first supported platform, the underlying design is generic.
-
Familiar jj UX — Uses revsets to target changes and jj’s clap-style cli look and feel.
Prerequisites
Section titled “Prerequisites”Installation
Section titled “Installation”Install the latest version and use as a jj subcommand (jj forge ...):
go install github.com/msuozzo/jj-forge/cmd/jj-forge@latestjj config set --user aliases.forge '["util", "exec", "--", "jj-forge"]'Or, to skip the install step and always run the latest version:
jj config set --user aliases.forge '["util", "exec", "--", "go", "run", "github.com/msuozzo/jj-forge/cmd/jj-forge@latest"]'Getting started
Section titled “Getting started”Clone a repository
Section titled “Clone a repository”repo clone analyzes repository ownership and configures remotes and workflow
automatically:
jj forge repo clone https://github.com/owner/repo.gitWorkflow is detected based on ownership:
- Your non-fork repo — develop-on-main workflow
- Your fork — PR-based workflow
- External repo — forks automatically, then PR-based workflow
Develop-on-main workflow
Section titled “Develop-on-main workflow”jj describe -m "my change"jj forge change submit # fast-forward mainPR-based workflow
Section titled “PR-based workflow”jj describe -m "my change"jj forge review open # upload and create pull request# ... edits happen ...jj forge review update # push content and update PR descriptions# ... review completed ...jj forge review merge # merge pull requestChecks
Section titled “Checks”Configure a check command (any shell command) to run against your changes:
jj config set --repo forge.check-command "go test ./..."Run checks manually:
jj forge change check # check the current changejj forge change check ::@- # check a range of changesChecks run in parallel: Each change is materialized into a persistent pool of working directories (default: 3 workers) using the backing git store. Results are cached by change ID and commit ID, so subsequent execution will reuse the last result.
Background execution
Section titled “Background execution”Use --detach to run checks in the background:
jj forge change check --detachAutomatic checks
Section titled “Automatic checks”When a check command is configured, review open, review update,
change submit, and review merge run checks before proceeding. Pass
--skip-check to bypass.
Templates
Section titled “Templates”jj-forge stores review and check data in the jj repo config. You can display
this data in jj log using template-aliases.
Set all template-aliases automatically:
jj forge repo setup-templates # set in repo configjj forge repo setup-templates --user # set in user configOr, copy [examples/templates.toml]!(examples/templates.toml) into your jj
config manually (jj config edit --user). The key formatters are:
"format_forge_check(commit)" = '''if(!immutable && forge_check_exists(change_id, commit_id), if(forge_check_matches(change_id, commit_id, "pass"), label("hint", "ci/√"), if(forge_check_matches(change_id, commit_id, "running"), label("description placeholder", "ci/~"), if(forge_check_matches(change_id, commit_id, "fail"), label("error heading", "ci/X")))))'''"format_forge_change(commit)" = '''if(!immutable && forge_change_exists(change_id), label("error", hyperlink(forge_change_uri(change_id), forge_change_name(change_id))))'''Then include them in your log template:
"format_short_commit_header(commit)" = '''separate(" ", format_short_change_id_with_change_offset(commit), format_forge_change(commit), format_forge_check(commit), format_short_signature(commit.author()), format_timestamp(commit_timestamp(commit)), commit.bookmarks(), commit.tags(), commit.working_copies(), format_short_commit_id(commit.commit_id()), format_commit_labels(commit), if(config("ui.show-cryptographic-signatures").as_boolean(), format_short_cryptographic_signature(commit.signature()) ),)'''This adds:
format_forge_change— a clickable hyperlink to the PR (e.g.,pr/42)format_forge_check— check status indicator:ci/√(pass),ci/~(running),ci/X(fail)
Note:
watchdoes not support hyperlinking in output. Consider usinghwatchwhich supports OSC 52 passthrough.
Stacked Reviews
Section titled “Stacked Reviews”jj-forge tracks parent-child relationships between changes using a
forge-parent trailer in commit descriptions. When you upload a stack of
changes, review open and review update automatically add or update these
trailers.
To prevent accidentally merging commits that still contain forge-parent
trailers, you can add a GitHub branch ruleset:
jj forge repo setup-rulesetDependent Review Links
Section titled “Dependent Review Links”When working with stacked reviews, jj-forge will manage Parents and Children links in descriptions so reviewers can navigate between related reviews. For example, the middle PR in a three-PR stack would have:
> Parents: [#1](https://github.com/owner/repo/pull/1)> Children: [#3](https://github.com/owner/repo/pull/3)review open will initialize them, review update will keep them synchronized
across modifications, and review merge will clean them up prior to
finalization.
Commands
Section titled “Commands”| Command | Description |
|---|---|
change check [REVSET] | Run the configured check command against changes |
change submit REVSET | Land changes directly by fast-forwarding the target branch |
review open [REVSET] | Create a pull request |
review update [REVSET] | Upload content and update PR descriptions with links |
review merge [REV] | Merge a pull request |
review close [REV] | Close a pull request and abandon the change |
review import [REV] | Find and import existing pull requests |
repo clone <url> [path] | Clone repository with automatic workflow detection |
repo setup-ruleset | Add a GitHub ruleset to prevent merging forge-parent commits |
repo setup-templates | Set template-aliases in jj config for forge visualization |
Run jj forge <command> --help for flags and details.
Configuration
Section titled “Configuration”| Key | Description |
|---|---|
forge.check-command | Shell command to run for checks |
forge.default-reviewer | Default reviewer username for review open |
Set values with:
jj config set --repo forge.check-command "go test ./..."jj config set --repo forge.default-reviewer "username"