One-liners: git sync
According to my shell history, git sync is what I use the most when using Git.
I have a bunch of repos that I switch between all day. So when I go to a repo, I want to be on the default branch and have the newest changes. git sync will check that out and pull, then delete local branches that don’t have a remote branch anymore.
This is what I have in my ~/.gitconfig:
[fetch]
prune = true
[pull]
rebase = true
[alias]
db = !git symbolic-ref refs/remotes/origin/HEAD --short | sed 's@origin/@@'
sync = !git checkout $(git db) && git pull && git worktree prune && git for-each-ref --format '%(refname:short) %(upstream:track)' refs/heads | awk '$2 ~ /gone/ {print $1}' | xargs -r git branch -D
Breaking it down
Prune on every fetch:
[fetch]
prune = true
This will delete remote-tracking refs for branches that don’t exist on the remote anymore. If you don’t do this, you’ll have stale origin/* branches, and sync will have no idea which local branches to delete.
Always rebase when pulling:
[pull]
rebase = true
This will make git pull rebase your changes instead of doing a merge, so your history stays linear without a merge commit on every pull.
Look up the default branch instead of hardcoding it:
[alias]
db = !git symbolic-ref refs/remotes/origin/HEAD --short | sed 's@origin/@@'
origin/HEAD will always point to the default branch on the remote, so if you remove origin/, you now have a branch you can check out. This also works whether someone is using main or master.
When you clone a repo, Git will automatically create origin/HEAD for you. If the default branch gets changed on the remote, this won’t update. You can use git remote set-head origin --auto to update it. This will also create origin/HEAD if you created your repo using git init and didn’t clone.
And here’s the sync command:
[alias]
sync = !git checkout $(git db) && git pull && git worktree prune && git for-each-ref --format '%(refname:short) %(upstream:track)' refs/heads | awk '$2 ~ /gone/ {print $1}' | xargs -r git branch -D
Having ! at the beginning creates a shell alias, which allows us to use pipes and command substitution. Here’s what each part does, in order:
git checkout $(git db): switch to the default branch.git pull: fetch and rebase any local commits onto the updated default branch.git worktree prune: forget worktrees whose directories no longer exist, since Git won’t delete a branch that’s checked out in one.git for-each-ref --format '%(refname:short) %(upstream:track)' refs/heads: list every local branch next to its tracking state, which reads[gone]once the upstream is deleted.awk '$2 ~ /gone/ {print $1}' | xargs -r git branch -D: extract those branch names and delete them.
We want -D because when you squash merge a branch into your default branch, Git will think your branch was never merged into it, so -d won’t allow you to delete the branch. Since the upstream is deleted, I no longer care about my branch. If you have work on a branch that you haven’t pushed and you want to keep the branch, use -d. Then just delete your squash-merged branches by hand.