Reputation: 1428
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
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
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:
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
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
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
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.
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
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 squash
ed 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