Это немного сложно, и, как обычно, чтобы понять, что происходит, мы должны нарисовать (часть) граф фиксации.
Вот что у вас видимо (исходя из вашего текста) было до неправильного слияния. У меня может быть неправильное количество коммитов, но общая диаграмма должна быть достаточно близкой:
...--A--B--C--D--E <-- master
\
F--G--H <-- dl2_phase_4
Что касается следующей части, мы должны угадать, потому что только вы (и другие лица, имеющие доступ к вашему репозиторию) имеете правильную информацию:
Сегодня кто-то неправильно объединил запрос на извлечение с мастером коммита, который должен был быть нацелен на ветку d2l_phase_4.
«Таргетинг» не имеет значения; важен график коммитов. Запрос на вытягивание просто означает, что кто-то опубликовал репозиторий с теми же коммитами, что и у вас, плюс как минимум еще один коммит. Ключевой вопрос заключается в том, где этот коммит находится в их графике, и я не знаю на него ответа. Но давайте просто скажем, что оно идет после D
, чтобы оно выглядело так:
__I <-- their-commit
/
...--A--B--C--D--E <-- master
\
F--G--H <-- dl2_phase_4
(Если оно идет после E
, у нас может быть быстрая перемотка вперед вместо слияния, хотя в конечном счете это не имеет значения. Если оно идет после F
, диаграммы становятся беспорядочными, хотя опять же в конечном счете это не имеет никакого значения.)
Затем это, как вы говорите, объединяется в master
, давая новый коммит слияния (я бы назвал его M
, но у нас будет больше слияний, поэтому давайте просто продолжим с отдельными буквами и назовем его J
):
__I
/ \
...--A--B--C--D--E--J <-- master
\
F--G--H <-- dl2_phase_4
(нам больше не нужно обозначать I
, так как это просто второй родитель J
— первый родитель — E
; различие между первым и вторым родителем обычно теряется на этих рисунках).
Затем вы, в соответствии с вашими тремя шагами, откатываете слияние, используя длинный запутанный процесс (отталкивание к разветвлению, откат от разветвления), который дает вам тот же эффект, что и простое откатывание коммита I
в новом коммите K
на master
:
__I
/ \
...--A--B--C--D--E--J--K <-- master
\
F--G--H <-- dl2_phase_4
Самое важное, что следует отметить в отношении фиксации K
, заключается в том, что ее дерево — сохраненный снимок — точно такое же, как дерево из фиксации E
. J
— это просто слияние E
и I
(следовательно, J = E + I)1, а K
имеет добавленное «отрицательное I», следовательно, K = E + I — I = E.
1Это не гарантируется! Если I
дублирует изменение, которое находится в E
, то J
на самом деле не является Е плюс I, это Е плюс I минус дублированная часть. Но если бы это было так, мы бы увидели эффект позже, поэтому I
не должно дублировать что-то в E
, или, возможно, I
идет после E
. Вот почему так важно иметь граф, а иногда и сам репозиторий.
Затем вы прошли еще один длинный запутанный процесс, который на самом деле сводится к простому git cherry-pick
:
- Черри выбрал исходный коммит из мастера [в dl2_phase_4].
Непонятно, какой из них является "оригиналом" (I
или J
, хотя если I
на самом деле было перемотано, то нет J
), но это тоже не имеет значения: мы просто получаем копию, которую я назову I'
:
__I
/ \
...--A--B--C--D--E--J--K <-- master
\
F--G--H--I' <-- dl2_phase_4
Хотя дерево для I'
отличается от дерева для I
, два относительных изменения — то, что мы получили бы при запуске git diff <id-of-D> <id-of-I>
и git diff <id-of-H> <id-of-I'>
, — это тоже самое. Вот что делает git cherry-pick
: он сравнивает некоторую фиксацию с родительской, затем применяет полученную разницу к текущей фиксации и делает новую фиксацию.
Наконец, я хотел обновить ветку d2l_phase_4 со всеми остальными материалами на master с момента создания ветки d2l_phase_4. Итак, снова в ветке d2l_phase_4 я слился с мастером, и это создало новый коммит слияния.
Теперь давайте нарисуем это:
__I
/ \
...--A--B--C--D--E--J--K <-- master
\ \
F--G--H--I'----L <-- dl2_phase_4
Однако - до меня дошло, что я сейчас слился в более ранний реверт из ветки master... [но] изучив состояние файлов на d2l_phase_4, нет никаких следов реверта, который был слит из master - это точно что я хочу - но я не понимаю.
Это попадает в первый ключевой элемент:
Git merge не смотрит ни на один из «внутренних» коммитов.
Почему моя избранная фиксация на d2l_phase_4 не была аннулирована возвратом, который был объединен с мастером? [отрезать]
Как видно из графика, мы объединяем фиксацию K
, оставаясь «включенной» фиксацией I'
. база слияния — фиксация фиксации, в которой соединяются линии графика — это фиксация B
.
Процесс слияния, слияние как глагол, работает, по сути, выполняя (и затем объединяя) два git diff
s: один из базы слияния в текущую фиксацию и один из базы слияния в другую. совершить. Следовательно:
git diff <id-of-B> <id-of-I'>
git diff <id-of-B> <id-of-K>
Мы уже отмечали, что с точки зрения дерева (на что смотрит git diff
) фиксация K
аналогична фиксации E
. Таким образом, первый diff игнорирует как неправильную фиксацию, так и ее реверсию. Второй diff, сравнивающий B
и I'
, рассматривает выбранный коммит как изменение, поэтому включает это изменение. Объединение первого и второго различий дает вам одну копию изменения.
Врет команда git log
, немного (а иногда и много)
Если я посмотрю на историю одного из задействованных файлов, в ветке d2l_phase_4, то я не увижу никаких следов возврата или чего-то еще, все, что я могу увидеть, была ли выбрана вишня, которую я сделал.
Теперь, когда у нас есть это окончательное слияние L
, git log
должно показать нам содержимое коммита L
, содержимое первого и второго родителей L
, содержимое их родителей и так далее.
Коммит L
является коммитом слияния, поэтому у него есть два родителя, K
и I'
. I'
— ваш лучший коммит, так что вы увидите его, когда git log
дойдет до этой точки.
K
— это простая фиксация с одним родителем, J
; но J
— это коммит слияния с двумя родителями, E
и I
. Вы ожидаете увидеть здесь коммит I
, и — эта часть становится сложной — иногда вы это делаете.
Когда вы запускаете git log -- <pathspecs>
, вы даете Git неявный аргумент, который (я думаю) не может быть написан явно, хотя --dense
и --sparse
могут быть достаточно близки для пояснительных целей. Они включают упрощение истории, а упрощение истории отбрасывает одну "сторону" слияния, если это возможно. В любом случае вы также указываете Git игнорировать все файлы, не указанные в ваших аргументах <pathspecs>
.
Когда Git просматривает коммит L
, это коммит слияния с двумя родителями, поэтому применяется эта часть правила упрощения истории:
Коммиты включаются, если они не TREESAME ни для одного родителя (хотя это можно изменить, см. --sparse
ниже). Если фиксация была слиянием, и она была TREESAME для одного родителя, следуйте только за этим родителем. ... В противном случае следуйте всем родителям.
Это «TREESAME» определяется как: после отбрасывания всех путей, кроме указанных, является ли усеченное дерево коммита таким же, как дерево одного из его родителей? Таким образом, для L
мы сравниваем его с K
и I'
после удаления всех файлов, кроме одного, о котором вы спрашиваете. Если версия файла в L
совпадает с версией в I'
— возможно, так и есть, — тогда git log
обрезает K
в этот момент и больше не видит ни K
, ни J
, ни родителей J
, ни E
, ни D
, ни C
. Однако он будет следовать за I'
назад к H
, а затем к G
и F
и B
и так далее, назад по истории.
Если версия файла в L
совпадает с версией в K
, то git log
обрезает I'
; но поскольку I'
определенно коснулся файла, Git следует здесь только за I'
.
Чтобы Git показал вам всю историю, вместо упрощения боковых ветвей, найденных на графике, вам нужно --full-history
. Git по-прежнему будет ограничивать историю коммитами, касающимися файла, но на этот раз он будет смотреть на обоих родителей L
.
Примеры всего этого есть в документации git log
a>, но это определенно блестящий материал.
Еще одна вещь, которую следует отметить, это то, что git log
часто ничего не говорит о самих коммитах слияния, если только вы не дадите команду «разделить» их (с помощью -m
) или принудительно объединить diff (с помощью -c
или --cc
). То есть он может распечатать сообщение журнала, но не показать никаких изменений, даже если git show
что-то покажет (комбинированный diff в стиле --cc
).
person
torek
schedule
03.02.2017