Как объединить два репозитория Git? - PullRequest
1434 голосов
/ 15 сентября 2009

Рассмотрим следующий сценарий:

Я разработал небольшой экспериментальный проект A в своем собственном репозитории Git. Сейчас он созрел, и я бы хотел, чтобы A стал частью более крупного проекта B, у которого есть свой большой репозиторий. Теперь я хотел бы добавить A в подкаталог B.

Как мне слить А в Б, не теряя истории ни с какой стороны?

Ответы [ 22 ]

1615 голосов
/ 11 мая 2012

Если вы хотите объединить project-a в project-b:

cd path/to/project-b
git remote add project-a path/to/project-a
git fetch project-a --tags
git merge --allow-unrelated-histories project-a/master # or whichever branch you want to merge
git remote remove project-a

Взято из: git, объединить разные репозитории?

Этот метод работал очень хорошо для меня, он короче и, на мой взгляд, намного чище.

Примечание: Параметр --allow-unrelated-histories существует только с git> = 2.9. См. Git - Документация по git merge / --allow-unrelated-history

Обновление : добавлено --tags в соответствии с предложением @jstadler для сохранения тегов.

593 голосов
/ 15 сентября 2009

Вот два возможных решения:

Подмодули

Либо скопируйте репозиторий A в отдельный каталог в более крупном проекте B, либо (возможно, лучше) скопируйте репозиторий A в подкаталог в проекте B. Затем используйте подмодуль git , чтобы сделать этот репозиторий a субмодуль из хранилища B.

Это хорошее решение для слабосвязанных репозиториев, где продолжается разработка в репозитории A, а основная часть разработки - это отдельная отдельная разработка в A. См. Также SubmoduleSupport и GitSubmoduleTutorial страниц на Git Wiki.

Слияние поддеревьев

Вы можете объединить репозиторий A с подкаталогом проекта B с помощью стратегии поддерева . Это описано в Слияние поддерев и Вы от Маркуса Принца.

git remote add -f Bproject /path/to/B
git merge -s ours --allow-unrelated-histories --no-commit Bproject/master
git read-tree --prefix=dir-B/ -u Bproject/master
git commit -m "Merge B project as our subdirectory"
git pull -s subtree Bproject master

(опция --allow-unrelated-histories необходима для Git> = 2.9.0.)

Или вы можете использовать git subtree tool ( репозиторий на GitHub ) от apenwarr (Avery Pennarun), анонсированный, например, в своем блоге Новая альтернатива подмодулям Git : git поддерево .


Я думаю, что в вашем случае (A должен быть частью более крупного проекта B) правильное решение было бы использовать объединение поддеревьев .

378 голосов
/ 21 февраля 2013

Одна ветвь другого хранилища может быть легко размещена в подкаталоге, сохраняя свою историю. Например:

git subtree add --prefix=rails git://github.com/rails/rails.git master

Это будет выглядеть как единый коммит, в котором все файлы главной ветки Rails будут добавлены в каталог "rails". Однако заголовок коммита содержит ссылку на старое древо истории:

Добавить 'rails /' из commit <rev>

Где <rev> - хеш коммита SHA-1. Вы все еще можете увидеть историю, обвините некоторые изменения.

git log <rev>
git blame <rev> -- README.md

Обратите внимание, что отсюда не видно префикса каталога, поскольку это старая ветка, оставшаяся без изменений. Вы должны относиться к этому как к обычному коммиту перемещения файла: вам понадобится дополнительный прыжок при его достижении.

# finishes with all files added at once commit
git log rails/README.md

# then continue from original tree
git log <rev> -- README.md

Существуют более сложные решения, такие как выполнение вручную или переписывание истории, как описано в других ответах.

Команда git-subtree является частью официального git-contrib, некоторые менеджеры пакетов устанавливают ее по умолчанию (OS X Homebrew). Но вам может понадобиться установить его самостоятельно в дополнение к git.

193 голосов
/ 15 сентября 2009

Подмодульный подход хорош, если вы хотите поддерживать проект отдельно. Однако, если вы действительно хотите объединить оба проекта в одном хранилище, у вас есть немного больше работы.

Первым делом можно было бы использовать git filter-branch, чтобы переписать имена всего во втором хранилище, чтобы оно находилось в подкаталоге, где вы хотели бы, чтобы они заканчивались. Таким образом, вместо foo.c, bar.html вы получите projb/foo.c и projb/bar.html.

Тогда вы сможете сделать что-то вроде следующего:

git remote add projb [wherever]
git pull projb

git pull сделает git fetch, а затем git merge. Не должно быть никаких конфликтов, если репозиторий, к которому вы обращаетесь, еще не имеет каталога projb/.

Дальнейший поиск показывает, что нечто подобное было сделано для слияния gitk в git. Об этом пишет Джунио С. Хамано: http://www.mail-archive.com/git@vger.kernel.org/msg03395.html

65 голосов
/ 01 февраля 2014

git-subtree это хорошо, но это, вероятно, не тот, который вы хотите.

Например, если projectA - каталог, созданный в B, после git subtree,

git log projectA

перечисляет только один коммит: объединение. Коммиты из объединенного проекта предназначены для разных путей, поэтому они не отображаются.

Ответ Грега Хьюгилла наиболее близок, хотя на самом деле он не говорит, как переписать пути.


Решение удивительно просто.

(1) В А,

PREFIX=projectA #adjust this

git filter-branch --index-filter '
    git ls-files -s |
    sed "s,\t,&'"$PREFIX"'/," |
    GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info &&
    mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE
' HEAD

Примечание. Это переписывает историю, поэтому, если вы намереваетесь продолжать использовать этот репозиторий A, вы можете сначала скопировать (скопировать) его одноразовую копию.

(2) Затем в B, запустить

git pull path/to/A

Вуаля! У вас есть каталог projectA в B. Если вы запустите git log projectA, вы увидите все коммиты из A.


В моем случае я хотел два подкаталога, projectA и projectB. В этом случае я также выполнил шаги (1) до B.

44 голосов
/ 11 июня 2011

Если оба хранилища имеют одинаковые типы файлов (например, два хранилища Rails для разных проектов), вы можете извлечь данные из вторичного хранилища в ваш текущий хранилище:

git fetch git://repository.url/repo.git master:branch_name

, а затем объединить его с текущим хранилищем:

git merge --allow-unrelated-histories branch_name

Если ваша версия Git меньше 2.9, удалите --allow-unrelated-histories.

После этого могут возникнуть конфликты. Вы можете разрешить их, например, с помощью git mergetool. kdiff3 может использоваться исключительно с клавиатурой, поэтому 5 файлов конфликта занимают при считывании кода всего несколько минут.

Не забудьте завершить слияние:

git commit
22 голосов
/ 27 февраля 2014

Я продолжал терять историю при использовании слияния, поэтому в итоге я использовал rebase, поскольку в моем случае два репозитория достаточно различны, чтобы не заканчивать слияние при каждом коммите:

git clone git@gitorious/projA.git projA
git clone git@gitorious/projB.git projB

cd projB
git remote add projA ../projA/
git fetch projA 
git rebase projA/master HEAD

=> разрешать конфликты, а затем продолжать столько раз, сколько необходимо ...

git rebase --continue

Это приводит к тому, что в одном проекте все коммиты от projA сопровождаются коммитами от projB

17 голосов
/ 19 августа 2016

В моем случае у меня было хранилище my-plugin и хранилище main-project, и я хотел сделать вид, что my-plugin всегда разрабатывалось в подкаталоге plugins main-project.

По сути, я переписал историю репозитория my-plugin, так что оказалось, что вся разработка происходила в подкаталоге plugins/my-plugin. Затем я добавил историю развития my-plugin в историю main-project и объединил два дерева вместе. Поскольку в репозитории main-project уже не было каталога plugins/my-plugin, это было тривиальное слияние без конфликтов. Получившийся репозиторий содержал всю историю обоих исходных проектов и имел два корня.

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) с глобусом (^(...)).

Затем я использую команду 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

Обратите внимание, что в Git версии до 2.9.0 опция --allow-unrelated-histories не существует. Если вы используете одну из этих версий, просто опустите опцию: сообщение об ошибке, которое --allow-unrelated-histories предотвращает, было также , добавленное в 2.9.0.

У вас не должно быть конфликтов слияния. Если вы это сделаете, это, вероятно, означает, что либо команда filter-branch работает неправильно, либо в main-project.

уже есть каталог plugins/my-plugin.

Обязательно введите пояснительное сообщение о коммите для всех будущих участников, задающихся вопросом, что за хакерство собиралось сделать хранилище с двумя корнями.

Вы можете визуализировать новый граф коммитов, который должен иметь два корневых коммита, используя указанную выше команду 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. Ваш пробег может отличаться.

Ссылки:

8 голосов
/ 10 марта 2016

Я пытался делать то же самое в течение нескольких дней, я использую git 2.7.2. Поддерево не сохраняет историю.

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

Я бы предложил сначала ветку B и работать в ветке.

Вот шаги без ветвления:

cd B

# You are going to merge A into B, so first move all of B's files into a sub dir
mkdir B

# Move all files to B, till there is nothing in the dir but .git and B
git mv <files> B

git add .

git commit -m "Moving content of project B in preparation for merge from A"


# Now merge A into B
git remote add -f A <A repo url>

git merge A/<branch>

mkdir A

# move all the files into subdir A, excluding .git
git mv <files> A

git commit -m "Moved A into subdir"


# Move B's files back to root    
git mv B/* ./

rm -rf B

git commit -m "Reset B to original state"

git push

Если вы сейчас зарегистрируете какой-либо из файлов в подкаталоге A, вы получите полную историю

git log --follow A/<file>

Это был пост, который помог мне сделать это:

http://saintgimp.org/2013/01/22/merging-two-git-repositories-into-one-repository-without-losing-file-history/

7 голосов
/ 05 мая 2018

Если вы хотите поместить файлы из ветви в репо B в поддерево репо A и , также сохраните история, продолжайте читать. (В приведенном ниже примере я предполагаю, что мы хотим, чтобы основная ветвь репо B была объединена с главной ветвью репо А.)

В репо А сначала сделайте следующее, чтобы сделать репо В доступным:

git remote add B ../B # Add repo B as a new remote.
git fetch B

Теперь мы создаем совершенно новую ветвь (только с одним коммитом) в репо А, которую мы называем new_b_root. Полученный коммит будет иметь файлы, которые были зафиксированы в первом коммите главной ветви репозитория B, но помещены в подкаталог с именем path/to/b-files/.

git checkout --orphan new_b_root master
git rm -rf . # Remove all files.
git cherry-pick -n `git rev-list --max-parents=0 B/master`
mkdir -p path/to/b-files
git mv README path/to/b-files/
git commit --date="$(git log --format='%ai' $(git rev-list --max-parents=0 B/master))"

Объяснение: Параметр --orphan команды checkout извлекает файлы из главной ветви A, но не создает коммит. Мы могли бы выбрать любой коммит, потому что затем мы все равно очищаем все файлы. Затем, еще не зафиксировав (-n), мы выбираем первый коммит из основной ветки B. (Вишня выбирает исходное сообщение о фиксации, которое, похоже, не делает прямая проверка.) Затем мы создаем поддерево, в которое мы хотим поместить все файлы из репозитория B. Затем мы должны переместить все файлы, которые были представлены в вишня к поддереву. В приведенном выше примере есть только файл README для перемещения. Затем мы фиксируем нашу корневую фиксацию B-repo, и в то же время мы также сохраняем временную метку первоначальной фиксации.

Теперь мы создадим новую ветку B/master поверх вновь созданной new_b_root. Мы называем новый филиал b:

git checkout -b b B/master
git rebase -s recursive -Xsubtree=path/to/b-files/ new_b_root

Теперь мы объединяем нашу b ветку с A/master:

git checkout master
git merge --allow-unrelated-histories --no-commit b
git commit -m 'Merge repo B into repo A.'

Наконец, вы можете удалить B удаленные и временные ветви:

git remote remove B
git branch -D new_b_root b

Конечный график будет иметь такую ​​структуру:

enter image description here

...