Злиття репозиторіїв в git

Є два сховища. Завдання - злити їх так, щоб файли з одного сховища потрапили в папку в іншому, і при цьому збереглася вся історія.

Я не знайшов в мережі повного опису всіх кроків (може бути, погано шукав), і тому вирішив набити пам'ятку, скомпільовану з різних джерел і власних експериментів.

Суть проблеми

Припустимо, у нас є два проекти, кожен з яких живе в окремому сховищі.

Назвемо перший проект 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 (сподіваюся, Ви дійсно хотіли саме цього!)

Мораль всієї байки: переписуйте свою історію і упорядковуйте репозиторії на здоров'я, але тільки пам'ятайте:

Злиття репозиторіїв в git

Злиття репозиторіїв в git

Схожі статті