Skip to content

Instantly share code, notes, and snippets.

@jennings
Created July 16, 2026 16:32
Show Gist options
  • Select an option

  • Save jennings/18fabf0595340ddcd9e386576c100dfb to your computer and use it in GitHub Desktop.

Select an option

Save jennings/18fabf0595340ddcd9e386576c100dfb to your computer and use it in GitHub Desktop.
---
name: jujutsu
description: Use Jujutsu (jj) version control instead of git when working in a Jujutsu repository
match:
- directory_exists: .jj
---
# Jujutsu (jj) Version Control
This repository uses Jujutsu (jj), a modern version control system. **Use `jj`
commands instead of `git` commands.** Never run raw `git` commands in a jj
repository unless explicitly asked.
## Core Concepts
- **The working copy IS a commit.** There is no staging area / index. Every
change you make to files is automatically part of the working-copy commit (`@`).
- **Change IDs vs Commit IDs.** Each change has a stable *change ID* that
persists across rebases/amends. Commit IDs (SHA hashes) change when content
changes. Prefer change IDs when referring to revisions.
- **No current branch.** You are always in "detached HEAD" mode. Bookmarks
(jj's name for branches) are optional labels you move manually.
- **Automatic rebasing.** When you rewrite a commit, all descendants are
automatically rebased on top. Bookmarks and the working copy update
automatically.
- **Conflicts are committable.** Merge conflicts are recorded in commits
without failing. You can resolve them later with `jj resolve` or by editing
the files and letting the working copy auto-snapshot.
- **Operation log.** Every repository mutation is recorded. Use `jj undo` or
`jj op restore` to recover from mistakes.
- **Immutable commits.** Commits on `main`/`trunk` and their ancestors are
typically immutable. Don't try to rewrite them.
- **Virtual root commit.** All commits descend from a single root (all-zero
hash). There is no "unborn branch" state.
## Revsets
Jujutsu uses a revset language to select commits:
| Symbol | Meaning |
|--------|---------|
| `@` | Working-copy commit |
| `@-` | Parent of working copy |
| `@--` | Grandparent of working copy |
| `X-` | Parent of X |
| `X..Y` | DAG range from X to Y (exclusive of X) |
| `::X` | Ancestors of X (inclusive) |
| `X::` | Descendants of X (inclusive) |
| `X \| Y` | Union |
| `X & Y` | Intersection |
| `trunk()` | The trunk/main bookmark |
| `mine()` | Commits authored by you |
| `bookmarks()` | All bookmarked commits |
| `all()` | All visible commits |
| `empty()` | Empty changes |
| `description(text)` | Commits whose description contains text |
| `diff_contains(text)` | Commits whose diff contains text |
## Command Reference — Git to jj
### Repository Setup
| Task | Command |
|------|---------|
| Init new repo | `jj git init` |
| Clone | `jj git clone <url> <dest>` |
### Daily Workflow
| Task | Command |
|------|---------|
| Status | `jj status` |
| Log (default: your work) | `jj log` |
| Log (all commits) | `jj log -r 'all()'` |
| Log (ancestors of @) | `jj log -r ::@` |
| Diff (working copy) | `jj diff` |
| Diff (specific revision) | `jj diff -r <rev>` |
| Diff (between two revs) | `jj diff --from A --to B` |
| Diff (summary/stat) | `jj diff --stat` or `jj diff -s` |
| Show a revision | `jj show <rev>` |
| Blame/annotate | `jj file annotate <path>` |
### Creating and Describing Commits
| Task | Command |
|------|---------|
| Describe working copy | `jj describe -m "message"` |
| Finalize commit & start new | `jj commit -m "message"` |
| Edit description of any commit | `jj describe <rev> -m "message"` |
| Create new empty change | `jj new` |
| Create change on top of X | `jj new X` |
| Create merge commit | `jj new A B` |
### Editing History
| Task | Command |
|------|---------|
| Amend into parent | `jj squash` (moves working copy changes into parent) |
| Squash into specific commit | `jj squash --into <rev>` |
| Interactive squash | `jj squash -i` |
| Split a commit | `jj split` (interactive) or `jj split -r <rev>` |
| Edit a historical commit | `jj edit <rev>` (sets it as working copy) |
| Edit commit contents | `jj diffedit -r <rev>` |
| Abandon/discard a commit | `jj abandon <rev>` |
| Restore working copy | `jj restore` (resets to parent, like `git checkout -- .`) |
| Restore specific paths | `jj restore <paths>...` |
| Restore from a revision | `jj restore --from <rev> <paths>...` |
| Rebase onto new parent | `jj rebase -s <rev> -d <destination>` |
| Rebase branch | `jj rebase -b <rev> -d <destination>` |
| Rebase single commit | `jj rebase -r <rev> -d <destination>` |
| Cherry-pick / duplicate | `jj duplicate <rev>` |
| Revert a commit | `jj revert -r <rev>` |
| Move to child commit | `jj next` |
| Move to parent commit | `jj prev` |
| Absorb changes into stack | `jj absorb` |
| Rearrange commits interactively | `jj arrange` |
| Undo last operation | `jj undo` |
### Bookmarks (Branches)
| Task | Command |
|------|---------|
| List bookmarks | `jj bookmark list` |
| Create bookmark | `jj bookmark create <name> -r <rev>` |
| Move bookmark | `jj bookmark move <name> --to <rev>` |
| Delete bookmark | `jj bookmark delete <name>` |
| Rename bookmark | `jj bookmark rename <old> <new>` |
### Tags
| Task | Command |
|------|---------|
| List tags | `jj tag list` |
| Create/set tag | `jj tag set <name> -r <rev>` |
| Delete tag | `jj tag delete <name>` |
### Remote Operations
| Task | Command |
|------|---------|
| Fetch | `jj git fetch` |
| Push all bookmarks | `jj git push --all` |
| Push specific bookmark | `jj git push --bookmark <name>` |
| Push current change | `jj git push -r @` (pushes bookmark pointing at @) |
| Add remote | `jj git remote add <name> <url>` |
| List remotes | `jj git remote list` |
### File Operations
| Task | Command |
|------|---------|
| List tracked files | `jj file list` |
| Untrack file | `jj file untrack <path>` (must be in .gitignore first) |
| Search files by pattern | `jj file search <glob>` |
| Show file at revision | `jj file show <path> -r <rev>` |
### Conflict Resolution
| Task | Command |
|------|---------|
| Resolve conflicts (merge tool) | `jj resolve <path>` |
| Resolve by editing | Edit the file directly, conflicts are marked inline |
### Operations / Recovery
| Task | Command |
|------|---------|
| View operation log | `jj op log` |
| Undo last operation | `jj undo` |
| Restore to operation | `jj op restore <op-id>` |
### Workspace
| Task | Command |
|------|---------|
| Show repo root | `jj root` |
## Common Workflows
### Start new work
```sh
jj new main # create a new change on top of main
# ... make changes ...
jj describe -m "Add feature X"
jj bookmark create feature-x # optional: label it
jj git push --bookmark feature-x
```
### Stack of changes
```sh
jj new main
# ... work on first change ...
jj describe -m "Step 1"
jj new # start next change on top
# ... work on second change ...
jj describe -m "Step 2"
jj new
# ... and so on ...
```
### Amend the current change
Just edit files. The working copy commit updates automatically. To update the
description: `jj describe -m "new message"`.
### Amend a parent or earlier commit
```sh
jj new <rev> # create a new empty commit on top of the one to amend
# ... make changes ...
jj squash # squash into the parent
```
Or use `jj squash --into <rev>` to push current changes into an earlier commit
without switching.
### Stash equivalent
```sh
jj new @- # create a new change as sibling, old work preserved
# ... do other work ...
jj edit <change-id> # go back to the stashed change
```
### Resolve merge conflicts
```sh
jj new main feature # create merge
# if conflicts exist, they are recorded in the commit
jj resolve # launch merge tool
# or edit files directly — jj auto-snapshots
```
### Create a pull request
```sh
jj git fetch
jj new main
# ... make changes ...
jj describe -m "My PR description"
jj bookmark create my-branch
jj git push --bookmark my-branch
# then use gh pr create as normal
```
## Committing Changes (for Claude)
When asked to commit in a jj repo, use this workflow instead of git:
1. Run `jj status` to see current state.
2. Run `jj diff` to review changes.
3. Run `jj log -r @` to see the current working-copy commit.
4. Describe the working copy: `jj describe -m "commit message"`.
5. Then `jj new` to start a fresh change (equivalent to finalizing the commit).
6. Optionally create/move a bookmark and push.
**Do NOT use `git add`, `git commit`, or any git commands.**
## Creating Pull Requests (for Claude)
1. `jj git fetch` to get latest remote state.
2. `jj log` to understand the change stack.
3. Ensure the change(s) to push have a bookmark: `jj bookmark create <name> -r <rev>`.
4. `jj git push --bookmark <name>`.
5. Use `gh pr create` as normal for the GitHub side.
## Important Behavioral Notes
- **No need to `git add`.** Editing/creating/deleting files is automatically
tracked.
- **`jj commit` = describe + new.** It finalizes the current change and starts
a fresh one. You can also just use `jj describe` + `jj new` separately.
- **`jj squash` without args** moves all changes from `@` into `@-` (like
`git commit --amend`).
- **Empty commits are normal.** The working copy often starts as an empty
change. This is expected.
- **Use `jj undo`** if something goes wrong. It reverts the last jj operation.
- **Bookmarks don't auto-advance.** After `jj new`, bookmarks stay on the old
commit. Move them with `jj bookmark move`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment