What Are Git Worktrees?
Git worktrees let you check out multiple branches of the same repository into separate directories — simultaneously. Instead of stashing, committing, or shelving your work to switch branches, you simply cd into another directory.
Each worktree shares the same .git object store, so there is no duplication of history and no need to re-clone.
Why did the developer plant a git repository in the garden? Because they wanted more branches — and ended up with a whole worktree.
Question: why I didn’t know this before?! I was today years old to discover that. Shame!
Why Use Worktrees?
No more context switching friction
Switching branches with git checkout or git switch rewrites your working directory. That means:
- Rebuilds get triggered (especially painful in C/C++ projects)
- IDE indexes refresh
- Running processes (servers, watchers) break
- Unsaved or uncommitted work must be stashed or committed
With worktrees, each branch lives in its own directory. You can keep a server running on develop while writing a feature in another directory.
Parallel work on multiple features
Need to fix a bug on main while in the middle of a feature? Open a new terminal, cd to the main worktree, fix it, push, and go back. No interruption.
Safer code reviews
Check out a colleague’s PR branch in a fresh worktree. Build it, run tests, poke around — all without disturbing your own branch.
Shared git history
All worktrees share one object database. Fetches, gc, and rerere data are shared. Disk usage is minimal compared to multiple clones.
Quick Reference
List existing worktrees
git worktree list
Example output:
/home/user/projects/my-app a1b2c3d [feature/auth]
/home/user/projects/my-app-hotfix d4e5f6g [hotfix/login-bug]
/home/user/projects/my-app-refactor h7i8j9k [refactor/db-layer]
Create a worktree from an existing branch
# Fetch the branch if it's remote-only
git fetch origin <branch-name>
# Create the worktree
git worktree add ../<directory-name> <branch-name>
Create a worktree with a new branch
git worktree add -b <new-branch> ../<directory-name> <base-branch>
Initialize submodules in the new worktree
Submodules are not automatically initialized in new worktrees. After creating one:
cd ../<directory-name>
git submodule update --init --recursive
Remove a worktree
# Delete the directory
rm -rf ../<directory-name>
# Clean up git's internal tracking
git worktree prune
Or in one step:
git worktree remove ../<directory-name>
Practical Example
Suppose you’re working on feature/auth in my-app/ and need to start work on feature/notifications:
# From within any existing worktree of the repo
cd my-app/
# Fetch and create the worktree
git fetch origin feature/notifications
git worktree add ../my-app-notifications feature/notifications
# Initialize submodules (if your project uses them)
cd ../my-app-notifications
git submodule update --init --recursive
Now you have two fully independent working directories:
my-app/ → feature/auth
my-app-notifications/ → feature/notifications
Each can have its own IDE window, running server, and build artifacts without interfering with the other.
Example Project
To make this concrete, here is a minimal C++ CMake project you can use to try worktrees yourself. It uses Conan 2 for dependency management and Catch2 for tests.
Project structure
my-cmake-app/
├── CMakeLists.txt
├── conanfile.txt
├── build.sh
├── .gitignore
├── src/
│ └── main.cpp
└── tests/
└── test_example.cpp
Files
CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(CMakeConanBoilerplate CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Catch2 3 REQUIRED)
add_executable(sample src/main.cpp)
add_executable(tests tests/test_example.cpp)
target_link_libraries(tests PRIVATE Catch2::Catch2WithMain)
include(CTest)
add_test(NAME ExampleTest COMMAND tests)
conanfile.txt
[requires]
catch2/3.5.0
[generators]
CMakeDeps
build.sh
#!/usr/bin/env bash
set -euo pipefail
BUILD_DIR="cmake-build-debug"
BUILD_TYPE="Debug"
while [[ $# -gt 0 ]]; do
case $1 in
--debug) BUILD_DIR="cmake-build-debug"; BUILD_TYPE="Debug"; shift ;;
--release) BUILD_DIR="cmake-build-release"; BUILD_TYPE="Release"; shift ;;
*) echo "Usage: $0 [--debug | --release]"; exit 1 ;;
esac
done
mkdir -p "$BUILD_DIR"
cd "$BUILD_DIR"
conan install .. -g CMakeDeps -s build_type=$BUILD_TYPE --output-folder=. --build=missing
cmake .. \
-DCMAKE_BUILD_TYPE=$BUILD_TYPE \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++
cmake --build . -- -j$(nproc)
echo "Build completed in $(pwd)"
src/main.cpp
#include <iostream>
int main(int ac, const char** av) {
std::cout << "Hello!\n";
return 0;
}
tests/test_example.cpp
#include <catch2/catch_all.hpp>
TEST_CASE("Simple addition", "[math]") {
REQUIRE(1 + 1 == 2);
}
Try it with worktrees
# Clone and build
git clone <your-repo-url> my-cmake-app
cd my-cmake-app
./build.sh --debug
# Now create a worktree for a new feature
git worktree add ../my-cmake-app-feature feature/new-parser
cd ../my-cmake-app-feature
./build.sh --debug
# Both directories build independently
# Edit code in either one without affecting the other
Notice how each worktree gets its own cmake-build-debug/ directory — builds are completely isolated.
Worktrees vs. Multiple Clones
| Worktrees | Multiple Clones | |
|---|---|---|
| Disk usage | Shared object store — minimal overhead | Full copy of history per clone |
| Fetching | Fetch once, all worktrees see it | Must fetch in each clone separately |
| Branch locking | A branch can only be checked out in one worktree | No restriction |
| Setup | One command | Clone + configure remotes + submodules each time |
Tips
- Naming convention — Use a short, recognizable prefix:
<project>-<feature>or<project>-<ticket-id>. This keeps the parent directory tidy andgit worktree listoutput readable. - IDE support — Most IDEs (CLion, VS Code, GoLand, IntelliJ) can open each worktree directory as a separate project with no issues.
- Don’t check out the same branch twice — Git prevents this by default. If you need to, use
git worktree add --detach. - Pruning — If you delete a worktree directory manually, run
git worktree pruneto clean up stale references. - Submodules — Always remember
git submodule update --init --recursiveafter creating a new worktree. This is easy to forget and will cause build failures. - Build artifacts — Each worktree gets its own working directory, so build caches and output are isolated. This is a feature, not a bug — it means builds in one worktree won’t corrupt another.
This article was generated with the assistance of AI and reviewed by a human.
