Объединение нескольких репозиториев 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/…
Йохан Шёберг
Также учтите
ptim
Сценарий join-git-repos.py отлично работает, если у вас есть отдельные репозитории, в каждом из которых есть основные ветви, которые вы хотите объединить.
Mark

Ответы:

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.

MiniQuark
источник
13
Спасибо за этот фрагмент - он сделал именно то, что мне было нужно (как только я учел, что Mac OS X sed не обрабатывает "\ t" (мне пришлось использовать ^ V ^ I).
Крейг Трейдер
6
Сначала я не мог заставить это работать и в конце концов нашел решение проблемы на другой старой доске объявлений. В последней строке я должен был заключить в кавычки имена файлов примерно так: mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEADа потом все заработало!
Йорин
3
Прикольная команда filter-branch - из справочных страниц git filter-branch. Вы должны сказать, что: а) она должна быть правильно приписана; б) я не буду запускать такую ​​команду только потому, что кто-то, даже с высокой репутацией, разместил ее в StackOverflow. Зная, что это из man-страниц, я узнаю.
тымтам
5
ОСТОРОЖНО! MacOS X не использует расширение sed для GNU, поэтому не знает последовательности \ t. Результат - испорченная история! Мое решение состояло в том, чтобы вставить код в файл сценария и написать в нем настоящий символ <TAB>. Из терминала можно открыть вкладку, нажав Ctrl + V, а затем написав <TAB>. Я не пробовал решение Крейга
Гил Веглиах
5
СМОТРЕТЬ (2)! Также обратите внимание, что если некоторые файлы или каталоги содержат дефисы ('-'), команда sed завершится неудачно. В этом случае вы можете заменить его чем-то вроде 's ~ \ t ~ & code / ~'. Здесь, применяя ту же логику, следите за «~» в именах
Гил Веглях
75

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

Аристотель Пагальцис
источник
33
Это сторонний инструмент, а не часть
мерзавца
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"

а затем объединить три отдельных репозитория в одно новое, выполнив что-то вроде:

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

Тогда вы сохраните свои истории, но продолжите с одним репо.

imz - Иван Захарящев
источник
Это нормально, но если вы объединяете одно хранилище в другое (т. Е. Phd был не пустым уже существующим хранилищем), то если в phd есть папки с именами, совпадающими с подпапками в каталоге кода, вы будете сталкиваться с проблемами как 'git pull .. / phd / code 'извлекает все коммиты с оригинальными путями и только в конце применяет коммит mv.
тымтам
1
@Tymek: но это все равно будет работать в такой ситуации, без проблем. Не очень приятно то, что пути в истории не будут «правильными» (соответствуют новым путям).
imz - Иван Захарящев
19

Вы можете попробовать стратегию слияния поддеревьев . Это позволит вам объединить репо B с репо A. Преимущество перед git-filter-branchним заключается в том, что вам не нужно переписывать историю репо A (ломая суммы SHA1).

Лейф Грюнвольдт
источник
Ссылка не работает, и это не сохранит историю, не так ли?
тымтам
3
@Tymek (К сожалению, части kernel.org все еще не работают после нарушения безопасности). Это ломает SHA1 входящего репо B. Но A остается неповрежденным.
Лейф Грюнволдт
2
Вот зеркало этого документа на данный момент ftp.sunet.se/pub/Linux/kernel.org/software/scm/git/docs/howto/…
Лейф Грюнвольдт
1
@LeifGruenwoldt Первая ссылка работает сейчас. И ссылка на зеркало исчезла, вы должны удалить ее, я полагаю.
Вадим Котов
9

Решение git-filter-branch работает хорошо, но учтите, что если ваше git-репо происходит из 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-experiments.com/blog/2010/03/merging-git-repositories.html

Gareth
источник
2
Спасибо! Я чесал голову, почему это не работает! Репо действительно пришел из SVN.
Артур Мальтсон,
1
Та же ошибка, когда я делаю это. Надеюсь. Также ссылка теперь не работает.
Райан
Не могли бы вы уточнить, что вы имели в виду, «изменив голову на ...», мой репозиторий основан на импорте SVN, и я столкнулся именно с этой проблемой, был бы очень признателен за помощь!
5

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

  1. Сначала создайте каталог, который будет содержать составные репо и объединенные репозитории, создайте каталог для каждого объединенного репо.

    $ mkdir new_phd
    $ mkdir new_phd / code
    $ mkdir new_phd / figure
    $ $ mkdir new_phd / thesis

  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 '--tag-name-filter' sed" s -. * - код _ & - "'HEAD

  4. После этого будет вдвое больше тегов, чем было до выполнения filter-branch. Старые теги остаются в репо и code_добавляются новые теги с префиксом.

    $ git tag
    mytag1
    code_mytag1

    Удалить старые теги вручную:

    $ ls .git / refs / tags / * | grep -v "/ code_" | XARGS RM

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

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

  6. Сделайте так же, как в пункте 4 MiniQuark anwser, но после выполнения pull и перед удалением .gitdir извлеките теги:

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

    Продолжать..

Это просто другое решение. Надеюсь, это поможет кому-то, это помогло мне :)

MichK
источник
5

git-stitch-repo из ответа Аристотеля Пагальциса работает только для репозиториев с простой линейной историей.

Ответ MiniQuark работает для всех репозиториев, но он не обрабатывает теги и ветви.

Я создал программу, которая работает так же, как описывает MiniQuark, но она использует один коммит слияния (с N родителями), а также воссоздает все теги и ветви, чтобы указать на эти коммиты слияния.

Посмотрите репозиторий git-merge-repos для примеров, как его использовать.

robinst
источник
3

Я создал инструмент, который делает эту задачу. Используемый метод аналогичен (внутренне создает некоторые вещи, такие как --filter-branch), но более дружественен. Является ли GPL 2.0

http://github.com/geppo12/GitCombineRepo

Джузеппе Монтелеоне
источник
3

На самом деле, git-stitch-repo теперь поддерживает ветки и теги, в том числе аннотированные теги (я обнаружил, что есть ошибка, о которой я сообщил, и она исправлена). Что я нашел полезным, так это с тегами. Поскольку теги прикреплены к коммитам, а некоторые решения (например, подход Эрика Ли) не справляются с тегами. Вы пытаетесь создать ветку на основе импортированного тега, и она отменяет любые мерзкие слияния / перемещения и отправляет вас обратно, как если бы консолидированный репозиторий был почти идентичен репозиторию, из которого пришел тег. Кроме того, возникают проблемы, если вы используете один и тот же тег в нескольких репозиториях, которые вы «объединили / объединили». Например, если у вас есть репозиторий A ad B, оба имеют тег rel_1.0. Вы объединяете репо A и репо B в репо AB. Поскольку теги rel_1.0 находятся на двух разных коммитах (один для A и один для B), какой тег будет виден в AB? Либо тег из импортированного репо A, либо из импортированного репо B, но не оба.

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

user3622356
источник
2

Последовательность, которую вы предложили

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

будет работать, но вы потеряете свою историю коммитов.

Patrick_O
источник
Потеря истории не так уж и плоха, но поскольку репозиторий предназначен для моей собственной работы (то есть, он частный), в нем есть много вещей, которые я не хочу версионировать или которые еще не версированы.
Уилл Робертсон
1

Чтобы объединить второй проект с основным проектом:

А) Во втором проекте

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

Б) В основном проекте:

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

В этой ветке делайте все тяжелые преобразования, которые вам нужно сделать, и зафиксируйте их.

C) Затем вернемся к мастеру и классическому слиянию двух ветвей:

git checkout master
git merge secondProject
user123568943685
источник
Это объединит все файлы и папки в корне обоих проектов git в один проект. Я сомневаюсь, что кто-нибудь захочет, чтобы это случилось.
Клинт
0

Я тоже добавлю сюда свое решение. По сути, это довольно простая оболочка bash-скрипта git filter-branch. Как и другие решения, он переносит только основные ветви и не переносит теги. Но полная история генерации коммитов перенесена, и это короткий сценарий bash, поэтому пользователям должно быть относительно легко просматривать или настраивать.

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

chrishiestand
источник
0

Этот bash-скрипт решает проблему с символами Sed Tab (например, в 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 и Райан сообщений. Приветствую их!

буэ
источник