Ramblings of an aging IT geek
← Ramblings of an aging IT geek
tooling

the git aliases i'd actually fight to keep

A pass over my git aliases a year on, what survived, what got promoted to shell functions, and the half-dozen I now reach for without thinking.

A mechanical keyboard lit by a terminal

I wrote about my git aliases a little over a year ago, and the honest thing to do is revisit them rather than pretend the list froze. Configs rot. You add a clever one, use it twice, and it sits in your .gitconfig like a jar of chutney at the back of the cupboard that you keep meaning to throw out. So this is a survivor's account: what's still there, what got cut, and the few that have quietly become load-bearing.

The rule hasn't changed. If I haven't typed it in a month, it goes. The difference now is that I've learned which aliases are actually aliases and which ones wanted to be shell functions all along.

the core that never moves

Here's the [alias] block as it stands today.

[alias]
    st = status -sb
    co = checkout
    br = branch -vv
    ci = commit
    last = log -1 HEAD --stat
    unstage = reset HEAD --
    lg = log --graph --oneline --decorate --all
    amend = commit --amend --no-edit
    undo = reset --soft HEAD~1
    fixup = commit --fixup
    ri = rebase -i --autosquash

st is still the one I lean on hardest. Short format, branch line at the top, three lines instead of a screenful. After a couple of years of muscle memory I genuinely fumble on machines that don't have it, my fingers type git st and the shell shrugs at me.

br has grown a -vv. The double-verbose shows the upstream tracking branch and how far ahead or behind each local branch is, which turns "do I still need this branch" from a guess into a glance. Small change, surprising amount of mileage.

Lines of source code on a dark terminal

the new pair that earned their place

The two genuinely new arrivals are fixup and ri, and they work together.

The workflow is this. I'm partway through a feature, I've got four or five commits, and I notice that commit three left a debug print in. Rather than make a sheepish "remove debug line" commit that future-me has to apologise for in review, I stage the fix and run git fixup <sha>, where the sha is commit three. Git makes a commit whose message is literally fixup! <original subject>. It's a marker, not a real commit.

Then when I'm ready to tidy up, git ri <base> opens an interactive rebase, and because of --autosquash git has already reordered the fixup commit to sit directly under its target and marked it as fixup. I read down the list, confirm nothing's gone sideways, save, and the history comes out clean as though I'd never left the debug line in at all.

pick   a1b2c3d add the parser
pick   d4e5f6a add the validator
fixup  9z8y7x0 fixup! add the validator
pick   b3c4d5e wire up the CLI

The first time the autosquash reordering happened on its own I actually said "oh, lovely" out loud at my desk. It's the kind of feature that does exactly the tedious bookkeeping you'd otherwise do by hand, and does it right every time.

There's a subtlety worth knowing. --autosquash only does its thing because the fixup commit's subject line starts with the literal text fixup! followed by the target commit's subject. git commit --fixup <sha> writes exactly that for you, which is the whole reason the alias exists rather than me typing the marker by hand and getting a character wrong. You can set rebase.autosquash = true in your config to make the flag the default, and I have, but I keep it in the alias too so the behaviour is obvious to anyone reading my dotfiles. There's also --squash, the sibling that keeps the message instead of discarding it, for the rarer case where the follow-up commit actually said something worth folding into the original.

the ones I tried and quietly dropped

For honesty's sake, here's the graveyard. I had a cleanup alias that pruned merged branches: !git branch --merged | grep -v '\*' | xargs git branch -d. It worked, mostly, but the grep -v '*' is fragile, it doesn't protect main or develop by name, and one distracted afternoon it nearly deleted a branch I hadn't actually finished with. Anything that deletes things based on a text filter is a trap. It went the same way as wip, off to a proper function with an explicit safelist.

I also briefly had aliases for stash operations and regretted every one. stash is already short, the subcommands are easy to remember, and aliasing them only added a layer of indirection that I had to mentally unwind every time something went wrong. Some commands are fine as they are. The skill is knowing which.

the ones that became functions

A year ago wip was an alias: !git add -A && git commit -m wip. It's gone from the [alias] block, not because I stopped using the idea but because I wanted it to be smarter than an alias comfortably allows. It now lives in my shell config as a small function that refuses to run on main or master, because a wip commit on a protected branch is exactly the sort of thing you do at the wrong moment and then push.

That's the real lesson from a year of this. The [alias] mechanism is brilliant for "type fewer characters for a command I already understand". The moment you want a conditional, a guard, or any actual logic, you're fighting the !-prefixed shell escape and you should just write a function with a real name and a real shape. Keeping that boundary clean has done more for the health of my config than any individual alias.

what I still refuse to alias

Same as last time, and I feel even more strongly now. git push --force stays long, and these days it's --force-with-lease when I do reach for it, because the lease check has saved me from clobbering a colleague's pushed work at least twice this year. The friction is the feature. Anything that can quietly destroy someone else's commits should make me type it out in full and think for a second.

I also still don't alias pull. I want to know, every single time, whether I'm about to merge or rebase, rather than discovering after the fact that a default I set in 2015 decided for me. Git nags about this now if you haven't set pull.rebase one way or the other, and I leave the nag on deliberately. It's a tiny moment of friction at exactly the point where an unconsidered merge commit can muddy an otherwise clean branch.

The broader principle behind the whole list is restraint. It's tempting to treat the [alias] block as a sport, to see how much you can compress, to feel clever about a two-letter command that expands into a baroque shell pipeline. But you pay for every alias twice: once when you write it, and again every time you sit at a machine that doesn't have it and your fingers betray you. The aliases that survive my yearly cull are the ones whose absence I'd genuinely feel, not the ones I admire from a distance.

So: eleven aliases, two new, one evicted to a function, and the rest unchanged because they were right the first time. If you're starting from scratch, take st, then come back for fixup and ri once the muscle memory's set. They're the two that turned tidying history from a chore into something I actually enjoy.