我有几个不同的 git 存储库,我想将它们合并到一个整体存储库中,同时保留它们的历史记录。 我已经找到了一种方法来做到这一点,但我对 git log 向我显示的单个文件历史记录有点困惑。
这是我的输出:
git log --oneline
组合回购的输出
------- (HEAD -> master) Merge repoC into mono repo
------- Merge repoB into mono repo
------- Merge repoA into mono repo
------- initial commit
------- Add README to repoC
------- Add README to repoB
------- Add README to repoA
git log --oneline repoA/README.md
组合回购的输出
------- Merge repoA into mono repo
git log --oneline -m --follow repoA/README.md
组合回购的输出
------- (from -------) (HEAD -> master) Merge repoC into mono repo
------- (from -------) Merge repoB into mono repo
------- (from -------) Merge repoA into mono repo
------- (from -------) Merge repoA into mono repo
------- initial commit
------- Add README to repoC
------- Add README to repoB
------- Add README to repoA
从所有单独的存储库作为捆绑包开始,我执行以下操作来创建我的整体存储库:
对于存储库 A/B/C
git init
echo "repo" > README.md
git add .
git commit -m 'Add README to repo'
git bundle create ../repo{A,B,C}.bundle --all
创建组合存储库 git init 回显"初始"> README.md git add .git commit -m 'initial commit'
对于每个存储库
mkdir repo{A,B,C}
git fetch ../repo{A,B,C}.bundle master
git merge --allow-unrelated-histories -s ours --no-commit FETCH_HEAD
git read-tree --prefix=repoA -u FETCH_HEAD
git commit -m "Merge repo{A,B,C} into mono repo"
为什么在使用"-m --follow"运行时,我会得到特定文件不相关的 git 提交历史记录?我希望只看到与文件相关的提交。
已更新(尝试记录具有不同名称和内容的文件):
git log -m --follow --oneline repoB/sue.md`
-------(from -------) (HEAD -> master) Merge repo C into mono repo`
-------(from -------) Merge repo B into mono repo`
-------(from -------) Merge repo B into mono repo`
为了扩展Mark Adelsberger的评论,你应该明白,在Git中,文件的身份是以相当奇怪的方式定义的。
版本控制系统 (VCS) 中的文件标识是一个核心概念。 VCS 如何知道文件include/lib.h
与文件lib/lib.h
"相同">
?某些 VCS 采用的方法是,当文件首次引入VCS 时,您告诉 VCS 一些特殊的东西,例如hg addpath
。 从那时起,每当重命名文件时,您还会告诉 VCS 一些特殊的事情,例如hg mv [--after]old-namenew-name
. VCS 可以使用它来跟踪某些提交系列中的文件标识:修订版 X 中的lib/lib.h
与修订版 R 中的include/lib.h
文件"相同",具体取决于您是否告诉 VCS R 和 X 之间存在重命名操作。
另一方面,Git 做了一些完全不同的事情:它试图通过内容来识别文件对,给定任何两个修订版。 也就是说,给定修订版 R 和 X 成对,Git 会查看R 中的每个文件和X 中的每个文件。 如果 R 和 X 都有名为include/lib.h
的文件,那么,那几乎可以肯定是同一个文件,因此lib/lib.h
(在R或X中)绝对不是与include/lib.h
(在另一个修订版中)相同的文件,但它可能与lib/lib.h
是同一个文件(在另一个修订版中)。 但是,如果两个修订版中的一个include/lib.h
而另一个修订版lib/lib.h
,则该文件可能在这两个修订版之间重命名。
通常,出于与 CPU 时间相关的原因,给定任何一对修订,如果两个修订中都存在某些路径P,Git会假定该文件未重命名。 对于git diff
- 但不是git merge
也不是git log
- 您可以添加一个标志,表示不要仅仅因为它们存在于两个修订版中就假设文件没有重命名。 这是-B
(中断配对)参数。
然后,只要启用了重命名检测(git diff
中的-M
选项,git log
中的--follow
选项,以及各种其他条件): 对于所有未配对的文件,无论是由于-B
还是因为给定的路径仅存在于两个修订版之一中, Git 查找具有相似内容的文件,为它们计算"相似性索引", 和/或类似名称。 (例如,如果两个文件都以/lib.h
结尾,则匹配组件名称有 +1 的奖励。 作为关键优化,因为它在内部很容易完成并且运行良好,Git 会快速将文件与 100% 相同的内容配对,只有在失败后,才会计算相似度指数。 然后,它将任何文件的相似性指数配对,该指数满足或超过您为其提供的百分比要求:-M50
是默认值,但您可以要求与-M75
"75%相似度">
,例如。这些配对文件是两个修订版中的"相同"文件。 对于git diff
也是如此,它会在配对文件之间产生差异,对于典型的git merge
,它运行两个git diff
,一个从合并基到两个提示提交中的一个,然后第二个从相同的合并基础到两个提示提交中的另一个。 最重要的是,对于--follow
,对于git log
也是如此:如果早期修订版中的文件具有不同的名称,则配对的文件名指示--follow
操作更改它正在寻找的文件名。
(您的merge -s ours
不是典型的合并:在计算源代码以配合新提交时,ours
策略会忽略除 HEAD 提交之外的所有内容,因此它根本不会打扰任何差异。
这对git log --follow
有何影响
要使git log --followpath
跟踪路径名为跨重命名路径的文件,Git 必须执行这些一次配对的差异,以便它可以检测到该文件实际上已重命名。 使用的对是 C 和 C本身的父级,其中C是由于图形漫游而找到的提交,即git log
即将显示或不显示的提交,具体取决于它是否触及了路径名为path的文件。
合并提交在这里存在一个问题。 合并提交的定义是它至少有两个父级。 这就是-m
(拆分合并)选项的用武之地:拆分合并意味着假装,在这一次git log
操作期间,具有 N 个父项的合并提交实际上是N个单独的不同提交。这些N个提交中的第一个有一个父级:合并的第一个父级。 第二个提交有一个父级:合并的第二个父级。 第 N 次提交将第 N 个父项作为单个父项,依此类推。 因此,如果合并有三个父项,则会拆分为三个虚拟提交,每个虚拟提交都有一个父项。
这解决了配对问题:这些虚拟提交中的每一个现在只有一个父级,Git 可以按照通常的方式运行差异来检测任何重命名。 如果 Git找到重命名,那只是意味着当它显示父提交时——在完成这N个虚拟提交中的每一个之后——它应该停止查找路径名路径,而是开始查找名称是差异中旧名称的文件。
由于您正在寻找repoA/README.md
,Git 开始寻找该特定路径。 Git 每次查找时都会在拆分的虚拟提交中找到该名称repoA/README.md
。 每个拆分虚拟提交的父级在名为README.md
的名义下都有该文件,因此在 Git 为每个父级打印一次拆分虚拟提交后——每个父/子对都有repoA/README.md
,因为每个这样的子提交(合并本身)都有repoA/README.md
——它会移动到父级,一次一个, 现在查找名为README.md
的文件。 它发现每个父提交都有这样的文件,因此它会打印每个父提交。