在我的功能分支中,我有大约 50 次提交。在第一次提交中,我创建了一个文件,该文件在后续提交中进行了大量修改。我现在意识到最好将该文件存储在不同的目录中,因此我想返回到第一次提交并在开始的正确位置创建它,以保持历史记录干净。我可以通过编辑第一个提交并移动文件来使用交互式 rebase 来完成此操作,但随后接触该文件的所有后续提交都会产生我必须手动解决的冲突。有没有办法告诉每个提交文件已被移动,以便他们自动将更改应用到正确的位置?
最佳答案
TL;DR
使用git filter-branch
。您可以使用 --index-filter
来提高速度,但这比较难使用;如果只有 50 次提交,请使用 --tree-filter
,它速度慢得多,但更容易使用:
git filter-branch --tree-filter <fill this in> --tag-name-filter cat -- --all
您通常应该在原始存储库的副本(克隆)上执行此操作,因为很容易弄乱过滤器分支以及从中恢复恢复的简单方法就是删除副本并重新开始。
一旦生效,请按照 the git filter-branch
documentation 中所述删除所有 refs/original/
名称。 。存储库最终将消肿(过滤器分支的大小将暂时增加一倍)。
长
Git 中的历史记录是(是?)提交。要更改历史记录,您需要将旧提交(提供旧历史记录)复制到新的不同提交(提供新历史记录)。因此,您的目标是将所有 50 次左右的提交替换为相同的新提交,除了文件被重新定位到其他路径。
正如您所提到的,您可以通过交互式 rebase 来做到这一点,但这很痛苦: rebase 的工作原理是将每个提交到副本转换为变更集(通过将该提交与其父级进行比较,以查看发生了什么变化),然后将相同的更改应用于某些现有提交。
有一个相当重的命令,git filter-branch
,其目的是在应用某种提交修饰符时复制提交。它有很多选择,因为它本身就非常慢;但从根本上来说,它包括:
- 列出要操作的每个提交(按哈希 ID)。就您而言,这只是“每次提交”。另外,创建一个旧哈希 ID → 新哈希 ID 的空映射。
然后,从最根(最旧/最祖先)提交开始:
- 将提交提取到临时工作区域。
- 应用各个过滤器。
- 根据结果构建新的提交。使用 HashMap 来映射父 ID,以便新提交指向先前复制的新提交。这为命令提供了新提交的哈希 ID。
- 从旧提交哈希→新提交哈希向映射添加条目。
最后,在对每个要过滤的提交执行上述操作后,循环遍历您告诉它更改的所有引用(主要是分支名称,但如果您使用
--标签名称过滤器
):- 将原始引用从
refs/whatever
重命名为refs/original/refs/whatever
。 - 使用 map 中找到的新哈希创建新的
refs/whatever
。
- 将原始引用从
在此过程结束时,您将拥有所有原始提交(使用 refs/original 来引用它们)以及所有新提交(使用分支名称)。
如果您只有一个分支名称(并且没有标签),则您需要提供的唯一名称就是这个分支名称,可能是 master
,但是 --all
会告诉 Git 查看所有引用,并且 --tag-name-filter cat
会告诉 Git 在更新标签名称时应该对标签名称进行的更改最终是不进行任何更改。
--tree-filter
指示 git filter-branch
对于步骤 1(提取提交),它应该执行完整且完整的提取,以临时保存git filter-branch
将自行构建的目录。 (其他过滤器选项尝试使用更快的仅提取到临时索引的技巧。)您提供给tree-filter
的一个或多个命令在此临时目录中运行,因此如果您需要做的就是重命名文件,命令:
mv old-relative-path new-relative-path
足够了(假设是 Unix/Linux 系统)。
关于git - 在 git 历史记录中移动文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50532572/