См. также: Как воспроизвести мои коммиты из локального репозитория Git поверх проекта, который я создал на github.com? вопрос (и мой ответ там), хотя ситуация немного другая, я думаю.
У вас есть как минимум три возможности:
Используйте прививки, чтобы соединить две истории, но не переписывать историю. Это означает, что у вас (и любого, у кого есть такие же трансплантаты) будет полная история, в то время как у других пользователей будет меньший репозиторий. Это также позволяет избежать проблем с переписанной историей, если кто-то уже начал работать над преобразованным репозиторием с более короткой историей.
Используйте прививки, чтобы соединить две истории, и проверьте правильность с помощью «git log» или «gitk» (или другого браузера/просмотрщика истории Git), а затем перепишите историю с помощью git filter-branch; затем вы можете удалить файл графтов. Это означает, что каждый, кто клонирует (извлекает) из переписанного репозитория, получит полную объединенную историю. Но переписывать историю — это большое «нет», если кто-то уже основывал работу на конвертированном репозитории краткой истории (но этот случай может не относиться к вам).
Используйте git replace strong> соединить две истории. Это позволит людям выбрать, хотят ли они полную историю или только текущую историю, выбрав получение refs/replace/
(тогда они получат полную историю) или нет (тогда они получат краткую историю). К сожалению, в настоящее время для этого требуется использовать еще не выпущенную версию Git, используя разрабатываемую («основную») версию или один из кандидатов на выпуск для 1.6.5. Иерархия refs/replace/
запланирована для следующей версии Git 1.6.5.
Ниже приведены пошаговые инструкции для всех этих методов: прививки (локальные), переписывание истории при помощи прививок и refs/replace/.
Во всех случаях я предполагаю, что у вас есть как текущая, так и историческая история репозитория в одном репозитории (вы можете добавить историю из другого репозитория, используя удаленное добавление git). Я также предполагаю, что (одна из) веток в репозитории краткой истории называется «мастер», а ветка (коммит) исторического репозитория, куда вы хотите прикрепить текущую историю, называется «история». Вам нужно будет заменить свои собственные имена веток (или идентификаторы коммитов).
Поиск коммита для прикрепления (корень краткой истории)
Во-первых, вам нужно найти коммит (идентификатор SHA-1) в краткой истории, который вы хотите прикрепить к полной истории. Это будет первая фиксация в короткой истории, то есть корневая фиксация (коммит без каких-либо родителей).
Есть два способа найти его. Если вы уверены, что у вас нет другого корневого коммита, вы можете найти последний (самый нижний) коммит в топологическом порядке, используя:
$ git rev-list --topo-order master | tail -n 1
(где tail -n 1
используется для получения последней строки вывода; вам не нужно использовать его, если у вас его нет.)
Если существует возможность нескольких корневых коммитов, вы можете найти все коммиты без родителей, используя следующую однострочную строку:
$ git rev-list --parents master | grep -v ' '
(где grep -v ' '
, то есть пробел между одинарными кавычками, используется для фильтрации всех коммитов, у которых есть родители). Затем вы должны проверить (используя, например, «git show <commit>
») эти коммиты, если их больше одного, и выбрать тот, который вы хотите прикрепить к более ранней истории.
Назовем этот коммит TAIL. Вы можете сохранить его в переменной оболочки, используя (при условии, что более простой метод работает для вас):
$ TAIL=$(git rev-list --topo-order master | tail -n 1)
В приведенном ниже описании я бы использовал $TAIL
для обозначения того, что вы должны заменить SHA-1 самой нижней фиксации в текущей (короткой) истории... или позволить оболочке сделать замену за вас.
Поиск коммита для присоединения (верхняя часть исторического репозитория)
Эта часть проста. Нам нужно преобразовать символическое имя коммита в идентификатор SHA-1. Мы можем сделать это с помощью «git rev-parse»:
$ git rev-parse --verify history^0
(где «история ^ 0» используется вместо «истории» на тот случай, если «история» является тегом; нам нужен SHA-1 коммита, а не объекта тега). Точно так же, как и при поиске коммита для присоединения, давайте назовем этот идентификатор коммита TOP. Вы можете сохранить его в переменной оболочки, используя:
$ TOP=$(git rev-parse --verify history^0)
Присоединение истории с помощью файла графтов
Файл grafts, расположенный в .git/info/grafts
(вам нужно создать этот файл, если он не существует, если вы хотите использовать этот механизм) используется для замены родительской информации для коммита. Это построчный формат, где каждая строка содержит SHA-1 коммита, который мы хотим изменить, за которым следует нуль или более списков коммитов, разделенных пробелами, которые мы хотим, чтобы данный коммит имел в качестве родителей; тот же формат, что и "git rev-list --parents <revision>
".
Мы хотим, чтобы коммит $TAIL, у которого нет родителей, имел $TOP в качестве единственного родителя. Таким образом, в файле info/grafts
должна быть строка с SHA-1 коммита $TAIL, разделенная пробелом SHA-1 коммита $TOP. Для этого вы можете использовать следующую однострочную строку (см. также примеры в git filter-branch документация):
$ echo "$TAIL $TOP" >> .git/info/grafts
Теперь вы должны проверить, используя «git log», «git log --graph», «gitk» или другой браузер истории, что вы правильно объединили истории.
Переписывание истории по файлу графтов
Обратите внимание, что это изменит историю!
Чтобы сделать историю, записанную в файле grafts, постоянной, достаточно использовать «git filter-branch», чтобы переписать нужные вам ветки. Если есть только одна ветвь, которую нужно переписать («мастер»), это может быть так же просто, как:
$ git filter-branch $TOP..master
(Это будет обрабатывать только минимальный набор коммитов). Если есть больше ветвей, затронутых историей присоединения, вы можете просто использовать
$ git filter-branch --all
Теперь вы можете удалить файл графтов. Проверьте, все ли так, как вы хотели, и удалите резервную копию в refs/original/
(подробности см. в документации по "git filter-branch").
Использование механизма refs/replace/
Это альтернатива файлу графтов. У него есть то преимущество, что его можно передавать, поэтому, если вы опубликовали краткую историю и не можете ее переписать (потому что другие основывали свою работу на короткой истории), то использование refs/replace/ может быть хорошим решением... ну, по крайней мере когда выйдет Git версии 1.6.5.
Механизм refs/replace/ работает иначе, чем файл grafts: вместо изменения информации родителя вы заменяете объекты. Итак, сначала вам нужно создать объект фиксации, который имеет те же свойства, что и $TAIL, но имеет $TOP в качестве родителя.
Мы можем использовать
$ git cat-file commit $TAIL > TAIL_COMMIT
(Имя временного файла является только примером).
Теперь вам нужно отредактировать файл 'TAIL_COMMIT' (он будет выглядеть так):
tree 2b5bfdf7798569e0b59b16eb9602d5fa572d6038
author Joe R Hacker 1112911993 -0700
committer Joe R Hacker 1112911993 -0700
Initial revision of "project", after moving to new repository
Теперь вам нужно добавить $TOP в качестве родителя, поместив строку с «parent $TOP» (где $TOP должен быть расширен до идентификатора SHA-1!) между заголовком «дерево» и заголовком «автор». После редактирования TAIL_COMMIT это должно выглядеть так:
tree 2b5bfdf7798569e0b59b16eb9602d5fa572d6038
parent 0f6592e3c2f2fe01f7b717618e570ad8dff0bbb1
author Joe R Hacker 1112911993 -0700
committer Joe R Hacker 1112911993 -0700
Initial revision of "project", after moving to new repository
При желании вы можете отредактировать сообщение коммита.
Теперь вам нужно использовать git hash-object для создания новой фиксации в репозитории. Вам нужно сохранить результат этой команды, который представляет собой SHA-1 нового объекта фиксации, например, так:
$ NEW_TAIL=$(git hash-object -t commit -w TAIL_COMMIT)
(Где опция «-w
» предназначена для фактической записи объекта в репозиторий).
Наконец, используйте git replace для замены $TAIL $NEW_TAIL:
$ git replace $TAIL $NEW_TAIL
Теперь осталось проверить (используя "git log" или какой-либо другой просмотрщик истории), верна ли история.
Теперь любой, кто хочет получить полную историю, должен добавить «+refs/replace/*:refs/replace/*
» в качестве одной из спецификаций pull refspec.
Последнее примечание. Я не проверял это решение, поэтому ваше мнение может отличаться.
person
Jakub Narębski
schedule
10.10.2009