返回 Skill 列表
extension
分类: 开发与工程无需 API Key

jj-version-control

当用户使用Jujutsu (jj)版本控制系统时,请求“用jj提交”、“在jj中变基”、“使用修订集”、“创建书签”、“用jj推送到git”、“拆分提交”、“压缩更改”、“编辑历史记录”,或提到jj特有的概念如更改、修订集、书签、操作日志或匿名分支时,应使用此技能。

person作者: jakexiaohubgithub

Jujutsu (jj) Version Control

Jujutsu (jj) is a Git-compatible distributed version control system with a fundamentally better mental model. It treats the working copy as a commit, distinguishes changes from revisions, and provides first-class conflict handling.

Core Mental Model

Key Paradigm Shifts from Git

| Git Concept | jj Concept | Implication | |-------------|------------|-------------| | Staging area/index | None - working copy IS a commit | No jj add needed; use jj split for selective commits | | Detached HEAD | Anonymous branches (default) | Work freely; create bookmarks only when sharing | | Branches auto-advance | Bookmarks are static pointers | Must jj bookmark set before jj git push | | Conflicts block work | Conflicts are first-class objects | Commit through conflicts, resolve later | | Commit hashes only | Change IDs + commit hashes | Stable identifiers even as commits evolve |

The @ Symbol

@ always refers to the current working copy commit. Most commands operate on @ by default.

Essential Commands

Daily Workflow

# View status and log
jj status                    # Current state (alias: jj st)
jj log                       # Commit graph with smart defaults
jj diff                      # Changes in current working copy
jj diff -r <revset>          # Changes in specific revision

# Working with changes
jj describe -m "message"     # Set/update commit message (any revision with -r)
jj new                       # Create new empty change (signals "done with this")
jj commit -m "message"       # Shorthand: describe + new
jj edit <id>                 # Move working copy to different change

History Manipulation

# Squash and move changes
jj squash                    # Move current changes into parent
jj squash -i                 # Interactive: select what to squash
jj move --from <id1> --to <id2>  # Move changes between any commits

# Split commits
jj split                     # Break current commit into multiple (interactive)
jj split -r <id>             # Split specific commit

# Rebase (always succeeds - conflicts become objects)
jj rebase -s <source> -d <dest>  # Rebase commit and descendants
jj rebase -b @ -d main          # Rebase current branch onto main

# Insert commits anywhere
jj new -A <id>               # Insert after (--insert-after)
jj new -B <id>               # Insert before (--insert-before)

# Remove commits
jj abandon                   # Discard commit, rebase children onto parent

Git Interoperability

# Setup (in existing Git repo)
jj git init --colocate       # Creates .jj alongside .git; both work

# Remote operations
jj git fetch                 # Fetch from remotes
jj git push                  # Push tracked bookmarks
jj git push --allow-new      # Push newly created bookmarks

# IMPORTANT: No jj git pull - explicitly fetch then rebase
jj git fetch && jj rebase -b @ -d main

Bookmark Management (Required for Pushing)

jj bookmark create <name>    # Create bookmark at @ (or -r <id>)
jj bookmark set <name>       # Move existing bookmark to @
jj bookmark list             # Show all bookmarks
jj bookmark track <name>@<remote>  # Start tracking remote bookmark
jj bookmark delete <name>    # Delete locally and on push

Critical: Bookmarks don't auto-advance. Before pushing:

jj bookmark set feature-x    # Move bookmark to current @
jj git push                  # Push the bookmark

Undo and Recovery

jj op log                    # All operations (more comprehensive than git reflog)
jj undo                      # Undo last operation
jj op restore --operation <id>  # Restore to any previous state
jj evolog                    # Evolution of current change over time

Revset Quick Reference

Revsets are a functional language for selecting commits.

Basic Operators

| Operator | Meaning | Example | |----------|---------|---------| | @ | Working copy | jj log -r @ | | @- | Parent of @ | jj diff -r @- | | @-- | Grandparent | jj log -r @-- | | ::x | Ancestors of x | jj log -r '::@' | | x:: | Descendants of x | jj log -r 'main::' | | x..y | Range (y not reachable from x) | jj log -r 'main..@' | | \| | Union | jj log -r 'a \| b' | | & | Intersection | jj log -r 'mine() & main..' | | ~ | Difference | jj log -r 'all() ~ trunk()' |

Key Functions

| Function | Returns | |----------|---------| | trunk() | Main branch (auto-detects main/master) | | bookmarks() | All bookmarked commits | | remote_bookmarks() | Remote bookmarks | | mine() | Commits by current user | | heads(revset) | Commits with no children | | roots(revset) | Commits with no parents in set | | ancestors(revset) | All ancestors | | descendants(revset) | All descendants |

Practical Revset Examples

# Work not yet pushed
jj log -r 'bookmarks() & ~remote_bookmarks()'

# My commits since branching from main
jj log -r 'mine() & main..@'

# Rebase all local branches onto updated main
jj rebase -s 'all:roots(trunk..@)' -d trunk

# Commits with conflicts
jj log -r 'conflict()'

# Empty commits (cleanup candidates)
jj log -r 'empty() & mine()'

Common Workflows

Starting New Feature

jj new -r main -m "feat: add feature X"   # Branch from main with message
# ... make changes ...
jj new                                     # Done with this, start next

Iterative Development (Squash Workflow)

# Work in @, make small changes
jj describe -m "WIP"
# ... edit code ...
jj squash                    # Move changes to parent
# Repeat until done
jj describe -m "feat: final message"

Rebasing onto Updated Main

jj git fetch
jj rebase -b @ -d main       # Rebase current branch onto main
# If conflicts, resolve with jj resolve or edit directly
jj bookmark set feature-x
jj git push

Creating Pull Requests

# Ensure bookmark exists and is current
jj bookmark create pr-feature -r @  # Or: jj bookmark set pr-feature
jj git push --allow-new             # --allow-new for new bookmarks
# Create PR via gh or web interface

Working with Conflicts

# Conflicts are committed, not blocking
jj rebase -s @ -d main       # May create conflicts
jj log                       # Shows conflict markers in graph
# Continue working if needed
jj resolve                   # Interactive resolution when ready
# Or edit conflict markers directly and jj describe

Configuration Tips

Essential Config (~/.jjconfig.toml)

[user]
name = "Your Name"
email = "your@email.com"

[ui]
default-command = "log"
diff-editor = ":builtin"    # Built-in TUI for split/squash -i

[revset-aliases]
'wip' = 'mine() & mutable() & ~empty()'
'stack' = 'trunk()..@'

Useful Aliases

[aliases]
# Move nearest ancestor bookmark to current commit
tug = ['bookmark', 'move', '--from', 'heads(::@- & bookmarks())', '--to', '@']

Common Pitfalls

Bookmark not advancing: Unlike Git branches, jj bookmarks don't auto-advance.

# Wrong assumption: bookmark follows after jj new
jj new
jj git push  # ERROR: bookmark still at old commit

# Correct: explicitly set before push
jj bookmark set <name>
jj git push

Force push is normal: jj rewrites history freely. Expect force pushes.

No jj git pull: Intentional design. Always:

jj git fetch
jj rebase -b @ -d main

Progressive Context

  • For advanced revsets and patterns: see references/revsets.md
  • For stacked PR workflows: see references/stacked-prs.md
  • For common workflow examples: see examples/workflows.md