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

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

--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? Чем это отличается от поведения по умолчанию (без этого флага)? Что значит «воссоздать» слияние и т. Д.


person Chris    schedule 10.04.2013    source источник
comment
Похоже, вы всегда должны использовать --preserve-merges. В противном случае существует вероятность потери истории, т. Е. Слияние фиксируется.   -  person VonC    schedule 27.05.2018


Ответы (2)


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

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

  • Перебазирование с сохранением слияния желает воспроизвести (некоторые) коммиты слияния, тогда как обычное перебазирование полностью игнорирует коммиты слияния.
  • Because it's willing to replay merge commits, merge-preserving rebase has to define what it means to replay a merge commit, and deal with some extra wrinkles
    • The most interesting part, conceptually, is perhaps in picking what the new commit's merge parents should be.
    • Воспроизведение коммитов слияния также требует явной проверки определенных коммитов (git checkout <desired first parent>), тогда как при обычном перебазировании об этом не нужно беспокоиться.
  • Merge-preserving rebase considers a shallower set of commits for replay:
    • In particular, it will only consider replaying commits made since the most recent merge base(s) -- i.e. the most recent time the two branches diverged --, whereas normal rebase might replay commits going back to the first time the two branches diverged.
    • Чтобы быть предварительным и неясным, я считаю, что в конечном итоге это средство отсеять повторное воспроизведение «старых коммитов», которые уже были «включены» в коммит слияния.

Сначала я попытаюсь «достаточно точно» описать, что делает rebase --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.

Rebase --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")

Rebase с аргументом --onto C должен быть очень похожим. Просто вместо того, чтобы начинать воспроизведение фиксации с ГОЛОВКИ B, вместо этого вы начинаете воспроизведение фиксации с ГОЛОВКИ C. (И используйте C_new вместо B_new.)

