Sapling SCM
Sapling is a source control system developed and used at Meta that places special emphasis on usability and scalability. Git and Mercurial users will find many of the basic concepts familiar, and that workflows like understanding your repository, working with stacks of commits, and recovering from mistakes, are substantially easier.
When used in conjunction with the Sapling server and virtual filesystem (not yet available publicly), Sapling scales to repositories with 10’s of millions of files, commits, and branches. While inside Meta it is primarily used for our large monorepo, the Sapling CLI also supports cloning Git repositories and can be used by individual developers to work with GitHub, etc.
Why make a new version control?
Sapling began 10 years ago as an effort to make Meta’s monorepo scale in the face of ever increasing engineering growth. Publicly available source control systems were not, and still are not, capable of handling repositories of this size. Instead of moving away from the monorepo and sacrificing engineering velocity, we decided to build the source control system we needed.
Along the way we realized there were also large opportunities to increase developer velocity by improving the source control experience. By investing in new UX, we’ve made it possible for even new engineers to understand their repository and do things that were previously only possible for power users.
Additionally, as we developed Sapling we ended up with internal abstractions that happened to make it straightforward for us to add Git support. This idea that the UX and scale of your version control could be separated from the repository format has allowed us to, in effect, have our cake and eat it too by letting us use our scalable system internally, and still interact with Git repositories where needed. We hope that this pattern might also provide an example path for how source control could evolve beyond the current industry wide status quo.
Basic concepts
The easiest way to understand the basic usage of Sapling is to see it in action. Below we clone a repo, make some commits/amends, undo some changes, and push the work.
# Clones the repository into the sapling directory.
# For git support, it uses git under the hood for clone/push/pull.
$ sl clone --git https://github.com/facebookexperimental/sapling
remote: Enumerating objects: 639488, done.
...
$ cd sapling
# 'sl' with no arguments prints the smartlog.
# It shows the commit you are on (@) and all of your local commits
# (none in this example since we just cloned). For each local commit it shows
# the short hash, the commit date, who made it, any remote bookmarks,
# and the commit title. With some configuration it can also show
# information from inside the commit message, like task numbers or
# pull request numbers.
$ sl
│ Use cached values
~
# Checkout a commit in main that I want to debug.
# The dashed line in smartlog indicates we're not showing some commits
# between main and my checked out commit.
$ sl goto a555d064c
$ sl
╷
╷
│ workingcopy: Give Rust status exclusive ownership of TreeState
~
$ vim broken_file.rs
$ vim new_file.txt
# 'sl status' shows which files have pending changes. 'sl st' also works.
# 'M' indicates the file is modified. '?' indicates it is present but not tracked.
$ sl status
M broken_file.rs
? new_file.txt
# 'sl add' marks the untracked file as new and tracked.
# It will now show up as 'A' in status.
$ sl add new_file.txt
$ sl commit -m "Fix bug"
$ vim broken_file.rs
$ sl commit -m "Add test"
$ sl
╷
╷
╷ │ Add test
╷ │
╷
╭─╯ Fix bug
│
│
~
# Go to the previous commit.
$ sl prev
$ sl
╷
╷
╷ │ Add test
╷ │
╷
╭─╯ Fix bug
│
│
~
# Amend the first commit
$ vim build.sh
$ sl amend
95e6c6b863b7 -> 35740664b28a "Fix bug"
automatically restacking children!
rebasing 13811857cc1e "Add test"
13811857cc1e -> d9368dec77e1 "Add test"
# Note how the stack remained together, despite editing the first commit.
$ sl
╷
╷
╷ │ Add test
╷ │
╷
╭─╯ Fix bug
│
│
~
# You can optionally create a local bookmark if you want.
$ sl bookmark my_task
$ sl
╷
╷
╷ │ Add test
╷ │
╷
╭─╯ Fix bug
│
│
~
# Abandon the second commit.
$ sl hide -r d9368dec7
$ sl
╷
╷
╭─╯ Fix bug
│
│
~
# Let's bring back the original version of the first commit.
# Note how smartlog marks the original commit as obsolete ('x')
# and explains how 95e6c6b86 was amended to become 35740664b.
$ sl unhide -r 95e6c6b86
$ sl
╷
╷
╭─╯ Fix bug
│
│
├─╯ Fix bug
│
│
~
# Rollback the amend we did earlier.
$ sl unamend
$ sl
╷
╷
╭─╯ Fix bug
│
│
~
$ sl status
M build.sh
$ sl revert --all
# Push the commit to the remote main branch.
# Note, when pushing to Git you would have to rebase onto main
# first via 'sl rebase --dest main'. When pushing to a Sapling server,
# the server would perform the rebase for you, as shown here.
$ sl push --to main
$ sl
│ Fix bug
~
Caveats
Some noteworthy caveats about Sapling:
- Some of the design decisions are geared towards corporate, always-online, single-master, rebase-instead-of-merge, monorepo environments. While Sapling is flexible enough to work outside of these constraints, it is most polished and battle tested in that kind of environment.
- Git support may have some remaining kinks to be worked out.