В этой статье рассмотрены некоторые общепринятые правила управления фиксациями (фиксация == git commit).
Сначала небольшая памятка по командам, связанным с фиксациями:
Команда
Функция
git add .
Добавление в индекс всех файлов - как измененных, так и новых.
git commit
Команда commit без опций откроет редактор по умолчанию (часто это vi или vim, иногда nano) для ввода сообщения фиксации.
git commit -a
Совмещенная команда, объединяет в себе git add и git commit, так называемый "ленивый" commit. Однако тут есть важный нюанс: в commit будут добавлены только измененные (проиндексированные) файлов, т. е. те файлы, которые ранее были добавлены в индекс. Таким образом, новые файлы не будут добавлены в commmit.
git commit --amend
Исправление последней фиксации, это аналог команды git reset --soft HEAD~1 (см. [2]) плюс новый git commit. Позволит изменить последнюю фиксацию - добавить новый файл, применить новые изменения, изменить сообщение фиксации. Полезно в случае, когда вы не хотите создавать лишнюю фиксацию, учитывающую новые изменения.
git show
Покажет, что вошло в последнюю фиксацию.
git commit -m "сообщение"
В команде фиксации сразу указывается сообщение.
git commit -am "сообщение"
Объединение функций опций -a и -m. Проиндексированные ранее файлы сразу будут добавлены в фиксацию, и сразу будет создана фиксация с указанным сообщением.
git show commit_id
Покажет, что вошло в указанную фиксацию. Здесь commit_id это идентификатор, или хеш фиксации. Хеши фиксаций показывает команда git log и git log --oneline.
git log
Покажет полный список фиксаций, начиная с самой свежей фиксации.
git log --oneline
Покажет более компактный (однострочный) список фиксаций. Хеши фиксаций будут сокращенные - из 7 шестнадцатеричных символов. Многострочные сообщения фиксации будут объединены в одну строку. Если сообщение фиксации было разделено на заголовок (тему) и тело фиксации, то в одной строке после 7 символов хеша будет выведен только заголовок (см. далее "Оформление commit-а с заголовком").
git reset
Отмена всех изменений в индексе, или очистка индекса (аналог команды git reset --mixed, или git reset --mixed HEAD, см. [2]). Т. е. те изменения в файлах, которые были только что добавлены в индекс командой git add, будут убраны из индекса. Как это выглядит: до команды git add команда git status покажет изменения красным шрифтом. После команды git add команда git status покажет изменения зеленым шрифтом. Если теперь выполнить git reset, то команда git status снова покажет изменения красным шрифтом, как будто команды git add не было.
git reset имя_файла
Отмена изменений в индексе только для указанного файла.
git reset HEAD~1
Отменяет (удаляет) последний коммит, но оставляет все изменения из него в вашей рабочей директории и в области подготовленных, или проиндексированных файлов (индекс, staging area). Это так называемый "мягкий" сброс (soft reset), который используется по умолчанию, если не указан другой режим.
git reset --hard
Эта команда сбрасывает проект на текущий коммит (то есть HEAD), но при этом очищает индекс и рабочую директорию. Это так называемый "жесткий" сброс проекта (безвозвратные изменения, откат на последнюю фиксацию). Что делает: не перемещает указатель ветки назад. Её основная задача — стереть все ваши локальные изменения, которые ещё не были закоммичены (включая те, что были в staging area, и те, что были просто в файлах). После выполнения команды рабочая директория станет точно такой же, как в последнем коммите. Созданные или измененные файлы, которые не были проиндексированы, останутся нетронутыми. Когда использовать: когда вы хотите полностью отказаться от всех текущих правок и вернуться к состоянию последнего коммита. Например, если вы запутались в изменениях и хотите начать заново с чистого листа.
git reset --hard HEAD~1
Эта команда сбрасывает на один коммит назад (на родительский коммит), и также очищает индекс и рабочую директорию. Что делает: удаляет последний коммит из истории вашей ветки (перемещает указатель HEAD на HEAD~1). После этого она приводит вашу рабочую директорию и индекс в точное соответствие с этим предыдущим коммитом. Все изменения, которые были в удалённом коммите, а также любые незакоммиченые правки, будут потеряны. Когда использовать: вы хотите полностью отменить последний коммит и все связанные с ним изменения, как будто его никогда не было. Это радикальный способ «откатить» последнюю работу.
Примечания:
1. Важный момент: поскольку --hard перезаписывает файлы, вы можете потерять свою работу. Если вы случайно выполнили эту команду, но коммит существовал, его ещё можно попытаться восстановить с помощью git reflog (журнала ссылок), но незакоммиченые изменения восстановить практически невозможно. Никогда не используйте --hard для коммитов, которые уже были отправлены (push) в удалённый репозиторий, так как это перепишет общую историю и создаст проблемы для других. 2. git reset HEAD~1 на самом деле не удаляет навсегда последний коммит. Он просто не будет отображаться командой git log. Т. е. если мы знаем его идентификатор commit_id (хеш), мы можем его вернуть обратно командой git reset commit_id. В качестве commit_id можно также указать ORIG_HEAD (см. врезку далее).
HEAD~1 — это синтаксис, который указывает Git на коммит, являющийся родителем текущего коммита (то есть на один коммит назад в истории фиксаций).
Разбор по частям:
HEAD — это специальный указатель (ссылка) на текущий коммит, на котором вы сейчас находитесь (вершина текущей ветки). ~ — оператор «предок по первой родительской линии». 1 — на сколько шагов назад нужно отступить.
Итого: HEAD~1 = «текущий коммит минус один шаг назад по истории» = родитель текущего коммита.
Как это можно представить наглядно? Представьте историю коммитов:
A ← B ← C ← D ↑ HEAD
- HEAD — это коммит D. - HEAD~1 — это коммит C (родитель D). - HEAD~2 — это коммит B. - HEAD~3 — это коммит A.
[Важные нюансы]
1. Только по первой линии родителей. Если коммит является результатом слияния (merge) и имеет двух родителей, `~` всегда идёт по первому родителю. Для обращения ко второму родителю используется `^2` (например, `HEAD^2`).
2. Синоним `HEAD^`. Запись `HEAD^` эквивалентна `HEAD~1` (один шаг назад), `HEAD^^` эквивалентна `HEAD~2` (два шага назад). Но `HEAD~2` и `HEAD^2` — это разные вещи: `~2` — два шага по первой линии, `^2` — второй родитель текущего коммита.
3. Работает не только с HEAD. Вместо `HEAD` можно указать любую ссылку: `main~1`, `feature~2`, хеш коммита и т.д.
Именно поэтому:
- `git reset HEAD~1` — сбрасывает ветку на родителя текущего коммита, то есть «отменяет последний коммит». - `git reset --hard HEAD~1` — делает то же самое, но ещё и удаляет все изменения из рабочей директории и индекса, приводя их в соответствие с коммитом-родителем.
`ORIG_HEAD` — это специальная ссылка (псевдо-ссылка) в Git, которая автоматически создаётся некоторыми командами перед выполнением «опасных» операций. Она служит «закладкой» или «точкой восстановления», запоминая, где находился указатель `HEAD` до того, как команда начала менять историю.
[Назначение и принцип работы ORIG_HEAD]
Основная идея: если после выполнения команды что-то пошло не так (например, слияние оказалось неудачным, или вы поняли, что перебазирование испортило историю), вы можете легко вернуться к исходному состоянию, используя `ORIG_HEAD`.
Согласно документации Git, `ORIG_HEAD` создаётся командами, которые «резко» перемещают `HEAD` (такими как `git am`, `git merge`, `git rebase`, `git reset`), чтобы записать позицию `HEAD` до начала операции.
Важное предостережение: ненадёжность при `rebase`. Хотя `ORIG_HEAD` кажется надёжным инструментом, у него есть важная особенность, о которой стоит знать. В официальной документации к `git rebase` прямо указано, что `ORIG_HEAD` не гарантированно указывает на исходную вершину ветки после завершения перебазирования, если во время этого процесса вы использовали другие команды, которые также перезаписывают `ORIG_HEAD` (например, `git reset`).
Это может сбивать с толку: вы начали rebase, прервались на разрешение конфликта, сделали `git reset`, а затем завершили rebase. В итоге `ORIG_HEAD` будет указывать на состояние после вашего `reset`, а не на то, что было до начала всего rebase.
[Более надёжная альтернатива: reflog]
Из-за этой особенности современный Git рекомендует использовать для восстановления не `ORIG_HEAD`, а журнал ссылок (reflog). Запись вида `branchname@{1}` (или `HEAD@{1}`) позволяет обратиться к предыдущему состоянию ветки и является более надёжным и предсказуемым способом отмены действий.
Таким образом, если вы выполнили `git reset --hard HEAD~1`, отменив последний коммит, и пожалели об этом, вы можете восстановить его с помощью `git reset --hard ORIG_HEAD`. Команда `reset` как раз входит в список тех, что создают `ORIG_HEAD` перед изменением `HEAD`. Это позволяет использовать `ORIG_HEAD` как быстрый способ «вернуть всё как было» после сброса.
Часто редактором по умолчанию для ввода сообщений commit является текстовый редактор vi. Его можно заменить на более дружелюбный редактор. Для настройки редактора, который Git будет использовать для сообщений коммитов, нужно изменить параметр `core.editor`. Это можно сделать глобально (для всех репозиториев) или локально (для текущего).
[Основная команда настройки]
Используйте `git config` с флагом `--global` (или `--local`), чтобы указать команду запуска вашего редактора.
Важное замечание: для графических редакторов (VS Code, Sublime Text и т.п.) часто нужно добавлять флаг ожидания (например, `--wait` или `-w`). Без него Git может продолжить работу, не дожидаясь, пока вы закроете файл, что приведёт к пустому коммиту.
Примеры для популярных редакторов:
Редактор
Команда настройки
VS Code
git config --global core.editor "code --wait"
Sublime Text
git config --global core.editor "subl --wait"
Vim
git config --global core.editor "vim"
Nano
git config --global core.editor "nano"
Notepad (Windows)
git config --global core.editor "notepad"
[Проверка и использование]
1. Проверить текущую настройку:
git config --global core.editor
Если команда ничего не возвращает, редактор не настроен.
2. Использование: gосле настройки при выполнении `git commit` (без флага `-m`) Git автоматически откроет выбранный вами редактор для ввода сообщения.
3. Редактирование последнего коммита: чтобы изменить сообщение уже сделанного коммита, используйте `git commit --amend`. Откроется тот же редактор с текстом последнего сообщения.
[Специализированные инструменты]
Существуют также утилиты, созданные специально для удобного редактирования сообщений коммитов (например, с поддержкой Conventional Commits). Примерами могут служить `committed` (TUI-редактор) или `jot` (минималистичный встроенный редактор). Их можно установить и указать в качестве `core.editor` аналогичным образом.
[Оформление commit-а с заголовком]
Если коммит многострочный, и после первой строки есть пустая строка, то первая строка считается заголовком, а остальные строки это так называемое тело коммита. Пример такого оформления сообщения коммита:
Добавлено несколько полезных фич.
Описание фичи1 Описание фичи2 Описание фичи3
Общепринятая практика - в заголовке указывать краткое описание коммита (это тема коммита), а в теле коммита давать подробные пояснения. Тогда команда git log --oneline покажет только заголовки (первые строки), что удобно для быстрого обзора фиксаций, а команда git log выведет заголовки вместе с телом фиксации, выдавая полную информацию.
[Правила оформления сообщений commit-ов]
1. Каждый коммит должен создавать рабочую версию репозитория.
2. Если сообщение многострочное, то оно должно быть разделено на заголовок (тему) фиксации и тело фиксации. Между темой и телом сообщения фиксации должна быть пустая строка.
3. Желательно, чтобы заголовок (тема) сообщения была ограничена 50 символами. Некоторые команды разработчиков договариваются между собой, что тема сообщения пишется в верхнем регистре (большими буквами). Также заголовок не должен заканчиваться точкой или другими знаками препинания.
4. Тело сообщения принято ограничивать 80 символами.
5. Тело сообщения должно объяснять, что было сделано, а не почему были сделаны эти изменения.
6. Тема сообщения должна отвечать на вопрос: что будет, если применить этот коммит?