DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

git checkout -- * 因未跟踪文件报 pathspec 错误

git checkout -- * 报未跟踪文件的 pathspec 错误时,先核对 shell 展开和索引状态,再用明确路径恢复。说明暂存区与 HEAD 的区别及覆盖前的备份要求。

想撤销工作区改动,在终端执行 git checkout -- *,却收到这样的错误:

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

这个文件明明就在当前目录,但从未加入 Git 暂存区。未加引号的星号会先被 shell 处理:在启用文件名展开的 POSIX 风格 shell 中,shell 先把 * 换成当前目录内匹配的名称,再启动 Git。Git 收到的参数里因此包含了它不跟踪的临时文件。

下面针对普通工作区、没有未解决合并冲突的文件恢复操作。Windows CMD、PowerShell 和禁用文件名展开的 shell 不应直接套用这一归因;同样的 pathspec 错误还可能来自路径拼写、执行目录或恢复来源不对。

确认报错文件是否在暂存区里

假设当前目录同时有 Git 已跟踪的 app.txt 和未跟踪的 databaseName.tmp,输入星号后,Git 收到的参数相当于:

git checkout -- app.txt databaseName.tmp

这是解释参数展开的示例,不要把它当成修复命令再次运行。-- 用于分隔路径参数,并不能阻止 shell 在启动程序前展开星号。

先在原来执行命令的目录检查实际状态。把示例文件名替换为错误里出现的路径,含空格的路径保留引号:

git status --short --untracked-files=all --ignored -- "databaseName.tmp"
git ls-files --error-unmatch -- "databaseName.tmp"

短状态中的 ?? 表示未跟踪,!! 表示被忽略的路径。第二条命令查询 Git 的索引,也就是暂存区;路径不在其中时会报错并返回 1。对于这里的具体文件名,这个结果有助于确认:磁盘上存在该文件,并不意味着 Git 有一份暂存内容可供恢复。

如果第二条命令能列出文件,就不要继续按“未跟踪文件被星号带入”处理。核对原错误中的完整路径、大小写、命令执行目录,以及是否实际指定了其他提交作为来源。文件在某个历史提交中存在,也不等于它现在就在索引中。

被 .gitignore 忽略同样不能阻止 shell 展开。忽略规则由 Git 使用,shell 在展开当前目录名称时并不读取这些规则。为了消除这条报错而先 git add 临时文件,会改变暂存区内容,也不等于撤销工作区修改。

明确恢复范围,再使用目录路径

如果真正要丢弃的是当前目录下已跟踪文件的未暂存修改,可以使用目录路径 .,避免让 shell 把每个磁盘文件名都传进去。但这仍然是覆盖工作区内容的操作,执行前要先检查改动,并将仍要保留的内容备份到仓库之外。

git diff -- .
git diff --cached -- .

第一条显示工作区相对于暂存区的改动;第二条显示暂存区相对于当前提交的改动。查看两者,是为了分清要丢弃的内容与已经暂存、准备提交的内容。差异输出可能包含密钥或本地配置,只在可信环境中查看。

只想处理一个文件时,优先使用明确路径。确认该文件的未暂存修改可以丢弃、需要的内容已经另行保存后,二选一执行:

# Git 2.23 及以上
git restore --worktree -- "app.txt"

# 旧版 Git 可使用
git checkout -- "app.txt"

这两条命令在上述普通场景下都从暂存区恢复工作区文件,不会替你删除那个无关的临时文件,也不切换分支。不要把它们连续执行当作两步修复。

确认整个当前目录都在本次恢复范围内,才把单个文件换成 .:

# Git 2.23 及以上
git restore --worktree -- .

# 旧版 Git 可使用
git checkout -- .

这里仍是二选一。目录路径会覆盖该目录及其下属目录中匹配的已跟踪文件;其中也可能有以点开头的配置文件。普通 shell 的 * 通常不把当前层级的点文件名展开出来,改用 . 后不能沿用原先对范围的想象,必须提前看过差异。

在子目录里,. 指的是当前子目录。不要为了让命令“覆盖得更全”就直接切到仓库根目录重跑,否则其他目录中尚未保存的修改也可能被覆盖。

没有写 HEAD,恢复来源就是暂存区

还有一种容易混淆的情况:命令成功了,文件却没有回到上一次提交的样子。

例如,app.txt 的提交版本是 A,修改为 B 后执行过 git add,随后又在工作区改为 C。此时暂存区保存 B,工作区是 C。运行不带提交来源的 git checkout -- app.txt,或默认来源的 git restore --worktree -- app.txt,会把工作区恢复成 B,暂存区仍然是 B。

因此,执行后仍有已暂存修改是正常的。若目标确实是把工作区文件恢复成当前提交版本,同时保留暂存区,Git 2.23 及以上可以指定恢复到 HEAD 版本:

git restore --source=HEAD --worktree -- "app.txt"

这同样会覆盖工作区,执行前仍需保存要保留的内容。不要误加 --staged:它指定的恢复位置包含暂存区,影响范围就变了。没有任何提交的新仓库也没有可用的 HEAD 提交版本,不能套用这条命令。

用差异和文件状态确认结果

从暂存区恢复所选文件后,再检查同一路径:

git diff -- "app.txt"
git diff --cached -- "app.txt"
git status --short --untracked-files=all --ignored -- "databaseName.tmp"

对于按暂存区恢复的普通文件,第一条应不再显示该文件的未暂存差异;第二条应保留执行前的暂存差异。若使用了 --source=HEAD 而暂存区与 HEAD 不同,第一条仍可能显示差异,不能据此认定恢复失败。

临时文件仍显示为未跟踪或被忽略,并不表示修复没有完成。这里解决的是把不存在于索引中的文件名误传给恢复命令的问题,临时文件是否需要清理由它的用途决定。不要追加批量删除或强制重置来追求一个完全空白的状态输出。

如果误覆盖了尚未暂存、也未提交的修改,应从事先备份或编辑器历史中找回。Git 不保证保存过这些内容,重新执行恢复命令无法把刚刚丢弃的工作区状态倒放回来。拿不准某一处改动是否有用时,保留文件并缩小恢复范围,比先覆盖再尝试找回更稳妥。