filter-repo 使用

从 filter-branch 转换

本文档面向熟悉 filter-branch 并希望学习如何转换到使用 filter-repo 的人。

目录

基本差异

使用 git filter-branch 时,你有一个 git 仓库,其中每个提交(在你指定的分支或修订版本内)都会被检出,然后你运行一个或多个 shell 命令来将工作副本转换为你想要的最终状态。

使用 git filter-repo 时,你实际上获得了一个编辑工具,可以操作仓库的 fast-export 序列化。这意味着有一个包含仓库所有内容的输入流,你通常不是以要运行的命令形式指定过滤器,而是使用许多常见的预定义过滤器,这些过滤器提供各种方式来基于仓库的组件(如路径名、文件内容、用户名或电子邮件等)对仓库进行切片、切块或修改。这使得常见操作更容易,即使它不如 shell 回调那么灵活。对于需要更复杂或特殊处理的情况,filter-repo 提供了 Python 回调,可以对从 fast-export 流中填充的数据结构进行操作,几乎可以做任何你想做的事。

filter-branch 默认在仓库的一个子集上工作,并要求你指定一个或多个分支,这意味着你需要指定 -- --all 来修改所有提交。相比之下,filter-repo 默认重写所有内容,如果你想限制到某个特定的分支集或提交范围,你需要指定 --refs <rev-list-args>。(但是,以连字符开头的任何 <rev-list-args> 都不被 filter-repo 接受,因为它们看起来像是不同选项的开始。)

filter-repo 还自动处理额外的问题,比如重写旧commit ID 的提交消息,使其引用重写后的commit ID,删除由于指定的过滤器而变为空的提交,以及在过滤操作结束时自动缩小和 gc 仓库。

filter-branch 示例的转换

删除文件

filter-branch 手册提供了三个删除单个文件的不同示例,基于不同级别的易用性与谨慎性和性能:

git filter-branch --tree-filter 'rm filename' HEAD
  • 这个命令会检出每个提交,运行 rm filename 命令,然后重新提交。
  • 如果文件不存在,rm 命令会报错,但过滤器会继续执行。
  • 这种方法最慢,因为它需要在每个提交上实际检出文件。
git filter-branch --tree-filter 'rm -f filename' HEAD
  • 这个命令与第一个类似,但使用了 rm -f,这意味着"强制删除"。
  • 即使文件不存在,也不会报错。
  • 仍然会检出每个提交,但比第一个命令稍微健壮一些。
git filter-branch --index-filter 'git rm --cached --ignore-unmatch filename' HEAD
  • 这个命令使用 --index-filter,它只操作 Git 索引,不会检出文件。
  • git rm --cached 从 Git 索引中删除文件,但不触及工作目录。
  • --ignore-unmatch 选项使得即使文件不在某些提交中也不会报错。
    这是最快和最高效的方法,特别是对于大型仓库。

所有这些在git filter-repo都变成了

git filter-repo --invert-paths --path filename

提取子目录

通过以下方式提取子目录:

git filter-branch --subdirectory-filter foodir -- --all

这是最容易转换的命令之一;它只是变成了

git filter-repo --subdirectory-filter foodir

将整个树移动到子目录

保留所有文件但将它们放在新的子目录中:

git filter-branch --index-filter \
    'git ls-files -s | sed "s-\t\"*-&newsubdir/-" |
            GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
                    git update-index --index-info &&
     mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD

变成了

git filter-repo --to-subdirectory-filter newsubdir

删除某个作者的提交

警告:这对于 filter-branch 和 filter-repo 都是一个糟糕的例子。它并不从仓库中删除用户所做的更改,它只是删除有问题的提交,同时将其更改压缩到任何后续提交中,就好像后续作者也对这些更改负责一样。如果你在看这个例子,git rebase 可能更适合你真正想要的。(另见这个解释 rebase 和 filter-repo 之间差异的说明

这个 filter-branch 例子

git filter-branch --commit-filter '
    if [ "$GIT_AUTHOR_NAME" = "Darl McBribe" ];
    then
        skip_commit "$@";
    else
        git commit-tree "$@";
    fi' HEAD

变成了

git filter-repo --commit-callback '
    if commit.author_name == b"Darl McBribe":
        commit.skip()
    '

重写提交消息 -- 删除文本

通过以下方式从提交消息中删除 git-svn-id: 行:

git filter-branch --msg-filter '
    sed -e "/^git-svn-id:/d"
    '

变成了

git filter-repo --message-callback '
    return re.sub(b"^git-svn-id:.*\n", b"", message, flags=re.MULTILINE)
    '

重写提交消息 -- 添加文本

通过以下方式向最后十个提交添加 Acked-by 行:

git filter-branch --msg-filter '
        cat &&
        echo "Acked-by: Bugs Bunny <bunny@bugzilla.org>"
    ' master~10..master

变成了

git filter-repo --message-callback '
        return message + b"Acked-by: Bugs Bunny <bunny@bugzilla.org>\n"
    ' --refs master~10..master

更改作者/提交者(/标签者?)信息

git filter-branch --env-filter '
    if test "$GIT_AUTHOR_EMAIL" = "root@localhost"
    then
            GIT_AUTHOR_EMAIL=john@example.com
    fi
    if test "$GIT_COMMITTER_EMAIL" = "root@localhost"
    then
            GIT_COMMITTER_EMAIL=john@example.com
    fi
    ' -- --all

变成了

# 确保 '<john@example.com> <root@localhost>' 是 .mailmap 中的一行,然后:
git filter-repo --use-mailmap

git filter-repo --email-callback '
  return email if email != b"root@localhost" else b"john@example.com"
  '

(作为额外的好处,这两种 filter-repo 替代方案也会修复标签者的电子邮件,而 filter-branch 示例不会)

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

友情链接更多精彩内容