问题

如何删除Git里来自pull request的commit中包含的个人敏感信息?

回答
你有没有遇到过这种情况:兴冲冲地合并了一个 `pull request`,却在提交历史里发现了一堆不该出现的敏感信息,比如密码、API密钥或者其他私密数据?这时候怎么办?别急,今天咱们就来聊聊如何清理这些“不速之客”,让你的 Git 历史重新变得干净整洁。

为什么会出现这种情况?

通常,这些敏感信息出现在 `pull request` 的 `commit` 里,是因为开发者在开发过程中,直接将密钥等敏感内容写在了代码里,然后提交并推送到了远程仓库。即使后来被删除或修改了,但只要这些 `commit` 还在历史记录中,敏感信息就依然存在被查阅的风险。尤其是在合并到主分支后,这些信息就会被永久地记录下来。

清理 Git 历史——这不是件容易事,但必须做!

清理 Git 历史,尤其是已经合并到主分支的提交,是一个有损操作,意味着它会改写提交历史。因此,在进行任何操作之前,请务必牢记以下几点:

1. 备份!备份!备份! 在动手之前,请务必将你的仓库(或者至少是你想清理的分支)完整地备份一份。可以通过创建一个新的分支来保存当前状态,或者直接复制整个仓库文件夹。以防万一,我们总要留条后路。
2. 沟通!沟通!沟通! 如果你不是一个人在维护这个仓库,并且这个仓库已经被其他人克隆和使用,那么改写历史可能会给他们带来麻烦(比如他们需要重新同步他们的本地仓库)。所以,在进行大刀阔斧的改动之前,一定要提前告知团队成员,并和他们商量好同步方案。
3. 谨慎操作,步步为营。 Git 的强大之处在于它的灵活性,但同时也是一把双刃剑。操作不当,可能会导致更严重的问题。所以,请仔细阅读每一步的说明,理解它的作用后再执行。

清理工具:`git filterbranch` 和 `BFG RepoCleaner`

在 Git 的工具箱里,有两个专门用来重写 Git 历史的利器:`git filterbranch` 和 `BFG RepoCleaner`。

`git filterbranch` 是 Git 自带的强大工具,功能非常全面,但相对而言,使用起来也比较复杂,对 Git 的理解要求较高。
`BFG RepoCleaner` 是一个第三方工具,它更专注于清理大文件和敏感信息,使用起来比 `git filterbranch` 简单得多,而且速度也更快。对于大多数清理敏感信息的需求来说,BFG 是一个更好的选择。

今天咱们重点讲讲如何用 BFG RepoCleaner 来解决问题,因为它更加高效和易用。

使用 BFG RepoCleaner 清理敏感信息

步骤一:安装 BFG RepoCleaner

如果你还没有安装 BFG,你需要先安装它。你可以从它的官方 GitHub 页面下载 jar 包,或者使用包管理器进行安装。

