There’s another awesome cover from The Practical Developer. This one is on git, the source control tool for kernel hackers that accidentally took over the world.
“I know Git! Depending on what you mean by ‘know.’”
ThePracticalDev on Twitter
First, you laugh, then you cry, then you cry again.
Then, if you’re like me, you wonder what “know” means.
Because a lot of people are taken to task for “not knowing” git, and thereby getting in trouble. The kind that ruins not only their day, but the day of everyone else depending on that repository.
That really sucks, and it turns off a lot of folks who would benefit from version control. It makes folks scared to ask for help, and it makes it hard to learn what to do and what not to do.
And it just doesn’t make sense.
Knowing Is Half The Battle
I know git.
Or, rather, I did. I slurped down git ready. I even worked my way tortuously through Git From The Bottom Up, the whole thing, from byte level on up. I read articles on best practices, I experimented, I refined my workflows.
At the end, I Knew Git.
It wasn’t worth it. A few months later, I promptly horked a merge and ended up sending files to myself via
scp and doing manual diffs. (Or maybe git’s merge algorithm balked. I still have no idea why kilobytes of story text just vanished.)
That was only the most painful of the numerous times I’ve shot myself in the foot with git — and I’m a one-person development shop, primarily using git to store my fiction as I write and revise it.
Clearly, I didn’t know sh
One of the major criticisms of git is that it is hard to use. This isn’t quite right. The problem is that the git learning curve looks like this:
Git has pitfalls, and when you step in one, it’s horrible from a UX perspective. Often:
- You have no advance warning
- Even after the fact, you get no indication anything is wrong
- You find out you did something wrong way after you did the wrong thing
- Problems are cryptic in nature and in messaging
- A small mistake can affect dozens of other people
- Mistakes arehard to recover from
- It’s hard to tell when the recovery is successful
- The whole time, you’re worrying that the system just ATE hours and hours of your productive time
Eew. That SUCKS. That’s like up there the UX 101 “don’t do this” checklist.
So, people learn six commands. And they stick with those six. Let’s explore why.
Don’t Do That, Then
So, there’s this fellow Tom. Tom pushed his changes to the master branch, and it’s got a thousand commits in it, because he was trying to be careful and commit incrementally. Now the master branch has a thousand commits on it, people are screaming, someone started fixing it and now there are two branches, folks have pushed their own changes, and everything is on fire.
Stupid Tom, right? You should go learn git, build-breaker.
The canonical advice in this situation amounts to GIT GUD, NEWB, the dismissive comment asshole-gamers use to make fun of people who they think aren’t skilled enough to contribute to their team. Or that are just dying. Missing is any sort of context, what GUD might mean, or why no one has produced a Git guide called GIT GUD yet. (Oh, wait.)
This is, frankly, bullshit. Coding isn’t competitive gaming. We are a team because we help each other. Software that’s interesting to write is also complex enough that it doesn’t fit in a single brain.
And on top of that — we control the playing field! It’s ours to rearrange! If we want to arm ourselves with BFG-9000s, the whole team, we can! The secret is thinking through our workflow, and altering our tools, policies, and processes to fit our needs.
Let’s count the fails in Tom’s example above:
- The team is relying on a master branch that blindly accepts pushes, rather than one that has a gatekeeper (e.g., pull requests) for sharing changes.
- Probably because the team thinks that “release engineer” or “build master” are dirty words, but if they have this sort of conflict, they probably need one.
- No pre-checks/tests to prevent pushing broken code — i.e., no checks for code content.
- No pre-checks to prevent pushing un-squashed commits (if that’s what Tom’s house has decided on) — i.e., no checks for code metacontent.
- No indication ahead of time that Tom was about to do something wrong
- No indication after the fact that anything was wrong
- No help on how to fix it
- No coordination on how and who fixes it
…and on and on.
Some of these are tool problems, but by and large they’re process problems that may also be solvable with tooling.
“Some kind of help is the kind of help we all can do without”
Git is an extremely flexible tool.
Part of this is because it exposes its abstractions.
The developers recognized that, and enshrined two layers of API: the in-the-wall plumbing one, and the porcelain layer. Basically, the porcelain is the part you see — like sinks, toilets, etc., which are often made of porcelain—and the plumbing is supposed to be for people to write GUIs and more porcelain on top of. The idea is you use the shiny stuff, and leave the innards alone.
(As a side note: no one I’ve talked to has ever understood the “porcelain” metaphor without explanation. Ten minutes of talking, then “oooooh…sure…” The pattern’s a good one — when I design a tool for the command line, I include options to let it dump machine-readable output—but the analogy is pretty opaque.)
Sadly, a lot of useful command-line workflows necessarily involve the plumbing. (I’m looking at you,
git log.) Even sadder, the porcelain isn’t a high enough level of abstraction for most users. And most annoyingly, the low-level and the high-level interfaces are mixed all together.
The vast majority of git usage looks like this:
- I get the latest version, possibly merging any of my existing changes.
- I make and save changes locally.
- Periodically, I share those changes.
Sounds simple, right? And you can do this in only(!) six git commands…or you can screw it up with three other commands, or you can do it in ten commands that end up making everyone’s life worse.
So people memorize the six commands that actually work. They could spend the braincells to really understand what’s going on — but it’s wasted work. We spend almost all of our time figuring out what we can ignore; otherwise, the world is overwhelming. So why do we make fun of people for stopping learning at an appropriate point?
One reason is the gotchas. Steps 2 and 3 use the same damn verbs to start, for example. Some git workflows make them look different by introducing single-purpose branches, and some go so far as to invent new verbs, but the standard is hard to fight, and there’s even a backlash against doing exactly that. (Which is a bit silly: don’t make modifications to your work environment that make you more productive and reduce the number of errors you make?)
There’s an impedance mismatch here, and it’s not entirely git’s fault. Almost all SCMs have this weakness — they don’t match users’ workflows well, if at all, instead giving you invisible LEGO blocks you have to fit together while wearing mittens:
git init. No, wait, I meant
git clone. Except if I’m not starting from scratch, then it’s some sort of
mergeor something, except, it did some of the merge for me, I think. Wait, am I even on the right branch? Hell. I better update my prompt to ALWAYS show me the branch to avoid that mistake again!
Step 2: Um…edit my files? Yay! that worked. (Unlike some SCMs like Perforce, which require an explicit “check out for editing” step.) Now
git commit. Commit! I just made changes! Why is nothing — oh, right. Add, then commit. Except some of the files didn’t get in.
git add -a? No,
git commit -a. Except this other website says never to do that. Just — just put the damn files in the changelist, okay?!
Step 3: Okay, I made my changes! You should be able to see them, I finally got them committed! …No? You can’t? What? Uh, share them? I pulled, so
push? Or is it another
clone? Wait, where is push going? How do I know? Why do I have to tell YOUR branch what MY branch is? Or — or I have to do a “pull request”? (Not actually a verb in git; instead a process we invented to organize our work in a particular way.) Oh, it means “someone else has to merge my changes instead”. Well, that’s good! Except now they’re telling me I need to rebase for some reason. Or is it squash? (Also not a verb.) Why is this even a thing that humans need to do manually? And it throws away history — is that okay? I thought we liked saving history, that’s why we used an SCM.
I’m exaggerating slightly for comic effect. But only slightly.
With git, when you fall off the side of the narrow walk-the-plank workflow, you create problems that you can generally only solve by really understanding what’s going on. (Or dusting off and nuking from orbit.) This is a pain in the ass, to say the least — and usually it falls on the shoulders of the one person who (sigh) has actually figured out how git really works, and they’re crabby about having to un-hork the repo again, and we’re at Git Gud, Newb again.
The light at the end of the tunnel
I have two points, I guess.
First, git is very powerful and hard to use at the same time. Many solutions have been proposed. One of these solutions is telling people to Learn Git Already. That solution is kinda dismissive and sucky. Another solution is to memorize six commands and just use those. This works until you make a mistake, and then you need to know a whole hell of a lot more.
Second, when programmers should devote hours and percents of their brain understanding git, that is wasted work. The point of a development system is to save your brain for hard things, like invalidating caches and naming things.
So telling your coworker to LEARN HOW TO GIT YOU MORON may feel good, but it’s not productive, and it’s bad advice. Instead, question how little of git they can get away knowing, and why. Really? Can they learn less? Can you know less? How little can your team get away with?
Then, feel happy when you “only” know six git commands. Cause 90% of the time, that’s all you need to know. And maybe that other 10% of the time should be designed out of your development workflows.