为什么在将多个存储库合并到一个整体存储库后对文件运行"git log -m --follow"时会出现不相关的历史记录?



我有几个不同的 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(在RX中)绝对不是与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的文件。 它发现每个父提交都有这样的文件,因此它会打印每个父提交。

最新更新