Перейти к содержанию

Осмысленный коммит

В первом уроке мы записали исходники учебного каталога. Теперь важно научиться сохранять историю так, чтобы она объясняла развитие проекта. Коммит с сообщением «разное» может содержать рабочий код, но через месяц трудно понять, зачем изменились сразу статья, меню и оформление. Размер коммита определяется связностью задачи, а не количеством файлов.

Рассмотрим небольшую редакционную правку: заменим обещание о частоте новых уроков описанием устройства каталога. Заодно увидим, почему открытый в редакторе файл и подготовленная версия могут отличаться. Оглавление курса и архив дают исходное состояние lesson-02/start. Все показанные результаты пока ожидаемые: учебные команды ещё не выполнялись.

Одна задача в истории

Начните с результата прошлого урока: ветка main, один коммит с тремя исходниками, чистый статус. Для независимого чтения перенесите файлы из lesson-02/start в отдельную новую папку, инициализируйте её и создайте начальный коммит по сценарию архива. Никогда не смешивайте подготовку лаборатории с рабочим репозиторием сайта.

В content/courses.md есть строка:

Новые уроки появляются каждую неделю.

Она создаёт конкретное обещание, которого редакция может не придерживаться. Заменим её точным описанием навигации:

Уроки сгруппированы по темам.

Это завершённая задача: посетитель получает другую информацию о каталоге. URL двух курсов и заголовок пока остаются прежними. Если одновременно появился замысел поменять цвет ссылок, он может быть отдельным изменением с собственным объяснением. Разделение упрощает чтение и позволяет позже вернуть только одну правку, сохранив другую.

Одно связное изменение не обязательно помещается в одном файле. Если вы переименовали переменную, обновление её использования в нескольких исходниках должно попадать вместе с определением. Иначе промежуточный коммит может описывать несогласованное состояние. В нашем примере достаточно одного файла, но правило относится к законченному результату.

Отличие на диске и подготовленный снимок

Сначала посмотрим правку, которая пока существует только в рабочем дереве:

git status --short
git diff -- content/courses.md
git add content/courses.md
git diff --cached -- content/courses.md

До add ожидается буква M во второй колонке статуса возле пути. После add она находится в первой колонке, поскольку изменённый текст уже в индексе. В diff одна удалённая строка обозначается минусом, новая — плюсом. Эти знаки являются оформлением сравнения, в сам Markdown их переносить не нужно.

-Новые уроки появляются каждую неделю.
+Уроки сгруппированы по темам.

Обычный git diff сравнивает рабочее дерево с индексом. Вариант --cached сравнивает индекс с последним коммитом. Когда вы только что добавили правку и больше ничего не меняли, обычный diff пуст, а подготовленный diff содержит новую строку. Это ожидаемое состояние: изменение не исчезло, оно перешло в подготовленную часть.

Теперь проведём небольшой мысленный опыт с двумя изменениями. Допустим, после добавления courses.md вы дописали в README.md заметку «Добавить поиск позднее». В кратком статусе будут два пути, но индекс содержит только правку страницы. Команда git commit без специальных параметров запишет подготовленное содержимое, а заметка README останется на диске.

Для основного сценария такую пробную заметку следует удалить вручную до продолжения. Мы хотим закончить урок чистым деревом и одним новым коммитом. Пример с заметкой объясняет границу индекса; он не добавляет ещё один обязательный исходник. Если вы действительно хотите сохранить заметку, сначала решите, входит ли она в тот же результат.

Проверка состава и сообщение

Перед записью полезно прочитать сводку и полный подготовленный diff:

git diff --cached --stat
git diff --cached
git commit -m "Описать группировку уроков в каталоге"
git show --stat --oneline HEAD

Сводка отвечает на вопрос, какие файлы затронуты. Полный diff показывает, что именно будет записано. Только сводки недостаточно: одна строка может содержать как безобидный текст, так и случайно добавленный пароль. В нашей лаборатории секретов нет, но привычка читать содержимое нужна до появления сложного проекта.

Сообщение называет намерение: описать группировку. Формулировка «изменить файл courses.md» сообщает лишь механический факт, который и так виден в diff. Хорошее короткое сообщение помогает отыскать нужный шаг в истории. Если причина сложнее, её можно пояснить в отдельном абзаце сообщения, открыв редактор обычной командой git commit без -m.

Не нужно перечислять каждую строку в заголовке. Описание должно быть полезным человеку, который ещё не читал вашу задачу. Для будущего исправления ссылок подошло бы сообщение «Вернуть постоянный адрес курса JavaScript»: оно объясняет ожидаемое поведение. Для изменения каталога сейчас важна группировка, поэтому именно её и называем.

Документация git-commit описывает сохранение подготовленного содержимого. В этом курсе используем обычный commit без -a: автоматическое добавление всех изменений отслеживаемых файлов затруднило бы наблюдение за индексом. Кроме того, -a само по себе не добавляет новые неотслеживаемые файлы.

Ошибочно подготовленный файл

Представим, что вы всё же добавили README вместе со страницей, а заметка должна остаться черновиком. Пока коммит не создан, можно убрать README из индекса, оставив его текст на диске:

git restore --staged README.md
git status --short
git diff --cached

Этот вариант restore действует на индекс. У команды есть и режим изменения рабочего файла, поэтому параметр --staged здесь существенен. Не сокращайте команду при повторении опыта: мы хотим изменить состав будущего коммита, сохранив черновую заметку. После операции снова прочитайте подготовленный diff и убедитесь, что он соответствует задаче.

Когда коммит уже существует, похожая ошибка решается другим способом: добавлением исправления, переносом локальной истории или обратным коммитом. Эти операции имеют разные последствия для остальных разработчиков. Пока достаточно понимать, что подготовка и сохранение — два разных этапа, и до сохранения состав проще уточнить.

В основном сценарии после удаления временных заметок ожидаются два коммита: первый снимок A и редакционная правка B. Ветка main указывает на B, а его родитель — A. Показанные буквы остаются условными обозначениями; реальные идентификаторы читайте из собственного лога.

Подготовленный diff полезно читать как самостоятельный текст: узнает ли другой разработчик, что поменялось, не открывая ваш редактор? Если новая формулировка ссылается на ещё не созданный раздел, задача может быть незаконченной даже при безупречном составе индекса. Git хранит выбранное состояние, но смысловую согласованность статьи определяет автор. В нашем примере названия и ссылки уже существуют, поэтому изменение пояснения не требует дополнительных файлов.

На следующем шаге добавим правила исключения файлов. Они помогут не спутать исходники с результатами сборки и локальными настройками, сохранив понятный состав истории.