Ситуация знакома каждому. Только что нажал Enter на git commit, и через полсекунды глаз цепляется за опечатку в сообщении. Или ты закоммитил, а потом сообразил, что забыл добавить один файл - и теперь стоишь перед выбором: то ли лепить второй коммит вроде "забыл файл", то ли что-то делать с уже готовым. Или ещё классика: настроил Git с чужим email на новой машине, накоммитил, а в логе теперь висит john.doe@example.com.
Во всех этих случаях речь идёт об одном - о последнем коммите. Его ещё никто не видел (или почти никто), и хочется не плодить мусор, а аккуратно его переписать. Ровно для этого есть git commit amend. Это самый частый и самый недопонятый инструмент переписывания истории. Недопонятый - потому что почти все думают, что amend "редактирует" коммит. А он не редактирует. И вот это различие - между "поправить" и "заменить" - то, ради чего стоит разобраться по-настоящему. Если ты усвоишь, что тут происходит на уровне объектной модели, ты перестанешь бояться и amend, и его старшего брата - интерактивного rebase.

Что на самом деле происходит: новый коммит вместо старого
Главный факт урока, запомни его намертво: amend не меняет существующий коммит. Он создаёт новый коммит и переставляет на него ветку. Старый коммит никуда не редактируется - он просто становится недостижимым (на него больше не указывает ни ветка, ни тег).
Почему так? Потому что коммит в Git неизменяем по своей природе. Его идентификатор - это хеш (SHA-1, а для новых репозиториев с Git 2.51 уже SHA-256) от его содержимого: дерева, родителя, автора, коммиттера, сообщения. Поменяй хоть один байт сообщения - и хеш станет другим. А другой хеш - это по определению другой объект. Поэтому "изменить последний коммит git" физически невозможно: можно только сделать новый объект коммита и сказать ветке указывать на него.
Покажу на пальцах. Был коммит C с хешем abc123. Ты делаешь amend - Git берёт его дерево (или новое, если ты что-то застейджил), его родителя, и пишет новый объект C2 с хешем def456. Ветка main, которая раньше показывала на abc123, теперь показывает на def456. А abc123 продолжает физически лежать в .git/objects - просто на него никто не ссылается.
Код: Выделить всё
до amend: после amend:
(C) abc123 (C) abc123 <- висит в reflog, недостижим
^ ничего на него не указывает
|
[main][HEAD] (C2) def456 <- новый коммит
^
|
[main][HEAD]
Практика: три типовые задачи amend
Задача 1. Исправить сообщение коммита. Если правишь только текст и ничего не стейджил:
Код: Выделить всё
$ git commit --amend
Код: Выделить всё
$ git commit --amend -m "fix: корректная валидация телефона в форме регистрации"
Код: Выделить всё
$ git log --oneline -1
3f9a1c2 (HEAD -> main) fix: корректная валидация телефона в форме регистрации
Код: Выделить всё
$ git reflog -2
3f9a1c2 HEAD@{0}: commit (amend): fix: корректная валидация телефона в форме регистрации
7b2e004 HEAD@{1}: commit: fix: валидция телефна
Задача 2. Добавить забытый файл. Классика. Закоммитил, увидел, что не хватает файла. Стейджишь его и амендишь:
Код: Выделить всё
$ git add src/phone.js
$ git commit --amend --no-edit
Тонкость: amend подхватывает только то, что в индексе (staging area). Если ты поправил файл, но не сделал git add, изменение в коммит не попадёт. Проверяй git status перед amend.
Задача 3. Переписать автора последнего коммита. Тот самый случай с чужим email. Сначала чини глобальный конфиг, чтобы грабли не повторились, потом амендишь:
Код: Выделить всё
$ git config user.email "ivan@cyberlake.ru"
$ git commit --amend --reset-author --no-edit
Код: Выделить всё
$ git commit --amend --author="Ivan Petrov <ivan@cyberlake.ru>" --no-edit
Код: Выделить всё
$ git log -1 --format="%an <%ae> | committer: %cn <%ce>"
Ivan Petrov <ivan@cyberlake.ru> | committer: Ivan Petrov <ivan@cyberlake.ru>
В коммите два набора людей и времени: автор (кто написал изменение) и коммиттер (кто положил его в репозиторий). При обычном коммите они совпадают. При amend дата автора по умолчанию сохраняется от старого коммита, а дата коммиттера ставится текущая - то есть %ad (author date) останется прежней, а %cd (committer date) обновится. Если тебе нужна свежая дата автора:
Код: Выделить всё
$ git commit --amend --date=now --no-edit
$ git commit --amend --date="2026-06-10 14:00:00" --no-edit
Отдельно про --allow-empty. Обычно Git не даёт сделать коммит без изменений. Но amend сам по себе уже работает с непустым коммитом, так что --allow-empty тут всплывает в другом сценарии: когда ты хочешь заменить уже существующий пустой коммит (например, маркер-заглушку) или когда amend в результате правок дал бы пустое дерево. На практике с amend ты его встретишь редко, чаще он нужен обычному git commit, но знать про связку полезно.
Почему НЕЛЬЗЯ amend'ить опубликованный коммит
А вот это - правило, нарушение которого ломает работу команде. Раз amend создаёт новый коммит и переставляет ветку, то у тебя локально один коммит, а у того, кто уже спулил твою ветку - старый. Истории разошлись.
Пока коммит не запушен - амендь сколько угодно, это твоя личная песочница. Как только коммит опубликован (запушен в общую ветку, на него кто-то мог опереться) - amend превращается в боль. Обычный git push после amend отлетит:
Код: Выделить всё
$ git push
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to 'origin'
hint: Updates were rejected because the tip of your current branch is behind
Код: Выделить всё
$ git push --force-with-lease
И главное организационное правило: переписывать опубликованную историю можно только на ветках, которые ты единолично контролируешь (своя feature-ветка перед ревью). На общих main/develop - никогда. А если ты заамендил коммит с утёкшим секретом (токен, пароль) и он уже был на сервере - помни: force-push убирает секрет из верхушки, но он остаётся в reflog сервера и в клонах. Секрет надо ротировать (отзывать и менять), а не надеяться, что переписывание истории его спрятало.
amend - это частный случай rebase -i
Теперь свяжем точки. amend правит только последний коммит. А что если опечатка в предпоследнем? Или нужно добавить файл в коммит трёхдневной давности? amend бессилен - он работает только с верхушкой.
Тут выходит интерактивный rebase. По сути git rebase -i - это amend для любого коммита в цепочке. Запускаешь:
Код: Выделить всё
$ git rebase -i HEAD~3
Код: Выделить всё
pick 9a1b2c3 add login form
edit 7d4e5f6 add phone field <- остановимся тут
pick 2c3d4e5 add tests
Кстати, в Git 2.54 (апрель 2026) появилась экспериментальная команда git history с операциями reword и split - это попытка дать более простой фасад для частых правок истории, чем rebase -i. Но под капотом идея та же: коммит неизменяем, любая "правка" - это пересоздание.
Грабли
- amend взял не те изменения. Помни: он коммитит индекс. Незастейдженные правки в коммит не войдут. Всегда смотри git status и git diff --staged перед amend.
- Заамендил вместо нового коммита и потерял прошлую версию. Не потерял - она в reflog (см. ниже). Паника тут лишняя.
- Force-push голым --force на общей ветке - затёр чужие коммиты. Только --force-with-lease, и только на своих ветках.
- Думаешь, что --amend поменял дату автора. Нет, по умолчанию author date сохраняется от старого коммита. Нужна свежая - явный --date=now.
- Заамендил коммит, который уже был на сервере, чтобы убрать секрет, и расслабился. Секрет в истории сервера/клонов остался. Ротируй секрет.
- Поменял user.email, заамендил, а автор прежний. Без --reset-author amend автора не трогает.
Допустим, ты заамендил, а через минуту понял, что прошлая версия коммита была правильной, и теперь она "пропала". Не пропала. Reflog - это журнал, куда Git пишет каждое движение HEAD и веток. Недостижимый старый коммит там есть.
Код: Выделить всё
$ git reflog
3f9a1c2 HEAD@{0}: commit (amend): новое сообщение
7b2e004 HEAD@{1}: commit: исходная версия, которую я хочу вернуть
a10ff45 HEAD@{2}: checkout: moving from develop to main
Код: Выделить всё
$ git reset --hard 7b2e004
HEAD is now at 7b2e004 исходная версия, которую я хочу вернуть
Мини-лаба: повтори руками
- Создай песочницу: git init amend-lab, зайди внутрь, git config user.email "test@local".
- Сделай файл a.txt, git add a.txt, git commit -m "первый коммт" (специально с опечаткой).
- Запиши хеш: git log --oneline -1. Это твой коммит до amend.
- Исправь сообщение: git commit --amend -m "первый коммит". Снова git log --oneline -1 - хеш изменился. Это новый коммит.
- Создай b.txt, git add b.txt, git commit --amend --no-edit. Проверь git show --stat HEAD - в коммите теперь оба файла, а сообщение прежнее.
- Поменяй автора: git commit --amend --author="QA Bot <qa@local>" --no-edit, проверь git log -1 --format="%an %ae".
- Открой git reflog - найди в нём все промежуточные версии с пометкой (amend).
- Откати на самую первую через git reset --hard на её хеш из reflog. Убедись, что b.txt снова нет (git status, ls). Поздравляю, ты прошёл цикл правки и восстановления.
- amend "редактирует" последний коммит или создаёт новый? Что происходит со старым и где он остаётся доступен?
- Ты заамендил уже запушенный в общую ветку коммит. Почему обычный git push отлетит и какой командой пушить правильно, чтобы не затереть чужое?
- Чем отличается --reset-author от обычного --amend при смене user.email? Сохранится ли дата автора без --date?
- Как связаны git commit --amend и git rebase -i edit? Что общего в их механике?
git amend - не редактор коммита, а его пересоздатель: новый объект, ветка переставлена, старый ушёл в reflog. Этим он лечит ровно последний коммит - опечатку в сообщении, забытый файл, чужого автора. На своих неопубликованных ветках амендь свободно. Опубликованное - только --force-with-lease и только осознанно, а утёкшие секреты ротируй. Не дотягиваешься до последнего коммита - бери rebase -i, это тот же amend для любого места истории. И помни про reflog: пока он жив, ни один amend не фатален.