◆

Git 忽略本地修改 assume-unchanged 与 skip-worktree

2023-12-19 09:480

有一类文件的处境很尴尬:它必须存在于仓库里(团队都要用),但每个人本地又必须改成自己的值,比如后端接口地址、本地数据库账号、调试开关。这种文件不能加进 .gitignore——.gitignore 只对未被跟踪的文件生效,已经提交过的文件写进去也照样会被 git status 报出修改。

git update-index 提供了两个标记位来处理这种场景:--assume-unchanged 和 --skip-worktree。它们表面效果一样,都是让 git status 不再显示这个文件的改动,但设计意图完全不同,用错了会在某次 git pull 之后把本地配置弄丢。下面先分别说用法,再讲清楚该选哪个。

assume-unchanged

用于暂时忽略本地对某个文件的修改,用于在您需要在本地修改文件,但不希望将这些本地修改提交到版本控制系统中时

需要先说清楚它的真实定位:这个标记本质是性能优化,不是保护机制。git status 每次都要 stat 工作区里所有文件来判断有没有变化,仓库文件数量特别大时这一步很慢。打上 assume-unchanged 相当于我们向 git 承诺"这个文件我不会改",于是 git 跳过对它的检查直接当作没变。

关键在于这是一个承诺,而不是约束。git 保留在任何时候丢弃这个标记的权利,一旦某个操作(典型的是 git pull、git checkout、git reset)需要覆盖这个文件,它会直接覆盖,本地改动就没了,而且因为标记的存在,事前也不会给出冲突提示。

忽略文件的本地修改

git update-index --assume-unchanged <file>

取消忽略

git update-index --no-assume-unchanged <file>

取消之后 git status 会立刻把之前积压的本地改动全部显示出来,这是正常现象,不是操作出错。

skip-worktree

允许您暂时跳过工作树(working tree)上的特定文件的本地修改

这个才是"我要在本地改它,但永远别提交"这个需求的正确答案。它的语义是明确告诉 git:索引里记录的版本才是权威的,工作区里这个文件的内容不作数,不要对比、不要覆盖。

和 assume-unchanged 最重要的区别在于遇到远程更新时的行为。带 skip-worktree 的文件在 git pull 时如果上游也改了它,git 不会闷头覆盖,而是会报错中止合并,提示这个文件有本地变更。虽然报错本身要处理,但至少我们的本地配置不会无声无息地消失。

跳过文件的本地修改

git update-index --skip-worktree <file>

取消跳过

git update-index --no-skip-worktree <file>

区别

  • git assume-unchanged: 这个选项适用于在文件的本地修改不太频繁且您确定文件不会被提交到版本控制时使用。
  • git skip-worktree: 更适合用于需要频繁本地修改的情况,但不希望将这些修改提交到版本控制中,例如本地配置文件或调试信息。

用一句话概括我们该怎么选:想让 git status 干净一点、纯粹为了减少噪音或提速,用 assume-unchanged;确实要在本地长期维护一份和仓库不同的内容,用 skip-worktree。日常开发中绝大多数场景属于后者。

查看哪些文件被标记了

这两个标记都不体现在 git status 里,打完就"看不见了",几个月后忘掉自己标过什么是常事。查看方式是 git ls-files -v,它会在每个文件前面输出一个状态字母:

git ls-files -v

小写 h 表示 assume-unchanged,大写 S 表示 skip-worktree,正常跟踪的文件是大写 H。文件多的时候只筛出被标记的:

# 列出所有 assume-unchanged 的文件
git ls-files -v | grep '^h'

# 列出所有 skip-worktree 的文件
git ls-files -v | grep '^S'

一个必须提前知道的坑

这两个标记都只存在于本地的 .git/index 文件里,不会被提交、不会被推送,也不会随 git clone 带给别人。也就是说它解决的是"我这台机器上的困扰",团队里每个人都得自己执行一遍。

由此带来一个真实风险:我们标记之后本地改了配置,因为看不到改动,很容易误以为仓库里那份就是我们看到的这份,进而在排查问题时得出错误结论。更麻烦的是新同事拉下代码,用的是仓库里的原始值,跑不通却查不到原因。所以真正的团队配置最好用 <xxx>.example 加 .gitignore 的模式来做——把模板提交进仓库,真实文件不跟踪。skip-worktree 更适合当作过渡手段或个人临时方案。

遇到冲突时怎么恢复

带着标记的文件在 git pull 或切分支时报错中止,处理顺序是先取消标记、备份本地内容、让 git 正常完成合并,最后再改回来并重新打上标记:

# 1. 先把本地这份存一份出来
cp config.local.js /tmp/config.local.js.bak

# 2. 取消标记,让 git 恢复正常管理
git update-index --no-skip-worktree config.local.js

# 3. 撤销本地改动,使工作区与索引一致
git checkout -- config.local.js

# 4. 正常拉取
git pull

# 5. 按备份改回自己的值,再重新打标记
git update-index --skip-worktree config.local.js

第 3 步的 git checkout -- 会丢弃该文件全部未提交的改动且不可恢复,所以第 1 步的备份不能省。稳妥一点也可以用 git stash push config.local.js 代替,改动进了 stash 还能 git stash pop 找回来。

💬留言板0

还没有留言,来发表第一条吧~

PAGE LOAD / LIVEGit 忽略本地修改 assume-unchanged 与 skip-worktree加载中