How to ignore changes in tracked files with Git

| 0 comments

We all know the .gitignore file as a place to exclude files from the version control system, unless they are not tracked. But once a file is committed, it won’t be affected by the .gitignore system.
Sometimes files get used on a general basis or by a CI server. Therefore they have to exist in the repository, but local and individual changes (for testing purposes) should not be committed. To ignore changes in tracked files the .gitignore system is not suitable.

Fortunately Git has a way to solve this issue. git update-index. But there are two options for this command --assume-unchanged and --skip-worktree. Long time I was aware only of the first, but then I realised that the second option is in most cases a more appropriate solution for the mentioned problem. Therefore I will try to shed some light on each of these ways and the difference between them.

assume-unchanged

You can set a bit for a file to mark it as “unchanged” so that Git will ignore it. This feature was planned as an option to optimise inefficient filesystems, because it makes Git to omit any checking and assume that the file hasn’t changed in the working tree (see git update-index). The problem is that this bit is not safe and will be overwritten in some cases. E.g. if a modified version of the file is pulled from the remote repository or a git reset --hard will be performed.

Set the bit and ignore an already tracked file:

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

Unset the bit and reenable tracking:

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

Instead of fixed file paths, you can use wildcards to ignore all files in a directory with <directory/*>.

List affected files:

git ls-files -v | grep ^[a-z] <path>

git ls-files shows the file status represented by a letter. If a lowercase letter is used, the file is marked as “assume unchanged”. Skip <path> to show the status of all files in the repository. (see git ls-files).

skip-worktree

If this bit is set for a file, Git runs all the normal check routines. Therefore Git knows if a file has been modified but ignores it and takes the version from the index instead of the one from the working tree. This way modifications in the working tree get ignored. The good thing is, that this bit is safe and does not get overwritten. However problems may occur if the file has changed in the local as well as in the remote repository. Stashing will not work and you have to manually reset the --skip-worktree bit. But this seems to be pretty ok.

Set the bit and ignore an already tracked file:

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

Unset the bit and reenable tracking:

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

List affected files:

git ls-files -v | grep ^[sS]

If the file is marked as “skip-worktree” git ls-files shows a “S”. If it is a lowercase “s”, the bit for “assume-unchanged” is set to. But the “skip-worktree” bit rules.

Differences

A post in the FallenGameR’s blog contains a good summary of the differences and the use case for both options. I will wrap up the essential differences here. Please see the mentioned blogpost for more details.

Operation --assume-unchanged --skip-worktree
File changed in local and remote repository.
git pull
Git wouldn’t overwrite local file, but output conflicts. Git wouldn’t overwrite local file, but output conflicts.
File changed in local and remote repository. git stash & git pull Discards all local changes, no option to restore them. Stash wouldn’t work. Manually unset “skip-worktree” bit first.
File changed only in remote repository. git pull File is updated, “assume-unchanged” bit is unset. File is updated, “skip-worktree” bit is preserved.
File changed only in local repository. git reset --hard File is reverted, “assume-unchanged” bit is unset. File is not reverted, “skip-worktree” bit is preserved.

Conclusion

After all the “skip-corktree” is the better fit for the mentioned problem and indeed it does a really good job in practise.
But no matter which option you choose there is one point to consider: The chosen bit will not be propagated by git push. This means that every user has to run the required commands individually.
The best you can do is to write a small script that contains these commands (and maybe some other settings). Then everybody can run this script after the first checkout of the repository.
There is a detailed description about this point on stackoverflow.

Here are the commands for a small demonstration of git update-index --skip-worktree:

// Create git repository
$ mkdir git-test
$ cd git-test
$ git init
Initialized empty Git repository in …

// Add a file in a new commit
$ echo “Hello World” > file01.txt
$ git add file01.txt
$ git commit -m “Adds a new file.”
[master (root-commit) a6053cf] Adds a new file.
 1 file changed, 1 insertion(+)
 create mode 100644 file01.txt

// Make some changes to the file
$ echo “here are some changes” >> file01.txt
$ git status
On branch master
Changes not staged for commit:
  (use “git add <file>…” to update what will be committed)
  (use “git checkout — <file>…” to discard changes in working directory)

	modified:   file01.txt

no changes added to commit (use “git add” and/or “git commit -a”)

// Ignore the tracked file
$ git update-index --skip-worktree file01.txt
$ git status
On branch master
nothing to commit, working directory clean

// Un-Ignore the tracked file again
$ git update-index --skip-worktree file01.txt
$ git status
On branch master
Changes not staged for commit:
  (use “git add <file>…” to update what will be committed)
  (use “git checkout — <file>…” to discard changes in working directory)

	modified:   file01.txt

no changes added to commit (use “git add” and/or “git commit -a”)

Leave a Reply

Required fields are marked *.