故事背景

在进入正题前,我们先来看一个简单的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 []byteb := []byte{}这种空字符;
  • echo <some text> 默认会在后面加line breaker。如果你不希望hash这个line breaker, 用echo -n <some text>

⚠️ 通过var来申明的是空指针,而通过:=的并不是,尽管他们的地址(0xc00000c0c0)是一样的。

发表评论

电子邮件地址不会被公开。 必填项已用*标注