如何从 git 历史记录中的文件中替换文本?

Posted

技术标签:

【中文标题】如何从 git 历史记录中的文件中替换文本?【英文标题】:How to substitute text from files in git history? 【发布时间】:2011-05-05 20:52:13 【问题描述】:

我一直使用基于界面的 git 客户端 (smartGit),因此对 git 控制台没有太多经验。

但是,我现在需要在历史记录中的所有 .txt 文件中替换一个字符串(因此,不是擦除整个文件,而只是替换一个字符串)。我找到了以下命令:

git filter-branch --tree-filter 'git ls-files -z "*.php" |xargs -0 perl -p -i -e "s#(PASSWORD1|PASSWORD2|PASSWORD3)#xXxXxXxXxXx#g"' -- --all

我试过了,但不幸的是,虽然密码确实被更改了,但所有二进制文件都已损坏。图片等都会损坏。

有没有更好的方法来做到这一点,不会损坏我的二进制文件?

谢谢。

编辑:

我弄混了一些东西。导致二进制文件损坏的实际代码是:

$ git filter-branch --tree-filter "find . -type f -exec sed -i -e 's/originalpassword/newpassword/g'  \;"

顶部的代码实际上删除了所有带有我的密码的文件。

【问题讨论】:

不能解决您的问题,但这类似于我不久前提出的问题:***.com/questions/2225454/… 确实,关于如何删除文件有很多答案。不过我需要替换一个字符串。 @Jimmy Cuadra,请看我的编辑,我实际上使用了不同的脚本,搞混了。也许它可以帮助您获得正确的命令。 【参考方案1】:

我建议使用BFG Repo-Cleaner,它是git-filter-branch 的更简单、更快的替代方案,专为从 Git 历史中重写文件而设计。

您应该在这里仔细按照以下步骤操作:https://rtyley.github.io/bfg-repo-cleaner/#usage - 但核心位是这样的:下载BFG's jar(需要 Java 7 或更高版本)并运行以下命令:

$ java -jar bfg.jar  --replace-text replacements.txt -fi *.php  my-repo.git

replacements.txt 文件应包含您想要执行的所有替换,格式如下(每行一个条目 - 请注意不应包含 cmets):

PASSWORD1 # Replace literal string 'PASSWORD1' with '***REMOVED***' (default)
PASSWORD2==>examplePass         # replace with 'examplePass' instead
PASSWORD3==>                    # replace with the empty string
regex:password=\w+==>password=  # Replace, using a regex
regex:\r(\n)==>$1               # Replace Windows newlines with Unix newlines

您的整个存储库历史记录将被扫描,.php 文件(大小小于 1MB)将执行替换:任何匹配的字符串(不在您的 最新 提交中)将被换了。

全面披露:我是 BFG Repo-Cleaner 的作者。

【讨论】:

难以置信! BFG 太棒了! 这对我帮助很大。谢谢你这么棒的项目。我也捐了。再次感谢您。 感谢@Bane - 真的很高兴它有帮助,感谢您支持这个项目! 如果上面的例子能列在 BFG 网站上就太好了!我不得不再次用谷歌搜索这个 SO 问题才能找到它们。 偶然发现了这个,很棒的项目!正是我希望它在最短的时间内完成的,谢谢队友【参考方案2】:

您可以通过将-name "pattern" 传递给find 来避免接触不需要的文件。

这对我有用:

git filter-branch --tree-filter "find . -name '*.php' -exec sed -i -e \
    's/originalpassword/newpassword/g'  \;"

【讨论】:

我试过了,但是查看 git 历史记录,所有文件都保持不变......我是否必须“重新设置”或其他东西(我很新),如果是的话我该怎么做这样做? @Volte 您使用的正则表达式很可能与任何内容都不匹配。如果表达式匹配某些内容,此命令将重写存储库历史记录(如变基)。 你是对的。结果发现,当我打算搜索 .h 时,我正在搜索 .php 文件:P 这就是我得到的盲目复制粘贴哈哈。干杯。 您的脚本对我不起作用(在 Windows 上的 Cygwin 中)。然而这有效:git filter-branch --tree-filter "find . -name '*.php' -type f -exec sed -i -e 's/originalpassword/newpassword/g' \;" 这救了我的@$$! TY @jweyrich,简而言之,赢得胜利。【参考方案3】:

使用 Git 2.24(2019 年第四季度),git filter-branch (and BFG) is deprecated

newren/git-filter-repo做你想做的事。 它的example section 中有一个几乎您想要的示例:

cd repo
git filter-repo --path-glob '*.txt' --replace-text expressions.txt

expressions.txt:

literal:originalpassword==>newpassword

但是,警告:正如Hasturkun 添加的the comments

使用--path-glob(或--path)会导致git filter-branch只保留符合这些规范的文件。 仅替换特定文件中的文本的功能在 bfg-ish 中提供为 -filint-history script。 否则,目前看来这只能通过自定义提交回调实现。 见newren/git-filter-repo issue 74

这是有道理的,考虑到--replace-text 选项本身就是blob callback。

【讨论】:

