2012-04-21 2 views
2

Этот вопрос звучит похоже на многие из предложенных здесь, но это неприятно отличается.Разделение ранее объединенного кода на несколько репозиториев git

У меня есть репозиторий git, который когда-то был хранилищем svn (который был когда-то репозиторием cvs). Это содержит данные, относящиеся к 1999 году.

Пришло время разбить этот один репозиторий на несколько разных хранилищ, сохранив всю эту богатую историю. Однако структура репозитория часто менялась. Все текущие проекты пришли из базового проекта, который вырос до нескольких проектов, которые сократились до двух проектов, а затем снова возросли. Код перемещен, но он никогда не дублируется; теперь он нашел последнее место отдыха в одном из нескольких зрелых проектов.

Это очень затрудняет разделение репозиториев, если я хочу сохранить историю. Использование ветки git-filter похоже на правильный подход, но все они, похоже, взламывают части репозитория и обрезают историю с ними.

EDIT ADDED Чтобы уточнить, вот небольшой пример, притворяющийся, что я в корне репозитория. Скажем хранилище выглядит следующим образом:

foo/ 
    bar/ 
     file.txt 
    baz/ 

Теперь, скажем, я редактировать содержимое file.txt. Затем я переименую его в newfile.txt. Затем я снова редактирую содержимое. Затем я перемещаю этот файл из bar/ и в baz/. Мой репозиторий теперь выглядит следующим образом:

foo/ 
    bar/ 
    baz/ 
     newfile.txt 

Хорошо, теперь давайте говорить, что я хочу разделить baz/ в отдельное хранилище. Использование git filter-branch или использование разделителя поддерева git потеряет все сообщения фиксации и историю для newfile.txt назад, когда оно было внутри bar/, и когда оно было названо file.txt.

Я понимаю, что проверка исторической версии может быть сумасшедшей; он может ссылаться на то, что называется ../bar/, или может ссылаться на недопустимый каталог, который не существует и не срабатывает эффектно. Мне все равно, если я могу посмотреть содержимое файла в любом конкретном редакторе.

END EDIT

Похоже, есть два пути для того, что я хочу сделать:

  1. клон репозитория N раз, сохранить папки, которые я хочу в этом хранилище (с помощью git rm-ing других папок) и каким-то образом взломать любые изменения, которые в конечном итоге не ссылаются на файлы, находящиеся в HEAD. Я понимаю, что это будет иметь несколько негативных побочных эффектов, поскольку проверка старых версий не обеспечит значимой базы кода - мне все равно. Для этого мне нужно найти способ получить все пути, которые происходят из всех файлов, существующих в HEAD, что я мог бы сделать с уродливым скриптом.

  2. Создайте какой-нибудь исторический указатель того, как выглядел репозиторий во время каждого индекса. Используйте фильтр дерева и отрубайте файлы, которые не соответствуют их соответствующей ревизии. Затем удалите файлы, которые не отображаются или не спускаются из файлов в HEAD.

Возможно ли найти все файлы, которые не отображаются в ГОЛОВЕ, и удалить любую историю, относящуюся к ним?Я не забочусь о воскрешении файлов, которые были давно удалены, и это, похоже, является основным моментом моей проблемы.

Альтернативные решения также будут оценены. Я относительно новичок в git, поэтому я, вероятно, не вижу ничего очевидного.

ответ

1

В итоге мне пришлось сделать это в несколько этапов процесса.

Во-первых, я получил список всех файлов путей, которые были когда-либо найденных в хранилище:

git log --pretty=format: --name-only --diff-filter=A | sort -u 

Пользуясь тем, что я был в состоянии определить, где файлы, которые я хотел, чтобы он жил в одной точке или другие. В моем случае они проживали в четырех отдельных каталогах в хранилище на протяжении всей их жизни. Я использовал эту информацию для ручного создания регулярного выражения, например (?:^foo|^bar/baz|^qux/(?:moo|woof)). Это соответствует каталогам, которые я хотел сохранить.

Затем я создал скрипт perl для сохранения этих путей и любых родительских путей, которые содержали их.

use Path::Class;  
if(scalar(@ARGV) < 1) { die "no regex"; } 

my $regex = qr/$ARGV[0]/;  
my @want; my @remove; my $last = undef; my $lastrm = undef; 

while(<STDIN>) { 
    chomp; 
    my $d = $_; 
    if($d =~ $regex) { 
     if(! defined($last) || ! dir($last)->subsumes(dir($d))) { 
      $last = $d; 
      push @want, $d; 
     } 
    } else { 
     if(! defined($last) || ! dir($last)->subsumes(dir($d))) { 
      push @remove, $d; 
     } 
    } 
} 
foreach $rm (@remove) { 
    my $no_rm = 0; 
    if(defined($lastrm) && dir($lastrm)->subsumes($rm)) { 
     $no_rm++; 
    } else { 
     foreach $keep (@want) { 
      if(dir($rm)->subsumes(dir($keep))) { 
       $no_rm++; 
      } 
     } 
    } 
    if($no_rm == 0) { 
     print "$rm\n"; 
     $lastrm = $rm; 
    } 
} 

Наконец, я использовал GIT-фильтр отделение использовать мой новый фильтр с моим регулярным выражением, чтобы пути, которые я хотел.

git filter-branch --prune-empty --index filter ' 
    git ls-tree -d -r -t --name-only --full-tree $GIT_COMMIT 
    | sort | /path/to/filter.pl "(?:regex|of|paths)" 
    | xargs -n 50 git rm -rf --cached --ignore-unmatch' -- --all 

Сортировка необходима, так как она гарантирует, что скрипт perl получит каталоги в их правильной иерархии.

Я надеюсь, что это поможет кому-то, так как мне потребовалось много-много часов, чтобы придумать это. :)

0

Вы должны изучить установку и использование поддерева git https://github.com/apenwarr/git-subtree, он обрабатывает репозиции разделения и сохраняет историю хорошо.

+0

hmm. Это похоже на то, что мне нужно. Я попробую. – lucasmo

+0

Это, кажется, не делает то, что мне нужно, к сожалению. Я еще раз поясню в этом вопросе. – lucasmo