← Вернуться в блог

Git cherry-pick: когда использовать (а когда нет)

tutorial git

Что такое 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 от гаданий.