I have a primary development branch (A) which has a long history. All release commits in A are tagged as such. I checked out the root commit of A, and branched into test (B).
So I have a primary branch A, and the head of branch B pointing at the root commit of A. My goal is to create a history of all releases by merging each tagged commit from A to B.
The first merge from A into B works as expected, no conflicts.
$git checkout B
$git merge [release-commit-ID] --squash
$git commit -m "release#"
The first commit works great, but all other commits treat all merge commits as complete conflicts. I see that the root of B is the same as the root of A, but no shared history is recognized after the first squashed merge commit from the first release commit in A into B. What is causing the conflicts and how do I get shared history to be recognized?
There is no shared history (or rather, not enough). There's nothing to recognize, which is why there are conflicts.
The key is the
--squash
flag:The first step attaches your
HEAD
to the branch nameB
, checking out whichever commit the nameB
identifies:Here the name
B
identifies commitE
(each of these letters stands in for the real hash IDs). Commit*
(which I would have namedB
, but you used that name for your branch) is the point at which the two development streams diverge, which means that when we work backwards (as Git does) it's the point where they come together. You now rungit merge --squash <hash>
where<hash>
identifies commitF
, orG
, orH
, so Git compares the contents of commit*
to the contents of commitE
to find out what you changed:and repeats with, say,
G
:Git now combines these two sets of changes, applies the combined changes to commit
*
's contents, and makes a new commit.If you don't use
--squash
, Git records the new commit with two parents:and now the most recent common starting point between the two lines is commit
G
. But if you do use--squash
, Git records the new commit with just one parent:and now the common starting point is unchanged. All the work through
G
is in commitI
, but commitI
doesn't remember why that work is there. A futuregit merge
from commitH
has to start over at commit*
.Git won't stop you from developing on branch
somebranch
, but in general, after agit merge --squash
, you should consider the branch you merged from to be "dead" and just stop using it at all. Once we've mergedG
withgit merge --squash
we should stop usingF
andG
entirely. This means we must also stop usingH
entirely. IfH
is useful, we should copy it to a new, different commit:giving us:
followed by:
to copy
H
to a very similar, but not identical, commitH'
:We can now discard
somebranch
:and rename
newbranch
tosomebranch
if we like.(Note that we can do this copy-with-name-moving thing in one step using
git rebase --onto
, withgit checkout somebranch; git rebase --onto B <hash of G>
. No matter how you do it, though, be aware thatgit cherry-pick
cannot copy merge commits, and anyone who is using the commits we want to kill off—here theF-G-H
chain—must do this killing-off-with-copying-the-ones-to-save thing. So before using--squash
, be sure you understand all the implications.)