Dotfiles jj
Use Jujutsu (jj) for version control. Covers workflow, commits, bookmarks, pushing to GitHub, absorb, squash, and stacked PRs. Use when working with jj, creating commits, pushing changes, or managing version control.
git clone https://github.com/coreyja/dotfiles
T=$(mktemp -d) && git clone --depth=1 https://github.com/coreyja/dotfiles "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/jj" ~/.claude/skills/coreyja-dotfiles-jj && rm -rf "$T"
.claude/skills/jj/SKILL.mdJujutsu (jj) Version Control
The Key Mental Model
In jj, the working copy IS a commit. This is fundamentally different from Git where you stage changes then commit. In jj, you're always working inside a commit.
This means:
- Changes you make are immediately part of the current commit
- You should start new work in a fresh commit (so you can squash it later)
- There's no staging area - your working directory is the commit
Core Workflow
Always Start with jj status
jj statusBefore any operation, check your state:
jj status
- "Working copy is clean" = you're in an empty commit, ready to work
- Shows file changes = current commit has work in it
- Shows description = current commit is already described
The Basic Pattern
# 1. Create a new commit BEFORE starting work jj new # 2. Make your changes (edit files) # 3. Describe what you did jj describe -m "Add user authentication" # 4. Create a new commit for the next task jj new # Repeat
Why
before work? If you work directly in a described commit, those changes get mixed in. Then you have to split them later. Starting fresh means you can always squash back if needed.jj new
Common Commands
jj status # Check current state - ALWAYS run first jj log # View commit history jj diff # See changes in current commit jj describe -m "" # Set/update commit message jj new # Create new empty commit jj squash # Move current changes into parent commit jj absorb # Auto-distribute changes to ancestor commits
Bookmarks (Not Branches)
jj uses "bookmarks" instead of Git branches. Key differences:
- Bookmarks are just labels pointing to commits
- They don't automatically advance when you commit
- You can have commits without any bookmark
jj bookmark list # List all bookmarks jj bookmark set foo -r @ # Create/move bookmark to current commit jj bookmark delete foo # Delete a bookmark
Pushing to GitHub
First Push (New PR)
Use
--change (-c) to auto-create a bookmark and push:
# Push current commit, auto-generates bookmark name jj git push -c @ # Push parent of working copy (common when @ is empty) jj git push -c @-
This creates a bookmark with an auto-generated name like
push-abcdefgh and pushes it.
Subsequent Pushes (Same PR)
Once a bookmark exists and tracks the remote, just push:
jj git push
jj knows which bookmarks have changed and pushes them. Force push is automatic when history rewrites.
Creating the PR
# Get the bookmark name from jj log or the push output jj log -r @ --no-graph # Create PR with gh CLI (must specify --head since gh uses git) gh pr create --head push-abcdefgh
Making More Changes to a PR
When you need to update an existing PR:
# 1. Create new commit on top of the PR commit jj new # 2. Make your changes # 3. Fold changes back into the PR commit jj squash # Moves all changes to parent # 4. Push the update jj git push
Never work directly in the PR commit. Always
jj new first, then squash back.
Squash vs Absorb
jj squash
- When You Know Where It Goes
jj squashMoves changes from current commit into parent:
jj squash # All changes to parent jj squash file.rs # Only specific files jj squash --into <commit> # Into a specific commit (not just parent)
jj absorb
- Auto-Distribute to Ancestors
jj absorbAnalyzes each changed line and moves it to the ancestor commit that last modified that line:
jj absorb
Best for stacked PRs: When you have commits A → B → C and fix things that belong in different commits,
jj absorb figures out where each change should go.
Try
jj absorb first. If it can't figure out where something goes (new lines, ambiguous context), use jj squash manually.
Stacked PRs Workflow
When working on multiple dependent PRs:
# Create stack: each commit = one PR jj new main # work on feature A jj describe -m "Feature A" jj git push -c @ jj new # work on feature B (depends on A) jj describe -m "Feature B" jj git push -c @ # Continue stacking...
After a PR Merges
When a PR in the middle of the stack merges:
# 1. Update the next PR's base branch on GitHub gh pr edit <PR_NUMBER> --base main # 2. Fetch the new main jj git fetch # 3. Rebase the remaining stack onto main jj rebase -r <first-remaining-commit> -d main # 4. Push all updated branches jj git push --all
The
* mark in jj log indicates bookmarks that need pushing.
Editing Earlier Commits
Need to modify a commit that has descendants?
# Switch working copy to that commit jj edit <commit-id> # Make changes (they go directly into that commit) # Return to where you were jj edit @ # Descendants are automatically rebased jj git push --all
Splitting Commits
When a commit has changes that should be separate:
# Interactive split jj split # Non-interactive: specify files for first commit JJ_EDITOR=true jj split -m "First commit message" file1.rs file2.rs # Remaining files stay in second commit jj describe -m "Second commit message"
Undo
Made a mistake? jj tracks all operations:
jj undo # Undo last operation jj op log # View operation history jj op restore <id> # Restore to specific operation
Common Mistakes
Working in a Described Commit
Wrong:
jj describe -m "Feature A" # ... keep making changes in same commit ... # Now Feature A commit has unrelated stuff mixed in
Right:
jj describe -m "Feature A" jj new # Start fresh for next work # ... make changes ... jj squash # If changes belong in Feature A
Forgetting --allow-new
on First Push
--allow-new# First time pushing a bookmark to a new remote jj git push --bookmark main --allow-new
After the bookmark exists on the remote, you don't need this flag.
Pushing a New Commit as a New PR
Wrong:
jj new # ... changes for existing PR ... jj git push -c @ # Creates NEW bookmark/PR!
Right:
jj new # ... changes for existing PR ... jj squash # Fold into existing PR commit jj git push # Updates existing bookmark