31

When I write code I break it into small logical changes that are easy and quick to review.

To do so, I use git rebase -i (interactive) to squash, drop and change order of commits.

I've noticed this sometimes leads to a different order of commits on a GitHub pull request (though the order is retained on the remote branch).

For example,

  • commit 1
  • commit 2
  • commit 3

might show up in the PR as:

  • commit 3
  • commit 1
  • commit 2

I've searched the internet and only managed to find this GitHub help page: Why are my commits in the wrong order? Their answer:

If you rewrite your commit history via git rebase or a force push, you may notice that your commit sequence is out of order when opening a pull request.

GitHub emphasizes Pull Requests as a space for discussion. All aspects of it--comments, references, and commits--are represented in a chronological order. Rewriting your Git commit history while performing rebases alters the space-time continuum, which means that commits may not be represented the way you expect them to in the GitHub interface.

If you always want to see commits in order, we recommend not using git rebase. However, rest assured that nothing is broken when you see things outside of a chronological order!

Is there a way to work around this?

EliadL
  • 6,230
  • 2
  • 26
  • 43
  • I think you reached quite good source of information. I believe github staff knows pretty much about github and it would be diffcult to find someone who knows more. So if [github help](https://help.github.com/articles/why-are-my-commits-in-the-wrong-order/) tells you that the only way to see commits in order is not to use `git rebase` probably there is **no other workarround**. – running.t Jun 22 '17 at 16:23
  • 2
    @running.t please see my answer. :) – EliadL Jun 22 '17 at 16:50
  • 2
    about "GitHub emphasizes Pull Requests as a space for discussion" from my experience, the commits page suffers from the same issue. all the commits are ordered by the creation time. Therefor merged commit are mixed up inside the "original" (pre-merge) commit tree – eplaut Jun 22 '17 at 19:16
  • 2
    Have been looking into this lately. I'm finding the GIT posted help text to be very un-useful. "Alters the space-time continuum" is nonsense. That's saying "because of magic" "git may reorder commits". – Thomas Bitonti Oct 18 '18 at 14:40
  • The base problem seems to be that there are two meanings of relative order to consider: The before-after relationship of commits on a branch, and the ordering of create times for commits. The first can be used to order commits only within the single branch in which they occur. The second can be used to order commits between branches. Note, however, that once new commits are created in the rebased branch, those commits have entirely new create dates. What is being preserved in the PR is the create time of the original commits. – Thomas Bitonti Oct 18 '18 at 14:44
  • This all seems dubious: Keeping related commits together and in their original order is much less confusing than reordering or interleaving them. – Thomas Bitonti Oct 18 '18 at 14:45
  • 1
    _we recommend not using git rebase_ is almost the same as _we recommend not using git at all_. – Pablo Jul 27 '21 at 07:59

6 Answers6

25

I've managed to work around this by:

  1. Locate the last commit that retained the order
  2. Run git rebase -i <hash of that commit>
  3. Replace all pick with reword
  4. Run git push -f

Before that, I tried changing only the first commit message, which also changes all the following hashes, but that didn't fix it.

I had to do it for every following commit too for it to work.

EliadL
  • 6,230
  • 2
  • 26
  • 43
  • Do you know if there's a way to bypass the actual rewording, and just fix the order? Basically the equivalent of immediately exiting `nano` for each commit's rewording. – Aaron Franke Sep 22 '19 at 19:09
  • @AaronFranke I found this answer but haven't tried it myself: https://stackoverflow.com/a/38234236/4960855 – EliadL Sep 22 '19 at 19:17
9

To automate what Eliad suggested I use a script from Piotr:

git rebase "$(git merge-base HEAD master)" --ignore-date -x 'git commit --amend -C HEAD --date="$(date -R)" && sleep 1.05'

