How to move one git repository into a subdirectory of another with rebase
Let’s say you’re building a side project in a git repository but eventually decide it belongs inside your main git repository in a subdirectory. How would you go about moving all the files over while maintaining the history of all the changes?
This came up for me recently, but most search results give steps for merging. Since I am a fan of rebasing, I had to come up with something slightly different. Here are the steps I took:
1. Move all files from all commits into a subdirectory
In the side repo:
# Move to a new branch `side_proj_subdir` in case you don’t want to mess up master
git checkout -b side_proj_subdir# Move all files from all commits into a `side_proj` directory
git filter-branch --tree-filter "mkdir -p side_proj; git mv -k * side_proj" HEAD
This will move all the files from all the commits into a new side_proj directory. For some reason, dotfiles like .gitignore are not included, so you will need to go back to those commits when they were created and make sure they are created in the proper subdirectory, amending the commits using interactive rebase. This can be tedious if you have a lot of these. Good luck.
2. Prefix all commit messages
To denote that all these commits pertain to a certain feature, I like prefixing the commit messages. In the side repo:
# Get the first commit hash
FIRST_COMMIT_HASH=$(git rev-list HEAD | tail -n 1)# Prefix `[side_proj] ` to all commits after the first one
git filter-branch -f --msg-filter 'sed "1 s/^/[side_proj] /"' ${FIRST_COMMIT_HASH}..HEAD
Note: this does not prefix the first commit unfortunately, but you can amend that one using your trusty interactive rebase:
git rebase -i --root
3. Add side repo as a remote
In your main repo, you’ll want to add your local side repo directory as a remote so that you can access all your fancy new commits:
# Add local side_proj directory as a remote
git remote add side_proj '../side_proj'# Get all of those commits
git fetch side_proj
4. Rebase all the commits!
Now the moment of truth. Since we are not merging, let’s grab all of those side project commits and put them on top of our main project. In the main repo:
# Get all your side repo commits. This may take a while as it will essentially wipe out all the main project files and create all the side project files.
git checkout side_proj/side_proj_subdir# Put all of these commits on top of your main project. This may also take a while since you are basically recreating your main project.
git rebase master
5. Cleaning up
Hopefully everything went ok without any conflicts. You should be in a state with all of your side project commits on top of master, in its own subdirectory, and with all commit messages prefixed appropriately. If you want, at this point you can remove your remote we added in step 3with:
git remote rm side_proj
Conclusion
I hope you found this guide helpful. All of these steps could easily be found by Googling, but I thought it would be nice to put it in one place. The end result for me was hundreds of commits over the course of four years sitting on top of master in a non-conflicting way, which was kind of cool.