Этот случай никогда не происходит.Причина в том, что один из входов операции объединения автоматически изменился.Тем не менее, человек B - которого я назову Бобом ниже - может увидеть конфликты слияния.
Помните, что слияние в Git - это действие с участием коммитов.То есть, когда мы выполняем слияние - форму глагола для слияния - мы находим некоторые коммиты и выполняем нашу работу.Также обратите внимание, что каждый коммит представляет собой полный снимок: всех файлов, замороженных во времени на момент этого конкретного коммита.Если вы хотите посмотреть, как выглядели ваши файлы вчера, в прошлом году или что-то еще, они есть в коммитах, сделанных в этот день.
Есть три входных данныхк операции слияния!Мы, как человек, управляющий Git, указываем один из них напрямую: например,
$ git merge origin/develop
.Имя origin/develop
преобразуется в какой-то конкретный идентификатор фиксации - большую некрасивую строку хеш-идентификатора, такую как 5d826e972970a784bd7a7bdf587512510097b8c7
, которая является фиксацией, которую мы имеем в нашем репозитории, которую мы получили ранее, вероятно, запустив git fetch
.(Помните, что git pull
- это, по сути, вспомогательная команда, означающая: запустить git fetch
для меня, затем, как только она закончится, запустить вторую команду Git, обычно git merge
, для меня .)
Второй вход для слияния - независимо от текущего коммита .То есть до того, как мы запустили git merge
(возможно, через git pull
), мы выполнили команду git checkout
:
$ git checkout develop
Это выбирает последний коммит в ветви , потому чтоопределение имени ветви равно «последний коммит в ветви».Это означает, что имена ветвей со временем меняются , что означает, что они означают .Чтобы увидеть, какой коммит master
находится сейчас, или какой коммит develop
прямо сейчас, вы можете запустить git rev-parse master
или git rev-parse develop
- который будет выдавать текущий идентификатор хеша.Запустите это снова позже, когда будет больше коммитов, и вы получите другой хэш-идентификатор.
Рисование истории
Итак, с учетом этого всегда стоит нарисовать График коммитов .График коммитов - это история в репозитории, потому что история в Git - не что иное, как коммиты.
Если вы ранее не рисовали графы коммитов, это потребует некоторой практики.Есть много способов нарисовать их - или просмотреть их в браузере или графическом интерфейсе, хотя у меня есть здоровое недоверие к графическим интерфейсам, потому что многие из них лгут (обычно по веским причинам, связанным с хорошей производительностью, но тем не менее, этораздражает).Есть много способов сделать это, но мне нравится делать это горизонтально для сообщений StackOverflow:
o--o--o <-- develop (HEAD)
/
...--o--*
\
o--o <-- origin/develop
На этих чертежах самые последние коммиты находятся справа, а время движется назад влево, имена ветвей указывают на самые правые (самые новые) коммиты, потому что по определению имя является последним коммитом в его ветке.Мы - и Git - должны начать с конца и перейти назад к более ранним коммитам, чтобы увидеть, как все происходит.
Из этого рисунка видно, что у нашего develop
есть три коммита - раунд o
узлы - на нем, которых нет в общей истории, в то время как у нашего origin/develop
есть два коммита, которых нет в общей истории.
Общая история начинается с коммита *
и продолжается во времени назад.Коммит *
, на этом чертеже, является базой слияния коммитов develop
и origin/develop
. Когда Git выполняет истинное слияние - при слиянии часть процесса слияния - три входа являются базой слияния, HEAD
commit (* 1086)*) и другой коммит (--theirs
).
То, как Git делаетслияние теперь на самом деле довольно просто увидеть.Поскольку каждый коммит является полным снимком, Git начинает с извлечения базового снимка слияния во временную область.Затем он извлекает наш коммит - ну, он уже там, на самом деле - и их коммит, и теперь он запускает два отдельных сравнения , которые мы можем воспроизвести одно привремя мы сами используем git diff
.Сначала мы находим хеш-идентификатор commit *
(возможно, посмотрев на график), затем запускаем:
git diff --find-renames <hash-of-*> HEAD # what we changed
git diff --find-renames <hash-of-*> origin/develop # what they changed
Все, что git merge
сейчас должен сделать, это объединить эти изменения .Начните с базовых файлов.Затем, где бы мы ни меняли файл, если они не меняли этот файл, используйте наш.Где бы они ни меняли файл, а мы нет, используйте их.Везде, где мы оба изменили один и тот же файл , объедините изменения.
Конфликты слияния возникают, если / когда комбинация двух наборов изменений пытается изменить одинаковые строки одинаковых файлов.В этом случае Git оставляет все три исходные копии файла (скрытые в Git index ) и прилагает все усилия для объединения изменений и некоторых маркеров конфликта в рабочем дереве.
Если Git вообще не сталкивается с конфликтами, Git делает новый коммит.Или, после того, как вы вручную исправили беспорядок, оставленный Git в случае возникновения конфликтов, вы сделаете окончательный коммит результата.Этот коммит выглядит немного странно на нашем графике, потому что он имеет два предыдущих коммита, а не обычный.Я дам этому новому коммиту письмо-идентификатор (не настоящий хеш-код, просто замену), M
:
o--o--o--M <-- develop (HEAD)
/ /
...--o--* /
\ /
o----o <-- origin/develop
Этот тип коммита слияние или объединяются в существительное: коммит с двумя родителями, причем два родителя - старый HEAD
(родитель # 1), а другой коммит (# 2).
Теперь предположим, что ваши персоны A (Алиса) и B (Боб) продолжают работать
Вы предлагаете Алисе выполнить слияние, а затем запустить git push
.Когда Алиса делает это, предполагая, что ее git push
успешно, она получает еще один репозиторий Git для вызова commit M
кончика его develop
.Возможно, этот третий репозиторий находится на GitHub, например:
o--o--o
/ \
...--o--o M <-- develop (HEAD)
\ /
o-----L
Я нарисовал это немного по-другому, но это тот же набор коммитов.Здесь нет origin/develop
, только коммит слияния M
, ведущий к обоим предыдущим коммитам.Я также снял отметку со старой точки обмена и дал письмо L
для текущего коммита Боба по состоянию на данный момент, потому что нам скоро понадобится поговорить об этом.
Теперь Боб запускает git fetch
в своем хранилище, чтобы забрать работу Алисы.Хотя у Боба нет новых коммитов, он приобретает новые.Обратите внимание, что его develop
уже существует и в настоящее время указывает на фиксацию L
:
o--o--o
/ \
...--o--o M <-- origin/develop
\ /
o-----L <-- develop (HEAD)
Обратите внимание, что у Боба develop
конец его ветви!Если у него есть незафиксированные изменения, он может запустить git stash
, чтобы сохранить их в коммите - на самом деле это два коммита, но давайте пока представим только один - это на ветке no :
o--o--o
/ \
...--o--o M <-- origin/develop
\ /
o-----L <-- develop (HEAD)
\
S [stash]
Теперь Боб может запустить git merge --ff-only
(или git pull
, который сделает еще одну выборку, которая ничего не делает, так как нет ничего новее, чем M
, а затем выполнит ускоренную перемотку "слияние"), чтобы сделать Боб develop
указывает на фиксацию M
:
o--o--o
/ \
...--o--o M <-- develop (HEAD), origin/develop
\ /
o-----L
\
S [stash]
Боб теперь может применить и уронить (или «выбросить») тайник, чтобы внести изменения в свою работу.дерево.Если это сработает, они будут готовы добавить и зафиксировать.Затем Боб делает новый коммит, который мы можем нарисовать как N
:
o--o--o
/ \
...--o--o M--N <-- develop (HEAD), origin/develop
\ /
o-----L
Содержимое N
уже содержит содержимое M
, потому что git stash apply
заняло S
, по сравнениюэто к его родителю, а затем сделал те же изменения к тому, что было в M
.
Когда Боб видит конфликт
ThТочка, в которой Боб видит конфликт, находится не во время извлечения и быстрой пересылки, а в момент, когда Боб запускает git stash apply
(или git stash pop
, который начинается с запуска git stash apply
).Это, на самом деле, запускает еще одно слияние!Но он выполняет слияние, которое не будет заканчиваться коммитом merge .Это только делает для слияния глагольной части действия.
Это слияние имеет коммит S
в качестве последнего, "другого коммита" ввода.Средний вход - HEAD
- коммит M
- как текущий коммит, как обычно, и первый вход - это основа слияния двух, то есть коммит L
:
o--o--o
/ \
...--o--o M <-- develop (HEAD), origin/develop
\ /
o-----L
\
S [stash]
Git сравнивает(diffs) L
и M
, чтобы выяснить, что "мы" сделали.(Конечно, это действительно то, что Алиса сделала, более или менее, но Git все равно.) Он сравнивает L
с S
, чтобы увидеть, что сделали "они" (на самом деле, Боб).Затем он объединяет или пытается объединить два набора изменений точно так же, как при обычном слиянии.
Если все идет хорошо, git stash
на этом останавливается.Нового коммита пока нет.Если все идет плохо, git stash
останавливается с конфликтом слияния, оставляя Бобу разрешить конфликт слияния.Когда Боб готов, следующий коммит, который делает Боб, будет обычным, а не коммитом слияния.
Если Боб использует git stash pop
и возникает конфликт слияния, Git останавливается после шага apply
, иУ Боба все еще есть тайник: он должен запустить git stash drop
, чтобы сбросить его после того, как исправит беспорядок.Но если Git считает, что слияние прошло хорошо, и Боб запустил git stash pop
, Git запустит git stash drop
для Боба.Поэтому нужно быть немного осторожнее с git stash
: иногда он применяет и применение, и удаление, но иногда происходит сбой после применения и не выполняет удаление.
(я в основном рекомендую избегать git stash
, но если вы собираетесь его использовать, примените и отбросьте отдельно, чтобы избежать случайного сброса тайника, если он применяется каким-либо образом Git считает, что это чисто, но на самом деле было ошибкой с вашей стороныЭто действительно случается, даже с экспертами Git, и выздоравливать от этого - боль.)