<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>git · wleeaf field notes</title><link>https://articles.wleeaf.dev/tags/git/</link><description>Hands-on guides and build logs from the wleeaf studio: self-hosting, Linux, networking, Docker, the command line and reverse engineering.</description><language>en-us</language><lastBuildDate>Mon, 30 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://articles.wleeaf.dev/tags/git/feed.xml" rel="self" type="application/rss+xml"/><item><title>Git Worktrees Changed How I Handle Branches</title><link>https://articles.wleeaf.dev/posts/git-worktrees/</link><pubDate>Mon, 30 Mar 2026 00:00:00 +0000</pubDate><guid>https://articles.wleeaf.dev/posts/git-worktrees/</guid><description>Switching branches without stashing, losing context, or waiting for node_modules to reinstall.</description><category>git</category><category>workflow</category><category>productivity</category><content:encoded><![CDATA[<p>The worst part of working on multiple branches is the context switching. You are deep in a feature branch, someone asks you to review a hotfix, and now you have to stash your changes, switch branches, wait for your build tools to catch up, do the review, switch back, pop the stash, and hope nothing got lost.</p>
<p>Git worktrees fix this entirely: they let you check out several branches at once, each in its own directory, all sharing one repository.</p>
<h2 id="what-is-a-worktree">What Is a Worktree?<a class="anchor" href="#what-is-a-worktree" aria-label="Link to “What Is a Worktree?”">#</a></h2>
<p>A worktree is a separate working directory that shares the same <code>.git</code> data. Each worktree can have a different branch checked out simultaneously:</p>
<div class="code">
<div class="code-head"><span class="code-lang">bash</span><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># You are on main in /home/user/project</span>
</span></span><span class="line"><span class="cl">git worktree add ../project-hotfix hotfix/urgent-fix</span></span></code></pre></div>
</div>
<p>Now you have two directories:</p>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code>/home/user/project          → main branch
/home/user/project-hotfix   → hotfix/urgent-fix branch</code></pre>
</div>
<p>Both share the same git history. Commits in one are visible in the other. But they have completely independent working directories.</p>
<h2 id="the-workflow">The Workflow<a class="anchor" href="#the-workflow" aria-label="Link to “The Workflow”">#</a></h2>
<p>My typical day looks like this:</p>
<div class="code">
<div class="code-head"><span class="code-lang">bash</span><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Main work</span>
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> ~/projects/app
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Someone needs a review - create a worktree</span>
</span></span><span class="line"><span class="cl">git worktree add ~/reviews/pr-142 origin/feature/new-api
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Review in a completely separate directory</span>
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> ~/reviews/pr-142
</span></span><span class="line"><span class="cl">npm install  <span class="c1"># only needed first time</span>
</span></span><span class="line"><span class="cl">npm <span class="nb">test</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Done reviewing, remove it</span>
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> ~/projects/app
</span></span><span class="line"><span class="cl">git worktree remove ~/reviews/pr-142</span></span></code></pre></div>
</div>
<p>The key advantage: my main working directory is completely untouched. No stashing, no branch switching, no reinstalling dependencies.</p>
<h2 id="practical-patterns">Practical Patterns<a class="anchor" href="#practical-patterns" aria-label="Link to “Practical Patterns”">#</a></h2>
<h3 id="parallel-testing">Parallel testing<a class="anchor" href="#parallel-testing" aria-label="Link to “Parallel testing”">#</a></h3>
<p>Run tests on two branches at the same time:</p>
<div class="code">
<div class="code-head"><span class="code-lang">bash</span><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git worktree add /tmp/test-main main
</span></span><span class="line"><span class="cl">git worktree add /tmp/test-feature feature/new-thing
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># In two terminals</span>
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> /tmp/test-main <span class="o">&amp;&amp;</span> npm <span class="nb">test</span>
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> /tmp/test-feature <span class="o">&amp;&amp;</span> npm test</span></span></code></pre></div>
</div>
<h3 id="long-running-branches">Long-running branches<a class="anchor" href="#long-running-branches" aria-label="Link to “Long-running branches”">#</a></h3>
<p>If you maintain a staging branch alongside main:</p>
<div class="code">
<div class="code-head"><span class="code-lang">bash</span><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git worktree add ~/staging staging</span></span></code></pre></div>
</div>
<p>Keep it around permanently. Push to staging by working in that directory. No branch switching ever.</p>
<h3 id="ci-simulation">CI simulation<a class="anchor" href="#ci-simulation" aria-label="Link to “CI simulation”">#</a></h3>
<p>Test your CI pipeline locally against a clean checkout:</p>
<div class="code">
<div class="code-head"><span class="code-lang">bash</span><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git worktree add /tmp/ci-test HEAD
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> /tmp/ci-test
</span></span><span class="line"><span class="cl">npm ci
</span></span><span class="line"><span class="cl">npm run lint
</span></span><span class="line"><span class="cl">npm <span class="nb">test</span>
</span></span><span class="line"><span class="cl">npm run build</span></span></code></pre></div>
</div>
<p>This catches the classic &ldquo;works on my machine&rdquo; issues where you have uncommitted files or stale build artifacts that mask problems.</p>
<h2 id="managing-worktrees">Managing Worktrees<a class="anchor" href="#managing-worktrees" aria-label="Link to “Managing Worktrees”">#</a></h2>
<div class="code">
<div class="code-head"><span class="code-lang">bash</span><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># List all worktrees</span>
</span></span><span class="line"><span class="cl">git worktree list
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Output:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># /home/user/project         abc1234 [main]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># /home/user/project-hotfix  def5678 [hotfix/urgent-fix]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># /tmp/ci-test               abc1234 (detached HEAD)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Remove a worktree</span>
</span></span><span class="line"><span class="cl">git worktree remove /home/user/project-hotfix
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Clean up stale worktree references</span>
</span></span><span class="line"><span class="cl">git worktree prune</span></span></code></pre></div>
</div>
<h2 id="gotchas">Gotchas<a class="anchor" href="#gotchas" aria-label="Link to “Gotchas”">#</a></h2>
<p>You cannot check out the same branch in two worktrees. Git prevents this because having the same branch in two places would make it ambiguous which working directory a commit applies to.</p>
<p>If you need to work on the same branch in two places, create a local tracking branch:</p>
<div class="code">
<div class="code-head"><span class="code-lang">bash</span><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git worktree add ../experiment experiment-copy
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> ../experiment
</span></span><span class="line"><span class="cl">git checkout -b experiment-local origin/experiment</span></span></code></pre></div>
</div>
<p>The other thing to watch out for is node_modules. Each worktree needs its own <code>node_modules</code> because paths are different. The first <code>npm install</code> in a new worktree takes time. After that, it is fast because the npm cache is shared.</p>
<h2 id="vs-git-stash">vs. git stash<a class="anchor" href="#vs-git-stash" aria-label="Link to “vs. git stash”">#</a></h2>
<p>Stash is fine for quick interruptions. If someone asks you a question and you need to check something on another branch for thirty seconds, stash works.</p>
<p>But for anything that takes more than a minute, worktrees are better. They do not mess with your working directory, they let you have multiple contexts open in different editor windows, and they do not have the failure mode where you forget to pop the stash and lose track of what you were doing.</p>
<p>I keep a <code>~/worktrees/</code> directory for temporary ones and let them accumulate during the week. Friday afternoon I run <code>git worktree list</code> and clean up anything I am done with.</p>
]]></content:encoded></item></channel></rss>