Git, вероятно, должен извиниться за то, что запутался здесь, но Git никогда не извиняется.
Флаги «предположить, что без изменений» и «пропустить рабочее дерево» предназначены только для файлов, о которых Git уже знает (файлы, которые отслеживаются ).
Перечисление имен или шаблонов в .gitignore
имеет смысл только для файлов, которые не отслеживаются .
Слово track в Git используется слишком часто: оно означает несколько разных вещей.
Чтобы объяснить все это, мы должны тщательно определить, что именно, отслежено означает в Git, в этом контексте. Но для этого необходимо, чтобы мы посмотрели на Git index , потому что точное определение отслеживаемого файла - это файл, который указан в индексе Git . Поэтому, если вы не знаете, что это за индекс, все равно это не имеет никакого смысла.
Определение индекса Гита
Вы, наверное, уже видели, что Git-репозиторий содержит - на самом деле, в основном состоит из - коммитов. commit сохраняет полный снимок некоторых исходных файлов, а также некоторую дополнительную информацию о самом коммите: кто его сделал, когда и так далее. Коммиты не являются изменениями! Вместо этого, коммиты имеют предыдущий или родительский коммит ID, и если вы попросите Git показать вам коммит, Git извлечет и сам коммит, и его parent, а затем сравните parent с коммитом. Что бы ни отличалось , Git покажет вам эту разницу. Это означает, что git show <hash>
показывает набор изменений, но на самом деле <hash>
идентифицирует снимок.
(Коммиты идентифицируются по тем большим уродливым хеш-идентификаторам, которые вы, несомненно, также видели. Они кажутся случайными, но на самом деле они представляют собой криптографические контрольные суммы всего содержимого каждого коммита. Они не очень полезны для работы людей тем не менее, поэтому мы склонны использовать имена веток для идентификации самого последнего коммита в ветке.)
Файлы, замороженные в коммитах Git, находятся в специальном, только для чтения, сжатом формате, полезном только для самого Git. Это означает, что для использования файлов Git должен разморозить и распаковать файлы, превратив их в их обычную форму, полезную для компьютера. Это ваше рабочее дерево (или рабочее дерево или любое изменение этих слов), где вы выполняете свою работу.
Если бы Git был похож на другие системы контроля версий, он мог бы на этом остановиться, только с коммитами (замороженными и только для Git) и рабочим деревом (незамерзшим, полезным для всех). Но Git не похож на другие системы контроля версий. Вместо этого Git вставляет промежуточное местоположение, которое Git по-разному называет index , staging area или cache (в зависимости от того, кто / какая часть Git является звонить).
Файлы в индексе хранятся в том же формате Git-only, что и в коммитах, но когда они находятся в индексе, они размораживаются. Процесс git checkout
по существу копирует файлы из замороженного коммита, в индекс (только для размораживания), а затем в рабочее дерево (распаковка и создание полезного формата).
Почему этот индекс вообще существует и требует, чтобы вы знали об этом? Чтобы получить полный ответ, вам нужно спросить Линуса Торвальдса, но мы можем указать на несколько вещей, которые он делает:
Это делает молниеносно новые коммиты. Git делает новые коммиты, замораживая содержимое индекса. Это на намного быстрее, чем пробираться по рабочему дереву, сжимая каждый файл заново. Файлы в индексе уже находятся в специальной сжатой форме Git-only, поэтому git commit
просто нужно заморозить эти копии.
Это дает нам (и Git) способ решить, какие файлы рабочего дерева отслеживаются , а какие не отслеживаются .
Отслеживаемый файл - это файл с копией в индексе. Это все, что нужно сделать, но это очень важно, потому что, как мы только что видели, Git делает new коммит из любого находится в индексе:
Запуск git add
копирует файл из рабочего дерева в индекс. Если в индексе ранее была копия, это заменяет старую копию новой из рабочего дерева. Если раньше не было копии, то теперь есть. Этот файл в форме, только что скопированной в указатель, войдет в сделанный вами следующий коммит.
Запуск git rm
удаляет файл из индекса (и рабочего дерева). Если раньше в индексе была копия, сейчас ее нет. Файл не будет находиться в следующем вашем коммите.
Непросто увидеть содержимое индекса напрямую. (Существует одна команда - git ls-files
, которая покажет ее, но эта конкретная команда в основном предназначена для средств отладки и записи, а не для повседневного использования.) Вместо этого, команда git status
показывает вам, что отличается в указателе. В частности, он выполняет два сравнения:
Сначала git status
сравнивает текущую фиксацию с индексом. Что бы это ни было отличается , git status
вызывает постановка для коммита . Это включает в себя файлы, которые являются новыми в индексе, удалены из индекса или просто отличаются в индексе от их версии HEAD
-commit.
Затем git status
сравнивает индекс с рабочим деревом. Что бы это ни было отличается , git status
вызывает , не ставится на коммит . Это включает в себя файлы, которые есть в индексе, но не в рабочем дереве или наоборот, и, конечно, файлы, которые находятся в обоих, но различаются.
Теперь мы можем определить assume-unchanged
, skip-worktree
и .gitignore
Теперь, когда у нас есть хорошая идея, что означает, что файл находится в индексе, чтобы эта конкретная копия файла перешла в следующую фиксацию - или , а не быть в индексе, чтобы не было в следующем коммите - мы можем посмотреть, что означают и делают эти различные опции.
Если файл не в индексе, но - в рабочем дереве, git status
будет жаловаться на вас во время второго сравнения. Он скажет вам: Эй, этот файл находится в рабочем дереве, возможно, вам следует добавить его в индекс. Если вы не хотите, чтобы git status
пожаловались на неотслеживаемый файл, вы можете перечислить файл в .gitignore
.
Этот список влияет только на неотслеживаемые файлы . Если файл уже находится в индексе - каким бы он ни был, независимо от того, был ли он зафиксирован или вы сделали для него git add
, - файл уже отслеживается. Перечисление файла в .gitignore
не будет иметь никакого эффекта.
Если файл равен в индексе и , вы изменили копию рабочего дерева, git status
проверит ее и скажет, что версия рабочего дерева изменен и, возможно, вам следует скопировать обновление в индекс, чтобы обновленная версия вошла в следующий коммит. Вот где приходят assume-unchanged
и skip-worktree
: с любым установленным битом git status
будет считать, что копия рабочего дерева не изменена, или пропустить ее (или оба) и не будет жаловаться на это.
Команда git add
подчиняется аналогичным правилам: если файл не в индексе (но находится в рабочем дереве), и вы используете команду «добавить все файлы» в массовом порядке , git add
будет не добавлять файлы, которые не отслеживаются и игнорируются. Также не добавляет файлы, которые отслеживаются, но помечаются как неизмененные или пропущенные. Таким образом, файл без отслеживания и игнорирования не будет отслеживаться и не будет в следующем коммите; и отслеживаемый, но пропущенный файл не будет обновлен , поэтому при следующей фиксации будет по-прежнему использоваться старая устаревшая индексная копия.
Каталоги немного странные
Git never хранит директории (или "папки", если вы предпочитаете это слово) вообще в коммитах. Git хранит только файлы . Индекс содержит только файлы 1 , и только у самой записи индекса есть биты предположения без изменений и пропуска рабочего дерева, поэтому вы не можете установить это для каталога, вы должны установить его для всех отслеживаемых файлы в каталоге.
Файл .gitignore
, однако, имеет специальную функцию для ускорения git status
. Оказывается, поиск по рабочему дереву обычно является самой медленной частью всего (вероятно, поэтому индекс существует вообще). Таким образом, если вы перечислите имя каталога в .gitignore
и , то в нет файлов , в которых этот каталог уже находится в индексе, Git будет использовать ярлык и не будет искать в этом каталоге вообще.
Это означает, что если ни один из файлов dir/*
в настоящее время не отслеживается, и вы перечисляете dir
в своих .gitignore
, git status
и git add .
, они никогда не будут выглядеть внутри dir
найти любой из этих файлов, поэтому он никогда не добавит их и никогда не будет жаловаться на то, что они не отслежены. Следовательно, перечисление каталога в .gitignore
может заставить Git не включать ни один из файлов в каталоге. Но после того, как вы отследили хотя бы один файл внутри каталога, Git все равно обязан сканировать каталог, так что это не всегда дает такой эффект.
Это немного сбивает с толку, и в крайних случаях вы можете использовать git check-ignore -v
или даже git ls-files --stage --debug
, чтобы выяснить, какие правила, если таковые имеются, игнорируют некоторые файлы или что именно в индекс, включая флаги предположения-неизмененного и пропуска рабочего дерева. Но это в основном работает, и довольно хорошо, на практике.
1 Технически, индекс может хранить некоторую информацию каталога, особенно при использовании неотслеживаемого кэша . Тем не менее, git ls-files
не показывает ничего из этого, по крайней мере в настоящее время, даже под --debug
.