Git squash commits in the middle of a branch

13,154

You can do an interactive rebase and hand select the commits which you want to squash. This will rewrite the history of your dev branch, but since you have not pushed these commits, there should not be any negative aftermath from this besides what might happen on your own computer.

Start with the following:

git checkout dev
git rebase -i HEAD~6

This should bring up a window showing you the following list of 7 commits, going back 6 steps from the HEAD of your dev branch:

pick 07c5abd message for commit A
pick dl398cn message for commit B
pick 93nmcdu message for commit C
pick lst28e4 message for commit D
pick 398nmol message for commit E
pick 9kml38d message for commit F
pick 02jmdmp message for commit G

The first commit shown (A above) is the oldest and the last is the most recent. You can see that by default, the option for each commit is pick. If you finished the rebase now, you would just be retaining each commit as it is, which is effectively a no-op. But since you want to squash certain middle commits, edit and change the list to this:

pick 07c5abd message for commit A
pick dl398cn new commit message for "H" goes here
squash 93nmcdu message for commit C
squash lst28e4 message for commit D
pick 398nmol message for commit E
pick 9kml38d message for commit F
pick 02jmdmp message for commit G

Note carefully what is happening above. By typing squash you are telling Git to merge that commit into the one above it, which is the commit which came immediately before it. So this says to squash commit D backwards into commit C, and then to squash C into B, leaving you with just one commit for commits B, C, and D. The other commits remain as is.

Save the file (: wq on Git Bash in Windows), and the rebase is complete. Keep in mind you can get merge conflicts from this as you might expect, but there is nothing special about resolving them and you can carry on as you would with any regular rebase or merge.

If you inspect the branch after the rebase, you will notice that the E, F, and G commits now have new hashes, dates, etc. This is because these commits have actually been replaced by new commits. The reason for this is that you rewrote history, and therefore the commits in general can no longer be the same as they were previously.

Share:
13,154

Related videos on Youtube

deadbeef
Author by

deadbeef

I do prefer to keep an air of mystery about myself :-)

Updated on June 05, 2022

Comments

  • deadbeef
    deadbeef about 2 years

    I want to squash several commits together in the middle of a branch without modifying the commits before and after.

    I have :

    A -- B -- C -- D -- E -- F -- G
    |                             |
    master                        dev
    origin/master
    

    I want to squash that into

    A -- H -- E -- F -- G
    |                   |
    master              dev
    origin/master
    

    Where H is equivalent to B -- C -- D. And I want to be able to specify the commit message of H. A is the last commit that has been pushed so all commits after that can be rewritten without messing up the server. The idea is to clean up the history before I fast forward master.

    How can I do that ?

    PS: Note that in my case I actually have a lot more than 3 commits to squash in the middle, but if I can do it with 3, I should be able to do it with more.

    PPS: Also, if possible, I would prefer a solution where E, F and G remain untouched (mostly regarding the commit date).

  • deadbeef
    deadbeef almost 8 years
    Worked like a charm, thanks ! I didn't expect this to be that easy. However, is there a way to do the same thing without rewriting the last 3 commits ? I noticed that git created 3 new commits (with commit date being the current date).
  • Tim Biegeleisen
    Tim Biegeleisen almost 8 years
    @deadbeef If you ponder your question for a while, you will realize why the answer must be no. Since you rewrote what happened before the E, F, and G commits, these three commits themselves are rewritten and are actually new commits.
  • deadbeef
    deadbeef almost 8 years
    I've been pondering a while and realized it was probably impossible yes :-). However I found git rebase --committer-date-is-author-date which basically does what I want (keeps the original commit date).
  • deadbeef
    deadbeef almost 8 years
    Also, as I mentioned I had a lot more than 3 commits to squash so I used git rebase -i <commit_sha> instead of what you mentioned which was easier than counting all those commits.
  • Tim Biegeleisen
    Tim Biegeleisen almost 8 years
    @deadbeef I didn't know about the keep committer date option, but the hashes of the commits have changed. They are bona-fide new commits. Good on you for not doing this to any published commits.
  • deadbeef
    deadbeef almost 8 years
    Found it on google. I tried it on the first rebase but it didn't work. However I ran it after and it changed all commits after the commit_sha. So in my case doing git rebase --committer-date-is-author-date sha_of_H did what I wanted.