Объединить два репозитория Git, не нарушая историю файлов


226

Мне нужно объединить два репозитория Git в совершенно новый, третий репозиторий. Я нашел много описаний того , как сделать это с помощью поддерева слияния (например , ответ Якуба Narębski в на Как объединить два хранилища Git? ) И следуя эти инструкции , в основном работаешь, за исключением того, что , когда я совершить поддерево слияния всех файлов из старых репозиториев записываются как новые добавленные файлы. Когда я это делаю git log, я вижу историю коммитов из старых репозиториев , но если я это сделаю, git log <file>то для этого файла будет показан только один коммит - слияние поддеревьев. Судя по комментариям к ответу выше, я не одинок в этой проблеме, но я не нашел опубликованных решений для нее.

Есть ли способ объединить репозитории и оставить историю отдельных файлов без изменений?


Я не использую Git, но в Mercurial я сначала сделал бы преобразование, если необходимо, чтобы исправить пути к файлам репозиториев, которые должны быть объединены, а затем принудительно вытащил один репо в цель, чтобы получить наборы изменений, а затем сделал бы слияние разных веток. Это проверено и работает;) Может быть, это помогает найти решение и для Git ... по сравнению с подходом слияния поддеревьев, я думаю, шаг преобразования отличается, когда история переписывается, а не просто отображает путь (если я понимаю, правильно). Это обеспечивает плавное объединение без какой-либо специальной обработки путей к файлам.
— Лусеро

Я также нашел этот вопрос полезным stackoverflow.com/questions/1683531/…
— nacross

Я создал дополнительный вопрос. Может быть интересно: объединить два репозитория Git и сохранить историю мастеров: stackoverflow.com/questions/42161910/…
— Dimitri Dewaele

Автоматизированное решение, которое работало для меня, было stackoverflow.com/a/30781527/239408
— xverges

Ответы:


269

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

Вот пример скрипта Powershell для склеивания двух репозиториев:

# Assume the current directory is where we want the new repository to be created
# Create the new repository
git init

# Before we do a merge, we have to have an initial commit, so we'll make a dummy commit
git commit --allow-empty -m "Initial dummy commit"

# Add a remote for and fetch the old repo
git remote add -f old_a <OldA repo URL>

# Merge the files from old_a/master into new/master
git merge old_a/master --allow-unrelated-histories

# Move the old_a repo files and folders into a subdirectory so they don't collide with the other repo coming later
mkdir old_a
dir -exclude old_a | %{git mv $_.Name old_a}

# Commit the move
git commit -m "Move old_a files into subdir"

# Do the same thing for old_b
git remote add -f old_b <OldB repo URL>
git merge old_b/master --allow-unrelated-histories
mkdir old_b
dir –exclude old_a,old_b | %{git mv $_.Name old_b}
git commit -m "Move old_b files into subdir"

Очевидно, что вместо этого вы можете объединить old_b со old_a (который становится новым объединенным репо), если вы предпочитаете это сделать - изменить скрипт так, чтобы он подходил.

Если вы также хотите перенести текущие ветви функций, используйте это:

# Bring over a feature branch from one of the old repos
git checkout -b feature-in-progress
git merge -s recursive -Xsubtree=old_a old_a/feature-in-progress

Это единственная неочевидная часть процесса - это не слияние поддеревьев, а скорее аргумент к обычному рекурсивному слиянию, которое сообщает Git, что мы переименовали цель, и помогает Git правильно выстраивать все.

Я написал чуть более подробное объяснение здесь .


16
Использование этого решения git mvне очень хорошо работает. когда вы позже используете a git logдля одного из перемещенных файлов, вы получаете только фиксацию при перемещении. Вся предыдущая история потеряна. это потому, что git mvна самом деле, git rm; git addно в один шаг .
— mholm815

15
Это то же самое, что и любая другая операция перемещения / переименования в Git: из командной строки вы можете получить всю историю git log --follow, либо все инструменты GUI сделают это автоматически. Насколько я знаю, с объединением поддерева вы не можете получить историю отдельных файлов, поэтому этот метод лучше.
— Эрик Ли,

3
@EricLee Когда репозиторий old_b объединен, я получаю много конфликтов слияния. Это ожидается? Я получаю КОНФЛИКТ (переименовывать / удалять)
— Джон

9
Когда я пытаюсь "dir -exclude old_a |% {git mv $ _. Имя old_a}", я получаю sh.exe ": dir: команда не найдена, а sh.exe": git: команда не найдена. Используя это работает: ls -I old_a | xargs -I '{}' git mv '{}' old_a /
— Джордж

5
Это 1(номер один) для lsи «глаз» для xargs. Спасибо вам за этот совет!
— Доминик Виал

149

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

  1. Добавьте второй репо как удаленный:

    cd firstgitrepo/
    git remote add secondrepo username@servername:andsoon
    
  2. Убедитесь, что вы загрузили все коммиты secondrepo:

    git fetch secondrepo
    
  3. Создайте локальную ветку из ветви второго репо:

    git branch branchfromsecondrepo secondrepo/master
    
  4. Переместите все его файлы в подкаталог:

    git checkout branchfromsecondrepo
    mkdir subdir/
    git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} subdir/
    git commit -m "Moved files to subdir/"
    
  5. Объедините вторую ветку с главной веткой первого репо:

    git checkout master
    git merge --allow-unrelated-histories branchfromsecondrepo
    

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


1
Шаг 2 не работает для меня: fatal: Недопустимое имя объекта: 'secondrepo / master'.
— Кит

@Keith: убедитесь, что вы добавили второе репо в качестве удаленного с именем «secondrepo» и что в этом репо есть ветка с именем «master» (вы можете просматривать ветки в удаленном репо с помощью команды git remote show secondrepo)
— Flimm

Я должен был сделать выборку, чтобы сломать это также. Между 1 и 2 я сделал git fetch secondrepo
— sksamuel

@monkjack: я отредактировал свой ответ, включив в него шаг git fetch. Не стесняйтесь редактировать ответ самостоятельно в будущем.
— Flimm

4
@MartijnHeemels Для более старой версии Git просто опустите --allow-unrelated-histories. Смотрите историю этого ответного поста.
— Flimm

8

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

  1. Создайте новый репозиторий в Github.

    введите описание изображения здесь

  2. Загрузите вновь созданный репозиторий и добавьте старый удаленный репозиторий.

    git clone https://github.com/alexbr9007/Test.git
    cd Test
    git remote add OldRepo https://github.com/alexbr9007/Django-React.git
    git remote -v
    
  3. Получить все файлы из старого репозитория, чтобы создать новую ветку.

    git fetch OldRepo
    git branch -a
    

    введите описание изображения здесь

  4. В основной ветке выполните слияние, чтобы объединить старое репо с вновь созданным.

    git merge remotes/OldRepo/master --allow-unrelated-histories
    

    введите описание изображения здесь

  5. Создайте новую папку для хранения всего нового созданного контента, который был добавлен из OldRepo, и переместите его файлы в эту новую папку.

  6. Наконец, вы можете загрузить файлы из объединенных репозиториев и безопасно удалить OldRepo из GitHub.

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


1
Это единственное решение, которое помогло мне сохранить историю git. Не забудьте удалить удаленную ссылку на старый репозиторий git remote rm OldRepo.
— Харубиёри

7

пожалуйста, посмотрите на использование

git rebase --root --preserve-merges --onto

связать две истории в начале своей жизни.

Если у вас есть пути, которые перекрываются, исправьте их

git filter-branch --index-filter

при использовании журнала убедитесь, что вы «находите копии труднее» с

git log -CC

Таким образом, вы найдете любые движения файлов в пути.


Документация Git рекомендует не перебазировать ... git-scm.com/book/en/v2/Git-Branching-Rebasing#_rebase_peril
— Стивен Тернер

7

Я превратил решение из @Flimm в git aliasподобное (добавлено в мой ~/.gitconfig):

[alias]
 mergeRepo = "!mergeRepo() { \
  [ $# -ne 3 ] && echo \"Three parameters required, <remote URI> <new branch> <new dir>\" && exit 1; \
  git remote add newRepo $1; \
  git fetch newRepo; \
  git branch \"$2\" newRepo/master; \
  git checkout \"$2\"; \
  mkdir -vp \"${GIT_PREFIX}$3\"; \
  git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} \"${GIT_PREFIX}$3\"/; \
  git commit -m \"Moved files to '${GIT_PREFIX}$3'\"; \
  git checkout master; git merge --allow-unrelated-histories --no-edit -s recursive -X no-renames \"$2\"; \
  git branch -D \"$2\"; git remote remove newRepo; \
}; \
mergeRepo"

12
Просто любопытно: вы действительно делаете это достаточно часто, чтобы использовать псевдоним?
— Паркер Коутс

1
Нет, я не знаю, но никогда не помню, как это сделать, так что псевдоним - это просто способ запомнить это.
— Фредрик Эрландссон

1
Да .. но попробуйте изменить компьютеры и забыть , чтобы переместить псевдонимы;)
— Кетцалькоатля

1
Какова стоимость $GIT_PREFIX?
— neowulf33

github.com/git/git/blob/… 'GIT_PREFIX' устанавливается как возвращаемое путем запуска 'git rev-parse --show-prefix' из исходного текущего каталога. Смотрите linkgit: git-rev-parse [1].
— Фредрик Эрландссон

3

Эта функция клонирует удаленное репо в локальный каталог репо:

function git-add-repo
{
    repo="$1"
    dir="$(echo "$2" | sed 's/\/$//')"
    path="$(pwd)"

    tmp="$(mktemp -d)"
    remote="$(echo "$tmp" | sed 's/\///g'| sed 's/\./_/g')"

    git clone "$repo" "$tmp"
    cd "$tmp"

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

    cd "$path"
    git remote add -f "$remote" "file://$tmp/.git"
    git pull "$remote/master"
    git merge --allow-unrelated-histories -m "Merge repo $repo into master" --edit "$remote/master"
    git remote remove "$remote"
    rm -rf "$tmp"
}

Как пользоваться:

cd current/package
git-add-repo https://github.com/example/example dir/to/save

Примечание. Этот сценарий может перезаписывать коммиты, но сохранит всех авторов и даты, это означает, что новые коммиты будут иметь другие хэши, и если вы попытаетесь отправить изменения на удаленный сервер, он сможет это сделать только с помощью клавиши Force, также он перезапишет коммиты на сервере. Поэтому, пожалуйста, сделайте резервные копии перед запуском.

Прибыль!


Я использую zsh, а не bash и v2.13.0 из git. Что бы я ни пробовал, я не смог добраться git filter-branch --index-filterдо работы. Обычно я получаю сообщение об ошибке, что файл индекса .new не существует. Это звонит в какие-нибудь колокола?
— Патрик Борода

@PatrickBeard Я не знаю, zsh, вы можете создать отдельный файл git-add-repo.shс функцией выше, в конце файла поставьте эту строку git-add-repo "$@". После этого вы можете использовать его из Zsh, как cd current/git/packageиbash path/to/git-add-repo.sh https://github.com/example/example dir/to/save
— Андрей Изман

Проблема обсуждалась здесь: stackoverflow.com/questions/7798142/… mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE" иногда терпит неудачу, поэтому вы должны добавить if test.
— Патрик Борода

1
Я бы не использовал этот метод! Я попробовал сценарий, наивно и дословно (я могу винить себя только за эту часть), и он забил мой локальный репозиторий. История выглядела в основном правильно, но выполнение git push обратно на Github привело к страшному «RPC не удалось; curl 55 SSL_write () вернул ошибку SYSCALL, errno = 32». Я пытался починить его, но он был непоправимо сломан. В итоге мне пришлось реконструировать вещи в новом локальном репо.
— Мейсон Фрид

@MasonFreed этот скрипт создает новую историю git с сочетанием обоих репо, поэтому его нельзя перенести в старое репо, ему нужно создать новое или нажать форсированную клавишу, значит, он переписывает ваш репо на сервере
— Андрей Изман

2

Выполните шаги для встраивания одного репо в другое репо, имея одну историю Git, объединяя обе истории Git.

  1. Клонируйте оба репозитория, которые вы хотите объединить.

git clone git@github.com: пользователь / parent-repo.git

git clone git@github.com: пользователь / child-repo.git

  1. Перейти на репо

cd child-repo /

  1. выполните приведенную ниже команду, замените путь my/new/subdir(3 случая) структурой каталогов, в которой вы хотите иметь дочернее хранилище.

git filter-branch --prune-empty --tree-filter 'if [! -e мой / новый / subdir]; затем mkdir -p my / new / subdir git ls-tree - только для имени $ GIT_COMMIT | xargs -I файлы mv файлы my / new / subdir fi '

  1. Перейти к родительскому репо

cd ../parent-repo/

  1. Добавить удаленное к родительскому репо, указав путь к дочернему репо

git remote добавить child-remote ../child-repo/

  1. Получить репо ребенка

git fetch child-remote

  1. Слить истории

git merge --allow-unrelated-историй child-remote / master

Если вы сейчас проверите журнал git в родительском репо, он должен объединить коммиты дочернего репо. Вы также можете увидеть тег, указывающий из источника фиксации.

Приведенная ниже статья помогла мне встроить одно хранилище в другое, создав одну историю Git, объединив обе истории Git.

http://ericlathrop.com/2014/01/combining-git-repositories/

Надеюсь это поможет. Удачного кодирования!


Шаг 3 не удался для меня с синтаксической ошибкой. Точки с запятой отсутствуют. Fixgit filter-branch --prune-empty --tree-filter ' if [ ! -e my/new/subdir ]; then mkdir -p my/new/subdir; git ls-tree --name-only $GIT_COMMIT | xargs -I files mv files my/new/subdir; fi'
— Юрий Л

1

Допустим, вы хотите объединить хранилище aс b(я предполагаю, что они расположены рядом друг с другом):

cd b
git remote add a ../a
git fetch a
git merge --allow-unrelated-histories a/master
git remote remove a

В случае, если вы хотите поместить aв подкаталог, сделайте следующее перед командами выше:

cd a
git filter-repo --to-subdirectory-filter a
cd ..

Для этого вам нужно git-filter-repoустановить ( filter-branchне рекомендуется ).

Пример объединения двух больших репозиториев и помещения одного из них в подкаталог: https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

Подробнее об этом здесь .

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.