У моєму випадку у мене було 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для 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
Зауважте, що в попередньому 2.9.0 Git --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. Ваш пробіг може відрізнятися.
Список літератури: