Commits

Augie Fackler committed b8942ed

Implement pick for non-commuting patches.

Comments (0)

Files changed (6)

 
 Inspired by git rebase --interactive.
 """
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle
 import tempfile
 import os
 
     oldctx = repo[ha]
     if oldctx.parents()[0] == ctx:
         ui.debug('node %s unchanged\n' % ha)
-        return oldctx, [], []
+        return oldctx, [], [], []
     hg.update(repo, ctx.node())
     fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
     fp = os.fdopen(fd, 'w')
     for chunk in gen:
         fp.write(chunk)
     fp.close()
-
     try:
         files = {}
         try:
             if not files:
                 ui.warn(_('%s: empty changeset')
                              % node.hex(ha))
-                return oldctx, [], []
+                return ctx, [], [], []
         finally:
             files = patch.updatedir(ui, repo, files)
             os.unlink(patchfile)
                            'hg histedit --continue'))
     n = repo.commit(files, oldctx.description(), oldctx.user(), oldctx.date(),
                     extra=oldctx.extra())
-    return repo[n], [oldctx.node(), ], []
+    return repo[n], [n, ], [oldctx.node(), ], []
 
 
 def edit(ui, repo, ctx, ha, opts):
     if opts.get('continue', False):
         if len(parent) != 0:
             raise util.Abort('no arguments allowed with --continue')
-        raise util.Abort('This part not done.')
-    tip, empty = repo.dirstate.parents()
+        (parentctxnode, created, replaced,
+         tmpnodes, existing, rules, keep, tip, ) = readstate(repo)
+        currentparent, wantnull = repo.dirstate.parents()
+        parentctx = repo[parentctxnode]
+        # discover any nodes the user has added in the interim
+        newchildren = [c for c in parentctx.children()
+                       if c.node() not in existing]
+        while newchildren:
+            created.extend([n.node() for n in newchildren])
+            newchildren = filter(lambda x: x.node() not in existing,
+                                 reduce(lambda x, y: x + y,
+                                        map(lambda r: r.children(),
+                                            newchildren)))
+        action, currentnode = rules.pop(0)
+        if action in ('e', 'edit', 'p', 'pick', ):
+            m, a, r, d = repo.status()[:4]
+            oldctx = repo[currentnode]
+            replaced.append(oldctx.node())
+            if m or a or r or d:
+                new = repo.commit(set(m+a+r+d), oldctx.description(), oldctx.user(), oldctx.date(),
+                                  extra=oldctx.extra())
+                created.append(new)
+                parentctx = repo[new]
+        else: # fold
+            raise util.Abort('continue post-fold not quite done')
+    elif opts.get('abort', False):
+        if len(parent) != 0:
+            raise util.Abort('no arguments allowed with --abort')
+        (parentctxnode, created, replaced, tmpnodes,
+         existing, rules, keep, tip, ) = readstate(repo)
+        ui.debug('restore wc to old tip %s\n' % node.hex(tip))
+        hg.clean(repo, tip)
+        ui.debug('should strip created nodes %s\n' %
+                 ', '.join([node.hex(n)[:12] for n in created]))
+        ui.debug('should strip temp nodes %s\n' %
+                 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
+        for nodes in (created, tmpnodes, ):
+            for n in reversed(nodes):
+                try:
+                    repair.strip(ui, repo, n)
+                except error.LookupError:
+                    pass
+        os.unlink(os.path.join(repo.path, 'histedit-state'))
+        return
+    else:
+        cmdutil.bail_if_changed(repo)
+        if os.path.exists(os.path.join(repo.path, 'histedit-state')):
+            raise util.Abort('history edit already in progress, try --continue or --abort')
 
-    cmdutil.bail_if_changed(repo)
+        tip, empty = repo.dirstate.parents()
 
-    if len(parent) != 1:
-        raise util.Abort('requires exactly one parent revision')
-    parent = parent[0]
 
-    revs = between(repo, parent, tip)
+        if len(parent) != 1:
+            raise util.Abort('requires exactly one parent revision')
+        parent = parent[0]
 
-    ctxs = [repo[r] for r in revs]
-    rules = '\n'.join([('pick %s %s' % (c.hex()[:12],
-                                     c.description().splitlines()[0]))[:80]
-                       for c in ctxs])
+        revs = between(repo, parent, tip)
 
-    rules += editcomment % (node.hex(parent)[:12], node.hex(tip)[:12], )
+        ctxs = [repo[r] for r in revs]
+        existing = [r.node() for r in ctxs]
+        rules = '\n'.join([('pick %s %s' % (c.hex()[:12],
+                                         c.description().splitlines()[0]))[:80]
+                           for c in ctxs])
 
-    rules = ui.edit(rules, ui.username())
+        rules += editcomment % (node.hex(parent)[:12], node.hex(tip)[:12], )
 
-    parentctx = repo[parent].parents()[0]
+        rules = ui.edit(rules, ui.username())
 
-    rules = [l for l in (r.strip() for r in rules.splitlines()) if l and not l[0] == '#']
-    rules = verifyrules(rules, repo, ctxs)
+        parentctx = repo[parent].parents()[0]
 
-    replaced = []
-    tmpnodes = []
-    for action, ha in rules:
-        parentctx, replaced_, tmpnodes_ = actiontable[action](ui, repo,
-                                                              parentctx, ha,
-                                                              opts)
+        rules = [l for l in (r.strip() for r in rules.splitlines()) if l and not l[0] == '#']
+        rules = verifyrules(rules, repo, ctxs)
+        keep = opts.get('keep', False)
+        replaced = []
+        tmpnodes = []
+        created = []
+
+
+    while rules:
+        writestate(repo, parentctx.node(), created, replaced, tmpnodes, existing,
+                   rules, keep, tip)
+        action, ha = rules.pop(0)
+        (parentctx, created_,
+         replaced_, tmpnodes_, ) = actiontable[action](ui, repo,
+                                                       parentctx, ha,
+                                                       opts)
+        created.extend(created_)
         replaced.extend(replaced_)
         tmpnodes.extend(tmpnodes_)
 
     hg.update(repo, parentctx.node())
 
-    if not opts.get('keep', False):
+    if not keep:
         ui.debug('should strip replaced nodes %s\n' %
                  ', '.join([node.hex(n)[:12] for n in replaced]))
         for n in reversed(replaced):
             except error.LookupError:
                 pass
 
+    ui.debug('should strip temp nodes %s\n' %
+             ', '.join([node.hex(n)[:12] for n in tmpnodes]))
     for n in reversed(tmpnodes):
-        ui.debug('should strip temp nodes %s\n' %
-                 ', '.join([node.hex(n)[:12] for n in replaced]))
         repair.strip(ui, repo, n)
+    os.unlink(os.path.join(repo.path, 'histedit-state'))
+
+
+def writestate(repo, parentctxnode, created, replaced,
+               tmpnodes, existing, rules, keep, oldtip):
+    fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
+    pickle.dump((parentctxnode, created, replaced,
+                 tmpnodes, existing, rules, keep, oldtip,),
+                fp)
+    fp.close()
+
+def readstate(repo):
+    """Returns a tuple of (parentnode, created, replaced, tmp, existing, rules, keep, oldtip, ).
+    """
+    fp = open(os.path.join(repo.path, 'histedit-state'))
+    return pickle.load(fp)
 
 
 def verifyrules(rules, repo, ctxs):
         (histedit,
          [('c', 'continue', False, 'continue an edit already in progress', ),
           ('k', 'keep', False, 'strip old nodes after edit is complete', ),
+          ('', 'abort', False, 'abort an edit in progress', ),
           ],
          __doc__,
          ),

tests/test-histedit-commute.out

 
  -c --continue  continue an edit already in progress
  -k --keep      strip old nodes after edit is complete
+    --abort     abort an edit in progress
 
 use "hg -v help histedit" to show global options
 % log before edit

tests/test-histedit-non-commute

 hg log --graph
 
 echo % edit the history
-HGEDITOR="cat $EDITED > $1 " hg histedit 177f92b77385 2>&1 | grep -v 'saving bundle'
+HGEDITOR="cat $EDITED > " hg histedit 177f92b77385 2>&1 | grep -v 'saving bundle'
+
+echo % abort the edit
+hg histedit --abort 2>&1 | grep -v 'saving bundle'
+
+echo
+echo
+echo % second edit set
+
+hg log --graph
+
+echo % edit the history
+HGEDITOR="cat $EDITED > " hg histedit 177f92b77385 2>&1 | grep -v 'saving bundle'
+
+echo % fix up
+echo a > e
+hg add e
+hg histedit --continue
+
+echo
+echo % just continue this time
+hg histedit --continue 2>&1 | grep -v 'saving bundle'
 
 echo % log after edit
 hg log --graph
+
+echo % EOF

tests/test-histedit-non-commute-abort

+#!/bin/sh
+
+cat >> $HGRCPATH <<EOF
+[extensions]
+histedit=
+hgext.graphlog=
+EOF
+
+EDITED=`pwd`/editedhistory
+cat > $EDITED <<EOF
+pick 177f92b77385 c
+pick 055a42cdd887 d
+pick bfa474341cc9 does not commute with e
+pick e860deea161a e
+pick 652413bf663e f
+EOF
+initrepo ()
+{
+    hg init r
+    cd r
+    for x in a b c d e f ; do
+        echo $x > $x
+        hg add $x
+        hg ci -m $x
+    done
+    echo a >> e
+    hg ci -m 'does not commute with e'
+}
+
+initrepo
+
+echo % log before edit
+hg log --graph
+
+echo % edit the history
+HGEDITOR="cat $EDITED > " hg histedit 177f92b77385 2>&1 | grep -v 'saving bundle'
+
+echo '% fix up (pre abort)'
+echo a > e
+hg add e
+hg histedit --continue 2>&1 | grep -v 'saving bundle'
+
+echo % abort the edit
+hg histedit --abort 2>&1 | grep -v 'saving bundle'
+
+echo % log after abort
+hg log --graph
+echo % EOF

tests/test-histedit-non-commute-abort.out

+% log before edit
+@  changeset:   6:bfa474341cc9
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     does not commute with e
+|
+o  changeset:   5:652413bf663e
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   4:e860deea161a
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     e
+|
+o  changeset:   3:055a42cdd887
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   2:177f92b77385
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% edit the history
+0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+unable to find 'e' for patching
+1 out of 1 hunks FAILED -- saving rejects to file e.rej
+e: No such file or directory
+abort: Fix up the change and run hg histedit --continue
+% fix up (pre abort)
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+file e already exists
+1 out of 1 hunks FAILED -- saving rejects to file e.rej
+abort: Fix up the change and run hg histedit --continue
+% abort the edit
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% log after abort
+@  changeset:   6:bfa474341cc9
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     does not commute with e
+|
+o  changeset:   5:652413bf663e
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   4:e860deea161a
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     e
+|
+o  changeset:   3:055a42cdd887
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   2:177f92b77385
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% EOF

tests/test-histedit-non-commute.out

    summary:     a
 
 % edit the history
+0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+unable to find 'e' for patching
+1 out of 1 hunks FAILED -- saving rejects to file e.rej
+e: No such file or directory
+abort: Fix up the change and run hg histedit --continue
+% abort the edit
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+
+% second edit set
+@  changeset:   6:bfa474341cc9
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     does not commute with e
+|
+o  changeset:   5:652413bf663e
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   4:e860deea161a
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     e
+|
+o  changeset:   3:055a42cdd887
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   2:177f92b77385
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% edit the history
+0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+unable to find 'e' for patching
+1 out of 1 hunks FAILED -- saving rejects to file e.rej
+e: No such file or directory
+abort: Fix up the change and run hg histedit --continue
+% fix up
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+file e already exists
+1 out of 1 hunks FAILED -- saving rejects to file e.rej
+abort: Fix up the change and run hg histedit --continue
+
+% just continue this time
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+adding branch
+adding changesets
+adding manifests
+adding file changes
+added 2 changesets with 2 changes to 2 files (+1 heads)
+adding branch
+adding changesets
+adding manifests
+adding file changes
+added 2 changesets with 2 changes to 2 files
 % log after edit
+@  changeset:   5:9ab84894b459
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   4:1fff3ae8199d
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     does not commute with e
+|
+o  changeset:   3:055a42cdd887
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   2:177f92b77385
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
 
-TODO
+% EOF