Я должен немного угадать здесь (обновление: подтверждено), но я думаю, что у нас есть такая ситуация:
- 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, мы просто должны следить за такими случаями.