How to Create Git Worktrees and Manage Multiple Branches
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
Recommended Worktree Setup Commands
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.