[PATCH 2/2] core: add core.convertAwareStatus to opt out of the content check
flat view
HOTtoday
From: Curtis Allen Smith <hidden>
Date: 2026-10-08 20:48:27
Subsystem:
documentation, the rest · Maintainers:
Jonathan Corbet, Linus Torvalds
The previous commit makes an index refresh read and convert a path whose size changed when conversion is active for it, so that "git status" agrees with "git diff" and "git add". Reading costs more than trusting the size, and when the path has a clean filter configured the cost includes running that filter -- Git LFS on a large file, say. Reading is cheap enough on current hardware that agreeing with "git diff" is the better default, but nobody should be stuck with it if their filters are expensive. Add core.convertAwareStatus: true (default) consult the conversion for any path that has one, including paths with a clean filter no-filter consult only the conversions Git performs itself, and decide a path with a clean filter on its size false always treat a size change as a modification, as Git did before Being ordinary configuration, it can equally be given for a single command: git -c core.convertAwareStatus=no-filter status Paths that are not subject to conversion are decided on their size alone in every mode, so this costs nothing in a repository that does not use conversion. Signed-off-by: Curtis Allen Smith <redacted> --- Documentation/config/core.adoc | 22 ++++++++++++++++ environment.c | 14 ++++++++++ environment.h | 7 +++++ read-cache.c | 12 +++++++++ t/t0020-crlf.sh | 47 ++++++++++++++++++++++++++++++++++ 5 files changed, 102 insertions(+)
diff --git a/Documentation/config/core.adoc b/Documentation/config/core.adoc
index 0b697f53f..9737c804f 100644
--- a/Documentation/config/core.adoc
+++ b/Documentation/config/core.adoc@@ -156,6 +156,28 @@ some fields (e.g. JGit); by excluding these fields from the comparison, the `minimal` mode may help interoperability when the same repository is used by these other systems at the same time. +core.convertAwareStatus:: + When a path is subject to content conversion -- the `text` and + `eol` attributes, `core.autocrlf`, a `working-tree-encoding`, + or a clean filter -- the size of the file in the working tree + is not determined by its contents alone, so a change in size + does not prove that the contents changed. When this variable + is missing or set to `true`, Git reads and converts such a + file before reporting it as modified, which keeps 'git status' + in agreement with 'git diff' and 'git add'. When set to + `no-filter`, Git does this only for the conversions it + performs itself, and a path with a clean filter configured + (Git LFS, for example) is reported as modified on a size + change without running the filter. When set to `false`, a + size change is always taken as a modification, which is what + Git did before this variable existed. ++ +Reading the file costs more than trusting its size, so `no-filter` +and `false` trade this consistency for speed in repositories where +running the filter, or reading the file at all, is too expensive. +Paths that are not subject to conversion are decided on the size +alone in every mode. + core.quotePath:: Commands that output paths (e.g. 'ls-files', 'diff'), will quote "unusual" characters in the pathname by enclosing the
diff --git a/environment.c b/environment.c
index c83cf4483..b079262c8 100644
--- a/environment.c
+++ b/environment.c@@ -344,6 +344,19 @@ int git_default_core_config(const char *var, const char *value, var, value); } + if (!strcmp(var, "core.convertawarestatus")) { + int b = git_parse_maybe_bool(value); + if (0 <= b) + cfg->convert_aware_status = b ? CONVERT_AWARE_STATUS_ALL + : CONVERT_AWARE_STATUS_NEVER; + else if (value && !strcasecmp(value, "no-filter")) + cfg->convert_aware_status = CONVERT_AWARE_STATUS_IN_PROCESS; + else + return error(_("invalid value for '%s': '%s'"), + var, value); + return 0; + } + if (!strcmp(var, "core.quotepath")) { quote_path_fully = git_config_bool(var, value); return 0;
@@ -766,6 +779,7 @@ void repo_config_values_init(struct repo_config_values *cfg) cfg->apply_sparse_checkout = 0; cfg->trust_ctime = 1; cfg->check_stat = 1; + cfg->convert_aware_status = CONVERT_AWARE_STATUS_ALL; cfg->zlib_compression_level = Z_BEST_SPEED; cfg->pack_compression_level = Z_DEFAULT_COMPRESSION; cfg->precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
diff --git a/environment.h b/environment.h
index b336459e9..49a88de27 100644
--- a/environment.h
+++ b/environment.h@@ -115,6 +115,12 @@ enum object_creation_mode { OBJECT_CREATION_USES_RENAMES = 1 }; +enum convert_aware_status { + CONVERT_AWARE_STATUS_NEVER = 0, + CONVERT_AWARE_STATUS_IN_PROCESS, + CONVERT_AWARE_STATUS_ALL +}; + struct repo_config_values { /* section "core" config values */ char *attributes_file;
@@ -130,6 +136,7 @@ struct repo_config_values { int apply_sparse_checkout; int trust_ctime; int check_stat; + enum convert_aware_status convert_aware_status; int zlib_compression_level; int pack_compression_level; int precomposed_unicode;
diff --git a/read-cache.c b/read-cache.c
index 8875706d8..2bb27a388 100644
--- a/read-cache.c
+++ b/read-cache.c@@ -554,9 +554,21 @@ static int size_change_is_conclusive(struct index_state *istate, const struct cache_entry *ce, struct stat *st) { + struct repo_config_values *cfg = repo_config_values(the_repository); + struct conv_attrs ca; + + if (cfg->convert_aware_status == CONVERT_AWARE_STATUS_NEVER) + return 1; + if (!S_ISREG(st->st_mode)) return 1; + if (cfg->convert_aware_status == CONVERT_AWARE_STATUS_IN_PROCESS) { + convert_attrs(istate, &ca, ce->name); + if (ca.drv) + return 1; + } + return !would_convert_to_git(istate, ce->name); }
diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh
index 88b728d50..4c127207e 100755
--- a/t/t0020-crlf.sh
+++ b/t/t0020-crlf.sh@@ -442,4 +442,51 @@ test_expect_success 'status sizes a text file by its CRLF pairs, not its CRs' ' ) ' +test_expect_success 'core.convertAwareStatus=false restores the size shortcut' ' + git init convert-aware && + ( + cd convert-aware && + echo "* text eol=lf" >.gitattributes && + printf "one\ntwo\nthree\n" >file.txt && + git add .gitattributes file.txt && + git commit -m initial && + printf "one\r\ntwo\r\nthree\r\n" >file.txt && + + git -c core.convertAwareStatus=false status --porcelain -uno >actual && + echo " M file.txt" >expect && + test_cmp expect actual && + + git -c core.convertAwareStatus=true status --porcelain -uno >actual && + test_must_be_empty actual + ) +' + +test_expect_success 'core.convertAwareStatus=no-filter leaves clean filters alone' ' + git init convert-aware-filter && + ( + cd convert-aware-filter && + write_script stripcr <<-\EOF && + tr -d "\015" + EOF + echo "file.txt filter=stripcr" >.gitattributes && + git config filter.stripcr.clean ./stripcr && + printf "one\ntwo\nthree\n" >file.txt && + git add .gitattributes file.txt && + git commit -m initial && + printf "one\r\ntwo\r\nthree\r\n" >file.txt && + + git -c core.convertAwareStatus=no-filter status --porcelain -uno >actual && + echo " M file.txt" >expect && + test_cmp expect actual && + + git status --porcelain -uno >actual && + test_must_be_empty actual + ) +' + +test_expect_success 'core.convertAwareStatus rejects an unknown value' ' + test_must_fail git -c core.convertAwareStatus=bogus status 2>err && + test_grep "invalid value" err +' + test_done
--
2.53.0