将Git子模块转换为子树后合并错误

20

我有一个项目,最初我使用子模块来处理一些依赖的代码。事实证明子模块并不适合这个项目(而且在实际使用中很难),所以我正在将每个子模块转换为子树(使用新的git-subtree功能)。

在我的工作库中,我已成功地删除了每个子模块,并将旧的子模块仓库添加为子树。这没有问题。

当我去另一个克隆并尝试从第一个克隆拉取时,我会在合并步骤中得到以下错误:

error: The following untracked working tree files would be overwritten by merge:
        sub/.gitignore
        sub/Makefile
        sub/README
        sub/src/main.c
        ... and so on for all files in sub/
Aborting

似乎是因为sub/中的文件从未真正存在于主仓库中,当Git应用补丁更新.gitmodules时,并不会删除子模块文件夹。在处理下一个提交时,Git试图创建现在作为主仓库一部分的sub/中的新文件时,所有这些文件都会与仍然存在于sub/中的文件冲突。

我发现的解决方法是在git pull之前使用rm -rf sub,这样可以避免这个问题。

我的问题是,是否有任何命令行开关可以和git merge一起使用,以便说“覆盖任何已经存在于工作目录中的文件”?更好的方法是,如果git merge查看现有文件的内容,并且该内容与它本来要创建的文件相同,则抑制错误消息并继续执行。

更新:我创建了演示此问题的Git存储库,以确切地展示我所说的内容。复制操作如下:

$ git clone https://github.com/ghewgill/q14224966.git
$ cd q14224966
$ git submodule init
$ git submodule update
$ git merge origin/branch

这应该会导致错误消息

error: The following untracked working tree files would be overwritten by merge:
    sub/Makefile
    sub/README
    sub/src/main.c
Please move or remove them before you can merge.
Aborting

你试过这个吗:http://goo.gl/z1XP9? - Efthymis
@Efthymis:完整链接为https://dev59.com/rXM_5IYBdhLWcg3wn0vT(请不要在此处使用URL缩短程序)。我尝试过`git merge -s recursive -X theirs origin/master`,但仍出现相同的错误。 - Greg Hewgill
是的,这似乎是git merge的一个短板。而我正渴望着那个赏金。 - Maic López Sáenz
我点赞了你的回答,因为这也发生在我身上 :D - Patt Mehta
4个回答

11

我知道你的问题是关于合并的,但我在合并 git 子模块方面也遇到了类似的问题。我认为这个解决方案可以解决你的问题,即使它并没有直接回答合并的问题。

我发现通过强制检出想要合并的分支,然后返回主分支,可以解决子模块的所有问题。

为了让你的示例工作起来:

$ git clone https://github.com/ghewgill/q14224966.git
$ cd q14224966
$ git submodule init
$ git submodule update
$ git checkout -f origin/branch
$ git checkout master
$ git merge origin/branch

这个方法有效是因为它基本上为您完成了rm -rf步骤。尽管这有点绕,如果您只有一个子模块(就像您的示例一样),可能不值得这样做。但我发现在处理许多子模块的项目时,它可以节省很多时间。

另外,正如评论中指出的那样,如果您想避免对工作树进行更改,可以使用这个方法:

$ git clone https://github.com/ghewgill/q14224966.git
$ cd q14224966
$ git submodule init
$ git submodule update
$ git reset origin/branch
$ git reset --hard master

这个方法的实现方式大致相同,但是避免了在过程中检出其他文件。我还没有机会在实际应用中使用它,但它看起来是一种可靠的方法。

另外还有一个$ git merge -s subtree origin/branch命令,它可以和你的例子一起使用,但如果涉及多个子模块时,我曾经遇到过意外结果。你可能会比我更幸运。


1
我花了一点时间才看到这个方法的巧妙之处。现在我注意到,你可以用git reset origin/branch; git reset --hard master来替换checkouts;这样就避免了所有工作树活动,除了想要删除的部分。 - jthill
@jthill 你说得对。你完全可以通过使用reset来跳过工作树的内容。我会更新我的答案以反映这一点。谢谢! - Jonathan Wren
不幸的是,使用 git reset 的第二种解决方案失败了,因为它会丢弃在创建分支后对 master 分支(例如 README)所做的任何更改。 - Greg Hewgill
另外,第一种解决方案(使用checkout -f)会保留sub/.git目录,除非使用rm -rf显式地删除它,否则将来可能会导致混淆。 - Greg Hewgill

2
(注意:我从未使用过子树,也不知道您的实际存储库有多复杂,因此这些解决方案可能并不适用于您。)
通过尝试您的示例存储库,我发现了两个解决方案,它们似乎都有效,但会产生不同的提交树:
  1. 使用 git merge -s resolve origin/branch

    ~/q14224966[master]> git reset --hard origin/master
    HEAD 现在位于 a231acd add submodule
    ~/q14224966[master]> touch other.c && git add . && git commit -m "New commit."
    [master bc771ac] New commit.
     0 files changed
     create mode 100644 other.c
    ~/q14224966[master]> git merge -s resolve origin/branch 
    尝试真正微不足道的索引合并...
    错误: 合并需要文件级别合并
    失败。
    尝试简单合并。
    简单合并失败,尝试自动合并。
    Adding sub/Makefile
    Adding sub/README
    Adding sub/src/main.c
    Merge made by the 'resolve' strategy.
     .gitmodules    | 3 ---
     sub            | 1 -
     sub/Makefile   | 1 +
     sub/README     | 1 +
     sub/src/main.c | 1 +
     5 files changed, 3 insertions(+), 4 deletions(-)
     delete mode 160000 sub
     create mode 100644 sub/Makefile
     create mode 100644 sub/README
     create mode 100644 sub/src/main.c
    ~/q14224966[master]> ls
    README   main.c   other.c  sub/
    ~/q14224966[master]> cd sub/
    ~/q14224966/sub[master]> ls
    Makefile  README    src/
    ~/q14224966/sub[master]> git status
    # 在分支 master
    # 您的分支领先 'origin/master' 5 个提交。
    #
    无文件要提交,干净的工作区
    ~/q14224966/sub[master]> cd ..
    ~/q14224966[master]> git status
    # 在分支 master
    # 您的分支领先 'origin/master' 5 个提交。
    #
    无文件要提交,干净的工作区
    

    这是产生的提交树:git commit tree - merge option

  2. 使用变基而不是合并:

    ~/q14224966[master]> git reset --hard origin/master 
    HEAD 现在位于 a231acd add submodule
    ~/q14224966[master]> touch other.c && git add . && git commit -m "New commit."
    [master ae66060] New commit.
     0 files changed
     create mode 100644 other.c
    ~/q14224966[master]> git rebase origin/branch 
    首先,倒回头以重播您的工作...
    应用: New commit.
    ~/q14224966[master]> ls
    README   main.c   other.c  sub/
    ~/q14224966[master]> cd sub/
    ~/q14224966/sub[master]> ls
    Makefile  README    src/
    ~/q14224966/sub[master]> git status
    # 在分支 master
    # 您的分支领先 'origin/master' 4 个提交。
    #
    无文件要提交,干净的工作区
    ~/q14224966/sub[master]> cd ..
    ~/q14224966[master]> git status
    # 在分支 master
    # 您的分支领先 'origin/master' 4 个提交。
    #
    无文件要提交,干净的工作区
    

    这是产生的提交树:git commit tree - rebase option


我尝试使用 merge -s resolve,但如果子模块中的文件仍然存在,则无法正常工作(请确保您从 git reset --hard origin/master && rm -rf sub && git submodule update 开始)。 - Greg Hewgill

1
你无法强制让git-merge(或任何其他命令)覆盖它认为不存在的文件。Git会尽力避免进行完全不可逆转的操作。但是,对于许多子模块,你可以使用git submodule foreach使删除更加容易和安全。
$ git submodule foreach 'rm -rf $toplevel/$path'
Entering 'sub'
$ git merge origin/branch
Updating a231acd..6b4d2f4
Fast-forward
...

这使得执行 rm -rf 操作稍微容易了一些,但它仍然是相同的解决方案。 - Greg Hewgill
哎呀,是的。但是你必须为这种情况做一些特别处理,所以,最好做最简单有效的方法。 - Eevee

0

尝试过了

git fetch --all

git reset --hard origin/master

但是没有起作用。

您可以使用“ours”合并策略:

git merge -s ours old-master

您还可以使用git-stash保存更改,然后使用git-stash apply恢复它们。


使用 merge -s ours 实际上并不执行合并操作,也无法解决问题。 - Greg Hewgill
是的,这是一个问题,你无法合并 :D 但你尝试过存储吗? - Patt Mehta
不幸的是,暂存并不能解决问题,因为当你取消暂存时仍然需要进行某种类型的合并。 - Greg Hewgill

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接