使用git filter-branch通过提交信息删除提交记录

13

简化版:

如果我有一个名为“foo-555”的分支,其中包含以下提交消息:

  • foo 555: blah
  • foo 123: blah blah
  • foo 555: blah blah blah
  • foo 321: blahblah

并且我想删除所有不以“foo 555:”开头的提交,是否可以使用git filter-branch(或任何其他工具)来完成?

详细版:

在我们的代码库中,每个提交消息都以某种模式开头:

Redmine #555: SOME_MESSAGE

我们还会经常进行一些变基操作,将可能的发布分支更改合并到特定问题分支。换句话说,我可能有一个名为“foo-555”的分支,但在合并到“预发布”分支之前,我需要获取“预发布”中存在而“foo-555”不存在的任何提交(以便“foo-555”可以快进合并到“预发布”)。

然而,由于“预发布”有时会发生变化,因此有时会出现这样的情况:您引入了来自“预发布”的提交,但稍后该提交会从“预发布”中删除。很容易识别出来来自“预发布”的提交,因为它们的提交消息中的号码不会与分支号码匹配。例如,如果我在“foo-555”分支中看到“Redmine #123: …”,那么我知道这不是我的分支的提交。

现在的问题是:我想删除所有“不属于”分支的提交;换句话说,任何具有以下特征的提交:

  • 在我的“foo-555”分支中,但不在“预发布”分支中(pre-release..foo-555)
  • 其提交消息不以“Redmine #555”开头
当然,“555”会因分支而异。有没有办法使用filter-branch(或其他工具)来实现这一点?目前我所能想到的唯一方法是进行交互式变基("git rebase -i"),手动删除所有“不良”提交。

你能不能将你想要的提交挑选到相关分支中,而不是选择性地挑选? - Jonathan Leffler
我们可以,但是假设我有10,555个提交和10个其他提交;我必须重置然后进行10次cherry picks(与一个filter-branch命令相比...如果这样的事情可能的话)。 - machineghost
3个回答

11

下面是一种使用 filter-branch 而非变基的快速解决方案。无需交互或解决冲突。

git filter-branch --commit-filter '
    if [ `git rev-list --all --grep "<log-pattern>" | grep -c "$GIT_COMMIT"` -gt 0 ]
    then
        skip_commit "$@";
    else
        git commit-tree "$@";
    fi'  HEAD

你可能想要使用以下代码来进行清理:

git reflog expire --expire=now
git gc --prune=now

3
对于有大量提交记录的仓库,这可能需要一些时间(例如,在具有数十万次提交的FreeBSD -CURRENT上每个提交筛选器需要几秒钟)。 用于“git rev-list --all [...]”条件的一个更快的替代方法是“git show $GIT_COMMIT | grep -c"<log-pattern>"”。 - trombonehero
这并没有真正解决所述的问题。git filter-branch 中的 skip_commit 实际上是将提交 压缩 掉,而不是删除它们的更改。要删除它们,您需要使用 git rebase -i 而不是 git filter-branch - Robin Green

5
编写一个脚本以删除带有Redmine #555的行:
#!/bin/sh

mv $1 $1.$$
grep -v 'Redmine #555' < $1.$$ > $1
rm -f $1.$$

当然,你可以按照自己的方式进行操作(例如将命令脚本输出到ed中进行编辑)。
然后,通过将EDITOR设置为你的脚本,启动你的变基过程:
EDITOR=/path/to/script git rebase -i REVISION

当然,仍无法保证完全完成——在重新设置时可能会出现由于遗漏修订版本而导致的错误。您仍然可以手动修复这些错误并使用git rebase --continue继续进行。


这似乎可以工作,但是...没有办法只使用Git(没有shell脚本),并通过“git filter-branch --commit-filter”自动化该过程吗? - machineghost
1
--commit-filter 的帮助文档特别提到 skip_commit 只是跳过了提交,但并没有跳过更改,建议使用 git-rebase 代替。filter-branch 将您的修订版本视为状态序列,并允许您对每个提交进行排列,但更改不会传播到子级。rebase 将您的修订版本视为补丁堆栈,并且任何中途的修改会传播到未来的修订版本。但这可能会导致失败,这就是为什么它不能完全自动化的原因。 - Ben Jackson

0

基于 Gingi 的答案,但简化了 if 语句

git filter-branch --commit-filter '
if [[ $(git show -s --format=%B "$GIT_COMMIT") == "fix" ]]
then
    skip_commit "$@";
else
    git commit-tree "$@";
fi'  master

请注意,在重写历史记录后,您应该删除所有本地和远程标签,否则您将会有悬挂的分支。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接