Reputation: 1492
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
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
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