Move directory from one repository to another, preserving history¶
This post is archived. It is left as is and won't receive updates.
I just moved one directory within a Git repository to a directory within another repository including its history. For example:
.
├── repositoryA
│ ├── directoryToKeep
│ ├── otherDirectory
│ └── someFile.ext
└── repositoryB
└── someStuff
The goal is to move directoryToKeep
into repositoryB
with its history, i.e., all commits that affect directoryToKeep
.
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.
If you have files tracked by git-lfs
, please note the update at the bottom first.
Here is how I did it, based on this blog post and StackOverflow topic:
Prepare the source repository¶
- Clone
repositoryA
(make a copy, don't use your already existing one) cd
to it-
Delete the link to the original repository to avoid accidentally making any remote changes
-
Using
filter-branch
, go through the complete history and remove all commits (or keep all commits affectingdirectoryToKeep
) not related todirectoryToKeep
.From the git documentation:
Quote
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 ofdirectoryToKeep
directly, which is also reflected in all the commits. If you want to create a separate repository just fordirectoryToKeep
, skip the next step. If instead you want to movedirectoryToKeep
torepositoryB
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 movedirectoryToKeep
into its own directory, which will update all remaining commits accordingly. -
Replace
directoryToKeep
with your actual directory before, and execute the following command usingindex-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
.If you get the error "mv: cannot stat ‘.new’: No such file or directory", you need to add the
--prune-empty
option tofilter-branch
to avoid empty commits.
You might need to perform the following optional steps:
-
There might be old untracked files. You can clean up the repository with the following commands:
-
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., usinggit log
.
Merge into target repository¶
- Clone
repository
(make a copy, don't use your already existing one) cd
into it-
Create a remote connection to
repositoryA
as a branch inrepositoryB
. -
Pull from the branch (this assumes you performed the changes above on
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.
- Personally, I would just delete the cloned repositories from step 1 and go back to the actual repository.
- If everything works, remove
directoryToKeep
fromrepositoryA
.
Updates to this blog post
- 19.01.2017: Updated step 2.4 with additional option (Thanks, Paul!)
- 18.12.2018: Updated step 1.5 with additional option to preserve tags (Thanks, Sandip!)
- 28.10.2019: If you have files tracked by
git-lfs
, there are is an additional step you need to perform After cloning the repository at the beginning, performgit lfs fetch --all
(source). As Evan pointed out in the comments, if the directory that should be kept does not have any large files, he performedgit lfs uninstall --local
to get rid of them.
Comments
Comments are currently not supported. For the time being, please send me an email.