git update-index --assume-unchanged and --skip-worktree
git update-index --assume-unchanged <file> tells git to stop checking that tracked file for changes (a performance hint), while --skip-worktree marks it as locally modified and not to be touched.
These two flags both hide changes to tracked files, but they mean different things. assume-unchanged is a performance hint git may ignore; skip-worktree expresses real intent to keep a local version. CI scripts misuse them often, so the distinction matters.
What it does
git update-index manipulates the staging index directly. --assume-unchanged sets a bit telling git not to bother checking that file for modifications (a speed hint on slow filesystems); git is free to ignore it, and operations like checkout or pull can overwrite the file. --skip-worktree is stronger: git tries to preserve the local file across many operations, intended for config you must keep locally edited.
Common usage
git update-index --assume-unchanged config/local.json
git update-index --no-assume-unchanged config/local.json # undo
# keep a locally edited file from being overwritten
git update-index --skip-worktree .env.local
# list files with the skip-worktree bit (S in column 2)
git ls-files -v | grep '^S'Options
| Flag | What it does |
|---|---|
| --assume-unchanged | Set the assume-unchanged bit (performance hint) |
| --no-assume-unchanged | Clear the assume-unchanged bit |
| --skip-worktree | Mark the file to be preserved locally |
| --no-skip-worktree | Clear the skip-worktree bit |
| --refresh | Re-check stat info for index entries |
| --chmod=+x <file> | Set the executable bit in the index |
In CI
Do not use assume-unchanged to hide secrets or config in CI: a fresh clone does not carry these bits, and any pull/checkout can overwrite the file anyway. If a checkout or merge in CI reports the file changed despite the bit, that is assume-unchanged working as designed (it is only a hint). For genuinely local-only files, .gitignore or skip-worktree is more reliable, but neither survives a clean clone.
Common errors in CI
"error: Your local changes to the following files would be overwritten by checkout" can appear even on assume-unchanged files, because the bit is advisory. "fatal: Unable to mark file <x>" means the path is not tracked; the flags only apply to files already in the index. The bits are per-clone and silently absent in CI runners.
Using this in CI
CI checkouts are shallow and detached by default, which changes the answer this command gives you. Commands that read history, branch names, or tags need the checkout configured for it.
- uses: actions/checkout@v4
with:
fetch-depth: 0 # history, tags, and git describe all need this
- run: |
git rev-parse --is-shallow-repository # expect false
git rev-parse --abbrev-ref HEAD # prints HEAD when detached