diff -r 808b762679cd -r 3c0d5016b2be mercurial/commands.py --- 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