Каковы последствия git config core.filemode false?

Для контекста, я испытываю это и пытаюсь решить эту проблему: Git в коде Visual Studio говорит, что файл изменен, даже если нет никаких изменений. Я использую cygwin на машине с Windows 10, но все мои коллеги используют Mac.

Ответ, получивший наибольшее количество голосов, говорит: git config core.filemode false, но я не могу понять, к чему это приведет. Это безопасно? Означает ли это, что если я создам сценарий оболочки, нажатие на него приведет к потере исполняемого бита? Означает ли это, что когда я вытаскиваю новый исполняемый файл, он теряет исполняемый бит? Какие есть подводные камни, если таковые имеются?

Я проверил документацию , но она также не отвечает на этот вопрос, а просто объясняет, когда вам нужно ее изменить.

3 ответа

TL;DR

Установка в Git игнорирует исполняемый бит результатов для файлов в вашем рабочем дереве. Вместо этого сохраняется режим любой существующей записи индекса (промежуточной области), если вы не используете git update-index --chmod. Новые записи файлового индекса получают режим. Это имеет смысл прежде всего, когда lstat() эмуляция в вашей собственной системе не поддерживает режимы правильно.

Длинный

Как правило, неправильно менять какой-либо из core.* настройки, в том числе core.fileMode (или - в документации нет единого мнения о том, следует ли использовать верхний регистр M, но на самом деле это все равно неважно). Есть некоторые особые случаи, когда вы можете установить его вручную, и здесь ваш вопрос правильный: что именно это делает?

Чтобы ответить на этот вопрос, мы должны начать с того, какие «режимы файлов» есть в первую очередь и как Git их определяет. Режим файла , в Git, действительно «+ х» или «-x» на совершало или чтобы быть приверженным большой двоичный объект , то есть обычный файл. В Git файлы - или, скорее, их содержимое - хранятся в коммитах как эти «объекты большого двоичного объекта»: сжатые, дедуплицированные и доступные только для чтения, найденные по хеш-идентификатору. 1 Но это просто данные файла , а не его состояние +x или -x, так откуда это взялось?

Что ж, если мы запустим и посмотрим на некоторые файлы, которые являются и не являются исполняемыми, мы обнаружим, что те, которые не являются исполняемыми, отображаются как:

      100644 <hash> 0       <name>

а те, которые являются исполняемыми, показаны как:

      100755 <hash> 0       <name>

Это или есть. Он хранится в объекте дерева Git , который Git строит во время запуска. git commit(хотя мы можем построить и раньше, используя). Объект дерева хранит как имя файла, так и этот режим, как это делает index / staging-area.2 (Индекс или промежуточная область - это то, что git ls-files --stage отображает.)

Итак, режимы = и =. Это оставляет нам еще одну загадку: почему это такие странные числа? Здесь и возникает вопрос, как Git определяет этот вопрос.

Поскольку Git изначально был написан для Linux и других Unix-подобных систем, Git сильно зависит от системного вызова . В некоторых других системах, отличных от Unix, это не настоящий системный вызов , но большинство, по крайней мере, подделывают его в какой-то библиотеке совместимости. (См., Например, что такое альтернатива lstat() в Windows?) statСемейство вызовов заполните в C, и эта структура содержит поле,. Поле состоит из различных составляемых битов:

  • Разрешения: это три младшие восьмеричные цифры. Файл, содержащий эти биты. Файл, который rwxr-xr-x есть в этих битах.

  • Три бита, которые не относятся к Git: они занимают следующую более высокую восьмеричную цифру. Поскольку они не применяются к Git, мы всегда получаем здесь ноль (если ОС предоставляет ненулевое значение, Git просто маскирует его). То есть посмотрим 0644 или 0755, например, как только мы включим три нижние восьмеричные цифры.

  • биты "формата" () в первых нескольких восьмеричных цифрах (например, или в 10xxxx или 04xxxx): они определяют, является ли объект файлом , каталогом , символической ссылкой и различными другими неприменимыми случаями. В каталоге есть биты 04 в этом поле, а в обычном файле есть биты 10в этом поле. Таким образом, после маскировки этими битами каталог оказывается mode 040xxx, для некоторых битов разрешений. Файл оказывается в режиме 100xxx, для некоторых битов разрешений.

Когда мы объединяем их, мы видим два из тех режимов, которые показывает Git: для исполняемого файла и для неисполняемого обычного файла. Конечно, каталог будет 040755 или 040700или что-то в этом роде, но Git не беспокоится о битах чтения / записи / выполнения в каталогах , поэтому он просто маскирует их: здесь мы видим третий режим, который показывает Git, 040000для объектов дерева, связанных с другим объектом дерева. 4 Это также источник symlink режим входа 120000: the S_IFMT биты здесь 12в Linux и Unix. В commitили тип записи gitlink , 160000, не соответствует никакому режиму Linux / Unix, но является побитовым результатом операции ИЛИ вместе S_IFDIR а также S_IFLNK биты режима ( 120000|040000).

