All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 1/2] utf8.c: Prepare workaround for iconv under macOS 14/15
@ 2026-01-11 19:51 tboegi
  2026-01-12  4:58 ` Junio C Hamano
  0 siblings, 1 reply; 14+ messages in thread
From: tboegi @ 2026-01-11 19:51 UTC (permalink / raw)
  To: tboegi, git, l.s.r

From: Torsten Bögershausen <tboegi@web.de>

MacOS14 (Sonoma) has started to ship an iconv library with bugs.
The same bugs exists even in MacOS 15 (Sequoia)

A bug report running the Git test suite says:

three tests of t3900 fail on macOS 26.1 for me:

  not ok 17 - ISO-2022-JP should be shown in UTF-8 now
  not ok 25 - ISO-2022-JP should be shown in UTF-8 now
  not ok 38 - commit --fixup into ISO-2022-JP from UTF-8

Here's the verbose output of the first one:

----- snip! -----
expecting success of 3900.17 'ISO-2022-JP should be shown in UTF-8 now':
                compare_with ISO-2022-JP "$TEST_DIRECTORY"/t3900/2-UTF-8.txt

--- /Users/x/src/git/t/t3900/2-UTF-8.txt 2024-10-01 19:43:24.605230684 +0000
+++ current     2025-12-08 21:52:45.786161909 +0000
@@ -1,4 +1,4 @@
 はれひほふ

 しているのが、いるので。
-濱浜ほれぷりぽれまびぐりろへ。
+濱浜ほれぷりぽれまび$0$j$m$X!#
not ok 17 - ISO-2022-JP should be shown in UTF-8 now
1..17
----- snap! -----

compare_with runs git show to display a commit message, which in this
case here was encoded using ISO-2022-JP and is supposed to be reencoded
to UTF-8, but git show only does that half-way -- the "$0$j$m$X!#" part
is from the original ISO-2022-JP representation.

That botched conversion is done by utf8.c::reencode_string_iconv().  It
calls iconv(3) to do the actual work, initially with an output buffer of
the same size as the input.  If the output needs more space the function
enlarges the buffer and calls iconv(3) again.

iconv(3) won't tell us how much space it needs, but it will report what
part it already managed to convert, so we can increase the buffer and
continue from there.  ISO-2022-JP has escape codes for switching between
character sets, so it's a stateful encoding.  I guess the iconv(3) on my
machine forgets the state at the end of part one and then messes up part
two.

[end of citation]

Working around the buggy iconv shipped with the OS can be done in
two  ways:
a) Link Git against a different version of iconv
b) Improve the handling when iconv needs a larger output buffer

a) is already done by default when either Fink [1] or MacPorts [2]
   or Homebrew [3] is installed.
b) is implemented here, in case that no fixed iconv is available:
   When the output buffer is too short, increase it (as before)
   and start from scratch (this is new).

This workound needs to be enabled with
'#define ICONV_RESTART_RESET'
and a makefile knob will be added in the next commit

Suggested-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Torsten Bögershausen <tboegi@web.de>

[1] https://www.finkproject.org/
[2] https://www.macports.org/
[3] https://brew.sh/

Signed-off-by: Torsten Bögershausen <tboegi@web.de>
---
 utf8.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/utf8.c b/utf8.c
index 35a0251939..96460cc414 100644
--- a/utf8.c
+++ b/utf8.c
@@ -515,6 +515,19 @@ char *reencode_string_iconv(const char *in, size_t insz, iconv_t conv,
 			out = xrealloc(out, outalloc);
 			outpos = out + sofar;
 			outsz = outalloc - sofar - 1;
+#ifdef ICONV_RESTART_RESET
+			/*
+			 * If iconv(3) messes up piecemeal conversions
+			 * then restore the original pointers, sizes,
+			 * and converter state, then retry converting
+			 * the full string using the reallocated buffer.
+			 */
+			insz += cp - (iconv_ibp)in; /* Restore insz */
+			cp = (iconv_ibp)in;         /* original start value */
+			outpos = out + bom_len;     /* original start value */
+			outsz = outalloc - bom_len - 1; /* new len */
+			iconv(conv, NULL, NULL, NULL, NULL); /* reset iconv machinery */
+#endif
 		}
 		else {
 			*outpos = '\0';
-- 
2.50.0.rc0.46.g7014b55638.dirty


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 1/2] utf8.c: Prepare workaround for iconv under macOS 14/15
  2026-01-11 19:51 [PATCH v2 1/2] utf8.c: Prepare workaround for iconv under macOS 14/15 tboegi
@ 2026-01-12  4:58 ` Junio C Hamano
  2026-01-12 16:25   ` [PATCH v3 0/2] Workaround " tboegi
                     ` (4 more replies)
  0 siblings, 5 replies; 14+ messages in thread
From: Junio C Hamano @ 2026-01-12  4:58 UTC (permalink / raw)
  To: tboegi; +Cc: git, l.s.r

tboegi@web.de writes:

> From: Torsten Bögershausen <tboegi@web.de>
>
> MacOS14 (Sonoma) has started to ship an iconv library with bugs.
> The same bugs exists even in MacOS 15 (Sequoia)
>
> A bug report running the Git test suite says:
>
> three tests of t3900 fail on macOS 26.1 for me:
>
>   not ok 17 - ISO-2022-JP should be shown in UTF-8 now
>   not ok 25 - ISO-2022-JP should be shown in UTF-8 now
>   not ok 38 - commit --fixup into ISO-2022-JP from UTF-8
>
> Here's the verbose output of the first one:
>
> ----- snip! -----

Doesn't this tell "git am" that your log message ends here, and ...

> expecting success of 3900.17 'ISO-2022-JP should be shown in UTF-8 now':
>                 compare_with ISO-2022-JP "$TEST_DIRECTORY"/t3900/2-UTF-8.txt
>
> --- /Users/x/src/git/t/t3900/2-UTF-8.txt 2024-10-01 19:43:24.605230684 +0000
> +++ current     2025-12-08 21:52:45.786161909 +0000

... makes the tool to apply the patch to file "current"?

> @@ -1,4 +1,4 @@
>  はれひほふ
>
>  しているのが、いるので。
> -濱浜ほれぷりぽれまびぐりろへ。
> +濱浜ほれぷりぽれまび$0$j$m$X!#
> not ok 17 - ISO-2022-JP should be shown in UTF-8 now
> 1..17
> ----- snap! -----

IOW, indent the displayed material used as an example in the
proposed log message.

> compare_with runs git show to display a commit message, which in this
> case here was encoded using ISO-2022-JP and is supposed to be reencoded
> to UTF-8, but git show only does that half-way -- the "$0$j$m$X!#" part
> is from the original ISO-2022-JP representation.
>
> That botched conversion is done by utf8.c::reencode_string_iconv().  It
> calls iconv(3) to do the actual work, initially with an output buffer of
> the same size as the input.  If the output needs more space the function
> enlarges the buffer and calls iconv(3) again.
>
> iconv(3) won't tell us how much space it needs, but it will report what
> part it already managed to convert, so we can increase the buffer and
> continue from there.  ISO-2022-JP has escape codes for switching between
> character sets, so it's a stateful encoding.  I guess the iconv(3) on my
> machine forgets the state at the end of part one and then messes up part
> two.
>
> [end of citation]
>
> Working around the buggy iconv shipped with the OS can be done in
> two  ways:
> a) Link Git against a different version of iconv
> b) Improve the handling when iconv needs a larger output buffer
>
> a) is already done by default when either Fink [1] or MacPorts [2]
>    or Homebrew [3] is installed.
> b) is implemented here, in case that no fixed iconv is available:
>    When the output buffer is too short, increase it (as before)
>    and start from scratch (this is new).
>
> This workound needs to be enabled with
> '#define ICONV_RESTART_RESET'
> and a makefile knob will be added in the next commit
>
> Suggested-by: René Scharfe <l.s.r@web.de>
> Signed-off-by: Torsten Bögershausen <tboegi@web.de>
>
> [1] https://www.finkproject.org/
> [2] https://www.macports.org/
> [3] https://brew.sh/
>
> Signed-off-by: Torsten Bögershausen <tboegi@web.de>
> ---
>  utf8.c | 13 +++++++++++++
>  1 file changed, 13 insertions(+)
>
> diff --git a/utf8.c b/utf8.c
> index 35a0251939..96460cc414 100644
> --- a/utf8.c
> +++ b/utf8.c
> @@ -515,6 +515,19 @@ char *reencode_string_iconv(const char *in, size_t insz, iconv_t conv,
>  			out = xrealloc(out, outalloc);
>  			outpos = out + sofar;
>  			outsz = outalloc - sofar - 1;
> +#ifdef ICONV_RESTART_RESET
> +			/*
> +			 * If iconv(3) messes up piecemeal conversions
> +			 * then restore the original pointers, sizes,
> +			 * and converter state, then retry converting
> +			 * the full string using the reallocated buffer.
> +			 */
> +			insz += cp - (iconv_ibp)in; /* Restore insz */
> +			cp = (iconv_ibp)in;         /* original start value */
> +			outpos = out + bom_len;     /* original start value */
> +			outsz = outalloc - bom_len - 1; /* new len */
> +			iconv(conv, NULL, NULL, NULL, NULL); /* reset iconv machinery */
> +#endif
>  		}
>  		else {
>  			*outpos = '\0';

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v3 0/2] Workaround for iconv under macOS 14/15
  2026-01-12  4:58 ` Junio C Hamano
@ 2026-01-12 16:25   ` tboegi
  2026-01-12 16:25   ` [PATCH v3 1/2] utf8.c: Prepare workaround " tboegi
                     ` (3 subsequent siblings)
  4 siblings, 0 replies; 14+ messages in thread
From: tboegi @ 2026-01-12 16:25 UTC (permalink / raw)
  To: tboegi, git, l.s.r

From: Torsten Bögershausen <tboegi@web.de>

Changes since v1:
  Re-fined commit message (thanks René)
  Installed a fresh Fink. It lives under /opt/sw now (/sw in older versions)
  However, people can choose other places, I did use the default.
  Make more clear that libiconv from Homebrew, Macports or Fink can be used.
Changes since v2:
  Even more details in commit message, and now
  git am works (Thanks Junio for the patience)


Torsten Bögershausen (2):
  utf8.c: Prepare workaround for iconv under macOS 14/15
  utf8.c: Enable workaround for iconv under macOS 14/15

 Makefile         | 16 ++++++++++++++++
 config.mak.uname |  1 +
 utf8.c           | 13 +++++++++++++
 3 files changed, 30 insertions(+)

-- 
2.50.0.rc0.46.g7014b55638.dirty


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v3 1/2] utf8.c: Prepare workaround for iconv under macOS 14/15
  2026-01-12  4:58 ` Junio C Hamano
  2026-01-12 16:25   ` [PATCH v3 0/2] Workaround " tboegi
@ 2026-01-12 16:25   ` tboegi
  2026-01-12 16:25   ` [PATCH v3 2/2] utf8.c: Enable " tboegi
                     ` (2 subsequent siblings)
  4 siblings, 0 replies; 14+ messages in thread
From: tboegi @ 2026-01-12 16:25 UTC (permalink / raw)
  To: tboegi, git, l.s.r

From: Torsten Bögershausen <tboegi@web.de>

MacOS14 (Sonoma) has started to ship an iconv library with bugs.
The same bugs exists even in MacOS 15 (Sequoia)

A bug report running the Git test suite says:

three tests of t3900 fail on macOS 26.1 for me:

  not ok 17 - ISO-2022-JP should be shown in UTF-8 now
  not ok 25 - ISO-2022-JP should be shown in UTF-8 now
  not ok 38 - commit --fixup into ISO-2022-JP from UTF-8

Here's the verbose output of the first one:

=================
expecting success of 3900.17 'ISO-2022-JP should be shown in UTF-8 now':
                compare_with ISO-2022-JP "$TEST_DIRECTORY"/t3900/2-UTF-8.txt

 --- /Users/x/src/git/t/t3900/2-UTF-8.txt 2024-10-01 19:43:24.605230684 +0000
 +++ current     2025-12-08 21:52:45.786161909 +0000
@@ -1,5 +1,5 @@
 はれひほふ

 しているのが、いるので。
 -濱浜ほれぷりぽれまびぐりろへ。
 +濱浜ほれぷりぽれまび$0$j$m$X!#
not ok 17 - ISO-2022-JP should be shown in UTF-8 now
1..17
=================

compare_with runs git show to display a commit message, which in this
case here was encoded using ISO-2022-JP and is supposed to be reencoded
to UTF-8, but git show only does that half-way -- the "$0$j$m$X!#" part
is from the original ISO-2022-JP representation.

That botched conversion is done by utf8.c::reencode_string_iconv().  It
calls iconv(3) to do the actual work, initially with an output buffer of
the same size as the input.  If the output needs more space the function
enlarges the buffer and calls iconv(3) again.

iconv(3) won't tell us how much space it needs, but it will report what
part it already managed to convert, so we can increase the buffer and
continue from there.  ISO-2022-JP has escape codes for switching between
character sets, so it's a stateful encoding.  I guess the iconv(3) on my
machine forgets the state at the end of part one and then messes up part
two.

[end of citation]

Working around the buggy iconv shipped with the OS can be done in
two  ways:
a) Link Git against a different version of iconv
b) Improve the handling when iconv needs a larger output buffer

a) is already done by default when either Fink [1] or MacPorts [2]
   or Homebrew [3] is installed.
b) is implemented here, in case that no fixed iconv is available:
   When the output buffer is too short, increase it (as before)
   and start from scratch (this is new).

This workound needs to be enabled with
'#define ICONV_RESTART_RESET'
and a makefile knob will be added in the next commit

Suggested-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Torsten Bögershausen <tboegi@web.de>

[1] https://www.finkproject.org/
[2] https://www.macports.org/
[3] https://brew.sh/

Signed-off-by: Torsten Bögershausen <tboegi@web.de>
---
 utf8.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/utf8.c b/utf8.c
index 35a0251939..96460cc414 100644
--- a/utf8.c
+++ b/utf8.c
@@ -515,6 +515,19 @@ char *reencode_string_iconv(const char *in, size_t insz, iconv_t conv,
 			out = xrealloc(out, outalloc);
 			outpos = out + sofar;
 			outsz = outalloc - sofar - 1;
+#ifdef ICONV_RESTART_RESET
+			/*
+			 * If iconv(3) messes up piecemeal conversions
+			 * then restore the original pointers, sizes,
+			 * and converter state, then retry converting
+			 * the full string using the reallocated buffer.
+			 */
+			insz += cp - (iconv_ibp)in; /* Restore insz */
+			cp = (iconv_ibp)in;         /* original start value */
+			outpos = out + bom_len;     /* original start value */
+			outsz = outalloc - bom_len - 1; /* new len */
+			iconv(conv, NULL, NULL, NULL, NULL); /* reset iconv machinery */
+#endif
 		}
 		else {
 			*outpos = '\0';
-- 
2.50.0.rc0.46.g7014b55638.dirty


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 2/2] utf8.c: Enable workaround for iconv under macOS 14/15
  2026-01-12  4:58 ` Junio C Hamano
  2026-01-12 16:25   ` [PATCH v3 0/2] Workaround " tboegi
  2026-01-12 16:25   ` [PATCH v3 1/2] utf8.c: Prepare workaround " tboegi
