如果 TL;DR:请参阅底部部分,我建议您可能真正想做的事情。
如何在git filter-branch 中测试日期
这个问题有多个部分,你必须决定你关心哪些部分。
- 所有 Git 提交都有两个时间戳:作者和提交者。
- 时间戳有两部分:Unix 样式“自 1970 年 1 月 1 日以来的秒数”和时区。
-
git filter-branch 复制提交;选择器(传递给git rev-list)选择哪些 提交被复制。为了制作副本,Git1 有效地提取原始提交,运行所有过滤器以进行所需的任何更改,然后从结果中进行新的提交。如果新提交与原始提交逐位相同,则它是原始提交,即“副本”不占用额外空间。
- 完成复制后,所有“正”引用(分支和标签名称)都会映射到副本。
最后一个重新映射步骤就是为什么在 git filter-branch 完成后,git log 会显示新的(修改的)副本而不是原始的(未修改的)提交。
1大量的git filter-branch 只是为了避免以缓慢而愚蠢的“提取、修改、重建”方式执行此操作的代码,因为这非常慢.聪明一点可以加快速度,这样,一个 filter-branch 命令可能只需要 10 分钟就可以完成,而不是 24 小时。但是要了解您在做什么,您可以使用这个更简单的思维模型:您实际上是将整个存储库重新复制到一个新的存储库,其中新的提交可能具有不同的哈希 ID 号。
难点:时间戳
在许多地方,Git 允许您编写简单的表达式,例如 2017-03-31。过滤器分支不是这些地方之一。而且,每个过滤器都是一点点shell脚本。 shell内置了一些基本的测试:例如test $num1 -ge $num2,会测试$num1的扩展数值是否大于或等于$num2的扩展值。
当您过滤提交时,Git 已将作者和提交者日期放入环境变量 GIT_AUTHOR_DATE 和 GIT_COMMITTER_DATE。但是,这些中的内容正是提交本身中的内容。让我们看一个示例提交:
$ git cat-file -p HEAD | sed 's/@/ /'
tree 52cbd85298ed8ee95776f93ddb0eede02a36c539
parent 71e38d40e93aa1357dad53f1599c2a1c00b54dc5
author Junio C Hamano <gitster pobox.com> 1491895625 -0700
committer Junio C Hamano <gitster pobox.com> 1491895625 -0700
Eleventh batch for 2.13
Signed-off-by: Junio C Hamano <gitster pobox.com>
因此,在这种情况下,GIT_AUTHOR_DATE 将包含 1491895625 -0700,并且 `GIT_COMMITTER_DATE 是相同的。为了比较,这里有一个来自不同提交的 sn-p:
author Hiroshi Shirosaki <h.shirosaki gmail.com> 1488779947 +0900
committer Eric Wong <e 80x24.org> 1488922143 +0000
如果您决定使用时间戳,则必须弄清楚如何将您关心的时间戳与相当于“2017-03-31”的时间戳进行比较。如果我们忽略时间 zone 信息,我们可以选择像1491000000 这样的数字并将其转换为当地时间:
$ date -r 1491000000
Fri Mar 31 15:40:00 PDT 2017
因此,如果使用太平洋夏令时间下午 3:40 对您有用,那么该数字将有效。然后我们可以将GIT_AUTHOR_DATE 和/或GIT_COMMITTER_DATE 与1491000000 进行比较,尽管我们仍然需要剪掉讨厌的时区偏移:
date_exceeds_1491000000() {
test $1 -ge 1491000000
}
现在我们可以写了:
if date_exceeds_1491000000 $GIT_AUTHOR_DATE
并让 shell 的参数拆分处理事情($1 将是空白之前的部分,$2 持有区域)。
由您决定如何将上面的示例与您的环境过滤器结合起来;这里最关键的是您是否要根据 作者 日期、提交者 日期或两者的某种组合进行过滤。
一种更简单的方法,但有一些注意事项:不使用时间戳
另一方面,您可以选择 not 直接在过滤器分支代码中测试时间戳。相反,您可以让git rev-list 说明符选择时间戳。这让你可以使用--since,确实让你写出方便的“2017-03-31”风格日期:
git filter-branch ... --since=2017-03-31 ...
这要简单得多,并且优点是甚至不必费心复制时间戳之前的提交。但是,它有缺陷。这些可能不是致命的,但您应该考虑它们。
首先,您不再控制使用GIT_AUTHOR_DATE 和GIT_COMMITTER_DATE 中的哪一个。 Git 使用提交者时间戳。如果那是你想要的,那太好了! :-)
其次,如果提交的日期以“倾斜”顺序排列,则会出现问题。考虑以下分支:
...--E--F--G--H--...
每个大写字母代表一个提交。假设E 的提交日期是昨天,F 是两周前,G 是今天,H 是一周前。换句话说,提交E,这四个中的第一个发生在昨天;然后,在此之前的两周,提交 F 发生在提交 E 之后。
这似乎是不可能的——而且并不常见。但它确实会发生,特别是如果提交是在不同的计算机上完成的,并且其中一台的日期错误。如果我现在提交某些内容,然后将日期设置回两周,然后提交其他内容,则较晚的提交具有较早的时间戳。
在这种情况下,git rev-list 可能2跳过一些提交,在选定的提交中留下“漏洞”。当馈送到git filter-branch 时,这将导致复制也跳过这些提交。如果--since 的截止日期是几天前,我们将复制E 和G,但不复制F 和H,这样新的链就变成了:
...--E'--G'--...
其中E' 是E 的新副本,G' 是G 的新副本。
如果您的提交时间戳都按正确的顺序排列,这实际上不会发生,一切都很好:您可以使用--since 来限制要复制的提交。
2Git 有一些优化,会根据提交的时间戳尝试停止运行。我已经以“有趣”的方式看到了这些削减提交,这些方式可能会影响到这一点。我认为这些优化有些破坏——Git 可能应该扫描整个存储库并设置一个配置标志,例如,core.trust-internal-timestamps,如果提交时间戳正确,则设置为 true,如果不正确,则设置为 false,初始值为unset 意思是“需要根据结果进行扫描和设置”。初始克隆可以扫描并设置值,添加新提交(例如git fetch 和git commit)可以检查是否将设置值从true 调整为false,如果传入的提交违反时间戳规则。
也不清楚时区偏移应该如何工作。在内部,Git 只是对数值执行strtoul,忽略时区。见function parse_commit_date in commit.c。
总结,加上最简单的方法
您可以通过将2017-03-31 转换为数字并使用test 命令(这也是您已经用来比较作者姓名和电子邮件的[ 命令)来显式测试日期;或者您可以使用--since 来限制您复制的提交集。使用哪个,--since 是否足够,取决于您。
最后一种方法更简单,但不是您所要求的。你还是应该考虑的。只需运行git log --topo-order(或使用--all --decorate --oneline --graph,您可以并且应该记住它是一只狗:all、decorate、o neline, graph;请注意 --graph 暗示 --topo-order) 并扫描结果,直到您看到一些停止过滤的好地方。记下这些提交哈希,然后将它们作为否定引用添加到您的修订选择器:
git filter-branch --env-filter ...your-filter-here... \
--tag-name-filter cat -- --branches --tags \
^123456789abcdef ^feedbeefdeadc0ffee
(这里 123456789abcdef 是其中一个提交哈希的缩写)。否定的提交散列 stop git filter-branch 来自查看该提交以及可从该提交访问的任何早期提交,因此它不会复制它们。由于您没有使用--parent-filter,因此您复制的之后 之后的任何提交都将引用原始未复制的提交。这种避免复制是基于 graph 的,因此如果日期有点损坏,则不会像 --since 那样“跳过”提交。
换句话说,您不复制的提交会一直存在并且没有改变。他们不需要时间来过滤,这使得git filter-branch 运行得更快——也许快得多,例如,将四小时的过滤工作转换为十秒的工作(这取决于你提交了多少次做复制与你不复制多少)。而且,它会注意不要在某个时间点之前“更早”地触及提交,不管它们的内部日期戳。