failing-onion-dial

Back

Git worktrees with Graphite

#coding#
Posted at 03, May, 2024

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.

Terminal window
# 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.

Terminal window
$ 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 worktree
gt track
# since the featA branch exists already, use modify
# instead of `gt create`
gt modify -m "feat: added hello.txt"
# push changes to GitHub
gt 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.sh
gt 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:

Last modified at 04, May, 2024