diff options
| author | cgd <cgd@NetBSD.org> | 1993-03-21 09:45:37 +0000 |
|---|---|---|
| committer | cgd <cgd@NetBSD.org> | 1993-03-21 09:45:37 +0000 |
| commit | 61f282557f0bc41c0b762c629a2f4c14be8b7591 (patch) | |
| tree | 38dac9066415c3d9887f4b1e4500d13ee1aae777 /gnu/usr.bin/diff/diff.c | |
| parent | a50e92ee94fef5ef9c87e87efc192c0a3f75f06e (diff) | |
initial import of 386bsd-0.1 sources
Diffstat (limited to 'gnu/usr.bin/diff/diff.c')
| -rw-r--r-- | gnu/usr.bin/diff/diff.c | 692 |
1 files changed, 692 insertions, 0 deletions
diff --git a/gnu/usr.bin/diff/diff.c b/gnu/usr.bin/diff/diff.c new file mode 100644 index 00000000000..5d4dffdca39 --- /dev/null +++ b/gnu/usr.bin/diff/diff.c @@ -0,0 +1,692 @@ +/* GNU DIFF main routine. + Copyright (C) 1988, 1989 Free Software Foundation, Inc. + +This file is part of GNU DIFF. + +GNU DIFF is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 1, or (at your option) +any later version. + +GNU DIFF is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU DIFF; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* GNU DIFF was written by Mike Haertel, David Hayes, + Richard Stallman and Len Tower. */ + +#define GDIFF_MAIN +#include "regex.h" +#include "diff.h" +#include "getopt.h" + + +/* Nonzero for -r: if comparing two directories, + compare their common subdirectories recursively. */ + +int recursive; + +/* For debugging: don't do discard_confusing_lines. */ + +int no_discards; + +/* Return a string containing the command options with which diff was invoked. + Spaces appear between what were separate ARGV-elements. + There is a space at the beginning but none at the end. + If there were no options, the result is an empty string. + + Arguments: OPTIONVEC, a vector containing separate ARGV-elements, and COUNT, + the length of that vector. */ + +static char * +option_list (optionvec, count) + char **optionvec; /* Was `vector', but that collides on Alliant. */ + int count; +{ + int i; + int length = 0; + char *result; + + for (i = 0; i < count; i++) + length += strlen (optionvec[i]) + 1; + + result = (char *) xmalloc (length + 1); + result[0] = 0; + + for (i = 0; i < count; i++) + { + strcat (result, " "); + strcat (result, optionvec[i]); + } + + return result; +} + +/* The numbers 129 and 130 that appear in the fourth element + for the context and unidiff entries are used as a way of + telling the big switch in `main' how to process those options. */ + +static struct option longopts[] = +{ + {"ignore-blank-lines", 0, 0, 'B'}, + {"context", 2, 0, 129}, + {"ifdef", 1, 0, 'D'}, + {"show-function-line", 1, 0, 'F'}, + {"speed-large-files", 0, 0, 'H'}, + {"ignore-matching-lines", 1, 0, 'I'}, + {"file-label", 1, 0, 'L'}, + {"entire-new-files", 0, 0, 'N'}, + {"new-files", 0, 0, 'N'}, + {"starting-file", 1, 0, 'S'}, + {"initial-tab", 0, 0, 'T'}, + {"text", 0, 0, 'a'}, + {"all-text", 0, 0, 'a'}, + {"ascii", 0, 0, 'a'}, + {"ignore-space-change", 0, 0, 'b'}, + {"minimal", 0, 0, 'd'}, + {"ed", 0, 0, 'e'}, + {"reversed-ed", 0, 0, 'f'}, + {"ignore-case", 0, 0, 'i'}, + {"print", 0, 0, 'l'}, + {"rcs", 0, 0, 'n'}, + {"show-c-function", 0, 0, 'p'}, + {"binary", 0, 0, 'q'}, + {"brief", 0, 0, 'q'}, + {"recursive", 0, 0, 'r'}, + {"report-identical-files", 0, 0, 's'}, + {"expand-tabs", 0, 0, 't'}, + {"ignore-all-space", 0, 0, 'w'}, + {"unified", 2, 0, 130}, + {"version", 0, 0, 'v'}, + {0, 0, 0, 0} +}; + +main (argc, argv) + int argc; + char *argv[]; +{ + int val; + int c; + int prev = -1; + int longind; + extern char *version_string; + + program = argv[0]; + + /* Do our initializations. */ + output_style = OUTPUT_NORMAL; + always_text_flag = FALSE; + ignore_space_change_flag = FALSE; + ignore_all_space_flag = FALSE; + length_varies = FALSE; + ignore_case_flag = FALSE; + ignore_blank_lines_flag = FALSE; + ignore_regexp = 0; + function_regexp = 0; + print_file_same_flag = FALSE; + entire_new_file_flag = FALSE; + no_details_flag = FALSE; + context = -1; + line_end_char = '\n'; + tab_align_flag = FALSE; + tab_expand_flag = FALSE; + recursive = FALSE; + paginate_flag = FALSE; + ifdef_string = NULL; + heuristic = FALSE; + dir_start_file = NULL; + msg_chain = NULL; + msg_chain_end = NULL; + no_discards = 0; + + /* Decode the options. */ + + while ((c = getopt_long (argc, argv, + "0123456789abBcC:dD:efF:hHiI:lL:nNpqrsS:tTuvw", + longopts, &longind)) != EOF) + { + if (c == 0) /* Long option. */ + c = longopts[longind].val; + switch (c) + { + /* All digits combine in decimal to specify the context-size. */ + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '0': + if (context == -1) + context = 0; + /* If a context length has already been specified, + more digits allowed only if they follow right after the others. + Reject two separate runs of digits, or digits after -C. */ + else if (prev < '0' || prev > '9') + fatal ("context length specified twice"); + + context = context * 10 + c - '0'; + break; + + case 'a': + /* Treat all files as text files; never treat as binary. */ + always_text_flag = 1; + break; + + case 'b': + /* Ignore changes in amount of whitespace. */ + ignore_space_change_flag = 1; + length_varies = 1; + break; + + case 'B': + /* Ignore changes affecting only blank lines. */ + ignore_blank_lines_flag = 1; + break; + + case 'C': + case 129: /* +context[=lines] */ + case 130: /* +unified[=lines] */ + if (optarg) + { + if (context >= 0) + fatal ("context length specified twice"); + { + char *p; + for (p = optarg; *p; p++) + if (*p < '0' || *p > '9') + fatal ("invalid context length argument"); + } + context = atoi (optarg); + } + + /* Falls through. */ + case 'c': + /* Make context-style output. */ + specify_style (c == 130 ? OUTPUT_UNIFIED : OUTPUT_CONTEXT); + break; + + case 'd': + /* Don't discard lines. This makes things slower (sometimes much + slower) but will find a guaranteed minimal set of changes. */ + no_discards = 1; + break; + + case 'D': + /* Make merged #ifdef output. */ + specify_style (OUTPUT_IFDEF); + ifdef_string = optarg; + break; + + case 'e': + /* Make output that is a valid `ed' script. */ + specify_style (OUTPUT_ED); + break; + + case 'f': + /* Make output that looks vaguely like an `ed' script + but has changes in the order they appear in the file. */ + specify_style (OUTPUT_FORWARD_ED); + break; + + case 'F': + /* Show, for each set of changes, the previous line that + matches the specified regexp. Currently affects only + context-style output. */ + function_regexp = optarg; + break; + + case 'h': + /* Split the files into chunks of around 1500 lines + for faster processing. Usually does not change the result. + + This currently has no effect. */ + break; + + case 'H': + /* Turn on heuristics that speed processing of large files + with a small density of changes. */ + heuristic = 1; + break; + + case 'i': + /* Ignore changes in case. */ + ignore_case_flag = 1; + break; + + case 'I': + /* Ignore changes affecting only lines that match the + specified regexp. */ + ignore_regexp = optarg; + break; + + case 'l': + /* Pass the output through `pr' to paginate it. */ + paginate_flag = 1; + break; + + case 'L': + /* Specify file labels for `-c' output headers. */ + if (!file_label[0]) + file_label[0] = optarg; + else if (!file_label[1]) + file_label[1] = optarg; + else + fatal ("too many file label options"); + break; + + case 'n': + /* Output RCS-style diffs, like `-f' except that each command + specifies the number of lines affected. */ + specify_style (OUTPUT_RCS); + break; + + case 'N': + /* When comparing directories, if a file appears only in one + directory, treat it as present but empty in the other. */ + entire_new_file_flag = 1; + break; + + case 'p': + /* Make context-style output and show name of last C function. */ + specify_style (OUTPUT_CONTEXT); + function_regexp = "^[_a-zA-Z]"; + break; + + case 'q': + no_details_flag = 1; + break; + + case 'r': + /* When comparing directories, + recursively compare any subdirectories found. */ + recursive = 1; + break; + + case 's': + /* Print a message if the files are the same. */ + print_file_same_flag = 1; + break; + + case 'S': + /* When comparing directories, start with the specified + file name. This is used for resuming an aborted comparison. */ + dir_start_file = optarg; + break; + + case 't': + /* Expand tabs to spaces in the output so that it preserves + the alignment of the input files. */ + tab_expand_flag = 1; + break; + + case 'T': + /* Use a tab in the output, rather than a space, before the + text of an input line, so as to keep the proper alignment + in the input line without changing the characters in it. */ + tab_align_flag = 1; + break; + + case 'v': + printf ("GNU diff version %s\n", version_string); + break; + + case 'u': + /* Output the context diff in unidiff format. */ + specify_style (OUTPUT_UNIFIED); + break; + + case 'w': + /* Ignore horizontal whitespace when comparing lines. */ + ignore_all_space_flag = 1; + length_varies = 1; + break; + + default: + usage (); + } + prev = c; + } + + if (optind != argc - 2) + usage (); + + if (ignore_regexp) + { + char *val; + bzero (&ignore_regexp_compiled, sizeof ignore_regexp_compiled); + val = re_compile_pattern (ignore_regexp, strlen (ignore_regexp), + &ignore_regexp_compiled); + if (val != 0) + error ("%s: %s", ignore_regexp, val); + ignore_regexp_compiled.fastmap = (char *) xmalloc (256); + } + + if (function_regexp) + { + char *val; + bzero (&function_regexp_compiled, sizeof function_regexp_compiled); + val = re_compile_pattern (function_regexp, strlen (function_regexp), + &function_regexp_compiled); + if (val != 0) + error ("%s: %s", function_regexp, val); + function_regexp_compiled.fastmap = (char *) xmalloc (256); + } + + if (output_style != OUTPUT_CONTEXT && output_style != OUTPUT_UNIFIED) + context = 0; + else if (context == -1) + /* Default amount of context for -c. */ + context = 3; + + switch_string = option_list (argv + 1, optind - 1); + + val = compare_files (0, argv[optind], 0, argv[optind + 1], 0); + + /* Print any messages that were saved up for last. */ + print_message_queue (); + + if (ferror (stdout) || fclose (stdout) != 0) + fatal ("write error"); + exit (val); +} + +usage () +{ + fprintf (stderr, "\ +Usage: diff [-#] [-abBcdefhHilnNprstTuvw] [-C lines] [-F regexp] [-I regexp]\n\ + [-L label [-L label]] [-S file] [-D symbol] [+ignore-blank-lines]\n\ + [+context[=lines]] [+unified[=lines]] [+ifdef=symbol]\n\ + [+show-function-line=regexp]\n"); + fprintf (stderr, "\ + [+speed-large-files] [+ignore-matching-lines=regexp] [+new-file]\n\ + [+initial-tab] [+starting-file=file] [+text] [+all-text] [+ascii]\n\ + [+minimal] [+ignore-space-change] [+ed] [+reversed-ed] [+ignore-case]\n"); + fprintf (stderr, "\ + [+print] [+rcs] [+show-c-function] [+binary] [+brief] [+recursive]\n\ + [+report-identical-files] [+expand-tabs] [+ignore-all-space]\n\ + [+file-label=label [+file-label=label]] [+version] path1 path2\n"); + exit (2); +} + +specify_style (style) + enum output_style style; +{ + if (output_style != OUTPUT_NORMAL + && output_style != style) + error ("conflicting specifications of output style"); + output_style = style; +} + +/* Compare two files (or dirs) with specified names + DIR0/NAME0 and DIR1/NAME1, at level DEPTH in directory recursion. + (if DIR0 is 0, then the name is just NAME0, etc.) + This is self-contained; it opens the files and closes them. + + Value is 0 if files are identical, 1 if different, + 2 if there is a problem opening them. */ + +int +compare_files (dir0, name0, dir1, name1, depth) + char *dir0, *dir1; + char *name0, *name1; + int depth; +{ + static char Standard_Input[] = "Standard Input"; + struct file_data inf[2]; + register int i; + int val; + int errorcount = 0; + int stat_result[2]; + + /* If this is directory comparison, perhaps we have a file + that exists only in one of the directories. + If so, just print a message to that effect. */ + + if (! entire_new_file_flag && (name0 == 0 || name1 == 0)) + { + char *name = name0 == 0 ? name1 : name0; + char *dir = name0 == 0 ? dir1 : dir0; + message ("Only in %s: %s\n", dir, name); + /* Return 1 so that diff_dirs will return 1 ("some files differ"). */ + return 1; + } + + /* Mark any nonexistent file with -1 in the desc field. */ + /* Mark unopened files (i.e. directories) with -2. */ + + inf[0].desc = name0 == 0 ? -1 : -2; + inf[1].desc = name1 == 0 ? -1 : -2; + + /* Now record the full name of each file, including nonexistent ones. */ + + if (name0 == 0) + name0 = name1; + if (name1 == 0) + name1 = name0; + + inf[0].name = dir0 == 0 ? name0 : concat (dir0, "/", name0); + inf[1].name = dir1 == 0 ? name1 : concat (dir1, "/", name1); + + /* Stat the files. Record whether they are directories. + Record in stat_result whether stat fails. */ + + for (i = 0; i <= 1; i++) + { + bzero (&inf[i].stat, sizeof(struct stat)); + inf[i].dir_p = 0; + stat_result[i] = 0; + + if (inf[i].desc != -1) + { + char *filename = inf[i].name; + + stat_result[i] = + strcmp (filename, "-") + ? stat (filename, &inf[i].stat) + : fstat (0, &inf[i].stat); + + if (stat_result[i] < 0) + { + perror_with_name (filename); + errorcount = 1; + } + else + inf[i].dir_p = + S_IFDIR == (inf[i].stat.st_mode & S_IFMT) + && strcmp (filename, "-"); + } + } + + /* See if the two named files are actually the same physical file. + If so, we know they are identical without actually reading them. */ + + if (output_style != OUTPUT_IFDEF + && inf[0].stat.st_ino == inf[1].stat.st_ino + && inf[0].stat.st_dev == inf[1].stat.st_dev + && stat_result[0] == 0 + && stat_result[1] == 0) + { + val = 0; + goto done; + } + + if (name0 == 0) + inf[0].dir_p = inf[1].dir_p; + if (name1 == 0) + inf[1].dir_p = inf[0].dir_p; + + /* Open the files and record their descriptors. */ + + for (i = 0; i <= 1; i++) + { + if (inf[i].desc == -1) + ; + else if (!strcmp (inf[i].name, "-")) + { + inf[i].desc = 0; + inf[i].name = Standard_Input; + } + /* Don't bother opening if stat already failed. */ + else if (stat_result[i] == 0 && ! inf[i].dir_p) + { + char *filename = inf[i].name; + + inf[i].desc = open (filename, O_RDONLY, 0); + if (0 > inf[i].desc) + { + perror_with_name (filename); + errorcount = 1; + } + } + } + + if (errorcount) + { + + /* If either file should exist but fails to be opened, return 2. */ + + val = 2; + + } + else if (inf[0].dir_p && inf[1].dir_p) + { + if (output_style == OUTPUT_IFDEF) + fatal ("-D option not supported with directories"); + + /* If both are directories, compare the files in them. */ + + if (depth > 0 && !recursive) + { + /* But don't compare dir contents one level down + unless -r was specified. */ + message ("Common subdirectories: %s and %s\n", + inf[0].name, inf[1].name); + val = 0; + } + else + { + val = diff_dirs (inf[0].name, inf[1].name, + compare_files, depth, 0, 0); + } + + } + else if (depth == 0 && (inf[0].dir_p || inf[1].dir_p)) + { + + /* If only one is a directory, and it was specified in the command line, + use the file in that dir whose basename matches the other file. */ + + int dir_arg = (inf[0].dir_p ? 0 : 1); + int fnm_arg = (inf[0].dir_p ? 1 : 0); + char *p = rindex (inf[fnm_arg].name, '/'); + char *filename = concat (inf[dir_arg].name, "/", + (p ? p+1 : inf[fnm_arg].name)); + + if (inf[fnm_arg].name == Standard_Input) + fatal ("can't compare - to a directory"); + + inf[dir_arg].desc = open (filename, O_RDONLY, 0); + + if (0 > inf[dir_arg].desc) + { + perror_with_name (filename); + val = 2; + } + else + { + /* JF: patch from the net to check and make sure we can really free + this. If it's from argv[], freeing it is a *really* bad idea */ + if (0 != (dir_arg ? dir1 : dir0)) + free (inf[dir_arg].name); + inf[dir_arg].name = filename; + if (fstat (inf[dir_arg].desc, &inf[dir_arg].stat) < 0) + pfatal_with_name (inf[dir_arg].name); + + inf[dir_arg].dir_p + = (S_IFDIR == (inf[dir_arg].stat.st_mode & S_IFMT)); + if (inf[dir_arg].dir_p) + { + error ("%s is a directory but %s is not", + inf[dir_arg].name, inf[fnm_arg].name); + val = 1; + } + else + val = diff_2_files (inf, depth); + } + + } + else if (depth > 0 && (inf[0].dir_p || inf[1].dir_p)) + { + /* Perhaps we have a subdirectory that exists only in one directory. + If so, just print a message to that effect. */ + + if (inf[0].desc == -1 || inf[1].desc == -1) + { + if (entire_new_file_flag && recursive) + val = diff_dirs (inf[0].name, inf[1].name, compare_files, depth, + inf[0].desc == -1, inf[1].desc == -1); + else + { + char *dir = (inf[0].desc == -1) ? dir1 : dir0; + message ("Only in %s: %s\n", dir, name0); + val = 1; + } + } + else + { + /* We have a subdirectory in one directory + and a file in the other. */ + + if (inf[0].dir_p) + message ("%s is a directory but %s is not\n", + inf[0].name, inf[1].name); + else + message ("%s is a directory but %s is not\n", + inf[1].name, inf[0].name); + /* This is a difference. */ + val = 1; + } + } + else + { + + /* Both exist and both are ordinary files. */ + + val = diff_2_files (inf, depth); + + } + + /* Now the comparison has been done, if no error prevented it, + and VAL is the value this function will return. */ + + if (inf[0].desc >= 0) + close (inf[0].desc); + if (inf[1].desc >= 0) + close (inf[1].desc); + + done: + if (val == 0 && !inf[0].dir_p) + { + if (print_file_same_flag) + message ("Files %s and %s are identical\n", + inf[0].name, inf[1].name); + } + else + fflush (stdout); + + if (dir0 != 0) + free (inf[0].name); + if (dir1 != 0) + free (inf[1].name); + + return val; +} |
