Каковы последствия 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 не учитывать права доступа к файлу при определении того, был ли файл изменен.