@ 2026-01-12 16:25   ` tboegi
  2026-10-12 17:56   ` [PATCH v3/RFC 0/1] read-cache: do not trust a size change when conversion tboegi
  2026-10-12 17:56   ` [PATCH v3/RFC 1/1] read-cache: do not trust a size change when conversion is active tboegi
  4 siblings, 0 replies; 14+ messages in thread
From: tboegi @ 2026-01-12 16:25 UTC (permalink / raw)
  To: tboegi, git, l.s.r

From: Torsten Bögershausen <tboegi@web.de>

The previous commit introduced a workaround in utf8.c to deal
with broken iconv implementations.

It is enabled when a MacOS version is used that has a buggy
iconv library and there is no external library provided
(and linked against) from neither MacPorts nor Homebrew nor Fink.
For Homebrew, MacPorts and Fink we check if libiconv exist.
Introduce 2 new macros: HAS_GOOD_LIBICONV and NEEDS_GOOD_LIBICONV.

For Homebrew HAS_GOOD_LIBICONV is set when the libiconv directory
exist.
MacPorts can be installed with or without libiconv, so check if
libiconv.dylib exists (which is a softlink)

Fink compiles and installs libiconv by default.
Note that a fresh installation of Fink now defaults to /opt/sw.
Older versions used /sw as default, so leave the check and setting
of BASIC_CFLAGS and BASIC_LDFLAGS as is.
For the new default check for the existance of /opt/sw as well.
Add a check for /opt/sw/lib/libiconv.dylib which sets HAS_GOOD_LIBICONV

Signed-off-by: Torsten Bögershausen <tboegi@web.de>
---
 Makefile         | 16 ++++++++++++++++
 config.mak.uname |  1 +
 2 files changed, 17 insertions(+)

diff --git a/Makefile b/Makefile
index b7eba509c6..8aa489f3b6 100644
--- a/Makefile
+++ b/Makefile
@@ -1687,11 +1687,21 @@ ifeq ($(uname_S),Darwin)
 			BASIC_CFLAGS += -I/sw/include
 			BASIC_LDFLAGS += -L/sw/lib
                 endif
+                ifeq ($(shell test -d /opt/sw/lib && echo y),y)
+			BASIC_CFLAGS += -I/opt/sw/include
+			BASIC_LDFLAGS += -L/opt/sw/lib
+			ifeq ($(shell test -e /opt/sw/lib/libiconv.dylib && echo y),y)
+				HAS_GOOD_LIBICONV = Yes
+			endif
+                endif
         endif
         ifndef NO_DARWIN_PORTS
                 ifeq ($(shell test -d /opt/local/lib && echo y),y)
 			BASIC_CFLAGS += -I/opt/local/include
 			BASIC_LDFLAGS += -L/opt/local/lib
+			ifeq ($(shell test -e /opt/local/lib/libiconv.dylib && echo y),y)
+				HAS_GOOD_LIBICONV = Yes
+			endif
                 endif
         endif
         ifndef NO_APPLE_COMMON_CRYPTO
@@ -1714,6 +1724,7 @@ endif
 ifdef USE_HOMEBREW_LIBICONV
 ifeq ($(shell test -d $(HOMEBREW_PREFIX)/opt/libiconv && echo y),y)
 	ICONVDIR ?= $(HOMEBREW_PREFIX)/opt/libiconv
+	HAS_GOOD_LIBICONV = Yes
 endif
 endif
 endif
@@ -1859,6 +1870,11 @@ ifndef NO_ICONV
                 endif
 		EXTLIBS += $(ICONV_LINK) -liconv
         endif
+        ifdef NEEDS_GOOD_LIBICONV
+        ifndef HAS_GOOD_LIBICONV
+                BASIC_CFLAGS += -DICONV_RESTART_RESET
+        endif
+        endif
 endif
 ifdef ICONV_OMITS_BOM
 	BASIC_CFLAGS += -DICONV_OMITS_BOM
diff --git a/config.mak.uname b/config.mak.uname
index 38b35af366..3c35ae33a3 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -157,6 +157,7 @@ ifeq ($(uname_S),Darwin)
         endif
         ifeq ($(shell test "$(DARWIN_MAJOR_VERSION)" -ge 24 && echo 1),1)
 		USE_HOMEBREW_LIBICONV = UnfortunatelyYes
+		NEEDS_GOOD_LIBICONV = UnfortunatelyYes
         endif
 
 	# The builtin FSMonitor on MacOS builds upon Simple-IPC.  Both require
-- 
2.50.0.rc0.46.g7014b55638.dirty


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3/RFC 0/1] read-cache: do not trust a size change when conversion
  2026-01-12  4:58 ` Junio C Hamano
                     ` (2 preceding siblings ...)
  2026-01-12 16:25   ` [PATCH v3 2/2] utf8.c: Enable " tboegi
