Git中的提交:是快照/状态/图像还是更改/差异/补丁/增量?

Lut*_*elt 5 git

  • Git中的某些操作(例如checkout)似乎假定提交是工作树的快照或状态。
  • Git中的其他操作(例如rebase)似乎假定提交是更改:可以应用于工作树的一种运算符。

那么,什么是Git提交呢?

Von*_*onC 10

虽然可以解释为两者,但 GitHub 工程团队很明确(2020 年 12 月):

\n
\n

提交是快照,而不是差异

\n
\n

德里克·斯托利开始于

\n
    \n
  • 对象ID
  • \n
  • blob(文件内容)
  • \n
  • 树(目录列表)
  • \n
  • 提交:快照!
  • \n
\n
\n

对象ID

\n

了解 Git 对象最重要的部分是,Git 通过对象 ID(简称 OID)引用每个对象,为对象提供唯一的名称。
\n我们将使用该git rev-parse <ref>命令来发现这些 OID。
\n每个对象本质上都是一个纯文本文件,我们可以使用git cat-file -p <oid>

\n
\n
\n

Blob(文件内容)

\n

要发现当前版本的文件的 OID,请运行git rev-parse HEAD:<path>.
\n然后,使用git cat-file -p <oid>来查找其内容。

\n
\n
\n

树(目录列表)

\n

请注意,blob 包含文件内容,但不包含文件
\n名称来自 Git\xe2\x80\x99s 目录表示:树。
\n树是路径条目的有序列表,与该路径上的对象类型、文件模式和 OID 配对。
\n子目录也表示为树,因此树可以指向其他树!

\n
\n
\n

最后:

\n
\n
\n

提交:及时快照

\n

提交是时间的快照。每次提交都包含一个指向其根树的指针,代表当时工作目录的状态
\n该提交具有与先前快照相对应的父提交列表。
\n没有父项的提交是根提交,有多个父项的提交是合并提交。
\n提交还包含描述快照的元数据,例如作者和提交者(包括姓名、电子邮件地址和日期)以及提交消息。
\n提交消息是提交作者向父级描述该提交目的的机会。

\n

https://github.blog/wp-content/uploads/2020/12/commit.png?resize=399%2C268?w=399

\n

尽管提交是快照,但我们经常在历史视图或 GitHub 上将提交视为差异。事实上,提交消息经常提到这个差异。

\n

差异是通过比较提交及其父级的根树从快照数据动态生成的。Git 可以及时比较任意两个快照,而不仅仅是相邻的提交。

\n

计算差异是启用git cherry-pickgit rebase的原因。

\n
\n

而且由于提交没有差异...

\n
\n

Git 不\xe2\x80\x99t 跟踪重命名。Git 内部没有数据结构来存储提交与其父提交之间发生重命名的记录。
\n相反,Git 会尝试在动态差异计算期间检测重命名。此重命名检测分为两个阶段:精确重命名和编辑重命名。

\n

首次计算差异后,Git 检查该差异的内部模型以发现添加或删除了哪些路径。
\n自然地,从一个位置移动到另一个位置的文件将显示为从第一个位置删除并在第二个位置添加。Git 尝试匹配这些添加和删除以创建一组推断的重命名。

\n
\n


Lut*_*elt 5

了解Git粒子/波对偶

简短的回答:

中等答案: 这取决于。

长答案: Git有点像量子现象:两种观点都不能单独解释所有观察结果。继续阅读。

在内部,Git将使用这两种表示形式,(在概念上)取决于它在给定提交的存储空间和执行时间方面认为哪种表示效率更高。快照表示是主要的。

但是,从用户的角度来看,这取决于您的操作:

对偶1:作为快照提交与作为更改提交

实际上,当您将提交视为工作树的快照时,某些命令根本没有任何意义。这是最明显的checkout,但也适用于 stash和至少一半的fetchreset

对于其他命令,当您尝试以这种方式考虑提交时,可能会产生疯狂。 对于那些其他命令,提交清楚地视为变化

  • 无论是在补丁的形式,你可以看看(例如showdiff
  • 或者运营商的形式,你可以申请修改工作树(例如applycherry-pickpull
  • 或以运算符的形式,您可以申请修改其他提交(例如rebase
  • 或者运营商的形式,你可以应用到创建新的提交(例如mergecherry-pick

对偶二:作为固定事物进行提交与作为固定事物进行提交

双重性1的副作用可能会震惊习惯于其他版本控制系统的Git新手。这是事实,Git似乎甚至没有承诺自己的承诺。

??

假设您已经创建了一个分支X,其中包含您想作为commit A和想到的东西B。但是master进步了一点,所以你rebaseX到master

当你想到的AB为变化,但master作为一个快照 (哎,粒子和波在一次实验),这并不是一个问题:只要应用更改AB快照master

这种想法很自然,您几乎不会注意到Git现在已经重写了您的提交,A并且B:它们现在具有不同的快照内容,因此具有不同的SHA-1 ID。在Git中,您认为作为开发人员的概念性承诺不是千篇一律的事情,而是一些因使用存储库而发生变化的可变对象。

相反,如果您将所有三个(ABmaster)视为快照,或者将所有三个(,和)视为更改,则大脑会受伤并且无处可去。

免责声明

上面是非常简化的描述。在Git现实中,

  • 提交根本不是快照,它是一段元数据(快照的谁/何时/为什么)加上指向快照的指针;
  • 快照在Git术语中称为
  • commits-as-changes内部表示使用packfiles
  • 一些上述命令还具有其他角色,这些角色不适合相同的特征;
  • 甚至对于给定的角色,某些命令在某种程度上也属于某个类别(或-ies)的问题。

并且不要对Pro Git本书的Git的第一个特征(在“ Git基础知识 ”部分中)是“快照,而不是差异”感到困惑。

Git 毕竟复杂。

  • 我认为在“考虑”Git 时忽略 packfiles 更简单,因为 Git 的 packfile 实现隐藏得足够好,除非您试图优化整个存储库,否则您不需要关心它-磁盘。我们简单地观察到,对于“大多数”提交,只有一个“父提交”,并且为了将“快照”转换为“变更集”,我们只需减去即可。如果“B”是“A”的子级,则“B”的变更集就是“B - A”。 (3认同)