对于上下文,我正在经历这个问题并试图解决它:Git in Visual studio code 说文件已修改,即使没有更改我在 Windows 10 计算机上使用 cygwin,但我所有的同事都使用 Mac。
得票最高的答案是git config core.filemode false,但我无法弄清楚这样做的后果。安全吗?这是否意味着如果我创建一个 shell 脚本,推送它会丢失可执行位?这是否意味着当我提取新的可执行文件时,它将丢失可执行位?如果有的话,有哪些陷阱?
我已经检查了文档,但它也没有回答这个问题,它只是解释了您何时需要更改它。
tor*_*rek 19
设置core.filemode为false使 Git 忽略st_mode lstat()工作树中文件结果的可执行位。相反,任何现有索引(暂存区域)条目的模式都会被保留,除非您使用git update-index --chmod. 新文件索引条目获取模式100644。这主要是当lstat()当您自己的系统上的仿真不正确支持模式
更改任何设置通常都是错误的core.*,包括core.fileMode(或core.filemode\xe2\x80\x94,文档对于是否给它一个大写并不一致M,但实际上无论如何都没关系)。在某些特殊情况下,您可以手动设置它,这里您的问题是正确的:这到底是做什么的?
要回答这个问题,我们首先必须从什么是“文件模式”以及 Git 如何确定它们开始。在 Git 中,文件模式实际上是已提交或待提交的blob 对象(即普通文件)上的“+x”或“-x”。在 Git 中,文件\xe2\x80\x94,或者更确切地说,文件内容\xe2\x80\x94 作为这些“blob 对象”事物存储在提交中:压缩、去重和全只读,通过哈希 ID 找到。1 但这只是文件的数据,而不是它的 +x 或 -x 状态,那么它从何而来?
\n好吧,如果我们运行git ls-files --stage并查看一些可执行和不可执行的文件,我们会发现不可执行的文件显示为:
100644 <hash> 0 <name>\nRun Code Online (Sandbox Code Playgroud)\n而可执行的则显示为:
\n100755 <hash> 0 <name>\nRun Code Online (Sandbox Code Playgroud)\n那100644或者100755是mode. 它存储在 Git树对象中,Git 在我们运行时构建该对象git commit(尽管我们可以更早地使用 构建一些git write-tree)。树对象存储文件名和此模式,就像索引/暂存区域一样。2 (显示的是索引或暂存区域git ls-files --stage。)
因此,众数为100644=-x和100755= +x。这给我们留下了另一个谜团:为什么它们是这些奇怪的数字?这就是Git 如何确定这些问题的由来。
由于 Git 最初是为 Linux 和其他类 Unix 系统编写的,因此 Git 严重依赖于系统lstat调用。其他一些非 Unix 系统没有将此作为实际的系统调用,但大多数至少在某种兼容性库中伪造它。(参见,例如,Windows 中的 lstat() 替代方案是什么?)Unixstat系列调用在 C 中填充 a struct stat,并且该结构包含一个字段st_mode。该st_mode字段由各种可组合位组成:
权限:这些是最低的三位八进制数字。rw-r--r--包含这些位的文件644。rwxr-xr-x包含这些位的文件755。
不适用于 Git 的三位:它们占据下一个更高的八进制数字。由于它们不适用于 Git,因此我们总是得到零(如果操作系统提供非零值,Git 会将其屏蔽掉)。也就是说,例如,一旦我们包含了底部的三个八进制数字,我们就会看到0644or 。0755
“格式”位(S_IFMT),在前几个八进制数字中(例如,10or04中的10xxxxor 04xxxx):这些确定实体是否是文件、目录、符号链接以及各种其他不适用的情况。目录04在此字段中具有位,常规文件10在此字段中具有位。因此,在使用这些位进行屏蔽之后,目录最终会成为mode 040xxx,对于某些权限位xxx。100xxx对于某些权限位,文件最终会成为模式xxx。
当我们将它们组合起来时,我们会看到 Git 显示的两种模式:100755针对可执行文件,以及100644针对不可执行的常规文件。当然,目录st_mode将是040755or040700或类似的,但 Git 不关心目录上的读/写/执行位,因此它只是将它们屏蔽掉:在这里,我们看到 Git 显示的第三种模式,040000即链接到另一个树对象的树对象。4symlink这也是的入口模式的 来源120000:S_IFMT这里的位是12在 Linux 和 Unix 上的。commit或gitlink条目类型不对应于任何 Linux/Unix 模式,而是和模式位 ( )160000的按位结果。S_IFDIRS_IFLNK120000|040000
因此,这就是索引中所有模式条目的来源:它们直接来自st_modea 的字段struct stat,由 填充lstat,并进行以下更改:
对于树对象,权限是无关的并且被清零。(树对象首先不会出现在索引中;它们是根据需要创建的git write-tree文件名需要树对象时,它们是根据需要创建的。)对于符号链接\xe2\x80\x94 也是如此,在类 Unix 系统上,权限位是通常会忽略 \xe2\x80\x94 和 gitlinks(无论如何,它们都是 Git 内部的)。
对于文件来说,用户、组和其他读写位都被假装为rw-r--r--始终,而不管底层文件的实际模式如何。一个位的存在x会导致所有三个x位都被设置为索引模式。5
这包含了历史错误(见脚注 5),因此有些混乱。如果存储格式只保存文件类型,并且对于文件,+x或者-x,但它也为将来的扩展留下了空间(例如,整个 setuid+setgid+sticky 的 3 位集合当前始终是零,因此非零值可以获得意义)。
所有这些在类 Unix 环境中都是有意义的,其中模式位保存在普通的磁盘文件中。 但在其他系统中,lstat模式位实际上是伪造的。Windows 是这里的典型示例。没有“可执行位”,因此,lstat如果我们要组成任意位结果,则在 Windows 上对文件进行操作必须将所有文件显示为可执行文件,或者不将任何文件显示为可执行文件x。
因此,当您运行git init创建新存储库时,Git 会探测系统的底层行为。Git 使用操作系统“创建新文件”调用 ( open(name, O_CREAT|other_open_flags, mode)) 创建一个模式为 0644 的文件。然后,它尝试使用操作系统chmod调用将模式更改为 0755,然后使用操作系统lstat调用来查看更改是否“持续”。6 如果是这样,操作系统必须尊重x位,因此 Git 将设置core.filemode为true. 如果不是,操作系统必须忽略x位,因此 Git 将设置core.filemode为false.
稍后,如果core.filemode为false,Git 将lstat照常调用以获取每个文件的统计数据,但将完全忽略x结果中的三位st_mode。它将读取该文件的现有索引条目,以获取x要在该文件的任何新更新的索引条目中设置的位。此规则的一个例外是git update-index操作,其中用户可以指定整个模式,或使用--chmod:
git update-index --chmod=+x path/to/file.ext\nRun Code Online (Sandbox Code Playgroud)\n这会抓取现有的索引条目,检查它是否适用于文件 ( mode 100xxx),如果是,则用 替换该xxx部分755:该文件现在被标记为+x。同样,--chmod=-x将xxx部分替换为644(同样仅适用于常规文件;您不能--chmod符号链接或 gitlink)。
然而,如果core.filemode为true,git add文件上的任何普通文件都将读取并遵循工作树的x位。例如,如果lstat设置st_mode为,则索引条目将变为。如果设置为,则索引条目变为。100700100755lstatst_mode100444100644
也就是说,在与 Git 内部结构不太匹配的类 C 代码中,对于任何普通文件来说,新模式是:
\nce = 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}\nRun Code Online (Sandbox Code Playgroud)\n添加文件后,缓存条目(索引)mode字段将设置为new_mode。
1 Blob 对象的哈希 ID 严格由内容决定:它是以单词 为前缀的数据的校验和blob、一个 ASCII 空格 (0x20)、以十进制表示的字节数据大小和一个 ASCII NUL (0x00) ) 字节。校验和函数目前是 SHA-1,尽管即将到来的 Git 更改将开始使用 SHA-256。这种哈希实际上就是重复数据删除的工作原理:给定相同的字节序列,Git 会生成相同的哈希 ID。因此,如果文字文本hello world加上换行符CTRL-J字节作为 blob 对象存储在 Git 中,使用 SHA-1,我们有:
$ printf \'blob 12\\0hello world\\n\' | shasum\n3b18e512dba79e4c8300dd08aeb37f8e728b8dad -\nRun Code Online (Sandbox Code Playgroud)\n因此我们看到,在每个 Git 存储库中,每个仅包含一行的文件hello world都有 blob 哈希 ID 。尝试一下:3b18e512dba79e4c8300dd08aeb37f8e728b8dad
$ echo \'hello world\' > hello.txt\n$ git add hello.txt\n$ git ls-files --stage hello.txt\n100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0 hello.txt\nRun Code Online (Sandbox Code Playgroud)\n请注意,blob 哈希 ID3b18e512dba79e4c8300dd08aeb37f8e728b8dad正是我们计算出的值。
2树条目和索引条目之间存在一些重要区别。特别是,索引条目具有用正斜杠拼写出的文件全名,因此,例如,文件path/to/file.ext就是:path/to/file.ext在索引中。3 但作为一组树对象,Git 将其分解为伪目录,因此我们有path、to和file.ext。该path部分存储在提交的顶级树中;该to部分存储为path树的子树;并且该file.ext部分作为 blob 条目存储在to树中。顶级树有一个名为 name 的子树条目,path它保存保存 name 的子树的哈希 IDto以及保存 name 的子树的哈希 ID file.ext。(哇!)通过自下而上递归地工作,可以更容易地看到这一点:
我们在底层建立一棵树,持有100644 file.ext该名称下的任何其他名称to。我们将此树对象存储在对象数据库中,查找其内部哈希 ID。
现在我们构建另一棵树40000 to,保存我们刚刚构建的树的哈希 ID,以及 下所需的任何其他条目path。
最后,我们构建一棵树40000 path,保存我们在中间步骤中构建的树的哈希 ID,以及进入顶层所需的任何其他条目。
这组树是git write-tree使用此时 Git 索引中的任何内容构建的。然后,程序git write-tree发出顶级树的哈希 ID,该 ID 会进入git commit-tree构建的提交对象。
3当前索引格式使用压缩技巧来避免重复前导字符串。详细信息请参阅技术文档。
\n4前导零在对象中存储的模式中被去除,但出于例如输出tree显示的目的而重新插入。git ls-tree -r
5在 Git 的早期版本中,Gitmode字段中保留了更多模式位。事实证明这是一个错误。如今,为了向后兼容,Git允许存在( ),但绝不会创建任何新mode的,以便可以读取可追溯到此早期版本的 Git 的现有 Git 存储库。100664rw-rw-r--
6如果我没记错的话,实际测试包括:stat文件,翻转所有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
Run Code Online (Sandbox Code Playgroud)
可以看到,更改之前core.fileMode,git 保留了文件的可执行位(0755),而更改之后,新创建的文件丢失了可执行位(0644),而旧文件保留了旧的可执行位。
所以,总而言之:
使用 时git config core.filemode false,git 会忽略本地存储库上的可执行位更改。由于 git 只关心可执行位,因此这不会导致 0000 文件,而是 0644 文件。
这是否意味着如果我创建一个 shell 脚本,推送它会丢失可执行位?
是的
这是否意味着当我提取新的可执行文件时,它将丢失可执行位?
这取决于您的文件系统。某些文件系统(例如 NTFS)将每个文件的权限更改为 0777,而其他文件系统可能会丢失可执行位。
| 归档时间: |
|
| 查看次数: |
6775 次 |
| 最近记录: |