Вы правы. Вишневый кирк технически является операцией слияния - слияние как глагол , как мне нравится это выражать.
Большинство пользователей Git вскоре знакомятся с командой git merge
. Команда слияния может запустить эту операцию слияния, это слияние как идею глагола . Он также может производить коммит слияния , который использует слово слияние в качестве прилагательного, модифицирующего существительное commit . Фраза коммит слияния часто сокращается до слияния , в котором слово слияние используется как существительное. Важно не путать это слияние как существительное - коммит, имеющий тип слияние - с процессом, который его производит: действие слияния, слияние как глагол, даже если git merge
делает оба (в некоторых случаях). Причина, по которой важно разделять эти идеи, заключается в том, что другие команды Git выполняют действие, слияние как глагол , без коммита merge . Команда git cherry-pick
является одной из таких команд.
Понимание регулярных слияний
Тем не менее, чтобы понять, что означает сделать слияние - слияние как операция глагола - я думаю, полезно начать с того, что делает git merge
. Процесс слияния, как в git checkout branch1; git merge branch2
, включает в себя сначала поиск базы слияния коммит, так что для каждого слияния имеется три входа.
Предположим, что над проектом работают два разных программиста. Как обычно, мы предполагаем, что программиста A зовут Алиса, а программиста B зовут Боб. Алиса и Боб начинают с одного и того же хранилища и ветки и в конечном итоге делятся друг с другом своими новыми коммитами. Исходная ветвь, возможно master
, представляет собой прямую линейную серию коммитов, с более новыми коммитами справа:
...--F--G--H <-- master
Каждая заглавная буква здесь обозначает фактический хэш-идентификатор коммита.
Алиса теперь клонирует репозиторий, чтобы у нее были те же самые коммиты, и создает свою ветку alice
. В этой ветке она делает два новых коммита:
I--J <-- alice
/
...--F--G--H <-- origin/master
Боб также клонирует хранилище, так что он получает коммиты через H, и создает свою ветку bob
. На этой ветке он делает два коммита:
...--F--G--H <-- origin/master
\
K--L <-- bob
Помните, что каждый коммит имеет уникальный хэш-идентификатор , но каждый Git повсюду во вселенной согласен с тем, что идентификаторы хэша коммитов Алисы верны для двух коммитов Алисы, и идентификаторы хэша коммитов Боба верны для Боба. Таким образом, мы используем разные буквы I-J
для Алисы, чем K-L
для Боба. Когда мы помещаем эти две ветки в любой репозиторий Git, они сохраняют свои хэш-идентификаторы коммитов, поэтому мы в конечном итоге объединяем их так:
I--J <-- alice
/
...--F--G--H <-- master
\
K--L <-- bob
Кто бы ни контролировал , этот репозиторий может git checkout alice; git merge bob
или git checkout -b merged alice; git merge bob
. Просто для удовольствия, давайте сделаем последнее. Мы не потрудимся нарисовать master
(но имя все еще существует, все еще указывая на коммит H
).
I--J <-- alice, merged (HEAD)
/
...--F--G--H
\
K--L <-- bob
Так как merged
является текущей веткой (извлечено), вот где имя HEAD
прикреплено. Оба имени alice
и merged
идентифицируют коммит J
, когда мы запускаем git merge bob
.
Выбрав коммиты J
и L
для объединения, мы сообщаем Git, что он должен автоматически найти лучший общий коммит. Это коммит H
: тот, с которого начинали Алиса и Боб. Технически база слияния - это самый низкий общий предок (LCA) направленного ациклического графа (DAG), сформированный коммитами, и для нахождения LCA DAG используется алгоритм, который я описывал много раз ранее, но здесь это довольно очевидно визуально что лучший общий коммит это просто коммит H
.
Итак,обнаружив надлежащий базовый коммит слияния, Git теперь сравнивает снимок, связанный с коммитом H
, с двумя моментальными снимками, связанными с коммитами J
и L
. Мы можем заставить Git делать это так, чтобы мы , как люди, могли читать. Слияние делает это внутренним способом, который удобнее читать программам, но эффект тот же. Чтобы увидеть это для себя, мы бы использовали:
git diff --find-renames <em>hash-of-H</em> <em>hash-of-J</em>
: это говорит нам о том, что изменила Алиса. Это включает построчное внесение изменений в определенные файлы, а также во вновь созданные файлы, полностью удаленные файлы и любые обнаруженные переименованные файлы. С git diff
каждый из них распечатывается так, чтобы мы могли их видеть. 1 Для git merge
, Git просто сохраняет изменения в удобном для Git месте.
Обратите внимание, что мы вообще не смотрим на промежуточные коммиты. Мы просто сравниваем H
с J
, чтобы увидеть общий эффект.
git diff --find-renames <em>hash-of-H</em> <em>hash-of-L</em>
: это говорит нам, что Боб изменил. Процесс идентичен коммитам Алисы: мы не смотрим на что-то среднее, только начальную общую базу слияния H
и конечный коммит L
, чтобы увидеть, что сделал Боб.
То, что git merge
делает дальше, является сердцем слияния как глагола процесса. Git объединяет изменения. Начиная со снимка в базе слияния , Git применяет все изменения Алисы и все изменения Боба. Когда и где они конфликтуют, Git делает несколько вещей:
Он помещает все три входных файла в Git's index , который также называется staging area . Эти три файла идут в пронумерованных промежуточных слотах : слот № 1 предназначен для копии файла из базы слияния H
, слот № 2 предназначен для копирования файла из текущего коммита J
и слот № 3 предназначен для копирования файла из другого коммита L
.
В некоторых случаях, например, если Алиса удалила файл, в котором Боб изменил тот же файл, он помещает в индекс только два файла. Это также происходит при конфликте «добавить / добавить»: в H
не было файла, и Алиса и Боб создали файл с одинаковым именем. (Для сложных случаев переименования в Git есть небольшая ошибка / ошибка, в которой индексные копии имеют несколько имен, и все становится слишком сложным. К счастью, этот случай довольно редкий.) Но в целом конфликты помещают все три файла в индекс.
Затем, снова для случая конфликта, Git делает все возможное при слиянии и оставляет частично слитый результат, а также не объединенные разделы входных файлов, окруженные маркерами конфликта, в work-tree , в файле вы можете посмотреть и отредактировать. Помните, что файлы в индексе невидимы: вы должны скопировать их из индекса, прежде чем сможете их вообще использовать.
Если вы поклонник git mergetool
, то именно это и делает git mergetool
: он копирует три входных файла из индекса в рабочее дерево, где вы можете их просматривать и работать с ними. Затем он запускает любой нужный вам инструмент слияния, чтобы вы могли видеть все эти три файла, плюс, конечно, лучшее, что Git объединил в файл рабочего дерева. (Для себя я обычно предпочитаю установить merge.conflictStyle
на diff3
и просто работать с полученной копией рабочего дерева.)
Обратите внимание, что вы можете ссылаться на файлы в слоте 2 из текущей или HEAD
фиксации, используя --ours
:
git checkout --ours path/to/file.ext
Вы можете обращаться к файлам в слоте 3 из другого коммита, используя --theirs
. В слоте 1 нет сокращения (хотя, вероятно, должно быть: --base
).
Для всех файлов, в которых есть конфликты no , Git успешно объединяет изменения Алисы и изменения Боба, или просто берет файл Алисы (где Боб не сделал изменений), или просто берет файл Боба (где Алиса) без изменений). Или, часто для большинства файлов, все три копии каждого файла - базы слияния, Алисы и Боба - все совпадают, потому что никто ничего не изменил , в этом случае любой копия файла подойдет. Эти успешно объединенные файлы с изменениями Алисы и Боба, объединенными поверх того, что было в базе, пригодны для нового коммита слияния, и они попадают в рабочее дерево и область индекса / промежуточной области, автоматически объединяемые Git.
(Обратите внимание, что Алиса и Боб также могут внести такое же изменение , например, чтобы исправить опечатку в комментарии. В этом случае Git берет всего одну копию В противном случае дублируются изменения. Это действие «взять одну копию» не считается конфликтом.)
Это завершает часть процесса слияния как глагол. Затем команда git merge
либо останавливается из-за конфликта, либо продолжает объединение как -an-прилагательное коммит слияния. Если Git останавливается с конфликтом, до you можно исправить беспорядок, оставленный Git и в рабочем дереве, и в индексе, а затем завершить процесс, сделав коммит слияния, выполнив либо git merge --continue
или git commit
(оба замечают, что завершают конфликтующее слияние, и делают окончательный коммит слияния). Мы можем нарисовать это здесь:
I--J <-- alice
/ \
...--F--G--H M <-- merged (HEAD)
\ /
K--L <-- bob
Новый коммит M
, как и любой другой коммит, состоит в том, что он имеет снимок (объединенные изменения от Алисы и Боба, примененные к базе слияния H
, составляют этот снимок) и некоторые метаданные: кто сделал коммит (вы), когда (сейчас) и почему (вводимое вами сообщение). 2 Особенность M
в том, что у него не только один родитель, но два родители. Два родителя, по порядку, сначала J
, потому что merged
указали на J
до того, как мы выполнили git merge
, а затем L
, потому что это было обязательство, которое мы объединили.
1 Вывод git diff
может (и должен) рассматриваться как набор инструкций для превращения левого коммита в правый коммит. То есть вывод git diff
может сказать: В строке 41 удалите 2 строки. Затем в строке 75 вставьте эту новую строку. Для новых файлов diff скажет создать этот новый файл с этим содержимым , а для удаленных файлов diff скажет Ожидайте старый файл с этим содержимым; удалить этот файл . Если вы начнете с рабочего дерева, в котором извлечен левый коммит, и точно выполните все эти инструкции, вы получите рабочее дерево, соответствующее правому коммиту.
Поскольку вывод diff имеет читабельный (и редактируемый) текст, вы можете, конечно, применить только часть этого текста или применить все его плюс еще. Вы также можете попытаться применить его к коммиту, отличному от левого, и это то, о чем git format-patch
и git am
.
2 Обратите внимание, что git merge
предоставляет сообщение по умолчанию, в котором говорится, что причина, по которой вы сделали слияние, было для того, чтобы сделать слияние, и, как правило, также даются имена одной ветви (ветви, которую вы слили ) или обе ветви (ту, которую вы слили, затем into
, затем ту, на которой вы были). Этот второй бит информации иногда может быть немного полезным, и часто на самом деле больше нечего сказать. Но «Функция ветвления слияния / F», вероятно, не так хороша, как «Включенная функция F», за которой следует, например, фактическое описание функции.
Теперь мы можем полностью понять вишню
Когда мы используем git cherry-pick
, мы инструктируем Git копировать некоторый коммит. Мы начнем с графика коммитов, как и раньше. Точная форма графика не так важна, как мы увидим:
...--o--o--...--A'--A--o--o--o <-- somebranch
\
o--o--B <-- branch
НетТо есть, я звоню branch здесь branch
и commit B
, так как мне нравится использовать эти однобуквенные резервные элементы для хэшей коммитов.Я позвонил родителю A
A'
, как вы и сделали в своем вопросе.
Когда мы запускаем git checkout branch
, это присоединяет наш HEAD
к имени branch
и извлекает коммитB
в наш индекс и рабочее дерево.Теперь мы можем видеть и работать с файлами из коммита B
, как обычно, на кончике branch
.
Когда мы затем запускаем git cherry-pick A
- давая хэш A
напрямую, илииспользуя имя или относительное выражение, которое находит commit A
- Git находит и commit A
, и commit A'
.Commit A'
является просто родителем A
.Он должен быть единственным родителем: если commit A
является слиянием, git cherry-pick
отказывается выбрать любого из своих нескольких родителей и выдает нам сообщение об ошибке, в котором говорится, что we mustвыбрать этого родителя самостоятельно (используя опцию -m
).Если мы выберем этого родителя вручную, например, git cherry-pick -m 1 A
, то Cherry Pick использует родителя, которого мы выбрали, но обычно мы выбираем Cherry Pick без слияния.
Git теперь выполняет обычное слияние-as-a-verb action, но вместо находящего базу слияния, Git просто использует коммит A'
, который мы выбрали неявно.Это база слияния для двух git diff
операций.Файлы из коммита A'
будут помещаться в слот 1 индекса, если / когда это необходимо.
То, что входит в слот 2 индекса, остается таким же, как и всегда: коммит, который мы сейчас извлекли, т.е., совершить B
.Последний слот для файлов с коммита A
, который мы сказали cherry-pick.Следовательно, --ours
относится к файлам из фиксации B
, а --theirs
относится к файлам из фиксации A
.По большей части невидимая база слияния относится к файлам из коммита A'
, родителя коммита --theirs
.
Если возникают конфликты слияния, операция cherry-pick останавливается, как и git merge
,оставляя конфликты в рабочем дереве и трех (или иногда двух) копиях каждого файла в индексе.Вам решать, как навести порядок, а затем запустить git cherry-pick --continue
, чтобы завершить выбор вишни.
Если там , нет конфликтов слияния или после того, как вы исправили вещии запустите git cherry-pick --continue
, Git продолжает делать новый коммит.Как всегда, новый коммит использует все, что находится в области index / staging-area.Во всех полностью автоматических случаях Git лучше всего объединяет оба набора изменений и применяет эти объединенные изменения к файлам из A'
(база слияния).Новый коммит также копирует сообщение коммита из оригинального коммита.Затем вы получаете возможность редактировать это сообщение или нет, в зависимости от того, просили ли вы отредактировать (--edit
) или нет (--no-edit
).
Очень просто, но также очень часто,в случаях разница от A'
до B
невелика и / или между этой разницей и разницей от A'
до A
нет конфликтов.В таких случаях результат объединения изменений , сделанных с A'
до A
с изменениями, сделанными с A'
до B
, является таким же в результате простого исправлениясовершить B
напрямую.И на самом деле, в некоторых очень древних версиях Git git cherry-pick
действительно так и делал - он не запускал всю систему слияния с коммитом A'
в качестве базы слияния.Но в сложных ситуациях они могут давать разные результаты.Таким образом, современный Git выполняет полное слияние, используя A'
в качестве базы слияния, B
в качестве текущего коммита и A
в качестве другого коммита.То, что вы сказали в своем вопросе, было вашим пониманием, и это правильно.
Мы также можем полностью понять git revert
сейчас
Если,вместо git cherry-pick A
вы запускаете git revert A
(в то время как при коммите B
), Git снова выполняет трехстороннее слияние. Изменения заключаются в том, что на этот раз commit A
является базой слияния, commit B
является коммитом --ours
, а commit A'
- родительский элемент A
- другим коммитом. Конфликты возникают, когда разница от A
до B
, то есть «что мы изменили», конфликтует с разницей от A
до A'
, то есть «что они изменили». Путем изменения diff - создания набора инструкций для изменения commit A
с целью получения commit A'
- объединение двух diff-ов приводит к возврату любых изменений, сделанных с A'
до A
.