Jay Taylor's notes

back to listing index

Detach (move) subdirectory into separate Git repository

[web search]
Original source (stackoverflow.com)
Tags: git git-subtree repository-splitting repository-refactoring stackoverflow.com
Clipped on: 2016-11-30

I have a Git repository which contains a number of subdirectories. Now I have found that one of the subdirectories is unrelated to the other and should be detached to a separate repository.

How can I do this while keeping the history of the files within the subdirectory?

I guess I could make a clone and remove the unwanted parts of each clone, but I suppose this would give me the complete tree when checking out an older revision etc. This might be acceptable, but I would prefer to be able to pretend that the two repositories doesn't have a shared history.

Just to make it clear, I have the following structure:

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

But I would like this instead:

XYZ/
    .git/
    XY1/
    XY2/
ABC/
    .git/
    ABC/
asked Dec 11 '08 at 13:57
Image (Asset 3/25) alt=
matli
11.6k62432

protected by l̅ͯͪ̈̎̽̽if̏̇et͑̐̀͂̊̌̚im̔ͯ͊eͬ̐ Jun 30 '13 at 0:11

This question is protected to prevent "thanks!", "me too!", or spam answers by new users. To answer it, you must have earned at least 10 reputation on this site (the association bonus does not count).

5 upvote
  flag
This is trivial now with git filter-branch see my answer below. – jeremyjjbrown Aug 20 '14 at 14:12
4 upvote
  flag
@jeremyjjbrown is right. This is no longer difficult to do but it is difficult to find the right answer on Google because all the old answers dominate the results. – Agnel Kurian Oct 14 '14 at 5:39
up vote 1073 down vote accepted

Update: This process is so common, that the git team made it much simpler with a new tool, git subtree. See here: Detach (move) subdirectory into separate Git repository


You want to clone your repository and then use git filter-branch to mark everything but the subdirectory you want in your new repo to be garbage-collected.

  1. To clone your local repository:

    git clone /XYZ /ABC
    

    (Note: the repository will be cloned using hard-links, but that is not a problem since the hard-linked files will not be modified in themselves - new ones will be created.)

  2. Now, let us preserve the interesting branches which we want to rewrite as well, and then remove the origin to avoid pushing there and to make sure that old commits will not be referenced by the origin:

    cd /ABC
    for i in branch1 br2 br3; do git branch -t $i origin/$i; done
    git remote rm origin
    

    or for all remote branches:

    cd /ABC
    for i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; done
    git remote rm origin
    
  3. Now you might want to also remove tags which have no relation with the subproject; you can also do that later, but you might need to prune your repo again. I did not do so and got a WARNING: Ref 'refs/tags/v0.1' is unchanged for all tags (since they were all unrelated to the subproject); additionally, after removing such tags more space will be reclaimed. Apparently git filter-branch should be able to rewrite other tags, but I could not verify this. If you want to remove all tags, use git tag -l | xargs git tag -d.

  4. Then use filter-branch and reset to exclude the other files, so they can be pruned. Let's also add --tag-name-filter cat --prune-empty to remove empty commits and to rewrite tags (note that this will have to strip their signature):

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC -- --all
    

    or alternatively, to only rewrite the HEAD branch and ignore tags and other branches:

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
    
  5. Then delete the backup reflogs so the space can be truly reclaimed (although now the operation is destructive)

    git reset --hard
    git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
    git reflog expire --expire=now --all
    git gc --aggressive --prune=now
    

    and now you have a local git repository of the ABC sub-directory with all its history preserved.

Note: For most uses, git filter-branch should indeed have the added parameter -- --all. Yes that's really dash dash space dash dash all. This needs to be the last parameters for the command. As Matli discovered, this keeps the project branches and tags included in the new repo.

Edit: various suggestions from comments below were incorporated to make sure, for instance, that the repository is actually shrunk (which was not always the case before).

28 upvote
  flag
