Исправление доставки уже закоммичено в ветке новой функции, но нужно и в релизной ветке. Перенесём выбранный коммит через git cherry-pick, проверим новый результат и отдельно воспроизведём конфликт, чтобы отработать продолжение и отмену операции.

Выберите изменение и ветку назначения

Нужны Git и базовые навыки работы с ветками и коммитами. Все опыты выполняются в отдельном локальном репозитории. Команды записи файлов рассчитаны на macOS, Linux или Git Bash в Windows; реальная рабочая история для примера не понадобится.

git cherry-pick применяет изменение из выбранного коммита к текущей ветке и записывает результат новым коммитом. Исходный коммит остаётся в своей истории. Ветки сохраняют самостоятельность: новые изменения исходной ветки после этого сами в релизную не попадут.

Для этой задачи выбираем один самостоятельный коммит с исправлением. Если релизу нужны все изменения ветки, сначала рассмотрите обычное слияние по правилам проекта. Cherry-pick удобен, когда граница переноса действительно совпадает с отдельным изменением.

В рабочем репозитории перед любым переносом выполните:

git status --short --branch
git log --oneline --decorate -8

Текущая ветка — место назначения. Идентификатор после cherry-pick указывает источник. Путать эти роли опасно: команда может успешно сделать коммит в ветке, которую вы не собирались менять.

Пустой вывод изменений в status нужен для понятного исходного состояния. Незавершённую работу сохраните принятым способом до переноса. Не применяйте reset --hard или удаление файлов ради чистого статуса: это уже другая операция с риском потери работы.

Соберите учебную историю

Выберите папку для опытов, затем создайте репозиторий:

mkdir cherry-pick-lab
cd cherry-pick-lab
git init -b main
git config user.name 'Учебный разработчик'
git config user.email 'learner@example.invalid'
git config commit.gpgsign false
printf 'Доставка: 350 рублей\n' > delivery.txt
printf 'Каталог книг\n' > catalog.txt
git add delivery.txt catalog.txt
git commit -m 'Добавляет исходный каталог'
git branch release

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

git switch -c feature/delivery
printf 'Фильтр: по жанру\n' >> catalog.txt
git add catalog.txt
git commit -m 'Добавляет фильтр каталога'

Теперь сделайте самостоятельное исправление в другом файле:

printf 'Доставка: 250 рублей\n' > delivery.txt
git add delivery.txt
git commit -m 'Исправляет стоимость доставки'
git log --oneline -3

В истории видны исходный каталог, затем фильтр и исправление доставки. Для релиза нужна только последняя разница. Ветка новой функции должна сохранить фильтр у себя.

Посмотрите точный состав последнего коммита:

git show --stat HEAD
git show HEAD -- delivery.txt

В изменении должен быть один файл с заменой 350 на 250. Важно видеть саму разницу: сообщение коммита может обещать исправление доставки, а внутри окажутся дополнительные файлы. При переносе Git ориентируется на содержимое коммита.

Сохраните идентификатор в переменную текущего терминала:

fix_commit=$(git rev-parse HEAD)

Дальнейшие команды с этой переменной выполняйте в том же терминале. Если открыли новый, посмотрите git log feature/delivery и снова присвойте переменной проверенный идентификатор исправления. Копируйте настоящий хеш вашего репозитория: в другом запуске он будет отличаться.

Примените коммит в release

Переключитесь на ветку назначения и проверьте её:

git switch release
git status --short --branch
cat catalog.txt
cat delivery.txt

Каталог содержит только исходный заголовок, цена — 350 рублей. При чистом статусе выполните перенос:

git cherry-pick -x "$fix_commit"

Параметр -x добавляет в сообщение нового коммита ссылку на идентификатор источника. Для переноса исправления между долгоживущими ветками это помогает позже установить происхождение изменения. У команды есть и другие режимы, но для нашего одиночного коммита они не требуются.

После успешного выполнения проверьте файлы и историю:

cat delivery.txt
cat catalog.txt
git log -1 --format=full
git status --short --branch

Ожидается «Доставка: 250 рублей», а фильтра в каталоге по-прежнему нет. На вершине release появился новый коммит. Рабочее дерево чистое.

Сравните идентификаторы:

git rev-parse HEAD
git rev-parse feature/delivery

Они различаются. В нашем стенде исходный коммит исправления опирается на коммит фильтра, а новый коммит в release — на исходный каталог. У коммитов разные родители; кроме того, -x изменил сообщение нового. Cherry-pick перенёс изменение, исходная ветка осталась на месте.

Схематично до операции история была такой:

main, release:  A
                \
feature:         B -- C

После успешного переноса:

main:          A -- C'  release
                \
feature:         B -- C

B — фильтр, C — исходное исправление. C' содержит применённое исправление в другом окружении истории. Схема показывает родство коммитов; конкретный текст после применения всё равно нужно проверить.

Убедитесь, что коммит самодостаточен

Отсутствие конфликтов ещё не гарантирует работоспособность. Например, выбранный коммит вызывает функцию, которую добавили в предыдущем коммите исходной ветки. Git применит строку, но нужной функции в релизе не окажется.

Перед переносом проверьте файлы коммита и зависимости от окружающего кода. В нашем примере исправление меняет самостоятельную строку delivery.txt, поэтому фильтр каталога ему не нужен. Это свойство учебного изменения, а не гарантия команды Git.

Проверяйте поведение в целевой ветке. Для текстового стенда проверяем цену и отсутствие фильтра. Для приложения понадобится тест стоимости доставки или выбранный сценарий заказа. Прошедшие тесты исходной ветки не заменяют запуск на релизной: набор соседних изменений другой.

Если перенос требует половину новой функции, остановитесь и пересмотрите границу коммита. Иногда лучше отдельно подготовить самостоятельное исправление в исходной истории, чем тянуть цепочку случайно найденных зависимостей в релиз.

Воспроизведите конфликт без риска для работы

Успешный перенос завершён. Для конфликта создадим ещё одну ветку от исходного main. У release уже есть исправление, и повторять опыт в ней не нужно.

Выполните:

git switch -c release-conflict main
printf 'Доставка: 300 рублей\n' > delivery.txt
git add delivery.txt
git commit -m 'Устанавливает региональную цену'
git cherry-pick -x "$fix_commit"

Git увидит, что одна и та же исходная строка изменилась по-разному: в текущей ветке 350 превратилось в 300, в переносимом коммите — в 250. Команда остановится с конфликтом.

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

Посмотрите состояние:

git status
cat delivery.txt

В файле появятся маркеры конфликта примерно такого вида:

<<<<<<< HEAD
Доставка: 300 рублей
=======
Доставка: 250 рублей
>>>>>>> идентификатор-коммита

Идентификатор и подпись в вашем файле могут отличаться. HEAD здесь показывает текущую ветку release-conflict, нижняя часть относится к применяемому изменению. Git сообщает о несовместимости строк; выбор смысла остаётся за вами.

Откройте файл в редакторе и замените весь конфликтный участок на одну итоговую строку:

Доставка: 250 рублей

Удалите и разделители, и отброшенный вариант. В учебном стенде тот же результат можно получить командой:

printf 'Доставка: 250 рублей\n' > delivery.txt

Такая перезапись уместна только потому, что мы знаем полное содержимое этого маленького файла. В рабочем компоненте она могла бы стереть соседние изменения. Там редактируйте конфликтный участок и проверяйте весь затронутый сценарий.

Продолжите перенос после проверки

Проверьте отсутствие маркеров, затем отметьте файл разрешённым:

git diff --check
git add delivery.txt
git diff --cached -- delivery.txt
git status

В индексе ожидается переход от региональной цены 300 к выбранной цене 250. В status больше не должно быть неразрешённых путей. Индекс хранит итог, который попадёт в создаваемый коммит.

Завершите начатую операцию:

git cherry-pick --continue

Если открылся редактор сообщения, проверьте текст, сохраните его и закройте редактор. Используйте продолжение уже начатой операции.

Выполните контроль:

git status --short --branch
git log -1 --oneline
cat delivery.txt

Дерево должно стать чистым, на вершине появится коммит переноса, цена — 250 рублей. Если в реальном коде конфликт затронул обработчик события или расчёт, дополнительно проверьте поведение. Формально удалённые маркеры ещё не доказывают верное объединение логики.

Опция -x полезна для происхождения изменения, но не заменяет описание ручного решения. Если вы меняли смысл при разрешении конфликта, сохраните эту информацию в сообщении коммита и ревью по правилам команды.

Отмените незавершённую операцию

Иногда во время конфликта выясняется, что коммит зависит от неподготовленного кода или вы выбрали не ту ветку. Для незавершённой операции доступна отмена:

git cherry-pick --abort

Команда возвращает состояние к началу этой операции. В нашем конфликтном опыте это коммит с ценой 300, потому что он уже существовал до запуска cherry-pick.

Для отдельной тренировки создайте ещё одну ветку и повторите исходное столкновение:

git switch -c release-abort main
printf 'Доставка: 300 рублей\n' > delivery.txt
git add delivery.txt
git commit -m 'Готовит ветку для отмены переноса'
git cherry-pick -x "$fix_commit"

После ожидаемого конфликта выполните git cherry-pick --abort, затем git status --short --branch и cat delivery.txt. Ожидается чистое дерево и цена 300. Так вы проверите отмену на управляемом состоянии, где до операции точно не было незакоммиченных файлов.

Ручные правки, сделанные только для разрешения текущего конфликта, при отмене не становятся самостоятельным коммитом. Если нашли в них полезное отдельное решение, сохраните его осознанно до отказа от операции. Не рассчитывайте, что abort создаст запасную копию каждого варианта файла.

После завершённого переноса --abort уже не подходит: активной операции нет. Если ошибочный коммит попал в общую ветку, используйте принятый процесс отмены через git revert с идентификатором нового коммита. Это создаёт обратное изменение в истории; перед ним тоже нужны чистый статус и проверка затронутого поведения.

Различайте конфликт, пустой перенос и неверную ветку

Состояние Что оно означает Следующее действие
status показывает неразрешённые пути (unmerged paths) Итог файлов ещё не выбран Исправить содержимое, add, --continue
Git сообщает о пустом переносе Нужное изменение могло уже присутствовать Сравнить файлы и историю, затем осознанно решить вопрос --skip
Ошибка на незакоммиченных файлах Перенос мешает текущей работе Сохранить её до новой попытки
Успех в неожиданной ветке Текущая ветка была выбрана неверно Проверить новый коммит и исправить историю по правилам проекта
В файлах верно, тест падает Возможна зависимость от другого изменения Проанализировать исходный коммит и окружение назначения
В терминале открывается редактор Git ждёт сообщения коммита Сохранить сообщение и закрыть редактор

--skip пропускает текущий коммит в незавершённой последовательности. Это не универсальный ответ на конфликт. Сначала убедитесь, что нужное изменение уже есть или действительно не должно попасть в ветку. Иначе команда завершится без части ожидаемого результата.

Общий маршрут восстановления состояния разобран в материале «Первая помощь в Git». В задаче с cherry-pick полезнее всего сначала назвать активную операцию, выбранный источник и текущее назначение.

Проверьте себя двумя действиями. Вернитесь в release: цена должна быть 250, строки фильтра в каталоге быть не должно. Затем посмотрите feature/delivery через git show feature/delivery:catalog.txt: там фильтр должен сохраниться. Так вы подтвердите, что перенос не удалил исходное изменение и не забрал лишний коммит.

Для самостоятельной практики создайте от main ветку fix/catalog-title, сделайте в ней один коммит с новым заголовком и перенесите его в отдельную ветку release-title. Проверьте сообщение с -x, итог файла и чистый статус. После этого воспроизведите конфликт двух разных заголовков и отмените его через --abort.

Продолжить практику можно в интерактивных курсах HTML Academy. Перед переносом следующего настоящего исправления запишите пару: «этот коммит → эта текущая ветка» и проверьте её командами show и status.