Что именно делает git «rebase --preserve-merges» (и почему?)

355

Документацияrebase Git для команды довольно краткая:

--preserve-merges
    Instead of ignoring merges, try to recreate them.

This uses the --interactive machinery internally, but combining it
with the --interactive option explicitly is generally not a good idea
unless you know what you are doing (see BUGS below).

Так что же на самом деле происходит, когда вы используете --preserve-merges? Чем он отличается от поведения по умолчанию (без этого флага)? Что значит «воссоздать» слияние и т. Д.

Крис
источник
20
Предупреждение: начиная с Git 2.18 (Q2 2018, 5 лет спустя), в git --rebase-mergesконечном итоге заменит старый git --preserve-merges. Смотрите мой ответ ниже
VonC

Ответы:

464

Как и при обычном git rebase, git with --preserve-mergesсначала идентифицирует список коммитов, сделанных в одной части графа коммитов, а затем воспроизводит эти коммиты поверх другой части. Различия в отношении того, --preserve-mergesкакие коммиты выбираются для воспроизведения и как это воспроизведение работает для коммитов слияния.

Чтобы быть более ясным об основных различиях между обычным и сохраняющим слияние ребазом:

  • Сохраняющая слияние ReBase готова воспроизвести (некоторые) коммиты слияния, тогда как обычная ReBase полностью игнорирует коммиты слияния.
  • Поскольку он готов воспроизвести коммиты слияния, сохраняющий слияние перебаз должен определить, что означает воспроизведение коммита слияния, и справиться с некоторыми дополнительными складками.
    • Концептуально наиболее интересной частью является, пожалуй, выбор того, каким должен быть родитель слияния нового коммита.
    • Воспроизведение коммитов слияния также требует явной проверки определенных коммитов ( git checkout <desired first parent>), тогда как при обычном ребазе об этом не нужно беспокоиться.
  • Rebease с сохранением слияний рассматривает меньший набор коммитов для воспроизведения:
    • В частности, он будет рассматривать только повторные коммиты, сделанные после самой последней базы (ей) слияния - т.е. самого последнего времени, когда две ветви разошлись - тогда как обычная перебазировка может повторить коммиты, возвращающиеся к первому различию двух ветвей.
    • Чтобы быть предварительным и неясным, я считаю, что в конечном итоге это средство отсеивания воспроизведения «старых коммитов», которые уже были «включены» в коммит слияния.

Сначала я попытаюсь описать "достаточно точно", что перебазировать --preserve-merges делает , а затем будет несколько примеров. Конечно, можно начать с примеров, если это кажется более полезным.

Алгоритм "Бриф"

Если вы действительно хотите попасть в сорняки, загрузите исходный код git и изучите файл. git-rebase--interactive.sh . (Rebase не является частью ядра C в Git, а написан на bash. За кулисами он делится кодом с «интерактивной перебазкой».)

Но здесь я обрисую то, что я думаю в этом суть. Чтобы уменьшить количество вещей, о которых нужно подумать, я взял несколько свобод. (Например, я не пытаюсь уловить со 100% точностью точный порядок, в котором происходят вычисления, и игнорирую некоторые менее централизованные темы, например, что делать с коммитами, которые уже были выбраны между ветвями).

Во-первых, обратите внимание, что ребаз, не сохраняющий слияния, довольно прост. Это более или менее:

Find all commits on B but not on A ("git log A..B")
Reset B to A ("git reset --hard A") 
Replay all those commits onto B one at a time in order.

Ребаз --preserve-mergesсравнительно сложен. Вот так просто, как я смог сделать это без потери вещей, которые кажутся довольно важными:

Find the commits to replay:
  First find the merge-base(s) of A and B (i.e. the most recent common ancestor(s))
    This (these) merge base(s) will serve as a root/boundary for the rebase.
    In particular, we'll take its (their) descendants and replay them on top of new parents
  Now we can define C, the set of commits to replay. In particular, it's those commits:
    1) reachable from B but not A (as in a normal rebase), and ALSO
    2) descendants of the merge base(s)
  If we ignore cherry-picks and other cleverness preserve-merges does, it's more or less:
    git log A..B --not $(git merge-base --all A B)
Replay the commits:
  Create a branch B_new, on which to replay our commits.
  Switch to B_new (i.e. "git checkout B_new")
  Proceeding parents-before-children (--topo-order), replay each commit c in C on top of B_new:
    If it's a non-merge commit, cherry-pick as usual (i.e. "git cherry-pick c")
    Otherwise it's a merge commit, and we'll construct an "equivalent" merge commit c':
      To create a merge commit, its parents must exist and we must know what they are.
      So first, figure out which parents to use for c', by reference to the parents of c:
        For each parent p_i in parents_of(c):
          If p_i is one of the merge bases mentioned above:
            # p_i is one of the "boundary commits" that we no longer want to use as parents
            For the new commit's ith parent (p_i'), use the HEAD of B_new.
          Else if p_i is one of the commits being rewritten (i.e. if p_i is in R):
            # Note: Because we're moving parents-before-children, a rewritten version
            # of p_i must already exist. So reuse it:
            For the new commit's ith parent (p_i'), use the rewritten version of p_i.
          Otherwise:
            # p_i is one of the commits that's *not* slated for rewrite. So don't rewrite it
            For the new commit's ith parent (p_i'), use p_i, i.e. the old commit's ith parent.
      Second, actually create the new commit c':
        Go to p_1'. (i.e. "git checkout p_1'", p_1' being the "first parent" we want for our new commit)
        Merge in the other parent(s):
          For a typical two-parent merge, it's just "git merge p_2'".
          For an octopus merge, it's "git merge p_2' p_3' p_4' ...".
        Switch (i.e. "git reset") B_new to the current commit (i.e. HEAD), if it's not already there
  Change the label B to apply to this new branch, rather than the old one. (i.e. "git reset --hard B")

Ребаз с --onto Cаргументом должен быть очень похожим. Вместо того, чтобы начинать коммитное воспроизведение в HEAD of B, вы вместо этого начинаете коммитное воспроизведение в HEAD of C. (И используйте C_new вместо B_new.)

Пример 1

Например, взять граф фиксации

  B---C <-- master
 /                     
A-------D------E----m----H <-- topic
         \         /
          F-------G

m - это слияние с родителями E и G.

Предположим, что мы перебазировали тему (H) поверх главной (C), используя обычную, не сохраняющую слияния перебазу. (Например, тема оформления заказа; rebase master .) В этом случае git выберет следующие коммиты для воспроизведения:

  • выбрать D
  • выбрать E
  • выбрать F
  • выбрать G
  • выбрать H

а затем обновите граф фиксации следующим образом:

  B---C <-- master
 /     \                
A       D'---E'---F'---G'---H' <-- topic

(D '- это переигранный эквивалент D и т. Д.)

Обратите внимание, что коммит слияния m не выбран для воспроизведения.

Если бы мы вместо этого сделали --preserve-mergesребазирование H поверх C. (Например, тема оформления заказа; rebase --preserve-merges master .) В этом новом случае git выберет следующие коммиты для воспроизведения:

  • выбрать D
  • выбрать E
  • выберите F (на D 'в «подтемной» ветке)
  • выберите G (на F 'в «подтемной» ветке)
  • выбрать ветку слияния "подтема" в тему
  • выбрать H

Теперь м был выбран для воспроизведения. Также обратите внимание, что родители слияния E и G были выбраны для включения перед коммитом m.

Вот итоговый граф коммитов:

 B---C <-- master
/     \                
A      D'-----E'----m'----H' <-- topic
        \          / 
         F'-------G'

Опять же, D '- это выбранная (то есть воссозданная) версия D. То же самое для E' и т. Д. Каждый коммит не на мастере был воспроизведен. И E, и G (родители слияния m) были воссозданы как E 'и G', чтобы служить родителями m '(после перебазирования история дерева остается прежней).

Пример 2

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

Например, рассмотрим:

  B---C <-- master
 /                     
A-------D------E---m----H <-- topic
 \                 |
  ------- F-----G--/ 

Если мы перебазируем H (тема) поверх C (master), то коммиты, выбранные для перебазировки:

  • выбрать D
  • выбрать E
  • выбрать F
  • выбрать G
  • забрать м
  • выбрать H

И результат выглядит так:

  B---C  <-- master
 /    | \                
A     |  D'----E'---m'----H' <-- topic
       \            |
         F'----G'---/

Пример 3

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

Например, рассмотрим:

  B--C---D <-- master
 /    \                
A---E--m------F <-- topic

Если мы переназначим тему на master (сохраняя слияния), то коммиты для воспроизведения будут

  • выбрать слияние совершить м
  • выбрать F

Переписанный граф коммитов будет выглядеть так:

                     B--C--D <-- master
                    /       \             
                   A-----E---m'--F'; <-- topic

Здесь воспроизводимый коммит слияния m 'получает родителей, которые ранее существовали в графе коммитов, а именно D (ГОЛОВКА хозяина) и E (один из родителей исходного коммита слияния m).

Пример 4

Сохраняющий слияние ребаз может быть запутан в некоторых случаях «пустого коммита». По крайней мере, это верно только для некоторых старых версий git (например, 1.7.8.)

Возьмите этот граф коммитов:

                   A--------B-----C-----m2---D <-- master
                    \        \         /
                      E--- F--\--G----/
                            \  \
                             ---m1--H <--topic

Обратите внимание, что оба коммита m1 и m2 должны были включать все изменения из B и F.

Если мы попытаемся сделать git rebase --preserve-mergesH (топик) на D (мастер), то для воспроизведения будут выбраны следующие коммиты:

  • выбрать м1
  • выбрать H

Обратите внимание, что изменения (B, F), объединенные в m1, уже должны быть включены в D. (Эти изменения уже должны быть включены в m2, потому что m2 объединяет дочерние элементы B и F.) Следовательно, концептуально, воспроизведение m1 поверх Вероятно, D должен быть либо неактивным, либо создать пустой коммит (т. Е. Тот, в котором разница между последовательными ревизиями пуста).

Однако вместо этого git может отклонить попытку воспроизведения m1 поверх D. Вы можете получить ошибку, например, такую:

error: Commit 90caf85 is a merge but no -m option was given.
fatal: cherry-pick failed

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

Крис
источник
6
Я заметил, что git rebase --preserve-mergesэто намного медленнее, чем rebaseбез --preserve-merges. Является ли это побочным эффектом поиска правильных коммитов? Есть ли что-нибудь, что можно сделать, чтобы ускорить это? (Кстати ... спасибо за очень подробный ответ!)
Дэвид Алан Хьелле
7
Похоже, вы всегда должны использовать --preserve-merges. В противном случае есть вероятность потерять историю, то есть совершить слияние.
DarVar
19
@DarVar Вы всегда теряете историю из-за перебазирования, потому что вы утверждаете, что изменения были сделаны в другой кодовой базе, чем они фактически были.
Chronial
5
Это все еще «предварительный ответ»?
Эндрю Гримм
5
@Chronial Конечно, вы правы, что перебазирование всегда включает потерю истории, но, возможно, DarVar намекал на то, что вы не только теряете историю, но и вносите изменения в базу кода. Разрешение конфликта содержит информацию, которая теряется всеми возможными способами, чтобы сделать перебазирование. Вы всегда должны переделывать это. Неужели нет способа позволить git redo разрешить конфликт? Почему Git Cherry-Pick не может выполнить коммит слияния?
Nils_M
94

Git 2.18 (Q2 2018) значительно улучшит --preserve-mergeопцию, добавив новую опцию.

« git rebase» Научились « --rebase-merges» , чтобы трансплантировать всю топологию графа фиксации в других местах .

(Примечание: Git 2.22, Q2 2019, фактически устарел --preserve-merge , а Git 2.25, Q1 2020, перестает рекламировать его в git rebase --helpвыводе " " )

См совершают 25cff9f , совершают 7543f6f , совершают 1131ec9 , совершают 7ccdf65 , совершают 537e7d6 , совершают a9be29c , совершают 8f6aed7 , совершают 1644c73 , совершают d1e8b01 , совершают 4c68e7d , совершают 9055e40 , совершают cb5206e , совершают a01c2a5 , совершают 2f6b1d1 , совершают bf5c057 (25 апр 2018) от Johannes Schindelin ( dscho) .
Смотрите коммит f431d73 (25 апреля 2018 года) Стефана Беллера ( stefanbeller) .
См. Фиксацию 2429335 (25 апреля 2018) Филиппа Вуда ( phillipwood) .
(Слиты Junio C Hamano - gitster- в фиксации 2c18e6a , 23 мая 2018 года)

pull: принять --rebase-mergesдля воссоздания топологии ветки

Подобно preserveрежиму, просто передающему --preserve-merges параметр rebaseкоманде, mergesрежим просто передает --rebase-mergesпараметр.

Это позволит пользователям удобно перебазировать нетривиальные топологии фиксации при извлечении новых коммитов, не выравнивая их.


git rebaseНа странице man теперь есть полный раздел, посвященный перебазированию истории слияниями .

Выдержка:

Существуют законные причины, по которым разработчик может захотеть воссоздать коммиты слияния: сохранить структуру ветвей (или «топологию коммитов») при работе с несколькими взаимосвязанными ветвями.

В следующем примере разработчик работает над веткой тем, которая изменяет способ определения кнопок, и над другой веткой тем, которая использует этот рефакторинг для реализации кнопки «Сообщить об ошибке».
Вывод git log --graph --format=%s -5может выглядеть так:

*   Merge branch 'report-a-bug'
|\
| * Add the feedback button
* | Merge branch 'refactor-button'
|\ \
| |/
| * Use the Button class for all buttons
| * Extract a generic Button class from the DownloadButton one

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

Эта перебазировка может быть выполнена с помощью --rebase-mergesопции.


Смотрите коммит 1644c73 для небольшого примера:

rebase-helper --make-script: ввести флаг, чтобы перебазировать слияния

Секвенсор только что выучил новые команды, предназначенные для воссоздания структуры ветвей ( схожи по духу --preserve-merges, но с существенно менее разрушенным дизайном ).

Давайте позволим rebase--helperгенерировать списки задач, используя эти команды, запускаемые новой --rebase-mergesопцией.
Для топологии фиксации, подобной этой (где HEAD указывает на C):

- A - B - C (HEAD)
    \   /
      D

сгенерированный список задач будет выглядеть так:

# branch D
pick 0123 A
label branch-point
pick 1234 D
label D

reset branch-point
pick 2345 B
merge -C 3456 D # C

В чем разница --preserve-merge?
Commit 8f6aed7 объясняет:

Давным-давно этот разработчик подумал: не было бы неплохо, если бы, скажем, патчи Git для Windows поверх ядра Git можно было представить в виде зарослей ветвей и переместить их поверх ядра Git, чтобы поддерживать набор из нескольких патчей?

Оригинальная попытка ответить на это: git rebase --preserve-merges.

Тем не менее, этот эксперимент никогда не задумывался как интерактивный вариант, и он только поддерживал себя, git rebase --interactiveпотому что реализация этой команды выглядела уже очень, очень знакомо: она была разработана тем же человеком, который разработал --preserve-merges: действительно ваш.

И под «вашим по-настоящему» автором подразумевается сам Йоханнес Шинделин ( dscho) , который является главной причиной (вместе с несколькими другими героями - Ханнесом, Штеффеном, Себастьяном, ...), что у нас есть Git For Windows (хотя назад в тот день - 2009 - это было нелегко ).
Он работает в Microsoft с сентября 2015 года , что имеет смысл, учитывая, что Microsoft сейчас активно использует Git и нуждается в его услугах.
Эта тенденция началась в 2013 году, на самом деле, с TFS . С тех пор Microsoft управляет крупнейшим Git-репозиторием на планете ! И с октября 2018 года Microsoft приобрела GitHub .

Вы можете видеть, как Йоханнес говорит в этом видео для Git Merge 2018 в апреле 2018 года.

Некоторое время спустя другой разработчик (я смотрю на тебя, Андреас! ;-)) решил, что было бы неплохо разрешить --preserve-mergesобъединение с --interactive(с оговорками!) И сопровождающим Git (ну, временным сопровождающим Git). во время отсутствия Хунио, то есть) согласился, и именно тогда гламур --preserve-mergesдизайна начал распадаться довольно быстро и непривлекательно.

Здесь Джонатан говорит об Андреасе Швабе из Сьюз.
Вы можете увидеть некоторые из их обсуждений еще в 2012 году .

Причина? В --preserve-mergesрежиме, родители коммитов слияния (или, в этом отношении, любого коммита) не были указаны явно, но подразумевались именем коммита, переданным pickкоманде .

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

Увы, эти недостатки также помешали этому режиму (первоначальная цель которого состояла в том, чтобы обслуживать Git для нужд Windows, с дополнительной надеждой, что он может быть полезен и для других), чтобы обслуживать Git для нужд Windows.

Пять лет спустя, когда стало действительно невыносимо иметь одну громоздкую, большую серию патчей с мешаниной, частично связанных, частично не связанных патчей в Git для Windows, которые время от времени перекладывались на основные теги Git (вызывая незаслуженный гнев разработчика из злополучной git-remote-hgсерии, которая в первую очередь отменила конкурирующий подход Git для Windows, оставленный без сопровождения позже), была действительно несостоятельной, команда « Садовые ножницы Git » была рождена : скрипт, поросенок-поддержку на верхней части интерактивной перебазироваться, который сначала определит топологию ветвлений патчей, которые будут перебазированы, создаст псевдо-список задач для дальнейшего редактирования, преобразует результат в реальный список задач (интенсивно используяexec для «реализации» отсутствующих команд списка задач) и, наконец, воссоздания серия патчей поверх новой базовой фиксации.

(В этом патче упоминается скрипт садовых ножниц Git в коммите 9055e40 )

Это было в 2013 году.
И потребовалось около трех недель, чтобы придумать дизайн и реализовать его в виде сценария «из дерева». Излишне говорить, что для стабилизации реализации потребовалось несколько лет, и в то же время сам дизайн доказал свою надежность.

С этим патчем совершенство садовых ножниц Git приходит в git rebase -iсебя .
Передача --rebase-mergesопции создаст список задач, который можно легко понять, и где очевидно, как изменить порядок коммитов .
Новые ветки можно вводить, вставляя labelкоманды и вызывая merge <label>.
И как только этот режим станет стабильным и общепринятым, мы можем осудить ошибку проектирования, которая была--preserve-merges .


Git 2.19 (Q3 2018) улучшает новую --rebase-mergesопцию, заставляя ее работать --exec.

Параметр « --exec» для « git rebase --rebase-merges» поместил команды exec в неправильные места, что было исправлено.

Смотрите коммит 1ace63b (09 августа 2018 г.) и коммит f0880f7 (06 августа 2018 г.) от Johannes Schindelin ( dscho) .
(Слиты Junio C Hamano - gitster- в фиксации 750eb11 , 20 Aug 2018)

rebase --exec: заставить его работать с --rebase-merges

Идея --execсостоит в том, чтобы добавить execвызов после каждого pick.

С момента введения fixup!/ s quash!коммитов эта идея была расширена для применения к «выбору, возможно, сопровождаемому цепочкой исправлений / сквоша», т.е. exec не будет вставлен между a pickи любой из его соответствующих строк fixupили squashстрок.

Текущая реализация использует пакость для достижения этой цели: она предполагает , что только выбрать / Fixup / сквош команды, а затем вставляет в execлинию перед любым , pickно первым, и присоединяет окончательную.

С списком дел , порожденных git rebase --rebase-merges, эта простая реализация показывает свои проблемы: она производит точную неправильную вещь , когда есть label, resetи mergeкоманды.

Давайте изменим реализацию, чтобы сделать именно то, что мы хотим: искать pickстроки, пропускать любые цепочки исправлений / сквошей, а затем вставлять exec строки . Вспенить, промыть, повторить.

Примечание: мы стараемся вставлять перед комментариями строки, когда это возможно, так как пустые коммиты представлены закомментированными строками выбора (и мы хотим вставить строку exec предыдущего выбора перед такой строкой, а не после).

При этом также добавляйте execстроки после mergeкоманд, потому что они по духу похожи на pickкоманды: они добавляют новые коммиты.


В Git 2.22 (Q2 2019) исправлено использование refs / rewritten / иерархии для хранения промежуточных состояний перебазирования, что по сути создает иерархию для каждого рабочего дерева.

См. Коммит b9317d5 , коммит 90d31ff , коммит 09e6564 (07 марта 2019 г.) от Nguyễn Thái Ngọc Duy (pclouds ) .
(Слиты Junio C Hamano - gitster- в фиксации 917f2cd , 09 Apr 2019)

Убедитесь, что refs / rewritten / per-worktree

a9be29c (секвенсор: сделать ссылки, сгенерированные labelкомандой worktree-local, 2018-04-25, Git 2.19) добавляет refs/rewritten/в качестве ссылочного пространства для каждого рабочего дерева.
К сожалению (мой плохой) есть пара мест, которые нуждаются в обновлении, чтобы убедиться, что это действительно для каждого рабочего дерева.

- add_per_worktree_entries_to_dir()обновлен, чтобы удостовериться, что список ссылок просматривается для каждого рабочего дерева refs/rewritten/вместо одного для репо.

  • common_list[]обновляется, так что git_path()возвращает правильное местоположение. Это включает в себя " rev-parse --git-path".

Этот беспорядок создан мной.
Я начал пытаться исправить это с введением того, refs/worktree,где все рефери будут на дерево без специальной обработки.
Неудачные рефери / переписанные были раньше рефери / рабочего дерева, так что это все, что мы можем сделать.


С Git 2.24 (Q4 2019), " git rebase --rebase-merges" научился управлять различными стратегиями слияния и передавать им специфические для стратегии опции.

См. Сообщение 476998d (04 сентября 2019 г.) Элайджи Ньюрена ( newren) .
См. Коммит e1fac53 , коммит a63f990 , коммит 5dcdd74 , коммит e145d99 , коммит 4e6023b , коммит f67336d , коммит a9c7107 , коммит b8c6f24 , коммит d51b771 , коммит c248d32 , коммит 8c1e240 , коммит 5efed0e , коммит 68b547bac , коммит 2b547bac , совершает 6180b20 , совершают d5b581f (31 Июль 2019)Йоханнес Шинделин ( dscho),
(Слиты Junio C Hamano - gitster- в фиксации 917a319 , 18 Sep 2019)


В Git 2.25 (Q1 2020) логика, используемая для разделения локальных ссылок и глобальных ссылок репозитория на рабочих местах, исправлена, чтобы облегчить сохранение-слияние.

См. Коммит f45f88b , коммит c72fc40 , коммит 8a64881 , коммит 7cb8c92 , коммит e536b1f (21 октября 2019 г.) от SZEDER Gábor ( szeder) .
(Слиты Junio C Hamano - gitster- в фиксации db806d7 , 10 ноября 2019)

path.c: не вызывать matchфункцию без значения вtrie_find()

Подписано: SZEDER Gábor

'logs / refs' - это не рабочий путь, зависящий от дерева, но поскольку commit b9317d55a3 (убедитесь, что refs / rewritten / is per-worktree, 2019-03-07, v2.22.0-rc0) ' git rev-parse --git-path' возвращает поддельный путь если присутствует трейлинг ' /':

$ git -C WT/ rev-parse --git-path logs/refs --git-path logs/refs/
/home/szeder/src/git/.git/logs/refs
/home/szeder/src/git/.git/worktrees/WT/logs/refs/

Мы используем trie структуру данных, чтобы эффективно решить, принадлежит ли путь к общему каталогу или работает для дерева.

Так случилось, что b9317d55a3 вызвал ошибку, которая столь же стара, как и trieсама реализация, добавленная в 4e09cf2acfpath: оптимизировать общую проверку dir», 2015-08-31, Git v2.7.0-rc0 - слияние, перечисленное в пакете № 2 ).

  • Согласно описанию комментария trie_find(), он должен вызывать только данную функцию сопоставления 'fn' для "/ -or- \ 0-прекращенного префикса ключа, для которого три содержит значение".
    Это не так: есть три места, где trie_find () вызывает функцию match, но в одном из них отсутствует проверка на наличие значения.

  • b9317d55a3 добавил два новых ключа к trie:

    • 'logs/refs/rewritten ' и
    • ' logs/refs/worktree', рядом с уже существующим ' logs/refs/bisect'.
      В результате появился trieузел с путем ' logs/refs/', которого раньше не было, и к которому не было прикреплено значение.
      Запрос для ' logs/refs/' находит этот узел, а затем обращается к этому одному месту вызова matchфункции, который не проверяет существование значения, и, таким образом, вызывает matchфункцию со NULLзначением.
  • Когда matchфункция check_common()вызывается со NULLзначением, она возвращает 0, что указывает на то, что запрашиваемый путь не принадлежит общему каталогу, что в итоге приводит к фиктивному пути, показанному выше.

