TL; DR
Невозможно сравнить третий коммит, который вы не указали напрямую, с областью подготовки без использования хотя бы одной дополнительной команды Git. Если вы хотите выбрать базу слияния между коммитом подсказки ветви, заданным именем ветви B
, и содержимым области index / staging, вы должны использовать две команды Git. Например, в качестве двухстрочного сценария оболочки вы можете сделать:
base=$(git merge-base <B> HEAD)
git diff --cached $base # or --staged
Вы можете создать псевдоним Git, который делает это в одной строке, например:
git config --global alias.bvs='!f() { git diff --staged $(git merge-base $1 HEAD); } f'
(с именем bvs
, обозначающим Base Vs Staged), если вы хотите сократить, сколько вы должны набрать. (Этот псевдоним слегка ошибочен, так как он не проверяет, что вы фактически дали ровно один аргумент. Он также не проверяет, существует ли ровно одна такая фиксация базы слияния, но не проверяет git diff
; см. Ниже.)
Long-i sh
Будьте осторожны с пунктирным синтаксисом: git diff
обрабатывает его особенно.
Вы можете:
git diff
git diff --staged # or --cached, exactly the same thing
git diff <commit>
git diff --cached <commit>
git diff <commit1> <commit2>
git diff <commit1>..<commit2> # note two dots
git diff <commit1>...<commit2> # note three dots
(и даже больше! - но «больше» случаев не попадают в эту основную модель). В каждом из этих различных случаев вы выбираете две вещи для сравнения:
- commit1 vs commit2, для случаев, которые действительно используют два спецификатора коммита, но не используют три точки
- commit vs work-tree, для случаев, когда вы называете один коммит и не используете
--staged
- commit против index / staging-area, для случаев, когда вы называете один коммит и используете
--staged
или --cache
Пока что все они используют указанный вами коммит или оба коммита, которые вы указали . Но:
git diff <commit1>...<commit2> # note three dots
фактически сравнивает некоторые третий коммит с commit2
.
Третий коммит, выбранный Git, основан на результат выполнения:
git merge-base --all <commit1> <commit2>
или (ближе к тому, что на самом деле делает git diff
):
git rev-parse <commit1>...<commit2>
Команда git merge-base
находит все базы слияния из двух указанных коммитов, основанных на графе коммитов. График коммитов является результатом интерпретации всех сохраненных родителей коммитов. Диаграммы в принятом ответе на вопрос, на который вы ссылались в комментарии , помогают визуально объяснить основы слияния. (Эта ссылка, вероятно, должна была быть в вашем исходном вопросе 1 , поскольку она помогает определить проблему.)
Если вы запустите фактический git rev-parse
, показанный здесь, вы увидите вывод как this:
$ git rev-parse master...origin/maint
da72936f544fec5a335e66432610e4cef4430991
083378cc35c4dbcc607e4cdd24a5fca440163d17
^da72936f544fec5a335e66432610e4cef4430991
Это текстовое представление rev-parse для:
- фиксация, заданная именем
master
: конец ветви master
; - фиксация, указанная именем
origin/maint
: подсказка какой-то другой ветки Git maint
по состоянию на последний раз, когда я Git вызывал эту другую Git (origin/maint
мое имя для удаленного отслеживания для их maint
ветви); и - первый коммит, который должен быть исключен в цикле ревизий, который находит коммиты, достижимые из любого из двух коммитов, но не достижимые из обоих.
База слияния является Коммит, достижимый из обоих коммитов. Он более ограничен: это лучший такой коммит . 2 Может быть более одного "лучшего" коммита, и в этом случае git merge-base --all
найдет их все. Будет действовать и команда git rev-parse
: каждая из них должна быть исключена с префиксом ^
.
Что git diff A...B
с тремя точками делает для вызова внутреннего эквивалента git rev-parse
, чтобы найти все базы слияния. (Вы можете сделать это самостоятельно с помощью git merge-base --all
или с помощью git rev-parse
, хотя вы не получаете коммиты со всеми внутренними флагами, которые использует внутренний интерфейс.) Затем, заметив, что есть некоторые положительно выбранные коммиты и в хотя бы один отрицательно выбранный коммит, git diff
выбирает один из отрицательно выбранных коммитов, по-видимому, случайным образом, из списка. Поэтому, если git rev-parse
производит одну базу слияния, git diff
выбирает эту одну базу слияния. Если git rev-parse
перечисляет два или более, git diff
все равно просто выбирает один, вместо того, чтобы выдавать предупреждение или ошибку.
Чтобы сделать это вручную, просто запустите git merge-base
без --all
, что по сути делает то же самое. 3 Так вот, что мы должны сделать.
Найдя базу слияния, git diff
теперь сравнивает коммит базы слияния с правой стороной commit ha sh (вторая строка вывода git rev-parse
). Так что это тоже то, что мы должны делать.
Это приводит к двухэтапной операции, которую мы выписали, а затем оболочка выполнила одну команду командной строки (которая запускает две команды Git) в псевдониме.
1 Обратите внимание, что в своем вопросе вы сказали:
git diff master...HEAD
... сравнивает мастер с моей веткой (в мода GitHub PR)
На самом деле сравнение GitHub не совсем не так, и я понятия не имею, как они производят именно то, что производят. Но этот, то есть трехточечный синтаксис, использует базу слияния.
2 Определение best немного сложнее. Есть два хороших теоретических определения графа самого низкого общего предка графа, оба из статьи в Журнале алгоритмов , написанной Бендером, Майклом А и Фарах-Колтоном, Мартином и Пеммасани, Гиридхаром и Шиеной, Стивеном и Сумазин, Павел под названием Самые низкие общие предки на деревьях и направленные ациклы c графы:
Определение 1. Пусть G = (V, E) - DAG, и пусть x , y ∈ V . Пусть G x, y будет подграфом G , индуцированным множеством всех общих предков x и y . Определите SLCA ( x , y ) как набор узлов (листков) нулевой степени в G x, y . Самые низкие общие предки x и y являются элементами SLCA ( x , y ).
Определение 2. Для любого DAG G = (V, E) мы определим частично упорядоченный набор S = (V, ≼) следующим образом: элемент i ≼ j тогда и только тогда, когда i = j или (i, j) находится в переходном замыкании G tr из G . Пусть SLA C ( x , y ) будет набором максимальных элементов набора общих предков { z | z ≼ x ∧ z ≼ y } ⊆ V . Самые низкие общие предки x и y являются элементами SLA C ( x , y ).
3 Обратите внимание, что они могут выбрать различные коммиты слияния! Но вы не контролируете, какой из них git diff
выбирает, поэтому вам не особо важно, если git merge-base
выбирает другой.