📄 diff3.c
字号:
/* diff3 - compare three files line by line Copyright (C) 1988, 1989, 1992, 1993, 1994, 1995, 1996, 1998, 2001, 2002, 2004 Free Software Foundation, Inc. This program 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. This program 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 this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */#include "system.h"#include "paths.h"#include <stdio.h>#include <unlocked-io.h>#include <c-stack.h>#include <cmpbuf.h>#include <error.h>#include <exitfail.h>#include <file-type.h>#include <getopt.h>#include <inttostr.h>#include <quotesys.h>#include <version-etc.h>#include <xalloc.h>/* Internal data structures and macros for the diff3 program; includes data structures for both diff3 diffs and normal diffs. *//* Different files within a three way diff. */#define FILE0 0#define FILE1 1#define FILE2 2/* A three way diff is built from two two-way diffs; the file which the two two-way diffs share is: */#define FILEC FILE2/* Different files within a two way diff. FC is the common file, FO the other file. */#define FO 0#define FC 1/* The ranges are indexed by */#define RANGE_START 0#define RANGE_END 1enum diff_type { ERROR, /* Should not be used */ ADD, /* Two way diff add */ CHANGE, /* Two way diff change */ DELETE, /* Two way diff delete */ DIFF_ALL, /* All three are different */ DIFF_1ST, /* Only the first is different */ DIFF_2ND, /* Only the second */ DIFF_3RD /* Only the third */};/* Two way diff */struct diff_block { lin ranges[2][2]; /* Ranges are inclusive */ char **lines[2]; /* The actual lines (may contain nulls) */ size_t *lengths[2]; /* Line lengths (including newlines, if any) */ struct diff_block *next;};/* Three way diff */struct diff3_block { enum diff_type correspond; /* Type of diff */ lin ranges[3][2]; /* Ranges are inclusive */ char **lines[3]; /* The actual lines (may contain nulls) */ size_t *lengths[3]; /* Line lengths (including newlines, if any) */ struct diff3_block *next;};/* Access the ranges on a diff block. */#define D_LOWLINE(diff, filenum) \ ((diff)->ranges[filenum][RANGE_START])#define D_HIGHLINE(diff, filenum) \ ((diff)->ranges[filenum][RANGE_END])#define D_NUMLINES(diff, filenum) \ (D_HIGHLINE (diff, filenum) - D_LOWLINE (diff, filenum) + 1)/* Access the line numbers in a file in a diff by relative line numbers (i.e. line number within the diff itself). Note that these are lvalues and can be used for assignment. */#define D_RELNUM(diff, filenum, linenum) \ ((diff)->lines[filenum][linenum])#define D_RELLEN(diff, filenum, linenum) \ ((diff)->lengths[filenum][linenum])/* And get at them directly, when that should be necessary. */#define D_LINEARRAY(diff, filenum) \ ((diff)->lines[filenum])#define D_LENARRAY(diff, filenum) \ ((diff)->lengths[filenum])/* Next block. */#define D_NEXT(diff) ((diff)->next)/* Access the type of a diff3 block. */#define D3_TYPE(diff) ((diff)->correspond)/* Line mappings based on diffs. The first maps off the top of the diff, the second off of the bottom. */#define D_HIGH_MAPLINE(diff, fromfile, tofile, linenum) \ ((linenum) \ - D_HIGHLINE ((diff), (fromfile)) \ + D_HIGHLINE ((diff), (tofile)))#define D_LOW_MAPLINE(diff, fromfile, tofile, linenum) \ ((linenum) \ - D_LOWLINE ((diff), (fromfile)) \ + D_LOWLINE ((diff), (tofile)))/* Options variables for flags set on command line. *//* If nonzero, treat all files as text files, never as binary. */static bool text;/* Remove trailing carriage returns from input. */static bool strip_trailing_cr;/* If nonzero, write out an ed script instead of the standard diff3 format. */static bool edscript;/* If nonzero, in the case of overlapping diffs (type DIFF_ALL), preserve the lines which would normally be deleted from file 1 with a special flagging mechanism. */static bool flagging;/* Use a tab to align output lines (-T). */static bool initial_tab;/* If nonzero, do not output information for overlapping diffs. */static bool simple_only;/* If nonzero, do not output information for non-overlapping diffs. */static bool overlap_only;/* If nonzero, show information for DIFF_2ND diffs. */static bool show_2nd;/* If nonzero, include `:wq' at the end of the script to write out the file being edited. */static bool finalwrite;/* If nonzero, output a merged file. */static bool merge;char *program_name;static char *read_diff (char const *, char const *, char **);static char *scan_diff_line (char *, char **, size_t *, char *, char);static enum diff_type process_diff_control (char **, struct diff_block *);static bool compare_line_list (char * const[], size_t const[], char * const[], size_t const[], lin);static bool copy_stringlist (char * const[], size_t const[], char *[], size_t[], lin);static bool output_diff3_edscript (FILE *, struct diff3_block *, int const[3], int const[3], char const *, char const *, char const *);static bool output_diff3_merge (FILE *, FILE *, struct diff3_block *, int const[3], int const[3], char const *, char const *, char const *);static struct diff3_block *create_diff3_block (lin, lin, lin, lin, lin, lin);static struct diff3_block *make_3way_diff (struct diff_block *, struct diff_block *);static struct diff3_block *reverse_diff3_blocklist (struct diff3_block *);static struct diff3_block *using_to_diff3_block (struct diff_block *[2], struct diff_block *[2], int, int, struct diff3_block const *);static struct diff_block *process_diff (char const *, char const *, struct diff_block **);static void check_stdout (void);static void fatal (char const *) __attribute__((noreturn));static void output_diff3 (FILE *, struct diff3_block *, int const[3], int const[3]);static void perror_with_exit (char const *) __attribute__((noreturn));static void try_help (char const *, char const *) __attribute__((noreturn));static void usage (void);static char const *diff_program = DEFAULT_DIFF_PROGRAM;/* Values for long options that do not have single-letter equivalents. */enum{ DIFF_PROGRAM_OPTION = CHAR_MAX + 1, HELP_OPTION, STRIP_TRAILING_CR_OPTION};static struct option const longopts[] ={ {"diff-program", 1, 0, DIFF_PROGRAM_OPTION}, {"easy-only", 0, 0, '3'}, {"ed", 0, 0, 'e'}, {"help", 0, 0, HELP_OPTION}, {"initial-tab", 0, 0, 'T'}, {"label", 1, 0, 'L'}, {"merge", 0, 0, 'm'}, {"overlap-only", 0, 0, 'x'}, {"show-all", 0, 0, 'A'}, {"show-overlap", 0, 0, 'E'}, {"strip-trailing-cr", 0, 0, STRIP_TRAILING_CR_OPTION}, {"text", 0, 0, 'a'}, {"version", 0, 0, 'v'}, {0, 0, 0, 0}};intmain (int argc, char **argv){ int c, i; int common; int mapping[3]; int rev_mapping[3]; int incompat = 0; bool conflicts_found; struct diff_block *thread0, *thread1, *last_block; struct diff3_block *diff3; int tag_count = 0; char *tag_strings[3]; char *commonname; char **file; struct stat statb; exit_failure = 2; initialize_main (&argc, &argv); program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); c_stack_action (0); while ((c = getopt_long (argc, argv, "aeimvx3AEL:TX", longopts, 0)) != -1) { switch (c) { case 'a': text = true; break; case 'A': show_2nd = true; flagging = true; incompat++; break; case 'x': overlap_only = true; incompat++; break; case '3': simple_only = true; incompat++; break; case 'i': finalwrite = true; break; case 'm': merge = true; break; case 'X': overlap_only = true; /* Fall through. */ case 'E': flagging = true; /* Fall through. */ case 'e': incompat++; break; case 'T': initial_tab = true; break; case STRIP_TRAILING_CR_OPTION: strip_trailing_cr = true; break; case 'v': version_etc (stdout, "diff3", PACKAGE_NAME, PACKAGE_VERSION, "Randy Smith", (char *) 0); check_stdout (); return EXIT_SUCCESS; case DIFF_PROGRAM_OPTION: diff_program = optarg; break; case HELP_OPTION: usage (); check_stdout (); return EXIT_SUCCESS; case 'L': /* Handle up to three -L options. */ if (tag_count < 3) { tag_strings[tag_count++] = optarg; break; } try_help ("too many file label options", 0); default: try_help (0, 0); } } edscript = incompat & ~merge; /* -AeExX3 without -m implies ed script. */ show_2nd |= ~incompat & merge; /* -m without -AeExX3 implies -A. */ flagging |= ~incompat & merge; if (incompat > 1 /* Ensure at most one of -AeExX3. */ || finalwrite & merge /* -i -m would rewrite input file. */ || (tag_count && ! flagging)) /* -L requires one of -AEX. */ try_help ("incompatible options", 0); if (argc - optind != 3) { if (argc - optind < 3) try_help ("missing operand after `%s'", argv[argc - 1]); else try_help ("extra operand `%s'", argv[optind + 3]); } file = &argv[optind]; for (i = tag_count; i < 3; i++) tag_strings[i] = file[i]; /* Always compare file1 to file2, even if file2 is "-". This is needed for -mAeExX3. Using the file0 as the common file would produce wrong results, because if the file0-file1 diffs didn't line up with the file0-file2 diffs (which is entirely possible since we don't use diff's -n option), diff3 might report phantom changes from file1 to file2. Also, try to compare file0 to file1, because this is where changes are expected to come from. Diffing between these pairs of files is more likely to avoid phantom changes from file0 to file1. Historically, the default common file was file2, so some older applications (e.g. Emacs ediff) used file2 as the ancestor. So, for compatibility, if this is a 3-way diff (not a merge or edscript), prefer file2 as the common file. */ common = 2 - (edscript | merge); if (strcmp (file[common], "-") == 0) { /* Sigh. We've got standard input as the common file. We can't call diff twice on stdin. Use the other arg as the common file instead. */ common = 3 - common; if (strcmp (file[0], "-") == 0 || strcmp (file[common], "-") == 0) fatal ("`-' specified for more than one input file"); } mapping[0] = 0; mapping[1] = 3 - common; mapping[2] = common; for (i = 0; i < 3; i++) rev_mapping[mapping[i]] = i; for (i = 0; i < 3; i++) if (strcmp (file[i], "-") != 0) { if (stat (file[i], &statb) < 0) perror_with_exit (file[i]); else if (S_ISDIR (statb.st_mode)) error (EXIT_TROUBLE, EISDIR, "%s", file[i]); }#ifdef SIGCHLD /* System V fork+wait does not work if SIGCHLD is ignored. */ signal (SIGCHLD, SIG_DFL);#endif /* Invoke diff twice on two pairs of input files, combine the two diffs, and output them. */ commonname = file[rev_mapping[FILEC]]; thread1 = process_diff (file[rev_mapping[FILE1]], commonname, &last_block); thread0 = process_diff (file[rev_mapping[FILE0]], commonname, &last_block); diff3 = make_3way_diff (thread0, thread1); if (edscript) conflicts_found = output_diff3_edscript (stdout, diff3, mapping, rev_mapping, tag_strings[0], tag_strings[1], tag_strings[2]); else if (merge) { if (! freopen (file[rev_mapping[FILE0]], "r", stdin)) perror_with_exit (file[rev_mapping[FILE0]]); conflicts_found = output_diff3_merge (stdin, stdout, diff3, mapping, rev_mapping, tag_strings[0], tag_strings[1], tag_strings[2]); if (ferror (stdin)) fatal ("read failed"); } else { output_diff3 (stdout, diff3, mapping, rev_mapping); conflicts_found = false; } check_stdout (); exit (conflicts_found); return conflicts_found;}static voidtry_help (char const *reason_msgid, char const *operand){ if (reason_msgid) error (0, 0, _(reason_msgid), operand); error (EXIT_TROUBLE, 0, _("Try `%s --help' for more information."), program_name); abort ();}static voidcheck_stdout (void){ if (ferror (stdout)) fatal ("write failed"); else if (fclose (stdout) != 0) perror_with_exit (_("standard output"));}static char const * const option_help_msgid[] = { N_("-e --ed Output unmerged changes from OLDFILE to YOURFILE into MYFILE."), N_("-E --show-overlap Output unmerged changes, bracketing conflicts."), N_("-A --show-all Output all changes, bracketing conflicts."), N_("-x --overlap-only Output overlapping changes."), N_("-X Output overlapping changes, bracketing them."), N_("-3 --easy-only Output unmerged nonoverlapping changes."), "", N_("-m --merge Output merged file instead of ed script (default -A)."), N_("-L LABEL --label=LABEL Use LABEL instead of file name."), N_("-i Append `w' and `q' commands to ed scripts."), N_("-a --text Treat all files as text."), N_("--strip-trailing-cr Strip trailing carriage return on input."),
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -