Просмотр изменений: status, diff и индексация по частям

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

Просмотр изменений: status, diff и индексация по частям

Сообщение 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 и как из них выбираться
Боль, которую мы лечим: "что я вообще наменял?"

Знакомая картина. Ты три часа правил код, бегал между файлами, что-то чинил, что-то рефакторил, заодно поправил опечатку в README и закомментировал отладочный print. Пора коммитить. И вот тут новичок делает

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

git add . && git commit -m "fixes"
- и сваливает в одну кучу пять не связанных между собой правок. История превращается в помойку, где , и идут стопкой, и через месяц никто (включая тебя) не поймёт, где именно сломалась логика.

Профессионал так не делает. Перед коммитом он точно знает, что изменилось, где это изменилось (в рабочем каталоге или уже в индексе) и кладёт в коммит ровно одну осмысленную порцию. Инструменты для этого - git status, git diff во всех его ипостасях и интерактивная индексация git add -p. Это не "посмотреть глазками", это снайперский контроль над тем, что попадёт в историю. Разберём механику до винтика.

Изображение

Три дерева: почему diff бывает разный

Чтобы понять, посмотреть изменения git правильно, надо держать в голове три области (мы их называем "три дерева"):
  • Рабочий каталог (working tree) - твои файлы на диске, как есть прямо сейчас.
  • Индекс (index, он же staging area) - снимок того, что попадёт в следующий коммит. Физически это файл

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

    .git/index
    .
  • HEAD - последний коммит текущей ветки, отправная точка.
Команда копирует состояние из рабочего каталога в индекс. Команда

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

git commit
замораживает индекс в новый коммит. И вот ключевое: git diff показывает разницу между двумя из этих трёх деревьев, а какими именно - зависит от флагов. Отсюда и путаница у новичков: "почему git diff ничего не показывает, я же менял файл?". Потому что ты уже сделал , и разница между рабочим каталогом и индексом исчезла - изменение переехало в индекс.

Запомни три рабочие формы:
  • Код: Выделить всё

    git diff
    - рабочий каталог против индекса. То, что ты ещё не заиндексировал.
  • Код: Выделить всё

    git diff --staged
    (синоним

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

    --cached
    ) - индекс против HEAD. То, что уже заиндексировано и уйдёт в коммит.
  • Код: Выделить всё

    git diff HEAD
    - рабочий каталог против HEAD. Все изменения скопом, и заиндексированные, и нет.
git diff staged - это твоя проверка перед коммитом: "что именно я сейчас зафиксирую?". Возьми в привычку вызывать

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

git diff --staged
прямо перед каждым

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

git commit
. Это ловит случайно заиндексированный мусор лучше любого ревьюера.

git status: карта местности

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

git diff
показывает содержимое изменений, а

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

git status
- список файлов и их состояние. Посмотрим живой вывод:

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

$ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   src/auth.py

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:   src/auth.py
        modified:   README.md

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        notes.txt
Разбираем по секциям. Файл

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

src/auth.py
попал в обе секции - "Changes to be committed" и "Changes not staged". Это нормально и важно: часть правок в этом файле ты заиндексировал, а потом доправил ещё - и новые правки висят в рабочем каталоге. Индекс держит снимок, а не ссылку на файл, поэтому состояния расходятся.

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

README.md
изменён, но не заиндексирован.

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

notes.txt
- untracked, Git его раньше не видел.

Совет:

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

git status -s
даёт компактный двухколоночный вид. Левый символ - состояние индекса, правый - рабочего каталога:

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

$ git status -s
MM src/auth.py
 M README.md
?? notes.txt
- модифицирован и в индексе, и в каталоге (тот самый расходящийся файл). (пробел слева) - модифицирован только в каталоге. - untracked. Этот формат быстро читается глазами, когда файлов много.

Как читать унифицированный diff построчно

Вот сердце урока. Разберём реальный вывод

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

git diff
по косточкам:

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

$ git diff README.md
diff --git a/README.md b/README.md
index 3b18e51..d4a1c5f 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,8 @@ Installation
 Clone the repo and run:
-    pip install requirements.txt
+    pip install -r requirements.txt
+    python setup.py develop

 Run tests with pytest.
Идём сверху:
  • Код: Выделить всё

    diff --git a/README.md b/README.md
    - заголовок. - "до" (индекс), - "после" (рабочий каталог). Это служебные префиксы, а не реальные папки.
  • Код: Выделить всё

    index 3b18e51..d4a1c5f 100644
    - сокращённые хеши blob-объектов "до" и "после" плюс режим файла. - обычный файл, - исполняемый. Если режим сменился, тут будет видно.
  • Код: Выделить всё

    --- a/README.md
    и

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

    +++ b/README.md
    - какой файл считается старым (минус) и новым (плюс).
  • Код: Выделить всё

    @@ -10,7 +10,8 @@
    - это hunk header, граница куска изменений. Читается так: со старой стороны начиная со строки 10 показано 7 строк, с новой стороны начиная со строки 10 показано 8 строк. Строк стало на одну больше - мы добавили строку. Текст после второго (тут

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

    Installation
    ) - это "контекстный хвост", функция или заголовок, в котором живёт правка. Git угадывает его, чтобы ты понимал, куда смотреть.
  • Строки с пробелом в начале - контекст (не менялись, даны для ориентира). - удалено. - добавлено. Замена строки в diff выглядит как удаление старой плюс добавление новой - отдельной операции "изменить строку" в построчном diff нет.
