Thread (4 messages) 4 messages, 2 authors, 2h ago

[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
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help