Using Git is like being in a loving relationship. It’s all sunshine and lollipops until you screw up, and then things get really complicated, really quickly. (And half the time you don’t even know what you did wrong.)
Over the past few years I’ve made an effort to minimise the time I spend dealing with Git issues, and I’ve been pleased with the results. So I thought I might share my approach.
At a high level it’s pretty simple: align the way you work with the way Git works.
- In your task management system, create smallish tasks that will be completed by a single developer
- For each task, complete the work in a single branch
- When you’re done with the task, merge your branch as a single commit
Nothing earth shattering there. This is probably pretty close to what you do already.
I will elaborate anyway…
One developer per task
I have worked on a team that would create a single task to be worked on by multiple developers (usually back end and front end).
This is a terrible way to work.
While I understand all the arguments for this way of working, the reality is that these too-big tasks would often have nasty conflicts in multiple places, some in back end code from one developer and some in the front end from another developer. This made resolving conflicts a frequent and time-consuming chore.
(Also it messes with estimation, progress tracking and Git blame/history, but that’s beside the point.)
This is a good example of a small deviation from the ‘ideal’ process that results in a surprising amount of time-wasting.
One commit per branch (after merging)
I still remember the day I learned I would never rebase again. It was April 17 2016. The sun was shining, the birds were singing, I was having a pretty good hair day. GitHub had just announced the ability to squash and merge a branch through the web interface.
Even better, they would allow an administrator to force all merges to be squashed into a single commit.
With that one small setting, the act of rebasing — with all its commit-fiddling intricacies — could be defenestrated with extreme prejudice.
So, if you’re still rebasing, and this causes you any level of pain, consider the switch to squashing-when-merging and stop worrying about your commits during development.
(FYI, Bitbucket supports it too.)
Be careful moving and renaming
Git doesn’t track moved/renamed files. However, if it sees your branch has a removed file, and a new file, and they are, say, 90% similar, it will assume that it’s seeing a renamed file.
But if you or another developer change a little bit too much and one of you rename the file, a merge will be impossible. And even if you’re the only one changing the file, you will have lost all your history for that file.
It’s worth wrapping your head around this, because Git history can be a valuable thing, so allow me to over-explain it using two examples…
In this first one, I made a small change and renamed the file in a single commit:
Git worked out that even though I changed the file name, it’s still the ‘same’ file. So the history shows that I did the majority of the work last week, and only that one line is new.
In the next example, I started from the same point and did everything the same, except I also made some more significant changes further down the page.
Oh no, I changed too much! Git thinks the whole file was created today, and all other history has been lost. The ‘old’ version of the file will go down in history as having been deleted.
So I try not to move/rename files in the same branch if I’m making significant changes.
You really shouldn’t want to know any more than this, but for the keen, I’d like to share some words from Linus Torvalds (who created Git), in his trademark laid-back style:
So next time you think about a merge that might have been improved by tracking renames, please also think about a merge where one of the filenames came from two or more different sources through an earlier merge, and thank your benevolent Gods that they instructed me to make git be based purely on file contents. — Linus
A healthy dose of fear
Because I’m too lazy to go shopping, I like to make my own furniture. This is my favourite and most hated tool:
In many ways, Dennis Chopper is a lot like Git. I can’t imagine doing my job without him, but things will get very messy very quickly if I ever screw up.
I therefore have a strict rule when using it: “don’t screw up”. And it works — I have never once chopped my arm off.
I also have a healthy fear of Git, and ever since I implemented my “don’t screw up” rule, I have not once screwed up. It’s got nothing to do with skill, I assure you, I just don’t stick my hand under the blade.
I’m not sure if this counts as ‘advice’, but there you have it: don’t screw up.
When I’m picking up a new task to work on, I will take two seconds to think if it will require modifying a file that’s currently being worked on by another developer. If the answer is yes, and there is another task I can do instead, I’ll pick that other task.
With the above rules in place, it’s been a long time since I’ve done anything other than the basics: branch, commit, push, pull and merge.
And I have everything I need to do set as a keyboard shortcut in my IDE.
An example, why not: let’s say I’m working on a component and a fellow developer says “hey FYI I updated that component, you might wanna merge the latest master in”. I would press
b to select a branch,
enter to select origin/master,
enter, to select merge.
And boom, master will be merged into my branch and the file will update right in front of my eyes. This makes for a small-but-worthwhile reduction in context switching. I’m probably not going to change the ways of any CLI junkies, but it works for me.
Having minimised the amount of time I spend interacting with Git day-to-day, the only thing left to address is the dreaded merge conflict.
I have heard of people that prefer to resolve merge conflicts by looking at the little
>>>>>>> markers in a conflicted file. I’m tempted to mock this decision, but I see the appeal — it’s the artisanal approach.
I imagine these people also make their own pasta and cut their lawn with scissors.
“Hey Mike, watcha doin’ down there on the lawn, buddy?”
“Are they … scissors in your hand?”
“Of course, how do you cut your lawn?”
“Dude. Have you not heard of the lawn mower? Invented by Edwin Budding in 1830?”
“Oh, you mean those newfangled things with the spinning blades that you use standing up? I never got around to learning how to use one.”
“Dude! Come to my place right now. You just have to see what it’s like to cut grass with a lawn mower. It’s going to blow your freakin’ mind.”
“Hmm. Nah, I’m happy with the scissors, it only takes a day or so, and it pleases me that the scissors look like Git conflict markers as they go chop chop chop.”
Nowadays I rarely spend more than a few minutes on a merge conflict, maybe once or twice a month. This is partly due to avoiding them in the first place, and partly having a good conflict-resolution tool.
Early on I resolved them manually. Then for a few years I used SourceTree + KDiff. Then one fateful day someone told me I was a fool for not using WebStorm for everything, and I’ve never looked back.
It’s surprisingly time consuming to get a screenshot of a merge conflict, so the below is best you’re gonna get:
Being able to see the three files involved in a merge is immensely useful. In addition to the nice GUI, WebStorm will do some pretty clever resolution even when there are multiple changes to the same line of code. (KDiff, VS Code and others are probably good too.)
In many cases, resolving conflicts is a matter of a few mouse clicks; it takes a pretty gnarly conflict for me to actually have to copy/paste chunks of code, and by following all the steps above, this is a rare occurrence.
Making a concerted effort to minimise my Git interactions has saved me so much time that I just spent 10 hours of a sunny weekend writing a blog post about it.
Surely that speaks for itself.
Hey, thanks for reading, you’re great!