使用git filter-branch更改文件名大小写

esc*_*ten 3 git version-control git-filter-branch

我有一个git repo,其中一些文件的名称只有不同的分支.

作为一个简单的例子,在master中,有一个文件alpha/beta/foo.cpp,在分支中bar,有一个file alpha/beta/Foo.cpp.

问题是,当我尝试切换分支时,git将不允许我这样做.有一个错误,我目前没有方便,但它基本上看起来像

对文件alpha/beta/Foo.cpp的更改将被覆盖 - 中止

即使后续git status显示工作目录是干净的.

由于此repo尚未共享(它实际上是我正在进行迁移的大型Perforce软件仓库的镜像),我认为使用git filter-branch重写历史记录没有任何问题,但是当我这样做时,我做了任何区分大小写的更改迷路了.

我用的时候

git filter-branch -f -d /tmp/tmpfs/filter-it \
--tree-filter path/to/script \
--tag-name-filter cat --prune-empty -- --all
Run Code Online (Sandbox Code Playgroud)

脚本看起来像这样

#!/bin/bash
if [ -e alpha/beta/foo.cpp ] ; then
    mv alpha/beta/foo.cpp alpha/beta/Foo.cpp
fi
Run Code Online (Sandbox Code Playgroud)

最终的结果是重写refs(预期),但文件本身实际上并没有像我期望的那样在两个分支上重命名.

有什么建议?

小智 11

简答

以下解决方案是从多个来源修改的:

  1. filter-branch --index-filter总是以"致命:坏的来源"失败.

  2. 用Git重命名过去.

这是一个过滤器分支调用,它使用索引过滤器来重写提交而不需要工作副本,因此它应该运行得非常快.请注意,作为示例,我将文件重命名alpha/beta/foo.cppalpha/beta/Foo.cpp.

与任何可能具有破坏性的Git操作一样,强烈建议您在使用之前制作repo的备份克隆:

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
Run Code Online (Sandbox Code Playgroud)

请注意,这HEAD是可选的,因为它应该是默认值filter-branch.它将重写从根提交到HEAD指向的提交的所有提交.如果你想进一步提高filter-branch的速度,你可以传递一系列的提交而不是HEAD,例如

HEAD~20..HEAD
Run Code Online (Sandbox Code Playgroud)

只重写最后20次提交.范围的开头是独占的,即它不被重写,只有它的子节点,并且结尾HEAD也是可选的,因为它是默认值.

验证

做一些快速的健全性检查以验证过滤器分支是否符合您的预期,这是一个好主意.首先,将当前历史记录与之前的历史进行比较:

git diff --name-status refs/original/refs/heads/master
D       foo.cpp
A       Foo.cpp
Run Code Online (Sandbox Code Playgroud)

请注意,当先前的历史记录相对于当前历史记录进行比较时,当前历史记录不再具有foo.cpp(已删除),Foo.cpp而是添加到其中.

现在确认foo.cpp包含与以下内容完全相同的内容Foo.cpp:

git diff refs/original/refs/heads/master:foo.cpp Foo.cpp
Run Code Online (Sandbox Code Playgroud)

输出应为空,表示两个版本之间没有差异.

详细说明

从博客文章" 用Git重命名过去 "中还可以更详细地了解以下细分.我在这里总结一下.该脚本的基本思想是创建一个包含文件新名称foo(即foo成为Foo)的新索引文件,然后用新索引替换旧索引.

第1步:获取索引文件内容

首先,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...
Run Code Online (Sandbox Code Playgroud)

第2步:重命名文件

既然我们要重命名foo.cppFoo.cpp,我们用sed一个正则表达式替换字符串fooFoo:

"s:alpha/beta/foo.cpp:alpha/beta/Foo.cpp:"
Run Code Online (Sandbox Code Playgroud)

在上面的命令中,我使用冒号:来分隔sed命令中的正则表达式,但您也可以使用其他字符作为分隔符,例如管道|.我选择冒号而不是更标准的正斜杠/作为分隔符,这样就不必转义文件路径中使用的正斜杠.

管道后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...
Run Code Online (Sandbox Code Playgroud)

第3步:使用重命名的文件创建新索引

现在git ls-files --stage可以通过管道输入修改后的输出git update-index --index-info来重命名索引中的文件.因为我们想要创建一个全新的索引来替换旧索引,所以在调用git update-index命令之前,需要首先设置索引文件路径的一些环境变量:

GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info
Run Code Online (Sandbox Code Playgroud)

第4步:替换旧索引

现在我们只用新的索引替换旧索引,这有效地"重命名"了文件:

mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
Run Code Online (Sandbox Code Playgroud)

摘要

当一切都放在一起时,这是整个命令:

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
Run Code Online (Sandbox Code Playgroud)

文档

  1. git filter-branch.

  2. git ls-files.

  3. git update-index.

  4. Git环境变量.