Почему выходные данные этого диапазона ревизий r1..r2 включают коммиты, достижимые от r2? - PullRequest
0 голосов
/ 16 апреля 2019

На следующем графике доступны два последних коммита с r2 (HEAD) и r1 (75ec2933~1).

> git log --oneline --graph develop topic 75ec2933~1..HEAD
* 91cc860a (HEAD -> topic) 
* 1048e4d1 
* 1c28716e
| * f4a483cc (develop)
| *   b7cb53e6 
| |\
| |/
|/|
* | c7a197bd 
* | 3935a1a7 
| *   ad27a1fc 
| |\
| |/
|/|
* | 75ec2933 Merge branch 'develop' 
| * 5e55f38f 
|/
* 2effd96f          <--------------- 75ec2933~1 is r1
* ae6c987e 
* ecc2b546 

Я ожидал, что последние два коммита не будут частью вывода, потому что документация git-log говорит, что мы можем использовать диапазон ревизий, чтобы "Показывать только коммиты в указанном диапазоне ревизий". Кроме того, документация диапазона ревизий говорит это о r1..r2 нотации :

... вы можете запросить коммиты, которые достижимы из r2, за исключением тех, которые достижимы из r1 через ^ r1 r2, и это можно записать как r1..r2.

Итак, мой вопрос о том, почему мы можем видеть последние два коммита, которые, по-видимому, достижимы с r1.

Дополнительное расследование

Оказывается, 75ec2933~1 не 2effd96f, а 887b3cfa. Приведенный выше график скрывает это, что приводит меня в замешательство по поводу r2.

> git log ecc2b546..3935a1a7 --oneline --graph
* 3935a1a7
*   75ec2933 Merge branch 'develop' 
|\  
| * 2effd96f 
| * ae6c987e 
* 887b3cfa 
* 62e6be09 

1 Ответ

2 голосов
/ 16 апреля 2019

Я должен немного угадать здесь (обновление: подтверждено), но я думаю, что у нас есть такая ситуация:

  • Commit 75ec2933 является коммитом слияния, то есть имеет двух родителей.
  • Родитель # 1 имеет неизвестный хэш-идентификатор (обновление: 887b3cfa).
  • Родитель № 2 - 2effd96f.

В этом случае выражение 75ec2933~1..HEAD исключает родителя № 1, но не родителя № 2. Вы можете узнать, запустив:

git rev-parse 75ec2933^@

(обратите внимание на суффикс @ после каретки или шляпы ^). Существует довольно длинное объяснение полученного результата git log. Чтобы продемонстрировать это, я вместо этого собираюсь использовать Git-репозиторий для самого Git, так как он мне пригодится.

Пример

Вот что происходит, когда я делаю это с другим коммитом слияния в Git-репозитории для Git:

$ git rev-parse a562a11983^@
7fa92ba40abbe4236226e7d91e664bbeab8c43f2
ad6f028f067673cadadbc2219fcb0bb864300a6c

Здесь commit a562a11983 - это слияние с родителями 7fa92ba40a и ad6f028f06.

Если я запускаю git log --decorate --oneline --graph в репозитории Git для Git, позволяя git log запускаться с коммита b5101f9297 (старый совет master - я не обновлял свой репозиторий Git для Git уже несколько недель), результат начинается с этого:

* b5101f9297 (HEAD -> master) Fourth batch after 2.20
*   a562a11983 Merge branch 'it/log-format-source'
|\  
| * ad6f028f06 log: add %S option (like --source) to log --format
* |   7fa92ba40a Merge branch 'js/add-e-clear-patch-before-stating'
|\ \  
| * | fa6f225e01 add --edit: truncate the patch file
* | |   371820d5f1 Merge branch 'bc/tree-walk-oid'
|\ \ \  
| * | | 974e4a85e3 cache: make oidcpy always copy GIT_MAX_RAWSZ bytes
| * | | ea82b2a085 tree-walk: store object_id in a separate member
| * | | f55ac4311a match-trees: use hashcpy to splice trees
| * | | 36775ab524 match-trees: compute buffer offset correctly when splicing
| * | | 0a3faa45b1 tree-walk: copy object ID before use
| | |/  
| |/|   
* | |   a6e3839976 Merge branch 'jt/upload-pack-deepen-relative-proto-v2'

Использование git log --decorate --oneline --graph a562a11983^1..HEAD обрезает это до:

* b5101f9297 (HEAD -> master) Fourth batch after 2.20
* a562a11983 Merge branch 'it/log-format-source'
* ad6f028f06 log: add %S option (like --source) to log --format

Обратите внимание, что эта форма графика выглядит намного проще! Я удалил коммит a562a11983, но не зафиксировал ad6f028f06, поэтому похоже, что коммит a562a11983 имеет одного родителя, ad6f028f06, хотя на самом деле его два. По сути, git log --graph обманул нас.

