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