Как eftshift0 говорит , но не в тех же словах, хитрость здесь в том, что в объединении есть не два, а три входа.
Вы описаливаш процесс как:
Но развитие не совсем так.Обычно это выглядит так: создайте три ветви, начните работать над ними, создайте еще шесть, поработайте над некоторыми из них, удалите некоторые, создайте другие, поработайте над несколькими некоторое время, а затем - в конце концов - проверьте одну и запустите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).