Welcome back, intrepid developer! So far, you’ve mastered the basics of Jujutsu’s unique approach to version control, from its mutable history to the powerful operation log. You’ve seen how jj empowers you to shape your history with confidence. But what happens when your project grows, or when you need to juggle multiple development lines simultaneously without creating a mess of separate Git clones?

This chapter introduces you to jj’s elegant solution: workspaces. We’ll dive into how jj structures repositories and how workspaces allow you to manage multiple working directories, each potentially focused on a different task, all backed by a single, shared repository. This isn’t just about saving disk space; it’s about streamlining your workflow, improving context switching, and enabling more flexible development patterns.

By the end of this chapter, you’ll understand how jj workspaces differ fundamentally from traditional Git clones, how to set them up, and how to leverage them for a more organized and efficient codebase. You’ll be able to confidently manage multiple active development efforts within a single jj repository, a skill invaluable for modern software engineering.

Understanding Jujutsu’s Repository Model

To fully appreciate jj’s workspaces, it’s essential to grasp its fundamental repository model, which diverges significantly from Git.

The Central .jj Directory: Your True Repository

In Git, when you git clone a repository, you get a full copy of the repository’s history, objects, and a working directory, all contained within the .git directory. If you need to work on two different features concurrently, you often end up with two separate clones of the same repository on your disk, each with its own .git directory. This duplication can be redundant and cumbersome.

jj takes a different, more centralized approach. At its core, a jj repository consists of a single, central .jj directory that stores all the repository’s history, objects, and configuration. This .jj directory is the single source of truth for your project’s version history. It typically resides at the root of your primary working directory.

What is a Workspace?

A jj workspace is a directory on your filesystem where you can edit files and interact with the jj repository. Each workspace has its own specific state, including which commit is currently checked out into its working directory. Crucially, multiple workspaces can coexist, all sharing the same underlying .jj directory.

📌 Key Idea: Imagine your central .jj directory as a vast library containing all your project’s history books. A jj workspace is like a personal desk in that library. You can have multiple desks (workspaces), each with a different book (commit) open, but they all access the same collection of books in the central library (the .jj repository).

This model brings several powerful advantages:

  • Shared History, Always Current: All workspaces connected to the same .jj directory share the exact same commit history and operation log. There’s no need to git fetch or git pull history between local clones; it’s always immediately available to all workspaces.
  • Reduced Redundancy: You avoid duplicating the entire repository history on disk for each separate working directory, saving space and simplifying management.
  • Simplified Context Switching: You can dedicate different workspaces to different tasks (e.g., one for a new feature, another for a bug fix, one for experimentation). Switching between them is as simple as changing directories, and each workspace maintains its own isolated working directory state.
  • Atomic Repository Operations: Operations like jj undo or jj rebase affect the entire shared repository. This means changes made to history in one workspace are immediately reflected and accessible in others, providing a consistent view.

Let’s visualize this shared structure:

flowchart LR subgraph Workspace_A["Workspace A Feature Dev"] Files_A[Working Files A] end subgraph Workspace_B["Workspace B Hotfix"] Files_B[Working Files B] end Jj_Repo_Dir[jj Repository] Files_A -->|accesses| Jj_Repo_Dir Files_B -->|accesses| Jj_Repo_Dir

In this diagram, Workspace A and Workspace B are distinct directories on your filesystem, each containing its own set of working files. However, both interact with and draw their history from the same Central .jj Directory. This means a commit created in Workspace A is instantly visible and accessible in Workspace B, and vice-versa.

Reinforcing Working-Copy-as-a-Commit

This workspace model beautifully reinforces jj’s “working-copy-as-a-commit” philosophy. In jj, your working directory is a commit (the “working-copy commit”). When you switch workspaces or create a new one, you’re essentially setting up a new working directory that points to a specific (often new and empty) commit. All these working-copy commits, regardless of which workspace they belong to, are part of the same underlying jj repository.

Step-by-Step: Managing Multiple Workspaces

Let’s put this into practice. We’ll start by creating a new jj repository and then add a second workspace to it to simulate working on a separate task.

1. Initialize Your First jj Repository and Workspace

First, create a new directory for your project and initialize it as a jj repository. This directory will automatically become your first workspace, often referred to as the “default” workspace.

mkdir my_jj_project
cd my_jj_project
jj init

You should see output similar to:

Initialized a new jj repo in "my_jj_project" with a Git backend.

This command did two things:

  1. It created the my_jj_project directory (if it didn’t exist).
  2. It initialized the central .jj directory inside my_jj_project, setting up your repository.
  3. It registered my_jj_project as your first workspace.

