git+ is a lifestyle
Control your team git process with custom commands and magic
Note: Originally published for Mariko Kosaka’s 2016 web.advent.today calendar—make sure you check out all the other great articles for the month.
Here’s a familiar story, maybe: You grab another cup of coffee or tea and sit down to get started for the day. The feature you’ve been working on for two weeks finally made it through review and it’s time to merge. And then–
$ git checkout master
$ git merge some-feature
$ git push
! [rejected] master -> master (fetch first)
error: failed to push some refs to 'email@example.com:something/project.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
“THANKS FOR THE HINTS, SHITHUB!” you yell, immediately ashamed of yourself. You recall ohshitgit.com and Todd’s web advent article you read last week and you wonder why there are so many websites about how to fix git mistakes and omg now you’re so mad at git you could almost leave a comment about it. “Why do I have to remember these lists of repetitive commands to type in a specific order all the time??” you whine, contrivedly. “Why can’t
git just take care of this for me???”
It can! (Or else I totally wouldn’t have written this!)
At SparkPost, my team of about 20 engineers recently moved all of our code from mercurial into git. Several of us had used git for open-source projects for years and I’m a big fan, but not everyone was equally comfortable with it. And with a team our size, we had version control processes we were used to that were working for us.
Since a lot of that process was from a tool we’d used called
hg-flow, we first reached for
git-flow hoping it would make our git process easier. For reasons too boring to explain, it didn’t. We still wanted some process, but we couldn’t rely on everyone remembering “lists of repetitive commands to type in a specific order all the time”. So I made git+.
To borrow from my friends in the React community, git+ isn’t really a concrete thing so much as it’s a concept or idea or ✨ a lifestyle ✨. The thesis is uncontroversial: computers are much better than we are at doing lists of repetitive shit, so make them do it. And git makes it ridiculously easy to script up these lists and make them available as “custom commands”.
So now when we want to start a new hotfix branch for ticket “FAD-1234”, we do this:
$ git start hotfix FAD-1234
start isn’t a core git command. But for our team, it does this behind the scenes:
- Checkout master and pull (easy to forget!)
- Create a branch using our hotfix/ticket naming convention
If we’d used
git start feature (instead of hotfix), it would have checked out develop and branched from there instead. Less things for us to remember, but more customized to our process than a generic
flow library might be. And finishing a ticket is even better.
git finish push does all this for us:
- Determine if the branch is a hotfix or not, let’s assume it is in this example
- Checkout master and pull
- Merge branch in using
--no-ffto maintain branch history
- Push to
- Checkout develop and pull
- Merge branch in using
- Push to
- Delete local hotfix branch
- Delete remote hotfix branch
Have fun remembering all that on your own, suckers!
At this point you might be wondering: how does it work? And what if you don’t care about “flow” or about a develop/master branch strategy? Is this relevant to you at all? Well, it might be!
Let’s walk through a couple examples of more generic git+ customizations to demonstrate how it works. First, create a folder for your commands and add that folder to your
$ cd ~
$ mkdir git-commands
$ export PATH=$PATH:$HOME/git-commands
That’ll be enough to get you started. (Note: to make this work permanently, the full path to that directory needs to be added to the
$PATH permanently, usually by doing that export statement in your
~/.bash_profile or similar. (Another note: you could also fork and clone sparkpost/gitplus and use the commands folder there.))
Make a file called
git-whoa inside your new git-commands directory, open it, and add this:
Save it, make it executable (
$ chmod +x ./git-commands/git-whoa ), type
git whoa and gasp at the awesome unchecked power you’ve so quickly and easily acquired. 👑 I was really pretty shocked at how simple it was.
Another slightly more useful set of git+ examples are
git in, which shows a list of commits in the remote that you don’t have locally, and
git out, which shows commits locally that are not yet in remote.
git in command looks like this:
# exit on error
BRANCH=$(git rev-parse — abbrev-ref HEAD)
git fetch origin
git log $BRANCH..origin/$BRANCH
git out just switches the order of the log around:
git log $BRANCH..origin/$BRANCH.
Another more general use command we use is
git open, which opens the current git repo on the assumed github.com repo page:
# exit on error
open $(git config — get remote.origin.url | sed -e ‘s/git@/https:\/\//’ -e ‘s/github.com:/github.com\//’ -e ‘s/.git$//’)
You can do anything you want with this idea. (✨ lifestyle ✨) If you want to see some interesting examples of git shortcuts, take a look at Jessie Frazelle’s tweet from a few months ago (and the responses):
Granted, these are all git aliases, which are a little different for boring reasons, but they’re good at showing how creative you can get. My favorite from that set was Jessie’s own example, which checks out a GitHub PR branch by PR number.
Making that a git+ style command would be as simple as throwing the code in a file called
# exit on error
git fetch origin pull/$1/head:pr-$1
git checkout pr-$1
Which has the added benefit of being easier to read than a one-liner alias, too.
I’ll finish up with a few tips for creating good git commands:
- Don’t override real git commands. Mostly because it won’t work, so don’t bother trying. But even if it did work, it’d be all kinds of confusing and an all-around terrible idea.
- Always use set -e. If you’re using bash, this makes the script exit on error. That way you don’t have to worry about accidentally doing a
git pushwhen your earlier
git pulldidn’t succeed, etc.
- Document what the command does. You’ll forget, so you need to document the usage. You might not want to mess with creating a
manpage for your command (it’s possible but pretty complicated), but you should at least use comments in the file to document what the command does alongside the code itself.
- Be careful with doing anything besides git commands. It’s tempting to use this to do everything. One of my favorite things our version does is allow people to update the git+ commands by running
git plus updatefrom anywhere. It’s powerful and easy but don’t abuse the power by running deployment commands through
Again, if you want to check out more of the commands we use at SparkPost, take a look at our sparkpost/gitplus repo. Feel free to fork it and create your own git+ lifestyle—just let me know if you do, so I can steal some of your best commands back.