graft: add --continue support
authorMatt Mackall <mpm@selenic.com>
Wed, 12 Oct 2011 18:48:57 -0500
changeset 15241 e4d135632f6d
parent 15240 bfb93963bb39
child 15242 dac2edce4e4a
graft: add --continue support
mercurial/commands.py
tests/test-debugcomplete.t
--- a/mercurial/commands.py	Wed Oct 12 18:46:23 2011 -0500
+++ b/mercurial/commands.py	Wed Oct 12 18:48:57 2011 -0500
@@ -2449,14 +2449,15 @@
 
 @command(
     'graft',
-    [('e', 'edit', False, _('invoke editor on commit messages')),
-    ('D', 'currentdate', False,
-     _('record the current date as commit date')),
-    ('U', 'currentuser', False,
-     _('record the current user as committer'), _('DATE'))]
+    [('c', 'continue', False, _('resume interrupted graft')),
+     ('e', 'edit', False, _('invoke editor on commit messages')),
+     ('D', 'currentdate', False,
+      _('record the current date as commit date')),
+     ('U', 'currentuser', False,
+      _('record the current user as committer'), _('DATE'))]
     + commitopts2 + mergetoolopts,
     _('[OPTION]... REVISION...'))
-def graft(ui, repo, rev, *revs, **opts):
+def graft(ui, repo, *revs, **opts):
     '''copy changes from other branches onto the current branch
 
     This command uses Mercurial's merge logic to copy individual
@@ -2467,11 +2468,17 @@
     Changesets that are ancestors of the current revision, that have
     already been grafted, or that are merges will be skipped.
 
+    If a graft merge results in conflicts, the graft process is
+    aborted so that the current merge can be manually resolved. Once
+    all conflicts are addressed, the graft process can be continued
+    with the -c/--continue option.
+
+    .. note::
+      The -c/--continue option does not reapply earlier options.
+
     Returns 0 on successful completion.
     '''
 
-    cmdutil.bailifchanged(repo)
-
     if not opts.get('user') and opts.get('currentuser'):
         opts['user'] = ui.username()
     if not opts.get('date') and opts.get('currentdate'):
@@ -2481,8 +2488,24 @@
     if opts.get('edit'):
         editor = cmdutil.commitforceeditor
 
-    revs = [rev] + list(revs)
-    revs = scmutil.revrange(repo, revs)
+    cont = False
+    if opts['continue']:
+        cont = True
+        if revs:
+            raise util.Abort(_("can't specify --continue and revisions"))
+        # read in unfinished revisions
+        try:
+            nodes = repo.opener.read('graftstate').splitlines()
+            revs = [repo[node].rev() for node in nodes]
+        except IOError, inst:
+            if inst.errno != errno.ENOENT:
+                raise
+            raise util.Abort(_("no graft state found, can't continue"))
+    else:
+        cmdutil.bailifchanged(repo)
+        if not revs:
+            raise util.Abort(_('no revisions specified'))
+        revs = scmutil.revrange(repo, revs)
 
     # check for merges
     for ctx in repo.set('%ld and merge()', revs):
@@ -2509,26 +2532,36 @@
     if not revs:
         return -1
 
-    for ctx in repo.set("%ld", revs):
+    for pos, ctx in enumerate(repo.set("%ld", revs)):
         current = repo['.']
         ui.debug('grafting revision %s', ctx.rev())
-        # perform the graft merge with p1(rev) as 'ancestor'
-        try:
-            # ui.forcemerge is an internal variable, do not document
-            repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
-            stats = mergemod.update(repo, ctx.node(), True, True, False,
-                                    ctx.p1().node())
-        finally:
-            ui.setconfig('ui', 'forcemerge', '')
-        # 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, can't continue"),
-                             hint=_('use hg resolve and hg graft --continue'))
+
+        # we don't merge the first commit when continuing
+        if not cont:
+            # perform the graft merge with p1(rev) as 'ancestor'
+            try:
+                # ui.forcemerge is an internal variable, do not document
+                repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
+                stats = mergemod.update(repo, ctx.node(), True, True, False,
+                                        ctx.p1().node())
+            finally:
+                ui.setconfig('ui', 'forcemerge', '')
+            # 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:
+                # write out state for --continue
+                nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
+                repo.opener.write('graftstate', ''.join(nodelines))
+                raise util.Abort(
+                    _("unresolved conflicts, can't continue"),
+                    hint=_('use hg resolve and hg graft --continue'))
+        else:
+            cont = False
+
         # commit
         extra = {'source': ctx.hex()}
         user = ctx.user()
@@ -2540,6 +2573,10 @@
         repo.commit(text=ctx.description(), user=user,
                     date=date, extra=extra, editor=editor)
 
+    # remove state when we complete successfully
+    if os.path.exists(repo.join('graftstate')):
+        util.unlinkpath(repo.join('graftstate'))
+
     return 0
 
 @command('grep',
--- a/tests/test-debugcomplete.t	Wed Oct 12 18:46:23 2011 -0500
+++ b/tests/test-debugcomplete.t	Wed Oct 12 18:48:57 2011 -0500
@@ -243,7 +243,7 @@
   debugsub: rev
   debugwalk: include, exclude
   debugwireargs: three, four, five, ssh, remotecmd, insecure
-  graft: edit, currentdate, currentuser, date, user, tool
+  graft: continue, edit, currentdate, currentuser, date, user, tool
   grep: print0, all, text, follow, ignore-case, files-with-matches, line-number, rev, user, date, include, exclude
   heads: rev, topo, active, closed, style, template
   help: extension, command