Добавьте отсутствующее условие, trie_find()чтобы оно никогда не вызывало функцию сопоставления с несуществующим значением.

check_common() тогда больше не нужно будет проверять, что он получил ненулевое значение, поэтому удалите это условие.

Я полагаю, что нет других путей, которые могли бы вызвать подобный поддельный вывод.

AFAICT, единственная другая клавиша, приводящая к вызову функции соответствия со NULLзначением, это ' co' (из-за клавиш ' common' и ' config').

Однако, поскольку они не находятся в каталоге, принадлежащем общему каталогу, ожидается, что в результате будет получен путь к конкретному рабочему дереву.

VonC
источник
3
Я думаю, что это должен быть главный ответ, на --preserve-mergesсамом деле не «сохраняет» слияния так, как вы хотите, это очень наивно. Это позволяет вам сохранить коммиты слияния и их родительские коммиты, одновременно предоставляя вам гибкость интерактивного перебазирования. Эта новая функция удивительна, и если бы не этот хорошо написанный SO ответ, я бы не знал!
egucciar
@egucciar Спасибо. И это не единственная функция Git 2.18 ( stackoverflow.com/search?q=user%3A6309+%22git+2.18%22 ) и Git 2.19 ( stackoverflow.com/search?q=user%3A6309+%22git+2.19% 22 )
VonC
1
Чрезвычайно полезно, если вы пытаетесь переместить множество коммитов, как в этом
вопросе
1
О, это действительно то, что я искал с тех пор! У меня был обходной путь для таких случаев, когда нужно создать фиктивный коммит, объединяющий все слияния.
Карникер
Типичный Git. Осмелюсь задать простой вопрос, и вам, скорее всего, придется изучить историю Git, внутренние алгоритмы, все грязные детали реализации, а также вам необходимо знание теории графов, чтобы понять, что происходит.
Димитрис