下载 jar 包(推荐):
访问 [BFG RepoCleaner GitHub 页面](https://rtyley.github.io/bfgrepocleaner/),下载最新的 `bfg.jar` 文件,并将其放在一个方便访问的位置。

使用 Homebrew (macOS):
```bash
brew install bfg
```

步骤二:克隆一个干净的仓库副本

这是最最重要的一步。我们不要在本地正在工作的仓库上直接操作,而是要从远程仓库克隆一个全新的副本。这样做是为了保证你操作的是一个未被修改过的、最新的状态,并且避免在操作过程中损坏你当前的本地工作。

```bash
确保你不在你正在工作的仓库目录里!
cd .. 移动到你工作仓库的上一级目录

git clone mirror <你的仓库URL> <新仓库目录名>
cd <新仓库目录名>
```

`<你的仓库URL>`:替换成你的 Git 仓库的 URL,例如 `git@github.com:yourusername/yourrepo.git` 或 `https://github.com/yourusername/yourrepo.git`。
`<新仓库目录名>`:你可以给这个新的仓库副本起一个名字,比如 `myrepocleaned`。

使用 `mirror` 参数克隆会复制所有分支和提交历史,并且不检出工作目录。

步骤三:运行 BFG 进行清理

现在,进入你刚刚克隆的那个仓库副本目录 (`<新仓库目录名>`),准备运行 BFG。

我们主要会用到 BFG 的 `deletefiles` 和 `replacetext` 功能。

场景一:你明确知道被误提交的文件名

如果你知道敏感信息被包含在一个具体的文件里,并且你想要完全删除这个文件及其所有历史记录(包括它之前版本里的内容),可以使用 `deletefiles`。

例如,假设你有一个文件叫做 `config.dev.js`,里面包含了敏感信息:

```bash
java jar /path/to/bfg.jar deletefiles "config.dev.js" .
```

`/path/to/bfg.jar`:替换成你实际下载的 BFG jar 包的路径。
`"config.dev.js"`:替换成你要删除的那个文件的名字。如果你要删除多个文件,可以重复使用 `deletefiles` 参数,或者用逗号分隔文件名列表。
`.`:表示当前目录是 Git 仓库的根目录。

BFG 会扫描整个提交历史,找到所有包含 `config.dev.js` 这个文件(或者它的不同版本)的提交,并将其从历史中移除,创建一个新的提交来替换它。

场景二:敏感信息分散在多个文件,或者你不知道具体文件名,只知道敏感数据的模式

这是更常见的情况。比如你把 API 密钥直接写在了代码里,可能散落在 `.js`、`.py` 等文件中。这时候,我们可以让 BFG 搜索特定的文本模式并进行替换。

例如,你想替换所有包含类似 `"api_key": "sk_test_..."` 的字符串:

```bash
java jar /path/to/bfg.jar replacetext "api_key": "sk_test_." .
```

`"api_key": "sk_test_."`:这是我们要查找并替换的文本模式。请根据你的实际敏感信息来调整这个正则表达式。`.` 是一个通配符,表示匹配任意数量的任意字符。
BFG 会将所有匹配到的文本替换成一个占位符,通常是 ``。

重要的考虑点:

正则表达式的精确性: 使用 `replacetext` 时,正则表达式的准确性至关重要。如果你的正则表达式太宽泛,可能会意外地替换掉正常的文本。反之,如果太狭窄,则可能漏掉需要替换的敏感信息。务必在测试一下,确认后再大规模应用。
敏感信息类型: 如果你删除的是一个完整的密钥文件(如 `.env` 文件),使用 `deletefiles` 会比 `replacetext` 更干净彻底。如果敏感信息是散落在代码中的字符串,那么 `replacetext` 是更好的选择。
BFG 的工作方式: BFG 会创建一个新的 Git 对象数据库,而不是直接修改现有的。它扫描所有提交,如果发现匹配项(要删除的文件或要替换的文本),它会创建一个新的提交来取代旧的提交,并且删除不再被引用的旧提交对象。

步骤四:清理 Git 的引用和垃圾回收

BFG 执行完操作后,你的 Git 仓库对象数据库已经被修改了,但 Git 的引用(如 `refs/original`)可能仍然指向旧的提交。为了让更改真正生效并移除所有旧的提交信息,我们需要执行一些 Git 命令。

首先,进入你克隆的那个仓库副本目录 (`<新仓库目录名>`),然后执行:

1. 执行 `git reflog prune` 和 `git gc`:
这会清理掉所有不再被引用的提交对象,并进行垃圾回收,使得修改永久生效。

```bash
git reflog expire expire=now all && git gc prune=now aggressive
```
`git reflog expire expire=now all`:移除所有的 reflog 条目,相当于让 Git 认为所有提交都是旧的。
`git gc prune=now aggressive`:执行一次彻底的垃圾回收,删除所有不再被引用的对象。

注意: 在旧版本的 Git 中,可能只需要 `git gc prune=now`。但为了确保彻底清理,上述命令更保险。

步骤五:将清理后的仓库推送到远程仓库

现在,你的本地仓库副本已经清理干净了。接下来,你需要将这个清理后的版本推送到你的远程仓库。

非常重要: 由于我们改写了提交历史,你不能使用普通的 `git push`。你需要使用强制推送来覆盖远程的旧历史。

```bash
在你克隆的仓库副本目录里执行
git push force all
git push force tags
```

`git push force all`:强制推送所有分支。
`git push force tags`:强制推送所有标签。

再次强调: 这两个 `push force` 命令是危险的,因为它们会覆盖远程仓库的现有历史。务必确认你是在正确的、已经备份并与团队沟通好的情况下执行。

处理其他开发者

一旦你强制推送了修改后的历史,其他从这个仓库克隆或拉取过代码的开发者,他们的本地仓库就会出现不兼容的情况。他们需要采取以下步骤来同步到新的历史记录:

1. 将他们本地的、未干净的仓库存档或删除。
2. 从远程仓库重新克隆一份全新的、已经清理过的仓库。
3. 如果有他们本地未推送的修改,需要小心地将这些修改重新应用到新的本地仓库中。 这通常需要手动 diff 和 patch,或者将他们的修改作为一个新的 commit 来提交。

总结一下整个流程

1. 备份!备份!备份!
2. 团队沟通!
3. 从远程仓库克隆一个全新的 `mirror` 副本。
4. 使用 BFG RepoCleaner 进行清理:
`deletefiles` 删除已知文件。
`replacetext` 替换敏感文本模式。
5. 在清理后的仓库副本目录中执行 `git reflog prune` 和 `git gc`。
6. 强制推送 (`git push force all` 和 `git push force tags`) 到远程仓库。
7. 通知其他团队成员,让他们重新克隆或同步他们的本地仓库。

另一种选择:`git filterbranch` (更复杂但无需额外安装)

如果你不想安装第三方工具,也可以使用 `git filterbranch` 来实现类似的功能。但它的语法和操作会更复杂一些。

删除文件:
```bash
git filterbranch force indexfilter 'git rm cached ignoreunmatch <文件名>' pruneempty tagnamefilter cat all
```
替换 `<文件名>` 为你要删除的文件名。

替换文本(相对麻烦):
用 `git filterbranch` 实现文本替换通常需要编写一个脚本来处理,或者结合 `sed` 等命令。例如:
```bash
git filterbranch treefilter 'sed i "s/你的敏感文本模式/你的替换文本/g" $(grep rl "你的敏感文本模式" .)' HEAD
```
这里的 `sed` 命令会作用于工作目录,所以可能需要一些调整才能与 `filterbranch` 配合得更好。

最后的忠告

改写 Git 历史是一项非常强大的操作,请务必谨慎使用。一旦提交历史被改写,就无法轻易恢复到原始状态。所以,最好的防御永远是预防:

使用 `.gitignore` 文件: 将敏感文件(如 `.env`、密钥文件、配置文件等)添加到 `.gitignore` 文件中,防止它们被意外提交。
环境变量和密钥管理工具: 不要将敏感信息直接写在代码里。使用环境变量、密钥管理服务(如 HashiCorp Vault、AWS Secrets Manager)或专门的配置文件来管理敏感信息。
代码审查: 在合并 `pull request` 之前进行仔细的代码审查,可以帮助发现并阻止敏感信息被提交。

希望这篇文章能帮助你解决 Git 历史中出现的敏感信息问题。记住,安全第一,动手之前多备份、多思考!

网友意见

user avatar

假如你把敏感信息推送到 master 分支上了。

你可以先备份本地代码,然后回滚到敏感信息 commit 之前的一次 commit:

       git reset --hard HEAD~1      

然后新建一个分支,比如是 new-master ,然后把代码推送到这个分支

       git checkout -b new-master git push origin new-master:new-master     

然后把主分支删掉,然后重建出来:

       git push origin --delete master    git branch -d master              git checkout -b master             git push origin master               

然后把代码推上去,再把 new-master 给删除掉。

此外,你如果想把其他的提交历史迁移过来,可以利用类似的方式。先不做 reset 操作,重建 master 分支后,从 new-master 使用 cherry-pick 将没有关联到隐私信息的内容拉过来。


如果你是把代码推送到 GitHub 上,想把 log 删除掉,那可以联系 GitHub 的管理员。

类似的话题

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有