From: Andreas Gruenbacher Subject: diff3-style merges Implement a diff3-style merge format: with the --merge option alone, all hunks that apply without fuzz will be applied as usual, and hunks that apply within the allowed fuzz limit will be bracketed as: <<<<<<< old lines from patch ||||||| new lines from patch ======= merge result >>>>>>> When the --show-all option is given in addition, hunks that apply without fuzz will be bracketed as: <<<<<<< old lines from patch ======= merge result >>>>>>> Signed-off-by: Andreas Gruenbacher --- Makefile.in | 6 +- common.h | 20 +++++++ merge.c | 161 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ patch.c | 62 +++++++++++++---------- 4 files changed, 222 insertions(+), 27 deletions(-) Index: b/patch.c =================================================================== --- a/patch.c +++ b/patch.c @@ -48,22 +48,12 @@ struct utimbuf }; #endif -/* Output stream state. */ -struct outstate -{ - FILE *ofp; - bool after_newline; - bool zero_output; -}; - /* procedures */ static FILE *create_output_file (char const *, int); static LINENUM locate_hunk (LINENUM); static bool apply_hunk (struct outstate *, LINENUM); -static bool copy_till (struct outstate *, LINENUM); static bool patch_match (LINENUM, LINENUM, LINENUM, LINENUM); -static bool similar (char const *, size_t, char const *, size_t); static bool spew_output (struct outstate *); static char const *make_temp (char); static void abort_hunk_context (bool, bool); @@ -79,6 +69,7 @@ static void usage (FILE *, int) __attrib static void (*abort_hunk) (bool, bool) = abort_hunk_context; +static bool merge; static bool make_backups; static bool backup_if_mismatch; static char const *version_control; @@ -88,9 +79,6 @@ static bool remove_empty_files; /* true if -R was specified on command line. */ static bool reverse_flag_specified; -/* how many input lines have been irretractably output */ -static LINENUM last_frozen_line; - static char const *do_defines; /* symbol to patch using ifdef, ifndef, etc. */ static char const if_defined[] = "\n#ifdef %s\n"; static char const not_defined[] = "\n#ifndef %s\n"; @@ -108,7 +96,6 @@ static char *rejname; static char const * volatile TMPREJNAME; static int volatile TMPREJNAME_needs_removal; -static LINENUM last_offset; static LINENUM maxfuzz = 2; static char serrbuf[BUFSIZ]; @@ -119,7 +106,7 @@ int main (int argc, char **argv) { char const *val; - bool somefailed = false; + bool somemerged = false, somefailed = false; struct outstate outstate; char numbuf[LINENUM_LENGTH_BOUND + 1]; @@ -186,6 +173,7 @@ main (int argc, char **argv) reinitialize_almost_everything() ) { /* for each patch in patch file */ int hunk = 0; + int merged = 0; int failed = 0; bool mismatch = false; char *outname = outfile ? outfile : inname; @@ -295,7 +283,16 @@ main (int argc, char **argv) goto skip_hunk; } else if (!where) { - goto skip_hunk; + if (merge) { + if (merge_hunk(&outstate)) { + merged++; + mismatch = 1; + } else { + /* FIXME: try harder! */ + goto skip_hunk; + } + } else + goto skip_hunk; } else { if (!apply_hunk (&outstate, where)) goto skip_hunk; @@ -303,10 +300,16 @@ main (int argc, char **argv) if (verbosity == VERBOSE || (verbosity != SILENT && (fuzz || last_offset))) { - say ("Hunk #%d succeeded at %s", hunk, - format_linenum (numbuf, newwhere)); - if (fuzz) - say (" with fuzz %s", format_linenum (numbuf, fuzz)); + if (fuzz > mymaxfuzz) { + say ("Hunk #%d merged at %s with conflicts", hunk, + format_linenum (numbuf, newwhere)); + somefailed = true; + } else { + say ("Hunk #%d succeeded at %s", hunk, + format_linenum (numbuf, newwhere)); + if (fuzz) + say (" with fuzz %s", format_linenum (numbuf, fuzz)); + } if (last_offset) say (" (offset %s line%s)", format_linenum (numbuf, last_offset), @@ -325,6 +328,9 @@ skip_hunk: format_linenum (numbuf, newwhere)); } + if (merged) + somemerged = true; + if (!skip_rest_of_patch) { if (got_hunk < 0 && using_plan_a) @@ -374,7 +380,8 @@ skip_hunk: else { if (! outstate.zero_output - && pch_says_nonexistent (! reverse)) + && pch_says_nonexistent (! reverse) + && !merged) { mismatch = true; if (verbosity != SILENT) @@ -465,7 +472,7 @@ skip_hunk: if (outstate.ofp && (ferror (outstate.ofp) || fclose (outstate.ofp) != 0)) write_fatal (); cleanup (); - if (somefailed) + if (somemerged || somefailed) exit (1); return 0; } @@ -499,7 +506,7 @@ reinitialize_almost_everything (void) skip_rest_of_patch = false; } -static char const shortopts[] = "bB:cd:D:eEfF:g:i:lnNo:p:r:RstTuvV:x:Y:z:Z"; +static char const shortopts[] = "bB:cd:D:eEfF:g:i:lMnNo:p:r:RstTuvV:x:Y:z:Z"; static struct option const longopts[] = { {"backup", no_argument, NULL, 'b'}, @@ -514,6 +521,7 @@ static struct option const longopts[] = {"get", no_argument, NULL, 'g'}, {"input", required_argument, NULL, 'i'}, {"ignore-whitespace", no_argument, NULL, 'l'}, + {"merge", no_argument, NULL, 'M'}, {"normal", no_argument, NULL, 'n'}, {"forward", no_argument, NULL, 'N'}, {"output", required_argument, NULL, 'o'}, @@ -568,6 +576,7 @@ static char const *const option_help[] = " -r FILE --reject-file=FILE Output rejects to FILE.", "", " -D NAME --ifdef=NAME Make merged if-then-else output using NAME.", +" -M --merge Produce a diff3-style merge for rejects.", " -E --remove-empty-files Remove output files that are empty after patching.", "", " -Z --set-utc Set times of patched files, assuming diff uses UTC (GMT).", @@ -706,6 +715,9 @@ get_some_switches (void) case 'l': canonicalize = true; break; + case 'M': + merge = true; + break; case 'n': diff_type = NORMAL_DIFF; break; @@ -1289,7 +1301,7 @@ init_reject (void) /* Copy input file to output, up to wherever hunk is to be applied. */ -static bool +bool copy_till (register struct outstate *outstate, register LINENUM lastline) { register LINENUM R_last_frozen_line = last_frozen_line; @@ -1375,7 +1387,7 @@ patch_match (LINENUM base, LINENUM offse /* Do two lines match with canonicalized white space? */ -static bool +bool similar (register char const *a, register size_t alen, register char const *b, register size_t blen) { Index: b/Makefile.in =================================================================== --- a/Makefile.in +++ b/Makefile.in @@ -70,7 +70,8 @@ SRCS = $(LIBSRCS) \ maketime.c partime.c \ patch.c pch.c \ quote.c quotearg.c quotesys.c \ - util.c version.c xmalloc.c + util.c version.c xmalloc.c \ + merge.c OBJS = $(LIBOBJS) \ addext.$(OBJEXT) argmatch.$(OBJEXT) backupfile.$(OBJEXT) \ basename.$(OBJEXT) dirname.$(OBJEXT) \ @@ -78,7 +79,8 @@ OBJS = $(LIBOBJS) \ maketime.$(OBJEXT) partime.$(OBJEXT) \ patch.$(OBJEXT) pch.$(OBJEXT) \ quote.$(OBJEXT) quotearg.$(OBJEXT) quotesys.$(OBJEXT) \ - util.$(OBJEXT) version.$(OBJEXT) xmalloc.$(OBJEXT) hash.$(OBJEXT) + util.$(OBJEXT) version.$(OBJEXT) xmalloc.$(OBJEXT) hash.$(OBJEXT) \ + merge.$(OBJEXT) HDRS = argmatch.h backupfile.h common.h dirname.h \ error.h getopt.h gettext.h \ inp.h maketime.h partime.h pch.h \ Index: b/common.h =================================================================== --- a/common.h +++ b/common.h @@ -295,3 +295,23 @@ void *realloc (); #ifndef TTY_DEVICE #define TTY_DEVICE "/dev/tty" #endif + +/* Output stream state. */ +struct outstate +{ + FILE *ofp; + bool after_newline; + bool zero_output; +}; + +/* offset at which the previous hunk matched */ +XTERN LINENUM last_offset; + +/* how many input lines have been irretractably output */ +XTERN LINENUM last_frozen_line; + +bool similar (char const *, size_t, char const *, size_t); +bool copy_till (struct outstate *, LINENUM); + +/* Defined in merge.c */ +bool merge_hunk (struct outstate *); Index: b/merge.c =================================================================== --- /dev/null +++ b/merge.c @@ -0,0 +1,161 @@ +#define XTERN extern +#include +#include +#include +#include + +static bool context_matches_file (LINENUM, LINENUM); +static bool common_context (LINENUM, LINENUM, LINENUM); + +bool +merge_hunk (struct outstate *outstate) +{ + LINENUM old = 1; + LINENUM lastold = pch_ptrn_lines (); + LINENUM new = lastold + 1; + LINENUM lastnew = pch_end (); + LINENUM merge, lastmerge; + FILE *fp = outstate->ofp; + bool same_result, succeeded = true; + const char *name; + + while (pch_char(new) == '=' || pch_char(new) == '\n') + new++; + + merge = pch_first () + last_offset; + lastmerge = merge + lastold - 1; + if (! common_context(lastmerge, lastold, lastnew)) + lastmerge = merge - 1; + + /* Hide common prefix context */ + while (old <= lastold && new <= lastnew && merge <= lastmerge && + common_context(merge, old, new)) + { + old++; + new++; + merge++; + } + + /* Hide common suffix context */ + while (old <= lastold && new <= lastnew && merge <= lastmerge && + common_context(lastmerge, lastold, lastnew)) + { + lastold--; + lastnew--; + lastmerge--; + } + + assert (outstate->after_newline); + if (! copy_till(outstate, merge - 1)) + return false; + + /* "From" lines in the patch */ + name = pch_name(OLD); + if (!name) + name = ""; + fprintf(fp, outstate->after_newline + "\n<<<<<<<%*s\n", + strlen(name) ? strlen(name) + 1 : 0, name); + if (ferror (fp)) + write_fatal (); + outstate->after_newline = true; + while (old <= lastold) + { + outstate->after_newline = pch_write_line(old, fp); + old++; + } + + same_result = (lastmerge - merge == lastnew - new); + if (same_result) + { + LINENUM n = new, m = merge; + + while (n <= lastnew) + { + if (! context_matches_file (n, m)) + { + same_result = false; + break; + } + n++; + m++; + } + } + + if (! same_result) + { + /* "To" lines in the patch */ + name = pch_name(NEW); + if (!name) + name = ""; + fprintf(fp, outstate->after_newline + "\n|||||||%*s\n", + strlen(name) ? strlen(name) + 1 : 0, name); + if (ferror (fp)) + write_fatal (); + outstate->after_newline = true; + while (new <= lastnew) + { + outstate->after_newline = pch_write_line(new, fp); + new++; + } + } + + /* Merge result */ + fprintf(fp, outstate->after_newline + "\n=======\n"); + if (ferror (fp)) + write_fatal (); + outstate->after_newline = true; + + if (! copy_till(outstate, lastmerge)) + succeeded = false; + + /* If the merge result and the new file are the same, label the merge + result with the new file's name. */ + if (same_result) + { + name = pch_name(NEW); + if (!name) + name = ""; + } + else + name = ""; + fprintf(fp, outstate->after_newline + "\n>>>>>>>%*s\n", + strlen(name) ? strlen(name) + 1 : 0, name); + if (ferror (fp)) + write_fatal (); + outstate->after_newline = true; + outstate->zero_output = false; + return succeeded; +} + +static bool +context_matches_file (LINENUM old, LINENUM where) +{ + size_t size; + const char *line; + + line = ifetch (where, false, &size); + return size && + (canonicalize ? + similar(pfetch(old), pch_line_len(old), line, size) : + (size == pch_line_len(old) && + memcmp(line, pfetch(old), size) == 0)); +} + +static bool +common_context (LINENUM where, LINENUM old, LINENUM new) +{ + size_t size; + const char *line; + + if (pch_char(old) != ' ' || pch_char(new) != ' ') + return false; + + line = ifetch (where, false, &size); + return size && + (canonicalize ? + (similar(pfetch(old), pch_line_len(old), line, size) && + similar(pfetch(new), pch_line_len(new), line, size)) : + (size == pch_line_len(old) && size == pch_line_len(new) && + memcmp(line, pfetch(old), size) == 0 && + memcmp(line, pfetch(new), size) == 0)); +}