summaryrefslogtreecommitdiff
path: root/src/merge.c
diff options
context:
space:
mode:
authorAndreas Gruenbacher <agruen@suse.de>2009-04-05 00:28:48 +0200
committerAndreas Gruenbacher <agruen@suse.de>2009-04-05 08:12:05 +0200
commit85cd8a4cfe7bf55785e6799d0fab82527019ec99 (patch)
treec71c891632dfe3546a5d8162f04922741956887b /src/merge.c
parent4c5896b6c9ca3c8810728dde2b8500057f51a50a (diff)
downloadpatch-85cd8a4cfe7bf55785e6799d0fab82527019ec99.tar.gz
Move all source and header files into src/
Diffstat (limited to 'src/merge.c')
-rw-r--r--src/merge.c538
1 files changed, 538 insertions, 0 deletions
diff --git a/src/merge.c b/src/merge.c
new file mode 100644
index 0000000..56401a1
--- /dev/null
+++ b/src/merge.c
@@ -0,0 +1,538 @@
+/* Merge a patch
+
+ Copyright (C) 2009 Free Software Foundation, Inc.
+ Written by Andreas Gruenbacher <agruen@gnu.org>, 2009.
+
+ 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 of the License, 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; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
+
+#define XTERN extern
+#include <common.h>
+#include <minmax.h>
+#include <xalloc.h>
+#include <inp.h>
+#include <pch.h>
+#include <util.h>
+
+static LINENUM count_context_lines (void);
+static bool context_matches_file (LINENUM, LINENUM);
+static void compute_changes (LINENUM, LINENUM, LINENUM, LINENUM, char *,
+ char *);
+
+#define OFFSET LINENUM
+#define EQUAL_IDX(x, y) (context_matches_file (x, y))
+#include "bestmatch.h"
+
+#define EQUAL_IDX(ctxt, x, y) (context_matches_file (x, y))
+#define OFFSET LINENUM
+#define EXTRA_CONTEXT_FIELDS \
+ char *xchar; \
+ char *ychar;
+#define NOTE_DELETE(ctxt, xoff) ctxt->xchar[xoff] = '-';
+#define NOTE_INSERT(ctxt, yoff) ctxt->ychar[yoff] = '+';
+#define USE_HEURISTIC 1
+#include "diffseq.h"
+
+static LINENUM
+locate_merge (LINENUM *matched)
+{
+ LINENUM first_guess = pch_first () + in_offset;
+ LINENUM pat_lines = pch_ptrn_lines ();
+ LINENUM context_lines = count_context_lines ();
+ LINENUM max_where = input_lines - pat_lines + context_lines + 1;
+ LINENUM min_where = last_frozen_line + 1;
+ LINENUM max_pos_offset = max_where - first_guess;
+ LINENUM max_neg_offset = first_guess - min_where;
+ LINENUM max_offset = (max_pos_offset < max_neg_offset
+ ? max_neg_offset : max_pos_offset);
+ LINENUM where = first_guess, max_matched = 0;
+ LINENUM min, max;
+ LINENUM offset;
+
+ /* Note: we need to preserve patch's property that it applies hunks at the
+ best match closest to their original position in the file. It is
+ common for hunks to apply equally well in several places in a file.
+ Applying at the first best match would be a lot easier.
+ */
+
+ if (context_lines == 0)
+ goto out; /* locate_hunk() already tried that */
+
+ /* Allow at most CONTEXT_LINES lines to be replaced (replacing counts
+ as insert + delete), and require the remaining MIN lines to match. */
+ max = 2 * context_lines;
+ min = pat_lines - context_lines;
+
+ if (debug & 1)
+ {
+ char numbuf0[LINENUM_LENGTH_BOUND + 1];
+ char numbuf1[LINENUM_LENGTH_BOUND + 1];
+ say ("locating merge: min=%s max=%s ",
+ format_linenum (numbuf0, min),
+ format_linenum (numbuf1, max));
+ }
+
+ /* Do not try lines <= 0. */
+ if (first_guess <= max_neg_offset)
+ max_neg_offset = first_guess - 1;
+
+ for (offset = 0; offset <= max_offset; offset++)
+ {
+ if (offset <= max_pos_offset)
+ {
+ LINENUM guess = first_guess + offset;
+ LINENUM last;
+ LINENUM changes;
+
+ changes = bestmatch (1, pat_lines + 1, guess, input_lines + 1,
+ min, max, &last);
+ if (changes <= max && max_matched < last - guess)
+ {
+ max_matched = last - guess;
+ where = guess;
+ if (changes == 0)
+ break;
+ min = last - guess;
+ max = changes - 1;
+ }
+ }
+ if (0 < offset && offset <= max_neg_offset)
+ {
+ LINENUM guess = first_guess - offset;
+ LINENUM last;
+ LINENUM changes;
+
+ changes = bestmatch (1, pat_lines + 1, guess, input_lines + 1,
+ min, max, &last);
+ if (changes <= max && max_matched < last - guess)
+ {
+ max_matched = last - guess;
+ where = guess;
+ if (changes == 0)
+ break;
+ min = last - guess;
+ max = changes - 1;
+ }
+ }
+ }
+ if (debug & 1)
+ {
+ char numbuf0[LINENUM_LENGTH_BOUND + 1];
+ char numbuf1[LINENUM_LENGTH_BOUND + 1];
+ char numbuf2[LINENUM_LENGTH_BOUND + 1];
+ say ("where=%s matched=%s changes=%s\n",
+ format_linenum (numbuf0, where),
+ format_linenum (numbuf1, max_matched),
+ format_linenum (numbuf2, max + 1));
+ }
+
+ out:
+ *matched = max_matched;
+ return where;
+}
+
+static void
+print_linerange (LINENUM from, LINENUM to)
+{
+ char numbuf0[LINENUM_LENGTH_BOUND + 1];
+ char numbuf1[LINENUM_LENGTH_BOUND + 1];
+
+ if (to <= from)
+ printf ("%s",
+ format_linenum (numbuf0, from));
+ else
+ printf ("%s-%s",
+ format_linenum (numbuf0, from),
+ format_linenum (numbuf1, to));
+}
+
+static void
+merge_result (bool *first_result, int hunk, char const *what, LINENUM from,
+ LINENUM to)
+{
+ static char const *last_what;
+
+ if (*first_result && what)
+ {
+ printf ("Hunk #%d %s at ", hunk, what);
+ last_what = what;
+ }
+ else if (! what)
+ {
+ if (! *first_result)
+ {
+ fputs (".\n", stdout);
+ fflush (stdout);
+ last_what = 0;
+ }
+ return;
+ }
+ else if (last_what == what)
+ fputs (",", stdout);
+ else
+ printf (", %s at ", what);
+ print_linerange (from + out_offset, to + out_offset);
+ *first_result = false;
+}
+
+bool
+merge_hunk (int hunk, struct outstate *outstate, LINENUM where,
+ bool *somefailed)
+{
+ bool applies_cleanly;
+ bool first_result = true;
+ FILE *fp = outstate->ofp;
+ LINENUM old = 1;
+ LINENUM firstold = pch_ptrn_lines ();
+ LINENUM new = firstold + 1;
+ LINENUM firstnew = pch_end ();
+ LINENUM in;
+ LINENUM firstin;
+ char *oldin;
+ LINENUM matched;
+ LINENUM lastwhere;
+
+ /* Convert '!' markers into '-' and '+' to simplify things here. */
+ pch_normalize (UNI_DIFF);
+
+ assert (pch_char (firstnew + 1) == '^');
+ while (pch_char (new) == '=' || pch_char (new) == '\n')
+ new++;
+
+ if (where)
+ {
+ applies_cleanly = true;
+ matched = pch_ptrn_lines ();
+ }
+ else
+ {
+ where = locate_merge (&matched);
+ applies_cleanly = false;
+ }
+
+ in = firstold + 2;
+ oldin = xmalloc (in + matched + 1);
+ memset (oldin, ' ', in + matched);
+ oldin[0] = '*';
+ oldin[in - 1] = '=';
+ oldin[in + matched] = '^';
+ compute_changes (old, in - 1, where, where + matched,
+ oldin + old, oldin + in);
+
+ if (debug & 2)
+ {
+ char numbuf0[LINENUM_LENGTH_BOUND + 1];
+ char numbuf1[LINENUM_LENGTH_BOUND + 1];
+ LINENUM n;
+
+ fputc ('\n', stderr);
+ for (n = 0; n <= in + matched; n++)
+ {
+ fprintf (stderr, "%s %c",
+ format_linenum (numbuf0, n),
+ oldin[n]);
+ if (n == 0)
+ fprintf(stderr, " %s,%s\n",
+ format_linenum (numbuf0, pch_first()),
+ format_linenum (numbuf1, pch_ptrn_lines()));
+ else if (n <= firstold)
+ fprintf (stderr, " |%.*s",
+ (int) pch_line_len (n), pfetch (n));
+ else if (n == in - 1)
+ fprintf(stderr, " %s,%s\n",
+ format_linenum (numbuf0, where),
+ format_linenum (numbuf1, matched));
+ else if (n >= in && n < in + matched)
+ {
+ size_t size;
+ const char *line;
+
+ line = ifetch (where + n - in, false, &size);
+ fprintf (stderr, " |%.*s",
+ (int) size, line);
+ }
+ else
+ fputc('\n', stderr);
+ }
+ fflush (stderr);
+ }
+
+ if (last_frozen_line < where - 1)
+ if (! copy_till (outstate, where - 1))
+ return false;
+
+ for (;;)
+ {
+ firstold = old;
+ firstnew = new;
+ firstin = in;
+
+ if (pch_char (old) == '-' || pch_char (new) == '+')
+ {
+ LINENUM lines;
+
+ while (pch_char (old) == '-')
+ {
+ if (oldin[old] == '-' || oldin[in] == '+')
+ goto conflict;
+ else if (oldin[old] == ' ')
+ {
+ assert (oldin[in] == ' ');
+ in++;
+ }
+ old++;
+ }
+ if (oldin[old] == '-' || oldin[in] == '+')
+ goto conflict;
+ while (pch_char (new) == '+')
+ new++;
+
+ lines = new - firstnew;
+ if (verbosity == VERBOSE
+ || ((verbosity != SILENT) && ! applies_cleanly))
+ merge_result (&first_result, hunk, "merged",
+ where, where + lines - 1);
+ last_frozen_line += (old - firstold);
+ where += (old - firstold);
+ out_offset += new - firstnew;
+
+ if (firstnew < new)
+ {
+ while (firstnew < new)
+ {
+ outstate->after_newline = pch_write_line (firstnew, fp);
+ firstnew++;
+ }
+ outstate->zero_output = false;
+ }
+ }
+ else if (pch_char (old) == ' ')
+ {
+ if (oldin[old] == '-')
+ {
+ while (pch_char (old) == ' ')
+ {
+ if (oldin[old] != '-')
+ break;
+ if (pch_char (new) == '+')
+ goto conflict;
+ else
+ assert (pch_char (new) == ' ');
+ old++;
+ new++;
+ }
+ if (pch_char (old) == '-' || pch_char (new) == '+')
+ goto conflict;
+ }
+ else if (oldin[in] == '+')
+ {
+ while (oldin[in] == '+')
+ in++;
+
+ /* Take these lines from the input file. */
+ where += in - firstin;
+ if (! copy_till (outstate, where - 1))
+ return false;
+ }
+ else if (oldin[old] == ' ')
+ {
+ while (pch_char (old) == ' '
+ && oldin[old] == ' '
+ && pch_char (new) == ' '
+ && oldin[in] == ' ')
+ {
+ old++;
+ new++;
+ in++;
+ }
+
+ /* Take these lines from the input file. */
+ where += (in - firstin);
+ if (! copy_till (outstate, where - 1))
+ return false;
+ }
+ }
+ else
+ {
+ assert (pch_char (old) == '=' && pch_char (new) == '^');
+ /* Nothing more left to merge. */
+ break;
+ }
+ continue;
+
+ conflict:
+ /* Find the end of the conflict. */
+ for (;;)
+ {
+ if (pch_char (old) == '-')
+ {
+ while (oldin[in] == '+')
+ in++;
+ if (oldin[old] == ' ')
+ {
+ assert (oldin[in] == ' ');
+ in++;
+ }
+ old++;
+ }
+ else if (oldin[old] == '-')
+ {
+ while (pch_char (new) == '+')
+ new++;
+ if (pch_char (old) == ' ')
+ {
+ assert (pch_char (new) == ' ');
+ new++;
+ }
+ old++;
+ }
+ else if (pch_char (new) == '+')
+ while (pch_char (new) == '+')
+ new++;
+ else if (oldin[in] == '+')
+ while (oldin[in] == '+')
+ in++;
+ else
+ break;
+ }
+ assert (((pch_char (old) == ' ' && pch_char (new) == ' ')
+ || (pch_char (old) == '=' && pch_char (new) == '^'))
+ && ((oldin[old] == ' ' && oldin[in] == ' ')
+ || (oldin[old] == '=' && oldin[in] == '^')));
+
+ /* Output common prefix lines. */
+ for (lastwhere = where;
+ firstin < in && firstnew < new
+ && context_matches_file (firstnew, lastwhere);
+ firstin++, firstnew++, lastwhere++)
+ continue;
+ if (firstin == in && firstnew == new)
+ merge_result (&first_result, hunk, "already applied",
+ where, lastwhere - 1);
+ if (where != lastwhere)
+ {
+ where = lastwhere;
+ if (! copy_till (outstate, where - 1))
+ return false;
+ }
+
+ if (firstin < in || firstnew < new)
+ {
+ LINENUM common_suffix;
+ LINENUM lines;
+
+ /* Remember common suffix lines. */
+ for (common_suffix = 0,
+ lastwhere = where + (in - firstin);
+ firstin < in && firstnew < new
+ && context_matches_file (new - 1, lastwhere - 1);
+ in--, new--, lastwhere--, common_suffix++)
+ continue;
+
+ lines = 3 + (in - firstin) + (new - firstnew);
+ merge_result (&first_result, hunk, "UNMERGED",
+ where, where + lines - 1);
+ out_offset += 3 + (new - firstnew);
+
+ fputs (outstate->after_newline + "\n<<<<<<<\n", fp);
+ outstate->after_newline = true;
+ if (firstin < in)
+ {
+ where += (in - firstin);
+ if (! copy_till (outstate, where - 1))
+ return false;
+ }
+ fputs (outstate->after_newline + "\n=======\n", fp);
+ outstate->after_newline = true;
+ while (firstnew < new)
+ {
+ outstate->after_newline = pch_write_line (firstnew, fp);
+ firstnew++;
+ }
+ fputs (outstate->after_newline + "\n>>>>>>>\n", fp);
+ outstate->after_newline = true;
+ outstate->zero_output = false;
+ if (ferror (fp))
+ write_fatal ();
+
+ /* Output common suffix lines. */
+ if (common_suffix)
+ {
+ where += common_suffix;
+ if (! copy_till (outstate, where - 1))
+ return false;
+ in += common_suffix;
+ new += common_suffix;
+ }
+ *somefailed = true;
+ }
+ }
+ merge_result (&first_result, 0, 0, 0, 0);
+
+ assert (last_frozen_line == where - 1);
+ free (oldin);
+ return true;
+}
+
+static LINENUM
+count_context_lines (void)
+{
+ LINENUM old;
+ LINENUM lastold = pch_ptrn_lines ();
+ LINENUM context;
+
+ for (context = 0, old = 1; old <= lastold; old++)
+ if (pch_char (old) == ' ')
+ context++;
+ return context;
+}
+
+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 void
+compute_changes (LINENUM xmin, LINENUM xmax, LINENUM ymin, LINENUM ymax,
+ char *xchar, char *ychar)
+{
+ struct context ctxt;
+ LINENUM diags;
+
+ ctxt.xchar = xchar - xmin;
+ ctxt.ychar = ychar - ymin;
+
+ diags = xmax + ymax + 3;
+ ctxt.fdiag = xmalloc (2 * diags * sizeof (*ctxt.fdiag));
+ ctxt.bdiag = ctxt.fdiag + diags;
+ ctxt.fdiag += ymax + 1;
+ ctxt.bdiag += ymax + 1;
+
+ ctxt.heuristic = true;
+ ctxt.too_expensive = xmax + ymax;
+
+ compareseq (xmin, xmax, ymin, ymax, true, &ctxt);
+
+ ctxt.fdiag -= ymax + 1;
+ free (ctxt.fdiag);
+}