Patchwork [5,of,6] simplemerge: add ``base`` mode to ``mergemarkersscope`` config

login
register
mail settings
Submitter Pierre-Yves David
Date June 10, 2014, 8:50 p.m.
Message ID <fe435e1caaab581f4c67.1402433429@marginatus.alto.octopoid.net>
Download mbox | patch
Permalink /patch/4969/
State Changes Requested
Headers show

Comments

Pierre-Yves David - June 10, 2014, 8:50 p.m.
# HG changeset patch
# User Pierre-Yves David <pierre-yves.david@ens-lyon.org>
# 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.
Siddharth Agarwal - June 10, 2014, 10:16 p.m.
On 06/10/2014 01:50 PM, pierre-yves.david@ens-lyon.org wrote:
> # HG changeset patch
> # User Pierre-Yves David <pierre-yves.david@ens-lyon.org>
> # Date 1348740176 -7200
> #      Thu Sep 27 12:02:56 2012 +0200
> # Node ID fe435e1caaab581f4c67ea44c7075f38e7102575
> # Parent  9282f318c729d672c05fd3364b8d2fce80ecac3b
> simplemerge: add ``base`` mode to ``mergemarkersscope`` config

Following on from my earlier comment, this seems to be orthogonal to 
minimal vs plain, doesn't it? Minimal + base and plain + base both make 
sense. So in my world where minimal vs plain wouldn't even be an option, 
I'd instead go ahead and have a separate config called 
'ui.mergeincludebase' or similar.



>
> 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()"
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@selenic.com
> http://selenic.com/mailman/listinfo/mercurial-devel
Pierre-Yves David - June 10, 2014, 10:20 p.m.
On 06/10/2014 03:16 PM, Siddharth Agarwal wrote:
> On 06/10/2014 01:50 PM, pierre-yves.david@ens-lyon.org wrote:
>> # HG changeset patch
>> # User Pierre-Yves David <pierre-yves.david@ens-lyon.org>
>> # Date 1348740176 -7200
>> #      Thu Sep 27 12:02:56 2012 +0200
>> # Node ID fe435e1caaab581f4c67ea44c7075f38e7102575
>> # Parent  9282f318c729d672c05fd3364b8d2fce80ecac3b
>> simplemerge: add ``base`` mode to ``mergemarkersscope`` config
>
> Following on from my earlier comment, this seems to be orthogonal to
> minimal vs plain, doesn't it? Minimal + base and plain + base both make
> sense.

No, ``minimal`` is incompatible with ``base``. ``minimal`` simplify 
content that are differ from "base" but is the same in "local" and 
"other". If the content is the same in all of "base", "other" and 
"local" there is no conflict in the first place.

So, displaying the base means no minimization.
Siddharth Agarwal - June 11, 2014, 12:45 a.m.
On 06/10/2014 03:20 PM, Pierre-Yves David wrote:
>
>
> On 06/10/2014 03:16 PM, Siddharth Agarwal wrote:
>> On 06/10/2014 01:50 PM, pierre-yves.david@ens-lyon.org wrote:
>>> # HG changeset patch
>>> # User Pierre-Yves David <pierre-yves.david@ens-lyon.org>
>>> # Date 1348740176 -7200
>>> #      Thu Sep 27 12:02:56 2012 +0200
>>> # Node ID fe435e1caaab581f4c67ea44c7075f38e7102575
>>> # Parent  9282f318c729d672c05fd3364b8d2fce80ecac3b
>>> simplemerge: add ``base`` mode to ``mergemarkersscope`` config
>>
>> Following on from my earlier comment, this seems to be orthogonal to
>> minimal vs plain, doesn't it? Minimal + base and plain + base both make
>> sense.
>
> No, ``minimal`` is incompatible with ``base``. ``minimal`` simplify 
> content that are differ from "base" but is the same in "local" and 
> "other". If the content is the same in all of "base", "other" and 
> "local" there is no conflict in the first place.
>
> So, displaying the base means no minimization.

I talked about this with Pierre-Yves in person. He is indeed correct, as 
he is as usual, that 'minimal' is incompatible with 'base'. However I 
still think that only one of what is currently 'minimal' and 'plain' 
should exist. I've been convinced by Pierre-Yves that 'minimal' is 
almost completely broken. So my proposal is to introduce a config option 
called:

ui.mergemarkers3way = {true, false}

where 'true' stands for what is currently called 'base', and 'false' 
stands for 'plain'. I believe this should default to true.
Pierre-Yves David - June 11, 2014, 10:07 p.m.
On 06/10/2014 05:45 PM, Siddharth Agarwal wrote:
> On 06/10/2014 03:20 PM, Pierre-Yves David wrote:
>>
>>
>> On 06/10/2014 03:16 PM, Siddharth Agarwal wrote:
>>> On 06/10/2014 01:50 PM, pierre-yves.david@ens-lyon.org wrote:
>>>> # HG changeset patch
>>>> # User Pierre-Yves David <pierre-yves.david@ens-lyon.org>
>>>> # Date 1348740176 -7200
>>>> #      Thu Sep 27 12:02:56 2012 +0200
>>>> # Node ID fe435e1caaab581f4c67ea44c7075f38e7102575
>>>> # Parent  9282f318c729d672c05fd3364b8d2fce80ecac3b
>>>> simplemerge: add ``base`` mode to ``mergemarkersscope`` config
>>>
>>> Following on from my earlier comment, this seems to be orthogonal to
>>> minimal vs plain, doesn't it? Minimal + base and plain + base both make
>>> sense.
>>
>> No, ``minimal`` is incompatible with ``base``. ``minimal`` simplify
>> content that are differ from "base" but is the same in "local" and
>> "other". If the content is the same in all of "base", "other" and
>> "local" there is no conflict in the first place.
>>
>> So, displaying the base means no minimization.
>
> I talked about this with Pierre-Yves in person. He is indeed correct, as
> he is as usual, that 'minimal' is incompatible with 'base'. However I
> still think that only one of what is currently 'minimal' and 'plain'
> should exist. I've been convinced by Pierre-Yves that 'minimal' is
> almost completely broken. So my proposal is to introduce a config option
> called:
>
> ui.mergemarkers3way = {true, false}

I agree that keeping two modes would be a bit weird. Both option looks 
like the same and are perfectly compatible from a parsing tool point of 
view. I assume (read hope) nobody strongly rely on the agressive 
minimisation.

Given that minimal mode fail miserably in some critical situation, we 
cannot keep using it as default. However the behavior adjustment 
Siddharth suggested (never break a chunk in multiple piece) for 
``minimal`` could be a nice improvement. (I've no specific plan to carry 
it on however)

The option Siddarth suggests sound like a good end result to me.

> where 'true' stands for what is currently called 'base', and 'false'
> stands for 'plain'. I believe this should default to true.

For those who did not read the whole series, I also agree here. I 
believe displaying base should be the default as it is the only viable 
way to handled "grafted" merge.
Matt Mackall - July 19, 2014, 3:36 a.m.
On Tue, 2014-06-10 at 13:50 -0700, pierre-yves.david@ens-lyon.org wrote:
> # HG changeset patch
> # User Pierre-Yves David <pierre-yves.david@ens-lyon.org>
> # 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.
...
> +  $ cat a
> +  Small Mathematical Series.
> +  <<<<<<< local
> +  1
> +  2
> +  3
> +  6
> +  8
> +  ======= base
> +  One
> +  Two
> +  Three
> +  Four
> +  Five
> +  =======
> +  1
> +  2
> +  3
> +  4
> +  5
> +  >>>>>>> other

Except it's supposed to look like this:

$ merge -p -A local base other
<<<<<<< local
1
2
3
6
8
||||||| base
One
Two
Three
Four
Five
=======
1
2
3
4
5
>>>>>>> other
merge: warning: conflicts during merge

This would be better as something like internal:merge3way.

I also can't agree to accepting it as any sort of default behavior like
in patch 6: this is functionality you literally can't get in CVS or SVN
without patching the code and needs you to use the fairly obscure
setting "merge.conflictstyle=diff3" in Git... so you're going to have a
hard time convincing me it's an essential behavior change 58 years after
RCS was released and 9 years after Git and Hg.

https://lists.nongnu.org/archive/html/info-cvs/2002-06/msg00254.html
https://stackoverflow.com/questions/21740250/configuring-subversion-client-to-use-3-way-conflict-markers
Pierre-Yves David - July 25, 2014, 1:58 p.m.
On 07/19/2014 05:36 AM, Matt Mackall wrote:
> On Tue, 2014-06-10 at 13:50 -0700, pierre-yves.david@ens-lyon.org wrote:
>> # HG changeset patch
>> # User Pierre-Yves David <pierre-yves.david@ens-lyon.org>
>> # 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.
> ...
>> +  $ cat a
>> +  Small Mathematical Series.
>> +  <<<<<<< local
>> +  1
>> +  2
>> +  3
>> +  6
>> +  8
>> +  ======= base
>> +  One
>> +  Two
>> +  Three
>> +  Four
>> +  Five
>> +  =======
>> +  1
>> +  2
>> +  3
>> +  4
>> +  5
>> +  >>>>>>> other
>
> Except it's supposed to look like this:
>
> $ merge -p -A local base other
> <<<<<<< local
> 1
> 2
> 3
> 6
> 8
> ||||||| base
> One
> Two
> Three
> Four
> Five
> =======
> 1
> 2
> 3
> 4
> 5
>>>>>>>> other
> merge: warning: conflicts during merge
>
> This would be better as something like internal:merge3way.
>
> I also can't agree to accepting it as any sort of default behavior like
> in patch 6: this is functionality you literally can't get in CVS or SVN
> without patching the code and needs you to use the fairly obscure
> setting "merge.conflictstyle=diff3" in Git... so you're going to have a
> hard time convincing me it's an essential behavior change 58 years after
> RCS was released and 9 years after Git and Hg.
>
> https://lists.nongnu.org/archive/html/info-cvs/2002-06/msg00254.html
> https://stackoverflow.com/questions/21740250/configuring-subversion-client-to-use-3-way-conflict-markers

But none of CVS, SVN or merge(1) had to deal with magic merge from a 
graft (or a rebase, or and histedit or an evolve or …).

In this case, the two way markers is -broken- and produce bad result 
that result in user doing bad merge resolution.

See details here: 
http://www.selenic.com/pipermail/mercurial-devel/2014-June/059582.html

As the usage of such merge become more and more frequent in DVCS, we 
-need- a proper fixes for this bug.
Long Vu - July 25, 2014, 3:17 p.m.
On Fri, Jul 18, 2014 at 11:36 PM, Matt Mackall <mpm@selenic.com> wrote:
>
> Except it's supposed to look like this:
>
> $ merge -p -A local base other
> <<<<<<< local
> 1
> 2
> 3
> 6
> 8
> ||||||| base
> One
> Two
> Three
> Four
> Five
> =======
> 1
> 2
> 3
> 4
> 5
>>>>>>>> other
> merge: warning: conflicts during merge

I agree with the format proposed by Matt.  That is the format used for diff3.

>
> This would be better as something like internal:merge3way.

Defining a new merge tool "internal:merge3way" is also a more natural
way for those who has been explicitly specifying their merge tool.
The behavior for internal:merge is kept as is and no backward
compatible behavior is introduced.

I've been configuring diff3 as merge tool this way:

# don't attempt internal:merge first
diff3.premerge = False
diff3.args = -L local -L base -L other $local $base $other -m > $output

I have to say that having the base marker for internal:merge is
something I've always wanted to add but never found the time, hence
resorting to use diff3 as merge tool.

Kudos for the patch.

Having the base marker not only make graft or rebase merge conflict
easier to handle but also help for "normal" merge conflict.

The algorithm used is 3-way merge (based on 3 versions of the merge
hunks) so why at the time of manual conflict resolution, the user is
only given "other" and "local".  "base" was used together with "other"
and "local" by the merge engine to detect merge conflict, so normally
the user should also have the same infos as the merge engine to make
decision.

Again, kudosssssssss for the patch.

Patch

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()"