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