百科.dev
全部条目AI 编程趋势榜开源项目技术资讯提交条目
登录
< 返回工具列表
G

git-rs

> 编程语言
开源

git, 实现于 Rust 中, 仅供娱乐和教育使用 :crab:

1.4K stars0 点赞0 次浏览
访问官网GitHub

工具介绍

git, 实现于 Rust 中, 仅供娱乐和教育使用 :crab:

git-rs

Implementing git in rust for fun and education!

If you're looking for a native Rust Git implementation ready for use in anger, you might consider looking at gitoxide instead!

This is actually my second stab at it, so big blocks will land in place from my first attempt. I'm trying again this year after reading more of "Programming Rust" (Blandy, Orendorff).

TODO

  • Read objects from loose store
  • Read objects from pack store
    • Read packfile indexes
    • Read delta'd objects
    • Fix interface so we don't need to run open for each read()
    • BUG: certain OFS deltas are misapplied.
      • Isolate the error case
      • Fix it
  • Load refs off of disk
  • Parse git signatures ("Identity"'s)
  • Create iterator for walking commit graph
  • Create iterator for walking trees
    • Materialize trees to disk (post gitindex?)
  • Create index from packfile
    • Rename Storage trait to Queryable
    • Rework object loading API from <Type + Boxed reader> to "we take a writable object"
      • Carry the rework out through StorageSet
    • Create the index
    • Wrap it in a nice API
  • refs v2
    • Load refs on demand
    • Load packed-refs
  • .git/index support
    • Read git index cache
    • Write git index cache
  • Create interface for writing new objects
  • Add benchmarks
  • Code coverage
  • Create packfile from list of objects (API TKTK)
  • Network protocol
    • receive-pack
    • send-pack
  • Try publishing to crates
    • Write documentation
    • Use crate in another project

PLAN

2022-04-30 Update

  • I did a bit of optimizing and my (completely unscientific) benchmarking has us pretty close to native git log!
  • I'm currently measuring the performance of git log --pretty=oneline >/dev/null vs. git_rs_log >/dev/null against a local checkout of nodejs/node.
    • This is on a M1 Pro Max Macbook Pro.
    • git_rs_log started out at ~500ms for a complete walk of the repo history. Vanilla git was seeing ~280-300ms.
  • I'm used to using DTrace + flamegraphs to profile, but to my dismay using DTrace requires booting into recovery mode on modern macOS & disabling system integrity protection.
    • at least, that was what my first investigation turned up. It looks like there may be other options I missed.
  • My coworker Eric suggested using macOS's Instruments.app instead via cargo-instruments which worked a treat.
  • Running cargo instruments necessitated exposing a way to set the current working directory for git_rs_log, so I added clap.
  • I'm pleased to report we were able to bring git_rs_log down to 300-320ms for a full walk of node's history. Here's what I did:
    • Switching deflate2 from it's miniz_oxide backend (the default, written in rust) to native zlib was the biggest boost
    • Switching a sort_by_key to sort_unstable_by_key in packfile index reads was a small (5-7ms) win. (Packfile indices have a fanout table, a list of ids in ascending order by id value, and a list of offsets-in-the-packfile whose order corresponds to the ids. In order to use a packfile index to read from a packfile, though, you need to be able to read the offset for your incoming id request and the next id in the packfile in offset order. Hence the sort_by_key call – once we've loaded the ids and offsets, we have to keep a mapping of position of id -> position in packfile.)
    • Tuning up the commit parser gave me another 10ms or so. Out of expediency, I had originally treated commits as HTTP transaction-like – newline-separated key/value headers followed by a double newline then the message. Now I actually store the well-known fields directly on the struct in parsed form. (There's still room for improvement here, too!)
      • This required adding an Id::from_ascii_bytes(&[u8]) for hexadecimal-encoded ids
        • Before this you'd have to bounce through std::str::from_utf8 which validates that the vector contains valid utf8 before we validate that it only contains hexadecimal chars; now we can do both in one step.
  • I'm pretty happy with that performance (for now), so I'm looking for something to pick up next. Options include:
    • Support for the worktree index file, .git/index. This is the start of the path for writing objects to the Git database.
    • Better support for refs.
    • Support for SHA256 object format. (Vanilla Git supports this now, so it'd be interesting to dig into how it works.)
    • Another Rust project, for a change of pace.
      • Postgres change data capture support, a la Debezium (but not tied to kafka.)
      • A return to WASM text parsing (I have a private project called "watto" for this.)

2022-04-27 Update

  • I'm back! I finally have some free time (and, maybe more importantly, available attention) so I'm revisiting this project after a few years.
  • Most recently, I fixed a bug with the "identity" parser. "Identities" are the bits of commit and tag metadata that look roughly like Humanname <email> 10100100100 +7300.
    • It turns out my parsing had a bug: it was dropping the last character of the email.
    • This was surfaced to me -- not by tests as it should have been, to my embarrassment -- but by trying to run git_rs_log against the Node repository. It turns out someone had committed without an email: Foobar <> 1010020203 -4000.
    • My off-by-one error turned this into a panic, with the program safely -- if unexpectedly -- crashing on that input.
    • I fixed the parser bug and golfed the parser itself down using match statements and constants.
  • While I was in that part of the code, I did a little editorializing: renaming identity to human_metadata.
  • I also took the oppportunity to lazily parse the human metadata. There's no need to walk that entire bytestring unless someone asks for it.
    • It turns out that we do ask for it during the course of git log: if we have multiple branches we need to load up the commit metadata to compare timestamps, as the output order depends on commit timestamp.
    • But for straightline chains of commits we don't need to load any of that up.
      • This saves ~10-20 milliseconds on git log in the node repository.
  • Burying the lede: git_rs_log is about 100-200ms slower than git log --pretty=oneline, run against the node repository.
    • Well, that certainly seems like a useful north star, does it not?
    • Where are we spending time that git isn't?
  • Buoyed by my recent deep dive into the LLVM ecosystem, I briefly explored profile-guided optimization.
    • I'm happy to report that I got a working setup and understood the results.
    • I'm less happy to report that, well, the results weren't stunning. This kind of checks out: if the performance gap is down to the number of I/O system calls we're making, assuming git makes fewer system calls that's where our perf gap will be.
    • So that's my current number one goal.
  • My number two goal is to modernize this repo and bring it up to the Rust standards that I picked up from $dayjob (and in particular, via @fishrock123.)
    • That means:
      • implementing more standard traits on types,
      • adding integration tests,
      • adding type docs,
      • and being a little bit more circumspect about what the crate exposes as a public API.

2019-02-08 Update

  • It's been a minute!
  • As you might have seen, figuring out packfile indexing has forced a lot of changes on the repo.
    • There's now a src/pack/read.rs file that holds generic read implementations for any BufRead + Seek.
    • The signature of the Storage trait changed -- instead of returning a boxed read object, it now accepts a Write destination.
    • Further, Storage is now Queryable (a better name!).
      • Because we moved from returning a Box to accepting generic Write, we could no longer box Queryables.
        • I didn't know this about Rust, so TIL!
      • StorageSet objects had to be rethought as a result -- they could no longer contain Box'd Storage objects.
        • Instead, we put the compiler to work -- because storage sets are known at compile time, I implemented Queryable for the unit type, (), two types (S, T), and arrays of single types Vec<T>.
        • This means that a StorageSet may hold a single, top-level Queryable, which might contain nested heterogenous Queryable definitions.
          • It gives me warm, fuzzy feelings :revolving_hearts:
  • You might also note that we're not actually done indexing packfiles. :scream:
    • Here's the sitch: in order to create a packfile index, you have to run a CRC32 over the compressed bytes in the packfile.
    • The ZlibDecoder will pull more bytes from the underlying stream than it needs, so you can't take the route of handing it a CrcReader and get good results.
    • It's got to be a multi-pass deal.
      • The current plan is: run one pass to get offsets, un-delta'd shas and types.
      • Run a second pass to resolve CRCs and decompress deltas. This can be done in parallel.

2019-01-23 Update

  • It's time to start indexing packfiles.
    • This'll let us start talking to external servers and cloning things!
  • However, it's kind of a pain.
    • Packfiles (viewed as a store) aren't hugely useful until you have an index, so I had designed them as an object that takes an optional index from outside.
      • My thinking was that if an index was not given, we would build one in-memory.
    • That just blew up in my face, a little bit. :boom:
    • In order to build an index from a packfile you have to iterate over all of the objects.
      • For each object, you want to record the offset and the SHA1 id of the object at that offset.
      • However, the object might be an offset or a reference delta.
        • That means that in order to index a packfile, you've got to be able to read delta'd objects at offsets within the packfile (implying you already have the Packfile instance created) and outside of the packfile ( implying you have a StorageSet.)
        • In other words: my assumptions about the program design are wrong.
    • So, in the next day or so I'll be reversing course.
      • It should be possible to produce a Packfile as a non-store object and iterate over it.
      • The "store" form of a packfile should be the combination of a Packfile and an Index (a PackfileStore.)
        • This means I'll be splitting the logic of src/stores/mmap_pack into "sequential packfile reads" and "random access packfile reads (with an index.)"
  • It's fun to be wrong :tada:

2019-01-21 Update

  • Well, that was a fun bug. Let's walk through it, shall we?
    • This occasionally showed up when a delta would decode another delta'd object.
      • I found a hash that would reliably fail to load.
      • We'd fail the read because the incoming base object would not match the 2nd delta's "base size". [Here][ref_10].
      • Removing the check to see if I got the deltas wrong would cause the thread to panic -- the delta's base size wasn't a lie.
    • First, I switched back to my old mmap-less packfile implementation, because I recently touched that code. "Revert the thing you touched last" is a winning strategy

GitHub Issues· 0 开放

在 GitHub 查看全部

暂无开放 Issues,或尚未同步最近议题。

核心特点

  • •[x] Read objects from loose store
  • •[x] Read objects from pack store
  • •[x] Read packfile indexes
  • •[x] Read delta'd objects
  • •[x] Fix interface so we don't need to run open for each read()
  • •[x] BUG: certain OFS deltas are misapplied.
  • •[x] Isolate the error case
  • •[x] Fix it
  • •[x] Load refs off of disk
  • •[x] Parse git signatures ("Identity"'s)

> 标签

Rust

暂无评论,来聊聊你的看法吧

> 工具信息

发布日期2026年8月1日
最后更新2026年9月17日
分类编程语言
定价开源

> 相关工具

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言