git 如何跨提交树将 blob 与文件进行匹配?

2023-11-24

Git 书的第 3.1 章明确指出只有暂存文件才能作为 blob 存储在提交树中。

如果像提交对象一样,Blob 获得对其内容唯一的哈希 ID,那么 Git 如何管理跨提交跟踪 Blob 和文件之间的对应关系?不同提交中相同文件 blob 的哈希 ID 无法匹配,因为它们的内容不同。


一个简单的例子:

假设我刚刚创建了一个没有提交的空存储库。我创建一个文件 README.md,将其暂存并提交。 Git 存储一个树对象,该对象具有由 README.md 内容的哈希值标识的 blob。

假设我修改 README.md、暂存并提交。 Git 存储一个树对象,该对象具有由 README.md 修改内容的哈希值标识的 blob。当然,我们可以预期第二个哈希值与第一个提交树中标识 README.md 的 blob 的哈希值不同。

Git 将如何回答有关 README.md 历史记录的请求?

git log README.md

我的预感是它会遍历提交历史记录并比较相关的 blob,但我不明白 Git 如何知道 blob 对应于同一文件的不同版本,除非是在微不足道的情况下。



这实际上是一个很好的问题。

提交的内部存储形式部分相关,所以让我们考虑一下。个人提交实际上很小。这是 Git 的 Git 存储库中的一个,即 commitb5101f929789889c2e536d915698f58d5c5c6b7a:

$ git cat-file -p b5101f929789889c2e536d915698f58d5c5c6b7a | sed 's/@/ /'
tree 3f109f9d1abd310a06dc7409176a4380f16aa5f2
parent a562a119833b7202d5c9b9069d1abb40c1f9b59a
author Junio C Hamano <gitster pobox.com> 1548795295 -0800
committer Junio C Hamano <gitster pobox.com> 1548795295 -0800

Fourth batch after 2.20

Signed-off-by: Junio C Hamano <gitster pobox.com>

(the sed 's/@/ /'也许只是为了减少 Junio Hamano 必须收到的垃圾邮件数量:-) )。正如您在这里所看到的,提交对象通过另一个提交的哈希 ID 引用其父提交对象,a562a11983...。它还指的是一个tree对象的哈希ID,树对象的哈希ID开头3f109f9d1a。我们可以使用以下命令查看这个树对象git cat-file -p too:

$ git cat-file -p 3f109f9d1a | head
100644 blob de1c8b5c77f7566d9e41949e5e397db3cc1b487c    .clang-format
100644 blob 42cdc4bbfb05934bb9c3ed2fe0e0d45212c32d7a    .editorconfig
100644 blob 9fa72ad4503031528e24e7c69f24ca92bcc99914    .gitattributes
040000 tree 7ba15927519648dbc42b15e61739cbf5aeebf48b    .github
100644 blob 0d77ea5894274c43c4b348c8b52b8e665a1a339e    .gitignore
100644 blob cbeebdab7a5e2c6afec338c3534930f569c90f63    .gitmodules
100644 blob 247a3deb7e1418f0fdcfd9719cb7f609775d2804    .mailmap
100644 blob 03c8e4c613015476fffe3f1e071c0c9d6609df0e    .travis.yml
100644 blob 8c85014a0a936892f6832c68e3db646b6f9d2ea2    .tsan-suppressions
100644 blob 536e55524db72bd2acf175208aef4f3dfc148d42    COPYING

(该树有相当多的数据,所以我在这里只复制了前十行)。

在树内部,您可以看到模式 (100644), 类型 (blob——这是模式所暗示的,并且也记录在内部 Git 对象中;它实际上并没有存储在树对象中),哈希ID(de1c8b5c77f...) 和姓名 (.clang-format)的一个斑点。您还可以看到tree可以参考补充tree对象,就像这样的情况.github子树。

如果我们获取这个特定的 blob 对象哈希 ID,我们也可以通过哈希 ID 查看该对象的内容:

$ git cat-file -p de1c8b5c77f | head
# This file is an example configuration for clang-format 5.0.
#
# Note that this style definition should only be understood as a hint
# for writing new code. The rules are still work-in-progress and does
# not yet exactly match the style we have in the existing code.

# Use tabs whenever we need to fill whitespace that spans at least from one tab
# stop to the next one.
#
# These settings are mirrored in .editorconfig.  Keep them in sync.

(由于文件相当长,我再次将副本截断为 10 行)。

只是为了说明,让我们看一下.github子树也是:

$ git cat-file -p 7ba15927519648dbc42b15e61739cbf5aeebf48b
100644 blob 64e605a02b71c51e9f59c429b28961c3152039b9    CONTRIBUTING.md
100644 blob adba13e5baf4603de72341068532e2c7d7d05f75    PULL_REQUEST_TEMPLATE.md

那么,Git 对这些内容所做的就是根据需要递归地读取tree来自提交的对象。 Git 会将这些读入一个数据结构,它称之为index or cache。 (从技术上讲,内存版本是cache数据结构,尽管 Git 文档对于何时使用哪些名称往往有点松散。)因此通过读取提交构建的缓存b5101f929789889c2e536d915698f58d5c5c6b7a例如,会说那个名字.clang-format有模式100644和 blob 哈希de1c8b5c77f7566d9e41949e5e397db3cc1b487c,而名字.github/CONTRIBUTING.md有模式100644和 blob 哈希64e605a02b71c51e9f59c429b28961c3152039b9.

请注意,各种名称组件(.github plus CONTRIBUTING.md)实际上已经被加入到内存缓存中。 (在磁盘格式中,它们是通过算法技巧进行压缩的。)

帮助 Git 匹配文件名的内存缓存

最后,内部(内存中)缓存保存 元组。如果你要求 Git 比较提交b5101f929789889c2e536d915698f58d5c5c6b7a对于其他提交,Git 也会将其他提交读取到内存缓存中。其他缓存有一个名为.github/CONTRIBUTING.md,或者没有。

如果两个提交都有包含以下内容的文件相同的名字,Git 假设(为了 Git 现在正在进行的这一比较,见下文)这些是相同的文件。无论 blob 哈希值是否相同,都是如此。

我们在这里回答的真正问题与identity。在版本控制系统中,文件的标识确定该文件是否是两个不同版本中的“相同”文件(但是版本控制系统本身定义了版本)。这涉及身份的基本哲学问题,如这篇关于忒修斯之船思想实验的维基百科文章:我们怎么知道某事,甚至一些one,我们认为他们是谁或是什么?如果你在你和你的表弟鲍勃都很年轻的时候认识了他,然后你又遇到了一个叫鲍勃的人,他是你的表弟吗?那时你和他还很小;现在你长大了,年纪大了,经历也不同了。在现实世界中,我们从环境中寻找线索:鲍勃是你父母的兄弟姐妹的孩子吗?如果是这样,鲍勃可能is你很久以前见过的同一个表弟鲍勃,即使他(和你)现在看起来很不同。

当然,Git 不会做这些事情。在大多数情况下,两个文件都被命名的简单事实.github/CONTRIBUTING.md足以将它们识别为“同一文件”。名字是一样的,所以我们就完成了。

git diff提供额外服务

在我们的日常开发中,有时我们会遇到这样的情况:rename一份文件。一个名为a/b.c可能renamed to d/e.f or d/e.c因为某些原因。

假设我们正在提交a123456文件名为a/b.c。然后我们开始承诺f789abc。第二次提交没有a/b.c但确实有一个d/e.f。 Git 会简单地删除a/b.c从我们的索引(缓存的磁盘形式)和工作树中,并填充一个新的d/e.f进入我们的索引和工作树,一切都很好。

但是假设我们要求 Gitcompare a123456 with f789abc. Git could只需告诉我们:改变a123456 to f789abc, 消除a/b.c并创建一个新的d/e.f与这些内容。 That is what git checkout做到了,这就足够了。但如果内容完全匹配怎么办?还有更多高效的Git 告诉我们:改变a123456 to f789abc, 改名a/b.c to d/e.f.事实上,只要有正确的选择,git diff will就这样做:

git diff --find-renames a123456 f789abc

Git 是如何做到这一点的呢?答案在于计算文件标识.

查找文件标识

