Skip to content

Instantly share code, notes, and snippets.

@evanpurkhiser
Last active September 14, 2022 21:06
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save evanpurkhiser/1fb880ca14a7c2a35be05d4eeceeba9f to your computer and use it in GitHub Desktop.
Save evanpurkhiser/1fb880ca14a7c2a35be05d4eeceeba9f to your computer and use it in GitHub Desktop.

My git workflow

It typically takes me less than 30 seconds to open a pull request. Here's what that looks like.

$ git status  # ← What did I touch?
$ git add -p  # ← Actually read what I am commiting 
$ git ci      # ← An alias of git commit
$ git pr      # ← We'll talk about this part

After this the new GitHub PR is opened in my browser.

I think I have a pretty unique workflow that is optimized for very quickly creating and updating pull requests, specifically in the context of working on a team.

The basics

To start, I try to always pay attention to exactly what I am commiting. I typically look at what I've touched with git status and then force myself to say yes to each chunk I'll be commiting with git add -p. I focus on finding the right balance of concise commits while still making a change that is meaningful. Typically commits will either be a complete small feature, a clear building block of a larger feature, or a refactor.

I've found the smaller and more obvious the commit is, where no other changes have been littered in to the change, the easier it is for the reviewer to understand and approve the pull request. The quicker I can get a green approval checkmark, the quicker I can land my changes into main. I value this so much that I optimize for reviewabiltiy of the PR.

There are already a number of fantastic posts on this philosophy of committing, here are a few I like.

Opening the pull request

I've spent a lot of time iterating on the step for going from commit to pull request. This of course started with a lot of manual clicking in the GitHub UI, then as a few bash scripts, python embedded into bash, and is now a full blown npm package, @evanpurkhiser/tooling-personal.

The git pr invocation in my example above is an alias of pt pr, which is short for "personal tooling, pull request".

We'll talk about what my tooling does later on in the post, but first I want to explain the foundation of my git workflow. There are a couple choices I've made that help to automate away previously manual parts of opening a pull request. These choices on their own may seem completely backwards, but as a whole together, it makes things simple and fast.

  • First, I do not use branches.
  • Second, I make one commit per pull request.

Okay hear me out, because I know these sound wacky.

Branches, who needs 'em

For a while now I've found the idea of feature branching using git to be a bit clumsy. When I am building something, I typically have a lot of small changes that are puzzle pieces which make up a larger change as a whole. But this does not mean every change depends on each other. In fact often many parts of a feature are not dependent on each other at all, and later on a single commit can bring everything together into a whole.

What this means for my workflow is that I'm able to parallelize the reviewing process. I may have multiple pull requests all open at once, ready to merge into the main branch, while simultaneously I'm completing the changes needed to tie all those into one. Once everything is merged, I'm immediately ready to create a pull request for that change.

In a world of branching, all of this would require lots of careful merging/rebasing and updating of those branches, especially when you have multiple dependent changes. Something like git-stack can help with this, but I have admittedly not tried it.

I've also found it's not at all uncommon to come across changes you would like to make that may be completely unrelated to what you're working on, but are easy enough to quickly fix. I've always liked the idea of leaving a place you visit cleaner then when you arrived, I apply this to software as well. Some good examples of this might be fixing a lint warning or usage of a deprecated component that has a simple migration path.

Having a branch discourages making these changes. You would need to stash whatever you're doing, checkout a new branch, make the commit there, move back to your previous branch, and finally recontextualize yourself with what you were doing. Why bother? But these small changes are important to the health of your code base! What if instead you could just make that small change git add -p and git commit and immediately turn that into a pull request?

So if I'm not using feature branches what do I do instead? I just don't use branches. Every commit I make is just made directly on the main branch. Anything that is dependent on another change, simply is just committed later. Anything that is unrelated to whatever I am doing also just gets committed on the main branch. It's as simple as that.

To understand how any of this is at all reasonable, let's talk about how I think about commits.

One commit, one pull request

Something I lose from not using branches is the organization of my commits, branches give you a nice way to group a set of commits. But, why do I need that? I've already talked about making commits small and reviewable chunks, so why do I need to group commits at all? I'm going to merge squash my pull request into a single commit anyway. So instead of treating commits as sometimes the finished sausage and sometimes work in progress garbage, I always treat a commit as the equivalent of a PR.

Of course, the sausage has to be made somewhere. I do no write perfect code immediately.

Truth be told, I actually spend a lot of time with a pretty messy git working tree. It's quite common for me to have multiple uncommitted changes all happening at once, sometimes completely unrelated, and when I feel like I have something that is reasonable to become a pull request, I will turn it into a commit. git add -p is my best friend here. It lets me be very particular about what changes I would like to commit. Being able to edit the patch you're staging is incredibly powerful, and something I make heavy use of. If you're not familiar, Markus Wein has a great post on it.

You might be screaming "Ahh! Commit early and commit often! One day you're going to slip up and git reset --hard and lose everything!". I agree that's definitely a risk, and I realize my response to this is a bit crass. But. I have been using git for over a decade. I've become quite confident in my usage of the tool. It's honestly rare for me to make a mistake like this. So I am fine having lots of changes in my working tree.

Bringing it all together

This sounds very unique and all, but how do we go from a bunch of commits on the main branch to each one having it's own pull request? We don't even have a branch that we can push!

Well it's "simple". Whatever commit you want to make a pull request for, rebase it so it is just ahead of the tip of origin/main and push it up to a branch on GitHub.

$ git fetch
$ git rebase -i origin/main

#      <-----------------------------------|
# pick 016237a Update button text          |
# pick 2c2b757 Add `disabled` button prop  |
# pick 4b7719a Fix eslint warning   <------| <- Move this to the front
Successfully rebased and updated refs/heads/main.

$ git log --oneline

# 13bf9f1 (HEAD -> main) Add `disabled` button prop
# 5f8ad5c Update button text
# 5a60fb4 Fix eslint warning  <---------------- The commit we'll push 
# 8f69db0 (origin/master) Fix
# ...

$ git push origin 5a60fb4:refs/heads/fix-eslint-warning

Now we just have to open the GitHub repository, click a few buttons, assign a reviewer, and we're done! Repeat that process for the other commits you want to turn into pull requests and you've got the basics of my workflow down!

But wait. Didn't I say this was supposed to be fast? This seems slower than using branches! And now it's even more cumbersome too, what the heck.

Now make it go brrr

Of course I'm not actually running all these commands by hand every time. That's annoying and slow. But the nice thing about what I've described here versus manually branching, is that it's setup to be highly automated. Let's take a look at what my personal tooling pr command does.

$ git pr
#
#
#
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment