Это не совсем правильно.
Здесь есть несколько камней преткновения. Во-первых, может не быть общего предка или их может быть более одного. (Это довольно незначительно: это просто означает, что все фиксации копируются или все общие предки опускаются.) Во-вторых, мы можем не проверять фиксацию, указанную B
здесь. В-третьих, некоторые коммиты могут быть опущены, и в зависимости от формы перебазирования это может немного усложниться. Наконец, копирование происходит в режиме detached HEAD, а затем rebase копирует имя ветки (как если бы через git checkout -B
или git switch -C
, или git branch -f
, а затем git checkout
или git switch
).
Фактические фиксации, которые должны быть перечислены, зависят от аргумента upstream
для перебазирования, который можно указать следующим образом:
git rebase --onto <target> <upstream>
or:
git rebase <upstream>
Если параметр --onto <target>
не указан, target
совпадает с upstream
. Это коммит, который проверяется (в режиме detached-HEAD).
Документация по перебазированию сначала предлагает перечислить коммиты в:
upstream..HEAD
(конечно, перед этапом оформления заказа, так как он перемещается на HEAD
). Это не совсем так, поэтому текущая документация сразу немного исправляет себя:
Это тот же набор коммитов, который будет показан git log <upstream>..HEAD
; или по git log 'fork_point'..HEAD
, если активен --fork-point
(см. описание --fork-point
ниже); или по git log HEAD
, если указана опция --root
.
Чуть позже он добавляет это:
Обратите внимание, что любые коммиты в HEAD, которые вносят те же текстовые изменения, что и коммиты в HEAD..‹upstream›, опускаются (т. е. уже принятый патч с другим сообщением коммита или отметкой времени будет пропущен).
Лишь намного позже в нем упоминается, что коммиты слияния полностью опущены, если вы не используете параметр -p
(теперь устаревший) или параметр -r
(новый в Git 2.18).
Однако на самом деле здесь происходит то, что Git использует синтаксис с тремя точками для git rev-list
с режимом --left-right
.1 Базовый синтаксис с тремя точками:
git rev-list upstream...HEAD
перечисляет все коммиты, которые доступны из любого коммита, но не доступны из обоих коммитов. С точки зрения теории графов, это симметричная разность. Это кратко описано в документации gitrevisions. Это заставляет код обхода ревизий проверять коммиты, доступные из HEAD
, но не upstream
и коммиты, доступные из upstream
, но не HEAD
. При этом Git выполняет git patch-id
для каждой фиксации. Это позволяет git rebase
находить одинаковые коммиты (с точки зрения того, что они изменяют) и, таким образом, опускать их, если они уже были отобраны в вышестоящую ветку.
В частности, предположим, что у вас есть:
...--o--*--D--E--B'--F <-- their-branch
\
A--B--C <-- your-branch (HEAD)
и вы запускаете git rebase their-branch
, чтобы скопировать три ваших коммита A-B-C
после F
. Код перебазирования будет вычислять идентификаторы исправлений из A
, B
и C
, а также идентификаторы исправлений из D
, E
, B'
и F
. Учитывая, что коммит B'
является копией вашего коммита B
, он, вероятно,2 имеет тот же идентификатор исправления. Таким образом, Git опустит B
из списка копируемых коммитов.
Режим --fork-point
описан несколько косвенно, но сначала следует отметить, что --fork-point
является параметром по умолчанию в некоторых случаях, а --no-fork-point
— параметром по умолчанию в других. Режим fork-point работает с использованием ваших журналов ссылок. Подробнее об этом см. в разделе Git rebase - commit select в режиме fork-point.
Существует относительно новая опция --keep-base
, которая действительно выполняет базовые вычисления слияния. Вы можете вызвать его напрямую с помощью синтаксиса с тремя точками или использовать параметр --keep-base
, чтобы включить его.
Наконец, пропуск коммитов слияния (за исключением варианта -r
или параметра -p
, которого следует избегать) происходит потому, что Git буквально не может скопировать слияние. Пропуск слияний в основном аналогичен использованию опции --no-merges
при запуске git rev-list
. Когда вы используете параметр -r
, Git будет перечислять слияния и записывать их, а также будет использовать более причудливый новый интерактивный режим сценариев для повторного выполнения слияний. То есть, учитывая такой фрагмент графика:
...--o--*-------F <-- their-branch
\
\ B
\ / \
A D--E <-- your-branch (HEAD)
\ /
C
git rebase -r
будет производить:
B'
/ \
A' G--E' <-- your-branch (HEAD)
/ \ /
/ C'
/
...--o--*-------F <-- their-branch
\
\ B
\ / \
A D--E [abandoned]
\ /
C
где новый коммит слияния G
создается путем буквального запуска git merge
на коммитах B'
и C'
. Если вы сделали D
как злое слияние с использованием git merge --no-commit
, зло будет потеряно во время этого повторного слияния. Остальные коммиты, помеченные суффиксом (A'
и т. д.), выполняются путем копирования с использованием основного механизма выбора вишни3.
1В прежние времена git rebase
представляло собой несколько сценариев оболочки, и один из них действительно запускал git rev-list
вот так, хотя, насколько я помню, он использовал --right-only --cherry-pick
. С тех пор он был переписан на C, и теперь он... сложнее. :-)
2Идентификатор патча зависит от того, пришлось ли кому-то изменять его при копировании. Дополнительные сведения см. в документации git patch-id
.
3В старых версиях Git по умолчанию фактически используются git format-patch
и git am
или их внутренний эквивалент. Это также физически не может копировать слияния и пропускает некоторые случаи переименования, которые обнаруживает вишневый выбор. Во время добавления новой опции -r
все было настроено так, чтобы переключить значение по умолчанию на использование вишневого выбора, и совсем недавно (2.25?) это стало новым значением по умолчанию.
person
torek
schedule
20.09.2020