Why does `.git/index` change when I haven't done anything to my repository?

StackOverflow https://stackoverflow.com/questions/12126247

  •  28-06-2021
  •  | 
  •  

Question

With the latest Debian version of git (I'm using 1.7.2.5), I've noticed that a .git/index file may change mysteriously, without my having performed any operation that I feel should change the repository. (My shell occasionally runs git branch so it can display what branch is checked out, but that shouldn't change anything.) The change results in a .git/index file with the same length as the original, but containing different bits. What causes this change, and how can I stop it?

(The change is inconvenient because it messes things up for the Unison file synchronizer.)

Était-ce utile?

La solution 5

The culprit turned out to be Emacs VC mode: https://emacs.stackexchange.com/questions/38418/could-magit-be-writing-git-index-without-my-intervention

In order to make this text an answer, not a comment, I have to say more. So the correct answer is reproduced here:

Emacs VC uses timers to periodically refresh some information and calls git commands to do so and some of those touch the index.

Provided VC was the cause of this issue, then deleting Git from vc-handled-backends would likely fix it.

Autres conseils

The index file shouldn't just randomly change. That is the staging tree, a buffer between the repository of commits and the working tree. For efficiency, it also stores some metadata about the working tree (the checked out files which you can modify), which would allow faster status or diff results. To see what kind of such information is stored, try to execute git ls-files --debug. This should print, for each file and directory, something like:

path/to/file
  ctime: 1332898839:873326227
  mtime: 1332898839:873326227
  dev: 2052     ino: 4356685
  uid: 1000     gid: 100
  size: 3065    flags: 6c

So, if a file changes in any way on the disk, not as its content, but internal stuff like which inode it's using, it will trigger an update to the index file next time the index is used.

git branch doesn't update the index, since it only checks the .git/HEAD file and the .git/refs/heads and .git/packed-refs files, it doesn't care about the index or the working tree. git diff and git status, on the other hand, do work with the index.

I did an experiment: I copied the current index file, I created a new version of a file making sure that a new inode will be assigned to it (copy, remove original, rename the copy back to the original name), executed git status, and then compared the new index file with the original copy. Two things changed: a line that contained the affected file in it, and the changes were in the bytes right before the filename, and a few bytes right at the end of the index file, probably a timestamp for the last index computation. The overall size of the file remained the same.

Back to your problem, if you're not executing any command that touches the index yourself, then maybe you have another tool that does that for you: an IDE plugin or a file browser extension that knows about git repositories, and which checks the status of git repositories. Or, there's another process that changes the way files are stored on disk, like a disk-defrag utility.

I've come across this issue as well, and I believe it's the interaction between unison and git that is causing the problem. When unison synchronizes the two directories, it doesn't synchronize the ctimes. That means that in one copy of the git repository, say copy 2, the file ctimes don't match the times stored in .git/index. That means that .git/index in copy 2 will get updated the next time you run a git command that stats files. When unison runs, .git/index is copied to copy 1, but then its contents don't match the ctimes there. So the next time a git command is run there, the index is updated. Then unison copies it to copy 2, etc.

I haven't found a reasonable workaround for this. Setting core.trustctime=false doesn't help.

To the extent that .git/index is a cache, it should be omitted from synchronization by unison. But I believe that .git/index is also used to stage files, and one might start that process on one machine and finish it on another, which would require .git/index to be synchronized.

(I know some people think it's odd to synchronize git repos with unison, but the point of unison is that you can switch between working on two different machines and continue exactly where you left off. It's an amazing tool!)

This will probably not be the solution for the author of this question, but in my case the daily autocommit feature of etckeeper was the culprit.

I see the same issue on a setup where I have Unison syncing my home dir (containing 3 git repos) between 2 machines, as well as a cron job that cd's into each repo directory and runs a 'git status' everyday (and emails me if changes are not checked in). My testing indicates it's caused by the fact .git/index stores machine-specific data like the inode number of files[1].

To test this take a repo that is already synchronised and identical on the 2 machines. Copy the .git/index from one machine to the other, e.g. scp -p machineB:/home/me/myrepo/.git/index /home/me/myrepo/.git/index

Now compare the two files and you should see they're identical: sha1sum /home/me/myrepo/.git/index ssh machineB "sha1sum /home/me/myrepo/.git/index"

Now run: git status

Now compare the 2 files again and you'll find they've changed: sha1sum /home/me/myrepo/.git/index ssh machineB "sha1sum /home/me/myrepo/.git/index"

I don't see a solution to this, since you can't use git without running commands like git status which update the index.

[1] https://github.com/git/git/blob/867b1c1bf68363bcfd17667d6d4b9031fa6a1300/Documentation/technical/index-format.txt#L38

Disabling VC in Emacs does not really solve this problem. It only prevents Emacs to run git status by itself, but running it manually will still modify the file .git/index and lead to spurious modifications/conflicts with Unison.

The git mailing list suggests a workaround [1] that works for me:

  • Enable mtime syncing in Unison (times = true)
  • git config core.trustctime false
  • git config core.checkstat minimal

(The git-config options may be set globally of course.)

With these settings, git now only looks at the integral part of the mtime and at the file size when checking whether a file has been modified, and both are synchronized by Unison.

[1] https://marc.info/?l=git&m=157937653401027

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top