Git squash history after merge Git squash history after merge git git

Git squash history after merge


I was able to squash several commits after multiple merges from the master branch using the strategy found here: https://stackoverflow.com/a/17141512/1388104

git checkout my-branch            # The branch you want to squashgit branch -m my-branch-old       # Change the name to something oldgit checkout master               # Checkout the master branchgit checkout -b my-branch         # Create a new branchgit merge --squash my-branch-old  # Get all the changes from your old branchgit commit                        # Create one new commit

You will have to force an update if you need to push your squashed branch to a remote repository that you have previously pushed to, e.g. git push origin my-branch -f


The solution I ended up using was to manually recreate the history. I did this mainly because I didn't want to spend too much time looking for an elegant solution and there wasn't that much history (around 30 commits I'd have to manually merge).

So, I created a branch before I merged the huge upstream:

git checkout -b remove-history-fix <commit ID before merge>

Then re-merged the upstream using the --squash option.

git merge --squash <upstream tag>

Then manually cherry-picked the commits after the merge from the old branch (the one with the huge amount of upstream history).

git cherry-pick <commit ID>

After all those commits were merged into my remove-history-fix branch, I removed the branch with the upstream history.

git branch -D <upstream-history-branch>


A couple of options for you:

Limit Logging

Not exactly what you asked for, but possibly a good alternative, and a lot easier. This allows you to use git like normal, but hides all the stuff you don't want to see (assuming the issue is the history cluttering up your log and not the raw storage space. I think squashing the merge in your branch won't prevent git from including all the commits from upstream if you fetched the upstream for the merge action in the first place.).

In this case, you would do a normal merge, but when logging you would add --first-parent to the command.

For example, without the option I might have (assume "sample more" 1 to 3 was actually a lot more commits)

$ git log --oneline0e151bf Merge remote-tracking branch 'origin/master' into nosquashf578cbb sample more 37bc88cf sample more 2682b412 sample more 1fc6e1b3 Merge remote-tracking branch 'origin/master'29ed293 More stuff9577f30 my local change018cb03 Another commita5166b1 Initial

But, if I add --first-parent it cleans up to this:

$ git log --oneline --first-parent0e151bf Merge remote-tracking branch 'origin/master'fc6e1b3 Merge remote-tracking branch 'origin/master'9577f30 my local change018cb03 Another commita5166b1 Initial

Notice all of the commits from the master after I branched ("my local change" being my divergent commit) are gone. Only commits I made show up, including when I merged. If I had used better commit messages during the merge, I might even know what the batch of changes were.

Replace History

This is for what you asked.

Taking inspiration from https://git-scm.com/book/en/v2/Git-Tools-Replace

What we'll do here is squash the remote's history, replace their history with our squashed version from our perspective, and merge the squashed version.

In my example repository, the revisions that upstream added which I hadn't merged yet were 682b412 "sample more 1" to origin/master (f578cbb "sample more 3") (although not that long for this example, pretend there are 50 commits or whatever in between).

The first thing I want is a local branch of the remote side:

git checkout -b squashing origin/master

Next, I want to quickly squash it

git reset --soft 682b412~git commit -m "Squashed upstream"

Note the tilde ~ character. That causes our branch to be at the parent of the first commit in the range we want to squash, and because we specified --soft, our index is still at the last commit in the range we want to squash. The commit line results in a single commit that consists of what was our first through last, inclusive.

At this point, the origin/master and squashing branches have identical tree contents but different histories.

Now, we tell git that when it sees references to the original commit of origin/master, to use our squashed commit instead. Using git log I can see the new "Squashed upstream" commit is 1f0bc14, so we do:

git replace f578cbb 1f0bc14

From here on, your git will use the "squashed upstream" commit.

Back on our original branch (if it was "master")

git checkout mastergit merge f578cbb

This appears to merge the origin master (f578cbb), actually gets 1f0bc14's contents, but logs it as having a parent SHA1 of f578cbb

We no longer need the squashing branch, so you can get rid of it.

Now, let's say upstream added more features. In this simple example, on upstream's repo, a log might show this:

84f5044 new featuref578cbb sample more 37bc88cf sample more 2682b412 sample more 129ed293 More stuff018cb03 Another commita5166b1 Initia

After we fetch upstream though, if we look at its log from our repo, we see this instead:

84f5044 new featuref578cbb squashed upstream29ed293 More stuff018cb03 Another commita5166b1 Initial

Note how it appears to have squashed history to us as well, and more importantly, the squashed upstream SHA1 is showing the one used in upstream's history (for them it is really the "sample more 3" commit).

So, merging continues to work like normal

git merge origin/master

But we don't have such a cluttered log:

4a9b5b7 Merge remote-tracking branch 'origin/master' for new feature46843b5 Merge remote-tracking branch 'origin/master'84f5044 new featuref578cbb squashed upstreamfc6e1b3 Merge remote-tracking branch 'origin/master'29ed293 More stuff9577f30 my local change018cb03 Another commita5166b1 Initial

If the "new feature" commit in upstream was similarly a large number of commits, we could repeat this process to squash that down as well.