X-Git-Url: https://scripts.mit.edu/gitweb/wizard.git/blobdiff_plain/220b036751ee12ff5dce564d93330bdb6f9ce903..5b428fce4566ed627ad61f204935aaa8bc367932:/wizard/resolve.py diff --git a/wizard/resolve.py b/wizard/resolve.py index e2de1f2..4460cc6 100644 --- a/wizard/resolve.py +++ b/wizard/resolve.py @@ -12,10 +12,10 @@ for resolving conflicts in configuration files. The conflict resolution DSL is described here: Resolutions are specified as input-output pairs. An input -is a string with the conflict resolution markers ("<" * 7, -"=" * 7 and ">" * 7), with the HEAD content above the equals +is a string with the conflict resolution markers ``("<" * 7, +"=" * 7 and ">" * 7)``, with the HEAD content above the equals divider, and the upstream content below the equals divider. -Lines can also be marked as "***N***" where N is a natural +Lines can also be marked as ``***N***`` where N is a natural number greater than 0 (i.e. 1 or more), which means that an arbitrary number of lines may be matched and available for output. @@ -33,6 +33,8 @@ Here are some examples:: <<<<<<< downstream + ||||||| + common ======= upstream >>>>>>> @@ -59,6 +61,7 @@ then the user matched globs. import re import itertools +import logging re_var = re.compile("^\*\*\*(\d+)\*\*\*\\\n", re.MULTILINE) @@ -82,12 +85,22 @@ def spec_to_regex(spec): else: ret += re.escape(line) return ("(" + ret + ")", mappings) - ours_regex, ours_mappings = regexify(ours, -1, 1) - theirs_regex, theirs_mappings = regexify(theirs, 0, len(ours_mappings) + 1) + ours, split, common = ours.partition("|||||||\n") + if not split: + common = "***9999***\n" # force wildcard behavior + ours_regex, ours_mappings = regexify(ours, -1, 1) + common_regex, common_mappings = regexify(common, -2, 1 + len(ours_mappings)) + theirs_regex, theirs_mappings = regexify(theirs, 0, 1 + len(ours_mappings) + len(common_mappings)) + # unify the mappings ours_mappings.update(theirs_mappings) - return ("<<<<<<<[^\n]*\\\n" + ours_regex + "=======\\\n" + theirs_regex + ">>>>>>>[^\n]*(\\\n|$)", ours_mappings) + ours_mappings.update(common_mappings) + return ("<<<<<<<[^\n]*\\\n" + ours_regex + "\|\|\|\|\|\|\|\\\n" + common_regex + "=======\\\n" + theirs_regex + ">>>>>>>[^\n]*(\\\n|$)", ours_mappings) def result_to_repl(result, mappings): + """ + Converts a list of replacement strings and or references + into a replacement string appropriate for a regular expression. + """ def ritem_to_string(r): if type(r) is int: return "\\%d" % mappings[r] @@ -96,8 +109,39 @@ def result_to_repl(result, mappings): return "".join(map(ritem_to_string, result)) def resolve(contents, spec, result): + """ + Given a conflicted file, whose contents are ``contents``, attempt + to resolve all conflicts that match ``spec`` with ``result``. + """ rstring, mappings = spec_to_regex(spec) - print rstring regex = re.compile(rstring, re.DOTALL) repl = result_to_repl(result, mappings) - return regex.sub(repl, contents) + ret = "" + conflict = "" + status = 0 + for line in contents.splitlines(True): + if status == 0 and line.startswith("<<<<<<<"): + status = 1 + elif status == 1 and line.startswith("|||||||"): + status = 2 + elif status == 1 or status == 2 and line.startswith("======="): + status = 3 + # ok, now process + if status == 3 and line.startswith(">>>>>>>"): + status = 0 + conflict += line + ret += regex.sub(repl, conflict) + conflict = "" + elif status: + conflict += line + else: + ret += line + return ret + +def is_conflict(contents): + """ + Given ``contents``, return ``True`` if there are any conflicts in it. + """ + # Really really simple heuristic + return "<<<<<<<" in contents +