git config core.filemode false 的后果是什么?

Dan*_*lan 14 git cygwin

对于上下文,我正在经历这个问题并试图解决它:Git in Visual studio code 说文件已修改,即使没有更改我在 Windows 10 计算机上使用 cygwin,但我所有的同事都使用 Mac。

得票最高的答案是git config core.filemode false,但我无法弄清楚这样做的后果。安全吗?这是否意味着如果我创建一个 shell 脚本,推送它会丢失可执行位?这是否意味着当我提取新的可执行文件时,它将丢失可执行位?如果有的话,有哪些陷阱?

我已经检查了文档,但它也没有回答这个问题,它只是解释了您何时需要更改它。

tor*_*rek 19

长话短说

\n

设置core.filemodefalse使 Git 忽略st_mode lstat()工作树中文件结果的可执行位。相反,任何现有索引(暂存区域)条目的模式都会被保留,除非您使用git update-index --chmod. 新文件索引条目获取模式100644。这主要是当lstat()当您自己的系统上的仿真不正确支持模式

\n

长的

\n

更改任何设置通常都是错误的core.*,包括core.fileMode(或core.filemode\xe2\x80\x94,文档对于是否给它一个大写并不一致M,但实际上无论如何都没关系)。在某些特殊情况下,您可以手动设置它,这里您的问题是正确的:这到底是做什么的?

\n

要回答这个问题,我们首先必须从什么是“文件模式”以及 Git 如何确定它们开始。在 Git 中,文件模式实际上是已提交或待提交的blob 对象(即普通文件)上的“+x”或“-x”。在 Git 中,文件\xe2\x80\x94,或者更确切地说,文件内容\xe2\x80\x94 作为这些“blob 对象”事物存储在提交中:压缩、去重和全只读,通过哈希 ID 找到。1 但这只是文件的数据,而不是它的 +x 或 -x 状态,那么它从何而来?

\n

好吧,如果我们运行git ls-files --stage并查看一些可执行和不可执行的文件,我们会发现不可执行的文件显示为:

\n
100644 <hash> 0       <name>\n
Run Code Online (Sandbox Code Playgroud)\n

而可执行的则显示为:

\n
100755 <hash> 0       <name>\n
Run Code Online (Sandbox Code Playgroud)\n

100644或者100755mode. 它存储在 Git树对象中,Git 在我们运行时构建该对象git commit(尽管我们可以更早地使用 构建一些git write-tree)。树对象存储文件名和此模式,就像索引/暂存区域一样。2 (显示的是索引或暂存区域git ls-files --stage。)

\n

因此,众数为100644=-x100755= +x。这给我们留下了另一个谜团:为什么它们是这些奇怪的数字?这就是Git 如何确定这些问题的由来。

\n

由于 Git 最初是为 Linux 和其他类 Unix 系统编写的,因此 Git 严重依赖于系统lstat调用。其他一些非 Unix 系统没有将此作为实际的系统调用,但大多数至少在某种兼容性库中伪造它。(参见,例如,Windows 中的 lstat() 替代方案是什么?)Unixstat系列调用在 C 中填充 a struct stat,并且该结构包含一个字段st_mode。该st_mode字段由各种可组合位组成:

\n
    \n
  • 权限:这些是最低的三位八进制数字。rw-r--r--包含这些位的文件644rwxr-xr-x包含这些位的文件755

    \n
  • \n
  • 不适用于 Git 的三位:它们占据下一个更高的八进制数字。由于它们不适用于 Git,因此我们总是得到零(如果操作系统提供非零值,Git 会将其屏蔽掉)。也就是说,例如,一旦我们包含了底部的三个八进制数字,我们就会看到0644or 。0755

    \n
  • \n
  • “格式”位(S_IFMT),在前几个八进制数字中(例如,10or04中的10xxxxor 04xxxx):这些确定实体是否是文件目录符号链接以及各种其他不适用的情况。目录04在此字段中具有位,常规文件10在此字段中具有位。因此,在使用这些位进行屏蔽之后,目录最终会成为mode 040xxx,对于某些权限位xxx100xxx对于某些权限位,文件最终会成为模式xxx

    \n
  • \n
\n

当我们将它们组合起来时,我们会看到 Git 显示的两种模式:100755针对可执行文件,以及100644针对不可执行的常规文件。当然,目录st_mode将是040755or040700或类似的,但 Git 不关心目录上的读/写/执行位,因此它只是将它们屏蔽掉:在这里,我们看到 Git 显示的第三种模式,040000即链接到另一个树对象的树对象。4symlink这也是的入口模式的 来源120000S_IFMT这里的位是12在 Linux 和 Unix 上的。commitgitlink条目类型不对应于任何 Linux/Unix 模式,而是和模式位 ( )160000的按位结果。S_IFDIRS_IFLNK120000|040000

\n

因此,这就是索引中所有模式条目的来源:它们直接来自st_modea 的字段struct stat,由 填充lstat,并进行以下更改:

\n
    \n
  • 对于树对象,权限是无关的并且被清零。(树对象首先不会出现在索引中;它们是根据需要创建的git write-tree文件名需要树对象时,它们是根据需要创建的。)对于符号链接\xe2\x80\x94 也是如此,在类 Unix 系统上,权限位是通常会忽略 \xe2\x80\x94 和 gitlinks(无论如何,它们都是 Git 内部的)。

    \n
  • \n
  • 对于文件来说,用户、组和其他读写位都被假装为rw-r--r--始终,而不管底层文件的实际模式如何。一个位的存在x会导致所有三个x位都被设置为索引模式。5

    \n
  • \n
\n

这包含了历史错误(见脚注 5),因此有些混乱。如果存储格式只保存文件类型,并且对于文件,+x或者-x,但它也为将来的扩展留下了空间(例如,整个 setuid+setgid+sticky 的 3 位集合当前始终是零,因此非零值可以获得意义)。

\n

所有这些在类 Unix 环境中都是有意义的,其中模式位保存在普通的磁盘文件中。 但在其他系统中,lstat模式位实际上是伪造的。Windows 是这里的典型示例。没有“可执行位”,因此,lstat如果我们要组成任意位结果,则在 Windows 上对文件进行操作必须将所有文件显示为可执行文件,或者不将任何文件显示为可执行文件x

\n

因此,当您运行git init创建新存储库时,Git 会探测系统的底层行为。Git 使用操作系统“创建新文件”调用 ( open(name, O_CREAT|other_open_flags, mode)) 创建一个模式为 0644 的文件。然后,它尝试使用操作系统chmod调用将模式更改为 0755,然后使用操作系统lstat调用来查看更改是否“持续”。6 如果是这样,操作系统必须尊重x位,因此 Git 将设置core.filemodetrue. 如果不是,操作系统必须忽略x位,因此 Git 将设置core.filemodefalse.

\n

稍后,如果core.filemodefalse,Git 将lstat照常调用以获取每个文件的统计数据,但将完全忽略x结果中的三位st_mode。它将读取该文件的现有索引条目,以获取x要在该文件的任何新更新的索引条目中设置的位。此规则的一个例外是git update-index操作,其中用户可以指定整个模式,或使用--chmod

\n
git update-index --chmod=+x path/to/file.ext\n
Run Code Online (Sandbox Code Playgroud)\n

这会抓取现有的索引条目,检查它是否适用于文件 ( mode 100xxx),如果是,则用 替换该xxx部分755:该文件现在被标记为+x。同样,--chmod=-xxxx部分替换为644(同样仅适用于常规文件;您不能--chmod符号链接或 gitlink)。

\n

然而,如果core.filemodetruegit add文件上的任何普通文件都将读取并遵循工作树的x位。例如,如果lstat设置st_mode为,则索引条目将变为。如果设置为,则索引条目变为。100700100755lstatst_mode100444100644

\n

也就是说,在与 Git 内部结构不太匹配的类 C 代码中,对于任何普通文件来说,新模式是:

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

添加文件后,缓存条目(索引)mode字段将设置为new_mode

\n
\n

