1

我有几个不同的 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 echo "initial" > README.md git add 。git commit -m '初始提交'

对于每个回购

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`
4

1 回答 1

3

要扩展Mark Adelsberger 的评论,您应该了解在 Git 中,文件的身份是以一种相当奇怪的方式定义的。

版本控制系统 (VCS) 中的文件标识是一个核心概念。VCS 应该如何知道该文件include/lib.h是否与文件“相同”文件lib/lib.h

一些 VCS 采用的方法是,当文件首次引入VCS 时,您会告诉 VCS 一些特殊的内容,例如. 从那时起,任何时候文件被重命名,你也会告诉 VCS 一些特殊的东西,比如. VCS 可以使用它在一系列提交中跟踪文件的身份:在修订版 X 中,是否与修订版 R 中的文件“相同” ,具体取决于您是否告诉 VCS 存在R 和 X 之间的重命名操作。hg add pathhg mv [--after] old-name new-namelib/lib.hinclude/lib.h

另一方面,Git 做了一些完全不同的事情:它试图通过content识别给定任何两个修订版的文件对。也就是说,给定修订版 R 和 X 作为一对,Git 会查看R中的每个文件和 X中的每个文件。如果RX都有文件名为include/lib.h_ _lib/lib.h _ _include/lib.h _ lib/lib.h(在另一个版本中)。但是,如果两个修订版本中的一个具有include/lib.h并且另一个具有lib/lib.h,则该文件可能已被重命名在这两个修订之间。

一般来说,由于 CPU 时间相关的原因,给定任何一对修订版,如果两个修订版中都存在某个路径P,Git 会假定该文件没有被重命名。使用git diff-but not git mergeand not git log- 您可以添加一个标志来表示不要假设文件没有因为它们存在于两个版本中而被重命名。这是-B(中断配对)参数。

然后,只要启用重命名检测-M(选项 in git diff--followingit log和各种其他条件):对于所有配对的文件,无论是-B因为给定路径仅存在于两个修订版之一中,Git 都会查找具有相似内容的文件,为它们计算“相似性指数”和/或相似名称。(如果两个文件都以/lib.h例如。作为一个关键的优化,因为它很容易在内部进行并且运行良好,Git 会快速将文件与 100% 相同的内容配对,并且只有在失败后,才会计算相似度索引。)然后它将任何文件与相似度索引配对满足或超过您给它的百分比要求:-M50是默认值,但您可以要求“75% 相似度” -M75,例如。

这些配对文件是两个修订版中的“相同”文件。对于git diff,然后在配对文件之间产生差异,对于一个典型的git merge,它运行两个 git diffs ,一个从合并基础到两个提示提交之一,然后第二个从同一个合并基础到两个提示中的另一个提交。最重要的是,for--follow也是如此:如果较早版本中的文件具有不同git log的名称,则配对的文件名指示--follow操作更改它正在查找的文件名。

(您merge -s ours不是典型的合并:在计算源代码以与新提交一起使用时,该策略ours会忽略除 HEAD 提交之外的所有内容,因此它根本不会打扰任何差异。)

这如何影响git log --follow

为了跟踪路径名为跨重命名路径的文件 Git 必须执行这些一次对的差异,以便它可以检测到该文件实际上已被重命名。使用的对是C和C 本身的父级,其中C是由于图形遍历找到的提交,即即将显示或不显示的提交,取决于它是否触及路径名为path的文件。git log --follow pathgit log

合并提交在这里提出了一个问题。合并提交的定义是它至少有两个父级。这就是-m(拆分合并)选项的用武之地:拆分合并意味着在此操作的持续时间内假装具有Ngit log个父项的合并提交实际上是N个单独的不同提交。这N个提交中的第一个有一个父级:合并的第一个父级。第二个提交有一个父级:合并的第二个父级。第 N 个提交将第 N 个父级作为单亲,依此类推。因此,如果合并有三个父级,它会被分成三个虚拟提交,每个提交都有一个父级。

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

由于您正在寻找repoA/README.md,Git 开始寻找该特定路径。Git每次查找时都会在拆分虚拟提交中找到该名称。repoA/README.md每个拆分虚拟提交的父级在 name 下都有该文件README.md,因此在 Git 为每个父级打印一次拆分虚拟提交之后——每个父/子对都包含repoA/README.md在其中,因为每个这样的子提交(合并本身)都包含repoA/README.md在其中——它会移动到父母那里,一次一个,现在寻找名为README.md. 它发现每个父提交都有这样的文件,因此它打印每个父提交。

于 2018-06-05T15:30:32.637 回答