Git 对象 SHA-1 是文件内容还是文件名?

Git objects SHA-1 are file contents or file names?

我对文件的实际内容如何存储在 .git 中感到困惑。

例如Version 1test.txt中的实际文字内容。当我将它提交(第一次提交)到 repo 时,git returns 位于 .git\objects[=13=]caf113a95643d7c244332b0e0b287184cd049 中的该文件的 SHA-1。

当我在文本编辑器中打开文件 15af113a95643d7c244332b0e0b287184cd049 时,里面全是垃圾,像这样

x+)JMU074f040031QÐKÏ,ÉLÏË/Je¨}ºõw[Éœ„ÇR­ ñ·Î}úyGª*±8#³¨,1%>9?¯¯D¯¤¢„áôÏ3%³þú>š~}Ž÷*ë²-¶ç¡êÊòR“KâKòãs+‹sô

但我不确定这个垃圾是代表文本的加密形式 Version 1 还是由 SHA-1 代表 15af113a95643d7c244332b0e0b287184cd049

Git Magic 提及:

By the way, the files within .git/objects are compressed with zlib so you should not stare at them directly. Filter them through zpipe -d, or type (using git cat-file):

$ git cat-file -p .git/objects/0c/15af113a95643d7c244332b0e0b287184cd049

zpipe:

$ ./zpipe -d < .git/objects/0c/15af113a95643d7c244332b0e0b287184cd049

注意:对于zpipe,我必须先编译zpipe.c

sudo apt-get install zlib1g-dev
cd /usr/share/doc/zlib1g-dev/examples
sudo gunzip zpipe.c.gz
sudo gcc -o zpipe zpipe.c -lz

然后:

$ /usr/share/doc/zlib1g-dev/examples/zpipe -d < /usr/share/doc/zlib1g-dev/examples/zpipe -d <

您将得到如下结果:

vonc@VONCAVN7:/mnt/d/git/seec$ /usr/share/doc/zlib1g-dev/examples/zpipe -d < .git/objects/0d/b6225927ef60e21138a9762c41ea0db714ca0d
blob 2142 <full content there...>

您会看到 header 由类型和内容大小组成,后跟实际内容。

请参阅 Jeff Kunkle 的“Understanding Git Internals”,幻灯片 8,了解 blob 实际内容的说明:

主题行问题的正确答案:

Git objects SHA-1 are file contents or file names?

可能是 "neither",因为您指的是松散的 object 文件的内容,而不是原始文件——即使您指的是原始文件,也仍然不是很对。

一个松散的object,在Git,是一个普通文件。文件名由 object 的哈希 ID 构成。反过来,object 的哈希 ID 是通过计算 object 的内容 并附加前缀 header 的哈希来构建的。

前缀header取决于object类型。有四种类型:blobcommittagtree。 header 由一个 zero-terminated 字节字符串组成,该字符串由 ASCII(或等效的 UTF-8)字节字符串形式的类型名称组成,后跟 space,然后是十进制表示object 的大小(以字节为单位),后跟一个 ASCII NUL(Python 中的 b'\x00',如果你喜欢现代 Python 表示法,或者 '[=19=]' 如果你更喜欢 C).

在 header 之后是实际的 object 内容。因此,对于包含字节字符串 b'hello\n' 的文件,要散列的数据由 b'blob 6[=21=]hello\n:

组成
$ echo 'hello' | git hash-object -t blob --stdin
ce013625030ba8dba906f756967f9e9ca394464a
$ python3
[...]
>>> import hashlib
>>> s = b'blob 6[=10=]hello\n'
>>> hashlib.sha1(s).hexdigest()
'ce013625030ba8dba906f756967f9e9ca394464a'

因此,用于存储此文件的文件名是(派生自)ce013625030ba8dba906f756967f9e9ca394464a。作为一个松散的object,它变成了.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a

然而,该文件的 内容 b'blob 6[=24=]hello\n' 的 zlib-compressed 形式(显然 level=1——默认目前是 6,结果在该级别不匹配;不清楚 Git 的 zlib deflate 是否与 Python 完全匹配,但使用级别 1 在这里确实有效):

$ echo 'hello' | git hash-object -w -t blob --stdin
ce013625030ba8dba906f756967f9e9ca394464a
$ vis .git/objects/ce/013625030ba8dba906f756967f9e9ca394464a
x\^AK\M-J\M-IOR0c\M-HH\M-M\M-I\M-I\M-g\^B[=11=]0\^]\M-E\^D\^T$

(注意最后的 $ 又是 shell 提示;现在回到 Python3)

>>> import zlib
>>> zlib.compress(s, 1)
b'x\x01K\xca\xc9OR0c\xc8H\xcd\xc9\xc9\xe7\x02\x00\x1d\xc5\x04\x14'
>>> import vis
>>> print(vis.vis(zlib.compress(s, 1)))
x\^AK\M-J\M-IOR0c\M-HH\M-M\M-I\M-I\M-g\^B\^@\^]\M-E\^D\^T

其中 vis.py 是:

def vischr(byte):
    "encode characters the way vis(1) does by default"
    if byte in b' \t\n':
        return chr(byte)
    # control chars: \^X; del: \^?
    if byte < 32 or byte == 127:
        return r'\^' + chr(byte ^ 64)
    # printable characters, 32..126
    if byte < 128:
        return chr(byte)
    # meta characters: prefix with \M^ or \M-
    byte -= 128
    if byte < 32 or byte == 127:
        return r'\M^' + chr(byte ^ 64)
    return r'\M-' + chr(byte)

def vis(bytestr):
    "same as vis(1)"
    return ''.join(vischr(c) for c in bytestr)

(vis 生成可逆但可打印的二进制文件编码;这是我 1993 年对 cat -v 问题的回答)。

请注意,存储在 Git 存储库(在提交下)中的 文件名 仅显示为 路径名组件 存储在个人 tree object 中。计算树的散列 ID object 很重要;我在 githash.py.

下的 public "scripts" 存储库中有 Python 代码执行此操作