1 个不稳定版本
0.1.0 | 2019年5月27日 |
---|
#150 in #progress
89KB
2K SLoC
git-rs
用 Rust 实现 git 以娱乐和教育!
这实际上是我第二次尝试,因此许多大块代码都来自我的第一次尝试。在阅读了更多“Rust 编程”(Blandy, Orendorff)之后,我今年再次尝试。
待办事项
- 从散列存储读取对象
- 从打包存储读取对象
- 读取打包文件索引
- 读取 delta 对象
- 修复接口,以便我们不需要为每个
read
运行open
- BUG:某些 OFS deltas 应用错误。
- 隔离错误情况
- 修复它
- 从磁盘加载引用
- 解析 Git 签名("Identity")
- 创建用于遍历提交图的迭代器
- 创建用于遍历树形的迭代器
- 将树形实体化到磁盘(在 gitindex 之后?)
- 从打包文件创建索引
- 将
Storage
特性重命名为Queryable
- 从
<Type + Boxed reader>
重构对象加载 API 到“我们接受可写对象”- 通过
StorageSet
执行重构
- 通过
- 创建索引
- 用友好的 API 包装它
- 将
- refs v2
- 按需加载引用
- 加载打包引用
- 支持
.git/index
- 读取 Git 索引缓存
- 写入 Git 索引缓存
- 创建用于写入新对象的接口
- 添加基准测试
- 从对象列表创建打包文件(API TKTK)
- 网络协议
- receive-pack
- send-pack
- 尝试发布到 crates
- 编写文档
- 在其他项目中使用 crate
计划
2019-02-08 更新
- 已经有一段时间了!
- 正如你可能看到的,解决打包文件索引问题迫使对仓库进行了大量修改。
- 现在有一个
src/pack/read.rs
文件,它包含适用于任何BufRead + Seek
的通用读取实现。 Storage
特性的签名已更改——它不再返回boxed read对象,而是现在接受一个Write
目标。- 此外,
Storage
现在是Queryable
(更好的名字!)。- 由于我们已从返回boxed对象转换为接受通用
Write
,因此我们不能再对Queryable
进行boxing。- 我不知道 Rust 的这一点,所以学到新知识!
- 由于从返回boxed对象到接受通用
Write
的转换,StorageSet
对象必须重新考虑——它们不能再包含boxed的Storage
对象。- 相反,我们让编译器工作——因为存储集合在编译时已知,我为单元类型实现了
Queryable
,两个类型()
,以及单类型的数组Vec<T>
。 - 这意味着一个
StorageSet
可以包含一个单个的顶级Queryable
,其中可能包含嵌套异构的Queryable
定义。- 这让我感到很温暖,很舒服 💞
- 相反,我们让编译器工作——因为存储集合在编译时已知,我为单元类型实现了
- 由于我们已从返回boxed对象转换为接受通用
- 现在有一个
- 你可能还会注意到,我们实际上还没有完成索引打包文件。 😱
- 这里的情况是:为了创建打包文件索引,你必须在打包文件中的压缩字节上运行CRC32。
ZlibDecoder
将从底层流中拉取比它需要的更多字节,所以你不能采取将CrcReader
交给它的方法来获得良好的结果。- 它必须是一个多遍的处理过程。
- 当前的计划是:运行一次遍历来获取偏移量,未delta'd的shas和类型。
- 运行第二次遍历来解析CRC和解压缩delta。这可以在并行中进行。
2019-01-23 更新
- 是时候开始索引打包文件了。
- 这将使我们能够开始与外部服务器通信并克隆东西!
- 然而,这有点痛苦。
- 打包文件(视为存储)在没有索引的情况下并不非常有用,所以我将它们设计为一个可以接收外部可选索引的对象。
- 我的想法是,如果没有提供索引,我们将在内存中构建一个。
- 这在我面前爆炸了,一点。 💥
- 为了从一个打包文件中构建索引,你必须遍历所有的对象。
- 对于每个对象,你希望记录该偏移量处的对象的偏移量和SHA1 id。
- 然而,该对象可能是一个偏移量或引用delta。
- 这意味着为了索引打包文件,你必须能够读取打包文件中偏移量处的delta'd对象(这意味着你已经有了一个创建的
Packfile
实例)以及在打包文件外部(这意味着你有一个StorageSet
。) - 换句话说:我对程序设计的假设是错误的。
- 这意味着为了索引打包文件,你必须能够读取打包文件中偏移量处的delta'd对象(这意味着你已经有了一个创建的
- 所以,在接下来的几天里,我将改变方向。
- 应该能够生产一个非存储对象的
Packfile
并遍历它。 - 打包文件的“存储”形式应该是
Packfile
和Index
(一个PackfileStore
)的结合。- 这意味着我将把
src/stores/mmap_pack
的逻辑分成“顺序打包文件读取”和“带索引的随机访问打包文件读取”。
- 这意味着我将把
- 应该能够生产一个非存储对象的
- 打包文件(视为存储)在没有索引的情况下并不非常有用,所以我将它们设计为一个可以接收外部可选索引的对象。
- 犯错很有趣 🎉
2019-01-21 更新
- 嗯,那是一个有趣的错误。我们来看看,好吗?
- 这偶尔会在delta会解码另一个delta'd对象时出现。
- 我找到了一个可靠的失败加载的哈希值。
- 我们会因为传入的基本对象与第二个delta的“基本大小”不匹配而失败读取。这里。
- 移除检查我是否得到了错误的delta会导致线程恐慌——delta的基本大小没有说谎。
- 首先,我切换回了我旧的非mmap打包文件实现,因为我最近接触了那段代码。“恢复你最近接触的东西”是这些情况中的胜利策略:不需要昂贵的思考,可以快速缩小错误的范围。
- 唉,旧的打包文件实现也有这个bug。 无济于事。
- 我比较了这个Git实现与我的JS实现的输出。
- 我通过将JS实现的输出与vanilla git的hash进行比较来确认JS实现的输出是正确的。
- 确认之后,我记录了从文件中读取的偏移量和预期的大小。我将这些与添加到
git-rs
中的类似调试输出进行了比较。 - 偏移量和预期的大小匹配!
- 这意味着
- 我从打包文件中读取了正确的数据
- 我正确地读取了有符号整数
- 这个bug肯定是在将增量应用到基对象上的应用中
- 这意味着
- 从那里,我在这些状态转换之上添加了日志,注意操作的细节。
- 我在JS实现中也添加了相同的日志,发现(除了在第二次增量应用之前完全退出)命令是相同的。
- 所以,问题并不是我的增量代码错了:是我的
Read
状态机。
- 这时,我想:"这是一个读状态机的bug。 我知道这个。"
- 修复很简单,像这些修复通常一样。
- (我知道我需要为此编写一个测试。我知道。把你的羞愧加在我身上吧。)
- 这偶尔会在delta会解码另一个delta'd对象时出现。
- 那么 我们学到了什么?
- 请始终测试你的状态机,朋友们。
- (我说过一次,现在再说一次。) 可塑的参考实现会救你于水火。
- 确保你可以信任你的参考实现。
- 无论如何。树读取器现在工作正常了! 🌲🌳🌲
2019-01-19 更新
- 它稍微快一点了! 🎉
- mmap很好地加速了过程,将我们的运行时间减少了20毫秒。
- 尽管如此,我们仍然比git慢。这可能是因为我们立即加载了refset。
- 我保留了不可变的"每个读取一个文件"的打包文件存储;我认为它在将来可能会派上用场。
- 如果能将其作为一个基准来捕获而不是临时运行将是极好的。
- 我集成了树遍历迭代器,得到了一个惊喜
- 我的OFS delta代码中有一个bug!
- 这很奇怪,因为它只在某些存储库的某些blob中出现。
- 否则其他OFS delta似乎可以干净地解析。
- 案例分析:我作为提交遍历器的测试,加载了许多经过OFS-delta处理的提交。
- 还有一点值得关注:我已经将
src/bin.rs
分离出来,为树遍历和提交遍历创建了专门的二进制文件。
- 今天的主题:在一个测试用例中隔离故障。
- 更新:有一个参考实现是非常有帮助的。
- 我已经确认,参考实现可以读取破坏这个项目的对象。
- 我们读取的偏移量也是一样的(谢天谢地)
- 我进一步确认,将包文件实现更换为较旧、较慢的包文件不会影响任何东西。
- 我怀疑 这意味着可能存在我的delta代码(可能性很大!)我的varint解码代码(可能性非常大),或者是Deltas的Read实现的问题。哇,缩小了结果范围!
2019-01-15 更新
- 我添加了一个(实验性的)
git_rs::walk::tree
迭代器,它可以接受一个树并为每个项目产生一个路径和blob。- 它可能比应有的要慢:对于每个项目,它必须克隆一个
PathBuf
,因为我没有弄清楚生命周期。 - 如果你知道如何修复它,请 打开一个问题 并让我知道 💞
- 它可能比应有的要慢:对于每个项目,它必须克隆一个
- 我花了些时间清理构建过程中的警告。
- 哦!我还安装了 Clippy,它会警告Rust中的高级反模式!
- 我还在思考原始git和我们的git之间 2-3倍 的速度下降。
- 我想我可能要创建两个包文件接口——一个是“通用”的,另一个是“mmap”的,看看哪一个可以提高性能。
- 这也具有一个优点,即它是有风险的代码,这是我在Rust中还没有使用过的!
- 我想我可能要创建两个包文件接口——一个是“通用”的,另一个是“mmap”的,看看哪一个可以提高性能。
2019-01-06 更新
- 我编写了一个提交迭代器!第一次尝试保留了一个
Vec
,包含(Id, Commit)
,这样我们就可以始终从图中选择最近的“下一个”提交(因为提交可能有多个父提交)。- 但在完成“Rust编程”的集合部分时,我注意到
BinaryHeap
可用,它保持条目排序。在JS中,你很少能选择集合的底层存储机制,所以这没想到! - 无论如何。我在这个提交中用
BinaryHeap
代替了Vec
—— 这里。因为这将排序推入类型的Ord
实现中,这为使用多个不同排序的单个迭代器定义打开了可能性。很酷!
- 但在完成“Rust编程”的集合部分时,我注意到
- 在几个长期存在的仓库上进行测试,
git_rs
的结果与git
完全相同!- 然而,它花费的时间大约是 两倍:
git_rs
需要 60ms,而git
需要 30ms。 - 我认为我在这方面有所突破,这与包文件存储有关:每次从包文件中读取都会打开一个新的
File
实例。
- 然而,它花费的时间大约是 两倍:
- 我已经添加了一个 待办事项 部分,以跟踪接下来要做什么!
2019-01-02 更新
- 我实现了 引用加载。这有点痛苦!将
Path
类型翻译过来和过去花了一些功夫。 - 我一直试图了解Rust的惯用用法——我找到了一些资源
- 《Rust API指南》 文档非常实用。
- @mre 的 idiomatic rust 仓库 收集了许多有趣的链接。
- 我还随意浏览了 2018年RustConf的视频
- 因此,我为
Id
实现了FromStr
,希望它能提供更符合惯用法的API ——let id: Id = str.parse()?
2018-12-27 更新
- Rust的感觉更加自然。 这个链 写起来感觉很自然。我甚至只用最小的努力就 交叉索引了一个列表。
- 我将对象接口拆分为Type +boxed读取,并提供了一个将数据还原为Object的方法。这感觉很好!它让人们可以检查,例如,他们是否在引用Blob,而不必将整个Blob加载到内存中。
- 松散接口的闭包接口工作得相当不错,但由于REF_DELTA对象,打包接口需要能够请求一个存储集合的引用。这有点糟糕,因为它很快就会变成“与借用检查器斗争”。目前我认为前进的方向是构建一个包含异构
Box<Storage>
对象的存储集,其中Storage
是一个新的特质,它指定了get(&Id, &StorageSet)
。- 关于松散存储的备注:必须产生一个
git_rs::Error
而不是一个std::io::Error
,这感觉有点奇怪。还有改进的空间!
- 关于松散存储的备注:必须产生一个
- 哦!向这个lib crate添加二进制文件非常容易。现在我们可以使用
git log
查看其他仓库了!
2018-12-21 更新
- 决定放慢速度,并确保这次我为原始数据结构编写了测试。
- 我放弃了原来的
Box<Write>
特质对象设计,改为使用泛型进行对象实例读取和存储格式。
依赖关系
~11MB
~188K SLoC