Git 状态判断别只看输出,还要查退出码
文章用临时仓库复现 rev-parse、grep、stash 和 status 的结果误读:非空输出或记录数量不能单独证明路径存在或命令成功。判断时需结合退出码、实际执行的命令与比较路径,涉及时间戳时还应控制可选锁行为。
AI 深度解读
Git 命令有输出,不代表目标存在;差异统计很大,也不代表你要找的文件丢了。作者用一个可丢弃的仓库复现了几类误判:rev-parse 输出未解析的参数,grep 被同名函数替换,stash 比较范围选错,以及 git status 改变索引时间戳。
这些案例指向同一个结论:判断 Git 状态,要同时核对退出码、实际执行的命令和比较范围。 对经常让 AI 跑终端、检查改动和恢复文件的程序员,这比多记几条命令更实用。
输出不为空,为什么仍然可能失败
作者先检查了一个并不存在于 HEAD 中的路径。HEAD 指向当前提交,HEAD:<path> 则指定这次提交里的某个路径。
下面这段实验说明,变量里有内容,也可能是解析失败:
$ v=$(git rev-parse "HEAD:存在しないpath" 2>/dev/null); echo "値=[$v] exit=$?"
値=[HEAD:存在しないpath] exit=128
要看的是第二行的 exit=128。git rev-parse 把未解析成功的参数原样写到了标准输出,所以变量 v 非空。如果脚本只用 [ -n "$v" ] 判断,就会把失败当成路径存在。
作者建议用 git cat-file -e <rev>:<path> 检查目标是否存在。在他的复现中,不存在时退出码为 128,存在时为 0。如果需要查看树中的具体条目,则用 git ls-tree。
另一个坑发生在 grep。作者的环境里,同名 shell 函数覆盖了预期命令,还调用了另一种 grep 实现。执行 grep -c alpha s.txt,得到的是 error: unknown option '-G' 和退出码 1;执行 command grep -c alpha s.txt,才得到匹配数 1、退出码 0。
这里连「退出码为 1 就是没匹配」都不能直接套用,因为实际执行的程序已经变了。
排查顺序应该往前挪一步:先用 type grep 确认命令来源,再用 command grep 绕过同名 shell 函数,或者调用绝对路径。退出码必须结合实际执行者来解释。
stash 差异很大,可能只是比较错了对象
作者曾看到整个树有 3 个文件、434 行差异,于是判断 stash 不能可靠备份。但他真正关心的是另外 20 个条目,那 3 个文件并不在其中。
改为逐路径比较后,结果变成:15 项内容相同,5 项在两边都已删除,没有任何一项存在差异。针对这 20 项,作者认为备份是完整的。
问题出在统计口径。全仓差异回答的是「整个树哪里不同」,不能直接回答「我关心的这些文件有没有保存」。
stash 还有不同的比较对象。原文将 stash@{0} 解释为暂存工作时的工作树快照,stash@{0}^1 对应当时的 HEAD,stash@{0}^2 对应索引,也就是已暂存内容;stash@{0}^3 对应未跟踪文件。
因此,比较前至少要写清两件事:比较哪两个状态,以及检查哪些路径。否则,工作树、已暂存内容和未跟踪文件混在一起,数字再精确也可能答非所问。
先固定比较对象和路径,再解释差异数量。
只是查看状态,也可能改变时间戳
作者记录到,运行 git status 后,索引文件的修改时间,也就是 mtime,发生了变化。
这会影响一种常见排查方式:先查看仓库状态,再拿文件时间戳推断之前发生过什么。如果查看动作自身改变了时间戳,后面的推断就掺入了新影响。
当时间戳需要作为证据时,原文给出的检查命令是:
GIT_OPTIONAL_LOCKS=0 git -C <path> status --porcelain
这里要注意开头的 GIT_OPTIONAL_LOCKS=0。作者建议用它检查状态,避免普通 git status 中的可选锁定及相关索引刷新影响取证。-C <path> 指定仓库位置,--porcelain 则提供便于脚本处理的状态输出。
这个案例提醒我们:命令的用途是「查看」,并不保证它完全不写文件。尤其在排查时间顺序时,要把观察动作本身算进去。
我的判断
这篇最有价值的地方,是把「命令结果」拆成了几项可以核对的证据。相比只读终端文字、只看文件数量,这种检查方式更容易复核,也更适合约束 AI 编程助手的结论。
但它提供的是具体环境中的复现,没有跨 Git 版本、shell 配置和平台的对照数据。grep 被覆盖是环境问题,索引 mtime 的变化也是作者记录到的现象,不能据此推断每台机器都会出现相同行为。
stash 的结论也要收窄。15 项相同、5 项两边都删除,只能支持指定范围内两个状态一致,不能证明所有需要的内容都已保存。两边都没有某个文件,也不能替代对预期内容的确认。另外,stash@{0}^3 是否存在取决于 stash 是否保存了未跟踪文件,不能当成固定存在的节点。
原文没有给出性能或效率对照。这是一组有用的排错经验,不是关于 stash 可靠性的全面评测。
能拿来做什么
- 写 Node 或 shell 自动化时,把退出码和标准输出分开处理。检查提交中的路径,可以使用
git cat-file -e <rev>:<path>,不要仅凭输出字符串非空作判断。 - 让 AI 检查或恢复文件时,要求它列出比较的两个引用及具体路径,再报告差异。不要接受用全仓统计直接推导目标文件是否完整的结论。
- 遇到搜索结果异常时,先运行
type grep,确认是否有同名函数介入,再用command grep复核。 - 排查依赖时间戳的问题时,先使用
GIT_OPTIONAL_LOCKS=0 git -C <path> status --porcelain检查状态,并记录检查动作,避免把观察后的时间当成原始证据。