Что такое Git Cherry-Pick?
Критическое исправление бага только что попало в вашу branch разработки -- но тот же баг живёт и в продакшене. Вы не можете сделать merge всей branch разработки в релизную branch: она полна наполовину готовых функций. Что вам действительно нужно -- взять тот самый один commit и применить его в другом месте. Именно это и делает git cherry-pick.
Cherry-pick берёт изменения, внесённые существующим commit, и воспроизводит их на вашей текущей branch в виде совершенно нового commit. Новый commit содержит тот же diff и, по умолчанию, то же сообщение, но у него другой родитель и, следовательно, другой SHA. Эта деталь важнее, чем кажется: Git идентифицирует commit по SHA, поэтому после cherry-pick в репозитории появляются два разных commit, которые вносят одно и то же изменение. Запомните это -- здесь кроется и причина того, почему cherry-pick так полезен, и причина того, почему злоупотребление им аукнется позже.
Когда cherry-pick -- правильный инструмент
Бэкпорт hotfix в релизную branch
Классический случай. Вы исправили баг в main, но пользователи сидят на версии 2.3, и исправление нужно им прямо сейчас. Сделайте cherry-pick исправления в релизную branch:
git switch release/2.3
git cherry-pick a1b2c3d
Релизная branch получает ровно это исправление -- и ничего лишнего из main не прицепляется по дороге.
Commit попал не в ту branch
Вы собирались сделать commit в своей feature-branch, но находились в main. Cherry-pick предлагает чистое восстановление: переключитесь на branch, где commit должен находиться, сделайте там cherry-pick, а затем вернитесь и удалите его из branch, где ему не место. Если шаг с удалением вызывает сомнения, наш гайд о том, как отменить commit в Git, подробно разбирает каждый вариант.
Спасти одно исправление из заброшенной branch
Иногда эксперимент не выгорает, но один commit в середине branch исправлял настоящий баг. Вместо merge тупиковой branch сделайте cherry-pick единственного ценного commit в свою активную branch и позвольте остальному эксперименту спокойно отправиться на удаление.
Базовое использование
Простейшая форма принимает один commit:
git cherry-pick a1b2c3d
Git применяет изменения из этого commit и сразу создаёт новый commit на вашей текущей branch, повторно используя исходное сообщение. Можно передать и несколько commit за раз -- Git применит их по очереди, в том порядке, в котором вы их перечислили:
git cherry-pick a1b2c3d f4e5d6c
Практичные опции, которые стоит знать
-x: записать, откуда пришёл commit
При бэкпорте вам позже захочется узнать, откуда взялся commit. Опция -x добавляет к сообщению нового commit строку вида (cherry picked from commit a1b2c3d...):
git cherry-pick -x a1b2c3d
Сделайте это привычкой для любого cherry-pick между долгоживущими публичными branch. Через полгода эта единственная строка мгновенно скажет вам, попало ли исправление в релиз и откуда оно пришло.
Перенос диапазона commit
Можно сделать cherry-pick целого диапазона с помощью синтаксиса с двумя точками:
git cherry-pick A..B
Осторожно: это применяет commit, идущие после A, вплоть до B включительно. Сам A исключается. Если вы хотите включить A, напишите:
git cherry-pick A^..B
^ означает «родитель A», что возвращает A внутрь диапазона. Путаница между этими двумя вариантами -- одна из самых частых ошибок при cherry-pick.
--no-commit: применить без создания commit
Иногда вам нужны изменения, но не автоматический commit -- например, чтобы объединить несколько перенесённых commit в один или подправить результат перед фиксацией:
git cherry-pick --no-commit a1b2c3d
Изменения попадают в рабочий каталог и в staging, а commit вы создаёте сами, когда будете готовы.
Разрешение конфликтов во время cherry-pick
Cherry-pick может конфликтовать по той же причине, что и merge: переносимый commit был написан поверх кода, который отличается от вашей текущей branch. Когда это происходит, Git останавливается посреди операции и помечает конфликтующие файлы:
error: could not apply a1b2c3d... fix login timeout
hint: After resolving the conflicts, mark them with
hint: "git add/rm <pathspec>", then run
hint: "git cherry-pick --continue"
Теперь у вас три выхода:
git cherry-pick --continue-- после того как вы отредактировали конфликтующие файлы и добавили их в staging командойgit add, эта команда завершает cherry-pick и создаёт commit.git cherry-pick --abort-- отменяет всю операцию и возвращает branch ровно в то состояние, в котором она была до начала. Ничего не теряется, ничего не применяется.git cherry-pick --skip-- при переносе нескольких commit пропускает текущий и переходит к следующему. Полезно, когда один из commit диапазона оказывается уже применённым или ненужным.
Само разрешение конфликтов работает точно так же, как при конфликте merge: откройте каждый конфликтующий файл, выберите между конкурирующими hunk (или объедините их), удалите маркеры конфликта и добавьте файл в staging. Если маркеры конфликтов всё ещё пугают, наш разбор о том, как визуально разрешать конфликты merge в Git, применим и к cherry-pick.
Чего НЕ стоит делать с cherry-pick
Не переносите cherry-pick целые branch
Если вы ловите себя на том, что переносите cherry-pick пять, десять, двадцать commit из одной branch в другую -- остановитесь. Для этого существуют merge и rebase. Оба переносят работу между branch, позволяя Git понимать связь между ними, что делает будущие операции простыми и бесконфликтными. Если вы не уверены, что из двух подходит вашему рабочему процессу, наше сравнение rebase vs merge разбирает все компромиссы.
Дублированная история мстит позже
Помните: cherry-pick создаёт новый commit с новым SHA, и Git не хранит никакой официальной записи о том, что два commit представляют «одно и то же изменение». Если вы переносите cherry-pick commit из feature-branch в main, а потом всё-таки делаете merge этой feature-branch, Git вынужден согласовывать две копии одних и тех же изменений. Часто это проходит незаметно. Но как только одна из копий была впоследствии изменена, вы получаете сбивающие с толку конфликты, где обе стороны выглядят почти одинаково -- а история показывает одно изменение дважды, с двумя датами и двумя SHA, что усложняет работу с инструментами вроде git log и git bisect.
Хорошее правило: cherry-pick предназначен для переноса исключений -- hotfix, заблудившегося commit, спасённого исправления. В тот момент, когда он становится вашим обычным способом переноса работы между branch, история сама говорит вам, что стратегию ветвления пора пересмотреть.
Cherry-pick в GitSquid
В GitSquid cherry-pick находится там, где вы и ожидали бы: щёлкните правой кнопкой по любому commit на графе commit и выберите пункт cherry-pick в контекстном меню. Никакого копирования SHA туда-сюда между логом и терминалом.
Главный выигрыш -- знать, во что вы ввязываетесь. Conflict Predictor в GitSquid -- бесплатный для всех -- показывает, какие именно файлы будут конфликтовать до запуска merge, rebase или cherry-pick, с предпросмотром hunk и подсветкой синтаксиса. Вместо того чтобы обнаруживать конфликты посреди операции, вы видите их заранее и можете решить: продолжить, выбрать другой commit или спокойно подготовиться к разрешению.
А когда перенос всё-таки конфликтует, «Resolve in scratch worktree» открывает временный worktree в новой вкладке, чтобы вы могли разрешить всё там, пока ваш активный checkout остаётся нетронутым.
Краткая справка
| Команда | Что делает |
|---|---|
git cherry-pick <sha> |
Применяет один commit к текущей branch |
git cherry-pick <sha1> <sha2> |
Применяет несколько commit по порядку |
git cherry-pick -x <sha> |
Добавляет исходный SHA к сообщению commit |
git cherry-pick A..B |
Переносит commit после A до B (A исключается) |
git cherry-pick A^..B |
Переносит диапазон, включая A |
git cherry-pick --no-commit <sha> |
Применяет изменения без создания commit |
git cherry-pick --continue |
Завершает перенос после разрешения конфликтов |
git cherry-pick --abort |
Отменяет всё и восстанавливает branch как было |
git cherry-pick --skip |
Пропускает текущий commit, продолжает с остальными |
Cherry-pick -- это скальпель, а не лопата. Применяемый для редкого точного извлечения -- бэкпорт hotfix, заблудившийся commit, спасённое исправление -- это одна из самых приятных команд в Git. Применяемый как замена merge, он тихо наполняет вашу историю дубликатами, которые вернутся, чтобы вас преследовать. Знайте разницу, добавляйте -x при бэкпорте и держите --abort в голове как запасной выход.
Хотите увидеть, какие файлы будут конфликтовать, ещё до начала? Скачайте GitSquid бесплатно и позвольте Conflict Predictor избавить ваш следующий cherry-pick от гаданий.