Very good answer. Thanks! And to really get exactly what I wanted, I added "-- --all" to the filter-branch command. – matli Dec 12 '08 at 9:17
12 upvote
  flag
Why do you need --no-hardlinks? Removing one hardlink won't affect the other file. Git objects are immutable too. Only if you'd change owner/file permissions you need --no-hardlinks. – vdboor Feb 1 '10 at 9:58
65 upvote
  flag
An additional step I would recommend would be "git remote rm origin". This would keep pushes from going back to the original repository, if I'm not mistaken. – Tom Apr 5 '10 at 19:51
11 upvote
  flag
Another command to append to filter-branch is --prune-empty, to remove now-empty commits. – Seth Johnson Sep 12 '11 at 2:31
8 upvote
  flag
Like Paul, I did not want project tags in my new repo, so I did not use -- --all. I also ran git remote rm origin, and git tag -l | xargs git tag -d before the git filter-branch command. This shrunk my .git directory from 60M to ~300K. Note that I needed to run both of these commands to in order to get the size reduction. – saltycrane Nov 17 '11 at 21:18

The Easy Way™

It turns out that this is such a common and useful practice that the overlords of git made it really easy, but you have to have a newer version of git (>= 1.7.11 May 2012). See the appendix for how to install the latest git. Also, there's a real-world example in the walkthrough below.

  1. Prepare the old repo

    pushd <big-repo>
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    popd
    

    Note: <name-of-folder> must NOT contain leading or trailing characters. For instance, the folder named subproject MUST be passed as subproject, NOT ./subproject/

    Note for windows users: when your folder depth is > 1, <name-of-folder> must have *nix style folder separator (/). For instance, the folder named path1\path2\subproject MUST be passed as path1/path2/subproject

  2. Create the new repo

    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Link the new repo to Github or wherever

    git remote add origin <git@github.com:my-user/new-repo.git>
    git push origin -u master
    
  4. Cleanup, if desired

    popd # get out of <new-repo>
    pushd <big-repo>
    
    git rm -rf <name-of-folder>
    

    Note: This leaves all the historical references in the repository.See the Appendix below if you're actually concerned about having committed a password or you need to decreasing the file size of your .git folder.

...

Walkthrough

These are the same steps as above, but following my exact steps for my repository instead of using <meta-named-things>.

Here's a project I have for implementing JavaScript browser modules in node:

tree ~/Code/node-browser-compat

node-browser-compat
├── ArrayBuffer
├── Audio
├── Blob
├── FormData
├── atob
├── btoa
├── location
└── navigator

I want to split out a single folder, btoa, into a separate git repository

pushd ~/Code/node-browser-compat/
git subtree split -P btoa -b btoa-only
popd

I now have a new branch, btoa-only, that only has commits for btoa and I want to create a new repository.

mkdir ~/Code/btoa/
pushd ~/Code/btoa/
git init
git pull ~/Code/node-browser-compat btoa-only

Next I create a new repo on Github or bitbucket, or whatever and add it is the origin (btw, "origin" is just a convention, not part of the command - you could call it "remote-server" or whatever you like)

git remote add origin git@github.com:node-browser-compat/btoa.git
git push origin -u master

Happy day!

Note: If you created a repo with a README.md, .gitignore and LICENSE, you will need to pull first:

git pull origin -u master
git push origin -u master

Lastly, I'll want to remove the folder from the bigger repo

git rm -rf btoa

...

Appendix

Latest git on OS X

To get the latest version of git:

brew install git

To get brew for OS X:

http://brew.sh

Latest git on Ubuntu

sudo apt-get update
sudo apt-get install git
git --version

If that doesn't work (you have a very old version of ubuntu), try

sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git

If that still doesn't work, try

sudo chmod +x /usr/share/doc/git/contrib/subtree/git-subtree.sh
sudo ln -s \
/usr/share/doc/git/contrib/subtree/git-subtree.sh \
/usr/lib/git-core/git-subtree

