Please see Git - Difference Between 'assume-unchanged' and 'skip-worktree'. I'm not going to close this as a duplicate (yet?) though.
Today I made a change to appsettings.json and noticed the change showed up in the changes.
This requires more detail on your part, because "the changes" is ambiguous. Do you mean it shows up in git status
? Do you mean that if you run git diff
on two commits, the file shows as changed between those two commits? Or do you mean something else entirely?
Important background to know
The index, which is also called the staging area and the cache, is a key Git data structure. It contains every file that will go into each commit you make, and it starts out containing every file from the commit you have extracted into your work-tree (the directory where you do your work) from some starting commit. That is, once you have a Git repository—a collection of commits—you start out by using git checkout
to extract one particular commit into your index, and into a work-tree.
Generally this all happens on an initial git clone
, whose last step is git checkout master
. This puts you onto your own (newly-created) branch named master
at the same tip commit as your existing origin/master
, all of which were obtained by the git clone
command. That is, git clone
:
Creates a new, empty repository (a collection of zero commits), with a blank index and an empty work-tree.
Fills the repository from another Git (now you have many commits). The index is still empty / blank, as is the work-tree.
Uses their branch names—their master
, for instance—to create your remote-tracking names, such as origin/master
. Each such name identifies one commit. Over time, you and your Git, plus the other Git from which you are cloning, will arrange for these names to identify some other—usually, newer—commit, though at any time each name will still identify exactly one commit.
Creates your own master
based on the origin/master
your Git just made based on their Git's master
. (All of this complexity is required to understand why your master
and their master
do not always identify the same particular commit. They start out the same, and you'll often make them become the same again, once they become different. They are separate, though! Your origin/master
is your Git's memory of their Git's master
, so your origin/master
follows their master
around. Your own master
is yours, to do with as you will.)
Probably most important, your Git—as the last step of git clone
—extracts the contents of this particular commit, into your index and into your work-tree. That's where the files you will work on came from!
New commits are made from whatever is in the index
At various times, you will modify some file or files in the work-tree, then do:
git add file1 file2 ... fileN # or git add --all, or git add ., etc
git commit # with options if desired
The new commit uses whatever files are stored in the index (not the work-tree!) to make the new commit. The fact that Git uses the index, not the work-tree, is why you had to git add
the changed files: the index started out holding all the files, and it still holds all the files, but the ones in the index did not change when you modified the ones in the work-tree.
What git add path
does is to copy the work-tree file into the index, overwriting the copy that is currently in the index. Hence, by adding the file, you have changed the version in the index (to match the version in the work-tree). This means the next commit you make will have the new version of the file, instead of having the old version of the same file.
This is crucial since git commit
uses the index, not the work-tree, to make the commit! If you never add
a file that's already in the index, that means that the new commit still has the file, but it has the previous version of the file.
Note that, at all times, there are three versions of every file you should be concerned with:
The version in the current commit: git show HEAD:README.txt
, for instance. This version of the file is what's inside the commit. It cannot be changed, because no part of any commit can ever be changed.
You need this to exist in the commit so that later, git clone
or git checkout
can copy it out of the commit, to the index and work-tree. This version of the file is in a special, Git-only format. It is compressed, sometimes extremely well.
The version in the index: git show :README.txt
, for instance. This version is what's in the index / staging-area right now. It can be changed. Whatever is in it right now, will go int the next git commit
you make.
This version of the file is also in the special, Git-only format. The chief distinction between the index version and the HEAD
commit version is that you can overwrite the index version. Whatever is in it at the time you run git commit
, that version will be frozen into that new commit, and hence saved forever (or as long as the new commit continues to exist anyway).
The version in the work-tree: what you get if you run your editor, or compiler, or whatever, on the file. This version is really of very little interest to Git itself, even though it's the only one you can use directly. You can use it because it is not in a special, Git-only format.
This version of the file is mainly useful to Git in terms of git add
: git add
compresses this version into a new, special, Git-only format file that Git stuffs into the index, replacing whatever was in that index slot before. It's useful to you because it's a normal file, but Git does not have to care about that, and hence does not care.
When you run git status
, Git makes two separate comparisons:
First, Git compares the HEAD
commit to the index. Whatever is different here is "staged for commit". That's because, if you make a commit right now, Git will use everything in the index to make the commit. So this new commit—which will become the current commit—will differ from the current commit, by whatever is different in the index.
Second, Git compares the index to the work-tree. Whatever is different here is "not staged for commit". That is, you could use git add
to copy the work-tree file back into the index. If you don't do that, the next commit won't have this version of the file.
Note that you can have all three versions of the file being different! For instance, after you check out some commit, you can modify the work-tree file, copy the modified file into the index, and then modify the work-tree file some more. Now you have changes that are staged for commit, because git show HEAD:file
and git show :file
differ, and changes that are not staged for commit, because git show :file
and the contents of the file that Git doesn't use directly, but is in your work-tree, are different too.
Control bits in the index
There are two control bits you can set on an entry in the index: assume unchanged, and skip worktree. Both of them can be set (or cleared) with git update-index
, with the --assume-unchanged
, --no-assume-unchanged
, --skip-worktree
, and --no-skip-worktree
options.
The "assume unchanged" and "skip worktree" bits don't make Git ignore a file. Instead, they affect how Git goes about comparing the index version of the file to the work-tree version of the file. Setting either bit means that git add .
or git add --all
won't copy the work-tree version into the index, and git status
won't compare the index version to the work-tree version.
It does not change the way that the HEAD
-vs-index comparisons work. It does not change the way git commit
builds a new commit from whatever is in the index. It only affects whether Git bothers to compare the index and work-tree versions, and whether Git automatically copies from the work-tree to the index for en-masse git add
operations.
The two control bits have different intents: assume unchanged is meant for use on machines where git status
is very slow, to speed it up, while skip worktree is meant more for what you are doing. So you should be using --skip-worktree
. However, both should have the same effect in general.
Both bits will skip comparisons of the index contents to the work-tree contents. Git still needs to copy the file from the work-tree into the index to change it, so that if you don't copy the file into the index, it won't change. Each new commit will have the same old version of that file.
Note that if you git checkout
some other commit, or merge commits that you obtained from some other Git, or otherwise change which commit is the current commit, Git must replace the index version of this file with a new version. When Git does that, Git will want to overwrite the work-tree version of the file. If Git needs to overwrite the work-tree version of the file, Git will—regardless of any control bits—first check whether this would overwrite something you never committed.
If changing the current commit requires changing the index version of the file, and you have something uncommitted in your work-tree, none of these control bits stop Git from complaining. That's normal, and useful to you, because it means that you will have to save your (work-tree) version of the file somewhere, and then allow Git to replace your work-tree version with the new version from whichever commit you intend to use as the new current commit. Once that's happened, it's up to you to reconcile your saved work-tree version with the one Git extracted from the now-current commit, and maybe set one of those control bits again.