Repository Migration

Course 3 · Ch 10 · Final Chapter · Scenario
Full Repository Migration
Splitting and merging repos while preserving history, moving between hosts, and handling LFS/submodules along the way

This final chapter is the natural endpoint of the series — and a direct, larger-scale relative of Course 1, Chapter 9's "moving your project folder" scenario. Instead of moving a folder on disk, this is about restructuring entire repositories: splitting one into several, merging several into one, or moving a whole project to a different hosting provider — all while keeping the history that makes git useful in the first place.

The Three Migration Types

✂️ Splitting a repo
Extracting one subfolder of a large repo into its own standalone repository, ideally keeping that folder's commit history intact rather than starting fresh.
🔗 Merging repos
Combining two previously separate repositories into one — the reverse problem, and the legitimate use case behind Chapter 4's git subtree.
🏠 Moving hosts
GitHub → GitLab, self-hosted → GitHub, or similar — conceptually the simplest of the three, but with real gotchas around what doesn't transfer automatically.

Splitting a Repo with History Intact — git filter-repo

Naively copying a subfolder into a new repo and running git init loses everything — every commit, every "why was this written this way" buried in old commit messages. git filter-repo (the modern, recommended replacement for the older, slower git filter-branch) rewrites history to extract just one path's story.

$ # Clone a fresh copy specifically for this operation — filter-repo rewrites destructively
$ git clone https://github.com/you/big-monorepo.git extracted-project
$ cd extracted-project

$ # Keep ONLY history relevant to this one subfolder
$ git filter-repo --path frontend/web-app
Parsed 1,847 commits
New history written in 4.2 seconds...

$ # This clone's history now ONLY contains commits touching that path,
$ # with paths rewritten as if it had always been the repo root
$ git log --oneline | wc -l
312 # only the commits that actually touched frontend/web-app
$ # Point at a brand-new, empty GitHub repo and push the extracted history
$ git remote add origin https://github.com/you/web-app.git
$ git push -u origin main
filter-repo rewrites every commit hash — work on a throwaway clone, never your only copy
Like every history-rewriting tool in this course (interactive rebase, Chapter 2), filter-repo creates entirely new commit objects. Run it on a fresh clone made specifically for the migration, never on the team's actual working clone — if anything goes wrong, you simply delete the throwaway clone and start again from the untouched original.

Merging Repos While Preserving Both Histories

Chapter 4's git subtree command is the practical tool here — merging another repository's full history into a subfolder of your current one, rather than losing it.

$ git subtree add --prefix=libs/old-utils https://github.com/you/old-utils-repo.git main

Without --squash (contrast with Chapter 4's earlier example), this preserves the full individual commit history of the merged-in repository, interleaved into your main repo's own history — useful when that history itself still has ongoing value, not just the current file state.

Moving Between Hosting Providers

1
Mirror-clone the existing repo
git clone --mirror <old-url> — captures every branch, tag, and ref exactly, not just the default branch a normal clone would give you.
2
Create the new, empty repository on the destination host
No README, no initial commit, no .gitignore template — genuinely empty, so the push doesn't conflict with anything.
3
Push the mirror to the new host
git push --mirror <new-url> — transfers every branch, tag, and the complete commit history.
4
Migrate the things git history doesn't carry
Issues, PR discussions, wiki content, branch protection settings, GitHub Actions secrets — see the checklist below.

What doesn't transfer automatically

⚠️
Issues and PR discussion history
Lives in the platform's own database, not in git itself — needs its own export/import process (GitHub's API, or third-party migration tools) if that history matters.
⚠️
Branch protection rules and CODEOWNERS enforcement
CODEOWNERS the file transfers with the repo (Chapter 6), but the platform-level protection rules wrapping it need recreating on the new host.
⚠️
Actions secrets and CI configuration
Secrets (Chapter 5) never transfer between hosts even in principle — they're encrypted, platform-specific, and need re-entering manually on the new side.
⚠️
Git LFS objects
--mirror doesn't automatically carry LFS-tracked file content (Chapter 7) — run git lfs fetch --all then git lfs push --all <new-remote> explicitly.
⚠️
Submodule references
If a submodule's own URL (Chapter 4) is also moving, .gitmodules needs updating to point at its new location too — otherwise contributors hit exactly the kind of broken-reference confusion this whole series has returned to repeatedly.
Keep the old host read-only, don't delete it, for a transition period
Leaving the original repository accessible (even just as an archived, read-only reference) for a few weeks after migrating gives anyone who missed the announcement a way to find the new location, and provides a safety net if something about the migration turns out incomplete.

Command Reference

CommandWhat it does
git filter-repo --path <path>Rewrites history to keep only commits touching the given path
git subtree add --prefix=<path> <url> <branch>Merges another repo's full history into a subfolder (no --squash)
git clone --mirror <url>Clones every branch, tag, and ref exactly — for host migration
git push --mirror <url>Pushes a complete mirror to a new host
git lfs fetch --all / push --allExplicitly transfers LFS object content, which --mirror alone doesn't carry

Chapter 10 Quick Reference — and Series Wrap-Up

  • git filter-repo --path — extracts one subfolder's history into a standalone repo; always on a throwaway clone
  • git subtree add (no --squash) — merges another repo's full history into a subfolder, preserving both
  • git clone --mirror / git push --mirror — the correct pair for moving a complete repo between hosts
  • Doesn't transfer automatically: issues/PR history, branch protection settings, Actions secrets, LFS objects (needs explicit lfs fetch/push), submodule URLs pointing at old locations
  • Keep the old host accessible, read-only, for a transition period after migrating
  • Series recap (Course 3): internals (Ch1) → interactive rebase (Ch2) → reflog/recovery (Ch3) → submodules/subtrees (Ch4) → advanced Actions (Ch5) → securing a repo (Ch6) → large repos (Ch7) → bisect (Ch8) → team force-push recovery (Ch9) → full migration (Ch10)
  • Full series complete: 30 chapters across Foundations, Teams, and Advanced Mastery — from "what is git" to recovering a team from a history-rewrite disaster