[RFC] New core command: graft

Matt Mackall mpm at selenic.com
Sun Oct 9 17:23:38 CDT 2011


This is a command we discussed at the 1.5 sprint in Paris and almost got
finished there as a rebase feature, but then forgot about. Later, I went
and changed the merge core to make it simpler to do rebase's magic.

Now I've finally decided to try resurrecting the 'graft' command idea as
a core command.

The basic concept is similar to transplant, except:

- it uses real 3-way merge logic rather than patching
- it doesn't work between different repositories

The trick here is that rather than use the 'natural' merge ancestor, we
choose the parent of the grafted changeset. 

The implementation I have so far works, but there's still some work to
do:

- needs to actually support --continue
- needs testing

Among other things, I'm looking for feedback on the UI and what features
are critical. Right now, it copies the details of grafted changesets
(user/date/description) verbatim. That's an ok default, but it seems
like we'd like to be able to tweak that.


# HG changeset patch
# User Matt Mackall <mpm at selenic.com>
# Date 1318198312 18000
# Node ID fac84985882412bef5c9756c32002ea754f1a683
# Parent  231aac5280baae0614d7f278bb306b95c4a1488b
graft: add initial implementation

diff -r 231aac5280ba -r fac849858824 mercurial/commands.py
--- a/mercurial/commands.py	Sun Oct 09 16:14:37 2011 -0500
+++ b/mercurial/commands.py	Sun Oct 09 17:11:52 2011 -0500
@@ -2446,6 +2446,74 @@
     repo[None].forget(forget)
     return errs
 
+ at command('graft',
+    [],
+    _('[OPTION]... REVISION...'))
+def graft(ui, repo, rev, *revs, **opts):
+    '''copy individual changes from other branches onto the current branch
+
+    This command uses Mercurial's merge logic to copy individual
+    changes from other branches without merging branches. This is
+    sometimes known as 'backporting' or 'cherry-picking'.
+
+    Changesets that are ancestors of the current revision, that have
+    already been grafted, or that are merges will be skipped.
+
+    Returns 0 on successful completion.
+    '''
+
+    cmdutil.bailifchanged(repo)
+
+    revs = [rev] + list(revs)
+    revs = scmutil.revrange(repo, revs)
+
+    # check for merges
+    for ctx in repo.set('%ld and merge()', revs):
+        ui.warn(_('skipping ungraftable merge revision %s\n') % ctx.rev())
+        revs.remove(ctx.rev())
+    if not revs:
+        return -1
+
+    # check for ancestors of dest branch
+    for ctx in repo.set('::. and %ld', revs):
+        ui.warn(_('skipping ancestor revision %s\n') % ctx.rev())
+        revs.remove(ctx.rev())
+    if not revs:
+        return -1
+
+    # check ancestors for earlier grafts
+    ui.debug('scanning for existing transplants')
+    for ctx in repo.set("::. - ::%ld", revs):
+        n = ctx.extra().get('graft_source')
+        if n and n in repo:
+            r = repo[n].rev()
+            ui.warn(_('skipping already grafted revision %s\n') % r)
+            revs.remove(r)
+    if not revs:
+        return -1
+
+    for ctx in repo.set("%ld", revs):
+        current = repo['.']
+        ui.debug('grafting revision %s', ctx.rev())
+        # perform the graft merge with p1(rev) as 'ancestor'
+        stats = mergemod.update(repo, ctx.node(), True, True, False,
+                             ctx.p1().node())
+        # drop the second merge parent
+        repo.dirstate.setparents(current.node(), nullid)
+        repo.dirstate.write()
+        # fix up dirstate for copies and renames
+        cmdutil.duplicatecopies(repo, ctx.rev(), current.node(), nullid)
+        # report any conflicts
+        if stats and stats[3] > 0:
+            raise util.Abort(_('unresolved conflicts (see hg '
+                               'resolve, then hg graft --continue)'))
+        # commit
+        extra = {'graft_source': ctx.hex()}
+        repo.commit(text=ctx.description(), user=ctx.user(),
+                    date=ctx.date(), extra=extra)
+
+    return 0
+
 @command('grep',
     [('0', 'print0', None, _('end fields with NUL')),
     ('', 'all', None, _('print all revisions that match')),


-- 
Mathematics is the supreme nostalgia of our time.




More information about the Mercurial-devel mailing list