I have a repository and I would like to detach one of its directories into a new repo. This is a perfect place to get started, there is one caveat, however: the directory that I want to detach was renamed at some point. And if I follow the solution from that post with the new name of the directory then it seems I'm loosing the history before the re-name. Any ideas of a tweak to make it work in this situation?
2 Answers
git filter-branch
can operate on ranges of commits; so what we can do is filter the 'before' and 'after' separately, and use grafts to tack them together:
git branch rename $COMMIT_ID_OF_RENAME
git branch pre-rename rename~
## First filter all commits up to rename, but not rename itself
git filter-branch --subdirectory-filter $OLDNAME pre-rename
## Add a graft, so our rename rev comes after the processed pre-rename revs
echo `git rev-parse rename` `git rev-parse pre-rename` >> .git/info/grafts
## The first filter-branch left a refs backup directory. Move it away so the
## next filter-branch doesn't complain
mv .git/refs/original .git/refs/original0
## Now filter the rest
git filter-branch --subdirectory-filter $NEWNAME master ^pre-rename
## The graft is now baked into the branch, so we don't need it anymore
rm .git/info/grafts
This is marginally more complex if you need to filter multiple branches or tags; branches before the rename can be included into the first filter-branch, while ones after must be included before the ^rename
in the second filter-branch.
Another option would be to add an index filter (or tree filter) instead that checks for both directories, old and new, and keeps whichever is present.
Since you haven't provided a test repository, here's a quick sanity-check script for this scenario:
#!/bin/bash
set -u
set -e
set -x
rm -rf .git x y foo
git init
mkdir x
echo initial > x/foo
git add x/foo
git commit -m 'test commit 1'
echo tc2 >> x/foo
git commit -a -m 'test commit 2'
mv x y
git rm x/foo
git add y/foo
git commit -a -m 'test rename'
git branch rename HEAD
echo post rename >> y/foo
git commit -a -m 'test post rename'
git branch pre-rename rename~
git filter-branch --subdirectory-filter x pre-rename
echo `git rev-parse rename` `git rev-parse pre-rename` >> .git/info/grafts
mv .git/refs/original .git/refs/original0
git filter-branch --subdirectory-filter y master ^pre-rename
rm .git/info/grafts
git log -u
If this procedure does not work for you, then there is likely to be something else odd about your repository history that you haven't described, such as another rename hiding in the history.

- 224,562
- 31
- 268
- 324
-
Hmm, I tried your method with grafts (which I know nothing about) and it didn't work for me. After processing one of the "before" commits got empty, another one's files got moved into the root of the repo; I cannot make sense of it. I was thinking before of using a tree-filter but don't have any experience with that; would you care to elaborate show me some pointers? – akoprowski Jul 09 '11 at 23:25
-
Are you sure the before commit was using the correct subdir? Can you post your git repo somewhere? – bdonlan Jul 09 '11 at 23:31
-
Yup, rename was on the commit that did the renaming and pre-rename just before it. The repo is huge and partly private so I cannot really publish it anywhere :| – akoprowski Jul 09 '11 at 23:37
-
Try running some test filter-branches on the commit that ended up empty to see what the problem is there, I guess. It could be that it's been renamed more than once. – bdonlan Jul 09 '11 at 23:46
-
No, it's been renamed just once. The commit that ended up empty was the first commit creating `$OLDNAME`. – akoprowski Jul 09 '11 at 23:59
-
Found a small bug in which the commit of the rename itself would be handled incorrectly (fixed in edit), also put up an end-to-end test script. Since I can't see your private repo, I can't really help any further unless you have a testcase I can look at. – bdonlan Jul 10 '11 at 00:53
-
@akoprowski let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/1297/discussion-between-bdonlan-and-akoprowski) – bdonlan Jul 10 '11 at 00:53
-
Note: On some shells you may have to change `git filter-branch --subdirectory-filter $NEWNAME master ^pre-rename` to `git filter-branch --subdirectory-filter $NEWNAME master "^pre-rename"` – ejoerns Feb 21 '14 at 13:33
-
Any ideas on how to do this if there are more than one renames? I tried doing it as a two step process but didn't get it to work. – Rick Jan 10 '17 at 18:01
We (Matthew Flatt and me) wrote a program to do this: https://github.com/samth/git-slice

- 4,983
- 1
- 21
- 43