在我的功能分支中,我有大约50个提交。在第一次提交中,我创建了一个在后续提交中经过多次修改的文件。我现在意识到将该文件存储在不同的目录中会更好,所以我想回到第一个提交,然后在正确的位置创建它,以保持历史记录清洁。我可以通过编辑第一个提交并移动文件来使用交互式rebase执行此操作,但是接触该文件的所有后续提交都将产生我必须手动解决的冲突。有没有办法告诉每个提交文件已被移动,所以他们只是在正确的地方自动应用他们的更改?
答案 0 :(得分:2)
使用git filter-branch
。您可以使用--index-filter
来提高速度,但这很难使用;只使用50-ish提交,使用--tree-filter
,它更慢但更容易使用:
git filter-branch --tree-filter <fill this in> --tag-name-filter cat -- --all
您通常应该在原始存储库的副本(克隆)上执行此操作,因为它很容易搞定过滤器分支和恢复的简单方法从那里删除副本并重新开始。
一旦有效,请删除the git filter-branch
documentation中所述的所有refs/original/
个名称。存储库最终将消失(过滤器分支将暂时大小加倍)。
历史,在Git中,是(是?)提交。要更改历史记录,您需要将旧提交(提供旧历史记录)复制到新的不同提交(提供新历史记录)。因此,您的目标是将所有50-ish提交替换为除以外的新提交文件重定位到其他路径。
正如您所提到的,可以使用交互式rebase执行此操作,但它很痛苦:rebase通过将每个提交到副本转换为更改集(通过将该提交与其更改)进行比较来实现parent,查看更改的内容,然后将相同的更改应用于某些现有提交。
有一个相当重的命令git filter-branch
,其目的是在应用某种commit-modifier时复制提交。它有很多选择,因为它本身非常慢;但从根本上说,它包括:
然后,从最根(最老/最祖先)的提交开始:
最后,在对每个要过滤的提交执行上述操作之后,循环遍历所有引用,告诉它更改(主要是分支名称,但如果使用的话,标记名称也是如此) --tag-name-filter
):
refs/whatever
重命名为refs/original/refs/whatever
。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-ish系统)。