Насколько я понимаю, вы были в такой же ситуации, как и я сейчас: смесь изменений была зафиксирована в одной и той же ветке, но их нужно разделить на отдельные ветки.
Current state:
... -- A (master) -- C1 -- B1 -- B2 -- C2 -- B3 -- C3 -- B4 (devel)
Wanted state:
... -- A (master) -- B1’ -- B2’ -- B3’ -- B4’ (branch-B)
\
`---------- C1’ -- C2’ -- C3’ (branch-C)
В моем случае это состояние было достигнуто после беспорядочных экспериментов путем мягкого сброса до master
и внесения изменений построчно с помощью git gui
, помечая каждое изменение сообщением фиксации A
или B
. Итак, давайте предположим, что такая подготовка уже произошла, и коммиты An
, Bn
уже четко разделены.
Сбор вишен вручную может привести к ошибкам, так как в длинных последовательностях коммитов легко пропустить один. Поэтому вместо этого мы используем git rebase -i
во временной ветке, чтобы быть в безопасности.
>> git checkout devel
>> git checkout -b tmp
... -- A (master) -- C1 -- B1 -- B2 -- C2 -- B3 -- C3 -- B4 (devel, tmp, HEAD)
>> git rebase -i master
Это откроет файл задачи перебазирования. По умолчанию это
pick 552fe03 C1
pick cbf327a B1
pick bd1ca26 B2
pick 95320e6 C2
pick 7f21156 B3
pick 910a6fe C3
pick bfda579 B4
По сути, это «сбрасывает ветку на master
, затем выполняет команды, перечисленные в файле перебазирования». При настройке по умолчанию он по существу перестраивает ветку, оставляя все без изменений.
1. Изменение порядка коммитов.
Поэтому вместо этого мы переупорядочиваем коммиты.
pick cbf327a B1
pick bd1ca26 B2
pick 7f21156 B3
pick bfda579 B4
pick 552fe03 C1
pick 95320e6 C2
pick 910a6fe C3
В зависимости от того, какие изменения были внесены, могут возникать конфликты слияния, которые необходимо разрешить, прежде чем rebase
можно будет продолжить с git rebase --continue
.
Может быть полезно перемещать только один коммит за раз, повторяя git rebase -i master
несколько раз; В противном случае вы рискуете потерять работу, если вам придется прервать перебазирование.
Результат будет:
... -- A (master) -- C1 -- B1 -- B2 -- C2 -- B3 -- C3 -- B4 (devel)
\
`-- B1 -- B2 -- B3’ -- B4’ -- C1’ -- C2’ -- C3’ (tmp, HEAD)
2. Создайте ветки.
Теперь мы можем создать новые ветки,
>> git branch branch-C tmp
>> git branch branch-B <hash-of-B4’>
Предоставление состояния
... -- A (master) -- -- B1 -- B2 -- C1 -- B3 -- C2 -- C3 -- B4 (devel)
\
`-- B1’ -- B2’ -- B3’ -- B4’ (branch-B) -- C1’ -- C2’ -- C3’ (tmp, branch-C, HEAD)
3. Разъединить их еще одним ребазом.
Как описано в этом ответе на Разделить ветку git на две ветки?, теперь мы можем использовать команду rebase вида
>> git rebase --onto NEW_PARENT OLD_PARENT BRANCH_TO_MOVE
в таком случае:
>> git rebase --onto master branch-B branch-C
... -- A (master) -- B1 -- B2 -- C1 -- B3 -- C2 -- C3 -- B4 (devel)
|
|`-- B1’ -- B2’ -- B3’ -- B4’ (branch-B) -- C1’ -- C2’ -- C3’ (tmp)
|
`-- C1’’-- C2’’-- C3’’ (branch-C, HEAD)
Если все сработало, остается только принудительно удалить устаревшие/временные ветки (обычное удаление с -d
здесь не работает).
>> git branch -D devel
>> git branch -D tmp
... -- A (master) -- B1’ -- B2’ -- B3’ -- B4’ (branch-B)
\
`---------- C1’’-- C2’’-- C3’’ (branch-C, HEAD)
Без временных ответвлений.
Конечно, вам не обязательно использовать временные ветки. Тогда команды будут
>> git checkout devel
>> git rebase -i master # for reordering
>> git branch -m branch-C # rename ‘devel’ to ‘branch-C’
>> git branch branch-B <hash-of-B4’>
>> git rebase --onto master branch-B branch-C
Но перебазирование может дать сбой, и его легче восстановить при использовании ветвей, чем восстанавливать правильное состояние из git reflog
.
11.12.2020