Detach (move) subdirectory into separate Git repository
Asked Answered
P

26

1983

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/
Pilliwinks answered 11/12, 2008 at 13:57 Comment(4)
This is trivial now with git filter-branch see my answer below.Insufficient
@Insufficient 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.Abampere
Use of git filter-branch is discouraged. See warning in docs.Inhere
Please use an alternative history filtering tool such as git filter-repo.Stare
D
1273

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 --space-- 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).

Disqualify answered 11/12, 2008 at 13:57 Comment(28)
Good point! That would apply to most people using git filter-branch this way. In my case, I was segregating a library which had its own series of release numbers in tags, so I didn't want the project tags in my new repo. I'll edit that into the answer.Disqualify
As of today, filter-branch is not supported on Windows. It looks like it is coming soon though. Check the msysgit discussion group (at google groups) for details.Tirza
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.Trashy
git-filter-branch set_ident() calls LANG=C LC_ALL=C sed ... for maximum compatibility with non UTF-8 aware sed. Therefore, git-filter-branch dies on commits with UTF-8 characters in the author: or committer: fields (see git show --pretty=raw <commit SHA1>). A workaround, use a UTF-8 aware sed (e.g. GNU sed) and edit git-filter-branch: LANG=en_US.UTF-8 sed -ne ...Bencher
Also the not-yet-included 'git filter-branch --split-submodule' can be used to preserve consistent history. vi-server.org/vi/bin/git-extract-submodule.patchGlassware
I find that if the /XYZ repo is large, the /ABC repo won't be as small as desirable. To further shrink things: git clone /ABC /ABC.small ; cd /ABC.small ; git repack -A -d ; git gc --prune=DATE where DATE is date +%Y-%m-%d. For some reason if I don't git clone and work on the clone, nothing shrinks. :-/Drubbing
And if you want to rewrite your tags to not reference the old structure, add --tag-name-filter catArmed
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.Trypsin
The git man page recommends, instead of rm -rf .git/refs/original/, git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d; I guess the latter is more robust if refs are not stored in the right place. Moreover, I believe that 'git remote rm origin' is also needed to shrink the repo, otherwise the refs from origin will keep objects referenced. @jonp, I think that was the problem for you. Finally, to also rewrite other branches, one must set them up manually with git branch after cloninng, -- --all and remove HEAD (which stops rewriting of other branches).Flambeau
For many readers (including me), the answer by itself was quite imprecise. Therefore, I incorporated most of the suggestions from comments after testing them. In particular, I made sure that after following this procedure, the repository is actually shrunk (which was not the case for me and various other people). I hope that is OK.Flambeau
To remove references in Windows you can use: for /f %a IN ('git for-each-ref --format="%(refname)" refs/original/') DO git update-ref -d %aPhilps
I found git-subtree to be effective as well, and much simpler. Found via this related question/answer: https://mcmap.net/q/12522/-how-to-extract-a-git-subdirectory-and-make-a-submodule-out-of-itTeerell
On Windows, I first tried the steps from the powershell prompt, but step 5b failed. Then I re-tried all steps from the git bash, and everything worked perfectly. Thanks!Peplos
From the 5 or so similar questions, this is the most valuable answer for me together with this link: gbayer.com/development/…Warms
@Gragg has suggested to edit and remove the --aggressive option to gc "as it makes the size of the repo explode: see also link"Nahshon
One important note is that if you have multiple branches being preserved in Step #2, then you'll have to run Step #4 and Step #5 through each one. Or at least thats what happened for me.Rakel
Does this not create ABC/ instead of ABC/ABC/?Carmichael
So, I've probably used this answer 10 times now and I keep wanting to upvote it again. Tons of stuff on stackoverflow has been useful, but this answer is the most useful so far. I've even put it in evernote for super quick referencing. Thanks!Lapboard
How to use this if you want to take 2 directories?Groveman
N.B. this leaves the new repo structured as /ABC/ABC, instead of just /ABC (though someone edited the question after the fact to say that was what was wanted)Freiburg
@Disqualify - it should be noted that this approach fails if the ABC folder ever had a different name - in this case, the history stops at the point when it was renamed.Peptize
That didn't reclaim disk space for me, the .git folder must have a bunch of unused history still in there as the pruned folder is 10s of KB yet the .git is 1.5 GB! Any ideas?Gastrocnemius
After I used filter-branch, all of the files in that directory were removed.Turbid
Really useful this is. However, if /ABC used to be called /XY3 until 3 commits before HEAD, will the resulting ABC repository not only have these 3 commits? At least this is what I have found.Spoken
These answers are a bit confusing... Are the five steps in this answer now N/A? Is the "The Easy Way" answer below the right answer? When I first saw this answer, I figured the 'Detach subdirectory into separate Git repository' link was to more in depth documentation and was summarized by the five steps that followed. Can this be clarified? Perhaps add a header above the five steps, like "The Old Way (pre v1.7.11).Hatchery
Github wiki with the same steps help.github.com/articles/…Iceberg
Also, instead of cloning a repo in the first place, you can directly add a submodule which adds the new repo to git and creates the new repo into your current repo as well !Concede
github.com/newren/git-filter-repo is the suggested way by git itself to easily do this.Mylesmylitta
T
1603

