Breaking out a git repo subdirectory
I’ve been sporadically doing some creative coding in a sandbox repo I made to play in during my time at Recurse Center. It’s starting to become a real mishmash of languages. I just added some dependencies and a simple build system to my creative coding subdirectory, so it was about time to split that bad boy out into its own repo. But I was loath to lose my valuable commit history in the process.
This blog post tipped me off to the tricky git filter-branch command to filter your source and history down to a subdirectory, but overall my approach ended up taking a lot fewer steps than theirs.
Warning: filter-branch vs. filter-repo
git filter-branch warning:
git filter-branchhas a plethora of pitfalls that can produce non-obvious manglings of the intended history rewrite (and can leave you with little time to investigate such problems since it has such abysmal performance).
The safer approach is git filter-repo. Thanks very much to Coby for pointing this out to me!
git filter-branch comes built in to git, but note that git filter-repo requires some minimal setup to suit your bin preferences. I cloned it and then symlinked the executable.
git clone https://github.com/newren/git-filter-repo.git
cd git-filter-repo
`ln -s `pwd`/git-filter-repo` /usr/local/bin/git-filter-repo
Approach
Let’s say your original repo has this structure.
repo1
- dir1
- dir2
- targetdir
- file1
- file2
And you want to break out the contents of targetdir, with intact revision history, as a new repo repo2.
(For purposes of this walkthrough, I’m assuming that repo1 has a remote URL and that you also want your new repo2 to have a remote URL. Also, note that this approach will ignore any uncommitted work in repo1/targetdir.)
-
Move to the directory you want your new
repo2to live in. - Clone
repo1into a folder namedrepo2.mkdir repo2 git clone <repo1_git_URL> repo2 cd repo2 - The following incantation removes all git content that is not relevant to
targetdir, and bringstargetdir’s contents up to the root.git filter-repo --subdirectory-filter targetdir(Note that this still works if your target dir is nested, e.g.
dirA/dirB/targetdir.) - Double check that your desired log is intact and things are as expected!
git log git statusIf your original
repo1/.gitignoredefined recursive rules that are necessary for the contents oftargetdir, you will probably notice those now. Copy over what you need. -
When everything looks good,
git commitwhat you want. - Swap your remote URL from
repo1’s to reflect your new remote. Below I’m assuming that the remote is namedorigin, but you do you.git remote rm origin git remote add origin <repo2_git_url>
All done and ready to push!