Library / SDK
shopsys/monorepo-tools avatar
shopsys/monorepo-tools

shopsys monorepo-tools merges packages into a monorepo and splits them back

[READ-ONLY] Tools for building and splitting monolithic repository from existing packages (with their history unchanged). This package is used for splitting our monorepo and we share it with our community as it is. We do not intend to support or develop it any further. Feel free to fork it and adjust for your own need.

698 stars65 forksShellMIT

At a glance

What is it?
The shopsys monorepo-tools package is a set of shell scripts for building a monorepo from existing repositories and splitting it apart again, with the point of the exercise being that the git history of each package stays unchanged. The README says the tool is finished, shared as it is, and free to fork, so it is a worked example rather than a maintained dependency.
Who is it for?
monorepo-tools suits an engineer who needs a one-off migration from several repositories into one, or who wants to read a working implementation of history rewriting with filter-branch semantics. It does not suit a team looking for a maintained monorepo workflow, because the README states the project is not intended to be developed further.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly Shell, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The tool is finished, and the README says so first

The most important line in the README is the disclaimer at the top, and it appears before anything else. The package is used for splitting the author's own monorepo and is shared with the community as it is. The project does not intend to support or develop it any further, and readers are invited to fork it and adjust it for their own need.

That framing changes how you should read everything else. You are looking at a finished script set, not the front of a project. The licence is MIT, the code is a handful of shell scripts at the top level, and there is no release history in this repository, because the history of changes lives on the releases page of the shopsys/shopsys monorepo that maintains it.

The default branch is named 20.0, which is a branch naming convention rather than a version number for this tool, and the last push was on 2026-10-15. So the code is not abandoned in the sense of being broken, it is simply not going to gain features.

There is also a badge linking to a mention in an awesome monorepo list, which is the kind of reference that tells you this package is known in the community for a specific problem rather than being a general purpose monorepo framework. That is exactly what it is.

Building the monorepo rewrites history into subdirectories

The build script takes the names of remotes as arguments, and the README is explicit that the command will rewrite the history of all the mentioned repositories as if they were developed in separate subdirectories.

That single sentence is the whole value proposition. The alternative to rewriting history is to squash each package into a single import commit, which loses the authorship, the review trail and the ability to bisect. A filter-branch style rewrite keeps every commit, with its author, its message and its date, and only changes where the files live.

The usage line for the script is monorepo_build.sh with a list of remote names, each optionally in the form remote-name:subdirectory. If you do not provide a subdirectory, the remote name is used. The README notes that this may take a while depending on the size of your repositories, which is an honest warning for a script that rewrites every commit of every remote you point it at.

One rule is worth knowing before you run it. Only the master branch is merged together, and other branches are kept only from the first package, to avoid possible branch name conflicts. That is a pragmatic choice rather than a complete solution: a branch that exists in two packages with different content has to be resolved by a human, and the script declines to try.

Splitting pushes master and tags back to each origin

The split script is the mirror image, and it takes the same arguments.

bash
~/monorepo-tools/monorepo_split.sh \
    main-repository package-alpha:packages/alpha package-beta:packages/beta

The README says it will push all relevant changes into all of your remotes, splitting and pushing the master branch along with all the tags added in the monorepo. Other branches are not pushed. Again it may take a while, depending on the size of the monorepo.

The key property is stated as a note: the commits in the split repositories should be identical to those from the original repository, keeping the git history intact. If you have checked out the original master previously, you should be able to fast-forward to the new version after splitting.

The one exception is signed commits, and the README is unusually detailed about it, because GitHub signs commits made through its web interface by default. If your original repository has signed commits, the split commits will not be signed, and that will prevent the script from pushing the unsigned commits to the remote. The documented workaround is to add the force flag to the git push calls in the script, with the warning that it may cause unforeseen consequences if you are not sure what you are doing.

That is a genuine footgun and a genuine documentation win. Almost nobody would think to look for it.

The rest of the package is git plumbing you can read

The reference section lists the remaining scripts, and they are worth reading as a set, because each one is a small, well described operation on a git repository.

monorepo_add.sh adds repositories to an existing monorepo from specified remotes, which must already be added to your repository and fetched, and again only the master branch is added from each repo. rewrite_history_into.sh rewrites git history, including tags, so that all filepaths are in a specific subdirectory. rewrite_history_from.sh rewrites git history, including tags, so that only commits that made changes in a subdirectory are kept, rewriting all filepaths as if it was the root.

The pair is the interesting one, because together they are a filter-branch in two directions. Moving files into a subdirectory and moving them back are the two operations a monorepo migration needs, and the second one drops the commits that did not touch the directory, which is what keeps a package's history from filling up with unrelated commits.

