Это никогда не вопрос возраста . Для слияния это всегда вопрос баз слияния .
У каждого коммита есть родитель, а в случае коммитов слияния - два или более родителей. Истинное имя каждого коммита - это его хэш-идентификатор - большая уродливая строка букв и цифр, такая как 83232e38648b51abbcbdb56c94632b6906cc85a6
. Это истинное имя позволяет Git найти фактический коммит. Сам коммит хранит хэш-идентификатор своих родителей, что позволяет Git находить те коммиты. Они хранят хэш-идентификаторы своих родителей по очереди и так далее. В результате для большинства коммитов существует простая линейная обратная цепочка:
... <-F <-G <-H
, где H
- некоторый хэш-идентификатор коммита, а родительский элемент H
- G
, родительский - F
и т. Д.
Эта обратная цепь, которая в основном линейная, - это история. История в Git - это просто серия коммитов. Имя ветви, например master
, просто содержит один идентификатор хэша: идентификатор последнего коммита в цепочке. Таким образом, мы можем нарисовать выше, как:
...--F--G--H <-- master
У каждого коммита есть полный и полный снимок всех файлов - ну, все файлы на момент, когда вы сделали коммит, потому что, когда он сделан, каждый коммит замораживается во времени.
Ответвления происходят, потому что два разных коммита имеют один общий коммит в качестве (общего) родителя:
I--J <-- branch1
/
...--F--G--H
\
K--L <-- branch2
Git будет запускаться при каждом коммитном коммите - в данном случае J
и L
- и работать в обратном направлении. При запуске из обеих веток и в обратном направлении, две ветки получают общий общий коммит H
.
Если вы выберете одну ветку для git checkout
, вы получите один коммит:
I--J <-- branch1 (HEAD)
/
...--F--G--H
\
K--L <-- branch2
Git прикрепил HEAD
к имени branch1
, что означает, что commit J
- это зафиксированный вами коммит: branch1
идентифицирует commit J
.
Теперь вы запускаете git merge branch2
. Git находит commit L
, потому что имя branch2
указывает на L
. Начиная с J
и L
, Git работает в обратном направлении, один коммит за раз, чтобы найти общий коммит H
. Это база слияния .
Git теперь сравнивает снимок в H
- тот, с которого вы, на branch1
, начали - со снимка в J
, который вы, вероятно, также сделали и используете сейчас. Что бы здесь ни было отличается , это то, что вы изменили. Если вы изменили , измените myConstant = 26
на myConstant = 27
, это ваше изменение в этом конкретном файле. Если вы не изменили myConstant
вообще, вы сделали нет изменения.
Git также сравнивает снимок в H
- который является лучшим, с которого они, на branch2
, начали с того, с чего вы также начали (коммиты F
и G
тоже сработали бы, но они явно не так хорошо) - чтобы они совершили L
, чтобы увидеть, что они изменили.
Сделав сравнение с H
до J
и снова с H
до L
, Git теперь знает, что you изменилось, и что они изменились. Не имеет значения , когда вы изменили вещи, или , когда они изменили вещи. Все, что имеет значение, это то, изменил ли кто-то что-то В любом случае, Git извлекает все файлы из H
(не J
или L
), применяет оба набора изменений к ним и - если нет конфликтов - фиксирует результат:
I--J
/ \
...--F--G--H M <-- branch1 (HEAD)
\ /
K--L <-- branch2
Поскольку вы находитесь на branch1
, новый коммит M
продолжается branch1
. Поскольку это последний коммит, Git обновляет имя branch1
, чтобы идентифицировать коммит M
. Между тем, тем не менее, коммит M
имеет не один, а два родительских коммитов: J
, ваш предыдущий совет и L
, который по-прежнему является их ветвлением.
Теперь предположим, чтона вас и они внесли некоторые изменения в myConstant
. Затем, при попытке объединить ваши два изменения, Git объявит конфликт слияния . Это оставит беспорядок в рабочем дереве (и индексе) для того, кто бы ни работал git merge
, чтобы исправить. Затем human должен выяснить, что поместить в коммит M
, отредактировав конфликтующие файлы и исправив эту строку. Что бы ни делал человек, Гит предполагает, что это правильный результат. Скажем, человек выбирает 26 вместо 27 и совершает это. Commit M
теперь говорит myConstant = 26
, а Git уверен, что это правильно , даже если это не так. Или мы можем сказать, что человек выбирает 27 вместо 26 и совершает это; теперь Git уверен, что 27 правильно.
Предположим, что вы и они продолжают совершать коммиты:
I--J
/ \
...--F--G--H M--N--O <-- branch1
\ /
K--L--P--Q--R <-- branch2
Если вы сейчас выберете одну из этих веток для проверки, вы получите коммит O
или коммит R
. Допустим, вы выбрали O
и снова запустили git merge branch2
.
Git теперь начинается с O
и работает в обратном направлении: O
, N
, M
, оба- J
-and- L
.... И он начинается с R
и работает в обратном направлении: R
, Q
, P
, L
, .... Обратите внимание, что оба они достигают коммита L
. Commit L
является базой слияния, а не M
, L
. Так что Git будет отличаться от M
против O
, чтобы увидеть, что вы изменили. Это может включать изменение 26 на 27 или - если M
имеет неправильную константу - вообще ничего не делать. Затем он сравнивает L
с R
, чтобы увидеть, что они изменили.
Как и прежде, Git объединяет эти изменения, и если вы и они коснулись одних и тех же строк одного и того же файла, вы получите конфликт слияния. Вы очищаете его и делаете слияние обязательным. Если нет, и Git успешно все комбинирует самостоятельно, Git немедленно делает коммит слияния. В любом случае у вас теперь есть:
I--J
/ \
...--F--G--H M--N--O--S <-- branch1 (HEAD)
\ / /
K--L--P--Q--R <-- branch2
где S
- ваш результат слияния. Константа в этом файле устанавливается, однако вы, или Git, устанавливаете ее в зависимости от того, был ли конфликт и / или были ли вы и / или они изменили эту строку при сравнении L
(базы слияния) с O
и R
(две стороны слияния).
Найти того, кто что-то изменил, легко, кроме случаев, когда это трудно
В Git есть несколько инструментов, с помощью которых вы можете попытаться выяснить, кто установил myConstant
на любое значение. Два больших из них - git log
и git blame
. Оба начинают с некоторого коммита, чтобы увидеть, что находится в этом коммите, а затем работают назад, по одному коммиту за раз. Сравнивая то, что в родительском коммите с тем, что находится в child , Git может увидеть, скажем, что коммиты N
и O
имеют разные строки. Если это так, Git может сказать вам, что тот, кто сделал O
, изменил эту строку.
Но когда Git работает в обратном направлении через слияние, такое как S
или M
, , какой родитель должен сравнивать Git с S
? Ответы Git (множественное число) на это довольно сложно. Некоторые команды просто не мешают сравнивать - например, это то, что делает git log -p
. Другие выбирают одного из родителей и продолжают этот участок слияния, то есть, начиная с S
, возвращаются только к R
или только O
. Некоторые из них, такие как git show
, могут показать вам комбинированный дифференциал , который, в общем, показывает только то место, где произошел конфликт слияния (комбинированные дифференциалы опускают все файлы, в которых не было материалов с обеих сторон). ).
Поскольку такого рода проблемы часто возникают при конфликтах слияний, комбинированные различия часто несколько полезны для нахождения источника. Это не обязательно очень полезно, хотя: более полезно использовать git bisect
для автоматического поиска хороших и плохих фиксаций. Вы объявляете один коммит «хорошим» (если код работает / является правильным), а последующий коммит «плохим» (где код не работает / является неправильным), и Git автоматически выполняет поиск между этими двумя коммитами, работая с ответвлением. и-объединить структуры в графе фиксации, все автоматически.
ItТакже иногда полезно использовать git log -m -p
. То, что это делает, это разделить каждое слияние. Вместо того, чтобы рассматривать S
и M
как один коммит каждый, он делает вид, с целью git diff
-ing создать патч, что есть один S1
коммит с родителем O
и второй S2
совершить с родителем R
. Затем, достигнув M
, он делает вид, что есть один M1
коммит с родителем J
и второй M2
коммит с родителем L
. Каждое из этих разделенных коммитов получает простой, не -комбинированный diff для своего (теперь одинокого) родителя, показывая вам, как результат слияния отличается от этого одного родителя.
Все это полезные инструменты, чтобы увидеть, что произошло. Тем не менее, они не предотвратят ошибочные слияния в будущем: все, что может сделать Git, - это сравнить базу с двумя советами, чтобы увидеть, что вы изменили против того, что они изменили. Не имеет значения , когда: только что изменилось имеет значение.
Редактировать: Форматирование