A guided, convention-aware commit-message builder for Jujutsu.
jjc wraps the jj workflow with interactive prompts that enforce a consistent commit style β either Conventional Commits or Gitmoji β so you never have to remember the format again.
- Features
- Install
- Get started
- Bookmarks
- Configuration
- Supported conventions
- How it works
- Contributing
- License
- π― Convention auto-detection β Inspects the last 10 commits and picks the convention used most often. No flags needed for the common case.
- βοΈ Two conventions, one tool β Full support for Conventional Commits (
feat:,fix(scope):, β¦) and Gitmoji (β¨,π, β¦). - π Interactive prompts β Type, scope(s), and description are gathered through prompts with sensible defaults and pre-fill support.
- π§© Pre-fill everything β Pass
--message,--type, and--scopesto seed the prompts or skip them entirely. Great foraliases and editor integrations. - π·οΈ Bookmarks β Advance the nearest ancestor bookmark, or create/move named bookmarks to the new commit. Supports interactive selection when multiple ancestors match.
cargo install --git https://github.com/Odonno/jj-commitOr build from source:
git clone https://github.com/Odonno/jj-commit
cd jj-commit
cargo build --release
# binary is at ./target/release/jjcWith no flags, jjc inspects the last 10 commits and picks the convention used most often.
jjc? Commit type
> feat
fix
chore
docs
style
refactor
perf
[ββ to move, enter to select]
? Scope (leave empty to finish): auth
? Description: add OAuth2 login support
Resulting commit message:
feat(auth): add OAuth2 login support
Skip auto-detection and force a convention:
jjc --convention conventional
jjc --convention gitmojiSkip the type prompt entirely by passing --type (Conventional Commits only):
jjc --type fix? Scope (leave empty to finish):
? Description: handle null pointer in user resolver
Resulting commit message:
fix: handle null pointer in user resolver
Pass one or more --scopes flags to seed the scope list (Conventional Commits only):
jjc --type feat --scopes api --scopes ui? Scope (leave empty to finish): β api and ui already added
? Description: expose dark mode toggle
Resulting commit message:
feat(api,ui): expose dark mode toggle
Use --message to parse an existing commit string into the prompts so you can review and amend each field:
jjc --message "fix(auth): wrong token expiry"? Commit type [fix]
? Scope [auth]
? Description [wrong token expiry]
--message works with Gitmoji too, accepting both the shortcode (:bug:) and raw emoji (π) forms.
jjc --convention gitmoji? Gitmoji
> β¨ Introduce new features.
π Fix a bug.
ποΈ Critical hotfix.
π Add or update documentation.
β»οΈ Refactor code.
π₯ Remove code or files.
[ββ to move, enter to select]
? Description: streaming support for chat API
Resulting commit message:
β¨ streaming support for chat API
jjc can manage bookmarks as part of your commit.
Use --advance-bookmark (-a) to find the closest ancestor that holds a local bookmark and move it onto the newly created commit β handy for keeping a moving "main"-style bookmark pinned to your latest work:
jjc -aIf that ancestor carries several bookmarks, you get an interactive multi-select:
? Select bookmarks to advance to the new commit:
> [x] main
[x] release
[ ] wip
[ββ to move, space to toggle, enter to confirm]
With a single bookmark the choice is applied automatically. If no ancestor has any bookmark, jjc prints a warning instead of failing.
Use --bookmarks (-b, repeatable) to point one or more bookmarks at the new commit, creating them if they don't exist:
jjc -b feature-x -b v2The two flags cooperate: --bookmarks destinations are applied directly, while --advance-bookmark discovers ancestors interactively. They can be used together in a single invocation:
jjc --type feat --scopes ui --advance-bookmark --bookmarks releasejjc reads the same configuration the jj CLI does, in the same order:
- Built-in defaults from
jj-lib. - User config file β
$JJ_CONFIG(colon-separated, like$PATH), or otherwise$XDG_CONFIG_HOME/jj/config.toml(falling back to~/.config/jj/config.toml), the legacy~/.jjconfig.toml, and%APPDATA%\jj\config.tomlon Windows. - Environment overrides β
JJ_USERsetsuser.nameandJJ_EMAILsetsuser.email, taking precedence over the config file.
So if you've already configured jj, you're configured for jjc β nothing extra to do.
The --type / --convention conventional flow knows these types:
| Type | Use for |
|---|---|
feat |
A new feature |
fix |
A bug fix |
chore |
Maintenance tasks that don't touch src/docs |
docs |
Documentation only changes |
style |
Formatting, whitespace, semicolons, etc. |
refactor |
Code changes that neither fix a bug nor add a feature |
perf |
Performance improvements |
test |
Adding or correcting tests |
build |
Build system or external dependencies |
ci |
CI configuration files and scripts |
revert |
Reverting a previous commit |
The breaking-change marker (feat!:) is parsed from --message but not added by a prompt β use --message when you need it.
The Gitmoji flow ships the full gitmoji.dev table (80+ entries), presented with their description. Both the shortcode (:sparkles:) and raw emoji (β¨) forms are recognized when pre-filling with --message.
Unlike a thin wrapper that calls out to the jj binary, jjc links against jj-lib and performs the commit transaction in-process:
- Loads your real
jjstacked config and workspace (mirroring thejjCLI's lookup rules). - Snapshots the working copy β respecting
.gitignoreand auto-tracking new files, just likejj's defaultsnapshot.auto-track = "all()". - Rewrites the open working-copy commit with the snapshotted tree and your crafted message.
- Rebases any descendants, then checks out a fresh empty working-copy commit on top.
- Syncs the Git index and
HEADfor co-located Git repos so the Git view matches Jujutsu. - Optionally advances bookmarks before or after the commit lands.
This means you get the same resulting topology as jj commit β without spawning jj.
Before contributing a change, please run:
cargo fmt
cargo clippy -- -D warnings
cargo testSee LICENSE.