Я не могу понять поведение git rebase --onto

Я заметил, что два блока следующих команд git имеют различное поведение, и я не понимаю почему.

У меня есть ветки A и B, которые расходятся с одним коммитом

---BRANCH A-------COMMIT1-----
\--BRANCH B--

Я хочу перебазировать ветку B на последней A (и иметь commit1 на ветке B)

---BRANCH A-------COMMIT1-----
                          \--BRANCH B--

Нет проблем, если я сделаю:

checkout B
rebase A

Но если я сделаю:

checkout B
rebase --onto B A

Это не работает вообще, ничего не происходит. Я не понимаю, почему эти два поведения отличаются.

Git-клиент Phpstorm использует второй синтаксис, и поэтому он мне кажется совершенно неработающим, поэтому я спрашиваю о такой проблеме синтаксиса.

5 ответов

ТЛ; др

Правильный синтаксис для перебазирования B на вершине A с помощью git rebase --onto в вашем случае это:

git checkout B
git rebase --onto A B^

или перебазировать B на вершине A начиная с коммита, который является родителем B ссылка с B^ или же B~1,

Если вы заинтересованы в разнице между git rebase <branch> а также git rebase --onto <branch> читать дальше.

Быстрый: git rebase

git rebase <branch> собирается перебазировать ветку, которую вы в данный момент проверили, на которую ссылается HEAD, поверх последнего коммита, доступного из <branch> но не из HEAD,
Это наиболее распространенный случай перебазирования и, возможно, тот, который требует меньше планирования заранее.

          Before                           After
    A---B---C---F---G (branch)        A---B---C---F---G (branch)
             \                                         \
              D---E (HEAD)                              D---E (HEAD)

В этом примере F а также G коммиты, которые достижимы из branch но не из HEAD, поговорка git rebase branch возьму D это первый коммит после точки ветвления и перебазирование его (т. е. изменение его родителя) поверх последнего коммита, достижимого из branch но не из HEAD, то есть G,

Точное: git rebase --onto с 2 аргументами

git rebase --onto позволяет перебазировать, начиная с определенного коммита. Это дает вам точный контроль над тем, что перебазируется и где. Это для сценариев, где вам нужно быть точным.

Например, давайте представим, что нам нужно перебазировать HEAD точно поверх F начиная с E, Мы заинтересованы только в привлечении F в нашу рабочую ветвь, в то же время мы не хотим сохранять D потому что он содержит некоторые несовместимые изменения.

          Before                           After
    A---B---C---F---G (branch)        A---B---C---F---G (branch)
             \                                     \
              D---E---H---I (HEAD)                  E---H---I (HEAD)

В этом случае мы бы сказали git rebase --onto F D, Это означает:

Перебазировать коммит, достижимый из HEAD чей родитель D на вершине F,

Другими словами, измените родителя E от D в F, Синтаксис git rebase --onto затем git rebase --onto <newparent> <oldparent>,

Другой сценарий, в котором это пригодится, - это когда вы хотите быстро удалить некоторые коммиты из текущей ветви без необходимости интерактивной перебазировки:

          Before                       After
    A---B---C---E---F (HEAD)        A---B---F (HEAD)

В этом примере, чтобы удалить C а также E из последовательности вы бы сказали git rebase --onto B E или перебазировать HEAD на вершине B где был старый родитель E,

Хирург: git rebase --onto с 3 аргументами

git rebase --onto может пойти на шаг дальше с точки зрения точности. Фактически, это позволяет вам перебазировать произвольный диапазон коммитов поверх другого.

Вот пример:

          Before                                     After
    A---B---C---F---G (branch)                A---B---C---F---G (branch)
             \                                             \
              D---E---H---I (HEAD)                          E---H (HEAD)

В этом случае мы хотим перебазировать точный диапазон E---H на вершине F, игнорируя где HEAD в настоящее время указывает на. Мы можем сделать это, сказав git rebase --onto F D H, что значит:

Перебазировать диапазон коммитов, чей родитель D вплоть до H на вершине F,

Синтаксис git rebase --onto с диапазоном коммитов становится git rebase --onto <newparent> <oldparent> <until>, Хитрость здесь заключается в том, чтобы помнить, что коммит, на который ссылается <until> входит в ассортимент и станет новым HEAD после завершения ребазирования.

Это все, что вам нужно знать, чтобы понять --onto:

git rebase --onto <newparent> <oldparent>

Вы переключаете родителя в коммите, но не указываете шага коммита, только ша его текущего (старого) родителя.

Чтобы лучше понять разницу между git rebase а также git rebase --onto хорошо знать, каково возможное поведение обеих команд. git rebaseпозволяют нам перемещать наши коммиты поверх выбранной ветки. Как здесь:

git rebase master

и результат:

