Welcome back, future Jujutsu wizard! In the previous chapter, we successfully installed jj and confirmed it was ready for action. Now, it’s time to create our very first Jujutsu repository and dive into a concept that fundamentally differentiates jj from traditional Version Control Systems (VCS) like Git: the “working copy as a commit” model.
This chapter is a cornerstone of your jj journey. Understanding this core principle is crucial because it’s the foundation for jj’s powerful mutable history, streamlined workflows, and branchless development style. By the end, you’ll not only have a functioning jj repository but also a deep intuition for how jj perceives your code, preparing you for advanced techniques like stacked changes and effortless rebasing. Let’s make that paradigm shift together!
Understanding the Working Copy Commit: Jujutsu’s Core Difference
If you’re accustomed to Git, you’re familiar with a workflow where you explicitly add changes to a staging area, then commit them. Your working directory often contains uncommitted modifications, and once pushed, Git’s history is generally considered immutable. Jujutsu challenges this mental model entirely.
What is the “Working Copy as a Commit” (The @ Commit)?
In Jujutsu, your working directory—the collection of files you’re currently editing—is always treated as a live, mutable commit. This special commit is known as the working copy commit, often referred to by its symbolic name @.
- No Staging Area Needed: Forget
git add.jjautomatically tracks all changes in your working directory. Any file you modify, add, or delete immediately becomes part of your working copy commit. This simplifies your daily workflow by removing an entire step. - Always a Commit: From the moment you initialize a
jjrepository, a working copy commit exists. As you make changes, this commit continuously evolves, representing the current state of your project. - Parent-Child Relationship: Your working copy commit always has a parent. When you finalize changes into a new, “permanent” commit, your working copy automatically “moves” to become a new, empty commit with your just-created commit as its parent. It’s like a dynamic pointer to your current workspace.
Why This Model Matters for Developers
This design isn’t just a quirky difference; it’s a deliberate choice that simplifies many common development challenges and empowers jj’s unique capabilities:
- Streamlined Workflow: By eliminating the staging area,
jjreduces cognitive load. You focus purely on making and describing changes, rather than managing an intermediate state. This means less friction and more flow. - Natural Mutable History: Since your working copy is inherently a commit, modifying history (like amending a previous commit or reordering changes) feels incredibly natural. You’re simply editing a live commit, much like you edit a file in your editor. This is a game-changer for iterative development and code reviews.
- Foundation for Stacked Changes: This model is the bedrock of
jj’s branchless workflow. It allows you to easily build a “stack” of dependent commits, where each commit builds logically on the previous one. Your working copy is just the latest, evolving step in that continuous stack. This dramatically simplifies complex feature development and refactoring. - Undo Capabilities: Because every change to your working copy is tracked as part of its evolution,
jjcan offer powerful undo capabilities that are far more granular and reliable than traditional VCS. We’ll explore this in the next chapter!
Visualizing the Working Copy Flow
Let’s visualize how your working copy commit evolves as you make changes and finalize them into your repository’s history.
In this diagram:
Arepresents your starting point, a fresh directory.Cis the working copy commit (@), whichjjcreates upon initialization. It’s initially empty.- When you
Edit Files(D), your working copy commit (@) immediately reflects these changes (E). There’s no separateaddstep. Frepresents runningjj commit. This action takes the changes currently inEand finalizes them into a new, permanent commit (G).- Crucially,
Hshows thatjjthen automatically creates a new, empty working copy commit (@) on top ofG. This new@is ready for your next set of changes, maintaining the continuous “working copy as a commit” flow.
Step-by-Step: Your First Jujutsu Repository
Let’s get hands-on. We’ll create a new project, initialize a jj repository, make some changes, and see this “working copy as a commit” model in action.
1. Create a Project Directory
First, let’s set up a clean workspace for our new jj project.
mkdir my-first-jj-repo
cd my-first-jj-repo
2. Initialize the Jujutsu Repository
Now, let’s transform this ordinary directory into a jj repository. This command is conceptually similar to git init.
jj init
You should see output indicating success:
Initialized empty Jujutsu repository in "my-first-jj-repo" at ...
What just happened? jj created a hidden .jj directory within my-first-jj-repo. This directory is where jj stores all its internal repository data, including your history, the operation log, and configuration.
3. Inspect the Current State
Even though we haven’t created any files yet, jj has already established our working copy commit. Let’s inspect it using jj status.
jj status
You’ll see output similar to this:
Working copy: 588f98c8c6d7 (empty) (no description set)
Parent commit: zzzzzzzz (empty) (no description set)
Let’s break down this output, which is fundamental to understanding jj:
Working copy: 588f98c8c6d7 (empty) (no description set): This line describes your active working copy commit.588f98c8c6d7: This is a unique, short identifier (a commit ID, similar to a Git SHA) for your working copy commit. Your specific ID will be different.(empty): This indicates that your working directory currently contains no files or changes relative to its parent.(no description set): We haven’t given this ephemeral working copy commit a message yet.
Parent commit: zzzzzzzz (empty) (no description set): This describes the parent of your working copy commit. In a newly initializedjjrepository, the working copy’s parent is a special, immutable “root” commit. It’s always empty and often represented by allz’s. Think of it as the ultimate ancestor of all your changes.
To see a more historical view, we can use jj log -r @. The -r @ tells jj log to show the working copy commit and its ancestors.
jj log -r @
Output:
@ 588f98c8c6d7 (empty) (no description set)
o zzzzzzzz (empty) (no description set)
This log output clearly shows @ (our working copy commit) on top, with its parent being the root commit.
4. Create Your First File
Now, let’s create a simple text file. Remember, there’s no jj add!
echo "Hello, Jujutsu! This is my first file." > hello.txt
5. Check the Status Again
Let’s immediately check jj status to see how jj perceives this change without any explicit “add” command.
jj status
Output:
Working copy: 588f98c8c6d7 (no description set)
Added hello.txt
Parent commit: zzzzzzzz (empty) (no description set)
Notice the difference! The (empty) label is gone from your working copy description, and Added hello.txt clearly indicates that jj has recognized and included your new file within the working copy commit. Your working copy commit is no longer empty; it now contains hello.txt.
6. “Commit” Your Changes
To finalize the changes currently present in your working copy commit into a permanent part of your history, you use the jj commit command.
jj commit -m "Initial commit: Add hello.txt with a greeting"
Output:
Working copy: 1c3a7b9319e7 (empty) (no description set)
Committed as 3e9d4a2b1f0e "Initial commit: Add hello.txt with a greeting"
This output reveals the core mechanism of jj commit:
Committed as 3e9d4a2b1f0e "Initial commit: Add hello.txt with a greeting": Jujutsu took the current state of your previous working copy commit (the one that includedhello.txt) and created a new, immutable commit with the ID3e9d4a2b1f0e(your ID will differ) and the message you provided. This is now a stable part of your repository’s history.Working copy: 1c3a7b9319e7 (empty) (no description set): After creating the new commit,jjautomatically “moved” your working copy. It is now a brand new, empty commit that sits on top of the commit you just finalized. This new@is ready for your next set of changes, maintaining the continuous flow.
Let’s confirm this new state with jj log. This time, we’ll use -r @- to show the parent of the working copy, giving us a view of our actual history.
jj log -r @-
Output:
@ 1c3a7b9319e7 (empty) (no description set)
o 3e9d4a2b1f0e Initial commit: Add hello.txt with a greeting
o zzzzzzzz (empty) (no description set)
Fantastic! You now have a permanent commit (3e9d4a2b1f0e) in your history, representing the state where hello.txt was added. And your working copy (@) is patiently waiting, empty and ready for your next creative burst, sitting directly on top of that new commit.
Mini-Challenge: Evolving and Amending Your First Commit
Now that you’ve created a commit, let’s put jj’s mutable history to the test by modifying that commit. This is where jj truly shines compared to Git’s default immutable mindset.
Challenge:
- Add a new line of text to
hello.txt, perhaps asking a question. - Instead of creating a new commit on top, amend your previous commit (
3e9d4a2b1f0e) to include this new line and update its commit message to reflect the change.
Hints:
- Remember, your current working copy (
@) is always a commit. - To modify the parent of your working copy (which is your “Initial commit”), you’ll need a specific flag for the
jj commitcommand. Think about how other VCS tools handle “amending” a previous commit.
# Your code here
Stuck? Here's a guided approach:
First, let’s add the new line to hello.txt:
echo "How are you enjoying Jujutsu so far?" >> hello.txt
Next, let’s use jj status to confirm jj sees the modification:
jj status
You’ll see Modified hello.txt listed under your working copy. Now, to amend the parent of your working copy (our “Initial commit”), we use jj commit --amend. This command takes the current changes in your working copy and applies them to its parent, effectively rewriting that parent commit.
jj commit --amend -m "Initial commit: Add greeting and a question for Jujutsu"
Finally, let’s check jj log -r @- again. You’ll notice that the commit ID for your “Initial commit” has changed, and its message is updated!
jj log -r @-
(The output will show a new commit ID for the amended commit, and the new message. The old commit ID is gone from the immediate history.)
What to Observe/Learn:
You should have observed that jj commit --amend directly modified the content and message of the parent commit. This is a powerful demonstration of jj’s mutable history in action. Instead of creating a new commit on top and leaving the old one in history (which is what git commit --amend technically does, though the old commit becomes unreachable from branches), jj actually replaces the commit in your linear history. This results in a cleaner, more focused history that is much easier to manage, especially during iterative development and code review cycles. This direct mutation is a core differentiator from Git.
Common Pitfalls & Troubleshooting
Transitioning to jj from another VCS can sometimes lead to habits that don’t quite fit jj’s model. Here are a few common pitfalls:
- Searching for
jj add: The most common instinct for Git users is to look for a staging command. Remember,jjautomatically tracks all changes in your working directory. You don’t need toaddanything. Just make your changes, and thenjj commit(orjj describefor the working copy itself) when you’re ready to finalize them. - Confusing the Working Copy with a “Finished” Commit: The working copy commit (
@) is a special, ephemeral, and constantly changing commit. It’s your live workspace. When you runjj commit, you’re essentially taking a snapshot of that working copy’s state, finalizing it into a “real” commit, and then creating a new, empty working copy on top. Always remember@is your dynamic scratchpad. - Misinterpreting
jj commit’s Behavior: In Git,git commitcreates a new commit based on the staging area. Injj,jj commitcreates a new commit based on the current state of your working copy. If your working copy is empty,jj commitwill create an empty commit. If it has changes, those changes form the new commit. This distinction is subtle but important. If you want to describe the current working copy without creating a new commit, you’d usejj describe.
Summary
In this crucial chapter, you’ve taken your first significant steps with Jujutsu, grasping its most fundamental paradigm:
- You learned that in
jj, your working directory is always a commit (@), which dynamically reflects your current file state. This eliminates the need for a separate staging area. - You initialized your very first
jjrepository usingjj init. - You used
jj statusandjj logto inspect your working copy and its ancestral history. - You created files and observed how
jjautomatically tracks these changes within your working copy. - You finalized your working copy’s changes into a permanent commit using
jj commit -m "message". - You experienced
jj’s powerful mutable history firsthand by usingjj commit --amendto modify an existing commit, demonstrating howjjkeeps your history clean and linear.
This “working-copy-as-a-commit” model is the beating heart of jj’s philosophy, enabling all the advanced mutable history, stacked changes, and branchless workflows we’ll explore. In the next chapter, we’ll dive into jj’s incredibly robust operation log and discover how you can undo almost any action, no matter how complex, giving you unparalleled safety and flexibility.
References
- Jujutsu GitHub Repository
- Jujutsu Official Tutorial (on main branch)
- Jujutsu Releases
- Jujutsu Concepts Documentation
This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.