Here's a description and source of a new diff option to compare file and directory properties. I'd welcome any comments on the input and output formats, terminology, any other suggestions etc.

--compare=[content,time,mode,size,owner,group,all,objects]
This option provides a number of file properties to compare, for normal and both context output styles. Keyword 'content' means the file contents, and 'all' means all file properties (excluding 'objects'). A '~' prefix removes the keyword property. So --compare=all,~mode is the same as --compare=content,time,size,owner,group. The 'objects' keyword is not strictly a property. It means compare all 'file objects' with themselves and each other. By 'file objects', I mean regular files (including symbolic links), directories and special files (like 'character special files' etc, basically anything that's not a directory or regular file). Directories and special files are considered to be empty files as far as content is concerned. The intention is that --compare=all,objects gives enough information for 'patch' to be able to handle most changes to files and directory structures, other than changes to binary files. Note that special files are only handled in this way when in subdirectories. If specified at the top level, they would be treated like a regular file - this is in keeping with how 'diff' currently works.

Any difference in content or file properties causes the specified property information to be written to a modified context style header. Note that the keyword 'time' is not written - this saves space, and is more compatible with the existing context header line format.

  % diff -u --compare=all aaa bbb
--- aaa 2010-09-23 20:51:06.984375000 +0100 mode=-rw-r--r-- size=4 owner=Duncan group=None +++ bbb 2010-09-23 20:51:03.015625000 +0100 mode=-rwxr--r-- size=4 owner=Duncan group=None
  @@ -1 +1 @@
  -111
  +222

  % diff --compare=content aaa bbb
<<< aaa
>>> bbb
  1c1
< 111
  ---
> 222

  % diff --compare=mode c d
<<< c mode=-rw-r--r--
>>> d mode=-rwxr--r--

  % diff -r -u -N --compare=all,objects 6aDR 6bDR
  diff -r -u -N '--compare=all,objects' 6aDR 6bDR
--- 6aDR 2010-09-24 18:00:26.890625000 +0100 mode=drwxr-xr-x size=0 owner=Duncan group=None object=directory +++ 6bDR 2000-01-01 00:00:01.000000000 +0000 mode=drwxr-xr-x size=0 owner=Duncan group=None object=directory
  diff -r -u -N '--compare=all,objects' 6aDR/dir1 6bDR/dir1
--- 6aDR/dir1 1999-12-31 23:59:59.000000000 +0000 mode=drwxr-xr-x size=0 owner=Duncan group=None object=directory +++ 6bDR/dir1 2010-09-24 17:59:48.640625000 +0100 mode=drwxr-xr-x size=0 owner=Duncan group=None object=directory
  diff -r -u -N '--compare=all,objects' 6aDR/dir1/csf 6bDR/dir1/csf
  --- 6aDR/dir1/csf non-existent file
+++ 6bDR/dir1/csf 2010-09-24 17:59:48.640625000 +0100 mode=crw-rw-rw- size=0 owner=Duncan group=None object=character special file
  diff -r -u -N '--compare=all,objects' 6aDR/dir1/object2 6bDR/dir1/object2
--- 6aDR/dir1/object2 1999-12-31 23:59:59.000000000 +0000 mode=-rw-r--r-- size=2 owner=Duncan group=None object=regular file +++ 6bDR/dir1/object2 2000-01-01 00:00:01.000000000 +0000 mode=drwxr-xr-x size=0 owner=Duncan group=None object=directory
  @@ -1 +0,0 @@
  -a
diff -r -u -N '--compare=all,objects' 6aDR/dir1/object2/dir3 6bDR/dir1/object2/dir3
  --- 6aDR/dir1/object2/dir3 non-existent directory
+++ 6bDR/dir1/object2/dir3 2000-01-01 00:00:01.000000000 +0000 mode=drwxr-xr-x size=0 owner=Duncan group=None object=directory diff -r -u -N '--compare=all,objects' 6aDR/dir1/object2/dir3/file4 6bDR/dir1/object2/dir3/file4
  --- 6aDR/dir1/object2/dir3/file4 non-existent file
+++ 6bDR/dir1/object2/dir3/file4 2000-01-01 00:00:01.000000000 +0000 mode=-rw-r--r-- size=2 owner=Duncan group=None object=regular file
  @@ -0,0 +1 @@
  +a
  diff -r -u -N '--compare=all,objects' 6aDR/np 6bDR/np
--- 6aDR/np 2010-09-24 18:00:26.890625000 +0100 mode=prw-rw-rw- size=0 owner=Duncan group=None object=fifo
  +++ 6bDR/np non-existent file
  diff -r -u -N '--compare=all,objects' 6aDR/object1 6bDR/object1
--- 6aDR/object1 1999-12-31 23:59:59.000000000 +0000 mode=drwxr-xr-x size=0 owner=Duncan group=None object=directory +++ 6bDR/object1 2000-01-01 00:00:01.000000000 +0000 mode=-rw-r--r-- size=2 owner=Duncan group=None object=regular file
  @@ -0,0 +1 @@
  +a
diff -r -u -N '--compare=all,objects' 6aDR/object1/file2 6bDR/object1/file2 --- 6aDR/object1/file2 1999-12-31 23:59:59.000000000 +0000 mode=-rw-r--r-- size=2 owner=Duncan group=None object=regular file
  +++ 6bDR/object1/file2 non-existent file
  @@ -1 +0,0 @@
  -a

The --compare option format and the output format have been designed to balance the following criteria as much as possible:

a) backwards compatibility.
b) minimising the number of options.
c) making the output easy for users to read and 'patch' to process.

Alternative approaches for the format of the output header would have been to write only the specified file properties that actually differ. This would be more in keeping with what would normally be expected of 'diff' (i.e. show what's different, not what's the same). The properties could also have been written one per line e.g.:

  --- 6aDR/dir1/object2
  +++ 6bDR/dir1/object2
  --- time = 1999-12-31 23:59:59.000000000 +0000
  +++ time = 2000-01-01 00:00:01.000000000 +0000
  --- mode = -rw-r--r--
  +++ mode = drwxr-xr-x
  --- size = 2
  +++ size = 0
  --- object = regular file
  +++ object = directory

This would be a bit easier for 'patch' to process, but maybe less easy for the user to scan through. It would also be more prone to breaking any existing processors of 'diff' context output because there would be extra header lines. With the current approach, the property line can get quite long, but each property tends to be in roughly the same horizontal screen position, which makes it easier for users to scan through recursive diffs for a particular property. Something I haven't done, but which would fit in well with the 'one property per line' format is an option to not dereference links. (i.e. a --no-follow-link or --no-dereference option). This would cause symbolic links to be treated like any other special file, giving without --compare=all,objects a message like:

  File 6a/z is a regular file while file 6b/z is a symbolic link

and with --compare=all,objects, file properties relating to the link itself (rather than the file pointed to) and a header including:

  +++ object = symbolic link <name_of_the_file_pointed_to_here>

The only special files I've tested are 'character special files' and fifos, as I'm not too familar with most of them. There are probably special requirements for some of them that I'm unaware of.

Source changes are attached. If you want to test the updates out, you'll also need the gnulib modules filemode and idcache. (The new --if-different option (see yesterdays message) is also included in the source. The the two options are entirely distinct, but it's not too easy to disentangle the logic).




diff -u -d -x '*.[oea]*' -x '[.M]*' diffutils-3.0/src/analyze.c 
diffutils-3.0.KKK/src/analyze.c
--- diffutils-3.0/src/analyze.c 2010-04-15 13:58:08.000000000 +0100
+++ diffutils-3.0.KKK/src/analyze.c     2010-09-27 11:59:38.265625000 +0100
@@ -447,21 +447,37 @@
 
 /* If CHANGES, briefly report that two files differed.
    Return 2 if trouble, CHANGES otherwise.  */
-static int
+int
 briefly_report (int changes, struct file_data const filevec[])
 {
+  if (changes == 0 && (is_different & PROPERTIES))
+    changes = 1;
   if (changes)
     {
       char const *label0 = file_label[0] ? file_label[0] : filevec[0].name;
       char const *label1 = file_label[1] ? file_label[1] : filevec[1].name;
 
-      if (brief)
-       message ("Files %s and %s differ\n", label0, label1);
+      if (S_ISDIR (filevec[0].stat.st_mode) != 0)
+       {
+         if (S_ISDIR (filevec[1].stat.st_mode) != 0)
+           message ("Directories %s and %s differ\n", label0, label1);
+         else
+           message ("Directory %s and file %s differ\n", label0, label1);
+       }
       else
        {
-         message ("Binary files %s and %s differ\n", label0, label1);
-         changes = 2;
+         if (S_ISDIR (filevec[1].stat.st_mode) != 0)
+           message ("File %s and directory %s differ\n", label0, label1);
+         else
+           {
+             if (brief)
+               message ("Files %s and %s differ\n", label0, label1);
+             else
+               message ("Binary files %s and %s differ\n", label0, label1);
+           }
        }
+      if (! brief)
+       changes = 2;
     }
 
   return changes;
@@ -536,7 +552,30 @@
            }
        }
 