@ 2026-10-12 17:56   ` tboegi
  2026-10-12 17:56   ` [PATCH v3/RFC 1/1] read-cache: do not trust a size change when conversion is active tboegi
  4 siblings, 0 replies; 14+ messages in thread
From: tboegi @ 2026-10-12 17:56 UTC (permalink / raw)
  To: tboegi, git, curtis.allen.smith

From: Torsten Bögershausen <tboegi@web.de>

- Pick up the patch from Curtis Allen Smith
- Reduce the commit message and especially the code changes
- All tests pass here.
- The commit message feels still somewhat bumpy - better suggestions welcome.
- The original series add a configuration switch - that is removed
- I took ownwership of this patch and add a Signed-Off Curtis Allen Smith,
  is this OK ?
- All in all this is not the final version, trying to move things forward

Torsten Bögershausen (1):
  read-cache: do not trust a size change when conversion is active

 read-cache.c    | 25 ++++++++++++++++++++++++-
 t/t0020-crlf.sh | 45 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 69 insertions(+), 1 deletion(-)

-- 
2.50.0.rc0.46.g7014b55638


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v3/RFC 1/1] read-cache: do not trust a size change when conversion is active
  2026-01-12  4:58 ` Junio C Hamano
                     ` (3 preceding siblings ...)
  2026-10-12 17:56   ` [PATCH v3/RFC 0/1] read-cache: do not trust a size change when conversion tboegi
@ 2026-10-12 17:56   ` tboegi
  2026-10-12 19:11     ` Junio C Hamano
                       ` (2 more replies)
  4 siblings, 3 replies; 14+ messages in thread
From: tboegi @ 2026-10-12 17:56 UTC (permalink / raw)
  To: tboegi, git, curtis.allen.smith

From: Torsten Bögershausen <tboegi@web.de>

"git status" can report a file as modified while "git diff" and
"git add", which both run the clean filter, agree its contents are
unchanged:

	git init t && cd t
	printf '* text eol=lf\n' >.gitattributes
	printf 'one\ntwo\nthree\n' >file.txt
	git add . && git commit -m init

	printf 'one\r\ntwo\r\nthree\r\n' >file.txt

	git status --short      # ->  M file.txt
	git diff                # -> empty
	git add file.txt        # -> updates the index

Note that "clean filter" here means the "clean filter" and CRLF conversion,
and all converting of a file in the worktree into the index.

Problems that have been found in real live:
  Tools that rewrites unchanged files in native line endings e.g.
    javadoc, code generators, formatters and some editors change
    the size of every file it touches.
  Editors that insist to replace 4 space with a TAB,
    even if the file is changed manually forth and back -
    Using a clean filter that changes the TAB back into space when
    committing will cause the same confusion.

`git status` and `git diff` disagree because ie_modified()
in read-cache.c concludes that a file is modified
as soon as the size has changed.
This had been true when Git was young, before the clean filter
and CRLF conversion had been introduced.

Introduce size_change_is_conclusive() in read-cache.c, which calls
would_convert_to_git().
The content of the file in the worktree is now run through all filters
and CRLF conversion whenever the size is different.
After that, the new size (and mtime) is stored in the index
together with the modified status.

Technical correctness vs performance considerations:
Instead of running the chain of
`git status` `git diff` (hm, nothing changed ?) `git add`
we now handle everything in `git status`, which may give a slower
execution time for this command.
Different measurements came up with 20ms per file which are now
spend in `git status` instead of `git add`.

Signed-off-by: Curtis Allen Smith <curtis.allen.smith@gmail.com>
Signed-off-by: Torsten Bögershausen <tboegi@web.de>
---
 read-cache.c    | 25 ++++++++++++++++++++++++-
 t/t0020-crlf.sh | 45 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 69 insertions(+), 1 deletion(-)

diff --git a/read-cache.c b/read-cache.c
index c4cf08a3a3..9aebe4c394 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -9,6 +9,7 @@
 
 #include "git-compat-util.h"
 #include "config.h"
+#include "convert.h"
 #include "date.h"
 #include "diff.h"
 #include "diffcore.h"
@@ -438,6 +439,27 @@ int ie_match_stat(struct index_state *istate,
 	return changed;
 }
 
+/*
+ * A difference between the size of the file in the working tree and the
+ * size recorded for it in the index proves that the contents changed
+ * only as long as the two are byte-for-byte comparable.  That stops
+ * being true as soon as the path is run through a clean filter:
+ * rewriting a file with CRLF endings under "text eol=lf", for example,
+ * changes its size in the working tree without changing the blob Git
+ * would record for it.  For such a path the only way to tell is to read
+ * the contents and convert them, which is what we already do when only
+ * the mtime changed.
+ */
+static int size_change_is_conclusive(struct index_state *istate,
+				     const struct cache_entry *ce,
+				     struct stat *st)
+{
+	if (!S_ISREG(st->st_mode))
+		return 1;
+
+	return !would_convert_to_git(istate, ce->name);
+}
+
 int ie_modified(struct index_state *istate,
 		const struct cache_entry *ce,
 		struct stat *st, unsigned int options)
@@ -480,7 +502,8 @@ int ie_modified(struct index_state *istate,
 	     */
 	    (!S_ISLNK(st->st_mode) || ce->ce_stat_data.sd_size != MAX_PATH) &&
 #endif
-	    (S_ISGITLINK(ce->ce_mode) || ce->ce_stat_data.sd_size != 0))
+	    (S_ISGITLINK(ce->ce_mode) || ce->ce_stat_data.sd_size != 0) &&
+	    size_change_is_conclusive(istate, ce, st))
 		return changed;
 
 	changed_fs = ce_modified_check_fs(istate, ce, st);
diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh
index fd1cae09ed..88b728d509 100755
--- a/t/t0020-crlf.sh
+++ b/t/t0020-crlf.sh
@@ -397,4 +397,49 @@ test_expect_success 'New CRLF file gets LF in repo' '
 	test_cmp alllf alllf2
 '
 
