git move directory to another repository while keeping the history

I can't help you with git subtree, but with filter-branch it's possible.

First you need to create a common repository that will contain both source and destination branches. This can be done by adding a new "remote" beside "origin" and fetching from the new remote.

Use filter-branch on the source branch to rm -rf all directories except dir-to-move. After that you'll have a commit history that can be cleanly rebased or merged into the destination branch. I think the easiest way is to cherry-pick all non-empty commits from the source branch. The list of these commits can be obtained by running git rev-list --reverse source-branch -- dir-to-move

Of course, if the history of dir-to-move is non-linear (already contains merge commits), then it won't be preserved by cherry-pick, so git merge can be used instead.

Example create common repo:

cd repo-2
git remote add source ../repo-1
git fetch source

Example filter branch

cd repo-2
git checkout -b source-master source/master
CMD="rm -rf dir1 dir2 dir3 dir5"
git filter-branch --tree-filter "$CMD"

Example cherry-pick into destination master

cd repo-2
git checkout master
git cherry-pick `git rev-list --reverse source-master -- dir-to-move`

FWIW, the following worked for me after a number of iterations.

Clone both the repos to a temporary work area.

git clone <repourl>/repo-1.git 
git clone <repourl>/repo-2.git
cd repo-1
git remote rm origin # delete link to original repository to avoid any accidental remote changes
git filter-branch --subdirectory-filter dir-to-move -- --all  # dir-to-move is being moved to another repo.  This command goes through history and files, removing anything that is not in the folder.  The content of this folder will be moved to root of the repo as a result. 
# This folder has to be moved to another folder in the target repo.  So, move everything to another folder.
git filter-branch -f --index-filter \
'git ls-files -s | /usr/local/bin/sed -e "s/\t\"*/&dir-to-move\//" |
    GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
        git update-index --index-info &&
 mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD
# Above command will go through history and rewrite the history by adding dir-to-move/ to each files.  As a result, all files will be moved to a subfolder /dir-to-move.  Make sure to use gnu-sed as OSX sed doesn't handle some extensions correctly.  For eg. \t

Now switch to target repo and fetch everything from source.

git clone repo-2
git remote add master ../repo-1/
git pull master master --allow-unrelated-histories
git push  # push everything to remote 

Above steps assume that master branches are used for both source and targets. However, tags and branches were ignored.


My approach to move dir-to-move to repo 2. Start by cloning the repo 1 in a new location and cd to the repo 1 folder. In my case I am moving the folder from repo 1 branch develop to repo 2 where develop is not existent yet.

git filter-branch --subdirectory-filter dir-to-move -- --all     #line 1
git remote -v                                                    #line 2
git remote set-url origin repo_2_remote_url                      #line 3
git remote -v                                                    #line 4
git push origin develop                                          #line 5

Explanation for each line:

  1. Cleans all commits not related to dir-to-move, removes all files outside that folder, moves all contents in the root folder repo 1. From git docs:

Only look at the history which touches the given subdirectory. The result will contain that directory (and only that) as its project root

  1. You are still pointing to your origin repo 1 url
  2. Replace the origin url for your current folder to point to repo 2
  3. Check the URLs have been updated correctly
  4. Push to repo 2 the (newly created, in this case) develop branch

You can now clone or pull or fetch your repo 2 repository. You will have under repo 2 folder the contents of dir-to-move along with relevant history as expected.