12 minute read

Overview

This guide documents how git worktree can make a multi-branch workflow cleaner, safer, and easier to reason about.

The core problem was that I was switching between several active branches inside one local folder. Each branch had different files, different build outputs, different staging needs, and different responsibilities.

That created confusion because the same folder kept changing shape every time I switched branches.

A more reasonable model is to let each major branch have its own working folder.

one Git repository
→ multiple working folders
→ each folder can stand on a different branch
→ less switching issues
→ cleaner testing and deployment workflow

Situation

I was working across several active branches in the private DataInsideData repo:

main
→ stable base branch

feat/add-post
→ posts, how-tos, and fixes content

feat/community-projects
→ Builder Showcase platform work

feat/builder-staging
→ staging deploy workflow and public test site hardening

integration/did-merge
→ combined release candidate branch

staging/builder-showcase-campaign
→ public testing campaign branch for Builder Showcase form submissions
  • Switching between these branches in one folder caused extra noise.
  • Sometimes generated folder/files stayed behind.
  • Sometimes folders/files existed in one branch but not another.
  • Sometimes folders/files will gets deleted during branch switching.

It became difficult to know whether I was looking at real branch work, generated output, ignored files, or residue from a previous checkout.

Task

I needed a cleaner way to work across multiple branches without constantly reshaping one folder.

The goal was to create a workflow where each major branch could have its own local folder, while still sharing the same Git repository history.

That way, I could work on posts, Builder Showcase, staging, and integration without constantly switching one working directory back and forth.

What a Working Tree Means

A working tree is the folder of files you are actively editing.

When I open a repo in VS Code, I am usually looking at one working tree.

Example:

datainsidedata-website/

That folder contains the checked-out files for the branch I am currently standing on.

When I run:

git switch feat/add-post

Git changes that same folder to match the feat/add-post branch.

When I run:

git switch feat/community-projects

Git changes that same folder again to match the feat/community-projects branch.

That works, but it can become messy when branches have very different file structures.

What Git Worktree Adds

git worktree lets one Git repository have multiple working folders.

Instead of switching one folder between branches, I can create separate folders like this:

datainsidedata-website/
→ main or default workspace

datainsidedata-add-post/
→ feat/add-post

datainsidedata-community-projects/
→ feat/community-projects

datainsidedata-builder-staging/
→ feat/builder-staging

datainsidedata-integration/
→ integration/did-merge

datainsidedata-staging-campaign/
→ staging/builder-showcase-campaign

Each folder can be opened in a separate VS Code window.

Each folder can stay on its own branch.

That is the big unlock.

Why This Helps

Worktrees reduce confusion because the folder no longer has to transform every time I switch branches.

Instead of this:

one folder
→ switch branch
→ folder changes shape
→ generated files remain
→ ignored files remain
→ branch-specific folders appear or disappear

I get this:

one folder per major branch
→ each branch keeps its own working directory
→ less accidental carryover
→ easier local testing
→ easier staging deployments

Important Mental Model

A branch is a line of commits.

A working tree is a folder where one branch is checked out.

A worktree is an additional working folder connected to the same Git repository.

That means a worktree is not a copy-paste backup.

It is an official Git-managed checkout.

Why Copying Folders Was Confusing

Copying an entire repo folder manually can create confusion because Git does not know the intent behind the copy.

Manual copying can overwrite files, carry generated folders, duplicate ignored files, or mix work from branches that should stay separate.

git worktree is better because Git manages the relationship between the folders and the branches.

Instead of this:

copy entire folder manually
paste into another backup folder
switch branches
copy files back
wonder what changed

Use this:

git worktree add ../datainsidedata-add-post feat/add-post

Now Git creates a real branch-specific workspace.

Check Existing Worktrees

Before creating new worktrees, check what already exists:

git worktree list

Example output:

C:/Users/oneps/.../datainsidedata-website                 abc1234 [main]
C:/Users/oneps/.../datainsidedata-add-post                def5678 [feat/add-post]
C:/Users/oneps/.../datainsidedata-builder-staging         789abcd [feat/builder-staging]

