bigywater.tech
Git · 42

skip-worktree 与 assume-unchanged 的区别

git update-index 的 --assume-unchanged 和 --skip-worktree 都能让 git status 不再报告一个文件,到底有什么区别?这篇从 index 条目的标志位讲起,再逐个测 add、checkout、reset --hard、stash 的反应,看哪一个会悄悄丢掉你的修改。

0x8000 / 0x4000
assume-valid 位在 flags 里,skip-worktree 在扩展标志里(index 升到 v3)
丢失
git reset --hard 和 git stash 会还原 assume-unchanged 文件,本地修改丢失;skip-worktree 文件保留
静默 vs 拒绝
git add 对 assume-unchanged 文件静默无效,对 skip-worktree 文件明确拒绝并提示

标志位

实测 · 本机执行输出
  设置之前   index 版本 2 | flags=0x0005 assume-valid=False extended=False xflags=0x0000 skip-worktree=False
  assume-unchanged  版本 2 | flags=0x8005 assume-valid=True extended=False xflags=0x0000 skip-worktree=False
  再加 skip-worktree 版本 3 | flags=0x4005 assume-valid=False extended=True xflags=0x4000 skip-worktree=True
  $ git ls-files -v
  h a.txt
  S b.txt
  H c.txt

assume-unchanged 是 index 条目 flags 的最高位(assume-valid,0x8000);skip-worktree 在扩展标志里(0x4000),需要 index v3。git ls-files -v 用小写 h 和 S 区分。

status 看不见

实测 · 本机执行输出
  $ git status --short; echo "(a、b 被隐藏,c 正常显示)"; git diff --stat | tail -2
   M c.txt
  (a、b 被隐藏,c 正常显示)
   c.txt | 2 +-
   1 file changed, 1 insertion(+), 1 deletion(-)

两者都让 git status、git diff 不去检查工作区,文件被改也不报告。

add

实测 · 本机执行输出
  $ git add a.txt b.txt 2>&1 | head -5 | cut -c1-90
  The following paths and/or pathspecs matched paths that exist
  outside of your sparse-checkout definition, so will not be
  updated in the index:
  b.txt
  hint: If you intend to update such entries, try one of the following:
  a.txt: 工作区 blob be822d3a  暂存区 blob da0f8ed9  HEAD blob da0f8ed9  → 没有暂存
  b.txt: 工作区 blob 2ffaeecf  暂存区 blob c9c6af7f  HEAD blob c9c6af7f  → 没有暂存

对 assume-unchanged 的文件 git add 静默无效;对 skip-worktree 的文件会拒绝并提示“matched paths that exist outside of your sparse-checkout definition”。

checkout

实测 · 本机执行输出
  assume-unchanged 的 a.txt   → error: Your local changes to the following files would be overwritten by checkout: / 	a.txt / Please commit your changes or stash them before you swit | 切换后文件内容: a.txt 本地的未提交修改 | 本地修改还在: True
  skip-worktree 的 b.txt      → error: Your local changes to the following files would be overwritten by checkout: / 	b.txt / Please commit your changes or stash them before you swit | 切换后文件内容: b.txt 本地的未提交修改 | 本地修改还在: True
  普通的 c.txt                  → error: Your local changes to the following files would be overwritten by checkout: / 	c.txt / Please commit your changes or stash them before you swit | 切换后文件内容: c.txt 本地的未提交修改 | 本地修改还在: True

切换分支会覆盖这些文件时,git 仍会检查本地修改并拒绝切换——这和 status 的“视而不见”不同。

reset --hard 与 stash

实测 · 本机执行输出
  $ git reset --hard -q; for f in a b c; do printf "%s.txt: " $f; cat $f.txt; done
  a.txt: a1
  b.txt: b1 本地改
  c.txt: c1
  $ git stash -q 2>&1; for f in a b c; do printf "%s.txt: " $f; cat $f.txt; done; git stash list | wc -l; echo "-- stash 里保存了:"; git stash show --stat | head -3
  a.txt: a1
  b.txt: b1 本地改
  c.txt: c1
         1
  -- stash 里保存了:
   c.txt | 2 +-
   1 file changed, 1 insertion(+), 1 deletion(-)

assume-unchanged 的本质是对 git 的性能承诺“我不会改它”。如果违约,reset --hard 与 stash 都会把文件还原,本地修改悄悄丢掉。skip-worktree 则表示“别碰工作区这份”,reset 和 stash 都不动它。sparse-checkout 用的是 skip-worktree。

清除

实测 · 本机执行输出
  $ git update-index --no-assume-unchanged a.txt; git update-index --no-skip-worktree b.txt; git ls-files -v; git status --short
  H a.txt
  H b.txt
  H c.txt
   M b.txt

--no-assume-unchanged / --no-skip-worktree 去掉标记后,保留在工作区的修改重新出现在 status 里。

分步演示

skip-worktree 与 assume-unchanged
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。index 的标志位由我解析(ll.py 的 parse_index,含扩展标志);checkout 一节对三种文件各建一个独立的仓库测试
    • 没有验证:assume-unchanged 文件在 git merge、rebase、pull 过程中的行为;skip-worktree 与 git status 在 sparse-checkout 之外的用法(这里手动设置);git update-index --really-refresh 对两种标记的影响;--no-skip-worktree 之后文件被别的分支改过时的合并行为;官方文档建议的“不要用 assume-unchanged 当忽略功能”只验证了其中的几个后果
    京ICP备2026067502号-1京公网安备11010502063487号