I needed to merge multiple git repos into a single one using subdirectories, while preserving the history. I have used git filter-branch in the past to do the inverse. Searching around, I found git-filter-repo which made it a snap.

As an overview, I had the following repos, each with their own existing git history:

  • apps-foo
  • apps-bar
  • apps-baz

And I wanted to produce a single monorepo with (preserving the git history of all projects):

├── bar
├── baz
└── foo

First, I created two dirs to work with:

mkdir apps-clones
mkdir apps-monorepo

Next, I cloned each repo into app-clones:

cd apps-clones
for app in apps-foo apps-bar apps-baz; do
  git clone git@github.com:org/"$app"

Now each app can have it’s git history rewritten such that it was always in a subdirectory:

cd apps-clones
for app in apps-foo apps-bar apps-baz; do
  (cd "$app" && git filter-repo --force --to-subdirectory "${app##apps-}"/)

Now, if I viewed the git history for apps-foo, I would see all files present under a foo/ subdirectory.

On to the monorepo. Each of the repos above need to be imported. This is done by adding a local git origin pointing to the repo above, and merging it in.

cd apps-monorepo
for app in apps-foo apps-bar apps-baz; do
  git remote add "$app" ../apps-clones &&
    git fetch "$app" &&
    git merge "$app"/master --allow-unrelated-histories --no-ff -m "Add ${app##apps-}"

When this is done, the apps-monorepo repo has all 3 projects in subdirectories and a full git history. The first commit and merge commits per project are a little wonky, but it’s nice to be able to merge projects and keep their history.