$ sudo teach IT
Модуль 2 · Первые коммиты · Урок 2.3

Индекс и git diff

Вы научитесь видеть изменения до коммита и собирать коммит только из нужных файлов.

Теория + практика~20 минутНовичокgit diffиндекс

Что изменилось: git diff

git diff в Git — команда, которая показывает, что именно изменилось в файлах с последнего коммита, строка за строкой. Вы научитесь читать её вывод, отличать git diff от git diff --staged и собирать коммит из нужных файлов с помощью индекса.

Один коммит у вас уже есть, но проект будет меняться, и перед коммитом хочется убедиться, что вы изменили именно то, что собирались. Для этого и нужен git diff.

Простыми словами

Это как режим рецензирования в текстовом редакторе: Git кладёт две версии файла рядом и подчёркивает разницу.

Практика с примером

Добавим в index.html подключение стилей: одну строку <link rel="stylesheet" href="style.css"> сразу после строки с <title>. Сохраните файл и спросите у Git:

Терминал
$ git status
On branch main
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:   index.html

no changes added to commit (use "git add" and/or "git commit -a")

modified: index.html — файл отслеживается и изменился. Раздел Changes not staged for commit означает «изменения, не подготовленные к коммиту»: они ещё не в индексе. Подсказка про git restore — снова отмена, до неё доберёмся в следующем модуле. Теперь посмотрим, что именно изменилось:

Терминал
$ git diff
diff --git a/index.html b/index.html
index 10ae62b..5c2151d 100644
--- a/index.html
+++ b/index.html
@@ -3,6 +3,7 @@
 <head>
   <meta charset="UTF-8">
   <title>Алекс Иванов - Визтка</title>
+  <link rel="stylesheet" href="style.css">
 </head>
 <body>
   <h1>Алекс Иванов</h1>

Разбор по строкам

  • diff --git a/index.html b/index.html — сравниваются две версии файла index.html: a/ — старая (из последнего коммита), b/ — новая (в рабочей папке).
  • index 10ae62b..5c2151d 100644 — служебная строка: короткие номера старой и новой версии содержимого.
  • --- a/index.html и +++ b/index.html — условные обозначения: минусы относятся к старой версии, плюсы к новой.
  • @@ -3,6 +3,7 @@ — заголовок блока изменений. -3,6: в старой версии блок начинается с 3-й строки и занимает 6 строк. +3,7: в новой — с 3-й строки и занимает 7 (на одну больше).
  • Строки, начинающиеся с пробела, — контекст: они не менялись, Git показывает их, чтобы вы понимали, где находитесь.
  • + <link rel="stylesheet" href="style.css"> — плюс в начале означает «строка добавлена». Удалённая строка начиналась бы с минуса, а изменённая показывается парой: старая со знаком - и новая со знаком +.

Пейджер: как выйти, если вывод «завис»

Если вывод длиннее экрана, Git открывает его в постраничном просмотрщике, внизу которого стоит двоеточие или слово (END). Это не зависание: пробел — следующая страница, стрелки — прокрутка, q — выход и возврат в терминал.

Частая путаница: git diff молчит про новые файлы

git diff сравнивает только отслеживаемые файлы. Создали новый файл, а git diff ничего не показывает? Всё в порядке: файл неотслеживаемый, и сравнивать его пока не с чем. Он виден в git status.

Подробно — в документации: git diff.

git diff --staged: что попадёт в коммит

Когда вы выполняете git add, правка переезжает в индекс, и git diff перестаёт её показывать: сравнивать рабочую папку не с чем, она совпадает с индексом. Но убедиться перед коммитом, что именно туда попало, тоже нужно. Для этого есть флаг --staged (staged — «подготовленный», так называют файлы в индексе).

КомандаЧто с чем сравниваетОтвечает на вопрос
git diffрабочая папка и индексЧто я изменил, но ещё не подготовил к коммиту?
git diff --stagedиндекс и последний коммитЧто войдёт в коммит, если я прямо сейчас его сделаю?

Привычка, которая сэкономит нервы: перед каждым коммитом смотреть на git diff --staged. Читается он так же, как обычный git diff.

