Git Merge конфликт (гипотетический сценарий) - PullRequest
0 голосов
/ 19 декабря 2018

Привет, я довольно новичок в использовании git, но мне было интересно, как будет решен следующий гипотетический пример:

Есть два человека (A и B), участвующих в удаленном репо на одной ветви (мастер).Мы собираемся предположить, что B обновил репо, а A нет.А только что изменил новую строку кода и только что попытался продвинуться в репо, и ему сообщили, что ему нужно потянуть, чтобы быть в курсе событий.При вытягивании он сталкивается с конфликтом слияния и может его решить.После этого А успешно продвигается в репо.Теперь Б понимает, что обновление А важно, и решает, что ему нужно вытащить из репо, но у него уже есть локальные изменения, поэтому он просто прячется на время.После извлечения и последующего применения тайника B понимает, что он встречается с тем же конфликтом слияния, с которым встречался ранее.Теперь мой вопрос в этом сценарии: должны ли люди, вытаскивающие из репо, снова решать конфликт слияния, даже если другой человек уже делал это раньше, или есть другой способ обойти это?

Большое спасибо за любую помощь.

1 Ответ

0 голосов
/ 19 декабря 2018

Этот случай никогда не происходит.Причина в том, что один из входов операции объединения автоматически изменился.Тем не менее, человек 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, и выздоравливать от этого - боль.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...