Персональный сайт Александра Тауениса » Как отделить хвост ветки Git в отдельную ветку

Как отделить хвост ветки Git в отдельную ветку

При разработке очередной версии WebOne случился такой конфуз, что начал делать в master-ветке фичу, которую явно делать очень долго, и к ближайшему релизу точно не успею её доделать. Надо эти изменения выделить в отдельную ветку, чтобы можно было продолжить развитие программы от точки до начала реализации этой долгой фичи. И сейчас я расскажу, как это делается средствами консольного Git.

Допустим, есть серия из коммитов A…F (коммит «F» является головой ветки):

A─B─C─D─E─F─⇒  (исходнаяветка)

Её, соответственно, надо выделить в новую ветку:

A─B─C─⇒        (исходнаяветка)
    └─D─E─F─⇒  (новаяветка)

Есть множество способов, например вот такой: http://paratapok.ru/developer-tools/2593_kak-v-git-perenesti-commit-iz-odnoj-vetki-v-druguyu/ . Он пусть и делается через минимум команд, но может внести существенную путаницу при выполнении. Поэтому я вывел свой способ.

Для начала нам надо узнать хэши всех коммитов от C (точка отделения) до А (финиш отдельной ветки). Делается это или через git log, или можно подглядеть их в интерфейсе GitHub. Лучше всего это сделать в отдельном окне, т.к. в дальнейшем понадобятся все хэши.

Дальше надо убедиться, что мы находимся в исходной ветке (мало ли что) при помощи git checkout исходнаяветка.

Следующим шагом создаём новую ветку, и сразу же заносим туда первый её коммит: git checkout -b новаяветка коммит_D.

Казалось бы всё, но этого недостаточно. Сейчас скопировался только один коммит, причём произвольно вырезанный из середины. Теперь нужно при помощи cherry-pick вынести все коммиты в неё: git cherry-pick коммит_E; git cherry-pick коммит_F; ....

Думаете, всё? Нет, Git не WinRAR, чтобы быть простым. Все коммиты не перенеслись, а скопировались. Но скопировались весьма необычным образом — да, у них сейчас свои хэши, да и оригинальные коммиты в ветке исходнаяветка всё ещё на месте. Но если сделать ещё несколько магических заклинаний, то все «не нужные» в старой ветке коммиты окончательно переедут в новую, причём, сохраняя настоящие хэши и даты создания. Возвращаемся в исходную ветку (git checkout исходнаяветка), и жестоко обрезаем её по месту разделения: git reset --hard коммит_C.

Теперь на жёстком диске репозиторий приобрёл желаемый вид. Осталось силком загрузить эти изменения на сервер GitHub: git push --force. Почему нужно применять грубую force (силу)? Дело в том, что Git-сервера очень не любят изменений в уже загруженных коммитах. Это может привести к тому, что если кто-то будет работать с каким-то коммитом, то его изменения могут пропасть. Фактически, подобные операции противоречат сути системы контроля версий. Но, как известно, иногда без костылей и колхозных методов никак. Поэтому приходится делать вот такие операции.

Вот так выглядит итоговая инструкция по выделению коммитов в отдельную ветку:

git checkout исходнаяветка
git checkout -b новаяветка коммит_D
git cherry-pick коммит_E
git cherry-pick коммит_F
git checkout исходнаяветка
git reset --hard коммит_C
git push --force

Бонус

Что делать, если совсем запутался в Git, что-то намудрил с branch-ами и head-ами, и хочется всё это отменить? В первую очередь главное не паниковать, и не делать git push на сервер. Потому, как если этот самый push уже сделан, то все косяки уже отправлены на всеобщее обозрение, и отменить их уже будет сложнее.

Если же весь git-бардак всё ещё находится на жёстком диске, то его вполне реально отменить.

Вариант 1. git clone

Самый простой вариант — удалить текущую папку, и заново склонировать удалённый репозиторий. Это самый надёжный выход из ситуации. Но в случае если есть что-то важное среди файлов из .gitignore (например, запомненные положения окон IDE), то всё это тоже пойдёт лесом. Поэтому можно исхитриться по другому.

Вариант 2. Сброс всех веток до удалённого состояния

Если же удаление и клонирование заново не выход, можно пойти чуть хитрее. Для начала надо определиться, сколько веток есть в репозитории: сколько на сервере и сколько на локальной копии. Затем каждую из тех, которые есть на сервере можно сбросить до исходного состояния:

git checkout имяветки1 -f
git pull
git checkout имяветки2 -f
git pull
git checkout имяветки3 -f
git pull

Задача на 75% решена. Теперь осталось удалить ветки, которых не должно быть на сервере (допустим, это ветка № 4):

git checkout имяветки(1-3)
git branch -D имяветки4

Всё! Репозиторий теперь соответствует состоянию как будто бы после git clone, но со всеми файлами из числа указанных .gitignore. Им вообще ничего не сделалось.



Оставить комментарий

Защита от спама * Лимит времени истёк. Пожалуйста, перезагрузите CAPTCHA.