📄 diff.c
字号:
/* GNU DIFF main routine.
Copyright (C) 1988, 1989, 1992, 1993 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 2, 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, Len Tower, and Paul Eggert. */
#define GDIFF_MAIN
#include "diff.h"
#include "getopt.h"
#include "fnmatch.h"
#ifndef DEFAULT_WIDTH
#define DEFAULT_WIDTH 130
#endif
#ifndef GUTTER_WIDTH_MINIMUM
#define GUTTER_WIDTH_MINIMUM 3
#endif
static char const *filetype PARAMS((struct stat const *));
static char *option_list PARAMS((char **, int));
static int add_exclude_file PARAMS((char const *));
static int ck_atoi PARAMS((char const *, int *));
int compare_files PARAMS((char const *, char const *, char const *, char const *, int));
static int specify_format PARAMS((char **, char *));
static void add_exclude PARAMS((char const *));
static void add_regexp PARAMS((struct regexp_list **, char const *));
static void specify_style PARAMS((enum output_style));
static void usage PARAMS((void));
/* 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;
size_t length = 0;
char *result;
for (i = 0; i < count; i++)
length += strlen (optionvec[i]) + 1;
result = xmalloc (length + 1);
result[0] = 0;
for (i = 0; i < count; i++)
{
strcat (result, " ");
strcat (result, optionvec[i]);
}
return result;
}
/* Convert STR to a positive integer, storing the result in *OUT.
If STR is not a valid integer, return -1 (otherwise 0). */
static int
ck_atoi (str, out)
char const *str;
int *out;
{
char const *p;
for (p = str; *p; p++)
if (*p < '0' || *p > '9')
return -1;
*out = atoi (optarg);
return 0;
}
/* Keep track of excluded file name patterns. */
static char const **exclude;
static int exclude_alloc, exclude_count;
int
excluded_filename (f)
char const *f;
{
int i;
for (i = 0; i < exclude_count; i++)
if (fnmatch (exclude[i], f, 0) == 0)
return 1;
return 0;
}
static void
add_exclude (pattern)
char const *pattern;
{
if (exclude_alloc <= exclude_count)
exclude = (char const **)
(exclude_alloc == 0
? xmalloc ((exclude_alloc = 64) * sizeof (*exclude))
: xrealloc (exclude, (exclude_alloc *= 2) * sizeof (*exclude)));
exclude[exclude_count++] = pattern;
}
static int
add_exclude_file (name)
char const *name;
{
struct file_data f;
char *p, *q, *lim;
f.name = optarg;
f.desc = (strcmp (optarg, "-") == 0
? STDIN_FILENO
: open (optarg, O_RDONLY, 0));
if (f.desc < 0 || fstat (f.desc, &f.stat) != 0)
return -1;
sip (&f, 1);
slurp (&f);
for (p = (char *)f.buffer, lim = p + f.buffered_chars; p < lim; p = q)
{
q = (char *) memchr (p, '\n', lim - p);
if (!q)
q = lim;
*q++ = 0;
add_exclude (p);
}
return close (f.desc);
}
/* The numbers 129- that appear in the fourth element of some entries
tell the big switch in `main' how to process those options. */
static struct option const longopts[] =
{
{"ignore-blank-lines", 0, 0, 'B'},
{"context", 2, 0, 'C'},
{"ifdef", 1, 0, 'D'},
{"show-function-line", 1, 0, 'F'},
{"speed-large-files", 0, 0, 'H'},
{"ignore-matching-lines", 1, 0, 'I'},
{"label", 1, 0, 'L'},
{"file-label", 1, 0, 'L'}, /* An alias, no longer recommended */
{"new-file", 0, 0, 'N'},
{"entire-new-file", 0, 0, 'N'}, /* An alias, no longer recommended */
{"unidirectional-new-file", 0, 0, 'P'},
{"starting-file", 1, 0, 'S'},
{"initial-tab", 0, 0, 'T'},
{"width", 1, 0, 'W'},
{"text", 0, 0, 'a'},
{"ascii", 0, 0, 'a'}, /* An alias, no longer recommended */
{"ignore-space-change", 0, 0, 'b'},
{"minimal", 0, 0, 'd'},
{"ed", 0, 0, 'e'},
{"forward-ed", 0, 0, 'f'},
{"ignore-case", 0, 0, 'i'},
{"paginate", 0, 0, 'l'},
{"print", 0, 0, 'l'}, /* An alias, no longer recommended */
{"rcs", 0, 0, 'n'},
{"show-c-function", 0, 0, 'p'},
{"binary", 0, 0, 'q'}, /* An alias, no longer recommended */
{"brief", 0, 0, 'q'},
{"recursive", 0, 0, 'r'},
{"report-identical-files", 0, 0, 's'},
{"expand-tabs", 0, 0, 't'},
{"version", 0, 0, 'v'},
{"ignore-all-space", 0, 0, 'w'},
{"exclude", 1, 0, 'x'},
{"exclude-from", 1, 0, 'X'},
{"side-by-side", 0, 0, 'y'},
{"unified", 2, 0, 'U'},
{"left-column", 0, 0, 129},
{"suppress-common-lines", 0, 0, 130},
{"sdiff-merge-assist", 0, 0, 131},
{"old-line-format", 1, 0, 132},
{"new-line-format", 1, 0, 133},
{"unchanged-line-format", 1, 0, 134},
{"line-format", 1, 0, 135},
{"old-group-format", 1, 0, 136},
{"new-group-format", 1, 0, 137},
{"unchanged-group-format", 1, 0, 138},
{"changed-group-format", 1, 0, 139},
{"horizon-lines", 1, 0, 140},
{0, 0, 0, 0}
};
/*
int
main (argc, argv)
int argc;
char *argv[];
{
int val;
int c;
int prev = -1;
int width = DEFAULT_WIDTH;
// Do our initializations.
program = argv[0];
output_style = OUTPUT_NORMAL;
context = -1;
line_end_char = '\n';
// Decode the options.
while ((c = getopt_long (argc, argv,
"0123456789abBcC:dD:efF:hHiI:lL:nNpPqrsS:tTuU:vwW:x:X:y",
longopts, 0)) != EOF)
{
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;
setmode(0, O_BINARY);
setmode(1, O_BINARY);
break;
case 'b':
// Ignore changes in amount of white space.
ignore_space_change_flag = 1;
length_varies = 1;
ignore_some_changes = 1;
break;
case 'B':
// Ignore changes affecting only blank lines.
ignore_blank_lines_flag = 1;
ignore_some_changes = 1;
break;
case 'C': // +context[=lines]
case 'U': // +unified[=lines]
if (optarg)
{
if (context >= 0)
fatal ("context length specified twice");
if (ck_atoi (optarg, &context))
fatal ("invalid context length argument");
}
// Falls through.
case 'c':
// Make context-style output.
specify_style (c == 'U' ? 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);
{
int i, err = 0;
static char const C_ifdef_group_formats[] =
"#ifndef %s\n%%<#endif // not %s \n%c#ifdef %s\n%%>#endif // %s \n%c%%=%c#ifndef %s\n%%<#else // %s \n%%>#endif // %s \n";
char *b = xmalloc (sizeof (C_ifdef_group_formats)
+ 7 * strlen(optarg) - 14 // 7*"%s"
- 8 // 5*"%%" + 3*"%c" );
sprintf (b, C_ifdef_group_formats,
optarg, optarg, 0,
optarg, optarg, 0, 0,
optarg, optarg, optarg);
for (i = 0; i < 4; i++)
{
err |= specify_format (&group_format[i], b);
b += strlen (b) + 1;
}
if (err)
error ("conflicting #ifdef formats", 0, 0);
}
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.
add_regexp (&function_regexp_list, 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;
ignore_some_changes = 1;
break;
case 'I':
// Ignore changes affecting only lines that match the
specified regexp.
add_regexp (&ignore_regexp_list, optarg);
ignore_some_changes = 1;
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);
add_regexp (&function_regexp_list, "^[_a-zA-Z$]");
break;
case 'P':
// When comparing directories, if a file appears only in
the second directory of the two,
treat it as present but empty in the other.
unidirectional_new_file_flag = 1;
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 'u':
// Output the context diff in unidiff format.
specify_style (OUTPUT_UNIFIED);
break;
case 'v':
fprintf (stderr, "GNU diff version %s\n", version_string);
break;
case 'w':
// Ignore horizontal white space when comparing lines.
ignore_all_space_flag = 1;
ignore_some_changes = 1;
length_varies = 1;
break;
case 'x':
add_exclude (optarg);
break;
case 'X':
if (add_exclude_file (optarg) != 0)
pfatal_with_name (optarg);
break;
case 'y':
// Use side-by-side (sdiff-style) columnar output.
specify_style (OUTPUT_SDIFF);
break;
case 'W':
// Set the line width for OUTPUT_SDIFF.
if (ck_atoi (optarg, &width) || width <= 0)
fatal ("column width must be a positive integer");
break;
case 129:
sdiff_left_only = 1;
break;
case 130:
sdiff_skip_common_lines = 1;
break;
case 131:
// sdiff-style columns output.
specify_style (OUTPUT_SDIFF);
sdiff_help_sdiff = 1;
break;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -