Chris Maes
Chris Maes

Reputation: 37792

git rebase squash take second message (like fixup)

Suppose I have some commits:

<sha1> bug due to function1
<sha2> bug due to function2
... other commits

and I would like to squash commits 1 and 2 together, keeping only the message of the second commit, then I would use git rebase -i, edit to:

pick <sha1> bug due to function1
squash <sha2> bug due to function2
... other commits

And I would always need to edit the combined messages and delete the first one.

I know I could rearrange the commits and use fixup like this:

pick <sha2> bug due to function2
fixup <sha1> bug due to function1
pick <sha3> other commit

but then I have the risk that, reversing the order of the two commits, there might be some conflicts.

How could I achieve the same result with less manipulations, especially avoiding the editing of the combined message. Note that there might be many commits before commit 1 and after commit 2.

Upvotes: 4

Views: 906

Answers (4)

Guildenstern
Guildenstern

Reputation: 3841

Use fixup -C in the rebase todo editor. [1]

pick <sha1> bug due to function1
fixup -C <sha2> bug due to function2
... other commits

Documented in the editor session.

# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
#                    commit's log message, unless -C is used, in which case
#                    keep only this commit's message; -c is same as -C but
#                    opens the editor

Quirks

Note that the squash commit will get the author and timestamp from <sha1>, not from <sha2> (thanks to Johannes in the comments).

Notes

  1. Since Git v2.32.0

Upvotes: 2

VonC
VonC

Reputation: 1328122

Since the OP's question, do note the behavior of an interactive rebase did change (and is now fixed, Q1 2021).

When "git rebase -i"(man) processes fixup insn, there is no reason to clean up the commit log message, but we did the usual stripspace processing.
This has been corrected with Git 2.31 (Q1 2021).

See commit f7d42ce (28 Jan 2021) by Johannes Schindelin (dscho).
(Merged by Junio C Hamano -- gitster -- in commit 7e94720, 10 Feb 2021)

rebase -i: do leave commit message intact in fixup! chains

Reported-by: Vojtěch Knyttl
Helped-by: Martin Ågren
Signed-off-by: Johannes Schindelin

In 6e98de7 ("sequencer (rebase -i): add support for the 'fixup' and 'squash' commands", 2017-01-02, Git v2.12.0-rc0 -- merge listed in batch #8), this developer introduced a change of behavior by mistake: when encountering a fixup! commit (or multiple fixup! commits) without any squash! commit thrown in, the final git commit(man) was invoked with --cleanup=strip.
Prior to that commit, the commit command had been called without that --cleanup option.

Since we explicitly read the original commit message from a file in that case, there is really no sense in forcing that clean-up.

We actually need to actively suppress that clean-up lest a configured commit.cleanup may interfere with what we want to do: leave the commit message unchanged.


Git 2.43 (Q4 2023) updates an error message (which would probably never been seen).

See commit 82af2c6 (03 Sep 2023) by Oswald Buddenhagen (ossilator).
(Merged by Junio C Hamano -- gitster -- in commit d070b77, 13 Sep 2023)

sequencer: fix error message on failure to copy SQUASH_MSG

Signed-off-by: Oswald Buddenhagen
Acked-by: Phillip Wood

The message talked about renaming, while the actual action is copying.
This was introduced by 6e98de7 ("sequencer (rebase -i): add support for the 'fixup' and 'squash' commands", 2017-01-02, Git v2.12.0-rc0 -- merge listed in batch #8).

Instead of:

could not rename '%s' to '%s'

You now have:

could not copy '%s' to '%s'

Upvotes: 0

Chris Maes
Chris Maes

Reputation: 37792

A fully automated version. Suppose a git history like this:

... (as many commits as you like)
6acdc6f - commit message 3
46c9468 - commit message 2
9b28fd5 - commit message 1

then rebasing to squash commit 1 and 2 together and keep commit message 2:

git rebase -i 9b28fd5~

and then editing like this:

pick 9b28fd5 commit message 1
pick 46c9468 commit message 2
exec HH=$(git rev-parse HEAD); git reset --soft HEAD~2; git commit -C $HH
pick 6acdc6f commit message 3

a small explanation of the shell commands:

HH=$(git rev-parse HEAD) # store the HEAD sha1 in a variable; since the next call will change HEAD to HEAD~2
git reset --soft HEAD~2  # destroy the last two commits keeping the changes staged.
git commit -C $HH        # now commit all changes reusing the commit message from commit2 (using the sha1 that we saved in the variable)

Upvotes: 2

mkrufky
mkrufky

Reputation: 3388

First do git log to view the hashes of the last four commits. Reset to the last commit before the two that you want to squash:

git reset --hard <sha0>

...where is the last commit before the two that you want to squash.

Then, try the following commands in order:

git cherry-pick -n <sha1>
git cherry-pick -n <sha2>
git commit
git cherry-pick    <sha3>

The first two commands will combine <sha1> and <sha2> in your local staging sandbox. When you do git commit with no arguments, it will commit the changes using the commit message of the last commit. All you need to do is exit the editor after reviewing the commit message. The final cherry-pick applies the last commit, unchanged.

Upvotes: 2

Related Questions