Monorepo Branching: The Full-Repo Problem
Meta’s monorepo is built around a single main branch, which gives teams unified dependency management and makes large-scale refactoring practical. But it creates a real problem for teams that need to maintain multiple versions of their code. In a multi-repo setup, repository branches handle this naturally: you branch, cherry-pick, and merge between versions. In a monorepo, a branch freezes the entire repository, so unrelated projects and dependencies quickly go stale.
Full-repo branching works for workflows that never merge back to main — for example, a product release where development returns to main once the release ships. Sapling supports this pattern well through the sl bookmark family of commands. The trouble starts when merging back is required. Full-repo merges create commits with multiple parents, widening and de-linearizing the commit graph. In a large monorepo, that degrades performance for operations like sl log and sl blame for every user, not just those on branches.
The all-or-nothing nature of full-repo branches is another limitation. If a team needs to patch a legacy version or maintain a custom variant of just one project, branching forks the whole repository. In practice, teams worked around this by maintaining separate copies of their code, but that meant losing standard branch-management tooling and falling back to error-prone manual patch copying between directories.
Directory Branching in Sapling
Sapling’s directory branching bridges the gap between repository branches and copied directories. A directory in the monorepo behaves like a traditional branch: you create it by copying code, maintain it by cherry-picking and merging changes between directories, and inspect its history with full knowledge of the copies and merges made.
The key advantage is that directory branches merge at the directory level while remaining linear commits in the monorepo’s commit graph. This preserves merge workflows without introducing the scalability problems that come with repo-level merge commits.
Core Commands
Directory branching is built around the sl subtree command. To create a branch, use sl subtree copy to copy a directory or file to a new location, either at the current revision or from any historical revision. Sapling records metadata in the commit tracking the source directory, source revision, and copy relationship, which preserves the complete history of all files in the new branch. For code not yet in the monorepo, sl subtree import creates a directory branch from an external repository branch.
Once a branch exists, sl subtree graft and sl subtree merge cherry-pick or merge changes between directory branches. The stored copy/merge metadata lets Sapling reconstruct directory relationships and perform three-way merges scoped to the specific directory content — finding the common ancestor via the copy metadata and merging exactly as with a standard repository merge, but limited to that directory.
Integration With Build Systems and Tooling
Directory branches have an operational advantage: all branches are visible simultaneously at their latest versions. A single checkout lets CI test against multiple branches, and there are no hidden old branches still in use. At Meta, Buck2 handles inter-component dependencies through config modifiers (buck build -m), which select which branch is used for a given component build.
One tradeoff is code search: queries can return multiple hits per branch, which can clutter results when searching across many branches. Search systems with result ranking can handle this; unranked results become difficult to sift through.
Why Teams Adopt Directory Branching
Directory branching has been widely adopted across Meta’s engineering teams. Some teams combine it with full-repo branching — staying on an older commit to freeze most of the monorepo for stability, then using directory branching to merge in changes for specific projects. Three recurring reasons explain adoption:
- CI cost or disruption risk. Teams separated development and production versions of code to control when changes reach production, avoiding expensive CI runs or major disruptions.
- Long-running experimental collaboration. Large teams working for months on changes that could destabilize the production version. Collaboration at that scale makes a very large stack of diffs impractical as a branching substitute.
- Git migration unblocking. Even when the end goal is consolidation into the monorepo, Git repositories being migrated need an equivalent to Git branches during the transition. It is not always possible to consolidate all Git branches before migrating.
The default expectation in the monorepo remains a single version of code. Directory branching is a solution when those conditions apply — providing proper branching workflows without abandoning monorepo benefits.
Future Work: Lighter Git Integration
Sapling development is also leveraging directory branching to improve Git repository integration through a lightweight migration mechanism. Rather than irreversibly committing all Git repository commits into monorepo history, a soft link points to an external repository, and Sapling loads the Git history on demand when a user requests it. This lowers the entry barrier for Git repositories into the monorepo and supports integrations before committing to full history migration. It will be available as an option to the sl subtree import command when working with external Git repositories.



