Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I git "subtree split" but maintain the full folder hierarchy?

Tags:

git

I've been attempting to detatch a project sub-library into a new repo, but I want to detatch the full folder path, not just the target folder down.

When I filter, the resulting folder structure is parented from the target folder rather than the repository root (or an ancestor I specify) which effectively breaks package structure as classes no longer correctly match folders:

Original repo:

+- repo/master
    +- lib1
    +- lib2
    +- lib3
        +- ClassA
        +- ClassB
        +- ClassC

Git command:

git subtree split -P lib3 -b new-branch

Resulting structure:

+- repo/new-branch
    +- ClassA
    +- ClassB
    +- ClassC

Required structure:

+- repo/new-branch
    +- lib3
        +- ClassA
        +- ClassB
        +- ClassC

Is there any way to do this? I've bean reading about rebasing to somehow add the root "core" folder as if it was there before the split, but my git-fu is not strong enough, sadly :(

The other thing I tried is filter-branch to remove other directories, but I could only seem to remove one root-level folder, then git started complaining when I tried others.

Thanks :)

like image 609
Dave Stewart Avatar asked Oct 05 '15 03:10

Dave Stewart


1 Answers

git subtree split doesn't appear to offer an option for what you want (but it sounds useful so maybe you could contribute one to the project!)

So, there's two ways to do this, depending what you want.

1) Export a single directory (simpler option)

This takes advantage of the fact you want to move to another repo, so we can extract the subtree, and then relocate it in separate steps.

  1. Use git subtree split to extract the files you want to the an intermediate branch in your repository (you have already done this).

     git subtree split -P lib3 -b new-branch
    
  2. Create a new, empty repository:

     git init lib3-repo
     cd lib3-repo
     git commit --allow-empty -m 'Initial commit'
    
  3. Add the contents of the intermediate branch as a subtree:

     git subtree add -P lib3 repo new-branch
    

This should rewrite the history again and reinsert the missing directory level.

Every time you want to exchange history between the two repos you'll have to go through the intermediate branch (i.e. subtree split, then subtree pull), but it ought to work.

2) Export any set of files (more complex)

To keep multiple, specific subtrees, you'll need git filter-branch.

There are lots of ways to pick and choose which commits and files to keep or discard, but this recipe uses --index-filter to select files without having any access to the contents of the files.

To keep all files in the "lib3" and "src/core" directories, without editing their locations in any way.

git co -b new-branch
git filter-branch --index-filter \
    'git ls-files \
       | grep -v "^lib3/\|^src/core/" \
       | xargs --no-run-if-empty git rm --cached' \
    HEAD

The filter code is a shell-script that edits the Git index (we're using --index-filter, remember).

git ls-files is the same as ls except that it lists files in the repo, not in the working tree.

grep -v <pattern> gives everything that does not match the pattern, and \| in the pattern is an alternative, so we get the list of files to delete.

xargs --no-run-if-empty runs a command for each filename in the input from the pipe (unless there aren't any).

git rm --cached deletes files from the index.

This creates a branch (new-branch) that has the filtered files you want. You can import them into another repo using a normal pull command:

git init new-repo
cd new-repo

git remote add origin /path/to/old-repo 
git pull origin new-branch
like image 101
ams Avatar answered Oct 17 '22 11:10

ams