hgext3rd/evolve/cmdrewrite.py
changeset 3660 f018656ca3bf
parent 3616 f6d629514607
child 3661 61fdd25542a6
--- a/hgext3rd/evolve/cmdrewrite.py	Thu Apr 12 13:30:28 2018 +0800
+++ b/hgext3rd/evolve/cmdrewrite.py	Sun Mar 18 23:48:06 2018 +0530
@@ -33,6 +33,8 @@
 
 from mercurial.i18n import _
 
+from mercurial.utils import dateutil
+
 from . import (
     compat,
     state,
@@ -94,6 +96,7 @@
      ('a', 'all', False, _("match all files")),
      ('e', 'edit', False, _('invoke editor on commit messages')),
      ('', 'extract', False, _('extract changes from the commit to the working copy')),
+     ('', 'patch', False, _('make changes to wdir parent by editing patch')),
      ('', 'close-branch', None,
       _('mark a branch as closed, hiding it from the branch list')),
      ('s', 'secret', None, _('use the secret phase for committing')),
@@ -118,6 +121,8 @@
     """
     _checknotesize(ui, opts)
     opts = opts.copy()
+    if opts.get('patch'):
+        return amendpatch(ui, repo, *pats, **opts)
     if opts.get('extract'):
         return uncommit(ui, repo, *pats, **opts)
     else:
@@ -140,6 +145,127 @@
         finally:
             lockmod.release(lock, wlock)
 
+def amendpatch(ui, repo, *pats, **opts):
+    """logic for --patch flag of `hg amend` command."""
+    lock = wlock = tr = None
+    try:
+        wlock = repo.wlock()
+        lock = repo.lock()
+        tr = repo.transaction('amend')
+        cmdutil.bailifchanged(repo)
+        # first get the patch
+        old = repo['.']
+        p1 = old.p1()
+        rewriteutil.precheck(repo, [old.rev()], 'amend')
+        bookmarkupdater = rewriteutil.bookmarksupdater(repo, old.node(), tr)
+        diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
+        diffopts.nodates = True
+        diffopts.git = True
+        fp = stringio()
+        _writectxmetadata(repo, old, fp)
+        matcher = scmutil.match(old, pats, opts)
+        for chunk, label in patch.diffui(repo, p1.node(), old.node(),
+                                         match=matcher,
+                                         opts=diffopts):
+                fp.write(chunk)
+
+        fp.seek(0)
+        newpatch = ui.edit(fp.getvalue(), old.user(), action="diff")
+
+        afp = stringio()
+        afp.write(newpatch)
+        if pats:
+            # write rest of the files in the patch
+            restmatcher = scmutil.match(old, [], opts={'exclude': pats})
+            for chunk, label in patch.diffui(repo, p1.node(), old.node(),
+                                             match=restmatcher,
+                                             opts=diffopts):
+                    afp.write(chunk)
+
+        afp.seek(0)
+        # write the patch to repo and get the newnode
+        newnode = _writepatch(ui, repo, old, afp)
+
+        if newnode == old.node():
+            raise error.Abort(_("nothing changed"))
+        metadata = {}
+        if opts.get('note'):
+            metadata['note'] = opts['note']
+        compat.createmarkers(repo, [(old, (repo[newnode],))],
+                             metadata=metadata, operation='amend')
+        phases.retractboundary(repo, tr, old.phase(), [newnode])
+        hg.updaterepo(repo, newnode, True)
+        bookmarkupdater(newnode)
+        tr.close()
+    finally:
+        tr.release()
+        lockmod.release(lock, wlock)
+
+def _writepatch(ui, repo, old, fp):
+    """utility function to use filestore and patchrepo to apply a patch to the
+    repository with metadata being extracted from the patch"""
+    metadata = patch.extract(ui, fp)
+    pold = old.p1()
+
+    # store the metadata from the patch to variables
+    parents = (metadata.get('p1'), metadata.get('p2'))
+    date = metadata.get('date') or old.date()
+    branch = metadata.get('branch') or old.branch()
+    user = metadata.get('user') or old.user()
+    # XXX: we must extract extras from the patchfile too
+    extra = old.extra()
+    message = metadata.get('message') or old.description()
+    store = patch.filestore()
+    fp.seek(0)
+    try:
+        files = set()
+        try:
+            patch.patchrepo(ui, repo, pold, store, fp, 1, '',
+                            files=files, eolmode=None)
+        except patch.PatchError as err:
+            raise error.Abort(str(err))
+
+        finally:
+            del fp
+
+        memctx = context.memctx(repo, parents, message, files=files,
+                                filectxfn=store,
+                                user=user,
+                                date=date,
+                                branch=branch,
+                                extra=extra)
+        newcm = memctx.commit()
+    finally:
+        store.close()
+    return newcm
+
+def _writectxmetadata(repo, ctx, fp):
+    nodeval = scmutil.binnode(ctx)
+    parents = [p.node() for p in ctx.parents() if p]
+    branch = ctx.branch()
+    if parents:
+        prev = parents[0]
+    else:
+        prev = node.nullid
+
+    fp.write("# HG changeset patch\n")
+    fp.write("# User %s\n" % ctx.user())
+    fp.write("# Date %d %d\n" % ctx.date())
+    fp.write("#      %s\n" % dateutil.datestr(ctx.date()))
+    if branch and branch != 'default':
+        fp.write("# Branch %s\n" % branch)
+    fp.write("# Node ID %s\n" % node.hex(nodeval))
+    fp.write("# Parent  %s\n" % node.hex(prev))
+    if len(parents) > 1:
+        fp.write("# Parent  %s\n" % node.hex(parents[1]))
+
+    for headerid in cmdutil.extraexport:
+        header = cmdutil.extraexportmap[headerid](1, ctx)
+        if header is not None:
+            fp.write('# %s\n' % header)
+    fp.write(ctx.description().rstrip())
+    fp.write("\n\n")
+
 def _touchedbetween(repo, source, dest, match=None):
     touched = set()
     for files in repo.status(source, dest, match=match)[:3]: