Для большинства команд Git двухточечная запись ..
означает , исключая коммит, названный слева , как вы видели. (Перечисление всех коммитов начинается с коммита, названного справа, и опускание имени означает «текущий коммит», то есть HEAD
или @
.) Таким образом, есть два способа обойти это:
- начать исключение с коммита (ов) до этого коммита или
- используйте флаг, который сообщает команде Я хотел бы, чтобы коммит, как правило, был бы исключен .
Последнее кажется проще и на самом деле работает нормально в большинстве случаев:
git log --boundary --pretty=oneline test-tag..
Однако --boundary
ведет себя плохо в некоторых более сложных случаях, поэтому я предпочитаю начинать исключение с родительского (ых) коммита.
Первый родитель любого коммита легко найти с помощью суффикса ^
или суффикса ~
, обозначения:
git log --pretty=oneline test-tag^..
git log --pretty=oneline test-tag~..
Оба будут делать то, что вы хотите для этого конкретного случая, но у них тоже есть небольшой дефект. Возможно, вы захотите ознакомиться с этим обозначением, несмотря на незначительные недостатки, потому что это очень удобно. Суффиксная нотация тильды принимает необязательный номер (по умолчанию 1), и он подсчитывает количество первых прародителей.
Вы не показали фактические хэш-идентификаторы коммитов ваших четырех коммитов, поэтому позвольте мне использовать мой собственный случайный старый репозиторий:
$ git log --oneline -n 4
11ae6ca (HEAD -> master) add run-checks script
d1574b8 mxgroup.py: sort-of-mutual-exclusive-groups
b9491ea gerrit-review.sh: push to refs/for/
676699a wacky.py - replacing class-member functions
Ни один из этих коммитов не помечен, поэтому давайте посчитаем в обратном направлении от HEAD
/ master
/ @
:
@~1
означает фиксацию d1574b8
, шаг назад
@~2
означает фиксацию b9491ea
, два шага назад
@~3
означает фиксацию 676699a
, три шага назад
и т. Д.
Более подробно (читайте, когда будете готовы, выше на некоторое время достаточно)
Суффикс шапки или каретки (^
) также может принимать число, но это имеет смысл только тогда, когда речь идет о коммите merge . Коммит слияния - это любой коммит с более чем одним родителем , обычно два с использованием git merge
для слияния двух ветвей:
I--J <-- branch1
/
...--G--H
\
K--L <-- branch2
Здесь имена branch1
и branch2
обозначают коммиты J
и L
соответственно. (Здесь J
и L
в свою очередь заменяют настоящие большие уродливые хеш-идентификаторы.)
Предположим, мы запускаем git checkout branch1
, а затем git merge branch2
. Git будет идти назад от обоих коммитов с ответвлениями веток, сначала один шаг до I
и K
, затем два шага от каждого до H
. Коммит H
находится на обеих ветвях и является лучшим совместно используемым коммитом до подсказок двух ветвей, поэтому он является базой слияния для операции слияния.
Затем Git сравнит все файлы в коммите H
со всеми файлами в коммите J
, чтобы увидеть, какую работу кто-то проделал с branch1
. Отдельно Git будет сравнивать все файлы в H
с файлами в L
, чтобы увидеть, что кто-то делал на branch2
. Затем операция объединения объединяет эти изменения, применяя объединенные изменения к файлам в H
. Если все идет хорошо, Git делает новый коммит, используя файлы, к которым применены оба набора изменений, т. Е. Объединены две линии разработки. Новый коммит выглядит так:
I--J
/ \
...--G--H M <-- ??? (what name points to M?)
\ /
K--L
Новый коммит M
получает новый хэш-идентификатор, отличный от каждого существующего и будущего коммита. Некоторое имя ветки теперь должно быть обновлено, чтобы запомнить новый коммит M
. Фактическое имя ветви, которое обновляется, зависит от того, что вы сделали git checkout branch1; git merge branch2
или git checkout branch2; git merge branch1
. Какую бы ветку вы ни выбрали, это имя перемещается, а другое остается на месте. Выше мы сказали, что вы делаете git checkout branch1
, поэтому давайте переместим имя branch1
, чтобы указать на новый коммит M
:
I--J
/ \
...--G--H M <-- branch1
\ /
K--L <-- branch2
Поскольку новый коммит M
запоминает два предыдущих коммитов, а не только один, у нас теперь есть интересная пара случаев. Одним из двух является первый родитель из M
: в данном случае это commit J
, потому что имя branch1
использовалось для указания на J
. 1 M~1
и M^1
означает коммит J
: первый двух родителей M
.
Чтобы найти фиксацию L
, второй родительский элемент M
, вы не можете использовать суффикс ~
.Вместо этого вы должны использовать суффикс ^
и написать M^2
.Это говорит Git: найти коммит M, а затем найти его второй родитель.
Так что здесь суффикс ^
становится более полезным, чем суффикс ~
,В противном случае суффикс ~
более полезен, чем суффикс ~
, потому что вы можете выполнить обратный отсчет.Тем не менее, обратите внимание, что вы можете комбинировать суффиксы: M^2
получает ваш коммит L
, а M^2~
получает вас L
родитель K
.M^2~2
получает от вас K
родителя H
, к которому вы также можете обратиться, используя M^1~2
, или просто M~3
: начните с M
и вернитесь на три шага, используя первого родителя всякий раз, когдаесть выбор.
1 Если мы сделаем слияние наоборот, так что branch2
указывает на M
, его первый родитель - L
, ивторой родитель - J
.Кроме того, какой родитель является первым и какое имя ветви изменяется, процесс слияния часто полностью симметричен.
Включая коммит слияния с двухточечной нотацией
Предположим, что мы продолжим снастройку branch1
, которую мы имеем на данный момент, либо записываем хэш-идентификатор M
, либо устанавливаем тег для запоминания M
для нас:
I--J ..... tag: test-merge
/ \ .
...--G--H M <-- branch1
\ /
K--L <-- branch2
Затем мы приступаем к созданиюеще несколько коммитов:
I--J ..... tag: test-merge
/ \ .
...--G--H M--N--O <-- branch1
\ /
K--L <-- branch2
Что если бы мы сейчас хотели git log
коммитов M
до O
включительно?Наш обычный прием написания:
git log test-merge^..
не работает, потому что test-merge^
означает find commit M
, затем вернитесь к первому родителю J
и исключите J
(и ранее) .В результате вы видите K-L-M-N-O
: пять коммитов, где вы ожидали три.
Если вы попытаетесь:
git log test-merge^2..
, вы говорите Git find commit M
,затем вернитесь к своему второму родителю L
и исключите L
(и более ранние версии) .В результате вы видите H-I-M-N-O
: снова пять коммитов.
Чтобы увидеть это визуально, нарисуйте график выше.Отметьте исключенный коммит красным маркером.Продолжайте движение красным, отмечая все предыдущие коммиты, которые вы можете получить, работая задом наперед.Затем отметьте первый включенный коммит зеленым маркером.Сохраняйте зеленый цвет, отмечая все предыдущие коммиты, которые вы можете получить, работая задом наперед, но останавливайтесь, когда вы достигаете коммитов, отмеченных красным. * Способ получения только трех коммитов, которые вы хотите - кроме --boundary
, который иногда работает, но иногда нет - это сказать Git: исключить коммит J
, и исключить коммит L
.Вы можете сделать это, не используя двухточечную запись:
git log ^M^1 ^M^2 HEAD
Префикс ^
здесь говорит Git: не .Таким образом, git log
должен начинаться с HEAD
и работать в обратном направлении, но исключает M^1
, а также M^2
.
Правильный ответ
Конечно,это работает только потому, что у M
ровно два родителя.Мы перечислим обоих из них как "не показывать этот коммит или что-либо ранее" и мы получаем то, что хотим.Но если мы выберем коммит без слияния, ^<commit>^2
завершится неудачно, потому что нет второго родителя.Здесь нам нужна нотация, которая гласит о всех родителях коммита M
.
Оказывается, именно такая нотация есть.Он (и многие другие) перечислены в документации gitrevisions , и этот конкретный написан с суффиксом ^@
.Таким образом, мы получаем то, что хотим:
git log test-merge^@..
Это работает независимо от того, сколько родителей выбрано в выбранном коммите: мы исключаем всех из них, сохраняя сам коммит.
(Существует относительно новый суффикс ^-
, также введенный в Git 2.11. Эти страницы документации стоит периодически перечитывать.)