Git: первый коммит
Зачем это нужно
Представь: ты три часа доводил проект до рабочего состояния, потом «немного улучшил» — и всё сломалось. Как вернуть версию, которая работала? Копии project_final_2_НОВЫЙ в соседней папке — путь к хаосу: рано или поздно забудешь, какая из пяти копий рабочая. Для этого придумали систему контроля версий (version control system) — программу, которая запоминает состояния проекта в виде цепочки снимков, а не просто хранит файлы как есть. Самая распространённая такая система — Git. Каждый снимок называется коммитом (commit): что изменилось, когда и зачем. Можно листать историю, сравнивать версии и откатываться к любой точке — как чекпоинты в игре, только сохраняешь их сам и когда захочешь.
На чемпионате «Профессионалы» без этого никуда: работу сдают через git-репозиторий, и эксперты видят историю коммитов — по ней понятно, как ты шёл к результату, а не только что получилось в конце. Один гигантский коммит «всё сразу» в конце дня выглядит подозрительно: непонятно, что ты вообще делал первые три часа. А ровная цепочка маленьких осмысленных шагов — как рабочий журнал профессионала. Поэтому привычку коммитить понемногу и часто ставим с первого дня, и в этой главе разберём весь путь от пустой папки до первой такой цепочки буквально по молекулам: что именно происходит на диске после каждой команды и что означает каждое слово в ответе терминала.
Как это было
Сквозной пример: репозиторий шаг за шагом
Дальше — один сквозной проект my-app, который мы проведём через весь цикл: от пустой папки до нескольких коммитов в истории. Все команды и вывод терминала ниже — не выдумка и не сокращение: это дословный вывод настоящего Git (версия 2.43, та, что ставится по умолчанию из apt на свежей Ubuntu 24.04 — ровно такая машина ждёт тебя на чемпионате), полученный в чистом окружении именно для этой главы. Открой свой терминал и повторяй команды по ходу чтения — тренажёр в браузере команды Git не выполняет, поэтому весь текст ниже спроектирован так, чтобы можно было разобрать вывод не запуская его: с интерактивными разборами, самопроверками и печатью вручную.
git init: из папки в репозиторий
Создадим папку проекта и превратим её в репозиторий:
mkdir my-app
cd my-app
git init
Вот дословный ответ терминала:
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint: git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint: git branch -m <name>
Initialized empty Git repository in /home/student/my-app/.git/
Это не ошибка — все строки hint: (подсказка) Git печатает при первом init на свежей машине, и дальше в этой сессии их уже не будет. Смысл подсказки: у первой ветки (про ветки — подробно в следующей главе) нужно какое-то имя, версии Git до недавнего времени называли её master, сейчас многие сервисы (например GitHub) по умолчанию заводят main — и Git честно предупреждает, что имя зависит от настройки init.defaultBranch, а не зашито намертво. На чемпионатской машине или на своём компьютере название может оказаться любым из этих — дальше в главе используется master, ровно то имя, которое дал этот git init без дополнительных настроек.
Разберём финальную строку — ту, что реально сообщает результат:
Внешне в проекте как будто ничего не изменилось — но появилась скрытая папка .git (имя, начинающееся с точки, ls без флагов не показывает). Проверим:
ls -la
total 12
drwxr-xr-x 3 student student 4096 Aug 31 19:22 .
drwxr-xr-x 3 student student 4096 Aug 31 19:22 ..
drwxr-xr-x 7 student student 4096 Aug 31 19:22 .git
Заглянем внутрь — это и есть весь механизм Git, который отныне будет следить за проектом:
ls .git
HEAD
branches
config
description
hooks
info
objects
refs
Пока не нужно запоминать назначение каждого файла — со временем это станет привычным, а пока держи короткую расшифровку главного:
HEAD— указатель на текущую ветку: файлcat .git/HEADсодержит одну строкуref: refs/heads/master— «сейчас мы смотрим на ветку master».objects— сюда Git реально складывает содержимое коммитов, файлов и снимков в сжатом виде; это и есть история.refs— папка с указателями на ветки и метки: куда именно вobjectsсмотрит имяmaster.config— настройки конкретно этого репозитория (не путать с глобальными настройками пользователя, о них — в разделе про коммит).hooks,info,description,branches— служебные вещи для продвинутых сценариев, в этой главе не понадобятся.
Трогать .git руками не нужно никогда — вся работа с ним идёт только через команды git .... Если эту папку удалить, репозиторий исчезнет насовсем, а сами файлы проекта (main.py и другие) останутся на месте, просто без истории.
git status: читаем построчно
git status — самая частая команда цикла: она отвечает на вопрос «что вообще сейчас происходит с проектом». Спросим у пустого репозитория:
git status
On branch master
No commits yet
nothing to commit (create/copy files and use "git add" to track)
Три смысловых блока: на какой ветке мы находимся, сколько коммитов уже есть (пока ни одного), и что делать дальше. Теперь создадим файл и спросим снова:
printf 'print("Привет, чемпионат!")\n' > main.py
git status
On branch master
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
main.py
nothing added to commit but untracked files present (use "git add" to track)
Разберём блок про сам файл — почему он выглядит именно так:
И последняя строка блока — короткое резюме всего вывода:
git add: коробка для посылки
Git не коммитит всё подряд автоматически — и это осознанное решение, а не недоработка. Представь, что отправляешь посылку: на столе перед тобой десять разных вещей, но в коробку идут не все сразу, а только те, что ты сам туда положил. Коробка — это индекс (index), его ещё называют staging area («зона подготовки», «корзина для коммита»): промежуточное место между рабочей папкой (working directory — где ты правишь файлы как обычно) и историей коммитов. Команда git add кладёт конкретный файл в эту коробку; ничего в историю при этом ещё не попадает — снимок делает только следующая команда, git commit.
Смысл в том, что можно поправить сразу пять файлов, а закоммитить только два связанных между собой, — остальные подождут своего, отдельного коммита. Положим main.py в коробку:
git add main.py
git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: main.py
Обрати внимание: main.py переехал из раздела Untracked files в раздел Changes to be committed — сам файл на диске никак не изменился, поменялось только то, что о нём знает Git.
Цель: скорость ≥ 100 зн/мин, точность ≥ 90%
git add main.py
git commit: что внутри коммита
Прежде чем делать первый снимок, Git попросит представиться — один раз на весь компьютер, а не на каждый коммит. Имя и почта станут подписью под твоими коммитами, и без них коммит просто не получится создать:
git config --global user.name "Ivan Petrov"
git config --global user.email "ivan@example.com"
--global значит «на этом компьютере для всех репозиториев», а не только для my-app; без флага настройка подействовала бы только на текущий проект. Теперь можно сделать сам коммит:
git commit -m "add main screen"
[master (root-commit) 5ecfe79] add main screen
1 file changed, 1 insertion(+)
create mode 100644 main.py
Коммит готов — но что внутри него на самом деле, кроме файла? Спросим Git напрямую:
git log -1
commit 5ecfe795b9c92b2285b6869710e7e54f0b7f52a8
Author: Ivan Petrov <ivan@example.com>
Date: Mon Aug 31 19:22:49 2026 +0000
add main screen
Вот и ответ на вопрос «что внутри коммита» — ровно четыре вещи, и три из них программист не пишет руками:
Так что из «hash, author, date, message» руками пишется только последнее — сообщение. Остальное Git берёт из настроек и системных часов и вычисляет сам.
git log --oneline: история одной строкой
Изменим main.py и повторим цикл — теперь уже быстрее, раз шаги знакомы:
printf 'print("Привет, чемпионат!")\nprint("Удачи!")\n' > main.py
git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: main.py
no changes added to commit (use "git add" and/or "git commit -a")
Статус файла теперь modified — «изменён»: main.py уже был закоммичен раньше, поэтому Git сравнивает текущее содержимое с тем, что лежит в последнем коммите, а не считает файл незнакомым, как в самый первый раз. Посмотреть, что именно изменилось, можно командой git diff:
git diff
diff --git a/main.py b/main.py
index af6a7c4..6eb4577 100644
--- a/main.py
+++ b/main.py
@@ -1 +1,2 @@
print("Привет, чемпионат!")
+print("Удачи!")
Строка со знаком + в начале — то, что добавилось; строка без знака — то, что осталось как было. Добавим и закоммитим:
git add main.py
git commit -m "add welcome message"
[master 58da937] add welcome message
1 file changed, 1 insertion(+)
Обрати внимание — у этого коммита в квадратных скобках уже нет пометки (root-commit): она бывает только у самого первого коммита в истории. Теперь посмотрим на всю цепочку сразу:
git log --oneline
58da937 add welcome message
5ecfe79 add main screen
Свежий коммит — всегда первая строка, самый старый — последняя. Весь Git на базовом уровне сводится к одному короткому циклу, и теперь за каждым его шагом стоит конкретный, разобранный по молекулам смысл:
поменял файлы → git status (что изменилось) → git add (что закоммитить) → git commit -m "..." (зафиксировать снимок) → снова поменял файлы.
А git log --oneline — команда, к которой возвращаются в любой момент, чтобы охватить взглядом весь пройденный путь.
Теперь в обратную сторону: перед тобой три знакомых вывода git status — по каждому определи, что именно попадёт в снимок, если набрать git commit прямо сейчас.
git commit прямо сейчас, и нажми «Проверить». Если не попадёт ничего — жми «Проверить» сразу.Хорошие сообщения коммитов
Сообщение — это письмо себе и команде: через неделю по git log нужно понять, что происходило, не открывая сами файлы. Мировая привычка — писать по-английски, по формуле глагол в начальной форме + что сделано, как будто отдаёшь команду: add main screen, fix score reset, remove unused file, update readme.
Плохие сообщения — fix, asdf, изменения 2, последняя версия точно — через неделю не скажут вообще ничего ни тебе, ни эксперту на чемпионате. Хватает трёх-шести слов, зато честных и по делу.
Проверь формулу на живых ситуациях: набирай сообщение — чек-лист правил загорается прямо по мере набора.
git commit -m ""0/50- · начинается с глагола-действия по-английски: add, fix, remove, update…
- · после глагола сказано, что именно сделано (не просто «fix» или «123»)
- · не длиннее 50 символов
- · без точки в конце
.gitignore: что в историю не берём
Не всё в папке проекта заслуживает места в истории: черновики, временные файлы, кэш вроде __pycache__ — мусор, который создаётся сам и никому не нужен в репозитории. Заведём черновик и посмотрим, как он засоряет вывод:
echo "черновик" > draft.txt
git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
draft.txt
nothing added to commit but untracked files present (use "git add" to track)
Чтобы git status не показывал такие файлы снова и снова, в корне проекта заводят файл с особым именем .gitignore и перечисляют в нём шаблоны того, что нужно игнорировать — по одному на строку:
echo "draft.txt" > .gitignore
git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
nothing added to commit but untracked files present (use "git add" to track)
draft.txt пропал из списка — Git теперь его полностью игнорирует, зато появился сам .gitignore: он-то как раз обычный файл, и его нужно закоммитить, чтобы правило подействовало не только у тебя, но и у всей команды:
git add .gitignore
git commit -m "add gitignore for drafts"
git status
[master 920228d] add gitignore for drafts
1 file changed, 1 insertion(+)
create mode 100644 .gitignore
On branch master
nothing to commit, working tree clean
nothing to commit, working tree clean — «нечего коммитить, рабочая папка чистая» — это финальный, самый спокойный ответ git status: все файлы либо закоммичены, либо явно игнорируются, ничего не потеряно и ничего не забыто. При этом draft.txt никуда не делся — он спокойно лежит рядом на диске, просто Git про него больше не напоминает.
Шаблоны в .gitignore можно расширять: звёздочка означает «любое имя», поэтому *.log закрывает сразу все файлы логов, а __pycache__/ — целую папку:
__pycache__/
*.log
draft.txt
Типичные ошибки
Четыре сообщения, с которыми рано или поздно сталкивается каждый. Тексты ниже — дословный вывод настоящего Git, проверенный вживую специально для этой главы, а не пересказ по памяти.
1. Забыл git add
Изменил файл, но не добавил его в индекс, — и пробуешь коммитить сразу:
git commit -m "add welcome message"
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: main.py
no changes added to commit (use "git add" and/or "git commit -a")
Перевод: «изменения не подготовлены к коммиту... ничего не добавлено к коммиту». Причина: git commit фиксирует только то, что уже лежит в индексе (коробке из раздела про git add); индекс пуст — коммитить нечего, и вместо создания коммита Git просто ещё раз показывает status. Как починить: git add main.py, затем повторить git commit -m "...".
2. Пустой -m
git commit -m ""
Aborting commit due to empty commit message.
Перевод: «прерываю коммит из-за пустого сообщения». Причина: Git намеренно запрещает коммит без единого слова о том, что изменилось, — такое сообщение бесполезно для истории. Как починить: повторить команду с непустым текстом после -m, например git commit -m "add welcome message".
3. not a git repository
Команда Git запущена вне репозитория — например, в /tmp, где нет ни своей папки .git, ни родительской с ней:
git status
fatal: not a git repository (or any of the parent directories): .git
Перевод: «фатальная ошибка: это не git-репозиторий (и ни одна из родительских папок тоже)». Причина: Git ищет папку .git в текущем каталоге и поднимается вверх по родительским, пока не найдёт хоть одну, — и нигде её не находит. Как починить: перейти в папку своего проекта (cd my-app) командой cd, либо, если репозитория ещё не существует вовсе, создать его через git init.
4. Author unknown (identity не настроена)
На свежей машине, где git config --global user.name/user.email ещё ни разу не выполнялись, первый же git commit останавливается:
git commit -m "add main screen"
Author identity unknown
*** Please tell me who you are.
Run
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
to set your account's default identity.
Omit --global to set the identity only in this repository.
fatal: unable to auto-detect email address (got 'root@d5508f32bf9c.(none)')
Перевод: «личность автора неизвестна... расскажи мне, кто ты... не удалось автоматически определить почтовый адрес». Часть после got — то, что Git попытался подобрать сам по имени пользователя и имени компьютера (здесь это техническое имя контейнера, где проверялась эта глава; на реальной машине там будет твой логин и hostname) — и не смог. Причина: Git подписывает каждый коммит автором, а без user.name/user.email подписывать попросту нечем. Как починить: ровно то, что уже делали в разделе про git commit, — git config --global user.name "Имя Фамилия" и git config --global user.email "почта@пример.ру", один раз на весь компьютер.
Символы и команды главы
Шпаргалка по всему, что встретилось в этой главе. Вернись сюда, если забудешь, что делает та или иная команда или флаг.
| Команда / символ | Что делает | Пример |
|---|---|---|
git init | создаёт репозиторий: пустую папку .git внутри текущего каталога | git init |
.git | скрытая служебная папка — само хранилище истории проекта | ls -la |
git status | показывает состояние файлов: untracked / modified / уже в индексе | git status |
git add <file> | кладёт файл в индекс (staging area) — готовит к коммиту | git add main.py |
git commit -m "..." | создаёт коммит — снимок из того, что лежит в индексе | git commit -m "add main screen" |
-m | флаг message: сообщение коммита прямо в команде, без редактора | git commit -m "fix bug" |
git config --global | задаёт имя и почту автора для всех репозиториев на компьютере | git config --global user.name "..." |
git log | показывает историю коммитов от нового к старому | git log -1 |
--oneline | флаг: каждый коммит — одна строка (сокращённый hash + сообщение) | git log --oneline |
| hash | уникальный SHA-1-отпечаток коммита; для чтения сокращают до 7 символов | 5ecfe79 |
(root-commit) | пометка первого коммита в истории — у него нет родителя | [master (root-commit) 5ecfe79] |
.gitignore | файл со списком шаблонов, которые Git должен игнорировать | *.log |
Слова главы
Потренируйся печатать
Команды Git ты будешь набирать десятки раз в день — пусть пальцы запомнят их раньше головы.
Цель: скорость ≥ 110 зн/мин, точность ≥ 90%
git status
Теперь связка подлиннее — ровно то, что происходит после каждого маленького изменения в файле:
Цель: скорость ≥ 130 зн/мин, точность ≥ 90%
git add main.py && git commit -m "add welcome message"
Углубиться
Три состояния файла. Git различает рабочую папку (working directory — где ты правишь файлы как обычно), индекс (staging area — что отобрано в следующий снимок) и саму историю коммитов. git add двигает изменения из первой во вторую, git commit — из второй в третью. Когда вывод git status перестанет удивлять, значит, эта тройка уложилась в голове окончательно.
Коммит — не «сохранение файла». Git хранит не отдельные файлы построчно, а снимки всего проекта целиком на момент коммита, и каждый коммит знает своего родителя — предыдущий коммит в цепочке (кроме самого первого, root-commit, у него родителя нет). Так и получается история, по которой можно ходить назад. Hash из git log — это не случайный номер, а результат хеш-функции SHA-1 от содержимого коммита: файлов, автора, даты, сообщения и hash родителя. Поэтому подделать или незаметно изменить старый коммит невозможно — изменится hash, и это будет видно.
git add . — точка означает «всё из текущей папки». Быстро, но коварно: в коммит залетает всё подряд, включая случайный мусор вроде черновиков или временных файлов. Пока учишься, добавляй файлы по именам — заодно будешь глазами видеть, что именно кладёшь в снимок.
Дальше по теме:
- git-init — официальная документация — все флаги команды из первоисточника, включая создание bare-репозиториев
- git-commit — официальная документация — полный список флагов, включая
-a,--amendи работу с шаблонами сообщений
Куда дальше: проверенные ресурсы
- Pro Git: запись изменений в репозиторий — главный учебник по Git, официально переведён на русский; эта глава разбирала ровно тот же цикл status/add/commit, здесь — то же самое другими словами и чуть шире.
- Хабр: Git для самых маленьких — от первой команды до настройки SSH — свежий разбор именно с init/add/commit, доведён дальше, до подключения к серверу.
- Хабр: гайд по Git для начинающих — команды, ветки, типичные ошибки — отдельная секция про типичные ошибки новичков, хорошо дополняет эту главу.
- Индекс Git (staging area): полное объяснение — если метафора с коробкой понятна, но хочется увидеть индекс изнутри: как устроен файл
.git/indexи что умеетgit add -p.
- git-init — man-страница команды init: все флаги и режимы из первых рук.
- git-commit — man-страница команды commit:
-m,-a,--amendи остальные флаги.
-
github/gitignore — готовые шаблоны
.gitignoreпод любой язык и инструмент: скопируй нужный вместо того, чтобы выдумывать свой с нуля. -
Видео и материалы сообщества — ниже — ролики по теме и ссылки от студентов и преподавателей смотри в самом низу страницы.
Прежде чем браться за челлендж, прогони весь цикл в симуляторе: перед тобой виртуальный репозиторий с уже лежащим в папке README.md — доведи его от git init до цепочки минимум из трёх коммитов и посмотри на графе, как растёт история.
Мой первый проект
Как это устроено под капотом
Челлендж ⭐
Продолжи проект my-app из главы серией из трёх коммитов — каждый решает одну задачу и подписан по-английски по формуле «глагол + что»:
- Допиши в
main.pyстроку, которая спрашивает имя пользователя. Закоммить. - Создай в папке ещё один черновик, например
notes.txt, и добейся, чтобыgit statusперестал его показывать — так, как делали сdraft.txtв разделе про.gitignore. То, что для этого понадобилось изменить, — закоммить. - Исправь текст приветствия в первой строке
main.py. Закоммить.
⭐ Ступень со звёздочкой. После трёх коммитов запусти git log --oneline и убедись, что видишь ровно шесть строк (три коммита из главы — add main screen, add welcome message, add gitignore for drafts — плюс три новых). Затем ответь себе вслух, не подглядывая в терминал: у какого из шести коммитов в полном git log (без --oneline) была бы пометка (root-commit) — и почему именно у него, а не у остальных пяти.
Проверка: git log --oneline показывает минимум шесть коммитов, git status отвечает nothing to commit, working tree clean — при этом notes.txt спокойно лежит в папке, просто не в истории.
Решение (сначала попробуй сам)
# 1. дописали в main.py строку input("Как тебя зовут? ")
git add main.py
git commit -m "add name question"
# 2. создали notes.txt, затем дописали его в .gitignore
git status # notes.txt виден как untracked
echo "notes.txt" >> .gitignore
git status # notes.txt исчез из списка, изменился .gitignore
git add .gitignore
git commit -m "ignore notes file"
# 3. поправили текст в print(...)
git add main.py
git commit -m "fix greeting text"
git log --oneline # шесть строк
Ответ на ⭐: пометка (root-commit) была бы у самого первого коммита add main screen — единственного во всей цепочке, у которого нет родителя, потому что это вообще первый снимок, сделанный в этом репозитории. У всех остальных пяти коммитов родитель есть — предыдущий коммит по цепочке, поэтому пометка появляется у него ровно один раз за всю историю репозитория.
Обрати внимание на второй шаг: коммитим именно .gitignore, а не сам черновик — notes.txt в историю не попадает никогда, как и draft.txt раньше.
Сводная проверка по всей главе: 8 вопросов, 5:00 на всё, без подсказок по ходу. Вопросы показываются по одному, ответ изменить нельзя. Если время выйдет — экзамен завершится с тем, что успел ответить. Пересдавать можно сколько угодно раз.
Что должен уметь
- Объяснять своими словами, что такое репозиторий (repository), коммит (commit) и зачем проекту вообще нужна история изменений.
- Прогонять полный цикл
git init→git status→git add→git commit -m "..."→git log --onelineбез подсказок, вслепую понимая, что делает каждый шаг. - Читать вывод
git statusпострочно: отличать untracked-файл от modified, понимать, что уже лежит в индексе, и что означаетnothing to commit, working tree clean. - Объяснять метафору staging area как коробки для посылки — и почему
git addне коммитит файл сразу. - Перечислить, из чего состоит коммит (hash, author, date, message) и назвать, что из этого задаёт человек, а что — сам Git.
- Писать сообщение коммита по-английски по формуле «глагол + что сделано» — коротким и честным.
- Прятать мусорные файлы от Git через
.gitignoreи объяснять, почему сам.gitignoreпри этом коммитят. - Узнавать по точному тексту четыре типичные ошибки — забытый
git add, пустой-m,not a git repository, неизвестного автора (git config) — и знать, как чинить каждую.
Комментарии
Комментарии появятся после настройки. Нужен аккаунт GitHub — вход прямо в виджете выше.