Combine or rebase an arbitrarily large number of commits

theanine picture theanine · Mar 4, 2014 · Viewed 7.4k times · Source

Let's say my local git log shows:

739b36d3a314483a2d4a14268612cd955c6af9fb a
...
c42fff47a257b72ab3fabaa0bcc2be9cd50d5c89 x
c4149ba120b30955a9285ed721b795cd2b82dd65 y
dce99bcc4b79622d2658208d2371ee490dff7d28 z

My remote git log shows:

c4149ba120b30955a9285ed721b795cd2b82dd65 y
dce99bcc4b79622d2658208d2371ee490dff7d28 z

What's the easiest way to get to this (assuming an arbitrarily large number of local commits):

527b5810cfd8f45f18ae807af1fe1e54a0312bce a ... x
c4149ba120b30955a9285ed721b795cd2b82dd65 y
dce99bcc4b79622d2658208d2371ee490dff7d28 z

Answer

user456814 picture user456814 · Mar 4, 2014

Sometimes, You Might Not Want to Use Interactive Rebase

If the number of intermediary commits between A through X is relatively small, you could get by just fine by using interactive rebasing:

git rebase -i origin/master

However, in my personal experience, using interactive rebasing on a large number of commits is slow. I've run an interactive rebase on about a hundred commits at once (on a Windows machine using Git Bash), and it took a long time for msysgit to generate the interactive rebase commit editor that allows you to select which operations you want to run on which commit, because, well, the list ended up being very large.

In such a situation, you have a couple of workarounds.

Alternative #1: git reset

Mixed and soft resets can be used to modify your working-tree or staging area (respectively) to aggregate/collect all the changes between two commits A and Y, after which you can commit all of the modifications at once as a single commit.

I will only give an example of a soft reset, since it already leaves everything staged for you, while if you use a mixed reset, you'll have to stage the modifications anyways:

# You don't need to create the temp branch A if you
# copy the commit sha A down somewhere so that you can remember it.
git branch temp A

git reset --soft Y
git commit -m "Squash commits A through X"

# Verify that this new commit is equivalent to the final state at A
git diff A

Alternative #2: Using Patches

Another option is to simply use patches. Just generate a diff patch of the difference between A to Y, then apply the patch as a new commit on top of Y:

git diff y a > squash.patch
git checkout -b squash-branch y
git apply squash.patch
git commit -m "Squash commits A through X"

# Verify that this new commit is equivalent to the final state at A
git diff A

As pointed out by @A-B-B in the comments, this won't quite work if there are binary files involved. git diff --binary can be used to output a diff for binary files in addition to the text files, but I'm not sure if those diffs can then be used as patches as well.