Ответ 1
Потерпите меня какое-то время, прежде чем я отвечу на вопрос, как вас спрашивают. Один из ранних ответов прав, но есть маркировка и другие относительно незначительные (но потенциально запутывающие) проблемы, поэтому я хочу начать с чертежей веток и ярлыков отраслевых. Кроме того, люди, приходящие из других систем или, возможно, даже просто новые для контроля версий и git, часто считают ветки "линиями развития", а не "следами истории" (git реализует их как последние, а не первый, поэтому фиксация не обязательно на какой-либо конкретной "линии развития" ).
Во-первых, есть небольшая проблема с тем, как вы нарисовали свой график:
*------*---*
Master \
*---*--*------*
A \
*-----*-----*
B (HEAD)
Здесь точно такой же график, но с ярлыками, нарисованными по-разному и еще несколькими добавленными стрелками (и я пронумеровал узлы фиксации для использования ниже):
0 <- 1 <- 2 <-------------------- master
\
3 <- 4 <- 5 <- 6 <------ A
\
7 <- 8 <- 9 <-- HEAD=B
Почему это имеет значение, так как git совершенно свободен в отношении того, что означает, что для фиксации будет "on" какая-либо ветка - или, может быть, лучшая фраза - сказать, что какая-то фиксация "содержится" в некотором наборе ветвей. Невозможно переместить или изменить Commits, но метки перехода могут перемещаться.
В частности, имя ветки типа master
, A
или B
указывает на одно конкретное коммит. В этом случае master
указывает на фиксацию 2, A
указывает на фиксацию 6, а B
указывает на фиксацию 9. Первые несколько коммитов с 0 по 2 содержатся во всех трех ветвях; commits 3, 4 и 5 содержатся как внутри A
, так и B
; commit 6 содержится только в пределах A
; и фиксации с 7 по 9 содержатся только в B
. (Кстати, несколько имен могут указывать на одно и то же сообщение, и это нормально, когда вы создаете новую ветку.)
Прежде чем продолжить, позвольте мне еще раз рисовать график:
0
\
1
\
2 <-- master
\
3 - 4 - 5
|\
| 6 <-- A
\
7
\
8
\
9 <-- HEAD=B
Это просто подчеркивает, что это не горизонтальная линия коммитов, которая имеет значение, а отношения родителя/ребенка. Метка перехода указывает на начальную фиксацию, а затем (по крайней мере, так, как рисуются эти графики) мы перемещаемся влево, возможно, также поднимаемся или опускаемся по мере необходимости, чтобы найти родительские коммиты.
Когда вы переустанавливаете фиксации, вы фактически копируете эти коммиты.
Git никогда не может изменить фиксацию
Здесь есть одно "истинное имя" для любого коммита (или, действительно, любой объект в репозитории git), который является его SHA-1: эта строка размером в 40 символов, например 9f317ce...
, которую вы видите в git log
например. SHA-1 является криптографической контрольной суммой содержимого объекта. Содержимое - автор и коммиттер (имя и адрес электронной почты), метки времени, исходное дерево и список родительских коммитов. Родитель commit # 7 всегда фиксирует # 5. Если вы делаете в основном точную копию фиксации # 7, но устанавливаете ее родительскую команду для фиксации # 2 вместо фиксации # 5, вы получаете другую фиксацию с другим идентификатором. (На данный момент у меня не хватает отдельных цифр - обычно я использую одиночные прописные буквы для представления идентификаторов фиксации, но с ветвями с именем A
и B
Я думал, что это будет запутанно. Поэтому я позвоню копию # 7, # 7a, ниже.)
Что git rebase
делает
Когда вы попросите git переустановить цепочку коммитов, например, совершите # 7-8-9 выше, она должна скопировать их, по крайней мере, если они будут перемещаться в любом месте (если они не перемещаются он может просто оставить оригиналы на месте). По умолчанию копирование завершается из ветвящейся в данный момент ветки, поэтому git rebase
требуется только две дополнительные части информации:
- Записывает ли он его копию?
- Где должны быть копии? То есть, какой целевой идентификатор родителя для первого скопированного коммита? (Дополнительные коммиты просто указывают на скопированные, скопированные и т.д.).
Когда вы запустите git rebase <upstream>
, вы даете git выяснить обе части из одной части информации. Когда вы используете --onto
, вы можете рассказать git отдельно об обеих частях: вы все еще поставляете upstream
, но не вычисляете цель из <upstream>
, она только вычисляет коммиты для копирования из <upstream>
. (Кстати, я думаю, что <upstream>
не очень хорошее имя, но это то, что использует rebase, и у меня нет ничего лучшего, поэтому давайте поговорим об этом здесь. Rebase calls target <newbase>
, но я думаю, что цель - лучшее имя.)
Сначала рассмотрим эти два варианта. Оба предполагают, что вы находитесь на ветке B
в первую очередь:
-
git rebase master
-
git rebase --onto master A
При первой команде аргумент <upstream>
для rebase
равен master
. Во втором - A
.
Здесь, как git вычисляет, который скопирует: он передает текущую ветвь git rev-list
, а также передает <upstream>
на git rev-list
, но используя --not
- точнее, с эквивалентом двухточечной exclude..include
обозначений. Это означает, что нам нужно знать, как работает git rev-list
.
В то время как git rev-list
чрезвычайно сложный, большинство команд git используют его; это движок для git log
, git bisect
, rebase
, filter-branch
и т.д. - этот конкретный случай не слишком сложный: с двухточечной нотацией rev-list
перечисляет каждое фиксированное достижение с правой стороны, (в том числе и для фиксации), за исключением каждой фиксации, доступной с левой стороны.
В этом случае git rev-list HEAD
обнаруживает, что все коммиты достижимы из HEAD
, т.е. почти все совершает: совершает 0-5 и 7-9- и git rev-list master
обнаруживает, что все коммиты достижимы из master
, что commit #s 0, 1 и 2. Вычитание 0-через-2 из 0-5,7-9 оставляет 3-5,7-9. Это кандидат обязуется копировать, как указано в git rev-list master..HEAD
.
Для нашей второй команды мы имеем A..HEAD
вместо master..HEAD
, поэтому коммиты для вычитания составляют 0-6. Commit # 6 не отображается в наборе HEAD
, но это прекрасно: вычитая что-то, что не существует, не оставляет его там. Таким образом, получившиеся кандидаты к копированию 7-9.
Это все еще оставляет нам возможность выяснить цель перебазировки, то есть, где следует скопировать землю? Со второй командой ответ - "фиксация, идентифицированная аргументом --onto
". Поскольку мы сказали --onto master
, это означает, что целью является commit # 2.
rebase # 1
При первой команде мы не указали цель напрямую, поэтому git использует фиксацию, идентифицированную <upstream>
. Представленный <upstream>
был master
, который указывает на фиксацию # 2, поэтому целью является фиксация # 2.
Итак, первая команда начнется с копирования компиляции №3 с любыми минимальными изменениями, чтобы ее родительский элемент был зафиксирован # 2. Его родитель уже совершает # 2. Ничто не должно меняться, поэтому ничего не меняется, а rebase просто повторно использует существующий коммит # 3. Затем он должен скопировать # 4, чтобы его родительский номер был # 3, но родитель уже №3, поэтому он просто повторно использует # 4. Аналогично, №5 уже хорош. Он полностью игнорирует # 6 (что не в совокупности коммитов для копирования); он проверяет # 7-9, но все они хорошо, поэтому вся перебаза заканчивается просто повторным использованием всех исходных коммитов. Вы можете принудительно копировать копии с помощью -f
, но вы этого не сделали, поэтому вся эта перебаза заканчивается, ничего не делая.
rebase # 2
Вторая команда rebase использовала --onto
, чтобы выбрать # 2 в качестве своей цели, но сообщила git, чтобы скопировать только коммит 7-9. Исходный №7 - это фиксация №5, поэтому эта копия действительно должна что-то сделать. 2 Итак, git создает новый вызов commit-let this # 7a, который имеет фиксацию # 2 как своего родителя. Перестановка переходит к фиксации # 8: копия теперь нуждается в # 7a в качестве родителя. Наконец, rebase переходит на commit # 9, для которого требуется # 8a в качестве родителя. Когда все коммиты скопированы, последнее, что нужно сделать, - это переместить ярлык (помните, метки перемещаются и меняются!). Это дает такой график:
7a - 8a - 9a <-- HEAD=B
/
0 - 1 - 2 <-- master
\
3 - 4 - 5 - 6 <-- A
\
7 - 8 - 9 [abandoned]
ОК, но как насчет git rebase --onto master A B
?
Это почти то же самое, что и git rebase --onto master A
. Разница в том, что дополнительные B
в конце. К счастью, это различие очень просто: если вы даете git rebase
этот дополнительный аргумент, он сначала запускает git checkout
в этом аргументе. 3
Ваши исходные команды
В вашем первом наборе команд вы запустили git rebase master
, а на ветке B
. Как отмечалось выше, это большой нет-op: поскольку ничего не нужно перемещать, git ничего не копирует (если вы не используете -f
/--force
, которого вы не сделали). Затем вы проверили master
и использовали git merge B
, который, если ему сообщается 4 создает новый коммит с объединением. Поэтому ответ Dherik, с того момента, как я его видел, по крайней мере, здесь верен: объединение слияния имеет двух родителей, одним из которых является кончик ветки B
, и эта ветвь возвращается через три коммиты, которые находятся на ветке A
, и поэтому некоторые из того, что на A
завершается, сливаются в master
.
С вашей второй последовательностью команд вы сначала проверили B
(вы уже были на B
, поэтому это было избыточно, но было частью git rebase
). Затем у вас была переформатированная копия трех коммитов, создающая окончательный график выше, с фиксациями 7a, 8a и 9a. Затем вы проверили master
и совершили слияние с помощью B
(см. Сноску 4 снова). И снова ответ Дерика верен: единственное, чего не хватает, это то, что оригинальные, заброшенные коммиты не втянуты, и не так очевидно, что новые объединенные коммиты являются копиями.
1Это имеет значение только в том, что чрезвычайно трудно ориентировать определенную контрольную сумму. То есть, если кто-то, кому вы доверяете, говорит вам: "Я доверяю фиксации с ID 1234567...", это почти невозможно для кого-то, кого вы не можете доверять, - чтобы придумать коммит, который имеет тот же ID, но имеет различное содержимое. Шансы на его случайное происшествие - это 1 из 2 160 что гораздо менее вероятно, чем у вас сердечный приступ, когда он поражается молнией, а утопающий в цунами, будучи похищенным космическими инопланетянами.: -)
2 Фактическая копия выполняется с использованием эквивалента git cherry-pick
: git сравнивает дерево фиксации с его родительским деревом для получения diff, затем применяет diff к новому родительскому дереву.
3 В действительности это буквально истинно: git rebase
- это оболочка script, которая анализирует ваши параметры, а затем решает, какой тип внутренней перезагрузки запускаться: неинтерактивный git-rebase--am
или интерактивный git-rebase--interactive
. После выяснения всех аргументов, если есть один аргумент имени левой ветки, script делает git checkout <branch-name>
перед началом внутренней rebase.
4 Так как master
указывает на фиксацию 2 и commit 2 является предком commit 9, это, как правило, не будет совершать коммит в конце, но вместо этого сделает то, что git вызывает быстрый -forward работа. Вы можете указать git не выполнять эти быстрые переходы с помощью git merge --no-ff
. Некоторые интерфейсы, такие как веб-интерфейс GitHub и, возможно, некоторые графические интерфейсы, могут разделять различные виды операций, так что их "слияние" приводит к истинному слиянию, как это.
При ускоренном слиянии окончательный график для первого случая:
0 <- 1 <- 2 [master used to be here]
\
3 <- 4 <- 5 <- 6 <------ A
\
7 <- 8 <- 9 <-- master, HEAD=B
В любом случае фиксации от 1 до 9 теперь находятся на обеих ветвях, master
и B
. Разница, по сравнению с истинным слиянием, состоит в том, что из графика вы можете увидеть историю, включающую слияние.