By admin , 29 April 2026

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-repo refuses to run on existing remotes by default.
  • Coordinate with the team - SHAs change after rewrite.
  • Tags are preserved by filter-repo; verify with git 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.