diff options
author | Patrick Steinhardt <ps@pks.im> | 2020-05-16 13:48:04 +0200 |
---|---|---|
committer | Patrick Steinhardt <ps@pks.im> | 2020-05-16 14:02:42 +0200 |
commit | 3f201f75c6580743dfe557f99a196eed246df2bf (patch) | |
tree | e825cfaa2b5d57e2aa93235e7a14f954c8599d2c | |
parent | 915f88609a6685fbad1f5ad6fa7a5bb320f03138 (diff) | |
download | libgit2-3f201f75c6580743dfe557f99a196eed246df2bf.tar.gz |
checkout: fix file being treated as unmodified due to racy index
When trying to determine whether a file changed, we try to avoid heavy
operations by fist taking a look at the index, seeing whether the index
entry is modified already. This doesn't seem to cut it, though, as we
currently have the racy checkout::index::can_disable_pathspec_match test
case: sometimes the files get restored to their original contents,
sometimes they aren't.
The issue is caused by a racy index [1]: in case we modify a file, add
it to the index and then modify it again in-place without changing its
file, then we may end up with a modified file that has the same stat(3P)
info as we've currently got it in its corresponding index entry. The
mitigation for this is to treat files with the same mtime as the index
are treated as racily modified. We already have this logic in place for
the index, but not when doing a checkout.
Fix the issue by only consulting the index entry in case it has an older
mtime as the index. Previously, the following script reliably had at
least 20 failures, while now there is no failure to be observed anymore:
```bash
j=0
for i in $(seq 100)
do
if ! ./libgit2_clar -scheckout::index::can_disable_pathspec_match >/dev/null
then
j=$(($j + 1))
fi
done
echo "Failures: $j"
```
[1]: https://git-scm.com/docs/racy-git
-rw-r--r-- | src/checkout.c | 7 |
1 files changed, 4 insertions, 3 deletions
diff --git a/src/checkout.c b/src/checkout.c index 59ff873dd..272bd3789 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -217,9 +217,10 @@ static bool checkout_is_workdir_modified( ie = git_index_get_bypath(data->index, wditem->path, 0); if (ie != NULL && - git_index_time_eq(&wditem->mtime, &ie->mtime) && - wditem->file_size == ie->file_size && - !is_filemode_changed(wditem->mode, ie->mode, data->respect_filemode)) { + !git_index_entry_newer_than_index(ie, data->index) && + git_index_time_eq(&wditem->mtime, &ie->mtime) && + wditem->file_size == ie->file_size && + !is_filemode_changed(wditem->mode, ie->mode, data->respect_filemode)) { /* The workdir is modified iff the index entry is modified */ return !is_workdir_base_or_new(&ie->id, baseitem, newitem) || |