Long

Стоит отметить еще несколько пунктов, прежде чем углубляться в мельчайшие детали самого git log. Во-первых, синтаксис r1..r2 в обозначении gitrevisions эквивалентен r2 ^r1. На самом деле, если мы используем git rev-parse до , расширяем синтаксис, то это то, что мы видим:

git rev-parse a562a11983^1..HEAD
b5101f929789889c2e536d915698f58d5c5c6b7a
^7fa92ba40abbe4236226e7d91e664bbeab8c43f2

HEAD - это хэш коммита, начинающийся с b5101, а a562a11983^1 (суффикс ^ и номер 1) - это коммит, начинающийся с 7fa92b... Обратите внимание, что мы использовали каретку ^ как суффикс здесь, а не как префикс ; каретка в качестве префикса означает не , т. Е. Исключает ревизию, но вставляет как суффикс представляет один из многих других спецификаторов gitrevisions, таких как @, {commit} и, конечно, числовой выбор конкретного родителя.

Другим фактом является то, что каждая запись фиксирует ноль или более родительских хеш-идентификаторов. Большинство коммитов имеют ровно один родительский идентификатор. У самого первого коммита, который вы когда-либо делаете в репозитории, нет родителей, по той простой причине, что не может иметь родителей: родительские идентификаторы нового коммита должны существовать, действительный хеш коммита идентификаторы. Коммит без родителей называется root commit . Некоторые коммиты, обычно сделанные git merge, имеют двух родителей, и вы можете сделать многорукого слияния осьминога , у которого есть три или более родителей. Любой коммит с двумя или более родительскими хэш-идентификаторами по определению является коммит слиянием .

Поскольку большинство коммитов имеют одного родителя, мы обычно начинаем с конца цепочки таких коммитов, обычно помеченных меткой ветви, такой как master, и затем мы можем работать в обратном направлении по одному коммиту за раз:

... <-F <-G <-H   <-- master (HEAD)

Здесь хэш-идентификатор, хранящийся в имени ветви master, представлен заглавной буквой H. Мы говорим, что имя master указывает на коммит с идентификатором хеша H. Коммит H сам сохраняет хэш-идентификатор своего родительского коммита G, в котором хранится хэш-идентификатор коммита F и т. Д. Следовательно, начиная с H и возвращаясь к G, затем F и т. Д., Git может показать нам историю - коммиты, которые достижимы из имени master .

Последний пункт состоит в том, что git log фактически берет много начальных точек (мы можем назвать их конечными точками, но Git работает в обратном направлении). Каждый аргумент, который определяет ревизию, но не те, которые исключают ревизию из-за отрицания с префиксом ^, обеспечивают такую ​​отправную точку. Если вы не предоставите никаких начальных точек, git log будет использовать HEAD в качестве начальной точки.

Какgit log просматривает историю и затем отображает график

Если у нас do есть простая линейная цепочка, такая как:

...--F--G--H   <-- master (HEAD)

, тогда наша работа, если мы хотим подражатьgit log, это просто.Мы начинаем с коммита H и показываем его.Теперь мы закончили с H, поэтому мы возвращаемся на шаг назад к его родителю G.Мы показываем G, затем возвращаемся к F.Мы повторяем это до тех пор, пока не достигнем корневого коммита, у которого нет родителя и который позволяет нам остановиться, или пока пользователь не выйдет из git log.

Но предположим, что у нас есть граф с коммитом слияния:

       I--J
      /    \
...--H      M--N   <-- master (HEAD)
      \    /
       K--L

Мы начнем с показа коммита N, затем перейдем к M и покажем его. 1 Затем мы пойдем, чтобы ... подождите, мы идем кJ, или L?

Что делает git log, чтобы обработать это, так это сохранить очередь приоритетов коммитов, которые он пока не показал, в то время как также ходил по графу коммитов по одному коммиту за раз.Поэтому, когда вы запускаете git log без дополнительных аргументов или с HEAD или master в качестве аргумента, git log помещает коммит N в очередь.

Когда вочереди, задача проста: вытащите один коммит из очереди, покажите его и поместите его родительский (ые) в очередь, если они не были видны ранее во время этого git log (что обычно имеет место).Если в очереди более одного коммита, git log берет один в перед очереди, т. Е. Тот, который имеет наивысший приоритет.

Итак, если вы запускаете git log <start-point-1> <start-point-2> <start-point-3>, то, что делает Git, помещает все три начальные точки в приоритетную очередь.Поскольку ваша фактическая команда была:

git log --oneline --graph develop topic 75ec2933~1..HEAD

, у нас было три отправные точки, а именно develop (f4a483cc), topic и HEAD (75ec2933~1 является отрицательной ссылкой на некоторый хешЯ БЫ).Как выясняется, HEAD и topic оба имеют имя commit 91cc860a, поэтому очередь заканчивается только двумя коммитами.

