Правильно ли сказать, что git rebase эквивалентен git-выбору определенных коммитов с другого направления?

Я пытаюсь улучшить свое понимание (и общение) команд git.

Правильно ли говорить, что

git checkout A
git rebase B

точно эквивалентно

git checkout B
git cherry-pick <all_commits_from_common_ancestor_of_<A>_and_<B>_to_<A>>

А если нет, то по какому сценарию они расходятся?


person slackwing    schedule 20.09.2020    source источник
comment
Отвечает ли это на ваш вопрос? Git Cherry Pick vs Rebase   -  person Shridhar R Kulkarni    schedule 20.09.2020
comment
Я прочитал этот вопрос, но не смог впитать достаточно, чтобы уверенно ответить на вопрос о переводе одного на другой.   -  person slackwing    schedule 20.09.2020


Ответы (1)


Это не совсем правильно.

Здесь есть несколько камней преткновения. Во-первых, может не быть общего предка или их может быть более одного. (Это довольно незначительно: это просто означает, что все фиксации копируются или все общие предки опускаются.) Во-вторых, мы можем не проверять фиксацию, указанную 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
comment
Эй, спасибо за такой подробный ответ. Здесь есть несколько концепций, которые мне придется рассмотреть, когда я буду больше разбираться в git. Совсем не хочу упрощать ваш ответ, а повторяю, чтобы проверить мое понимание. Я разделил различия между двумя представленными методами на 3 класса. (1) Предварительные условия и непредвиденные состояния, например, может не быть общего предка или проверка может завершиться ошибкой. Имеет смысл; но я полагаю, что это нетипично для основных сценариев счастливого пути. - person slackwing; 25.09.2020
comment
(2) Как это происходит. Я вижу, что два подхода делают разные шаги, чтобы добраться до конца. Для новичка, просто желающего попасть туда, может и не быть никакой разницы, если только не возникнут конфликты. (3) Варианты. Я хотел спросить только о самом простом использовании, но я вижу, что с учетом расширенных параметров эти два метода также могут расходиться. - person slackwing; 25.09.2020
comment
Да, особенно странные ошибки, такие как сбой при оформлении заказа (часть 1), встречаются редко. Первый общий сбой, который ловит людей, заключается в том, что при выполнении перебазирования со слиянием в нем они не понимают, сколько коммитов будет скопировано (обе стороны слияния!) и что слияние удаляется с обеими ногами. , но ваша формулировка охватывает большую часть этого, но не случай слияния капель. Гораздо более редкий сбой происходит с трюками с fork-point и patch-ID: в частности, таким образом вы можете потерять коммит, который, как вы ожидали, будет скопирован. Все это часть части 2, как это происходит. - person torek; 25.09.2020