В моем случае у меня был my-plugin
репозиторий и main-project
репозиторий, и я хотел сделать вид, что my-plugin
он всегда разрабатывался в plugins
подкаталогеmain-project
.
По сути, я переписал историю my-plugin
репозитория так, чтобы казалось, что вся разработка происходила в plugins/my-plugin
подкаталоге. Затем я добавил историю развития my-plugin
в main-project
историю и объединил два дерева. Поскольку plugins/my-plugin
в main-project
хранилище еще не было каталога , это было тривиальное слияние без конфликтов. Получившийся репозиторий содержал всю историю обоих исходных проектов и имел два корня.
TL; DR
$ cp -R my-plugin my-plugin-dirty
$ cd my-plugin-dirty
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
$ cd ../main-project
$ git checkout master
$ git remote add --fetch my-plugin ../my-plugin-dirty
$ git merge my-plugin/master --allow-unrelated-histories
$ cd ..
$ rm -rf my-plugin-dirty
Длинная версия
Сначала создайте копию my-plugin
хранилища, потому что мы собираемся переписать историю этого хранилища.
Теперь перейдите к корню my-plugin
хранилища, проверьте вашу основную ветку (возможно master
) и выполните следующую команду. Конечно, вы должны заменить my-plugin
и plugins
какими бы ни были ваши настоящие имена.
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
Теперь для объяснения. git filter-branch --tree-filter (...) HEAD
запускает (...)
команду при каждом доступном коммите HEAD
. Обратите внимание, что это работает непосредственно с данными, хранящимися для каждого коммита, поэтому нам не нужно беспокоиться о понятиях «рабочий каталог», «индекс», «подготовка» и так далее.
Если вы запустите filter-branch
команду, которая потерпит неудачу, она оставит позади некоторые файлы в .git
каталоге, и при следующей попытке filter-branch
она будет жаловаться на это, если вы не укажете эту -f
опцию filter-branch
.
Что касается самой команды, мне не особо повезло, когда я сделал bash
то, что хотел, поэтому вместо этого я использую команду zsh -c
make zsh
. Сначала я установил extended_glob
опцию, которая включает ^(...)
синтаксис в mv
команде, а также glob_dots
опцию, которая позволяет мне выбирать точечные файлы (такие как .gitignore
) с помощью glob ( ^(...)
).
Далее я использую mkdir -p
команду для создания одновременно plugins
и plugins/my-plugin
одновременно.
Наконец, я использую zsh
функцию «негативный глобус», ^(.git|plugins)
чтобы сопоставить все файлы в корневом каталоге репозитория, кроме .git
и только что созданной my-plugin
папки. (Исключение здесь .git
может и не понадобиться, но попытка переместить каталог в себя является ошибкой.)
В моем репозитории начальная фиксация не включала никаких файлов, поэтому mv
команда вернула ошибку при первоначальной фиксации (так как ничего не было доступно для перемещения). Поэтому я добавил || true
так, чтоgit filter-branch
бы не прерывать.
--all
Вариант говорит filter-branch
переписать историю для всех ветвей в хранилище, а дополнительный --
надо сказать , git
чтобы интерпретировать его как часть списка опций для филиалов переписывать, а в качестве опцииfilter-branch
себе.
Теперь перейдите в свой main-project
репозиторий и отметьте любую ветку, в которую вы хотите объединиться. Добавьте вашу локальную копию my-plugin
хранилища (с измененной историей) в качестве удаленного main-project
с:
$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY
Теперь у вас будет два несвязанных дерева в истории коммитов, которые вы можете визуализировать, используя:
$ git log --color --graph --decorate --all
Чтобы объединить их, используйте:
$ git merge my-plugin/master --allow-unrelated-histories
Обратите внимание, что в Git до 2.9.0 эта --allow-unrelated-histories
опция не существует. Если вы используете одну из этих версий, просто опустите опцию: --allow-unrelated-histories
предупреждающее сообщение об ошибке также было добавлено в 2.9.0.
Вы не должны иметь никаких конфликтов слияния. Если вы это сделаете, это, вероятно, означает, что либо filter-branch
команда работала некорректно, либо в ней уже был plugins/my-plugin
каталог main-project
.
Обязательно введите пояснительное сообщение о коммите для всех будущих участников, задающихся вопросом, что за хакерство собиралось сделать хранилище с двумя корнями.
Вы можете визуализировать новый граф коммитов, который должен иметь два корневых коммита, используя приведенную выше git log
команду. Обратите внимание, что будет объединена только master
ветка . Это означает, что если у вас есть важная работа над другими my-plugin
ветвями, которые вы хотите объединить в main-project
дерево, вы должны воздерживаться от удаления my-plugin
удаленного, пока вы не выполните эти слияния. Если вы этого не сделаете, то коммиты из этих веток все еще будут в main-project
репозитории, но некоторые будут недоступны и подвержены возможной сборке мусора. (Кроме того, вам придется ссылаться на них по SHA, потому что удаление удаленного удаляет его ветви удаленного отслеживания.)
По желанию, после того, как вы объединили все, что хотите сохранить my-plugin
, вы можете удалить my-plugin
пульт, используя:
$ git remote remove my-plugin
Теперь вы можете безопасно удалить копию my-plugin
репозитория, историю которого вы изменили. В моем случае я также добавил уведомление об устаревании в реальный my-plugin
репозиторий после того, как слияние было завершено и отправлено.
Протестировано на Mac OS X El Capitan с git --version 2.9.0
и zsh --version 5.2
. Ваш пробег может варьироваться.
Ссылки: