Advantages of Monolithic Version Control
danluu.com
danluu.com
Here's the thing: if all the code is in a single repo, it's really easy for me to find all users of a given function. This means that, if I need to alter the behavior of a function, I can sanely pursue any of three options:
1. Know for a fact I can alter the behavior without impacting
anyone.
2. Refactor across the whole code base in one shot.
3. Deprecate the API and provide a replacement immediately,
then work with teams to get off the deprecated API.
In all three cases, I can trivially know exactly what I'm impacting and make a decision on how I want to impact it. As a bonus, not only can I much more directly control deprecation times, but it's dramatically less likely that one of my client programs keeps deploying with an outdated, insecure/buggy version of my library.In my view, the whole point of semver is handling situations wherein I cannot do that. For example, if I publish a library on GitHub, it's simply not reasonable for me to know who's using my library and why. Thus, semver provides a contract between me and you: since I can't know what you're doing, I promise not to do certain things with my library so that you can use it with some confidence. But there is a cost here: it's harder for you to track upstream, it's harder for me to know who's still using what in the older library, and it's harder for me to figure out how I can help people upgrade.
I'm emphatically not saying semver is bad, but I am saying that it's deliberately compensating for source code federated across many repos. A properly managed monorepo keeps you from having to worry about it.
In an organization where everyone's familiar with and works on every codebase, you don't need that. But my experience is that codebases need to have an owning team and teams that are larger than ~12 become unwieldy, and from what I've heard from friends who work there even e.g. Facebook tends to work in smaller teams than that.
So fine, I know that the people who depend on my library are the Foo team in Australia and the Bar team in New York. But I still wouldn't want to refactor their codebase without, at least, a code review from someone on that team - in which case I want separate pull requests for the library change, the project foo change and the project bar change. And I don't want one PR to block the others, so I want to make a library release but have foo depend on the old version until their change goes through. In short I think I'd always take option 3.
The producer in that case is obviously completely unaware of what you're doing so they cannot make decisions based on that fact. They may, for example, deprecate an API and provide no direct equivalent because they don't know anyone's relying on it. Then you suddenly have to come up with additional code to restore an equivalent API...
The subrepo approach is OK if you're tracking some external code. In that case it's comparable to semver but possibly better integrated with your build/tooling, you win something. But if it's internal to your organization, you're hacking around what a single repo could provide you.
I cannot imagine that this is true for Google. There is no way Gmail is locked to the same version as Search or the same version as Maps.
Basically, I think lmm and gecko are both right: there should be exactly one repository per project, where a project is defined in terms of a set of stable interfaces with the outside world. Whether the total set of stuff your company is doing should be one project or a few or many, depends on the nature of the stuff you do, what it has to deal with, and your company structure; but the answer to that should determine your repository structure.
I searched but I honestly can't find them. The only things I can think of are: implementation problems (repo too large, every git pull takes more time than strictly necessary for your project) and tainting tools like "git log".
Those don't seem very fundamental. Solvable by a "monogit" wrapper, if someone put their mind to it. Or are they?
Is there a fundamental problem I'm missing? I feel stupid for asking, given how matter of fact he dismissed it :(
EDIT: I guess what I'm trying to say is: monorepos feel, theoretically, like a strict superset of many independent ones. It's just the tooling that makes it less convenient.
At some large companies the simple act of making a commit takes the better part of an hour. Same with checking out, updating, etc.
This has serious implications for the development style and productivity. At the very least, not every developer can adapt to such a style.
"It's too slow" is not a trivial problem to solve.
Anything but.
Depends on your viewpoint, but I'd argue it's not actually that hard to solve unless you also require you solve it within the context of Git. For example, in most systems, you can do something called a narrow checkout, where you only grab part of the tree of the whole repository. Subversion, Monotone, Perforce, and (soon) Mercurial all support this, whereas Git does not--and, the last I looked at the Git protocol, it wouldn't have been sane to implement. (Been about two years at this point, so I apologize if that has changed.)Beyond that, even local commit in a single repo is actually pretty easy to solve if you have file watchers (powered by e.g. inotify, kqueue), which at least Mercurial does, and I'd honestly swear Git does also.
At that point, I don't think there's a lot left that inherently has to make commits take the better part of an hour. If you are at such a company, please take a moment to run strace or something; I'm really curious where the tool's spending so much time.
Git's "sparse checkout" feature can grab a part of a larger repo from memory.
Since Version 1.9 (released early 2014), you can use such a repo like a normal one (push, clone from it, ...)
Go on vacation for two weeks? You get to go on another vacation while you wait for your monorepo to update. Similarly, good luck ever trying to work from a coffee shop -- at the very least you probably have 50mb of updates because everyone in the company is committing code all the time.
That said, I find your note a bit amusing. I'd have agreed a decade ago, but nowadays, 50 MB should take effectively zero time for you to both download and apply. It's worth revisiting things like this as LANs and the internet get faster; I think the speed comment is a bit outdated now.
I agree that it should take effectively zero time for me to download and apply 50MB of updates, but poor quality internet is still pretty easy to find (working with plane wifi) -- maybe my coffee shops have worse internet than yours!
If you care, you should read about how Google makes sure this doesn't happen.
> By putting our revision history in the cloud we provide engineers with complete access to all the source, and yet almost no time is spent checking out code.
http://google-engtools.blogspot.com/2011/06/build-in-cloud-a...
http://google-engtools.blogspot.com/2011/08/build-in-cloud-h...
Without any barriers, it's way too easy for things to devolve into spaghetti code.
So let's say you want to update a repo which depends on another one:
Update project A, commit changes.
# Your product hasn't changed at this point
Update project B, commit changes.
# Your product hand't changed at this point
In metarepo, checkout to the new 'master' branches of Projects A and B, commit that to metarepo
# Your product is now updated!
Both of which are far greater of a pain in the ass than monorepos.
The solution is that changeA must be backwards compatible. In a complex system you always need to have some kind of backwards compatibility, at least for some time.
In the end both (mono and multi repos) doesn't really work in huge, complex scenarios.
>> With multiple repos... having to split a project because it’s too big or has too much history for your VCS is not optimal... With a monorepo, projects can be organized and grouped together in whatever way you find to be most logically consistent, and not just because your version control system forces you to organize things in a particular way.
Uuuuh, if your one project has to be split because it's too big for your VCS, then you aren't going to make that thing smaller by putting multiple projects in with it.
>> A side effect of the simplified organization is that it’s easier to navigate projects.
That's a UI issue. Build a better UI, don't use a dirty hack, especially one that has other implications.
>> A side effect of that side effect is that, with monorepos, it’s often the case that it’s very easy to get a dev environment set up to run builds and tests.
With the growing trend of package managers being able to install dependencies straight from a git repository, I don't see this being an issue much longer. Again, this is a UI issue.
>> This probably goes without saying, but with multiple repos, you need to have some way of specifying and versioning dependencies between them.
Yeah, no shit, that's just good software development. The argument here is that a monorepo lets you be lazy.
It's a huge benefit to be able to have all your mobile apps and web apps and whatever in the same repo, because then you can easily see who calls what, and how various RPCs are used, etc. Don't knock it until you've tried it.
(I used to think monorepos were dumb, but over the course of several years I came around.)
If you are developing a big system with lots of components that need to work together (e.g. an embedded machine vision system with a multitude of related data-recording, calibration, simulation & test utilities and subsystems), then the matrix showing which versions and configurations are compatible with one-another quickly grows to an unmanageable size.
Unless you have a god-like configuration management system, the only practical approach is to go "green trunk" and co-version all of your components. Granted, this doesn't necessarily force you to use a single repo, but a single repo is (at least initially) the simplest approach.
Of course, you could go "old-skool" and define interfaces up-front then freeze them, but this just slows your development down to a snail's pace. Better (IMHO) to co-version your components then lean on your integration tests to maintain compatibility.
If you change an Open Source library there is no way to check with users of your library, because they are hard to track down and use various different build processes. In a monorepo, this is much easier. You could create an automatic "build and test everything".
And while a monorepo would help a lot with discoverability, I think that the promises this article makes about cross-project changes are a bit optimistic since it ignores the difficulty of doing deployments in live distributed systems. Even if you have a single git repo, there will certainly be an order you need to deploy them in so that things don't break when the API consumer gets updated before the API provider. Google/FB/Twitter/etc certainly have a better deployment system than we do, so maybe for them it's easy, but it's not something that is just solved by going to a monorepo.
Google is using a Perforce lookalike that is not actually Perforce, but they're moving to Mercurial. Subversion also scales up to similar sizes, although I confess to not knowing what "similar" means. (I do know that Google did an experiment and concluded that Subversion would scale to their needs, but they were already committed to Perforce by that point.)
EDIT: Got corrected by someone who has good reason to know.
Do you have a source for this move to mercurial? This is the second time I've heard it, but couldn't find a source before.
That should tell you how well Perforce handles huge repos, and I imagine it's not gotten better.
Believe me, I'm not saying that perforce is flawless. But there's a reason its used heavily in the game dev and media industries. Does it suck to administer? Certainly. Does that mean that Google's problems with scaling or operating something like perforce apply to most of us? No. Most of us can resolve issues with Perforce by applying some limitations and governance because we're not operating at the massive and distributed scale that google is.
I'm a former such engineer; I still prefer smaller repos. There's enough engineers at both companies that I can assure you such opinions (and knowledge) are quite varied.
> it’s often the case that it’s very easy to get a dev environment set up to run builds and tests.
I've worked with both; in both cases, the workflow was essentially a checkout, followed by a build, followed by running the tests. I've found this is more a product of the environment (i.e., do the developers care about tests being easy to run) than the VCS in use.
> With a monorepo, you just refactor the API and all of its callers in one commit.
I'd restate this: with a monorepo, you must refactor the API and all of its callers in one commit. You cannot do it gradually, or you will break someone. A gradual refactor is only possible in multiple repositories, specifically multiple repositories that obey something resembling semantic versioning. You make your breaking change, and because it is a breaking change, you up the version to indicate that. Reverse-dependencies wishing to update then must make the change, but can do so at their leisure.
I've seen some truly heroic work done to get "APIs with thousands of usages across hundreds of projects get refactored". Sometimes it _is_ easy: you can track down the callers with a grep, and fix them with a perl script. But I think you must limit yourself to changes of that nature: massive refactors too great for a script would leave you to edit the call sites. Though, with thousands of callers, this is probably true anyways, I find having to move even a couple dozen through a major change (such as one where the paradigm expressed by the API is completely wrong) is difficult if you must update them all at once.
Last, the most common "monorepo" system I've seen is Perforce, and compared to git it has such stark usability issues that I'd rather not go back to it (staging, git add -p, bisect, real branches). This comment though,
> where it’s impossible to do a single atomic commit across multiple files
I would hesitate to use "atomic" to describe commits in Perforce; if you check out CL X, make some changes, and "commit" ("submit" is Perforce's term), the parent of your new CL might be Y, not X, and you might get no warnings about this, either. Collisions on an individual file will prevent the submit from going through, but changes on separate files (that together represent a human-level merge conflict) , will not get caught. (They wouldn't show as merge conflicts in git, either, but git will tell you that someone updated the code, and refuse your push; unit tests must catch these, but in Perforce's case, you must run them after making your change permanently visible to the world.)
These days, with the use of shelving, there are integrations with CI tools such as Jenkins which provide "pre-flight" builds before checkin - thus before your changes are world visible.
If you didn't reuse code (across repos) then you wouldn't have any of the problems that a monolithic repo solves.
What I don't get is the example from Twitter. If I need a fellow developer to fix projectB and projectC for me to fix projectA, I ask him to fix it. As soon as he has committed it to their respective repos, the buildserver would pick it up, and I can expect the next time it builds projectA, it pulls the lastest version of projectB and C and use them.
The alternative with monorepo is - you make a change, bump the version number, commit and push it. You watch for the commit you need (in a monorepo this might be harder than it have to be, alternatively the developer can send you an IM) and hopefully there isn't any merge conflicts when it's done. You pull the commit, make your fix and commit it.
It's largely a matter of poor tooling.
For our .net projects, I've set up an internal NuGet repo to handle internal dependencies.
This honestly sounds more like the best workaround because the tooling isn't good.