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.