Вот откуда берутся все записи режима в индексе: они прямо из поля struct stat, заполненный пользователем, со следующими изменениями:

  • Для древовидного объекта разрешения не имеют значения и обнуляются. (Объекты дерева не появляются в индексе в первую очередь; они создаются по запросу, когда имя файла требует его.) То же самое справедливо для символических ссылок - где в Unix-подобной системе биты разрешений обычно игнорируются - и для gitlinks (которые в любом случае являются внутренними для Git).

  • Для файла пользователь, группа и другие биты чтения и записи притворяются rw-r--r--всегда, независимо от фактического режима базового файла. Присутствие бита приводит к тому, что все три бита устанавливаются в индексном режиме.5

Это учитывает исторические ошибки (см. Сноску 5) и, следовательно, несколько беспорядочно. Было бы намного проще, если бы формат хранения просто содержал тип файла, а для файлов или -x, например, но он также оставляет место для будущего расширения (например, весь набор из 3 бит setuid + setgid + липкий набор в настоящее время всегда равен нулю, поэтому ненулевые значения могут иметь смысл).

Все это имеет смысл в Unix-подобной среде, где биты режима сохраняются в обычных файлах на диске. Но в других системах биты режима буквально подделаны. Windows является здесь каноническим примером. Не существует «исполняемого бита», поэтому файл в Windows должен либо отображать все файлы как исполняемые, либо ни один файл как исполняемый, если мы хотим получить результат с произвольным битом.

Следовательно, когда вы бежите git initчтобы создать новый репозиторий , Git исследует базовое поведение системы. Git создает файл с вызовом ОС «создать новый файл» ( open(name, O_CREAT|other_open_flags, mode)) в режиме 0644. Затем он пытается использовать ОС chmodвызовите, чтобы изменить режим на 0755, а затем использует вызов ОС, чтобы увидеть, «прилипнет» ли изменение.6 Если это так, ОС должна учитывать биты, поэтому Git установит значение true. В противном случае ОС должна игнорировать биты, поэтому Git установит значение false.

Позже, если установлено значение false , Git будет вызывать как обычно, чтобы получить данные статистики для каждого файла, но полностью проигнорирует три бита в результате. Он будет читать существующую запись индекса для этого файла, чтобы получить биты для установки в любой новой обновленной записи индекса для этого файла. Единственным исключением из этого правила является git update-index операция, где пользователь может указать весь режим или использовать флаг:

      git update-index --chmod=+x path/to/file.ext

Это захватывает существующую запись индекса, проверяет, есть ли она для файла ( mode 100xxx), и если да, заменяет деталь на 755: теперь файл отмечен +x. Сходным образом, --chmod=-x заменяет xxx часть с 644 (опять же только для обычных файлов; нельзя --chmod символическая ссылка или gitlink).

Если core.filemodeэто верно , однако, любой обычный git add в файле будет читать и подчиняться рабочему дереву xбиты. Если установлено на 100700, например, запись в указателе станет 100755. Если lstat имеет st_mode установлен в 100444, запись индекса становится 100644.

То есть в C-подобном коде, который не совсем соответствует внутреннему устройству Git, новый режим для любого обычного файла:

      ce = lookup_existing_cache_entry(path);
if (core_filemode) {
    // Note: the link in banyudu's answer goes to code
    // that checks `& 0100`, not `& 0111`.  Perhaps Git
    // only inspects the user's bit.
    new_mode = st.st_mode & 0111 ? 100755 : 100644;
} else {
    new_mode = ce != NULL && ce->ce_mode == 100755 ? 100755 : 100644;
}

После добавления файла в поле записи (индекса) кэша устанавливается значение new_mode.


1 Хэш-идентификатор объекта blob определяется строго содержимым: это контрольная сумма данных с префиксом слова blob, пробел ASCII (0x20), размер данных в байтах, выраженный в десятичном формате, и байт ASCII NUL (0x00). В настоящее время используется функция контрольной суммы SHA-1, хотя в предстоящих изменениях Git будет использоваться SHA-256. Фактически, это хеширование - это то, как работает дедупликация: при той же последовательности байтов Git производит тот же идентификатор хэша. Итак, если буквальный текст плюс CTRL-Jбайт новой строки хранится в Git как объект blob с использованием SHA-1, мы имеем:

      $ printf 'blob 12\0hello world\n' | shasum
