Git 对象 SHA-1 是文件内容还是文件名?
Git objects SHA-1 are file contents or file names?
我对文件的实际内容如何存储在 .git 中感到困惑。
例如Version 1
是test.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类型。有四种类型:blob
、commit
、tag
和tree
。 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 代码执行此操作
我对文件的实际内容如何存储在 .git 中感到困惑。
例如Version 1
是test.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 (usinggit 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类型。有四种类型:blob
、commit
、tag
和tree
。 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.