Поєднання декількох сховищ git


207

Скажімо, у мене є налаштування, яке виглядає приблизно так

phd/code/
phd/figures/
phd/thesis/

З історичних причин всі вони мають власні сховища git. Але я хотів би об'єднати їх в єдиний, щоб трохи спростити речі. Наприклад, зараз я можу зробити два набори змін і зробити щось подібне

cd phd/code
git commit 
cd ../figures
git commit

Було б (зараз) приємно просто виконати

cd phd
git commit

Здається, є кілька способів зробити це за допомогою підмодулів або витягування з моїх підпозицій, але це трохи складніше, ніж я шукаю. Принаймні, я був би задоволений

cd phd
git init
git add [[everything that's already in my other repositories]]

але це не схоже на однолінійний. Чи є щось у цьому, gitщо може мені допомогти?


Також врахуйте цей чудовий підхід: stackoverflow.com/questions/1425892/…
Йохан Шьоберг

Також врахуйте: saintgimp.org/2013/01/22/…
ptim

Join-git-repos.py сценарій робить хорошу роботу , якщо у вас є окремі репозиторії, кожен майстер - гілки , які ви хочете об'єднати.
Марк

Відповіді:


149

Ось таке рішення я дав тут :

  1. Спочатку зробіть повну резервну копію каталогу phd: я не хочу нести відповідальність за ваші втрачені роки важкої роботи! ;-)

    $ cp -r phd phd-backup
    
  2. Перемістіть вміст phd/codeдо phd/code/codeта виправіть історію так, щоб вона виглядала так, як вона завжди була там (для цього використовується команда git's filter-branch ):

    $ cd phd/code
    $ git filter-branch --index-filter \
        'git ls-files -s | sed "s#\t#&code/#" |
         GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
         git update-index --index-info &&
         mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' HEAD
    
  3. Те саме для змісту phd/figuresта phd/thesis(просто замініть codeна figuresта thesis).

    Тепер структура вашого каталогу повинна виглядати так:

    phd
      |_code
      |    |_.git
      |    |_code
      |         |_(your code...)
      |_figures
      |    |_.git
      |    |_figures
      |         |_(your figures...)
      |_thesis
           |_.git
           |_thesis
                |_(your thesis...)
    
  4. Потім створіть сховище git у кореневому каталозі, витягніть все до нього та видаліть старі сховища:

    $ cd phd
    $ git init
    
    $ git pull code
    $ rm -rf code/code
    $ rm -rf code/.git
    
    $ git pull figures --allow-unrelated-histories
    $ rm -rf figures/figures
    $ rm -rf figures/.git
    
    $ git pull thesis --allow-unrelated-histories
    $ rm -rf thesis/thesis
    $ rm -rf thesis/.git
    

    Нарешті, тепер у вас повинно бути те, що ви хотіли:

    phd
      |_.git
      |_code
      |    |_(your code...)
      |_figures
      |    |_(your figures...)
      |_thesis
           |_(your thesis...)
    

Однією з приємних сторін цієї процедури є те, що вона залишить неповернені файли та каталоги на місці.

Сподіваюсь, це допомагає.


Лише одне слово попередження: якщо у вашому codeкаталозі вже є codeпідкаталог або файл, все може піти не так (те саме figuresі, thesisзвичайно). Якщо це так, просто перейменуйте цей каталог або файл, перш ніж пройти всю цю процедуру:

$ cd phd/code
$ git mv code code-repository-migration
$ git commit -m "preparing the code directory for migration"

І коли процедура закінчена, додайте цей останній крок:

$ cd phd
$ git mv code/code-repository-migration code/code
$ git commit -m "final step for code directory migration"

Звичайно, якщо codeпідкаталог або файл не є версією, просто використовуйте mvзамість цього git mvі забудьте про git commits.


13
Дякую за цей фрагмент - він робив саме те, що мені потрібно (одного разу я рахував Mac OS X sed, не обробляючи "\ t" (мені довелося використовувати ^ V ^ I замість цього).
Craig Trader,

6
Спочатку я не міг змусити це працювати, і в кінцевому підсумку знайшов рішення проблеми на іншій старій дошці повідомлень. В останньому рядку я повинен був поставити лапки навколо імен файлів так: mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEADі тоді це спрацювало чудово!
Жорін

3
Функціональна команда фільтр-гілка - зі сторінки сторінки git's filter-branch. Вам слід сказати, що: а) це слід віднести правильно б) я не запускатиму таку команду лише тому, що хтось, навіть з високою репутацією, розмістив її в StackOverflow. Знаючи, що це зі сторінок man, я буду.
tymtam

5
СТЕРЕЖИСЬ! MacOS X не використовує розширення GNU sed, тому не знає послідовності \ t. Результат - заплутана історія! Моє рішення полягало в тому, щоб вставити код у файл сценарію, в ньому записати реальний <TAB> символ. З терміналу можна ввести вкладку, натискаючи ctrl + v, а потім записати <TAB>. Я не пробував рішення Крейга
Гіл Вельях,

4
ГЛЕДАТИ (2)! Також зауважте, що якщо деякі файли або каталоги містять дефіси ('-'), команда sed не вдасться. У такому випадку ви можете замінити його чимось на зразок 's ~ \ t ~ & code / ~'. Тут, застосовуючи ту саму логіку, стежте за іменами '~'
Гіл Вельях,

75

git-stitch-repoбуде обробляти git-fast-export --all --date-orderвисновки в репозиторіях git, заданих у командному рядку, та створюватиме відповідний потік, git-fast-importякий створить нове сховище, що містить усі коміти в новому дереві комісій, що поважає історію всіх сховищ джерела.


33
А, це сторонній інструмент, а не частина git… :-)
Арістотель Пагалціс

1
Дійсно, тепер ви мені кажіть :) Ну добре, я вважаю, що мені довелося навчитися встановлювати пакети CPAN одного дня…
Буде Робертсон

1
Дякуємо, що вказали на цю команду. Просто використовую його, щоб допомогти перенести кілька репостів з SVN в Git.
підпис

1
ПОПЕРЕДЖЕННЯ може не працювати, якщо у вас є гілки / злиття! З сторінки git-stich-repo : "git-stich-repo прекрасно працює з сховищами, що мають лінійну історію (без злиття). Поліпшення алгоритму зшивання, доданого у версії 0.06, має бути придатним для роботи із сховищами, які мають гілки і злиття ».
Брайан П

6
Це зовнішній скрипт, відповідь занадто короткий і не дуже корисний, у цього сценарію є проблеми з об'єднаннями, не багато людей попрацюють з Perl або CPAN, і це недостатньо пояснено у відповіді. Отже ... -1, вибачте.
Харалан Добрев

20

Можливо, просто (подібно до попередньої відповіді, але за допомогою більш простих команд), роблячи в кожному з окремих старих сховищ команду, яка переміщує вміст у відповідний ім'я, підкаталог, наприклад:

$ cd phd/code
$ mkdir code
# This won't work literally, because * would also match the new code/ subdir, but you understand what I mean:
$ git mv * code/
$ git commit -m "preparing the code directory for migration"

а потім злиття трьох окремих репостів в один новий, роблячи smth на зразок:

$ cd ../..
$ mkdir phd.all
$ cd phd.all
$ git init
$ git pull ../phd/code
...

Тоді ви збережете свої історії, але продовжуватиметесь одним репо.


Це нормально, але якщо ви об'єднуєте одне репо в інше (наприклад, phd був не порожнім уже існуючим репо), тоді, якщо у phd були папки з іменами, такими як підпапки в каталозі кодів, у вас виникнуть проблеми, як 'git pull .. / phd / code 'витягує всі коміти з оригінальними шляхами, і лише в кінці він застосовує mv-комітет.
tymtam

1
@Tymek: але це все одно буде працювати в цій ситуації, без проблем. Що не буде приємно - це те, що шляхи в історії не будуть "правильними" (відповідають новим шляхам).
imz - Іван Захарящев

19

Ви можете спробувати стратегію злиття піддерева . Це дозволить вам об'єднати репо B в репо А. Перевага в git-filter-branchтому, що він не вимагає від вас переписувати історію репо А (розбиваючи сум SHA1).


Посилання не працює, і це не збереже історію, чи не так?
tymtam

3
@Tymek (Вибачте, частина kernel.org все ще не працює після порушення безпеки). Це порушує SHA1 вхідного репо B. Але A залишається неушкодженим.
Лейф Грюнвольдт

2
Ось дзеркало цього документа на даний момент ftp.sunet.se/pub/Linux/kernel.org/software/scm/git/docs/howto/…
Leif Gruenwoldt

1
@LeifGruenwoldt Перше посилання працює зараз. І дзеркальна ланка відсутня, ви, мабуть, її слід видалити.
Вадим Котов

9

Рішення гілки-фільтр-філія працює добре, але зауважте, що якщо ваше git repo походить від імпорту SVN, воно може не працювати з повідомленням типу:

Rewrite 422a38a0e9d2c61098b98e6c56213ac83b7bacc2 (1/42)mv: cannot stat `/home/.../wikis/nodows/.git-rewrite/t/../index.new': No such file or directory

У цьому випадку вам потрібно виключити початкову редакцію з гілки фільтра - тобто змінити HEADв кінці на [SHA of 2nd revision]..HEAD- див .:

http://www.git.code-experiment.com/blog/2010/03/merging-git-repositories.html


2
Дякую! Я чухав голову, чому це не працює! Репо дійсно походив із SVN.
Артур Мальсон

1
Та сама помилка, коли я це роблю. Отримали мої сподівання. Також посилання зараз розірвано.
Райан

Не могли б ви пояснити, що ви мали на увазі під "зміною голови на" ...? "

5

Рішення @MiniQuark мені дуже допомогло, але, на жаль, воно не враховує теги, що знаходяться у сховищах джерел (принаймні, у моєму випадку). Нижче моє вдосконалення відповіді на @MiniQuark.

  1. Спочатку створіть каталог, який буде містити складені репо та об'єднані репо, створити каталог для кожного об'єднаного.

    $ mkdir new_phd
    $ mkdir new_phd / код
    $ mkdir new_phd / фігури
    $ mkdir new_phd / теза

  2. Зробіть витяг з кожного сховища та отримайте всі теги. (Представлення інструкцій лише для codeпідкаталогу)

    $ cd new_phd / code
    $ git init
    $ git pull ../../original_phd/code master
    $ git fetch ../../original_phd/code refs / tags / *: refs / tags / *

  3. (Це вдосконалення до пункту 2 у відповіді MiniQuark) Перемістіть вміст new_phd/codeдо new_phd/code/codeта додайте code_префікс перед кожним тегом

    $ git filter-branch --index-filter 'git ls-files -s | sed "s- \ t \" * - & code / - "| GIT_INDEX_FILE = $ GIT_INDEX_FILE.new git update-index --index-info && mv $ GIT_INDEX_FILE.new $ GIT_INDEX_FILE '- тег-ім'я-фільтр' sed" s -. * - код _ & - "'ГОЛОВА

  4. Після цього буде двічі більше тегів, ніж це було раніше, ніж робити фільтр-гілку. Старі теги залишаються в репо, а нові теги з code_префіксом додаються.

    $ git тег
    mytag1
    code_mytag1

    Видаліть старі теги вручну:

    $ ls .git / refs / tags / * | grep -v "/ code_" | xargs rm

    Повторіть пункт 2,3,4 для інших підкаталогів

  5. Тепер у нас є структура каталогів, як у @MiniQuark anwser пункт 3.

  6. Виконайте так, як у пункті 4 провідника MiniQuark, але після витягування та перед вилученням .gitdir виберіть теги:

    $ git fetch каталог refs / теги / *: refs / теги / *

    Продовжуйте ..

Це просто ще одне рішення. Сподіваюся, це комусь допомагає, мені це допомогло :)


5

git-stitch-repo з відповіді Арістотеля Пагальциса працює лише для сховищ з простою лінійною історією.

Відповідь MiniQuark працює для всіх сховищ, але вона не обробляє теги та гілки.

Я створив програму, яка працює так само, як описує MiniQuark, але вона використовує одну комісію злиття (з N батьками), а також відтворює всі теги та гілки, щоб вказати на ці об'єднання.

Перегляньте сховище git-merge-repos для прикладів його використання.



3

Насправді git-stitch-repo тепер підтримує гілки та теги, включаючи помічені теги (я виявив помилку, про яку я повідомив, і вона виправлена). Що мені здалося корисним - це теги. Оскільки теги додаються до комітетів, а деякі рішення (на зразок підходу Еріка Лі) не вдається розібратися з тегами. Ви намагаєтеся створити гілку імпортованого тегу, і він скасує будь-які git злиття / переміщення та пересилає вас назад, як консолідований сховище, близький до сховища, з якого походить тег. Також є проблеми, якщо ви використовуєте один і той же тег у кількох сховищах, які ви "об'єднали / консолідували". Наприклад, якщо у вас є реклама A B B, обидва мають тег rel_1.0. Ви зливаєте РЕПО А і РЕПО Б у РЕПО АВ. Оскільки теги rel_1.0 є у двох різних комітах (один для A і один для B), який тег буде видно в AB? Або тег із імпортованого репо-версії A, або з імпортного репо-В, але не те й інше.

git-stitch-repo допомагає вирішити цю проблему, створюючи теги rel_1.0-A та rel_1.0-B. Ви можете не мати змоги перевірити тег rel_1.0 і очікувати обох, але принаймні ви можете бачити і те, і теоретично, ви можете об'єднати їх у загальну локальну гілку, а потім створити тег rel_1.0 на цій об'єднаній гілці (якщо вважати, що ви просто злиття та не зміна вихідного коду). Краще працювати з гілками, так як ви можете об'єднатись як гілки кожного репо в місцеві гілки. (dev-a та dev-b можуть бути об'єднані в локальну гілку dev, яку потім можна буде натиснути на походження).


2

Послідовність, яку ви запропонували

git init
git add *
git commit -a -m "import everything"

буде працювати, але ви втратите свою історію комісій.


Втратити історію не так вже й погано, але оскільки сховище призначено для моєї власної роботи (тобто це приватне), там є багато речей, які я не хочу переосмислити, або які ще не піддані версії.
Буде Робертсон

1

Щоб об'єднати secondProject в mainProject:

А) У другому Проект

git fast-export --all --date-order > /tmp/secondProjectExport

Б) У головному проекті:

git checkout -b secondProject
git fast-import --force < /tmp/secondProjectExport

У цій гілці зробити всі важкі перетворення, які вам потрібно зробити, і здійснити їх.

В) Потім поверніться до головного і класичне злиття між двома гілками:

git checkout master
git merge secondProject

Це об'єднало б усі файли та папки в корені обох git-проектів в один проект. Я сумніваюся, _anyone_ хотів би, щоб це сталося.
Клінтм

0

Я також кину своє рішення. Це в основному досить проста баш-скрипка git filter-branch. Як і інші рішення, він мігрує лише головні гілки та не мігрує теги. Але повна історія вчинення майстра мігрується, і це короткий сценарій башти, тому користувачам слід порівняно легко переглядати або налаштувати.

https://github.com/Oakleon/git-join-repos


0

Цей скрипт bash працює навколо проблеми символів вкладки sed (наприклад, на MacOS) та випуску відсутніх файлів.

export SUBREPO="subrepo"; # <= your subrepository name here
export TABULATOR=`printf '\t'`;
FILTER='git ls-files -s | sed "s#${TABULATOR}#&${SUBREPO}/#" |
  GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
  git update-index --index-info &&
  if [ -f "$GIT_INDEX_FILE.new" ]; then mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE; else echo "git filter skipped missing file: $GIT_INXEX_FILE.new"; fi'

git filter-branch --index-filter "$FILTER" HEAD

Це поєднання miniquark , Marius-butuc і Райан повідомлень. Привіт їм!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.