Опция --graph немного изменяет приоритетную очередь.По умолчанию фиксация с наивысшей датой , то есть самой далекой в ​​будущем или наименьшей в прошлом, идет впереди очереди.С --graph или --topo-order это же правило действует, но добавляется дополнительное правило: родительский коммит не может быть показан, пока не будут показаны все его дочерние элементы.В этом случае это дополнительное исключение не действует на данный момент, так как 91cc860a и f4a483cc не имеют отношения родитель / потомок.

Так что git log начинается с того, что из этих двух имеет более позднюю дату, котораяэто 91cc860a он же HEAD и topic.Git печатает этот коммит одним * и находит его родителя 1048e4d1, который попадает в очередь.1048e4d1 также новее, чем f4a483cc, поэтому Git показывает это следующим.Это непосредственный родитель предыдущего коммита, поэтому пришло время показать его.Это будет продолжаться немного, так что мы увидим:

* 91cc860a (HEAD -> topic) 
* 1048e4d1 
* 1c28716e

1c28716e имеет родителя c7a197bd, а c7a197bd является предком f4a483cc, поэтому он не должен отображаться, независимо отего дата.Теперь Git начинает работать над отображением f4a483cc, который является обычным коммитом:

| * f4a483cc (develop)

Родитель f4a483cc - b7cb53e6, поэтому b7cb53e6 переходит в очередь.Этот коммит имеет c7a197bd в качестве предка, поэтому Git показывает b7cb53e6 следующее:

| *   b7cb53e6 

... и b7cb53e6 само является слиянием, помещая его родителей c7a197bd и ad27a1fc вочередь.Но c7a197bd уже находится в очереди, поэтому ничего не происходит.

Теперь c7a197bd находится на фронте очереди, поэтому git log показывает это.Это первый и единственный родительский элемент 1c28716e и второй родительский элемент b7cb53e6, поэтому git log --graph показывает это немного странным образом:

| |\
| |/
|/|
* | c7a197bd 

Правая нисходящая нога показывает этот второйродитель-Несс.Прямая нога в конце концов соединится с первым родителем b7cb53e6.

. Этот же паттерн продолжается некоторое время, но затем мы сталкиваемся с неудачным случаем:

* | 3935a1a7 
| *   ad27a1fc 
| |\
| |/
|/|
* | 75ec2933 Merge branch 'develop' 
| * 5e55f38f 
|/
* 2effd96f          <--------------- ???

На этом этапе Git показал commit 75ec2933 (у которого есть два родителя, 887b3cfa, который является родителем # 1, и 2effd96f, который является родителем # 2).Git должен был бы поместить 887b3cfa в очередь, но мы сказали, что не: ^75ec2933~1 означает ^887b3cfa, что означает не показывать 887b3cfa, что сохраняетэто из очереди.Таким образом, показав 75ec2933, очередь содержала хэш-идентификаторы фиксации 5e55f38f и 2effd96f.Git показал 5e55f38f, что позволило ему перейти к 2effd96f.Когда git log --graph показал это, он даже не увидел , что был второй обрезанный родитель, поэтому неправильно нарисовал график, как если бы этого родителя не было. 2


1 Стоит отметить: когда git log показывает обычный коммит, если действует -p, он дифференцирует коммит против его (одиночного) родителя так,чтобы показать коммит, который действительно является снимком, как изменение.Но когда он попадает в коммит слияния, он не знает, какой родитель использовать для сравнения, поэтому он вообще не беспокоится о проведении сравнения!Вы можете заставить его показывать один или несколько разностей с помощью дополнительных опций git log.

2 Справедливости ради, представление в памяти в этой точке, вероятно, не есть второй родитель.Код git log содержит некоторый код «родительского переписывания», используемый для упрощения истории, и он, вероятно, также срабатывает и здесь.


Заключение

Обычно я говорю людям, что если git logпоказывая странные результаты, они должны добавить --graph, чтобы он одновременно рисовал график - ну, в общем, грубое ASCII-приближение - и подчинялся топологии графа при прохождении коммитов, так что отношения родитель / потомок, которые часто имеют решающее значение, показываютвверх.К сожалению, когда вы используете отрицание, чтобы обрезать части графика, это может обмануть код рисования графика в лжи.Вероятно, в коде рисования графика должна быть показана истинная ситуация с несколькими родителями, и, следовательно, она вынуждена рисовать последнюю часть более примерно так:

|/|
* | 75ec2933 Merge branch 'develop' 
|\|
| |\
| * | 5e55f38f 
|/ /
* | 2effd96f 
* | ae6c987e 
* | ecc2b546 

Но это не так, если / пока кто-то не сможетдобавьте это в Git, мы просто должны следить за такими случаями.

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