Git Subtree Merging reports conflict when merging

2020-03-25 03:44发布

I'm getting started with learning subtree merging in git 1.8.2. I have created a simple example to test a change to a third party repo migrating into a main project.

I'm following the 6.7 Git Tools - Subtree Merging example.

The 'sub' project is included as a subdirectory in the 'main' project.

After I make a change to the 'sub' project, git reports a conflict when I try to merge the change into the 'main' project.

Test Summary

  1. Created repos for projects 'main' and 'sub' (sub instead of rack)
  2. Add remote named sub_remote to main that refers to sub
  3. Track sub_remote using sub_branch
  4. Change and commit one line in a file in the 'sub' project
  5. Pull changes from sub over to main/sub_branch
  6. Merge main/sub_branch into main/master.

The merge fails with a conflict. Merge is confused about which version of the changed line to keep.

<<<<<<< HEAD
main
=======
main upstream change
>>>>>>> sub_branch
main.git
sub
sub.git
tm

Complete Test Script

#!/bin/sh

# initialize empty repos
for i in main sub
do
  rm -rf $i{,.git}
  mkdir $i.git
  cd $i.git;
  git --bare init;
  cd ..;
  git clone $i.git
  cd $i
  echo $i > readme.md
  git add readme.md
  git commit -a -m "added readme.md"
  git push origin master
  cd ..
done

# add data to sub
ls > sub/data
cd sub
git add data
git commit -m "Added data"
git push origin master
cd ..

# add sub as a sub-tree in main
cd main
git remote add sub_remote ../sub.git
git fetch sub_remote
git checkout -b sub_branch sub_remote/master
git checkout master
git read-tree --prefix=sub/ -u sub_branch
git commit -m "Added sub"
git push origin master
cd ..

# make change to sub
cd sub
sed -i -e 's/main$/main upstream change/' data
git commit -a -m "upstream change made to data"
git push origin master
cd ..

# merge sub change to main
cd main
git checkout sub_branch
git pull

#merge sub_branch changes into master
git checkout master
git merge -s subtree sub_branch
cat sub/data

2条回答
做个烂人
2楼-- · 2020-03-25 03:58

The problem is that Git 1.8.2.2 (March 2013) was just the version introducing a bug which affects git merge -s subtree.
The Subtree Merging section mentioned by Tim Swast, which still has the issue, should work better with Git 2.19 (Q3 2018).

The automatic tree-matching in "git merge -s subtree" was broken 5 years ago and nobody has noticed since then, which is now fixed.

See commit 2ec4150 (02 Aug 2018) by Jeff King (peff).
Helped-by: René Scharfe (rscharfe).
(Merged by Junio C Hamano -- gitster -- in commit 60858f3, 17 Aug 2018)

score_trees(): fix iteration over trees with missing entries

In score_trees(), we walk over two sorted trees to find which entries are missing or have different content between the two.
So if we have two trees with these entries:

one   two
---   ---
a     a
b     c
c     d

we'd expect the loop to:

  • compare "a" to "a"
  • compare "b" to "c"; because these are sorted lists, we know that the second tree does not have "b"
  • compare "c" to "c"
  • compare "d" to end-of-list; we know that the first tree does not have "d"

And prior to d8febde (match-trees: simplify score_trees() using tree_entry(), 2013-03-24, Git 1.8.2.2) that worked.
But after that commit, we mistakenly increment the tree pointers for every loop iteration, even when we've processed the entry for only one side.
As a result, we end up doing this:

  • compare "a" to "a"
  • compare "b" to "c"; we know that we do not have "b", but we still increment both tree pointers; at this point we're out of sync and all further comparisons are wrong
  • compare "c" to "d" and mistakenly claim that the second tree does not have "c"
  • exit the loop, mistakenly not realizing that the first tree does not have "d"

So contrary to the claim in d8febde, we really do need to manually use update_tree_entry(), because advancing the tree pointer depends on the entry comparison.

That means we must stop using tree_entry() to access each entry, since it auto-advances the pointer.
Instead:

  • we'll use tree_desc.size directly to know if there's anything left to look at (which is what tree_entry() was doing under the hood)
  • rather than do an extra struct assignment to "e1" and "e2", we can just access the "entry" field of tree_desc directly.
查看更多
别忘想泡老子
3楼-- · 2020-03-25 04:17

What read-tree is doing in this case is just adding to a directory in your current tree the contents of another tree. This contents are added just like regular file and directories being created and added, there is no history carried over. All these files will be treated as if you created them.

When you try to merge the process fails since it sees that sub_branch history created the data file, and the target directory also contain a different data file created by you.

The page you are using is missing a very important step to make subtree works properly, so that you can actually be able to pull updates to it.

The proper example can be seen in both these pages: https://www.kernel.org/pub/software/scm/git/docs/howto/using-merge-subtree.html https://help.github.com/articles/working-with-subtree-merge

What it is missing in your case is to properly link the history when you create the subtree:

# create the merge record but not change anything in your tree yet
git merge -s ours --no-commit sub_branch
# bring the changes and place them in the proper subdirectory
git read-tree --prefix=sub/ -u sub_branch

After this your main repository will contain the history of the sub repository. Calls to the merge that was failing should now work properly. Calling git log --graph will let you see how the different commits are being merged.

查看更多
登录 后发表回答