我有一个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
脚本看起来像这样
#!/bin/bash
if [ -e alpha/beta/foo.cpp ] ; then
mv alpha/beta/foo.cpp alpha/beta/Foo.cpp
fi
最终结果是重写refs(预期),但文件本身实际上并没有像我期望的那样在两个分支上重命名。
有什么建议吗?
答案 0 :(得分:11)
以下解决方案是从多个来源修改的:
filter-branch --index-filter always failing with "fatal: bad source"
这是一个过滤器分支调用,它使用索引过滤器来重写没有工作副本的提交,因此它应该非常快地运行 。请注意,例如,我将文件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指向的提交的所有提交。如果您想进一步提高过滤器分支的速度,可以传递一系列提交而不是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
输出应为空,表示两个版本之间没有差异。
以下细分还可以从博客文章" Renaming The Past With 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...
由于我们要将foo.cpp
重命名为Foo.cpp
,我们将sed
与正则表达式一起使用foo
替换字符串Foo
:
"s:alpha/beta/foo.cpp:alpha/beta/Foo.cpp:"
在上面的命令中,我使用冒号:
来分隔sed
命令中的正则表达式,但您也可以使用其他字符作为分隔符,例如pipe {{1 }}。我选择冒号而不是更标准的正斜杠|
作为分隔符,因此不必转义文件路径中使用的正斜杠。
通过/
管道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"
答案 1 :(得分:0)
我的.profile别名基于@cupcake的答案,解决了如何扩展变量的问题。
使用示例:
mvidx src / myfile.cs src / myfolder / myfile.cs origin / develop..feature / myfeature
〜/ .profile bash配置文件
alias mvidx=rewriteIndexToMoveFile
red="\e[0;31m"
green="\e[0;32m"
rewriteIndexToMoveFile() {
if [ $# -ne 3 ] ; then
echo -e "Rewrite index to move a file in a range of commits."
echo -e "Args: <from file path> <to file path> <range of commits>"
echo -e "${green}Examples:"
echo -e "mvidx src/myproject/myfile.cs src/myproject/subfolder/myfile.cs origin/develop..feature/myfeature"
return
fi
fromFilePath=$1
toFilePath=$2
revisionRange=$3
echo -e "Renaming ${red}$fromFilePath${nc} to ${red}$toFilePath${nc}."
git filter-branch --index-filter 'git ls-files -s \
| sed "s|\t'"$fromFilePath"'|\t'"$toFilePath"'|" \
| GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info \
&& mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' \
$revisionRange
}