Как я могу объединить репозитории Git в линейную историю?

У меня есть два репозитория git R1 а также R2, которые содержат коммиты из двух периодов разработки продукта: 1995-1997 и 1999-2013. (Я создал их, преобразовав существующие репозитории RCS и CVS в Git.)

R1:
A---B---C---D

R2:
K---L---M---N

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

A---B---C---D---K---L---M---N

Обратите внимание, что между R1 а также R2 файлы были добавлены, удалены и переименованы.

Я попытался создать пустой репозиторий, а затем слить на него содержимое.

git remote add R1 /vol/R1.git
git fetch R1

git remote add R2 /vol/R2.git
git fetch R2

git merge --strategy=recursive --strategy-option=theirs R1
git merge --strategy=recursive --strategy-option=theirs R2

Тем не менее, это оставляет в конечных файлах, которые были в редакции D, но не в редакции K, Я мог бы создать искусственный коммит для удаления лишних файлов между слияниями, но мне это кажется не элегантным. Кроме того, благодаря этому подходу конечный результат содержит слияния, которые на самом деле не происходили.

4 ответа

Решение

Использование git filter-branch

Используя трюк прямо со страницы руководства git-filter-branch:

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

git init repo
cd repo
git remote add R1 /vol/R1.git
git fetch R1
git remote add R2 /vol/R2.git
git fetch R2

Затем укажите "мастер" (текущая ветвь) на кончик "мастера" R2.

git reset --hard R2/master

Теперь мы можем перенести историю "хозяина" R1 в начало.

git filter-branch --parent-filter 'sed "s_^\$_-p R1/master_"' HEAD

Другими словами, мы вставляем поддельный родительский коммит между D а также K так новая история выглядит так:

A---B---C---D---K---L---M---N

Единственное изменение в K через N в том, что KРодительский указатель изменяется, и поэтому все идентификаторы SHA-1 меняются. Сообщение о коммите, автор, метка времени и т. Д. Остаются прежними.

Объединение более двух репозиториев вместе с filter-branch

Если у вас есть более двух хранилищ, скажем, от R1 (самый старый) до R5 (самый новый), просто повторите git reset а также git filter-branch команды в хронологическом порядке.

PARENT_REPO=R1
for CHILD_REPO in R2 R3 R4 R5; do
    git reset --hard $CHILD_REPO/master
    git filter-branch --parent-filter 'sed "s_^\$_-p '$PARENT_REPO/master'"' HEAD
    PARENT_REPO=$CHILD_REPO
done

Использование трансплантатов

В качестве альтернативы использованию --parent-filter возможность filter-branchвместо этого вы можете использовать механизм прививки.

Рассмотрим исходную ситуацию добавления R2/master как ребенок (то есть, новее, чем) R1/master, Как и прежде, начните с указания текущей ветви (master) до кончика R2/master,

git reset --hard R2/master

Теперь вместо запуска filter-branch команда, создать "прививку" (поддельный родитель) в .git/info/grafts который связывает "корневой" (самый старый) коммит R2/master (K) до чаевых (новейших) коммитов в R1/master (D). (Если есть несколько корней R2/master, следующий будет связывать только один из них.)

ROOT_OF_R2=$(git rev-list R2/master | tail -n 1)
TIP_OF_R1=$(git rev-parse R1/master)
echo $ROOT_OF_R2 $TIP_OF_R1 >> .git/info/grafts

На данный момент, вы можете посмотреть на свою историю (скажем, через gitk) чтобы увидеть, выглядит ли это правильно. Если это так, вы можете сделать изменения постоянными через:

git filter-branch

Наконец, вы можете очистить все, удалив файл трансплантата.

rm .git/info/grafts

Использование трансплантатов, вероятно, больше работы, чем использование --parent-filter, но у него есть то преимущество, что он может собрать более двух историй с одним filter-branch, (Вы можете сделать то же самое с --parent-filter, но сценарий станет очень уродливым и очень быстрым.) У него также есть преимущество, позволяющее вам увидеть ваши изменения до того, как они станут постоянными; если это выглядит плохо, просто удалите файл трансплантата, чтобы прервать его.

Слияние более двух хранилищ вместе с трансплантатами

Чтобы использовать метод пересадки с R1 (самый старый) до R5 (самый новый), просто добавьте несколько строк в файл прививки. (Порядок, в котором вы запускаете echo Команды не имеют значения.)

git reset --hard R5/master

PARENT_REPO=R1
for CHILD_REPO in R2 R3 R4 R5; do
    ROOT_OF_CHILD=$(git rev-list $CHILD_REPO/master | tail -n 1)
    TIP_OF_PARENT=$(git rev-parse $PARENT_REPO/master)
    echo "$ROOT_OF_CHILD" "$TIP_OF_PARENT" >> .git/info/grafts
    PARENT_REPO=$CHILD_REPO
done

Что насчет git rebase?

Несколько других предложили использовать git rebase R1/master вместо git filter-branch Команда выше. Это займет разницу между пустым коммитом и K а затем попробуйте применить его к D, в результате чего:

A---B---C---D---K'---L'---M'---N'

Это, скорее всего, приведет к конфликту слияния и даже может привести к созданию ложных файлов в K' если файл был удален между D а также K, Единственный случай, когда это будет работать, это если деревья D а также K идентичны

(Другое небольшое отличие состоит в том, что git rebase изменяет информацию коммиттера для K' через N', в то время как git filter-branch не.)

Оригинальный плакат гласит:

R1:
A---B---C---D

R2:
K---L---M---N

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

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

A---B---C---D---K---L---M---N

Обратите внимание, что между R1 и R2 файлы были добавлены, удалены и переименованы.

Так что я точно знаю, что если первый коммит нового репо, Kбыли идентичны или слегка изменены по сравнению с последним коммитом старого репо, Dтогда вы могли бы просто получить R1История в R2затем перебазируйте граф фиксации R2 на график из R1:

# From R2
git fetch R1
git checkout master
git rebase --onto R1/master --root

Нелинейные истории (когда у вас есть коммиты слияния)

Это при условии, что R2График линейный. Если он имеет коммиты слияния, вы можете попытаться сделать то же самое, указав, что вы хотите сохранить коммиты слияния,

git rebase --preserve-merges --onto R1/master --root

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

Объединение двух радикально разных историй?

Оригинальный плакат гласил:

Обратите внимание, что между R1 и R2 файлы были добавлены, удалены и переименованы.

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

Документация

Вот что я сделал, что сработало:

git init
git remote add R1 /vol/R1.git
git fetch R1
git remote add R2 /vol/R2.git
git fetch R2
git co -B master R2/master
git rebase R1/master
git push -f

Все, что вам нужно, это:git rebase с какой веткой вы перебазируете.

Короче говоря, rebase перематывает все коммиты ветки и объединяет их с коммитами ветки, которую вы перебазируете.

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

Удачи!

Другие вопросы по тегам