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
--squashflag:The first step attaches your
HEADto the branch nameB, checking out whichever commit the nameBidentifies:Here the name
Bidentifies 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 commitEto 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
Gis in commitI, but commitIdoesn't remember why that work is there. A futuregit mergefrom commitHhas 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 mergedGwithgit merge --squashwe should stop usingFandGentirely. This means we must also stop usingHentirely. IfHis useful, we should copy it to a new, different commit:giving us:
followed by:
to copy
Hto a very similar, but not identical, commitH':We can now discard
somebranch:and rename
newbranchtosomebranchif 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-pickcannot copy merge commits, and anyone who is using the commits we want to kill off—here theF-G-Hchain—must do this killing-off-with-copying-the-ones-to-save thing. So before using--squash, be sure you understand all the implications.)