# Jujutsu Version Control System ## Introduction Jujutsu (command-line tool `jj`) is a next-generation version control system designed to be powerful, easy to use, and compatible with Git repositories. Unlike traditional VCS tools, Jujutsu treats the working copy as an actual commit that is automatically snapshotted and amended with each operation, eliminating the need for Git's staging area or stash. The system uses an innovative "working-copy-as-a-commit" model where every change is immediately recorded, providing a unified interface for working with both current changes and historical commits. Built on a flexible backend architecture, Jujutsu currently uses Git repositories for storage (via the gitoxide library), ensuring full compatibility with existing Git workflows while adding features like stable change IDs, automatic rebasing of descendants, and first-class conflict support. Jujutsu records every operation performed on the repository in an operation log, enabling complete undo/redo functionality and making it easy to recover from mistakes or understand how the repository reached its current state. The system supports conflict tracking as first-class objects that can be committed and propagated through history, with automatic conflict resolution propagation when descendants are rebased. Change IDs can reference specific versions using a numeric suffix (e.g., `xyz/0` for the latest version, `xyz/1` for the previous version), making it easy to work with hidden commits and disambiguate divergent changes. Inspired by Git's performance focus, Mercurial's revset language and anonymous branching, and Darcs's conflict handling, Jujutsu combines proven concepts into a modern tool that simplifies common workflows like patch-based development, interactive history rewriting, and collaborative work on shared repositories. The architecture is designed for safe concurrent replication, allowing repositories to be stored in distributed file systems or backed up while in use without risk of corruption. ## APIs and Key Functions ### Initialize and Clone Git Repository Initialize a new Jujutsu workspace backed by a Git repository or clone an existing Git repository for use with Jujutsu. ```bash # Initialize a new Git-backed repository jj git init myproject cd myproject # Clone an existing Git repository jj git clone https://github.com/octocat/Hello-World cd Hello-World # Check status - working copy is automatically a commit jj status # Output: # The working copy has no changes. # Working copy (@) : kntqzsqt d7439b06 (empty) (no description set) # Parent commit (@-): orrkosyo 7fd1a60b master | (empty) Merge pull request # Initialize with colocated Git repo (can use both jj and git commands) jj git init --colocate myrepo cd myrepo git status # Works alongside jj commands jj log # Also works ``` ### Create and Modify Commits Work with commits using Jujutsu's automatic working-copy commit model, where changes are tracked without manual staging. ```bash # Describe the current working-copy commit (set commit message) jj describe -m "Add user authentication" # Make changes to files (automatically tracked) echo "fn authenticate() {}" >> src/auth.rs jj status # Output: # Working copy changes: # M src/auth.rs # Working copy (@) : pqvwxyz 5a3b2c1d Add user authentication # View diff of working-copy changes jj diff # Output shows color-words diff by default # View git-style diff jj diff --git # Output: # diff --git a/src/auth.rs b/src/auth.rs # index 980a0d5..1ce3f81 100644 # --- a/src/auth.rs # +++ b/src/auth.rs # @@ -1,0 +1,1 @@ # +fn authenticate() {} # Create new commit on top of current (finalize current work) jj new # Working copy (@) now at: abcdefg 9h8i7j6k (empty) (no description set) # Parent commit (@-): pqvwxyz 5a3b2c1d Add user authentication # Create new commit on specific revision jj new main jj new -r "trunk()" # Edit a specific commit (not just working copy) jj edit # Changes working copy to that commit for modification ``` ### Query Commit History with Revsets Use Jujutsu's powerful revset language to query and filter commits with algebraic expressions. ```bash # View commit log jj log # Shows graph of commits with change IDs and commit IDs # View specific revisions using revset expressions jj log -r "main" jj log -r "@" # Current working copy jj log -r "@-" # Parent of working copy jj log -r "trunk()" # Main branch (auto-configured) # Reference specific versions of hidden or divergent changes jj log -r "xyz/0" # Latest version of change ID xyz jj log -r "xyz/1" # Previous version of change ID xyz jj log -r "xyz/2" # Even earlier version # Restore a commit to its previous state jj restore --from xyz/1 --to xyz # Query by author jj log -r 'author("alice@example.com")' # Query by date range jj log -r 'author_date(after:"2024-01-01")' jj log -r 'committer_date(before:"2024-12-31")' # Descendants and ancestors jj log -r '@::' # All descendants of working copy jj log -r '::@' # All ancestors of working copy jj log -r 'main::@' # Commits between main and working copy # Commits reachable from multiple revisions jj log -r 'main | feature-branch' # Mutable vs immutable commits jj log -r 'mutable()' jj log -r 'immutable()' # Complex queries with boolean logic jj log -r 'author("alice") & committer_date(after:"2024-01")' jj log -r 'description(glob:"fix*") ~ author("bot")' # Matches "fix*" but not by bot # Empty commits jj log -r 'empty()' # Show detailed commit information jj show jj show -r "main" ``` ### Rewrite History with Automatic Rebasing Modify commits and let Jujutsu automatically rebase descendants, propagating changes through the commit graph. ```bash # Describe (set message) for any commit, not just working copy jj describe -r -m "Updated commit message" # Descendants automatically rebased # Rebase commits explicitly jj rebase -s -d # Rebase source and descendants onto destination jj rebase -b -d # Rebase entire branch onto destination jj rebase -r -d # Rebase single revision (without descendants) # Example: Move feature branch to latest main jj rebase -b feature-branch -d main # Squash changes from working copy into parent jj squash jj squash -m "Combined commit message" # Squash specific revision into another jj squash -r --into # Interactive squash (select which changes to move) jj squash -i # Split current commit into two jj split # Opens interactive editor to select which changes go in first commit # Duplicate a commit (create copy) jj duplicate # Abandon commits (remove from history, keep descendants) jj abandon # Children are rebased onto parents # Example workflow: Fix earlier commit jj new # Start editing that commit # Make fixes... jj squash -r @ --into # Move fixes into original # All descendants automatically rebased with fixes ``` ### Intelligent Change Integration with Absorb Automatically move working-copy changes into appropriate ancestor commits based on context. ```bash # Basic absorb - automatically distribute changes to relevant commits jj absorb # Analyzes changes and moves each hunk to the commit that last modified those lines # Example scenario: # You have commits A -> B -> C (working copy has fixes for both A and B) echo "fix for A" >> file_a.rs echo "fix for B" >> file_b.rs jj absorb # Changes to file_a.rs absorbed into commit A # Changes to file_b.rs absorbed into commit B # Commit C automatically rebased with both fixes # Absorb with interactive mode jj absorb -i # Review each change before absorbing # Absorb into specific revision range jj absorb -r "main..@" # Only consider commits between main and working copy ``` ### Manage Bookmarks (Branches) Create, track, and manipulate bookmarks which are Jujutsu's equivalent to Git branches. ```bash # List all bookmarks jj bookmark list # Output: # feature-auth: kqvwxyz 5a3b2c1d Add authentication # main: orrkosyo 7fd1a60b Initial commit # Create bookmark at current commit jj bookmark create feature-auth # Create bookmark at specific revision jj bookmark create feature-ui -r # Move bookmark to different commit jj bookmark set main -r # Delete bookmark jj bookmark delete old-feature # Track remote bookmark (new syntax with --remote) jj bookmark track main --remote origin # Creates local "main" bookmark tracking "main@origin" # Track bookmark from all remotes jj bookmark track feature-branch # Tracks feature-branch from all remotes that have it # Untrack remote bookmark jj bookmark untrack main --remote origin # Untrack from all remotes jj bookmark untrack feature-branch # List remote bookmarks jj bookmark list --all-remotes # Rename bookmark jj bookmark rename old-name new-name ``` ### Manage Tags Create and list Git-compatible tags for marking specific commits. ```bash # List all tags jj tag list # Output: # v1.0.0: abc123 "Release version 1.0.0" # v1.1.0: def456 "Release version 1.1.0" # List tags sorted by name, creation date, or other criteria jj tag list --sort name jj tag list --sort committer-date # Create a tag at current commit jj tag create v2.0.0 # Create a tag at specific revision jj tag create v2.1.0 -r # Create annotated tag with message jj tag create v2.2.0 -m "Major release with new features" # Example: Tag a release jj bookmark create release-candidate jj tag create v1.5.0 -r release-candidate -m "Version 1.5.0 release" ``` ### Sync with Git Remotes Fetch from and push to Git remotes while maintaining Jujutsu's enhanced features. ```bash # Add a Git remote jj git remote add origin https://github.com/user/repo.git # List remotes jj git remote list # Fetch from all remotes jj git fetch # Updates remote-tracking bookmarks like main@origin # Fetch from specific remote jj git fetch --remote origin # Push current bookmark jj git push # Pushes bookmark tracking current commit # Push specific bookmark jj git push --bookmark feature-auth # Push all bookmarks jj git push --all # Push with creation of remote bookmark jj git push --bookmark new-feature --create # Example workflow: Sync with upstream jj git fetch jj rebase -b my-feature -d main@origin jj git push --bookmark my-feature # Export changes to colocated Git repo jj git export # Updates Git refs to match Jujutsu bookmarks # Import changes from colocated Git repo jj git import # Updates Jujutsu from Git refs ``` ### Resolve Conflicts Work with conflicts as first-class objects that can be committed and propagated through history. ```bash # Create a merge commit (may have conflicts) jj new main feature-branch # Working copy (@) now at: merged-commit (conflict) # View conflict status jj status # Output: # Working copy changes: # C src/auth.rs # Working copy (@) : abcd1234 5a3b2c1d (conflict) Merge branches # View files with conflicts jj diff # Shows conflict markers in files with origin information # Example conflict marker: # <<<<<<< nlqwxzwn 7dd24e73 "Add authentication" # def authenticate(): # return True # ======= # def login(): # return False # >>>>>>> kpqrstuv 8ee35f84 "Add login function" # Resolve conflicts interactively jj resolve # Opens merge tool for each conflicted file # Resolve specific file jj resolve src/auth.rs # Mark conflict as resolved after manual editing # Edit file manually, then: jj new # Conflict resolved, descendants can be rebased # List conflicts in repository jj log -r 'conflict()' # Restore file from specific revision jj restore --from main src/auth.rs jj restore --to --from file.rs # Example: Conflict resolution propagation # Commit A has conflict between B and C jj new A jj resolve # Fix conflict jj squash --into A # Descendants of A automatically rebased with resolution applied ``` ### Undo Operations with Operation Log Leverage the operation log to undo mistakes or restore previous repository states. ```bash # View operation log jj operation log # Output: # @ 2024-01-15 10:30:15.000 -08:00 - 2024-01-15 10:30:15.000 -08:00 # │ rebase -b feature -d main # │ args: jj rebase -b feature -d main # ○ 2024-01-15 10:15:42.000 -08:00 - 2024-01-15 10:15:42.000 -08:00 # │ describe -m "Add feature" # Undo last operation jj undo # Reverts the last operation, restoring previous state # Undo multiple operations jj undo --operation jj op undo --op # Restore to specific operation jj operation restore jj op restore # Example: Recover from bad rebase jj rebase -b feature -d wrong-branch # Oops, wrong destination jj operation log # Find operation before rebase jj undo # Restore to before rebase # Abandon old operations (cleanup) jj operation abandon .. # Removes operations from log (doesn't undo them) ``` ### Configure Jujutsu Customize behavior through hierarchical configuration files with user, repo, and workspace scopes. ```toml # ~/.config/jj/config.toml (User configuration) # User identity [user] name = "Alice Developer" email = "alice@example.com" # UI preferences [ui] color = "auto" # auto, always, never, debug diff.format = "color-words" # git, color-words pager = "less -FRX" paginate = "auto" graph.style = "curved" # curved, square, ascii editor = "vim" diff-editor = "vimdiff" merge-editor = "meld" # Git settings [git] auto-local-bookmark = true push-bookmark-prefix = "push-" fetch = ["origin"] # Signing [signing] sign-all = false backend = "gpg" # gpg, ssh key = "alice@example.com" # Custom revset aliases [revset-aliases] 'my-commits' = 'author("alice@example.com")' 'recent' = 'author_date(after:"7 days ago")' 'wip' = 'description(glob:"wip*")' # Template aliases [template-aliases] 'my_log' = 'commit_id.short() ++ " " ++ description.first_line()' 'format_timestamp(time)' = 'time.format("%Y-%m-%d %H:%M")' # Command aliases [aliases] st = ["status"] l = ["log"] lg = ["log", "--graph"] co = ["checkout"] ``` ```bash # Edit user config jj config edit --user # Edit repo config jj config edit --repo # Edit workspace config (workspace-specific settings) jj config edit --workspace # View effective config jj config list # Get specific config value jj config get user.name # Set config value from command line jj config set --user user.name "Alice Developer" # Repo-specific config (.jj/repo/config.toml) # Example: Different merge tool per project [ui] merge-editor = "code --wait --merge" ``` ### Customize Log Output with Templates Format command output using Jujutsu's template language for customized display. ```bash # Basic template usage jj log -T 'commit_id.short() ++ " " ++ description.first_line()' # Output: abc123 Add user authentication # Template with multiple fields jj log -T ' commit_id.short() ++ " " ++ author.name() ++ " <" ++ author.email() ++ "> " ++ author_date.format("%Y-%m-%d") ++ "\n" ++ description.first_line() ' # Conditional formatting jj log -T ' if(conflict, label("conflict", "CONFLICT "), "") ++ if(empty, label("empty", "EMPTY "), "") ++ commit_id.short() ++ " " ++ description.first_line() ' # Color labels jj log -T ' label("commit_id", commit_id.short()) ++ " " ++ label("description", description.first_line()) ' # Configure default templates in config file # ~/.config/jj/config.toml [templates] log = ''' label("commit_id", commit_id.short()) ++ " " ++ label("author", author.name()) ++ " " ++ label("timestamp", author_date.format("%Y-%m-%d")) ++ "\n" ++ description.first_line() ''' # Use template alias [template-aliases] 'format_commit' = ''' commit_id.short() ++ " " ++ if(conflict, "[CONFLICT] ", "") ++ description.first_line() ''' # Then use it: jj log -T 'format_commit' ``` ### Create Custom Commands Extend Jujutsu by creating custom commands with full access to the repository API. ```rust // custom-command-example/main.rs use jj_cli::cli_util::{CliRunner, CommandHelper, RevisionArg}; use jj_cli::command_error::CommandError; use jj_cli::ui::Ui; use std::io::Write; #[derive(clap::Parser, Clone, Debug)] enum CustomCommand { /// Analyze commits for specific patterns Analyze(AnalyzeArgs), } #[derive(clap::Args, Clone, Debug)] struct AnalyzeArgs { /// The revision to analyze #[arg(default_value = "@")] revision: RevisionArg, /// Pattern to search for #[arg(long)] pattern: String, } fn run_custom_command( ui: &mut Ui, command_helper: &CommandHelper, command: CustomCommand, ) -> Result<(), CommandError> { match command { CustomCommand::Analyze(args) => { let mut workspace_command = command_helper.workspace_helper(ui)?; let commit = workspace_command.resolve_single_rev(ui, &args.revision)?; // Access commit data let description = commit.description(); let author = commit.author(); // Analyze and report if description.contains(&args.pattern) { writeln!( ui.status(), "Found pattern '{}' in commit by {}", args.pattern, author.name )?; } Ok(()) } } } fn main() -> std::process::ExitCode { CliRunner::init() .add_subcommand(run_custom_command) .run() .into() } // Build and use: // cargo build --release // ./target/release/custom-command analyze --pattern "fix" -r main ``` ### Implement Custom Backend Create custom storage backends for specialized repository requirements. ```rust // custom-backend-example/main.rs use async_trait::async_trait; use jj_cli::cli_util::CliRunner; use jj_lib::backend::{Backend, BackendResult, Commit, CommitId, Tree, TreeId}; use jj_lib::git_backend::GitBackend; use jj_lib::repo::StoreFactories; use jj_lib::repo_path::RepoPath; use jj_lib::settings::UserSettings; use jj_lib::workspace::Workspace; use std::path::Path; #[derive(Debug)] struct CustomBackend { inner: GitBackend, } impl CustomBackend { fn init(settings: &UserSettings, store_path: &Path) -> Result> { let inner = GitBackend::init_internal(settings, store_path)?; Ok(Self { inner }) } fn load(settings: &UserSettings, store_path: &Path) -> Result> { let inner = GitBackend::load(settings, store_path)?; Ok(Self { inner }) } } #[async_trait] impl Backend for CustomBackend { fn name(&self) -> &str { "custom" } fn commit_id_length(&self) -> usize { self.inner.commit_id_length() } fn change_id_length(&self) -> usize { self.inner.change_id_length() } async fn read_commit(&self, id: &CommitId) -> BackendResult { // Add custom logic here (e.g., logging, caching, encryption) self.inner.read_commit(id).await } async fn write_commit( &self, contents: Commit, sign_with: Option<&mut jj_lib::backend::SigningFn>, ) -> BackendResult<(CommitId, Commit)> { // Add custom logic here self.inner.write_commit(contents, sign_with).await } async fn read_tree(&self, path: &RepoPath, id: &TreeId) -> BackendResult { self.inner.read_tree(path, id).await } async fn write_tree(&self, path: &RepoPath, contents: &Tree) -> BackendResult { self.inner.write_tree(path, contents).await } // Implement remaining Backend trait methods... } fn create_store_factories() -> StoreFactories { let mut factories = StoreFactories::empty(); factories.add_backend( "custom", Box::new(|settings, store_path| { Ok(Box::new(CustomBackend::load(settings, store_path)?)) }), ); factories } fn main() -> std::process::ExitCode { CliRunner::init() .add_store_factories(create_store_factories()) .run() .into() } // Initialize workspace with custom backend: // Workspace::init_with_backend( // &settings, // wc_path, // &|settings, store_path| Ok(Box::new(CustomBackend::init(settings, store_path)?)), // signer, // )?; ``` ### Work with Multiple Workspaces Create and manage multiple workspaces sharing the same repository with independent working copies. ```bash # Add new workspace jj workspace add ../feature-workspace # Creates new workspace directory with separate working copy # Add workspace at specific revision jj workspace add --revision ../bugfix-workspace # List all workspaces jj workspace list # Output: # default: /home/user/project # feature: /home/user/feature-workspace # bugfix: /home/user/bugfix-workspace # Switch between workspaces (just cd to different directory) cd ../feature-workspace jj status # Shows this workspace's working copy # Remove workspace jj workspace forget feature # Removes workspace from repo (doesn't delete directory) # Example: Parallel development jj workspace add ../workspace-review -r main cd ../workspace-review # Review code while keeping main workspace on feature branch # Each workspace has independent working-copy commit # But shares same operation log and repository history ``` ### File Operations Track, untrack, and examine files in the repository with granular control. ```bash # List tracked files jj file list jj file list -r # Show file content jj file show README.md jj file show -r src/main.rs # Track previously untracked file jj file track new-file.txt # Untrack file (keeps in working copy, removes from repo) jj file untrack build/output.txt # Annotate file (git blame equivalent) jj file annotate src/main.rs # Output: # abc123 (Alice 2024-01-01): fn main() { # def456 (Bob 2024-01-15): println!("Hello"); # abc123 (Alice 2024-01-01): } # Annotate with specific revision jj file annotate -r file.rs # Search for content in files (like git grep) jj file search --pattern 'TODO' jj file search -p 'fix' src/ # Search for pattern and show matching lines with file names # Search with glob patterns jj file search --pattern 'glob:*error*' jj file search --pattern 'substring:deprecated' # Search in specific revision jj file search -r main --pattern 'function' # Search example output: # src/main.rs:42: // TODO: implement this function # src/utils.rs:15: // TODO: add error handling # Set or remove executable bit for files jj file chmod x script.sh jj file chmod executable build.sh # Make file non-executable jj file chmod n old-script.sh jj file chmod normal file.txt # Change executable bit in specific revision jj file chmod -r x deploy.sh # Example: Track new files automatically echo "new-feature" > feature.txt jj status # Output: # Working copy changes: # A feature.txt # (Automatically detected as new file) ``` ## Summary Jujutsu represents a fundamental reimagining of version control that addresses many pain points in traditional systems while maintaining compatibility with existing Git infrastructure. The working-copy-as-a-commit model eliminates the mental overhead of staging areas and stashes, making every state of the repository a proper commit that can be queried, modified, and reverted. Change IDs provide stable identifiers that survive history rewrites, making it trivial to track how a logical change evolves through multiple iterations of rebasing and amendment. The automatic rebase feature means that when you modify an ancestor commit, all descendants are immediately updated, and if you resolve a conflict in one commit, that resolution automatically propagates to affected descendants—a workflow that would require careful manual management in Git. The operation log creates a complete audit trail of every action taken on the repository, enabling effortless undo of mistakes and making it easy to help teammates understand and recover from problems in their repositories. The practical implications of Jujutsu's design make it particularly well-suited for modern development workflows involving frequent history rewriting, code review iterations, and complex merges. Teams practicing patch-based development where individual commits are refined through multiple review cycles benefit from the change ID stability and automatic rebasing. Projects with large histories and many contributors gain from the revset query language which can express complex commit selections algebraically. The first-class conflict handling allows teams to commit and share work-in-progress even when conflicts exist, deferring resolution until sufficient context is available. Configuration through TOML files, extensibility via custom commands and backends, and the template language for customizing output make Jujutsu adaptable to diverse workflows. Git compatibility ensures that adopting Jujutsu doesn't require coordinating an entire team—individual developers can use `jj` while colleagues continue with `git`, as both tools operate on the same underlying repository format. Whether working alone on experimental features or collaborating on large-scale projects, Jujutsu's design principles of simplicity, automatic operation, and reliable undo make version control more intuitive and less error-prone.