Я не могу понять поведение git rebase --onto
Я заметил, что два блока следующих команд git имеют различное поведение, и я не понимаю, почему.
У меня есть ветки A
и B
которые расходятся с одним commit
---COMMIT--- (A)
\
--- (B)
Я хочу перебазировать ветку B
на последней A
(и получить коммит на ветке B
)
---COMMIT--- (A)
\
--- (B)
Нет проблем, если я сделаю:
checkout B
rebase A
Но если я сделаю:
checkout B
rebase --onto B A
Это не работает вообще, ничего не происходит. Я не понимаю, почему эти два поведения отличаются.
Gp-клиент Phpstorm использует второй синтаксис, и поэтому мне кажется, что он полностью нарушен, поэтому я спрашиваю об этой проблеме синтаксиса.
Ответы
Ответ 1
TL;DR
Правильный синтаксис для rebase B
поверх A
с помощью git rebase --onto
в вашем случае:
git checkout B
git rebase --onto A B^
или rebase B
поверх A
, начиная с commit, являющегося родителем 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 с двумя аргументами
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
, или rebase HEAD
поверх B
, где старый родитель был E
.
Хирург: git rebase --onto с тремя аргументами
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
после завершения переадресации.
Ответ 2
Это все, что вам нужно знать, чтобы понять --onto
:
git rebase --onto <newparent> <oldparent>
Вы переключаете родителя на фиксацию, но вы не предоставляете sha коммита, а только его текущий (старый) родительский.
Ответ 3
Короче говоря, учитывая:
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
пропускается, потому что она уже применена к цели --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
(см. 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".
Избегайте явных проверок.
По умолчанию аргумент 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.
Ответ 4
Проще говоря, 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
.
Ответ 5
Для 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)
Ответ 6
В другом случае трудно понять git rebase --onto
: когда вы перебазируете коммит в результате симметричного селектора разности (три точки '...
')
Git 2.24 (Q4 2019) лучше справляется с этим делом:
Смотрите коммит 414d924, коммит 4effc5b, коммит c0efb4c, коммит 2b318aa (27 августа 2019 г.) и коммит 793ac7e, совершить 359eceb (25 августа 2019 года) Дентоном Лю (Denton-L
).
При поддержке: Эрик Саншайн (sunshineco
), Джунио С. Хамано (gitster
), Æвар Арнфьорд Бьярмасон (avar
) и Йоханнес Шинделин (dscho
).
Смотрите коммит 6330209, коммит c9efc21 (27 августа 2019 г.) и коммит 4336d36 (25 августа 2019 г.) от Ævar Arnfjörð Bjarmason (avar
).
При поддержке: Эрик Саншайн (sunshineco
), Джунио С. Хамано (gitster
), Æвар Арнфьорд Бьярмасон (avar
) и Йоханнес Шинделин (dscho
).
(Merged by Junio C Hamano -- [TG412] -- in commit 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
, достижимый topic
HEAD: это только G
, но не сам F
.
В этом случае ускоренная перемотка вперед включит F
в перебазированную ветку, что неправильно.
С помощью дополнительной логики мы обнаруживаем, что восходящая и основная база слияния это F
. Так как на F
нет, это означает, что мы не отказываемся от полного набора фиксирует из master..topic
.
Так как мы исключаем некоторые коммиты, ускоренная перемотка вперед не может быть выполнена, поэтому мы правильно возвращаем 0.
Добавьте "-f
" в тестовые случаи, которые не были выполнены в результате этого изменения, потому что они не ожидали быстрой перемотки вперед, так что перебазировка была вынуждена.