В моем случае у меня было хранилище my-plugin
и хранилище main-project
, и я хотел сделать вид, что my-plugin
всегда разрабатывалось в подкаталоге plugins
main-project
.
По сути, я переписал историю репозитория my-plugin
, так что оказалось, что вся разработка происходила в подкаталоге plugins/my-plugin
. Затем я добавил историю развития my-plugin
в историю main-project
и объединил два дерева вместе. Поскольку в репозитории main-project
уже не было каталога plugins/my-plugin
, это было тривиальное слияние без конфликтов. Получившийся репозиторий содержал всю историю обоих исходных проектов и имел два корня.
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
, чтобы zsh
выполнил команду. Сначала я устанавливаю опцию extended_glob
, которая включает синтаксис ^(...)
в команде mv
, а также опцию glob_dots
, которая позволяет мне выбирать точечные файлы (такие как .gitignore
) с глобусом (^(...)
).
Затем я использую команду 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
работает неправильно, либо в main-project
.
уже есть каталог
plugins/my-plugin
.
Обязательно введите пояснительное сообщение о коммите для всех будущих участников, задающихся вопросом, что за хакерство собиралось сделать хранилище с двумя корнями.
Вы можете визуализировать новый граф коммитов, который должен иметь два корневых коммита, используя указанную выше команду 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
. Ваш пробег может отличаться.
Ссылки: