In this post I show how to ensure your commits in a GitHub pull request (PR) are in the order you expect for reviewers - i.e. the logical order of the commits to a branch, not the date order.
Warning: this post uses rebasing to rewrite the history of a Git branch. You should only ever do that to a branch if you're sure other users aren't basing their own work on it!
The setup: cleaning up a local branch using rebasing
Let's imagine you're working on a feature in a branch, and you've made several commits. In the image below, we have a branch called test
based off the master branch that has 3 commits:
"Commit 1"
is the first commit"Commit 2"
is the second commit"Commit 3"
is the *drum roll* third commit
At this point you've just about finished the feature, but to make things easier for your colleagues, you decide to clean-up your local branch before creating a Pull Request.
Looking at your commits, you decide that it makes sense for "Commit 3"
to be the first one on the branch, coming before the others.
Likely you would do extra work here, like squashing some commits, splitting others etc. into something that makes a logical story. The example here of rearranging commits is just the simplest case. Of course, if you squash everything into a single commit, then this whole post is moot!
You can easily rearrange commits using interactive rebase by running
git rebase origin/master -i
This pops up an editor listing the current commits on the branch, and lets you rearrange, edit or squash them for example
pick 68f39b8 commit 1
pick d605e5a commit 2
pick f3b9e40 commit 3
# Rebase 82df143..d605e5a onto 82df143 (3 commands)
By rearranging the commits in the file, git will rearrange the commits in the test
branch. Now it will look the following:
"Commit 3"
is now the first commit"Commit 1"
is the second commit"Commit 2"
is the third commit
An important point here is that while rearranging the commits changes the git history, the date associated with the commit doesn't change (on the right hand side of the image above). We'll come back to that later…
With the branch all cleaned up, it's time to push your work to the server. You push the branch to GitHub, and create a pull request for viewing by your colleagues.
I like to create pull request from the command line using the
hub
command line tool. I wrote abouthub
in a previous post.
Everything probably looks OK initially, but if you look a little closer, there's something not quite right…
The problem: GitHub doesn't preserve commit order
The problem is that the order of commits shown in the Pull Request do not reflect the actual order of the commits in the branch:
The image above shows the original commit order of 1, 2, 3 instead of the revised order of 3, 1, 2. That's not very helpful, given that we specifically reordered the commits in the branch to make more sense to reviewers who are reviewing commits in sequence.
Not that GitHub displays commit in ascending order of date, whereas the gitk tool I used in the first two screenshot display commits in descending order of the branch.
This is a known issue in GitHub, with a page dedicated to it on their help pages. Unfortunately, their solution isn't entirely useful:
"If you always want to see commits in order, we recommend not using
git rebase
."
As someone who uses rebasing as a standard part of their workflow, that's not very helpful.
The problem is that GitHub orders the commits shown in the pull request by the author date, not by their logical position in the branch. If you think back to the image of our branch prior to pushing, you'll remember the dates on the commits didn't change when we rebased. That's what GitHub uses, and is the root of the problem.
It's worth mentioning that there are actually two dates associated with each commit: the author date, and the committer date. GitHub uses the author date to control ordering, which is not changed when you reorder commits using rebasing.
After spending a while rebasing a branch recently, only to have GitHub randomly scatter the commits in the PR, I went looking for a solution.
The solution: changing the dates on commits
The only solution I could come up with was to rewrite the commit dates to be in the same order as the commits. I looked for a few automated ways of doing this using git filter-branch
and similar that I won't show here. The main problem I had was that these were too fast. The commits all ended up having the same date. That made things even worse in the pull request - now the commits were ordered completely randomly!
Instead, I opted for a slightly more manual approach. The approach I took was:
- Interactively rebase the branch
- Mark each commit as requiring editing
- For each commit, edit the commit date to be the current time
In 30 seconds, a moderately sized branch can be updated to ensure all the commit dates match their position in the branch.
The first command is
git rebase origin/master -i
This starts an interactive rebase as before. For each commit in the editor that pops up, change pick
to edit
(before the SHA1 hash and title of the commit):
edit f3b9e40 commit 3
edit 68f39b8 commit 1
edit d605e5a commit 2
# Rebase 82df143..d605e5a onto 82df143 (3 commands)
Multi-cursor editing can really speed up this process - I use VS code as my git editor, which has built-in support for multi-cursor editing.
When you close the editor, git will start the rebase process. It will apply a single commit, and then wait, allowing you to make any changes. Rather than change any files, run the command below, which updates the dates on the commit without changing anything else:
git commit --amend --no-edit --date=now
Next, run the following command to move to the next one:
git rebase --continue
Keep running those two commands until the rebase is complete. At this point you're all done, and your commit dates should be in ascending order, nicely matching the commit order in the branch:
If you push this branch to the server and create a pull-request (I used --force-with-lease
to push over the top of the original branch) then the commits in the pull request will now have the same order as the commits in your local branch:
So the final question is: should you actually do this? As someone who frequently heavily rebases branches before creating a PR, I will. But I absolutely wouldn't recommend rebasing a branch like this if other people are (or might be) basing work of your commits. Changing the commit dates changes the SHA hash of the commit, and can be a good way to make yourself very unpopular with your colleagues!
Summary
In this post I discussed the problem that GitHub pull requests don't show commits in the order they are found in a branch. Instead, it shows them ordered by author date. If you have rebased your branch, it's possible you will have rearranged some commits, and edited others, so that the author date no longer reflects the logical order of commits. This can sometimes be confusing for reviewers, if they're viewing commits one-by-one.
To fix the problem, I restored to brute-force. I edited each commit in the branch and updated the time. By stepping through and editing each commit after performing all other rebasing, the date order now matches the logical commit order of the branch so the GitHub pull request commit order will now match the logical commit order too!