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) + +@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 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')