This command shows which folders are connected to which branches.

Create a Worktree for an Existing Local Branch

From the main repo folder, create a separate folder for an existing branch:

git worktree add ../datainsidedata-add-post feat/add-post

Create one for Builder Showcase:

git worktree add ../datainsidedata-community-projects feat/community-projects

Create one for staging:

git worktree add ../datainsidedata-builder-staging feat/builder-staging

Create one for integration:

git worktree add ../datainsidedata-integration integration/did-merge

Each folder can now be opened separately in VS Code.

Open a Worktree in VS Code

Open the new worktree folder:

code ../datainsidedata-add-post

Or:

code ../datainsidedata-builder-staging

This creates a cleaner workflow because each VS Code window is tied to a specific branch folder.

Create a New Branch and Worktree Together

Sometimes the branch does not exist yet.

For example, a staging campaign branch:

git worktree add -b staging/builder-showcase-campaign ../datainsidedata-staging-campaign main

This means:

Create a new branch named staging/builder-showcase-campaign
starting from main
and check it out into ../datainsidedata-staging-campaign

Then push it:

cd ../datainsidedata-staging-campaign
git push -u origin staging/builder-showcase-campaign

Create a Worktree from a Remote Branch

If the branch exists on GitHub but not locally, fetch first:

git fetch origin

Then create a local branch and worktree from the remote branch:

git worktree add -b feat/add-post ../datainsidedata-add-post origin/feat/add-post

This is useful when the remote branch exists but the local branch does not.

Important Rule About Branches and Worktrees

Git usually does not allow the same branch to be checked out in two worktrees at the same time.

That means if feat/add-post is already checked out in:

datainsidedata-add-post/

Git will usually stop me from also checking out feat/add-post in:

datainsidedata-website/

That is a safety feature.

It prevents two folders from editing the same branch at the same time and confusing the branch state.

Suggested Folder Layout

A clean local layout could look like this:

DataEden_Github/
  datainsidedata-website/
  datainsidedata-add-post/
  datainsidedata-community-projects/
  datainsidedata-builder-staging/
  datainsidedata-integration/
  datainsidedata-staging-campaign/

Each folder has one job.

datainsidedata-website/
→ main or general admin workspace

datainsidedata-add-post/
→ content work

datainsidedata-community-projects/
→ Builder Showcase feature work

datainsidedata-builder-staging/
→ staging workflow hardening

datainsidedata-integration/
→ combined release candidate testing

datainsidedata-staging-campaign/
→ public testing campaign branch

DataInsideData Branch Map

The branch map can look like this:

feat/add-post
→ write and update content

feat/community-projects
→ build Builder Showcase features

feat/builder-staging
→ maintain staging deploy workflow

staging/builder-showcase-campaign
→ run public testing campaign

integration/did-merge
→ combine approved work for release testing

main
→ protected production-ready base

From the original private repo folder:

git worktree list

Create content worktree:

git worktree add ../datainsidedata-add-post feat/add-post

Create Builder Showcase worktree:

git worktree add ../datainsidedata-community-projects feat/community-projects

Create staging worktree:

git worktree add ../datainsidedata-builder-staging feat/builder-staging

Create integration worktree:

git worktree add ../datainsidedata-integration integration/did-merge

Create staging campaign worktree if needed:

git worktree add -b staging/builder-showcase-campaign ../datainsidedata-staging-campaign main

Working in a Feature Worktree

Open the feature worktree:

cd ../datainsidedata-community-projects
code .

Work normally:

git status
git add .
git commit -m "Update Builder Showcase submit form"
git push

This keeps Builder Showcase work in its own folder and branch.

Working in the Content Worktree

Open the content worktree:

cd ../datainsidedata-add-post
code .

Work normally:

git status
git add .
git commit -m "Add Git branch switching fixes guide"
git push

This keeps posts, how-tos, and fixes separate from Builder Showcase work.

Working in the Staging Worktree

Open the staging worktree:

cd ../datainsidedata-builder-staging
code .

Work on staging deployment files:

.github/workflows/deploy-staging.yml
_config_staging.yml
staging-only checks
public deploy exclusions