Пример 1

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

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

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

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

  • выберите D
  • выберите E
  • выберите F
  • выберите G
  • выберите H

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

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

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

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

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

  • выберите D
  • выберите E
  • выберите F (на D 'в ветке' subtopic ')
  • выберите G (на F 'в ветке' subtopic ')
  • выберите Объединить ветку "подтема" в тему
  • выберите H

Теперь для воспроизведения был выбран m . Также обратите внимание, что родители слияния 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

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

  • выбрать слияние совершить м
  • выберите 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-merges из H (тема) на D (мастер), то для воспроизведения будут выбраны следующие коммиты:

  • выбрать m1
  • выберите 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 не любит создавать пустые коммиты.

person Community    schedule 10.04.2013
comment
@DarVar Вы всегда теряете историю при перебазировании, потому что вы утверждаете, что изменения были внесены в кодовую базу, отличную от той, где они были на самом деле. - person David Alan Hjelle; 10.07.2013
comment
Пример 1 мне кажется совершенно логичным, поскольку на базу слияния влияет операция rebase. Но что касается примера 2, в чем именно польза от этого? Перебазирую одну ветку и не трогаю базу слияния. Поэтому я ожидал, что он изменит только коммиты в этой ветке, то есть m 'должен иметь E' и исходный G в качестве родителей слияния. - person DarVar; 04.12.2013
comment
@Chronial правильный. Выполняя перебазирование, вы уже говорите, что готовы взорвать свой текущий железнодорожный путь (историю веток), убрать все железнодорожные шпалы со своего пути (зафиксировать) и вставить их под главный железнодорожный путь. Затем ветвь объекта просто становится ссылкой на железнодорожную ветку дальше по пути от связи, на которую ссылается мастер. Вы уже потеряли историю, указывающую на то, что другой трек вообще когда-либо существовал. Мастер и ваша функциональная ветка теперь являются просто ссылками на коммиты в одной строке истории. - person Chronial; 04.12.2013
comment
@DarVar Напротив, я думаю, что это подчеркивает некоторые из причин, по которым переписывание слияний трудно всегда делать правильно, и поэтому вы должны организовать свой рабочий процесс таким образом, чтобы никогда не приходилось использовать _1_. Вообще говоря, поскольку ребазинг - это переписывание вещей, если вы применяете его таким образом, что это повлияет на вещи за пределами вашего локального репозитория, то, вероятно, есть лучший подход к общему рабочему процессу. Конечно, бывают случаи, когда вам приходится что-то ломать из-за обстоятельств, которые вы не можете контролировать, хотя ... - person Carsten; 27.03.2014
comment
Это все еще предварительный ответ? - person Chev; 29.04.2014
comment
@Chronial Конечно, вы правы, что ребазинг всегда включает потерю истории, но, возможно, DarVar намекал на тот факт, что вы не только теряете историю, но и изменяете базу кода. Разрешение конфликта содержит информацию, которая теряется всеми возможными способами, которые можно выполнить при перебазировании. Его всегда нужно переделывать. Неужели нет возможности позволить git переделать разрешение конфликта? Почему Git Cherry не может выбрать коммит слияния? - person twalberg; 23.02.2015
comment
В связи с этим вопросом: чтобы объединить репо с другим, сохраняя даты и историю фиксации, см .: axiac.ro/blog/2014/11/merging-git-repositories - person Andrew Grimm; 03.12.2015
comment
@AndrewGrimm удалил это, поскольку вики сообщества и более 200 представителей должны быть в порядке :-) - person Nils_M; 10.12.2015
comment
Есть ли способ принудительно сохранить для всех глобальных перемещений с помощью git config? Для git pull --rebase мы можем использовать _1_, но для классического rebase? Это сработает? _2_ - person Stefan Profanter; 15.04.2016
comment
_1_ устарел. Используйте режим _2_ или _3_, который похож по духу и работает быстрее. - person Ciro Santilli 新疆再教育营六四事件ۍ 04.08.2016
comment
Я думаю, это должен быть главный ответ, git config --global pull.rebase preserve на самом деле не сохраняет слияния, как вы хотите, это очень наивно. Это позволяет вам сохранить коммиты слияния и их отношения родительских коммитов, давая вам гибкость интерактивного ребазирования. Эта новая функция потрясающая, и если бы не хорошо написанный S.O. ответ, я бы не знал! - person Rmatt; 20.10.2016
comment
@egucciar Спасибо. И это не единственная функция Git 2.18 (stackoverflow.com/search?q=user%3A6309+ % 22git + 2.18% 22) и Git 2.19 (stackoverflow.com/search? q = пользователь% 3A6309 +% 22git + 2.19% 22) - person iomario; 29.04.2020

Git 2.18 (второй квартал 2018 г.) значительно улучшит параметр --preserve-merge, добавив новый параметр.

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

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

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

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

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

git rebase на странице руководства теперь есть полный раздел история со слияниями.


Извлекать:

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

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

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

*   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

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

См. Небольшой пример commit 1644c73:


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

Убедитесь, что refs / rewritten / относится к рабочему дереву

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

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

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

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

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

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

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

Первоначальная попытка ответить на этот вопрос была такой: git rebase --preserve-merges.

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

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

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

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

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

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

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

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

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

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

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

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

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


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

См. commit 1ace63b (09 августа 2018 г.) и commit f0880f7 (06 августа 2018 г.) от Йоханнес Шинделин (dscho).
(Объединено Junio ​​C Hamano - gitster - < / a> в commit 750eb11, 20 августа 2018 г.)

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

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

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

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

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

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

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

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

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


См. commit b9317d5, совершить 90d31ff, a> (7 марта 2019 г.) Нгуен Тхай Нгок Дуй (pclouds).
(Объединено Junio ​​C Hamano - gitster - в commit 917f2cd, 09 апреля 2019 г.)

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

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

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

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

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

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


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

См. commit f45f88b, зафиксировать c72fc40, , совершить 7cb8c92, commit e536b1f (21 октября 2019 г.) от (SZEDER Gá szeder).
(Объединено Junio ​​C Hamano - gitster - в commit db806d7, 10 ноября 2019 г.)


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

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

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

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

Как оказалось, b9317d55a3 вызвал ошибку, которая устарела, как и сама реализация trie, добавленная в 4e09cf2acf ("path: оптимизировать общую проверку каталога", 31.08.2015, Git v2 .7.0-rc0 - слияние, перечисленное в пакет № 2).

$ 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_find(), он должен вызывать указанную функцию сопоставления 'fn' только для префикса с завершением / -или- \ 0 ключа, для которого дерево содержит значение.
Это неверно: есть три места, где trie_find () вызывает функцию сопоставления, но в одном из них отсутствует проверка существования значения.

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

  • Когда функция match check_common() вызывается со значением NULL, она возвращает 0, что указывает на то, что запрошенный путь не принадлежит общему каталогу, что в конечном итоге приводит к фиктивному пути, показанному выше.

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

    • 'logs/refs/rewritten', and
    • Я заметил, что --preserve-merge намного медленнее, чем git rebase без --rebase-merges. Это побочный эффект поиска правильных коммитов? Есть ли что-нибудь, что можно сделать, чтобы это ускорить? (Кстати… спасибо за очень подробный ответ!)
  • check_common() больше не нужно будет проверять, получено ли значение, отличное от NULL, поэтому удалите это условие.

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

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

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

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

rebase-helper --make-script: введите флаг для перебазирования слияний

person VonC    schedule 27.05.2018
comment
Чрезвычайно полезно, если вы пытаетесь перемещать куски коммитов, как в этом вопросе / ответе, stackoverflow.com/questions/45059039/ - person egucciar; 31.08.2018
comment
О, это действительно то, что я искал с тех пор! У меня был обходной путь вручную для таких случаев, когда нужно создать фиктивный коммит, объединяющий все слияния. - person VonC; 31.08.2018
comment
Типичный Git. Осмелитесь ли вы задать простой вопрос, и вам, скорее всего, придется изучить историю Git, внутренние алгоритмы, все беспорядочные детали реализации, плюс вам также понадобится специальность по теории графов, чтобы понять, что происходит. - person okovko; 08.09.2018
comment
@VonC LOL! Это именно то, что я часто делаю. - person carnicer; 30.10.2018
comment
Есть ли способ отредактировать конфигурацию, чтобы сделать --preserve-merges по умолчанию? - person Dimitrios Menounos; 28.03.2020
comment
@JoeSchr Зачем сохранять-объединять? Сейчас он устарел и больше не рекламируется. Но в любом случае обратите внимание на параметр _1_ : по умолчанию можно установить любую политику. - person Dimitrios Menounos; 28.03.2020
comment
Извините, что перепутала. Я имел в виду _1_ офс, еще не пил кофе ... Тай за ссылку. - person JoeSchr; 31.01.2021
comment
@VonC, знаете ли вы, как настроить его для интерактивных перемещений? вот собственно причина, по которой я это ищу? - person VonC; 31.01.2021
comment
@JoeSchr Нет, я не нашел конфигурации по умолчанию для самого --rebase-merges. Возможно, вы можете указать его для той ветки, которую хотите перенастроить (используя _2_), а затем попробуйте свой _3_, но я не уверен, что это сработает. - person JoeSchr; 31.01.2021
comment
@VonC, спасибо! Возможно, я просто неправильно понимаю вариант использования _1_ Думаю, мне нужно будет изучить его подробнее - person JoeSchr; 31.01.2021
comment
См. совершить 25cff9f, совершить 7543f6f, commit bf5c057 (25 апреля 2018 г.) от Йоханнес Шинделин (_6_).
См. commit f431d73 (25 апреля 2018 г.), Стефан Беллер (_7_) .
См. совершить 2429335 (25 апреля 2018 г.) от Филип Вуд (_8_).
(Объединено Junio ​​C Hamano - _9_ - в фиксации 2c18e6a, 23 мая 2018 г.) - person VonC; 31.01.2021