Отменить поправку старого коммита, используя другую ветку - PullRequest
1 голос
/ 15 января 2020

Предположим, у меня есть git rebase ветвь b2 моего хранилища, исправляющая более старый коммит (c1). Этот коммит существует, без изменений, в другой ветви b1 (и есть несколько более общих коммитов в обеих ветках после c1, тогда b2 отличается от b1).

Теперь я хочу использовать, чтобы отменить мою поправку к c1 на b2. Как мне это сделать, чтобы история двух ветвей снова стала максимально идентичной?

1 Ответ

2 голосов
/ 15 января 2020

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:

  1. Перечислить некоторый список совершает для копирования. В приведенном выше примере это были 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.

  2. Выберите цель для --onto , Если вы используете --onto, вы выбираете это напрямую. Если нет, вы выбираете его с аргументом upstream. Например, для git rebase master восходящий поток - master, а цель --onto - это фиксация, на которую указывает имя master. Git делает здесь git checkout --detach (или внутренний эквивалент), чтобы отделиться от ветки копируемыми коммитами.

  3. Начать копирование коммитов, как если бы это было git cherry-pick, по одному за раз. Некоторые операции перебазирования буквально используют git cherry-pick, а некоторые - нет.

  4. Когда копирование завершится, переместите оригинальное имя ветви так, чтобы оно указывало на 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 таким образом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...