The rest are support operations. original_refs_restore.sh restores the original git history after a rewrite, and original_refs_wipe.sh wipes it. tag_refs_backup.sh backs up tag refs into refs/original-tags/, and tag_refs_move_to_original.sh moves them back. load_branches_from_remote.sh deletes all local branches and creates all the non-remote-tracking branches of a specified remote.

The README says the reference is a short description and that for detailed information you should go to the scripts themselves and read the comments. For shell scripts, that is the right advice.

The quick start is four commands against an empty repository

The quick start section is short enough to follow exactly, and the first step is to download the tools somewhere stable.

bash
git clone https://github.com/shopsys/monorepo-tools ~/monorepo-tools

The second step is to prepare an empty repository with the remotes you intend to merge. You create a new git repository for your monorepo and add all your existing packages as remotes, and the README notes that you can add as many remotes as you want. In the example, three packages are prepared from GitHub.

bash
git init
git remote add main-repository http://github.com/vendor/main-repository.git
git remote add package-alpha http://github.com/vendor/alpha.git
git remote add package-beta http://github.com/vendor/beta.git
git fetch --all --no-tags

Then the build runs, listing the remote names as arguments with their target subdirectories.

bash
~/monorepo-tools/monorepo_build.sh \
    main-repository package-alpha:packages/alpha package-beta:packages/beta

After that the master branch contains all the packages in separate directories, in the example main-repository/ at the top level and packages/alpha/ and packages/beta/ beneath it.

One environment detail is stated in the requirements section: you may need to update your git, with 2.16.1 given as the version that was tested. The scripts lean on modern git history rewriting behaviour, and the warning is worth heeding rather than ignoring, because a rewrite that behaves differently on an older git is exactly the kind of thing that produces a bad merge months later.

Adding a package after the fact is a manual procedure

The README anticipates the case where the monorepo already exists and you want to add a package later, and the instruction is explicit: do not use monorepo_build.sh, and do the steps by hand instead.

The procedure is to create the new repository, add it as a remote in the monorepo, create the corresponding directory in the monorepo, add the code and commit it, and then run the split tool with the new package included alongside the others.

bash
~/monorepo-tools/monorepo_split.sh \
    main-repository package-alpha:packages/alpha package-beta:packages/beta package-gamma:packages/gamma

The reason for the manual route is not explained, but the shape of it is clear from the tool set. monorepo_build.sh rewrites the history of everything it is given, which is the right operation for the initial import and the wrong one for a repository that already has a combined history, since running it again would rewrite commits that are already in monorepo form.

The monorepo_add.sh script exists for the general case of adding repositories to an existing monorepo from specified remotes, where only the master branch is added from each repo. The documented procedure for a new package is the manual one, and the script is in the package for the cases it fits.

The whole workflow is therefore: build once, develop in the monorepo, split back to the origins on every change, and add new packages by hand.

Editorial conclusion

monorepo-tools suits an engineer who needs a one-off migration from several repositories into one, or who wants to read a working implementation of history rewriting with filter-branch semantics. It does not suit a team looking for a maintained monorepo workflow, because the README states the project is not intended to be developed further. Before running it, check the git version on your machine against the 2.16.1 the scripts were tested on, and decide what to do about signed commits, because the split step refuses to push unsigned commits unless you force it.

Frequently asked questions

What does shopsys monorepo-tools do?

It provides shell scripts for building a monorepo from existing packages and for splitting a monorepo back into the original repositories, with the point of the exercise being that the git history of each package stays unchanged. The scripts rewrite history so that all filepaths live in a specific subdirectory, and back again.

Is monorepo-tools still maintained?

No. The README states that the package is shared as it is, that the project does not intend to support or develop it any further, and that readers should feel free to fork it and adjust it for their own need. The history of changes is recorded on the releases page of the shopsys/shopsys monorepo that maintains it.

What happens to branches other than master during the build?

Only the master branch is merged together, and other branches are kept only from the first package, to avoid possible branch name conflicts. The split step pushes the master branch along with all the tags added in the monorepo, and other branches are not pushed.

Why does the split fail with signed commits?

GitHub signs commits made through its web interface by default, and the split commits are not signed, so the script will not push the unsigned commits to the remote. The README says you can add the force flag to the git push calls in the script, while warning that it may cause unforeseen consequences if you are not sure what you are doing.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. shopsys/monorepo-tools on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/shopsys-monorepo-tools.svg)](https://hysenlabs.com/projects/shopsys-monorepo-tools)

Was this useful?