.gitattributes 里的 filter 和 merge 属性可以改变 git 存取与合并文件的方式。这篇用一个脱敏密码的 clean 过滤器和一个取最大版本号的 merge driver,把 clean / smudge 何时触发、为什么要互逆、driver 收到什么参数逐个验证,再对比内置的 binary 与 union。
git add 之后,暂存区里的 blob 内容: $ git cat-file -p :app.conf host=db.example.com password=<REDACTED> port=5432 工作区里的文件内容(没变): $ cat app.conf host=db.example.com password=hunter2 port=5432 过滤器被调用: clean app.conf
filter 属性把文件交给两条命令:clean 在 add(和比较)时把工作区内容转成要存进 blob 的内容,smudge 在 checkout 时把 blob 转成工作区内容。blob 里存的是 clean 的输出。
git status(stat 没变,已过 racy 窗口): (没有调用过滤器) 改了文件后 git status: clean app.conf git diff --stat: clean app.conf rm + git checkout -- app.conf: smudge app.conf 检出之后工作区的内容: $ cat app.conf host=db.example.com password=<REDACTED> port=5432 (clean 把密码换成 <REDACTED>,smudge 只是原样输出,所以检出后真实密码没了——clean 是有损的,工作区里改动过的版本只在你手里)
stat 没变的 git status 不调用过滤器;内容变了的 status、git diff 会调用 clean;checkout 调用 smudge。
$ git hash-object app.conf; git hash-object --no-filters app.conf; echo "(第一行带过滤器,第二行不带,两者不同)" 71ed8d46a93931349875316b0dc188e70eba107b 932399015780455b072248fbc431adae8e5cc092 (第一行带过滤器,第二行不带,两者不同)
clean 有损(例如脱敏)时,smudge 无法还原,工作区被检出覆盖后真实内容就丢了;git hash-object --no-filters 可以看不经过滤的 hash,与带过滤器的 hash 不同。
$ git cat-file -p :a.txt; cat a.txt; git status --short HELLO WORLD Hello World $ cat a.txt; git status --short; echo "(检出的是 hello world;重新 clean 得到 HELLO WORLD,与暂存区一致,所以 status 干净——clean(smudge(x)) == x 才是要求)" hello world (检出的是 hello world;重新 clean 得到 HELLO WORLD,与暂存区一致,所以 status 干净——clean(smudge(x)) == x 才是要求)
好的过滤器满足 clean(smudge(x)) == x:检出得到的内容再 clean 后要与暂存区一致,git status 才是干净的。检出的内容本身可以和原来不同(大小写被改变)。
$ git merge theirs 2>&1 | head -3; cat VERSION
Auto-merging VERSION
Merge made by the 'ort' strategy.
VERSION | 2 +-
version=5
driver 的实际调用:
driver 被调用: base=version=1 ours=version=3 theirs=version=5 标记长度=7 路径=VERSION
(内置的文本合并会在这里报冲突;自定义 driver 退出 0 表示它已经把合并结果写进了 %A,git 采用)merge=名字 加 merge.名字.driver 命令:git 把基础、ours、theirs 写成三个临时文件(%O %A %B),还传冲突标记长度 %L 和路径 %P;driver 把结果写回 %A,退出 0 表示成功,非 0 表示冲突。
merge=(默认 text) → Auto-merging f.txt | CONFLICT (content): Merge conflict in f.txt | Automatic merge failed; fix conflicts and t;f.txt 内容: '共同的一行⏎<<<<<<< HEAD⏎ours 加的⏎=======⏎theirs 加的⏎>>>>>>> theirs' merge=binary → warning: Cannot merge binary files: f.txt (HEAD vs. theirs) | Auto-merging f.txt | CONFLICT (content): Merge c;f.txt 内容: '共同的一行⏎ours 加的' merge=union → Auto-merging f.txt | Merge made by the 'ort' strategy. | f.txt | 1 +;f.txt 内容: '共同的一行⏎ours 加的⏎theirs 加的'
默认文本合并冲突时写标记;merge=binary 不合并,留 ours 并标记冲突;merge=union 把两边的新增行都保留,不冲突。