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

时间:2014-05-24 03:44:18

标签: 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

脚本看起来像这样

#!/bin/bash
if [ -e alpha/beta/foo.cpp ] ; then
    mv alpha/beta/foo.cpp alpha/beta/Foo.cpp
fi

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

有什么建议吗?

2 个答案:

答案 0 :(得分:11)

简答

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

  1. filter-branch --index-filter always failing with "fatal: bad source"

  2. Renaming The Past With Git

  3. 这是一个过滤器分支调用,它使用索引过滤器来重写没有工作副本的提交,因此它应该非常快地运行 。请注意,例如,我将文件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),然后用新的索引替换旧索引之一。

    第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...
    

    第2步:重命名文件

    由于我们要将foo.cpp重命名为Foo.cpp,我们将sed与正则表达式一起使用foo替换字符串Foo

    "s:alpha/beta/foo.cpp:alpha/beta/Foo.cpp:"
    

    在上面的命令中,我使用冒号:来分隔sed命令中的正则表达式,但您也可以使用其他字符作为分隔符,例如pipe {{1 }}。我选择冒号而不是更标准的正斜杠|作为分隔符,因此不必转义文件路径中使用的正斜杠。

    通过/管道git ls-files --stage后,您应该获得以下内容:

    sed

    步骤3:使用重命名文件

    创建新索引

    现在,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

    步骤4:替换旧索引

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

    GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info
    

    摘要

    当一切都放在一起时,这里再次发出整个命令:

    mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
    

    文档

    1. git filter-branch

    2. git ls-files

    3. git update-index

    4. Git environment variables

答案 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
}