JavaRush /Курсы /Claude code /Git-first recovery: откат AI-итераций

Git-first recovery: откат AI-итераций

Claude code
6 уровень , 2 лекция
Открыта

1. Сначала остановить лавину, потом лечить

Claude полез не туда — и хочется ответить эмоционально: «нет-нет, исправь всё обратно и сделай нормально». В этот момент вы теряете управление. У вас уже один плохой diff, а таким запросом вы дописываете второй, ещё менее понятный.

Пример из Commerce OS. Вы просили вынести refund decision logic из RefundController в отдельный RefundService. Scope узкий. А Claude «заодно» тронул OrderService, поменял таймаут в PaymentProviderClient и прошёлся форматированием по чужому файлу. Скажете «почини всё» — он чинит поверх собственного хаоса. Станет только труднее понять, что произошло.

Сначала остановите лавину: перестаньте просить правки и посмотрите на состояние проекта как на факт. Git для этого и нужен. Он не спорит и не обещает, что «теперь точно всё готово». Он показывает: какие файлы изменены, чем отличаются, какие коммиты есть.

Ситуация Импульсивная реакция Рабочая реакция
Claude тронул лишние файлы «Исправь всё обратно» Посмотреть git status, потом git diff по каждому подозрительному файлу
Тесты упали после серии правок «Попробуй ещё раз починить» Решить, что оставить, а что откатить, и вернуть проект в понятное состояние
Появился плохой коммит «Сейчас поверх него сделаем хороший» Выбрать осознанный rollback: revert, reset или новая ветка

AI-assisted разработку взрослой делает не скорость первой правки. А умение спокойно восстановиться после неудачной.

2. Git разделяет черновик, staging и историю

Чтобы команды отката не казались заклинаниями, держите в голове три слоя Git. Без этого restore, revert и reset сливаются в кашу: всё «что-то откатывает», а что именно — непонятно.

Модель простая. Файлы на диске — текущее рабочее состояние. Staging area — изменения, которые ждут следующего коммита. История коммитов — то, что уже зафиксировано.

Слой Что это Что обычно с ним делают
Рабочая директория Файлы на диске прямо сейчас Смотрят git diff, возвращают через git restore
Подготовленные изменения То, что уже добавили в staging area Проверяют перед коммитом, при необходимости убирают из staged
История коммитов Уже сохранённые шаги Отменяют через git revert или локально переписывают через git reset

Почему это важно в AI-работе? Потому что Claude за один проход может задеть все три слоя. Просто поменять файлы. Подготовить их к коммиту. Или уже создать коммит. Команда восстановления зависит от того, где сейчас проблема.

Ещё граница. Git возвращает код и историю проекта, но не внешние эффекты. Запустил Claude миграцию БД, дёрнул внешний сервис, изменил удалённую систему — Git вернёт файлы, но не это. В этой лекции мы говорим про файловое и коммитное восстановление. То, что Git делает очень хорошо.

3. Первая пара команд: git status и git diff

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

git status отвечает на вопрос что затронуто: на какой вы ветке, какие файлы изменены. Часто уже видно, что Claude вышел за scope. Работали с refund-flow, а в выводе OrderService и PaymentProviderClient — сигнал смотреть diff по ним.

git status
# On branch refund-refactor
# Changes not staged for commit:
#   modified: src/main/java/com/acme/commerce/refund/RefundController.java
#   modified: src/main/java/com/acme/commerce/order/OrderService.java
#   modified: src/main/java/com/acme/commerce/payments/PaymentProviderClient.java

git diff отвечает на вопрос в чём именно проблема. Открывать diff по всему проекту сразу не нужно — на длинной задаче удобнее по конкретным файлам.

git diff -- src/main/java/com/acme/commerce/payments/PaymentProviderClient.java
# - private int timeoutMs = 5000;
# + private int timeoutMs = 30000;
# + // additional formatting changes below

