Dev Notes

Software Development Resources by David Egan.

Branch Management With Git Worktree


Git
David Egan

If you need to frequently switch between Git branches, the worktree feature can be a useful way of working since it allows you to check out more than one branch at a time.

It avoids the necessity of either committing or stashing changes before switching to a different branch because each branch is managed in a new working tree - i.e. worktree branches exist as a separate directory trees on your filesytem.

You can add a new worktree at any path - I find it convenient to add .worktrees to a global .gitignore and add worktree branches to a .wortrees subdirectory of the main project root.

Why Bother?

You’re deep in refactoring on a feature branch and you need to review a co-worker’s PR for a hotfix urgently.

You could add & commit your changes, but they are currently not in a satisfactory state and you are aiming to keep the project commit history as clean as possible. You could stash changes, change to the PR branch, and pop the stash when finished, but this is messy and can be confusing if you’re managing multiple stashes.

Adding the PR as a new worktree branch allows you to simply change directory into the new PR branch without worrying about disturbing complex ongoing changes in the branch you’re working on.

Basic Example: Add New Branch as a Worktree

git worktree add <path>

To add a hotfix worktree, from the project root:

git worktree add .worktrees/hotfix

This creates a new branch named hotfix - the final component of .worktrees/hotfix - and checks it out at the .worktrees/hotfix path.

Add a second worktree branch:

git worktree add .worktrees/feature-1

The project file structure now looks like this:

.
├── README.md
└── .worktrees
    ├── feature-1
    │   └── README.md
    └── hotfix
        └── README.md

The top level README.md is in the original branch.

At this point, running git branch from the top level of the project shows:

  dev
+ feature-1
+ hotfix
* master

This shows a branch dev that is not a worktree, along with two worktree branches feature-1 and hotfix marked with the + symbol.

The current checked-out branch master is marked with a *.

If you move into hotfix - cd .worktrees/hotfix, git branch will now show:

  dev
+ feature-1
* hotfix
+ master

In each case, the current branch is colorised and marked with an asterisk. The plus symbol indicates that you can access/work on the branch by moving in to the relevant directory.

In the example above, the dev branch does not have a linked worktree.

Work on Existing Branch in a New Worktree

To set up an existing branch as a worktree, run git worktree add <path> <branch>. For example:

git worktree add .worktrees/my-existing-branch my-existing-branch

A new working tree is created at .worktrees/my-existing-branch and my-existing-branch is checked out at this location.

Remove Worktrees

Run git worktree remove worktree-name - where worktree-name is the final component of the worktree path. If you’re unsure of the worktree name, git worktree list will list worktrees in the current repo:

git worktree list
/tmp/tmp.e4JD71KDKU                       0f4b170 [master]
/tmp/tmp.e4JD71KDKU/.worktrees/feature-1  3471bba [feature-1]
/tmp/tmp.e4JD71KDKU/.worktrees/hotfix     565c127 [hotfix]

To remove the hotfix worktree:

git worktree remove hotfix

The hotfix worktree is now removed, but the branch is still available should you still need it.

If you just delete the worktree directory without using git worktree remove, don’t panic - Git has you covered. Garbage collection will eventually remove the metadata files for the (now non-existent) worktree.

Working on Branches

To work on a worktree branch, move into the correct directory. Make changes as required and use git add and git commit as usual. When you’re finished, simply return to the original directory.

Fetch a PR GitHub and Set Up as a Worktree

Save this script as an executable file named gitfetch-pr in your PATH:

#!/usr/bin/env bash
# /path/gitfetch-pr
set -euo pipefail

function usage {
	cat <<- EOF
	Provide the PR reference number from within a Git project:
	$(basename $0) <PR reference number> 
	EOF
}

[[ $# != 1 ]] && { usage; exit 1; }
[[ -d "$PWD/.git" ]] || { echo "No .git directory detected."; usage; exit 2; }
[[ "${1}" =~ ^[0-9]+$ ]] || { echo "The PR reference should be an integer."; usage; exit 3; }

git fetch origin "pull/${1}/head:PR${1}"
echo "Done."

You can now run gitfetch-pr 42 to fetch the commit associated with pull request #42 and set this up as a branch PR42.

To set this up as a worktree, run:

git worktree add .worktrees/PR42 PR42

You can view the PR42 commit by navigating to .worktrees/PR42.

You could also set this up as a git alias rather than a shell script, as demonstrated in the following example.

Fetch & Checkout a GitLab Merge Request

This can be achieved with a git alias. Add the following to ~/.gitconfig:

[alias]
	# Allows: `git mr origin 5` to fetch & checkout MR 5 from origin
	mr = !sh -c 'git fetch $1 merge-requests/$2/head:mr-$1-$2 && git checkout mr-$1-$2' -

To use, run git mr origin 42 fetches the specified merge request into a local mr-upstream-42 branch.

To use this as a worktree:

git worktree add .worktrees/mr42 mr-origin-42

This adds the branch mr-origin-42 to the worktree directory .worktrees/mr42.

Using Standard Branch Comparison

You can still compare across branches and pull specific hunks as you would usually do. For example, if working in the hotfix worktree you can view (and diffput/diffget) differences between the current file in the current branch and the same file in the feature-1 branch using vim fugitive:

:Gvdiff feature-1:%

Alternatively, open a split window in your editor, open the same file in a different worktree and pull changes using the editor’s fucntionality.

References


comments powered by Disqus