По умолчанию контекста три строки сверху и снизу. Хочешь больше -

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

git diff -U10
(десять строк), меньше - (только сами правки, без окружения). Когда правишь длинную строку и хочешь видеть изменение внутри строки, а не "вся строка удалена / вся добавлена", включай word-diff:

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

$ git diff --word-diff README.md
@@ -10,7 +10,7 @@ Installation
    pip install [-requirements.txt-]{+-r requirements.txt+}
Удалённое в , добавленное в . Для текстов, конфигов и документации это спасение: на длинной строке сразу видно, что поменялось одно слово, а не вся строка целиком. Вариант

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

--word-diff=color
красит вместо скобок, если терминал умеет в цвет.

git add -p: индексация изменений git по кускам

А вот и профи-навык, ради которого затевался урок. Индексация изменений git по частям - это

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

git add -p
(от ). Git режет твои правки на hunk-и и спрашивает по каждому: брать или нет. Так из одного намешанного файла рождаются чистые атомарные коммиты.

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

$ git add -p src/auth.py
@@ -42,6 +42,7 @@ def login(user, password):
     token = make_token(user)
+    log.debug("token issued for %s", user)
     return token
@@ -88,7 +89,7 @@ def logout(session):
-    session.clear()
+    session.invalidate()
     return True
(1/2) Stage this hunk [y,n,q,a,d,s,e,?]?
Git нашёл два независимых изменения и предлагает решить по каждому. Расшифровка клавиш:
Допустим, ты добавил отладочный лог и починил реальный баг с инвалидацией сессии. Логически это два разных коммита. Жмёшь на debug-логе, на фиксе - индексируется только баг-фикс. Коммитишь его осмысленным сообщением. Потом второй заход

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

git add -p
, и лог уходит отдельным коммитом. История чистая, каждый коммит делает одну вещь.

Про (split) есть важная грабля - о ней ниже. Про (edit): открывается hunk как патч, и тут правило простое. Хочешь не индексировать добавленную строку () - удали её строку из патча целиком. Хочешь не индексировать удаление () - замени минус на пробел (преврати в контекст). Не трогай счётчики в , Git их пересчитает сам. Ошибся - Git скажет "patch does not apply" и предложит редактировать заново или пропустить.

Старший брат -

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

git add -i
, полноценное интерактивное меню (status / update / revert / add untracked / patch / diff). На практике 95% времени хватает

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

git add -p
, а нужен редко. И учти: работает не только у .

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

git restore -p
,

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

git stash -p
,

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

git checkout -p
,

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

git reset -p
- тот же патч-режим для откатов и стэша. Освоил один раз - применяешь везде.

git restore: снять из индекса и откатить файл

Заиндексировал лишнее? git restore - современная команда (стабильна с Git 2.51, больше не экспериментальная) с чётким разделением ролей, в отличие от перегруженного

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

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

    git restore --staged auth.py
    - снять из индекса. Возвращает файл в индексе к состоянию HEAD, рабочий каталог не трогает. Твои правки на диске целы, просто разиндексированы. Это безопасная операция.
  • Код: Выделить всё

    git restore auth.py
    - откатить рабочий каталог к состоянию индекса. Внимание: правки в рабочем каталоге теряются безвозвратно, отмены через reflog тут нет. Git даже подсказывает это в

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

    git status
    : "discard changes in working directory".
  • Код: Выделить всё

    git restore --source=HEAD~2 auth.py
    - вытащить версию файла из конкретного коммита.
Именно эти подсказки печатает

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

git status
в скобках - читай их, Git буквально диктует нужную команду. Старый эквивалент -

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

git reset HEAD auth.py
(разиндексировать) и

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

git checkout -- auth.py
(откатить). Их в интернете полно, поэтому знать надо, но в новом коде пиши : намерение читается с первого взгляда.

diff между файлами, ветками и коммитами

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

git diff
умеет сравнивать не только дерево с индексом:

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

$ git diff src/auth.py              # один файл, каталог vs индекс
$ git diff main feature            # разница между двумя ветками
$ git diff main..feature           # то же самое (двухточечная форма)
$ git diff main...feature          # с момента, как ветки разошлись
$ git diff HEAD~3 HEAD             # последние три коммита целиком
$ git diff abc123 def456 -- src/   # между коммитами, только папка src
$ git diff --stat main feature     # сводка: сколько строк в каких файлах
Особое внимание на две точки против трёх.

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

