Detach many subdirectories into a new, separate Git repository Detach many subdirectories into a new, separate Git repository git git

Detach many subdirectories into a new, separate Git repository


Instead of having to deal with a subshell and using ext glob (as kynan suggested), try this much simpler approach:

git filter-branch --index-filter 'git rm --cached -qr --ignore-unmatch -- . && git reset -q $GIT_COMMIT -- apps/AAA libs/XXX' --prune-empty -- --all

As mentioned by void.pointer's comment, this will remove everything except apps/AAA and libs/XXX from current repository.

Prune empty merge commits

This leaves behind lots of empty merges. These can be removed by another pass as described by raphinesse in his answer:

git filter-branch --prune-empty --parent-filter \'sed "s/-p //g" | xargs -r git show-branch --independent | sed "s/\</-p /g"'

⚠️ Warning: The above must use GNU version of sed and xargs otherwise it would remove all commits as xargs fails. brew install gnu-sed findutils and then use gsed and gxargs:

git filter-branch --prune-empty --parent-filter \'gsed "s/-p //g" | gxargs git show-branch --independent | gsed "s/\</-p /g"' 


Manual steps with simple git commands

The plan is to split individual directories into its own repos, then merge them together. The following manual steps did not employ geek-to-use scripts but easy-to-understand commands and could help merge extra N sub-folders into another single repository.

Divide

Let's assume your original repo is: original_repo

1 - Split apps:

git clone original_repo apps-repocd apps-repogit filter-branch --prune-empty --subdirectory-filter apps master

2 - Split libs

git clone original_repo libs-repocd libs-repogit filter-branch --prune-empty --subdirectory-filter libs master

Continue if you have more than 2 folders. Now you shall have two new and temporary git repository.

Conquer by Merging apps and libs

3 - Prepare the brand new repo:

mkdir my-desired-repocd my-desired-repogit init

And you will need to make at least one commit. If the following three lines should be skipped, your first repo will appear immediate under your repo's root:

touch a_file_and_make_a_commit # see user's feedbackgit add a_file_and_make_a_commitgit commit -am "at least one commit is needed for it to work"

With the temp file commited, merge command in later section will stop as expected.

Taking from user's feedback, instead of adding a random file like a_file_and_make_a_commit, you can choose to add a .gitignore, or README.md etc.

4 - Merge apps repo first:

git remote add apps-repo ../apps-repogit fetch apps-repogit merge -s ours --no-commit apps-repo/master # see below note.git read-tree --prefix=apps -u apps-repo/mastergit commit -m "import apps"

Now you should see apps directory inside your new repository. git log should show all relevant historical commit messages.

Note: as Chris noted below in the comments, for newer version(>=2.9) of git, you need to specify --allow-unrelated-histories with git merge

5 - Merge libs repo next in the same way:

git remote add libs-repo ../libs-repogit fetch libs-repogit merge -s ours --no-commit libs-repo/master # see above note.git read-tree --prefix=libs -u libs-repo/mastergit commit -m "import libs"

Continue if you have more than 2 repos to merge.

Reference: Merge a subdirectory of another repository with git


Why would you want to run filter-branch more than once? You can do it all in one sweep, so no need to force it (note that you need extglob enabled in your shell for this to work):

git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch $(ls -xd apps/!(AAA) libs/!(XXX))" --prune-empty -- --all

This should get rid of all the changes in the unwanted subdirectories and keep all your branches and commits (unless they only affect files in the pruned subdirectories, by virtue of --prune-empty) - no issue with duplicate commits etc.

After this operation the unwanted directories will be listed as untracked by git status.

The $(ls ...) is necessary s.t. the extglob is evaluated by your shell instead of the index filter, which uses the sh builtin eval (where extglob is not available). See How do I enable shell options in git? for further details on that.