Когда Git сделает успешное слияние с конфликтом? - PullRequest
1 голос
/ 19 июня 2019

Если у меня новый чистый проект (мастер), и я добавляю новый файл, который содержит:

Мастер: test.txt

"123"

Теперь я создаю новую ветвь с именем «MyBranch», а затем меняю содержимое файла:

MyBranch: test.txt

"193"

Теперь я хочу объединить Mybranch --> Master,

В этой ситуации я не знаю, будет ли это слияние или конфликт. Я видел ситуации, когда есть конфликт и когда происходит успешное слияние, но я все еще не нашел золотое правило.

Вопрос

Когда git вызывает конфликт против успешного слияния?

Ответы [ 3 ]

3 голосов
/ 19 июня 2019

git вызывает конфликт, когда в одной и той же части кода было два изменения в разных ветвях.

В вашей ситуации, потому что вы сделали только одно редактирование в Mybranch, при слиянии нет конфликтов.

Если бы на Мастере был новый коммит, в котором я изменил текст на "987" и зафиксировал этот код ПЕРЕД попыткой слияния, у нас возникнет конфликт.

Полезный ресурс: https://www.atlassian.com/git/tutorials/using-branches/git-merge

2 голосов
/ 19 июня 2019

В общих чертах, конфликты возникают, когда, имея две ветви с определенным общим предком, каждая из ветвей имеет различный набор изменений для данного числа строк.

Итак ....если у вас есть общий предок A, затем ветви B и C и от A до B вы удалили 5 строк из файла ... а между A и C вы изменили любую из этих пяти строк, вы получите конфликт.Есть много видов конфликтов.Модифицированы 5 строк в одну сторону, модифицированы те же пять строк в другую сторону ... Вы сливаетесь, бум!Существуют и другие виды конфликтов, такие как: редактирование 5 строк в одном файле ... удаление файла в другой ветви.Слияние: boom!

Если у вас есть набор изменений в одной ветви, а в другой - изменений нет, конфликт не будет.

1 голос
/ 20 июня 2019

Как eftshift0 говорит , но не в тех же словах, хитрость здесь в том, что в объединении есть не два, а три входа.

Вы описаливаш процесс как:

  • создать новый коммит на master
  • создать новую ветку MyBranch
  • создать новый коммит на MyBranch
  • run:

    git checkout master
    git merge MyBranch
    

Но развитие не совсем так.Обычно это выглядит так: создайте три ветви, начните работать над ними, создайте еще шесть, поработайте над некоторыми из них, удалите некоторые, создайте другие, поработайте над несколькими некоторое время, а затем - в конце концов - проверьте одну и запуститеgit merge на другом .Этот более хаотичный рабочий процесс приводит к более сложному вводу в процесс слияния, который, как я упоминал выше, действительно состоит из трех коммитов.

Из этих трех коммитов Вы можете выбрать только два .Третий, Git выбирает для вас автоматически.

Давайте уделим немного времени рассмотрению процесса создания нового коммита с чертежами.Вы:

git checkout somebranch
<edit various files>
git add <some edited files>
git commit

Шаг checkout заполняет ваш индекс - ваш предложенный следующий коммит, более или менее - и ваше рабочее дерево всеми файлами из коммита, которые находятся в текущем tip ветви somebranch:

...--F--G--H   <-- somebranch

(где каждая буква соответствует фактическому хэш-идентификатору фиксации).Родителем последнего коммита H является коммит G;Родитель G - F, и так далее в обратном направлении вниз по линии коммитов, которые «включены» (содержатся внутри) ветви.

Когда вы редактируете файлы, вы меняете работукопия.Затем вам нужно git add скопировать эти изменения обратно в индекс - git commit использует индекс (предложенный коммит, с файлами в специальном состоянии Git-ified freeze-dry), а не рабочее дерево (файлы вих обычная повседневная форма, используемая обычными не Git-программами).Последний шаг, git commit, упаковывает индексные копии всех файлов - не только тех, которые вы изменили, но также всех неизмененных файлов, которые были в индексе из-заcommit H - и сохраняет их в новом коммите, где они навсегда заморожены, чтобы в любой момент в будущем вы могли вернуться к новому коммиту.

Давайте назовем этот новый коммит I.Родитель I будет H:

...--F--G--H   <-- somebranch
            \
             I

Шаг last для git commit - обновить имя somebranchтак что он указывает на фиксацию I вместо фиксации H:

...--F--G--H--I   <-- somebranch

(и нам больше не нужен излом на чертеже).

Итак,с вашим конкретным процессом вы выбрали ветку master, и вы добавили новый коммит:

...--F--G--H--I   <-- master

Затем вы создали новую ветку name .По умолчанию это новое имя также указывает на фиксацию I.Коммиты до I теперь включены (содержатся внутри) обе ветви:

...--F--G--H--I   <-- master, MyBranch

Теперь нам нужен еще один элемент для наших чертежей.Откуда Git знает, какую ветку name использовать здесь?Ответ заключается в том, что Git присваивает специальное имя HEAD во всех подобных столицах одному из названий веток.Это ветвь, в которой вы находитесь, в описании git status, когда написано on branch master или on branch MyBranch.Допустим, вы создали и попали на MyBranch, поэтому у нас есть:

...--F--G--H--I   <-- master, MyBranch (HEAD)

Теперь вы создаете еще один новый коммит.Это получает большой уродливый хэш-идентификатор, но мы просто назовем его J.Как и раньше, Git сделает новый коммит из того, что есть в вашем индексе, поэтому вы редактируете файлы, git add их, чтобы скопировать их обратно в индекс, и git commit, чтобы сделать J:

...--F--G--H--I   <-- master
               \
                J   <-- MyBranch (HEAD)

(Обратите внимание, что HEAD остается присоединенным к обновленной ветви.)

Сейчас можно запустить git checkout master; git merge Mybranch - но это слияние не очень интересно. Это слишком тривиально. Здесь никогда не будет никакого конфликта слияния. Фактически, по умолчанию git merge заметит, что объединение не требуется, и вместо этого выполните ускоренную перемотку . Вам нужно будет запустить git merge --no-ff, чтобы заставить его слиться (что бы хорошо).

Вместо слияния прямо сейчас, давайте сначала сделаем новый коммит на master. На самом деле, просто для того, чтобы слияние получило букву M, давайте сделаем два коммитов на master, так что у нас есть:

                K--L   <-- master (HEAD)
               /
...--F--G--H--I
               \
                J   <-- MyBranch

Теперь, когда мы запускаем git merge MyBranch, Git должен сделать какую-то реальную работу.

Первая часть заключается в поиске трех входных коммитов:

  • Один из этих трех коммитов - тот, на котором вы сейчас находитесь - коммит L, или master, или HEAD (оба имени, а также необработанный идентификатор хеша, выберите конкретный коммит) , Файлы этого конкретного коммита будут в вашем индексе и рабочем дереве, без изменений в индексе или рабочем дереве (git merge проверяет, что это так, и в целом не позволит вам начать процесс слияния с «грязным» "индекс или рабочее дерево).

  • Одним из этих трех является коммит, который вы назвали: в git merge MyBranch вы назвали коммит J.

  • Последний коммит - тот, который вы не можете выбрать - это то, что Git называет базой слияния , и это общая начальная точка , из которой два ветви разошлись. Благодаря тому, как мы их рисовали, легко увидеть, что это за коммит: это коммит I.

    Когда мы начинаем с commit L и работаем в обратном направлении (как это делает Git), мы перечисляем коммиты L, затем K, затем I, затем H и так далее. Между тем, когда мы начинаем с коммита J и работаем в обратном направлении, мы перечисляем коммиты J, затем I, затем H и т. Д.

    Общие коммиты - это коммит I, H, G, F и т. Д., Но общий коммит best является последним из них, или коммит I , Так что commit I является базой слияния.

Теперь, когда git merge нашел три входа, его основная работа начинается:

  • Сравнить коммит I - точнее, снимок в нем - для фиксации L снимка. Все, что здесь отличается, это вещи мы изменились на master:

    git diff --find-renames <hash-of-I> <hash-of-L>
    
  • Сравнить коммит I с коммитом J. Что бы здесь ни отличалось, вещи они (ок, "мы") изменились на MyBranch:

    git diff --find-renames <hash-of-I> <hash-of-J>
    
  • Объедините результаты этих двух сравнений.

  • Применить объединенные изменения к снимку в коммите I.

Если все идет хорошо, объединенные изменения - те, которые взяли снимок с I до L, плюс те, которые взяли его с I до J - теперь и в индексе, и в рабочем дереве копии каждого файла, и git merge фиксирует результат, как если бы git commit, но делает коммит, который запоминает и L и J:

                K--L
               /    \
...--F--G--H--I      M   <-- master (HEAD)
               \ ___/
                J   <-- MyBranch

Тот факт, что при слиянии используется фиксация I, не записывается напрямую, а подразумевается: база слияния коммитов L и J равна I и всегда будет всегда, потому что каждый бит информация внутри любого коммита замораживается навсегда. Факты таковы, что родитель L - K, а K - I, например: эти факты нельзя изменить. Родители M теперь L и J, и их нельзя изменить. Никакая часть какого-либо коммита не может быть изменена никогда: самое большее, мы можем сделать новые и улучшенные (и различные) коммиты и просто прекратить использовать старые, но мы не можем изменить старые.

Вы получаете конфликты слияния, когда дела не идут хорошо. Например, если diff от I до J говорит об изменении строки 10 файла test.txt с ABC на XYZ, а diff с I на L говорит об изменении строки 10 файл test.txt из ABC в AEIOU, эти изменения конфликтуют. Git будет:

  • оставить все три входных файлов в индексе (в специальных высокоразмерных промежуточных слотах , которые мы здесь не будем рассматривать) и
  • оставить беспорядочную версию файла с конфликтом слиянием в рабочем дереве, где вы можете просматривать и работать с ним / с ним.

Слияние не закончено , но и не завершено . Факт незавершенного слияния теперь записывается в двух местах:

  • в индексе, потому что заняты ненулевые промежуточные слоты; и
  • в файле, содержащем хеш-идентификатор commit J, так что возможный шаг слияния "make commit" знает, какой хеш-идентификатор следует включить в качестве другого родителя. (Первый родительский хэш-идентификатор L доступен через HEAD как всегда.)

Ваша работа становится: Создайте правильную комбинацию для этого файла и запишите ее в индекс на staging-slot-zero, чтобы объявить, что этот файл разрешен . Команда git add позаботится о второй части - записи в указатель - но вы должны придумать правильный контент самостоятельно или с помощью инструмента слияния.

После разрешения всех конфликтов вы запускаете:

git merge --continue

или

git commit

, которые оба делают одно и то же в этом случае: git merge --continue гарантирует, что есть текущее слияние, которое может быть завершено, и если это так, буквально просто запускает git commit (на данный момент в любом случае - Git-for- Пользователи Windows продолжают вставлять одну команду в другую, вместо того, чтобы запускать другую, потому что Windows, очевидно, требуются часы ok ... хорошо, десятые доли секунды? ..., чтобы связать одну команду с другой, против микросекунд в Linux).

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