简短回答
以下解决方案是从多个来源修改而来的:
使用“fatal: bad source”错误的filter-branch --index-filter。
使用Git重命名过去。
这里是一个filter-branch调用,它使用索引过滤器在没有工作副本的情况下重写提交,因此应该运行非常快。请注意,作为示例,我将文件alpha/beta/foo.cpp
重命名为alpha/beta/Foo.cpp
。
与任何可能具有破坏性的Git操作一样,在使用之前强烈建议您备份存储库的克隆:
git filter-branch --index-filter '
git ls-files --stage | \
sed "s:alpha/beta/foo.cpp:alpha/beta/Foo.cpp:" | \
GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
git update-index --index-info && \
mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
' HEAD
请注意,
HEAD
是可选的,因为它应该是
filter-branch
的默认值。它将重写从根提交到指向
HEAD
的提交的所有提交。如果您想进一步提高
filter-branch
的速度,可以传递一系列提交而不是
HEAD
,例如:
HEAD~20..HEAD
重新编写最近的20个提交。范围的开头是排除在外的,也就是说,它不会被重写,只有它的子代会被重写,而结束点HEAD
是可选的,因为它是默认的。
验证
做一些快速的检查来验证过滤分支是否按照预期进行了操作是一个好主意。首先,将当前历史记录与之前的历史记录进行比较:
git diff --name-status refs/original/refs/heads/master
D foo.cpp
A Foo.cpp
请注意,相对于当前历史记录而言,之前的历史记录中不再包含
foo.cpp
(它被删除了),而是添加了
Foo.cpp
。
现在确认一下
foo.cpp
和
Foo.cpp
包含完全相同的内容:
git diff refs/original/refs/heads/master:foo.cpp Foo.cpp
输出应为空,意味着两个版本之间没有差异。
详细说明
下面的细节还可以从博客文章“使用Git重命名过去”中获取更详细的解释。我在这里进行总结。脚本的基本思想是创建一个新的索引文件,其中包含文件foo
的新名称(即foo
变成Foo
),然后用新的索引替换旧的索引。
第一步:获取索引文件内容
首先,当前的索引文件内容以可以被输入到git update-index
中的形式输出,使用--stage
选项:
git ls-files --stage
100644 195ff081f7d0d37a60181de790ae1c6b9f177be8 0 alpha/beta/foo.cpp
100644 0504de8997941bf10bcfb5af9a0bf472d6c061d3 0 LICENSE
100644 6293167f0eb7389b2f6f6b73e838d3a547787cbf 0 README.md
...etc...
步骤2:重命名文件
由于我们想要将foo.cpp
重命名为Foo.cpp
,因此我们使用带有正则表达式的sed
来替换字符串foo
为Foo
:
"s:alpha/beta/foo.cpp:alpha/beta/Foo.cpp:"
在上述命令中,我使用冒号
:
来分隔
sed
命令中的正则表达式,但您也可以使用其他字符作为分隔符,如pipe
|
。我选择使用冒号而不是更常见的正斜杠
/
作为分隔符,以便无需转义文件路径中使用的正斜杠。
通过管道将
git ls-files --stage
传递给
sed
后,您应该会得到以下结果:
git ls-files --stage | sed "s:alpha/beta/foo.cpp:alpha/beta/Foo.cpp:"
100644 195ff081f7d0d37a60181de790ae1c6b9f177be8 0 alpha/beta/Foo.cpp
100644 0504de8997941bf10bcfb5af9a0bf472d6c061d3 0 LICENSE
100644 6293167f0eb7389b2f6f6b73e838d3a547787cbf 0 README.md
...etc...
步骤三:使用重命名后的文件创建新索引
现在,修改过的git ls-files --stage
输出可以通过管道符号传递给git update-index --index-info
,以在索引中重命名该文件。因为我们想要创建一个全新的索引来替换旧的索引,所以在调用git update-index
命令之前需要设置一些环境变量,以指定索引文件的路径:
GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info
步骤四:替换旧索引
现在我们只需用新的索引替换旧的索引,这实际上就像是“重命名”了文件:
mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
摘要
当所有内容组合在一起时,以下是完整的命令:
git filter-branch --index-filter '
git ls-files --stage | \
sed "s:alpha/beta/foo.cpp:alpha/beta/Foo.cpp:" | \
GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
git update-index --index-info && \
mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
' HEAD
文档
git filter-branch
。
git ls-files
。
git update-index
。
Git环境变量。
git mv
与index-filter一起可能会更快(只要重命名有效),因为你不需要使用它来检出工作副本,而不是tree-filter。 - user456814git mv foo Foo
和index-filter应该可以解决问题。 在OS X上,有人需要找出如何使其工作。 - user456814git config core.ignorecase false
并再次尝试。现在我要睡觉了...明天早上我们就会知道这是否起了作用。 - escoutengit mv
无法与 index-filter 一起使用,因为它需要一个可操作的工作副本。这个答案据说可以解决问题,但我自己还没有尝试过。 - user456814