Git cherry-pick: несколько коммитов, ветки, конфликты

Git cherry-pick: как скопировать коммит из другой ветки, перенести несколько коммитов или диапазон, разрешить конфликты и когда лучше merge или rebase.

HUMANавтор @mrsaynothing44d5 мин чтения

TL;DR: git cherry-pick <sha> копирует коммит из любой ветки в вашу текущую — тот же патч, новый SHA, история никуда не сдвигается. Для нескольких коммитов перечислите SHA или возьмите диапазон (git cherry-pick A..B); учтите, что диапазон исключает A, так что пишите A^..B, если его нужно включить. Если пик остановился на конфликте — разрешите его, git add, затем git cherry-pick --continue. Cherry-pick — чтобы перенести конкретный фикс; когда нужна вся ветка целиком, берите merge или rebase.

Что git cherry-pick делает на самом деле?

Cherry-pick берёт существующий коммит и применяет его diff как новый коммит в вашей текущей ветке. Оригинал остаётся где был; копия получает свежий SHA. Git ничего не «перемещает» — те, кто потом удивляется, почему коммит всё ещё виден в старой ветке, видят ровно это.

Модель «копия, а не перенос» определяет, когда cherry-pick — правильный инструмент:

  • Нужен один фикс из фичевой ветки на main прямо сейчас, без мержа остального.
  • Хотфикс, закоммиченный не в ту ветку, должен попасть в нужную.
  • Патч надо воспроизвести на релизной ветке, которая никогда не мержит из main.

Смежный навык — умение откатить коммит, который оказался ошибкой; механика этого разобрана в git undo last commit: keep the changes.

Как cherry-pickнуть коммит из другой ветки?

Найдите SHA, переключитесь на целевую ветку, пикайте:

# 1. Найдите коммит в исходной ветке
git log feature/payment-fix --oneline -5

# 2. Переключитесь на ветку, которая должна его получить
git switch main

# 3. Скопируйте его
git cherry-pick 1a2b3c4

Два удобства, которые стоит знать:

  • git cherry-pick <branch> пикает верхний (tip) коммит ветки — удобно, но легко сработать случайно, держа в голове устаревшее представление о том, где этот tip.
  • После пика git log -1 --stat подтверждает, что приземлилось. Одна секунда чтения — и revert не понадобится.

Коммиты остаются в feature/payment-fix; удаляйте ветку когда угодно — скопированный коммит на main имеет собственный SHA и никак не зависит от старого.

Как перенести несколько коммитов?

Три формы, по убыванию частоты:

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

# 1. Явный список — применяется в порядке перечисления
git cherry-pick 1a2b3c4 5d6e7f8

# 2. Диапазон — всё после A вплоть до B включительно
git cherry-pick A..B

# 3. Диапазон вместе с A
git cherry-pick A^..B

Различие A..B против A^..B — классический сюрприз: A..B исключает A. Если вы визуализируете диапазон из git log и пикаете oldest..newest, вы молча пропускаете самый старый коммит. Когда цель — «несколько самых старых, по порядку», пишите oldest^..newest, и off-by-one исчезает.

Чтобы слить несколько пиков в один коммит вместо трёх, стейджируйте без коммита с -n / --no-commit и закоммитьте один раз:

git cherry-pick -n 1a2b3c4 5d6e7f8
git commit -m "Backport: payment retry fixes"

Почему git cherry-pick не работает?

Четыре реальные причины, по убыванию частоты:

1. Пик остановился на конфликте. Git применяет патч, натыкается на строку, изменённую в обеих ветках, и ставит последовательность на паузу:

# разрешите файлы, затем:
git add <resolved-files>
git cherry-pick --continue   # или --abort, чтобы вернуться к состоянию до пика

--continue не опционален и не подразумевается — пока вы его не выполнили, вы внутри приостановленной последовательности cherry-pick, и git status будет об этом напоминать.

2. Пик пуст («The previous cherry-pick is now empty»). Изменение уже есть в этой ветке — обычно из более раннего пика или сквош-мержа. Пропустите через git cherry-pick --skip или форсируйте пустой коммит через --allow-empty, если маркер действительно нужен.

3. Коммит — merge-коммит. У мержа два родителя, поэтому «примени этот diff» неоднозначно — git отказывается, а не угадывает. Скажите, с каким родителем считать diff:

git cherry-pick -m 1 <merge-sha>   # родитель 1 = ветка, *в* которую вы мержили

4. Не то рабочее дерево или detached HEAD. Пик приземлится туда, куда указывает HEAD. git branch --show-current перед пиком; если вывод пуст, вы в detached HEAD, и коммит осиротеет при переключении.

Cherry-pick vs merge vs rebase: что и когда?

Команда Что приземляется в целевой ветке История Когда использовать
git cherry-pick <sha> Только названные коммиты Копия, новые SHA Один конкретный фикс должен переехать сейчас
git merge <branch> Всё, что есть в ветке Merge-коммит или fast-forward Нужна вся ветка, расхождение видно
git rebase <base> Все коммиты ветки, воспроизведённые заново Линейная, SHA переписаны Нужна ветка как чистая линейная история
git revert <sha> Обратный коммит Добавляет undo-коммит Ушедший коммит надо отменить в общей истории

Правило в одну строку: cherry-pick переносит выборку; merge и rebase переносят всё. Тянуться к cherry-pick, чтобы «синхронизироваться» с веткой, — признак, что вам на самом деле нужен merge; а если ветка — main вашего форка против upstream, полный ритуал описан в sync a fork with upstream, step by step.

Привычка, от которой стоит отказаться: вечно держать один и тот же коммит в нескольких ветках. Каждый будущий фикс в исходной ветке потребует нового пика, и ветки рано или поздно разъезжаются. Бэкпорты в релизные ветки — нормальный паттерн; постоянная параллельная вселенная — нет.

Workflow cherry-pick: кратко

git log <source-branch> --oneline -5   # найдите SHA(ы)
git switch <target-branch>             # окажитесь в нужном месте
git cherry-pick A^..B                  # диапазон, список или одиночный SHA
# при конфликте: resolve → git add → git cherry-pick --continue
git log -1 --stat                      # проверьте, что приземлилось

Найти, переключиться, пикнуть, проверить. У команды репутация опасной, которой она не заслуживает: diff либо применяется, либо всё останавливается и объясняет почему. Единственная по-настоящему разрушительная ошибка — пикнуть не в ту ветку, и git log -1 перед пушем делает её трудно прозевать.