Понимание эффекта git reset на индекс
У меня небольшой конфликт при чтении документации / учебников по сбросу git: Для git reset --mixed
например, документация гласит:
Следующее, что будет сделано для сброса, это обновить индекс с содержимым любого снимка
HEAD
сейчас указывает на
Причиной моего конфликта является тот факт, что я ожидаю очистки индекса вместо обновления индекса. Индекс очищен или обновлен с любым снимком HEAD
сейчас указывает на?
1 ответ
TL;DR
Индекс всегда обновляется. Индекс содержит следующий коммит, который вы намереваетесь сделать, поэтому он никогда не будет пустым. (Что, никогда? Ну, вряд ли когда-либо: он пуст в новом только что созданном репозитории, в котором нет файлов, и ничего не сохранит, если вы запустите git commit
прямо сейчас. Это также пусто, если вы git rm
все.)
Долго
Ваша путаница здесь почти наверняка связана с комментарием, сделанным PetSerAl. Новичкам в Git часто говорят или показывают, или, по крайней мере, заставляют поверить, что коммиты и / или индекс Git содержат изменения, но это неверно! Как только вы избавитесь от этого неверного убеждения, некоторые из загадок Git станут более понятными. (Не весь Git имеет смысл ни для кого, даже для меня. Так что не волнуйтесь, если потребуется много времени, чтобы поймать Git.)
В Git коммит содержит полный снимок всех ваших файлов. Он также содержит некоторые метаданные - информацию о самом коммите, например ваше имя, адрес электронной почты и метку времени. В метаданные включен хэш-идентификатор родительского коммита коммита - или, для коммита слияния, нескольких родителей, множественного числа, - и, сравнивая коммиты с их родителями, Git показывает вам изменения. Каждый коммит имеет свой уникальный хэш-идентификатор, такой как 8858448bb49332d353febc078ce4a3abcc962efe
(это идентификатор коммита в Git-репозитории для Git). Этот коммит является снимком, но этот коммит имеет родителя (в данном случае, 67f673aa4a...
), так что Git может показать вам 8858448bb4...
извлекая как ранее 67f673aa4a
а также 8858448bb4
, затем сравнивая два. git show
команда делает именно это, так что вы видите то, что изменилось в 8858448bb4
, а не то, что в 8858448bb4
,
(Это все равно, что сказать вам, что сегодня на 5 градусов теплее или прохладнее, чем вчера, и более или менее ветрено, вместо того, чтобы указывать погоду в виде набора чисел. База данных хранит абсолютные значения, но в основном мы хотим знать, лучше ли это.)
Индекс хранит следующий коммит, который вы можете сделать
Вы можете увидеть коммиты Git по-разному и, конечно же, назвать их по их хэш-идентификаторам, как я делал выше. Вы можете видеть свое дерево работы - именно там, где Git позволяет вам просматривать и редактировать ваши файлы - напрямую: там, на вашем компьютере, в их обычной повседневной форме. Но вы не можете видеть индекс очень хорошо. Это невидимо. Это проблема, потому что это также важно.
Большинство систем контроля версий вообще не имеют индекса, или, если у них есть что-то подобное, держите его так хорошо скрытым, что вам никогда не придется об этом знать. Но Git делает эту странную вещь, заставляя вас понимать индекс Git, и в то же время держать его немного скрытым.
Если вы действительно хотите увидеть список файлов, которые сейчас находятся в индексе, вы можете использовать git ls-files
:
$ git ls-files | head
.clang-format
.editorconfig
.gitattributes
.github/CONTRIBUTING.md
.github/PULL_REQUEST_TEMPLATE.md
.gitignore
.gitmodules
.mailmap
.travis.yml
.tsan-suppressions
$ git ls-files | wc -l
3454
В этом репозитории Git для Git содержится почти 3500 файлов. Это много файлов! Вот почему Git скрывает это: в нем слишком много вещей, чтобы понять их.
Но это также, почему Git показывает нам коммиты, сравнивая их с их родителями. Отображение всего содержимого 8858448bb4
было бы слишком много, так git show 8858448bb4
показывает нам, что изменилось в 8858448bb4
, против его родителя. Git делает то же самое с индексом, показывая нам, что мы изменили, а не выкидывая все это.
Я думаю, это то, что заставляет людей думать, что Git хранит изменения. Git показывает изменения, поэтому Git должен хранить их... но это не так! Git хранит целые снимки. Git определяет изменения каждый раз, когда вы просите Git показать вам что-то.
Имея это в виду, давайте посмотрим, как мы видим индекс.
Индекс находится между текущим коммитом и рабочим деревом
Теперь мы знаем, что каждый коммит является полным снимком. Если бы Git создавал новую копию каждого файла каждый раз, когда мы делали коммит, хранилище становилось очень большим и очень быстрым. Так что он этого не делает, и одна часть того, как он этого не делает, действительно проста. В то время как каждый коммит является полным снимком, файлы внутри каждого коммита полностью и полностью доступны только для чтения. Никто из них не может измениться. Это означает, что каждый коммит может поделиться некоторыми или всеми своими файлами с некоторыми ранее коммитами!
Git просто нужно убедиться, что каждый раз, когда мы запускаем git commit
он замораживает все содержимое файла навсегда - или, если не навсегда, по крайней мере до тех пор, пока этот новый коммит продолжает существовать. Поэтому файлы внутри каждого коммита замораживаются. Они также сжаты в специальный формат Git-only (который очень хорошо работает для текстовых файлов, но часто не так хорош для бинарных файлов, как изображения). Это сжатие занимает время, иногда много времени, но делает хранилище небольшим.
Очевидно, что замороженные Git-only файлы полезны только для самого Git, поэтому нам нужна копия каждого файла из текущего коммита, извлеченного, размороженного, распакованного и сделанного полезным. Эти полезные копии попадают в дерево работ, где мы выполняем свою работу.
Другие системы контроля версий делают то же самое. В гипотетической системе контроля версий XYZ вы запускаете xyz checkout commit
и он копирует коммит из хранилища глубокой заморозки, оттаивает его, распаковывает и сохраняет в вашем рабочем дереве. Вы делаете некоторую работу, и в конце концов вы бежите xyz commit
, Теперь он просматривает все ваше рабочее дерево, повторно сжимает каждый файл, замораживает его и проверяет, есть ли у него уже замороженная версия на складе или нужно ли ее тоже туда поместить. Каждый из этих шагов занимает много секунд или минут, пока вы идете за кофе или чем-то еще.
То, что делает Git со своим индексом, очень умно: индекс является промежуточной областью между хранилищем глубокой заморозки (хранилище, полное коммитов) и полезной формой (размороженные файлы в вашем рабочем дереве). Изначально он содержит те же файлы, которые были в глубокой заморозке. Они оттаяли (вроде), но все еще находятся в специальной форме Git-only, и они соединены с полностью оттаявшей, распакованной версией в вашем рабочем дереве.
Когда вы изменяете файлы в своем рабочем дереве или добавляете и / или удаляете файлы, копии индекса теряют синхронизацию с рабочим деревом. Теперь Git может сравнивать индексную копию с копией рабочего дерева и сообщать вам, что вы изменили, но еще не подготовили.
Как только у вас есть какой-то файл, как вы хотите, вы запускаете git add file
, Это сразу же сжимает файл в специальный формат Git и помещает эту копию в индекс. Теперь индексная копия, которая является полной копией, только что сжатой, совпадает с копией рабочего дерева, но отличается от принятой копии.
В любой момент вы можете заставить Git сравнить совершенное (HEAD
) копия каждого файла в индексной копии:
git diff --cached
Для файлов, которые одинаковы, Git ничего не говорит. Для файлов, которые отличаются, Git выводит список файлов и показывает разницу.
Точно так же в любой момент вы можете заставить Git сравнивать индексную копию каждого файла с копией рабочего дерева:
git diff
Для файлов, которые одинаковы, Git ничего не говорит. Для файлов, которые отличаются, Git выводит список файлов и показывает разницу.
(Примечание: добавление --name-status
имеет git diff
покажет вам имена файлов с префиксом M
для модифицированных, если они модифицированы. Git использует A
для вновь добавленного файла, D
для удаленного файла и так далее. Файл удаляется в индексе, просто полностью удаляя его из индекса. Файл добавляется в индекс, если он находится в индексе, но не в HEAD
.)
git status
команда выполняет оба этих сравнения, с --name-status
Ограничитель. Для файлов, которые отличаются между HEAD
и индекс, они подготовлены для фиксации. Для файлов, которые отличаются между индексом и рабочим деревом, они не подготовлены для фиксации.
Наглядно:
HEAD index work-tree
---------- ---------- ----------
README.txt README.txt README.txt
main.py main.py main.py
HEAD
копия заморожена, потому что она находится в коммите. Индекс и копии рабочего дерева могут меняться, но изначально все три совпадают. Вы меняете копию рабочего дерева и используете git add
скопировать его обратно в индекс, сжав и en-Git-ing (если "en-Git-ing" - это слово, а это не так). Если вы не хотели менять его в индексе, используйте git reset
(с его значением по умолчанию --mixed
действие, или способ, которым он работает с любым отдельным файлом), чтобы скопировать замороженный файл обратно в индекс.
Это также почему git commit
так быстро, по сравнению с xyz commit
Когда ты бежишь git commit
У Git уже есть все файлы, которые будут добавлены в новом коммите, в правильной форме. Не нужно повторно сжимать все файлы рабочего дерева и проверять, соответствуют ли они замороженным зафиксированным версиям. В индексе есть все, что нужно: все, что ему нужно сделать, это заморозить копию индекса, и, если это совпадает с предыдущим коммитом, поделиться файлом с предыдущим коммитом.
Более того, поскольку индекс "знает", какие файлы соответствуют рабочему дереву, а какие нет, 1 и имеет дополнительную информацию о том, что находится в хранилище, это делает git checkout
быстрее тоже. Предположим, вы на master
с его около 3500 файлов, и вы git checkout
какая-то другая ветвь с примерно 3300 файлами, все одинаковые. Около 200 файлов различаются между двумя коммитами (может быть, несколько новых или удалены). Git может использовать индекс, чтобы знать, что ему может понадобиться в рабочем дереве, и вообще не трогать эти файлы около 3300.
Следовательно, вместо сканирования системы XYZ и, возможно, трогательных 3500 файлов, Git сканирует и, возможно, трогает 200 файлов, экономя более 94% работы.
1 Это часто требует сканирования рабочего дерева. Индекс хранит копии (кэширует) данные о рабочем дереве, чтобы ускорить это. Вот почему индекс иногда называют кешем. Другие VCS, такие как Mercurial, имеют кэш рабочего дерева (Mercurial называет его dirstate), но в отличие от индекса Git, он должным образом скрыт: вам не нужно знать об этом.