git worktree: Branches as Folders

In short. You run git stash every time you move to another branch. Then you sit through a full rebuild when you come back. This post removes that waste. A worktree turns a branch switch into a folder change. Three commands get you started, and three traps are worth knowing before you do.

Dotori Bluetooth Numpad - Your phone as a 17-key number pad

A free app I built myself. Give it a try.

The cost of switching is in the build output, not the source

git switch swaps tracked files and nothing else. The expensive things are what sits beside them: obj/, bin/, node_modules/. When the whole source tree changes, the incremental build is invalidated. Moving to another branch to check one line then costs you minutes. Work in progress has to be pushed into a stash, and popping it afterwards brings conflicts.

Cloning the repository a second time solves that, but it duplicates the history and the objects too. A worktree attaches several working folders to one repository. Objects and refs are shared, and only the checkout is separate. It has been built in since Git 2.5 on 29 July 2015, with add, list and prune. There is nothing to install.

ApproachHistoryBuild cacheParallel work
stash + switchOne copyInvalidated on every switchNo
Extra cloneDuplicated per folderKeptYes
worktreeSharedKeptYes

Add more clones and you have to run fetch in each folder. A branch created in one of them is invisible in the others. A worktree shares refs, so a branch you created next door shows up right where you are.

Three commands to start

git worktree add ../hotfix -b hotfix   # a new branch and a folder in one go
git worktree list                      # which branch is attached to which folder
git worktree remove ../hotfix          # clean up the folder (the branch stays)

The first line creates the sibling folder ../hotfix and checks a new branch out into it. Your original folder is untouched. The build output and the file you were editing stay exactly as they were. To pull out an existing branch, drop -b: git worktree add ../review feature/login. list prints one line per folder with its sha and branch.

.../demo    dfd2991 [main]
.../hotfix  dfd2991 [hotfix]

There are only three traps

  1. The same branch cannot live in two folders. Check out a branch that is already attached and Git refuses with fatal: 'main' is already used by worktree at .... Branches and folders are one to one.
  2. remove deletes the folder only. The branch stays behind, so delete it separately with git branch -d. The other way round, if you deleted the folder in Explorer, the registration is left behind and git worktree prune clears it.
  3. A linked worktree's .git is a file, not a folder. It holds one line: gitdir: .../worktrees/hotfix. Scripts and tools that assume a .git directory and build paths from it break here.

The bare layout takes away the main folder's privilege

The default arrangement is "main repository plus attached folders". That gives the main one a special position. Delete it and the rest are orphaned. You also have to remember which folder in the list is the original. To put every folder on equal footing, hide a bare repository and give each branch one folder.

proj/
  .bare/          # the bare repository (no working files here)
  .git            # a file holding one line, "gitdir: ./.bare"
  master/         # folder = branch
  feature-login/

Fetch with git clone --bare <url> .bare and create the .git pointer file. Then produce the first folder with git worktree add master. From then on branch names and folder names are one to one. ls alone shows everything you have open.

Summary

The real cost of a branch switch is not swapping source but invalidating build output. A worktree separates the folders and takes that cost to zero. add, list and remove are all you need. Remember that a branch and a folder are one to one, and that .git is a file, and nothing goes wrong.

Run git worktree add ../hotfix -b hotfix once in the repository you have open right now. The next post uses these folders as parallel work slots and merges them all in one line at the end.