1. Шаг 1. Уводим работу в ветку
Лёха ушёл, Олег у себя. Сейф собран — Тёма проживает его сам, начисто, на той правке reward-flow, которую хотел внести с утра. Первым делом — увести работу в сторону от main. Тёма поворачивается к Сане, но на секунду тормозит: как там ветку-то назвать правильно?
— Сань, я хочу отдельную ветку под правку. Назвать как — есть разница?
— Разницы для git нет, — отвечает Саня ровно. — Но по-хорошему имя должно говорить, что за работа. fix-reward понятнее, чем test123 — через неделю сам вспомнишь. Завести?
— Заводи, fix-reward. И работай только в ней, main не трогай.
● git checkout -b fix-reward
Switched to a new branch 'fix-reward'
— Готово, я в fix-reward, — докладывает Саня. — main не трогаю.
Тёма проверяет: точно, он в ветке, main стоит нетронутый. Между рабочим порталом и экспериментом теперь стена. И он ловит себя на том, что впервые пускает Саню в код спокойно.
— Ну давай, — говорит он почти весело. — Ломай, пробуй. Накосячишь — откачусь, делов-то.
2. Шаг 2. Правка и дифф с лишним
По старой привычке Тёма кидает задачу коротко, не заморачиваясь:
— Сань, там с наградой что-то не то капает. Разберись с начислением, ну и вообще глянь, чтоб красиво было.
— Уточню, — тут же откликается Саня. — «Начисление» — это reward-flow. А «чтоб красиво было» — размыто. Под это я могу зацепить и соседние зоны. Сузить область или оставить как есть?
Тёма отмахивается — привычно, как отмахивался месяц назад — git-то уже есть:
— Да делай, чё ты. По ходу разберёшься.
Ровно то, от чего его весь вчерашний день отучал Лёха. Саня не возражает и начинает работать. Чинит начисление. А под «чтоб красиво» — лезет причёсывать соседнюю зону, лидерборд, которую трогать никто не просил.
Но Тёма не спешит принимать все правки. Он делает то, ради чего с утра и собирал весь сейф, — прежде чем принять, открывает просмотр изменений:
/diff
И сверяет глазами: всё ли тут из задачи?
reward-flow.js
+ const reward = base * multiplier; // это по делу — начисление
leaderboard.js
+ sortByScore(players); // ← а это откуда? Я про лидерборд не просил
Начисление — по делу. Но снизу, в leaderboard.js, — чужая строка. Тёма хмурится секунду — и сам вспоминает Санин вопрос.
— Саня ж предупреждал, — доходит до него вслух. — «Красиво» — это и был лидерборд. Он спросил, я отмахнулся.
Не Саня напортачил. Саня как раз честно уточнил. Напортачила размытая постановка — ровно та, про которую Лёха месяц долбил. Заход кривой, этот заход не берём. Хорошо, что глянул /diff до принятия, а не влил вслепую.
3. Шаг 3–4. Сброс к чистой точке и фиксация коммитом
Тёма не лезет чистить руками по строчке. Раз заход кривой — сбрасывает ветку к последней зафиксированной точке, к той чистой, что он поставил с утра. Просит словами:
Сбрось ветку к последней зафиксированной точке, начнём заново.
И тут Саня, не дожидаясь отмашки, влезает сам:
— Стоп, для протокола. Ты просишь git reset --hard — он сносит незафиксированное без спроса, вернуть будет нельзя. Всё, что я наменял и не закоммитил, исчезнет. Тебе же это и надо?
— Именно это и надо, — кивает Тёма. — Сноси.
— Принято. Ещё нюанс: если я по ходу создал совсем новый файл, которого git ещё не видел, — сброс его не тронет, останется лежать. Не пугайся, потом уберём.
● git reset --hard HEAD
HEAD is now at a1b2c3d рабочий портал до правок
Терминал написал ровно то, чего он ждал: ветка стоит на точке «рабочий портал до правок». А значит, на диске сейчас должно лежать чистое состояние — и правильное начисление, и лишний хвост в лидерборде стёрты одной операцией, файлы вернулись как были до Сани. Должно. Проверить бы.
И тут, за секунду до того как глянуть на результат, его царапает. Та ночь. Он ведь уже бил по кнопке отмены — уверенный, что спасёт, — а она не дотянулась. Агент уже отработал сброс, а Тёма медлит открывать портал, и в животе короткий холодок: вдруг и эта штука так же. Вдруг гляну сейчас — а там по-прежнему битьё.
Глянул.
Чисто. Ни единого следа. Соседняя зона, которую Саня зацепил, — на месте, целая. Весь мусор, всё, что он наворотил, — как корова языком. Не «почти убрал», не «вроде вычистил». Будто Саня сюда вообще не заходил.
— Опа, — вслух говорит Тёма пустой комнате. И ещё раз, тише: — Опа.
Одна команда. Секунда. Вот ровно этого ему той ночью и не хватило. Тогда он часами выковыривал необратимое руками — а отматывать было некуда, ни одной точки позади. А сейчас точка есть, и откат к ней — не подвиг, не археология, не «ну вроде помню, как было». Спокойно ткнул — и вернулся. Взрослая версия той самой кнопки, по которой он бил той ночью впустую. Только эта дотянулась.
Теперь чистовой заход. И в этот раз Тёма формулирует иначе — узко, точно, как научен:
— Сань, только reward-flow, только начисление. Лидерборд и соседние зоны не трогай вообще.
— Вот это другой разговор, — отзывается Саня. — Границы понятны, гадать не буду.
Проходит как надо. Тёма снова смотрит /diff — только reward-flow.js, ничего лишнего. Принимает. И чтобы закрепить хорошее состояние, просит зафиксировать точку:
Зафиксируй правку: начисление в reward-flow.
● git commit -m "fix: начисление в reward-flow"
[fix-reward e4f5g6h] fix: начисление в reward-flow
Теперь на диске, в истории проекта, лежит новая честная точка — та, к которой всегда можно вернуться, хоть после закрытой сессии, хоть через неделю. main всё это время оставался чистым: вся ошибка прожила и умерла внутри ветки, не долетев до рабочего портала ни на секунду.
4. Контур обратимости и проверка
Тёма записывает в EVIDENCE_LOG, что вышло за день. Лёха, всё так же вполголоса, заглядывает через плечо.
— Смотри-ка, — отмечает он. — Раньше ты сюда что писал? Только победы: вот это заработало. А сегодня у тебя тут весь провал разложен по полочкам.
— Ну да, — Тёма проглядывает свою запись. — Вот ветка fix-reward и чистая точка до правок. Вот кривой заход, где Саня зацепил лишнее, и /diff, который это поймал. Вот сброс — кривое стёрто. Вот коммит удачного захода. И main, который весь день не дрогнул.
— Вот именно. — Лёха чуть оживляется. — Впервые записан не только успех, но и безопасный провал: как заход сорвался и как проект это пережил без единой царапины. Та обратимость, которую я утром заставлял тебя доказывать ещё на бумаге, — вот она, доказана делом. Давай теперь сверимся по-быстрому, всё ли собрал. Отвечай.
Он проговаривает по пунктам, как контрольный список:
— Работа шла в ветке fix-reward, не прямо на main?
— Да.
— Перед принятием смотрел /diff, что агент наменял?
— Смотрел, на том и поймал лишнее.
— Кривой заход откатил сбросом ветки, а не выколупывал руками?
— Сбросом. Одной операцией.
— Чистовой зафиксировал коммитом, точка стоит? main весь цикл нетронут? И git гонял сам агент, ты команды руками не печатал?
— Стоит. Нетронут. Ни одной команды не набрал руками, — Тёма загибает пальцы. — Всё сошлось.
— Ну вот, — кивает Лёха. — Собрал как надо. Правка принята через ветку и откат, кривое исчезло без хирургии, main чист.
Тёма откидывается, выдыхает. Ни фейерверка в груди, ни азарта, как от новой игрушки, — что-то ровное, тяжёлое, будто впервые встал на твёрдое.
— Сань, — говорит он в чат. — Ветку под следующую задачу сразу заводить?
— Заводить, — отвечает Саня. — Я готов пробовать.
— Ну и пробуй. — Тёма усмехается, глядя на чистый портал. — Теперь-то можно. Не потому что ты поумнел — ты прежний. А потому что я - поумнел и подо мной пол появился. Упаду — поднимусь.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