aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2023-10-23 13:56:37 -0700
committerJunio C Hamano <gitster@pobox.com>2023-10-23 13:56:37 -0700
commit5edbcead426056b54286499149244ae4cbf8b5f7 (patch)
treeea98f4a8da0ff289aa9a87ff2c28893b05d6f8c6
parent626f689f7903b51342cf31ad3792ba5e9e41586d (diff)
parent5143ac07b17e2b025865378fce24cc11ac7bf8b1 (diff)
downloadgit-5edbcead426056b54286499149244ae4cbf8b5f7.tar.gz
Merge branch 'bc/racy-4gb-files'
The index file has room only for lower 32-bit of the file size in the cached stat information, which means cached stat information will have 0 in its sd_size member for a file whose size is multiple of 4GiB. This is mistaken for a racily clean path. Avoid it by storing a bogus sd_size value instead for such files. * bc/racy-4gb-files: Prevent git from rehashing 4GiB files t: add a test helper to truncate files
-rw-r--r--Makefile1
-rw-r--r--statinfo.c20
-rw-r--r--t/helper/test-tool.c1
-rw-r--r--t/helper/test-tool.h1
-rw-r--r--t/helper/test-truncate.c25
-rwxr-xr-xt/t7508-status.sh16
6 files changed, 62 insertions, 2 deletions
diff --git a/Makefile b/Makefile
index 9c6a2f125f..03adcb5a48 100644
--- a/Makefile
+++ b/Makefile
@@ -852,6 +852,7 @@ TEST_BUILTINS_OBJS += test-submodule-nested-repo-config.o
TEST_BUILTINS_OBJS += test-submodule.o
TEST_BUILTINS_OBJS += test-subprocess.o
TEST_BUILTINS_OBJS += test-trace2.o
+TEST_BUILTINS_OBJS += test-truncate.o
TEST_BUILTINS_OBJS += test-urlmatch-normalization.o
TEST_BUILTINS_OBJS += test-userdiff.o
TEST_BUILTINS_OBJS += test-wildmatch.o
diff --git a/statinfo.c b/statinfo.c
index 17bb8966c3..9367ca099c 100644
--- a/statinfo.c
+++ b/statinfo.c
@@ -2,6 +2,22 @@
#include "environment.h"
#include "statinfo.h"
+/*
+ * Munge st_size into an unsigned int.
+ */
+static unsigned int munge_st_size(off_t st_size) {
+ unsigned int sd_size = st_size;
+
+ /*
+ * If the file is an exact multiple of 4 GiB, modify the value so it
+ * doesn't get marked as racily clean (zero).
+ */
+ if (!sd_size && st_size)
+ return 0x80000000;
+ else
+ return sd_size;
+}
+
void fill_stat_data(struct stat_data *sd, struct stat *st)
{
sd->sd_ctime.sec = (unsigned int)st->st_ctime;
@@ -12,7 +28,7 @@ void fill_stat_data(struct stat_data *sd, struct stat *st)
sd->sd_ino = st->st_ino;
sd->sd_uid = st->st_uid;
sd->sd_gid = st->st_gid;
- sd->sd_size = st->st_size;
+ sd->sd_size = munge_st_size(st->st_size);
}
int match_stat_data(const struct stat_data *sd, struct stat *st)
@@ -51,7 +67,7 @@ int match_stat_data(const struct stat_data *sd, struct stat *st)
changed |= INODE_CHANGED;
#endif
- if (sd->sd_size != (unsigned int) st->st_size)
+ if (sd->sd_size != munge_st_size(st->st_size))
changed |= DATA_CHANGED;
return changed;
diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
index 9010ac6de7..876cd2dc31 100644
--- a/t/helper/test-tool.c
+++ b/t/helper/test-tool.c
@@ -86,6 +86,7 @@ static struct test_cmd cmds[] = {
{ "submodule-nested-repo-config", cmd__submodule_nested_repo_config },
{ "subprocess", cmd__subprocess },
{ "trace2", cmd__trace2 },
+ { "truncate", cmd__truncate },
{ "userdiff", cmd__userdiff },
{ "urlmatch-normalization", cmd__urlmatch_normalization },
{ "xml-encode", cmd__xml_encode },
diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
index f134f96b97..70dd4eba11 100644
--- a/t/helper/test-tool.h
+++ b/t/helper/test-tool.h
@@ -79,6 +79,7 @@ int cmd__submodule_config(int argc, const char **argv);
int cmd__submodule_nested_repo_config(int argc, const char **argv);
int cmd__subprocess(int argc, const char **argv);
int cmd__trace2(int argc, const char **argv);
+int cmd__truncate(int argc, const char **argv);
int cmd__userdiff(int argc, const char **argv);
int cmd__urlmatch_normalization(int argc, const char **argv);
int cmd__xml_encode(int argc, const char **argv);
diff --git a/t/helper/test-truncate.c b/t/helper/test-truncate.c
new file mode 100644
index 0000000000..3931deaec7
--- /dev/null
+++ b/t/helper/test-truncate.c
@@ -0,0 +1,25 @@
+#include "test-tool.h"
+#include "git-compat-util.h"
+
+/*
+ * Truncate a file to the given size.
+ */
+int cmd__truncate(int argc, const char **argv)
+{
+ char *p = NULL;
+ uintmax_t sz = 0;
+ int fd = -1;
+
+ if (argc != 3)
+ die("expected filename and size");
+
+ sz = strtoumax(argv[2], &p, 0);
+ if (*p)
+ die("invalid size");
+
+ fd = xopen(argv[1], O_WRONLY | O_CREAT, 0600);
+
+ if (ftruncate(fd, (off_t) sz) < 0)
+ die_errno("failed to truncate file");
+ return 0;
+}
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
index 6928fd89f5..6c46648e11 100755
--- a/t/t7508-status.sh
+++ b/t/t7508-status.sh
@@ -1745,4 +1745,20 @@ test_expect_success 'slow status advice when core.untrackedCache true, and fsmon
)
'
+test_expect_success EXPENSIVE 'status does not re-read unchanged 4 or 8 GiB file' '
+ (
+ mkdir large-file &&
+ cd large-file &&
+ # Files are 2 GiB, 4 GiB, and 8 GiB sparse files.
+ test-tool truncate file-a 0x080000000 &&
+ test-tool truncate file-b 0x100000000 &&
+ test-tool truncate file-c 0x200000000 &&
+ # This will be slow.
+ git add file-a file-b file-c &&
+ git commit -m "add large files" &&
+ git diff-index HEAD file-a file-b file-c >actual &&
+ test_must_be_empty actual
+ )
+'
+
test_done