git cherry-pick: перенос отдельных коммитов

Рейтинг: 61% · 6 голосов
Самый подробный курс по Git - от первого коммита до объектной базы и packfiles. Терминал и первый репозиторий, ветки и слияние, конфликты, удалённая работа и форджи (GitHub, GitLab), rebase и переписывание истории, спасение кода через reflog и fsck, stash и worktree, bisect и blame, подмодули и монорепо, хуки и подпись коммитов, внутреннее устройство и масштаб. Для новичков и профи. Актуально на 2026 (Git 2.54, дорожная карта 3.0).
Ответить
Аватара пользователя
Pavel_Git
Сообщения: 52
Зарегистрирован: 11 май 2026, 05:31

git cherry-pick: перенос отдельных коммитов

Сообщение Pavel_Git »

Оглавление курса (52)
  1. Что такое контроль версий и почему победил Git
  2. Установка Git на Windows, macOS и Linux и первая настройка
  3. Терминал и как спрашивать у Git: выживание в командной строке
  4. Ментальная модель Git: три состояния, коммит-снимок и граф
  5. Первый репозиторий: init, add, commit, status
  6. Просмотр изменений: status, diff и индексация по частям
  7. История проекта: git log, диапазоны и поиск
  8. .gitignore: что не должно попасть в репозиторий
  9. .gitattributes: окончания строк, бинарники и атрибуты
  10. Ветки как указатели: branch, switch и detached HEAD
  11. Слияние веток: fast-forward против трёхстороннего merge
  12. Конфликты слияния: анатомия и уверенное разрешение
  13. Практикум: твой первый день с Git от нуля до push
  14. Удалённые репозитории: remote, fetch, базовый pull и refspec
  15. git push: безопасная отправка и защита от перезаписи
  16. Клонирование: shallow, partial clone, bundle и backfill
  17. GitHub, GitLab и форки: pull request, rulesets и защита веток
  18. Рабочие процессы команды: GitFlow, GitHub Flow, trunk-based
  19. Merge queue и merge trains: безопасное вливание в trunk
  20. Культура code review и Conventional Commits
  21. git reset: три дерева и отмена индексации и коммитов
  22. git revert: безопасная отмена в общей истории
  23. Правка последнего коммита: git commit --amend
  24. git rebase: пересадка истории и золотое правило
  25. Интерактивный rebase: переписать серию коммитов
  26. git cherry-pick: перенос отдельных коммитов (вы здесь)
  27. Stacked diffs: цепочка зависимых PR
  28. Массовое переписывание истории: git filter-repo и git replace
  29. git reflog: журнал, который помнит всё
  30. Восстановление утерянных коммитов, веток и файлов
  31. Откат опасных операций: reset --hard, сломанный rebase, плохой merge
  32. git stash: тайник для незавершённой работы
  33. git worktree: несколько рабочих деревьев одного репозитория
  34. git bisect: бинарный поиск регрессии
  35. git blame, mailmap и археология кода
  36. Теги, релизы, archive и bundle
  37. Подмодули: внешние репозитории внутри проекта
  38. Subtree, монорепозитории и sparse-checkout
  39. Git LFS и большие файлы
  40. Вспомогательные команды: clean, mv, rm, restore
  41. Псевдонимы и кастомизация .gitconfig
  42. Хуки Git: автоматизация на клиенте и сервере
  43. Подпись коммитов и тегов: SSH, GPG и Sigstore
  44. Внутреннее устройство I: объектная база Git
  45. Внутреннее устройство II: ссылки, HEAD, notes и reftable
  46. Внутреннее устройство III: индекс, packfiles, gc и обслуживание
  47. От SHA-1 к SHA-256: переход на новый хеш
  48. Огромные репозитории: partial clone, FSMonitor, Scalar
  49. Git в CI/CD: shallow, кеш, merge_group и безопасность
  50. Куда движется Git: дорожная карта 3.0 и экосистема
  51. Профессиональный setup: собираем рабочее окружение Git
  52. 30+ типичных ошибок Git и как из них выбираться
Боль, которую решает git cherry-pick

Ситуация из жизни. У тебя в проде ветка 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 7f4e9a2. Это не a1b2c3d. Содержимое diff то же, объект другой. Дата авторства сохранилась (Date: Jun 8 - когда фикс родился в main), а дата коммита стала текущей. Автор остался прежним, коммиттером Git запишет тебя. Это правильное поведение: авторство принадлежит тому, кто написал код, а ты лишь перенёс.

Можно указывать коммит как угодно: по 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)
Важная оговорка: -x не ставят при переносе коммитов между разными репозиториями (форки), где исходный SHA для чужого читателя бессмыслен.

-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)"
Несколько коммитов и диапазон A..B

Можно передать список:

Код: Выделить всё

git cherry-pick a1b2c3d e4f5a6b 9c8d7e6
Git применит их по очереди, в указанном порядке, каждый - отдельным коммитом.

Диапазон. Тут главная ловушка новичков. Запись A..B означает не включая A, то есть коммиты от A (исключительно) до B (включительно). Сам A не переносится.

