在 Git 中,提交是从不更改的。 rebase 和 git commit --amend 都不会更改任何提交,因为这是不可能的。1
这里的诀窍在于定义“提交”。你怎么知道哪个提交是哪个?如果我说“在 Git 存储库中提交 Git”,那么那里有超过 40,000 个提交。 我指的是哪一个?
我告诉你的明确而明确的方式是给你哈希 ID,例如,9b7cbb315923e61bb0c4297c701089f30e116750。这是一个特定提交的真实名称:
$ git cat-file -p 9b7cbb315923e61bb0c4297c701089f30e116750 | sed 's/@/ /'
tree 4ba58c32960dcecc1fedede9c9362f5c10158f08
parent 77933f4449b8d6aa7529d627f3c7b55336f491db
author Junio C Hamano <gitster pobox.com> 1418845774 -0800
committer Junio C Hamano <gitster pobox.com> 1418845774 -0800
Git 2.2.1
Signed-off-by: Junio C Hamano <gitster pobox.com>
此名称永久附加到此特定提交。不过,这肯定是一个笨拙和丑陋的名字。有一个更短、更漂亮的wieldy 名字不是很好吗?还有一个:我可以给你指点v2.2.1:
$ git rev-parse v2.2.1^{commit}
9b7cbb315923e61bb0c4297c701089f30e116750
但事实上,v2.2.1 根本不是提交,它是一个标签。具体来说,它是一个标签名称(在refs/tags/v2.2.1 或packed-refs 文件中名称为v2.2.1 下)指向一个带注释的标签 对象,2 而不是而不是直接提交:
$ git rev-parse v2.2.1
7c56b20857837de401f79db236651a1bd886fbbb
标签对象里面有提交 ID,加上一大堆额外的 goop,包括“PGP 签名”:
$ git cat-file -p v2.2.1 | sed 's/@/ /'
object 9b7cbb315923e61bb0c4297c701089f30e116750
type commit
tag v2.2.1
tagger Junio C Hamano <gitster pobox.com> 1418851265 -0800
Git 2.2.1
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1
iQIcBAABAgAGBQJUkfPBAAoJELC16IaWr+bLjfgP/iA78fk3NkTEROoyIVq6kPDH
pZAlm4ObsKXAdl6sFqWe7xFxGExHYzJ5L3qGXs3VM+9Z3iDe2WZN3WbK3aFtYqfU
AYRSTpnPzDf4L0vfyqiFS7//+LoeM2TogAV7SLdehMlodsL5HR6FiSz1zffSq8D0
Ci4XpGWHkqXLhfvUPC7foCgGpf7l38gsbJPbdkyKLK9/wtLSfkk45vK+wY6o3CCv
JKBFr468958fvw+j73nxiT+Vne7TeL1Bq1kCq9M65dAjOpFjZiD408NaF7jTcNcx
TMjdKoVlDNFHcUPMv9B5C308sRVUylmeUzb8XrQNji0+1NA5ivVgDfZsudWUtlTj
jo9xku0Np4IdXPwxJNlO5tC2rnof4gdD4jWPJj/DvellNKCDXuLuXDZSKZDI9GSr
OzLsad8uFX3MySPe+evIVF6qGS2KzI8PGNrohqWaPkX8cug22EW7lKJFpjYJb5gP
3nJUJvbsrMeyoH/GqxPzA5clqMGtsirnTiapMILNRmlC+3rzc0DkLw90BM6vKNOC
eDTOI9Xj1JS9qbD6fEkxVNrXRDz0TFbtpFbFTtKk4zfAc/jTOqE9fqpV7afoQfON
e1NwrjR5Kcts7ev23Y0G1WH3t2L0N2/q27kcjrulCEH1vtXlmaZFU6o+WKUVV7iH
/YQnjNUOgRxQ1zBGof7h
=yJ4Q
-----END PGP SIGNATURE-----
PGP 签名让我们决定我们是否相信 Junio C Hamano 真的制作并签署了这个标签。它使用比 SHA-1 更强大的加密数字签名形式(这很好,因为 SHA-1 至少在理论上是可破解的),它还支持分布式验证和撤销签名的能力(SHA-1 本身没有)。
不过,最终,这只会帮助我们如果我们信任和/或可以验证的人已经制作了这样的 PGP 签名标签,或者已经 PGP 签名了提交。从理论上讲,对每个提交进行签名可能会更强一些,因为那时提交上直接有一个数字签名;但在实践中,签署标签更方便,而且同样好,因为我们不经常破坏 SHA-1(而且,至少使用当前的蛮力方法,如果我们这样做,它会留下明显的痕迹,尽管那是远远超出了这个答案的范围,也有点超出了我的正确描述——密码学不是我的领域)。
1嗯,如果可以break the SHA-1 hash,理论上是可以的。如果你想出一个新的、不同的对象但仍然产生相同的 hash,Git 的行为方式意味着如果你已经有了旧对象,你将永远不会拿起这个新对象。此规则适用于所有 Git 对象(提交、树、带注释的标签和 blob),所有这些对象都由它们的哈希命名。
git rebase 和 git commit --amend 所做的,看起来就像他们更改了提交一样,是为现有提交制作新副本,然后 改组名称。新提交具有新的、不同的哈希值,并且由于 later(后代)提交实际上包含其直接祖先(父)提交的哈希值,因此“更改”了一个提交的哈希值(即复制提交对象到一个新的、不同的提交对象)强制更改在其余提交中冒泡。然后,我们将现有的(短、分支或标记)name 重新指向新链的末端。
这就是为什么给定一个我们认为可信的端点,我们可以将该信任扩展到链或树中的每个先前对象。技术术语是Merkle tree。
2这使得它成为 Git 所谓的“带注释标签”:一个标签名称(它本身就是一个“轻量级标签”)指向一个带注释标签的对象,存储在 Git 中存储库,标签对象指向其他 Git 对象——通常是提交,但也可能是另一个标签,甚至是树或 blob。然而,即使是“另一个标签”也有些罕见——在 Git 的 Git 存储库中只有三个标签——而另外两个几乎闻所未闻。