从 Git 存储库历史记录中删除所有文件,其路径在文件名中具有转义 \ ,并带有 git filter-repo

Remove all files from Git repo history with path having escape \ in filename with git filter-repo

提问人:klor 提问时间:1/18/2023 最后编辑:klor 更新时间:1/22/2023 访问量:381

问:

我有特殊的文件名,带有转义\字符,存储在 Debian 10 Linux 的 Git 存储库中。

问题:无法在 Windows 上 git checkout 文件,文件名中包含不兼容的字符。

例:

git log --all --name-only -m --pretty= '*\\*'
"systemd/system/default.target.wants/snap-git\\x2dfilter\\x2drepo-7.mount"
"systemd/system/multi-user.target.wants/snap-git\\x2dfilter\\x2drepo-7.mount"
"systemd/system/snap-git\\x2dfilter\\x2drepo-7.mount"

我在 Windows 结帐时收到以下 Git 错误:

C:\Git\bin\git.exe reset --hard "5ef1cac3a03304c35b455edf32bd1bb78060c5b9" --
error: invalid path 'systemd/system/default.target.wants/snap-git\x2dfilter\x2drepo-7.mount'
fatal: Could not reset index file to revision '5ef1cac3a03304c35b455edf32bd1bb78060c5b9'.
Done

问题重现步骤:

# Clone repository, to be executed on a safe repo:
git clone --no-local /source/repo/path/ /target/path/to/repo/clone/
# Cloning into '/target/path/to/repo/clone'...
# remote: Enumerating objects: 9534, done.
# remote: Counting objects: 100% (9534/9534), done.
# remote: Compressing objects: 100% (4776/4776), done.
# remote: Total 9534 (delta 4215), reused 8043 (delta 3136), pack-reused 0
# Receiving objects: 100% (9534/9534), 7.41 MiB | 16.78 MiB/s, done.
# Resolving deltas: 100% (4215/4215), done.

cd /target/path/to/repo/clone/

# List the files with escape \ from repo history into a list file:
git log --all --name-only -m --pretty= '*\\*' | sort -u >/opt/git_repo_files_w_escape.txt

# Remove the files with escape \ from repo history:
git filter-repo --invert-paths --paths-from-file /opt/git_repo_files_w_escape.txt
Parsed 592 commits
New history written in 0.25 seconds; now repacking/cleaning...
Repacking your repo and cleaning out old unneeded objects
HEAD is now at 71128f3 .gitignore: ADD snap-git to be ignored
Enumerating objects: 9354, done.
Counting objects: 100% (9354/9354), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3694/3694), done.
Writing objects: 100% (9354/9354), done.
Total 9354 (delta 4085), reused 9354 (delta 4085), pack-reused 0
Completely finished after 0.55 seconds.


# List files with escape \ to check result:
git log --format="reference" --name-status --diff-filter=A '*\\*'
# "systemd/system/default.target.wants/snap-git\\x2dfilter\\x2drepo-7.mount"
# "systemd/system/multi-user.target.wants/snap-git\\x2dfilter\\x2drepo-7.mount"
# "systemd/system/snap-git\\x2dfilter\\x2drepo-7.mount"

#  Unfortunately it seems filter-repo was executed, but log still lists filenames with escape \ :-( 

问题:

1) 如何从 Git 存储库历史记录中删除所有文件,路径在文件名中至少有一个转义\字符?

(原因:无法在 Windows 上签出文件名中包含不兼容字符的文件)

UPDATE1:

尝试按照建议将输入文件列表中的字符串替换为 - ,但 git history remove 仍然不成功:\\x2d

# List the files with escape \ from repo history into a list file:
git log --all --name-only -m --pretty= '*\\*' | sort -u >/opt/git_repo_files_w_escape.txt

# Replace \\x2d string to - in git_repo_files_w_escape.txt:
sed -i 's/\\\\x2d/-/g' /opt/git_repo_files_w_escape.txt

# Remove the listed files from repo history:
git filter-repo --invert-paths --paths-from-file /opt/git_repo_files_w_escape.txt
Parsed 592 commits
New history written in 0.25 seconds; now repacking/cleaning...
Repacking your repo and cleaning out old unneeded objects
HEAD is now at 71128f3 .gitignore: ADD snap-git to be ignored
Enumerating objects: 9354, done.
Counting objects: 100% (9354/9354), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3694/3694), done.
Writing objects: 100% (9354/9354), done.
Total 9354 (delta 4085), reused 9354 (delta 4085), pack-reused 0
Completely finished after 0.55 seconds.


# List files with escape \ to check result:
git log --format="reference" --name-status --diff-filter=A '*\\*'
# "systemd/system/default.target.wants/snap-git\\x2dfilter\\x2drepo-7.mount"
# "systemd/system/multi-user.target.wants/snap-git\\x2dfilter\\x2drepo-7.mount"
# "systemd/system/snap-git\\x2dfilter\\x2drepo-7.mount"

#  Unfortunately log still lists filenames with \\x2d :-(

UPDATE2:

尝试替换 in git_repo_files_w_escape.txt to 或,但都没有导致从 Git 历史记录中删除文件名的文件。\\x2d\\\\x2d\x2d\\x2d

UPDATE3:

我正在寻找一个基于 git filter-repo 的工作解决方案。

还有什么想法吗?

反斜杠 字符 git-history

评论

