Что такое «ярлык» при интерактивной перебазировке в Git - PullRequest
4 голосов
/ 08 апреля 2020

При интерактивной перебазировке Git откроет редактор с командами, которые можно использовать. Три из этих команд имеют отношение к тому, что называется label.

# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.

Что это такое label и как его можно использовать?

1 Ответ

3 голосов
/ 08 апреля 2020

комментарий Чепнера совершенно прав: метки - это то, как работает git rebase --rebase-merges. Если вы не используете --rebase-merges, вам больше ничего не нужно знать.

Перебазирование как концепция

Перебазирование в целом произведений путем копирования коммитов, как если по git cherry-pick. Это потому, что невозможно изменить любой существующий коммит. Когда мы используем git rebase, то, что мы хотим, в конце концов, это какой-то набор изменений - незаметных или вопиющих решений для нас - для некоторых существующих коммитов.

Это технически невозможно вообще, но если мы посмотрим, как мы (люди) используем Git и находим коммиты, в конце концов это легко. Мы не меняем коммиты вообще! Вместо этого мы копируем их в новые и улучшенные коммиты, затем используем новые и забываем (или оставляем) старые.

Поиск коммитов: рисование графика

То, как мы используем Git и находим коммиты, заключается в том, чтобы полагаться на тот факт, что каждый коммит записывает идентификатор ha sh своего непосредственного предшественника или parent commit. Это означает, что коммиты формируют обратные цепочки:

... <-F <-G <-H   <--branch

Имя ветви branch содержит фактический необработанный идентификатор J last совершить в цепи. В этом случае, каким бы ни был фактический идентификатор ha sh ID, мы рисуем его с буквой H в качестве замены.

Commit H содержит в качестве части метаданных необработанные ha sh ID более раннего коммита, который мы называем G. Мы говорим, что H указывает на G, а branch указывает на H.

Commit G, конечно, указывает на его parent F, который указывает еще дальше. Поэтому, когда мы используем Git, мы начинаем с имени ветки , которое запоминает для нас и Git фиксацию last в цепочке. Оттуда мы Git работаем в обратном направлении, по одному коммиту за раз, по цепочке.

коммит слияния - это просто коммит как минимум с двумя родителями, вместо обычного , Таким образом, коммит слияния M выглядит следующим образом:

...--J
      \
       M   <-- somebranch
      /
...--L

, где *1070* и L являются двумя родителями слияния. Как правило (хотя и не обязательно), сначала разветвляется история, затем сливается:

          I--J
         /    \
...--G--H      M--N--...
         \    /
          K--L

, и мы можем назвать I-J и K-L ответвления ответвлением , или мы можем рассматривайте все, вплоть до M и / или N, как одну ветвь - в конце концов, должно быть какое-то имя ветви, указывающее на некоторую фиксацию вправо. Как еще мы нашли коммит M во-первых?

(Мы можем, если захотим, добавить имена веток, указывающие на любой коммит в любое время. Добавление имени ветки означает что все коммиты теперь находятся в дополнительной ветке, сверх той ветки, в которой они находились ранее. Удаление имени удаляет эту ветвь из набора ветвей, содержащих эти коммиты.)

Пройдя назад через коммит слияния хитроумно: Git должен начать смотреть на обе вилки, здесь обе вилки I-J и K-L. Git делает это внутренне с git log и git rev-list, используя приоритетную очередь , хотя мы не будем go вдаваться в подробности здесь.

В любом случае, ключ здесь в том, что поскольку коммиты хранят родительские га sh идентификаторы, а стрелки все указывают назад, коммиты образуют Directed Acycli c График или DAG. Мы - и Git - находим коммит, используя имя ветки, которое по определению указывает на last коммит в некоторой части DAG. Оттуда мы Git идем задом наперед.

Перебазируем в двух словах

Предположим, мы должны были взять некоторую существующую простую цепочку коммитов, такую ​​как A-B-C здесь:

...--o--o--*--o--o   <-- master
            \
             A--B--C   <-- branch (HEAD)

и копируют их в новые коммиты следующим образом:

                   A'-B'-C'  <-- HEAD
                  /
...--o--o--*--o--o   <-- master
            \
             A--B--C   <-- branch

