I use git worktrees as my primary method of managing local repositories when doing development work.
The exact method consists of cloning a bare repository and using git worktree add <branch name>
to create individual folders for each feature that will be developed.
For specific details, see this blogpost that my workflow mostly resembles.
A couple months ago, I was made aware of Graphite which is effectively an overlay for git with GitHub that facilitates easier dependent changes, a.k.a. stacked diffs. Personally, I don’t have much need for the stacked diff workflow outside of work since my personal projects generally don’t require code review. I’ve also moved off GitHub for personal projects and only use it for open source contributions, which are mainly smaller fixes and changes. However, my employer recently got Graphite enterprise and it’s much more appealing in that context. I need constant code review for any change I submit and usually have a need to break out changes into multiple separate steps (such as release or smaller features). Over the past couple weeks, I decided to primarily use Graphite and integrate it with my git worktrees workflow.
Getting Started
Let’s assume we are developing in the repo foo
.
The repo will get cloned as a bare directory and we’ll create a worktree for the default branch main
.
# clone a bare repo with a worktree for the default branch$ gclone https://example.com/foo/foo...$ cd foo$ ls -a. .. .bare .git main
Now, we can start developing a new feature called featA
, so let’s create a worktree for that.
$ git worktree add featA$ ls -a. .. .bare .git featA main$ cd featA
In the new featA
worktree we can now begin developing our new feature.
As an example, let’s create a file and try to submit it through Graphite.
echo "hello" > hello.txt# necessary on every new worktreegt track# since the featA branch exists already, use modify# instead of `gt create`gt modify -m "feat: added hello.txt"# push changes to GitHubgt submit
Great, we’ve accomplished the same workflow for pushing a single PR up to GitHub as we could without Graphite.
Now, let’s take advantage of stacked diffs to add a sequential change after the addition of hello.txt
.
echo "cat hello.txt" > hello.shgt create -m "feat: script to output hello.txt"gt submit
This gives us two separate PRs that are linked together without having to manually point and rebase branches in multiple places.
To make edits to either PR, use gt up
and gt down
to navigate between each diff, add changes, then run gt modify && gt submit
.
If you need to pull in upstream changes, run gt sync
and it should usually be straightforward to resolve.
Appendix
The code for the gclone
command is shown below
function gclone if not [ (count $argv) -eq 1 ] echo "usage: gclone <git repository>"\n echo "clone a git repository into an organized subfolder, similar to go mod" return end set -l path (echo "$argv[1]" | sed 's/.*:\/\///' | sed 's/:/\//g' | sed 's/\.git$//' | sed 's/^git@//') set -l path "$CODEPATH/$path" mkdir -p "$path" git clone --bare "$argv[1]" "$path/.bare" zoxide add "$path" echo "gitdir: ./.bare" > $path/.git git config -f $path/.bare/config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" git -C $path fetch origin set -l base_branch (git -C $path remote show origin | sed -n '/HEAD branch/s/.*: //p') git -C $path worktree add "$base_branch" cd "$path/$base_branch" echo "🎄 worktree was created at $path/$base_branch"end
Caveats:
- As of writing, there can be potential issues with work getting erased in other worktrees when using Graphite. For example, be cautious when running
gt sync
with unstaged changes on themain
(default) worktree.