3b18e512dba79e4c8300dd08aeb37f8e728b8dad  -

Итак, мы видим, что каждый файл, содержащий только одну строку hello worldимеет идентификатор хэша blob, везде в каждом репозитории Git . Попытайся:

      $ echo 'hello world' > hello.txt
$ git add hello.txt
$ git ls-files --stage hello.txt
100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0       hello.txt

Обратите внимание на идентификатор хэша большого двоичного объекта, 3b18e512dba79e4c8300dd08aeb37f8e728b8dad, именно то, что мы рассчитали.

2 Между записями в дереве и записями указателя есть несколько важных различий . В частности, в индексной записи полное имя файла записано с косой чертой, так что, например, file - это просто: path/to/file.extв индексе. 3 Но как набор древовидных объектов Git разбивает его на псевдокаталоги, так что у нас есть, и. Часть хранится в дереве верхнего уровня фиксации; часть сохраняется как поддерево дерева; и часть сохраняется как запись большого двоичного объекта в дереве. В дереве верхнего уровня есть запись в поддереве с именем, содержащая хэш-идентификатор поддерева, содержащего имя, и хеш-идентификатор поддерева, содержащего имя. file.ext. (Уфф!) Это легче увидеть, работая рекурсивно снизу вверх:

  • Строим дерево на нижнем уровне холдинга 100644 file.ext и любые другие имена под toимя. Мы сохраняем этот объект дерева в базе данных объектов, находя его внутренний хэш-идентификатор.

  • Теперь строим еще одно дерево холдинга 40000 to и хэш-идентификатор дерева, которое мы только что построили, вместе с любыми другими записями, необходимыми для перехода под path.

  • Наконец, мы строим дерево холдинга 40000 path и хэш-идентификатор дерева, которое мы построили на среднем этапе, плюс любые другие записи, необходимые для перехода на верхний уровень.

Этот набор деревьев и есть то, что строится с использованием того, что сейчас есть в индексе Git. В git write-tree затем программа выдает хэш-идентификатор дерева верхнего уровня, который входит в объект фиксации, который git commit-tree строит.

3 Текущий формат индекса использует уловки сжатия, чтобы избежать повторения начальных строк. См. Подробную информацию в технической документации .

4 Начальный ноль удаляется в режимах, хранящихся в tree объект, но повторно вставлен для отображения в git ls-tree -r вывод, например.

5 В очень ранних версиях Git больше битов режима сохранялось в поле Git. Это оказалось ошибкой. Сегодня для обратной совместимости Git позволяет существовать mode из 100664 ( rw-rw-r--), но никогда не будет создавать новых , чтобы можно было читать существующие репозитории Git, относящиеся к этой ранней версии Git.

6 Если я правильно помню, фактический тест состоит из: стат файла, перевернуть все биты X ( new_mode = old_mode ^ 0111), chmod, stat еще раз и посмотрите, изменился ли результат. В таком случае соблюдается хотя бы один X-бит. В противном случае X-бит не учитывается.

Кажется, что git заботится только об исполняемом бите, поэтому файл в git может быть только 644 или 755. исходный код

Я только что сделал тест:

      $ mkdir test && cd test && git init
$ touch before && chmod a+x before && git add before && git commit -m 'before' && git ls-tree HEAD

> [master (root-commit) 1cb9c41] before
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100755 before
100755 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    before

$ git config core.fileMode false

$ touch after && chmod a+x after && git add after && git commit -m 'after' && git ls-tree HEAD

> [master b4d7a48] after
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 after
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    after
100755 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    before

Как видите, до core.fileMode change, git сохраняет исполняемый бит файла (0755), в то время как после изменения вновь созданные файлы теряют исполняемый бит (0644), а старые файлы сохраняют старый исполняемый бит.

Итак, вкратце:

С участием git config core.filemode false, git игнорирует изменение исполняемого бита в локальном репозитории. Поскольку git заботится только об исполняемом бите, это приведет не к файлу 0000, а к файлу 0644.

Означает ли это, что если я создам сценарий оболочки, нажатие на него приведет к потере исполняемого бита?

да

Означает ли это, что когда я вытаскиваю новый исполняемый файл, он теряет исполняемый бит?

Это зависит от вашей файловой системы. Некоторые файловые системы, такие как NTFS, изменяют разрешение каждого файла на 0777, в то время как другие могут потерять исполняемый бит.

Краткий ответ: Windows и Linux имеют разные модели разрешений. Git внутренне использует модель Linux.

В Windows git config core.filemode false сообщает Git не учитывать права доступа к файлу при определении того, был ли файл изменен.

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