The goal
You want to move a directory from one repo to another, keeping the full commit history of the moved files. Plain copy-paste loses provenance; git filter-repo preserves it.
Step 1: extract the subset
git clone <source-repo> source-extract
cd source-extract
git filter-repo --path packages/widget --path-rename packages/widget:
This rewrites the clone to contain only files under packages/widget, moved to the root. Every commit that touched those files is preserved; commits that did not are dropped.
Step 2: import into the target
cd ../target-repo
git remote add widget-source ../source-extract
git fetch widget-source
git merge --allow-unrelated-histories widget-source/main
The merge brings the widget history into the target repo, alongside the existing history.
Step 3: place the files
If you want the widget under vendor/widget instead of root:
# Before merging, in source-extract:
git filter-repo --to-subdirectory-filter vendor/widget
Then merge as before; files land in vendor/widget.
Cleaning up
git remote remove widget-source
rm -rf ../source-extract
Verifying history
git log --follow vendor/widget/index.js
git blame vendor/widget/index.js
Both should show the original authors and dates. --follow traces across renames.
Removing the files from the source
If the move is permanent, delete from the source:
cd source-repo
git filter-repo --invert-paths --path packages/widget
This rewrites the source to remove the widget. Force-push and have collaborators reclone. SHAs change.
Alternative: git subtree
For a lightweight, non-destructive merge:
git subtree add --prefix=vendor/widget <source-url> main --squash
This brings the source's main into vendor/widget as a single squashed commit. Updates can be pulled later:
git subtree pull --prefix=vendor/widget <source-url> main --squash
Subtree preserves less detail but is simpler.
Avoiding pitfalls
- Always work on clones, not your primary checkouts.
- Run on a fresh clone -
filter-reporefuses to run on existing remotes by default. - Coordinate with the team - SHAs change after rewrite.
- Tags are preserved by
filter-repo; verify withgit tag.
The big picture
This pattern underlies many monorepo migrations and modularisation efforts. Done carefully, downstream developers see a clean history that traces back to the original commits. Done sloppily, you orphan history and confuse git blame for years.