我如何知道 git 提交是否已更改?

How do I know if a git commit has been changed?

有人在几个月前犯了错误。之后,完成了多个其他提交。是否可以通过修改或变基来查看是否有人更改了该特定提交的内容?如果是,如何?

Git 中的一项提交从未 更改。 rebase 和 git commit --amend 都不会更改任何提交,因为这是不可能的。1

这里的技巧在于定义 "a commit"。你怎么知道哪个提交是哪个?如果我说 "a commit in the Git repository for 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.1packed-refs 文件中以名称 v2.2.1 找到)指向一个 带注释的标签 对象, 2 而不是直接提交:

$ git rev-parse v2.2.1
7c56b20857837de401f79db236651a1bd886fbbb

标签对象里面有提交 ID,加上一大堆额外的 goop,包括 "PGP signature":

$ 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 至少在理论上是可破解的)还支持分布式验证和 revoke 签名(SHA-1 本身没有)。

不过,最后,这只会帮助我们 如果 我们信任的人 and/or 可以验证已经制作了这样的 PGP 签名标签,或者已经进行了 PGP 签名一个承诺。从理论上讲,签署每个提交可能会更强大一些,因为在提交上直接有数字签名;但在实践中,签名标签要方便得多,而且同样好,因为我们不会经常去破解 SHA-1(而且,至少使用当前的蛮力方法,如果我们这样做,它会留下明显的标记,尽管那是远远超出了这个答案的范围,也超出了我的描述范围——密码学不是我的领域。


1嗯,可以的话理论上是可以的break the SHA-1 hash。 Git 如果你想出一个新的、不同的对象但仍然产生相同的 hash 的行为方式意味着你将永远不会拾取这个新对象如果你已经有了旧的,虽然。此规则适用于所有 Git 对象(提交、树、带注释的标签和 blob),所有这些对象均由其哈希值命名。

git rebasegit commit --amend 所做的, 看起来 就像他们更改提交一样,是制作现有提交的新副本,然后 随机排列名字。新提交具有新的、不同的哈希值,并且由于 later(后代)提交字面上包含其直接祖先(父)提交的哈希值,"changing" 一个提交的哈希值(即,将提交对象复制到一个新的、不同的提交对象)强制更改在其余提交中冒泡。然后我们将现有的(short、branch 或 tag)name 重新指向新链的顶端。

这就是为什么,给定一个我们认为可信的端点,我们可以将该信任扩展到链或树中的每个先前对象。这个的技术术语是 Merkle tree.

2这使得它成为 Git 所称的 "annotated tag":标签名称(它本身就是 "lightweight tag")指向到存储在 Git 存储库中的注释标签对象,标签对象指向其他一些 Git 对象——通常是提交,但也可能是另一个标签,甚至是树或 blob。然而,即使是 "another tag" 也很少见——在 Git 的 Git 存储库中只有三个——而另外两个几乎闻所未闻。