Как разбить репозиторий git и следовать переименованиям каталогов?
В настоящее время у меня есть большой репозиторий git, который содержит много проектов, каждый из которых находится в своем собственном подкаталоге. Мне нужно разбить его на отдельные репозитории, каждый проект в собственном репо.
Я пробовал git filter-branch --prune-empty --subdirectory-filter PROJECT master
Однако многие каталоги проектов прошли несколько переименований в своей жизни, а git filter-branch
не выполняет переименование, поэтому эффективно извлеченное репо не имеет истории до последнего переименования.
Как я могу эффективно извлечь подкаталог из одного большого репозитория git и следовать за всем, что каталог переименовывает обратно в прошлое?
Ответы
Ответ 1
Благодаря @Chronial, я смог приготовить script для массажа моего репозитория git в соответствии с моими потребностями:
git filter-branch --prune-empty --index-filter '
# Delete files which are NOT needed
git ls-files -z | egrep -zv "^(NAME1|NAME2|NAME3)" |
xargs -0 -r git rm --cached -q
# Move files to root directory
git ls-files -s | sed -e "s-\t\(NAME1\|NAME2\|NAME3\)/-\t-" |
GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
git update-index --index-info &&
( test ! -f "$GIT_INDEX_FILE.new" \
|| mv -f "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE" )
'
В основном, что это делает:
-
Удаляет все файлы вне из трех каталогов NAME1, NAME2 или NAME3, которые мне нужны (один проект был переименован в NAME1 → NAME2 → NAME3 за время его существования).
-
Перемещает все внутри эти три каталога в корень репозитория.
-
Мне нужно было проверить, существует ли "$ GIT_INDEX_FILE.new", так как импорт svn в git создает коммиты без каких-либо файлов (только для записей в каталогах). Нужно, только если репо было создано с помощью git svn clone '.
Ответ 2
Я не думаю, что git имеет встроенную функцию для этого. Вам нужно будет создать собственный фильтр. Просто используйте git filter-branch --prune-empty --tree-filter YOURSCRIPT
. Затем ваш script должен будет определить правильную папку (возможно, имя конкретного файла в ней или, возможно, у вас есть список всех имен, которые этот проект имел в прошлом), удалить все остальное и переместить содержимое папки вверх уровень.
Если ваше репо действительно велико, и у вас нет ночи, чтобы запустить этот script, вы можете добиться такого же эффекта намного быстрее с помощью --index-filter
, но писать, что script будет сложнее. Вам нужно будет использовать команды git для изменения индекса вместо команд изменения файловой системы.
Ответ 3
У меня был очень большой репозиторий, из которого мне нужно было извлечь одну папку; даже --index-filter
, как прогнозировалось, займет 8 часов. Вот что я сделал вместо этого:
- Получить список всех прошлых имен папки. В моем случае было только два,
old-name
и new-name
.
-
Для каждого имени:
$ git checkout master
$ git checkout -b filter-old-name
$ git filter-branch --subdirectory-filter old-name
Это даст вам несколько отключенных ветвей, каждая из которых содержит историю для одного из имен.
-
В ветке filter-old-name
должна быть завершена фиксация, которая переименовала папку, а ветвь filter-new-name
должна начинаться с той же фиксации. (То же самое происходит, если было несколько переименований: вы закончите с эквивалентным количеством ветвей, каждый из которых будет делиться совместно с следующим.) Нужно удалить все, а другое снова заново создать его. Убедитесь, что эти два коммита имеют одинаковое содержимое; если они этого не делают, файл был изменен в дополнение к переименованию, и вам нужно будет объединить изменения. (В моем случае у меня не было этой проблемы, поэтому я не знаю, как ее решить.)
Простой способ проверить это - попробовать перезагрузить filter-new-name
поверх filter-old-name
, а затем сжать два коммита вместе: git должен жаловаться, что это создает пустую фиксацию. (Обратите внимание, что вы захотите сделать это на резервной ветке, а затем удалите ее: rebasing удаляет информацию коммиттера из коммитов, тем самым теряя часть истории, которую вы хотите сохранить.)
-
Следующий шаг состоит в том, чтобы перевести две ветки вместе, пропустить две коммиты, которые переименовали папку. (В противном случае будет странный прыжок, где все будет удалено и воссоздано.) Это включает в себя поиск полной SHA (все 40 символов!) двух коммитов и помещение их в git info, при этом первая ветвь имени фиксируется первым, а ветвь старого имени - вторая.
$ echo $NEW_NAME_SECOND_COMMIT_SHA1 $OLD_NAME_PENULTIMATE_COMMIT_SHA1 >> .git/info/grafts
Если вы сделали это правильно, git log --graph
теперь должен показывать строку с конца новой истории до начала старой истории.
-
Этот трансплантат временно является временным: он еще не является частью истории и не будет следовать вместе с клонами или нажатиями. Чтобы сделать его постоянным:
$ git filter-branch
Это позволит отфильтровать ветку, не пытаясь вносить какие-либо дальнейшие изменения, делая трансплантат постоянным (изменение всех коммитов в ветки filter-new-name
). Теперь вы можете удалить файл .git/info/grafts
.
В конце всего этого вы должны теперь иметь в ветке filter-new-name
всю историю из обоих имен для этой папки. Затем вы можете использовать этот отдельный репозиторий или объединить его в другой, или что бы вы ни делали с этой историей.