Ситуация из жизни. У тебя в проде ветка release/2.3, на ней крутится боевой релиз. В main кипит работа: десятки новых коммитов, рефакторинг, недопиленные фичи. И тут падает баг в проде. Кто-то уже починил его в main одним аккуратным коммитом, но мержить весь main в релиз нельзя - притащишь сырьё, которое тестировщики ещё не видели. Тебе нужен ровно один коммит. Не ветка, не диапазон в полсотни патчей - один.
Вот для этого и существует cherry-pick git. Команда git cherry-pick берёт изменение конкретного коммита и накладывает его поверх твоей текущей ветки как новый коммит. Не перемещает, не переносит физически - именно копирует diff и создаёт свежий объект с новым SHA. Это и есть бэкпорт коммита git: вытащить починку из активной разработки и положить в стабильную ветку, не таща за собой весь хвост.
Если ты помнишь объектную модель из ранних уроков - cherry-pick не двигает существующий коммит-объект (он иммутабельный), а строит новый: то же дерево изменений, но другой родитель, другое время, другой автор-коммиттер. Поэтому SHA всегда меняется. Запомни это намертво - половина граблей вокруг cherry-pick растёт именно из непонимания, что копия и оригинал - это два разных коммита с одинаковым содержимым diff.

Как это работает внутри: перенести коммит git без магии
Механика проста, если думать про коммит не как про снимок, а как про дельту. Git берёт коммит C, смотрит на разницу между C и его родителем (C^), и пытается применить эту разницу как трёхстороннее слияние (merge-стратегия ort) поверх твоего HEAD. Если diff ложится чисто - рождается новый коммит. Если нет - конфликт, всё как при обычном мерже.
Базовый сценарий. Ты на release/2.3, нужный фикс - в main.
Код: Выделить всё
git switch release/2.3
git cherry-pick a1b2c3d
Код: Выделить всё
[release/2.3 7f4e9a2] fix: null pointer in PaymentService
Date: Mon Jun 8 14:22:10 2026 +0300
1 file changed, 4 insertions(+), 1 deletion(-)
Можно указывать коммит как угодно: по SHA, по имени ветки (тогда берётся её верхушка), относительно - main~2 (второй коммит от верхушки main), по тегу. Всё, что резолвится в коммит.
Флаги, которые отличают новичка от инженера: -x, -e, -n
Три флага, без которых бэкпорт превращается в кашу.
-x - дописывает в сообщение строку вида (cherry picked from commit a1b2c3d...). Это не косметика. В релиз-ветках через полгода никто не вспомнит, откуда фикс. Строка -x даёт обратную ссылку на оригинал в main - аудит, трассировка, поиск. Правило: для бэкпортов между ветками одного репозитория всегда -x.
Код: Выделить всё
git cherry-pick -x a1b2c3d
Код: Выделить всё
fix: null pointer in PaymentService
(cherry picked from commit a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0)
-e (--edit) - открыть редактор и поправить сообщение перед коммитом. Полезно, когда в бэкпорте надо дописать номер тикета релиза или пометку backport.
-n (--no-commit) - применить изменения в рабочее дерево и индекс, но не коммитить. Ты получаешь staged-изменения и можешь их доработать, объединить с другими, или собрать несколько cherry-pick в один коммит. Классика - собрать руками сложный бэкпорт из двух связанных фиксов:
Код: Выделить всё
git cherry-pick -n a1b2c3d
git cherry-pick -n e4f5a6b
git commit -m "backport: payment hotfix (2 commits squashed)"
Можно передать список:
Код: Выделить всё
git cherry-pick a1b2c3d e4f5a6b 9c8d7e6
Диапазон. Тут главная ловушка новичков. Запись A..B означает не включая A, то есть коммиты от A (исключительно) до B (включительно). Сам A не переносится.
Код: Выделить всё
git cherry-pick feature~5..feature
Код: Выделить всё
git cherry-pick A^..B
Конфликты: --continue, --abort, --quit
Diff не всегда ложится чисто - в релиз-ветке файл мог разойтись с main. Тогда:
Код: Выделить всё
git cherry-pick a1b2c3d
Auto-merging src/PaymentService.php
CONFLICT (content): Merge conflict in src/PaymentService.php
error: could not apply a1b2c3d... fix: null pointer
hint: After resolving the conflicts, mark them with
hint: "git add/rm <pathspec>", then run "git cherry-pick --continue".
- git cherry-pick --continue - продолжить после разрешения (при диапазоне идёт к следующему коммиту).
- git cherry-pick --abort - всё отменить, вернуть ветку в исходное состояние до начала операции.
- git cherry-pick --quit - выйти из процесса, но не откатывать уже применённые коммиты. Нужен редко: когда в диапазоне часть применилась, ты доволен, а остаток брать передумал.
Cherry-pick merge-коммита: флаг -m
У merge-коммита два родителя, и Git не знает, относительно какого считать diff. Поэтому простой cherry-pick merge-коммита падает:
Код: Выделить всё
git cherry-pick 5a6b7c8
error: commit 5a6b7c8 is a merge but no -m option was given.
fatal: cherry-pick failed
Код: Выделить всё
git cherry-pick -m 1 5a6b7c8
Чем cherry-pick отличается от rebase --onto и от revert
Три команды постоянно путают.
cherry-pick копирует коммиты в текущую ветку, оригиналы остаются на месте. Источник не трогается. Идеален для бэкпорта одного-двух коммитов.
rebase --onto перемещает (пересаживает) серию коммитов на новое основание и переписывает историю ветки - старые коммиты становятся недостижимы. Это операция над веткой целиком, не над отдельным патчем. Если надо переселить хвост из десяти коммитов на другой базис - это rebase --onto, а не десять cherry-pick.
revert - противоположность по смыслу: создаёт коммит с обратным diff, чтобы отменить эффект уже влитого коммита, не переписывая историю. cherry-pick добавляет изменение, revert вычитает.
Простое правило: cherry-pick - "дай мне это изменение сюда тоже", rebase --onto - "перенеси эту ветку на другой фундамент", revert - "отмени это, но честно, новым коммитом".
git cherry: какие коммиты ещё не в апстриме
Перед бэкпортом полезно узнать, что из твоей ветки уже уехало в апстрим, а что нет - чтобы не пикать дубли. Для этого есть git cherry.
Код: Выделить всё
git cherry -v main release/2.3
Код: Выделить всё
+ 7f4e9a2 fix: null pointer in PaymentService
- 3a2b1c0 chore: bump version
+ 9d8c7b6 feat: retry on timeout
То же самое мощнее даёт git log. Опция --cherry-mark помечает patch-эквивалентные коммиты знаком = , а уникальные - знаком +:
Код: Выделить всё
git log --oneline --cherry-mark main...release/2.3
Код: Выделить всё
git log --oneline --cherry-pick --right-only main...release/2.3
Типичные грабли
- Забыл -x на бэкпорте - через полгода никто не найдёт оригинал. Для кросс-веточных переносов внутри репо -x обязателен.
- Перепутал границу диапазона: A..B не включает A. Недосчитался коммита - проверь, нужен ли A^..B.
- Пикаешь дубликаты, потому что не свериться через git cherry. Получаешь два коммита с одинаковым diff и потом конфликты при будущем мерже.
- Простой cherry-pick merge-коммита без -m - падает с явной ошибкой. Это не баг, это требование указать mainline.
- Застрял "внутри" операции при конфликте и делаешь обычный git commit вместо --continue. Закрывай операцию правильным флагом.
- Массовый cherry-pick десятков коммитов вместо мержа/rebase - дублируешь историю, ломаешь дальнейшие слияния. cherry-pick - для точечного, не для переноса веток целиком.
Повтори по шагам, всё локально.
Код: Выделить всё
git init backport-lab && cd backport-lab
git commit --allow-empty -m "init"
git switch -c release/1.0
git switch -c main release/1.0
# фикс в main
printf "fixed\n" > bug.txt
git add bug.txt
git commit -m "fix: critical bug in parser"
# что ещё не в релизе
git cherry -v release/1.0 main
# бэкпорт с обратной ссылкой
git switch release/1.0
git cherry-pick -x main
git log --oneline -1
git show --stat HEAD
Контрольные вопросы
- Почему после cherry-pick у коммита другой SHA, хотя diff тот же? Что при этом меняется, а что сохраняется?
- Что делает -x и в каком случае его ставить НЕ нужно?
- Сколько коммитов перенесёт git cherry-pick X~3..X и какой из граничных коммитов не попадёт?
- Чем git cherry и git log --cherry-pick отличаются по способу сравнения коммитов от простого сравнения по SHA?
git cherry-pick - это копир одного изменения в текущую ветку с рождением нового коммита. Для бэкпорта коммита git добавляй -x (обратная ссылка), для тонкой сборки -n, для правки сообщения -e. Диапазон A..B исключает A. Конфликты разруливаются как при мерже через --continue/--abort/--quit, а merge-коммит требует -m. Не путай с rebase --onto (переезд ветки) и revert (отмена). И перед серией переносов всегда сверяйся через git cherry и git log --cherry-pick - чтобы пикать только то, чего реально нет в апстриме.