使用Roberto Tyley的BFG Repo-Cleaner移除git库中的二进制文件

2015 年 1 月 13 日

源码管理系统不是永远不出问题的黑盒子。如果你没有合理地维护好你的仓库,对于要使用它们的开发人员来说,这些仓库会变成一个重大的负担。有些工具能帮你维护它们,Roberto Tyley 的 BFG Repo-Cleaner for Git 就是这样一个工具。这个工具可以用来删除那些你不小心提交到 git 分支上的二进制大文件。

InfoQ:给那些不熟悉 git 的读者解释一下,为什么二进制大文件是个问题?

Roberto:在 git 中,所有和你共用一个库的人,当他们把库下载下来时,他们会得到你这个项目的完整历史记录。一般情况下这没什么问题——真正的代码只占很少体积,但拥有完整的历史记录使你能够实施分布式的合作、快速得到 diff 结果以及更多的便利——但项目的历史记录会慢慢膨胀。如果你提交了大文件——比如 100MB 以上——你仓库的体积会飞速增长,并且当其他人 clone 这个仓库的时候,他们要下载的数据就更多了。最终你会意识到你犯了一个错误,但是就算你决定要通过一个 commit 来删除这些文件,它们其实还在那儿,在你的历史数据中,下载整个仓库所花的时间不会有任何变化。

除了大文件以外,不小心 commit 进去的密码或其他隐私数据也有这个问题——就算你删掉它们,它们实际上还在历史数据中。你只是犯了个错,但是 git 却不让你纠正。现在我们该怎么办?

InfoQ:要从 git 库中移除二进制或其他不合适的文件,最基本的方法是使用 git-filter-branch 命令。你能不能解释一下这个命令到底做了什么,以及为什么你认为它还不够好。

Roberto:没错,git-filter-branch 命令是一个强大的工具——就像一把万能的电锯一样——它调用脚本来重写 git 的历史记录,从头开始写。你给它一个 bash 脚本,它会从头到尾针对每个 commit 运行这个脚本,一个接一个,逐步重建历史记录。你的脚本可以做你想做的任何事——删除文件、移除不想要的文本(比如密码),或者把每个 jpg 文件都替换为一张烤土豆的照片。一旦 git-filter-branch 结束后,你的 git 历史记录可能从表面上看起来完全一样——一样的日期,一样的注解等等——但是这个新的历史记录实际上和以前完全不一样了——你已经动过它了。

从 2007 年开始,git-filter-branch 就成为标准 git 的一部分,当时 git 发布才两年。所以很久以来 git-filter-branch 一直是解决 git 历史记录问题的权威方法。但它有两个问题:

  1. 很难用
  2. 太慢了

难用的原因有几个——git-filter-branch 不是要达到一个固定的目的,它本身并不给你解决方案。它很灵活,理论上可以做任何事,但是你必须清楚自己在干什么。你想做的一件最简单的事情可能是——从每个 commit 中删除一个文件——以下是你的做法:

复制代码
git filter-branch --index-filter 'git rm --cached --ignore-unmatch big.mp4' --tag-name-filter cat -- --all

这命令行太不友好了。而且如果你想做点更复杂事——比如删掉某个密码所有出现过的地方,会怎么样?或者删掉所有你不小心放进去的大文件?git-filter-branch 本身不支持找出这些东西,你必须写一个复杂的脚本,才能告诉 git-filter-branch 去删除什么。网上有无数为 filter-branch 编写的脚本——我在写 BFG 之前就写过一些。几乎同一时间,我的一些在 Guardian 的同事想要从我们最大的代码库中删除密码,几天后他们搞出一个 filter-branch 脚本,这脚本看起来要跑数个小时——他们只能小心翼翼地把它放到计划任务中,让它在晚上跑,以免影响到团队中的其他开发人员,而且他们费了好大劲,预先测试了这个脚本好多次,确保排除所有的 bug。

整件事情干下来太痛苦了。虽然 git-filter-branch 是一个高级工具,但 git 已经越来越普及(这种趋势持续至今),大量的人会遇到这个问题,这会给他们带来无尽的困惑和痛苦。所以这看起来是一个值得去解决的问题。

我已经比较精通 git 了,写过 Agit,一个 Android 下的 git 客户端,并且对 git 数据存储结构的基本原理有很好的理解。当我思考 git-filter-branch 为什么这么慢的时候,我意识到只要你愿意对此问题的需求稍作调整,你就能得到一个快很多的解决方案。

