主题行中问题的正确答案:
Git 对象 SHA-1 是文件内容还是文件名?
可能“两者都不是”,因为您指的是松散对象文件的内容,而不是原始文件——即使您指的是原始文件,这仍然不太正确。
松散的对象,在 Git 中,是一个普通文件。文件名由对象的哈希 ID 构成。反过来,对象的哈希 ID 是通过计算对象内容的哈希来构造的,带有前缀标头。
前缀标题取决于对象类型。有四种类型:blob、commit、tag 和tree。标头由一个以 0 结尾的字节字符串组成,该字符串由作为 ASCII(或等效的 UTF-8)字节字符串的类型名称组成,后跟一个空格,然后是对象大小的十进制表示(以字节为单位),然后是通过 ASCII NUL(如果您更喜欢现代 Python 表示法,则在 Python 中为b'\x00',如果您更喜欢 C,则为'\0')。
在标题之后是实际的对象内容。因此,对于包含字节字符串b'hello\n' 的文件,要散列的数据由b'blob 6\0hello\n 组成:
$ echo 'hello' | git hash-object -t blob --stdin
ce013625030ba8dba906f756967f9e9ca394464a
$ python3
[...]
>>> import hashlib
>>> s = b'blob 6\0hello\n'
>>> hashlib.sha1(s).hexdigest()
'ce013625030ba8dba906f756967f9e9ca394464a'
因此,用于存储此文件的文件名是(派生自)ce013625030ba8dba906f756967f9e9ca394464a。作为一个松散的对象,它变成了.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a。
然而,该文件的 内容 是 b'blob 6\0hello\n' 的 zlib 压缩形式(显然,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\000\^]\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 对象中的路径名组件。计算树对象的哈希 ID 并非易事;我在githash.py 下的公共“脚本”存储库中有执行此操作的 Python 代码。