Используется режим Git detached HEAD , где HEAD указывает непосредственно совершить. Таким образом, имя branch по-прежнему находит оригинальные коммиты, а HEAD, отсоединенный сейчас, находит новые копии. Не слишком заботясь о том, что именно отличается от в новых копиях, что если мы теперь заставим Git на переместить имя branch, чтобы оно указывает не на C, а на C' вместо этого? То есть, с точки зрения чертежа, мы сделаем это:

                   A'-B'-C'  <-- branch (HEAD)
                  /
...--o--o--*--o--@   <-- master
            \
             A--B--C

Переместив branch, мы также повторно присоединим наш HEAD, чтобы мы могли вернуться в обычную повседневную жизнь Git режим, а не в середине перебаз. И теперь, когда мы будем искать коммиты, мы найдем новые копии, а не оригиналы. Новые копии новые: Они имеют разные идентификаторы ha sh. Если бы мы на самом деле запомнили ха sh идентификаторы, мы бы увидели это ... но мы находим коммиты, начиная с имени ветви и работая в обратном направлении, и когда мы это сделаем, мы полностью отказались от оригиналов и видим только новые копии.

Так вот, как работает перебазировка, в любом случае, при отсутствии слияний. Git:

  • перечисляет некоторые коммиты для копирования;
  • отсоединяет HEAD от места, куда должны идти копии;
  • копирует коммиты, как если бы git cherry-pick (часто на самом деле с git cherry-pick), по одному за раз; а затем
  • перемещает имя ветви и повторно присоединяет HEAD.

(Здесь много угловых случаев, таких как: что произойдет, если вы начнете с отделенная ГОЛОВА, и что происходит с конфликтами слияния. Мы просто проигнорируем все это.)

Немного о черри

Выше я сказал:

Не беспокоясь о том, что именно отличается в новых копиях ...

Что именно отличается от других? Ну, сам коммит содержит снимок всех ваших файлов, плюс метаданные: имя и адрес электронной почты того, кто сделал коммит, сообщение в журнале и т. Д., И все это важно для Git DAG, родительский га sh ID этого коммита. Поскольку новые копии следуют после другой точки - старая база была *, а новая база @ - очевидно, что родительские идентификаторы ha sh должны были измениться.

Учитывая, что добавление нового коммита работает, устанавливая родительский объект нового коммита в текущий коммит, обновленные родительские элементы происходят автоматически во время процесса копирования, когда мы копируем коммиты, по одному коммиту за раз. То есть сначала мы проверяем коммит @, затем копируем A в A'. Родитель A' - @, автоматически. Затем мы копируем B в B' и родительский элемент B' автоматически равен A'. Так что здесь нет настоящих магов c: это просто основы c, каждый день Git.

Снимки, вероятно, тоже разные, и вот тут действительно появляется git cherry-pick. Cherry- Пик должен просматривать каждый коммит как некоторый набор изменений . Чтобы просмотреть коммит как изменение, мы должны сравнить снимок коммита с моментальным снимком родителя коммита.

То есть, учитывая:

...--G--H--...

мы можем увидеть, что изменил в H, сначала выделив G во временную область, затем выделив H во временную область, а затем сравнив две временные области. Для файлов, которые одинаковы, мы вообще ничего не говорим; для файлов, которые отличаются, мы создаем список сравнения. Это говорит нам о том, что изменилось в H.

Так что для git cherry-pick, чтобы скопировать коммит, нужно просто превратить коммит в изменения. Это требует взгляда на родителя коммита. Для коммитов A-B-C это не проблема: родитель A равен *; родитель B является A; и родитель C является B. Git может найти первый набор изменений - * против A - и применить изменения к снимку в @, и сделать A' таким образом. Затем он находит изменения A -vs- B и применяет их к A' для создания B и т. Д.

Это прекрасно работает для обычных однополых коммитов. вообще не работает для коммитов слияния.

Копирование слияния невозможно, поэтому rebase не пытается

Предположим, у нас есть набор коммитов с пузырем слияния, и этот набор коммитов сам может быть перебазирован:

           I--J
          /    \
         H      M   <-- feature (HEAD)
        / \    /
       /   K--L
      /
...--G-------N--O--P   <-- mainline

Мы могли бы git rebase коммит feature поверх коммита P сейчас же. Если мы это сделаем, результат по умолчанию будет либо:

...--G-------N--O--P   <-- mainline
                    \
                     H'-I'-J'-K'-L'  <-- feature (HEAD)

, либо:

