D4664: mergecommit: add a new extension to merge in-memory and create a commit

pulkit (Pulkit Goyal) phabricator at mercurial-scm.org
Wed Sep 19 12:52:09 UTC 2018


pulkit created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  This patch adds a new mergecommit command which is very useful for workflows
  where there is automated merging involved. This patch uses existing in-memory
  merge API to merge in-memory and create a commit.
  
  I was thinking about --commit flag to merge command, but then we need other
  commitopts too, so created a new command.
  
  This patches filemerge.py to not raise IMMConflictsError on first conflict
  because we need a list of conflicts in the end.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D4664

AFFECTED FILES
  hgext/mergecommit.py
  mercurial/filemerge.py

CHANGE DETAILS

diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -910,7 +910,7 @@
 
         if r:
             if onfailure:
-                if wctx.isinmemory():
+                if wctx.isinmemory() and not ui.configbool('merge', 'inmemory'):
                     raise error.InMemoryMergeConflictsError('in-memory merge '
                                                             'does not support '
                                                             'merge conflicts')
diff --git a/hgext/mergecommit.py b/hgext/mergecommit.py
new file mode 100644
--- /dev/null
+++ b/hgext/mergecommit.py
@@ -0,0 +1,106 @@
+from mercurial import (
+    context,
+    destutil,
+    error,
+    extensions,
+    hg,
+    merge as mergemod,
+    phases,
+    registrar,
+    scmutil,
+)
+from mercurial.i18n import _
+
+cmdtable = {}
+command = registrar.command(cmdtable)
+
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+ at command('mergecommit',
+    [('', 'dest', '', _('merge destination')),
+     ('', 'message', '', _('description of the merge commit')),
+     ('', 'date', '', _('date of the merge commit')),
+     ('r', 'rev', '', _('revision to merge')),
+     ('', 'tool', '', _('specify merge tool')),
+    ], ())
+def mergecommit(ui, repo, node=None, **opts):
+    """Merge the rev passed into dest (or working directory parent) and
+    creates a commit with specified options if no conflicts occur.
+    
+    If conlicts occur, returns 1 and print the list of unresolved files on
+    stderr. Also, the conflicted state is not applied to the working directory.
+    To do that, you should run `hg merge`
+    
+    This does not update to destination node to merge the rev, it uses in-memory
+    merging and also creates in-memory commit. This also does not update to the
+    new commit formed."""
+
+    if opts.get('rev') and node:
+        raise error.Abort(_("please specify just one revision"))
+    if not node:
+        node = opts.get('rev')
+
+    if node:
+        node = scmutil.revsingle(repo, node).node()
+    else:
+        node = repo[destutil.destmerge(repo)].node()
+
+    destnode = None
+    if opts.get('dest'):
+        destnode = scmutil.revsingle(repo, opts.get('dest')).node()
+
+    overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
+    with ui.configoverride(overrides, 'mergecommit'):
+        return merge(repo, node, destnode, date=opts['date'],
+                     message=opts['message'])
+
+def merge(repo, node, destnode, date, message):
+    """merges the node into destnode or parent of working directory and creates
+    a commit if no conflicts occur.
+    
+    If conflicts are there, it returns 1 and prints the list of unresolved files
+    on stderr"""
+
+    # create a memwctx to merge
+    wctx = context.overlayworkingctx(repo)
+    # setting destnode as p1 if passed
+    if destnode:
+        currentp1 = repo[destnode]
+    else:
+        currentp1 = repo['.']
+    wctx.setbase(currentp1)
+
+    stats = None
+    try:
+        # actual merging
+        stats = mergemod.update(repo, node, True, None, wc=wctx)
+    except error.InMemoryMergeConflictsError:
+        pass
+
+    if stats is None or stats.unresolvedcount > 0:
+        # there were conflicts
+        ms = mergemod.mergestate.read(repo)
+        unresolved = list(ms.unresolved())
+        repo.ui.warn("list of unresolved files: %s\n" % ', '.join(unresolved))
+        mergemod.mergestate.clean(repo)
+        return 1
+
+    branch = currentp1.branch()
+    desc = message
+    if not desc:
+        desc = "in-memory merge commit"
+    if not date:
+        date = None
+    p1 = currentp1.node()
+    p2 = node
+
+    # creating a memctx and then commiting it
+    memctx = wctx.tomemctx(desc, parents=(p1, p2), branch=branch, date=date)
+    overrides = {('phases', 'new-commit'): phases.secret}
+    with repo.ui.configoverride(overrides, 'memorymerge'):
+        newctx = repo.commitctx(memctx)
+    wctx.clean()
+    mergemod.mergestate.clean(repo)
+    repo.ui.status("new commit formed is %s\n" % repo[newctx].hex()[:12])
+    return 0



To: pulkit, #hg-reviewers
Cc: mercurial-devel


More information about the Mercurial-devel mailing list