假设提交L(左侧)有一些文件(a/b.c)不在提交中R(对于右侧)。进一步假设提交R有一些文件(d/e.f)不在提交中L。而不是立即告诉我们:您应该删除 L 文件并使用 R 文件,Git 现在可以比较contents两个文件的。

由于 Git 对象哈希的性质——它们是完全确定性的,基于文件内容——它是真的很容易让 Git 检测到a/b.c in L100% 相同d/e.f in R。在这种特殊情况下,它们将具有完全相同的哈希 ID!所以 Git 会这样做:如果有某个文件消失了L以及出现在的其他一些文件R,并且 Git 被要求find重命名时,Git 检查哈希 ID 匹配。如果它找到一些文件,它会将这些文件配对(并将它们从不匹配文件的队列中取出,该队列保存来自L and R,是“重命名检测队列”)。

那些具有不同名称的文件已被识别为同一个文件。毕竟,小表弟鲍勃和大表弟鲍勃是一样的——只不过在这种情况下,你们俩都还需要很小。

所以,如果这个重命名检测没有yet将文件配对L与一R,Git会更加努力。现在它将提取实际的斑点,并计算某种“匹配百分比”。这使用了一个复杂的小算法,我不会在这里描述,但如果两个文件中有足够多的子字符串匹配,Git 将声明文件为 50、60、75 或更多百分比similar.

在重命名队列中找到一对彼此相似度为 72% 的文件后,Git 继续将这些文件与所有其他文件进行比较。如果它发现这两者之一与另一个相似度为 94%,则该相似配对会击败 72% 相似配对。如果不是,72% 的相似度就足够了——至少 50%——所以 Git 会将这两个文件配对并声明它们具有相同的身份。

无论如何,如果比赛足够好的话and是所有未配对文件中最好的一个,则采用该特定匹配。再说一遍,小鲍勃表弟毕竟和鲍勃大表弟是一样的。

运行此测试后all不匹配的文件对,git diff获取匹配结果并调用这些文件renamed。同样,只有当您使用时才会发生这种情况--find-renames (or -M),并且您可以设置临界点如果您愿意,可以选择 50% 以外的其他值。

打破不正确的匹配

The git diff命令提供另一种服务。请注意,我们开始于assuming如果提交L and R有相同的文件name,这些文件是相同的file,即使内容不同。但如果他们不是呢?如果什么file in L被重命名为bettername in R, and有人创建了一个新的file in R?

为了处理这个问题,git diff提供-B(或“中断配对”)选项。和-B实际上,如果一开始通过名称标识的文件太不匹配,那么它们的配对就会被破坏。dis-相似的。也就是说,Git 将检查两个 blob 哈希值是否匹配,如果不匹配,Git 将计算相似度索引。如果指数下跌below达到某个阈值后,Git 将中断配对并将两个文件放入重命名检测队列中,然后再运行--find-renames样式重命名检测器。

作为一个特殊的变化,Git 将re-pair损坏的配对,除非它们非常不同以至于您不希望这样做。因此对于-B你实际上指定two相似度阈值:第一个数字是何时暂时中断配对,第二个数字是何时永久中断配对。

git merge uses git diff --find-renames

当你使用git merge要执行三向合并,需要三个输入:

  • 合并基础提交,它是两个提示提交的祖先;和
  • 左右提交,--ours and --theirs.

Git 运行two git diff内部命令。一个将基数与L另一个将基数与R.

这两个差异都与--find-renames已启用。如果从基数到L找到一个重命名,Git 知道使用changes显示在该重命名上。同样,如果从基数到R找到重命名后,Git 就知道要使用这些更改。它将组合两组更改,并尝试(但通常会失败)组合两个重命名(如果两个差异都显示重命名)。

git log --follow还使用重命名检测器

使用时git log --follow,Git 遍历提交历史记录,一次一个提交对(子项和父项),从父项到子项进行差异。它会打开一种有限形式的重命名检测代码,以查看您正在使用的文件是否是--follow-ing 在该提交对中被重命名。如果是这样,尽快git log移动到父级,它更改它查找的名称。这一技术工作得相当好,但在合并时存在一些问题(因为合并提交有多个父项)。

