git update-index 的 --assume-unchanged 和 --skip-worktree 都能让 git status 不再报告一个文件,到底有什么区别?这篇从 index 条目的标志位讲起,再逐个测 add、checkout、reset --hard、stash 的反应,看哪一个会悄悄丢掉你的修改。
设置之前 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 区分。
$ 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 不去检查工作区,文件被改也不报告。
$ 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”。
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 的“视而不见”不同。
$ 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 里。