...--G-------N--O--P   <-- mainline
                    \
                     H'-K'-L'-I'-J'  <-- feature (HEAD)

(я не стал рисовать в оставленных коммитах, чтобы сэкономить место .)

До 1259 * можно выбрать заказ на I-J и K-L во время процесса перебазирования списком-копированием-копированием. Коммит M, слияние, просто отбрасывается: две ветви, которые привели к коммиту слияния M, сведены в одну простую линейную цепочку. Это избавляет от необходимости копировать коммит M, за счет того, что иногда он не может копировать коммиты очень хорошо (с большим количеством конфликтов слияния) и, конечно, уничтожает наш приятный маленький пузырь слияния, если мы хотим его сохранить.

Cherry-pick не может скопировать слияние ...

Хотя вы можете запустить git cherry-pick на коммите слияния, результирующий коммит является обычным, без слияния совершить. Кроме того, вы должны указать Git, какого из родителей использовать. Выбор Cherry по своей сути должен отличать родителей коммитов от коммитов, но слияние имеет двух родителей, а Git просто не знает, какой из двух использовать. Вы должны сказать ему, какой из них ... и затем он копирует изменения, найденные в diff, а это не совсем то, о чем git merge.

... так что перебазировать и сохранить merges, git rebase повторно выполняет слияния

Для всего этого git rebase это означает, что для "сохранения" слияния Git необходимо запустить git merge себя.

То есть предположим, что нам дано:

           I--J
          /    \
         H      M   <-- feature (HEAD)
        / \    /
       /   K--L
      /
...--G-------N--O--P   <-- mainline

, и мы хотим достичь:

                         I'-J'
                        /    \
                       H'     M'  <-- feature (HEAD)
                      / \    /
                     /   K'-L'
                    /
...--G-------N--O--P   <-- mainline

Git rebease может сделать это , но для этого необходимо:

  • скопировать H в H' и опустить маркер здесь;
  • выбрать один из I или K для копирования в I' или K', затем скопируйте J или L далее; скажем, мы выбираем I-J, чтобы сделать;
  • перетащить маркер, указывающий на J';
  • git checkout H' копия, сделанная ранее с использованием маркера;
  • скопируйте K и L сейчас, в K' и L' и поместите здесь маркер

, чтобы в качестве нашего промежуточного результата на данный момент мы получили:

                         I'-J'   <-- marker2
                        /
                       H'  <-- marker1
                      / \
                     /   K'-L'   <-- marker3
                    /
...--G-------N--O--P   <-- mainline

Git может теперь git checkout совершить J', используя маркер 2, выполнить git merge для фиксации L', используя маркер 3, и, таким образом, создать коммит M', новое слияние, которое использовало H' в качестве его базы слияния и J' и L' в качестве двух его коммитов с ветвлением.

Как только слияние завершено, выполняется rebase-as-a-целое и Git можно удалить маркеры и восстановить имя ветви feature как обычно.

Если мы немного сообразительны, мы можем иногда позволить HEAD выступать в качестве одного из трех маркеров, но это более просто просто бросайте маркеры каждый раз. Я не уверен, какой метод на самом деле использует git rebase --rebase-merges.

Команды label, reset и merge создают и используют различные маркеры. Команда merge требует, чтобы HEAD указывал на коммит, который будет первым родителем полученного слияния (поскольку git merge работает таким образом). Интересно, что синтаксис предполагает, что слияния осьминога здесь запрещены: они должны просто работать и, следовательно, должны быть разрешены.

(-C в команде merge может использовать необработанный идентификатор ha sh из исходный коммит слияния, так как он всегда неизменен. Метки, которые вы увидите, если вы используете --rebase-merges с набором коммитов, который содержит слияния, генерируются Git из сообщений фиксации, и до недавнего времени не было ошибка здесь.)

Примечание: слияния зла и слияния --ours не выживают

Когда Git повторно выполняет слияние, он просто использует обычный механизм слияния. Git не знает ни о каких флагах, использованных во время слияния, или о каких-либо изменениях, представленных как «злое слияние». Так что -X ours, или --ours, или дополнительные изменения просто теряются во время такого рода перебазировки. Конечно, если при слиянии возникают конфликты слияния, у вас есть возможность повторно вставить изменения злого слияния или полностью повторить слияние, как вам нравится.

См. Также Злые слияния в git?

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