I'm looking for a scripting-suitable plumbing command (or commands) that are equivalent to the high-level porcelain command git checkout -- .
My initial thought would be to use git checkout-index --all --force
, however this does not fully restore the working directory in the case of core.autocrlf = input
:
#!/bin/bash
set -ex
rm -rf repo
git init repo
cd repo
git config --local core.autocrlf input
python3 -c 'open("foo", "wb").write(b"1\r\n2\r\n")'
git add foo
python3 -c 'open("foo", "wb").write(b"3\r\n4\r\n")'
git checkout-index --all --force
echo 'I expect this `git status` to have no modifications'
git status
This produces the following output:
+ rm -rf repo
+ git init repo
Initialized empty Git repository in /tmp/foo/repo/.git/
+ cd repo
+ git config --local core.autocrlf input
+ python3 -c 'open("foo", "wb").write(b"1\r\n2\r\n")'
+ git add foo
warning: CRLF will be replaced by LF in foo.
The file will have its original line endings in your working directory.
+ python3 -c 'open("foo", "wb").write(b"3\r\n4\r\n")'
+ git checkout-index --all --force
+ echo 'I expect this `git status` to have no modifications'
I expect this `git status` to have no modifications
+ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: foo
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: foo
Note that git checkout -- .
correctly restores the working directory to the contents of the index
, even in this case.
This appears to be a bug in git checkout-index
: it doesn't know how to deal with the fact that the work-tree matches the index after all.
If we add git ls-files --stage --debug
, we see that after the first git add
, the index contains:
100644 1191247b6d9a206f6ba3d8ac79e26d041dd86941 0 foo
ctime: <ct>
mtime: <mt>
dev: <d> ino: <i>
uid: <u> gid: <g>
size: 6 flags: 0
(I have replaced irrelevant variable values with <...>
here). Note that the listed size is 6, not 4: this is the size of the work-tree file, which really is 6 bytes long because it contains \r\n
line endings.
Next, we do:
python3 -c 'open("foo", "wb").write(b"3\r\n4\r\n")'
which replaces the file, rewriting the existing inode with new time stamps and new contents. The new contents are 6 bytes long.
Then we do:
git checkout-index [arguments]
which overwrites the work-tree file with the index contents, just as git checkout
does. The file is now 4 bytes long ... but the index still says the file is 6 bytes long.
If we rename foo
, so that git checkout-index
has to re-create foo
with a different inode number, we find that the stat
information in the index is still out of date. In other words, even though git checkout-index
is re-writing foo
, it never updates the cached stat information. So git status
's internal index-vs-work-tree diff uses the fast path (compare cached stat data to stat data for actual in-file-system file) and assumes it must be modified.
(Oddly enough, git update-index --refresh -q
won't touch the cache info either, and I am not sure why not.)
The solution would seem to be to use git checkout
directly, at least until git checkout-index
is fixed.