结论

文件标识这就是全部。因为 Git 事先不知道该文件a/b.c提交中L是或不是“相同”的文件作为文件d/e.f提交中R,Git可以使用重命名检测决定。在某些情况下——例如检查提交L or R——这一点也不重要。在某些情况下,例如区分两个提交,这很重要,但仅限于我们作为人类试图理解发生了什么。但在某些情况下,例如合并,很重要.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

git 如何跨提交树将 blob 与文件进行匹配? 的相关文章

  • Phonegap使用命令行工具添加插件

    我是phonegap的新手 我按照phonegap官方网站中定义的步骤使用命令行工具创建项目 nodejs 我成功创建项目并添加平台 但是 当我尝试添加插件时出现以下错误 命令 cordova plugin add https git wi
  • 如何让 mod_wsgi 在 Mac 上运行?

    几个小时以来 我一直在尝试在 Mac 上安装最新版本的 mod wsgi 3 3 我使用的是 Snow Leopard 并且有系统附带的 Apache Apache 2 2 15 和 Python 2 6 1 r261 67515 版本 我
  • Git:如何修改服务器的提交?

    我已经在 EC2 上的 git 服务器上推送了一些提交 而不是在 github 上 如何修改 git 服务器上的这些提交 操作就像 删除提交 例如变基 更改提交消息 是否可以 非常感谢 你几乎可以通过用力推动来完成所有事情 将您的本地树更改
  • GIT:提交时“致命:无法写入 new_index 文件”

    当我尝试将更改提交到本地存储库时 我收到以下消息 致命 无法写入 new index 文件 As this 线程说明 http luhman org blog 2010 04 05 git fatal unable write newind
  • 如何创建名称中带有正斜杠的标签

    当我已经有了类似的标签时 有什么方法可以创建名称中带有正斜杠的 git 标签吗 假设我有 1 16 0 标签 并且我想创建 1 16 0 1 0 0 标签 git tag 1 16 0 1 0 0 error refs tags 1 16
  • 如何诊断和修复 git fatal: 无法读取树

    我在用着git管理项目上的文件 并不断遇到这个问题 当我跑步时git status我收到消息 fatal unable to read tree e2d920161d41631066945a3cbcd1b043de919570 据我了解 我
  • 在我的例子中,GIT 添加恢复(保留更改)

    在我对文件进行更改之后 我用git add FILE NAME 然后 我想将其恢复为未添加但与此同时保留更改 这个怎么做 git reset FILE NAME会做的 See the git reset manual https git s
  • Gitolite git 克隆错误

    我正在尝试在我的服务器 Macos 服务器 上设置 gitolite 我按照此处找到的安装文档中的说明进行操作 http sitaramc github com gitolite doc 1 INSTALL html http sitara
  • 在推送后检索孤立的提交对象 --force

    Doing push force总是有点冒险 这里有一个例子 说明它如何产生一些问题 例如远程丢失修订版本 假设 有一个人Bob已更新远程master分支来自B to C 还有另外一个人Mike还没有获取此更新并且HEAD of his m
  • Jmeter 和 Bitbucket 服务器负载测试

    我是 Jmeter 的新手 我有一个本地托管的 Bitbucket 服务器 有时 当 Bamboo plan 触发并发 git 克隆操作时 会发现 Bitbucket 服务器变得缓慢 无响应 我必须重新启动服务 我想通过对另一个本地创建的
  • “解决方案的源代码控制之间似乎存在差异......”

    我们在 Visual Studio 2005 和 TFS 中经常遇到此错误 任何人都可以帮助我们查明此消息的原因吗 完整的消息是 解决方案的来源之间似乎存在差异 控制有关某些项目的信息以及 项目文件 为了解决这个差异 有必要检查 项目文件并
  • 如何在 Visual Studio 2013 中使用 Git 的外部 diff 工具?

    我找到了这个帖子 http architects dzone com articles how configure diff and merge这解释了如何让 Visual Studio 2013 在比较 Git 中的文件时使用内置 dif
  • 使用 git 合并两个截然不同的分支?

    我有我的master分支和我的verydifferentbranch它们有相同的祖先 大约 300 次提交前 现在verydifferentbranch功能完整我想把它放在master下面branch 进行变基会导致每个补丁都有很多合并冲突
  • Gitlab 备份错误:包对象因信号 9 死亡

    我有一个存储库 有 2 个分支 大小为 3 5GB 每个分支大约 1 5GB git 本身就有 700MB 这是错误 备份失败 失败的 失败 opt gitlab embedded bin git git dir mountdata git
  • Git Hook - 在我推送到 github 后让服务器拉取

    我有一个本地存储库 并且我在实时服务器上设置了另一个存储库 www site com projects ProjectA 我想要实现的目标非常简单 在我推送到 GitHub 后 我想要存储库位于www site com projects P
  • 没有分支的 svn 存储库签出

    我必须检查一个有 8 个分支的存储库 我不会下载每个分支大小 400MB 只需检查文件夹结构和主干即可 repo trunk repo tags
  • 如何将yarn add/npm install与monorepos一起使用

    我需要从 GitHub 中的私有 monorepo 下载节点包 类似于 monorepoProject subProjectA subProjectB 还有两个子项目A and 子项目B是 typescript 项目 如下图所示 subPr
  • 无法访问 Github 403 错误:权限被拒绝

    我是 Git Github 的新手 所以请原谅我的困惑 问题是 我刚刚对本地存储库进行了更改 并且想推送到 Github 上的原点 特别是 gh pages 之前 我一直在使用另一个 Github 帐户 因此我更改了 user name 和
  • 设置 Git osxkeychain 错误

    我正在设置 Git 并且正在关注http lifehacker com 5983680 how the heck do i use github http lifehacker com 5983680 how the heck do i u
  • “submodule”似乎是一个 git 命令,但我们无法执行它

    我已经克隆了一个 git 存储库 它是一个 Angular 7 和 NET Core 应用程序 项目中一切正常 但是当我尝试恢复 npm 包时 出现以下错误 Microsoft TeamFoundation Team Explorer Gi

