Хотя на это уже довольно хорошо ответили, есть еще один способ взглянуть на все это. Так на это смотрит сам Git. Все четыре операции — выбор вишни, слияние, перебазирование и возврат — используют один и тот же механизм, а флаги --ours
и --theirs
для git checkout
, а также расширенные параметры -X ours
и -X theirs
в конечном итоге ссылаются на одни и те же вещи, используя одни и те же внутренние код. Мне нравится называть этот механизм слиянием как глаголом, потому что сначала мы знакомимся с ним через git merge
, когда слияние должно выполнять настоящее слияние.
Случай слияния
При выполнении реального слияния термины имеют смысл. Начнем с того, что можно проиллюстрировать следующим образом:
I--J <-- ourbranch (HEAD)
/
...--G--H
\
K--L <-- theirbranch
Здесь имя ourbranch
выбирает фиксацию J
, которая является нашей фиксацией в нашей ветке (в данном случае это одна из двух таких фиксаций, хотя количество фиксаций исключительно в нашей собственной ветке должно быть не менее 1, чтобы вызвать настоящее слияние). Имя theirbranch
выбирает фиксацию L
, которая является их фиксацией в их ветке (опять же, одна из двух, причем здесь необходима по крайней мере одна фиксация).
Что Git делает, чтобы выполнить это слияние — объединить как глагол некоторый набор файлов — для каждого файла во всех трех коммитах H
, J
и L
сравнивает файл в H
с этим в J
, чтобы увидеть, что мы изменили, и сравнить файл в H
с файлом в L
, чтобы узнать, что они изменили. Затем Git объединяет эти два набора изменений, применяя объединенные изменения ко всему, что находится в H
.
Коммит H
— это база слияния коммит, коммит J
— наш коммит, а коммит L
— их коммит. Любое различие, будь то новый файл, добавленный нами, или файл, удаленный ими, или что-то еще, относится к коммиту H
.
Чтобы запустить слияние через механизм слияния, Git делает предварительно слегка оптимизированную версию следующего:
Настраивать:
- read merge base commit (
H
) into index at slot 1
- прочитать
ours
коммит (HEAD
= J
) в индекс в слоте 2
- прочитать
theirs
commit (L
) в индекс в слоте 3
Определите одинаковые файлы. Обратите внимание, что шаги 2 и 3 повторяются для каждого файла.
- if there's a file named F in all three slots, it's the same file
- в противном случае, если в слоте 1 есть что-то, попробуйте угадать переименования, которые свяжут базовый файл слияния в слоте 1 с нашим или их файлом с другим именем, который находится в слоте 2 и/или слоте 3; если не удается найти файл для переименования, наша и/или их сторона удалила этот файл; эти случаи также могут привести к конфликту высокого уровня, такому как переименование/изменение или переименование/удаление, когда мы объявляем конфликт и продолжаем, не выполняя шаг 3.
- в противном случае (ничего в слоте 1, но что-то в слотах 2 и 3) мы имеем конфликт добавления/добавления: объявить этот конкретный файл конфликтующим и двигаться дальше, не выполняя шаг 3
Замкните легкие случаи и сделайте сложные случаи с помощью низкоуровневого слияния:
- if the blob hash IDs in slots 1, 2, and 3 all match, all three copies are the same; use any of them
- если хеш-идентификатор большого двоичного объекта в слоте 1 совпадает с идентификатором в слоте 2 или 3, значит, кто-то не изменил файл, а кто-то изменил; использовать измененный файл, т. е. взять файл, который отличается
- otherwise, all three slots differ: do a changed-block-of-lines by changed-block, low-level merge
- if there's a merge conflict during the low level merge,
-X ours
or -X theirs
means "resolve the conflict using ours/theirs" where ours is whatever is in slot 2 and theirs is whatever is in slot 3
- обратите внимание, что это означает, что везде, где нет конфликта, например, только одна сторона изменила строку 42, расширенный вариант
-X
вообще не применяется, и мы принимаем изменение, независимо от того, является оно нашим или их
В конце этого процесса любой полностью разрешенный файл перемещается обратно в его обычное положение нулевого слота, при этом записи слотов 1, 2 и 3 удаляются. Любой неразрешенный файл остается со всеми тремя занятыми индексными слотами (в конфликтах удаления и добавления/добавления некоторые слоты пусты, но используется какой-то слот с ненулевым номером стадии, что помечает файл как конфликтующий ).
Следовательно, объединить или объединить как глагол работает в индексе Git.
Все описанные выше действия происходят в индексе Git с побочным эффектом, когда обновленные файлы остаются в вашем рабочем дереве. Если есть конфликты низкого уровня, ваши файлы рабочего дерева помечаются маркерами конфликта и различными разделами из строк, соответствующих копиям файлов, которые находятся в слотах индекса 1 (база слияния), 2 (наша) или 3 (их).
В конечном итоге это всегда сводится к одному и тому же уравнению: 1 = база слияния, 2 = наша, 3 = их. Это верно, даже если команда, которая загружает индекс, не git merge
.
Вишневый выбор и возврат используют механизм слияния
Когда мы запускаем git cherry-pick
, у нас есть график коммитов, который выглядит так:
...--P--C--...
\
...--H <-- somebranch (HEAD)
Буквы P
и C
здесь обозначают любую пару коммитов родитель-потомок. C
может даже быть фиксацией слияния, если мы используем параметр -m
для указания какого родителя использовать. (Нет никаких реальных ограничений на то, где три коммита живут на графике: я нарисовал его с H
дочерним элементом некоторого коммита, который идет до P
, но он может быть после пары P-C
, как в ...-E-P-C-F-G-H
, например, или может быть не должно быть никакой связи между коммитами P-C
и H
, если у вас есть несколько непересекающихся подграфов.)
Когда мы запускаем:
git cherry-pick <hash-of-C>
Git сам найдет коммит P
, используя родительскую ссылку от C
до P
. P
теперь действует как база слияния и считывается в индексный слот 1. C
действует как --theirs
коммит и считывается в индексный слот 3. Наш текущий коммит H
является --ours
коммитом и считывается во второй слот индекса. механизм работает сейчас, поэтому наша фиксация — HEAD
, а их фиксация — C
, с базой слияния, которая появляется, если мы устанавливаем merge.conflictStyle
на diff3
или если мы используем git mergetool
для запуска инструмента слияния, — это фиксация P
.
Когда мы запускаем:
git revert <hash-of-C>
происходит то же самое, за исключением того, что на этот раз коммит C
— это база слияния в слоте 1, а коммит P
— это коммит --theirs
в слоте 3. Коммит --ours
в слоте 2, как обычно, из HEAD
.
Обратите внимание, что если вы используете вишневый выбор или возврат к ряду коммитов:
git cherry-pick stop..start
выбор вишни работает по одному коммиту за раз, используя сначала топологически более старые коммиты, в то время как возврат работает по одному коммиту за раз, используя сначала топологически новые коммиты. То есть учитывая:
...--C--D--E--...
\
H <-- HEAD
git cherry-pick C..E
сначала копирует D
, затем E
, но git revert C..E
сначала возвращает E
, а затем D
. (Коммит C
не играет роли, поскольку синтаксис с двумя точками исключает фиксации, достижимые с левой стороны выражения с двумя точками. См. документацию gitrevisions для получения дополнительной информации.)
Rebase - это повторный выбор вишни
Команда rebase работает путем повторного запуска git cherry-pick
, после использования git checkout --detach
или git switch --detach
для перехода в режим отсоединенного HEAD. (Технически теперь это делается только внутри; в старые времена некоторые версии git rebase
, основанные на сценариях оболочки, действительно использовали git checkout
, хотя и с хэш-идентификатором, который все равно всегда переходил в отсоединенный режим.)
Когда мы запускаем git rebase
, мы начинаем примерно так:
C--D--E <-- ourbranch (HEAD)
/
...--B--F--G--H <-- theirbranch
Мы бежим:
git checkout ourbranch # if needed - the above says we already did that
git rebase theirbranch # or, git rebase --onto <target> <upstream>
Первое — ну, второе — что это делает, это входит в режим отсоединения HEAD, при этом фиксация HEAD является фиксацией, которую мы выбрали с нашим аргументом --onto
. Если бы мы не использовали отдельный флаг и аргумент --onto
, --onto
брался бы из одного аргумента, который мы указали, в данном случае theirbranch
. Если мы не использовали отдельный аргумент upstream
, указанный нами аргумент — в данном случае theirbranch
— используется для обеих целей.
Git также (во-первых, поэтому приведенное выше является вторым) перечисляет необработанные хэш-идентификаторы каждого коммита, который необходимо скопировать. Этот список намного сложнее, чем кажется на первый взгляд, но если мы проигнорируем дополнительные сложности, то в основном это результат:
git rev-list --topo-order --reverse <hash-of-upstream>..HEAD
в данном случае это хэш-идентификаторы коммитов C
, D
и E
: три коммита, которые доступны из ourbranch
, но недоступны из theirbranch
.
После того, как git rebase
сгенерировал этот список и перешел в режим detached-HEAD, то, что мы имеем сейчас, выглядит так:
C--D--E <-- ourbranch
/
...--B--F--G--H <-- theirbranch, HEAD
Теперь Git запускает один файл git cherry-pick
. Его аргументом является хэш-идентификатор коммита C
, первого коммита, который нужно скопировать. Если мы посмотрим выше на то, как работает выбор вишни, мы увидим, что это операция слияния как глагола, при этом база слияния является родителем C
, то есть фиксация B
, текущая фиксация или --ours
фиксация H
, и фиксация, подлежащая копированию или --theirs
, является фиксацией C
. Вот почему наши и их кажутся противоположными.
Однако, как только эта операция выбора вишни завершена, мы теперь имеем:
C--D--E <-- ourbranch
/
...--B--F--G--H <-- theirbranch
\
C' <-- HEAD
Теперь Git копирует коммит D
с git cherry-pick
. Базой слияния теперь является фиксация C
, фиксация --ours
— фиксация C'
, а фиксация --theirs
— D
. Это означает, что и наши, и их коммиты являются нашими, но на этот раз наш коммит — это тот, который мы только что создали несколько секунд (или миллисекунд) назад!
Он основан на существующей фиксации H
, которая принадлежит им, но это фиксация C'
, которая принадлежит нам. Если мы получаем какие-либо конфликты слияния, они, несомненно, являются результатом того, что они основаны на H
, возможно, включая какое-то разрешение конфликтов, которое мы выполнили вручную, чтобы создать C'
. Но в буквальном смысле все три входных коммита наши. Слот индекса № 1 — из фиксации C
, слот индекса № 2 — из фиксации C'
, а слот индекса № 3 — из фиксации D
.
Как только мы все это сделали, наша картина теперь:
C--D--E <-- ourbranch
/
...--B--F--G--H <-- theirbranch
\
C'-D' <-- HEAD
Теперь Git запускает git cherry-pick
на хеше коммита E
. База слияния — это коммит D
, а наши и их коммиты — D'
и E
соответственно. Итак, еще раз, во время перебазирования все три коммита наши, хотя конфликты слияния, вероятно, являются результатом построения на H
.
Когда последний выбор сделан, Git завершает перебазирование, беря имя ourbranch
из старого коммита E
и вставляя его в новый коммит E'
:
C--D--E [abandoned]
/
...--B--F--G--H <-- theirbranch
\
C'-D'-E' <-- ourbranch (HEAD)
Теперь мы вернулись к обычному режиму работы с прикрепленной головкой, и поскольку git log
начинается с того места, где мы находимся сейчас — с момента фиксации E'
— и работает в обратном направлении, что никогда не посещает исходную фиксацию C
, кажется, что мы каким-то образом изменили исходные три совершает. У нас нет: они до сих пор там, в нашем репозитории, доступны через специальную псевдо-рефу ORIG_HEAD
и доступны через наши рефлоги. Мы можем вернуть их по крайней мере на 30 дней по умолчанию, после чего git gc
сможет их пожинать, а затем они действительно исчезнут. (Ну, пока мы не git push
переместили их в какой-нибудь другой репозиторий Git, где они все еще хранятся.)
person
torek
schedule
16.09.2020
git show some_commit_hash
и искать добавленные/удаленные файлы. 2) Сопоставьте их с текущимgit status
(с обратным добавлением/удалением, потому что вы возвращаетесь). 3) Прибыль. - person 0x5453   schedule 15.09.2020revert
, если он отличается от других. - person pkamb   schedule 15.09.2020