After you become familiar with git, it becomes easy to fall into a rut of using the same commands in the same way to the same ends. For me, for almost all of my career, this workflow looked something like this
- Make a small change
- Run
git diff
compulsively - Repeat steps 1 & 2 until satisfied with a small changeset
- Run
git add .
- Run
gcam
(if you use the zsh git plugin ), or,git commit -m
with some moderately helpful message likegit commit -m "Fixed bug in user model"
- Has it been a minute? If so, merge your trunk branch back in
git merge main
to get the most recent changes - Repeat steps 1-6 until the assignment is finished
- Run
gpsup
, or,git push --set-upstream origin $(git_current_branch)
- Create a pull request from the provided link
- Merge with a merge commit
I got really good at doing this really fast. And because I am paranoid about losing my progress, I committed changes a lot, resulting in PRs with 20, 30, maybe even 40 commits to merge in. On the one hand, this has always kept me in the running for āMost Commitsā awards at work (even though Iāll be the first to admit that number of commits says way more about my workflow than my output or actual value/production š ). On the other though, my commits were largely meaningless, unhelpful, and due to the nature of merging trunk back in, they were completely nonlinear.
I vaguely knew that rebase
was a way to help with that, but honestly I was afraid of it! I had used it once or twice before with devastating results and had abandoned it wholesale. Not until a co-worker walked me through rebase
workflows did I begin to see the light and change my own workflow to create a more linear history.
I should pause and mention that learning the difference between merging and rebasing is outside the scope of this article. Lots of better written blogs on the internet will tell you what you need to do know about that. I mostly want to talk about my own journey towards using rebase
, and maybe give a few helpful hints about my new workflow using rebase
.
Telling A Story #
Why does it matter? git rebase
or git merge
ā if all of your changes are going to make their way onto main
and onto production, why should it matter if your commit history is linear or nonlinear?
At the end of the day, itās about telling a story with your git commits. This brings us to one of the greatest ironies of being a software developer: Coding is a creative endeavor used to accomplish deterministic ends. In other words, the process of writing code is more like writing a song or concerto than it is like assembling an Ikea bookshelf ā it requires back and forth, writing and deleting, and lots of discovery. Like writing a song, there are an infinite number of ways to accomplish the same ends. But thatās just it: there are ends that need accomplishing. At the end of the day, the code we write has to do the thing we need it to do, regardless of what it looks like. And, letās not forget, code is run on computers which are ruthlessly deterministic. Creatively forging solutions for an unfeeling machine is a marriage of left and right brain unlike anything else Iāve ever experienced.
The problem comes when I use commits that I authored when I was in my creative flow to tell others how that code accomplishes the deterministic ends that I wrote it to accomplish. I shouldnāt expect others to understand how I came to the solution I did, but I should expect them to be able to see a logical linking of corresponded changes.
Using git rebase
allows me to re-write and re-group my commits in a way that makes more sense to people whose only concern are the deterministic solutions that the code provides. In this way, it allows me to tell a more easily understandable story about why I made the changes I did.
Since this realization, Iāve actually come to take a great deal of pride in my commit messages. I feel like anybody who now stumbles on my PRs or who sees my name as an author when they run git log
will be able to understand why I made the changes I did in the order I did
.
Going From Workflow to Toolbox #
So what is my workflow now? Refreshingly, I donāt use the same workflow on each and every branch. Instead, git rebase
has given me a box full of tools to use in the right situation. Letās go over some of these tools!
git rebase -i #
This is the workhorse of the toolbox. I use this command lots of different ways but mainly (1) whenever I need to bring in changes from main
, and (2) when Iām about ready to push up my branch to origin
and I want to clean up and regroup my commits
Running git rebase -i main
on this dummy repo Iāve created gives you a screen that looks like this:
As I just mentioned, this is the command I use when Iām getting ready to clean up my commits to tell a story. Normally, once Iām in the interactive ( -i
) git window, Iāll follow this workflow:
- Reorder commits that are related. This is a good opportunity to move your
fixes typo
orlinting fixes
commit to be closer to the original line it edits, but itās also an opportunity group together changes by file or by domain. For example, if I were building out a frontend that called a new controller, I might ping and pong between the front and back ends ā build out a REST method, commit, then build out the corresponding UI, commit, then build out the next REST method, commit, etc. But maybe I decide that it would be better to group together all my controller changes and then all my frontend changes? This is the time to do that - Once my commits are in a good order, Iāll decide which ones I want to keep. The commands I use most commonly here are
fixup
, which will meld that commit with the previous one,reword
, if I want to give a more comprehensive commit message for a commit, and occasionallydrop
if the commit is for changes that I ended up deleting later anyways. Continuing with the previous example, maybe instead of having multiple commits (Added create method to controller
,Added delete method to controller
) I decide I want just one bigComplete CRUD methods on users_controller
. I couldfixup
all those previous commits and thenreword
it to reflect those changes
At this point, grouping my frontend and backend changes and rewording those commits to reflect all the changes, my terminal should look something like this:
- Run
:wq
and let git do itās thing! If youāve chosen toreword
any commits, at this point git will stop along its rebase process and let you rewrite the commit
git commit āfixup abc123def #
This is a little hack to make life easier when you finally decide to git rebase
. Letās say I add my name to the README
and this commit produces the hash 02ba0ca
. Later I learn that I have spelled my name wrong. Instead of writing a new commit and then remembering where it should go and where to fixup
-it when I rebase, I can tell git to associate it with the commit that I will ultimately want to fixup
it on by making the change, staging the commits, then running git commit --fixup 02ba0ca
Now if I run git log
you see something kind of different:
It doesnāt rebase
and fixup
for you ā it commits a placeholder. The real magic comes when youāre ready to rebase. If you run git rebase -i --autosquash main
youāll see that Git automatically sets that commit to fixup
to the commit you indicated:
If you find yourself doing this alot, you can run
Which enables you to drop the --autosquash
flag when you rebase.
git undo #
This is my hackiest of all hacks with rebase, and I donāt use it that often, but often enough to include here. Sometimes if my commits are a real mess, I actually want to just start from scratch with all my changes uncommitted and unstaged and pick and choose changes to commit in the order I want.
When I go this route, I follow this workflow
- Run
git rebase -i
and fixup ALL my commits to 1 single commit (you couldsquash
them too) - Run
git reset --soft HEAD^
ā this undoes the last commit but keeps your changes - Run
git reset
to unstage all the changes
This gives you a blank canvas to work with. You can then use a GUI editor like Tower to select bits and pieces to stage then commit, or if youāre hardcore, you could run git add --patch
and do it from the command line.
Hopefully you can use these tools to tell the story of your code, and enjoy it like I have.
Happy coding!