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