В моем случае у меня был 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 -cmake 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. Ваш пробег может варьироваться.
Ссылки: