How to move directory from a git repo to another

This post is a copy from Matthias Schoettle’s blog

Let us just moved one directory within a Git repository to a directory within another repository including its history. For example:

repositoryA/
```console
.........../directoryToKeep
.........../otherDirectory
.........../someFile.ext

repositoryB/
.........../someStuff

The goal is to move directoryToKeep into repositoryB with its history, i.e., all commits that affect directory1. If instead, you want to create a repository just for the contents of directoryToKeep, just skip the last step of the preparation of the source repository.

A. Prepare the source repository

  1. Clone repositoryA (make a copy, don’t use your already existing one)
  2. cd to it
  3. Delete the link to the original repository to avoid accidentally making any remote changes
    git remote rm origin
  4. Using filter-branch, go through the complete history and remove all commits (or keep all commits affecting directoryToKeep) not related to directoryToKeep.

    git filter-branch --subdirectory-filter <directoryToKeep> -- --all

    From the git documenta

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

    You might need to add --prune-empty to avoid empty commits, in my case it was not necessary.
    This means that the result will be repositoryA containing the contents of directoryToKeep directly, which is also reflected in all the commits. If you want to create a separate repository just for directoryToKeep, skip the next step. If instead you want to move directoryToKeep to repositoryB into its own directory, you basically have two options. You might be fine with the way the commits are and create an additional commit that moves all files into a directory. However, if you are a perfectionist like myself, you can perform the following command to move directoryToKeep into its own directory, which will update all remaining commits accordingly.

  5. Replace directoryToKeep with your actual directory before, and execute the following command using index-filter this time:
    git filter-branch --index-filter '
    git ls-files -sz | 
    perl -0pe "s{\t}{\tdirectoryToKeep/}" |
    GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
        git update-index --clear -z --index-info &&
        mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
    ' HEAD

    If you want to preserve tags and update them, you need to add –tag-name-filter cat.

There might be old untracked files. You can clean up the repository with the following commands:

git reset --hard
git gc --aggressive
git prune
git clean -df

If you just want a new repository for directoryToKeep, you should be able to just push it. Otherwise follow the second step.
It’s also good at this point to make sure that the result is correct, e.g., using git log.

B. Merge into target repository

  1. Clone repository (make a copy, don’t use your already existing one)
  2. cd into it
  3. Create a remote connection to repositoryA as a branch in repositoryB.
    git remote add <branch-name-repoA> /path/to/repositoryA
  4. Pull from the branch (this assumes you performed the changes above on master)
    git pull --allow-unrelated-histories <branch-name-repoA> master

    Note: Because your branch and master don’t have a common base, git 2.9+ will refuse to merge them without the --allow-unrelated-histories option. It will create a merge commit to merge the current HEAD with your branch. The editor for the commit message should appear. Enter a meaningful commit message and proceed. Now you’re done and can push.

About Thibaud Kloczko

Graduated in CFD, Thibaud Kloczko is a software engineer at Inria. He is involved in the development of the meta platform dtk that aims at speeding up life cycle of business codes into research teams and at sharing software components between teams from different scientific fields (such as medical and biological imaging, numerical simulation, geometry, linear algebra, computational neurology).

Leave a Reply

Your email address will not be published.