+test_expect_success 'status does not report a CRLF-only rewrite as modified' '
+	git init eol-status &&
+	(
+		cd eol-status &&
+		echo "* text eol=lf" >.gitattributes &&
+		printf "one\ntwo\nthree\n" >file.txt &&
+		git add .gitattributes file.txt &&
+		git commit -m initial &&
+
+		# a generator rewrites the file with CRLF, same content
+		printf "one\r\ntwo\r\nthree\r\n" >file.txt &&
+		git status --porcelain -uno >actual &&
+		test_must_be_empty actual &&
+		git diff --exit-code &&
+
+		# a real change is still reported
+		printf "one\r\ntwo\r\nfour\r\n" >file.txt &&
+		git status --porcelain -uno >actual &&
+		echo " M file.txt" >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'status sizes a text file by its CRLF pairs, not its CRs' '
+	git init eol-status-lone-cr &&
+	(
+		cd eol-status-lone-cr &&
+		echo "* text eol=lf" >.gitattributes &&
+		printf "one\rtwo\nthree\n" >file.txt &&
+		git add .gitattributes file.txt &&
+		git commit -m initial &&
+
+		# "git add" keeps the lone CR and drops the others
+		printf "one\rtwo\r\nthree\r\n" >file.txt &&
+		git status --porcelain -uno >actual &&
+		test_must_be_empty actual &&
+
+		# the converted size matches the blob, the content does not
+		printf "one\rtwo\nthrEE\r\n" >file.txt &&
+		git status --porcelain -uno >actual &&
+		echo " M file.txt" >expect &&
+		test_cmp expect actual
+	)
+'
+
 test_done
-- 
2.50.0.rc0.46.g7014b55638


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v3/RFC 1/1] read-cache: do not trust a size change when conversion is active
  2026-10-12 17:56   ` [PATCH v3/RFC 1/1] read-cache: do not trust a size change when conversion is active tboegi
@ 2026-10-12 19:11     ` Junio C Hamano
  2026-10-13  1:29       ` Junio C Hamano
  2026-10-13  5:34     ` Johannes Sixt
  2026-10-13  6:58     ` [PATCH v4] " Curtis Allen Smith
  2 siblings, 1 reply; 14+ messages in thread
From: Junio C Hamano @ 2026-10-12 19:11 UTC (permalink / raw)
  To: tboegi; +Cc: git, curtis.allen.smith

tboegi@web.de writes:

> From: Torsten Bögershausen <tboegi@web.de>
>
> "git status" can report a file as modified while "git diff" and
> "git add", which both run the clean filter, agree its contents are
> unchanged:
>
> 	git init t && cd t
> 	printf '* text eol=lf\n' >.gitattributes
> 	printf 'one\ntwo\nthree\n' >file.txt
> 	git add . && git commit -m init
>
> 	printf 'one\r\ntwo\r\nthree\r\n' >file.txt
>
> 	git status --short      # ->  M file.txt
> 	git diff                # -> empty
> 	git add file.txt        # -> updates the index
>
> Note that "clean filter" here means the "clean filter" and CRLF conversion,
> and all converting of a file in the worktree into the index.
>
> Problems that have been found in real live:

"in real life"?

>   Tools that rewrites unchanged files in native line endings e.g.
>     javadoc, code generators, formatters and some editors change
>     the size of every file it touches.

That is not just size but contents are changed, no?

>   Editors that insist to replace 4 space with a TAB,
>     even if the file is changed manually forth and back -

"back and forth"?

>     Using a clean filter that changes the TAB back into space when
>     committing will cause the same confusion.

This also changes size as a result of changing contents.

> `git status` and `git diff` disagree because ie_modified()
> in read-cache.c concludes that a file is modified
> as soon as the size has changed.

"when size differs, without looking at the contents"?

> Signed-off-by: Curtis Allen Smith <curtis.allen.smith@gmail.com>
> Signed-off-by: Torsten Bögershausen <tboegi@web.de>

How is the collaboration going here, Have you taken over the series,
or are you two working together on the topic (or I take that your
intention to CC Curtis is to work together starting with this
version)?

I am wondering if we want co-authored-by or similar when we lose the
RFC status.

> ---
>  read-cache.c    | 25 ++++++++++++++++++++++++-
>  t/t0020-crlf.sh | 45 +++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 69 insertions(+), 1 deletion(-)
>
> diff --git a/read-cache.c b/read-cache.c
> index c4cf08a3a3..9aebe4c394 100644
> --- a/read-cache.c
> +++ b/read-cache.c
> @@ -9,6 +9,7 @@
>  
>  #include "git-compat-util.h"
>  #include "config.h"
> +#include "convert.h"
>  #include "date.h"
>  #include "diff.h"
>  #include "diffcore.h"
> @@ -438,6 +439,27 @@ int ie_match_stat(struct index_state *istate,
>  	return changed;
>  }
>  
> +/*
> + * A difference between the size of the file in the working tree and the
> + * size recorded for it in the index proves that the contents changed
> + * only as long as the two are byte-for-byte comparable.  That stops

"compatable" -> "identical"?

> + * being true as soon as the path is run through a clean filter:
> + * rewriting a file with CRLF endings under "text eol=lf", for example,
> + * changes its size in the working tree without changing the blob Git
> + * would record for it.  For such a path the only way to tell is to read
> + * the contents and convert them, which is what we already do when only
> + * the mtime changed.
> + */
> +static int size_change_is_conclusive(struct index_state *istate,
> +				     const struct cache_entry *ce,
> +				     struct stat *st)
> +{
> +	if (!S_ISREG(st->st_mode))
> +		return 1;
> +
> +	return !would_convert_to_git(istate, ce->name);
> +}

Wooo, that is surprisingly simple.  Even attribute look-up is not
free.  How much overhead are we adding to the case where no clean
filter is involved, I wonder?


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3/RFC 1/1] read-cache: do not trust a size change when conversion is active
  2026-10-12 19:11     ` Junio C Hamano
@ 2026-10-13  1:29       ` Junio C Hamano
  2026-10-13  6:45         ` Curtis Smith
  0 siblings, 1 reply; 14+ messages in thread
From: Junio C Hamano @ 2026-10-13  1:29 UTC (permalink / raw)
  To: tboegi; +Cc: git, curtis.allen.smith

Junio C Hamano <gitster@pobox.com> writes:

> tboegi@web.de writes:
>
>> From: Torsten Bögershausen <tboegi@web.de>
>> ...
>> Signed-off-by: Curtis Allen Smith <curtis.allen.smith@gmail.com>
>> Signed-off-by: Torsten Bögershausen <tboegi@web.de>
>
> How is the collaboration going here, Have you taken over the series,
> or are you two working together on the topic (or I take that your
> intention to CC Curtis is to work together starting with this
> version)?
>
> I am wondering if we want co-authored-by or similar when we lose the
> RFC status.

If you two are collaborating outside the mailing list, that would be
fine and I would have no reason to complain.  But such collaboration
is invisible to others, of course, so I would like to double-check
that you are not unilaterally taking over the topic.  Can you
confirm that you are working well together, or say something
similar, before we proceed to other technical points?

This is a tangent, but it is the reason why my mailer settings
drop into the trash all incoming Git-related messages that are
addressed only to me without CC'ing the list.  This forces me to
resist the temptation to collude with selected others behind
closed doors.

