Являются ли "git add file" и "git checkout - file" симметричными?

У меня есть следующее понимание git add file а также git checkout -- file (но я не уверен, правильно ли это).

Всякий раз, когда мы редактируем файлы с помощью текстового редактора, мы делаем это в рабочем каталоге. Каждый раз мы можем переместить файл в так называемый staging area выполняя git add file_name, Если мы отредактируем файл снова (после git add) мы меняем файл в рабочем каталоге и, таким образом, в рабочем каталоге у нас есть файл в "новом" состоянии, в то время как в staging area файл находится в "старом" состоянии.

Когда мы используем git add снова мы переводим файл в промежуточной области в "новое" состояние (состояние из рабочего каталога).

Если мы делаем git checkout -- file_nameЯ предполагаю, что мы берем файл из промежуточной области и используем его для перезаписи файла в рабочем каталоге. Таким образом, мы можем перевести файл в рабочем каталоге в "старое" состояние. Это правильно?

Что мне также непонятно, так это если мы скопируем или переместим файл из области подготовки. Другими словами, делает git checkout -- file изменить состояние файла в staging area, Можем ли мы сказать, что после git checkout -- file файл в промежуточной области изменяет состояние файла до его предыдущего состояния в промежуточной области?

2 ответа

Решение

Это почти, но не совсем, что симметрично.

Это правда, что git add file копирует файл на сцену (он же "индекс"). Однако то, как это происходит, немного странно.

Внутри git-репо все хранится как git-объект. Каждый объект имеет уникальное имя, его SHA-1 (эти строки из 40 символов, такие как 753be37fca1ed9b0f9267273b82881f8765d6b23Это от фактического .gitignore У меня тут). Имя строится путем вычисления хэша содержимого файла (более или менее - есть некоторая хитрость, чтобы убедиться, что вы не делаете файл из дерева каталогов или не делаете коммит, например, и не вызываете коллизию хеша). Git предполагает, что независимо от содержимого SHA-1 будет уникальным: никакие два разных файла, дерева, фиксации или аннотированные теги никогда не будут хэшировать одно и то же значение.

Файлы (и символические ссылки) являются объектами типа "blob". Таким образом, файл, который находится в репозитории git, хэшируется, и где-то в git есть отображение: "файл с именем .gitignoreхэш-значение " to " 753be37fca1ed9b0f9267273b82881f8765d6b23").

В репо деревья каталогов хранятся в виде объектов типа "дерево". Объект дерева содержит список имен (например, .gitignore), режимы, типы объектов (другое дерево или большой двоичный объект) и SHA-1:

$ git cat-file -p HEAD:
100644 blob 753be37fca1ed9b0f9267273b82881f8765d6b23    .gitignore
[snip]

Объект commit возвращает вам (или git) объект дерева, который в итоге получает идентификаторы BLOB-объектов.

Промежуточная область ("указатель"), с другой стороны, представляет собой просто файл, .git/index, Этот файл содержит1 имя (в смешной слегка сжатой форме, которая выравнивает деревья каталогов), "номер этапа" в случае конфликтов слияния и SHA-1. Фактическое содержимое файла, опять же, блоб в git-репо. (Git не хранит каталоги в индексе: в индексе есть только реальные файлы, использующие этот плоский формат.)

Итак, когда вы делаете:

git add file_name

Git делает это (более или менее, и я намеренно зачищаю фильтры):

  1. Вычислить хеш для содержимого файла file_name (git hash-object -t blob).
  2. Если этот объект еще не находится в репо, запишите его в репо (используя -w возможность hash-object).
  3. Обновить .git/index (или же $GIT_INDEX_FILE) так что у него есть отображение под именем file_nameна имя, которое вышло из git hash-object, Это всегда запись "stage 0" (это нормальная версия без конфликта слияний).

Таким образом, файл на самом деле не "в" промежуточной области, он действительно "в" самом репо! То, что находится в области подготовки, является названием для отображения SHA-1.

В отличие от git checkout [<tree-ish>] -- file_name Является ли это:

  1. Если дано <tree-ish> (имя коммита, идентификатор объекта дерева и т. д. - в основном все, что git может разрешить в дереве), выполнить поиск имени по найденному дереву путем преобразования аргумента в объект дерева. Используя идентификатор объекта, расположенный таким образом, обновите хеш в индексе, как этап 0. (Если file_name именует объект дерева, git рекурсивно обрабатывает все файлы в каталоге, который представляет дерево.) При создании записей этапа 0 любые конфликты слияния на file_name сейчас решены.

    В противном случае выполните поиск по имени в индексе (не уверен, что произойдет, если file_name это каталог, вероятно, git читает рабочий каталог). Преобразовать file_name к идентификатору объекта (который будет каплей к этому моменту). Если запись ступени 0 отсутствует, выдается сообщение об ошибке "unmerged", если не указано иное. -m, --ours, --theirs опции. С помощью -m "разгрузит" файл (удалит запись этапа 0 и заново создаст конфликтующее объединение2), пока --ours а также --theirs оставьте любую запись стадии 0 на месте (разрешенный конфликт остается разрешенным).

  2. В любом случае, если это еще не ошибка, используйте блоб SHA-1(s), расположенный таким образом, чтобы извлечь копию репо (или копии, если file_name называет каталог) в рабочий каталог.

Итак, короткая версия "да и нет": git checkout иногда изменяет индекс, а иногда только использует его. Однако сам файл никогда не сохраняется в индексе, только в репо. если ты git add файл, измените его еще немного, и git add опять же, это оставляет позади то, что git fsck найдет как "болтающийся шарик": объект без ссылки.


1 Я намеренно опускаю множество других вещей в индексе, чтобы git работал хорошо и позволял --assume-unchanged и т. д. (Они не относятся к действию добавления / извлечения здесь.)

2 Это воссоздание уважает любое изменение merge.conflictstyleтак что если вы решили, что вам нравится diff3 выход и уже есть конфликтующее слияние без diff3 стиль, вы можете изменить конфигурацию git и использовать git checkout -m чтобы получить новый рабочий каталог слияния с новым стилем.

Когда вы добавляете файл git add Вы отметили, что хотите зафиксировать файл именно с этим состоянием. Git запоминает это состояние файла и сохраняет его неизменным, пока вы его фиксируете или сбрасываете. Таким образом, все манипуляции с файлом после постановки будут происходить с файлом в рабочем каталоге, а не с постановкой.
Когда ты бежишь git checkout git изменит только неустановленные файлы на версию HEAD. Чтобы изменить промежуточные файлы на вашу версию HEAD, вам нужно запустить git reset

Другие вопросы по тегам