Стоит отметить, что коммиты не содержат и не содержат изменений .Коммиты содержат полных снимков .Зафиксировать зрителей намеренно лгать вам (а вы обычно хотите сделать это), чтобы показать фиксацию как набор изменений.
Механизм, стоящий за этим, важен.Фиксация - это моментальный снимок - например, отчет о погоде, в котором говорится, что в настоящее время температура составляет 20 ° C, - и вы хотите узнать, насколько отличается сейчас.Вы должны выбрать другой коммит - например, «вчера» - для , когда вы хотите сравнение.Тогда, если вчера было 19 ° C, а сегодня 20 ° C, разница заключается в том, что на 1 ° C теплее.Но чтобы получить это, вы должны были выбрать предыдущий день для сравнения.
Каждый коммит в Git уникально идентифицируется по его хэш-идентификатору.Идентификатор хэша - это то, как Git может получить все файлы, которые находятся в моментальном снимке, и метаданные о коммите, например, кто его сделал, когда и почему (сообщение журнала).Одним из элементов, сохраняемых в каждом коммите, является хэш-идентификатор предыдущего коммита.Это то, что средства просмотра коммитов Git используют для построения различий.
Средство просмотра коммитов не покажет вам каждую строку каждого файла в коммите с хешем H .Вместо этого он найдет предшественника коммита - его parent , в терминах Git.У этого родителя есть какой-то другой хэш G .Зритель извлекает и фиксирует, сравнивает их и сообщает вам: Между G и H эти файлы разные, с этими изменениями в этих строках. Обычно это намного короче - и намного большеполезнее - чем , вот полный снимок в H .
Но это разбивается при слияниях.Если мы нарисуем хороший линейный набор коммитов:
... <-F <-G <-H <-- you-are-here
(стрелки указывают назад, потому что каждый коммит записывает своего родителя; родители не помнят своих детей), легко сравнить G
против H
,Но в итоге вы объединяете две линии развития:
o--...--o--K
/ \
...--* M <-- mainline
\ /
o--o--...--L <-- branch
Основная линия в какой-то момент разделяется, и развиваются два разных человека или группы.Затем мы - или кто-то, в любом случае - использовали git checkout mainline; git merge branch
и прошли через весь страшный 1 и магический 2 процесс слияния , который привел к этому коммит слияния M
.
Фиксация M
, как и любой другой коммит, состоит из моментального снимка и некоторых метаданных.Снимок похож на любой другой снимок.Единственная особенность M
в том, что в своих метаданных он не просто указывает коммит K
в качестве родителя.Вместо этого он перечисляет и коммитов - K
и L
- как его двух родителей.
1 Это на самом деле не страшно.
2 Это тоже не волшебство;см. ниже.
Как работает автоматическое слияние Git
Давайте кратко рассмотрим git merge
и конфликты слияния .Если нет конфликтов, Git сам выполняет слияние.Обычно такие случаи не приводят к такого рода затруднениям, поэтому давайте посмотрим, что происходит, когда является конфликтом.
Чтобы начать слияние, Git просто сравнивает *
против K
- так же, как Git всегда сравнивает любую простую пару коммитов - чтобы выяснить, что же отличается.Затем Git сравнивает *
-vs- L
, чтобы выяснить, в чем дело.Затем Git объединяет два набора изменений .Это для слияния , или то, что я люблю называть слияние как глагол , часть процесса слияния.Объединенные изменения должны применяться к моментальному снимку в коммите *
.
Помните, что каждый коммт держит снимок.В коммите *
все файлы находятся в том состоянии, в котором они находились во время создания *
.В коммите K
все файлы находятся в каком-то другом состоянии, а в коммите L
все файлы находятся в третьем состоянии.Могут даже быть файлы в K
, которых нет в *
, и / или в L
, которых нет в *
и т. Д., Но обычно большинство файлов находятся в основном во всех трех входах.
Предположим, «мы» означает людей, которые работали по линии K
, а «они» означает людей, которые работали по линии L
.Мы изменили файлы A, B и C. Измененные файлы B, C и D. Затем Git просто берет все наши изменения в A и все их изменения в D. Эта часть проста, потому что мы не касались D иони не касались A. Эта часть слияния выполнена.
Теперь Git выясняет, какие строки мы изменили в файле B, а какие строки они изменены в одном файле.Если наши строки вообще не перекрывают свои строки - обратите внимание, что Git рассматривает «просто касание» как иногда перекрывающееся - тогда Git может просто применить оба изменения к файлу B из коммита *
.Эта часть слияния уже выполнена.
Git выясняет, какие строки мы изменили в C, и какие строки они изменили.Ох, на этот раз мы оба изменили одинаковые строки.Git записывает в рабочее дерево комбинацию изменений с маркерами конфликта и объявляет объединение конфликтным .
с момента объединения конфликтует, Git останавливается и получает помощь от человека, который выполняет слияние.Это их работа, чтобы исправить это.Есть много способов исправить это, но все они заканчиваются одинаково: тот, кто делает исправление, записывает правильную версию файла C
в рабочее дерево и запускает git add C
, чтобы сообщитьGit: это правильный результат.
Git не проверяет, что они написали, он просто берет все, что они положили в окончательный файл.Если они полностью все испортили, например, полностью выбросив ваш код, Git с этим согласен!Git предполагает, что они знают, что делают.
Теперь они запускают git commit
или git merge --continue
, а Git использует завершенный снимок слияния для создания коммита слияния M
, который выглядит следующим образоммы его нарисовали.
Вернемся к вашей проблеме
Итак, давайте вернемся к нашему средству просмотра коммитов.Вы просите его просмотреть коммит M
.Он показывает вам метаданные как обычно - имя того, кто сделал коммит, и так далее.Он может показывать или не отображать оба родительских хеш-идентификатора, в зависимости от программы просмотра.Вероятно, он показывает сообщение в журнале, которое человек, который запустил git merge
, использовал для записи , почему они выполнили слияние и сохранили все важные заметки.Если бы этот человек был очень усердным, сообщение в журнале могло бы быть даже полезным ... но, увы, большинство людей используют автоматически сгенерированное, в основном бесполезное сообщение журнала: "объединить ветвь ...".
Теперь вашзритель должен показать, что вы хотите изменить в этом коммите.Но теперь есть проблема.Чтобы показать, что изменило , зритель должен посмотреть на родительский коммит и сравнить.Нет одного родителя.Есть два родителей.Какой из них будет использовать зритель?
Фактический ответ здесь зависит от зрителя.Некоторые зрители просто сдаются и показывают вам ничего .Например, git log -p
делает именно это.Похоже, вы можете использовать этот вид зрителя.Другой зритель, который запускает git show
, пытается быть полезным: он фактически сравнивает слияние M
с обоими родителями, K
и L
.Но, увы, этот зритель пытается быть слишком полезным.Он касается мест, где слияние могло иметь конфликты слияния , так что он не отображает любые файлы, где файл в M
точно соответствует или один в K
или один в L
.
Если человек, который произвел слияние , сделал это неправильно путем выброса некоторых изменений файла, которые должны были в M
, этот вид зрителя также будет выбросить эти изменения с дисплея. В этом случае файл C точно соответствует их копии из commit L
. Так что git show
, как средство просмотра слияния, не покажет вам файл C .
(Использование git log
в качестве средства просмотра коммитов, конечно, еще хуже: оно не показывает ни A, B, C, ни D, даже если это четыре файла, в которых произошли некоторые изменения.)
Вы можете поручить git log
(и git show
) разбить коммит слияния на два виртуальных коммита. То есть дано:
...--K
\
M
/
...--L
вы можете получить их притвориться , что они имеют:
...--K--M1
...--L--M2
и покажет вам сначала K
-vs- M1
, затем L
-vs- M2
. Это часто несколько полезно для этих случаев. Для этого добавьте -m
к git log
или git show
. (Обратите внимание, что M1
и M2
никогда не заходят в репозиторий, они просто делают предварительные коммиты во время части "покажи мне разницу" при просмотре коммит-слияния.)
Суть как бы
Если кто-то сделает плохой снимок слияния, многие зрители просто не покажут вам это. Чтобы найти его, нужно посмотреть коммиты до и после слияния. Если кто-то продолжает делать это, вам нужно научить его, как правильно объединяться. Редко выбрасывают свои изменения и используют мое вместо правильно. Git предлагает это как вариант, но они должны использовать его осторожно, а не только потому, что это решает их конфликты.