Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

git: merging a subtree from one branch to another

I'm having trouble merging a subtree of a development branch back into the integration branch.

I have two branches, one was used for development and one is used for integration. Extensive development has been done in the development branch and I want to merge a portion of it back in. The specific portion that I want to merge is all contained in one subtree of the development branch.

My directory structure is like this:

[Branch A]              [Branch B]
    |                       |
    +--Dir1                 +--Dir1
    +--Dir2                 +--Dir2
        |                   |   |
        +--DirA             |   +--DirA
            |               |       |
            +--File1        |       +--File1
            +--File2        |       +--File2
                            |       +--File3
                            |       +--File4
                            +--Dir3

I want to merge Branch B/Dir2/DirA into Branch A/Dir2/DirA. I want File1 and File2 to be merged and File3 and File4 should be created in Branch A. I don't want to pick up Dir3, or any changes in Dir1.

I've tried the steps outlined by kernel.org for merging subtrees, but they fail when I do the git read-tree with:

error: Entry 'Dir1/DirA/File1' overlaps with 'Dir1/DirA/File1'.  Cannot bind.

I've tried using the subtree script which is hosted on github, but I am not having much luck with it. When I do:

git checkout Branch_A
git subtree merge -P Dir2/DirA Branch_B

I see evidence that Dir3 has been merged and the merge fails with conflicts.

I could cherry-pick the files to merge, but that seems unnecessarily convoluted for what should be a common and straight-forward problem.

like image 405
Brendan Avatar asked Jun 21 '11 23:06

Brendan


2 Answers

What you want may be simply:

git merge Branch_B -s ours  [OPTIONAL]
git checkout Branch_B Dir2/DirA

Details

This recipe copies a subtree from Branch_B to Branch_A, and (OPTIONALLY) also creates a merge-commit in git's project history:

  1. Switch to Branch_A in case you aren't there already.

    git checkout Branch_A

  2. (OPTIONAL) Establish a merge commit, without changing any files in your workspace (merge strategy "ours"). This "empty" commit just adds the other branch as a second parent, creating a history link. If that's unimportant to you, you may skip this step; it's not required for the subsequent step.

    git merge Branch_B -s ours

    NOTE: only do this in a clean workspace; this command may fail if you have any files modified or staged.

  3. Copy the contents of subtree Dir2/DirA from Branch_B into your workspace, without changing your active branch.

    git checkout Branch_B Dir2/DirA

  4. Commit the files from Branch_B into Branch_A. If you omit --amend then a new commit is created for this step; using it will make the new files part of the earlier merge commit instead.

    git commit --amend

The form of checkout in step 3 may seem unfamiliar, but the official documentation states that the customary branch-switching behavior is actually just a convenience when this form is not used:

If no paths are given, git checkout will also update HEAD to set the specified branch as the current branch.

So if you do supply an explicit path, it will not switch branches, just copy the files from that path.

like image 193
Sean Gugler Avatar answered Oct 06 '22 02:10

Sean Gugler


The problem you have is that git isn't designed to do such a thing. Consider the following history that might result in your two branches A and B from the question:

C - D - E - F - G - H
 \
  H - I

with branch A pointing at commit I and branch B pointing at commit H. Now you want to take over some changes in B into A.

For example commits D and F modified Dir2, E, G and H did something to Dir1 and Dir3. So actually you only want to have commits D and F added to your integration branch A.

That means that branch B wasn't created carefully enough and you actually want to redo it into (at least) two clean topic branches. Something like:

git checkout -b topic_I_want_to_merge_touching_Dir2 C
git cherry-pick D
git cherry-pick F
git checkout -b topic_I_dont_want_to_merge_now C
git cherry-pick E
git cherry-pick G
git cherry-pick H
git checkout B
git merge topic_I_want_to_merge_touching_Dir2

If there are commits that are touching both, Dir1 and Dir2 then these commits are "bad", as they don't address a single topic, but does several things at once. In this case you might want not only reshuffle the commits, but also change the commits to be good ones using git-rebase -i.

Of course you could just merge branch A and fix-up the resulting tree. But that would make it hardly possible to continue working on the "non-merged" changes in A, because when you merge a descendant of A you would have to fix-up the resulting tree once more, because the changes of E, G and H would not be included in the tree that git prepares for you, as these commits are already merged.

like image 24
Uwe Kleine-König Avatar answered Oct 06 '22 00:10

Uwe Kleine-König