Измените родительский родительский коммит, чтобы он указывал на другой коммит (соединяя два независимых репозитория git)

У меня есть проект с более чем 3-летней историей в хранилище SVN. Он был перенесен в git, но парень, который сделал это, просто взял последнюю версию и выбросил все эти 3 года истории.

Теперь у проекта есть последние 3-4 месяца истории в одном репозитории, а я импортировал остальные 3 года истории SVN в новый репозиторий git.

Есть ли какой-нибудь способ соединить корневой коммит второго репозитория с последним коммитом первого?

Это что-то вроде этого:

  *   2017-04-21 - last commit on master
  |   
  *   2017-03-20 - merge branch Y into master
  |\  
  | * 2017-03-19 - commit on branch Y
  | | 
  * | 2017-03-18 - merge branch X into master
 /| * 2017-02-17 - commit on another new branch Y
* |/  2017-02-16 - commit on branch X
| *   2017-02-15 - commit on master branch
* |   2017-01-14 - commit on new branch X
 \|   
  *   2017-01-13 - first commit on new repository
  |   
  *   2017-01-12 - init new git project with the last version of the code in svn repository
  .   
  .   
There is no relationship between the two different repositories yet, this is what I wanna
do. I want to connect the root commit of 2nd repository with the last commit of the first
one.
  .
  .   
  *   2017-01-09 - commit
  |   
  *   2017-01-08 - commit
  |   
  *   2017-01-07 - merge
 /|   
* |   2016-01-06 - 2nd commit the other branch
| *   2016-01-05 - commit on trunk
* |   2016-01-04 - commit on new branch
 \|   
  *   2015-01-03 - first commit
  |   
  *   2015-01-02 - beggining of the project

Обновить:

Я просто узнаю, что мне нужно сделать git rebase , но как? Пожалуйста, давайте рассмотрим даты фиксации, как если бы это были коды SHA-1... Ответ был на использование git filter-branch с --parent-filter вариант, а не git rebase,

Обновление 2:

Я попробовал команду git filter-branch --parent-filter 'test $GIT_COMMIT = 443aec8880e898710796a1c4fb4decea1ca5ff66 && echo "-p 98e2b95e07b84ad1e40c3231e66840ea910e9d66" || cat' HEAD и это не сработало

PS D:\git\rebase-test\rep2cc> git filter-branch --parent-filter 'test $GIT_COMMIT = 443aec8880e898710796a1c4fb4decea1ca5ff66 && echo "-p 98e2b95e07b84ad1e40c3231e66840ea910e9d66" || cat' HEAD
fatal: ambiguous argument '98e2b95e07b84ad1e40c3231e66840ea910e9d66 || cat': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

Обновление 3:

Он не работал в Windows CMD или PowerShell, но работал в Git Bash для Windows.

1 ответ

Решение

Перво-наперво: вам нужен один репо со всей доступной историей.

Сделайте клон репо с недавней историей. Добавьте репо со старой историей в качестве удаленного. Я рекомендую, чтобы этот клон был "зеркалом", и вы закончите, заменив исходное репо этим. Но поочередно вы можете оставить --mirror выключите, и вы закончите, нажав (возможно принудительное нажатие в зависимости от того, какой подход вы используете), все ссылки возвращаются к исходному.

git clone --mirror url/of/current/repo
cd repo
git remote add history url/of/historical/repo
git fetch history

Следующее, что вам нужно сделать, это выяснить, где вы будете склеивать историю. Терминология, описывающая это, немного нечеткая, я думаю... вам нужно найти два коммита, которые соответствуют самой последней ревизии SVN, для которой обе истории имеют коммит. Например, ваш репозиторий SVN содержал версии 1, 2, 3 и 4. Теперь у вас есть

Recent-History Repo

C --- D --- E --- F <--(master)

Old-History Repo

A --- B --- C' --- D'

где A представляет версию 1, B представляет версию 2, C а также C' представляют версию 3 и D а также D' представляют версию 4. E а также F являются работой, созданной после первоначальной миграции. Итак, вы хотите объединить коммиты, чей родитель D (E в этом примере) на D',

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

Переписывая недавнюю историю

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

Если на самом деле есть только одна ветка, то вы можете буквально использовать rebase

git rebase --onto D' D master

(где D а также D' заменяются идентификатором SHA коммитов).

Скорее всего, у вас есть некоторые ветви и слияния в недавней истории; в этом случае операция rebase станет проблемой очень быстро. С другой стороны, вы можете воспользоваться тем, что D имеет то же дерево, что и D' Таким образом, ребаз и повторный родитель более или менее эквивалентны.

Так что вы можете использовать git filter-branch с --parent-filter сделать переписать. Основываясь на примерах в документации на https://git-scm.com/docs/git-filter-branch вы бы сделали что-то вроде

git filter-branch --parent-filter 'test $GIT_COMMIT = D && echo "-p D'" || cat' HEAD

(где опять D а также D' заменяются идентификатором SHA коммитов).

Это создает "резервные" ссылки, которые вам нужно очистить. В конце концов вы получите

A --- B --- C' --- D' --- E' --- F' <--(master)

Это факт, что F был заменен F' что создает необходимость в жестком сокращении (более или менее).

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

Если вы сделали обычный клон, то вам нужно push -f все ссылки на происхождение, и это, вероятно, оставит позади некоторый беспорядок в репо происхождения.

Использование "замены коммита"

Другой вариант не создает жестких сокращений, но он оставляет вас с небольшими головными болями, чтобы иметь дело с навсегда. Ты можешь использовать git replace, В вашем комбинированном репо

git replace `D` `D'`

По умолчанию, когда генерируется вывод журнала или что-то еще, если git находит D, это заменит D' (и его история) в выводе.

Есть несколько известных глюков. Там могут быть неизвестные глюки. И по умолчанию "заменяющие ссылки", которые заставляют все это работать, не являются общими, так что вам нужно сознательно их загружать и извлекать.

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