microsoft/git, "Why is this fork needed?"
The fork's own statement of purpose, and the clearest sentence anywhere about why the private protocol could not go upstream: partial clone is "the official version of that functionality".
Between 2016 and 2026 Microsoft shipped three clever compatibility layers, operated each one at scale, and then replaced it with the real system it had been faking: a virtual filesystem for Git, a Linux system-call translator, and a compiler hosted on a JavaScript virtual machine. This guide reconstructs all three from the repositories, design documents, release notes and issue threads they left behind, and extracts the condition that decides, before you build, whether a shim is an abstraction or a debt.
One problem, stated without naming any technology: you have to run somebody else's thing on your substrate, and you can either fake its interface or host its implementation.
Every architect meets this fork. Another system has semantics you need and you cannot adopt it directly, because it is too slow at your scale, or it does not run where you need it, or it belongs to a vendor whose release cadence is not yours. So you build a layer that presents the same interface and does something cheaper underneath. The layer ships in months, works immediately for the common case, and is the reason your project is viable at all. Then you spend years discovering that the interface you promised is larger than the interface you implemented, and the difference belongs to somebody else's roadmap.
Microsoft ran that experiment three times in public between 2016 and 2026, on three
unrelated parts of its developer platform, and it ended the same way each time. The evidence
for all three sits in repositories anybody can read, which is the only reason this guide
exists: the network policy in force for this research reached
github.com and nothing else, so every claim below comes from a README, a design
document, a release note, an issue thread or a commit, and none of it comes from a blog post,
a talk or a conference paper.
None of the three replacements removed the cost. Each one moved it to a new boundary and
made it measurable, and in two of the three cases the superseded shim is still shipping
because it is still the better answer for one specific workload. Microsoft's own
documentation tells readers to choose WSL 1 over WSL 2 when their files must live on the
Windows filesystem, and microsoft/git still carries the GVFS protocol that
Git rejected, seven Git releases after Scalar landed in the upstream product.
Scope. This guide covers Microsoft's developer-facing runtimes and tooling: Git at monorepo scale, the Linux subsystem, the TypeScript compiler, and the Mono runtime as a fourth and shorter case. It does not cover Azure's internal infrastructure, Windows itself, Microsoft 365, or the serving architecture behind its AI products, because none of those have a comparable public repository record. It also does not cover the commercial arguments; the sources here are engineering artefacts, and they are silent on revenue.
The common shape across the three programmes, with the two components that every account underplays: the protocol you accidentally own, and the tail you never implement.
Put the three side by side and the same four parts appear. There is an interception point, placed as low as the platform allows, because the lower it sits the more software it fools: a filesystem projection driver in the kernel for VFS for Git, a system-call translation layer for WSL 1, and for the compiler the JavaScript runtime itself, which is an interception point Microsoft did not choose so much as inherit from the decision to self-host. There is an emulated core, the portion of the target's behaviour that is both common and cheap. There is a tail, the portion that is neither, which the shim does not implement and documents its way around. And there is, usually unplanned, a protocol or format you now own, because faking an interface efficiently nearly always requires inventing a side channel.
That last part is the one that turns a shim into a programme. VFS for Git could not fetch
objects one at a time over ordinary Git transport and stay interactive, so Microsoft
specified the GVFS protocol: four operations on three endpoints, including a
GET /gvfs/prefetch that streams packfiles and a POST /gvfs/sizes
that exists purely so the projected filesystem can report file sizes without downloading
content. Read the
protocol document
and you are looking at a second Git wire protocol, with a cache-server tier behind it, owned
and versioned by a team whose product was supposed to be a filesystem driver.
The replacement architecture has a different shape, and it is worth naming precisely because it is not simply "use the real thing". In all three cases what shipped was the real implementation plus a narrow bridge at exactly one boundary, plus an explicit compatibility ledger of behaviour that was dropped. WSL 2 runs a real Linux kernel, built by Microsoft from the stable branch and published as its own kernel tree, and bridges to Windows files across the virtual machine boundary. Scalar runs stock Git and bridges to the object store through a promisor remote. The Go compiler runs as a native binary and bridges to the JavaScript ecosystem over a protocol rather than in process, which the port's own authors flagged as the weak point of the language choice.
Scalar is the cleanest instance of the pattern, because Microsoft wrote the policy down. The philosophy document says it in two sentences: "Scalar intends to do very little more than the standard Git client. We actively implement new features into Git instead of Scalar, then update Scalar only to configure those new settings." What had been a filesystem driver became a list of configuration defaults, and the feature list is a roll call of upstream Git work: partial clone, background prefetch, sparse-checkout, the filesystem monitor, the commit-graph, the multi-pack-index and incremental repack.
Chosen for reach, paid for in blast radius. A kernel-resident driver fools every tool on the machine, and it also panics every tool on the machine, which is what the macOS issue threads record.
Evidence: VFS for Git issue 328, issue 340
The side channel that makes the emulation fast becomes a versioned interface with servers, clients and a compatibility promise. Nine years on it is the reason the Git fork still exists.
Evidence: GVFS protocol v1, microsoft/git README
The artefact that makes leaving possible. The Go port shipped a file listing every behaviour it deliberately does not reproduce, which is what converted an unbounded rewrite into a bounded one.
Evidence: typescript-go CHANGES.md
Six forks in the road, each with what was chosen, what was rejected, the reason stated in the sources, and the condition that flips it.
The remaining three decisions are smaller but carry most of the day-two consequences. The first is what to do with the protocol the shim left behind. Microsoft's answer is visible in the fork's README, and it is unusually candid: the GVFS protocol "is not appropriate to include in the core Git client because partial clone is the official version of that functionality". The protocol lives on because Azure Repos speaks it, so the fork lives on too, and is still cutting releases in 2026 against upstream Git 2.55. A shim's protocol outlives the shim by as long as its servers do.
The second is who owns the defaults after you move upstream. When Derrick Stolee opened a series to audit and document Scalar's configuration in November 2025, the framing was that "the Scalar config options could use some documented justification", and the review that followed pulled in Junio Hamano, Patrick Steinhardt and Johannes Schindelin before the series was taken. Stolee also reported that "while working to justify each config option, I found some stale or incorrect config options". This is the hidden price of hosting the real thing: your tuning becomes a public argument, conducted on someone else's schedule, and some of your settings turn out to have been wrong for years.
The third is the one most teams never make explicitly, and it is the one that decides whether the escape is fundable. The Go port shipped a changes file that enumerates what the new compiler deliberately does not do: Closure header files and most Closure-specific features, a set of JSDoc tag behaviours, and constructor-function expando declarations. The document's own justification is that trimming "makes the implementation much simpler and more like TypeScript". You do not get to leave the compatibility layer while still promising everything the compatibility layer promised.
| Decision | Chosen | Rejected | Because | Flips when | Evidence |
|---|---|---|---|---|---|
| Interception point for Git at scale | Kernel filesystem projection | Upstream Git features | They did not exist yet | Upstream cadence beats your maintenance horizon | VFS for Git README |
| Scope of the replacement tool | Configuration only | A second Git-like product | "We actively implement new features into Git instead of Scalar" | The setting you need cannot be upstreamed | Scalar philosophy |
| Fate of the private protocol | Keep it in a fork | Propose it upstream | "Partial clone is the official version of that functionality" | Your servers can speak the standard | microsoft/git README |
| Linux compatibility on Windows | Real kernel in a VM | Growing the syscall translator | Kernel updates arrive without a Microsoft work item | Cross-boundary file traffic dominates | WSL version comparison |
| Compiler move | Structural port to Go | Clean-sheet rewrite | Existing behaviour is the specification | You have a real spec and conformance tests | Discussion 411 |
| Compatibility surface of the new implementation | Narrow it, in writing | Full fidelity | Simpler implementation, fewer inherited oddities | The dropped tail has no migration path | CHANGES.md |
There are no published Microsoft incident reports in this corpus, for the reason given in section 1. What there is instead is better in one respect: defect threads and release notes, with dates, states and the maintainers arguing in the open.
Sorted by mechanism rather than by product, the failures fall into four classes, and each class attaches to a different part of Figure 2. The first two are failures of the shim. The third is a failure of the escape. The fourth is the failure of never escaping at all.
Everything quantitative in the corpus, with its context and its date. The gaps are listed too, because they are where the risk is.
| Metric | Value | Where | Context | As of | Kind | Source |
|---|---|---|---|---|---|---|
| Repository shape that motivates partial clone | 3.5M files, 500K dirs | Git project | Files in every commit; the example given for narrow cone checkouts | current doc | Reported | design notes |
| Full clone cost at that shape | hours to days, 100+GiB | Git project | Before partial clone, stated as the motivating problem | current doc | Reported | design notes |
| Index cost model after sparse index | O(HEAD) to O(Populated) | Git project | Complexity of status and add in a sparse checkout | current doc | Reported | design document |
| WSL 2 speedup, Linux-side filesystem | up to 20× | Microsoft | Unpacking a zipped tarball, initial versions | 2024-11-19 | Vendor claim | WSL docs |
| WSL 2 speedup, developer commands | 2 to 5× | Microsoft | git clone, npm install, cmake on various projects | 2024-11-19 | Vendor claim | WSL docs |
| WSL 2 write throughput to a Windows drive | 40.4 MB/s | Reporter | 1 GB write test on a mounted drive | 2019-06-19 | Measured, single reporter | issue 4197 |
| WSL 1 write throughput, same drive | 442 MB/s | Reporter | Comparison run in the same thread | 2019-06-19 | Measured, single reporter | issue 4197 |
| Guest memory retained by the VM | 7 of 16 GB | Reporter | Page cache not returned until shutdown | 2019-06-17 | Measured, single reporter | issue 4166 |
| Age of both WSL boundary defects | 7 years | Derived | June 2019 to this guide's research date, both still open | 2026-09-17 | Derived | 4166 |
| Sparse-index defects in one release | 3 | Git project | Corruption, path validation, silent index expansion | Git 2.34 | Reported | release notes |
| GVFS protocol surface | 4 operations, 3 endpoints | Microsoft | The private transport the shim required | protocol v1 | Reported | protocol |
| Attention the retired shim still holds | 6.1k stars | GitHub | VFS for Git, not archived, superseded by recommendation | 2026-09-17 | Measured | repository |
| Mono patch-only period | Jul 2019 to Feb 2024 | Microsoft | Last major release to last patch release before handover | notice current | Reported | README notice |
| WSL source publication | 2025-05-15 | Microsoft | First open source commit in the repository | 2025-05-15 | Reported | commit history |
The two WSL speedup figures are Microsoft's own, undated in origin and carried forward in a document last revised in November 2024; treat them as claims about initial versions, not as a current benchmark. The three throughput and memory figures come from single reporters on single machines, which is exactly the evidence quality you would have about your own users, and the reason they are worth reading is that they were never contradicted in seven years. Four numbers an architect would want do not exist anywhere in this corpus: how many engineers used VFS for Git, what its cache-server tier cost, how long a hydration stall lasted at the ninety-ninth percentile, and the headline speed multiple for the native TypeScript compiler, which lives in an announcement post this session could not reach.
Every source behind this page, graded. There are no blog posts, talks or papers in it, because the network policy for this research reached only one host; that absence is itself worth knowing when you judge the claims.
The fork's own statement of purpose, and the clearest sentence anywhere about why the private protocol could not go upstream: partial clone is "the official version of that functionality".
A design document that reads as policy: implement features in Git, and keep the local tool to configuration. It also names the single exception, the GVFS protocol, and says it is never intended to reach the standard client.
The replacement, itemised: partial clone, background prefetch, sparse-checkout, filesystem monitor, commit-graph, multi-pack-index, incremental repack. Every item is an upstream Git feature rather than a Microsoft component.
The retirement notice, written as a recommendation rather than a deprecation: new deployments should consider Scalar, which combines "the lessons from operating VFS for Git at scale with new developments in Git". The repository still carries 6.1k stars and is not archived.
Four operations on three endpoints, including a prefetch stream and a sizes call that exists so the projected filesystem can answer stat without fetching content. The side channel that made the emulation viable, specified as a public interface.
The upstream answer to the same problem, with the motivating scale stated plainly: 3.5M files per commit, clones taking hours or days and 100+GiB. Introduces the promisor remote as the thing that replaces on-demand hydration.
States the cost model change, from O(HEAD) to O(Populated), and admits in advance that sparse directory entries "violate expectations about the index format". The four-phase plan is the rollout mechanism for a change that cannot be feature-flagged away.
Three defects in one release: index corruption from uninitialised data, broken rejection of paths with trailing slashes, and index_name_pos() silently expanding the sparse index and breaking cache-tree walks.
The sparse feature had a bug in the code deciding which path is inside the checkout cone. The same release declares fetch and pull sparse-index clean, which is the audit advancing one command at a time.
"The 'scalar' addition from Microsoft is now part of the core Git installation", and the diagnose command is lifted out of Scalar into git bugreport. The end state of a five-year upstreaming effort, recorded in two lines.
Background maintenance, one of Scalar's original jobs, becomes a Git command with a scheduler; fsmonitor integration begins. The migration of a private tool's features into the public one, visible release by release.
Derrick Stolee audits Scalar's settings for upstream, finding "some stale or incorrect config options" along the way, and is reviewed by Junio Hamano, Patrick Steinhardt and Johannes Schindelin before the series is taken.
Monorepo-scaling work still in flight in 2026, from the same authors. Note the methodological trap: GitGitGadget closes a pull request when the series is integrated by way of the mailing list, so a closed and unmerged state is not a rejection.
The clearest statement of the thesis, by the vendor: WSL 1 "used a translation layer that was built by the WSL team", WSL 2 ships a kernel, and kernel updates are "immediately ready for use" without waiting on Microsoft. The same page marks cross-OS filesystem performance as a WSL 1 advantage.
40.4 MB/s against 442 MB/s on the same drive under the old shim, reported the month WSL 2 previewed, still open seven years later. The canonical example of a cost moving to the new boundary rather than disappearing.
The guest's page cache stays in the guest until shutdown; a reporter measured 7 GB of 16 GB held. Microsoft's documentation still points at this issue as the tracking item five years later.
Failures in the kernel extension's vnode and file-operation handlers, with failed assertions and panics in close events, and a maintainer goal of failing "gracefully in the kext instead of crashing the system". Still open.
The projection kernel extension colliding with an endpoint protection product. The compatibility surface is not just the emulated system; it is every other driver your users install.
Touching an unrelated FUSE mount panics the machine while the projection driver is loaded. Filed within a year of the macOS effort starting.
Ryan Cavanaugh frames the work as a port rather than a rewrite, and picks the language for structural resemblance, memory layout control, garbage collection and concurrency. The stated weakness, in-process JavaScript interop, is the one the ecosystem later felt.
The compatibility ledger: Closure header files and most Closure features removed, constructor-function expando declarations no longer supported, JSDoc tag behaviour narrowed, all justified as making the implementation "simpler and more like TypeScript".
The staging repository declares the port complete and schedules its own archive. The status table is the interesting part: type checking, emit and build are done, while the programmatic API is "not ready", which is precisely the in-process interop the language choice traded away.
Memory consumption reported against the native compiler's editor path in a large pnpm monorepo. Evidence that the resource profile moved with the port rather than shrinking.
Stewardship from the 2016 Xamarin acquisition, a last major release in July 2019, patch releases until February 2024, then handover to WineHQ while Microsoft's own fork inside dotnet/runtime takes the workloads.
What survived: the Mono runtime as the implementation for mobile, browser WebAssembly and WASI workloads inside the main .NET repository. The reimplementation became a component of the thing it once imitated.
"Initial open source commit for WSL", on the developer documentation file, dated 2025-05-15. The contributing guide now solicits changes to the product's source, and the build instructions are in the repository.
Build prerequisites, a configured developer environment, and an invitation to contribute features and bug fixes to the subsystem itself. The emulation-era product is now a codebase outsiders can compile.
The kernel tree shipped inside WSL 2, maintained in public and built from the stable branch. The "real thing" in this case is a fork of somebody else's project that Microsoft tracks rather than reimplements.
Still cutting VFS-suffixed releases against upstream Git 2.55, including a merge that addresses a 2026 CVE, and still shipping GVFS-specific configuration. The fork is a standing obligation, not a historical artefact.
The oldest dated artefact in this corpus for the Git programme: a user objecting in February 2017 to the Windows 10 requirement. It fixes the start of the decade and it shows the platform coupling from week one.
TypeScript 7.0.2 is the current release at the date of this guide, which is the native compiler shipping under the product's own version number. The announcement text it links to is on a host this research could not reach.
Six rungs. The first three are an evening each and teach you what the shim costs; the last three are what it takes to know whether you can ever leave.
Write a user-space filesystem over a remote or synthetic source, so that
ls and cat work but content is fetched on access. Keep it to a
few hundred lines.
Done when: an unmodified tool traverses the tree without knowing. Teaches: why interception is seductive, and how little code buys how much compatibility.
Log every operation the kernel or runtime sends your layer during one real workload. Sort by frequency, then by whether you implement it faithfully.
Done when: you can state the ratio of calls you emulate correctly to calls you approximate. Teaches: the tail is measurable on day one, and almost nobody measures it.
Produce the file that lists what you will never implement, in the style of the Go port's changes document. Circulate it to the people who will depend on the layer.
Done when: someone objects to an entry, and you either implement it or record the objection. Teaches: the difference between an abstraction and an open-ended promise.
Run the workload against the real system directly, behind a flag, with no emulation. Measure both, including the new boundary you just created.
Done when: you have per-operation costs for both paths on the same workload. Teaches: that the replacement wins on some axes and loses on one, and which one.
Take the smallest behaviour your layer provides that the real project lacks, and propose it there. Track the calendar time from first message to released version.
Done when: you can put a number of months on "we could upstream this instead". Teaches: the exchange rate between your maintenance horizon and somebody else's release cadence, which is the whole decision.
Keep the shim and the direct path in production behind a per-user switch, with the boundary metrics on one dashboard, and a documented rule for which workload gets which.
Done when: the retirement decision cites the dashboard rather than a preference. Teaches: that the honest end state is often both, scoped by workload, exactly as Microsoft's own documentation ended up recommending.
A compatibility layer is cheap where the workload is common and unbounded where it is rare, because the rare part is defined by somebody else's system and changes on their schedule. So the question to answer before you build one is not "can we emulate this", which is nearly always yes, but "can we write down the part we will never emulate, and will our users accept that list". If you can write the list, you have an abstraction with an exit. If you cannot, you have taken on a permanent obligation to track another organisation's roadmap, and the three programmes traced here all show how that ends: you eventually host the real thing, you pay a new and smaller cost at a narrower boundary, and you keep the old layer alive for the one workload it still wins.
What actually worked, given a corpus of one host. These are repository queries rather than search-engine queries, which is the technique this guide was forced into and would now use anyway.
github.com/<org>/<repo>/commits/<branch>/<path>raw.githubusercontent.com/<org>/<repo>/HEAD/README.mdrepo:<org>/<repo> is:issue sort:created-ascpath:Documentation/technical <mechanism>path:docs/adr OR path:**/DESIGN.md <mechanism>repo:<org>/<repo> "philosophy" OR "alternatives considered"repo:<org>/<repo> is:discussion "why"raw.githubusercontent.com/git/git/master/Documentation/RelNotes/<version>.adoc<feature> "has been corrected" OR "broke" in:file path:RelNotesrepo:<org>/<repo> is:pr "regression" sort:created-descrepo:<org>/<repo> is:issue sort:reactions-+1-desc is:openrepo:<org>/<repo> is:issue label:"needs-author-feedback" slower OR "not supported"CHANGES.md OR BREAKING.md OR "intentional changes" in:pathOne caution learned the hard way in this research: a pull request shown as closed and unmerged on GitHub may have been integrated through a mailing list, which is how the Git project works. Check where the project actually reviews code before you read a merge state as a verdict.