Commit and push:

git status
git add .
git commit -m "Harden staging deploy workflow"
git push

Working in the Integration Worktree

Open the integration worktree:

cd ../datainsidedata-integration
code .

Bring in feature branch work:

git merge feat/add-post
git merge feat/community-projects
git merge feat/builder-staging

Test locally:

bundle exec jekyll build
bundle exec jekyll build --config _config.yml,_config_staging.yml --destination _site_staging_full

Push integration:

git push origin integration/did-merge

Deploy staging from integration:

gh workflow run deploy-staging.yml --ref integration/did-merge

Staging Campaign Worktree

A staging campaign branch can exist separately from the production candidate.

This is useful when testing a public LinkedIn outreach or form submission campaign.

staging/builder-showcase-campaign
→ public testing branch

integration/did-merge
→ release candidate branch

main
→ final protected branch

Deploy staging from the campaign branch:

cd ../datainsidedata-staging-campaign
gh workflow run deploy-staging.yml --ref staging/builder-showcase-campaign

This allows public testing without forcing the campaign branch to merge into main.

Worktree and Ignored Files

Each worktree has its own working folder.

That means ignored files like .env are not automatically shared across all worktrees.

If a worktree needs local secrets, it may need its own .env.

Example:

datainsidedata-community-projects/.env
datainsidedata-builder-staging/.env
datainsidedata-integration/.env

Because .env is ignored, it should stay out of Git.

A safe pattern is:

.env
→ ignored and manually backed up

.env.example
→ tracked as a template

Worktree and Generated Files

Generated files are also per worktree.

Each worktree may create its own:

_site/
_site_staging_full/
.jekyll-cache/
.sass-cache/

That is okay.

Those should stay ignored and rebuildable.

If a generated folder causes confusion, delete it inside that worktree and rebuild.

Remove-Item -Recurse -Force .\_site_staging_full
Remove-Item -Recurse -Force .\.jekyll-cache

Then rebuild:

bundle exec jekyll build --config _config.yml,_config_staging.yml --destination _site_staging_full

Worktree and Dependencies

Depending on the project setup, each worktree may also need its own dependency install or bundle path.

For a Jekyll project, if dependencies are managed outside the repo globally, the worktree may already build.

If local vendor folders are used, each worktree may need setup.

Useful check:

bundle install
bundle exec jekyll build

Keep dependency folders ignored:

vendor/
node_modules/

Worktree and Stashing

Worktrees reduce the need to stash because I do not have to constantly switch one folder between branches.

But stash is still useful inside a specific worktree if I have unfinished work.

Example:

git stash push -u -m "WIP before testing integration merge"

Then later:

git stash list
git stash apply stash@{0}

The difference is that with worktrees, stash becomes less of a daily branch-switching crutch and more of a temporary safety tool.

Worktree and Merging

The merge rule stays the same.

Stand on the branch that should receive the changes.

Example from the integration worktree:

cd ../datainsidedata-integration
git merge feat/add-post

This means:

integration/did-merge receives feat/add-post

Then:

git merge feat/community-projects

This means:

integration/did-merge receives feat/community-projects

Worktrees do not change Git merge logic. They make it easier to stand in the right place.

Worktree and Pull Requests

Worktrees also support a cleaner PR process.

Example release flow:

feat/add-post
→ merged into integration/did-merge

feat/community-projects
→ merged into integration/did-merge

feat/builder-staging
→ merged into integration/did-merge

integration/did-merge
→ PR into main

Open a PR:

cd ../datainsidedata-integration
gh pr create --base main --head integration/did-merge

After merge, update the main worktree:

cd ../datainsidedata-website
git switch main
git pull origin main

Updating Worktrees

Each worktree is its own working folder, so update it from inside that folder.

Example:

cd ../datainsidedata-add-post
git pull

Another:

cd ../datainsidedata-community-projects
git pull

For integration:

cd ../datainsidedata-integration
git pull

If the branch has no upstream yet:

git push -u origin branch-name

Listing Worktrees

Check all connected worktrees:

