Ответ Рене правильный, но все эти слегка причудливые (с использованием набора символов в виде ячеек) диаграммы немного устарели, начиная с вашей собственной:
───M1───M2───M3───S────M4 (master)
└────D1───D2───┘ (develop)
└────F1───F2 (feature)
Причина в следующем:
... Я слил ветку разработки с мастером, используя 'squash and merge'.
The "squash"кнопка «Объединить» в кликабельных графических интерфейсах или git merge --squash
в командной строке делает новый коммит, который не является объединением .Вместо:
...--M1--M2--M3--S--M4 <-- master
\ /
D1-----D2 <-- develop
то, что вы на самом деле получили, было:
...--M1--M2--M3--S--M4 <-- master
\
D1--D2 <-- develop
Нет никаких отношений предка / потомка между S
и любым из D
s, ни между какими-либоM
, но M1
и любой из D
с.Это как раз то, что отключает последующие операции Git.Если вы сделаете фактическое слияние - используя обычные git merge
, или git merge --no-ff
, или не щадящую щелчковую кнопку GitHub, , тогда S
(и, следовательно, M4
также) будут потомкамиD1
и D2
.
Но это не то, что мы имеем.Поэтому, когда мы смотрим на feature
и master
, мы видим это:
...--M1--M2--M3--S--M4 <-- master
\
D1--D2--F1--F2 <-- feature
Мы видим, что feature
имеет четыре коммитов, достижимых из его коммитов tip (включая tip)передайте), которые недоступны с master
, а именно D1-D2-F1-F1
.Независимо от того, есть ли какое-либо имя, указывающее на D2
, эти четыре коммита являются четырьмя, которые находятся на feature
, которые также не включены на master
.(Commit M1
находится на обеих ветвях, как и все, что осталось от него.)
Я думаю, что хороший способ думать об этом состоит в том, что "слияние в сквош" имеет побочный эффект: убивает ветвь, которая была только что слита.Коммиты D1
и D2
теперь фактически "мертвы" и должны считаться, по крайней мере, умеренно радиоактивными.Как сказал Ахмад Кундакджи , в конечном итоге они часто будут безвредны.Но они могут сделать вашу историю коммитов уродливой - как в , почему это было зафиксировано дважды, один раз как два отдельных коммита, а затем как один больший? , если feature
фактически объединено вместо сквоша - объединено«- и в худшем случае они могут вызвать конфликты позже из-за некоторого изменения в коммите, который является потомком S
.
Поскольку объединенная ветвь теперь« мертва », ее следует удалить.То есть вы должны запустить git branch -D develop
.Но перед этим убедитесь, что коммиты D1
и D2
сами не содержатся ни в каких других ветвях - которые, конечно, в вашем случае равны .Если они содержатся в других ветвях, вы должны реконструировать эти ветки как варианты, которые больше не имеют этих двух коммитов.
Обратите внимание, что «перебазировать и объединить» (еще одна кликающая кнопка на GitHub - фактическивсе три объединены в одну кнопку с внутренним раскрывающимся списком, но это просто способ обозначить три разные кнопки) также имеет побочный эффект уничтожения ветвей, поскольку rebase действительно означает копировать старые коммиты вновые, затем прекратите использовать старые коммиты в пользу новых копий .
Я бы нарисовал график post- git rebase --onto master develop feature
таким образом, сохранив "мертвые" develop
на рисунке:
F1'-F2' <-- feature
/
...--M1--M2--M3--S--M4 <-- master
\
D1--D2 <-- develop
\
F1--F2 [abandoned]
Это делает более понятным, что F1
и F2
все еще существуют под их уникальными хэш-идентификаторами фиксации.Они просто больше не легко найти , так как нет имени, которое мы можем использовать, чтобы найти их. 1 Начиная с имени feature
, мы сначала находим заменяющую копию F2'
что мы должны использовать вместо F2
, затем F1'
, что мы должны использовать вместо F1
, затем M4
, затем S
и так далее, назад в прошлое.Команды Git, такие как git log
, не найдут старые скучные коммиты, которые мы заменили блестящими новыми, которые начинаются с F2'
вместо M1
.
(И, сейчас безопасно удалить develop
, если нет других ветвей , а также включают D1
. Обратите внимание, что включение D2
автоматически включает D1
, так что это единственное, что мы должны упомянуть здесь.)
В тВ длинной команде git rebase --onto master develop feature
у нас есть три интересных аргумента (плюс ключевое слово option --onto
, конечно).Начиная с конца и работая в обратном направлении, поскольку Git выполняет не , мы имеем:
feature
в качестве дополнительного аргумента, который the git rebase
документация вызывает [<branch>]
: git rebase
говорит начать с git checkout feature
.Если вы сделаете это сами, вы можете оставить этот последний аргумент.Это буквально просто передается git checkout
, 2 , поэтому оно всегда должно быть именем ветви.
develop
как то, что в документации git rebase
называется [<upstream>]
: сообщает git rebase
, что обязывает не копировать.Если вы пропустите этот аргумент, Git использует все, что настроено в качестве целевой (или текущей) ветви upstream .Это имя передается через git rev-parse
, поэтому оно может быть практически любым: необработанный хэш-идентификатор, имя тега, git describe
выходные данные, имя ветви, относительная операция, подобная HEAD~2
, и т. Д.on.
--onto master
, как то, что документация git rebase
вызывает <newbase>
: сообщает git rebase
, куда поместить копии фиксации , что в конечном итоге направляет егогде указать точку ветвления после завершения копирования.Если вы пропустите это, по умолчанию будет <upstream>
, и, как и <upstream>
, оно будет передано через git rev-parse
.
Так что эта команда командной строки git rebase
означает:
После проверки feature
скопируйте все коммиты, доступные из кончика филиала, исключая любые коммиты, которые также достижимы из коммита, обозначенного develop
, а также исключая любые другие коммиты, которыеВы, git rebase
, чувствуете необходимость пропустить. 3 Делайте копии в правильном топологически отсортированном порядке, чтобы сначала копировались более ранние, менее зависимые коммиты, а затем копируются более зависимые коммитыпотом.Разместите каждую копию так, чтобы скопированная first коммит была получена сразу после коммита, обозначенного master
.Когда вы скопировали последний коммит, дерните имя ветки feature
так, чтобы оно указывало на последний скопированный коммит или, если коммиты не были скопированы, непосредственно на коммит, обозначенный master
.
Когда Git закончил делать это, вы получите коммиты, которые выглядят так, как мы их нарисовали.(Вы также находитесь на ветке feature
, если только ребята из Git не исправили то, что я считаю незначительной ошибкой в git rebase
- похоже, если вы скажете, что rebase запускается с помощью git checkout feature
, но вы, скажем,master
, в конце концов он должен оставить вас на master
, а не на feature
. Конечно, если перебазировка должна останавливаться из-за конфликтов, она останавливается в режиме «отсоединенного HEAD», но когда вы git rebase --continue
илиgit rebase --abort
чтобы возобновить или прекратить операцию, она должна в конечном итоге вернуть вас туда, где вы были, даже если это не feature
.)
1 Там - это имен, по которым вы можете найти оригинальные F2
и F1
, хранящиеся в Git's reflogs .Рефлог для любого ссылочного имени, в том числе для имен ветвей, содержит журнал, в котором фиксируется это имя ветки, идентифицируемое по их хэш-идентификаторам, по какой-то конкретной отметке времени.Каждое обновление ссылки, сделанное, например, git update-ref refs/heads/feature
, сохраняет значение предыдущее в журнале и добавляет значение new с отметкой времени, когда это новое значение было простозаписано (и сообщение reflog о том, что происходит).
Запустите git reflog feature
, чтобы просмотреть записи reflog для refs/heads/feature
.Для самого HEAD
есть дополнительный журнал.Запустите git reflog
или git reflog HEAD
, чтобы увидеть это.Обратите внимание, что старые записи в конце концов истекают;подробнее об этом см. подкоманду git reflog expire
и документацию git reflog
.
Обратите внимание, что git reflog show
- это используемая здесь подкоманда -на самом деле просто запускается git log -g
, поэтому вы можете использовать git log -g
вместо git reflog
здесь.
Удаление любой ветки удаляет ее reflog, но записи в журнале HEAD
остаются.В некоторых будущих версиях Git планируется сохранить повторные журналы из удаленных ветвей, чтобы можно было «удалить» ветку, но эти планы пока довольно шероховаты и не сфокусированы, а также имеют некоторые проблемы с реализацией.
2 В какой-то момент слово буквально здесь было буквально правильным, так как git rebase
был большим сценарием оболочки.Но теперь многие части git rebase
написаны на C. Команда git checkout
также написана на C, и когда вы собираете Git, вы создаете двоичные файлы, которые разделяют некоторый внутренний код реализации.Если git rebase
является кодом C, который вызывает тот же внутренний код, что и отдельный git checkout
двоичный код, является ли буквально вызывающим git checkout
, или он теперь образный? Какова правильная семантика слова литерал здесь?Должен ли литерал требовать сопоставления внешних и конечных интерфейсов или только соответствующих конечных узлов?
3 Коммит, который git rebase
пропускает самостоятельноявляются:
Слияние совершает.Это буквально невозможно скопировать.В некоторых режимах git rebase
будет повторно выполнять слияния по мере необходимости.Этими режимами являются старый --preserve-merges
и новомодный, улучшенный --rebase-merges
;оба хитрые, и я не собираюсь пытаться описать их здесь.То есть, хотя в документации по rebase говорится о поиске списка коммитов для копирования с помощью <upstream>..HEAD
, на самом деле он использует <upstream>...HEAD
для нахождения симметричного различия двух наборов коммитов: тех, которые достижимы из HEAD
, но не из восходящего потока.аргумент, и те, которые достижимы из аргумента восходящего потока, но не из HEAD
.
В этой последней части используется способность команды git rev-list --left-right
различать, с какой "стороны" происходят такие коммиты:фиксирует, что может быть скопировано справа - доступно с HEAD
, но не с <upstream>
.Тем не менее, те, которые находятся с левой стороны от этой симметричной разности, это те, которые «после точки отсечения», но достижимы с <upstream>
.В этой конкретной ситуации эти коммиты - это S
и M4
.Так что Git вычисляет ID патча для этих двух коммитов, используя git patch-id
.Он также вычисляет идентификатор патча для каждого из кандидатов для копирования - в данном случае F1
и F2
.Если идентификаторы патчей любого из кандидатов на копирование совпадают с идентификаторами патчей в другой половине, Git приходит к выводу, что они, должно быть, уже были выбраны в <upstream>
и должны быть опущены во время копирования.
Этот вывод обычно правильно, но в некоторых случаях это может быть неправильно! Это всегда хорошая идея для проверки результата любой из автоматизированных операций Git. Путь не так, например, если один коммит исправляет паразит }
в строке сам по себе, и есть еще один заблудился }
на отдельной строке в последующем ряду.Эти два исправления относятся к разным исходным строкам и могут иметь отличающиеся отступы , но для git patch-id
они такое же изменение , что и идентификатор патчасоздается путем удаления номеров строк и некоторых пробелов.