May 12, 2012

Extract a Directory From a Git Branch/Repository Preserving The History

At work we wanted to extract from a branch a subdirectory containing a specific feature that had to be used from everyone in the team.

Just for sake of example, let's define the terms of our problem. Our repository (MAIN) structure looks like:

MAIN/
        Library/
            libA/
            libB/
            libC/

Our goal is to extract the libA contained in the branch FTR-B in its own branch called FTR-A.

If you are not interested in preserving the history the task is quite easy. Git checkout is our friend in this case.

$ git checkout master
$ git checkout -b FTR-A
$ git checkout FTR-B Library/libA
$ git commit -a -m "Extract libA"
$ git push

More complicated is the case in which preserving the history is a matter of importance. For God's sake git has a powerful command git filter-branch. The following are my notes and observations, for pleasing my poor memory.

  • Clone the local repository into a temporary repository DIRTY.
$ git clone ~/MAIN/ ~/DIRTY/
$ cd DIRTY
$ git checkout FTR-B
  • Remove everything except the desired subdirectory ( libA ).
$ git filter-branch --prune-empty --tree-filter 'rm -rf Library/libB Library/libC'

Basically, Git filter-branch executes a command on each commit in a specific branch. Instead of the option tree-filter you could have used subdirectory-filter that removes everything except the desired directory moving it up to the root project. I will explain it better in another post.

  • Clean all the cruft
$ git gc --aggressive
  • Merge the new feature in its own branch FTR-A.
$ cd ~/MAIN
$ git remote add dirty ~/DIRTY
$ git fetch dirty
$ git branch dirty remotes/tools/FTR-B
$ git checkout master
$ git checkout -b FTR-A
$ git merge dirty
$ git remote rm dirty
  • Create a pull request