Four days ago we wrote about the first two weeks of the grit factory. The first roadmap was done and the second was just starting. As of this morning, all fifteen items of the second roadmap are done too. This post covers what it set out to do, what landed, where it fell short, and what the factory does next.
The goal
The first roadmap built the library. By the end of it, grit-lib could do most of what Git does, but the grit command line exposed only a sliver of that:
grit logcouldn't take a revision. It always started atHEAD, solog main..featurewas impossible, and so were--author,--grep,--sinceand-p.grit commitalways staged everything, sogrit addhad no effect on what got committed.pickandrevertstopped with "grit can't resolve conflicts yet".pushhad no--force-with-lease, so you couldn't push after grit's own history rewrites.clonehad no--depthor-b, andfetchtook no refspec, so you couldn't fetch a pull request.
In most of these cases the library could already do the work and it simply wasn't wired through. So the second roadmap was mostly about the CLI, plus a few real library gaps: commit filtering, pickaxe search, non-interactive hunk selection, a shared conflict state machine and change ids.
We chose options by a few rules:
- Common paths only. An option makes the cut if developers reach for it every day or agents need it. "Git has it" isn't enough.
- Agent-first. Every list is bounded with
-n, every mutation has--dry-run, nothing needs a terminal or an editor, and every result is available as JSON. - Git's name where it's well known, like
--since,--ff-onlyand--force-with-lease. A grit name only where Git's is a known trap. - The library does the work. Each option is a
grit-libcapability with its own Rust tests, and the CLI just passes it through. - One way per concept. Revision selection, pathspecs, diff format, message input and conflict handling are shared argument groups, not a copy per command.
The roadmap had fifteen items. Two were about speed: a census that measures every command on every kind of repository against git, for time and peak memory, and an item to close the biggest gaps it found. Then came thirteen command items, in this order:
| # | Item | Workstream |
|---|---|---|
| 1 | Revision selection and commit filtering (log, revs) | CLI foundations |
| 2 | Shared diff output (diff, show, log -p) | CLI foundations |
| 3 | Change ids on every commit | History |
| 4 | Precise commits: status, add, commit | Worktree |
| 5 | Branches, tags and upstream tracking | Branching |
| 6 | Remotes: fetch, pull, push | Network |
| 7 | Conflicts you can work through: merge, pick, revert | Branching |
| 8 | Rebase and history editing | History |
| 9 | Hunk selection for agents (replacing -p) |
Worktree |
| 10 | Clone and remote management | Network |
| 11 | Inspection extras: blame, reflog, shortlog, resolve | History |
| 12 | Workspace extras: stash, worktree, reset, rm, mv, clean, config, init | Worktree |
| 13 | Consistency audit, docs and the JSON contract | Docs |
What got done
The plan was written on the evening of October 19, and the last item finished on the morning of October 24. In that time:
- About 1,100 commits landed on
main, adding roughly 400,000 lines and removing 97,000. - Most of the code went into the library.
grit-libgained 213,000 lines andgrit-cli61,000. Most of the rest is benchmark baselines and the JSON schemas. - Five releases shipped, v0.5.15 through v0.5.19.
Here are the factory's running totals, compared with the last post:
| October 20 | October 24 | |
|---|---|---|
| Agents launched | 2,656 | 3,776 |
| Agent runs | 4,314 | 6,196 |
| Agent-hours | 2,544 | 3,562 |
| Reviews | 1,601 | 2,343 |
| Changes landed | 653 | 952 |
| Tokens | 24.5 billion | 37.8 billion |
Highlights
Revisions, filters and diffs that work everywhere
log and revs now share one set of revision arguments: ranges, ^X, @{u}.., --since, --author, --grep, --first-parent, pathspecs, --merges, --all and the rest. Filters run inside the walk, so -n counts the commits that match, as in Git.
$ grit log v2.48.0..v2.49.0 --author Junio --no-merges -n 3
683c54c change sxyormnr Junio C Hamano 1 year ago Git 2.49
4b68faf change kvxmsovl Junio C Hamano 1 year ago A bit more updates after -rc2
87a0bdb change nwwmolmw Junio C Hamano 1 year ago Git 2.49-rc2
The same goes for diff output. diff, show and log share --stat, --name-only, --name-status, -U, -w and rename detection, and their JSON uses the same files[] shape everywhere.
Change ids on every commit
Every commit grit makes now carries a change-id header, in the format agreed on the Git mailing list with Jujutsu and Gerrit, and the same one GitButler writes. The id stays with a change through every rewrite, so you can follow a change across amends, rebases and history edits even though its commit id changes:
$ grit log
8a03b68 change rrtltpnn Scott Chacon just now second
2629fde change qnksprqn Scott Chacon just now first
$ grit history reword HEAD -m "second, reworded"
$ grit log
57a46da change rrtltpnn Scott Chacon just now second, reworded
2629fde change qnksprqn Scott Chacon just now first
The header sits in the commit object itself, where git fsck accepts it and git cat-file -p shows it. A commit made elsewhere, without the header, gets the same deterministic id Jujutsu would show, and grit never rewrites a commit just to add one. The JSON says which is which with change_id_stored.
Building this turned up a real bug: every history rewrite was silently dropping all extra commit headers, including change ids written by Jujutsu or GitButler. Rewrites now carry every extra header forward, except signatures.
Commits and conflicts you can control
grit commit still stages everything by default, but --staged commits only the index and commit -- <paths> commits only those paths. Agents get something better than add -p: grit diff --json gives every hunk a stable id. grit add --hunks, --lines or --patch-file then stages exactly that selection, and a stale id is rejected instead of being applied to the wrong lines.
Conflicts work the same way everywhere. merge, pick, revert and the new top-level grit rebase stop with conflict markers and a recorded operation, and then take --continue, --abort or --skip. merge --check and rebase --check tell you whether there will be conflicts without touching the working tree. status reports the conflicts and the operation in progress, with next steps in its JSON.
Remotes
fetch takes refspecs, so you can fetch a pull request head. pull has --ff-only and --rebase. push has --force-with-lease and --dry-run. clone has --depth, -b and partial clone with --blobs none. remote gained remove, set-url, rename, show and prune.
A JSON contract
The last item was a pass over every command for consistency. -n always means a limit, every mutating command has --dry-run, and message, author and path arguments behave the same everywhere. Every command's --json output now has a published, versioned schema: 114 of them, snapshot-tested so a change to the output shows up in review. The grit skill agent guide was updated with recipes for precise commits, conflicts, rebasing and pushing with a lease, fetching a pull request, and shallow clones.
Speed
The census gave us a daily benchmark of the everyday commands against git, on Linux with a 4-core Xeon and git 2.43. Out of 22 local measurements in the latest run, grit is faster on 10, level on 2 and slower on 10. On the commands that do real work it's ahead. A cherry-pick takes 0.56× of git's time, a merge 0.72×, a switch 0.84× and a commit 0.85×.
It's behind on the commands that should be nearly instant. show and listing branches with thousands of refs take about 2× git's time, log 1.65× and a clean status 1.4×. In absolute terms those are a few milliseconds each, which is often startup cost. The speed agent works on the worst gap every day, and the full daily table is at maint.grit-scm.com/cooking/bench.
Where it fell short
Every item is marked done, but "done" means the plan's steps landed and passed review. It doesn't mean every option works the way a person would expect:
log --followdidn't ship. It was a P0 option in the first item and needs a rename-following walk in the library. It's back on the list.- Some bugs only show up in real use. In the last two days, using grit day to day turned up two that the tests didn't catch.
grit statustook almost 8 seconds in grit's own repository, because counting how far a branch was ahead ofmainslowed down quadratically on a history that keeps mergingmainback in. It also reported the wrong numbers. Andgrit log -n <N> -S <text>failed on git/git: one fast path tried to read a submodule's commit as if it were in the main repository (#1667). Both commands had tests. Neither test looked like a real repository used in a real way.
The second kind of failure is the reason for what comes next.
What's next: dogfooding
The factory now has two modes. In roadmap mode it plans and builds roadmap items, which is what it has done for the past three weeks. In dogfood mode it starts no new roadmap items. Instead it uses grit the way different kinds of people would, all day, on every platform. With the second roadmap done, that's the mode it runs in next.
Every six hours a wave starts on Cursor's Linux cloud and on each of our own machines. Each session in a wave plays one of ten personas:
| Persona | What they do with grit |
|---|---|
| Individual developer | Uses grit as their daily driver on a few personal projects |
| Team developer | Shares a repository with teammates on git: rebases, force-pushes with lease, cherry-picks hotfixes |
| Large-monorepo engineer | Works in hundreds of thousands of files and deep history, and watches time and peak memory |
| Tool builder on grit-lib | Writes Rust tools that link the library: a changelog generator, a linter, an editor integration |
| Pluggable-backend author | Stores objects and refs somewhere other than the filesystem |
| Git server builder | Serves repositories over smart HTTP to real git clients |
| New-CLI author | Builds a different version-control front end on grit-lib, and notes every bit of logic stuck in grit-cli |
| Automation and CI scripter | Drives grit from scripts, CI and agents, and relies on JSON and exit codes |
| Git power user | Alternates git and grit in repositories with every advanced feature turned on |
| Newcomer | Installs grit, reads the help and follows their nose |
Each session designs its own scenarios for its persona, runs them with both grit and git, compares time and peak memory, and files what it finds as issues labeled by persona and platform. Slowness, memory bloat and gaps in the library's API count as bugs too. The issue loop triages and fixes them ahead of other work, and every fix has to address the root cause in grit-lib and report speed and memory before and after. The next wave checks each fix again on every platform. A fix that didn't hold is reopened, and one that's reopened twice comes to me.
The first two roadmaps were about building grit. This phase is about whether people can actually rely on it. You can watch the waves, and the bugs they find, live at maint.grit-scm.com.