Thanks.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3/RFC 1/1] read-cache: do not trust a size change when conversion is active
  2026-10-12 17:56   ` [PATCH v3/RFC 1/1] read-cache: do not trust a size change when conversion is active tboegi
  2026-10-12 19:11     ` Junio C Hamano
@ 2026-10-13  5:34     ` Johannes Sixt
  2026-10-13  6:48       ` Curtis Smith
  2026-10-13  6:58     ` [PATCH v4] " Curtis Allen Smith
  2 siblings, 1 reply; 14+ messages in thread
From: Johannes Sixt @ 2026-10-13  5:34 UTC (permalink / raw)
  To: tboegi; +Cc: git, curtis.allen.smith

Am 12.10.26 um 19:56 schrieb tboegi@web.de:
> @@ -438,6 +439,27 @@ int ie_match_stat(struct index_state *istate,
>  	return changed;
>  }
>  
> +/*
> + * A difference between the size of the file in the working tree and the
> + * size recorded for it in the index proves that the contents changed
> + * only as long as the two are byte-for-byte comparable.  That stops
> + * being true as soon as the path is run through a clean filter:

I cannot agree with this reasoning. The index is a cache of the stat
information. The size recorded in the index is only used as a clue
whether the file was changed or not. It does *not* record the size of
the data that the object database holds for the blob.

If your analysis found that, after the content was `git add`-ed, the
size in the index is *not* identical to the size taken from the
filesystem, then the problem is that an incorrect size was put into the
index, and not that the size between the blob data in the database and
the filesystem's file are different (and unusable).

> + * rewriting a file with CRLF endings under "text eol=lf", for example,
> + * changes its size in the working tree without changing the blob Git
> + * would record for it.  For such a path the only way to tell is to read
> + * the contents and convert them, which is what we already do when only
> + * the mtime changed.
> + */

-- Hannes


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3/RFC 1/1] read-cache: do not trust a size change when conversion is active
  2026-10-13  1:29       ` Junio C Hamano
@ 2026-10-13  6:45         ` Curtis Smith
  0 siblings, 0 replies; 14+ messages in thread
From: Curtis Smith @ 2026-10-13  6:45 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: tboegi, git

Junio C Hamano <gitster@pobox.com> writes:

> If you two are collaborating outside the mailing list, that would be
> fine and I would have no reason to complain.  But such collaboration
> is invisible to others, of course, so I would like to double-check
> that you are not unilaterally taking over the topic.

We are not collaborating off the list, and Torsten did not ask me
before sending v3.  v3 is my v1 patch 1 with the optimization taken
out, but it keeps the size_change_is_conclusive() helper.  In my
reply to you on Saturday I proposed the fix inlined into the
condition in ie_modified() instead, with no new function.

I would like to keep the topic.  v4 follows in this thread: that
inlined fix, my tests, a short log message, and Torsten credited with
Helped-by.  Thanks, Torsten, for the review.

From your earlier message:

> Wooo, that is surprisingly simple.  Even attribute look-up is not
> free.  How much overhead are we adding to the case where no clean
> filter is involved, I wonder?

The lookup is the last test in the "changed & DATA_CHANGED"
condition, so it runs only for a path whose size changed.  A clean
tree never reaches it.  With no conversion configured, "git status"
on 10000 files with a size change takes 26ms on stock git and 24ms
with the fix.  The full numbers are below the "---" in v4.

On Mon, Oct 12, 2026 at 7:29 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Junio C Hamano <gitster@pobox.com> writes:
>
> > tboegi@web.de writes:
> >
> >> From: Torsten Bögershausen <tboegi@web.de>
> >> ...
> >> Signed-off-by: Curtis Allen Smith <curtis.allen.smith@gmail.com>
> >> Signed-off-by: Torsten Bögershausen <tboegi@web.de>
> >
> > How is the collaboration going here, Have you taken over the series,
> > or are you two working together on the topic (or I take that your
> > intention to CC Curtis is to work together starting with this
> > version)?
> >
> > I am wondering if we want co-authored-by or similar when we lose the
> > RFC status.
>
> If you two are collaborating outside the mailing list, that would be
> fine and I would have no reason to complain.  But such collaboration
> is invisible to others, of course, so I would like to double-check
> that you are not unilaterally taking over the topic.  Can you
> confirm that you are working well together, or say something
> similar, before we proceed to other technical points?
>
> This is a tangent, but it is the reason why my mailer settings
> drop into the trash all incoming Git-related messages that are
> addressed only to me without CC'ing the list.  This forces me to
> resist the temptation to collude with selected others behind
> closed doors.
>
> Thanks.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3/RFC 1/1] read-cache: do not trust a size change when conversion is active
  2026-10-13  5:34     ` Johannes Sixt
@ 2026-10-13  6:48       ` Curtis Smith
  0 siblings, 0 replies; 14+ messages in thread
From: Curtis Smith @ 2026-10-13  6:48 UTC (permalink / raw)
  To: Johannes Sixt; +Cc: tboegi, git

Johannes Sixt <j6t@kdbg.org> writes:

> I cannot agree with this reasoning. The index is a cache of the stat
> information. The size recorded in the index is only used as a clue
> whether the file was changed or not. It does *not* record the size of
> the data that the object database holds for the blob.

Agreed, and that comment is worded badly.  The size in the index is
correct here: it is the size of the LF file as it was checked out or
added.  The tool then rewrites the file with CRLF endings, so the
size on disk really does change.

The problem is what ie_modified() concludes from that.  A size change
proves that the bytes in the working tree changed.  Without
conversion, that means the content Git would record changed too.
With conversion it does not: the CRLF file converts to the same
blob, which is why "git diff" shows nothing.

v4 has no such comment, and its log message says it this way.

On Mon, Oct 12, 2026 at 11:34 PM Johannes Sixt <j6t@kdbg.org> wrote:
>
> Am 12.10.26 um 19:56 schrieb tboegi@web.de:
> > @@ -438,6 +439,27 @@ int ie_match_stat(struct index_state *istate,
> >       return changed;
> >  }
> >
> > +/*
> > + * A difference between the size of the file in the working tree and the
> > + * size recorded for it in the index proves that the contents changed
> > + * only as long as the two are byte-for-byte comparable.  That stops
> > + * being true as soon as the path is run through a clean filter:
>
> I cannot agree with this reasoning. The index is a cache of the stat
> information. The size recorded in the index is only used as a clue
> whether the file was changed or not. It does *not* record the size of
> the data that the object database holds for the blob.
>
> If your analysis found that, after the content was `git add`-ed, the
> size in the index is *not* identical to the size taken from the
> filesystem, then the problem is that an incorrect size was put into the
> index, and not that the size between the blob data in the database and
> the filesystem's file are different (and unusable).
>
> > + * rewriting a file with CRLF endings under "text eol=lf", for example,
> > + * changes its size in the working tree without changing the blob Git
> > + * would record for it.  For such a path the only way to tell is to read
> > + * the contents and convert them, which is what we already do when only
> > + * the mtime changed.
> > + */
>
> -- Hannes
>

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v4] read-cache: do not trust a size change when conversion is active
  2026-10-12 17:56   ` [PATCH v3/RFC 1/1] read-cache: do not trust a size change when conversion is active tboegi
  2026-10-12 19:11     ` Junio C Hamano
  2026-10-13  5:34     ` Johannes Sixt