随机推荐

  • 如何根据标签更改 Chart.js 点的颜色

    我有一个 Chart js 折线图 其中标签是星期几 我想根据具体日期 周一至周日 更改点背景 我可以根据数据值更改背景颜色 但这不是我需要的 相反 我想给每一天 标签 一个不同的色点 例如 这就是我如何根据数据值更改点 不是我需要的 ch
  • 带有 EduTools 插件的 Kotlin Koans:“无法启动检查”

    我正在尝试遵循科特林公案Android Studio 中的教程安装 EduTools 插件 and 选择 Kotlin Koans 课程 一切正常 但是当我尝试时 检查任务 in the 任务描述面板 我收到此错误 启动检查失败 我也尝试了
  • 覆盖功能

    我正在学习一所著名大学提供的 iOS 在线课程 我不明白为什么使用以下代码override这是合法的 根据官方定义 我们使用override重写超类的方法 下面代码中的子类和超类在哪里 什么被覆盖以及被什么覆盖 public overrid
  • 使用 cv::Mat 进行高效的 C++ 四元数乘法

    我想乘以 2 个四元数 它们存储在cv Mat结构 我希望该功能尽可能高效 到目前为止我有以下代码 Quaternion multiplication void multiplyQuaternion const Mat q1 const M
  • 使用 ServiceStack.ORMLite 实现工作单元和存储库模式的最佳实践

    假设有两个存储库接口 interface IFooRepository void Delete int id interface IBarRepository void Delete int id 以及工作单元界面 例如 interface
  • React 从 URL 内联导入 SVG

    我正在解决一个问题 其中 SVG 图像需要从 URL AWS S3 加载到反应组件 我能够使用本地文件中的 SVG 内联反应组件成功显示和加载图像 但是 svg 文件需要从 S3 存储桶内联加载 JS svg 导入不适用于 URL 所以我想
  • 从 Python 中的 webbrowser.get() 调用 Chrome Web 浏览器

    我应该如何调用 webbrowser get 函数以便打开 chrome 网络浏览器 我正在运行 Ubuntu 11 04 和 Python 版本 2 7 使用 webbrowser get chrome 会产生错误 快速解决方法是将 Ch
  • HttpClient 不支持 PostAsJsonAsync 方法 C#

    我正在尝试从我的 Web 应用程序调用 Web API 我正在使用 Net 4 5 在编写代码时出现错误HttpClient不包含定义PostAsJsonAsync method 下面是代码 HttpClient client new Ht
  • 批处理在 for /f 命令上过早关闭

    我有一个批处理文件 在 Windows XP 中 激活了命令扩展 其中包含以下行 for f s in type version txt do set VERSION s 在某些计算机上 它工作得很好 如图所示这个问题 但在其他方面它杀死了
  • UISplitViewController 和方向 - iOS < 5.0

    我使用 splitviewcontroller 作为我的应用程序的根视图 我需要将登录和注册视图显示为 splitviewcontroller 顶部的模式视图 当我尝试从 splitViewController 的 rootview 的 v
  • “RepeatedCompositeContainer”类型的对象不可 JSON 序列化

    使用 Google Client Library 与视觉库交互 我有一个从图像中检测标签的功能 GoogleVision py import os from google cloud import vision from google cl
  • HTML Canvas - 绘制弯曲箭头

    我正在尝试在 html 画布中绘制弯曲的箭头 我画一条曲线没有问题 但我不知道如何放置 gt 在线的末端 方向 ctx beginPath ctx fillStyle rgba 55 217 56 opacity ctx moveTo th
  • 是什么触发了此异常实例:“java.lang.IllegalArgumentException:观察者为空。”如何避免呢?

    返回原始版本时出现此异常ListActivity使用用户选择的项目的内容打开新活动后 它只出现在冰淇淋三明治上 这是痕迹 java lang IllegalArgumentException The observer is null at
  • 如何从 String 中获取 Date 对象

    DateFormat formatter new SimpleDateFormat MM dd yyyy HH mm ss Date d Date formatter parse dateTime System out println da
  • C++ 将字符串转换为十六进制[重复]

    这个问题在这里已经有答案了 可能的重复 C 将十六进制字符串转换为有符号整数 我已经在谷歌上搜索过 但没有找到任何帮助 所以这是我的问题 我有已经包含十六进制代码的字符串 例如 string s1 5f0066 我想将此字符串转换为十六进制
  • Tablesorter zebra 在排序之前不会条纹

    我有我的桌子 它们很棒 我可以对它们进行排序 而且效果非常好 只是在我第一次对它们进行排序之前 它们不会进行斑马条纹 我的理解是 一旦表排序器初始化 它们就会被条带化 不是这样吗 这是来自此处的 tablesorter v 2 10 最新版
  • Graphics.MeasureCharacterRanges 给出错误的尺寸计算

    我正在尝试将一些文本渲染到 Web 表单应用程序中图像的特定部分 文本将由用户输入 因此我想改变字体大小以确保它适合边界框 我的代码在概念验证实现上做得很好 但我现在正在针对设计器的资产进行尝试 这些资产更大 并且我得到了一些奇怪的结果 我
  • Edittext 行号和当前行光标位置。

    现在我正在开发一个 Android 应用程序 我创建了一个具有功能的自定义键盘 我正在使用 edittext 来显示输入的文本 编辑文本可能有 n 行 现在我的问题是我的键盘上有一个向上按钮 所以如果我单击向上按钮 那么我必须转到前几行的相
  • Flask-SQLAlchemy:SQLALCHEMY_ENGINE_OPTIONS 设置不正确

    我刚刚将我的项目 Flask SQLAlchemy 版本更新到最新版本 v2 4 由于某些 SQL Alchemy 配置参数已被弃用 我现在遵循文档并将 SQLALCHEMY ENGINE OPTIONS 作为字典添加到我的配置类中 但是
  • git 如何跨提交树将 blob 与文件进行匹配?

    Git 书的第 3 1 章明确指出只有暂存文件才能作为 blob 存储在提交树中 如果像提交对象一样 Blob 获得对其内容唯一的哈希 ID 那么 Git 如何管理跨提交跟踪 Blob 和文件之间的对应关系 不同提交中相同文件 blob 的