Now, let’s create a simple file and commit it. This will give us some history to work with.

echo "Hello from the main feature!" > main.txt
jj st

You’ll see main.txt listed as an untracked change. Let’s create a new commit for it.

jj new
jj files add main.txt
jj commit -m "feat: Initial commit with main.txt"

You’ve now got a jj repository with one workspace (my_jj_project) and one commit.

2. Add a Second Workspace for a Bug Fix

Now, imagine you’re deep into developing a new feature in my_jj_project, but a critical bug report comes in for something unrelated. You need to switch contexts quickly without stashing or committing your in-progress work. This is a perfect scenario for adding a new workspace.

We’ll add a new workspace named bug_fix_workspace to our existing my_jj_project repository.

jj workspace add bug_fix_workspace

You’ll see output like:

Added workspace "bug_fix_workspace" in "../bug_fix_workspace".
Working copy now at: 23f7e366a7b2 (no description set)

Notice that jj created a new directory named bug_fix_workspace one level up from my_jj_project. This is jj’s default behavior: new workspaces are created as sibling directories to the initial repository directory. This keeps the central .jj directory (which is inside my_jj_project) separate and shared among all workspaces.

Quick Note: If you prefer the new workspace to be a subdirectory (e.g., my_jj_project/new_feature), you can specify a path relative to the current working directory: jj workspace add my_jj_project/new_feature. However, the default sibling behavior is often cleaner for managing distinct development lines at the same level.

3. Explore and Work in the New Workspace

Let’s navigate into our newly created workspace and see what’s there.

cd ../bug_fix_workspace
ls

You should see main.txt! This immediately demonstrates that the new workspace has access to the files and history of the shared repository.

Let’s check the status in this new workspace:

jj st

You’ll see something like:

Current working copy: 23f7e366a7b2 (no description set)
Parent commit: f2a1b9c8d7e6 feat: Initial commit with main.txt

This shows that your working copy in bug_fix_workspace is currently on a new, empty commit whose parent is the “Initial commit” you made earlier. This is your isolated starting point for the bug fix.

Now, let’s make a change specific to this bug_fix_workspace.

echo "Fixing a critical bug!" > bug_fix.txt
jj new
jj files add bug_fix.txt
jj commit -m "fix: Implemented critical bug fix"

4. Observe Changes Across Workspaces

Now for the magic! Let’s go back to our original my_jj_project workspace and see if we can observe the bug_fix.txt file and the new commit.

cd ../my_jj_project
ls

You will not see bug_fix.txt here. Why? Because my_jj_project’s working copy is currently on its own (empty) commit, which doesn’t include the changes from bug_fix_workspace. Each workspace maintains its own working copy state.

However, the commit itself is part of the shared history. Let’s verify that using jj log.

jj log

You should now see both commits in the history, including the “fix: Implemented critical bug fix” commit, even though you made it in a different workspace!

@ 6f7a8b9cde0f (my_jj_project) (working copy) (no description set)
o 1234567890ab (bug_fix_workspace) fix: Implemented critical bug fix
o f2a1b9c8d7e6 feat: Initial commit with main.txt
o 000000000000 (empty) (root)

(Note: Commit hashes will differ for you. The key is to see both commits and their associated workspace names.)

This clearly illustrates the shared history. The bug_fix_workspace commit is instantly visible from my_jj_project, even though its files aren’t checked out here. This is incredibly powerful for keeping track of all ongoing development.

5. Listing and Forgetting Workspaces

You can always see which workspaces are linked to your current repository using jj workspace list. This command provides a quick overview of all active development contexts.

jj workspace list

Output:

my_jj_project: 6f7a8b9cde0f (no description set)
bug_fix_workspace: 1234567890ab fix: Implemented critical bug fix

This command shows all active workspaces, their names, their current working-copy commit ID, and a short description.

If you’re done with a workspace (e.g., the bug fix is complete and merged), you can remove its link to the repository using jj workspace forget. This command does not delete the directory or its files; it just severs the link to the .jj repository. You can then manually delete the directory if you wish.

jj workspace forget bug_fix_workspace

Output:

Forgot workspace "bug_fix_workspace".

Now, if you run jj workspace list again, bug_fix_workspace will be gone from the list of active workspaces. The bug_fix_workspace directory and its contents (bug_fix.txt) are still on your disk, but they are no longer managed by jj.

To clean up the physical directory, you would then rm -rf ../bug_fix_workspace.

Mini-Challenge: Feature and Experimentation Workspaces

Let’s put your new knowledge of workspaces to the test with a slightly more complex scenario.

Challenge:

  1. Start with a fresh jj repository in a new directory called my_app.
  2. Create an initial commit with a main.py file containing print("Hello, World!").
  3. Add a new workspace called feature_login.
  4. In feature_login, modify main.py to add a new function def login(user): print(f"User {user} logged in!") and call it. Commit this change.
  5. Back in your original my_app workspace, add another new workspace called experiment_db.
  6. In experiment_db, add a new file database.py with some experimental database connection code (e.g., print("Connecting to database...")). Commit this.
  7. Return to your original my_app workspace and use jj log to observe all three commits from your different workspaces.

Hint: Remember the commands: mkdir, cd, jj init, jj new, jj files add, jj commit -m, jj workspace add <name>, jj log. Pay close attention to which directory you’re in before executing jj commands or creating files.

What to observe/learn: You should see that commits from feature_login and experiment_db are both visible in the jj log from your initial my_app workspace. This demonstrates how a single .jj repository can effectively manage multiple independent lines of development. You also won’t see database.py or the changes to main.py (from feature_login) in your initial my_app workspace’s file system. This reinforces that each workspace maintains its own working copy state while sharing a common history.

Common Pitfalls & Troubleshooting

Working with workspaces introduces new powerful concepts, but also a few areas where developers new to jj might stumble.

  • Misunderstanding Shared History: A common mistake for Git users is thinking that jj workspaces are completely isolated, similar to separate Git clones. Remember, they share the same underlying .jj directory and thus the same history. If you jj rebase, jj amend, or jj squash a commit in one workspace, that change is immediately visible (and potentially affects) other workspaces, as you’re modifying the shared repository history. This is a powerful feature, not a bug, but it requires a mental model shift.
    • 🧠 Important: Changes to history are global to the jj repository, not local to a workspace. Always be mindful of this when rewriting history.
  • Forgetting Your Current Workspace: When you have multiple workspaces, it’s easy to lose track of which directory you’re currently in and, by extension, which workspace is active. This can lead to making changes in the wrong place.
    • Tip: Always check your shell prompt (if configured to show the current directory) or use pwd and jj st to confirm your context before making changes. jj workspace list also reminds you of all active workspaces.
  • Accidental File Modifications: If you open files from one workspace in your editor, but your terminal is in another workspace, you might make changes to files that aren’t part of your current working-copy commit. This can lead to confusion when jj st doesn’t show expected changes.
    • Tip: Ensure your editor and terminal are consistently aligned with the workspace you intend to modify. Many IDEs integrate well with jj and can show the current commit.
  • Deleting Workspaces Incorrectly: jj workspace forget only severs the logical link between the working directory and the .jj repository. It does not delete the physical directory or its files.
    • Tip: If you want to fully remove a workspace’s directory from your filesystem, you must do so manually (e.g., rm -rf) after running jj workspace forget.

Summary

In this chapter, we’ve explored one of jj’s most powerful organizational features: workspaces. This model provides a fresh perspective on managing your codebase, especially when dealing with concurrent development efforts.

Here are the key takeaways:

  • Centralized Repository (.jj): Unlike Git’s distributed clones, jj uses a single .jj directory to store all history and objects for a project.
  • Multiple Workspaces: Workspaces are separate physical directories, each acting as an independent working copy, all linked to and sharing the central .jj repository.
  • Key Benefits: Workspaces offer reduced disk space, simplified context switching, isolated working directories, and a consistent, shared view of history across all development lines.
  • Reinforces Working-Copy-as-a-Commit: Each workspace’s current state is a working-copy commit within the shared repository.
  • Core Commands:
    • jj init: Initializes a new repository and its first workspace.
    • jj workspace add <name>: Creates a new workspace linked to the current repository, typically as a sibling directory.
    • jj workspace list: Shows all active workspaces, their names, and their current working-copy commits.
    • jj workspace forget <name>: Disconnects a workspace from the repository (does not delete the physical directory).
  • Mental Model Shift: This workspace model significantly differs from Git’s “repository per clone” approach, fostering a more integrated and efficient way to manage concurrent development.

Understanding jj workspaces is crucial for leveraging its full potential, especially in projects requiring parallel development efforts or frequent context switching. By adopting this model, you can streamline your development workflow and maintain a cleaner, more organized codebase.

In the next chapter, we’ll delve deeper into how jj interacts with traditional Git, allowing you to seamlessly integrate jj’s powerful workflows into existing Git-based projects and collaborate effectively with others who might still be using Git.

References

This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.