mercurial/commands.py
changeset 40434 3c0d5016b2be
parent 40394 1feb4b2c8e40
child 40578 db61a18148a4
--- a/mercurial/commands.py	Thu Oct 18 12:31:06 2018 +0200
+++ b/mercurial/commands.py	Sun Oct 14 17:08:18 2018 +0200
@@ -2249,6 +2249,8 @@
 @command(
     'graft',
     [('r', 'rev', [], _('revisions to graft'), _('REV')),
+     ('', 'base', '',
+      _('base revision when doing the graft merge (ADVANCED)'), _('REV')),
      ('c', 'continue', False, _('resume interrupted graft')),
      ('', 'stop', False, _('stop interrupted graft')),
      ('', 'abort', False, _('abort interrupted graft')),
@@ -2294,6 +2296,35 @@
 
     .. container:: verbose
 
+      The --base option exposes more of how graft internally uses merge with a
+      custom base revision. --base can be used to specify another ancestor than
+      the first and only parent.
+
+      The command::
+
+        hg graft -r 345 --base 234
+
+      is thus pretty much the same as::
+
+        hg diff -r 234 -r 345 | hg import
+
+      but using merge to resolve conflicts and track moved files.
+
+      The result of a merge can thus be backported as a single commit by
+      specifying one of the merge parents as base, and thus effectively
+      grafting the changes from the other side.
+
+      It is also possible to collapse multiple changesets and clean up history
+      by specifying another ancestor as base, much like rebase --collapse
+      --keep.
+
+      The commit message can be tweaked after the fact using commit --amend .
+
+      For using non-ancestors as the base to backout changes, see the backout
+      command and the hidden --parent option.
+
+    .. container:: verbose
+
       Examples:
 
       - copy a single change to the stable branch and edit its description::
@@ -2317,6 +2348,15 @@
 
           hg log -r "sort(all(), date)"
 
+      - backport the result of a merge as a single commit::
+
+          hg graft -r 123 --base 123^
+
+      - land a feature branch as one changeset::
+
+          hg up -cr default
+          hg graft -r featureX --base "ancestor('featureX', 'default')"
+
     See :hg:`help revisions` for more about specifying revisions.
 
     Returns 0 on successful completion.
@@ -2332,6 +2372,9 @@
 
     revs = list(revs)
     revs.extend(opts.get('rev'))
+    basectx = None
+    if opts.get('base'):
+        basectx = scmutil.revsingle(repo, opts['base'], None)
     # a dict of data to be stored in state file
     statedata = {}
     # list of new nodes created by ongoing graft
@@ -2411,13 +2454,16 @@
         revs = scmutil.revrange(repo, revs)
 
     skipped = set()
-    # check for merges
-    for rev in repo.revs('%ld and merge()', revs):
-        ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
-        skipped.add(rev)
+    if basectx is None:
+        # check for merges
+        for rev in repo.revs('%ld and merge()', revs):
+            ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
+            skipped.add(rev)
     revs = [r for r in revs if r not in skipped]
     if not revs:
         return -1
+    if basectx is not None and len(revs) != 1:
+        raise error.Abort(_('only one revision allowed with --base '))
 
     # Don't check in the --continue case, in effect retaining --force across
     # --continues. That's because without --force, any revisions we decided to
@@ -2425,7 +2471,7 @@
     # way to the graftstate. With --force, any revisions we would have otherwise
     # skipped would not have been filtered out, and if they hadn't been applied
     # already, they'd have been in the graftstate.
-    if not (cont or opts.get('force')):
+    if not (cont or opts.get('force')) and basectx is None:
         # check for ancestors of dest branch
         crev = repo['.'].rev()
         ancestors = repo.changelog.ancestors([crev], inclusive=True)
@@ -2521,8 +2567,9 @@
         if not cont:
             # perform the graft merge with p1(rev) as 'ancestor'
             overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
+            base = ctx.p1() if basectx is None else basectx
             with ui.configoverride(overrides, 'graft'):
-                stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'graft'])
+                stats = mergemod.graft(repo, ctx, base, ['local', 'graft'])
             # report any conflicts
             if stats.unresolvedcount > 0:
                 # write out state for --continue