Как добавить историю в репозиторий Git? - PullRequest
18 голосов
/ 03 августа 2009

У меня есть проект, который существовал в двух репозиториях SVN. Второй репозиторий SVN был создан путем простого добавления репозиториев из проверки старого репозитория SVN без удаления информации SCM. Содержимое файлов идентично байту, но нет связанных метаданных SCM.

Я взял новый SVN-репозиторий и портировал его в Git-репозиторий через git-svn. Теперь я хотел бы импортировать старый репозиторий и каким-то образом заставить его связать новый репозиторий, чтобы я мог видеть историю обоих. Есть ли простой способ сделать это, не соединяя вручную два хранилища?

Ответы [ 2 ]

28 голосов
/ 10 октября 2009

См. Также: the Как мне воспроизвести мои коммиты из локального репозитория Git, поверх проекта, который я разветвлял на github.com? вопрос (и мой ответ там) , хотя ситуация немного иная, я думаю.


У вас есть как минимум три возможности:

  • Используйте трансплантаты , чтобы объединить две истории, но не переписывать историю. Это означает, что у вас (и у любого, у кого есть такие же прививки) будет полная история, тогда как у других пользователей будет меньший репозиторий. Это также позволяет избежать проблем с переписанной историей, если кто-то уже начал работать поверх преобразованного хранилища с более короткой историей.

  • Используйте трансплантаты, чтобы объединить две истории, и проверьте, что это правильно, используя "git log" или "gitk" (или другой браузер / просмотрщик истории Git), затем переписайте историю , используя git filter-branch ; тогда вы можете удалить файл трансплантатов. Это означает, что каждый, кто клонирует (извлекает) из переписанного репозитория, получит полную объединенную историю. Но переписывание истории - это большая проблема, если кто-то уже основывает работу на преобразованном коротко-историческом хранилище (но этот случай может не относиться к вам).

  • Используйте git replace , чтобы объединить две истории. Это позволило бы людям выбирать, хотят ли они полную историю или только текущую историю, выбрав для выборки refs/replace/ (тогда они получают полную историю) или нет (тогда они получают короткую историю). К сожалению, для этого требуется в настоящее время использовать еще не выпущенную версию Git, используя версию для разработчиков («master») или одного из кандидатов на выпуск для 1.6.5. В следующей версии Git 1.6.5 планируется иерархия refs/replace/.


Ниже приведены пошаговые инструкции для всех этих методов: пересадки (локальные), переписывание истории с использованием прививок и refs / replace /.

Во всех случаях я предполагаю, что у вас есть текущая и историческая история хранилища в одном хранилище (вы можете добавить историю из другого хранилища, используя git remote add ). Я также предполагаю, что (одна из) ветвей в репозитории с короткой историей называется «master», а ветвь (commit) исторического репозитория, к которому вы хотите прикрепить текущую историю, называется «history». Вам придется заменить свои собственные имена ветвей (или идентификаторы коммитов).

Поиск коммита для прикрепления (корень короткой истории)

Во-первых, вы должны найти фиксацию (идентификатор 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

(где «history ^ 0» используется вместо «history» на тот случай, если «history» является тегом; нам нужен SHA-1 коммита, а не объекта тега). Точно так же, как при поиске коммита для присоединения, давайте назовем этот коммит ID TOP. Вы можете сохранить его в переменной оболочки, используя:

$ TOP=$(git rev-parse --verify history^0)

Объединение истории с использованием файла трансплантатов

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

История переписывания в соответствии с файлом трансплантатов

Обратите внимание, что это изменит историю!

Чтобы сделать историю, записанную в файле графтов, постоянной, достаточно использовать "git filter-branch", чтобы переписать нужные вам ветви. Если существует только одна ветвь, которую нужно переписать ('master'), это может быть просто:

$ git filter-branch $TOP..master

(Это будет обрабатывать только минимальный набор коммитов). Если на истории присоединения больше ветвей, вы можете просто использовать

$ git filter-branch --all

Теперь вы можете удалить файл трансплантатов. Проверьте, все ли так, как вы хотели, и удалите резервную копию в refs/original/ (подробности см. В документации по "git filter-branch").

Использование ссылок / замены / механизма

Это альтернатива файлу трансплантатов. Он имеет преимущество в том, что его можно передавать, поэтому, если вы опубликовали краткую историю и не можете переписать ее (потому что другие основывают свою работу на короткой истории), то использование refs / replace / может быть хорошим решением ... ну, по крайней мере когда выйдет Git версии 1.6.5.

Механизм refs / replace / действует по-другому, чем файл трансплантатов: вместо изменения информации родителя вы заменяете объекты. Итак, сначала вы должны создать объект коммита, который имеет те же свойства, что и $ 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!) Между заголовком «tree» и заголовком «author». После редактирования '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/*' в качестве одной из опорных ссылок.

Последнее замечание: Я не проверял это решение, поэтому ваш пробег может отличаться .

5 голосов
/ 03 августа 2009

Сначала создайте точку пересадки , чтобы прикрепить две истории. Затем запустите git filter-branch над хранилищем, чтобы сделать изменение постоянным. Это изменит идентификаторы всех коммитов вниз по течению от трансплантата, примечание.

...