main..feature
- прямая разница состояний двух веток.

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

main...feature
- разница от их общего предка (merge base) до feature, то есть "что нового именно в feature, не считая того, что параллельно наехало в main". Перед открытием pull request смотри именно

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

git diff main...feature
- увидишь ровно свой вклад, без шума чужих коммитов, что попали в main за это время.

А даёт сводку без простыни кода - идеально оценить размах изменений перед тем, как нырять в детали:

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

$ git diff --stat HEAD~3 HEAD
 src/auth.py    | 14 ++++++++------
 README.md      |  3 ++-
 tests/test.py  | 22 ++++++++++++++++++++++
 3 files changed, 33 insertions(+), 6 deletions(-)
Типичные грабли
  • "git diff пустой, а я менял файл". Ты уже сделал . Смотри

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

    git diff --staged
    или

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

    git diff HEAD
    . Классика номер один.
  • Путаница --staged и без него.

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

    git diff
    - то, что не в коммите.

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

    git diff --staged
    - то, что уйдёт в коммит. Перепутал - закоммитил не то.
  • split не предлагается. доступен, только если внутри hunk-а есть неизменённые строки-контекст, разделяющие правки. Две соседние правки без контекста между ними Git разбить не сможет - тут спасёт только (ручная правка).
  • git restore без --staged стёр работу.

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

    git restore file
    (без флага) выбрасывает правки рабочего каталога без права на возврат. Хотел разиндексировать - всегда добавляй

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

    --staged
    . Перепутал флаг - потерял час работы.
  • CRLF и пробелы засоряют diff. Целые файлы краснеют из-за смены переводов строк. Лови пробельный шум флагом

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

    git diff --check
    , а игнорируй пробелы при чтении через

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

    git diff -w
    (но не коммить с , это только для просмотра).
  • Бинарники. Git честно пишет

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

    Binary files a/logo.png and b/logo.png differ
    и не лезет показывать байты. Это норма, а не ошибка.
Мини-лаба: собери два чистых коммита из одной кучи

Повтори руками по шагам, это десять минут и навык в пальцах:

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

mkdir diff-lab && cd diff-lab
git init -b main
printf 'line one\nline two\nline three\n' > app.txt
git add app.txt && git commit -m "init"

# Намеренно мешаем две независимые правки в один файл
printf 'line one CHANGED\nline two\nline three\nline four added\n' > app.txt

git status                 # видим modified, файл не в индексе
git diff                   # читаем hunk: где -, где +
git diff --word-diff       # ту же правку первой строки видно по словам
git add -p app.txt         # жмём s если предложит, индексируем порциями
git diff --staged          # что уйдёт в коммит
git diff                   # что осталось в рабочем каталоге
git commit -m "rewrite line one"

git add -p app.txt         # вторую правку - отдельным коммитом
git commit -m "add line four"
git log --oneline          # два осмысленных коммита вместо одного "fixes"

# Бонус: разиндексировать и откатить
printf 'oops\n' >> app.txt
git add app.txt
git restore --staged app.txt   # сняли из индекса, правка на диске цела
git diff                       # видим oops в рабочем каталоге
git restore app.txt            # откатили - oops исчез
Контрольные вопросы
Итог

Три дерева - рабочий каталог, индекс, HEAD - и три формы

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

git diff
между ними.

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

git status
даёт карту,

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

git diff
- содержимое,

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

--word-diff
спасает на длинных строках, и

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

main...feature
- на ревью. А

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

git add -p
превращает кашу из правок в цепочку атомарных коммитов, где каждый делает ровно одно.

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

git restore
аккуратно снимает из индекса или откатывает файл - помни про разницу с

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

--staged
и без. Освоишь это - и твоя история перестанет быть свалкой "fixes", а станет документом, по которому можно вести расследование. В следующем уроке зафиксируем порции в настоящие коммиты и разберём анатомию хорошего сообщения.
👍4 ❤️1 🔥2 😄 🤔2
Аватара пользователя
rjholland
Сообщения: 2
Зарегистрирован: 12 май 2026, 07:57

Re: Просмотр изменений: status, diff и индексация по частям

Сообщение rjholland »

Спасибо, наконец-то дошло почему git diff показывал пусто - я же сам делал git add до этого. Теперь всегда сначала git diff --staged перед коммитом.
👍1 ❤️1 🔥 😄 🤔
Аватара пользователя
WasmUser
Сообщения: 1
Зарегистрирован: 26 май 2026, 05:34

Re: Просмотр изменений: status, diff и индексация по частям

Сообщение WasmUser »

Граблю с restore поймал на своей шкуре месяц назад, снёс полдня работы одной командой без --staged. Жаль урока тогда не было.
👍1 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
Первый репозиторий: init, add, commit, status
Следующая глава →
История проекта: git log, диапазоны и поиск

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

Поделиться темой: ✈ Telegram VK
Похожие запросы: Git команды для начинающих

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

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

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