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

时间:2018-06-05 13:12:52

标签: git commit bundles monorepo git-history

我有几个不同的git repos,我想在保留历史的同时合并成一个整体回购。我找到了一种方法来做到这一点,但我对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

从作为捆绑包的所有单独的回购开始,我执行以下操作来创建我的整体回购:

对于Repos 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     echo“initial”> 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 commit历史记录?我希望只能看到与该文件相关的提交。

更新(尝试使用不同名称和内容的文件的日志):

  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`

1 个答案:

答案 0 :(得分:2)

要扩展Mark Adelsberger's comment,你应该明白,在Git中,文件的身份是以一种相当奇怪的方式定义的。

版本控制系统(VCSes)中的

文件标识是核心概念。 VCS应该如何知道文件include/lib.h是或不是,#34;相同"归档为文件lib/lib.h

有些VCS采用的方法是,当文件首次引入进入VCS时,您会告诉VCS一些特殊的东西,例如hg add path。从那时起,只要文件重命名,您告诉VCS一些特殊的东西,例如hg mv [--after] old-name new-name。 VCS可以使用它来跟踪一系列提交中文件的标识:修订版X中的lib/lib.h是否为"相同的"在rev R中归档为include/lib.h,具体取决于您是否已告知VCS R和X之间存在重命名操作。

另一方面,Git做了一些截然不同的事情:它试图通过 content 识别文件对,给定任何两个版本。也就是说,如果将R和X作为一对修改,Git会查看 R 中的每个文件和 X 中的每个文件。如果 R X 都有名为include/lib.h的文件,那么几乎可以肯定是相同的文件,因此{{1 (在R或X中)绝对不是lib/lib.h相同的文件(在另一个版本中),但它可能与include/lib.h相同的文件(在另一个修订版)。但是,如果两个修订版中只有一个版本lib/lib.h而另一个版本include/lib.h,则该文件可能已在这两个版本之间重命名

通常,出于与CPU时间相关的原因,在给定任何修订对的情况下,如果两个修订版中都存在某个路径 P ,则Git会假定该文件未重命名。使用lib/lib.h - 但不是git diff而不是git merge - 您可以添加一个标记,表示不要假设文件未重命名,因为它们存在于两个版本中< / em>的。这是git log(中断配对)参数。

然后,只要启用重命名检测-B中的-M选项,git diff中的--follow以及其他各种条件, :对于 un 配对的所有文件,由于git log或者由于给定路径仅存在于两个修订版之一中,Git会查找类似内容的文件< / em>,计算一个&#34;相似性指数&#34;对于他们,和/或相似的名称。 (例如,如果两个文件都以-B结尾,那么匹配组件名称会有+1加值。作为一项关键优化,因为它在内部很容易做,并且运行良好,Git会很快配对具有100%同义内容的文件,并且只有在此失败后才计算相似性索引。)然后将具有满足或超过您给出的百分比要求的相似性索引的任何文件配对:/lib.h是默认值,但你可以要求&#34; 75%的相似度&#34;例如,-M50

这些配对文件是&#34;相同&#34;两个版本中的文件。这对于-M75来说是正确的,然后会在配对文件之间产生差异,而对于典型的git diff则会产生 2 git merge s,一个从合并基础到两个提示之一,然后是第二个从同一个合并基础到两个提交提交中的另一个。最重要的是,对于git diff--follow也是如此:配对的文件名将git log操作指向更改它正在查找的文件名for 如果早期版本中的文件具有不同的名称。

(您的--follow 是典型的合并:merge -s ours策略在计算源代码时会忽略除HEAD提交以外的所有合并,因此它根本没有任何差异。)

这会如何影响ours

要让git log --follow在重命名后跟随路径名为 path 的文件,Git必须执行这些一对一的差异,以便它可以检测到该文件在事实上改名了。使用的对是 C 的父级和 C本身,其中 C 是由于图形遍历而找到的提交,即提交{ {1}}即将显示或不显示,具体取决于它是否触及路径名为 path 的文件。

合并提交在这里存在问题。合并提交的定义是它至少有两个父项。这是git log --follow path(拆分合并)选项的用武之地:拆分合并意味着在这一个git log操作的持续时间内假装合并提交, N parent,实际上是 N 分开的不同提交。这些 N 提交中的第一个具有一个父级:合并的第一个父级。第二个提交有一个父级:合并的第二个父级。第N次提交将第N个父级作为单个父级,依此类推。因此,如果合并有三个父项,它将分为三个虚拟提交,每个提交一个父项。

这解决了配对问题:每个虚拟提交现在只有一个父级,Git可以通常的方式运行diff,以检测任何重命名。如果Git 找到重命名,那只意味着在完成 N 虚拟提交之后,当它显示提交时-it应该停止查找路径名 path ,然后开始查找名称为diff中 old 名称的文件。

由于你正在寻找-m,Git开始寻找那条特定的路径。每次看起来,Git 在拆分虚拟提交中找到该名称git log。每个拆分虚拟提交的父级都有一个名为repoA/README.md的文件,因此在Git打印每个父级一次的拆分虚拟提交后,每个父/子对都有repoA/README.md,因为每个这样的子提交(合并本身在其中有README.md - 它一次一个地移动到父项,现在查找名为repoA/README.md的文件。它发现每个父提交都有这样一个文件,因此它打印每个父提交。