git log --all 在过滤器分支内不起作用

标签 git git-log git-filter-branch

我正在编写一个 git filter-branch --tree-filter 命令,该命令使用 git log --follow 来检查是否应在执行期间保留或删除某些文件过滤。

基本上,我想保留包含文件名的提交,即使该文件被重命名和/或移动

这是我正在运行的过滤器:

git filter-branch --prune-empty --tree-filter '~/preserve.sh' -- --all

这是我在 preserve.sh 中使用的命令:

git log --pretty=format:'%H' --name-only --follow --all -- "$f"

结果是,当我在新路径中搜索文件时,创建后来移动到另一个路径的文件的提交将从历史记录中删除,这是不应该发生的。例如:

commit 1: creates foo/hello.txt;

commit 2: moves foo/hello.txt to bar/hello.txt;

using git filter-branch passing bar/hello.txt yields a history with only commit 2.

一开始,我以为问题发生是因为我没有在git log中使用--all,即在分析commit 1<时/em> 它找不到 foo/hello.txt 因为它只在过去的历史记录中查找 bar/hello.txt 在任何地方都没有提到。但后来我添加了 --all,它会查看所有提交(包括“ future ”的提交),但是没有任何变化。

我检查了正在创建文件的提交,运行了该日志命令并且它起作用了(列出了 foo/hello.txtbar/hello.txt),所以没有什么问题。我还记录了由 filter-branch 运行时 log 命令的结果,在这种情况下,我可以看到在 commit 1 中找不到文件(仅 bar/hello.txt 已列出)。

我认为发生这个问题是因为 git 在内部将每个提交复制到“新的存储库”结构,因此当它分析提交 1 时,较新的提交尚不存在。

有没有办法解决这个问题,或者有其他方法来解决重写历史记录同时保留重命名/移动的问题?

我正在运行 this answer 中找到的脚本的修改版本.

最佳答案

本质上,您想要在这里做的是:

  1. 构建存储库中所有提交的映射,并按哈希 ID 进行索引。
  2. 对于每次提交,确定您希望在运行过滤器时保留/使用的路径名。
  3. 运行git filter-branch - 或者,此时,只需运行您自己的代码,因为您在步骤 1 中构建的 map 以及您在步骤 2 中计算的内容是重要部分filter-branch 的作用——将旧提交复制到新提交。
  4. 如果您使用自己的代码,请为最后复制的提交创建或更新分支名称。

你可以git read-tree将每次提交复制到一个索引中——可以使用主索引,也可以使用临时索引——然后使用Git工具修改索引,以便安排其中包含您希望保留的名称和哈希 ID。然后使用 git write-tree 和 git commit-tree 构建新的提交,就像 filter-branch 一样。

更简单的情况

如果您没有太多的文件替代名称,您也许可以稍微简化一下。例如,假设存储库中的历史记录(提交链)如下所示,有两个巨大的历史瓶颈 B1B2:

  _______________________          ________________          _________
 /                       \        /                \        /         \--bra
< large cloud of commits  >--B1--< cloud of commits >--B2--<    ...    >--nch
 \_______________________/        \________________/        \_________/--es

您想要保留的文件名在三个大气泡中的任何一个内都相同,但在提交B2时进行了大规模重命名,因此中间气泡中的名称不同,同样在 B1 处进行了大规模重命名,因此第一个气泡中的名称不同。

在这种情况下,您可以在任何过滤器(树过滤器、索引过滤器等任何您喜欢的过滤器(但索引过滤器比树过滤器快得多))中执行清晰的历史测试,以确定要保留的文件名。请记住,过滤器分支正在按拓扑顺序逐一复制提交,以便在创建任何新复制的子级之前创建新复制的父级。也就是说,它首先处理第一组的提交,然后复制瓶颈提交 B1,然后处理第二组的提交,依此类推。

正在复制的提交的哈希 ID 可用于您的过滤器(无论您使用哪个过滤器):它是 $GIT_COMMIT。所以你只需要测试:

  • $GIT_COMMITB1 的祖先吗?如果是这样,您就属于第一组。
  • $GIT_COMMITB2 的祖先吗?如果是这样,您就属于第一组或第二组

因此,由“保留名称集中的名称”组成的索引过滤器可以写为:

if git merge-base --is-ancestor $GIT_COMMIT <hash of B1>; then
    set_of_names=/tmp/list1
elif git merge-base --is-ancestor $GIT_COMMIT <hash of B2>; then
    set_of_names=/tmp/list2
else
    set_of_names=/tmp/list3
fi
...

其中文件 /tmp/list1/tmp/list2/tmp/list3 包含要保留的文件的名称。您现在只需编写 ... 代码来实现“在索引过滤操作期间保留固定的文件名集”。这实际上已经完成了,无论如何,在 this answer 中。至 extract multiple directories using git-filter-branch (正如您今天早些时候发现的那样)。

关于git log --all 在过滤器分支内不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59385247/

相关文章:

git - 作者未映射到 bitbucket 用户

git - Git更新后无法推送

Git:如何找到提交之间的最短路径

Git:如何列出此分支上的提交但不是来自 merge 分支的提交

git - 从 master 分支部署特定功能

git - 你如何在 Git 提交中搜索制表符?

git - 混淆编写 git filter-branch 将所有文件移动到子目录

git - 如何从 git 上的 filter-branch 命令中删除重复的提交?

git - 更改 Git 历史记录中的电子邮件地址

Git push 为大型仓库挂起