SHA256
1
0
forked from pool/patch
patch/diff3-style-merges-simple-merge.diff

443 lines
12 KiB
Diff

From: Andreas Gruenbacher <agruen@suse.de>
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 <agruen@suse.de>
---
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 <common.h>
+#include <inp.h>
+#include <pch.h>
+#include <util.h>
+
+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));
+}