Migrating to a Monorepo from Microservices with Git Subtree
As systems grow, the “one repo per microservice” pattern can lead to significant overhead: dependency hell, fragmented CI/CD, and difficulty in cross-service refactoring. Migrating to a monorepo often becomes the logical next step for many engineering teams.
The biggest technical challenge during this migration is preserving the commit history of each individual service. You don’t want to just copy files; you want to bring the years of context, bug fixes, and development history with them.
git subtree is the perfect tool for this job. Unlike git submodule, it stores the actual code and history directly in the target repository, making the monorepo truly self-contained.
The Migration Strategy
The goal is to bring each microservice into a subdirectory of the new monorepo while keeping its history intact.
1. Prepare the destination monorepo
First, initialize your new monorepo (if you haven’t already):
mkdir my-monorepo
cd my-monorepo
git init
touch .gitignore
git add .gitignore
git commit -m "initial monorepo commit"
2. The subtree Migration Command
For each microservice you want to migrate, you can use the following pattern. Let’s say you have a service named api-service located in a sibling directory.
# Define the service name
name="api-service"
# 1. Add the old repo as a remote
git remote add subtree/$name ../$name
# 2. Fetch the history from the old repo
git fetch subtree/$name
# 3. Add the repo as a subtree into a new directory
git subtree add -P $name subtree/$name main
Breakdown of the command:
git remote add subtree/$name ../$name: This points to the local path of your old microservice. You can also use a URL here if needed.git fetch subtree/$name: Grabs all the commits from the original repo.git subtree add -P $name subtree/$name main: This is the magic.-P $name: Specifies the Prefix (the directory name) where the service will live in the monorepo.subtree/$name: The remote we just added.main: The branch we want to import (adjust if your default branch ismaster).
Why use Subtree over Submodules?
- Simplicity: Developers don’t need to learn new commands like
git submodule update --init. They justgit pullandgit pushas usual. - Atomic Commits: You can make a single commit that spans multiple services (e.g., changing a shared API contract and updating the consumer in one go).
- History Preservation: Every single commit from the original repo is now part of the monorepo’s history.
- No External Dependencies: The monorepo contains everything. You don’t have to worry about old repos being deleted or moved.
Post-Migration Cleanup
Once you’ve migrated all services, you should:
- Remove Remotes: Once the subtree is added, you no longer need the remotes added in step 2.
git remote remove subtree/$name - Update CI/CD: You’ll likely need to adjust your build pipelines to trigger based on changes in specific subdirectories.
- Consolidate Tools: This is a great time to unify your linting, testing, and deployment configurations across services.
Migrating to a monorepo is as much about culture as it is about tooling. But with git subtree, the technical transition is smooth, safe, and historical.