David's Deliriums

A Go developer who also likes Rust. I write here.

How I Git

Git in Brief

Over my time as a software engineer I’ve grown quite acquainted with git, the popular version control software that powers GitHub, GitLab, BitBucket, and several other cloud services. At its core, git simply tracks changes in files. I’m not an expert on exactly how it does that, I can just give that 300,000 foot view of what it does. I’m going to assume you have a basic operational knowledge of git and its vocabulary in this post - if you’re not sure what commit, branch, merge, remote and HEAD mean, I would recommend finding a good “intro to git” tutorial. GitHub has a good one, and you can’t go wrong with the Git Book.

Branching

Git branching is a very contentious topic - there have to be at least three branching strategies per developer out there. I’ve always been a fan of git-flow, and have used some form of that (or at least tried) for most of my time as a developer. I have done away with a specific “release” branch, instead opting to use version tags on master to signify releases (using semantic versioning). The main thing I took away from git-flow was naming of development branches. I have adopted a simple naming pattern for my branches, f/* for feature branches and b/* for bugfix branches. After those prefixes should come a short, dash-separated description of what the branch should accomplish, e.g. f/new-attack-animation or b/data-validation-correction. The branches should also contain very focused changes as well - apart from maybe an initial “build the MVP” branch. I don’t like branches to span more than a day or two of work, and giving them a name that focuses them on a small set of changes helps with that.

Committing

After I’ve made my branch and made my changes, it comes time to commit my changes. The going advice on committing is to commit early and often. The idea behind doing that is to have a bunch of small commits that make it easy to see what changed when, which makes finding bugs easier. I tend to preach this advice, and then code for an entire day without writing a single commit. This is only because I have come to appreciate the way git add --interactive works and use it to craft targeted commits. The idea behind this workflow is that I can make all the changes I want, and then craft commits that contain atomic changes from the group later without interrupting any flow I might be experiencing. Another nice thing about interactively adding files (or parts of files) to the stage is being able to easily skip changes made specifically for local development. I have also become a fan of rebasing my commits before pushing to the remote branch - this gives me the ability to take a bunch of small, related commits and make them into a single commit for the related changes.

Merging

Once I’ve made commits and completed the goal of the branch, it comes time to bring those changes back to master. This is the part of my git workflow where I haven’t completely made up my mind - I’m split between squash-n-merge or rebasing. At this particular moment rebasing is winning out, as I tend to delete branches as soon as I am done with them. The reason this matters is that, under the squash-n-merge model, you lose history when you merge, as a bunch of small commits turn into one monster. It also makes git-blame less effective, as you don’t get an immediate sense of why a change was made. When you rebase, you don’t lose history, you just rewrite it a little. But then you’re dealing with potential conflicts as you add lots of collaborators.

Overall, as long as you have a clearly established pattern for using git and enforce the pattern, those conflict with collaborators will be less frequent.