Git Epiphany: Merge is the Opposite of Checkout

09/ 3/10
git, workflow

I was just chatting with Erik “Heezy” Hinton about the best way to selectively merge files with Git and started to wrap my mind around Git’s most misunderstood command, checkout. The conventional wisdom is that git-checkout’s normal use is for switching branches. If I’m on master, I could git checkout devel to move to the development branch, or git checkout -b foo to create a new foo branch. This is considered a “safe” use of git-checkout. But, as it is written, checkout has a “destructive” side as well, which instantly changes content in your current working tree. If you git checkout . after you’ve saved local changes, it’ll revert everything in your repo to the state of the last commit. If you git checkout bar.txt, you’ll just revert that file. Likewise, if you git checkout devel bar.txt you’ll bring over the development version of bar.txt without concern for the current branch’s version of it. In this sense, git-checkout treats the current working tree as “hostile,” in the face of whatever you’re bringing into it.

On the contrary, git-merge treats any foreign object as “hostile” and protects, at all costs, your current tree. It does whatever it can to prevent you from changing anything in your tree that can’t be cleanly merged.

This question of “hostility preference” is really what sets git-merge and git-checkout apart, and why the commands are actually opposites. To prove it, I’ll say that it is actually possible to completely emulate git-merge with just git-checkout and git add --patch (which allows you to selectively add parts of files). They actually make this even easier for you with the --ours and --theirs flags. When you think about it, git-checkout isn’t just switching branches, it’s actually replacing every file in your working tree with another version of it, but keeping a reference to the original. That’s why there’s no “safe” and “destructive” version of this much-misunderstood command, there’s just the one git-checkout.