之间的差异。 git 中的(点)和 *(星号)通配符

Difference between . (dot) and * (asterisk) wildcards in git

我有一个本地存储库,并试图放弃自上次通过

提交以来的所有更改
git checkout HEAD -- *

命令。一切正常,即使更改是在某个子目录中。但是当我添加一些未跟踪的文件(满足 .gitignore 中的掩码)时,对存储库的根目录说 'Ignored.txt',上述命令失败并显示消息

error: pathspec 'Ignored.txt' did not match any file(s) known to git

相比之下,

git checkout HEAD -- .

按预期工作。所以我想知道:

What is the difference between . and * wildcards in git?

Git 没有看到星号。它通过 shell 扩展到当前目录中的所有文件和目录名称(不以点开头)。点不是通配符,它​​只是表示 "the current directory"。所以

git checkout HEAD -- .

检查当前目录及其所有子目录,即 git 知道的所有内容。带星号,git 看到

git checkout HEAD -- tracked-file1 tracked-file2 Ignored.txt

但它不知道如何检出被忽略的文件:因此出现错误。

这就是文件通配。 https://www.w3resource.com/linux-system-administration/file-globbing.php

. 表示 "current directory" 而 * 表示 "substitute it for all possible values"。通常情况下,bash(如果您使用的是 bash)将采用 * 并实际上将其替换为您所在位置的所有可能值(如果您仅提供一个 * 作为参数)和将(在替换所有值之后)将其交给 git。只有当没有匹配的值时(例如,你写 'blahblah*' 并且那里没有匹配该模式的文件)然后 bash 才会放弃替换值并将一个 '*' 移交给 git.

首先要知道的是,. 不是通配符。

.* 都可以是 path specs(这就是您在使用的命令中使用它们的意思)。要了解路径规范的解释方式,您可以查看 git 词汇表中的路径规范 (https://git-scm.com/docs/gitglossary)

但另一个复杂情况是,在您的 "failing" 示例中,git 是 而不是 收到 * 的路径规范,因为您的 shell 在将 * 传递给 git 之前扩展它。因此,要完全理解此行为,您还可以参考 shell 关于它如何预处理命令行的文档。

* 作为路径规范传递而不受您的 shell 的干扰,您可以逃避它。 (假设您正在使用 bash 或类似的东西,这可能看起来像

git checkout -- '*'

但同样,这取决于正在使用的 shell。)

主要 差异根本不在 Git 中。

因为不在Git里,有没有区别,如果有,有什么区别,取决于not-Git命令解释器你正在使用

Shell 对比 Git

在 Unix-ish 系统(包括 Linux)上,命令解释器(或“shell”)扩展了 * 为你。各种shell,例如bashzshfishtcshdash等等(其中大多数的名字以在 sh) 中,将 * 解释为“当前工作目录中的大部分文件”。这种扩展称为globbing.

这些shell以任何方式解释和扩展.。这意味着:

git xyzzy -- .

使用三个参数 xyzzy--. 调用 git。但是:

git xyzzy -- *

使用七个参数调用 gitxyzy--abcde,如果当前工作目录下有名为abcde的文件.

Git 很少使用工作目录

Git 主要对存储在存储库中的 提交 感兴趣。为了构建 new 提交,Git 使用其 index,也存储在存储库中。索引实际上以特殊的 Git-only 压缩格式保存文件——与提交中的内容基本相同。

文件的索引副本是可写的,而任何文件的提交副本都是只读的。 (从技术上讲,Git 只是将 objects 保存在它的主对象数据库中,索引和提交副本都只是对这些对象的引用。你不能覆盖任何对象,但你可以添加一个 new 对象并切换索引引用;您不能更改提交的引用。效果是提交中的文件被冻结,而索引中的文件被解冻/可写。)

这种内部 Git-only 文件格式对您(用户)和计算机上的大多数程序都没有用处。所以这些文件 需要扩展。它们会扩展到您的 work-tree 中,该工作树位于可能具有子目录(子文件夹)的目录(或文件夹)中。您可以将 当前工作目录 设置为这些工作树目录中的任何一个。然后 shell 将 * 扩展为在工作树中当前目录中找到的文件的名称。

Git 主要使用您当前的工作目录来查找存储库数据库,其中 Git 的真实文件存在。工作树副本仅供您 fiddle 随意使用。

一些Git命令确实使用工作树

当然,git checkoutgit add 都以不同的方式使用工作树。但是请注意,由于工作树是供 您的 使用的,因此您可以将存储库本身 不存在 的文件放入其中。

文件可以在您的工作树中,但不能在您的索引中。 (实际上,索引存在于工作树和适当的存储库“之间”,并为 Git 提供了一个位置来存储将进入 next 提交的文件.) 处于这种状态的文件——在你的工作树中,但不在你的索引中——被称为 untracked.

Git 本质上不知道未跟踪的文件。但是它们在您的工作树中(根据定义),所以如果这也是您当前的工作目录,并且您使用 * 并让 shell 将 * 扩展为这些文件名,您将向 Git 传递一个它当前不知道的文件名。

如果您使用的命令是git add,您将告诉Git:将此文件从工作树复制到索引中。那将在索引中创建文件,以便它现在将在您进行的下一次提交中。 Git 没问题!

但是如果你使用的命令是git checkout,你会告诉Git:将这个文件从索引复制到工作树中。 Git 不会在索引中找到文件,所以它会报错。 (它不会触及工作树中的文件。)

请注意,未跟踪的文件可以但不一定也会忽略(这不是一个很好的术语,但它是Git 使用)。您通过在名为 .gitignore 的文件中列出文件名称或名称模式来告诉 Git 不要抱怨未跟踪的文件。在这种情况下,git add 将警告您忽略未跟踪和忽略的文件:Git 不会将该文件复制到索引中,即使您使用 git add *.

Git 知道.

如果您使用 git add .git checkout -- .,则 shell 根本不会扩展。 Git 看到 . 并且知道这意味着“当前工作目录”,所以如果它是合适的——例如,对于 git add——Git 将读取当前工作目录。它可以将在那里找到的文件与索引中已有的文件进行比较,并知道如何为 git add 更新它们,包括 not 添加文件是 (1)未跟踪和 (2) 在 .gitignore 中列出。 (根据定义,已经在索引中的文件不会被忽略,因此这些文件也会从工作树更新到索引中。)

使用 git checkout -- .,Git 直接在索引中查找,根本看不到未跟踪的文件。

特殊情况:“点文件”,根本没有文件,CMD.EXE

以上内容相当简单,但有几个特殊情况使它复杂化:

  • 大多数shell在扩展时不匹配点文件(例如.profile.gitignore*。因此,如果您在当前目录中有文件 .gitattributes.gitignore,并使用 git checkout -- *,您可能不会将这些文件的索引版本复制到工作树中。

  • 如果当前目录中根本没有文件,或者 none 匹配像 *.asdf 这样的 glob 模式,一些 shell 会抱怨并中止命令,但其他人只是将模式 -*.asdf 甚至 * - 传递给你正在 运行ning.

    的程序
  • DOS 风格 CMD.EXE 不展开 *.

对于后两种情况,Git 本身 确实 看到了 *。现在 Git 有机会进行 glob 扩展,而在 Git 的情况下,Git 匹配 .-文件,如 .gitattributes.gitignore。因此,如果当前工作目录 没有非点文件 ,但确实有 .gitattributes.gitignore,而您 运行:

git checkout -- *

然后在这种特殊情况下,Git 会将 .gitattributes.gitignore 从索引复制到工作树。