@ 2026-10-13  6:58     ` Curtis Allen Smith
  2026-10-13  9:44       ` Phillip Wood
  2 siblings, 1 reply; 14+ messages in thread
From: Curtis Allen Smith @ 2026-10-13  6:58 UTC (permalink / raw)
  To: git; +Cc: Curtis Allen Smith, Torsten Bögershausen, Junio C Hamano

"git status" can report a file as modified while "git diff" and
"git add" agree that its contents are unchanged:

	git init t && cd t
	printf '* text eol=lf\n' >.gitattributes
	printf 'one\ntwo\nthree\n' >file.txt
	git add . && git commit -m init

	printf 'one\r\ntwo\r\nthree\r\n' >file.txt

	git status --short      # ->  M file.txt
	git diff                # -> empty
	git add file.txt        # -> nothing to commit

ie_modified() takes a change in the file's size since the index
entry was last refreshed as proof that the file is modified, without
looking at the contents.  A size change proves only that the bytes in
the working tree changed.  When the path is subject to conversion
(the "text" and "eol" attributes, core.autocrlf, a clean filter),
different bytes can convert to the same blob, as the CRLF rewrite
above does.

For such a path, fall through to ce_modified_check_fs(), as is
already done when only the mtime changed.  Paths without conversion
return early as before.

Helped-by: Torsten Bögershausen <tboegi@web.de>
Signed-off-by: Curtis Allen Smith <curtis.allen.smith@gmail.com>
---
Changes since v3:
 - The fix is inlined into the condition in ie_modified(), with no
   helper function.
 - Shorter log message, using Junio's wording; the "real life"
   examples are gone.

Cost, i9-14900K under WSL2, warm cache, fastest of 5 runs of
"git status -uno", stock 6de20f6 against this patch:

	10000 files x 2.6 KB, no conversion       stock      v4
	  clean tree                               5 ms     5 ms
	  10000 files with a size change          26 ms    24 ms

	10000 files x 2.6 KB, "* text=auto eol=lf"
	  clean tree                               5 ms     5 ms
	  10000 genuinely modified                25 ms   137 ms
	  10000 CRLF-rewritten, 1st run           21 ms   143 ms
	  the same, every run after               21 ms     5 ms

	200 files x 1 MB, "* text=auto eol=lf"
	  clean tree                               3 ms     3 ms
	  200 genuinely modified                   3 ms   498 ms
	  200 CRLF-rewritten, 1st run              3 ms   584 ms
	  the same, every run after                3 ms     3 ms

The attribute lookup happens only for a path whose size changed, so a
clean tree pays nothing, and a tree without conversion pays one
lookup per size-changed path (the first block).  A converted path
whose size changed is now read, converted and hashed, which is what
already happens when only its mtime changes.  Stock git keeps
reporting the CRLF-rewritten files as modified on every run.

A follow-up that skips the conversion and the hash when the length
already rules out a match is ready if wanted.  It takes the
"genuinely modified" rows to 57 ms and 25 ms.

 read-cache.c    |  4 +++-
 t/t0020-crlf.sh | 45 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/read-cache.c b/read-cache.c
index c4cf08a3a..70f4787de 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -9,6 +9,7 @@
 
 #include "git-compat-util.h"
 #include "config.h"
+#include "convert.h"
 #include "date.h"
 #include "diff.h"
 #include "diffcore.h"
@@ -480,7 +481,8 @@ int ie_modified(struct index_state *istate,
 	     */
 	    (!S_ISLNK(st->st_mode) || ce->ce_stat_data.sd_size != MAX_PATH) &&
 #endif
-	    (S_ISGITLINK(ce->ce_mode) || ce->ce_stat_data.sd_size != 0))
+	    (S_ISGITLINK(ce->ce_mode) || ce->ce_stat_data.sd_size != 0) &&
+	    !(S_ISREG(st->st_mode) && would_convert_to_git(istate, ce->name)))
 		return changed;
 
 	changed_fs = ce_modified_check_fs(istate, ce, st);
diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh
index fd1cae09e..b76c678ce 100755
--- a/t/t0020-crlf.sh
+++ b/t/t0020-crlf.sh
@@ -397,4 +397,49 @@ test_expect_success 'New CRLF file gets LF in repo' '
 	test_cmp alllf alllf2
 '
 
+test_expect_success 'status does not report a CRLF-only rewrite as modified' '
+	git init eol-status &&
+	(
+		cd eol-status &&
+		echo "* text eol=lf" >.gitattributes &&
+		printf "one\ntwo\nthree\n" >file.txt &&
+		git add .gitattributes file.txt &&
+		git commit -m initial &&
+
+		# a generator rewrites the file with CRLF, same content
+		printf "one\r\ntwo\r\nthree\r\n" >file.txt &&
+		git status --porcelain -uno >actual &&
+		test_must_be_empty actual &&
+		git diff --exit-code &&
+
+		# a real change is still reported
+		printf "one\r\ntwo\r\nfour\r\n" >file.txt &&
+		git status --porcelain -uno >actual &&
+		echo " M file.txt" >expect &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'status sees through a CRLF rewrite of a file with a lone CR' '
+	git init eol-status-lone-cr &&
+	(
+		cd eol-status-lone-cr &&
+		echo "* text eol=lf" >.gitattributes &&
+		printf "one\rtwo\nthree\n" >file.txt &&
+		git add .gitattributes file.txt &&
+		git commit -m initial &&
+
+		# "git add" keeps the lone CR and drops the others
+		printf "one\rtwo\r\nthree\r\n" >file.txt &&
+		git status --porcelain -uno >actual &&
+		test_must_be_empty actual &&
+
+		# the converted length matches the blob, the content does not
+		printf "one\rtwo\nthrEE\r\n" >file.txt &&
+		git status --porcelain -uno >actual &&
+		echo " M file.txt" >expect &&
+		test_cmp expect actual
+	)
+'
+
 test_done
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v4] read-cache: do not trust a size change when conversion is active
  2026-10-13  6:58     ` [PATCH v4] " Curtis Allen Smith
@ 2026-10-13  9:44       ` Phillip Wood
  0 siblings, 0 replies; 14+ messages in thread
