Reorder git commit history by date

This is just an idea, I did not test it for this scenario but I used it (in a different way) to join two Git repositories and keep the original commit dates.

If the history has branches and merges I think it's impossible to re-order them and keep the structure, even manual. The best you can get is a linear history.

Save the commit hashes and the timestamps (%ct = commit date, %at = author date) into a file, sort them by author date:

$ git log --pretty='%H %at %ct' --author-date-order --reverse > /tmp/hashlist

If the order provided by the command above does not satisfy you then force the order by sorting the output using its second field (the author date):

$ git log --pretty='%H %at %ct' | sort -k 2 > /tmp/hashlist

Create a new repository to hold the history ordered by author date. Create an initial commit setting the committer date in the past (before the oldest commit in your repository):

$ GIT_COMMITTER_DATE='2010-01-01 12:00:00' GIT_AUTHOR_DATE='2010-01-01 12:00:00' git commit --allow-empty

Put your own date into the command above.

Add the old repository as a remote into the new one, fetch all its commits. DO NOT set the master branch of the new repo to track the one of the old repo.

Create a script that will cherry pick the provided commit and apply it on top of the current branch, keeping the original author date and commit date:

$ echo 'GIT_AUTHOR_DATE=@$2 GIT_COMMITTER_DATE=@$3 git cherry-pick $1' > /tmp/pick
$ chmod +x /tmp/pick

If you don't want to keep either the original author date or the committer date (or both) then just remove the corresponding assignment from the above command line.

Use the new script with xargs to pick each commit in the selected order and commit it on top of your new master branch.

$ cat /tmp/hashlist | xargs -n 3 /tmp/pick

If everything went well then remove the temporary files created during the process.

$ rm /tmp/hashlist
$ rm /tmp/pick

Remarks:

  • You will get a linear history. The original branches and merges won't be re-created into the new history timeline.
  • The unmerged branches will not be copied at all. You can use git rebase to copy and attach them to the new commits.
  • Even if your repo does not have branches, there is still a high probability to get conflicts on cherry picks; it depends a lot of the changes introduced by the commits in the new order.
  • If it doesn't work you can always remove the new repository and start over (or quit trying); the old repository is not changed.

reorder by interactive rebase

Assuming you would like to change the commit order only on one branch (master).

Create a copy of the branch. This step is optional, you may do it directly on master branch.

git checkout -b master2 master

Create a list of all commits. This command will sort them by Author time, oldest first.

git log --pretty='%H %at' | sort -k2 -n | awk '{ print "pick "$1 }' > /tmp/rebase.list

The output file looks like:

pick <hash of C1>
pick <hash of A1>
pick <hash of A2>
pick <hash of B1>
..

Run the interactive rebase going all the way back to the root of the repository

git rebase -i --root

You will be presented with the current list of commits. Delete everything and load the content of rebase.list file (if your editor happens to be vi, issue commands :1,$d and :r /tmp/rebase.list). Save and exit.

After resolving the conflicts, you should end up with master2 branch with all commits reordered.