1 Blob 对象的哈希 ID 严格由内容决定:它是以单词 为前缀的数据的校验和blob、一个 ASCII 空格 (0x20)、以十进制表示的字节数据大小和一个 ASCII NUL (0x00) ) 字节。校验和函数目前是 SHA-1,尽管即将到来的 Git 更改将开始使用 SHA-256。这种哈希实际上就是重复数据删除的工作原理:给定相同的字节序列,Git 会生成相同的哈希 ID。因此,如果文字文本hello world加上换行符CTRL-J字节作为 blob 对象存储在 Git 中,使用 SHA-1,我们有:

\n
$ printf \'blob 12\\0hello world\\n\' | shasum\n3b18e512dba79e4c8300dd08aeb37f8e728b8dad  -\n
Run Code Online (Sandbox Code Playgroud)\n

因此我们看到,在每个 Git 存储库中,每个仅包含一行的文件hello world都有 blob 哈希 ID 。尝试一下:3b18e512dba79e4c8300dd08aeb37f8e728b8dad

\n
$ echo \'hello world\' > hello.txt\n$ git add hello.txt\n$ git ls-files --stage hello.txt\n100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0       hello.txt\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,blob 哈希 ID3b18e512dba79e4c8300dd08aeb37f8e728b8dad正是我们计算出的值。

\n

2条目和索引条目之间存在一些重要区别。特别是,索引条目具有用正斜杠拼写出的文件全名,因此,例如,文件path/to/file.ext就是:path/to/file.ext在索引中。3 但作为一组树对象,Git 将其分解为伪目录,因此我们有pathtofile.ext。该path部分存储在提交的顶级树中;该to部分存储为path树的子树;并且该file.ext部分作为 blob 条目存储在to树中。顶级树有一个名为 name 的子树条目,path它保存保存 name 的子树的哈希 IDto以及保存 name 的子树的哈希 ID file.ext。(哇!)通过自下而上递归地工作,可以更容易地看到这一点:

\n
    \n
  • 我们在底层建立一棵树,持有100644 file.ext该名称下的任何其他名称to。我们将此树对象存储在对象数据库中,查找其内部哈希 ID。

    \n
  • \n
  • 现在我们构建另一棵树40000 to,保存我们刚刚构建的树的哈希 ID,以及 下所需的任何其他条目path

    \n
  • \n
  • 最后,我们构建一棵树40000 path,保存我们在中间步骤中构建的树的哈希 ID,以及进入顶层所需的任何其他条目。

    \n
  • \n
\n

这组树是git write-tree使用此时 Git 索引中的任何内容构建的。然后,程序git write-tree发出顶级树的哈希 ID,该 ID 会进入git commit-tree构建的提交对象。

\n

3当前索引格式使用压缩技巧来避免重复前导字符串。详细信息请参阅技术文档。

\n

4前导零在对象中存储的模式中被去除,但出于例如输出tree显示的目的而重新插入。git ls-tree -r

\n

5在 Git 的早期版本中,Gitmode字段中保留了更多模式位。事实证明这是一个错误。如今,为了向后兼容,Git允许存在( ),但绝不会创建任何mode的,以便可以读取可追溯到此早期版本的 Git 的现有 Git 存储库。100664rw-rw-r--

\n

6如果我没记错的话,实际测试包括:stat文件,翻转所有X位(new_mode = old_mode ^ 0111),chmod,再次stat,看看结果是否改变。如果是,则至少遵守一位 X 位。如果不是,则不遵守任何 X 位。

\n


ban*_*udu 6

看来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
Run Code Online (Sandbox Code Playgroud)

可以看到,更改之前core.fileMode,git 保留了文件的可执行位(0755),而更改之后,新创建的文件丢失了可执行位(0644),而旧文件保留了旧的可执行位。

所以,总而言之:

使用 时git config core.filemode false,git 会忽略本地存储库上的可执行位更改。由于 git 只关心可执行位,因此这不会导致 0000 文件,而是 0644 文件。

这是否意味着如果我创建一个 shell 脚本,推送它会丢失可执行位?

是的

这是否意味着当我提取新的可执行文件时,它将丢失可执行位?

这取决于您的文件系统。某些文件系统(例如 NTFS)将每个文件的权限更改为 0777,而其他文件系统可能会丢失可执行位。