From: Phillip Wood @ 2026-10-13  9:44 UTC (permalink / raw)
  To: Curtis Allen Smith, git; +Cc: Torsten Bögershausen, Junio C Hamano

Hi Curtis

On 13/10/2026 07:58, Curtis Allen Smith wrote:
> "git status" can report a file as modified while "git diff" and
> "git add" agree that its contents are unchanged:
> 
> 	git init t && cd t
> 	printf '* text eol=lf\n' >.gitattributes
> 	printf 'one\ntwo\nthree\n' >file.txt
> 	git add . && git commit -m init
> 
> 	printf 'one\r\ntwo\r\nthree\r\n' >file.txt
> 
> 	git status --short      # ->  M file.txt
> 	git diff                # -> empty
> 	git add file.txt        # -> nothing to commit
> 
> ie_modified() takes a change in the file's size since the index
> entry was last refreshed as proof that the file is modified, without
> looking at the contents.  A size change proves only that the bytes in
> the working tree changed.  When the path is subject to conversion
> (the "text" and "eol" attributes, core.autocrlf, a clean filter),
> different bytes can convert to the same blob, as the CRLF rewrite
> above does.
> 
> For such a path, fall through to ce_modified_check_fs(), as is
> already done when only the mtime changed.  Paths without conversion
> return early as before.

The commit message talks about "git status" but presumably this change 
affects the output of "git diff-files" and "git diff-index" as well. 
Does this change improve the performance of "git diff" because it 
no-longer tries to diff two blobs that are identical after conversion or 
is the existing implementation smart enough to avoid that already?

> A follow-up that skips the conversion and the hash when the length
> already rules out a match is ready if wanted.  It takes the
> "genuinely modified" rows to 57 ms and 25 ms.

What does that entail? A file that consists of blank lines can double or 
halve its length going from '\n' to '\r\n' and vice-versa. Is that range 
narrow enough to avoid converting a significant number of "real" changes?

Looking at t0020-crlf.sh none of the existing tests use a separate 
repository, so maybe we could avoid that overhead here as well.

Thanks

Phillip

>   read-cache.c    |  4 +++-
>   t/t0020-crlf.sh | 45 +++++++++++++++++++++++++++++++++++++++++++++
>   2 files changed, 48 insertions(+), 1 deletion(-)
> 
> diff --git a/read-cache.c b/read-cache.c
> index c4cf08a3a..70f4787de 100644
> --- a/read-cache.c
> +++ b/read-cache.c
> @@ -9,6 +9,7 @@
>   
>   #include "git-compat-util.h"
>   #include "config.h"
> +#include "convert.h"
>   #include "date.h"
>   #include "diff.h"
>   #include "diffcore.h"
> @@ -480,7 +481,8 @@ int ie_modified(struct index_state *istate,
>   	     */
>   	    (!S_ISLNK(st->st_mode) || ce->ce_stat_data.sd_size != MAX_PATH) &&
>   #endif
> -	    (S_ISGITLINK(ce->ce_mode) || ce->ce_stat_data.sd_size != 0))
> +	    (S_ISGITLINK(ce->ce_mode) || ce->ce_stat_data.sd_size != 0) &&
> +	    !(S_ISREG(st->st_mode) && would_convert_to_git(istate, ce->name)))
>   		return changed;
>   
>   	changed_fs = ce_modified_check_fs(istate, ce, st);
> diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh
> index fd1cae09e..b76c678ce 100755
> --- a/t/t0020-crlf.sh
> +++ b/t/t0020-crlf.sh
> @@ -397,4 +397,49 @@ test_expect_success 'New CRLF file gets LF in repo' '
>   	test_cmp alllf alllf2
>   '
>   
> +test_expect_success 'status does not report a CRLF-only rewrite as modified' '
> +	git init eol-status &&
> +	(
> +		cd eol-status &&
> +		echo "* text eol=lf" >.gitattributes &&
> +		printf "one\ntwo\nthree\n" >file.txt &&
> +		git add .gitattributes file.txt &&
> +		git commit -m initial &&
> +
> +		# a generator rewrites the file with CRLF, same content
> +		printf "one\r\ntwo\r\nthree\r\n" >file.txt &&
> +		git status --porcelain -uno >actual &&
> +		test_must_be_empty actual &&
> +		git diff --exit-code &&
> +
> +		# a real change is still reported
> +		printf "one\r\ntwo\r\nfour\r\n" >file.txt &&
> +		git status --porcelain -uno >actual &&
> +		echo " M file.txt" >expect &&
> +		test_cmp expect actual
> +	)
> +'
> +
> +test_expect_success 'status sees through a CRLF rewrite of a file with a lone CR' '
> +	git init eol-status-lone-cr &&
> +	(
> +		cd eol-status-lone-cr &&
> +		echo "* text eol=lf" >.gitattributes &&
> +		printf "one\rtwo\nthree\n" >file.txt &&
> +		git add .gitattributes file.txt &&
> +		git commit -m initial &&
> +
> +		# "git add" keeps the lone CR and drops the others
> +		printf "one\rtwo\r\nthree\r\n" >file.txt &&
> +		git status --porcelain -uno >actual &&
> +		test_must_be_empty actual &&
> +
> +		# the converted length matches the blob, the content does not
> +		printf "one\rtwo\nthrEE\r\n" >file.txt &&
> +		git status --porcelain -uno >actual &&
> +		echo " M file.txt" >expect &&
> +		test_cmp expect actual
> +	)
> +'
> +
>   test_done


^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2026-10-13  9:44 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-11 19:51 [PATCH v2 1/2] utf8.c: Prepare workaround for iconv under macOS 14/15 tboegi
2026-01-12  4:58 ` Junio C Hamano
2026-01-12 16:25   ` [PATCH v3 0/2] Workaround " tboegi
2026-01-12 16:25   ` [PATCH v3 1/2] utf8.c: Prepare workaround " tboegi
2026-01-12 16:25   ` [PATCH v3 2/2] utf8.c: Enable " tboegi
2026-10-12 17:56   ` [PATCH v3/RFC 0/1] read-cache: do not trust a size change when conversion tboegi
2026-10-12 17:56   ` [PATCH v3/RFC 1/1] read-cache: do not trust a size change when conversion is active tboegi
2026-10-12 19:11     ` Junio C Hamano
2026-10-13  1:29       ` Junio C Hamano
2026-10-13  6:45         ` Curtis Smith
2026-10-13  5:34     ` Johannes Sixt
2026-10-13  6:48       ` Curtis Smith
2026-10-13  6:58     ` [PATCH v4] " Curtis Allen Smith
2026-10-13  9:44       ` Phillip Wood

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.