这不起作用,所以我浏览了文档。你有一个小错字。在expressions.txt 中它应该是文字:originalpassword==>newpassword @KausUntwale 谢谢。我已经相应地编辑了答案。如果您看到其他内容,请不要犹豫编辑它。 我在一个 repo 上尝试了这个,结果是一个带有单个提交的 repo,并且只有 --path-glob 中提到的文件。我希望我的 repo 中的许多提交仍然存在,并且与 glob 不匹配的文件未被触及。 @Otzen 它应该按您预期的方式工作。不知道那里出了什么问题。 使用--path-glob(或--path)会导致git filter-branch只保留符合这些规范的文件。在bfg-ish 作为-filint-history 脚本中提供了仅替换特定文件中的文本的功能。否则,看起来这目前只能通过自定义提交回调实现。另见github.com/newren/git-filter-repo/issues/74【参考方案4】:

我在 /usr/local/git/findsed.sh 创建了一个文件,内容如下:

find . -name 'githubDirToSubmodule.sh' -exec sed -i '' -e 's/What I want to remove//g'  \;

我运行了命令:

git filter-branch --tree-filter "sh /usr/local/git/findsed.sh"

命令说明

当您运行 git filter-branch 时,它会逐个检查您曾经提交的每个修订版。 --tree-filter 在每个提交的修订版上运行 findsed.sh 脚本,保存它,然后进入下一个修订版。

find 命令查找特定文件或文件集并在该文件上执行 (-exec) sed 编辑器。 sed 是一个命令,它在 s/ 之后使用正则表达式并将其替换为 / 和 /g 之间的字符串(在我的示例中为空白)。 是对 find 命令给出的文件路径的引用。文件路径被提供给 sed,因此 sed 知道要处理什么。 \;刚刚结束 -exec 命令。

将 shell 脚本和命令分成单独的部分可以减少引号 '' 或 "" 的复杂性。

特点

我在 Mac 上成功实现了这一点,显然 sed 是 Mac 上的一个特定(旧?)版本。这很重要,因为它有时表现不同。确保执行 sed -i '' 否则它会在文件末尾添加“-e”,认为这就是我想要命名我的备份文件的名称。 -i '' 表示不制作备份文件,只需在原地编辑文件即可,不需要备份文件。

指定 -name 'filename.sh' 帮助我避免了另一个我无法解决的问题。还有另一个带有 .sh 的文件,该文件以没有换行符结尾。 sed 出于某种原因,会在末尾添加一个换行符,尽管 's/blah/blah/g' 与该文件中的任何内容都不匹配。因此,我没有解决这个问题,而是告诉 find 忽略所有其他文件。

其他有效的命令

此外,我发现这些命令可以在 findsed.sh 文件中使用(一次只有一个命令,而不是多个命令,所以将其他命令注释掉):

find . -name '.publishNewZenPackFromGithub.sh.swp' -exec rm -f  \;
find . -name '*' -exec grep -H PassToRemove  \;

享受吧!

【讨论】:

【参考方案5】:

更多关于git-filter-repo的信息

https://***.com/a/58252169/895245 提供基础知识,这里有更多信息。

安装

至少从 git 2.5 开始,它没有与主线 git 一起提供,所以:https://superuser.com/questions/1563034/how-do-you-install-git-filter-repo/1589985#1589985

python3 -m pip install --user git-filter-repo

使用提示

这是我倾向于使用的更常见的方法:

git filter-repo --replace-text <(echo 'my_password==>xxxxxxxx') HEAD

地点:

Bash 进程替换允许我们不为简单替换创建文件 HEAD 只影响当前分支

仅修改一系列提交

How to modify only a range of commits with git filter-repo instead of the entire branch history?

git filter-repo --replace-text <(echo 'my_password==>xxxxxxxx') --refs HEAD~2..HEAD

使用 Python API 替换

更复杂的替换,可以使用Python API,见:How to use git filter-repo as a library with the Python module interface?

【讨论】:

我自己的答案的链接以及您自己的答案中的更多有用细节?好吧...自动投票。那些死灵法师徽章不会自己创造。 @VonC 我忘了给你点赞!!!我是故意的!!! 如何在您的单行解决方案中回显多个替换表达式? @s.k &lt;(echo 'my_password==&gt;xxxxxxxx'; echo 'my_password2==&gt;xxxxxxxx')&lt;(printf my_password==&gt;xxxxxxxx\nmy_password2==&gt;xxxxxxxx\n) 应该都可以工作。【参考方案6】:

可能是外壳扩展问题。如果 filter-branch 在评估命令时丢失了 "*.php" 周围的引号,则它可能会扩展为空,因此 git ls-files -z 会列出所有文件。

您可以检查过滤器分支源或尝试不同的引用技巧,但我要做的只是制作一个执行树过滤器并传递该脚本的单行 shell 脚本。

【讨论】:

这个班轮会是什么样子? 您现在传递给--tree-filter '...' 的确切内容。 好建议;将实际的可执行脚本传递给 filter-branch 通常比尝试处理所有引用要容易得多。 我在windows上,它支持bat脚本吗? 请看我的编辑,我实际上使用了不同的脚本,搞混了。

以上是关于如何从 git 历史记录中的文件中替换文本?的主要内容,如果未能解决你的问题,请参考以下文章

从GIT历史记录中删除文件

如何将文件从一个文件夹移动到同一git存储库中的另一个文件夹保留历史记录[重复]

如何使用 git 扩展从其历史记录中查看旧版本的 Excel 文件

如何删除 Git 仓库中的历史提交记录

使用 Git 版本控制查看文件的更改历史记录

Git如何删除历史记录中的大文件详解