Тут и видно, что правка не относится к задаче. Просили вынести refund logic — получили изменение таймаута в payment-клиенте. Это не «может, пригодится». Это лишнее: либо объясните серьёзной причиной, либо откатите без сантиментов.

Маршрут принятия решения:

flowchart TD
    A[Неудачная AI-итерация] --> B[git status]
    B --> C[git diff]
    C --> D{Есть нужные изменения?}
    D -- Нет --> E[Откатить лишнее]
    D -- Да --> F{Есть commit?}
    F -- Нет --> G[git restore по файлам]
    F -- Да --> H{История уже shared?}
    H -- Да --> I[git revert]
    H -- Нет --> J[git reset или новая ветка]

Алгоритм убирает суету. Сначала видите реальность, потом выбираете минимальный rollback.

4. Карта rollback: restore, revert, reset, ветка

На словах команды отката похожи: что-то вернуть, убрать, отменить. На практике задачи разные, и не та команда усложняет жизнь. Мыслите не названиями, а сценариями: что произошло и насколько далеко ушло.

Команда / подход Когда применять Что меняется Где нужна осторожность
git restore
Лишние правки есть в файлах, но коммита ещё нет Возвращает содержимое файла Можно случайно стереть полезные несохранённые изменения
git revert
Плохой коммит уже есть, и историю лучше не переписывать Создаёт новый коммит-отмену История становится длиннее, но безопаснее
git reset
Плохой коммит есть только в вашей локальной ветке Сдвигает указатель ветки назад Опасен на общей истории и при привычке делать всё бездумно
Новая ветка Текущая попытка слишком грязная и дорогая в спасении Вы начинаете заново с чистой базы Нужно не потерять полезное, если оно всё-таки было

Коммита ещё нет — самый мягкий вариант git restore. Основной инструмент против «Claude полез не туда», особенно когда полезное перемешано с лишним по нескольким файлам.

git restore src/main/java/com/acme/commerce/order/OrderService.java
git restore src/main/java/com/acme/commerce/payments/PaymentProviderClient.java
git diff
# в diff остались только refund-related изменения

Файл уже в staging area — сначала уберите его оттуда, потом верните содержимое:

git restore --staged src/main/java/com/acme/commerce/order/OrderService.java
git restore src/main/java/com/acme/commerce/order/OrderService.java
# файл убран и из staged, и из рабочего diff

Плохой шаг стал коммитом — выходит git revert. Спокойная команда: не делает вид, что коммита не было, а честно создаёт новый, который его отменяет. Для истории, которую уже видели другие, это обычно лучший путь.

git log --oneline -3
# a1b2c3d M2: выделил RefundService
# 7f8e9d0 M1: добавил тесты refund-flow

git revert a1b2c3d
# создан новый commit, отменяющий изменения a1b2c3d

git reset сильнее и опаснее. Он не отменяет коммит, а передвигает ветку назад — будто шагов не было. Удобно, если коммит локальный и его никто не видел. На общей истории — плохая идея. Правило простое: сомневаетесь — revert, не reset.

git reset --hard HEAD~1
# последний локальный commit убран из ветки
# все несохранённые изменения тоже потеряны

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

git switch -c refund-refactor-retry main
# новая чистая ветка от main для повторной аккуратной попытки

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

5. Маленькие коммиты делают rollback дешёвым

Проблемы с откатом начинаются раньше ошибки — в момент плохой дисциплины коммитов. Один гигантский коммит «refactor refund flow + cleanup + fix tests + small improvements» откатить частично почти нельзя: либо отменяете всё, либо режете коммит вручную.

Дешевле жить с маленькими milestone-коммитами. Сделали характеризационные тесты — зафиксировали. Выделили RefundService без изменения публичного контракта — зафиксировали. Вынесли порог одобрения refund в конфиг — зафиксировали. Тогда любой rollback локальный, а не катастрофический.

git log --oneline -3
# f34a1d2 M3: вынес refund-threshold в application.yml
# c91be10 M2: выделил RefundService
# 6aa43ff M1: добавил характеризационные тесты refund-flow

