Subject: Re: [bug-diffutils] bug#76452: 3.11 critical bug: diff -Naur a b does not show a unified diff when one of the file is empty

From: Collin Funk <[email-masked]>
Date: Thu, 20 Feb 2025 22:10:44 -0800

Hi Katie,

Kate Deplaix <[email-masked]> writes:

> With diffutils 3.10, "diff -Naur a b" returns:
>
> diff -Naur a/test b/test
> --- a/test   2025-02-20 14:50:07.870258052 +0000
> +++ b/test   2025-02-20 14:50:18.957287641 +0000
> @@ -0,0 +1 @@
> +content
>
> but with diffutils 3.11, the same command returns:
>
> File a/test is a regular empty file while file b/test is a regular file

Good catch.

CC'ing Paul Eggert since I think that I found the cause and it appears
unintentional to me.

Commit e016d12581ac4ea6be1ded88279527ceface74e1 has the following
ChangeLog:

    diff: improve symlink handling, avoiding a race
    [...]
    (compare_prepped_files): Use filetype and stat macros, not detype.
    [...]

Where filetype refers to a constant string returned by Gnulib's
c_file_type function. The relevant lines are here:

  if (S_ISREG (st->st_mode))
    return st->st_size == 0 ? N_("regular empty file") : N_("regular file");

So empty files will be treated as different file types because of that
change.

I have attached a patch to check for this condition.

Collin

>From 7d4c55202ec4c0366062ff03d27a4883861ff222 Mon Sep 17 00:00:00 2001
From: Collin Funk <[email-masked]>
Date: Thu, 20 Feb 2025 21:59:55 -0800
Subject: [PATCH] diff: don't treat empty files as a different file type

Reported by Kate Deplaix <[email-masked]> in
<https://lists.gnu.org/r/bug-diffutils/2025-02/msg00005.html>.

* src/diff.c (compare_prepped_files): Check for two regular files where
one has a zero size and the other has a non-zero size.
---
 src/diff.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/diff.c b/src/diff.c
index fa6100e..0c41fd2 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -1226,7 +1226,10 @@ compare_prepped_files (struct comparison const *parent,
   if (toplevel
       ? (!S_ISLNK (cmp->file[0].stat.st_mode)
 	 != !S_ISLNK (cmp->file[1].stat.st_mode))
-      : (cmp->file[0].filetype != cmp->file[1].filetype
+      : ((cmp->file[0].filetype != cmp->file[1].filetype
+          && !(S_ISREG (cmp->file[0].stat.st_mode)
+               && S_ISREG (cmp->file[1].stat.st_mode)
+               && (!!cmp->file[0].stat.st_size == !!!cmp->file[1].stat.st_size)))
 	 || ! (S_ISREG (cmp->file[0].stat.st_mode)
 	       || S_ISLNK (cmp->file[0].stat.st_mode)
 	       || S_ISCHR (cmp->file[0].stat.st_mode)
-- 
2.48.1