Git для Windows ошибочно создаёт альтернативные потоки данных
Новости Отредактировано
3К открытий3К показов
Выяснилось, что Git странно себя ведёт, когда речь идёт о файлах, в именах которых содержится двоеточие.
Кроме как в изначальном префиксе диска (например, C:\
), Windows не допускает иных двоеточий в именах файлов или путях. У Unix такого ограничения нет. Для проверки был создан тестовый репозиторий, содержащий один файл foo:bar
, содержащий запись hello
. Клонирование репозитория стандартной версией Git for Windows не выдаёт никаких ошибок или предупреждений:
Вместо файла с именем foo:bar
, тем не менее, вы получите пустой файл foo
:
Это и так странно, но ещё интереснее то, что Git имеет другой взгляд на это:
Git замечает неотслеживаемый файл foo
, но считает, что foo:bar
тоже присутствует и содержит исходную строку.
Всё становится ещё более странным, когда вы активируете опцию core.fscache (которая включена по умолчанию в версиях 2.8.2 и более поздних) — после этого foo:bar
регистрируется как отсутствующий:
Но почему всё так происходит?
Главной причиной этого является довольно неочевидная фича NTFS, называющаяся «альтернативные потоки данных». Про это также можно почитать здесь и здесь.
Вкратце, файлы в NTFS — не просто данные, а наборы из одного и более потоков данных. То, что мы считаем содержимым файла, на самом деле является содержимым главного, неименованного потока. Также данные можно хранить и в других, именованных потоках. К этим потокам можно прямо обращаться, прибавляя :streamname
к обычному пути файла, например, к потоку MyStream
в файле qwerty.txt
можно получить доступ по пути qwerty.txt:MyStream
.
Так, хотя foo:bar
и не является допустимым именем файла в Windows, файловые API Windows рады принять его для операций чтения/записи, т.к. это вполне нормальный путь к чему-то в файловой системе, в нашем случае — альтернативный поток bar
файла foo
.
Что делает Git
Теперь, когда мы знаем про альтернативные потоки данных, всё проясняется.
При клонировании Git перемещает контент по пути foo:bar
. Это полностью разрешенный путь, поэтому система не выдаёт ошибок. В результате мы получаем файл foo
без содержимого в основном потоке данных (поскольку его длина равна 0), но 6 байтов в альтернативном потоке bar
:
Git использует различные алгоритмы проверки в зависимости от того, включена ли опция core.fscache
.
Если core.fscache
отключена, метаданные файла проверяются по одному, что приводит к вызову GetFileAttributesEx для каждого пути. Git даже не догадывается, что имеет дело с альтернативным потоком, потому что эти API ведут себя так же, как и в случае нормального пути. Существует ли foo:bar
? Да! Совпадает ли последнее время изменения с ожидаемым? Тоже да! А содержимое? Аналогично! Отлично, этот файл менять не нужно.
Если же core.fscache
включена, Git кэширует метаданные файла, затем читает их из кэша, а не вызывает файловые API напрямую. Это приводит к другой ситуации — при нумерации файлов в директории Windows отмечает лишь foo
, поскольку это единственный присутствующий файл. Поэтому кэш, когда у него запрашивают метаданные foo:bar
, считает, что такого файла нет.
Вывод
Всё это достаточно глупо и вообще не должно происходить. Git должен находить неправильное имя файла, выдавать ошибку и не пытаться записывать файл на диск. Именно так уже обрабатываются валидные в Unix, но невалидные в Windows имена файлов (например, файл с именем \Windows\System32\crypt32.dll
будет заблокирован). Такие файлы будут корректно обрабатываться вне зависимости от опции core.fscache
.
3К открытий3К показов