Допустим, плохим оказался шаг M3. Вы не трогаете полезные M1 и M2 — откатываете только последний шаг и продолжаете с понятной базы. Не теряете целый день из-за одной неудачной идеи.

Поэтому milestone и commit идут рядом. Milestone без коммита — только намерение. Коммит без milestone — абстрактная запись в истории. Вместе они дают дешёвый rollback: отменить один понятный шаг, не трогая остальное.

6. Сценарий Commerce OS: спасаем refund-flow

Соберём всё в одну сцену. В Commerce OS задача: вынести decision logic из RefundController в RefundService, не меняя публичный API и не трогая интеграцию с платёжным провайдером. Scope узкий, тесты на refund-flow добавлены на прошлом milestone. Вы дали Claude понятный шаг — а он включил режим «инициативный стажёр».

После правок изменены не только RefundController и новый RefundService, но и OrderService с PaymentProviderClient. В PaymentProviderClient вырос таймаут, в OrderService — перестановки методов и лишнее форматирование. Тот момент, когда нельзя писать «исправь всё». Сначала смотрите на Git.

git status
git restore src/main/java/com/acme/commerce/order/OrderService.java
git restore src/main/java/com/acme/commerce/payments/PaymentProviderClient.java
./gradlew test --tests "*Refund*"   # BUILD SUCCESSFUL
git add src/main/java/com/acme/commerce/refund/RefundController.java \
        src/main/java/com/acme/commerce/refund/RefundService.java \
        src/test/java/com/acme/commerce/refund/RefundControllerTest.java
git commit -m "Выделил RefundService без изменения публичного контракта"

Смысл не в командах, а в порядке. Сначала убираете лишнее. Потом запускаете проверку по refund-направлению. И только когда остаётся чистый diff внутри согласованного scope — делаете commit.

Вариант посложнее: плохой коммит уже сохранён локально. Не отправлен никому, живёт только в вашей ветке — допустим аккуратный reset. Попал в общую историю — берите revert. История будет длиннее, но это честная длина: через два дня вы или коллега поймёте, что был неудачный шаг, его отдельно отменили, работа пошла дальше.

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

7. Подключаем Claude заново после отката

После rollback главное — не вернуться в ту же ловушку. Откатили лишнее, а потом снова пишете «ну теперь доделай всё нормально» — история повторится. Claude должен получить не упрёк, а чистое узкое состояние проекта и точную следующую задачу.

Обопритесь на task spec и текущий diff. Скажите буквально: вот ветка, вот какие файлы остались чисто, вот чего делать нельзя, вот обязательная команда проверки. Здесь полезна новая чёткая формулировка, а не «ну ты же помнишь, что было полчаса назад».

Сейчас в ветке refund-refactor оставлены только изменения по refund-flow.
Не трогай OrderService и PaymentProviderClient.
Сделай только один шаг: перенеси decision logic в RefundService,
сохрани публичный API RefundController и не меняй конфигурацию таймаутов.
После изменений покажи список файлов и команду проверки.

Это работает лучше эмоционального «переделай». Вы задаёте Claude не настроение, а границы. А Git к этому моменту убрал хаос и вернул проект в состояние, где следующая итерация снова дешёвая и понятная.

Главный сдвиг урока: Git-first recovery — это не недоверие к Claude и не «делать всё вручную». Это привычка сначала работать с фактами: смотреть на diff, выбирать минимальный rollback, держать историю ветки понятной. В таком режиме Claude перестаёт быть источником паники и становится тем, кем должен быть в здоровом workflow: сильным помощником, за которым всё равно нужно читать.

1
Задача
Claude code, 6 уровень, 2 лекция
Недоступна
Сначала diff, потом restore
Сначала diff, потом restore
1
Задача
Claude code, 6 уровень, 2 лекция
Недоступна
Revert плохого commit без переписывания истории
Revert плохого commit без переписывания истории
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