Индекс — черновик коммита

Теперь понятно, зачем нужен индекс. Допустим, за рабочий час вы сделали сразу три дела: подключили стили к странице, написали сами стили и завели README. Одним коммитом «Всякое» можно всё закрыть, но через месяц не вспомнишь, что где менялось. Лучше разложить по смыслу: стили — в один коммит, README — в другой. Индекс позволяет собрать коммит из выбранных файлов, а остальное оставить на потом.

Создайте файл style.css (пока только первый блок, остальное допишем чуть позже) и файл README.md. Теперь в проекте три разных состояния файлов сразу:

Терминал
$ git status
On branch main
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:   index.html

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	README.md
	style.css

no changes added to commit (use "git add" and/or "git commit -a")

Для стилей нужны index.html и style.css. README пока подождёт:

Терминал
$ git add index.html style.css
$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   index.html
	new file:   style.css

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	README.md

В индексе два файла, README остался неотслеживаемым. Коммит заберёт только то, что в индексе — именно поэтому индекс называют черновиком коммита: пока вы не нажали commit, его можно пополнять.

Файл, изменённый после add

Есть тонкость, на которой спотыкаются почти все. Индекс запоминает файл таким, каким он был в момент git add. Если после этого вы продолжили править файл, новые правки в индекс сами не попадают.

Проверим. Допишите в конец style.css остальные правила (код ниже), а затем посмотрите на статус:

CSS · style.css (добавить в конец файла)
h1 {
  margin-bottom: 4px;
}

.lead {
  color: #666;
  margin-top: 0;
}

h2 {
  border-bottom: 2px solid #3b82f6;
  padding-bottom: 4px;
}
Терминал
$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   index.html
	new file:   style.css

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:   style.css

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	README.md

Файл style.css теперь значится в двух разделах сразу: как новый в индексе и как изменённый в рабочей папке. Противоречия нет: в индексе лежит старая версия (только блок body), в рабочей папке — новая, полная. Сравнить их поможет пара команд:

Терминал
$ git diff
diff --git a/style.css b/style.css
index 64b1bf7..f6c2e01 100644
--- a/style.css
+++ b/style.css
@@ -7,3 +7,17 @@ body {
   color: #222;
   background: #fafafa;
 }
+
+h1 {
+  margin-bottom: 4px;
+}
+
+.lead {
+  color: #666;
+  margin-top: 0;
+}
+
+h2 {
+  border-bottom: 2px solid #3b82f6;
+  padding-bottom: 4px;
+}

git diff показывает только то, что вы дописали после add. А git diff --staged показал бы то, что уже ждёт коммита (первая версия style.css и правку в index.html). Чтобы в коммит попала полная версия, нужен ещё один git add:

Терминал
$ git add style.css
$ git diff --staged --stat
 index.html |  1 +
 style.css  | 23 +++++++++++++++++++++++
 2 files changed, 24 insertions(+)

Разбор по строкам

  • --stat — сокращённый вывод: вместо самих строк Git показывает, в каких файлах сколько строк изменилось.
  • index.html | 1 + — в файле одна добавленная строка (+).
  • style.css | 23 +++… — в новом файле 23 строки, и это уже полная версия: add положил в индекс то, что было в рабочей папке сейчас.
  • 2 files changed, 24 insertions(+) — итог: два файла, 24 добавленных строки (1 + 23).

Забыли повторный add: в коммит уйдёт старая версия

Если бы вы сейчас сразу сделали коммит, в историю попала бы первая версия style.css без заголовков, а дописанные правила остались бы «висеть» в рабочей папке. Поэтому после любой правки файла, который уже добавлен в индекс, снова делайте git add и сверяйтесь со status: нужный файл не должен оставаться в разделе Changes not staged for commit.

Практика у себя

Доведём начатое до двух коммитов в папке ~/projects/portfolio. Если вы уже повторяли примеры выше, часть шагов сделана, пропустите их.

Шаг 1. Подключите стили. В index.html после строки <title>…</title> вставьте строку <link rel="stylesheet" href="style.css">. Сохраните и выполните git diff. Вывод должен совпасть с разобранным выше: одна добавленная строка с плюсом, + <link …>.

Шаг 2. Создайте style.css с первым блоком:

CSS · style.css
body {
  max-width: 640px;
  margin: 40px auto;
  padding: 0 16px;
  font-family: Arial, sans-serif;
  line-height: 1.6;
  color: #222;
  background: #fafafa;
}

Шаг 3. Создайте README.md. Это файл с описанием проекта в простом текстовом формате Markdown; решётка в начале строки делает её заголовком.

Markdown · README.md
# portfolio

Личный сайт-визитка Алекса Иванова. Учебный проект курса по Git.

Шаг 4. Подготовьте к коммиту только файлы про стили и убедитесь, что README остался в стороне:

Терминал
$ git add index.html style.css
$ git status

Ожидаемо: в Changes to be committed два файла (modified: index.html и new file: style.css), в Untracked files только README.md.

Шаг 5. Допишите в конец style.css остальные правила (блок «CSS · style.css (добавить в конец файла)» выше), сохраните, выполните git status и убедитесь, что style.css теперь значится в двух разделах. Выполните git diff: вы должны видеть только дописанные правила.

Шаг 6. Добавьте файл ещё раз и проверьте, что в индексе полная версия. В git status файл style.css должен остаться только в разделе Changes to be committed:

Терминал
$ git add style.css
$ git diff --staged --stat
 index.html |  1 +
 style.css  | 23 +++++++++++++++++++++++
 2 files changed, 24 insertions(+)

Шаг 7. Первый коммит дня:

Терминал
$ git commit -m "Добавить стили"
[main 8c23940] Добавить стили
 2 files changed, 24 insertions(+)
 create mode 100644 style.css

Теперь git status показывает только README.md среди Untracked files. Хеш в скобках у вас будет другой.

Шаг 8. Второй коммит:

Терминал
$ git add README.md
$ git commit -m "Добавить README"
[main f58d529] Добавить README
 1 file changed, 3 insertions(+)
 create mode 100644 README.md
$ git status
On branch main
nothing to commit, working tree clean

Проверьте себя

  • Рабочая папка чиста: nothing to commit, working tree clean.
  • Вы сделали два отдельных коммита: «Добавить стили» (два файла) и «Добавить README» (один файл).
  • Вы можете объяснить, чем отличаются git diff и git diff --staged.
  • Вы знаете, что после правки уже добавленного файла нужен повторный git add.

Частые ошибки

Забыли add после правки: в коммит ушла старая версия. В git status файл остался в Changes not staged for commit, хотя коммит уже сделан. Исправление простое — выполнить git add и сделать ещё один коммит. Но лучше проверять статус до коммита.

git add . добавил лишнее. Точка добавляет всё в текущей папке, включая черновики, архивы и файлы редактора. Перед git add . смотрите на git status, а после — на git diff --staged или git status. Если что-то лишнее попало, Git подсказывает в выводе, как убрать файл из индекса (git restore --staged); подробно — в следующем модуле.

git diff пустой, хотя вы правили файл. Причины: правка уже в индексе (смотрите git diff --staged), файл новый и неотслеживаемый (смотрите git status) или вы не сохранили файл в редакторе.

Вывод «завис» с двоеточием внизу. Это пейджер. Нажмите q.

Что важно запомнить

  • git diff показывает изменения в рабочей папке, которые ещё не в индексе; git diff --staged — то, что уже в индексе и войдёт в коммит.
  • В выводе diff строка с + добавлена, с - удалена, строки с пробелом — контекст.
  • Индекс — черновик коммита: в коммит попадают только добавленные файлы, а остальные можно закоммитить отдельно.
  • Индекс хранит файл таким, каким он был в момент git add. После новых правок нужен повторный git add.
  • Перед каждым коммитом смотрите git status и git diff --staged.

Теперь в проекте три коммита. Как посмотреть список всех сохранённых версий, открыть любую из них и сравнить между собой — в следующем уроке.

На следующий урок