Є два сховища. Завдання - злити їх так, щоб файли з одного сховища потрапили в папку в іншому, і при цьому збереглася вся історія.
Я не знайшов в мережі повного опису всіх кроків (може бути, погано шукав), і тому вирішив набити пам'ятку, скомпільовану з різних джерел і власних експериментів.
Суть проблеми
Припустимо, у нас є два проекти, кожен з яких живе в окремому сховищі.
Назвемо перший проект libfoo. Це архіполезная бібліотека з довгої і знатної історією.
У ній багато файлів, всі з яких нам дуже важливі.
Другий проект - це mainproject, вбивця Facebook, Twitter і (раптово) Wikipedia.
Він теж дуже великий
І після трьох років розробки ми зрозуміли, що нам життєво необхідно злити обидва сховища в один. А саме, перемістити бібліотеку в репозиторій проекту (в нову папку libfoo). Зрозуміло, при цьому хочеться зберегти всю історію змін.
Disclaimer
У цьому записі я проводжу всі зміни прямо в самих репозиторіях. Але при роботі з реальними репозиторіями я вкрай рекомендую проводити подібні маніпуляції виключно з копіями Ваших репозиторіїв. Незважаючи на те, що git в більшості випадків намагається зберегти призначені для користувача дані, деякі з використовуваних нами команд здатні безслідно видалити вміст сховища.
Тому проводите всі експерименти на копіях робочих репозиторіїв. Тим більше, що клони в git створюються дуже швидко. А коли всі маніпуляції будуть завершені, то репозиторії можна синхронізувати через push / pull.
Загалом, я вважаю, що попередив Вас. Бережіть свої дані!
Переписуємо шлях до файлів
Перший крок на шляху до злиття репозиторіїв в солодкому екстазі - це зміна шляху до файлів в репозиторії з бібліотекою. Якщо зараз файли лежать в корені, то після наших маніпуляцій вони повинні переміститися в папку libfoo (яка буде розташована теки сховища libfoo).
Домогтися цього нам допоможе термоядерна команда git filter-branch. Вона призначена для активного редагування історії в дусі "1984". Історія змінюється безпосередньо через зміну (точніше, пересозданіе) коммітов, тому всі правки незворотні. Невдале застосування filter-branch може відправити Ваш репозиторій на звалище.
Тому уважно стежте за руками:
Прапор --tree-filter командує git-у, що наступна за ним команда повинна бути застосована до кожного коммітов в ланцюжку (в нашому випадку до всієї гілці master). Фільтрація пройде успішно тільки в тому випадку, коли команда завершується зі статусом 0, і все коммітов вдається накласти.
Сама команда шелла теж складається з двох частин. Спочатку ми створюємо папку libfoo. Потім переміщаємо в неї всі файли, за винятком самої libfoo. Типовий приклад мощі шелла: за допомогою пайпа і чиєїсь матері складаємо з базових команд складне дію, яке зробить за людину все монотонну роботу.
Якщо ми не помилилися в наборі команди, то git перепише все коммітов сховища та явить нам нову, покращену бібліотеку libfoo:
При цьому збережеться вся історія змін (хоча, зрозуміло, зміняться хеші коммітов):
При бажанні можна скастовать git log на будь-який конкретний файл і переконатися, що з його історією теж все в порядку.
Тепер можна зайнятися і самим злиттям.
об'єднуємо репозиторії
Залишилось зовсім небагато. Переходимо в наш уберпроект mainproject і витягуємо все коммітов з зміненого сховища бібліотеки:
git буде здивований, що віддалений репозиторій не має з нашим загальних коммітов, і попередить нас про це. Не варто турбуватися - все йде за планом.
Однак сам план залежить від того, як саме Ви хочете злити дві гілки. Це залежить, головним чином, від прийнятої у Вашій команді політики управління гілками в репозиторії.
Можна піти по Linus-way і злити дві гілки через звичайний merge. В такому випадку створиться Комміт, у якого буде два предка: один з libfoo, і один з mainproject:
Є й інший варіант. Наприклад, ми на своєму робочому проекті заборонили коммітов з декількома батьками (заради простої лінійної історії коммітов). В такому випадку треба зливати гілки через rebase.
І в жодному разі не забуваємо підчистити віддалений репозиторій - він нам більше не потрібен!
Що в підсумку
Ми отримали, що хотіли: в репозиторії mainproject з'явилася папка libfoo з усіма файлами з проекту libfoo (сподіваюся, Ви дійсно хотіли саме цього!)
Мораль всієї байки: переписуйте свою історію і упорядковуйте репозиторії на здоров'я, але тільки пам'ятайте: