Wednesday, June 3, 2015

Changing the Date on a Series of Git Commits

Fixing Your Mistakes

Sometimes you make a mistake when committing to Git. You’ve either committed the changes with the wrong author tag or at the wrong time. For example, you do a bunch of work and then forget to commit before going on holiday. If you commit when you get back, and if you’re a pendant, you might want to change the dates to when you actually did the work.

I looked into doing this and the various pieces of information I found around the web never seemed to work on its own. I had to cobble together several sources before I got it to work. Hopefully this will save someone the trouble in the future

You can access the Git command line from Visual Studio by selecting Changes in the Team Explorer window, then select Open Command Prompt from the Actions menu.

The Setup

You can start by reviewing the previous commits. For example

 git log -n 4

commit 9d3f11eeaec8751e386bfcb1dbd4fa9267603afd
Author: Colin Svingen <something@somewhere.com>
Date:   Mon Jun 1 17:26:53 2015 -0400

    Fixed a bug...

commit 65ad3df245852a5f8d14a314ee63194687eeb25b
Author: Colin Svingen <something@somewhere.com>
Date:   Mon Jun 1 17:26:21 2015 -0400

    This will change the world!!!1!!

commit 0eb45721f4d0bd1a630bfac8df73ff12c53efb9d
Author: Colin Svingen <something@somewhere.com>
Date:   Mon Jun 1 10:54:07 2015 -0400

    I impress even myself!

commit f51ced7692f8509b8b5eb2c47c65cb64aec9f415
Author: Colin Svingen <something@somewhere.com>
Date:   Mon Jun 1 10:49:30 2015 -0400

    Wow, what a crazy feature!

The important part of the information we need are the commit hashes. To get just the hashes for the last four commits run:

git log -n 4 --pretty=format:"%H"

9d3f11eeaec8751e386bfcb1dbd4fa9267603afd
65ad3df245852a5f8d14a314ee63194687eeb25b
0eb45721f4d0bd1a630bfac8df73ff12c53efb9d
f51ced7692f8509b8b5eb2c47c65cb64aec9f415

Changing the Dates

Now what you need to do is rewrite the history of your branch.

Note: Do not rewrite the history of your repository if you have already shared it. Only do this on a local copy that you have never pushed.

At this point you should make a copy of your local repository as a backup. This could all end in disaster.

To rewrite the history we will use the filter-branch command in Git.

The general form for filter-branch is:

git filter-branch --env-filter "<shell code>"

The whole shell code part is what makes this difficult on Windows. If you have Cygwin, you are probably better off using it as I believe it would be more straight forward. The problem with the Windows command prompt is that the shell code is Bash code, but the escaping and quoting between Windows and Bash gets a little dicey.

The following (don’t run this) would change the date on the all the commits:

git filter-branch --env-filter "GIT_AUTHOR_DATE='Mon May 28 10:49:30 2015 -0400'; GIT_COMMITTER_DATE='Mon May 28 10:49:30 2015 -0400';"

If you ignored my warning and ran it anyway, or made another mistake, you can restore the backup:

git reset --hard refs/original/refs/heads/master

However, what is needed is a way to change specific commits. To do that, you need to wrap the --env-filter in some logic. Specifically an if statement:

git filter-branch --env-filter "if test $GIT_COMMIT = 'f51ced7692f8509b8b5eb2c47c65cb64aec9f415'; then GIT_AUTHOR_DATE='Mon May 28 10:49:30 2015 -0400'; GIT_COMMITTER_DATE='Mon May 28 10:49:30 2015 -0400'; fi"

Note: On Windows this has to be all on one line. If you break it up, it does not work (unlike on Linux).

If you have to run more than one filter-branch, you will need to add the -f switch to force overwritting of the local backup.

When you run this command it will rewrite all of the future commits. That means that they will get new commit ids and the old ones will be invalid, so if you are rewriting multiple commits you need to manually loop. For example (continuing from the previous commands):

git log -n 3 --pretty=format:"%H"

32a62db0c350e8009477ceadbcf99a9b4529647f
7c97b135de3bb4c61c4c9ebea9580cc195e9dd26
3b0f476ca46d925b37990784f578bad424206e65

git filter-branch -f --env-filter "if test $GIT_COMMIT = '3b0f476ca46d925b37990784f578bad424206e65'; then GIT_AUTHOR_DATE='Mon May 28 10:54:07 2015 -0400'; GIT_COMMITTER_DATE='Mon May 28 10:54:07 2015 -0400'; fi"

git log -n 2 --pretty=format:"%H"

1ccf53c0b570c0303cfde658b33b950bb55cc665
578e6a450ff5318981367fe1f6f2390ce60ee045

git filter-branch -f --env-filter "if test $GIT_COMMIT = '578e6a450ff5318981367fe1f6f2390ce60ee045'; then GIT_AUTHOR_DATE='Mon May 28 17:26:21 2015 -0400'; GIT_COMMITTER_DATE='Mon May 28 17:26:21 2015 -0400'; fi"

And so on… When you’re done, you should be left with something like the following:

git log -n 4

commit a7f42406561f7385bd91310829bad17fdf63cc7a
Author: Colin Svingen <something@somewhere.com>
Date:   Mon May 28 17:26:53 2015 -0400

    Fixed a bug...

commit 8a6c4eb659476d4a562182e46c3511b80422eebe
Author: Colin Svingen <something@somewhere.com>
Date:   Mon May 28 17:26:21 2015 -0400

    This will change the world!!!1!!

commit 02818ff733593fbad7157db01813229e54323080
Author: Colin Svingen <something@somewhere.com>
Date:   Mon May 28 10:54:07 2015 -0400

    I impress even myself!

commit 3003d52854f2996a1d4272350bb59a7ca2069770
Author: Colin Svingen <something@somewhere.com>
Date:   Mon May 28 10:49:30 2015 -0400

    Wow, what a crazy feature!

And that is it. Now you can push your modified repo.

References

How to reset date/timestamps for one or more Git commits.
How can one change the timestamp of an old commit in Git.
Changing the timestamp of a previous Git commit.
Generate a Git patch for a specific commit.

3 comments:

  1. Thanks for this write-up! It was exactly what I was looking for. However -- it didn't work for me :(

    I don't know if it because of the version I am using, but with your code, ALL commits got the same date.

    After several hours of trial and error, I found the command that works for me:

    git filter-branch -f --env-filter "if [ $GIT_COMMIT = '3c6c40f768dd789c5d9e435d8f00a95e2cf57732' ]; then export GIT_AUTHOR_DATE='2010-05-03T16:00+03:00'; export GIT_COMMITTER_DATE='2010-05-03T16:00+03:00'; fi"

    (Also, see you can use ISO date formatting? Saves a lot of time not having to figure out how to write the required date!)

    ReplyDelete
    Replies
    1. I don't think I was explicit enough in the post. This is for making changes from the Windows cmd.exe command line. Based on your "if [];" syntax I'd say you're using a real shell. Following the four links in the references section gives lots of detail for the non-Windows world.

      Delete
    2. Not sure what you mean with a "real shell", but I did enter these commands on the Windows cmd.exe command line. :-)

      Delete