Код: Выделить всё

git cherry-pick feature~5..feature
Это возьмёт последние пять коммитов ветки feature. Если хочешь включить нижнюю границу - используй A^..B (от родителя A) или явный синтаксис:

Код: Выделить всё

git cherry-pick A^..B
Граница A^ означает "начиная с самого A". Перепутать .. и забыть про исключённую границу - причина номер один, почему из пяти коммитов приезжают четыре.

Конфликты: --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 add разрешённого файла, затем:
  • git cherry-pick --continue - продолжить после разрешения (при диапазоне идёт к следующему коммиту).
  • git cherry-pick --abort - всё отменить, вернуть ветку в исходное состояние до начала операции.
  • git cherry-pick --quit - выйти из процесса, но не откатывать уже применённые коммиты. Нужен редко: когда в диапазоне часть применилась, ты доволен, а остаток брать передумал.
Состояние операции Git держит в .git/sequencer и .git/CHERRY_PICK_HEAD. Пока этот файл есть - ты "внутри" cherry-pick, и git status честно напишет про незавершённую операцию. Не пытайся в этот момент делать обычный commit вручную без понимания - закончи через --continue или --abort.

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
Надо указать номер mainline-родителя через -m. -m 1 - первый родитель (обычно ветка, в которую вливали, чаще всего то, что нужно). -m 2 - второй (влитая ветка).

Код: Выделить всё

git cherry-pick -m 1 5a6b7c8
Это берёт изменения мержа относительно первого родителя - то есть суммарную дельту, которую влили. Случай редкий и тонкий: чаще честнее перенести исходные коммиты фичи по отдельности, чем тащить merge-коммит. Но если в истории сквош-мерж или octopus - -m бывает единственным выходом. Запоминай нумерацию: -m 1 почти всегда то, что ты хочешь.

Чем 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
Разбор. Знак + - коммит есть в release/2.3, но в main нет patch-эквивалента (не применён). Знак - - коммит уже есть в апстриме как cherry-pick (patch-эквивалентен), пикать не нужно. git cherry сравнивает по содержимому diff (patch-id), а не по SHA - поэтому он видит копии, у которых SHA другой. Это ровно то, что нужно для бэкпорт-учёта.

То же самое мощнее даёт git log. Опция --cherry-mark помечает patch-эквивалентные коммиты знаком = , а уникальные - знаком +:

Код: Выделить всё

git log --oneline --cherry-mark main...release/2.3
А --cherry-pick вообще выкидывает из вывода всё, что уже перенесено, оставляя только реально уникальное:

Код: Выделить всё

git log --oneline --cherry-pick --right-only main...release/2.3
Есть и сокращение: опция --cherry - это синоним --right-only --cherry-mark --no-merges. Обрати внимание на три точки (...) - это симметрическая разность, а не диапазон. Связка git cherry и git log --cherry-pick - твой главный инструмент аудита: перед серией бэкпортов сначала смотришь, что реально не доехало, и пикаешь только это.

Типичные грабли
  • Забыл -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 picked from commit ...), а SHA отличается от оригинала в main. Затем повтори git cherry - после бэкпорта коммит должен пометиться знаком - (уже перенесён). Дополнительно: создай конфликтующее изменение в release/1.0, повтори cherry-pick, словишь конфликт - и пройди цикл add -> --continue, потом ещё раз с нуля через --abort.

Контрольные вопросы
  • Почему после 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 - чтобы пикать только то, чего реально нет в апстриме.
👍3 ❤️1 🔥 😄 🤔3
Аватара пользователя
pandas_veteran
Сообщения: 1
Зарегистрирован: 27 май 2026, 17:20

Re: git cherry-pick: перенос отдельных коммитов

Сообщение pandas_veteran »

Долго не доходило почему SHA меняется, а тут наконец щёлкнуло: diff тот же, родитель другой - значит объект новый. Спасибо за объяснение через объектную модель.
👍 ❤️ 🔥 😄 🤔
Аватара пользователя
vladimirtokar
Сообщения: 1
Зарегистрирован: 02 июн 2026, 05:17

Re: git cherry-pick: перенос отдельных коммитов

Сообщение vladimirtokar »

Вопрос: если я уже сделал бэкпорт без -x, можно ли как-то дописать ссылку на оригинал потом, или проще git commit --amend руками поправить сообщение?
👍 ❤️1 🔥 😄 🤔2
Ответить
← Предыдущая глава
Интерактивный rebase: переписать серию коммитов
Следующая глава →
Stacked diffs: цепочка зависимых PR

Все главы курса «Git профессионально: от первого коммита до внутреннего устройства»

Поделиться темой: ✈ Telegram VK
Похожие запросы: git commit: фиксация измененийgit tag: теги и релизыgit blame: кто изменил строкуПодпись коммитов в Git (SSH, GPG)git cherry-pick: перенос коммитов

Вернуться в «Git профессионально: от первого коммита до внутреннего устройства»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя