Подтвердить что ты не робот

Как разбить репозиторий git и следовать переименованиям каталогов?

В настоящее время у меня есть большой репозиторий git, который содержит много проектов, каждый из которых находится в своем собственном подкаталоге. Мне нужно разбить его на отдельные репозитории, каждый проект в собственном репо.

Я пробовал git filter-branch --prune-empty --subdirectory-filter PROJECT master

Однако многие каталоги проектов прошли несколько переименований в своей жизни, а git filter-branch не выполняет переименование, поэтому эффективно извлеченное репо не имеет истории до последнего переименования.

Как я могу эффективно извлечь подкаталог из одного большого репозитория git и следовать за всем, что каталог переименовывает обратно в прошлое?

4b9b3361

Ответ 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 всю историю из обоих имен для этой папки. Затем вы можете использовать этот отдельный репозиторий или объединить его в другой, или что бы вы ни делали с этой историей.