Add git sync, and stop trusting the cached default branch

BuildEmpire/Totara renamed its default branch from main to totara-20.
Nothing local noticed, because refs/remotes/origin/HEAD is written once at
clone time and remote.<name>.followRemoteHEAD defaults to `create`, which
only fills the ref in when it is missing. So `git default-branch` still
said main, as did nvim's diff-against-branch prompt.

bin/git-sync does the start-of-work sequence for a fork - fast-forward the
default branch from upstream, push it to origin - and works out which
branch that is by asking the server, not the cache. It sets the cached
HEADs from the answer, so everything reading that ref agrees afterwards.
It lives in bin/ rather than as an alias because git picks up git-<name>
on the $PATH as a subcommand, and this is more shell than a gitconfig
alias should hold. followRemoteHEAD = always is set for origin as well, so
an ordinary fetch keeps the ref current without running the script.

The nvim prompt now prefers origin, then upstream, then the remaining
remotes. It took the first remote alphabetically before, which in
be-edition means kdog - a colleague's fork - rather than anything
authoritative.

Verified against a fixture of bare repos whose default branch is
totara-20 and whose cached HEAD is stale: the branch is created tracking
origin when absent, fast-forwarded when behind, pushed to the fork,
refuses to merge when the local copy has diverged, and is a no-op on a
second run. The nvim prompt was checked in three real repos.
This commit is contained in:
Jonny Barnes 2026-08-27 16:05:49 +01:00
commit 80683b82c1
No known key found for this signature in database
4 changed files with 137 additions and 11 deletions

View file

@ -57,6 +57,34 @@ variables, sourced from `.zshrc`. Keep git out of it:
> `~/.gitconfig.lock` and spew `error: could not lock config file`. Put git
> settings in `.gitconfig.local` instead.
## Fork workflow
Work repos are forks: `origin` is mine, `upstream` is the one PRs are raised
against. `git sync``bin/git-sync`, found as a subcommand because it is on the
`$PATH` — does the start-of-work dance:
```
git sync # fast-forward the default branch from upstream, push it to origin
```
It asks the server which branch is the default rather than assuming `main`,
fast-forwards only (a diverged default branch is something to look at, not to
merge), and pushes to `origin` so the fork's copy matches. Then branch off it as
usual, push the branch to `origin`, and raise the PR against `upstream`.
The awkward part is that **git caches the default branch and does not refresh
it**. `refs/remotes/origin/HEAD` is written once, at clone time; the default
`remote.<name>.followRemoteHEAD = create` only fills it in when missing. So
BuildEmpire/Totara renaming its default from `main` to `totara-20` left every
clone still reporting `main``git default-branch` included. Two things fix
that: `followRemoteHEAD = always` in the gitconfig re-points the ref on every
fetch, and `git sync` sets it explicitly from what the server just said.
Anything wanting the base branch should read that ref, and prefer `origin` when
doing so. `git remote` sorts alphabetically, so picking the first remote in
be-edition returns `kdog` — a colleague's fork. nvim's `<leader>gm`
(diff-against-branch) tries `origin`, then `upstream`, then the rest.
## Light and dark mode
Most of this is now handled natively and needs no configuration: