Brutal wisdom
Pato Z
Posted on March 23, 2022
A kid and an old man sit by a campfire by the side of the road in the middle of nowhere. There's something quite odd about the old man.
Perhaps it's the glint of mischief in his one good eye (the one not covered by the eye patch). Perhaps it's his sly smile or the reflection of the fire light in his teeth that look like diamonds*. Perhaps it's his huge sword that rests by his cane or his fashion choice wearing full barbarian attire (leather underwear and all).
* or the fact they actually ARE diamonds.
The old man is telling a story the kid has heard a million times, but it's a great story, one that once started, should be told all the way to the end, it's a story of...
Brutal wisdom
Listen kid, if you want to make it in this profession*, you'd better have a few tricks up your sleeve. The fact that our uniform is pretty much leather underwear and boots makes the whole "sleeve" situation all the more complicated...
* that is, to survive.
I'll tell you a few basic tips, the ones I store in my metaphorical suitcase of knowledge**.
** or "luggage" if you would.
Let's start with the basics...
Getting your bearings
Some distance away a bunch of elderly warriors are fast asleep. They call themselves "The Silver Horde". One of them is snoring loudly, the rest don't seem to mind.
When you go adventurin' it's very important to know where you're goin'. See Mad Hamish over there, the old man points to a senior barbarian snoring loudly amongst the Horde, when he was younger he spent months on an uneventful and boring quest only to later realize he was questin' in the wrong direction.
You need to learn to read a map, and the best map out there it's called git log, but not just any git log
, a very specific one:
git log --decorate --graph --oneline
This shows a nice view of your commit history like this one (newest commit first):
* 85535bb493 (HEAD -> your/branch) Sit in your throne and mope in boredom
* b0e7ec39d5 Keep fighting while looking awesome
* ef95ab8b04 Trip, fall and lose dentures
* c5059d4b97 Swing axe in style
* d3a023c22d Crack some skulls
Of course, no barbarian would ever type that long git log
command. Instead accomplished barbarians would just:
git config --global alias.ll "log --decorate --graph --oneline"
And then use git ll
instead.
Now let's talk about something else...
A small step goes a long way
When you put your hip at risk with every roundhouse kick, you learn to take it slowly. If there's a lesson for you to learn here, this is it: write wee tiny commits!
You renamed a function or variable: commit!
You changed a function signature: commit!
You moved a file: commit!
You swapped the execution order of some functions*: commit!
You extracted a function: commit!
You inlined a function: commit!
You changed some logic: commit!
You refactored some imperative logic into an expression: commit!
If in doubt: commit!
* hopefully pure functions.
There is a catch though: every commit must build. Every commit should be something you'd be proud to put in production**.
** even more importantly, every commit should be one that your mum would be proud to pin on her fridge, whatever that is.
No broken commits. No "fix the mess I coded two commits ago" commits. No "let me revert all this nonsensical attempt at solving this problem" commits. No "...and now I remove all those debug logging functions from the code" commits. No "fix code review comments" commits.
Keep your history tidy and you will be regarded as the hero you are. Your legend will live on forever.
But what if you already messed up? You need...
Savegames
For most adventures, having no plan is the best plan. When that plan fails, having a plan B is the bestest of plans.
The kid could swear the sleeping seniors snore in approval of that statement.
Every now and then even the most accomplished heroes take a wrong turn and find themselves surrounded by evil, outnumbered and outcrossbowed.
Before you know things will get nasty, so it's always useful to have a checkpoint to go back to if things get too messy*.
* too messy by barbarian standards can by worse than debugging hardware with a shaky pulse.
To create a save game all you need to do is run:
git branch <save-game-name>
# for example: git branch checkpoint
To load a save game you just run:
git reset --hard <save-game-name>
# for example: git reset --hard checkpoint
As with any save game out there, loading a save game means losing all your unsaved progress so be careful.
But what if you're in a pickle** and forgot to save?
** poor Old Vincent, GNU.
Don't worry, life also has...
Autosaves
Manual savegames are always handy, but in case you messed it up big and monsters are in hot pursuit, you can always fish for an autosave that can magically get you out of this situation*.
* it's a million to one chance, so you know the odds are in your favor.
You can check the autosaves by running:
git reflog
That will show a list of commit ids, surely one of those is a safe enough state to go back to (newest changes first):
de42f4a (HEAD -> master) HEAD@{0}: checkout: moving from cd7a1bb60f3c6eacba7e78cb5c665c75da19686f to master
cd7a1bb HEAD@{1}: checkout: moving from master to cd7a1bb
de42f4a (HEAD -> master) HEAD@{2}: reset: moving to HEAD~1
cd7a1bb HEAD@{3}: Swing axe like in style
de42f4a HEAD@{4}: Crack some skulls
The reflog is append-only so you're pretty safe moving between those commits, but beware of some spells like gc
and prune
that might mess up with this.
Never making mistakes
You don't get to be my age in this line of work without bending a few rules. In this case, I'm talking about the rules of space and time.
You just tripped mid fight and lost your dentures? It happens. And when this happens you wish you could go back and change the past.
So that's exactly what you do!
You concentrate really hard and do a:
git rebase -i <base-branch>
# usually: git rebase -i origin/master
That move it's called "the interactive rebase" and it helps you overcome your mistakes and always look like a legend.
It usually shows something like this (oldest commit first):
pick d3a023c22d Crack some skulls
pick c5059d4b97 Swing axe in style
pick ef95ab8b04 Trip, fall and lose dentures
pick b0e7ec39d5 Keep fighting while looking awesome
pick 85535bb493 Sit in your throne and mope in boredom
Just find the broken commit, replace the pick
with an edit
, save and exit your editor*.
* as you grow old like me, trivial tasks like tying your boot laces and exiting your text editor can become impossibly difficult.
Now watch where you step, fix the issue, git add
your files, then git rebase --continue
.
Type the new commit message and carry on.
A quick git ll
shows (newest commit first):
* 4ff8e57a4d (HEAD -> your/branch) Sit in your throne and mope in boredom
* 6a998b0893 Keep fighting while looking awesome
* 804cfb513c Pretend to fall and dodge attack
* c5059d4b97 Swing axe in style
* d3a023c22d Crack some skulls
Better than standing up is never falling down
But what if you tripped, fell, fought on the floor for a while, then stood up and kept fighting, is there a way to avoid this mess?*
* surely your sciatica would appreciate it.
Of course there is, let's say you are here (git ll
, newest commit first):
* 4ff8e57a4d (HEAD -> your/branch) Stand up and recover dentures
* 6a998b0893 Fight on the floor, kick evil in the groin
* ef95ab8b04 Trip, fall and lose dentures (**)
* c5059d4b97 Swing axe in style
* d3a023c22d (origin/master) Crack some skulls
** coding barbarians usually break tests instead of losing dentures, but the pain is more or less the same.
You can git rebase -i origin/master
(oldest commit first):
pick c5059d4b97 Swing axe in style
pick ef95ab8b04 Trip, fall and lose dentures
pick 6a998b0893 Fight on the floor, kick evil in the groin
pick 4ff8e57a4d Stand up and recover dentures
You can move the "stand up" commit right after the "trip and fall" commit like this:
pick c5059d4b97 Swing axe in style
pick ef95ab8b04 Trip, fall and lose dentures
pick 4ff8e57a4d Stand up and recover dentures
pick 6a998b0893 Fight on the floor, kick evil in the groin
And then change the pick
to a squash
, save and exit:
pick c5059d4b97 Swing axe in style
pick ef95ab8b04 Trip, fall and lose dentures
squash 4ff8e57a4d Stand up and recover dentures
pick 6a998b0893 Fight on the floor, kick evil in the groin
Update the commit message (or don't***), save and exit again.
*** if you don't want to change the message, you can use fixup
instead of squash
.
And now the end result looks like this (git ll
, newest commit first):
* 959eca46a8 (HEAD -> your/branch) Keep fighting while looking awesome
* 804cfb513c Pretend to fall and dodge attack
* c5059d4b97 Swing axe in style
* d3a023c22d (origin/master) Crack some skulls
(you can use reword
in git rebase -i
to change a commit message, like 959eca46a8
above)
Refactoring quests
Every now and then you'll have to embark on a refactoring quest. Those quests are strange because if done right, after you're done no one will know they happened. And that's because they don't alter the (functional) world in any perceivable way.
A responsible barbarian would ensure that the code that needs changin' is properly covered by tests. The problem with this is that when you start refactorin' you probably don't know the code that needs changin'. So more often than not, you'll finish the refactor, then check that your refactored code is properly covered by tests and if not you'd add the missing tests.
Good barbarians go home satisfied with this. Great barbarians go one step further.
Let's say you are here (git ll
, newest commit first):
* 6a998b0893 (HEAD -> your/branch) Check if the coast is clear (write tests)
* 742408667f Inline treasure in underwear
* ba001cea18 Extract treasure
* cd2d094a79 Refactor evil away
Great barbarians would git rebase -i origin/master
(oldest commit first):
pick cd2d094a79 Refactor evil away
pick ba001cea18 Extract treasure
pick 742408667f Inline treasure in underwear
pick 6a998b0893 Check if the coast is clear (write tests)
They'd then move the last commit to the very top like this:
pick 6a998b0893 Check if the coast is clear (write tests)
pick cd2d094a79 Refactor evil away
pick ba001cea18 Extract treasure
pick 742408667f Inline treasure in underwear
And then change the first pick
to an edit
(or a break
), save and exit the editor:
edit 6a998b0893 Check if the coast is clear (write tests)
pick cd2d094a79 Refactor evil away
pick ba001cea18 Extract treasure
pick 742408667f Inline treasure in underwear
The rebase will stop right after the test commit, but before any changes to other code.
This is the perfect time to run the tests. If they fail at this point you know that the refactor commits that follow did some functional change and this could very well be a bug.
After your done testing and the tests pass, you can just run git rebase --continue
*.
* and then you run the tests again for good measure.
Your trusty companion
Some think we barbarians are solitary creatures, they're wrong. While you're cracking skulls over here, you need someone watching your back over there*.
* making sure you don't fall, swing your axe in style and take your pills.
And there is one companion that is by far the best companion to bring to any adventure. But there's a catch...
Remember when I was telling you to take it slowly and write wee tiny commits?
Well this companion will only join your party if you agree to make tiny commits and have all those commits pass the build. Annoying, I know, but the payoff is BIG.
This companion is called git bisect.
You've been cursed with an annoying bug? You've been poisoned by a strange venom? bisect
is your friend!
bisect
will go look for the solution, antidote or whatever while you go take a much needed nap.
This is how it works:
git bisect start <bad-commit> <good-commit>
# Usually git bisect start HEAD origin/master
That will wake up bisect
and will pick a commit for you to test. Of course no self-respecting barbarian would manually test the code when they can write a script to test it for them:
git bisect run <some-script-that-repros-the-bug>
# for example git bisect run ./run-tests.sh the-broken-test
The script you pass to bisect run
needs only return an exit status of 0
if the commit is good and anything else (except 125
**) if the commit is bad.
** the best numbers are arbitrarily hardcoded into the fabric of the multiverse, like pi.
A typical bisect run
script would build your code and run some repro tests.
After bisect
starts running you can go take a nap and let it work. Once it finishes, it'll tell you which is the first commit that failed the script (usually the commit that has the bug).
Once you know where evil lurks you can make a note of the commit id and then git bisect --reset
to go back to normal.
Finally, do a swift git rebase -i
, edit
that commit and nip the evil in the bud.
The final test
The old man stares intently in silence and then says:
Right, let's see what you've learned?
A cold shiver runs through the kid's spine remembering an inter-dimensional flashback of a high-school class. The kid shakes that nasty feeling while reluctantly handing over some notes.
The senior barbarian reads them out loud:
git config --global alias.ll "log --decorate --graph --oneline"
-
git ll
to get your bearings -
git branch <savegame-name>
to create a savegame -
git reset --hard <savegame-name>
to load a savegame -
git reflog
to fish for an autosave -
git rebase -i <base-branch>
to change the past and always look like a legend -
git bisect start HEAD <base-branch>
and thengit bisect run <some-script-that-repros-the-bug>
to havebisect
work while you take a nap - And most importantly: write wee tiny commits!
The old man cracks a wide smile and says:
Good, good, you've been paying attention.
Now for the final piece of advice, keep your eyes open, your knees warm and be on the lookout for the three most important things in life: hot water, good dentistry and soft lavatory paper.
Posted on March 23, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.