Andrew
Andrew

Reputation: 1492

Use Git rebase to condense old commits

Suppose I had a git history that looks like this:

-- A -- B -- C -- D -- 
    \            /
     -----E----- 

Is there a relatively quick way to condense B and C into a single commit?

I would like my final history to look like:

-- A -- BC' -- D' --
    \         /
     ----E----

It's easy to condense B and C using vanilla rebasing - it seems to be getting the new Commit replacing B and C to become the ancestor or D that is troublesome.

With a branch pointing to D checked out, and running git rebase -i master (predecessor to A), and electing to squash C, my resulting history looks like:

A -- BC' -- E'
 \
  --- E

Upvotes: 1

Views: 214

Answers (2)

torek
torek

Reputation: 487745

I'm not sure that this is impossible using git rebase "out of the box" as it were (with --preserve), but it's easy1 to do with git filter-branch instead, by filtering such that you drop commit B entirely. The filter sequence will then copy the tree for C to C' and make C''s parent be A, and will copy the tree for D to D' and make D''s parents (there will still be two) be E and C' respectively.

Note that while I call the copy C' here, it has C's tree, which does include whatever was changed in B as well. So you can properly call it BC' instead and you get just what you drew.

git filter-branch --commit-filter '
    if [ "$GIT_COMMIT" = id-of-B ]; then skip_commit "$@";
    else git commit-tree "$@"; fi' branchname

(fill in the ID for B here, and an appropriate positive-ref; use branchname~5..branchname or similar to reduce the number of commits iterated-over, choosing your ~ value high enough to make filter-branch see what's needed but low enough not to "copy" commits that won't change after all).

You can also do this with lower level git commands (specifically, two git commit-tree commands followed by a forced branch-move). Given footnote 1 below, this might wind up being easier (depends whether D is the tip-most commit of the branch in question, and your comfort level with filter-branch).


1Inasmuch as anything with git filter-branch can ever be said to be "easy"... :-) Even once you understand it thoroughly, there's still the post-filtering clean-up to do as well.

Upvotes: 1

Tim Biegeleisen
Tim Biegeleisen

Reputation: 520878

Let's say that this diagram represents your master branch:

master: A <- B <- C <- D (what you have)
master: A <- S <- D      (what you want)

You can use git rebase -i, which is rebase in interactive mode. You will be given a list of commits as shown here:

git checkout master
git rebase -i master

pick 0vmw31r comment for commit D
pick dnmwe91 comment for commit C
squash 209vmiq comment for commit B
pick d89gmlk comment for commit A

Once you are done, type the following to finish the rebase:

git rebase --continue

By choosing squash for commit B, it will combine commit B into commit C, leaving you with what you want. Keep in mind that you may have merge conflicts from squashing the B and C commits.

It's easy to condense B and C using vanilla rebasing - it seems to be getting the new Commit replacing B and C to become the ancestor or D that is troublesome.

It is not only easy to get the new Commit replacing B and C to become the ancestor of D, it is the default behavior.

Upvotes: 1

Related Questions