From patchwork Tue Jun 10 20:50:29 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: [5, of, 6] simplemerge: add ``base`` mode to ``mergemarkersscope`` config From: Pierre-Yves David X-Patchwork-Id: 4969 Message-Id: To: mercurial-devel@selenic.com Cc: pierre-yves.david@ens-lyon.org Date: Tue, 10 Jun 2014 13:50:29 -0700 # HG changeset patch # User Pierre-Yves David # Date 1348740176 -7200 # Thu Sep 27 12:02:56 2012 +0200 # Node ID fe435e1caaab581f4c67ea44c7075f38e7102575 # Parent 9282f318c729d672c05fd3364b8d2fce80ecac3b simplemerge: add ``base`` mode to ``mergemarkersscope`` config This options give access to a feature already present in ``internal:merge``: displaying merge base base content. In the basic merge (calling ``hg merge``) case, including more context to the merge markers is an interesting addition. But this extra information is the only viable option in case conflict from grafting (, rebase, etc…). When grafting ``source`` on ``destination``, the parent of ``source`` is used as the ``base``. When all three changesets add content in the same location, the marker for ``source`` will contains both ``base`` and ``source`` content. Without the content of base exposed, there is no way for the user to discriminate content coming from ``base`` and content commit from ``source``. Practical example (all addition are in the same place): * ``destination`` adds ``Dest-Content`` * ``base`` adds ``Base-Content`` * ``source`` adds ``Src-Content`` Grafting ``source`` on ``destination`` will produce the following conflict: <<<<<<< destination Dest-Content ======= Base-Content Src-Content >>>>>>> source This that case there is no way to distinct ``base`` from ``source``. As a result content from ``base`` are likely to slip in the resolution result. However, adding the base make the situation very clear: <<<<<<< destination Dest-Content ======= base Base-Content ======= base Base-Content Src-Content >>>>>>> source Once the base is added, the addition from the grafted changeset is made clear. User can compare the content from ``base`` and ``source`` to make an enlightened decision during merge resolution. diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py --- a/mercurial/filemerge.py +++ b/mercurial/filemerge.py @@ -304,27 +304,31 @@ def _formatconflictmarker(repo, ctx, tem '{ifeq(branch, "default", "", "{branch} ")}' + '- {author|user}: {desc|firstline}') _defaultconflictlabels = ['local', 'other'] -def _formatlabels(repo, fcd, fco, labels): +def _formatlabels(repo, fcd, fco, fca, labels): """Formats the given labels using the conflict marker template. Returns a list of formatted labels. """ cd = fcd.changectx() co = fco.changectx() + ca = fca.changectx() ui = repo.ui template = ui.config('ui', 'mergemarkertemplate', _defaultconflictmarker) template = templater.parsestring(template, quoted=False) - tmpl = templater.templater(None, cache={ 'conflictmarker' : template }) + tmpl = templater.templater(None, cache={'conflictmarker': template}) - pad = max(len(labels[0]), len(labels[1])) + pad = max(len(l) for l in labels) - return [_formatconflictmarker(repo, cd, tmpl, labels[0], pad), - _formatconflictmarker(repo, co, tmpl, labels[1], pad)] + newlabels = [_formatconflictmarker(repo, cd, tmpl, labels[0], pad), + _formatconflictmarker(repo, co, tmpl, labels[1], pad)] + if len(labels) > 2: + newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad)) + return newlabels def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None): """perform a 3-way merge in the working directory mynode = parent node before merge @@ -382,12 +386,15 @@ def filemerge(repo, mynode, orig, fcd, f ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca)) markerstyle = ui.config('ui', 'mergemarkers', 'detailed') if not labels: labels = _defaultconflictlabels + markersscope = ui.config('ui', 'mergemarkersscope', 'plain') + if markersscope == 'base' and len(labels) < 3: + labels.append('base') if markerstyle != 'basic': - labels = _formatlabels(repo, fcd, fco, labels) + labels = _formatlabels(repo, fcd, fco, fca, labels) needcheck, r = func(repo, mynode, orig, fcd, fco, fca, toolconf, (a, b, c, back), labels=labels) if not needcheck: if r: diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt --- a/mercurial/help/config.txt +++ b/mercurial/help/config.txt @@ -1229,11 +1229,12 @@ User interface controls. the first line of the commit description. ``mergemarkersscope`` Set the amount of information included in the markers. The default ``plain`` includes the whole content of the conflicting chunk. - Alternatively, ``minimal`` can be used to reduce the size of conflicting + ``base`` will also include the content from the merge base in a third block. + Finally, ``minimal`` can be used to reduce the size of conflicting chunk as much as possible. ``minimal`` can be heavily confuse when unrelated content added to the same location share some common line (blank, common programming construct) by chance. ``portablefilenames`` diff --git a/mercurial/simplemerge.py b/mercurial/simplemerge.py --- a/mercurial/simplemerge.py +++ b/mercurial/simplemerge.py @@ -413,17 +413,20 @@ def simplemerge(ui, local, base, other, raise util.Abort(msg) return text name_a = local name_b = other + name_base = None labels = opts.get('label', []) if len(labels) > 0: name_a = labels[0] if len(labels) > 1: name_b = labels[1] if len(labels) > 2: - raise util.Abort(_("can only specify two labels.")) + name_base = labels[2] + if len(labels) > 3: + raise util.Abort(_("can only specify three labels.")) try: localtext = readfile(local) basetext = readfile(base) othertext = readfile(other) @@ -436,14 +439,22 @@ def simplemerge(ui, local, base, other, out = opener(os.path.basename(local), "w", atomictemp=True) else: out = sys.stdout reprocess = not opts.get('no_minimal') + if reprocess and name_base: + msg = _("display of base incompatible conflict minimization") + raise util.Abort(msg, hint=_("add --no-minimal")) + m3 = Merge3Text(basetext, localtext, othertext) + extrakwargs = {} + if name_base is not None: + extrakwargs['base_marker'] = '=======' + extrakwargs['name_base'] = name_base for line in m3.merge_lines(name_a=name_a, name_b=name_b, - reprocess=reprocess): + reprocess=reprocess, **extrakwargs): out.write(line) if not opts.get('print'): out.close() diff --git a/tests/test-conflict.t b/tests/test-conflict.t --- a/tests/test-conflict.t +++ b/tests/test-conflict.t @@ -180,5 +180,40 @@ Test minimalist config settings ======= 4 5 >>>>>>> other Hop we are done. + +("base" setting) + + $ hg up -q --clean . + $ printf "\n[ui]\nmergemarkersscope=base\n" >> .hg/hgrc + + $ hg merge 1 + merging a + warning: conflicts during merge. + merging a incomplete! (edit conflicts, then use 'hg resolve --mark') + 0 files updated, 0 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon + [1] + $ cat a + Small Mathematical Series. + <<<<<<< local + 1 + 2 + 3 + 6 + 8 + ======= base + One + Two + Three + Four + Five + ======= + 1 + 2 + 3 + 4 + 5 + >>>>>>> other + Hop we are done. diff --git a/tests/test-contrib.t b/tests/test-contrib.t --- a/tests/test-contrib.t +++ b/tests/test-contrib.t @@ -195,11 +195,11 @@ 2 labels [1] too many labels $ python simplemerge -p -L foo -L bar -L baz conflict-local base conflict-other - abort: can only specify two labels. + abort: display of base incompatible conflict minimization [255] binary file $ python -c "f = file('binary-local', 'w'); f.write('\x00'); f.close()"