Amend и интерактивный rebase
git commit --amend переписывает последний коммит (новый SHA); git rebase -i HEAD~N открывает todo-список для reword, edit, squash, fixup, drop и переупорядочивания коммитов. Оба переписывают историю — золотое правило: не переписывай коммиты, которые другие уже подтянули.
Твоя ветка работает, но история стыдная: «wip», «fix typo», «actually fix it», «fix the fix». Ни один ревьюер не хочет это читать, а сообщения коммитов не рассказывают никакой истории. Git позволяет переформировать неряшливую ветку в чистую, читаемую последовательность, прежде чем её кто-то увидит, — починив последний коммит через --amend или переписав целую серию коммитов интерактивным rebase. Эта сила приходит с одним железным правилом о том, чью историю тебе позволено переписывать.
К концу этого урока ты сможешь амендить последний коммит, запускать интерактивный rebase, чтобы переименовывать и сквошить коммиты, и сформулировать золотое правило, которое не даёт переписыванию истории разрушить твою команду.
После этого урока ты сможешь чинить последний коммит через git commit --amend, использовать git rebase -i HEAD~N с pick, reword, edit, squash, fixup и drop, чтобы прибрать ветку, безопасно прерывать rebase и применять золотое правило rebase.
git commit --amend заменяет последний коммит — он не правит его на месте. Используй его, чтобы починить сообщение самого свежего коммита или вложить забытое изменение:
git commit --amend -m "Add rate limiter with backoff" # починить сообщение
# или: сначала застейджить забытый файл, затем заамендить, чтобы включить его
git add src/limiter.js
git commit --amend --no-edit # сохранить старое сообщениеЧто важно, amend создаёт совершенно новый коммит с новым SHA; исходный отбрасывается (он задерживается лишь в reflog). Вот почему amend считается переписыванием истории: коммит, который может быть у других, — больше не тот коммит, что у тебя теперь.
git rebase -i HEAD~N открывает todo-список для последних N коммитов. Интерактивный rebase — это рабочая лошадка для переформирования нескольких коммитов разом:
git rebase -i HEAD~4 # править последние 4 коммитаGit открывает редактор, перечисляя эти коммиты от старых к новым, каждый с префиксом-действием pick. Ты меняешь слово-действие на каждой строке (и можешь переупорядочить строки), сохраняешь, и git переигрывает коммиты согласно твоим инструкциям. Ничто не окончательно, пока ты не сохранишь и не закроешь, — и даже тогда git rebase --abort отменяет всю операцию.
Действия todo — это небольшой, выучиваемый словарь. Те, что ты будешь использовать постоянно:
pick <sha> оставить коммит как есть
reword <sha> сохранить изменения, отредактировать сообщение коммита
edit <sha> остановиться здесь, чтобы изменить содержимое коммита
squash <sha> слить в предыдущий коммит, ОБЪЕДИНЯЯ оба сообщения
fixup <sha> слить в предыдущий коммит, ОТБРАСЫВАЯ это сообщение
drop <sha> полностью удалить коммитПереупорядочивание неявно: подвинь строку вверх или вниз — и коммит сдвинется в истории. squash и fixup — это как четыре коммита «fix typo» схлопываются в один чистый коммит; fixup — тихая версия, выбрасывающая мусорные сообщения.
▸Почему это работает
Зачем вообще чистить историю, если код идентичен? Потому что история — это документация для следующего инженера (часто тебя самого). Ревьюер, читающий десять коммитов «wip», не может сказать, что делает каждое изменение; ревьюер, читающий три коммита — «Add parser», «Add parser tests», «Handle empty input», — может проверить каждый по отдельности и довериться git bisect и git blame позже. Чистая история — это не тщеславие; она делает кодовую базу отлаживаемой через месяцы.
Золотое правило rebase: никогда не переписывай коммиты, которые другие уже подтянули. Amend и интерактивный rebase оба заменяют коммиты новыми SHA. Если эти старые коммиты были только локальными, вреда нет. Но если ты запушил, а коллега подтянул, переписывание заставит твою и его ветку разойтись — тот же ущерб общей истории, что ты видел с reset. Так что:
Переписывай свободно: локальные коммиты или фичевую ветку, которую никто не подтягивал
НИКОГДА не переписывай: коммиты, уже запушенные на общую ветку, на которой строят другиеЕсли rebase пошёл наперекосяк до того, как ты закончил, отступи чисто:
git rebase --abort # вернуть ветку ровно в состояние до rebaseПрибрать ветку из четырёх коммитов перед открытием PR.
В твоей ветке: Add parser, wip, fix typo, tests. Тебе нужны два опрятных коммита: парсер и его тесты.
git rebase -i HEAD~4Открывается редактор:
pick a1 Add parser
pick b2 wip
pick c3 fix typo
pick d4 testsТы переписываешь это, чтобы вложить неряшливую середину в парсер, а тесты оставить отдельно:
pick a1 Add parser
fixup b2 wip
fixup c3 fix typo
reword d4 testsСохрани. Git вкладывает b2 и c3 в a1 (отбрасывая их сообщения), затем останавливается на d4, чтобы дать тебе переименовать его в Add parser tests. Результат — два чистых коммита с новыми SHA. Поскольку эта ветка никогда не пушилась, переписывание ничего не ломает. Если бы ты уже запушил и коллега подтянул, ты бы её не трогал — или очень обдуманно скоординировал бы форс-пуш.
▸Частая ошибка
Частая катастрофа: ребейз общей ветки, а затем git push --force, который перезаписывает remote и осиротляет совпадающие коммиты у всех остальных. Если тебе действительно нужно форс-пушнуть переписанную личную ветку, предпочитай git push --force-with-lease: он откажется перезаписывать, если remote сдвинулся с твоего последнего fetch, ловя случай, когда коллега запушил тем временем. У обычного --force такой проверки безопасности нет.
Ты использовал `git commit --amend`, чтобы починить сообщение последнего коммита. Что на самом деле произошло с этим коммитом?
git commit --amend заменяет самый свежий коммит — починяя его сообщение или вкладывая забытые изменения — порождая новый SHA. git rebase -i HEAD~N открывает todo-список, где pick, reword, edit, squash, fixup, drop и переупорядочивание позволяют переформировать несколько коммитов в чистую, пригодную для ревью историю; git rebase --abort отступает безопасно. Обе операции переписывают историю, так что соблюдай золотое правило rebase: никогда не переписывай коммиты, которые другие уже подтянули — и предпочитай --force-with-lease вместо --force, если приходится пушить переписанную личную ветку. Дальше — заглавная тема этого юнита: изменение автора и дат коммита, где те же механики переписывания применяются к самой идентичности.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.