Skip to content

Instantly share code, notes, and snippets.

@zhengkai
Last active October 21, 2020 06:58
Show Gist options
  • Save zhengkai/7ef891c31e37067482f9455d998e52e4 to your computer and use it in GitHub Desktop.
Save zhengkai/7ef891c31e37067482f9455d998e52e4 to your computer and use it in GitHub Desktop.
GNU diff 和 git diff-tree

GNU diff 和 git diff-tree

这只是在学习 git 里 object id 概念过程中的一个实际例子,如果对 object id 已经很熟悉了那可能不需要看了

目前在配置文件管理上有这么一个需求。同一个目录有三个格式相同的目录,目录下的文件和格式几乎相同,根目录在 git 里,因此三个目录会产生不同的历史。需求就是,可以比较任意历史的任意目录。

这个网页工具的最终效果是这样:

我开始是分成三步来做这事

第一步,分别 cd 到三个不同目录获取列表,下行中的 $name 是三个目录名( dev / prod / stable

git log -n 50 --pretty=format:"%h %ct $name" .

第二步,根据列表生成不同的目录,例如 dc65220d-dev 或者 ffd38cd7-prod 这种目录,表明 commit id 和 name

第三步,任意两个目录之间 diff -u -r,并把这一操作最终显示在网页上

但是同事 sung1011 觉得方法不优雅,因为需要占用大量硬盘空间,又给出了改进方法,直接在 git 库里做比较,原来的第二步取消,第三步改为这么两步

第 3.1 步,根据 commit id 和 name 获取 object id

git ls-tree dc65220d 'config/dev'

返回结果类似,第三段 9e5bbaa0995dc2afc2e5d265385541e62c60bc1a 就是 object id,注意第二段的类型,tree 表示目录,如果 blob 就是单个文件

040000 tree 9e5bbaa0995dc2afc2e5d265385541e62c60bc1a	config/dev

ls-tree 分别获取这两个目录的 object id

第 3.2 步,使用 git diff-tree 比较,跟之前的 diff 很相似,只是目录名被换成 object id,例如

git diff-tree -u -r 9e5bbaa0995dc2afc2e5d265385541e62c60bc1a d2d1482c22e5a1a99f4ecfb1bb17e9a5d5d27975

回过头来再看第一步,取列表也可以改一下,使用裸仓库(git clone --bare)。

原本的操作,需要分别进入目录取 log,在裸仓库要变形一下,原本的:

cd "config/$name" && git log -n 50 --pretty=format:"%h %ct $name" .

改写为在仓库目录任意位置:

git log -n 50 --pretty=format:"%h $name %ct" -- "config/$name"

虽然 git diff-tree 看起来更优雅(不需要一堆临时目录),但实际使用效果并不够理想

  1. 在 diff 文件时细微的差异,例如有新增文件,GNU diff 里只会列一下文件名,而 diff-tree 会显示完整文件内容并且每行开头有个 + 号,但实际上从产品角度想要的还是 GNU diff 的那种。为了解决这个问题查到参数 --diff-filter=[(A|C|D|M|R|T|U|X|B)...[*]] 可以只选 M 而忽略 A|D,但看到这个参数后又引发了新的疑惑,不知道 diff-tree 的时候是否会有 R(renamed)、C(copied)等内容,因为只是想判断两个目录的绝对差异,而不是想查明上下文的历史变化。

  2. diff-tree 的时间是不稳定的,这很好理解,因为节省了空间,所以查询内容有大量的指针跳转,需要花更多时间。在我的机械硬盘上 diff-tree 花的时间大体分三档:1ms 左右,50ms 左右、2s 左右,而 diff 是稳定的 350ms 左右。虽然结果已经被缓存了,但 diff-tree 首次查询时间波动还是有点大。

  3. 这是纯粹需求的原因,不是实现的问题。diff 之后下一步就是复制选定的配置目录到某台测试机上,以及在 diff 过程中有可能需要查看某个版本的完整文件。这就需要 git cat-file 之类的,而如果是生成到很多目录的话,直接通过 Nginx 访问静态文件而不需要自定义接口了。

最终只是把仓库改成裸仓库,而没用 diff-tree,但通过这次实践,增加了对 object id 的理解,记下来也许以后别的需求会用得上。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment