Давайте возьмем ваше слово и превратим его в реальную картину.У вас есть имя ветви master
, которое указывает на последний коммит в серии коммитов.Я назову этот последний коммит master
, который имеет большой уродливый идентификатор хеша, H
здесь.Commit H
записывает фактический хеш-идентификатор родительского коммита, который я назову G
, и так далее.Мы говорим, что мастер указывает на H
, а H
указывает на G
и т. Д.
... <-F <-G <-H <--master
Эти баллы s фактически являются частью коммитов, за исключением самого последнего указателя, который приходит непосредственно от имени master
.Коммиты, однажды сделанные, никогда не могут быть изменены, поэтому H
навсегда указывает на G
.Однако названия веток можно изменить в любое время!Так как те, что внутри коммитов, связаны навсегда, и я хочу нарисовать те, которые указывают назад (влево), но также вверх или вниз, давайте соединим их с линиями и зарезервируем подвижные стрелки для названий веток.(Если бы у меня были разные типы стрелок, я мог бы рисовать в тексте, я использовал бы сильные, похожие на ромб, неизменяемые стрелки для стрелок внутреннего коммита, и большие мягкие, легко изогнутые, для названий ветвей. Но я должен работать сtext-art here.)
...--F--G--H <-- master
Вы добавили новое имя ветви feature1
и другое новое имя ветви feature2
, чтобы все три имени указывали на фиксацию H
:
...--F--G--H <-- master, feature1, feature2
Затем, находясь на feature1
, вы сделали новый коммит, которому звоните C1
:
$ git checkout feature1
... do work, git add files ...
$ git commit
, что привело к следующему:
C1 <-- feature1 (HEAD)
/
...--F--G--H <-- master, feature2
Обратите внимание, что feature1
имеет HEAD
прикрепленный к нему.Вот как Git знал, когда он создавал новый коммит C1
, указывающий на существующий коммит H
, что имя для перемещения было feature1
.Имя HEAD
все еще прикреплено к feature1
прямо сейчас, с именем feature1
, указывающим на C1
, а master
и feature2
все еще указывает на H
.
Теперь вызапустите:
$ git checkout feature2
, которая - наряду с подготовкой рабочей области для работы с коммитом H
- присоединяет HEAD
к имени feature2
:
C1 <-- feature1
/
...--F--G--H <-- master, feature2 (HEAD)
Commit H
- это то, что вы сейчас извлекли в Git index и ваше рабочее дерево, так что теперь вы снова выполняете некоторую работу, git add
для обновления индекса и git commit
для создания снимка C2
из обновленного индекса.Это перемещает имя feature2
, указывающее на фиксацию C2
:
C1 <-- feature1
/
...--F--G--H <-- master
\
C2 <-- feature2 (HEAD)
Теперь вы говорите так:
Как объединить «feature2» в ветке «feature1» вускоренная перемотка вперед, если это возможно, а также обычное слияние?
A перемотка вперед Операция состоит из взятия одного из этих названий ветвей, такого как master
, и «скольжения вперед»(вправо, на нашем рисунке, напротив пути, по которому идут внутренние стрелки), так что он указывает на какой-то другой коммит.Начиная с нового коммита, мы должны иметь возможность вернуться к исходному коммиту.Если мы не можем этого сделать, то сделанное нами движение не является ускоренной перемоткой вперед.
Таким образом, мы можем перемотать вперед master
, что в настоящее время указывает на H
, так что оно указывает на C1
или C2
.Это перемещает его вперед и вверх, или вперед и вниз, и в любом случае мы можем начать с того места, где мы приземлились, и вернуться обратно к H
.Но как вы сдвинете стрелку вперед с C1
, чтобы она указала на C2
?Вы не можете: это в буквальном смысле невозможно.Вы можете сдвинуть его вниз , чтобы оно указывало на C2
и полностью забывало C1
, но это не то, что вам нужно.
Следовательно, мы должны прибегнуть кнормальное слияние.Для этого:
$ git checkout feature1
$ git merge feature2
Это будет настоящее слияние, а не ускоренная перемотка вперед.
Пожалуйста, помогите избежать рекурсивного слияния как можно больше.
Реальное слияние является рекурсивным слиянием, 1 , поэтому невозможно объединить коммиты C1
и C2
без слияния.
Подробнее о слиянии читайте в других публикациях StackOverflow или в хорошей книге по Git.
Обратите внимание, что у вас есть еще один вариант: вы можете скопировать коммит C2
в новый и другой коммит C3
. Мы можем назвать его C2'
вместо C3
, чтобы указать, что это копия C2
. Предположим, мы изменили две вещи в этой новой копии:
- Начальным исходным деревом будет дерево
C1
. Мы применим к этому исходному дереву все изменения, которые мы внесли в терминах H
-vs- C2
.
- Родителем новой копии будет
C1
, а не H
.
Если мы рисуем этот новый коммит, он выглядит так:
C2' <-- ???
/
C1 <-- feature1
/
...--F--G--H <-- master
\
C2 <-- feature2
Как только мы сделаем эту C2'
копию C2
- и получим имя ветки, которое найдет commit C2'
- мы получим фиксацию, которую мы можем использовать для ускоренной перемотки вперед. Имя feature1
теперь может скользить вперед-вверх, так что feature1
указывает на C2'
: с C2'
мы можем вернуться к C1
и затем обратно к H
и G
и F
и т. Д.
Самый простой способ сделать C2'
- это использовать git cherry-pick
, который копирует коммиты. 2 Самый простой способ сделать это с веткой feature2
- это использовать git rebase
, который использует git cherry-pick
для копирования коммитов, затем перемещает имя ветви , чтобы указать на скопированные коммиты, оставляя оригиналы в пользу новых копий. Но это тема для другого вопроса (а перебазирование уже хорошо освещено в других публикациях StackOverflow).
1 Технически, рекурсивное слияние - это конкретная стратегия слияния в Git. Существуют и другие встроенные стратегии - resolve
, octopus
и ours
, но ни одна из них не поможет в этой ситуации.
2 Это копирование с помощью cherry-pick использует механизм слияния Git, поэтому вы все еще объединяетесь! Вы не можете избежать слияния. Объятия слияния! Учитесь и любите это, потому что вы будете часто его использовать.