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

169

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

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

---COMMIT--- (A)
\
 --- (B)

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

---COMMIT--- (A)
         \
          --- (B)

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

checkout B
rebase A

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

checkout B
rebase --onto B A

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

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

Xmanoux
источник

Ответы:

412

ТЛ; др

Правильный синтаксис для перебазирования 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, что означает:

Rebase диапазон коммитов , чей родитель Dдо Hсверху F.

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

Энрико Кампидоглио
источник
1
Хороший ответ. Просто небольшое дополнение для общего случая: <oldparent>имя ломается, если две части диапазона находятся в разных ветвях. Как правило, это: «Включать каждый коммит, который доступен, <until>но исключать каждый коммит, который доступен <oldparent>».
musiKk
50
git rebase --onto <newparent> <oldparent>лучшее объяснение --onto поведения, которое я видел!
ronkot
4
Спасибо! Я немного боролся с этим --ontoвариантом, но это сделало его кристально ясным! Я даже не понимаю, как я не мог понять это раньше: D Спасибо за отличный "учебник" :-)
grongor
3
Хотя этот ответ превосходен, я чувствую, что он не охватывает все возможные случаи. Последняя синтаксическая форма также может быть использована для выражения более тонкого типа перебазирования. Исходя из примера в Pro Git (2-е изд.), D не обязательно должен быть предком H. Вместо этого D и H также могут быть коммитами с общим предком - в этом случае Git выяснит их общего предка. и переиграйте от этого предка до H на F.
Пастафарианец
1
Это было полезно. Страница руководства не объясняет аргументы вообще.
a544jh
61

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

git rebase --onto <newparent> <oldparent>

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

разрывать
источник
4
Коротко и просто. На самом деле, осознание того, что я должен предоставить родительский коммит того, который я хочу перебазировать вместо того коммита, заняло у меня больше всего времени.
Антониоссс
1
Важным моментом является то, что вы выбираете дочерний элемент oldparent из текущей ветви, потому что один коммит может быть родительским для многих коммитов, но когда вы ограничиваете себя текущей ветвью, то коммит может быть родительским только для одного коммита. Другими словами, родительское семейство отношений уникально для ветви, но не обязательно, если вы не указали ветку.
Трисмегистос,
2
На заметку: вам нужно быть в ветке или добавить имя ветки в качестве 3-го параметра git rebase --onto <newparent> <oldparent> <feature-branch>
Джейсон Портной
1
Этот ответ поразителен, прямо в точку, необходимую в этой теме
Джон Калвинер,
13

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

      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-й дивергентный коммит, branchт. Е. Который Dведет к 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

Вы не заметите, что после rebase gitпереходит branchк последнему перебазированному коммиту и делает git checkout branch(см. git reflogИсторию). Что интересно, когда вторым аргументом является хеш коммита, а перебазирование имени ветки все еще работает, но ветка не перемещается, поэтому вы попадаете в «отсоединенную головку» вместо того, чтобы быть извлеченными в перемещенную ветвь.

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

masterВ --ontoвзят из 1 - го git rebaseаргумента.

                   git rebase master
                              /    \
         git rebase --onto master master

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

git rebase --onto master HEAD~
git rebase --onto master HEAD~ HEAD  # Expanded.

Будет ли перебазироваться одну фиксацию указываемого HEADдо masterи в конечном итоге в «отдельных РУКОВОДИТЕЛЯ».

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

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

(branch) $ git rebase master
(branch) $ git rebase master branch  # Expanded.
(branch) $ git rebase master $(git rev-parse --abbrev-ref HEAD)  # Kind of what git does.

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

(master) $ git rebase master branch
(branch) $ # Rebased. Notice checkout.
WloHu
источник
8

Проще говоря, 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

В этом случае вы говорите мерзавец перебазироваться совершающими от topicAк topicBв верхней части master.

Готье
источник
8

Чтобы лучше понять разницу между 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

womanonrails
источник
@Makyen Конечно, я буду помнить это в будущем :)
womanonrails
Итак, мы можем читать git rebase --onto F Dкак set child родительского элемента D как F , не так ли?
Prihex
2

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

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

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

checkout branchB
rebase --onto master branchA 

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

      o'---o' (branchB)
     /
o---o (master)
     \
      o---o---o---o (branchA)
Майкл Майреггер
источник
1
Не могли бы вы объяснить немного больше, если мы хотим сделать ребаз на мастер, почему он становится текущей веткой? Если бы вы сделали rebase --onto branchA branchB, это поместило бы всю главную ветвь в голову ветви A?
Полимераза
8
не должно ли это быть checkout branchB: rebase --onto master branchA?
goldenratio
4
Почему за это проголосовали? это не делает то, что говорит, что делает.
De Novo
Я отредактировал и исправил ответ, чтобы людям не нужно было сначала ломать свои ветки репо, а сразу после этого приходить и читать комментарии ... 🙄
Kamafeather
0

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

Git 2.24 (Q4 2019) лучше справляется с этим делом:

См. Коммит 414d924 , коммит 4effc5b , коммит c0efb4c , коммит 2b318aa (27 августа 2019 г.) и коммит 793ac7e , коммит 359eceb (25 августа 2019 г.) от Denton Liu ( Denton-L) .
При поддержке: Эрик Саншайн ( sunshineco) , Хунио С. Хамано ( gitster) , Эвар Арнфьорд Бьярмасон ( avar) и Йоханнес Шинделин ( dscho) .
См. Коммит 6330209 , коммит c9efc21 (27 августа 2019 г.) и коммит 4336d36 (25 авг. 2019 г.). Автор Ævar Arnfjörð Bjarmason ( avar),
Помогают: Эрик Саншайн ( sunshineco) , Хунио С. Хамано ( gitster) , Эвар Арнфьорд Бьярмасон ( avar) и Йоханнес Шинделин ( dscho) .
(Слиты Junio C Hamano - gitster- в фиксации 640f9cd , 30 Sep 2019)

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

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

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

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

На этом этапе, следующим образом: « Каковы различия между двойной точкой„ ..“и тройной точкой» ...«в Git дифференциалов совершающих диапазоны? »

https://sphinx.mythic-beasts.com/~mark/git-diff-help.png

Здесь: « master...» означает master...HEAD, что B: HEAD является боковым HEAD (в настоящее время проверено): вы переходите на B.
Что ты упрекаешь? Любой коммит не в master, а достижимый из 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 , достижимый topicHEAD: Gтолько он, а не Fсам.
Ускоренная перемотка в этом случае будет включать Fв перебазированную ветку, что неправильно.

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

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

VonC
источник