-      changes = briefly_report (changes, cmp->file);
+      if (brief || compare <= CONTENT)
+       /* Brief output option, or binary file and only comparing contents.  */
+       changes = briefly_report (changes, cmp->file);
+      else
+       {
+         /* Binary file. */
+         if (changes == 1 || (is_different & PROPERTIES))
+           {
+             files[0] = cmp->file[0];
+             files[1] = cmp->file[1];
+             setup_output (cmp);
+             begin_output ();
+            /* Report binary file content differences that can't be explicitly 
shown.  */
+             if (changes == 1)
+               message ("Contents of %s and %s differ\n",
+                               file_label[0] ? file_label[0] : 
cmp->file[0].name,
+                               file_label[1] ? file_label[1] : 
cmp->file[1].name);
+             else
+               /* PROPERTIES_DIFFER == TRUE.  */
+               changes = 1;
+           }
+       }
+      if (changes == 1)
+       is_different |= CONTENT;
     }
   else
     {
@@ -634,17 +673,21 @@
       else
        changes = (script != 0);
 
+      if (changes == 1)
+       is_different |= CONTENT;
+
       if (brief)
        changes = briefly_report (changes, cmp->file);
       else
        {
+         if (changes == 0 && (is_different & PROPERTIES))
+           changes = 1;
          if (changes || !no_diff_means_no_output)
            {
              /* Record info for starting up output,
                 to be used if and when we have some output to print.  */
-             setup_output (file_label[0] ? file_label[0] : cmp->file[0].name,
-                           file_label[1] ? file_label[1] : cmp->file[1].name,
-                           cmp->parent != 0);
+             setup_output (cmp);
+             begin_output ();
 
              switch (output_style)
                {
@@ -686,6 +729,14 @@
 
              finish_output ();
            }
+         /* Report a non-existent file / regular file (normally empty)
+            comparison that produces no 'content' differences, as having
+            different 'content'.  */
+         if (compare > 0 && !(is_different & CONTENT) &&
+             (cmp->file[0].desc == -1 || cmp->file[1].desc == -1))
+           message ("Contents of %s and %s differ\n",
+                       file_label[0] ? file_label[0] : cmp->file[0].name,
+                       file_label[1] ? file_label[1] : cmp->file[1].name);
        }
 
       free (cmp->file[0].undiscarded);
diff -u -d -x '*.[oea]*' -x '[.M]*' diffutils-3.0/src/context.c 
diffutils-3.0.KKK/src/context.c
--- diffutils-3.0/src/context.c 2010-04-15 20:53:08.000000000 +0100
+++ diffutils-3.0.KKK/src/context.c     2010-09-27 11:59:38.265625000 +0100
@@ -21,6 +21,9 @@
 #include "diff.h"
 #include "c-ctype.h"
 #include <inttostr.h>
+#include <file-type.h>
+#include <filemode.h>
+#include <idcache.h>
 #include <stat-time.h>
 #include <strftime.h>
 
@@ -36,59 +39,120 @@
 /* The value find_function returned when it started searching there.  */
 static lin find_function_last_match;
 
-/* Print a label for a context diff, with a file name and date or a label.  */
+/* Print a label for a context or normal diff, with a file name and date, a 
file
+   name and file properities, or a label.  */
 
 static void
-print_context_label (char const *mark,
+print_label (char const *mark,
                     struct file_data *inf,
-                    char const *label)
+                    char const *label,
+                    int const file_num)
 {
+  char whitespace = (compare == 0) ? '\t' : ' ';
   if (label)
     fprintf (outfile, "%s %s\n", mark, label);
   else
     {
-      char buf[MAX (INT_STRLEN_BOUND (int) + 32,
-                   INT_STRLEN_BOUND (time_t) + 11)];
-      struct tm const *tm = localtime (&inf->stat.st_mtime);
-      int nsec = get_stat_mtime_ns (&inf->stat);
-      if (! (tm && nstrftime (buf, sizeof buf, time_format, tm, 0, nsec)))
+      fprintf (outfile, "%s %s", mark, inf->name);
+      if (compare > CONTENT || (compare == CONTENT && inf->desc == -1))
+       for (int i=compare_width[0] * file_num;i>0;--i)
+         fprintf (outfile, " ");
+      if (inf->desc == -1 && compare > 0)
+       fprintf (outfile, "%cnon-existent %s\n", whitespace,
+                         (S_ISDIR (inf->stat.st_mode) != 0) ? "directory" : 
"file");
+      else
        {
-         verify (TYPE_IS_INTEGER (time_t));
-         if (LONG_MIN <= TYPE_MINIMUM (time_t)
-             && TYPE_MAXIMUM (time_t) <= LONG_MAX)
+         if (compare == 0 || (compare & TIME))
            {
-             long int sec = inf->stat.st_mtime;
-             sprintf (buf, "%ld.%.9d", sec, nsec);
+             char buf[MAX (INT_STRLEN_BOUND (int) + 32,
+                           INT_STRLEN_BOUND (time_t) + 11)];
+             struct tm const *tm = localtime (&inf->stat.st_mtime);
+             int nsec = get_stat_mtime_ns (&inf->stat);
+             if (! (tm && nstrftime (buf, sizeof buf, time_format, tm, 0, 
nsec)))
+               {
+                 verify (TYPE_IS_INTEGER (time_t));
+                 if (LONG_MIN <= TYPE_MINIMUM (time_t)
+                     && TYPE_MAXIMUM (time_t) <= LONG_MAX)
+                   {
+                     long int sec = inf->stat.st_mtime;
+                     sprintf (buf, "%ld.%.9d", sec, nsec);
+                   }
+                 else if (TYPE_MAXIMUM (time_t) <= INTMAX_MAX)
+                   {
+                     intmax_t sec = inf->stat.st_mtime;
+                     sprintf (buf, "%"PRIdMAX".%.9d", sec, nsec);
+                   }
+                 else
+                   {
+                     uintmax_t sec = inf->stat.st_mtime;
+                     sprintf (buf, "%"PRIuMAX".%.9d", sec, nsec);
+                   }
+               }
+             fprintf (outfile, "%c%s", whitespace, buf);
            }
-         else if (TYPE_MAXIMUM (time_t) <= INTMAX_MAX)
+         if (compare & MODE)
            {
-             intmax_t sec = inf->stat.st_mtime;
-             sprintf (buf, "%"PRIdMAX".%.9d", sec, nsec);
+             char modebuf[12];
+             filemodestring (&inf->stat, modebuf);
+             modebuf[10]='\0';
+             fprintf (outfile, " mode=%s",modebuf);
            }
-         else
+         if (compare & SIZE)
            {
-             uintmax_t sec = inf->stat.st_mtime;
-             sprintf (buf, "%"PRIuMAX".%.9d", sec, nsec);
+             fprintf (outfile, " size=");
+             for (int i=compare_width[3] * file_num;i>0;--i)
+               fprintf (outfile, " ");
+             fprintf (outfile, "%lli", inf->stat.st_size);
+           }
+         if (compare & OWNER)
+           {
+             fprintf (outfile, " owner=%s", getuser (inf->stat.st_uid));
+             for (int i=compare_width[4] * file_num;i>0;--i)
+               fprintf (outfile, " ");
            }
+         if (compare & GROUP)
+           {
+             fprintf (outfile, " group=%s", getgroup (inf->stat.st_gid));
+             for (int i=compare_width[5] * file_num;i>0;--i)
+               fprintf (outfile, " ");
+           }
+         if (compare & OBJECTS)
+           {
+             fprintf (outfile, " object=%s", file_type (&inf->stat));
+           }
+         putc ('\n', outfile);
        }
-      fprintf (outfile, "%s %s\t%s\n", mark, inf->name, buf);
     }
 }
 
-/* Print a header for a context diff, with the file names and dates.  */
+/* Print a header for a context or normal diff, with the file names,
+   and dates or properties.  */
 
 void
-print_context_header (struct file_data inf[], bool unidiff)
+print_header (struct file_data inf[], enum output_style output_style)
 {
-  if (unidiff)
-    {
-      print_context_label ("---", &inf[0], file_label[0]);
-      print_context_label ("+++", &inf[1], file_label[1]);
-    }
-  else
+  switch (output_style)
     {
-      print_context_label ("***", &inf[0], file_label[0]);
-      print_context_label ("---", &inf[1], file_label[1]);
+    case OUTPUT_CONTEXT:
+      print_label ("***", &inf[0], file_label[0], -1);
+      print_label ("---", &inf[1], file_label[1], +1);
+      break;
+
+    case OUTPUT_UNIFIED:
+      print_label ("---", &inf[0], file_label[0], -1);
+      print_label ("+++", &inf[1], file_label[1], +1);
+      break;
+
+    case OUTPUT_NORMAL:
+      if (compare >= CONTENT)
+       {
+         print_label ("<<<", &inf[0], file_label[0], -1);
+         print_label (">>>", &inf[1], file_label[1], +1);
+       }
+      break;
+
+    default:
+      break;
     }
 }
 
diff -u -d -x '*.[oea]*' -x '[.M]*' diffutils-3.0/src/diff.c 
diffutils-3.0.KKK/src/diff.c
--- diffutils-3.0/src/diff.c    2010-04-15 20:53:08.000000000 +0100
+++ diffutils-3.0.KKK/src/diff.c        2010-09-27 11:59:38.265625000 +0100
@@ -28,9 +28,11 @@
 #include <exclude.h>
 #include <exitfail.h>
 #include <file-type.h>
+#include <filemode.h>
 #include <fnmatch.h>
 #include <getopt.h>
 #include <hard-locale.h>
+#include <idcache.h>
 #include <prepargs.h>
 #include <progname.h>
 #include <sh-quote.h>
@@ -72,10 +74,6 @@
 static void check_stdout (void);
 static void usage (void);
 
-/* If comparing directories, compare their common subdirectories
-   recursively.  */
-static bool recursive;
-
 /* In context diffs, show previous lines that match these regexps.  */
 static struct regexp_list function_regexp_list;
 
@@ -112,9 +110,11 @@
 enum
 {
   BINARY_OPTION = CHAR_MAX + 1,
+  COMPARE_OPTION,
   FROM_FILE_OPTION,
   HELP_OPTION,
   HORIZON_LINES_OPTION,
+  IF_DIFFERENT_OPTION,
   IGNORE_FILE_NAME_CASE_OPTION,
   INHIBIT_HUNK_MERGE_OPTION,
   LEFT_COLUMN_OPTION,
@@ -160,6 +160,7 @@
   {"binary", 0, 0, BINARY_OPTION},
   {"brief", 0, 0, 'q'},
   {"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION},
+  {"compare", 1, 0, COMPARE_OPTION},
   {"context", 2, 0, 'C'},
   {"ed", 0, 0, 'e'},
   {"exclude", 1, 0, 'x'},
@@ -170,6 +171,7 @@
   {"help", 0, 0, HELP_OPTION},
   {"horizon-lines", 1, 0, HORIZON_LINES_OPTION},
   {"ifdef", 1, 0, 'D'},
+  {"if-different", 0, 0, IF_DIFFERENT_OPTION},
   {"ignore-all-space", 0, 0, 'w'},
   {"ignore-blank-lines", 0, 0, 'B'},
   {"ignore-case", 0, 0, 'i'},
@@ -522,6 +524,44 @@
 #endif
          break;
 
+       case COMPARE_OPTION:
+         compare = 0;
+         {
+           char *s=strdup (optarg);
+           char *ps=strtok (s, ",");
+           while (ps)
+             {
+               bool negate=(*ps=='~');
+               if (negate) ++ps;
+               unsigned int compare_item=0;
+               if (strcmp (ps, "content") == 0)
+                 compare_item = CONTENT;
+               else if (strcmp (ps, "time") == 0)
+                 compare_item = TIME;
+               else if (strcmp (ps, "mode") == 0)
+                 compare_item = MODE;
+               else if (strcmp (ps, "size") == 0)
+                 compare_item = SIZE;
+               else if (strcmp (ps, "owner") == 0)
+                 compare_item = OWNER;
+               else if (strcmp (ps, "group") == 0)
+                 compare_item = GROUP;
+               else if (strcmp (ps, "objects") == 0)
+                 compare_item = OBJECTS;
+               else if (strcmp (ps, "all") == 0)
+                 compare_item = ~OBJECTS;
+               else
+                 try_help ("invalid compare option `%s'", ps);
+               if (negate)
+                 compare &= ~compare_item;
+               else
+                 compare |= compare_item;
+               ps=strtok (NULL, ",");
+             }
+           free (s);
+         }
+         break;
+
        case FROM_FILE_OPTION:
          specify_value (&from_file, optarg, "--from-file");
          break;
@@ -538,6 +578,10 @@
          horizon_lines = MAX (horizon_lines, MIN (numval, LIN_MAX));
          break;
 
+       case IF_DIFFERENT_OPTION:
+         if_different = true;
+         break;
+
        case IGNORE_FILE_NAME_CASE_OPTION:
          ignore_file_name_case = true;
          break;
@@ -659,6 +703,15 @@
     tabsize = 8;
   if (! width)
     width = 130;
+  /*  Side-by-side style (normally) outputs the whole files, even if they are 
the same,
+      so switch off the 'only difference if different' option.  */
+  if (if_different && (output_style == OUTPUT_SDIFF && !suppress_common_lines))
+    if_different = false;
+
+  /*  The 'compare' option only applies to normal and both context styles.  */
+  if (compare > 0 && (output_style != OUTPUT_NORMAL &&
+            output_style != OUTPUT_CONTEXT && output_style != OUTPUT_UNIFIED))
+    compare = 0;
 
   {
     /* Maximize first the half line width, and then the gutter width,
@@ -852,6 +905,15 @@
   N_("-B  --ignore-blank-lines  Ignore changes whose lines are all blank."),
   N_("-I RE  --ignore-matching-lines=RE  Ignore changes whose lines all match 
RE."),
   N_("--strip-trailing-cr  Strip trailing carriage return on input."),
+  N_("--if-different  Compare file contents only if the file"),
+  N_("                sizes or timestamps are different"),
+  N_("--compare=PROPERTY[,PROPERTY...]"),
+  N_("                       Properties to compare; PROPERTY may be any"),
+  N_("                       of `content', `time', `mode', `size',"),
+  N_("                       `owner', `group', `all' (for all of these),"),
+  N_("                       or `objects'; properties are applied"),
+  N_("                       sequentially, a `~' prefix switching that"),
+  N_("                       property off."),
 #if O_BINARY
   N_("--binary  Read and write data in binary mode."),
 #endif
@@ -996,6 +1058,156 @@
 # endif
 #endif
 }
+
+/* cmp.file[f].desc markers */
+#define NONEXISTENT   (-1)                /* nonexistent file */
+#define UNOPENED      (-2)                /* unopened file (e.g. directory) */
+#define TEMPNONEXIST (-99)                /* temporarily treat file object as 
nonexistent */
+#define ERRNO_ENCODE(errno) (-3 - (errno)) /* encoded errno value */
+#define ERRNO_DECODE(desc)  (-3 - (desc))  /* inverse of ERRNO_ENCODE */
+
+/* Compare the file properties (except content).  */
+static void
+compare_properties (struct file_data const file[]) {
+  if (compare == 0) return;
+
+  is_different = 0;
+  bool file0_exists = file[0].desc != NONEXISTENT;
+  bool file1_exists = file[1].desc != NONEXISTENT;
+  bool only_one_file_exists = !file0_exists || !file1_exists;
+  compare_width[0] = compare_width[3] = compare_width[4] = compare_width[5] = 
0;
+
+  if (only_one_file_exists)
+    is_different |= PROPERTIES;
+
+  if ((compare & TIME) && (only_one_file_exists ||
+                               different_time (&file[0].stat,&file[1].stat)))
+    is_different |= TIME;
+
+  if (compare & MODE)
+    {
+      char modebuf0[12],modebuf1[12];
+      if (file0_exists)
+       filemodestring (&file[0].stat, modebuf0);
+      if (file1_exists)
+       filemodestring (&file[1].stat, modebuf1);
+      if (only_one_file_exists || strcmp(modebuf0,modebuf1) != 0)
+       is_different |= MODE;
+    }
+
+  if ((compare & SIZE) && (only_one_file_exists ||
+                               file[0].stat.st_size != file[1].stat.st_size ))
+    {
+      is_different |= SIZE;
+      compare_width[3] = 0;
+      off_t q1 = file0_exists ? file[0].stat.st_size : 0;
+      off_t q2 = file1_exists ? file[1].stat.st_size : 0;
+      do {
+       if (q1 /= 10) ++compare_width[3];
+       if (q2 /= 10) --compare_width[3];
+      } while ((q1 | q2) != 0);
+    }
+
+  if ((compare & OWNER) && (only_one_file_exists ||
+                               file[0].stat.st_uid != file[1].stat.st_uid))
+    {
+      is_different |= OWNER;
+      compare_width[4] = strlen (file0_exists ? getuser (file[0].stat.st_uid) 
: "") -
+                        strlen (file1_exists ? getuser (file[1].stat.st_uid) : 
"");
+    }
+
+  if ((compare & GROUP) && (only_one_file_exists ||
+                               file[0].stat.st_gid != file[1].stat.st_gid))
+    {
+      is_different |= GROUP;
+      compare_width[5] = strlen (file0_exists ? getgroup (file[0].stat.st_gid) 
: "") -
+                        strlen (file1_exists ? getgroup (file[1].stat.st_gid) 
: "");
+    }
+
+  if ((compare & OBJECTS) && (only_one_file_exists ||
+                               file_type (&file[0].stat)!=file_type 
(&file[1].stat)))
+    is_different |= OBJECTS;
+
+  if ((is_different & PROPERTIES) || compare & CONTENT)
+    compare_width[0] = strlen (file[0].name) - strlen (file[1].name);
+
+  return;
+}
+
+/* Show the properties of two files (one of which can be nonexistent).
+   Value is EXIT_SUCCESS if the properties are the same, EXIT_FAILURE if
+   different, EXIT_TROUBLE if there is a problem opening them.  */
+static int
+show_properties (struct comparison const *cmp) {
+  if (brief)
+    return briefly_report (0, cmp->file);
+  else
+    {
+      files[0] = cmp->file[0];
+      files[1] = cmp->file[1];
+      setup_output (cmp);
+      begin_output ();
+      /* This is a difference.  */
+      return EXIT_FAILURE;
+    }
+}
+
+/* Compare the contents of two files (one of which can be nonexistent).
+   Value is EXIT_SUCCESS if the contents are the same, EXIT_FAILURE if
+   different, EXIT_TROUBLE if there is a problem opening them.  */
+static int
+compare_contents_and_properties (struct comparison *cmp,const bool same_files) 
{
+
+  int f;
+  int status = EXIT_SUCCESS;
+
+  /* Open the files and record their descriptors.  */
+
+  if (cmp->file[0].desc == UNOPENED)
+    if ((cmp->file[0].desc = open (cmp->file[0].name, O_RDONLY, 0)) < 0)
+      {
+       perror_with_name (cmp->file[0].name);
+       status = EXIT_TROUBLE;
+      }
+  if (cmp->file[1].desc == UNOPENED)
+    {
+      if (same_files)
+       cmp->file[1].desc = cmp->file[0].desc;
+      else if ((cmp->file[1].desc = open (cmp->file[1].name, O_RDONLY, 0))
+              < 0)
+       {
+         perror_with_name (cmp->file[1].name);
+         status = EXIT_TROUBLE;
+       }
+    }
+
+#if HAVE_SETMODE_DOS
+  if (binary)
+    for (f = 0; f < 2; f++)
+      if (0 <= cmp->file[f].desc)
+       set_binary_mode (cmp->file[f].desc, true);
+#endif
+
+  /* Compare the files, if no error was found.  */
+
+  if (status == EXIT_SUCCESS)
+    status = diff_2_files (cmp);
+
+  /* Close the file descriptors.  */
+
+  if (0 <= cmp->file[0].desc && close (cmp->file[0].desc) != 0)
+    {
+      perror_with_name (cmp->file[0].name);
+      status = EXIT_TROUBLE;
+    }
+  if (0 <= cmp->file[1].desc && cmp->file[0].desc != cmp->file[1].desc
+      && close (cmp->file[1].desc) != 0)
+    {
+      perror_with_name (cmp->file[1].name);
+      status = EXIT_TROUBLE;
+    }
+  return status;
+}
 
 /* Compare two files (or dirs) with parent comparison PARENT
    and names NAME0 and NAME1.
@@ -1040,13 +1252,6 @@
   memset (cmp.file, 0, sizeof cmp.file);
   cmp.parent = parent;
 
-  /* cmp.file[f].desc markers */
-#define NONEXISTENT (-1) /* nonexistent file */
-#define UNOPENED (-2) /* unopened file (e.g. directory) */
-#define ERRNO_ENCODE(errno) (-3 - (errno)) /* encoded errno value */
-
-#define ERRNO_DECODE(desc) (-3 - (desc)) /* inverse of ERRNO_ENCODE */
-
   cmp.file[0].desc = name0 ? UNOPENED : NONEXISTENT;
   cmp.file[1].desc = name1 ? UNOPENED : NONEXISTENT;
 
@@ -1112,11 +1317,11 @@
        }
     }
 
-  /* Mark files as nonexistent as needed for -N and -P, if they are
-     inaccessible empty regular files (the kind of files that 'patch'
-     creates to indicate nonexistent backups), or if they are
-     top-level files that do not exist but their counterparts do
-     exist.  */
+  /* Mark files as nonexistent as needed for --new-file (-N) and
+     --unidirectional-new-file, if they are inaccessible empty
+     regular files (the kind of files that 'patch' creates to
+     indicate nonexistent backups), or if they are top-level files
+     that do not exist but their counterparts do exist.  */
   for (f = 0; f < 2; f++)
     if ((new_file || (f == 0 && unidirectional_new_file))
        && (cmp.file[f].desc == UNOPENED
@@ -1190,21 +1395,48 @@
     }
   else if (DIR_P (0) & DIR_P (1))
     {
+      /* Both objects are directories (D). One could be nonexistent (N),
+        if --new-file or --unidirectional-new-file were requested.
+        DD DN.  */
+
       if (output_style == OUTPUT_IFDEF)
        fatal ("-D option not supported with directories");
 
-      /* If both are directories, compare the files in them.  */
+      bool reported = false;
+      if (compare & OBJECTS)
+       {
+         compare_properties (cmp.file);
+         if ((is_different & PROPERTIES))
+           {
+             status = show_properties (&cmp);
+             reported = true;
+           }
+
+         if (report_identical_files && status == EXIT_SUCCESS)
+           {
+             message ("Directories %s and %s are identical\n",
+                       file_label[0] ? file_label[0] : cmp.file[0].name,
+                       file_label[1] ? file_label[1] : cmp.file[1].name);
+             reported = true;
+           }
+       }
+
+      /* Compare the files in them.  */
 
       if (parent && !recursive)
        {
          /* But don't compare dir contents one level down
             unless -r was specified.
             See POSIX 1003.1-2001 for this format.  */
-         message ("Common subdirectories: %s and %s\n",
-                  cmp.file[0].name, cmp.file[1].name);
+         if (!reported)
+           message ("Common subdirectories: %s and %s\n",
+                    cmp.file[0].name, cmp.file[1].name);
        }
       else
-       status = diff_dirs (&cmp, compare_files);
+       {
+         int new_status = diff_dirs (&cmp, compare_files);
+         status = MAX (status, new_status);
+       }
     }
   else if ((DIR_P (0) | DIR_P (1))
           || (parent
@@ -1215,14 +1447,41 @@
        {
          /* We have a subdirectory that exists only in one directory.  */
 
+         /* FIXME - the previous comment is wrong? */
+         /* The objects are a special file and nonexistent file.
+            SN.  */
+         bool done = false;
+         if ((compare & OBJECTS)
+             && (new_file || (unidirectional_new_file
+                              && cmp.file[0].desc == NONEXISTENT)))
+           {
+             compare_properties (cmp.file);
+             if (is_different & PROPERTIES)
+               {
+                 /* The objects are a special file and nonexistent file,
+                    with --new-file or unidirectional-new-file.
+                    SN.  */
+                 status = show_properties (&cmp);
+                 done = true;
+               }
+           }
+
          if ((DIR_P (0) | DIR_P (1))
              && recursive
              && (new_file
                  || (unidirectional_new_file
                      && cmp.file[0].desc == NONEXISTENT)))
-           status = diff_dirs (&cmp, compare_files);
-         else
            {
+             /* FIXME - can we ever get here? */
+printf("XXXX Let Duncan Moore know if you see this message XXXX\n"); // !!
+             int new_status = diff_dirs (&cmp, compare_files);
+             status = MAX (status, new_status);
+           }
+         else if (!done)
+           {
+             /* The objects are a special file and nonexistent file,
+                with no --new-file or unidirectional-new-file.
+                SN.  */
              char const *dir;
 
              /* PARENT must be non-NULL here.  */
@@ -1237,71 +1496,121 @@
        }
       else
        {
-         /* We have two files that are not to be compared.  */
+         /* The objects are regular files (R), directories (D) or
+            special files (S). None of them are nonexistent.
+            DR SR DS SS.  */
+         if (compare & OBJECTS)
+           {
+             compare_properties (cmp.file);
+             if (S_ISREG (cmp.file[0].stat.st_mode)
+                 || S_ISREG (cmp.file[1].stat.st_mode))
+               {
+                 /* DR SR.  */
+                 if (!(compare == 0 || (compare & CONTENT)))
+                   /* The file contents are not being compared.
+                      The properties must be different.  */
+                   status = show_properties (&cmp);
+                 else
+                   {
+                     int nonreg_file = S_ISREG (cmp.file[0].stat.st_mode) ? 1 
: 0;
+                     int temp_desc = cmp.file[nonreg_file].desc;
+                     cmp.file[nonreg_file].desc = TEMPNONEXIST;
+                     status = compare_contents_and_properties(&cmp,same_files);
+                     cmp.file[nonreg_file].desc = temp_desc;
+                   }
+               }
+             else
+               /* DS SS.  */
+               status = show_properties (&cmp);
 
-         /* See POSIX 1003.1-2001 for this format.  */
-         message5 ("File %s is a %s while file %s is a %s\n",
-                   file_label[0] ? file_label[0] : cmp.file[0].name,
-                   file_type (&cmp.file[0].stat),
-                   file_label[1] ? file_label[1] : cmp.file[1].name,
-                   file_type (&cmp.file[1].stat));
+            /* Report (normally) empty regular files (E), directories and
+               special files that produce no 'content' differences as having
+               different 'content', since this won't have been shown.  */
+             bool report = true;
+             if ((is_different & CONTENT) &&
+                 (S_ISREG (cmp.file[0].stat.st_mode) ||
+                  S_ISREG (cmp.file[1].stat.st_mode)))
+               report = false;
 
-         /* This is a difference.  */
-         status = EXIT_FAILURE;
+             if (report && ((DIR_P (0) | DIR_P (1)) ||
+                            (!(is_different & CONTENT) &&
+                              (S_ISREG (cmp.file[0].stat.st_mode) ||
+                               S_ISREG (cmp.file[1].stat.st_mode)))))
+               {
+                 /* DE SE DS.  */
+                 if (compare == 0 || (compare & CONTENT))
+                   {
+                     status = MAX (status, EXIT_FAILURE);
+                     if (!brief)
+                       message ("Contents of %s and %s differ\n",
+                               file_label[0] ? file_label[0] : 
cmp.file[0].name,
+                               file_label[1] ? file_label[1] : 
cmp.file[1].name);
+                   }
+               }
+
+             if (DIR_P (0) | DIR_P (1))
+               {
+                 /* DS DR.  */
+                 if (recursive && (new_file
+                                   || (unidirectional_new_file && DIR_P (1))))
+                   {
+                     cmp.file[DIR_P (0)].desc = NONEXISTENT;
+                     int new_status = diff_dirs (&cmp, compare_files);
+                     status = MAX (status, new_status);
+                   }
+               }
+           }
+         else
+           {
+             /* DS DR SR SS.  */
+             /* We have two files that are not to be compared.  */
+
+             /* See POSIX 1003.1-2001 for this format.  */
+             message2 ("File %s is a %s while file %s is a %s\n",
+                       file_label[0] ? file_label[0] : cmp.file[0].name,
+                       file_type (&cmp.file[0].stat),
+                       file_label[1] ? file_label[1] : cmp.file[1].name,
+                       file_type (&cmp.file[1].stat));
+
+             /* This is a difference.  */
+             status = EXIT_FAILURE;
+           }
        }
     }
-  else if (files_can_be_treated_as_binary
-          && 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)
-    {
-      message ("Files %s and %s differ\n",
-              file_label[0] ? file_label[0] : cmp.file[0].name,
-              file_label[1] ? file_label[1] : cmp.file[1].name);
-      status = EXIT_FAILURE;
-    }
   else
     {
-      /* Both exist and neither is a directory.  */
-
-      /* Open the files and record their descriptors.  */
+      /* Both objects 'exist' and neither is a directory.
+        One file may be marked nonexistent - it exists only
+        for the purposes of --new-file or --unidirectional-new-file.
+        RR RN.  */
 
-      int oflags = O_RDONLY | (binary ? O_BINARY : 0);
+      compare_properties (cmp.file);
 
-      if (cmp.file[0].desc == UNOPENED)
-       if ((cmp.file[0].desc = open (cmp.file[0].name, oflags, 0)) < 0)
-         {
-           perror_with_name (cmp.file[0].name);
-           status = EXIT_TROUBLE;
-         }
-      if (cmp.file[1].desc == UNOPENED)
+      if ( !(compare == 0 || (compare & CONTENT) ) ||
+             ( if_different &&
+           !different_time_or_size (&cmp.file[0].stat, &cmp.file[1].stat) ) )
        {
-         if (same_files)
-           cmp.file[1].desc = cmp.file[0].desc;
-         else if ((cmp.file[1].desc = open (cmp.file[1].name, oflags, 0)) < 0)
-           {
-             perror_with_name (cmp.file[1].name);
-             status = EXIT_TROUBLE;
-           }
+         /* The file contents are not being compared.  */
+         if (is_different & PROPERTIES)
+           status = show_properties (&cmp);
        }
-
-      /* Compare the files, if no error was found.  */
-
-      if (status == EXIT_SUCCESS)
-       status = diff_2_files (&cmp);
-
-      /* Close the file descriptors.  */
-
-      if (0 <= cmp.file[0].desc && close (cmp.file[0].desc) != 0)
+      else if (files_can_be_treated_as_binary
+              && 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)
        {
-         perror_with_name (cmp.file[0].name);
-         status = EXIT_TROUBLE;
+         /* The file contents are being compared and can be treated as binary. 
 */
+         message ("Files %s and %s differ\n",
+                  file_label[0] ? file_label[0] : cmp.file[0].name,
+                  file_label[1] ? file_label[1] : cmp.file[1].name);
+         status = EXIT_FAILURE;
        }
-      if (0 <= cmp.file[1].desc && cmp.file[0].desc != cmp.file[1].desc
-         && close (cmp.file[1].desc) != 0)
+      else
        {
-         perror_with_name (cmp.file[1].name);
-         status = EXIT_TROUBLE;
+         /* The file contents are being compared, and may or may not be 
binary.  */
+
+         status = compare_contents_and_properties(&cmp, same_files);
+
        }
     }
 
diff -u -d -x '*.[oea]*' -x '[.M]*' diffutils-3.0/src/diff.h 
diffutils-3.0.KKK/src/diff.h
--- diffutils-3.0/src/diff.h    2010-04-15 20:53:08.000000000 +0100
+++ diffutils-3.0.KKK/src/diff.h        2010-09-27 11:59:38.281250000 +0100
@@ -83,6 +83,46 @@
 
 XTERN enum output_style output_style;
 
+/* A bit-field of file properties to compare and output for --compare.
+   All bits 0, means do a standard diff comparison.  */
+enum
+{
+  /* Compare file contents.  */
+  CONTENT = 1 << 0,
+
+  /* Compare file timestamps.  */
+  TIME    = 1 << 1,
+
+  /* Compare file modes.  */
+  MODE    = 1 << 2,
+
+  /* Compare file sizes.  */
+  SIZE    = 1 << 3,
+
+  /* Compare file owners.  */
+  OWNER   = 1 << 4,
+
+  /* Compare file groups.  */
+  GROUP   = 1 << 5,
+
+  /* Compare file types.  */
+  OBJECTS = 1 << 6,
+
+  /* Mask for all properties except content.  */
+  PROPERTIES = ~CONTENT
+};
+/* The properties to compare.  */
+XTERN unsigned int compare;
+/* True if the property is different.  */
+XTERN unsigned int is_different;
+
+/* The difference in output width of the --compare properties of
+   the two files.  */
+XTERN int compare_width[7];
+
+/* Only compare file contents if the sizes or timestamps are different.  */
+XTERN bool if_different;
+
 /* Nonzero if output cannot be generated for identical files.  */
 XTERN bool no_diff_means_no_output;
 
@@ -135,6 +175,10 @@
 /* Ignore changes that affect only lines matching this regexp (-I).  */
 XTERN struct re_pattern_buffer ignore_regexp;
 
+/* If comparing directories, compare their common subdirectories
+   recursively.  */
+XTERN bool recursive;
+
 /* Say only whether files differ, not how (-q).  */
 XTERN bool brief;
 
@@ -316,10 +360,11 @@
 /* Declare various functions.  */
 
 /* analyze.c */
+int briefly_report (int, struct file_data const []);
 int diff_2_files (struct comparison *);
 
 /* context.c */
-void print_context_header (struct file_data[], bool);
+void print_header (struct file_data[], enum output_style);
 void print_context_script (struct change *, bool);
 
 /* dir.c */
@@ -358,9 +403,12 @@
 enum changes analyze_hunk (struct change *, lin *, lin *, lin *, lin *);
 void begin_output (void);
 void debug_script (struct change *);
+bool different_time (const struct stat *, const struct stat *);
+bool different_time_or_size (const struct stat *, const struct stat *);
 void fatal (char const *) __attribute__((noreturn));
 void finish_output (void);
 void message (char const *, char const *, char const *);
+void message2 (char const *, char const *, char const *, char const *, char 
const *);
 void message5 (char const *, char const *, char const *, char const *, char 
const *);
 void output_1_line (char const *, char const *, char const *, char const *);
 void perror_with_name (char const *);
@@ -369,5 +417,5 @@
 void print_message_queue (void);
 void print_number_range (char, struct file_data *, lin, lin);
 void print_script (struct change *, struct change * (*) (struct change *), 
void (*) (struct change *));
-void setup_output (char const *, char const *, bool);
+void setup_output (struct comparison const *);
 void translate_range (struct file_data const *, lin, lin, long int *, long int 
*);
diff -u -d -x '*.[oea]*' -x '[.M]*' diffutils-3.0/src/util.c 
diffutils-3.0.KKK/src/util.c
--- diffutils-3.0/src/util.c    2010-04-15 20:53:08.000000000 +0100
+++ diffutils-3.0.KKK/src/util.c        2010-09-27 11:59:38.281250000 +0100
@@ -83,6 +83,13 @@
 }
 
 void
+message2 (char const *format_msgid, char const *arg1, char const *arg2,
+         char const *arg3, char const *arg4)
+{
+  message5 (format_msgid, arg1, arg2, arg3, arg4);
+}
+
+void
 message5 (char const *format_msgid, char const *arg1, char const *arg2,
          char const *arg3, char const *arg4)
 {
@@ -151,14 +158,16 @@
 
 static char const *current_name0;
 static char const *current_name1;
-static bool currently_recursive;
+static bool show_title;
 
 void
-setup_output (char const *name0, char const *name1, bool recursive)
+setup_output (struct comparison const *cmp)
 {
-  current_name0 = name0;
-  current_name1 = name1;
-  currently_recursive = recursive;
+  current_name0 = file_label[0] ? file_label[0] : cmp->file[0].name;
+  current_name1 = file_label[1] ? file_label[1] : cmp->file[1].name;
+  show_title = cmp->parent != 0 ||
+           ((compare & OBJECTS) && (S_ISDIR (cmp->file[0].stat.st_mode) !=0
+                                 || S_ISDIR (cmp->file[1].stat.st_mode) !=0));
   outfile = 0;
 }
 
@@ -247,26 +256,15 @@
 
       /* If handling multiple files (because scanning a directory),
         print which files the following output is about.  */
-      if (currently_recursive)
+      if (show_title)
        printf ("%s\n", name);
     }
 
   free (name);
 
-  /* A special header is needed at the beginning of context output.  */
-  switch (output_style)
-    {
-    case OUTPUT_CONTEXT:
-      print_context_header (files, false);
-      break;
-
-    case OUTPUT_UNIFIED:
-      print_context_header (files, true);
-      break;
-
-    default:
-      break;
-    }
+  /* A special header is needed at the beginning of context output,
+     and normal output with --compare.  */
+  print_header (files, output_style);
 }
 
 /* Call after the end of output of diffs for one file.
@@ -786,3 +784,30 @@
 
   fflush (stderr);
 }
+
+/* Return 1 if timestamp of *ST1 and *ST2 is different,
+   and 0 otherwise. */
+
+bool
+different_time (const struct stat *st1, const struct stat *st2)
+{
+  return ( st1->st_mtime != st2->st_mtime
+#ifdef ST_MTIM_NSEC
+       || st1->st_mtim.ST_MTIM_NSEC != st2->st_mtim.ST_MTIM_NSEC
+#endif
+        ) ? 1 : 0;
+}
+
+/* Return 1 if timestamp or size of *ST1 and *ST2 are different,
+   and 0 otherwise. */
+
+bool
+different_time_or_size (const struct stat *st1, const struct stat *st2)
+{
+  return ( st1->st_size != st2->st_size
+       || st1->st_mtime != st2->st_mtime
+#ifdef ST_MTIM_NSEC
+       || st1->st_mtim.ST_MTIM_NSEC != st2->st_mtim.ST_MTIM_NSEC
+#endif
+        ) ? 1 : 0;
+}

Reply via email to