221

Would it make sense to perform git rebase while preserving the commit timestamps?

I believe a consequence would be that the new branch will not necessarily have commit dates chronologically. Is that theoretically possible at all? (e.g. using plumbing commands; just curious here)

If it is theoretically possible, then is it possible in practice with rebase, not to change the timestamps?

For example, assume I have the following tree:

master <jun 2010>
  |
  :
  :
  :     oldbranch <feb 1984>
  :     /
oldcommit <jan 1984>

Now, if I rebase oldbranch on master, the date of the commit changes from feb 1984 to jun 2010. Is it possible to change that behaviour so that the commit timestamp is not changed? In the end I would thus obtain:

      oldbranch <feb 1984>
      /
 master <jun 2010>
    |
    :

Would that make sense at all? Is it even allowed in git to have a history where an old commit has a more recent commit as a parent?

Olivier Verdier
  • 46,998
  • 29
  • 98
  • 90
  • 3
    It's funky that the answer to the question is indeed "you don't need to do anything - that's how it works be default". But now suppose you want commit to be sorted in proper date order while doing rebase (which is pretty natural scenario if you think about it). Now, I wasn't able to find how to achieve that, and posted my q as http://stackoverflow.com/questions/12270357/really-flatten-a-git-merge – pfalcon Sep 04 '12 at 19:51
  • 3
    David mentions another option to reset committer date: `git rebase --committer-date-is-author-date SHA`. See [my edited answer below](http://stackoverflow.com/a/2976598/6309) – VonC Jun 17 '14 at 13:59
  • I just wrote an [extensive answer](http://stackoverflow.com/a/30819930/4265352) on a [similar question](http://stackoverflow.com/q/30790645/4265352) whose author tried the answers explained here and couldn't apply them in a satisfactory manner. – axiac Jun 13 '15 at 14:50

7 Answers7

234

Update June 2014: David Fraser mentions in the comments a solution also detailed in "Change timestamps while rebasing git branch", using the option --committer-date-is-author-date (introduced initially in Jan. 2009 in commit 3f01ad6

Note that the --committer-date-is-author-date option seems to leave the author timestamp, and set the committer timestamp to be the same as the original author timestamp, which is what the OP Olivier Verdier wanted.

I found the last commit with the correct date and did:

git rebase --committer-date-is-author-date SHA

See git am:

--committer-date-is-author-date

By default the command records the date from the e-mail message as the commit author date, and uses the time of commit creation as the committer date.
This allows the user to lie about the committer date by using the same value as the author date.

Note: with Git 2.29 (Q4 2020), git rebase --committer-date-is-author-date or --ignore-date will also work with:

  • interactive rebase (rebase -i/rebase --interactive)
  • for the root commit (git rebase --root)

See "Change timestamps while rebasing git branch".


(Original answer, June 2012)

You could try, for a non-interactive rebase
(see just above: with Git 2.29, Q4 2020, that will work with an interactive rebase as well)

git rebase --ignore-date

(from this SO answer)

This is passed to git am, which mentions:

 --ignore-date

By default the command records the date from the e-mail message as the commit author date, and uses the time of commit creation as the committer date.
This allows the user to lie about the author date by using the same value as the committer date.

For git rebase, this option is "Incompatible with the --interactive option."

Since you can change at will the timestamp of old commit date (with git filter-branch), I suppose you can organize your Git history with whatever commit date order you want/need, even set it to the future!.


As Olivier mentions in his question, the author date is never changed by a rebase;
From the Pro Git Book:

  • The author is the person who originally wrote the work,
  • whereas the committer is the person who last applied the work.

So, if you send in a patch to a project and one of the core members applies the patch, both of you get credit.

To be extra clear, in this instance, as Olivier comments:

the --ignore-date does the opposite of what I was trying to achieve!
Namely, it erases the author's timestamp and replace them with the commits timestamps!
So the right answer to my question is:
Do not do anything, since git rebase does actually not change authors' timestamps by default.


As DylanYoung adds in the comments, using "How to identify conflicting commits by hash during git rebase?":

Using the SEQUENCE_EDITOR variable and rebase interactive, you would just loop over the current todo list and add a command setting the GIT_COMMITER_DATE to the date of the original commit before each commit in the todo.

It's a bit less fiddly because you have the list of original commits to start with (you don't have to hack into git internals to find it), but a bit more work because you have to handle the entire list at once. –

Once you're able to identify the original commit you can do something like:

git rebase -x 'GIT_COMMITTER_DATE="git show -s --format=%ci ``get_current_commit``" git commit --amend --no-edit
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • 2
    Interesting about the arbitrary dates to commit. However, `git rebase --ignore-date` does not work. It changes the dates of the rebased commits. – Olivier Verdier Jun 04 '10 at 18:21
  • @Olivier: strange: did you made a *non*-interactive rebase? And between the Author Date and the Committer Date, are we sure to monitor the "right" date? – VonC Jun 05 '10 at 08:20
  • 2
    Thanks VonC, the difference between author and committer timestamp, that's what makes it all suddenly clear. I wrote the answer to my question in my post, but feel free to adapt your answer to reflect that. – Olivier Verdier Jun 05 '10 at 08:44
  • @Olivier: good point. I have completed my answer to reflect the difference between the two dates. – VonC Jun 05 '10 at 10:39
  • 5
    to be more precise: the `--ignore-date` does the *opposite* of what I was trying to achieve! Namely, it erases the author's timestamp and replace them with the commits timestamps! So the right answer to my question is: do not do anything, since `git rebase` does actually not change authors' timestamps by default. – Olivier Verdier Jun 05 '10 at 11:01
  • 10
    Note that the `--committer-date-is-author-date` option seems to leave the author timestamp, and set the committer timestamp to be the same as the original author timestamp, which is what Olivier wanted... – David Fraser Jun 17 '14 at 10:32
  • @DavidFraser good point. I have included your comment in the answer for more visibility. – VonC Jun 17 '14 at 13:58
  • 1
    Running `git rebase --committer-date-is-author-date SHA_of_root_commit` on all of my branches zipped them all back together again! Yay! – endolith Aug 17 '16 at 15:33
  • Tip: check status with: `git log --pretty=fuller | egrep '^commit|Date'` – bsb Nov 30 '17 at 12:21
  • @VonC In short, there is no way to preserve the committer date with `git-rebase` right? The best you can do is to set the committer date equal to the author date. – Utku Mar 07 '21 at 22:34
  • @Utku Yes, that is the idea. – VonC Mar 07 '21 at 22:40
  • Sure you can. Use `git rebase -x` or `git rebase -i` with the `SEQUENCE_EDITOR` environment variable set to a little script, but there are lots of edge cases you'll have to handle I suspect. – DylanYoung May 27 '21 at 23:53
  • 1
    @DylanYoung do you have an example of such a script? – VonC May 28 '21 at 04:47
  • Not off the top or I'd add an answer, but I believe this is the key: `https://stackoverflow.com/questions/2118364/how-to-identify-conflicting-commits-by-hash-during-git-rebase`. Once you're able to identify the original commit you can do something like: `git rebase -x 'GIT_COMMITTER_DATE="git show -s --format=%ci ``get_current_commit``" git commit --amend --no-edit`' – DylanYoung May 28 '21 at 17:40
  • Forgive the formatting, not sure how to do nested backticks in SO. – DylanYoung May 28 '21 at 17:41
  • Using the SEQUENCE_EDITOR variable and rebase interactive, you would just loop over the current `todo` list and add a command setting the `GIT_COMMITER_DATE` to the date of the original commit before each commit in the `todo`. It's a bit less fiddly because you have the list of original commits to start with (you don't have to hack into git internals to find it), but a bit more work because you have to handle the entire list at once. – DylanYoung May 28 '21 at 17:45
  • @DylanYoung Interesting. I have tried and included your comment in the answer. Please edit it if I have misconstrued your contribution. – VonC May 28 '21 at 19:46
  • `--committer-date-is-author-date` will *not* work if the commit and author dates of a commit already differ. There does not seem to be a way to preserve the **commit** date of the commit in the case that the commit and author dates are different. – wheeler Oct 21 '22 at 20:22
  • @wheeler true, I suspect it did not matter in the context of the OP though. – VonC Oct 21 '22 at 20:37
  • `--committer-date-is-author-date` works for me when i wanted to preserve the old dates. – soMuchToLearnAndShare Oct 24 '22 at 11:34
140

If you've already screwed up the commit dates (perhaps with a rebase) and want to reset them to their corresponding author dates, you can run:

git filter-branch --env-filter 'GIT_COMMITTER_DATE=$GIT_AUTHOR_DATE; export GIT_COMMITTER_DATE'

Andy
  • 11,215
  • 5
  • 31
  • 33
  • 2
    I just tried this, but no effect. I got the folowing output: `WARNING: Ref 'refs/heads/master' is unchanged`. I am using git version 1.7.9.5 on Linux (64 bit) – Markus N. Nov 09 '13 at 11:39
  • 33
    I'd like to add another approach if you've already screwed up but don't want to iterate through the whole history: `git rebase --committer-date-is-author-date ` This way, git will reset the commit date only for the commits applied upon (which is probably the same branch name you used when you screwed up). – speakman Jun 17 '14 at 10:05
  • 1
    The accepted answer didn't work in 2016, but @speakman's answer did! – Theodore R. Smith Jun 21 '16 at 22:22
  • 2
    @speakman's answer didn't work in October 2016, but Andy's did! – Amedee Van Gasse Oct 25 '16 at 08:55
  • 2
    This does not work on Windows. I was able to get it to work using Windows Bash. – vaindil Feb 14 '17 at 19:59
  • https://stackoverflow.com/a/11179245/539149 worked as of 2017/07/31, https://stackoverflow.com/questions/2973996/git-rebase-without-changing-commit-timestamps#comment37478036_11179245 did not. It's too bad that git so often chooses non-human-oriented defaults due to its unix roots, but thankfully is powerful enough to undo them when needed. – Zack Morris Jul 31 '17 at 18:15
47

A crucial question of Von C helped me understand what is going on: when your rebase, the committer's timestamp changes, but not the author's timestamp, which suddenly all makes sense. So my question was actually not precise enough.

The answer is that rebase actually doesn't change the author's timestamps (you don't need to do anything for that), which suits me perfectly.

Olivier Verdier
  • 46,998
  • 29
  • 98
  • 90
  • 4
    +1 - I have a git alias in place (https://coderwall.com/p/euwpig/a-better-git-log) that apparently uses the committer's timestamp, which was confusing me. Gitk and git log both show the author's timestamp. – 1615903 Jan 21 '16 at 11:59
21

By default, git rebase will set the committer's timestamp to the time when the new commit is created, but keep the author's timestamp intact. Most of time, this is the desired behavior, but at some scenarios, we dot not wish to change the commiter's timestamp either. How can we accomplish that? Well, here is the trick I usually do.

First, make sure each of the commits you are about to rebase has a unique commit message and author timestamp (This is where is trick needs improvements, currently it suits my needs though).

Before the rebase, record the committer's timestamp, author's timestamp and commit message of all the commits which will be rebased to a file.

#NOTE: BASE is the commit where your rebase begins
git log --pretty='%ct %at %s' BASE..HEAD > hashlog

Then, let the actual rebase take place.

Finally, we replace the current committer's timestamp with the one recorded in the file if the commit message is the same by using git filter-branch.

 git filter-branch --env-filter '__date=$(__log=$(git log -1 --pretty="%at %s" $GIT_COMMIT); grep -m 1 "$__log" ../../hashlog | cut -d" " -f1); test -n "$__date" && export GIT_COMMITTER_DATE=$__date || cat'

If something goes wrong, just checkout git reflog or all the refs/original/ refs.

Furthormore, you can do the similar thing to the author's timestamp.

For example, if the author's timestamp of some commits are out of order, and without rearrange these commits, we just want the author's timestamp to show in order, then the following commands will help.

git log --pretty='%at %s' COMMIT1..COMMIT2 > hashlog
join -1 1 -2 1 <(cat hashlog | cut -f 1 | sort -nr | awk '{ print NR" "$1 }') <(cat hashlog | awk '{ print NR" "$0 }') | cut -d" " -f2,4- > hashlog_
mv hashlog_ hashlog
git filter-branch --env-filter '__date=$(__log=$(git log -1 --pretty="%s" $GIT_COMMIT); grep -m 1 "$__log" ../../hashlog | cut -d" " -f1); test -n "$__date" && export GIT_AUTHOR_DATE=$__date || cat'
audun
  • 134
  • 1
  • 7
weynhamz
  • 1,968
  • 18
  • 18
  • This is a great trick! It allowed me to only rewrite 75 commits instead of 1100+ from using the other answers. – audun Nov 17 '17 at 01:20
  • This is fantastic! Is there a way to modify the script to also preserve the original committer as well? – David DeMar Jan 31 '18 at 14:39
  • @DavidDeMar should be, the same, just change the git log --pretty to record the original email, and modify the script accordingly. – weynhamz Feb 07 '18 at 10:25
6

The real solution seems to be from Reddit. Augmented slightly, here it is:

git -c rebase.instructionFormat='%s%nexec GIT_COMMITTER_DATE="%cD" git commit --amend --no-edit --allow-empty --allow-empty-message' rebase -i
user541686
  • 205,094
  • 128
  • 528
  • 886
  • Please go a bit more into detail why this is the considered the "real" solution – dfsg76 Feb 23 '23 at 22:28
  • 1
    @dfsg76 because it actually does what was asked without its, ands, or buts. The rest of the answers either reset the commit time to the author time (?!), require messing with the repo to add a post-rewrite hook, or fail when there are duplicate dates and commit messages. – user541686 Feb 24 '23 at 00:10
3

post-rewrite hook

This hook works for all of git rebase, git pull --rebase and git commit --amend.

.git/hooks/post-rewrite

set -eu
echo post-rewrite
if [ ! "${CIROSANTILLI_GITHOOKS_DISABLE:-0}" = 1 ]; then
  declare -a olds
  declare -A oldnew
  while IFS= read -r line; do
    echo "$line"
    old="$(echo "$line" | cut -d ' ' -f1)"
    new="$(echo "$line" | cut -d ' ' -f2)"
    oldnew[$old]="$new"
    olds+=("$old")
    news+=("$new")
  done
  git reset --hard "${news[0]}~"
  for old in "${olds[@]}"; do
    new="${oldnew[$old]}"
    git cherry-pick "$new" &>/dev/null
    olddate="$(git log --format='%cd' -n 1 "$old")"
    CIROSANTILLI_GITHOOKS_DISABLE=1 \
      GIT_COMMITTER_DATE="$olddate" \
      git commit \
      --amend \
      --no-edit \
      --no-verify \
      &>/dev/null \
    ;
  done
  echo
fi

GitHub upstream.

Don't forget to:

chmod +x .git/hooks/post-rewrite

This is a good way to do --committer-date-is-author-date by default on chosen repos, before someone finally patches a config to set it by default.

And it also works with --committer-date-is-author-date which does not seem to be exposed on git pull --rebase.

See also:

Tested on git 2.19, Ubuntu 18.04.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
0

This is the command I used for my case:

GIT_AUTHOR_EMAIL=xaionaro@dx.center git rebase --root -x "bash -c 'git commit --amend --reset-author -CHEAD --date=\"\$(git show --format=%ad -s)\"'"

Here git show --format=%ad -s extracts the current date and --date re-enforces it. And rebase --root -x executes the command bash -c 'git commit --amend --reset-author -CHEAD --date="$(git show --format=%ad -s)"' for each commit.