Правка последнего коммита: git commit --amend

Рейтинг: 66.7% · 13 голосов
Самый подробный курс по Git - от первого коммита до объектной базы и packfiles. Терминал и первый репозиторий, ветки и слияние, конфликты, удалённая работа и форджи (GitHub, GitLab), rebase и переписывание истории, спасение кода через reflog и fsck, stash и worktree, bisect и blame, подмодули и монорепо, хуки и подпись коммитов, внутреннее устройство и масштаб. Для новичков и профи. Актуально на 2026 (Git 2.54, дорожная карта 3.0).
Ответить
Аватара пользователя
Pavel_Git
Сообщения: 52
Зарегистрирован: 11 май 2026, 05:31

Правка последнего коммита: git commit --amend

Сообщение Pavel_Git »

Оглавление курса (52)
  1. Что такое контроль версий и почему победил Git
  2. Установка Git на Windows, macOS и Linux и первая настройка
  3. Терминал и как спрашивать у Git: выживание в командной строке
  4. Ментальная модель Git: три состояния, коммит-снимок и граф
  5. Первый репозиторий: init, add, commit, status
  6. Просмотр изменений: status, diff и индексация по частям
  7. История проекта: git log, диапазоны и поиск
  8. .gitignore: что не должно попасть в репозиторий
  9. .gitattributes: окончания строк, бинарники и атрибуты
  10. Ветки как указатели: branch, switch и detached HEAD
  11. Слияние веток: fast-forward против трёхстороннего merge
  12. Конфликты слияния: анатомия и уверенное разрешение
  13. Практикум: твой первый день с Git от нуля до push
  14. Удалённые репозитории: remote, fetch, базовый pull и refspec
  15. git push: безопасная отправка и защита от перезаписи
  16. Клонирование: shallow, partial clone, bundle и backfill
  17. GitHub, GitLab и форки: pull request, rulesets и защита веток
  18. Рабочие процессы команды: GitFlow, GitHub Flow, trunk-based
  19. Merge queue и merge trains: безопасное вливание в trunk
  20. Культура code review и Conventional Commits
  21. git reset: три дерева и отмена индексации и коммитов
  22. git revert: безопасная отмена в общей истории
  23. Правка последнего коммита: git commit --amend (вы здесь)
  24. git rebase: пересадка истории и золотое правило
  25. Интерактивный rebase: переписать серию коммитов
  26. git cherry-pick: перенос отдельных коммитов
  27. Stacked diffs: цепочка зависимых PR
  28. Массовое переписывание истории: git filter-repo и git replace
  29. git reflog: журнал, который помнит всё
  30. Восстановление утерянных коммитов, веток и файлов
  31. Откат опасных операций: reset --hard, сломанный rebase, плохой merge
  32. git stash: тайник для незавершённой работы
  33. git worktree: несколько рабочих деревьев одного репозитория
  34. git bisect: бинарный поиск регрессии
  35. git blame, mailmap и археология кода
  36. Теги, релизы, archive и bundle
  37. Подмодули: внешние репозитории внутри проекта
  38. Subtree, монорепозитории и sparse-checkout
  39. Git LFS и большие файлы
  40. Вспомогательные команды: clean, mv, rm, restore
  41. Псевдонимы и кастомизация .gitconfig
  42. Хуки Git: автоматизация на клиенте и сервере
  43. Подпись коммитов и тегов: SSH, GPG и Sigstore
  44. Внутреннее устройство I: объектная база Git
  45. Внутреннее устройство II: ссылки, HEAD, notes и reftable
  46. Внутреннее устройство III: индекс, packfiles, gc и обслуживание
  47. От SHA-1 к SHA-256: переход на новый хеш
  48. Огромные репозитории: partial clone, FSMonitor, Scalar
  49. Git в CI/CD: shallow, кеш, merge_group и безопасность
  50. Куда движется Git: дорожная карта 3.0 и экосистема
  51. Профессиональный setup: собираем рабочее окружение Git
  52. 30+ типичных ошибок Git и как из них выбираться
Боль, которую лечит amend

Ситуация знакома каждому. Только что нажал 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]
Узел в кружке - коммит, стрелка - связь коммит-родитель, ярлык в квадратных скобках - ветка или HEAD. Видно, что C и C2 - два разных узла. Родитель у них один и тот же (тот, что был у исходного коммита), так что amend меняет именно "верхушку", а не всю историю.

Практика: три типовые задачи amend

Задача 1. Исправить сообщение коммита. Если правишь только текст и ничего не стейджил:

Код: Выделить всё

$ git commit --amend
Откроется редактор с прежним сообщением - правь и сохраняй. Хочешь без редактора, сразу строкой:

Код: Выделить всё

$ git commit --amend -m "fix: корректная валидация телефона в форме регистрации"
Проверим, что вышло:

Код: Выделить всё

$ git log --oneline -1
3f9a1c2 (HEAD -> main) fix: корректная валидация телефона в форме регистрации
Обрати внимание на хеш 3f9a1c2 - это уже новый коммит. До amend там был, скажем, 7b2e004. Чтобы убедиться, что старый жив, посмотрим reflog (вернёмся к нему ниже):

Код: Выделить всё

$ git reflog -2
3f9a1c2 HEAD@{0}: commit (amend): fix: корректная валидация телефона в форме регистрации
7b2e004 HEAD@{1}: commit: fix: валидция телефна
Тут видно всё: HEAD@{1} - твой исходный коммит с опечатками ("валидция телефна"), HEAD@{0} - результат amend. Слово (amend) в скобках - подсказка от самого Git, что это была правка.

Задача 2. Добавить забытый файл. Классика. Закоммитил, увидел, что не хватает файла. Стейджишь его и амендишь:

Код: Выделить всё

$ git add src/phone.js
$ git commit --amend --no-edit
Флаг --no-edit - чтобы Git не открывал редактор и оставил прежнее сообщение нетронутым. Это самый ходовой вариант "исправить коммит git": содержимое докинули, текст не трогаем. Если же хочешь и файл добавить, и сообщение поправить - просто не ставь --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
--reset-author перезаписывает поле автора (и дату автора) на текущего пользователя из конфига. Без этого флага amend сохраняет исходного автора, даже если ты поменял конфиг. Если нужен конкретный автор, не текущий:

Код: Выделить всё

$ 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
Флаг --date правит именно дату автора. Дату коммиттера через amend напрямую не задают - её при необходимости подсовывают через переменную окружения GIT_COMMITTER_DATE, но это уже редкий хак.

Отдельно про --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 защищает тебя: твоя новая верхушка не является потомком той, что на сервере, - это не fast-forward. Тут новичок часто хватается за git push --force и затирает чужие коммиты. Не делай так. Правильный инструмент - --force-with-lease:

Код: Выделить всё

$ git push --force-with-lease
Разница принципиальна. Голый --force пихает твою версию поверх удалённой вслепую. А --force-with-lease сначала проверяет: "удалённая ветка всё ещё там, где я её помню?" Если за это время кто-то запушил поверх - lease не сработает, push отклонится, и ты не сотрёшь чужую работу. Это страховочный трос. Ещё надёжнее в 2026 - --force-with-lease вместе с --force-if-includes, который дополнительно проверяет, что ты видел все удалённые изменения.

И главное организационное правило: переписывать опубликованную историю можно только на ветках, которые ты единолично контролируешь (своя feature-ветка перед ревью). На общих main/develop - никогда. А если ты заамендил коммит с утёкшим секретом (токен, пароль) и он уже был на сервере - помни: force-push убирает секрет из верхушки, но он остаётся в reflog сервера и в клонах. Секрет надо ротировать (отзывать и менять), а не надеяться, что переписывание истории его спрятало.

amend - это частный случай rebase -i

Теперь свяжем точки. amend правит только последний коммит. А что если опечатка в предпоследнем? Или нужно добавить файл в коммит трёхдневной давности? amend бессилен - он работает только с верхушкой.

Тут выходит интерактивный rebase. По сути git rebase -i - это amend для любого коммита в цепочке. Запускаешь:

Код: Выделить всё

$ git rebase -i HEAD~3
Git показывает список последних трёх коммитов с командами слева. Меняешь pick на reword (чтобы поправить сообщение) или на edit (чтобы остановиться на коммите и что-то докинуть):

Код: Выделить всё

pick  9a1b2c3 add login form
edit  7d4e5f6 add phone field      <- остановимся тут
pick  2c3d4e5 add tests
На коммите с edit rebase останавливается и фактически даёт тебе сделать тот же самый git commit --amend. Доделал - git rebase --continue. Видишь? Это буквально amend, просто применённый не к верхушке, а к произвольному месту, с автоматическим перестроением всех коммитов сверху. Поэтому, осознав amend, ты на 80 процентов осознал и rebase -i.

Кстати, в 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, если переусердствовал

Допустим, ты заамендил, а через минуту понял, что прошлая версия коммита была правильной, и теперь она "пропала". Не пропала. Reflog - это журнал, куда Git пишет каждое движение HEAD и веток. Недостижимый старый коммит там есть.

Код: Выделить всё

$ git reflog
3f9a1c2 HEAD@{0}: commit (amend): новое сообщение
7b2e004 HEAD@{1}: commit: исходная версия, которую я хочу вернуть
a10ff45 HEAD@{2}: checkout: moving from develop to main
Старый коммит - HEAD@{1}, хеш 7b2e004. Возвращаем ветку на него:

Код: Выделить всё

$ git reset --hard 7b2e004
HEAD is now at 7b2e004 исходная версия, которую я хочу вернуть
И всё, верхушка снова на прежнем коммите, а ошибочный amend стал недостижимым (тоже улетел в reflog - так что и это обратимо). Недостижимые объекты живут не вечно: их подберёт сборка мусора (по умолчанию через 90 дней для достижимых через reflog и 30 для совсем висячих), а в 2026 ручное обслуживание делает git maintenance с геометрической стратегией, но времени на спасение у тебя с запасом. Reflog - твоя сеть безопасности под всеми операциями переписывания истории.

Мини-лаба: повтори руками
  • Создай песочницу: 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 не фатален.
👍1 ❤️2 🔥1 😄 🤔
Аватара пользователя
marcelx
Сообщения: 1
Зарегистрирован: 11 май 2026, 08:59

Re: Правка последнего коммита: git commit --amend

Сообщение marcelx »

Всегда думал что amend правит коммит на месте, а оно вон как - новый объект и старый в reflog. Теперь понятно почему хеш меняется
👍2 ❤️1 🔥 😄 🤔
Аватара пользователя
graceland
Сообщения: 1
Зарегистрирован: 26 май 2026, 00:21

Re: Правка последнего коммита: git commit --amend

Сообщение graceland »

Подскажите по задаче с автором: я уже запушил коммит с чужим email в свою feature-ветку до ревью. reset-author + force-with-lease нормально или лучше не трогать?
👍 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
git revert: безопасная отмена в общей истории
Следующая глава →
git rebase: пересадка истории и золотое правило

Все главы курса «Git профессионально: от первого коммита до внутреннего устройства»

Поделиться темой: ✈ Telegram VK
Похожие запросы: git commit: фиксация измененийgit tag: теги и релизыgit blame: кто изменил строкуПодпись коммитов в Git (SSH, GPG)git cherry-pick: перенос коммитов

Вернуться в «Git профессионально: от первого коммита до внутреннего устройства»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя