Git:将多个存储库合并到一个目录中以保留历史记录时发生冲突
Git: Conflicts merging multiple repositories into one directory keeping the history
我有几个 (~20) Git 个存储库,它们的文件不重叠。我想将他们的主分支合并到一个(新的)存储库中。
经过一番阅读,我想到了以下过程。
- 创建目标存储库(git init)并更改为它
git remote add <name> <url>
git fetch <name>
git merge <name>/master --allow-unrelated-histories -m "Imported"
git remote rm <name>
- 重复 2-5,直到合并所有存储库
第一个版本库很好地合并并且历史记录完好无损,但后来我遇到了合并冲突。
例如对于不同目录中具有相同名称的不同文件(我这边没有重命名):
CONFLICT (rename/rename): Rename "Splittermond_CharGen_JFX/.project"->"BootloaderPlugin/.project" in branch "HEAD" rename "Splittermond_CharGen_JFX/.project"->"Splittermond_Zhoujiang/.project" in "splimo-common/master"
例如来自我在项目历史中移动的文件(树中剩余的版本是最近的位置):
CONFLICT (rename/delete): Splittermond_BuU/src/org/prelle/rpgframework/splittermond/buu/BestienUndUngeheuerPlugin.java deleted in HEAD and renamed to Splittermond_BuU/src/main/java/org/prelle/rpgframework/splittermond/buu/BestienUndUngeheuerPlugin.java in splimo-common/master. Version splimo-common/master of Splittermond_BuU/src/main/java/org/prelle/rpgframework/splittermond/buu/BestienUndUngeheuerPlugin.java left in tree.
我认为 Git 跟踪文件的能力可能是问题所在,但我对此还很陌生,不知道如何解决这个问题。
感谢任何帮助或提示。
[更新]
看起来我至少有两个存储库 - 虽然不再重叠 - 曾经重叠过。我有一个 git 回购 A,抱怨它删除了现在在回购 B 中的文件。我有一个回购 B,它曾经包含现在在回购 A 中的文件。
有没有办法合并两者,保留所有未删除文件的历史记录?
我一点也不清楚发生了什么,我需要访问有问题的存储库和你的命令来重现它。但是,在考虑如何实现这一切时,请牢记两个关键点:
- 在 Git 中,历史 是 提交(或者,更清楚地说,"the commits are the history")。如果你想保留历史,这意味着你想保留现有的提交。
- Merging(动词形式,to merge)的意思是,在Git中,找到一个公共基础提交两个(可能很长)提交链,以便比较 "what we did on our branch" 和 "what they did on their branch" 自从那个共同点 .
一个正常的合并有两个"sides"。我称它们为 L 表示左、本地或 --ours
,R 表示右、远程或 --theiRs
。它也有这个合并基础提交,这是我们和他们在开始做自己的事情之前开始的共同点。 Git 通过 运行:
将 "what we did" 与 "what they did" 合并
git diff --find-renames B L # base to left/local: what we did
git diff --find-renames B R # base to right: what they did
如果 "we" 和 "they" 修改了相同文件的相同行,或者我们添加了一个文件 path/to/new.txt
而他们添加了相同的 path/to/new.txt
但它有不同的内容,或者我们删除了 path/to/old.txt
而他们修改了 path/to/old.txt
.
当您使用 --allow-unrelated-histories
时,您是在告诉 Git 如果没有共同的提交——这在这里通常是正确的——Git 应该 假装 有一个共同的基础,由一个根本没有文件的提交组成。也就是说,对于git diff
两个命令中的B,应该用Git代替the empty tree,这样每个文件都是新的。
现在,你说:
... [some fairly large number of] Git repositories that are non-overlapping in their files
如果是这样的话,那么L和R就不可能同时存在path/to/new.txt
。如果两边都有新文件,则文件重叠。
此外,如果历史真的不相关,你不能得到rename/rename或rename/delete冲突,因为不会有合并基础和Git 每次都会为 B 使用空树。您遇到这种冲突的事实表明历史 是 相关的,因此 Git 找到了一个共同的合并基础,并且 git diff
来自那个共同的合并base 在一侧查找重命名操作,在另一侧查找不同的重命名或删除操作。
因为是这种情况,无法使用如何更轻松地合并所有这些不相关的历史记录的明显答案:有些文件 是 重叠的,这种方法不会'工作没那么容易。但是如果它们真的都是不重叠的,合并它们的方法是从所有存储库中获取所有提交,然后构建一个主 "octopus merge" 提交(这里我使用 merge 作为形容词或名词,而不是动词)其树是通过在所有适当的分支提示上使用 git read-tree -m
来构建合并索引而生成的,其提交是由 运行 git write-tree
然后 git commit-tree
(带有适当的标志)。
不过,我不太愿意提供这个方法,因为如果它要起作用,你真的需要不相关的输入,而你看到的失败告诉我你没有不相关的输入。
可能是重命名检测产生了错误的发现。那么你可以尝试通过在合并命令中添加 -Xno-renames
来避免它:
...
git merge <name>/master --allow-unrelated-histories -m "Imported" -Xno-renames
...
它不会对您造成伤害,因为您正在合并不相关的历史并且不要期望任何重命名。
感谢 toreks 的帮助,我想出了以下解决方案:
- 使用
git init
初始化新存储库
git remote add <name> <url>
git fetch <name>
git merge <name>/master --allow-unrelated-histories -m "Reimported"
git remote rm <name>
git ls-files > /tmp/keep-these.txt
git filter-branch --force --index-filter "git rm --ignore-unmatch --cached -qr . ; cat /tmp/keep-these.txt | xargs git reset -q $GIT_COMMIT --" --prune-empty --tag-name-filter cat -- --all
- 对每个存储库重复步骤 2-7
添加的步骤 6-7 取自 new-repo-with-copied-history-of-only-current-tracked-files
希望对您有所帮助。
我有几个 (~20) Git 个存储库,它们的文件不重叠。我想将他们的主分支合并到一个(新的)存储库中。
经过一番阅读,我想到了以下过程。
- 创建目标存储库(git init)并更改为它
git remote add <name> <url>
git fetch <name>
git merge <name>/master --allow-unrelated-histories -m "Imported"
git remote rm <name>
- 重复 2-5,直到合并所有存储库
第一个版本库很好地合并并且历史记录完好无损,但后来我遇到了合并冲突。
例如对于不同目录中具有相同名称的不同文件(我这边没有重命名):
CONFLICT (rename/rename): Rename "Splittermond_CharGen_JFX/.project"->"BootloaderPlugin/.project" in branch "HEAD" rename "Splittermond_CharGen_JFX/.project"->"Splittermond_Zhoujiang/.project" in "splimo-common/master"
例如来自我在项目历史中移动的文件(树中剩余的版本是最近的位置):
CONFLICT (rename/delete): Splittermond_BuU/src/org/prelle/rpgframework/splittermond/buu/BestienUndUngeheuerPlugin.java deleted in HEAD and renamed to Splittermond_BuU/src/main/java/org/prelle/rpgframework/splittermond/buu/BestienUndUngeheuerPlugin.java in splimo-common/master. Version splimo-common/master of Splittermond_BuU/src/main/java/org/prelle/rpgframework/splittermond/buu/BestienUndUngeheuerPlugin.java left in tree.
我认为 Git 跟踪文件的能力可能是问题所在,但我对此还很陌生,不知道如何解决这个问题。
感谢任何帮助或提示。
[更新] 看起来我至少有两个存储库 - 虽然不再重叠 - 曾经重叠过。我有一个 git 回购 A,抱怨它删除了现在在回购 B 中的文件。我有一个回购 B,它曾经包含现在在回购 A 中的文件。 有没有办法合并两者,保留所有未删除文件的历史记录?
我一点也不清楚发生了什么,我需要访问有问题的存储库和你的命令来重现它。但是,在考虑如何实现这一切时,请牢记两个关键点:
- 在 Git 中,历史 是 提交(或者,更清楚地说,"the commits are the history")。如果你想保留历史,这意味着你想保留现有的提交。
- Merging(动词形式,to merge)的意思是,在Git中,找到一个公共基础提交两个(可能很长)提交链,以便比较 "what we did on our branch" 和 "what they did on their branch" 自从那个共同点 .
一个正常的合并有两个"sides"。我称它们为 L 表示左、本地或 --ours
,R 表示右、远程或 --theiRs
。它也有这个合并基础提交,这是我们和他们在开始做自己的事情之前开始的共同点。 Git 通过 运行:
git diff --find-renames B L # base to left/local: what we did
git diff --find-renames B R # base to right: what they did
如果 "we" 和 "they" 修改了相同文件的相同行,或者我们添加了一个文件 path/to/new.txt
而他们添加了相同的 path/to/new.txt
但它有不同的内容,或者我们删除了 path/to/old.txt
而他们修改了 path/to/old.txt
.
当您使用 --allow-unrelated-histories
时,您是在告诉 Git 如果没有共同的提交——这在这里通常是正确的——Git 应该 假装 有一个共同的基础,由一个根本没有文件的提交组成。也就是说,对于git diff
两个命令中的B,应该用Git代替the empty tree,这样每个文件都是新的。
现在,你说:
... [some fairly large number of] Git repositories that are non-overlapping in their files
如果是这样的话,那么L和R就不可能同时存在path/to/new.txt
。如果两边都有新文件,则文件重叠。
此外,如果历史真的不相关,你不能得到rename/rename或rename/delete冲突,因为不会有合并基础和Git 每次都会为 B 使用空树。您遇到这种冲突的事实表明历史 是 相关的,因此 Git 找到了一个共同的合并基础,并且 git diff
来自那个共同的合并base 在一侧查找重命名操作,在另一侧查找不同的重命名或删除操作。
因为是这种情况,无法使用如何更轻松地合并所有这些不相关的历史记录的明显答案:有些文件 是 重叠的,这种方法不会'工作没那么容易。但是如果它们真的都是不重叠的,合并它们的方法是从所有存储库中获取所有提交,然后构建一个主 "octopus merge" 提交(这里我使用 merge 作为形容词或名词,而不是动词)其树是通过在所有适当的分支提示上使用 git read-tree -m
来构建合并索引而生成的,其提交是由 运行 git write-tree
然后 git commit-tree
(带有适当的标志)。
不过,我不太愿意提供这个方法,因为如果它要起作用,你真的需要不相关的输入,而你看到的失败告诉我你没有不相关的输入。
可能是重命名检测产生了错误的发现。那么你可以尝试通过在合并命令中添加 -Xno-renames
来避免它:
...
git merge <name>/master --allow-unrelated-histories -m "Imported" -Xno-renames
...
它不会对您造成伤害,因为您正在合并不相关的历史并且不要期望任何重命名。
感谢 toreks 的帮助,我想出了以下解决方案:
- 使用
git init
初始化新存储库
git remote add <name> <url>
git fetch <name>
git merge <name>/master --allow-unrelated-histories -m "Reimported"
git remote rm <name>
git ls-files > /tmp/keep-these.txt
git filter-branch --force --index-filter "git rm --ignore-unmatch --cached -qr . ; cat /tmp/keep-these.txt | xargs git reset -q $GIT_COMMIT --" --prune-empty --tag-name-filter cat -- --all
- 对每个存储库重复步骤 2-7
添加的步骤 6-7 取自 new-repo-with-copied-history-of-only-current-tracked-files
希望对您有所帮助。