我有一个本地存储库,尝试放弃自上次提交以来的所有更改
git checkout HEAD -- *
命令。即使所做的更改位于某个子目录中,一切也都可以正常工作。但是,当我添加一些未跟踪的文件(使.gitignore
中的掩码令人满意)时,在存储库的根目录中说“ Ignored.txt”,以上命令将失败,并显示消息
error: pathspec 'Ignored.txt' did not match any file(s) known to git
相反,
git checkout HEAD -- .
按预期工作。所以我想知道:
git中
.
和*
通配符之间有什么区别?
答案 0 :(得分:4)
主要差异不是主要。
因为它不在Git中,所以是否有所不同,如果有,有什么差异,取决于您使用的not-Git命令解释器。
在Unix-ish系统(包括Linux)上,是命令解释器(或“ shell”)为您扩展了*
。各种外壳,例如bash
和zsh
和fish
以及tcsh
和dash
等(大多数名称以{{1}结尾) }),将sh
解释为“当前工作目录中的大多数文件”。这种扩展称为globbing。
这些shell 不以任何方式解释和扩展*
。这意味着:
.
使用三个参数git xyzzy -- .
,git
和xyzzy
调用--
。但是:
.
使用七个参数调用git xyzzy -- *
:git
,xyzy
,--
,a
,b
,{{1} },c
,如果当前工作目录中有名为d
,e
,a
,b
和c
的文件。 / p>
Git主要对存储在存储库中的 commits 感兴趣。为了构建 new 提交,Git使用它的 index (也存储在存储库中)。索引实际上以特殊的,仅Git的压缩格式保存文件,与提交中的内容基本相同。
文件的索引副本是可写的,而任何文件的提交副本都是只读的。 (从技术上讲,Git只是将 objects 保留在其主对象数据库中,索引和提交副本都只是对这些对象的引用。您不能覆盖任何对象,但是可以添加 new < / em>对象并切换索引引用;您不能更改提交的引用。其结果是提交中的文件被冻结,而索引中的文件被解冻/可写。)
这种仅Git的内部文件格式对您,用户或计算机上的大多数程序都没有用。因此,这些文件必须进行扩展。它们被扩展到您的工作树中,该文件位于可能具有子目录(子文件夹)的目录(或文件夹)中。您可以将当前工作目录设置为任何这些工作树目录。然后,Shell将d
扩展为工作树中此当前目录中找到的文件的名称。
Git主要使用您当前的工作目录查找Git实际文件所在的存储库数据库。工作树副本仅供您随意摆弄。
当然,e
和*
都以各种方式使用工作树。但是请注意,由于工作树是供您的使用的,因此您可以在存储库中将不存在的文件放入其中。
文件可以在您的工作树中,但不能在您的索引中。 (实际上,该索引位于工作树和适当的存储库之间,并为Git提供了一个存放将进入 next 提交的文件的位置。)此状态(在您的工作树中,但不在索引中)被称为未跟踪。
Git本质上不了解未跟踪的文件。但是它们在您的工作树中(根据定义),因此,如果这也是您当前的工作目录,并且您使用git checkout
并让Shell将git add
扩展为这些文件名,您将将当前不知道的文件名传递给Git。
如果您使用的命令是*
,您将告诉Git:将此文件从工作树复制到索引中。这将在索引中创建文件,以便现在将在您进行的下一次提交中。 Git很好!
但是,如果您使用的命令是*
,您将告诉Git:将此文件从索引复制到工作树中。 Git在索引中找不到文件,所以它会抱怨。 (它不会触摸工作树中的文件。)
请注意,未跟踪的文件可以(但不一定)也被忽略(这不是一个很好的术语,但这是Git使用的一个术语)。您通过在名为git add
的文件中列出未跟踪文件的名称或名称模式来告诉Git不要抱怨。在这种情况下,git checkout
会警告您忽略了一个未被跟踪和忽略的文件:即使您使用.gitignore
,Git也不会将该文件复制到索引中。
git add
如果使用git add *
或.
,则shell根本不会扩展。 Git看到了git add .
并知道这意味着“当前工作目录”,因此,如果合适,例如git checkout -- .
,Git将读取当前工作目录。它可以将找到的文件与索引中已经存在的文件进行比较,并知道如何为.
更新它们,包括不添加(1)未跟踪的文件和( 2)在git add
中列出。 (根据定义,已经在中的文件不会被忽略,因此它们也将从工作树更新到索引中。)
使用git add
,Git可以直接在索引中查找,根本看不到未跟踪的文件。
上面的内容非常简单,但是有几种特殊情况使它复杂化:
大多数shell 在扩展.gitignore
时都不匹配点文件(例如git checkout -- .
或.profile
)。因此,如果当前目录中有文件.gitignore
和*
,并且使用.gitattributes
,则可能不会将这些文件的索引版本复制到工作树中。
如果当前目录中根本没有文件,或者没有文件与.gitignore
之类的glob模式匹配,则某些shell会抱怨并中止命令,而其他shell只会传递该模式-{{1 }}甚至git checkout -- *
-运行的程序上。
DOS样式的CMD.EXE不会展开*.asdf
。
对于最后两种情况,Git本身确实参见*.asdf
。现在, Git 有机会进行全局扩展,在Git的情况下,Git 确实匹配*
文件,例如*
和{{1} }。因此,如果当前工作目录没有非非点文件,但确实有*
和.
,则运行:
.gitattributes
然后在这种情况下,Git会将.gitignore
和.gitattributes
从索引复制到工作树。
答案 1 :(得分:3)
Git没有看到星号。它通过外壳扩展为当前目录中的所有文件名和目录名(不以点开头)。该点不是通配符,仅表示“当前目录”。所以
git checkout HEAD -- .
检出当前目录及其所有子目录,即git知道的一切。带有星号的git会看到
git checkout HEAD -- tracked-file1 tracked-file2 Ignored.txt
但是它不知道如何检出被忽略的文件:因此出现错误。
答案 2 :(得分:2)
首先要了解的是,.
不是通配符。
.
和*
都可以是路径规范(这就是在您使用的命令中使用它们的意思。)要了解路径规范的解释方式,您可以在git词汇表(https://git-scm.com/docs/gitglossary)中的路径规范下查看
但是另一个麻烦是,在您的“失败”示例中,git 不是接收到*
的路径规范,因为您的外壳在将*
传递给它之前将其展开git。因此,要完全理解此行为,您还可以参考外壳程序的文档,了解它如何预处理命令行。
通过*
作为路径规范,而不会受到外壳程序的干扰,则可以对其进行转义。 (假设您使用的是bash或类似工具,则它看起来可能是
git checkout -- '*'
但同样,这取决于所使用的外壳。)
答案 3 :(得分:1)
那是文件混乱。 https://www.w3resource.com/linux-system-administration/file-globbing.php
.
表示“当前目录”,而*
表示“将其替换为所有可能的值”。通常情况是,bash(如果您使用的是bash)将使用*并实际上将其替换为您所在的所有可能值(如果您仅提供一个*作为参数),并且将(替换所有值之后)交给git。只有在没有匹配值的情况下(例如,您写“ blahblah *”并且那里没有与模式匹配的文件),bash才会放弃替换该值,并将“ *”移交给git。