在以前的项目中,我经常运行git checkout -- *
来丢弃工作目录中的所有更改。
-
在我当前的项目中,我得到以下内容:
$ git checkout -- * error: pathspec 'node_modules' did not match any file(s) known to git.
-
然后我做一个
git status
:$ git status On branch <feature_branch> Your branch is ahead of 'origin/<feature_branch>' by 2 commits. (use "git push" to publish your local commits) Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: <relative_file_path_in_current_app_dir> no changes added to commit (use "git add" and/or "git commit -a")
-
我读了另一篇StackOverflow帖子,说
git fetch
可以解决这个路径规范错误。我试过了,它从远程打印了一个新的功能分支:$ git fetch remote: Counting objects: 67, done. remote: Compressing objects: 100% (67/67), done. remote: Total 67 (delta 55), reused 0 (delta 0) Unpacking objects: 100% (67/67), done. From https://bitbucket.org/<repo_name> b42b31e05..e7f3858ad <unrelated_bug_branch> -> origin/<unrelated_bug_branch> 36b2cd4e9..ac87583fd develop -> origin/develop 61945b8ef..22a63fd7e <unrelated_feature_1_branch> -> origin/<unrelated_feature_branch> 322a39980..1f8752f2c <unrelated_feature_2_branch> -> origin/<unrelated_feature_2_branch> * [new branch] <unrelated_feature_3_branch> -> origin/<unrelated_feature_3_branch> 5fe02b8b3..a27140571 <unrelated_feature_4_branch> -> origin/<unrelated_feature_4_branch>
-
我做了另一个
git status
,它与上面的#2相同。 -
我可以针对特定文件,这将起作用:
$ git checkout -- <relative_file_path_in_current_app_dir>
-
git status
现在显示我有一棵干净的工作树。
我的路径规范错误的根本原因到底是什么,我该如何解决它?我可以同时签出特定文件,但我只是对这个错误感到好奇。
恐怕链接的问题 Git:无法结帐分支 - 错误:路径规范"..."不匹配 Git 已知的任何文件都是一团糟:正如您所指出的,它有很多答案,几乎没有解释。 同时,ElpieKay的评论有正确的答案:node_modules
是一个文件或目录,你让你的Git忽略了,所以当你要求你的Git更新它时,它说:嗯? 现在更新什么?
长篇描述
问题的根源源于 Git 在一个命令中塞进了太多的东西。git checkout
命令可以:
- 从一些现有的远程跟踪名称(如
origin/develop
)自动创建一个新分支(例如develop
的新分支名称);或 - 签出现有分支(具有给定名称的分支,如
master
);或 - 查看现有的历史提交(例如,通过标签名称(如
v1.2
)或原始哈希 ID),从而产生 Git 所谓的分离 HEAD。
所有这些操作都以某种方式移动了 Git 的HEAD
概念。 特殊名称HEAD
(全大写字母 - 这在Linux系统上通常是必需的,而Windows用户通常可以通过以小写形式键入head
)是Git如何记住您所在的分支(如果有的话)。 因此,上述三种git checkout
改变您所在的分支,或者 - 对于听起来很可怕,但实际上内部很正常,1分离的 HEAD 案例,即缺少任何分支。
例如,1Git 在冲突或交互式变基时使用这种分离的 HEAD 状态。 将其用于正常开发不是一个好主意,但对于临时工作或这些内部到 Git 状态来说,这很好。 只需完成你的变基,或者任何导致暂时分离的 HEAD 的东西,Git 就会重新连接 HEAD,你可以继续走动,你的头牢牢地重新连接。:-)
但是git checkout
可以做更多根本不涉及改变HEAD
的事情,这就是git checkout --
的目的。 在这里,Git 可以:
- 将索引中的一个或多个文件提取到工作树中:
git checkout --filename(s)
,或 - 从特定提交中提取一个或多个文件,首先将其写入索引,然后写入工作树:
git checkoutcommit-specifier--filename(s)
。
--
将提交说明符(如master
、develop
、1f3a907
等)与文件名分开。如果文件名为master
:git checkout master
会将您的HEAD
切换到master
,而不是签出名为master
的文件,则通常需要--
。 它有时是可选的:如果你有一个名为master
的文件,但你想从develop
的尖端获取副本,写git checkout develop master
可以让 Git 清楚这一点(即使它让普通人感到困惑)。
git checkout
可以做更多的事情,但让我们停止这三组明显非常不同的操作:(1)将HEAD更改为不同的分支,(2)将HEAD更改为在特定提交时分离,以及(3)根本不更改HEAD,只需从索引或特定提交中获取一个文件或多个文件即可。
Git 在git checkout
文档中使用各种语法标记来表达这些不同的操作。 引用这一点——我将引用那里的整个可怕的七项清单——我们看到:
-q
] [-f
] [-m
] [<branch>
] git 结帐 [-q
] [-f
] [-m
]--detach
[<branch>
]git 结帐 [-q
] [-f
] [-m
] [--detach
]
<commit>
git 结帐[-q
] [-f
] [-m
] [-b
|-B
|--orphan
]<new_branch>
[<start_point>
]
git 结帐[-f
|--ours
|--theirs
|-m
|--conflict=<style>
] [<tree-ish>
] [--
]<paths>...
git 结帐 [<tree-ish>
] [--
]<pathspec>...
git
结帐(-p
|--patch
) [<tree-ish>
] [--
] [<paths>...
]
其中三个是我们在这个答案中讨论的操作(另外四个是git checkout
可以做的更多事情,其中一些可能应该是不同的 Git 命令)。 让我们来看看它们。
按名称签出分支(或签出提交并分离 HEAD)
从第一行开始:
git 结帐[
<branch>
]
(我省略了简化它的选项)。 这将在尖括号中显示分支名称,这意味着您应该填写一个。 它也在方括号中,所以你可以省略它,但如果你把它省略,它意味着"留在当前的分支上",这是一件愚蠢的事情。 像这样将单词放在尖括号中branch
将其标记为某些人所说的元变量,即您应该填写的内容,元变量的名称告诉您这里会发生什么类型:分支名称!
这是切换到某个现有分支,或从某个现有的远程跟踪名称操作创建新分支。 您指定的分支名称是要切换到或自动创建的分支名称。 Git 将查找具有该名称的现有分支,如果找不到,将查看所有远程跟踪名称(origin/master
、origin/develop
等),以查看这些名称之一是否可以origin/
删除并成为您请求的名称。
(第二行引用与第一行类似,但插入了--detach
。 第三行与前两行类似,但不是<branch>
而是说<commit>
. 在第二个命令行中,--detach
是必需的,在第三个命令行中,它仍然是可选的。<commit>
元变量意味着您可以使用任何命名提交的内容,而不仅仅是分支名称。 这些是产生分离 HEAD 的变体:它们以与切换分支相同的方式签出提交,但它们在此过程中会切断您的 HEAD,因此您没有分支。 从本质上讲,如果你给git checkout
一个参数来命名一个提交,但不是分支名称,Git 只是假设你的意思是--detach
。 如果要在使用分支名称时分离,则必须添加--detach
。 最后一件事不是大多数人想做的事情,但手册页无论如何都涵盖了它。
链接的问题及其答案主要是关于从远程跟踪名称创建新分支。也就是说,他们回答了这个问题:
如果我说
git checkout feature-X
我得到一个错误,但随后我运行git fetch
,然后再次执行git checkout feature-X
,它有效。 为什么?
这里的答案是,你第一次跑git checkout develop
,你没有origin/feature-X
,但是git fetch
跑完之后,你确实有origin/feature-X
。 反过来,这是因为git fetch
创造了它,因为其他人在origin
上创造了feature-X
,这是最近一段时间。git fetch
让你的 Git 在origin
调用 Git,并获取其所有分支和提交的列表。 您的 Git 会加载您尚未拥有的所有新提交,并创建或更新所有origin/*
名称,现在您有origin/feature-X
.
签出特定文件
倒数第二个引用的语法行:
git 结帐[
<tree-ish>
] [--
]<pathspec>...
显示了两个元变量。 第一个拼写为<tree-ish>
,这是 Git 的简写:你可以在这里使用分支名称,或者提交哈希,或者我可以用来查找我称之为树对象的任何内容。Git 有很多方法可以指定提交哈希 ID,这意味着涵盖所有这些方法,以及一些你不太可能遇到的更奇怪的极端情况。 第一个元变量是可选的,如果你省略它,Git 将从 Git 的索引中签出文件(我们在这里没有真正描述,我不会详细介绍,因为这已经很长了)。
第二个元变量是<pathspec>
。 请注意,它不是可选的! 这是 Git 语言的简写,用于文件名,或带有*
的模式,或许多其他内容中的任何一个,这些内容也太长而无法在这里讨论。...
部分意味着您可以列出其中的多个。 这些路径规范命名您希望 Git 在<tree-ish>
中找到的一个或多个特定文件(您命名的提交,如果您命名了提交)。
哪里出了问题
当你写:
git checkout -- *
您的 shell(可能是bash
)会扩展*
以匹配当前工作目录中的所有文件。阿拉伯数字因此,如果你有文件README
和hello
和node_modules
等等,Git看到的是:
git checkout -- README hello node_modules ...
没有<tree-ish>
,所以 Git 会在你的索引中查找名为README
、hello
、node_modules
等的文件。
如果它没有找到其中之一 - 并且没有找到node_modules
- Git 抱怨:
error: pathspec 'node_modules' did not match any file(s) known to git.
什么都不做。
如果你改用.
,你的 shell 运行:
git checkout -- .
Git 认为.
是<pathspec>
论据。 这意味着"当前目录中 Git 已知的所有文件",因此这将执行您想要的操作。 你也可以写:
git checkout -- '*'
它使用引号来保护*
免受bash
(或您正在使用的任何外壳)的影响。 然后Git将看到*
,Git 将*
扩展到"当前目录中Git已知的所有文件"。 但是写.
更容易。
2请注意,在Windows上,CMD.EXE不会扩展*
,而是将其传递给Git,让Git扩展*
,并且此错误永远不会发生!