为什么no-op filter-branch会产生分歧,我该如何解决?

时间:2015-07-16 19:18:54

标签: git github git-filter-branch

我有一种情况,我将几年的提交合并到一个存储库中。其中一个提交有一个注释,它是与修复程序相关的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而不损坏除了之后发生的任何提交...并在所有分支上获得效果?

2 个答案:

答案 0 :(得分:4)

如果现有消息以换行符结尾,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是提交内容的校验和)。到目前为止没什么大不了的,但是让我们说只有五个提交(在这种情况下都是主要的,不重要),我们将使用过滤器分支过滤器修改中间的一个:

A <- B <- C <- D <- E        [original]

我们假设C的实际SHA-1以30001开头。现在让我们在filter-branch操作的中间构建一个部分结果:

A <- B <- C'

让我们说,通过一些奇怪的巧合,新的SHA-1以提交3的第2版30002开头。

让我们看一下原始提交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 rebasegit filter-branch是堂兄弟。两者都只是读取现有的提交,应用一些更改,并将结果写为新提交; filter-branch以编程方式完成所有操作 - 即,没有--interactive模式 - 并且具有非常广泛和复杂的规范集以进行更改,然后可以将其应用于多个分支,而不是一个分支。)

答案 1 :(得分:2)

还有一个额外的地方可能是罪魁祸首(在我的情况下)。考虑:

$ 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获取原始消息,没有编码元信息。   即使你使用8位透明的msg-filter(例如   普通猫),重新创建的提交将不包含该编码   的元信息。

     

(这有点不精确,因为过滤器得到了编码   信息,它可以通过GIT_COMMIT env变量读取它。   它是输出,不控制编码。至少我没有   知道怎么......)

他使用Graft Points修复了我们特定情况下的一般混乱。这超出了我目前的git知识,所以我不会试图解释它。