4

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

例如Version 1是 中的实际文本内容test.txt。当我将它提交(第一次提交)到 repo 时,git 为该文件返回一个 SHA-1,该文件位于.git\objects\0c\15af113a95643d7c244332b0e0b287184cd049.

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

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

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

4

2 回答 2

5

主题行中问题的正确答案:

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

可能“都不是”,因为您指的是松散对象文件的内容,而不是原始文件——即使您指的是原始文件,这仍然不太正确。

在 Git 中,一个松散的对象是一个普通文件。文件名由对象的哈希 ID 构成。反过来,对象的哈希 ID 是通过计算对象内容的哈希以及附加的前缀标头来构造的。

带前缀的标头取决于对象类型。有四种类型:blobcommittagtree。标头由一个以 0 结尾的字节字符串组成,该字符串由作为 ASCII(或等效的 UTF-8)字节字符串的类型名称组成,后跟一个空格,然后是对象大小的十进制表示(以字节为单位),然后是通过 ASCII NUL(b'\x00'在 Python 中,如果您更喜欢现代 Python 表示法,或者'\0'如果您更喜欢 C)。

在标题之后是实际的对象内容。因此,对于包含字节字符串的文件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 完全匹配,但使用 level 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 代码。

于 2017-06-10T19:36:27.203 回答
3

Git Magic提到:

顺便说一句,.git/objects 中的文件是用 zlib 压缩的,所以你不应该直接盯着它们看。通过 过滤它们zpipe -d,或输入(使用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...>

您会看到由类型和内容大小组成的标题,然后是实际内容。

有关 blob 实际内容的说明,请参阅 Jeff Kunkle 的“了解 Git 内部结构”,幻灯片 8:

杰夫昆克尔

于 2017-06-10T17:47:55.757 回答