Thanks to rui.araujo from the comments.

clearing your history

By default removing files from git doesn't actually remove them from git, it just commits that they aren't there anymore. If you want to actually remove the historical references (i.e. you have a committed a password), you need to do this:

git filter-branch --prune-empty --tree-filter 'rm -rf <name-of-folder>' HEAD

After that you can check that your file or folder no longer shows up in the git history at all

git log -- <name-of-folder> # should show nothing

However, you can't "push" deletes to github and the like. If you try you'll get an error and you'll have to git pull before you can git push - and then you're back to having everything in your history.

So if you want to delete history from the "origin" - meaning to delete it from github, bitbucket, etc - you'll need to delete the repo and re-push a pruned copy of the repo. But wait - there's more! - If you're really concerned about getting rid of a password or something like that you'll need to prune the backup (see below).

making .git smaller

The aforementioned delete history command still leaves behind a bunch of backup files - because git is all too kind in helping you to not ruin your repo by accident. It will eventually deleted orphaned files over the days and months, but it leaves them there for a while in case you realize that you accidentally deleted something you didn't want to.

So if you really want to empty the trash to reduce the clone size of a repo immediately you have to do all of this really weird stuff:

rm -rf .git/refs/original/ && \
git reflog expire --all && \
git gc --aggressive --prune=now

git reflog expire --all --expire-unreachable=0
git repack -A -d
git prune

That said, I'd recommend not performing these steps unless you know that you need to - just in case you did prune the wrong subdirectory, y'know? The backup files shouldn't get cloned when you push the repo, they'll just be in your local copy.

Credit

answered Jul 25 '13 at 17:10
Image (Asset 5/25) alt=
CoolAJ86
36.6k85063
10 upvote
  flag
git subtree is still part of the 'contrib' folder and isn't installed by default on all distros. github.com/git/git/blob/master/contrib/subtree – onionjake Aug 2 '13 at 14:06
11 upvote
  flag
@krlmlr sudo chmod +x /usr/share/doc/git/contrib/subtree/git-subtree.sh sudo ln -s /usr/share/doc/git/contrib/subtree/git-subtree.sh /usr/lib/git-core/git-subtree To activate on Ubuntu 13.04 – rui.araujo Aug 26 '13 at 6:39
25 upvote
  flag
If you have pushed a password to a public repository, you should change the password, not try to remove it from the public repo and hope nobody saw it. – Miles Rout Sep 18 '13 at 3:43
5 upvote
  flag
this seems to make a new repo with the contents of ABC/, but the new repo doesn't contain the folder ABC/ itself, as the question asked. How would you do this? – woojoo666 Oct 8 '14 at 12:14
3 upvote
  flag
This solution doesn't preserve history. – Cœur Nov 11 '15 at 2:06

Paul's answer creates a new repository containing /ABC, but does not remove /ABC from within /XYZ. The following command will remove /ABC from within /XYZ:

git filter-branch --tree-filter "rm -rf ABC" --prune-empty HEAD

Of course, test it in a 'clone --no-hardlinks' repository first, and follow it with the reset, gc and prune commands Paul lists.

answered Jun 5 '09 at 13:15
Image (Asset 7/25) alt=
pgs
6,15221717
50 upvote
  flag
make that git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch ABC" --prune-empty HEAD and it will be much faster. index-filter works on the index while tree-filter has to checkout and stage everything for every commit. – fmarc Sep 17 '09 at 19:58
46 upvote
  flag
in some cases messing up the history of repository XYZ is overkill ... just a simple "rm -rf ABC; git rm -r ABC; git commit -m'extracted ABC into its own repo'" would work better for most people. – Evgeny Oct 28 '10 at 23:24
2 upvote
  flag
You probably wish to use -f (force) on this command if you do it more than once, e.g., to remove two directories after they have been separated. Otherwise you will get "Cannot create a new backup." – Brian Carlton Apr 18 '11 at 17:59
4 upvote
  flag
If you're doing the --index-filter method, you may also want to make that git rm -q -r -f, so that each invocation won't print a line for each file it deletes. – Eric Naeseth Oct 12 '11 at 19:55
   upvote
  flag
I would suggest editing Paul's answer, only because Paul's is so thorough. – Erik Aronesty Mar 5 '14 at 15:38

I’ve found that in order to properly delete the old history from the new repository, you have to do a little more work after the filter-branch step.

  1. Do the clone and the filter:

    git clone --no-hardlinks foo bar; cd bar
    git filter-branch --subdirectory-filter subdir/you/want
    
  2. Remove every reference to the old history. “origin” was keeping track of your clone, and “original” is where filter-branch saves the old stuff:

    git remote rm origin
    git update-ref -d refs/original/refs/heads/master
    git reflog expire --expire=now --all
    
  3. Even now, your history might be stuck in a packfile that fsck won’t touch. Tear it to shreds, creating a new packfile and deleting the unused objects:

    git repack -ad
    

There is an explanation of this in the manual for filter-branch.

answered Oct 19 '09 at 21:10
Image (Asset 8/25) alt=
Josh Lee
85k16181207
3 upvote
  flag
I think somethink like git gc --aggressive --prune=now is still missing, isn't it? – Albert Jul 11 '12 at 20:14
1 upvote
  flag
@Albert The repack command takes care of that, and there wouldn’t be any loose objects. – Josh Lee Jul 11 '12 at 20:57
1 upvote
  flag
just repack didn't work for me, needed to do git gc – jsvnm Aug 30 '12 at 11:56
   upvote
  flag
yeah, git gc --aggressive --prune=now reduced much of new repo – Tomek Wyderka Apr 2 '13 at 9:01
   upvote
  flag
Simple and elegant. Thanks! – Marco Pelegrini Aug 29 at 2:30

Edit: Bash script added.

The answers given here worked just partially for me; Lots of big files remained in the cache. What finally worked (after hours in #git on freenode):

git clone --no-hardlinks file:///SOURCE /tmp/blubb
cd blubb
git filter-branch --subdirectory-filter ./PATH_TO_EXTRACT  --prune-empty --tag-name-filter cat -- --all
git clone file:///tmp/blubb/ /tmp/blooh
cd /tmp/blooh
git reflog expire --expire=now --all
git repack -ad
git gc --prune=now

With the previous solutions, the repository size was around 100 MB. This one brought it down to 1.7 MB. Maybe it helps somebody :)


The following bash script automates the task:

!/bin/bash

