Понимание эффекта 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, он должным образом скрыт: вам не нужно знать об этом.

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