summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Steinhardt <ps@pks.im>2020-05-16 13:48:04 +0200
committerPatrick Steinhardt <ps@pks.im>2020-05-16 14:02:42 +0200
commit3f201f75c6580743dfe557f99a196eed246df2bf (patch)
treee825cfaa2b5d57e2aa93235e7a14f954c8599d2c
parent915f88609a6685fbad1f5ad6fa7a5bb320f03138 (diff)
downloadlibgit2-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.c7
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) ||