故事背景
在进入正题前,我们先来看一个简单的Git操作场景。
我创建了一个名为’test_shasum’的项目,并初始化了git环境:
$ mkdir ~/tmp/test_shasum
$ cd ~/tmp/test_shasum
$ git init
Initialized empty Git repository in /Users/xiali/tmp/test_shasum/.git/
接下来,我创建一个名叫‘main.go’的文件,内容只有一行package声明:
$ echo 'package main' > main.go
$ cat main.go
package main
我把这个文件commit到树上:
$ git add main.go
$ git commit -m 'init commit'
[master (root-commit) 244a180] init commit
1 file changed, 1 insertion(+)
create mode 100644 main.go
现在,让我们来看看git的objects里有了些什么:
$ tree .git/objects/
.git/objects
├── 06
│ └── ab7d0f9a35a7d1070711496d6ca1cb892a258f
├── 24
│ └── 4a1807bafb82e071767847e15e57cf5689b9f6
├── b9
│ └── 270df7070cc6a5e7dbdec610a7ce4f54c47b20
├── info
└── pack
多了三个东西,不用说,一定是commit, tree和blob山大件。
我们分别来看看这些hash值对应的都是啥。
- 这个
06ab...
对应的是我们的’main.go’文件的内容。也就是blob:
$ git cat-file -p 06ab
package main
- 这个
244a...
对应的是我们的commit:
$ git cat-file -p 244a
tree b9270df7070cc6a5e7dbdec610a7ce4f54c47b20
author iamharvey <39893271+iamharvey@users.noreply.github.com> 1588260068 +0800
committer iamharvey <39893271+iamharvey@users.noreply.github.com> 1588260068 +0800
init commit
- 最后这个
b927...
对应的是我们的tree:
$ git cat-file -p b927
100644 blob 06ab7d0f9a35a7d1070711496d6ca1cb892a258f main.go
让我们重新聚焦blob。这个ab7d0f9a35a7d1070711496d6ca1cb892a258f
应该是对应文本package main
。在一篇文章中,我了解到git并不是简单的在文本上hash, 而是加了点东西。实际hash的内容其实是:
Blob + <hash的实际内容字符数> + \0 + <文本> + <line breaker>
这里hash的实际内容字符数 = 文本字符数 + 1,因为我们还得算上
$ echo 'blob 13\0package main' | shasum
06ab7d0f9a35a7d1070711496d6ca1cb892a258f -
结果和我们在git里看到的是一样的。
在GO中计算SHA1 Sum
让我们在GO中计算一下这个SHA1的Sum值。调用crypt
库,写几行代码搞定它!
func main() {
b := []byte(`blob \0package main\n`)
h := sha1.New()
h.Write(b)
s := h.Sum(nil)
fmt.Printf("%x\n", s)
}
结果如下:
$ go run main.go
c8950f4833722ba3370f4748074c46728f83e8f9
等等!Sum值不一样。啥情况?!这就是今天同事来问我的问题。其实不难理解,这个问题主要在这个\0
上。\0
并不是字符串\
+ 0
。在echo时,这个\0
其实是NUL byte(0x00
)。所以正确打开它的方式应该是[]byte{0x00}
。我们把代码改一改,问题就解决了:
func main() {
b := []byte("blob 13")
b = append(b, []byte{0x00}...)
b = append(b, []byte("package main\n")...)
h := sha1.New()
h.Write(b)
s := h.Sum(nil)
fmt.Printf("%x\n", s)
}
篇尾小贴士:
\0
是一个’隐形‘的NUL byte,表达为[]byte{0x00}
, 是有占坑的。非同于var b []byte
或b := []byte{}
这种空字符;echo <some text>
默认会在后面加line breaker。如果你不希望hash这个line breaker, 用 echo -n <some text>
。
⚠️ 通过var
来申明的是空指针,而通过:=
的并不是,尽管他们的地址(0xc00000c0c0)是一样的。