Яндекс Bigtechnight перетяжка
Яндекс Bigtechnight перетяжка
Яндекс Bigtechnight перетяжка

Git для Windows ошибочно создаёт альтернативные потоки данных

Новости Отредактировано

3К открытий3К показов

Выяснилось, что Git странно себя ведёт, когда речь идёт о файлах, в именах которых содержится двоеточие.

Кроме как в изначальном префиксе диска (например, C:\), Windows не допускает иных двоеточий в именах файлов или путях. У Unix такого ограничения нет. Для проверки был создан тестовый репозиторий, содержащий один файл foo:bar, содержащий запись hello. Клонирование репозитория стандартной версией Git for Windows не выдаёт никаких ошибок или предупреждений:

			C:\src
> git clone https://github.com/latkin/filetest.git
Cloning into 'filetest'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 3
Unpacking objects: 100% (3/3), done.
Checking connectivity... done.
		

Вместо файла с именем foo:bar, тем не менее, вы получите пустой файл foo:

			C:\src
> cd .\filetest\
C:\src\filetest
> dir -force

    Directory: C:\src\filetest

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d--h--        7/17/2016   5:53 PM                .git
-a----        7/17/2016   5:47 PM              0 foo
		

Это и так странно, но ещё интереснее то, что Git имеет другой взгляд на это:

			C:\src\filetest
> git status
On branch master
Your branch is up-to-date with 'origin/master'.
Untracked files:
  (use "git add <file>..." to include in what will be committed)

        foo

nothing added to commit but untracked files present (use "git add" to track)
		

Git замечает неотслеживаемый файл foo, но считает, что foo:bar тоже присутствует и содержит исходную строку.

Всё становится ещё более странным, когда вы активируете опцию core.fscache (которая включена по умолчанию в версиях 2.8.2 и более поздних) — после этого foo:bar регистрируется как отсутствующий:

			C:\src\filetest
> git config core.fscache true
C:\src\filetest
> git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    foo:bar

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        foo

no changes added to commit (use "git add" and/or "git commit -a")
		

Но почему всё так происходит?

Главной причиной этого является довольно неочевидная фича NTFS, называющаяся «альтернативные потоки данных». Про это также можно почитать здесь и здесь.

Вкратце, файлы в NTFS — не просто данные, а наборы из одного и более потоков данных. То, что мы считаем содержимым файла, на самом деле является содержимым главного, неименованного потока. Также данные можно хранить и в других, именованных потоках. К этим потокам можно прямо обращаться, прибавляя :streamname к обычному пути файла, например, к потоку MyStream в файле qwerty.txt можно получить доступ по пути qwerty.txt:MyStream.

Так, хотя foo:bar и не является допустимым именем файла в Windows, файловые API Windows рады принять его для операций чтения/записи, т.к. это вполне нормальный путь к чему-то в файловой системе, в нашем случае — альтернативный поток bar файла foo.

Что делает Git

Теперь, когда мы знаем про альтернативные потоки данных, всё проясняется.

При клонировании Git перемещает контент по пути foo:bar. Это полностью разрешенный путь, поэтому система не выдаёт ошибок. В результате мы получаем файл foo без содержимого в основном потоке данных (поскольку его длина равна 0), но 6 байтов в альтернативном потоке bar:

			C:\src\filetest
> Get-Item .\foo -Stream * | ft Stream,Length

Stream Length
------ ------
:$DATA      0
bar         6

C:\src\filetest
> cat .\foo:bar
hello
		

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К показов