Merging packages and their histories into a Lerna monorepo

 •  Filed under git

Update: You probably just want to use lerna import. But if you'd like a manual technique, or aren't actually using Lerna, read on.


Let's say you have a Lerna monorepo called acme. You have a package called app, presently in its own repository, that you want to move into repo. And you want to preserve the whole git history of app in the merge, so it becomes part of the history of acme. The process is actually pretty simple.

acme looks like this:

acme
|-- package.json
\-- packages
    |-- foo
    \-- bar

So app is going to go join foo and bar in the pantheon of packages.

Plan ahead, because we are going to do a force-push over acme's master branch. Make sure your collaborators know what is about to happen.

The process

You'll need git-filter-repo. Go download that.

  1. Fork app. We're going to push over master of this fork.
  2. Clone your fork of app. Then cd app.
  3. Move the repo's contents into a subdirectory and update history accordingly using
    git filter-repo --to-subdirectory-filter "packages/app"
    The old contents of the app repo should now all be contained in packages/app.
  4. git push origin master --force to push this crazy new history over your fork's history.
  5. cd over to the acme repository
  6. Add your fork of app as a remote with
    git remote add app git@github.com:you/app.git
  7. git merge app/master --allow-unrelated-histories
  8. View the git log and make sure it looks like you expect it to.
  9. Sweet, forbidden fruit. Savor it: git push origin master --force

Resolving works in progress

At this point you'll start getting messages from your colleagues asking what horrible thing you've done. You can help guide them a bit.

To update their master branch, they'll need to check out master, git fetch and then git reset --hard origin/master.

If they had a change in progress for app, they can use
git diff [some commit hash] > mychange.diff
to create a patch file that they can then apply to the acme repository using
git apply mychange.diff.

If they had a feature branch on acme, they should be able to use git rebase origin/master -i to reconcile the histories.

If they just had some uncommitted work on acme, they should be able to do git stash, git reset --hard origin/master, then git stash pop.

Finishing up

You'll want to adapt app a little bit to its new home. You'll want to refactor these things (at least) out of app and into top-level files in acme:

  • .gitignore
  • ESLint config
  • Prettier config/ignore
  • Travis config
  • Dev dependencies from app/package.json