The Easy Way™

It turns out that this is such a common and useful practice that the overlords of Git made it really easy (added in version 1.7.11 - May 2012). Also, there's a real-world example in the walkthrough below.

  1. Prepare the old repo

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

    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: <name-of-new-branch> is a branch you will be creating in the existing/old repo, NOT the new one [that comes later].

    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> && cd ~/<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 <[email protected]:user/new-repo.git>
     git push -u origin master
    
  4. Cleanup inside <big-repo>, if desired

     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 ~/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

cd ~/node-browser-compat/
git subtree split -P btoa -b btoa-only

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

mkdir ~/btoa/ && cd ~/btoa/
git init
git pull ~/node-browser-compat btoa-only

Next, I create a new repo on GitHub or Bitbucket, or whatever and add it as the origin

git remote add origin [email protected]:node-browser-compat/btoa.git
git push -u origin 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 master
git push origin master

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

git rm -rf btoa

Clearing your history

By default, removing files from Git doesn't actually remove them; it just commits that they aren't there any more. If you want to actually remove the historical references (i.e. you 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 delete 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

Toronto answered 25/7, 2013 at 17:10 Comment(41)
...in particular, not on Ubuntu, even not when using ppa:git-core/ppa :-(Cardona
@rui.araujo: I ended up with creating a small shell script in my ~/bin directory that sources git-subtree.sh: gist.github.com/6338779. Of course this has to be chmod +x-d, too,Cardona
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.Cathodoluminescence
You can override the push error when pushing to GitHub by doing git push -f to force the overwriting.Palmitin
@rui.araujo your comment is very useful and makes the answer complete. It would be good if it was added to the actual answer.Sallie
This doesn't work for me. When I call git pull </path/to/big-repo> <name-of-new-branch>, I get an error: Couldn't find remote ref <name-of-new-branch>.Dripstone
This really helped me with a lot of repositories, specially using svn2git and then splitting the repository into multiple Git repos... Look at github.com/marcellodesales/svnedge-* for examples.Nutrient
according to kernel.org/pub/software/scm/git/docs/git-filter-branch.html for git filter-branch, using git rm instead of rm is faster.Ignoramus
on ubuntu 13.10 you can find it in the specified location: /usr/share/doc/git/contrib/subtree/git-subtree.sh (without having to use the ppa).Underbrush
When cleaning up, shouldn't the branch created with git subtree be deleted as well?Polyphone
What about if we have a subdirectory which was previously renamed? I tried this on a <name-of-folder> which at some point in time was renamed from <another-name-of-folder>. When I look at the history in <new-repo>, the initial commit is the one which renamed the folder - all of the history before that is erased from <new-repo>Peptize
re "latest git on OS X": there is no need to mess with brew or any other extra package manager because the XCode command line tools include Git. Available from Apple free of charge, installs/updates without any hassle.Cystitis
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?Cephalization
In the example it looks like it's just foo names to me. xyz, xyz1, xyz2, etc. Anyway mkdir ABC/; git mv ./* ABC/. I'm sure there's some way to give a prefix - maybe git read-tree --prefix=spoon-knife/ -u spoon-knife/master. See help.github.com/articles/about-git-subtree-merges.Toronto
To pull the split subtree into a folder: git subtree add --prefix <new/folder/path> </path/to/big-repo> <name-of-new-branch>Getraer
@Getraer I tried this on an empty repo and it didn't work. But I added my first commit and after that your solution worked for me.Ambivalence
Is there a way to apply this method in order to split multiple sub folders into a new git repository?Frizzy
What I finally did to split multiple sub folders into a new project was in 1. before the subtree split, move the sub folders I wanted into a unique sub-folder, rewriting history to keep it when pulling (step 2.): git filter-branch --tree-filter "mkdir <name-of-folder>; mv sub1 sub2 <name-of-folder>/" HEADFrizzy
@AnthonyO. Could you elaborate on your method ? Or even write an answer ? I am stuck here and would upvote it for sure.Uprise
I finally used the solution given in stackoverflow.com/questions/19954485/… it works well for me so far.Uprise
Hey @AnthonyO. I am trying something similar to what you wrote to move multiple sub-directories, but its failing for me. The error I get is rename sub1 to folder/sub1: No such file or directory and same for the second directory. Am I missing something ?Cominform
@Cominform & PierreHenry : I've written what I did in a separated answer. Here it is: https://mcmap.net/q/12521/-detach-move-subdirectory-into-separate-git-repositoryFrizzy
Hey @AnthonyO. I also asked a similar question here: Splitting many subdirectories into a new, separate Git repository and got it to work. But thanks, I will try out your answer too.Cominform
It's probably a good idea to pass --prune-empty to filter-branch to remove the empty commits that happen due to removing the directoryComplaisant
As a corollary to this very useful answer - should anyone else use the subtree approach, be advised to manually split-out the .gitattributes file! I fell victim to this and got a CRLF/LF warning after adding a single ' ' to a file.Gild
As far as I understand with this method (git subtree) you will loose both branches and tags. Using git filter-branch-method as described in https://mcmap.net/q/12521/-detach-move-subdirectory-into-separate-git-repository it is possible to keep/rewrite branches and tags.Gragg
This solution doesn't preserve history.Aricaarick
If your objective is to link the new repository as a submodule of the original project, then you need this answer (Usage §) of "How to extract a git subdirectory and make a submodule out of it?".Bloomsbury
If at some point in the past you ran git subtree, you may get a fatal: bad object error when running the command again. Avoid this by passing in the --ignore-joins option.Louvre
This method turns out to be essentially useless -- if you follow it you lose ALL branches other than master, and all the history that may be in those branches.Easement
See rogerdpack's answer if you ever did any directory renames/moves in your commits which you wish to preserve. The filter-branch and subtree methods will not keep truncate commits at directory renames.Peptize
The special directory path requirements highlight the big problem of git being internally inconsistent sometimes, which makes it that much more obtuse.Homozygote
The popd and pushd command make this rather implicit and harder to grok what it intends to do ...Lorant
The git subtree split command is much slower than the filter-branch approach.Churchwoman
On CentOS 7, do sudo yum install git-subtree to get the subtree command.Psychomotor
what about if your initial repository has many branches? this solution just preserves the master branchInsignificance
Using the git subtree approach describe here, is there a way to preserve existing branches (and tags)? Since the whole relevant history is kept, that should be about restoring a pointer to a given commit?Sleeper
How would you keep the btoa folder in the new repository?Dancette
For those coming here in 2022: A very good (probably better) alternative to the answer provided here, based on git filter-repo, can be found further down in stackoverflow.com/questions/359424/…Beale
Could I be getting an issue because original subfolder was in a private repo and the new one is public?Isagoge
@AnatolyBugakov No. git is completely unaware any settings on GitHub. The CLI program was created without any relation to the SaaS product.Toronto
D
1273

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 --space-- 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).

Disqualify answered 11/12, 2008 at 13:57 Comment(28)
Good point! That would apply to most people using git filter-branch this way. In my case, I was segregating a library which had its own series of release numbers in tags, so I didn't want the project tags in my new repo. I'll edit that into the answer.Disqualify
As of today, filter-branch is not supported on Windows. It looks like it is coming soon though. Check the msysgit discussion group (at google groups) for details.Tirza
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.Trashy
git-filter-branch set_ident() calls LANG=C LC_ALL=C sed ... for maximum compatibility with non UTF-8 aware sed. Therefore, git-filter-branch dies on commits with UTF-8 characters in the author: or committer: fields (see git show --pretty=raw <commit SHA1>). A workaround, use a UTF-8 aware sed (e.g. GNU sed) and edit git-filter-branch: LANG=en_US.UTF-8 sed -ne ...Bencher
Also the not-yet-included 'git filter-branch --split-submodule' can be used to preserve consistent history. vi-server.org/vi/bin/git-extract-submodule.patchGlassware
I find that if the /XYZ repo is large, the /ABC repo won't be as small as desirable. To further shrink things: git clone /ABC /ABC.small ; cd /ABC.small ; git repack -A -d ; git gc --prune=DATE where DATE is date +%Y-%m-%d. For some reason if I don't git clone and work on the clone, nothing shrinks. :-/Drubbing
And if you want to rewrite your tags to not reference the old structure, add --tag-name-filter catArmed
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.Trypsin
The git man page recommends, instead of rm -rf .git/refs/original/, git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d; I guess the latter is more robust if refs are not stored in the right place. Moreover, I believe that 'git remote rm origin' is also needed to shrink the repo, otherwise the refs from origin will keep objects referenced. @jonp, I think that was the problem for you. Finally, to also rewrite other branches, one must set them up manually with git branch after cloninng, -- --all and remove HEAD (which stops rewriting of other branches).Flambeau
For many readers (including me), the answer by itself was quite imprecise. Therefore, I incorporated most of the suggestions from comments after testing them. In particular, I made sure that after following this procedure, the repository is actually shrunk (which was not the case for me and various other people). I hope that is OK.Flambeau
To remove references in Windows you can use: for /f %a IN ('git for-each-ref --format="%(refname)" refs/original/') DO git update-ref -d %aPhilps
I found git-subtree to be effective as well, and much simpler. Found via this related question/answer: https://mcmap.net/q/12522/-how-to-extract-a-git-subdirectory-and-make-a-submodule-out-of-itTeerell
On Windows, I first tried the steps from the powershell prompt, but step 5b failed. Then I re-tried all steps from the git bash, and everything worked perfectly. Thanks!Peplos
From the 5 or so similar questions, this is the most valuable answer for me together with this link: gbayer.com/development/…Warms
@Gragg has suggested to edit and remove the --aggressive option to gc "as it makes the size of the repo explode: see also link"Nahshon
One important note is that if you have multiple branches being preserved in Step #2, then you'll have to run Step #4 and Step #5 through each one. Or at least thats what happened for me.Rakel
Does this not create ABC/ instead of ABC/ABC/?Carmichael
So, I've probably used this answer 10 times now and I keep wanting to upvote it again. Tons of stuff on stackoverflow has been useful, but this answer is the most useful so far. I've even put it in evernote for super quick referencing. Thanks!Lapboard
How to use this if you want to take 2 directories?Groveman
N.B. this leaves the new repo structured as /ABC/ABC, instead of just /ABC (though someone edited the question after the fact to say that was what was wanted)Freiburg
@Disqualify - it should be noted that this approach fails if the ABC folder ever had a different name - in this case, the history stops at the point when it was renamed.Peptize
That didn't reclaim disk space for me, the .git folder must have a bunch of unused history still in there as the pruned folder is 10s of KB yet the .git is 1.5 GB! Any ideas?Gastrocnemius
After I used filter-branch, all of the files in that directory were removed.Turbid
Really useful this is. However, if /ABC used to be called /XY3 until 3 commits before HEAD, will the resulting ABC repository not only have these 3 commits? At least this is what I have found.Spoken
These answers are a bit confusing... Are the five steps in this answer now N/A? Is the "The Easy Way" answer below the right answer? When I first saw this answer, I figured the 'Detach subdirectory into separate Git repository' link was to more in depth documentation and was summarized by the five steps that followed. Can this be clarified? Perhaps add a header above the five steps, like "The Old Way (pre v1.7.11).Hatchery
Github wiki with the same steps help.github.com/articles/…Iceberg
Also, instead of cloning a repo in the first place, you can directly add a submodule which adds the new repo to git and creates the new repo into your current repo as well !Concede
github.com/newren/git-filter-repo is the suggested way by git itself to easily do this.Mylesmylitta
O
140

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.

Oporto answered 5/6, 2009 at 13:15 Comment(5)
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.Indoors
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.Lohse
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."Ploughshare
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.Kreegar
I would suggest editing Paul's answer, only because Paul's is so thorough.Kalvin
S
98

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.

Suppletory answered 19/10, 2009 at 21:10 Comment(5)
I think somethink like git gc --aggressive --prune=now is still missing, isn't it?Seale
@Seale The repack command takes care of that, and there wouldn’t be any loose objects.Suppletory
yeah, git gc --aggressive --prune=now reduced much of new repoLawyer
Simple and elegant. Thanks!Sterilant
And after all this I'm still getting the same error that I was getting before. fatal: packed object xxxxxx (stored in .git/objects/pack/pack-yyyyyyyy.pack) is corruptClog
D
56

When running git filter-branch using a newer version of git (2.22+ maybe?), it says to use this new tool git-filter-repo. This tool certainly simplified things for me.

Filtering with filter-repo

Commands to create the XYZ repo from the original question:

# create local clone of original repo in directory XYZ
tmp $ git clone [email protected]:user/original.git XYZ

# switch to working in XYZ
tmp $ cd XYZ

# keep subdirectories XY1 and XY2 (dropping ABC)
XYZ $ git filter-repo --path XY1 --path XY2

# note: original remote origin was dropped
# (protecting against accidental pushes overwriting original repo data)

# XYZ $ ls -1
# XY1
# XY2

# XYZ $ git log --oneline
# last commit modifying ./XY1 or ./XY2
# first commit modifying ./XY1 or ./XY2

# point at new hosted, dedicated repo
XYZ $ git remote add origin [email protected]:user/XYZ.git

# push (and track) remote master
XYZ $ git push -u origin master

assumptions: * remote XYZ repo was new and empty before the push

Filtering and moving

In my case, I also wanted to move a couple of directories for a more consistent structure. Initially, I ran that simple filter-repo command followed by git mv dir-to-rename, but I found I could get a slightly "better" history using the --path-rename option. Instead of seeing last modified 5 hours ago on moved files in the new repo I now see last year (in the GitHub UI), which matches the modified times in the original repo.

Instead of...

git filter-repo --path XY1 --path XY2 --path inconsistent
git mv inconsistent XY3  # which updates last modification time

I ultimately ran...

git filter-repo --path XY1 --path XY2 --path inconsistent --path-rename inconsistent:XY3
Notes:
  • I thought the Git Rev News blog post explained well the reasoning behind creating yet another repo-filtering tool.
  • I initially tried the path of creating a sub-directory matching the target repo name in the original repository and then filtering (using git filter-repo --subdirectory-filter dir-matching-new-repo-name). That command correctly converted that subdirectory to the root of the copied local repo, but it also resulted in a history of only the three commits it took to create the subdirectory. (I hadn't realized that --path could be specified multiple times; thereby, obviating the need to create a subdirectory in the source repo.) Since someone had committed to the source repo by the time I noticed that I'd failed to carry forward the history, I just used git reset commit-before-subdir-move --hard after the clone command, and added --force to the filter-repo command to get it to operate on the slightly modified local clone.
git clone ...
git reset HEAD~7 --hard      # roll back before mistake
git filter-repo ... --force  # tell filter-repo the alterations are expected
  • I was stumped on the install since I was unaware of the extension pattern with git, but ultimately I cloned git-filter-repo and symlinked it to $(git --exec-path):
ln -s ~/github/newren/git-filter-repo/git-filter-repo $(git --exec-path)
Doubletalk answered 21/11, 2019 at 21:22 Comment(4)
Using git-filter-repo should definitely be the preferred approach at this point. It’s much, much faster and safer than git-filter-branch, and safeguards against a lot of the gotchas one can run into when rewriting one’s git history. Hopefully this answer gets some more attention, since it’s the one to address git-filter-repo.Ozan
acutally I am currently trying to get things to work with git filter-repo but unfortunately after running it, I am missing files, which were added in a commit, containing a path that was removed by filter-repo. For example: Foo/ Foo.cs Bar/ Bar.cs All were added in the same commit. I want to move Foo and Bar in separate repos. So I cloned my repo in a folder matching the new repo name and did git filter-repo -path Foo Foo get's removed too. I am talking about a much bigger repo and it is working for every other file but not if it's a constellation like this.Konya
If files were previously moved/renamed, this will not automatically retain the history before the move/rename. However, if you include the original paths/filenames in the command, that history will not be removed. For example, git filter-repo --path CurrentPathAfterRename --path OldPathBeforeRename. git filter-repo --analyze produces a file renames.txt that can be helpful in determining these. Alternatively, you may find a script like this helpful.Lithic
to move the subdir content to the root folder and keep only subdir files and corresponding history: git filter-repo --subdirectory-filter path/to/subdirCorabelle
P
40

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
Poolroom answered 9/6, 2011 at 15:41 Comment(0)
I
27

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 .
Insufficient answered 20/8, 2014 at 14:11 Comment(5)
This worked like a charm. YOUR_SUBDIR in the above example is the subdirectory that you want to KEEP, everything else will be removedAcidforming
Updates based on you comment.Insufficient
This does not answer the question. From the docs it says The result will contain that directory (and only that) as its project root. and indeed this is what you will get, i.e. the original project structure is not preserved.Spadework
@Spadework Can you illustrate your issue with XYZ and ABC as in the question, to show what's wrong?Peptize
@Insufficient is it possible to reuse the cloned repo and not use a new repo, i.e. my question here stackoverflow.com/questions/49269602/…Dementia
W
19

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/

Wagonage answered 22/3, 2010 at 20:55 Comment(1)
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.Apiculture
F
19

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 <[email protected]: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.

Frizzy answered 6/8, 2015 at 15:26 Comment(3)
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.Bookmobile
This does not work if any of the subdirs have changed during the history in git. How can this be solved?Kruter
@Kruter see rogerdpack's answer. Took me a while to find it after reading and absorbing all the info in these other answers.Peptize
M
13

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.

I lost contiuity after filter-branch

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

Melbamelborn answered 17/4, 2012 at 5:12 Comment(4)
I like the style of that graph. May I ask what tool you're using?Hagbut
Tower for Mac. I really like it. It's almost worth switching to Mac for in itself.Melbamelborn
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!Soricine
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 -- --allSoricine
B
7

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
Banderole answered 25/7, 2009 at 10:1 Comment(0)
M
7

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_this_dir # did some work here, then renamed it to

ABC/
    /move_this_dir_renamed

If you do a normal git filter style to extract "move_this_dir_renamed" you will lose file change history that occurred from back when it was initially "move_this_dir" (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 like this:

  1. Clone the multi-module project locally
  2. Branches - check what's there: git branch -a
  3. Do a checkout to each branch to be included in the split to get a local copy on your workstation: git checkout --track origin/branchABC
  4. Make a copy in a new directory: cp -r oldmultimod simple
  5. Go into the new project copy: cd simple
  6. Get rid of the other modules that aren't needed in this project:
  7. git rm otherModule1 other2 other3
  8. Now only the subdir of the target module remains
  9. Get rid of the module subdir so that the module root becomes the new project root
  10. git mv moduleSubdir1/* .
  11. Delete the relic subdir: rmdir moduleSubdir1
  12. Check changes at any point: git status
  13. Create the new git repo and copy its URL to point this project into it:
  14. git remote set-url origin http://mygithost:8080/git/our-splitted-module-repo
  15. Verify this is good: git remote -v
  16. Push the changes up to the remote repo: git push
  17. Go to the remote repo and check it's all there
  18. Repeat it for any other branch needed: git checkout branch2

This follows the github doc "Splitting a subfolder out into a new repository" steps 6-11 to push the module 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. But at least you are guaranteed not to lose older commits!

Mcgovern answered 19/9, 2016 at 18:46 Comment(1)
Found the needle in the git haystack! Now I can keep ALL my commit history.Peptize
S
6

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
Squinch answered 12/11, 2014 at 13:22 Comment(0)
D
6

I recommend GitHub's guide to splitting subfolders into a new repository. The steps are similar to Paul's answer, but I found their instructions easier to understand.

I have modified the instructions so that they apply for a local repository, rather than one hosted on GitHub.


Splitting a subfolder out into a new repository

  1. Open Git Bash.

  2. Change the current working directory to the location where you want to create your new repository.

  3. Clone the repository that contains the subfolder.

git clone OLD-REPOSITORY-FOLDER NEW-REPOSITORY-FOLDER
  1. Change the current working directory to your cloned repository.

cd REPOSITORY-NAME
  1. To filter out the subfolder from the rest of the files in the repository, run git filter-branch, supplying this information:
    • FOLDER-NAME: The folder within your project that you'd like to create a separate repository from.
      • Tip: Windows users should use / to delimit folders.
    • BRANCH-NAME: The default branch for your current project, for example, master or gh-pages.

git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME  BRANCH-NAME 
# Filter the specified branch in your directory and remove empty commits
Rewrite 48dc599c80e20527ed902928085e7861e6b3cbe6 (89/89)
Ref 'refs/heads/BRANCH-NAME' was rewritten
Diffidence answered 31/8, 2017 at 14:2 Comment(3)
Nice post, but I notice the first paragraph of the doc you linked says If you create a new clone of the repository, you won't lose any of your Git history or changes when you split a folder into a separate repository. Yet according to comments on all the answers here both filter-branch and the subtree script result in the loss of history wherever a subdirectory has been renamed. Is there anything that can be done to address this?Peptize
Found the solution for preserving all commits, including those preceding directory renames/moves - it's rogerdpack's answer to this very question.Peptize
The only problem is that I can't use the cloned repo any moreDementia
L
5

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.

Livi answered 10/3, 2014 at 17:39 Comment(0)
K
4

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
Kutchins answered 28/10, 2010 at 2:36 Comment(1)
what is cat here?Mcgovern
D
4

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 [email protected]:MyTeam/mynewrepo.git
    git remote add origin etc. did not work, returned "remote origin already exists"

  4. $ git push --progress some_name master

Duhl answered 7/2, 2012 at 19:7 Comment(0)
S
3

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.

Soricine answered 31/5, 2013 at 10:1 Comment(0)
S
3

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 : [email protected]: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 [email protected]: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 [email protected]:simpliwp/xyz.git

Situla answered 11/2, 2015 at 10:28 Comment(2)
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)Puto
Props to AndrewD for posting this solution. I have forked his repo to make it work on OSX (github.com/ricardoespsanto/git-splits) if that's useful to anyone elseTymon
A
2

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.

Athelstan answered 12/6, 2009 at 2:49 Comment(0)
G
2

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.
Gina answered 6/1, 2016 at 2:42 Comment(0)
C
1

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'
Cunctation answered 29/3, 2013 at 20:18 Comment(0)
B
1

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.

Brentonbrentt answered 10/2, 2016 at 16:57 Comment(1)
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.Bookmobile
A
1

You can easily try the https://help.github.com/enterprise/2.15/user/articles/splitting-a-subfolder-out-into-a-new-repository/

This worked for me. The issues i faced in the steps given above are

  1. in this command git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME BRANCH-NAME The BRANCH-NAME is master

  2. if the last step fails when committing due to protection issue follow - https://docs.gitlab.com/ee/user/project/protected_branches.html

Acrylic answered 10/1, 2019 at 7:23 Comment(0)
W
0

I've found quite straight forward solution, The idea is to copy repository and then just remove unnecessary part. This is how it works:

1) Clone a repository you'd like to split

git clone [email protected]:testrepo/test.git

2) Move to git folder

cd test/

2) Remove unnecessary folders and commit it

rm -r ABC/
git add .
enter code here
git commit -m 'Remove ABC'

3) Remove unnecessary folder(s) form history with BFG

cd ..
java -jar bfg.jar --delete-folders "{ABC}" test
cd test/
git reflog expire --expire=now --all && git gc --prune=now --aggressive

for multiply folders you can use comma

java -jar bfg.jar --delete-folders "{ABC1,ABC2}" metric.git

4) Check that history doesn't contains the files/folders you just deleted

git log --diff-filter=D --summary | grep delete

5) Now you have clean repository without ABC, so just push it into new origin

remote add origin [email protected]:username/new_repo
git push -u origin master

That's it. You can repeat the steps to get another repository,

just remove XY1,XY2 and rename XYZ -> ABC on step 3

Workbook answered 28/3, 2019 at 14:7 Comment(2)
Nearly perfect ... but you forgot "git filter-branch --prune-empty" to remove all old commits that are now empty. To do before push to origin master !Margit
If you made the mistake and still want to "repush" after having removed old empty commit, perform : "git push -u origin master --force-with-lease"Margit
H
-1

Found this wonderful article Original reference easy to follow. Documenting it here in case if it get's inaccessible.

1. Preparing the current repository

$ cd path/to/repository
$ git subtree split -P my-folder -b my-folder
Created branch 'my-folder'
aecbdc3c8fe2932529658f5ed40d95c135352eff

The name of the folder must be a relative path, starting from the root of the repository.

2. Creating the new repository

$ cd my-folder
$ git init
Initialized empty Git repository in /Users/adamwest/Projects/learngit/shop/my-folder/.git/
$ git add .
$ git commit -m "initial commit"
[master (root-commit) 192c10b] initial commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 file

Here we just need to cd to the new folder, initialise the new repository, and commit any contents.

3.Add new remote repository and push

$ git remote add origin [email protected]:robertlyall/my-folder.git
$ git push origin -u master
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 199 bytes | 199.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To github.com:robertlyall/my-folder.git
 * [new branch]      master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

We add the new repository remote from GitHub here, then push our first commit to it.

4. Remove folder from main repository and push

$ cd ../
$ git rm -rf my-folder
rm 'my-folder/file'
$ git commit -m "Remove old folder"
[master 56aedbe] remove old folder
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 my-folder/file
$ git push
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Delta compression using up to 4 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 217 bytes | 217.00 KiB/s, done.
Total 2 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:robertlyall/shop.git
   74dd8b3..56aedbe  master -> master

Finally, we cd back to the rooot directory, remove the folder from our main repository, then commit and push the change. Now, we have the folder in our main repository but linked to a completely separate repository that can be reused across multiple projects.

Histochemistry answered 10/7, 2021 at 12:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.