Являются ли "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 делает это (более или менее, и я намеренно зачищаю фильтры):
- Вычислить хеш для содержимого файла
file_name
(git hash-object -t blob
). - Если этот объект еще не находится в репо, запишите его в репо (используя
-w
возможностьhash-object
). - Обновить
.git/index
(или же$GIT_INDEX_FILE
) так что у него есть отображение под именемfile_name
на имя, которое вышло изgit hash-object
, Это всегда запись "stage 0" (это нормальная версия без конфликта слияний).
Таким образом, файл на самом деле не "в" промежуточной области, он действительно "в" самом репо! То, что находится в области подготовки, является названием для отображения SHA-1.
В отличие от git checkout [<tree-ish>] -- file_name
Является ли это:
Если дано
<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 на месте (разрешенный конфликт остается разрешенным).В любом случае, если это еще не ошибка, используйте блоб 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