将源代码文件的 Git 存储库从 ISO-8859-1 编码切换为 UTF-8 编码

Switching a Git repository from ISO-8859-1 to UTF-8 encoding for source code files

本周末我将使用 fast-export 将一个大型 Mercurial 项目转换为 Git。我已经测试了好几次了,结果很好。

我们还想将我们的源代码编码(很多带有变音符号的德语 comments/string 文字)从 ISO-8859-1 转换为 UTF-8(所有其他非 java 文件在 repo 中应该保持原样),并且 Git 迁移为我们提供了一个机会,因为无论如何每个人都需要再次克隆。但是,我没有找到好的方法。

  1. 我已经尝试了 this comment on SO. However while this seems ideal, due to the size of the repository (about 200000 commits, 18000 code files) it would take much more time than just the weekend I have. I've tried running it (in a heavily optimized version where the list of files is chunked and the sublists are converted in parallel (using GNU parallel)) 中的 git filter-tree --tree-filter ... 方法,直接从具有 72 个内核的 linux VM 上的 64GB tmpfs 卷,但仍然需要几天时间。 ..
  2. 或者,我尝试了一种简单的方法,即在任何活动分支上单独执行转换并提交更改。然而,结果并不令人满意,因为那时我几乎总是在合并或挑选转换前提交时遇到冲突。
  3. 现在我再次 运行 方法 1,但不尝试重写所有分支的完整历史记录(--all<rev-list>),而是仅重写当前活动分支可访问的所有提交branches' 并且无法通过某些过去的提交访问,这(希望)是所有当前分支的前身(branch-a branch-b branch-c --not old-tag-before-branch-a-b-c-forked-off as <rev-list>)。它仍然是 运行,但我担心我不能真正相信结果,因为这似乎是一个非常糟糕的主意。
  4. 我们可以像方法 2 一样通过正常提交切换 master 分支中的编码,但这同样会使挑选修复 from/to master 成为一场灾难。而且它会引入很多编码问题,因为开发人员在主分支和非转换分支之间切换时肯定会忘记更改他们的 IDE 设置。

所以现在,我觉得最好的解决方案就是坚持使用 ISO-8859-1。

有人有想法吗?有人提到也许 reposurgeon 基本上可以使用其 transcode 操作执行方法 1,性能比 git filter-tree --tree-filter ... 好得多,但我不知道它是如何工作的。

git filter-branch 中的树过滤器本质上很慢。它的工作原理是将每个提交提取到一个临时目录中的完整树中,让您更改每个文件,然后找出您更改的内容并从您留下的每个文件中进行新提交。

如果您通过快速导出/快速导入导出和导入, 就是转换数据的时候了:您有文件的扩展数据内存,但不是文件系统形式,然后将其写入 export/import 管道。此外,git fast-import 本身是一个 shell 脚本,因此在其中插入过滤是微不足道的,而 hg-fast-export 是一个 Python 程序,因此在其中插入过滤也是微不足道的。明显的地方是 here:只需重新编码 d.

您可以考虑使用 git filter-branch --index-filter,而不是 --tree-filter(默认值)。这个想法是 --index-filter,没有结帐步骤(即工作树在每次迭代中都没有(重新)填充)。

所以您可以考虑为 git filter-branch --index-filter 编写一个过滤器 它将使用 git ls-files——类似这样的东西:

  1. 调用 git ls-files --cached --stage 并遍历每个条目。

    只考虑那些 100644 文件模式——即普通文件。

  2. 对于每个条目 运行 类似于

    sha1=`git show ":0:$filename" \
        | iconv -f iso8859-1 -t utf-8 \
        | git hash-object -t blob -w --stdin`
    git update-index --cacheinfo "10644,$sha1,$filename" --info-only
    
  3. 冲洗,重复。

我想出的另一种方法是从 换个角度:git fast-export生成的流格式 git fast-import 使用的是纯文本¹(只需将您的出口商的 输出到 less 或其他寻呼机并自行查看)。

你可以使用你最喜欢的 PL 编写一个过滤器来解析 流,重新编码任何 data 块。流的组织方式使得 不使用 SHA-1 哈希,因此您可以随时重新编码。 我理解的唯一明显的问题是 data 块没有 有关它们将在结果中代表哪个文件的信息 提交(如果有的话),所以如果你的历史中有非文本文件,你可能 需要根据每个数据块的内容进行猜测 或者通过记住它看到的斑点让你的处理器更复杂 并在看到 commit 记录后决定对其中的哪些进行重新编码 它将文件名分配给(一些)那些 blob。


¹ 记录在 git-fast-import(1)—运行 git help fast-import.

我遇到了完全相同的问题,解决方案基于@kostix answer of using as the basis of the --index-filter option of filter-branch,但是,有一些额外的改进。

  1. 使用git diff --name-only --staged检测暂存区的内容
  2. 迭代此列表并过滤:
    1. git ls-files $filename,即不是删除的文件
    2. git show ":0:$filename" | file - --brief --mime-encoding 的结果不是 binary,也就是说,它是一个文本文件,也不是 UTF-8 编码的
  3. 对每个文件使用检测到的 MIME 编码
  4. 使用iconv转换文件
  5. 检测文件模式git ls-files $filename --stage | cut -c 1-6

这是我的 bash 函数的外观:

changeencoding() {
    for filename in `git diff --name-only --staged`; do
        # Only if file is present, i.e., filter deletions
        if [ `git ls-files $filename` ]; then
            local encoding=`git show ":0:$filename" | file - --brief --mime-encoding`
            if [ "$encoding" != "binary" -a  "$encoding" != "utf-8" ]; then
                local sha1=`git show ":0:$filename" \
                    | iconv --from-code=$encoding --to-code=utf-8 \
                    | git hash-object -t blob -w --stdin`
                local mode=`git ls-files $filename --stage | cut -c 1-6`
                git update-index --cacheinfo "$mode,$sha1,$filename" --info-only
            fi
        fi
    done
}