I've been using coding agents a lot lately, usually with several tasks going on in parallel. Each gets a Git worktree so I can leave things open without switching branches all day. It works well. Except my disk kept filling up.
Every new worktree meant another checkout, and then there were the dependencies, generated files and build output. I started looking for a way to make those directories cheaper to keep around. I eventually wrote Parhel, a small Git extension in Rust that creates worktrees using copy-on-write (CoW).
Where the disk space goes
Git worktrees let you check out multiple branches of one repository into separate directories. I use them to keep a feature open while looking at something else, or to give different agents their own files to work on.
For example:
git worktree add -b feature/search ../my-app-search
Git already shares the repository's object database and most of its metadata. So a worktree isn't a full second Git clone. But Git checks out the working files again, and a fresh worktree doesn't bring along ignored directories such as:
node_modules/.venv/-
target/in a Rust project - Compiled assets, generated code, and local caches
The usual next step is to install dependencies again or copy over the files you need. Do that across enough worktrees and the storage adds up. Package-manager caches help with part of it, but the generated output and other project files still need somewhere to live.
If you're curious about exactly what Git shares, its worktree documentation covers the layout.
How copy-on-write helps
Say you have a large node_modules/ directory and a few hundred megabytes of compiled output. You want another worktree, preferably with all of that ready to use.
Copying normally means writing all those bytes again. With CoW, the new files initially share data blocks with the originals. If you modify one of them, the filesystem allocates new blocks for what changed. You can edit the two copies independently; they just happen to share storage while their contents are the same.
There's some overhead for metadata, and the savings shrink as files diverge. But if you're mainly creating worktrees and editing source code while dependencies and build output stay largely unchanged, that's a useful tradeoff.
On Linux, the mechanism is commonly called a reflink. The FICLONE documentation describes how separate files can share storage until a write requires a copy.
You may not need anything beyond git worktree add. If your worktrees are small, there's little to gain. A shared package cache can also reduce how much a dependency installation costs. I wanted to reuse the working directory as a whole, including ignored files that aren't managed by a package manager.
Trying it with Parhel
Parhel is a Git external command, so after installing it you use git parhel. It creates an ordinary Git worktree from the directory you're working in, with CoW cloning wherever the filesystem allows it.
It brings the ignored directories too. You can still use git worktree list, move, lock and remove afterward. Nothing about the Git side of the workflow changes.
Install
On macOS with Homebrew:
brew install myrk-kale/tap/parhel
Or with Rust's package manager on macOS or Linux:
cargo install parhel
The Cargo installation requires Rust 1.85 or newer, and Parhel needs Git 2.29 or newer. There are also Linux .deb, .rpm, and release archives in the installation instructions.
Check that Git can find the command:
git parhel --version
Create a worktree
From your existing project:
git parhel add ../my-app-search --new-branch feature/search
Internally, Parhel registers a worktree with Git and clones the files from the source directory. The new worktree gets the dependencies and output that were already there, so in many projects you can just start working.
By default, Parhel expects a clean source worktree. If you intentionally want to carry over uncommitted changes and other untracked files, use --allow-dirty:
git parhel add ../my-app-experiment --new-branch experiment --allow-dirty
Changes that were staged in the source arrive unstaged in the new worktree.
Check your filesystem
On macOS, APFS already supports cloning. On Linux, Btrfs and XFS with reflink support can do it directly. ext4 needs a little more explanation.
| System | How Parhel uses CoW |
|---|---|
| macOS on APFS | Native filesystem cloning via clonefile
|
| Linux on Btrfs | Reflink cloning |
| Linux on XFS with reflink enabled | Reflink cloning |
| Linux on ext4 | A one-time setup creates a separate Btrfs-backed CoW area |
To check what Parhel can use on your machine:
git parhel doctor
On Linux, the doctor command can tell you when the directory's filesystem doesn't support reflinks. XFS needs to have reflinks enabled when the filesystem was created.
Ext4 needs a separate CoW area
ext4 doesn't support native reflinks. To make CoW work there, Parhel can create a Btrfs filesystem inside a sparse image and mount it as a separate area for your user. You set it up once:
sudo git parhel setup
This needs systemd, Btrfs support, and the tools listed in the Parhel documentation. It also makes system-level changes, so I recommend reading that part before running the command.
The first run copies the source into the Btrfs area as a baseline. That initial copy takes time and uses real space. Later worktrees share unchanged blocks with that baseline, and the path you asked for becomes a symlink to the worktree inside the store. ext4 itself hasn't gained CoW support; the sharing happens inside Btrfs.
You can also use --copy to make a full copy on any filesystem, or --fallback to use a regular Git worktree without copying the ignored files. Neither option saves disk space through CoW, but both work without setting up the store.
What I measured
I ran a benchmark on macOS with 78 MB of node_modules (about 20,000 files) and a 300 MB generated artifact. I compared getting a regular worktree ready by copying the ignored files over with creating one through Parhel.
| Creating a ready-to-use worktree | Time | Additional disk space |
|---|---|---|
git worktree add, followed by cp -R for dependencies and output |
6.9 s | 383 MiB |
git parhel add |
0.5 s | About 8 MiB |
The difference was pretty striking in that project. A plain git worktree add wouldn't copy the ignored directories in the first place, so the baseline includes cp -R to get both worktrees ready with the same files. You can find the details in the README; your results will depend on the files and filesystem involved.
A note about du
One thing that can be misleading: Finder and du -sh may report the full size of both directories after cloning. The files have their own logical sizes even when they're sharing blocks on disk.
I check how much free space changed before and after creating a worktree:
df -h .
# Create a worktree on the same filesystem.
df -h .
On ext4, remember to check the filesystem backing the store too. The initial baseline is a real copy, and the destination path points into the mounted Btrfs area.
Where you still need to be careful
.env files and external services
Parhel copies ignored files, which can include local credentials and .env files. If two worktrees contain the same database URL, they'll connect to the same database. A migration from one worktree can affect the other. That's worth thinking about before giving several agents their own working directories.
If a file needs different values per worktree, exclude it with .parhelignore and create a separate configuration in the destination:
.env
.env.*
.parhelignore uses Gitignore-style patterns and applies to untracked files Parhel would otherwise carry over. Git still controls tracked files.
Paths inside virtual environments
A Python virtual environment can contain scripts pointing to the directory where it was created. Symlinks and generated configuration from other tools can have the same problem.
Parhel rewrites source paths in untracked text files where it can and adjusts relevant symlinks. It leaves binary files alone because patching an embedded path could corrupt them. Some Conda environments, for example, are better left out through .parhelignore and rebuilt when needed.
The mechanism is generic. Parhel doesn't need to know whether a directory belongs to Python, Node.js, or another tool.
Removing a worktree
Git doesn't ask about ignored files when removing a worktree. If you had a local database or generated results in there, those can disappear along with it.
I recommend checking first:
git -C ../my-app-search status --ignored --short
For a normal CoW worktree, removal is the usual Git command:
git worktree remove ../my-app-search
Worktrees in the ext4-backed store are locked so Git won't forget them when the mount is unavailable. To remove one of those:
git worktree unlock ../my-app-search
git worktree remove ../my-app-search
git parhel doctor --fix
There are other edge cases, including sparse checkouts and submodules, covered in the README. I'd read those before using it on a repository you can't afford to disrupt.
I mostly use it with coding agents
The reason I started this was the number of parallel tasks I keep open with coding agents. I wanted to create another worktree and let an agent get on with its task without waiting for me to rebuild the environment.
Parhel works without an agent. If you use one, there's an optional skill in the repository that teaches it when to use Parhel and which details to check before copying or removing files:
npx skills add myrk-kale/parhel
You can install it globally with -g as well. The skill covers things like .env files and ignored content, which are easy to overlook when you're moving quickly.
I've kept Parhel focused on worktree creation. It doesn't replace the Git workflow I already have, and it should be just as useful if you don't use coding agents at all.
It's still a 0.x project. There are more than 430 tests covering Git operations and real filesystem behavior, but I'm sure there are setups I haven't seen yet. I've documented the tests, filesystem implementation, and known limitations in the repository.
If you've been dealing with the same disk space problem, I'd be interested to hear how you've handled it, especially if your worktrees have something Parhel won't copy safely.
Top comments (0)