What you will achieve
You will combine several existing Git repositories into a single monorepo, preserving each repo's history under its own subdirectory. The result: one repo, one CI pipeline, atomic cross-package commits.
Why monorepo
Multi-repo setups make cross-cutting changes hard. Renaming a function exported by a shared library and updating every caller becomes a coordination dance across PRs. In a monorepo, it is one PR. The trade-off: tooling investment and repo-size management.
Prerequisites
- Recent Git (2.30+).
git filter-repoinstalled:pip install git-filter-repo.- The source repositories you want to combine.
Step 1: prepare each source
For each source repo, create a fresh clone and rewrite history so all files live under a subdirectory matching their target location in the monorepo.
mkdir monorepo-staging && cd monorepo-staging
# Repo 1: web app
git clone --no-local /path/to/web-source web-prep
cd web-prep
git filter-repo --to-subdirectory-filter packages/web
cd ..
# Repo 2: shared UI library
git clone --no-local /path/to/ui-source ui-prep
cd ui-prep
git filter-repo --to-subdirectory-filter packages/ui
cd ..
# Repo 3: CLI tool
git clone --no-local /path/to/cli-source cli-prep
cd cli-prep
git filter-repo --to-subdirectory-filter packages/cli
cd ..
Each prep repo now has its files under the chosen subdirectory; commits, authors, and dates are preserved.
Step 2: create the monorepo
git init monorepo
cd monorepo
git commit --allow-empty -m "Initial monorepo commit"
Step 3: import each source
git remote add web-source ../web-prep
git fetch web-source
git merge --allow-unrelated-histories web-source/main -m "Import web from web-source"
git remote remove web-source
git remote add ui-source ../ui-prep
git fetch ui-source
git merge --allow-unrelated-histories ui-source/main -m "Import ui from ui-source"
git remote remove ui-source
git remote add cli-source ../cli-prep
git fetch cli-source
git merge --allow-unrelated-histories cli-source/main -m "Import cli from cli-source"
git remote remove cli-source
Step 4: verify history
git log --oneline
git log --follow packages/web/src/index.js
git blame packages/ui/src/Button.js
Each subdirectory's history should trace back to the original commits.
Step 5: monorepo tooling
Adopt a build orchestrator that scopes work to changed packages:
# package.json
{
"private": true,
"workspaces": ["packages/*"],
"scripts": {
"build": "turbo run build",
"test": "turbo run test",
"lint": "turbo run lint"
}
}
Common choices: Turborepo, Nx, Lerna, Bazel, Pants. Pick one and commit.
Step 6: affected-only CI
# Run tests only for packages affected by the PR
turbo run test --filter=...[origin/main]
# Or with Nx
npx nx affected:test --base=origin/main
Without affected-only filtering, every PR runs every test, and CI times balloon.
Step 7: sparse checkout
Developers working on one package can avoid checking out the whole tree:
git clone --filter=blob:none --sparse <url>
cd monorepo
git sparse-checkout init --cone
git sparse-checkout set packages/web
The clone is small and grows on demand.
Step 8: archive the source repos
Make the source repos read-only. Document the migration in their READMEs pointing to the monorepo. Do not delete - history archaeology may need them.
Step 9: communicate
Train the team:
- New clone URL.
- Build orchestrator commands.
- How to add a new package.
- CI behaviour - affected-only.
- How to find history of files moved from old repos.
Step 10: cross-package refactors
The payoff: rename a function in packages/ui and update its callers in packages/web in one commit. The whole change is reviewed and merged together; no version coordination dance.
Pitfalls
- Repository size grows. Plan for partial clone and sparse checkout from day one.
- CI is slow without affected-only filtering. Set it up before scaling contributors.
- Permission granularity is harder. If sub-teams need walled-off code, monorepo may not be right.
- The first month after migration is rough - tooling, conventions, and habits all need to align.
The result
One repo with full history of every package, ready for atomic cross-cutting changes, with build and CI tooling that scales. The migration is a real engineering project, but the daily benefits compound for years.