Nick Bull
Nick Bull

Reputation: 9876

Squashing past git commits

I have a git history as such:

* aab941c (HEAD -> master) Added ...
| * 2519b79 (participantQueue) Added ...
| * 39c4efb Created ...
|/  
* 87b0cf7 Import ...
| * 5569822 (settings) Modified ...
|/  
* ee67831 Added ...
* c07902f Added ...
* 7f5ab04 Added ...
* 82be721 Modified from `A` to `B`
* d86702b Modified from `A` to `B`
* 8cad721 Modified from `A` to `B`
* 5db240b Removing ...

I want to squash 8cad721 to 82be721, and end up with:

* aab941c (HEAD -> master) Added ...
| * 2519b79 (participantQueue) Added ...
| * 39c4efb Created ...
|/  
* 87b0cf7 Import ...
| * 5569822 (settings) Modified ...
|/  
* ee67831 Added ...
* c07902f Added ...
* 7f5ab04 Added ...
* 82be721 Modified from `A` to `B` (82be721, d86702b squashed into here)
* 5db240b Removing ...

I have tried git rebase -i 5db240b as suggested in other SO answers, replacing pick to squash for d86702b and 82be721. This results in the following:

* 4d91ea0 (HEAD -> master) Added ...
* 7040d3c Import ...
* f6c0fb5 Added ...
* 299c918 Added ...
* 58b209f Added ...
* e8b36f7 Modified from `A` to `B`
| * 2519b79 (participantQueue) Added default participant properties to unpaired queue export
| * 39c4efb Created ...
| * 87b0cf7 Import ...
| | * 5569822 (settings) Modified ...
| |/  
| * ee67831 Added ...
| * c07902f Added ...
| * 7f5ab04 Added ...
| * 82be721 Modified from `A` to `B`
| * d86702b Modified from `A` to `B`
| * 8cad721 Modified from `A` to `B`
|/  
* 5db240b Removing ...

What command should I be using instead?


After implementing the below answer by LeGEC, I have the following:

* 669164c (participantQueue) Added ...
* 5bc13a8 Created ...
| * 6abf940 (settings) Modified ...
| | * 3518be1 (HEAD -> master) Added ...
| |/  
|/|   
* | 2692632 Import ...
|/  
* 810389b Added ...
* 0c85217 Added ...
* 9284cff Added ...
* eee5eef Modified  from `A` to `B`
* 5db240b Removing ...

This threw me for a second as it does look different, but one more commit to master reorders the "trunk" of the git log output as before.

Upvotes: 0

Views: 92

Answers (2)

Mark Adelsberger
Mark Adelsberger

Reputation: 45819

git rebase only rewrites the history of one branch. This may seem strange, but the thing to understand is that rebase does not "edit" existing commits, or even remove them. It only creates new commits, and then optionally moves a single branch so that it uses the new commits instead of the old ones. (In fact commits cannot be changed at all. That's why your new commit has a new commit ID; i.e. in your question you said you wanted to go from

* 82be721 Modified from `A` to `B`
* d86702b Modified from `A` to `B`
* 8cad721 Modified from `A` to `B`
* 5db240b Removing ...

to

* 82be721 Modified from `A` to `B` (82be721, d86702b squashed into here)
* 5db240b Removing ...

That can never happen; 82be721 is immutably a child of d86702b[1]. At a glance that may sound academic, but it's exactly why your other branch doesn't "see" the change - because it's still looking at commit 82be721.)

As noted by LeGEC's answer, one solution in this case is to rewrite each branch's history. This is workable (but still inconvenient) only because your history is relatively simple.

Another option is to use git filter-repo[2], which can rewrite the entire history (even if that history has many branches, tags and other refs, as well as merges that would thwart rebase). Part of why this option is reasonable is that you're just squashing commits - which can be handled as a reparenting rather than a rebase. Details can be found int he filter-repo docs: https://github.com/newren/git-filter-repo


[1] This is slightly stretching the truth because we're working with ID prefixes rather than full ID's. It is possible - though extraordinarily unlikely - to have two commits whose ID's start with the same 7 hex digits. Even so, they would be distinct objects and some other part of their ID would differ. It is truly inconceivable that the exact same ID would be calculated for two commits.

[2] This is one type of operation that could also be done with filter-repo's older cousin, git filter-branch; but it's generally not recommended to use that any longer.

Upvotes: 0

LeGEC
LeGEC

Reputation: 52216

You have to also rewrite the history of participantQueue and settings, so that they fork off the new commits you created for master.

You can use git rebase --onto ... :

# for 'settings' :
git checkout settings
# rewrite onto f6c0fb5 the history coming after ee67831 :
git rebase --onto f6c0fb5 ee67831

# for 'participantQueue' :
git checkout participantQueue
# rewrite onto 7040d3c the history coming after 87b0cf7 :
git rebase --onto 7040d3c 87b0cf7

If you had a bigger set of branches or tags to move, you would be better off using a global rewriting tool, such as git filter-repo.

In your case, with only two extra branches to move, this more manual way will work just as well.

Upvotes: 1

Related Questions