user7692855
user7692855

Reputation: 1428

Git rebase Keep latest commit date

I've been working on a feature branch which is ready to be push to remote. It has 5 or so commits some are very quick commits which do not deserve it's own commit. I would like the feature to be one commit in total before I push it to remote for review.

I would normally do:

git rebase -i HEAD~5

Then fold the commits into the previous commit. This will give me one commit as I would like however, the commit date is the date of the first commit. I would like one commit with the commit date as the last commit.

How do I fold the commits into the last commit instead of the first commit?

Upvotes: 10

Views: 3350

Answers (4)

TTT
TTT

Reputation: 29159

Here's yet another (kind of cool) way to accomplish the same thing:

Cherry-pick your top commit to make an empty copy of it:

git cherry-pick --keep-redundant-commits HEAD

Now interactive rebase as you would have before, either specifying the exact commit ID or adding 1 to the previous parent count:

git rebase -i HEAD~6

Now move the last commit to the top of the list (for your convenience Git will notate that the commit is empty). Pick the top commit, and change all commits below it to "f" for "fixup". Save and exit, and you're done.

Effectively you're doing the identical squash, but using the author, date, and message of the last commit instead of the first one.

Upvotes: 3

Masa Sakano
Masa Sakano

Reputation: 2267

The answer by @torek is extensive, detailed, and excellent. Let me add 3 practical command-line options so you can easily copy and paste, along with a bit of background.

The Git repository keeps two pieces of time information: author-time and commit-time. What you want to modify is the author-time of the last commit (or HEAD in the current branch), which you have just made, as opposed to the commit-time (I believe it is difficult to modify the latter and anyway you don't need it, either, in most cases).

Using the example diagram of the git history by @torek immediately after git rebase -i HEAD~5,

           E--F--G--H--I   [abandoned]
          /
A--B--C--D--J   <-- master

commit J is the current head, which has the newest commit time (newer than any other commits) and author time the same as that of commit E.

Now, what you do is to create another commit K with an author time of your choice, which will become the new HEAD (see the answer by @torek for detail). You can make it by running git commit --amend --date=XXX, but what is your choice of the author-time "XXX"?

I can think of 3 most common options:

  1. If you don't mind a slight difference in time and are fine with just the current time,

      git commit --amend --no-edit --date=now
    
  2. If your choice is the last amended time (commit-time), do either of (they are just aliases)

      git rebase --ignore-date HEAD
      git rebase --committer-date-is-author-date HEAD
    
  3. If your choice is the commit-time (and author-time) of your last git commit, i.e., commit I with commit-ID of SHA_I

      git commit --amend --no-edit --date=`git log -n 1 --format=%ct SHA_I`
    

Note that SHA_I in (7-char) string should have been printed out when you did git commit, which created commit I. Or, git reflog will display the list.

If you want to confirm the commit-date before amending, do git log -n 1 --format=%ci [SHA_I] (or %cd); and for the author-date, replace %ci with %ai etc.

Upvotes: 2

shubhparekh
shubhparekh

Reputation: 41

git rebase -i SHA

After you rebase there are 3 options available select any one of them. Option 1 works for recent commit only. Option 2 and 3 does not work with interactive rebase hence a separate rebase call is needed else would have clubbed in above rebase call only.

  1. git commit --amend --no-edit --date=now
  2. git rebase --ignore-date SHA
  3. git rebase --committer-date--is-author-date SHA

In your scenario you can find the latest commit date before rebase and provide that after rebase via option 1. If you are not aware of date format to use try echo $(date)

(from this SO answers answer1, answer2)

Upvotes: 3

torek
torek

Reputation: 490118

You can set any arbitrary date you like on any new commit.

When you combine commits together with rebase -i, you're not actually changing the old commits, you are instead making one new commit, after which you simply stop using the old commits:

(before rebase -i)

A--B--C--D--E--F--G--H--I   <-- master

(after rebase -i)

           E--F--G--H--I   [abandoned]
          /
A--B--C--D--J   <-- master

where J is the new "all things combined" commit.

The bad news is that git rebase -i is designed to retain the author date-stamp on the commit into which other commits are squashed or fixup-ed. So it already uses the "set arbitrary date" feature to force J to have E's date. The interactive rebase command has no flag to change this.1

But there's a simple workaround: having made new commit J as the new-and-improved variant of E-F-G-H-I, you can simply use the same general idea to abandon J in favor of even-newer, more-improved K, which is just like J except that it has the date you desire.

To do this, after rebasing, run git commit --amend --date=... (supply whatever date you like for the ... part). You can change the author as well; see the git commit documentation. This will make new commit K to replace J, just as J replaced E-F-G-H-I, leaving you with:

           E--F--G--H--I   [abandoned]
          /
A--B--C--D--K   <-- master
          \
           J   [abandoned]

The commits that you have abandoned will, if nothing else lets you find them, eventually (somewhere after 30 days or so) lose their last methods of finding them—their IDs are retained in reflogs for some expiration period in case you want them back—and once they're truly unreferenced, the garbage collection pass (as run by git gc --auto which in turn is run for you by various other Git commands) will remove them for real.


1The non-interactive, git am based git rebase has a flag, but doesn't have the squash feature.

Upvotes: 6

Related Questions