How Josh helps Rust manage code across multiple repositories | Inside Rust Blog
June 4, 2026 · Jakub Beránek, Ralf Jung
The Rust Project develops and maintains several developer tools, such as Cargo, Clippy, rustfmt, Rust Analyzer and Miri. These tools are developed in separate git repositories, which enables customizing their issue and pull request (PR) management, Continuous Integration (CI) workflows and development processes based on the needs of the specific teams that maintain them. However, these tools also need to be somehow integrated within the main rust-lang/rust repository, primarily for the following two reasons:
The CI of this central repo is responsible for distributing the Rustup components containing these tools, which we then ship to Rust programmers.
When we make a breaking change to the (unstable) internal compiler API, on which some of these tools depend, we sometimes need to fix them in the same pull request, to ensure that they keep working on the nightly toolchain.
This means that we have to deal with the classical problem of managing development of code<br>across several repositories that depend on one another. This post covers our experience and some problems that we encountered with this cross-repository code sharing and describes how we leverage an awesome git tool called Josh (Just One Single History), which helps us overcome these challenges.
Problem statement
First, let us define more concretely what is the problem that we are trying to solve. We have one "parent" repository (in our case rust-lang/rust), which has a set of dependencies, which we will call "subprojects" (e.g. rust-lang/miri or rust-lang/rust-analyzer). The parent and the subprojects are developed independently. The parent must have access to a specific snapshot of the subproject's source code. Periodically, the subproject's code in the parent repo should be updated to a newer version. And ideally, the parent should be able to atomically change both its and the subproject's source code.
There are several traditional ways of attempting to solve this issue, which did not fully work for us for various reasons.
Monorepo
If both the parent and the subprojects all live inside the same repository (a "monorepo"), then the desired properties described above are trivially satisfied. The parent always sees the latest version of each subproject, and it is possible to atomically change both the parent and the subproject inside a single pull request. While this approach has many benefits, and rust-lang/rust is conceptually a monorepo (it contains the source code of the compiler, the standard library, rustdoc and many other things), it also causes some friction. The compiler repo is quite large, which can be scary for new contributors, and it has quite slow and complex CI. Its development processes are also mostly tuned to the Compiler team's needs, but our developer tools are maintained by other teams that use different review or issue tracking practices.
Keeping some subprojects (such as Miri or Rust Analyzer) in their own repositories thus makes their CI faster, enables them to be deployed separately from the main Rust toolchain, and simplifies onboarding of new maintainers, who can more easily obtain scoped permissions to merge PRs only for the given subproject, and not "all of Rust".
git submodules
Perhaps the most straightforward way of including a git repository in another one is to use git submodules. We currently use submodules for some dependencies, such as Cargo and LLVM. Their benefit is that they are easy to set up, you simply point a directory to a specific commit SHA of an external repository, and that's it. Unfortunately, that is where most of its advantages end.
In practice, submodules can be quite annoying to work with. Developers have to properly checkout the submodules using e.g. git clone --recursive or git submodule update, otherwise they can remain empty or contain the wrong commit. It is in fact a relatively common mistake for someone to accidentally commit and push an unrelated submodule change to their pull request branch, because git sometimes keeps submodules in a "dirty" state when switching branches. We had to build custom logic to checkout specific submodules to the correct commit when building a given Rust artifact in our build tooling (bootstrap) to alleviate this, but it is not perfect.
Another big disadvantage of submodules is that they do not allow atomically modifying the subproject and the parent in a single PR, because the submodule is fundamentally only a link to a repository. When we make (an internal) breaking change in rust-lang/rust, we would thus have to:
Merge the rust-lang/rust PR with the breaking change
Update the subproject in a separate subproject PR
Merge another rust-lang/rust PR which updates its submodule to point to the new subproject commit created in step 2.
This is a lot of additional busywork, which makes it harder to land changes that affect...