A "squash merge" is not a merge at all, so this drawing is wrong in several important ways:
D--E--F--H'--I--J--K (mybranch)
/ \
A--B--C--G--H--L--M--N--O (master)
... but luckily it gives me everything we need to know, to know how to handle this as easily as possible.
First, you said that H'
was a squash merge from master into mybranch
. The \
should probably have been /
(we want "later" commits to be to the right of "earlier" commits), but it really should not indicate a merge at all, and probably should be named differently.
So, here is how I would re-draw the graph fragment (as correctly as I can manage anyway—you will want to re-check all of this carefully on your end before using this):
D--E--F----BCGH'--I--J--K <-- mybranch
/
A--B--C--G--H----L--M--N--O <-- master
The commit named BCGH'
here is the squash "merge"1 of commits B--C--G--H
, which you had called H'
. It is a copy of all the work from the original four commits.
Using rebase, omitting the squash
You might now want to rebase mybranch
on the tip of master
. This is probably the easiest and best approach, though it does mean "rewriting history" (pretending that you started your work after commit O
). To do that, use an interactive rebase:
$ git checkout mybranch
$ git rebase -i master
and then specifically delete commit BCGH'
from the pick
list. This will ask Git to cherry-pick each of commits D
, E
, F
, I
, J
, and K
(in that order unless you also re-order the pick
lines):
D--E--F----BCGH'--I--J--K [will be abandoned]
/
A--B--C--G--H----L--M--N--O <-- master
\
D'-E'-F'-I'-J'-K' <-- mybranch
Using merge, getting Git to do the work
Alternatively, if you really want to merge, make a new (but temporary) branch, starting from commit A
, onto which you cherry-pick just those same six commits:
$ git checkout -b tempbranch mybranch~7 # this should start from A
$ git cherry-pick mybranch~7..mybranch~4 # pick D, E, and F
$ git cherry-pick mybranch~3..mybranch # pick I, J, and K
D--E--F----BCGH'--I--J--K <-- mybranch
/
A--B--C--G--H----L--M--N--O <-- master
\
D'--E'--F'--I'--J'--K' <-- tempbranch
Note that tempbranch
has no BCGH'
copy of the commits from master
, and is otherwise basically the same as what we would get if we rebased onto the tip of master.2 The point of making this particular copy is to obtain the source tree associated with this new commit K'
, because now we can run:
$ git merge master
to make a new (albeit temporary) merge commit T
, made without interference from BCGH'
:
D--E--F----BCGH'--I--J--K <-- mybranch
/
A--B--C--G--H----L--M--N--O <-- master
\ \
D'--E'--F'--I'--J'--K'----T <-- tempbranch
This new merge commit T
has the source tree we would like to obtain if we make a real merge from master
into mybranch
. So now let's start that merge, which fails with merge conflicts because of commit BCGH'
:
$ git checkout mybranch
$ git merge master
This fails with conflicts, so let's now resolve all the conflicts by discarding the entire merge result and replacing it with the tree from temporary merge T
. Note that you must be at the top level of your git tree here so that .
refers to everything:
$ git rm -rf .
$ git checkout tempbranch -- .
The first step discards the entire work tree (and index/staging-area contents), and the second step creates an all-new work tree (and index contents) from the tipmost commit of tempbranch
, which is commit T
.
Now you can commit the result (though it's wise to check it carefully first—of course, you can do that on branch tempbranch
before you even get this far). Once you commit, you will have this:
D--E--F----BCGH'--I--J--K-P <-- mybranch
/ /
A--B--C--G--H----L--M--N--O <-- master
\ \
D'--E'--F'--I'--J'--K'----T <-- tempbranch
where P
is a proper merge, using the source tree from T
. It is now safe to delete tempbranch
entirely:
$ git branch -D tempbranch
and you are left with a merge that Git did using a temporary branch that omitted commit BCGH'
.
More options, and notes on how and why to do whatever you do
There are additional ways to work this, such as making a new branch starting from commit F
, doing a real merge from master
into this new branch (instead of a squash "merge"), then cherry-picking I
through K
and doing another real merge from master
.
The only good reasons to use any one method, compared to any other method, are:
- if you like that method better;
- if it is easier for you; and/or
- if other people have some of those commits and you don't want to make the other people do more work.
In the end, commits give you two things: a state (a work-tree, plus metadata like commit author, date, and log message), and a history (what commits come before this commit?). The idea is to make exactly as many commits as needed to provide enough state and history to make sense and enable cooperative development, without providing so much state and history as to obscure sense and disable cooperative development.
1The quotes around "merge" are because, again, a squash merge is not a merge. A merge is a commit with two or more parents, and these squash "merge" commits do not record a second parent: they discard key information, which is why they become so much trouble later if you are not very careful with them.
2What I mean here is it's the same in terms of commits as what we would want if we were to rebase. It is, of course, different in terms of the associated source trees, since it does not contain any of the work done in B
, C
, G
, H
, L
, M
, N
, or O
. A rebase starting from O
starts from a source tree that has all that work done.