if (( $# < 3 ))
then
    echo "Usage:   $0 </path/to/repo/> <directory/to/extract/> <newName>"
    echo
    echo "Example: $0 /Projects/42.git first/answer/ firstAnswer"
    exit 1
fi


clone=/tmp/${3}Clone
newN=/tmp/${3}

git clone --no-hardlinks file://$1 ${clone}
cd ${clone}

git filter-branch --subdirectory-filter $2  --prune-empty --tag-name-filter cat -- --all

git clone file://${clone} ${newN}
cd ${newN}

git reflog expire --expire=now --all
git repack -ad
git gc --prune=now
answered Jun 9 '11 at 15:41
Image (Asset 9/25) alt=
Simon A. Eugster
2,50631724

This is no longer so complex you can just use the git filter-branch command on a clone of you repo to cull the subdirectories you don't want and then push to the new remote.

git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
git push <MY_NEW_REMOTE_URL> -f .
answered Aug 20 '14 at 14:11
Image (Asset 10/25) alt=
jeremyjjbrown
4,09422145
3 upvote
  flag
This worked like a charm. YOUR_SUBDIR in the above example is the subdirectory that you want to KEEP, everything else will be removed – J.T. Taylor Apr 19 '15 at 6:18
   upvote
  flag
Updates based on you comment. – jeremyjjbrown Apr 20 '15 at 18:43

Update: The git-subtree module was so useful that the git team pulled it into core and made it git subtree. See here: Detach (move) subdirectory into separate Git repository

git-subtree may be useful for this

http://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt (deprecated)

http://psionides.jogger.pl/2010/02/04/sharing-code-between-projects-with-git-subtree/

answered Mar 22 '10 at 20:55
Image (Asset 12/25) alt=
D W
1,41011841
1 upvote
  flag
git-subtree is now part of Git, although it's in the contrib tree, so not always installed by default. I know it is installed by the Homebrew git formula, but without its man page. apenwarr thus calls his version obsolete. – echristopherson May 10 '13 at 16:04

The original question wants XYZ/ABC/(*files) to become ABC/ABC/(*files). After implementing the accepted answer for my own code, I noticed that it actually changes XYZ/ABC/(*files) into ABC/(*files). The filter-branch man page even says,

The result will contain that directory (and only that) as its project root."

In other words, it promotes the top-level folder "up" one level. That's an important distinction because, for example, in my history I had renamed a top-level folder. By promoting folders "up" one level, git loses continuity at the commit where I did the rename.

Image (Asset 13/25) alt=

My answer to the question then is to make 2 copies of the repository and manually delete the folder(s) you want to keep in each. The man page backs me up with this:

[...] avoid using [this command] if a simple single commit would suffice to fix your problem

answered Apr 17 '12 at 5:12
Image (Asset 14/25) alt=
MM.
1,18321420
1 upvote
  flag
I like the style of that graph. May I ask what tool you're using? – Slipp D. Thompson Mar 30 '13 at 18:17
2 upvote
  flag
Tower for Mac. I really like it. It's almost worth switching to Mac for in itself. – MM. Apr 2 '13 at 21:02
1 upvote
  flag
Yep, though in my case, my subfoldered targetdir had been renamed at some point and git filter-branch simply called it a day, deleting all commits made prior to the rename! Shocking, considering how adept Git is at tracking such things and even migration of individual content chunks! – Jay Allen May 31 '13 at 9:25
1 upvote
  flag
Oh, also, if anyone finds themselves in the same boat, here's the command I used. Don't forget that git rm takes multiple args, so there's no reason to run it for each file/folder: BYEBYE="dir/subdir2 dir2 file1 dir/file2"; git filter-branch -f --index-filter "git rm -q -r -f --cached --ignore-unmatch $BYEBYE" --prune-empty -- --all – Jay Allen May 31 '13 at 9:26

To add to Paul's answer, I found that to ultimately recover space, I have to push HEAD to a clean repository and that trims down the size of the .git/objects/pack directory.

i.e.

$ mkdir ...ABC.git
$ cd ...ABC.git
$ git init --bare

After the gc prune, also do:

$ git push ...ABC.git HEAD

Then you can do

$ git clone ...ABC.git

and the size of ABC/.git is reduced

Actually, some of the time consuming steps (e.g. git gc) aren't needed with the push to clean repository, i.e.:

$ git clone --no-hardlinks /XYZ /ABC
$ git filter-branch --subdirectory-filter ABC HEAD
$ git reset --hard
$ git push ...ABC.git HEAD
answered Jul 25 '09 at 10:01
Image (Asset 16/25) alt=
Case Larsen
10913

Here is a small modification to CoolAJ86's "The Easy Way™" answer in order to split multiple sub folders (let's say sub1and sub2) into a new git repository.

The Easy Way™ (multiple sub folders)

  1. Prepare the old repo

    pushd <big-repo>
    git filter-branch --tree-filter "mkdir <name-of-folder>; mv <sub1> <sub2> <name-of-folder>/" HEAD
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    popd
    

    Note: <name-of-folder> must NOT contain leading or trailing characters. For instance, the folder named subproject MUST be passed as subproject, NOT ./subproject/

    Note for windows users: when your folder depth is > 1, <name-of-folder> must have *nix style folder separator (/). For instance, the folder named path1\path2\subproject MUST be passed as path1/path2/subproject. Moreover don't use mvcommand but move.

    Final note: the unique and big difference with the base answer is the second line of the script "git filter-branch..."

  2. Create the new repo

    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Link the new repo to Github or wherever

    git remote add origin <git@github.com:my-user/new-repo.git>
    git push origin -u master
    
  4. Cleanup, if desired

    popd # get out of <new-repo>
    pushd <big-repo>
    
    git rm -rf <name-of-folder>
    

    Note: This leaves all the historical references in the repository.See the Appendix in the original answer if you're actually concerned about having committed a password or you need to decreasing the file size of your .git folder.

answered Aug 6 '15 at 15:26
Image (Asset 17/25) alt=
Anthony O.
4,32913676
   upvote
  flag
This worked for me with slight modification. Because my sub1 and sub2 folders didn't exist with the initial version, I had to modify my --tree-filter script as follows: "mkdir <name-of-folder>; if [ -d sub1 ]; then mv <sub1> <name-of-folder>/; fi". For the second filter-branch command I replaced <sub1> with <sub2>, omitted creation of <name-of-folder>, and included -f after filter-branch to override the warning of an existing backup. – pglezen Feb 11 at 19:38
   upvote
  flag
This does not work if any of the subdirs have changed during the history in git. How can this be solved? – nietras Mar 3 at 12:06

Proper way now is the following:

git filter-branch --prune-empty --subdirectory-filter FOLDER_NAME [first_branch] [another_branch]

GitHub now even have small article about such cases.

But be sure to clone your original repo to separate directory first (as it would delete all the files and other directories and you probable need to work with them).

So your algorithm should be:

  1. clone your remote repo to another directory
  2. using git filter-branch left only files under some subdirectory, push to new remote
  3. create commit to remove this subdirectory from your original remote repo
answered Nov 12 '14 at 13:22
Image (Asset 18/25) alt=

For what it's worth, here is how using GitHub on a Windows machine. Let's say you have a cloned repo in residing in C:\dir1. The directory structure looks like this: C:\dir1\dir2\dir3. The dir3 directory is the one I want to be a new separate repo.

Github:

  1. Create your new repository: MyTeam/mynewrepo

Bash Prompt:

  1. $ cd c:/Dir1
  2. $ git filter-branch --prune-empty --subdirectory-filter dir2/dir3 HEAD
    Returned: Ref 'refs/heads/master' was rewritten (fyi: dir2/dir3 is case sensitive.)

  3. $ git remote add some_name git@github.com:MyTeam/mynewrepo.git
    git remote add origin etc. did not work, returned "remote origin already exists"

  4. $ git push --progress some_name master

answered Feb 7 '12 at 19:07
Image (Asset 19/25) alt=
James Lawruk
15.7k138996

As I mentioned above, I had to use the reverse solution (deleting all commits not touching my dir/subdir/targetdir) which seemed to work pretty well removing about 95% of the commits (as desired). There are, however, two small issues remaining.

FIRST, filter-branch did a bang up job of removing commits which introduce or modify code but apparently, merge commits are beneath its station in the Gitiverse.

This is a cosmetic issue which I can probably live with (he says...backing away slowly with eyes averted).

SECOND the few commits that remain are pretty much ALL duplicated! I seem to have acquired a second, redundant timeline that spans just about the entire history of the project. The interesting thing (which you can see from the picture below), is that my three local branches are not all on the same timeline (which is, certainly why it exists and isn't just garbage collected).

The only thing I can imagine is that one of the deleted commits was, perhaps, the single merge commit that filter-branch actually did delete, and that created the parallel timeline as each now-unmerged strand took its own copy of the commits. (shrug Where's my TARDiS?) I'm pretty sure I can fix this issue, though I'd really love to understand how it happened.

In the case of crazy mergefest-O-RAMA, I'll likely be leaving that one alone since it has so firmly entrenched itself in my commit history—menacing at me whenever I come near—, it doesn't seem to be actually causing any non-cosmetic problems and because it is quite pretty in Tower.app.

answered May 31 '13 at 10:01
Image (Asset 20/25) alt=
Jay Allen
30915

I had exactly this problem but all the standard solutions based on git filter-branch were extremely slow. If you have a small repository then this may not be a problem, it was for me. I wrote another git filtering program based on libgit2 which as a first step creates branches for each filtering of the primary repository and then pushes these to clean repositories as the next step. On my repository (500Mb 100000 commits) the standard git filter-branch methods took days. My program takes minutes to do the same filtering.

It has the fabulous name of git_filter and lives here:

https://github.com/slobobaby/git_filter

on GitHub.

I hope it is useful to someone.

answered Mar 10 '14 at 17:39
Image (Asset 21/25) alt=
slobobaby
39839

Use this filter command to remove a subdirectory, while preserving your tags and branches:

git filter-branch --index-filter \
"git rm -r -f --cached --ignore-unmatch DIR" --prune-empty \
--tag-name-filter cat -- --all
answered Oct 28 '10 at 2:36
Image (Asset 22/25) alt=
cmcginty
38.9k26108119
   upvote
  flag
what is cat here? – rogerdpack Sep 19 at 18:35

It appears that most (all?) of the answers here rely on some form of git filter-branch --subdirectory-filter and its ilk. This may work "most times" however for some cases, for instance the case of when you renamed the folder, ex:

 ABC/
    /move_me # did some work here, then renamed it to

ABC/
    /move_me_renamed

If you do a normal git filter to extract "move_me_renamed" you will lose file change history that occurred from back when it was initially named move_me (ref).

It thus appears that the only way to really keep all change history (if yours is a case like this), is, in essence, to copy the repository (create a new repo, set that to be the origin), then nuke everything else and rename the subdirectory to the parent, ex:

  $ cp git_repo git_repo_subdir
  $ cd git_repo_subdir
  $ git rm "list of everything in the root that is not your_subdir"
  $ git mv your_subdir/* .
  $ rmdir your_subdir

(follow steps 6-11 here if you want to push this to a new repo).

This will not save you any space in your .git folder, but it will preserve all your change history for those files even across renames. And this may not be worth it if there isn't "a lot" of history lost, etc.

answered Sep 19 at 18:46
Image (Asset 23/25) alt=
rogerdpack
23.1k1393144

Check out git_split project at https://github.com/vangorra/git_split

Turn git directories into their very own repositories in their own location. No subtree funny business. This script will take an existing directory in your git repository and turn that directory into an independent repository of its own. Along the way, it will copy over the entire change history for the directory you provided.

./git_split.sh <src_repo> <src_branch> <relative_dir_path> <dest_repo>
        src_repo  - The source repo to pull from.
        src_branch - The branch of the source repo to pull from. (usually master)
        relative_dir_path   - Relative path of the directory in the source repo to split.
        dest_repo - The repo to push to.
answered Jan 6 at 2:42
Image (Asset 24/25) alt=
vangorra
918915

Put this into your gitconfig:

reduce-to-subfolder = !sh -c 'git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter cookbooks/unicorn HEAD && git reset --hard && git for-each-ref refs/original/ | cut -f 2 | xargs -n 1 git update-ref -d && git reflog expire --expire=now --all && git gc --aggressive --prune=now && git remote rm origin'
answered Mar 29 '13 at 20:18
Image (Asset 25/25) alt=
grosser
8,34942844

The Easier Way

  1. install git splits. I created it as a git extension, based on jkeating's solution.
  2. Split the directories into a local branch #change into your repo's directory cd /path/to/repo #checkout the branch git checkout XYZ
    #split multiple directories into new branch XYZ git splits -b XYZ XY1 XY2

  3. Create an empty repo somewhere. We'll assume we've created an empty repo called xyz on GitHub that has path : git@github.com:simpliwp/xyz.git

  4. Push to the new repo. #add a new remote origin for the empty repo so we can push to the empty repo on GitHub git remote add origin_xyz git@github.com:simpliwp/xyz.git #push the branch to the empty repo's master branch git push origin_xyz XYZ:master

  5. Clone the newly created remote repo into a new local directory
    #change current directory out of the old repo cd /path/to/where/you/want/the/new/local/repo #clone the remote repo you just pushed to git clone git@github.com:simpliwp/xyz.git

answered Feb 11 '15 at 10:28
AndrewD
912718
   upvote
  flag
An advantage of this method compared to "The Easy Way" is that the remote is already set up for the new repo, so you can immediately do a subtree add. In fact this way seems easier to me (even without git splits) – M.M May 12 '15 at 5:58

You might need something like "git reflog expire --expire=now --all" before the garbage collection to actually clean the files out. git filter-branch just removes references in the history, but doesn't remove the reflog entries that hold the data. Of course, test this first.

My disk usage dropped dramatically in doing this, though my initial conditions were somewhat different. Perhaps --subdirectory-filter negates this need, but I doubt it.

answered Jun 12 '09 at 2:49

I'm sure git subtree is all fine and wonderful, but my subdirectories of git managed code that I wanted to move was all in eclipse. So if you're using egit, it's painfully easy. Take the project you want to move and team->disconnect it, and then team->share it to the new location. It will default to trying to use the old repo location, but you can uncheck the use-existing selection and pick the new place to move it. All hail egit.

answered Feb 10 at 16:57
stu
2,966125172
   upvote
  flag
The "fine and wonderful" part of subtree is that your subdirectory's history comes along for the ride. If you don't need the history, then your painfully easy method is the way to go. – pglezen Feb 11 at 19:55

Your Answer

asked

7 years ago

viewed

182914 times

active

2 days ago

Blog

Featured on Meta

Hot Network Questions

Technology Life / Arts Culture / Recreation Science Other
  1. Stack Overflow
  2. Server Fault
  3. Super User
  4. Web Applications
  5. Ask Ubuntu
  6. Webmasters
  7. Game Development
  8. TeX - LaTeX
  1. Software Engineering
  2. Unix & Linux
  3. Ask Different (Apple)
  4. WordPress Development
  5. Geographic Information Systems
  6. Electrical Engineering
  7. Android Enthusiasts
  8. Information Security
  1. Database Administrators
  2. Drupal Answers
  3. SharePoint
  4. User Experience
  5. Mathematica
  6. Salesforce
  7. ExpressionEngine® Answers
  8. Cryptography
  1. Code Review
  2. Magento
  3. Signal Processing
  4. Raspberry Pi
  5. Programming Puzzles & Code Golf
  6. more (7)
  1. Photography
  2. Science Fiction & Fantasy
  3. Graphic Design
  4. Movies & TV
  5. Music: Practice & Theory
  6. Seasoned Advice (cooking)
  7. Home Improvement
  8. Personal Finance & Money
  1. Academia
  2. more (8)
  1. English Language & Usage
  2. Skeptics
  3. Mi Yodeya (Judaism)
  4. Travel
  5. Christianity
  6. English Language Learners
  7. Japanese Language
  8. Arqade (gaming)
  1. Bicycles
  2. Role-playing Games
  3. Anime & Manga
  4. Motor Vehicle Maintenance & Repair
  5. more (17)
  1. MathOverflow
  2. Mathematics
  3. Cross Validated (stats)
  4. Theoretical Computer Science
  5. Physics
  6. Chemistry
  7. Biology
  8. Computer Science
  1. Philosophy
  2. more (3)
  1. Meta Stack Exchange
  2. Stack Apps
  3. Area 51
  4. Stack Overflow Talent
site design / logo © 2016 Stack Exchange Inc; user contributions licensed under cc by-sa 3.0 with attribution required
rev 2016.12.1.4267