为什么 no-op filter-branch 会产生分歧,我该如何解决?
Why does a no-op filter-branch create divergence, and how do I fix that?
我有一种情况,我将几年的提交合并到一个存储库中。其中一个提交有一条评论,它是与修复相关的 Address Sanitizer 日志的粘贴。
这听起来还不错,只是地址 Sanitizer 日志看起来像这样:
==10856==ERROR: AddressSanitizer: heap-buffer-overflow on address
0x62a00000b201 at pc 0x47df61 bp 0x7fffffff2ca0 sp 0x7fffffff2c98
READ of size 1 at 0x62a00000b201 thread T0
#0 0x47df60 in Expand_Series ../src/core/m-series.c:145
#1 0x47e5a7 in Extend_Series ../src/core/m-series.c:187
#2 0x466e0c in Scan_Quote ../src/core/l-scan.c:462
#3 0x46a797 in Scan_Token ../src/core/l-scan.c:918
#4 0x46e263 in Scan_Block ../src/core/l-scan.c:1188
...
在这种情况下,它上升到 #250 左右。 GitHub 扫描 #XXX 模式,如果它们与问题编号匹配,请在引用的问题上注明提及。所以突然 GitHub 认为这个提交是对每个问题和拉取请求的评论,并且会这样做一段时间。
我想我只需要使用 git filter-branch
因为我真的不介意打破历史记录 (我已经做了一个过滤器分支来摆脱一些东西我不想)。但是,在我进行合并并继续工作之前,我做了另一个过滤器分支。既然我已经注意到这个在 GitHub 中弹出,我想回去重写它并且不介意每个分支上的每个提交 在 之后得到一个新的哈希值。我没问题。
我开始重写了,但我不明白为什么会有这么多分歧。在我对评论进行任何更改之前,它似乎已经完成了影响事情的重写。作为一个简单的测试,我尝试了我认为应该是空操作的东西:
git filter-branch -f --msg-filter 'sed "s/a/a/g"' -- --all
我不是 sed 人,但我的理解是,这将重做所有提交消息并将 a
替换为 a
。 (Ayn Rand 会很高兴。)
它并没有像我的实际替代者那么多的提交发散...600 而不是 1000。但是它完全发散表明我在这里有某种误解。我怎样才能重写 that commit message in the history 而不会损坏除了它之后发生的提交之外的任何提交...并在所有分支上产生影响?
如果现有消息 不是 以换行符结尾,sed
将添加一个(至少某些版本的 sed,包括我测试过的那个这里):
$ printf 'foo\nbar'
foo
bar$ printf 'foo\nbar' | sed 's/a/a/'
foo
bar
$
这意味着您的测试邮件过滤器可能更改了邮件。根据您的结果,我猜测至少有一个提交,从某些分支提示返回的大约 600 个提交,是以这种方式修改的。 (我以前亲眼见过这个问题。)
(另一种可能性是某种 Unicode 规范化,尽管我还没有看到 sed
会发生这种情况。)
假设是这种情况,您的诀窍就是找到一个不影响其他提交的命令。一个好的方法是使用环境变量 $GIT_COMMIT
来识别要触摸的提交,并确保你做的事情是真正的空操作(cat
msg-filter 可能比sed
,例如)所有其他提交:
... --msg-filter 'if [ $GIT_COMMIT == <the one> ]; then fix_msg; else cat; fi' ...
至于对所有分支产生影响,您的-- --all
应该已经做到了。
听起来您已经知道为什么剩余的提交会获得新的 SHA-1,但为了完整起见,我也会将其包括在内。你可以跳过这部分,这是给其他人看问题的。
如果提交被修改,它会得到一个新的 SHA-1(根据定义,因为 SHA-1 是提交内容的校验和)。到目前为止没什么大不了的,但是假设只有五个提交(在这种情况下全部在 master 上,这无关紧要)我们将使用 filter-branch 过滤器修改中间的一个:
A <- B <- C <- D <- E [original]
假设 C
的实际 SHA-1 以 30001
开头)。现在让我们在过滤器分支操作的中间构建一个部分结果:
A <- B <- C'
比方说,由于某种奇怪的巧合,新的 SHA-1 以 30002
、提交 3 的版本 2 开头。
让我们看一下(部分)原始提交 D
:
$ git cat-file -p HEAD^
tree 954019cba5244a4a135ff62258660b3d2e3a8087
parent 30001...
提交 D
按编号表示提交 C
。所以 filter-branch
,虽然它没有改变任何关于 D
的 else,但必须构建一个新的提交 D'
,它说 parent 30002...
:
A <- B <- C' <- D'
同样,filter-branch
被迫将旧提交 E
复制到新 E'
:
A <- B <- C' <- D' <- E' [replacement]
因此,任何更改某些提交的 filter-branch
也会更改所有后续提交。 (对于 git rebase
也是如此。事实上,git rebase
和 git filter-branch
是表兄弟。两者都只是读取现有提交,应用一些更改,并将结果写为新提交;filter-branch 以编程方式完成这一切——即没有 --interactive
模式——并且有一组非常广泛和复杂的规范来进行更改,然后可以将其应用于多个分支,而不是一个分支.)
还有一个地方可能是罪魁祸首(在我的例子中)。考虑:
$ git cat-file -p 20b9cd59c6c6a1a2bccfb2ddb9af68c083a28698
tree dee80bcd856b23aceb8946473bf64d9aef0fe629
parent b12dc8b9388dc0a2ae34563426043a612d296195
author XXX <xxx@example.com> 1355477802 +0200
committer XXX <xxx@example.com> 1355478447 +0200
encoding cp1251
Add (literally) three characters to one file that will
inadvertently create hours of fun for people years later.
这是编码,在本例中为 Windows 1251。
找到它的人是这样总结的:
msg-filter gets the raw message, no encoding meta-information.
So even when you use an 8-bit transparent msg-filter (such as a
plain cat), the re-created commit won't contain that encoding
meta-information.
(That's slightly imprecise, because the filter gets the encoding
information, it could read it via the GIT_COMMIT env variable.
It's the output, that doesn't control encoding. At least I don't
know how...)
他使用 Graft Points 修复了我们特定情况下的一般混乱情况。这超出了我目前的 git 知识范围,因此我不会尝试解释它。
我有一种情况,我将几年的提交合并到一个存储库中。其中一个提交有一条评论,它是与修复相关的 Address Sanitizer 日志的粘贴。
这听起来还不错,只是地址 Sanitizer 日志看起来像这样:
==10856==ERROR: AddressSanitizer: heap-buffer-overflow on address
0x62a00000b201 at pc 0x47df61 bp 0x7fffffff2ca0 sp 0x7fffffff2c98
READ of size 1 at 0x62a00000b201 thread T0
#0 0x47df60 in Expand_Series ../src/core/m-series.c:145
#1 0x47e5a7 in Extend_Series ../src/core/m-series.c:187
#2 0x466e0c in Scan_Quote ../src/core/l-scan.c:462
#3 0x46a797 in Scan_Token ../src/core/l-scan.c:918
#4 0x46e263 in Scan_Block ../src/core/l-scan.c:1188
...
在这种情况下,它上升到 #250 左右。 GitHub 扫描 #XXX 模式,如果它们与问题编号匹配,请在引用的问题上注明提及。所以突然 GitHub 认为这个提交是对每个问题和拉取请求的评论,并且会这样做一段时间。
我想我只需要使用 git filter-branch
因为我真的不介意打破历史记录 (我已经做了一个过滤器分支来摆脱一些东西我不想)。但是,在我进行合并并继续工作之前,我做了另一个过滤器分支。既然我已经注意到这个在 GitHub 中弹出,我想回去重写它并且不介意每个分支上的每个提交 在 之后得到一个新的哈希值。我没问题。
我开始重写了,但我不明白为什么会有这么多分歧。在我对评论进行任何更改之前,它似乎已经完成了影响事情的重写。作为一个简单的测试,我尝试了我认为应该是空操作的东西:
git filter-branch -f --msg-filter 'sed "s/a/a/g"' -- --all
我不是 sed 人,但我的理解是,这将重做所有提交消息并将 a
替换为 a
。 (Ayn Rand 会很高兴。)
它并没有像我的实际替代者那么多的提交发散...600 而不是 1000。但是它完全发散表明我在这里有某种误解。我怎样才能重写 that commit message in the history 而不会损坏除了它之后发生的提交之外的任何提交...并在所有分支上产生影响?
如果现有消息 不是 以换行符结尾,sed
将添加一个(至少某些版本的 sed,包括我测试过的那个这里):
$ printf 'foo\nbar'
foo
bar$ printf 'foo\nbar' | sed 's/a/a/'
foo
bar
$
这意味着您的测试邮件过滤器可能更改了邮件。根据您的结果,我猜测至少有一个提交,从某些分支提示返回的大约 600 个提交,是以这种方式修改的。 (我以前亲眼见过这个问题。)
(另一种可能性是某种 Unicode 规范化,尽管我还没有看到 sed
会发生这种情况。)
假设是这种情况,您的诀窍就是找到一个不影响其他提交的命令。一个好的方法是使用环境变量 $GIT_COMMIT
来识别要触摸的提交,并确保你做的事情是真正的空操作(cat
msg-filter 可能比sed
,例如)所有其他提交:
... --msg-filter 'if [ $GIT_COMMIT == <the one> ]; then fix_msg; else cat; fi' ...
至于对所有分支产生影响,您的-- --all
应该已经做到了。
听起来您已经知道为什么剩余的提交会获得新的 SHA-1,但为了完整起见,我也会将其包括在内。你可以跳过这部分,这是给其他人看问题的。
如果提交被修改,它会得到一个新的 SHA-1(根据定义,因为 SHA-1 是提交内容的校验和)。到目前为止没什么大不了的,但是假设只有五个提交(在这种情况下全部在 master 上,这无关紧要)我们将使用 filter-branch 过滤器修改中间的一个:
A <- B <- C <- D <- E [original]
假设 C
的实际 SHA-1 以 30001
开头)。现在让我们在过滤器分支操作的中间构建一个部分结果:
A <- B <- C'
比方说,由于某种奇怪的巧合,新的 SHA-1 以 30002
、提交 3 的版本 2 开头。
让我们看一下(部分)原始提交 D
:
$ git cat-file -p HEAD^
tree 954019cba5244a4a135ff62258660b3d2e3a8087
parent 30001...
提交 D
按编号表示提交 C
。所以 filter-branch
,虽然它没有改变任何关于 D
的 else,但必须构建一个新的提交 D'
,它说 parent 30002...
:
A <- B <- C' <- D'
同样,filter-branch
被迫将旧提交 E
复制到新 E'
:
A <- B <- C' <- D' <- E' [replacement]
因此,任何更改某些提交的 filter-branch
也会更改所有后续提交。 (对于 git rebase
也是如此。事实上,git rebase
和 git filter-branch
是表兄弟。两者都只是读取现有提交,应用一些更改,并将结果写为新提交;filter-branch 以编程方式完成这一切——即没有 --interactive
模式——并且有一组非常广泛和复杂的规范来进行更改,然后可以将其应用于多个分支,而不是一个分支.)
还有一个地方可能是罪魁祸首(在我的例子中)。考虑:
$ git cat-file -p 20b9cd59c6c6a1a2bccfb2ddb9af68c083a28698
tree dee80bcd856b23aceb8946473bf64d9aef0fe629
parent b12dc8b9388dc0a2ae34563426043a612d296195
author XXX <xxx@example.com> 1355477802 +0200
committer XXX <xxx@example.com> 1355478447 +0200
encoding cp1251
Add (literally) three characters to one file that will
inadvertently create hours of fun for people years later.
这是编码,在本例中为 Windows 1251。 找到它的人是这样总结的:
msg-filter gets the raw message, no encoding meta-information. So even when you use an 8-bit transparent msg-filter (such as a plain cat), the re-created commit won't contain that encoding meta-information.
(That's slightly imprecise, because the filter gets the encoding information, it could read it via the GIT_COMMIT env variable. It's the output, that doesn't control encoding. At least I don't know how...)
他使用 Graft Points 修复了我们特定情况下的一般混乱情况。这超出了我目前的 git 知识范围,因此我不会尝试解释它。