forked from pool/patch
443 lines
12 KiB
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));
|
|
+}
|