-.- --. .-. --..

Git fetch's --prune option cleans up notes!

I spent a lot of time today trying to figure out why notes I was adding were getting removed at seemingly random points. Even notes that I pushed to remote with a non-default ref weren’t showing up after I added another local-only commit note. This turned out to be another case in the “two different tools working fine individually combine to create weird behaviour” phenomenon.

For a long time now I had the following setting in my main gitconfig file:

    prune = true
    pruneTags = true

At a very high level, pruning ensure that all the local refs are maintained 1=1 with the configured remotes, and any remote branch that doesn’t exist locally gets removed from local. pruneTags does the same thing for tags. The PRUNING section of git-fetch manpage has more information on this.

This, with a combination of my shell configuration drove me mad for a bit.

Pruning and notes behaviour

I completely missed this part: the local notes refs will get pruned if they’re not explicitly synced1 to the remote:

$ git fetch --prune
From github.com:kgrz/repo-that-doesnt-exit
 - [deleted]             (none)     -> refs/notes/commits
 - [deleted]             (none)     -> refs/notes/something

Notice the refs/notes/commits item? This is the default reference under which notes get added. This behaviour was surprising in hindsight because another local-first annotation mechanism that similarly needs explicit syncing is Git’s tagging system, and that has a specific flag for pruning. git fetch --prune won’t prune local tags that weren’t pushed to remote.

Shell prompt

I use the excellent pure prompt in my main (zsh) shell. The plugin also comes with a script that frequently syncs the state of the git repostory, using git fetch --all. This way, the prompt can show helpful arrows as and when it knows that there are some changes on the remote, or perhaps when you switch to a new branch that has diverged from the remote upstream.

Without this script, you’d have to periodically run git fetch --all or plain git fetch, inorder for git to know the difference between the local and remote branches.

Needless to say, a combination of auto-running git fetch, with pruning turned on wreaked havoc!

How this manifested

I had a branch on the remote and local that had a note on the last commit. On running git log the first time in a new shell, the remote note showed up. When I tried to add a local note using git note add -m 'some message', that didn’t show up sometimes. This got very confusing and I ended up spending lots of time debugging this thinking the issue might be in the git config or git itself. Only when I tried this out in a VM did I realized the problem.

So the moral of the story is, avoid running prune or configuring it globally unless you’re absolutely sure you’re not adding any local-only refs.

The bright side of all this, though, is our team now has a very rudimentary build information tagging workflow without requiring another layer of abstraction (think: a website that maintains the build <-> commit mappings).

  1. Syncing notes has to be done explicitly using git push origin refs/notes/<ref>; plain git push won’t do this, which is a good default to have anyway. A good resource I’ve found on this is this cheatsheet