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;
+}