Git diff --no-index:仓库外也能比较目录,退出码一却未必只有一种原因

10-01 3阅读

拿到两份解压后的配置目录,想知道哪些文件变了,并不一定要先建立仓库。git diff --no-index 可以直接比较磁盘上的两个路径。它适合检查生成文件、发行包或导出结果,但命令返回非零时,脚本不能立即下结论说比较过程出了故障。

这个模式隐含 exit-code:正常完成且相同返回零,发现差异返回一。容易遗漏的边界是,不能反过来认为看到一就必定是正常差异。本文在 Git 2.52.0 上实测,不存在的输入也可能返回一,并在标准错误中说明无法访问。

同时保留结果和诊断

将代码保存为 demo.sh,用 bash demo.sh 运行。它只在新建的临时目录中写入两份小配置,不初始化仓库。compare_paths 先确认两端都是目录,随后分别保存补丁和标准错误,并立刻保存命令状态,防止后面的打印操作覆盖退出码。

Git diff --no-index:仓库外也能比较目录,退出码一却未必只有一种原因

AI概念示意图:以抽象物件说明本文主题,不代表真实界面或运行结果。

#!/usr/bin/env bash
set -eu
work=$(mktemp -d)
mkdir "$work/old" "$work/new"
printf 'color=blue\n' > "$work/old/config.txt"
cp "$work/old/config.txt" "$work/new/config.txt"

compare_paths() {
  local label=$1 left=$2 right=$3 rc
  if [[ ! -d "$left" || ! -d "$right" ]]; then
    printf '%s=input-error\n' "$label"
    return 0
  fi
  if git --no-pager diff --no-index --no-ext-diff --no-textconv \
      -- "$left" "$right" > "$work/$label.patch" 2> "$work/$label.err"; then
    rc=0
  else
    rc=$?
  fi
  if [[ -s "$work/$label.err" ]]; then
    printf '%s=needs-review(%s)\n' "$label" "$rc"
    return 0
  fi
  case "$rc" in
    0) printf '%s=equal\n' "$label" ;;
    1) printf '%s=different\n' "$label" ;;
    *) printf '%s=failed(%s)\n' "$label" "$rc" ;;
  esac
}
compare_paths same "$work/old" "$work/new"
printf 'color=red\n' > "$work/new/config.txt"
compare_paths changed "$work/old" "$work/new"
compare_paths missing "$work/old" "$work/missing"

if git --no-pager diff --no-index --no-ext-diff --no-textconv \
    -- "$work/old" "$work/missing" > "$work/raw.patch" 2> "$work/raw.err"; then
  raw_rc=0
else
  raw_rc=$?
fi
test "$raw_rc" -ne 0
test -s "$work/raw.err"
test ! -s "$work/same.patch"
test -s "$work/changed.patch"
printf 'raw-missing-status=%s diagnostics=yes\n' "$raw_rc"
printf 'work=%s\n' "$work"

依次观察相同、差异和无效输入

前面三行应为 same=equal、changed=different、missing=input-error。第一轮内容完全相同;第二轮只把颜色从蓝改成红;第三轮指定不存在的目录,被输入检查拦住。changed.patch 中应能看到删去蓝色、增加红色的两行,原来的两个目录都不会被修改。

最后的原始探针故意绕过包装函数,只为观察命令行为。它要求状态非零且诊断文件非空,不把某个具体错误码写成跨版本保证。在本文环境中 raw-missing-status 为一,所以仅写“零相同、一不同、其他失败”的分支并不够稳妥。

把疑点交给人工复核

包装函数在 input-error 和 needs-review 后返回零,是为了让演示继续跑完各组情况;其他标签也只用于展示。生产调用方必须另行规定失败返回码和停止规则,或检查明确的结果字段,不能把这个示例脚本最终退出零当作目录验收成功。

示例保守地把任何标准错误归为 needs-review。这样做可能把一些警告也留下来,但不会在诊断尚未读清时宣称比较成功。正式工具应记录实际版本、参数、状态和诊断,根据经过验证的错误类型处理,不能靠忽略全部标准错误来让结果更干净。

目录存在性检查仍不是完整保证:检查之后文件可能被移动,权限也可能改变。若输出将用于发布验收,先让两份输入保持稳定,再执行比较;对失败保留现场和日志。这里的预检查是减少明显输入错误,并不提供文件系统快照或并发保护。

命令明确关闭外部差异驱动和文本转换,目的是观察普通文件的比较结果,避免本机工具配置改变演示行为。路径放在双引号中,并在选项后使用双横线。补丁写到待比较目录之外,防止下一次比较把自己的结果文件也算进去。

看见差异之后再决定如何处理

阅读补丁时先确认左右顺序:左边是旧目录,右边是新目录;互换后增删方向也会互换。如果只需要文件清单,可选择适合的显示选项,但仍应保留同样的状态处理。程序读取路径列表时还要考虑文件名中的空格与换行,不能随意按空白拆开。

空目录、所有权、时间戳等信息并不等同于文本补丁能表达的内容。若任务要求精确验收一份文件树,另列需要比较的元数据,并使用对应工具检查。一个没有文本差异的结果,不能替代对权限、链接和运行所需环境的完整确认。

no-index 的价值是把熟悉的差异展示用到普通路径上。它不会替你暂存、提交或应用补丁。本例输出实验目录供复查;核对完成后再按自己的文件管理流程处理临时文件,不必为了比较两份资料给它们添加版本控制历史。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。