InfoQ:你的项目网站说,BFG Repo-Cleaner 比 git-filter-branch“快 10 到 720 倍”。这是如何做到的?

Roberto:准确地说,如果跟 git-filter-branch 能处理的最简单任务(比如删除一个已知的文件)做比较,这个数据是 10 到 1000 倍。再举个独立人士的评测结果,Elliot Glaysher(开发 Chrome 的 Google 软件工程师)在把 Chromium 代码库从 svn 迁移到 git 的时候,测试了 BFG 的性能——BFG 做这件事情只要十分钟,而 git-filter-branch 需要三天——大约快乐 430 倍。

这里有一个视频,对比了 git-filter-branch(跑在四核 Mac 电脑上)和 BFG(跑在 Raspberry Pi 上) 速度: https://www.youtube.com/watch?v=Ir4IHzPhJuI

BFG 之所以能在性能上有如此大的改进,原因在于它本来就和 git-filter-branch 不一样。

BFG 做到了,因为我们充分考虑了最常见的场景,以及 git 本身存储数据的方式。这个场景就是删除不想要的文件——至少我想说这是最常见的使用 git-filter-branch 的场景,并且也是我感兴趣的场景。针对这个特定的问题,git 的实现方式决定了我们可以采取完全不同的方案:在 git 中,所有文件和文件夹的数据都只存储一次,并且每个都会有一个独一无二的 id——git-id。如果大量的 commit 都没有修改某个文件,那么这个文件只会被保存一次。如果这个文件有两个版本,并且需要在两个版本间来回切换,这个文件只会存储两次,每个版本各一次。所以,如果一个文件只存储一次,因为它出现在 100 个 commit 中,就要对它清理 100 次,这肯定是不现实的。BFG 只对 git 库中的每个对象清理一次,然后记住它的 git-id,以后遇到它,不把它计算在内就行了。我们对功能做了限制——git-filter-branch 能让你做任何事,而 BFG 不行——以此换来性能上的巨大改进。

速度的提升还受益于没有进程间的切换(JVM 会搞定一切,不需要在 C 代码和 bash 代码间来回切),以及能充分利用你的多核电脑。我很高兴看到 BFG 会使你所有的处理器核发烫,每个核都在尽其所能地删除文件和目录——但是很不幸 git-filter-branch 只能串行处理工作,一个接一个地处理 commit——前一个处理完之前,无法开始下一个。

BFG 的速度——明显是——一大优势。它可以让人们更高效地对改写代码库历史记录进行安全的试验,确定无误后再开始真正动手清理代码库,把结果 push 到服务器,通知所以开发人员删掉旧库,拉一份全新的——这种沟通的事情对于一个大型团队来说,工作量很大。

InfoQ:你为什么选择用 Scala 写 BFG Repo-Cleaner 而不是直接用 Java?

Roberto: Guardian 公司热衷于使用 Scala,并且对于绝大多数工作让我在 Java 和 Scala 之间选,我每次都会选 Scala。事实上这个项目用 Scala 非常合适:基于 JVM,我就可以利用高性能的 JGit 库。作为函数式编程语言,Scala 很适合去处理 git 的不可变数据结构,并且能利用并行开发的优势。用 Scala 编程是一种乐趣。在我为 ScalaDays 准备的演讲《Scala 帮助 git 运行得更快》中,我对 Scala 的优势做了更深入的剖析。

InfoQ:你有没有想过提供一个 GUI 版本的 BFG Repo-Cleaner,作为独立的工具也好,作为其他 git 工具的插件也好?

Roberto:虽然我觉得作为命令行工具 BFG 工作得很好,我也做了一些有意思的可视化方面的尝试。六月份我在泰特现代艺术馆(Tate Modern)举行的“玩转空间(Hack The Space)”活动上,把 git-filter-branch 和 BFG 操作 git 库历史记录的过程用实验性的 3D 过程展示了出来。除了视觉效果非常漂亮以外,它也非常清晰地显示了两者在速度方面差别这么大!此举的另一个目的是更清楚地解释 BFG 对你的仓库到底做了些什么——我可以理解人们很担心把删除旧文件的任务交给 BFG(哪怕它不会碰你当前的文件,因为我们假设用户过去犯过的错误不会再犯,当前库中的文件都是他们要的)。有时候在需求方面,用户想要的和他们真正需要的东西有一定的差距,图形化的展示对于这两者的统一很有帮助。