nikicc
  • 638
  • 7
  • 15
  • Thanks nikicc. Can you break it down with explanations for each part so that readers like me can understand it? Also, why the `sleep 1.05`? – EliadL Dec 18 '18 at 10:27
  • @Eliad the sleep is there so that there is at least one-second difference between the commit hashes. I never checked this into detail, to be honest, but on a high level, it re-commits all your commits on a certain branch to give then new timestamps, which will make GitHub show them in order. – nikicc Jan 25 '19 at 13:51
  • With 2.21.0 this gives "fatal: cannot combine am options with either interactive or merge options" – mschmoock Mar 30 '19 at 14:00
  • 1
    @mschmoock does this work: `git rebase "$(git merge-base HEAD master)" -x 'git commit --amend -C HEAD --date="$(date -R)" && sleep 1.05'`? I also had some similar problems and they got fixed by removing the `--ignore-date` while all still seems to be fine. – nikicc Mar 30 '19 at 14:31
  • 1
    Is there a way to preserve timestamps and reorder commits chronologically? – Steve May 24 '22 at 00:37
5

You might not have to fix anything anymore (July 2020, 3 years later)

See:

Pull request commits now ordered chronologically

We are changing the way commits are ordered in the pull request timeline and commits view.

Commits are currently ordered by author date, which can cause commits to appear out of order in some scenarios, like after rebasing.

With this change, commits are ordered according to their chronological order in the head branch, which is consistent with the ordering in Git.

This ordering is also reflected in the List commits on a pull request REST API and PullRequest object's timeline connection in GraphQL.

Learn more about pull requests

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
1

How to fix commit order in GitHub pull requests, broken by git rebase?

That could be done with the (still being discussed) 2019 git evolve command:

Objective

Create an "evolve" command to help users craft a high quality commit history.

Users can improve commits one at a time and in any order, then run git evolve to rewrite their recent history to ensure everything is up-to-date.
We track amendments to a commit over time in a change graph. Users can share their progress with others by exchanging their change graphs using the standard push, fetch, and format-patch commands.

Status

This proposal has not been implemented yet.

Background

Imagine you have three sequential changes up for review and you receive feedback that requires editing all three changes.
We'll define the word "change" formally later, but for the moment let's say that a change is a work-in-progress whose final version will be submitted as a commit in the future.

While you're editing one change, more feedback arrives on one of the others.
What do you do?

The evolve command is a convenient way to work with chains of commits that are under review.
Whenever you rebase or amend a commit, the repository remembers that the old commit is obsolete and has been replaced by the new one.

Then, at some point in the future, you can run "git evolve" and the correct sequence of rebases will occur in the correct order such that no commit has an obsolete parent.

