Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detach subdirectory (that was renamed!) into a new repo

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?

like image 265
akoprowski Avatar asked Jul 09 '11 22:07

akoprowski


People also ask

What is git filter branch?

Lets you rewrite Git revision history by rewriting the branches mentioned in the <rev-list options>, applying custom filters on each revision. Those filters can modify each tree (e.g. removing a file or running a perl rewrite on all files) or information about each commit.


1 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.

like image 156
bdonlan Avatar answered Oct 06 '22 18:10

bdonlan