tl; dr: Вы можете делать что хотите с помощью команды, подобной
git rebase --onto master feature-branch~3 feature-branch
(где feautre-branch~3
работает в примере, но в общем случае это будет любое выражение, которое разрешает коммит B
- например, хеш коммита для B
и т. Д.)
Чтобы узнать, почему это работает, читайте дальше ...
Во-первых, мы должны немного уточнить вашу диаграмму. Вы сказали
Я работал над веткой возможностей, и коммиты C & D были случайно объединены с мастером (сложная история). Другие, работающие в репо, отменили эти коммиты, поскольку это нарушает сборку.
(выделение добавлено). Теперь слияние может выполнять одно из двух [1], но дублирование коммитов C
и D
не входит в их число. Поведение по умолчанию будет выглядеть так:
A -- B -- C -- D -- !CD -- F <-(master)
\
E <-(feature-branch)
Поскольку master
, по-видимому, находилось в B
во время случайного слияния, поведение по умолчанию было бы "перемоткой вперед" master
к существующему коммиту D
. Тогда feature-branch
рука не создается до тех пор, пока D
.
(я также внес некоторые изменения в обозначения. !CD
указывает на объединение, которое отменило C
и D
(без необходимости в аннотации). И я считаю, что этот способ рисования линий более читабелен. Важным моментом является то, как C
и D
представлены ...)
Другая возможность, если вы использовали опцию --no-ff
(или если master
отклонился от feature-branch
до слияния), выглядела бы больше как
C - D -- E <--(feature-branch)
/ \
A -- B ----- M -- !CD -- F <--(master)
Опять же merge
не будет дублировать C
или D
; вместо этого он создаст «коммит слияния» M
, который включает в себя изменения из C
и D
в историю master
(и делает C
и D
«достижимыми» из master
).
В обоих случаях «база слияния» между feature-branch
и master
после слияния равна D
. Таким образом, вам нужно решить две проблемы: проблему «дубликата патча», описанную в документации; и база слияния находится в точке, исключающей ваши коммиты - потому что rebase
даже не будет рассматривать копирование коммитов, которые уже доступны из апстрима. [2]
Часто полезно решить только проблему "слияния". В конце мы увидим, почему вы не обязаны это делать, но для полноты вот как вы это сделаете:
Сначала найдите выражение, которое разрешается для фиксации B
(например, в фиксации для B
или feature-branch~3
в приведенных выше примерах). Используйте это в команде как
git rebase -f feature-branch~3 feature-branch
Это скопирует коммиты C
, D
и E
, так что у вас будет
E
/
A -- B -- C -- D -- !CD -- F <-(master)
\
C' -- D' -- E' <-(feature-branch)
(где E
технически все еще существует, но недоступен, поэтому в конечном итоге может быть уничтожен gc
). Конечно, это предполагает «ускоренную перемотку вперед»; если бы у вас был коммит слияния, он выглядел бы иначе, но результат был бы таким же.
И в результате вы можете без проблем объединить feature-branch
в master
. Но если вы хотите от rebase
feature-branch
до master
(при подготовке к ускоренной перемотке вперед), вам все равно придется решить исходную проблему, которую вы указали: C'
и D'
будут пропущены как дубликаты C
и D
.
Вы хотите помешать проверке дубликатов идентификатора патча; и хотя кажется, что для этого нет реальной возможности, вы можете сделать это, не давая git знать, где искать дубликаты. Вместо указания master
в качестве восходящего потока, передайте commit B
в качестве восходящего; а затем перебазировать --onto master
.
git rebase --onto master feature-branch~3 feature-branch
Не только git
не имеет смысла учитывать C
и D
, когда вы даете эту команду, но и потому, что вы больше не используете master
в качестве восходящего потока, master
-to- feature-branch
База слияния больше не имеет значения. Вы явно говорите, что B
- ваш восходящий поток, так что это решает обе проблемы одновременно, поэтому, как я уже отмечал выше, вам, в конце концов, не придется беспокоиться о команде rebase -f
.
[1] На самом деле, есть, по крайней мере, третье, что он может сделать. Если бы вы указали опцию --squash
, то она не осуществила бы реальное слияние, а вместо этого создала бы один новый коммит CD
в master
. По сути, это то же самое, что и коммит слияния, который обычно создается, за исключением того, что он не включает D
в качестве родителя.
C - D -- E <--(feature-branch)
/
A -- B ----- CD -- !CD -- F <--(master)
Это полезно для некоторых конкретных ситуаций, но в целом я не рекомендую его, так как git «теряет учет» того факта, что C
и D
уже «учтены» в master
. В вашей ситуации это может сработать хорошо - вот почему я знаю, что вы не сделали этого таким образом и не включили эту возможность в основной текст ответа. Но в большинстве случаев это затрудняет будущие взаимодействия между ветвями.
[2] Эти проблемы кажутся похожими, но различаются. В одном случае C
уже уже само по себе достижимо от восходящего потока, к которому вы переходите; в другом случае это не так, но другой коммит, который вносит те же изменения .