git notes 让你给已有的提交加说明而不改变提交的 hash。它存在哪里、怎么显示、改写历史之后会怎样、为什么 clone 不带笔记?这篇逐项实测,并看笔记很多时 tree 怎么扇出。
$ git notes add -m "这次提交通过了人工评审,评审人:小王" HEAD
HEAD 的 hash 没变: True
$ git log -2 --format='%h %s%n%N' | sed -E 's/^[0-9a-f]{7} /<sha7> /'
<sha7> 提交二
这次提交通过了人工评审,评审人:小王
<sha7> 提交一
$ git for-each-ref --format='%(refname)' refs/notes; git notes list | sed -E 's/[0-9a-f]{40}/<sha40>/g; s/\b[0-9a-f]{7}\b/<sha7>/g'
refs/notes/commits
<sha40> <sha40>给提交加笔记后提交本身的 hash 不变,git log 在提交后面多显示一段 Notes:(%N 占位符)。笔记存在另一个 ref refs/notes/commits 里。
$ git cat-file -p refs/notes/commits^{tree} | sed -E 's/[0-9a-f]{40}/<sha40>/g; s/\b[0-9a-f]{7}\b/<sha7>/g'; echo "--- 文件名就是被注解的提交的完整 hash;内容:"; git cat-file -p refs/notes/commits:$(git rev-parse HEAD)
100644 blob <sha40> <sha40>
--- 文件名就是被注解的提交的完整 hash;内容:
这次提交通过了人工评审,评审人:小王
文件名等于 HEAD 的完整 hash: True
$ git log --format="%s" refs/notes/commits
Notes added by 'git notes add'
再改一次(append / 编辑)就是在这个“分支”上再加一个提交:
$ git notes append -m "补充:已合并到主线" HEAD; git log --format="%s" refs/notes/commits | tr "\n" "|"; echo; git notes show HEAD
Notes added by 'git notes append'|Notes added by 'git notes add'|
这次提交通过了人工评审,评审人:小王
补充:已合并到主线refs/notes/commits 是一个普通分支:它的 tree 把“被注解的提交的完整 hash”当文件名,笔记内容是文件内容;每次修改在这个分支上加一个提交,所以笔记本身也有历史。
$ git notes --ref=build add -m "构建 #1234 成功" HEAD; git for-each-ref --format="%(refname)" refs/notes | tr "\n" " "; echo
refs/notes/build refs/notes/commits
$ git log -1 --format='%s%n%N' | head -4; echo '--- 加上 --notes=build:'; git log -1 --notes=build --format='%s' ; git log -1 --notes=build | tail -3
提交二
这次提交通过了人工评审,评审人:小王
补充:已合并到主线
--- 加上 --notes=build:
提交二
Notes (build):
构建 #1234 成功
$ git config notes.displayRef "refs/notes/*"; echo "--- 设置 notes.displayRef=refs/notes/* 之后:"; git log -1 | tail -5
--- 设置 notes.displayRef=refs/notes/* 之后:
补充:已合并到主线
Notes (build):
构建 #1234 成功git log 默认显示 refs/notes/commits 的笔记(%N 占位符);用 --ref 可以建别的命名空间,--notes=<名> 或 notes.displayRef 控制显示哪些。
$ git log -1 --format="%s 笔记: [%N]" T1 笔记: [] 笔记仍然挂在旧提交上: 1 条笔记,对应的提交现在已不在 topic 分支上 $ git log -1 --format="%s 笔记: [%N]"; git notes list | wc -l | sed "s/^ */现在 notes 里的条数: /" T1 笔记: [T1 的笔记 ] 现在 notes 里的条数: 2
笔记按提交 hash 挂着,rebase / amend 得到新 hash 后默认看不到旧笔记;设置 notes.rewriteRef 才会在改写时把笔记复制到新提交。
10 条笔记:顶层 tree 有 10 个条目,类型:hash 作文件名;第一个条目:2cc2efbbe309
50 条笔记:顶层 tree 有 50 个条目,类型:hash 作文件名;第一个条目:0b92f10531fd
100 条笔记:顶层 tree 有 84 个条目,类型:两位十六进制的目录(扇出);第一个条目:06
150 条笔记:顶层 tree 有 116 个条目,类型:两位十六进制的目录(扇出);第一个条目:02
200 条笔记:顶层 tree 有 145 个条目,类型:两位十六进制的目录(扇出);第一个条目:02
300 条笔记:顶层 tree 有 181 个条目,类型:两位十六进制的目录(扇出);第一个条目:01笔记不多时 tree 条目就是完整 hash;笔记增多(本例 50 到 100 条之间)git 自动改用两位十六进制的子目录来扇出,和 .git/objects 的做法相同。
$ git ls-remote /work/l66remote.git | sed -E "s/^[0-9a-f]{40}/<sha40>/"
<sha40> HEAD
<sha40> refs/heads/main
$ git notes list | wc -l | sed "s/^ */clone 之后本地的笔记数: /"
clone 之后本地的笔记数: 0
$ git fetch -q origin "refs/notes/*:refs/notes/*"; git notes list | wc -l | sed "s#^ *#显式 fetch 之后本地的笔记数: #"; git log -1 --format="%s: %N" | head -2
显式 fetch 之后本地的笔记数: 1
提交二: 这次提交通过了人工评审,评审人:小王refs/notes/* 不在默认的 fetch / push refspec 里,clone 也不带笔记。要同步需要显式地推送、抓取 refs/notes/*:refs/notes/*。