diff --git a/git/install.sh b/git/install.sh index 8e3047d..b3925fb 100755 --- a/git/install.sh +++ b/git/install.sh @@ -12,6 +12,18 @@ git config --global user.name "$GIT_NAME" git config --global user.email "$GIT_EMAIL" say "git identity: $GIT_NAME <$GIT_EMAIL>" +# Auto-prune stale remote-tracking refs on every fetch/pull. +# +# Without this, `git branch -r` keeps listing branches that were deleted on the +# server, and zsh's git completion happily offers them — so you tab-complete a +# branch name into `git push origin --delete` and get "remote ref does not +# exist" for something that was already gone. The prefix makes it worse: the +# completion offers `origin/foo`, but the ref on the server is just `foo`, so +# even a live branch fails unless you strip it by hand. See the `gbd` helper in +# zsh/zshrc (the grmb helper) for that half. +git config --global fetch.prune true +say "fetch.prune = true (stale remote-tracking refs cleaned on every fetch)" + # Plaintext credential cache in $HOME (survives container/VM rebuilds that # wipe everything outside $HOME). Git creates ~/.git-credentials with 0600 # the first time a credential is saved. diff --git a/zsh/zshrc b/zsh/zshrc index 7d0509a..0893519 100644 --- a/zsh/zshrc +++ b/zsh/zshrc @@ -34,6 +34,46 @@ SAVEHIST=10000 setopt SHARE_HISTORY setopt HIST_IGNORE_DUPS +# Delete remote branches without fighting tab-completion. +# +# `git push origin --delete ` completes to `origin/foo`, but `origin/foo` +# is a LOCAL remote-tracking name — the ref on the server is just `foo`. So the +# command zsh completes for you is the one git rejects, with "remote ref does +# not exist", which reads like the branch is missing rather than misnamed. +# +# grmb takes either form. It fetches with --prune first so branches already +# deleted on the server stop being offered at all (that is the other half of +# this annoyance, and `fetch.prune = true` from dotfiles/git/install.sh makes it +# automatic everywhere else). +# +# Named grmb, not gbd: oh-my-zsh's git plugin already binds gbd to +# `git branch --delete`, aliases resolve before functions, and the collision +# fails in the worst possible way — `gbd origin/foo` would quietly try to +# delete a LOCAL branch instead of the remote one. Check with `type ` +# before adding any g* helper here. +# +# grmb origin/my-branch # completed form, works +# grmb my-branch other # bare form, several at once +grmb() { + emulate -L zsh + local remote=origin b rc=0 + if [ "$#" -eq 0 ]; then + print -u2 "grmb: usage: grmb ... (origin/ prefix optional)" + return 2 + fi + git fetch --prune --quiet "$remote" || return 1 + for b in "$@"; do + b=${b#${remote}/} + if git push "$remote" --delete "$b"; then + print "deleted ${remote}/${b}" + else + print -u2 "grmb: could not delete ${b} (already gone? check: git ls-remote --heads ${remote})" + rc=1 + fi + done + return $rc +} + # Per-machine customizations (secrets, keys, local overrides) live in # ~/.zshrc.local, which bootstrap never touches. See zsh/zshrc.local.example # for the variables modules look for. Loaded last so it can override anything.