Git Worktree: Managing Multiple Working Directories
What is a Git Worktree?
A Git worktree is a powerful Git feature that allows you to have multiple working directories associated with a single Git repository. Instead of cloning the same repository multiple times, you can check out different branches in separate directories while sharing the same .git directory.
This makes it easy to work on multiple features, bug fixes, or code reviews simultaneously without the overhead of multiple clones.
And it is also great for spinning some LLM processes to experiment with different branches without messing up your main working directory.
Folder structure
Here is how you would structure your worktrees for different branches. This example shows three worktrees for different branches: feature/new-user-api, chore/dependency-update, and fix/security-patch.
gitGraph commit id: "Initial" commit id: "Base setup" branch feature/new-user-api checkout feature/new-user-api commit id: "Add user model" commit id: "Add user service" commit id: "Add user endpoint" checkout main branch chore/dependency-update checkout chore/dependency-update commit id: "Update dependency" checkout main branch fix/security-patch checkout fix/security-patch commit id: "Fix vulnerability" checkout main merge feature/new-user-api merge chore/dependency-updateEach worktree has its own working directory, index (for staged changes), and HEAD (current branch reference), but they all share the same object database (stores the actual data like commits, files, and history, same stashes), refs (pointers to branches and tags), and configuration (repository settings like remotes and hooks).
It would look like this in the filesystem:
When you fetch origin/main, all worktrees see the updated commits because they share the same object database.
Setup and Usage
Initial Setup with Bare Repository
First make a bare clone of your project.
This creates a bare repository that contains only Git data without any working files.
While worktrees work with normal repository you want to use a bare repository because it offers better separation. It’s just a pure Git data storage while the worktrees handle file operations. But most importantly, it prevents conflicts since you can’t accidentally work in the “main” directory since it has no files.
The bare repository serves as the central storage for all branches and commits.
This configuration ensures that all remote branches are fetched and available in your bare repository, allowing you to create worktrees based on these branches. It is done automatically when you clone normally, but not with a bare repository.
Creating Worktrees
Now that you have your repository set up, you can create worktrees for different branches.
This will create three separate directories outside the bare repository, each checked out to the specified branch. Exactly like the diagram above. Each worktree will have its own copy of the project files for that specific branch. We used the -b to create the branch if it didn’t exist.
Development Workflow Example
Let’s say you’re working on three different tasks simultaneously: implementing a new user API, updating project dependencies, and addressing a critical security vulnerability. Here’s how you’d manage this with worktrees:
But you could do the same in parallel on the other branches:
Management Commands
Now that you have worked with multiple worktree, you might want to manage them.
You can also remove a worktree when you’re done with it. Cleaning up the not only the directory but also the references in the main repository.
If that doesn’t work you could try --force or delete the folder directly. But in that case with just folder deleted, the worktree still exists and becomes stale, and you should prune it to remove the references.
Stacked Diff with Git Worktrees
Git worktrees are particularly useful when working with stacked diffs, where multiple dependent changes are developed in sequence.
For example, if we are working on some new API feature, and we have a new UI that’ll depend on it, we could create a new worktree for the UI branch that is based on the API branch.
With the start point of the worktree set as feature/new-user-api, the feature/new-user-ui branch will be stacked on top of the feature/new-user-api branch. Its worktree is located at /my-project/new-user-ui.
This allows you to work on new-user-ui while keeping it dependent on the changes in new-user-api. Once the base branch is ready, you can merge it and rebase the stacked branch as needed.
gitGraph commit id: "Initial Commit" commit id: "Base Setup" branch feature/new-user-api checkout feature/new-user-api commit id: "Add User Model" commit id: "Add User Service" commit id: "Add User Endpoint" branch feature/new-user-ui checkout feature/new-user-ui commit id: "Add UI Components" commit id: "Integrate with API" checkout main merge feature/new-user-api merge feature/new-user-uiHowever, you should not abuse stacked diffs as breaking refactorings in the base branch can cause significant headaches when rebasing. Depending on the type of changes, the rebase complexity will compound with each stacked branch.
Due to the nature of stacked diff, squashing commits is not recommended as it will force a complex rebasing. As the commit history will be rewritten, you will rebase the final state of the branch with each previous commit. It becomes tedious very quickly, for a 10 commit branch times 3 stacked branches, you will have to do 30 rebase instead of 3 normally.
.png)