git worktree list

This helps prevent confusion about which branch is checked out where.

Removing a Worktree

When a worktree is no longer needed, remove it safely:

git worktree remove ../datainsidedata-staging-campaign

If the folder was manually deleted, clean up Git’s worktree records:

git worktree prune

Use prune when Git still remembers a worktree folder that no longer exists.

Moving a Worktree

If a worktree folder needs to move:

git worktree move ../old-folder-name ../new-folder-name

This keeps Git aware of the new location.

Best Practices

Use worktrees for long-lived or high-context branches.

Good candidates:

content branch
Builder Showcase branch
staging branch
integration branch
campaign branch

Avoid using worktrees for tiny one-line changes unless the overhead is worth it.

Keep branch names clear.

Examples:

feat/add-post
feat/community-projects
feat/builder-staging
integration/did-merge
staging/builder-showcase-campaign

Keep worktree folder names clear.

Examples:

datainsidedata-add-post
datainsidedata-community-projects
datainsidedata-builder-staging
datainsidedata-integration
datainsidedata-staging-campaign

Do not manually copy entire repo folders as a branch-management strategy.

Use git worktree add instead.

Run git worktree list often.

Keep .env backed up manually.

Keep generated output ignored.

Commit and push inside the worktree where the work belongs.

Common Mistakes

Trying to check out the same branch in two worktrees:

Git usually blocks this.

Forgetting which folder is on which branch:

git branch --show-current

Assuming .env is shared:

It is not automatically shared across worktrees.

Assuming generated files matter:

They can usually be rebuilt.

Deleting a worktree folder manually and forgetting to prune:

git worktree prune

Copying folders manually instead of using Git-managed worktrees:

This can recreate the original confusion.

When Worktrees Are Better Than Switching Branches

Worktrees are better when:

branches have different file structures
generated files cause branch-switching noise
multiple features are active at the same time
one branch powers a staging campaign
one branch prepares integration releases
different VS Code windows are useful

When Normal Branch Switching Is Still Fine

Normal branch switching is fine when:

the change is small
the branches have similar file structures
there is no generated output confusion
the working tree is clean
only one feature is active

Result

Using worktrees gives the DataInsideData workflow a cleaner structure.

Instead of constantly switching one folder between unrelated branches, each major branch can live in its own workspace.

That makes it easier to:

write content
build Builder Showcase
maintain staging
test integration
run a public campaign
protect main

The biggest lesson is that worktrees are not just convenience. They are workflow architecture.

STAR Summary

Situation

I was switching between multiple DataInsideData branches and seeing files carry over, disappear, or show up unexpectedly in VS Code Source Control.

Task

I needed a cleaner way to manage branch-specific work without constantly reshaping one local folder.

Action

I learned to use git worktree so each major branch could have its own local folder and VS Code window.

Result

The workflow became easier to reason about.

Feature work, staging work, content work, integration testing, and campaign testing could happen in separate spaces while still sharing one Git repository history.

Final Mental Model

Branch
→ a line of commits

Working tree
→ the folder currently checked out for editing

Git worktree
→ an additional Git-managed working folder for another branch

Feature worktree
→ focused development

Staging worktree
→ public testing preparation

Integration worktree
→ release candidate testing

Main worktree
→ stable base

Final Worktree Workflow

Create a worktree for each major branch.
Open each worktree in its own VS Code window.
Commit work inside the correct worktree.
Merge feature branches into the integration worktree.
Deploy staging from the campaign or integration branch.
Open a PR from integration into main.
Pull main after the PR is merged.
Remove old worktrees when they are no longer needed.

Quick Command Reference

List worktrees:

git worktree list

Add an existing branch as a worktree:

git worktree add ../folder-name branch-name

Create a new branch and worktree:

git worktree add -b new-branch-name ../folder-name main

Remove a worktree:

git worktree remove ../folder-name

Clean up stale worktree records:

git worktree prune

Check current branch:

git branch --show-current

Open in VS Code:

code ../folder-name

This turns multi-branch development from one shifting folder into a set of clean, purpose-built workspaces.

Updated: