Patchwork Add support for 'union' merge strategy

login
register
mail settings
Submitter Erik Huelsmann
Date June 26, 2015, 7:24 p.m.
Message ID <CACOoB6ixtP4zEbuDCCS1Pm6kE=teVdMfEMmS4ymwdkwXuhoALA@mail.gmail.com>
Download mbox | patch
Permalink /patch/9791/
State Changes Requested
Headers show

Comments

Erik Huelsmann - June 26, 2015, 7:24 p.m.
As discussed on IRC, I'm submitting my first mercurial patch: to support
the 'union' merge strategy, where both sides of a conflict are included in
the result and not marking the file as conflicted.

Please tell me how to proceed. Thanks for your comments and help!

Regards,

Erik.

# HG changeset patch
# User erik <erik@hucs.nl>
# Date 1435346089 -7200
#      Fri Jun 26 21:14:49 2015 +0200
# Node ID d35c0f4e71ed16627a2c95539121bfda2b253157
# Parent  ff5172c830022b64cc5bd1bae36b2276e9dc6e5d
internal merge tool: add support for 'union' merge strategy.

One use-case for this merge strategy is the Changelog file being changed
on multiple branches and conflicting when being merged back to the main
branch.

**extrakwargs):
@@ -383,7 +396,7 @@
     if not opts.get('print'):
         out.close()

-    if m3.conflicts:
+    if m3.conflicts and not mode == 'union':
         if not opts.get('quiet'):
             ui.warn(_("warning: conflicts during merge.\n"))
         return 1
Matt Harbison - June 26, 2015, 10:41 p.m.
On Fri, 26 Jun 2015 15:24:53 -0400, Erik Huelsmann <ehuels@gmail.com>  
wrote:

> As discussed on IRC, I'm submitting my first mercurial patch: to support
> the 'union' merge strategy, where both sides of a conflict are included  
> in
> the result and not marking the file as conflicted.
>
> Please tell me how to proceed. Thanks for your comments and help!
>
> Regards,
>
> Erik.

Hi Erik,

Take a look at the commit guidelines [1].  There are a few things about  
the commit message that can be cleaned up.  It would probably be useful to  
explain in the commit message what a 'union' merge is.  The blurb outside  
the patch doesn't get brought along into the repo, making it harder to  
figure out what it is supposed to do.

You might want to enable --git diffs in your configuration, and use the  
patchbomb extension (or email in TortoiseHg).

I'm not well versed in merge code myself, so I can't offer comments on the  
code.  But you will want to add a test [2], so it doesn't silently break  
in the future.

When you get that fixed up and resend, the convention is to use a V2  
--flag.

--Matt

[1] https://mercurial.selenic.com/wiki/ContributingChanges
[2] https://mercurial.selenic.com/wiki/WritingTests

> # HG changeset patch
> # User erik <erik@hucs.nl>
> # Date 1435346089 -7200
> #      Fri Jun 26 21:14:49 2015 +0200
> # Node ID d35c0f4e71ed16627a2c95539121bfda2b253157
> # Parent  ff5172c830022b64cc5bd1bae36b2276e9dc6e5d
> internal merge tool: add support for 'union' merge strategy.
>
> One use-case for this merge strategy is the Changelog file being changed
> on multiple branches and conflicting when being merged back to the main
> branch.
>
> diff -r ff5172c83002 -r d35c0f4e71ed mercurial/filemerge.py
> --- a/mercurial/filemerge.py Wed Jun 24 13:41:27 2015 -0500
> +++ b/mercurial/filemerge.py Fri Jun 26 21:14:49 2015 +0200
> @@ -212,10 +212,7 @@
>              util.copyfile(back, a) # restore from backup and try again
>      return 1 # continue merging
>
> -@internaltool('merge', True,
> -              _("merging %s incomplete! "
> -                "(edit conflicts, then use 'hg resolve --mark')\n"))
> -def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files,
> labels=None):
> +def __imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels,
> mode):
>      """
>      Uses the internal non-interactive simple merge algorithm for merging
>      files. It will fail if there are any conflicts and leave markers in
> @@ -232,10 +229,38 @@
>
>          ui = repo.ui
>
> -        r = simplemerge.simplemerge(ui, a, b, c, label=labels)
> +        r = simplemerge.simplemerge(ui, a, b, c, label=labels,  
> mode=mode)
>          return True, r
>      return False, 0
>
> +
> +@internaltool('merge', True,
> +              _("merging %s incomplete! "
> +                "(edit conflicts, then use 'hg resolve --mark')\n"))
> +def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files,
> labels=None):
> +    """
> +    Uses the internal non-interactive simple merge algorithm for merging
> +    files. It will fail if there are any conflicts and leave markers in
> +    the partially merged file. Markers will have two sections, one for
> each side
> +    of merge."""
> +
> +    return __imerge(repo, mynode, orig, fcd, fco, fca, toolconf,
> +                    files, labels, 'merge')
> +
> +
> +@internaltool('union', True,
> +              _("merging %s incomplete! "
> +                "(edit conflicts, then use 'hg resolve --mark')\n"))
> +def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files,
> labels=None):
> +    """
> +    Uses the internal non-interactive union merge algorithm for merging
> +    files. It will use both sides if there are any conflicts."""
> +
> +    return __imerge(repo, mynode, orig, fcd, fco, fca, toolconf,
> +                    files, labels, 'union')
> +
> +
> +
>  @internaltool('merge3', True,
>                _("merging %s incomplete! "
>                  "(edit conflicts, then use 'hg resolve --mark')\n"))
> diff -r ff5172c83002 -r d35c0f4e71ed mercurial/simplemerge.py
> --- a/mercurial/simplemerge.py Wed Jun 24 13:41:27 2015 -0500
> +++ b/mercurial/simplemerge.py Fri Jun 26 21:14:49 2015 +0200
> @@ -92,9 +92,9 @@
>                  newline = '\r\n'
>              elif self.a[0].endswith('\r'):
>                  newline = '\r'
> -        if name_a:
> +        if name_a and start_marker:
>              start_marker = start_marker + ' ' + name_a
> -        if name_b:
> +        if name_b and end_marker:
>              end_marker = end_marker + ' ' + name_b
>          if name_base and base_marker:
>              base_marker = base_marker + ' ' + name_base
> @@ -112,17 +112,20 @@
>                      yield self.b[i]
>              elif what == 'conflict':
>                  self.conflicts = True
> -                yield start_marker + newline
> +                if start_marker is not None:
> +                    yield start_marker + newline
>                  for i in range(t[3], t[4]):
>                      yield self.a[i]
>                  if base_marker is not None:
>                      yield base_marker + newline
>                      for i in range(t[1], t[2]):
>                          yield self.base[i]
> -                yield mid_marker + newline
> +                if mid_marker is not None:
> +                    yield mid_marker + newline
>                  for i in range(t[5], t[6]):
>                      yield self.b[i]
> -                yield end_marker + newline
> +                if end_marker is not None:
> +                    yield end_marker + newline
>              else:
>                  raise ValueError(what)
>
> @@ -345,18 +348,24 @@
>                  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:
> -        name_base = labels[2]
> -    if len(labels) > 3:
> -        raise util.Abort(_("can only specify three labels."))
> +    mode = opts.get('mode', '')
> +    if mode == 'union':
> +        name_a = None
> +        name_b = None
> +        name_base = None
> +    else:
> +        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:
> +            name_base = labels[2]
> +        if len(labels) > 3:
> +            raise util.Abort(_("can only specify three labels."))
>
>      try:
>          localtext = readfile(local)
> @@ -374,7 +383,11 @@
>
>      m3 = Merge3Text(basetext, localtext, othertext)
>      extrakwargs = {}
> -    if name_base is not None:
> +    if mode == 'union':
> +        extrakwargs['start_marker'] = None
> +        extrakwargs['end_marker'] = None
> +        extrakwargs['mid_marker'] = None
> +    elif 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,
> **extrakwargs):
> @@ -383,7 +396,7 @@
>      if not opts.get('print'):
>          out.close()
>
> -    if m3.conflicts:
> +    if m3.conflicts and not mode == 'union':
>          if not opts.get('quiet'):
>              ui.warn(_("warning: conflicts during merge.\n"))
>          return 1
>

Patch

diff -r ff5172c83002 -r d35c0f4e71ed mercurial/filemerge.py
--- a/mercurial/filemerge.py Wed Jun 24 13:41:27 2015 -0500
+++ b/mercurial/filemerge.py Fri Jun 26 21:14:49 2015 +0200
@@ -212,10 +212,7 @@ 
             util.copyfile(back, a) # restore from backup and try again
     return 1 # continue merging

-@internaltool('merge', True,
-              _("merging %s incomplete! "
-                "(edit conflicts, then use 'hg resolve --mark')\n"))
-def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files,
labels=None):
+def __imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels,
mode):
     """
     Uses the internal non-interactive simple merge algorithm for merging
     files. It will fail if there are any conflicts and leave markers in
@@ -232,10 +229,38 @@ 

         ui = repo.ui

-        r = simplemerge.simplemerge(ui, a, b, c, label=labels)
+        r = simplemerge.simplemerge(ui, a, b, c, label=labels, mode=mode)
         return True, r
     return False, 0

+
+@internaltool('merge', True,
+              _("merging %s incomplete! "
+                "(edit conflicts, then use 'hg resolve --mark')\n"))
+def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files,
labels=None):
+    """
+    Uses the internal non-interactive simple merge algorithm for merging
+    files. It will fail if there are any conflicts and leave markers in
+    the partially merged file. Markers will have two sections, one for
each side
+    of merge."""
+
+    return __imerge(repo, mynode, orig, fcd, fco, fca, toolconf,
+                    files, labels, 'merge')
+
+
+@internaltool('union', True,
+              _("merging %s incomplete! "
+                "(edit conflicts, then use 'hg resolve --mark')\n"))
+def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files,
labels=None):
+    """
+    Uses the internal non-interactive union merge algorithm for merging
+    files. It will use both sides if there are any conflicts."""
+
+    return __imerge(repo, mynode, orig, fcd, fco, fca, toolconf,
+                    files, labels, 'union')
+
+
+
 @internaltool('merge3', True,
               _("merging %s incomplete! "
                 "(edit conflicts, then use 'hg resolve --mark')\n"))
diff -r ff5172c83002 -r d35c0f4e71ed mercurial/simplemerge.py
--- a/mercurial/simplemerge.py Wed Jun 24 13:41:27 2015 -0500
+++ b/mercurial/simplemerge.py Fri Jun 26 21:14:49 2015 +0200
@@ -92,9 +92,9 @@ 
                 newline = '\r\n'
             elif self.a[0].endswith('\r'):
                 newline = '\r'
-        if name_a:
+        if name_a and start_marker:
             start_marker = start_marker + ' ' + name_a
-        if name_b:
+        if name_b and end_marker:
             end_marker = end_marker + ' ' + name_b
         if name_base and base_marker:
             base_marker = base_marker + ' ' + name_base
@@ -112,17 +112,20 @@ 
                     yield self.b[i]
             elif what == 'conflict':
                 self.conflicts = True
-                yield start_marker + newline
+                if start_marker is not None:
+                    yield start_marker + newline
                 for i in range(t[3], t[4]):
                     yield self.a[i]
                 if base_marker is not None:
                     yield base_marker + newline
                     for i in range(t[1], t[2]):
                         yield self.base[i]
-                yield mid_marker + newline
+                if mid_marker is not None:
+                    yield mid_marker + newline
                 for i in range(t[5], t[6]):
                     yield self.b[i]
-                yield end_marker + newline
+                if end_marker is not None:
+                    yield end_marker + newline
             else:
                 raise ValueError(what)

@@ -345,18 +348,24 @@ 
                 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:
-        name_base = labels[2]
-    if len(labels) > 3:
-        raise util.Abort(_("can only specify three labels."))
+    mode = opts.get('mode', '')
+    if mode == 'union':
+        name_a = None
+        name_b = None
+        name_base = None
+    else:
+        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:
+            name_base = labels[2]
+        if len(labels) > 3:
+            raise util.Abort(_("can only specify three labels."))

     try:
         localtext = readfile(local)
@@ -374,7 +383,11 @@ 

     m3 = Merge3Text(basetext, localtext, othertext)
     extrakwargs = {}
-    if name_base is not None:
+    if mode == 'union':
+        extrakwargs['start_marker'] = None
+        extrakwargs['end_marker'] = None
+        extrakwargs['mid_marker'] = None
+    elif 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,