Before                              After
A---B---C---F---G (master)          A---B---C---F---G (master)
         \                                           \
          D---E (HEAD next-feature)                   D'---E' (HEAD next-feature)

git rebase --ontoболее точен. Это позволяет нам выбрать конкретную фиксацию, с которой мы хотим начать, а также где мы хотим закончить. Как здесь:

git rebase --onto F D

и результат:

Before                                    After
A---B---C---F---G (branch)                A---B---C---F---G (branch)
         \                                             \
          D---E---H---I (HEAD my-branch)                E'---H'---I' (HEAD my-branch)

Чтобы получить более подробную информацию, я рекомендую вам ознакомиться с моей собственной статьей о git rebase --onto overview

Короче говоря, учитывая:

      Before rebase                             After rebase
A---B---C---F---G (branch)                A---B---C---F---G (branch)
         \                                         \   \
          D---E---H---I (HEAD)                      \   E'---H' (HEAD)
                                                     \
                                                      D---E---H---I

git rebase --onto F D H

Который такой же как (потому что --onto принимает один аргумент):

git rebase D H --onto F

Означает перебазирование коммитов в диапазоне (D, H] поверх F. Обратите внимание, что диапазон исключает левую руку. Он эксклюзивен, потому что проще указать 1-й коммит, набрав, например, branch позволить git найти 1-й дивертированный коммит, который приводит к H,

OP чехол

    o---o (A)
     \
      o (B)(HEAD)

git checkout B
git rebase --onto B A

Может быть изменено на одну команду:

git rebase --onto B A B

То, что здесь выглядит как ошибка, это размещение B что означает "переместить некоторые коммиты, которые ведут к ветви B на вершине B, Вопрос в том, что такое "некоторые коммиты". Если вы добавите -i флаг вы увидите, что это единственный коммит, указанный HEAD, Коммит пропускается, потому что он уже применен к --onto цель B и так ничего не происходит.

Эта команда бессмысленна в любом случае, когда имя ветви повторяется так. Это потому, что диапазон коммитов будет некоторым коммитам, которые уже находятся в этой ветке, и во время ребазинга все они будут пропущены.

Дальнейшее объяснение и применимое использование git rebase <upstream> <branch> --onto <newbase>,

git rebase по умолчанию.

git rebase master

Расширяется до:

git rebase --onto master master HEAD
git rebase --onto master master current_branch

Автоматическая проверка после ребаз.

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

git checkout branch
git rebase master

Вы не заметите, что после ребаз git движется branch к недавно перебазированному коммиту и делает git checkout branch, Что интересно, когда вторым аргументом является хеш коммита, а перебазирование имени ветки все еще работает, но ветка не перемещается, так что вы попадаете в "отсоединенную головку", вместо этого вы получаете извлеченную ветку.

Пропустить первичные дивергентные коммиты.

master в --onto взято из 1-го git rebase аргумент, но это может быть любой коммит или ветвь. Таким образом, вы можете ограничить количество коммитов ребаз, взяв последние и оставив первичные дивертированные коммиты.

git rebase --onto master HEAD~

Перебазирует один коммит, указанный HEAD,

Избегайте явных проверок.

По умолчанию HEAD или же current_branch контекстуально взяты с того места, где вы находитесь. Именно поэтому большинство людей обращаются к ветке, которую они хотят перебазировать. Но когда аргумент задан явно, вам не нужно проверять его перед перебазированием, чтобы передать его неявным образом.

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

Формулировка Git здесь немного сбивает с толку. Может помочь, если вы сделаете вид, что команда выглядит так:

git rebase --onto=<new_base> <old_base> [<branch>]

Если мы на branch теперь его можно опустить:

git rebase --onto=<new_base> <old_base>

И если new_base такой же как old_base, мы можем опустить --onto параметр:

git rebase <new_old_base>

Это может показаться странным: как вы перебазируете, если старая база совпадает с новой? Но подумайте об этом так, если у вас есть функциональная ветка foo, это уже (вероятно) основано на какой-то фиксации в вашем mainветка. "Перебазируя", мы делаем только коммит, на котором он основан на более актуальном.

(По факту, <old_base> это то, что мы сравниваем branchпротив. Если это ветвь, то git ищет общего предка (см. Также --fork-point); если это коммит в текущей ветке, используются коммиты после этого; если это коммит, у которого нет общего предка с текущей веткой, используются все коммиты из текущей ветки. <new_base>также может быть фиксацией. Так, например, git rebase --onto HEAD~ HEAD будет совершать коммиты между старой базой HEAD и текущие HEAD и поместите их поверх HEAD~, эффективно удаляя последнюю фиксацию.)

Проще говоря, git rebase --onto выбирает диапазон коммитов и перебазирует их на коммит, указанный в качестве параметра.

Прочитайте справочные страницы для git rebaseИщите "на". Примеры очень хороши:

example of --onto option is to rebase part of a branch. If we have the following situation:

                                   H---I---J topicB
                                  /
                         E---F---G  topicA
                        /
           A---B---C---D  master

   then the command

       git rebase --onto master topicA topicB

   would result in:

                        H'--I'--J'  topicB
                       /
                       | E---F---G  topicA
                       |/
           A---B---C---D  master

В этом случае вы говорите git перебазировать коммиты из topicA в topicB на вершине master,

За onto вам нужно две дополнительные ветви. С помощью этой команды вы можете применить коммиты из branchB которые основаны на branchA на другую ветку, например master, В образце ниже branchB основывается на branchA и вы хотите применить изменения branchB на master без применения изменений branchA,

o---o (master)
     \
      o---o---o---o (branchA)
                   \
                    o---o (branchB)

с помощью команд:

checkout master
rebase --onto branchA branchB

вы получите следующую иерархию коммитов.

      o'---o' (branchB)
     /
o---o (master)
     \
      o---o---o---o (branchA)

Есть еще один случай, когда git rebase --onto трудно понять: когда вы перебазируете на фиксацию в результате симметричного селектора различий (три точки '...')

Git 2.24 (четвертый квартал 2019 г.) лучше справляется с этой задачей:

См. Фиксацию 414d924, фиксацию 4effc5b, фиксацию c0efb4c, фиксацию 2b318aa (27 августа 2019 г.) и фиксацию 793ac7e, фиксацию 359eceb (25 августа 2019 г.) Дентон Лю (Denton-L).
Помощник: Эрик Саншайн (sunshineco), Юнио С. Хамано (gitster), à † var Arnfjrà ° Bjarmason (avar) и Йоханнес Шинделин (dscho).
См. Коммит 6330209, коммит c9efc21 (27 августа 2019 г.) и коммит 4336d36 (25 августа 2019 г.) от † var Arnfjörà ° Bjarmason (avar).
Помощник: Эрик Саншайн (sunshineco), Юнио С. Хамано (gitster), à † var Arnfjrà ° Bjarmason (avar) и Йоханнес Шинделин (dscho).
(Слияние Junio ​​C Hamano -gitster- в коммите 640f9cd, 30 сен 2019)

rebase: перемотка вперед --onto в большем количестве случаев

Раньше, когда у нас был следующий график,

A---B---C (master)
     \
      D (side)

Бег 'git rebase --onto master... master side'приведет к D быть всегда перебазированным, несмотря ни на что.

На этом этапе прочтите "В чем разница между двумя точками"..'и тройная точка "..."в диапазонах фиксации различий Git? "

Вот: "master... " относится к master...HEAD, который B: HEAD - это сторона HEAD (в настоящее время проверено): вы выполняете переустановку на B.
Что ты ребазируешь? Любая фиксация не в мастере и доступная изside ветка: есть только одна фиксация, подходящая под это описание: D... который уже поверх B!

Опять же, до Git 2.24 такой rebase --onto приведет к D быть всегда перебазированным, несмотря ни на что.

Однако желаемое поведение состоит в том, что rebase должна заметить, что это ускоренная перемотка, и сделать это вместо этого.

Это похоже на rebase --onto B A ОП, который ничего не сделал.

Добавить обнаружение в can_fast_forwardтак что этот случай может быть обнаружен и будет выполнена перемотка вперед.
Прежде всего, перепишите функцию, чтобы использовать gotos, что упростит логику.
Далее, поскольку

options.upstream &&
!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)

условия были сняты в cmd_rebase, мы повторно вводим замену в can_fast_forward.
В частности, проверка баз слиянияupstream а также head исправляет неудачный случай в t3416.

Сокращенный график для t3416 выглядит следующим образом:

        F---G topic
       /
  A---B---C---D---E master

и неудачная команда была

git rebase --onto master...topic F topic

Раньше Git видел, что существует одна база слияния (C, Результат master...topic), а слияние и включение были одинаковыми, поэтому он неправильно возвращал 1, указывая на то, что мы можем перемотать вперед. Это приведет к тому, что перемещенный граф будет 'ABCFG'когда мы ждали'ABCG'.

А rebase --onto C F topicозначает любую фиксацию после F, достижимый topic ГОЛОВА: то есть G только не Fсам.
Перемотка вперед в этом случае будет включатьF в перебазированной ветке, что не так.

С помощью дополнительной логики мы обнаруживаем, что базой слияния восходящего потока и головы является F. Поскольку он неF, это означает, что мы не перебазируем полный набор коммитов из master..topic.
Поскольку мы исключаем некоторые коммиты, перемотка вперед не может быть выполнена, поэтому мы правильно возвращаем 0.

Добавлять '-f'для проверки случаев, которые не удались в результате этого изменения, потому что они не ожидали быстрой перемотки вперед, чтобы принудительно выполнить перебазирование.

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