0赞 matt 1/18/2023
冒号不是反斜杠,那么我们在这里谈论什么?
2赞 matt 1/18/2023
否则这不和你 stackoverflow.com/questions/75112545/ 一样吗......
2赞 matt 1/18/2023
反斜杠本身也不是转义字符。这只是一个反斜杠。
2赞 matt 1/18/2023
但这并不能使转义反斜杠结果路径中的字符。这只是一种与 bash 交谈的方式。
1赞 matt 1/18/2023
也许只有当一个人不理解字符串转义时。否则它们是相同的。

答:

0赞 LeGEC 1/20/2023 #1

fwiw,这适用于 linux 系统,这允许我重写 HEAD 提交,而无需在磁盘上签出文件:

git ls-files | grep -a -e '\\' | while read f; do
    f=$(echo $f | sed -e 's|"||g')
    new=$(echo "$f" | sed -e 's|\\\\x2d|-|g')
    git show "@:$f" > $new
    git rm --cached "$f"
    git add "$new"
done

git status
git commit --amend

相同的命令应该适用于 Windows。git-bash

评论

0赞 klor 1/20/2023
谢谢你的回答。但这个答案只重写了 HEAD,而不是整个回购历史。我正在寻找一个基于 git filter-repo 的工作解决方案。
1赞 LeGEC 1/20/2023
@klor:如果您按原样执行命令,是的。它还为编写一组命令提供了基础,这些命令重命名名称中包含“\\”的文件,这可以为您提供一种将其转换为脚本的方法,例如,您可以使用该脚本进行调用。不幸的是,我没有足够的时间来研究您的问题的完整解决方案。git filter-branch
0赞 LeGEC 1/20/2023
也许摆弄类似的东西(在较小的存储库上尝试以检查效果)regex:(.*)(\\\\x2d)(.*)=>\1-\3
0赞 j6t 1/21/2023 #2

假设您有许多要修复的文件分散在层次结构中,则解决方案看起来很乏味。您可以改用 和 的组合来修改整个历史记录中的文件名。git filter-repogit fast-exportgit fast-import

git fast-export --no-data --all > exported

现在删除包含反斜杠的文件条目:

grep -v '^[DM] .*\\' exported > fixed

除了删除文件外,您还可以修改文件名。例如,要用破折号替换反斜杠,您可以尝试以下操作:-

sed -e '/^[DM] /s,\\,-,g' < exported > fixed

现在,您可以调查这两个文件之间的差异,以确保没有修改任何提交消息:

diff -u exported fixed | less

现在尝试导入修改后的历史记录:

git fast-import < fixed

这将停止并显示一个错误,告诉您不会修改分支,因为旧的分支头不是新头的子集。如果没有其他错误,您现在可以强制修改:

git fast-import --force < fixed

评论

0赞 Elijah Newren 1/22/2023
不,使用 filter-repo 一点也不乏味。但是,更重要的是,建议对快速导出进行程序化编辑时,应该伴随着大警告。避免了最糟糕的问题,但你应该真正强调该选项的重要性,以避免人们针对其他问题修改您的解决方案,从而放弃该选项,然后损坏他们的存储库。此外,即使使用 --no-data,也存在从提交消息中删除行并损坏流的风险。编写 filter-repo 的部分原因是以编程方式编辑快速导出流可能会有风险。--no-data
4赞 Elijah Newren 1/22/2023 #3

你根据一个常见但不正确的假设,将错误的输入到 filter-repo 中,这是关于 git 日志如何工作的。

看看你自己的输出:

$ git log --format="reference" --name-status --diff-filter=A '*\\*'
"systemd/system/default.target.wants/snap-git\\x2dfilter\\x2drepo-7.mount"
"systemd/system/multi-user.target.wants/snap-git\\x2dfilter\\x2drepo-7.mount"
"systemd/system/snap-git\\x2dfilter\\x2drepo-7.mount"

让我们以第一行为例。如果你要将其存储在一个文件中,并将其传递给 --paths-from-file,那么 git-filter-repo 将寻找一个名为 remove.存储库中没有此类文件。相反,你有一个名为 .(请注意,我已经删除了两个字符和其中两个字符。"systemd/system/default.target.wants/snap-git\\x2dfilter\\x2drepo-7.mount"systemd/system/default.target.wants/snap-git\x2dfilter\x2drepo-7.mount"\

这里的问题是,您假设 git log 会按原样列出文件名,只要有特殊字符,它就不会这样做。您通常可以通过设置 core.quotepath=false 来解决这个问题(这在您有非 ASCII 字符时特别有帮助),但当您有反斜杠时,即使这样也会被忽略。

以下是可能更适合您生成要排除的文件名列表的方法:

git log -z --all --name-only -m --pretty= '*\\*' | tr '\0' '\n' | sort -u >/opt/git_repo_files_w_escape.txt

但它假定您没有带换行符的文件名。(但是,如果您确实有带有换行符的文件,那么 --paths-from-file 将不适合您。

更简单的方法是绕过创建具有错误名称的文件列表,然后通过模式以编程方式删除它们:

git filter-repo --filename-callback 'return None if b'\\' in filename else filename'

评论

0赞 klor 1/22/2023
@newren 非常感谢您为我指出正确的解决方案!您的解决方案运行良好,它删除了所有文件名中有反弹的文件。你是对的,这不是一个错误,只是 git 日志结果的格式不正确,无法输入到 .git filter-repo