Take the 2-minute tour ×

This question is based on Detach subdirectory into separate Git repository

Instead of detaching a single subdirectory, I want to detach a couple. For example, my current directory tree looks like this:


And I would like this instead:


The --subdirectory-filter argument to git filter-branch won't work because it gets rid of everything except for the given directory the first time it's run. I thought using the --index-filter argument for all unwanted files would work (albeit tedious), but if I try running it more than once, I get the following message:

Cannot create a new backup.
A previous backup already exists in refs/original/
Force overwriting the backup with -f

Any ideas? TIA

share|improve this question
add comment

5 Answers

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 usese the sh builtin eval (where extglob is not available).

share|improve this answer
Interesting idea. I have a similar problem but could not get it to work, see stackoverflow.com/questions/8050687/… –  manol Nov 10 '11 at 10:43
This is pretty much what I needed, though I had sprinkling of both files and folders across my repo... Thanks :) –  stephelton Dec 5 '11 at 0:58
hm. even with extglob turned on I'm getting an error near my parenthesis: syntax error near unexpected token `(' my command looks like: git filter-branch -f --index-filter "git rm -r -f --cached --ignore-unmatch src/css/themes/!(some_theme*)" --prune-empty -- --all an ls with src/css/themes/!(some_theme*) returns all the other themes so extglob does appear to be working... –  robdodson Dec 2 '12 at 19:01
You can get your shell to evaluate the glob as described in stackoverflow.com/a/8079852/396967 –  kynan Dec 3 '12 at 19:56
@MikeGraf I don't think that will give the desired result: escaping would match a literal "!" etc. in your path. –  kynan Jan 31 '13 at 1:05
show 2 more comments
up vote 10 down vote accepted

Answering my own question here... after a lot of trial and error.

I managed to do this using a combination of git subtree and git-stitch-repo. These instructions are based on:

First, I pulled out the directories I wanted to keep into their own separate repository:

cd origRepo
git subtree split -P apps/AAA -b aaa
git subtree split -P libs/XXX -b xxx

cd ..
mkdir aaaRepo
cd aaaRepo
git init
git fetch ../origRepo aaa
git checkout -b master FETCH_HEAD

cd ..
mkdir xxxRepo
cd xxxRepo
git init
git fetch ../origRepo xxx
git checkout -b master FETCH_HEAD

I then created a new empty repository, and imported/stitched the last two into it:

cd ..
mkdir newRepo
cd newRepo
git init
git-stitch-repo ../aaaRepo:apps/AAA ../xxxRepo:libs/XXX | git fast-import

This creates two branches, master-A and master-B, each holding the content of one of the stitched repos. To combine them and clean up:

git checkout master-A
git pull . master-B
git checkout master
git branch -d master-A 
git branch -d master-B

Now I'm not quite sure how/when this happens, but after the first checkout and the pull, the code magically merges into the master branch (any insight on what's going on here is appreciated!)

Everything seems to have worked as expected, except that if I look through the newRepo commit history, there are duplicates when the changeset affected both apps/AAA and libs/XXX. If there is a way to remove duplicates, then it would be perfect.

share|improve this answer
Neat tools you found here. Insight on "checkout": "git pull" is the same as "git fetch && git merge". The "fetch" part is innocuous since you are "fetching locally". So I think this checkout command is the same as "git merge master-B", which is a bit more self-evident. See kernel.org/pub/software/scm/git/docs/git-pull.html –  phord Jul 27 '10 at 22:37
Unfortunately the git-stitch-repo tool is broken due to bad dependencies nowadays. –  Henrik Jan 28 '13 at 13:18
@Henrik What problem were you experiencing exactly? It works for me, although I had to add export PERL5LIB="$PERL5LIB:/usr/local/git/lib/perl5/site_perl/" to my bash config so that it could find Git.pm. Then I installed it with cpan. –  Aaron Mahan Mar 27 '13 at 3:54
add comment

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 -- . && git reset -q $GIT_COMMIT -- apps/AAA libs/XXX' --prune-empty -- --all
share|improve this answer
thanks David this solution works with me unlike git stitch which duplicated each commit more than once –  Mahmoud Adam Nov 3 '13 at 0:14
add comment

Yeah. Force overwriting the backup by using the -f flag on subsequent calls to filter-branch to override that warning. :) Otherwise I think you have the solution (that is, eradicate an unwanted directory at a time with filter-branch).

share|improve this answer
add comment

I have writen a git filter to solve exactly this problem. It has the fantastic name of git_filter and is located at github here:


It is based on the excellent libgit2.

I needed to split a large repository with many commits (~100000) and the solutions based on git filter-branch took several days to run. git_filter takes a minute to do the same thing.

share|improve this answer
add comment

Your Answer