关于插件,我真的很想简化一下在 BFG 中用脚本自定义处理动作的方式——接近 git-filter-branch 的灵活性,但依然保持快速。Christian Hoffmeister 最近把 BFG 集成进了一个自定义工具(git-timeshift),但离达到我满意的程度还有差距。

按照现在的情况,命令行版本的 BFG 被广泛采用——twitter 上有很多很棒的帖子在讨论如何使用它,BFG 文档网站的引用者统计记录显示,BFG 在被形形色色的用户使用,从主流的投资银行,到研究型实验室、手机生产商,甚至那些为军用飞机编写航天软件的神秘公司。基于下载量来做个估算,据此我猜测,BFG 自发布以来,已经为大家节省了大约 30 人年的工作量。

BFG 是开源、完全免费的,并且我希望你们的读者下次需要清理 git 历史记录的时候,BFG 能给他们带来良好的体验。

关于受访者

Roberto Tyley是 Guardian 公司的开发人员、Guardian 会员项目的技术经理,BFG、gu-who、Agit 的作者,也为其他很多开源项目做贡献。他以前在 GitHub 工作过,创建了"animated diff"项目,他热衷于解释事物的原理。

参考英文原文: Removing Binary Files from git using Roberto Tyley’s BFG Repo-Cleaner

2015 年 1 月 13 日 22:161701
用户头像

发布了 77 篇内容, 共 30.4 次阅读, 收获喜欢 18 次。

关注

评论

发布
暂无评论
发现更多内容

今日份学习之Spring Boot自动配置实现原理

比伯

Java 编程 架构 面试 计算机

第十周作业

fmouse

极客大学架构师训练营

架构师训练营第六周作业

丁乐洪

架构师训练营第十周作业

_

极客时间架构师一期 第十周作业

架构师训练营—第十周作业

Geek_shu1988

LeetCode题解:860. 柠檬水找零,模拟情境,JavaScript,详细注释

Lee Chen

算法 LeetCode 前端进阶训练营

架构师训练营第十周总结

_

总结 极客大学架构师训练营

架构师训练营第 1 期 -- 第十周作业

发酵的死神

极客大学架构师训练营

一次有效的产品需求头脑风暴

Bruce Talk

敏捷开发 Agile Product Owner

STL 源码剖析之五大组态常量介绍

herongwei

c++ 源码 后端 stl

第十周总结

fmouse

极客大学架构师训练营

架构师训练营 1 期第 10 周:模块分解 - 总结

piercebn

极客大学架构师训练营

大家都知道jmeter,但是它会让你的工作效率至少提升80%

996小迁

Java 编程 程序员 架构 面试

试试,阿里P7的笔试题:多线程按序打印如何实现?

Java架构师迁哥

redis 基础数据 sets 业务场景分析

sinsy

redis 业务场景分析

程序员:我熟悉多线程!面试官:都不敢写精通,还敢要26K?

Crud的程序员

编程 程序员 面试 多线程

食堂就餐卡系统设计

cc

在Spring data中使用r2dbc

程序那些事

WebFlux R2DBC 程序那些事 spring data spring-data-r2dbc

如果不想你被称做掉包侠,那么请有效地学习机器学习算法知识

计算机与AI

学习

架构师Week6总结

lggl

总结

在网上的AG账户登录异常说是涉嫌套利不给办理取出怎么办?

Geek_a6658e

专业出黑团队

刘华:我最近听到最对味的话,就是“先Scale down再Scale out”

刘华Kenneth

DevOps 敏捷

架构师训练营一期学习心得

cc

刘华:上云后,你的架构设计可以更飞

刘华Kenneth

云计算 架构设计 技术选型 云平台

《华为数据之道》读书笔记:第 5 章 面向“联接共享”的数据底座建设

方志

大数据 数据中台 数据仓库 数字化转型

一万三千字的HashMap面试必问知识点详解

云流

Java 编程 面试 计算机

尾调用与尾递归

哈希说

算法

Java-Mock简化单元测试

落日楼台H

Java 测试 单元测试 Mock Mock测试框架

架构师训练营—第十周学习总结

Geek_shu1988

初学小白你不知道的C语言经典算法(附带答案)

ShenDu_Linux

c++ 程序员 算法 C语言 数据结构与算法

作业-第6周

arcyao

使用Roberto Tyley的BFG Repo-Cleaner移除git库中的二进制文件-InfoQ