TL; DR
Использование git rebase --onto
. Укажите цель с аргументом --onto
и укажите коммиты , а не для копирования с обычным аргументом upstream
. Отсюда трудно сказать, какими именно должны быть эти аргументы; см. подробное обсуждение ниже.
Long
Это требование:
... чтобы история двух ветвей снова стала максимально идентичной
означает, что становится важным, чтобы вы знали, что git rebase
работает путем копирования коммитов. Для краткого изложения идей отметим, что:
- каждый коммит имеет уникальный идентификатор ha sh;
- каждый коммит содержит снимок всех файлов; и
- каждый коммит также содержит некоторые метаданные.
Метаданные в коммите включают автора, коммиттера и сообщение журнала, но также - ключ ко всему этому процессу - ха sh Идентификатор родителя коммита.
Чтобы превратить коммит в набор изменений, т. Е. Чтобы узнать, что кто-то изменил в любом данном коммите, мы имеем Git сравнить приверженность своему родителю. В коммите хранится идентификатор sh его родителя, чтобы git show
или git log -p
могли найти его самостоятельно.
Между тем, имя ветви , как b2
, просто содержит га sh ID последнего коммита в ветке. Таким образом, мы можем нарисовать их - коммиты и имена веток - вот так:
... <-c1 <-c2 <-c3 <--b2
, где каждый c i является фактическим коммитом, представленным его ха sh, а стрелка, выходящая из чего-то, означает указывает на: имя ветви b2
указывает на фиксацию c3
, c3
указывает на c2
, c2
указывает на c1
и т. Д.
Ничто в любом коммите не может измениться, поэтому мы можем нарисовать внутренние стрелки, от коммита до коммита, как соединительные линии вместо стрелок, если мы помним, что они являются частью ребенка и указывают назад к родителю. Это позволяет нам рисовать в грубой текстовой графике более одной ветви. Я буду использовать X1
и выше, так как это всего лишь пример, не совсем связанный с вашей отправной точкой:
...--X1 <-- branch1
\
X2--X3 <-- branch2
Если branch1
получает больше коммитов, новейший в конечном итоге приводит к X1
:
...--X1--X4--X5 <-- b1
\
X2--X3 <-- b2
Теперь вернемся к вашей первоначальной настройке:
Предположим, я git rebase'd ветку b2
моего репозитория, исправляя более старую фиксацию ( c1
). Этот коммит существует, без изменений, в другой ветке b1
(и есть несколько более общих коммитов в обеих ветвях после c1
, тогда b2
отличается от b1
).
I ' Я нарисую как можно более точную картину исходной установки:
c4--c5 <-- b1
/
...--c0--c1--c2--c3
\
c6--c7 <-- b2
(я заполняю недостающие детали догадками, хотя в итоге догадки не должны иметь большого значения.)
Когда вы запустили git rebase -i <start-point>
на b2
и изменили / отредактировали коммит c1
, Git пришлось скопировать c1
в какой-то новый и другой коммит. Новый коммит имеет автора и сообщение журнала, как обычно, которые изначально были настроены для копирования из c1
, но у него есть новый и другой идентификатор ha sh и, возможно, другой снимок и / или другое сообщение журнала (или даже другой автор), в зависимости от того, что именно вы изменили. Давайте назовем новую копию c1'
, чтобы отделить их друг от друга:
c4--c5 <-- b1
/
...--c0--c1--c2--c3
\
c1' [copying in progress]
Поскольку c1
было скопировано в c1'
, Git теперь было * вынуждено копировать c2
на новый c2'
:
c4--c5 <-- b1
/
...--c0--c1--c2--c3
\
c1'-c2' [copying in progress]
Разница от c1'
до c2'
равна разнице от c2
до c1
. То есть, если мы сравним c2
с его родителем c1
, мы получим некоторый набор изменений. Если мы сравним c2'
с c1'
, мы получим те же изменения , даже если c1
имеет содержимое, отличное от c1'
.
Теперь, когда c2
заменяется на c2'
, это заставляет Git копировать c3
в c3'
. Это заставляет Git копировать c6
и c7
тоже. Окончательный результат копирования c7'
и git rebase
заканчивается нажатием name b2
over, так что конечный результат:
c4--c5 <-- b1
/
...--c0--c1--c2--c3--c6--c7 [old b2, now abandoned]
\
c1'-c2'-c3'-c6'-c7' <-- b2 (HEAD)
Теперь я хочу по существу отменить мою поправку к c1 на b2. Как мне это сделать, чтобы история двух ветвей снова стала максимально идентичной?
С тех пор вы могли добавить еще больше коммитов:
c4--c5 <-- b1
/
...--c0--c1--c2--c3--c6--c7 [old b2, now abandoned]
\
c1'-c2'-c3'-c6'-c7'-c8--c9 <-- b2 (HEAD)
Что вы могли бы сделать хотел бы в конечном итоге это:
c4--c5 <-- b1
/
...--c0--c1--c2--c3--c6--c7--c8'-c9' <-- b2 (HEAD)
\
c1'-c2'-c3'-c6'-c7'-c8--c9 [abandoned]
Это, вероятно, нормально (и обычно намного проще делать ), если вы в конечном итоге:
c4--c5 <-- b1
/
...--c0--c1--c2--c3--c6"-c7"-c8'-c9' <-- b2 (HEAD)
с ни один из оставленных оставленных коммитов не получен. 1
Чтобы получить любой из них, вы хотите:
- сказать Git, чтобы скопировать хотя бы
c8
и c9
(при условии, что они существуют) и - сообщают Git , а не копируют
c1'
через c3'
, и это означает используя git rebase --onto
, так что вы можете разделить две инструкции, которые обычно объединяет git rebase
.
То есть то, что делает git rebase
:
Перечислить некоторый список совершает для копирования. В приведенном выше примере это были c1
до c7
. Если вы используете git rebase -i
, список переходит в лист изменяемых инструкций, используя слово pick
вместе с ха sh (сокращенный) каждого коммита и строку темы из сообщения журнала коммитов.
Обычно, merge коммиты - те, у кого два или более родителей - сразу же исключаются из списка. (Есть некоторые режимы, которые не отклоняют их; они более сложные, и мы их здесь проигнорируем.)
Какие коммиты перечислены в списке? Это из вашего аргумента upstream
: коммиты, которые должны быть скопированы - это те, которые достижимы с HEAD
путем перехода назад, коммит к коммиту: c7
приводит к c6
, который возвращается к c3
, который возвращается к c2
и так далее. Но из этого списка - который может go вернуться очень далеко - мы удалим любые коммиты, достижимые из аргумента upstream
. Так что если upstream
является идентификатором ha sh c0
, мы уберем c0
* из списка, а также любой коммит до c0
. Это означает, что список начинается с c1
и заканчивается c7
, пропуская недостижимые c4
и c5
.
Выберите цель для --onto
, Если вы используете --onto
, вы выбираете это напрямую. Если нет, вы выбираете его с аргументом upstream
. Например, для git rebase master
восходящий поток - master
, а цель --onto
- это фиксация, на которую указывает имя master
. Git делает здесь git checkout --detach
(или внутренний эквивалент), чтобы отделиться от ветки копируемыми коммитами.
Начать копирование коммитов, как если бы это было git cherry-pick
, по одному за раз. Некоторые операции перебазирования буквально используют git cherry-pick
, а некоторые - нет.
Когда копирование завершится, переместите оригинальное имя ветви так, чтобы оно указывало на HEAD
, который является последним скопированным коммитом, или - если мы вообще не копировали коммиты - цель --onto
. Затем вернитесь к этой ветви, как если бы git checkout <em>name</em>
.
Использование --onto
позволяет изменить аргумент upstream
, не устанавливая при этом также цель rebase.
Итак, если вы хотите скопировать просто c8
и c9
, вы можете проверить путем проверки, что цель --onto
равна c7
, и что первый коммит вы не хотите скопировать c7
. Это не потребует git rebase --onto
в конце концов. Если у вас есть идентификатор ha sh, равный c7
, вы можете, например, выполнить:
git rebase <hash-of-c7>
в филиале b2
. Но чтобы найти оригинал c7
, перед первой перебазкой вам нужно будет рыться в перелогах. Это может быть трудно, так как рефлоги, как правило, содержат много движения, и как только вы скопировали один коммит один раз, вы, возможно, скопировали его много раз. 2
Так что мы можем вместо этого просто позвольте Git скопировать c6'
и c7'
снова. Мы установим upstream
на c3'
в качестве первого коммита , а не для копирования, и установим c3
в качестве цели --onto
:
git rebase --onto <hash-of-c3> <hash-of-c3'>
например. Git будет перечислять коммиты, возвращаясь от HEAD
(c9
) до достижения c3'
, которое вы сказали не копировать (и ничего раньше). Это будет перечислять c6'
, c7'
, c8
и c9
в качестве коммитов для копирования. Копии go после c3
(--onto
). Обратите внимание, что коммиты c3
и c3'
хорошо видны как в истории, так и в сыром ASCII-графике, который Git dr aws, который вы можете просмотреть с помощью:
git log --graph --oneline b1 b2
, так что это даст вам ваш га sh Идентификаторы для --onto
и вышестоящих аргументов.
1 c9
и его заброшенная история все еще там, в вашем Git хранилище, если вы хочу их позже. Их можно найти через Git s reflogs . Записи reflog сохраняются только в течение некоторого периода времени. По умолчанию от 1 до 3 месяцев срок действия записей reflog истекает и они удаляются. Как только это произойдет, сами оставленные коммиты также можно будет удалить по-настоящему, после чего вы не сможете вернуть их, по крайней мере, не через свой собственный Git.
(Подробности reflogs и explog expiration немного сложны, но не все, что здесь уместно.)
2 Чтобы просмотреть reflog для ветви b2
, запустите git reflog b2
. Если вам повезет, копий и случайных движений не много, и вы сможете найти c7
таким образом.