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

标签 git github git-filter-branch

我有一种情况,我将几年的提交 merge 到一个存储库中。其中一个提交有一条评论,该评论是与修复相关的 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 认为这个提交是对每个问题和 pull 请求的评论,并且会持续一段时间。

我想我只需要使用 git filter-branch 因为我真的不介意打破历史 (我已经做了一个 filter-branch 来摆脱一些我不想要的东西)。但是,在我进行 merge 并继续工作之前,我做了另一个过滤器分支。既然我已经注意到 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 上,这并不重要),我们将使用过滤器分支过滤器修改中间的一个:

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

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

A <- B <- C'

比方说,出于某种奇怪的巧合,新的 SHA-1 以 30002 开头,Commit 3 的版本 2。

让我们看一下(部分)原始提交 D:

$ git cat-file -p HEAD^
tree 954019cba5244a4a135ff62258660b3d2e3a8087
parent 30001...

Commit D 按编号表示提交 C。所以 filter-branch,虽然它没有改变 else 关于 D,但必须构建一个新的提交 D'父级 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 模式——并且有一组非常广泛和复杂的规范进行更改,然后可以将其应用于多个分支,而不是一个分支。)

关于git - 为什么 no-op filter-branch 会产生分歧,我该如何解决?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31462726/

相关文章:

Java + Github + Docker

git - 无法更新 : no tracked branch

git - 在工作树层次结构中向上移动 .git 文件夹

git - .gitignore 中包含的文件消失

android - 如何在本地 repo 中切换 android 版本?

git - 是否有类似于 Subversion Edge 的 GIT Web Admin

android - 如何将github源代码插入到我的项目中

git - 将 git 存储库移动到同一路径中的子目录,同时保留其所有历史记录

git - 在 `git filter-branch` 或 `git-rebase` 期间构建哈希查找表

git cherry-pick : output resulting new commit sha