且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2022-10-15 11:54:30

简答



以下解决方案已从多个来源:


  1. filter-branch --index-filter总是与致命:坏源一起失败


  2. 用Git重命名过去



  3. 这是一个过滤分支调用,它使用索引过滤器重写提交而没有工作副本,所以它应该运行 真的很快 翁>。请注意,作为示例,我将文件 alpha / beta / foo.cpp 重命名为 alpha / beta / Foo.cpp $。
    $ b

    与任何具有破坏性的Git操作一样,强烈建议您在使用此

    git filter-branch --index-filter'
    git ls -files --stage | \
    seds: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 ),然后用新的索引替换旧的索引。



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



首先,当前索引文件的内容以一种形式输出,然后可以输入 git update-index $ c $使用 - 阶段选项:

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 with Foo

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

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



在管道 git ls-files --stage sed ,你应该得到以下内容:

  git ls-files --stage | SED S:α/β/ Foo.cpp中:α/β/ Foo.cpp中: 
100644 195ff081f7d0d37a60181de790ae1c6b9f177be8 0α/β/ Foo.cpp中
100644 0504de8997941bf10bcfb5af9a0bf472d6c061d3 0 LICENSE
100644 6293167f0eb7389b2f6f6b73e838d3a547787cbf 0 README.md
... etc ...



第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 
$ c步骤4:替换旧索引

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

  mv$ GIT_INDEX_FILE.new$ GIT_INDEX_FILE



总结



下面是整个命令,当一切都放在一起:

git filter-branch --index-filter'
git ls-files --stage | \
seds: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



文档


  1. git filter-branch


  2. git ls-files


  3. git update-index

  4. Git环境变量


I've got a git repo where some files differ in name by case only across branches.

As a simplified example, in master, there's a file alpha/beta/foo.cpp and in branch bar, there's a file alpha/beta/Foo.cpp.

The problem is that when I attempt to switch branches, git won't allow me to do it. There's an error that I don't have handy at the moment, but it basically looks like

changes to file alpha/beta/Foo.cpp would be overwritten -- aborting

even though a subsequent git status shows the working directory is clean.

Since this repo is not yet shared (it's actually a mirror of a large Perforce depot that I'm working on migrating), I see no problem with using git filter-branch to rewrite the history, but when I do so, any case-sensitive changes I make are lost.

When I use

git filter-branch -f -d /tmp/tmpfs/filter-it \
--tree-filter path/to/script \
--tag-name-filter cat --prune-empty -- --all

with the script looking like this

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

the end result winds up with rewritten refs (expected) but the files themselves are not actually renamed across both branches as I would expect.

Any suggestions?

The Short Answer

The following solution was modified from multiple sources:

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

  2. Renaming The Past With Git.

Here is a filter-branch invocation that uses an index-filter to rewrite the commits without a working copy, so it should run really fast. Note that, as an example, I'm renaming the file alpha/beta/foo.cpp to alpha/beta/Foo.cpp.

As with any potentially destructive Git operation, it is highly recommended that you make a backup clone of your repo before you use this:

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

Note that HEAD is optional, because it should be the default for filter-branch. It will rewrite all commits from the root commit to the commit pointed to by HEAD. If you want to increase the speed of the filter-branch even more, you can pass a range of commits instead of HEAD, such as

HEAD~20..HEAD

to rewrite just the last 20 commits. The beginning of the range is exclusive, i.e. it's not rewritten, only its children are, and the ending HEAD is again optional, since its a default.

Verification

It's a good idea to do some quick sanity-checks to verify that the filter-branch did what you expected it to do. First, compare the current history with the previous history:

git diff --name-status refs/original/refs/heads/master
D       foo.cpp
A       Foo.cpp

Notice that when the previous history is compared relative to the current one, the current history no longer has foo.cpp (it's deleted), while Foo.cpp was added to it.

Now confirm that foo.cpp contains the exact same content as Foo.cpp:

git diff refs/original/refs/heads/master:foo.cpp Foo.cpp

The output should be empty, meaning that there are no differences between the two versions.

Detailed Explanation

The following breakdown is also available in more detail from the blog post "Renaming The Past With Git". I am summarizing it here. The basic idea of the script is to create a new index file that contains the new name for the file foo (i.e. foo becomes Foo), and then replace the old index with the new one.

Step 1: Get the Index File Contents

First, the current index file contents are output in a form that can then be fed into git update-index, using the --stage option:

git ls-files --stage
100644 195ff081f7d0d37a60181de790ae1c6b9f177be8 0       alpha/beta/foo.cpp
100644 0504de8997941bf10bcfb5af9a0bf472d6c061d3 0       LICENSE
100644 6293167f0eb7389b2f6f6b73e838d3a547787cbf 0       README.md
...etc...

Step 2: Rename the File

Since we want to rename foo.cpp to Foo.cpp, we use sed with a regular expression to replace the string foo with Foo:

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

In the above command, I'm using a colon : to delimit the regexes in the sed command, but you can use other characters as delimiters too, such as pipe |. I chose a colon instead of the more standard forward-slash / as a delimeter so that it wasn't necessary to escape the forward-slashes used in the file paths.

After piping git ls-files --stage through sed, you should get the following:

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

Step 3: Create a New Index with the Renamed File

Now the modified output of git ls-files --stage can be piped into git update-index --index-info to rename the file in the index. Because we want to create an entirely new index to replace the old one, some environment variables for the path to the index file need to be set first, before invoking the git update-index command:

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

Step 4: Replace the Old Index

Now we just replace the old index with the new one, which effectively "renames" the file:

mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"

Summary

Here's the whole command again, when everything is put together:

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

Documentation

  1. git filter-branch.

  2. git ls-files.

  3. git update-index.

  4. Git environment variables.