Git does not really have a notion of "uncommitted file". What it does have is the index and the work-tree.
The main thing Git stores are commits:
Commits are permanent (mostly1), completely-read-only entities stored in a database of sorts (a simple key-value store, really) that allow Git to access the complete snapshot of the source you, or the committer, made when you, or the committer, made that commit. Along with that snapshot, you—I'll leave out the "or the committer" from here on but of course it is implied—get a chance to add your own metadata, specifically, the log message about why you made that commit.
The "true name" of any commit is its hash ID. Git uses the hash ID as the key in the key-value store, to retrieve the commit. Each commit also contains the hash ID of its predecessor or parent commit (or, for merge commits, two or more parent hash IDs—this is what makes them "merge commits").
One commit is always the current commit. That's the one commit you selected (via git checkout
) to work with. Because commits are read-only, you cannot change this commit. What you can do is, at some point, make a new commit. Normally, this new commit will use the current commit as the new commit's parent, and then become the current commit, and this is why you can always get back every file you ever committed: commits are permanent (mostly) and read-only (completely) and remember their parents.
The files stored with a commit—the snapshot you made—are saved in a compressed, Git-only format that is not useful to anything other than Git. So these files must be extracted from each commit before you can use them. Hence Git also has:
A work-tree. Here, Git can extract the files from a commit into the format in which the computer uses them. These files should not be shared across computers, not because it cannot work, but because it can and this just makes for big headaches, as you are discovering.
Since files in the work-tree are stored in the native format, and are used by other programs, Git offers the ability to modify the files—specifically things like line-endings and permissions bits—as they come out of a commit on the way into the work-tree, and as they go from the work-tree into a commit. But there's one more key item and this is where the biggest headaches come from.
The index. This item sits between the current commit, and the work-tree.
The index stores all the files in their special Git-only format. It starts out containing the files as they were when they were committed. The key difference between the commit's copy of the files and the index's copy is that you can change the ones in the index. You change them by replacing them wholesale, using git add
to copy the work-tree file back into the index.
When you make a new commit, Git simply uses whatever is in the index at that time. All the files are already there, all pre-packaged in the Git-only format. This makes committing very fast.
What this also means is that the transformation from Git-only format to "useable by this computer" format, and vice versa, happens on the copy from index to work-tree (which changes files from Git-only to useable) and git add
copy from work-tree to index (which changes useable to Git-only).
This is almost always the slowest-by-far part of dealing with commits and files, so the index keeps track of (indexes!) the work-tree, using OS-specific information. That OS-specific information, found via the OS about the work-tree, goes into the index.
If you share the work-tree and index and .git
files across machines, what happens is that the index itself becomes useless, because the OS-specific work-tree data stored in the index is for the VM or the host, but never for both at the same time.
When the index is correct and describes the work-tree correctly, git status
is fast and accurate. When it's not, the two diffs it must run—see my answer to the question you linked—cannot be done nearly as efficiently. If you use any kinds of file transformations, they must either be re-run, or assumed to have changed files.
The TL;DR of all of this is: Never share a Git repository this way, use the fetch and push mechanisms to share it instead. This is not because it does not work, but rather because it can work, but becomes a horrible experience. The file name case-folding issues you identified are the tip of another whole nightmare iceberg (not directly solved by not-sharing the repository, but at least possible to solve that way).
1You can remove a commit, as long as you also remove all of its children and their children and so on. That is, removing a commit requires a sort of commit-line genocide. It's often a bad idea to do this, and if you are going to do it, you usually have to copy the entire chain of children—but sometimes it's a good idea, and in fact this is what git rebase
does internally.
Note that git commit --amend
does not change a commit. Instead, it just shoves aside (and thus eventually kills off and removes) the existing end-of-chain commit by creating a new replacement end-of-chain commit, using the current commit's parent as the new commit's parent.