Список команд Git выучить нельзя — их сотни, и половина в работе не встречается. Зато можно понять устройство, после которого команды перестают быть заклинаниями: у Git есть три места, где лежит твой код, и почти каждая команда просто переносит изменения из одного в другое.
Команды перемещают изменения между четырьмя местами
Первый репозиторий: четыре команды
git init -b main
Initialized empty Git repository in /home/anya/project/.git/
Появилась папка .git — в ней и живут индекс с историей. Пока в ней пусто, и git status это подтверждает:
git status
On branch main
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)
Untracked значит «Git видит файл, но не следит за ним». Чтобы начал следить — git add; а чтобы перестал показывать вовсе — .gitignore:
git add main.py
git status
On branch main
No commits yet
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: main.py
Файл переехал в индекс — это и есть «Changes to be committed». Никуда, кроме индекса, он пока не попал: истории всё ещё нет.
git commit -m "первый коммит"
[main (root-commit) 2a57751] первый коммит
1 file changed, 1 insertion(+)
create mode 100644 main.py
2a57751 — короткий идентификатор коммита, по нему на него потом ссылаются. После коммита рабочий каталог, индекс и история совпадают, и git status становится скучным:
On branch main
nothing to commit, working tree clean
Доказательство, что мест действительно три
Пока всё совпадает, поверить в три дерева трудно. Разведём их: изменим файл, добавим в индекс, а потом изменим ещё раз — не добавляя.
echo "print('пока')" >> main.py
git add main.py
echo "print('ещё')" >> main.py
git status -s
MM main.py
?? scratch.log
Две буквы M подряд — это не опечатка. Левая означает «изменён относительно последнего коммита и добавлен в индекс», правая — «изменён относительно индекса и не добавлен». Один и тот же файл одновременно в двух состояниях. Полный status показывает его дважды:
Changes to be committed:
modified: main.py
Changes not staged for commit:
modified: main.py
Untracked files:
scratch.log
Две команды diff сравнивают разные пары
diff смотрят в разные щели между тремя версиями. Поэтому «я же видел изменения в git diff, а закоммитилось другое» — не мистика, а забытый git add.Проверяется двумя командами, которые показывают разные изменения одного файла:
git diff
diff --git a/main.py b/main.py
index fdcddf7..f3c6fb6 100644
--- a/main.py
+++ b/main.py
@@ -1,2 +1,3 @@
print('привет')
print('пока')
+print('ещё')
git diff --staged
diff --git a/main.py b/main.py
index 06e4cf4..fdcddf7 100644
--- a/main.py
+++ b/main.py
@@ -1 +1,2 @@
print('привет')
+print('пока')
Без аргументов diff сравнивает рабочий каталог с индексом, с флагом --staged — индекс с последним коммитом. Отсюда правило: перед коммитом смотри git diff --staged, потому что именно это и будет записано.
Команды, которые нужны каждый день
| Команда | Что делает | Откуда куда |
|---|---|---|
git init | заводит репозиторий в текущей папке | создаёт .git |
git clone <адрес> | скачивает чужой репозиторий с историей | удалённый → диск |
git status | показывает, что где лежит | ничего не меняет |
git add <файл> | помечает изменения для следующего коммита | рабочий каталог → индекс |
git commit -m "…" | записывает содержимое индекса в историю | индекс → репозиторий |
git diff | изменения, которые не попадут в коммит | рабочий каталог ↔ индекс |
git diff --staged | изменения, которые попадут | индекс ↔ последний коммит |
git log | история коммитов | читает репозиторий |
git push | отправляет свои коммиты | репозиторий → удалённый |
git pull | забирает чужие и вливает к себе | удалённый → репозиторий |
git switch <ветка> | переключает ветку | меняет рабочий каталог |
git restore <файл> | откатывает файл к состоянию индекса | индекс → рабочий каталог |
История
git log --oneline
67b7a6c третья строка
42217e9 добавил прощание
2a57751 первый коммит
Без --oneline каждый коммит занимает шесть строк с автором и датой — на длинной истории это неудобно. Формат настраивается:
git log --oneline --format='%h %ad %s' --date=short
67b7a6c 2026-08-31 третья строка
42217e9 2026-08-31 добавил прощание
2a57751 2026-08-31 первый коммит
Ветки
Ветка в Git — это не копия проекта, а движущийся указатель на коммит. Поэтому создание ветки мгновенно независимо от размера репозитория:
git switch -c feature/greeting
Switched to a new branch 'feature/greeting'
git branch
* feature/greeting
main
Звёздочка отмечает текущую. Вернуться — git switch main. Старый способ, git checkout, делает и это, и ещё десяток разных вещей, из-за чего его и разделили на switch (ветки) и restore (файлы). В новых проектах пиши switch.
Чем ветка является на самом деле, чем -d отличается от -D и почему origin/main — не ветка, разобрано отдельно в статье про ветки.
Удалённый репозиторий
git remote add origin https://github.com/anya/project.git
git push -u origin main
To https://github.com/anya/project.git
* [new branch] main -> main
branch 'main' set up to track 'origin/main'.
origin — просто имя по умолчанию для «того самого» удалённого репозитория, не ключевое слово. Флаг -u связывает локальную ветку с удалённой один раз, после чего хватает голого git push.
Обратная команда:
git pull
Already up to date.
git pull сначала выполняет git fetch, затем интегрирует полученную историю. Режим зависит от флагов и настроек: слияние, rebase или только fast-forward. Конфликты возникают при интеграции, а не при скачивании; команда отмены зависит от того, идёт merge или rebase.
Что путают чаще всего
- add — не «сохранить»Он лишь помечает, что войдёт в следующий коммит. Изменив файл после
add, ты закоммитишь старую версию — ровно то, что показано выше двумя буквамиMM. - commit — не «отправить»Коммит остаётся на твоей машине. Пока не сделан
push, ни коллеги, ни GitHub о нём не знают, и переустановка системы унесёт его с собой. - pull — это fetch плюс интеграцияЕсли не хочется сразу сливать, делай
git fetchи смотри, что прилетело. Особенно полезно, когда в ветке есть незакоммиченные правки. - «Ветка» — это указатель, а не папкаПереключение ветки меняет содержимое рабочего каталога на месте. Никаких
project-v2рядом заводить не нужно, в этом и смысл. - Незакоммиченное не защищено ничемИстория хранит только коммиты. Правки в рабочем каталоге не переживут неудачную команду вроде
git reset --hard— коммить часто, это дёшево.
Частые вопросы
Чем git add отличается от git commit
add переносит изменения из рабочего каталога в индекс, commit записывает содержимое индекса в историю. Разделение нужно, чтобы собрать коммит из части правок: поправил три файла, а закоммитил один — остальные останутся ждать.
Зачем вообще нужен индекс
Чтобы коммит был осмысленной единицей, а не слепком папки на случайный момент. Индекс позволяет разложить работу по коммитам задним числом и посмотреть git diff --staged перед записью.
Как отменить git add
git restore --staged <файл> — уберёт из индекса, сам файл не тронет. Эту подсказку Git печатает прямо в выводе git status, её достаточно прочитать.
Что такое origin и main
origin — обычное имя удалённого репозитория после клонирования. Имя начальной ветки задаётся через git init -b или настройку init.defaultBranch; в примере выше мы явно выбрали main. Без настройки Git 2.55 использует master. Оба — просто имена, их можно менять.
Почему git checkout заменили на switch и restore
checkout перегружен: он переключал ветки, восстанавливал файлы и создавал ветки одной командой, и опечатка в аргументе приводила к неожиданному результату. В Git 2.23 работу разделили: switch — про ветки, restore — про файлы.
Где потренироваться
Git осваивается только руками: заведи пустую папку (mkdir и cd, если терминал пока непривычен), сделай в ней git init и повтори последовательность из этой статьи, подглядывая в git status после каждого шага. Проверить себя на вопросах, которые задают на собеседовании, можно в тесте по Git.