{"id":44190194,"url":"https://github.com/christianromney/jujutsu-notes","last_synced_at":"2026-02-09T17:03:22.908Z","repository":{"id":322452051,"uuid":"1050611177","full_name":"christianromney/jujutsu-notes","owner":"christianromney","description":"My notes on jujutsu version control","archived":false,"fork":false,"pushed_at":"2025-11-04T14:39:38.000Z","size":773,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-11-04T15:23:51.281Z","etag":null,"topics":["git","github","jujutsu","version-control"],"latest_commit_sha":null,"homepage":"https://christianromney.github.io/jujutsu-notes/","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/christianromney.png","metadata":{"files":{"readme":"readme.org","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-04T17:17:03.000Z","updated_at":"2025-11-04T14:45:51.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/christianromney/jujutsu-notes","commit_stats":null,"previous_names":["christianromney/jujutsu-notes"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/christianromney/jujutsu-notes","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/christianromney%2Fjujutsu-notes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/christianromney%2Fjujutsu-notes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/christianromney%2Fjujutsu-notes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/christianromney%2Fjujutsu-notes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/christianromney","download_url":"https://codeload.github.com/christianromney/jujutsu-notes/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/christianromney%2Fjujutsu-notes/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29273141,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-09T13:47:44.167Z","status":"ssl_error","status_checked_at":"2026-02-09T13:47:43.721Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["git","github","jujutsu","version-control"],"created_at":"2026-02-09T17:03:15.193Z","updated_at":"2026-02-09T17:03:22.879Z","avatar_url":"https://github.com/christianromney.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"#+TITLE: Jujutsu VCS Tutorial\n#+AUTHOR: Christian Romney\n#+DATE: 2025-11-03\n\n* Tutorial Overview\n\nJujutsu (jj) is a modern version control system that layers powerful features on top of Git. Think of it as Git with a better command-line interface and workflow - you keep your existing Git repositories and remotes, but gain features like automatic rebasing, first-class conflicts, and an operation log that makes mistakes reversible.\n\nThis tutorial teaches you jj in the context of GitHub-based development. You'll learn how jj's design makes common Git workflows simpler and safer, particularly around history editing and managing stacks of changes for pull requests.\n\n*Target Audience:* Developers with intermediate to advanced Git experience, working with GitHub\n\n*What You'll Learn:*\n- Core jj concepts and how they differ from Git\n- Daily workflow: creating, editing, and navigating changes\n- Managing bookmarks (branches) and syncing with remotes\n- Stacking changes for better pull request workflows\n- Safe history editing and conflict resolution\n\n* 1. Setup \u0026 Installation\n\nLet's get jj installed and configured. The setup process will feel familiar if you've used Git before.\n\n** A. Installation\n\nOn macOS, install jj via Homebrew:\n\n#+begin_src bash\nbrew install jujutsu\n#+end_src\n\n** B. Essential Configuration\n\nConfigure your identity for commits. Note that jj uses ~--user~ instead of Git's ~--global~:\n\n#+begin_src bash\njj config set --user user.name \"Your Name\"\njj config set --user user.email \"your.email@example.com\"\n#+end_src\n\n** C. Commit Signing Setup\n\nMany organizations require signed commits for security. Jujutsu supports both GPG and SSH signing - choose whichever you already have configured.\n\n*** For GPG:\n\n#+begin_src bash\n# List available GPG keys and fingerprints\ngpg --list-secret-keys --keyid-format=long\n\n# Configure jj to use GPG\njj config set --user signing.backend \"gpg\"\njj config set --user signing.behavior \"own\"\njj config set --user signing.key \"4ED556E9729E000F\"  # Replace this sample with your key fingerprint\n#+end_src\n\n*** For SSH (simpler option):\n\n#+begin_src bash\n# Find your SSH public key\nls ~/.ssh/*.pub\ncat ~/.ssh/id_ed25519.pub  # Or your key name\n\n# Configure jj to use SSH\njj config set --user signing.backend \"ssh\"\njj config set --user signing.behavior \"own\"\njj config set --user signing.key \"~/.ssh/id_ed25519.pub\"\n#+end_src\n\nBoth of these examples use the \"own\" signing behavior which signs commits you create or edit.\n\n** D. Initializing a Co-located Repository\n\nJujutsu works alongside Git in \"co-located\" repositories. This means you can use both ~jj~ and ~git~ commands on the same repo, and they stay synchronized automatically.\n\n#+begin_src bash\njj git init --colocate  # In existing Git repo, or use jj git clone\n#+end_src\n\nAfter running this command, you'll have both ~.jj~ and ~.git~ directories. Git will show \"detached HEAD\" - this is normal and expected with jj. The two systems synchronize on every jj command, so you can use Git tools when needed while primarily working with jj.\n\n* 2. Core Concepts \u0026 Daily Workflow\n\nNow that jj is installed, let's understand its core concepts. These differ from Git in ways that make common operations simpler and safer.\n\n** A. Understanding the Working Copy\n\nThe first mind-shift: in jj, your working copy *is* a commit. There's no staging area - when you edit files, those changes automatically become part of the current commit (represented by ~@~).\n\n#+begin_src bash\n# Make changes to files...\njj status      # Working copy automatically amends!\njj diff        # See what changed in @\n#+end_src\n\nEvery time you run a jj command, it automatically snapshots your working directory and amends the ~@~ commit. This eliminates the need for ~git add~ - your changes are always part of a commit.\n\n** B. Understanding Changes vs Commits\n\nThis is a critical distinction: jj tracks both *change IDs* and *commit IDs*.\n\n- *Change ID*: Identifies a logical piece of work. Stays the same when you edit/amend the commit.\n- *Commit ID*: Identifies a specific snapshot. Changes every time you modify the commit.\n\n*Why both?* Change IDs let you refer to \"that feature I'm working on\" across rewrites. Commit IDs identify exact historical snapshots. This means you can use change IDs in your daily workflow without worrying about them changing when you amend commits.\n\nChanges eliminate the need for branches in the way that Git imagines them.\n\n#+begin_src bash\njj log                       # Shows both IDs for each commit\n# Example output:\n# @  youzwxvz christian.a.romney@gmail.com 2025-11-03 22:12:08 e35b5e0f\n# │  Create jujutsu VCS tutorial outline\n# ◆  pswmtnwq christian.a.romney@gmail.com 2025-09-05 08:04:28 main 7cc5e620\n# │  Reorder content and fix headings\n#\n# First part (youzwxvz) = Change ID (stays stable when you amend)\n# Last part (e35b5e0f) = Commit ID (changes every time you modify)\n#+end_src\n\n** C. Basic Operations \u0026 Workflow\n\nThe key concept: every jj command auto-amends the working copy commit. This means you're always building on your current change until you explicitly create a new one.\n\n#+begin_src bash\n# Viewing state\njj status                    # See what's changed in working copy\njj diff                      # Show changes in detail\njj log                       # See commit graph with both change and commit IDs\n\n# Making changes\n# ... edit files ...\n# Changes automatically amend @ on next jj command\n\n# Finishing a change and starting new work\njj new                       # \"Finish\" current commit, create new empty one on top\njj new -m \"message\"          # Same, but set message for new commit\njj describe -m \"message\"     # Set/update commit message for current change\njj commit -m \"message\"       # Shortcut: describe + new (set message and move forward)\n\n# Navigating between changes\njj edit \u003cchange-id\u003e          # Switch working copy to a different change\njj prev                      # Move to parent commit (creates new @ on parent)\njj next                      # Move to child commit (creates new @ on child)\njj prev --edit               # Edit parent directly (like jj edit @-)\n\n# Viewing history\njj evolog                    # Evolution log - see how a change evolved over time\njj interdiff --from @ --to @-  # Compare changes between two commits\n\n# Modifying commits\njj metaedit                  # Modify metadata (author, timestamps, change-id)\n\n# Safety net\njj undo                      # Undo last operation\njj redo                      # Redo operation (after undo)\n#+end_src\n\n*Think of ~jj new~ as:* \"I'm done with this change, start a new one\"\n\n** D. When to Use: ~jj new~ vs ~jj commit~ vs ~jj describe~\n\nWith multiple commands that seem similar, it helps to know when to use each:\n\n*~jj new~*: Start a new empty commit on top\n- Use when: You want to start fresh work without setting a message yet\n- Result: New empty commit becomes @\n\n*~jj describe~*: Set/update the commit message for @\n- Use when: You want to add/change the message but keep working on @\n- Result: @ gets a message, stays as working copy\n\n*~jj commit -m \"message\"~*: Shortcut for ~jj describe + jj new~\n- Use when: You're done AND have a message ready\n- Result: @ gets the message and new empty commit created on top\n- *Most similar to ~git commit~*\n\n*~jj commit \u003cfiles\u003e~*: Like ~jj split~ but simpler - move specific files to a new commit\n- Use when: You want to commit only certain files\n- Result: Selected files go to @, rest stays in new child commit\n\n*~jj prev~ / ~jj next~*: Navigate linearly through a stack\n- Use when: Working through a stack of commits sequentially\n- Result: Creates new @ on parent/child (by default) or edits directly (with ~--edit~)\n- Shortcut for ~jj new \u003cparent\u003e~ or ~jj new \u003cchild\u003e~\n\n** E. Working with Files\n\nJujutsu provides commands for inspecting and managing files, though most file operations happen naturally through editing.\n\n*Listing and viewing:*\n#+begin_src bash\njj file list                 # List all tracked files in @\njj file show \u003cpath\u003e          # Show contents of file in @\njj file show \u003cpath\u003e -r \u003crev\u003e # Show file contents in specific revision\n#+end_src\n\n*Tracking files (usually automatic):*\n#+begin_src bash\njj file track \u003cpath\u003e         # Explicitly mark file as tracked\njj file untrack \u003cpath\u003e       # Stop tracking file\n# Note: jj auto-tracks new files by default!\n#+end_src\n\n*When to use ~jj file~ commands:*\n- ~jj file list~: See what files are in a commit (like ~git ls-files~)\n- ~jj file show~: View file contents without checking out\n- ~jj file track/untrack~: Rarely needed - use when you want explicit control over tracking\n\n*~jj interdiff~ - Compare Two Versions:*\n\nOne particularly useful command for PR workflows is ~jj interdiff~, which compares what changed between two commits:\n\n#+begin_src bash\njj interdiff --from \u003cold-version\u003e --to \u003cnew-version\u003e\n# Common use: See what changed after addressing PR feedback\njj interdiff --from @-- --to @   # Compare last two commits\n#+end_src\n\n*When to use:*\n- Reviewing what changed between PR iterations\n- Seeing how a change evolved after feedback\n- Note: For same change across history, use ~jj evolog -p~ instead\n\n** F. Practice: Apply What You've Learned\n\nNow it's your turn! Try these exercises to solidify your understanding:\n\n1. Make some changes to files in your repository\n2. Run ~jj status~ and ~jj diff~ to see what changed\n3. Use ~jj describe -m \"Your message\"~ to set a commit message\n4. Run ~jj new~ to start a new change\n5. Make more changes and check ~jj log~ to see both commits\n6. Try using ~jj prev~ to go back to your first change\n7. Experiment with ~jj next~ to move forward again\n\n*Challenge:* Create a change with multiple files, then use ~jj commit \u003cfiles\u003e~ to commit only specific files, leaving the rest in a new commit.\n\n* 3. History Editing \u0026 Rebasing\n\nOne of jj's superpowers is making history editing safe and straightforward. Unlike Git, where history operations can feel risky, jj provides strong guarantees.\n\n** A. How jj Makes This Easy\n\n- All operations logged (~jj op log~)\n- ~jj undo~ reverses last operation\n- Working on your local commits is safe\n\nThis means you can confidently rewrite history knowing you can always undo mistakes.\n\n** B. Common Operations\n\nLet's understand when and why you'd use these history editing commands:\n\n- *~squash~*: Combine multiple commits into one. Use when you have \"fix typo\" or \"address review\" commits that should be part of the original change.\n\n- *~rebase -d \u003cdest\u003e~*: Move your changes to build on top of a different commit (the \"destination\"). \"Rebasing onto main\" means updating your work to start from the latest main branch instead of wherever you originally branched from.\n\n- *~abandon~*: Discard a change you no longer need. Unlike delete, this preserves the descendants by rebasing them onto the abandoned commit's parent.\n\n*Commands:*\n\n#+begin_src bash\njj describe             # Change commit message\njj squash               # Squash @ into parent (combine commits)\njj edit \u003cchange-id\u003e     # Resume editing an old commit\njj rebase -d main       # Rebase onto main (move changes to build on main)\njj abandon              # Discard a change (preserves descendants)\njj fix                  # Run formatters/linters on commits automatically\n#+end_src\n\n*~jj fix~ - Automatic Code Formatting:*\n\nThe ~jj fix~ command applies configured formatters (prettier, black, clang-format, etc.) to commits and updates descendants automatically:\n\n#+begin_src bash\njj fix                  # Fix all mutable commits\njj fix -s @             # Fix only current commit\njj fix -s 'main..@'     # Fix all commits in current stack\n#+end_src\n\nConfigure tools in ~.jj/config.toml~ or global config.\n\n** C. Demo Examples\n\n*Fixing a typo in an earlier commit:*\n\nHere's where jj really shines - you can edit any commit in your history:\n\n#+begin_src bash\njj edit \u003cchange-id\u003e     # Switch to that commit\n# Fix the typo...\n# Descendants automatically rebase on your changes!\njj new                  # Move forward to continue working\n#+end_src\n\n*When to use squash - combining fixup commits:*\n\nIf you've accumulated small fixup commits, squash combines them into the original change:\n\n#+begin_src bash\n# Scenario: You're at @ and realize you need to fix something in @-\n# Make the fixes in your current working copy...\njj squash --into @-     # Squash current changes into parent\n\n# Alternative: Already created separate \"fix typo\" commits? Squash them:\njj squash -r \u003cfixup-change-id\u003e --into \u003ctarget-change-id\u003e\n#+end_src\n\n*Note:* When you edit an earlier commit, jj automatically rebases all descendants. No manual rebasing needed!\n\n** Practice: Apply What You've Learned\n\nTry these history editing exercises:\n\n1. Create two commits with ~jj commit -m \"message\"~\n2. Use ~jj edit~ to go back to the first commit and make a change\n3. Run ~jj log~ to see how descendants automatically rebased\n4. Create a small \"fix typo\" commit and use ~jj squash~ to combine it with its parent\n5. Try ~jj rebase -d main~ to move your changes onto a different base\n6. Use ~jj undo~ if you make a mistake - see how easy it is to recover!\n\n*Challenge:* Create three commits, then use ~jj edit~ to modify the middle one. Watch how jj automatically updates the third commit.\n\n* 4. Working with Bookmarks\n\nNow let's talk about how to organize and name your work. Jujutsu uses \"bookmarks\" which are similar to Git branches but with important differences.\n\n** A. What Are Bookmarks?\n\nBookmarks are jj's version of Git branches - named pointers to commits.\n\n*Think of bookmarks as:* Labels you attach to commits to track your work and push to GitHub as branches.\n\n** B. How Bookmarks Differ from Git Branches\n\nUnderstanding these differences helps explain why bookmarks feel more natural in daily use:\n\n*Key differences:*\n\n- *Automatic movement*: Bookmarks automatically follow commits when you rebase or rewrite them. Git branches stay fixed unless you explicitly move them.\n\n- *No \"active\" bookmark*: In Git, you're always \"on\" a branch. In jj, there's no active bookmark - you work with commits directly via change IDs.\n\n- *Conflict handling*: Bookmarks can become conflicted (shown with ~??~) when updated from multiple sources. You resolve these explicitly.\n\n*Why this matters:* Bookmarks track your intent (which commits belong to which feature) while jj handles the mechanics of keeping them up-to-date.\n\n** C. Creating and Managing Bookmarks\n\n#+begin_src bash\njj bookmark list                         # Show all bookmarks\njj bookmark set feature-x -r @           # Create or update bookmark (most common)\njj bookmark create feature-x -r @        # Create NEW bookmark (fails if exists)\njj bookmark move feature-x -r \u003crev\u003e      # Move existing bookmark\njj bookmark delete old-feature           # Delete local bookmark\njj bookmark track feature-x@origin       # Start tracking \u003cbranch\u003e@\u003cremote\u003e with a bookmark\n#+end_src\n\n** D. Understanding Bookmark Tracking\n\nTracking is a concept that helps keep your local bookmarks synchronized with remotes.\n\n*What does tracking do?*\n\nWhen you track a remote bookmark (like ~feature-x@origin~), ~jj git fetch~ will automatically create or update a local bookmark (~feature-x~) to match the remote.\n\n*Without tracking:* You can still see and reference ~feature-x@origin~, but fetch won't automatically create a local ~feature-x~ bookmark.\n\n*When to track:* Track remote bookmarks you're actively working on and want to stay synchronized with.\n\n** E. Which Command to Use?\n\nWith multiple bookmark commands available, here's when to use each:\n\n- *~set~*: Use this most of the time - works for both new and existing bookmarks\n- *~create~*: When you want an error if the bookmark already exists (safer but stricter)\n- *~move~*: When you need advanced features like ~--allow-backwards~ or ~--from~ filters\n\n** F. Tags vs Bookmarks\n\nTags serve a different purpose than bookmarks - they mark immutable points in history.\n\n*Tags* are like bookmarks but immutable - they mark specific points (usually releases):\n\n#+begin_src bash\njj tag list                  # List all tags\n# Note: Create tags via git (jj syncs them automatically)\ngit tag v1.0.0\njj git fetch                 # Import tags from git\n#+end_src\n\n*Key difference:* Tags don't move when commits are rewritten. Bookmarks follow commits.\n\n** Practice: Apply What You've Learned\n\nPractice working with bookmarks:\n\n1. Create a bookmark on your current change: ~jj bookmark set my-feature -r @~\n2. Run ~jj bookmark list~ to see all your bookmarks\n3. Make some changes and use ~jj log~ to see the bookmark automatically moved with your commit\n4. Try ~jj bookmark set another-feature -r @-~ to create a bookmark on your parent commit\n5. Use ~jj bookmark delete~ to clean up bookmarks you don't need\n\n*Challenge:* Create two bookmarks pointing to different commits in your history, then use ~jj edit~ to work on each one.\n\n* 5. Syncing with Remotes\n\nWith bookmarks in place, let's connect your local work to GitHub.\n\n** A. Fetching Updates\n\nFetching in jj works similarly to Git, pulling down remote changes:\n\n#+begin_src bash\njj git fetch                 # Pull changes from remote\njj log                       # See remote bookmarks (e.g., main@origin)\njj rebase -d main@origin     # Rebase your work onto latest main\n#+end_src\n\n** B. Basic Push (without bookmarks)\n\nFor quick prototyping, you can push single changes with auto-generated bookmarks:\n\n#+begin_src bash\njj git push --change @ --allow-new   # Pushes current change with generated bookmark name\n#+end_src\n\nThis is useful for one-off experiments but for real work you'll want to create explicit bookmarks.\n\n** Practice: Apply What You've Learned\n\nPractice syncing with remotes (if you have a remote repository):\n\n1. Run ~jj git fetch~ to get the latest changes from your remote\n2. Check ~jj log~ to see remote bookmarks like ~main@origin~\n3. Create a new change and bookmark: ~jj bookmark set test-sync -r @~\n4. Try pushing with ~jj git push --bookmark test-sync --allow-new~\n5. Make a change to your bookmark and push again (no ~--allow-new~ needed this time)\n6. Practice rebasing onto the latest main: ~jj rebase -d main@origin~\n\n*Challenge:* Create a local change, push it, then use ~jj edit~ to modify it and push the updated version.\n\n* 6. Stacked Changes / PR Workflow\n\nOne of jj's most powerful patterns is \"stacking\" - breaking large features into small, dependent pull requests. Let's understand why and how.\n\n** A. Why Stack Changes?\n\n*The problem with large PRs:*\n- Hard to review (reviewers lose focus)\n- Risky to merge (many changes at once)\n- Slow feedback cycle (must finish everything before getting feedback)\n\n*Stacking solves this by:*\n- Breaking large features into small, reviewable chunks\n- Getting feedback on early parts while working on later parts\n- Making each PR focused and easy to understand\n\n*Example:* Instead of one huge \"Add user authentication\" PR, stack:\n1. PR 1: Add database schema for users\n2. PR 2: Add authentication API endpoints (builds on PR 1)\n3. PR 3: Add login UI (builds on PR 2)\n\nEach PR can be reviewed and merged independently!\n\n*What if your change is already too big?* Use ~jj split~ to break it apart:\n\n#+begin_src bash\njj split                # Interactively choose which changes go into first commit\n# jj opens an editor showing all changes\n# Select which hunks belong in the first commit\n# Everything else stays in the second commit\n#+end_src\n\nAfter splitting, you have two commits where you had one - perfect for creating separate PRs!\n\n** B. Building Dependent PRs\n\nHere's the workflow for creating a stack:\n\n#+begin_src bash\njj new main                                      # Start from main\n# Make changes...\njj new                                            # Start second change\n# Make more changes...\njj bookmark set feature-part1 -r @-              # Bookmark first change\njj bookmark set feature-part2 -r @               # Bookmark second change\njj git push --bookmark feature-part1 --allow-new # First time push requires --allow-new\njj git push --bookmark feature-part2 --allow-new\n#+end_src\n\nNote the ~--allow-new~ flag - this is required the first time you push a new bookmark to protect against typos.\n\n** C. Addressing PR Feedback\n\nWhen you get feedback on a PR in your stack, jj makes it easy to fix:\n\n#+begin_src bash\njj edit \u003cchange-id\u003e     # Go back to specific change in stack\n# Make fixes...\njj new                  # Move forward\njj git push --bookmark feature-part1  # Update existing bookmark (no --allow-new needed)\n#+end_src\n\nAll descendant changes automatically rebase on your fixes!\n\n** Practice: Apply What You've Learned\n\nPractice creating stacked changes:\n\n1. Start from main: ~jj new main~\n2. Make changes for \"part 1\" of a feature\n3. Run ~jj new~ to start \"part 2\" building on part 1\n4. Make more changes for part 2\n5. Create bookmarks: ~jj bookmark set feature-part1 -r @-~ and ~jj bookmark set feature-part2 -r @~\n6. Try using ~jj split~ on a commit with multiple changes to break it into logical pieces\n7. Practice editing an earlier commit in your stack and watch descendants auto-rebase\n\n*Challenge:* Create a three-commit stack where each commit builds on the previous one. Edit the middle commit and verify the third commit updates automatically.\n\n* 7. Understanding Revsets\n\nThroughout this tutorial we've used expressions like ~@~, ~main~, and ~main@origin~. These are \"revsets\" - jj's query language for selecting commits.\n\n** A. What Are Revsets?\n\nRevsets are jj's query language for selecting commits. Think of them as \"commit selectors.\"\n\n*Full reference:* https://jj-vcs.github.io/jj/latest/revsets/\n\nYou've already been using simple revsets, but they can be much more powerful.\n\n** B. Basic Revset Syntax\n\n*Symbols:*\n- ~@~ - your working copy commit\n- ~@-~ - parent of working copy\n- ~main~ - a bookmark/branch\n- ~main@origin~ - bookmark on remote\n\n*Common Functions:*\n- ~trunk()~ - main development branches (main, master, trunk)\n- ~tags()~ - tagged releases\n- ~bookmarks()~ - all local bookmarks\n- ~remote_bookmarks()~ - bookmarks on remotes\n\n*Examples:*\n#+begin_src bash\njj log -r @                    # Show working copy\njj log -r @-                   # Show parent\njj log -r main..@              # Commits between main and working copy\njj log -r 'author(alice)'      # Commits by alice\n#+end_src\n\n** Practice: Apply What You've Learned\n\nExperiment with revsets:\n\n1. Run ~jj log -r @~ to see just your working copy\n2. Try ~jj log -r @-~ to see your parent commit\n3. Use ~jj log -r main..@~ to see all commits between main and your working copy\n4. Experiment with ~jj log -r 'bookmarks()'~ to see all bookmarked commits\n5. Try ~jj log -r 'trunk()'~ to see main development branches\n6. Use revsets with other commands: ~jj diff -r @-~ or ~jj show -r main~\n\n*Challenge:* Create a revset expression to show all your commits that aren't in main yet.\n\n* 8. Immutable Commits \u0026 Safe History\n\nNow that you understand how to work with history, let's talk about the safety rails that prevent you from breaking shared work.\n\n** A. Why This Matters\n\nIn Git, it's easy to accidentally rewrite commits that others have based work on, breaking their repositories. Force pushing can overwrite work. These accidents cause frustration and lost time.\n\nJujutsu prevents these problems through immutable commits - certain commits are protected from rewriting.\n\n** B. What Are Immutable Commits?\n\nJujutsu protects certain commits from being rewritten:\n- ~trunk()~ - main/master branches\n- ~tags()~ - tagged releases\n- ~untracked_remote_bookmarks()~ - commits pushed to remotes\n\n*Configuration example (optional to show):*\n#+begin_src bash\njj config list | grep immutable\n#+end_src\n\nThese protections mean:\n- *No rewriting shared history* - can't accidentally rebase commits others have\n- *No force push needed* - jj checks remote state before pushing (like ~--force-with-lease~)\n- *Safe by default* - protects your team from broken histories\n\n** C. When You Need to Update a Pushed Commit\n\nIf you need to update a commit you've already pushed, jj ensures you do it safely:\n\n#+begin_src bash\njj git push --bookmark my-feature  # Fails if remote diverged\n# Must fetch first if remote changed:\njj git fetch\njj rebase -d main@origin          # Resolve conflicts\njj git push --bookmark my-feature  # Now succeeds\n#+end_src\n\n*Best practice:* Don't rewrite commits others have based work on!\n\n** Practice: Apply What You've Learned\n\nUnderstanding immutable commits:\n\n1. Run ~jj config list | grep immutable~ to see your immutability settings\n2. Try to rebase a commit that's already pushed - observe the safety checks\n3. Create a local change, push it, then try ~jj edit~ on it\n4. Understand which commits are protected: run ~jj log -r 'trunk()'~ and ~jj log -r 'tags()'~\n5. Practice the safe update pattern: fetch, rebase, then push\n\n*Challenge:* Intentionally try to force-push to see how jj prevents unsafe operations. Then learn the correct way to update pushed commits.\n\n* 9. Conflict Resolution\n\nLet's talk about what happens when your changes conflict with others' changes - an inevitable part of collaborative development.\n\n** A. How jj Handles Conflicts Differently\n\nJujutsu takes a unique approach to conflicts that gives you more flexibility:\n\n*Key differences from Git:*\n\n- *Conflicts are first-class objects*: You can commit with unresolved conflicts and continue working\n- *No special commands needed*: No ~git rebase --continue~ or ~git merge --continue~ - just edit the conflict and the change is automatically amended\n- *Operations don't fail*: ~jj rebase~ succeeds even with conflicts; descendants automatically rebase too\n\n*Why this matters:* You can keep your work rebased on main without blocking on conflict resolution. Resolve conflicts when you're ready.\n\n** B. Three Ways to Resolve Conflicts\n\n*Method 1: Manual editing (simple conflicts)*\n#+begin_src bash\njj rebase -d main        # May create conflicts - operation still succeeds!\njj status                # Shows conflicted files with conflict markers\n# Edit files to resolve (markers: \u003c\u003c\u003c\u003c\u003c\u003c\u003c %%%%%%% +++++++ \u003e\u003e\u003e\u003e\u003e\u003e\u003e)\njj diff                  # Verify resolution\n# No special command needed - conflict resolved automatically\n#+end_src\n\n*Method 2: Using a merge tool (complex conflicts)*\n#+begin_src bash\njj resolve --list        # List all conflicted files\njj resolve path/to/file  # Opens merge tool for specific file\njj resolve               # Resolve all conflicts interactively, one by one\n# Built-in shortcuts: --tool=:ours (use our side) or --tool=:theirs (use their side)\n#+end_src\n\n*Method 3: View what changed during resolution*\n#+begin_src bash\njj interdiff --from \u003cbefore-resolve\u003e --to @  # See what you changed while resolving\n# Useful for reviewing your conflict resolution decisions\n#+end_src\n\n** C. Advanced Features\n\n- *Postpone resolution*: Keep commits rebased while deferring conflict fixes\n- *Better conflict markers*: Shows \"diff to apply\" rather than just both sides\n- *Descendants auto-rebase*: When you resolve a conflict, descendants update automatically\n\n** D. A Word of Caution\n\n*Don't postpone conflicts indefinitely!*\n\nWhile jj lets you defer resolution, conflicts have real consequences:\n\n- *Tests may fail*: Conflicted code won't compile or run correctly\n- *Blocks collaboration*: Can't share conflicted commits with teammates\n- *Compounds over time*: More rebases = more potential conflicts to untangle\n- *Breaks local development*: Your working copy may be broken until conflicts are resolved\n\n*Best practice:* Postpone briefly for convenience (finish current thought, switch tasks), but resolve conflicts before:\n- Pushing to remote\n- Asking for code review\n- Switching to work on dependent changes\n\n** Practice: Apply What You've Learned\n\nPractice conflict resolution (you'll need two branches that modify the same files):\n\n1. Create a conflict intentionally by rebasing onto a branch with conflicting changes\n2. Run ~jj status~ to see which files are conflicted\n3. Open a conflicted file and examine the conflict markers (~\u003c\u003c\u003c\u003c\u003c\u003c\u003c~, ~%%%%%%%~, ~+++++++~, ~\u003e\u003e\u003e\u003e\u003e\u003e\u003e~)\n4. Try ~jj resolve --list~ to see all conflicts\n5. Use ~jj resolve~ to open your merge tool and resolve a conflict\n6. Practice continuing work with unresolved conflicts, then resolve them later\n7. Use ~jj interdiff~ to review what you changed while resolving\n\n*Challenge:* Create a stack of three commits, create a conflict in the first one, and watch how jj handles conflicts in descendant commits.\n\n* 10. Advanced Power Features\n\nIf you have extra time, here are some advanced features that showcase jj's power.\n\n** A. ~jj absorb~ - Automatic Fixup Distribution\n\nIntelligently moves changes from your working copy into the appropriate commits in your stack:\n\n#+begin_src bash\n# You've made fixes across multiple files that belong to different commits\njj absorb                # Automatically distributes changes to where they belong\n# jj analyzes which commit last touched each line and moves changes there\n#+end_src\n\n*When to use:* After making scattered fixes across a stack of commits - let jj figure out where each change belongs.\n\n** B. ~jj diffedit~ - Interactive Change Editing\n\nEdit the changes in a commit directly, like using a visual diff editor:\n\n#+begin_src bash\njj diffedit -r \u003cchange-id\u003e   # Opens diff editor to modify the commit's changes\n# Add/remove hunks, modify lines - more powerful than manual editing\n#+end_src\n\n*When to use:* Surgically edit what a commit changes without touching other commits.\n\n** C. ~jj parallelize~ - Restructure Commit Relationships\n\nConvert a linear stack into parallel siblings for independent testing:\n\n#+begin_src bash\njj parallelize \u003crevset\u003e      # Makes commits siblings instead of linear ancestors\n# Useful for testing independent features in parallel\n#+end_src\n\n*When to use:* You have multiple independent features in a stack that don't actually depend on each other.\n\n** D. ~jj op log~ and ~jj op restore~ - Time Travel\n\nView and restore any previous state of your repository:\n\n#+begin_src bash\njj op log                    # See every operation you've performed\njj op restore \u003coperation-id\u003e # Go back to any previous state\n# More powerful than undo - can restore from any point in history\n#+end_src\n\n*When to use:* Made a complex mistake? Just restore to before you made it!\n\n** E. ~jj duplicate~ - Copy Commits\n\nCreate copies of commits with the same content but different change IDs:\n\n#+begin_src bash\njj duplicate \u003cchange-id\u003e           # Duplicate commit onto its current parent\njj duplicate -d main \u003cchange-id\u003e   # Duplicate commit onto a different base (main)\n# Useful for trying different approaches without losing the original\n#+end_src\n\n*When to use:*\n- Experiment with changes without losing the original\n- Apply the same change to multiple branches\n- Create a backup before risky operations\n\n** F. ~jj bisect~ - Find When a Bug Was Introduced\n\nAutomatically find which commit introduced a bug using binary search:\n\n#+begin_src bash\njj bisect run 'cargo test'         # Automatically test each commit\n# jj will binary search through history, running your test command\n# Finds the first commit where the test fails\n#+end_src\n\n*When to use:* When you know something broke but don't know which commit caused it.\n\n** Practice: Apply What You've Learned\n\nExperiment with advanced features:\n\n1. Create a stack of commits with scattered changes, then use ~jj absorb~ to automatically distribute your working copy changes\n2. Try ~jj duplicate~ to create a backup of a commit before making risky edits\n3. Use ~jj diffedit~ to surgically modify what a commit changes\n4. Create multiple independent features in a linear stack, then use ~jj parallelize~ to make them siblings\n5. Practice ~jj op log~ to view your command history, then try ~jj op restore~ to revert to an earlier state\n6. If you have a test suite, experiment with ~jj bisect run 'your-test-command'~ to find a breaking commit\n\n*Challenge:* Create a complex mistake (multiple bad commits), then use ~jj op log~ and ~jj op restore~ to roll back to before the mistake happened.\n\n* 11. Wrap-up \u0026 Resources\n\n** Key Takeaways\n\n- Working copy is a commit; ~jj new~ to start fresh\n- Change IDs stay stable across amendments; commit IDs change\n- Immutable commits prevent rewriting shared history\n- Operation log (~jj op log~) + ~jj undo~ = safety net\n- Colocated repos let you use both jj and git\n- Bookmarks track your work; use ~jj bookmark set~ for most cases\n- Stacking changes creates better PRs\n\n** Resources\n\n- ~jj help [command]~\n- https://jj-vcs.github.io/jj/latest/\n\n* Command Reference Table\n\n| Command | Description | Example |\n|---------|-------------|---------|\n| ~jj git init~ | Initialize colocated jj+git repo | ~jj git init~ |\n| ~jj git clone~ | Clone a Git repository | ~jj git clone https://github.com/user/repo~ |\n| ~jj status~ | Show working copy changes, auto-snapshots working copy | ~jj status~ |\n| ~jj diff~ | Show changes in working copy commit | ~jj diff~ |\n| ~jj log~ | Display commit graph with change IDs | ~jj log~ |\n| ~jj evolog~ | Show evolution history of a change (how it was modified over time) | ~jj evolog~ |\n| ~jj interdiff~ | Compare changes between two commits (useful for PR iterations) | ~jj interdiff --from @-- --to @~ |\n| ~jj new~ | Create new empty commit on top of current, \"finishing\" current change | ~jj new~ |\n| ~jj new \u003cchange-id\u003e~ | Create new commit based on specific revision | ~jj new main~ |\n| ~jj describe~ | Set or change commit message for current or specified commit | ~jj describe -m \"feat: add feature\"~ |\n| ~jj commit~ | Shortcut: describe + new (most like git commit) | ~jj commit -m \"message\"~ |\n| ~jj commit \u003cfiles\u003e~ | Commit only specific files (like split but simpler) | ~jj commit src/*.rs -m \"message\"~ |\n| ~jj metaedit~ | Modify commit metadata (author, timestamps, change-id) without changing content | ~jj metaedit --author \"Name \u003cemail\u003e\"~ |\n| ~jj edit \u003cchange-id\u003e~ | Make specified commit the working copy, allows resuming work on old commits | ~jj edit abc123~ |\n| ~jj prev~ | Move to parent commit in a stack (creates new @ on parent by default) | ~jj prev~ |\n| ~jj next~ | Move to child commit in a stack (creates new @ on child by default) | ~jj next~ |\n| ~jj prev --edit~ | Edit parent commit directly | ~jj prev --edit~ |\n| ~jj squash~ | Move changes from working copy into parent commit | ~jj squash~ |\n| ~jj split~ | Split a commit into two commits interactively | ~jj split~ |\n| ~jj abandon~ | Abandon current change, removing it from history | ~jj abandon~ |\n| ~jj rebase -d \u003cdest\u003e~ | Rebase current change onto destination commit/bookmark | ~jj rebase -d main~ |\n| ~jj fix~ | Apply configured formatters/linters to commits | ~jj fix -s @~ |\n| ~jj file list~ | List tracked files in a revision | ~jj file list~ |\n| ~jj file show~ | Show contents of file in a revision | ~jj file show path/to/file -r @~ |\n| ~jj file track~ | Explicitly track a file | ~jj file track .gitignore~ |\n| ~jj file untrack~ | Stop tracking a file | ~jj file untrack temp.txt~ |\n| ~jj bookmark create~ | Create a new bookmark (fails if bookmark already exists) | ~jj bookmark create feature-x -r @~ |\n| ~jj bookmark set~ | Create or update a bookmark to point to a commit (works for new and existing) | ~jj bookmark set feature-x -r @~ |\n| ~jj bookmark list~ | List all local and remote bookmarks | ~jj bookmark list~ |\n| ~jj bookmark move~ | Move existing bookmarks with advanced options (supports --allow-backwards, --from) | ~jj bookmark move feature-x -r @~ |\n| ~jj bookmark delete~ | Delete a local bookmark | ~jj bookmark delete old-feature~ |\n| ~jj bookmark track~ | Start tracking \u003cbranch\u003e@\u003cremote\u003e with a bookmark | ~jj bookmark track feature-x@origin~ |\n| ~jj tag list~ | List all tags | ~jj tag list~ |\n| ~jj git fetch~ | Fetch changes from Git remote | ~jj git fetch~ |\n| ~jj git push~ | Push bookmarks to Git remote (checks remote state, safe by default) | ~jj git push --bookmark feature-x~ |\n| ~jj git push --allow-new~ | Push new bookmark to remote for first time (required for new bookmarks) | ~jj git push --bookmark feature-x --allow-new~ |\n| ~jj git push --change~ | Push single change with auto-generated bookmark | ~jj git push --change @ --allow-new~ |\n| ~jj op log~ | Show operation log (history of jj commands run) | ~jj op log~ |\n| ~jj op restore~ | Restore repository to a previous operation state | ~jj op restore \u003coperation-id\u003e~ |\n| ~jj undo~ | Undo the last operation | ~jj undo~ |\n| ~jj redo~ | Redo an operation (after using undo) | ~jj redo~ |\n| ~jj resolve~ | Resolve conflicts using a merge tool | ~jj resolve~ |\n| ~jj resolve --list~ | List all conflicted files | ~jj resolve --list~ |\n| ~jj absorb~ | Automatically distribute working copy changes to appropriate commits in stack | ~jj absorb~ |\n| ~jj diffedit~ | Interactively edit changes in a commit using a diff editor | ~jj diffedit -r \u003cchange-id\u003e~ |\n| ~jj parallelize~ | Convert linear commits to parallel siblings | ~jj parallelize \u003crevset\u003e~ |\n| ~jj duplicate~ | Create a copy of a commit with the same content | ~jj duplicate \u003cchange-id\u003e~ |\n| ~jj bisect~ | Find which commit introduced a bug using binary search | ~jj bisect run 'cargo test'~ |\n| ~jj config set --user~ | Set user-level configuration | ~jj config set --user signing.backend \"gpg\"~ |\n| ~jj sign~ | Manually sign commits | ~jj sign~ |\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchristianromney%2Fjujutsu-notes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchristianromney%2Fjujutsu-notes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchristianromney%2Fjujutsu-notes/lists"}