Part of making the "evolve" command work involves tracking the edits to a commit over time, which is why we need an change graph.
However, the change graph will also bring other benefits:

  • Users can view the history of a change directly (the sequence of amends and rebases it has undergone, orthogonal to the history of the branch it is on).
  • It will be possible to quickly locate and list all the changes the user currently has in progress.
  • It can be used as part of other high-level commands that combine or split changes.
  • It can be used to decorate commits (in git log, gitk, etc) that are either obsolete or are the tip of a work in progress.
  • By pushing and pulling the change graph, users can collaborate more easily on changes-in-progress.
    This is better than pushing and pulling the changes themselves since the change graph can be used to locate a more specific merge base, allowing for better merges between different versions of the same change.
  • It could be used to correctly rebase local changes and other local branches after running git-filter-branch.
  • It can replace the change-id footer used by gerrit.
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • 1
    Note to self: That was my **21000th answer** on Stack Overflow (in 125 months), 6 months after the [20000th answer](https://stackoverflow.com/a/51915541/6309). Before that, [19000th answer](https://stackoverflow.com/a/49421565/6309), [18000th answer](https://stackoverflow.com/a/46860745/6309), [17000th answer](https://stackoverflow.com/a/43703956/6309), [16000th answer](http://stackoverflow.com/a/40698777/6309), [15000th answer](http://stackoverflow.com/a/37539529/6309) [14000th answer](http://stackoverflow.com/a/34327286/6309), [13000th answer](http://stackoverflow.com/a/31640408/6309), ... – VonC Feb 24 '19 at 21:09
  • Thanks, that's interesting (your answer). If and when it becomes a practical solution, I will consider accepting it. – EliadL Feb 25 '19 at 00:30
  • 1
    @EliadL As it happens, there might be a more practical solution now: https://stackoverflow.com/a/62897861/6309 – VonC Jul 14 '20 at 14:50
1

I propose a ~/.gitconfig alias like this:

[alias]                                                                            
    rewrite = rebase -x 'git commit --amend -C HEAD --date=\"$(date -R)\" && sleep 1.05'

After you completed your regular rebase -i master fixup stuff, you can then simply do a git rewrite master to fix it for GitHub commit order. It will create a new commit each second based on the old ones. By overwriting the --date it will force GitHub to keep the correct order.

mschmoock
  • 20,084
  • 6
  • 33
  • 35
  • Can you elaborate on how this works? And specifically, how does this alias know at which commit I'd like to start "fixing"? – EliadL Mar 31 '19 at 09:24
  • @EliadL Note: Just fixed the missing `"\"` escaped quotes. It knows where to start by giving the commit or branch as last argument (`git rewrite master`). It then creates a new commit with new dates each second, so GitHub can't mess with the ordering. – mschmoock Mar 31 '19 at 10:32
  • Thanks. Actually the `\"` didn't work for me, but the original `"` did. While it seems to work, the new date overrides `AuthorDate` instead of the `CommitDate` (`Author` might be overridden too, didn't check). Since this method *loses some information*, it's less preferable than the currently accepted answer, which doesn't lose that information. – EliadL Mar 31 '19 at 12:51
  • @EliadL Did it work as Alias without `\"` ? If you dont use alias but just copy and past command into console it works without. But in an alias it should not work without escaping. What information do you mean is lost? Because actually this is intended to loose information (the commit date) so GitHub is forced to see correct order... – mschmoock Mar 31 '19 at 17:13
  • Oh yes, sorry. I meant that the `\"` didn't work with copy-paste. Didn't try it as alias. About the lost information, the accepted answer retains the original `AuthorDate` and only updates the `CommitDate`. Your solution simply overrides the `AuthorDate`. Since both solutions work, the one that retains more information seems preferable in my opinion. – EliadL Mar 31 '19 at 22:03
  • I am trying this but I want to do this locally on my repo in case I rewrite hashes somewhere random and I didn't want to. It's not picking up this alias though if I implement a .gitconfig with this command, is there something like `source [profile_name]` with bash that I have to run in order to get it recognised? – Scott Anderson May 16 '19 at 17:19
  • @EliadL I have found `git rebase -x 'git commit --amend -C HEAD && sleep 1.05'` to do the trick of changing the `CommitDate` on the intervening commits without altering the `AuthorDate`. It seems to make commits show up in the expected order on github. But even for the original command that alters `AuthorDate` you can do `git rebase -x 'git commit --amend -C HEAD --date=now && sleep 1.05'` in newer versions of git. The date portion is simpler. I'm also not entirely sure the sleep is necessary, but I'm keeping it in so the timestamps look more reasonable. – harish Feb 19 '20 at 21:47
-1

The --ignore-date option of rebase can be used to reset the author date of commits. This will fix the order of commits on github.

With a force-push, you can even fix the commit order on an existing pull request.

For example:

  1. git checkout my_pull_request_branch
  2. git rebase --ignore-date origin/master
  3. git push -f origin my_pull_request_branch
mkohler
  • 11
  • 1
  • I've reproduced this via `touch 1 2 3; git add 1; git commit -m "1"; git add 2; git commit -m "2"; git add 3; git commit -m "3; git push"` and then did `git rebase -i @~3` to change the order from 123 to **312**. Then ran `git rebase --ignore-date origin/master` as you've suggested with `git push -f`, and got **132** instead of 312 in the PR. Running your lines again generated 312. Every time I run them again, it switches back and forth to 132 and 312. **Unfortunately this seems indeterministic so I cannot accept this answer.** Thanks for trying though! – EliadL Dec 18 '18 at 10:24
  • The `--ignore-date` option to rebase is not used when doing an interactive rebase, this is because rebase has multiple implementations depending on what sort of rebase is being done. The rebase command shown in this answer will use the `--ignore-date` option. See https://git-scm.com/docs/git-rebase#_incompatible_options – Leaf Garland Jan 15 '19 at 03:27
  • @LeafGarland I don't see where `--ignore-date` and `--interactive` are being used in the same command. Can you please clarify? – EliadL Jan 20 '19 at 12:27
  • @Eliad, sorry - I saw that you were running an interactive rebase but did not spot that it was a different rebase to the one where you using the `--ignore-date` option. – Leaf Garland Jan 24 '19 at 20:25