amend: do not traceback on no-ops
authorPatrick Mezard <patrick@mezard.eu>
Tue, 12 Jun 2012 13:28:39 +0200
changeset 265 24943df310d4
parent 264 1c21865bf8ba
child 266 0071322a0875
amend: do not traceback on no-ops If rewrite() generated changeset happened to be an existing one, the call would traceback when trying to obsolete the changeset with itself. Instead, leave gracefully, marking any intermediate changeset extinct.
hgext/evolve.py
tests/test-amend.t
--- a/hgext/evolve.py	Tue Jun 12 11:53:02 2012 +0200
+++ b/hgext/evolve.py	Tue Jun 12 13:28:39 2012 +0200
@@ -57,6 +57,11 @@
 #############################
 
 def rewrite(repo, old, updates, head, newbases, commitopts):
+    """Return (nodeid, created) where nodeid is the identifier of the
+    changeset generated by the rewrite process, and created is True if
+    nodeid was actually created. If created is False, nodeid
+    references a changeset existing before the rewrite call.
+    """
     if len(old.parents()) > 1: #XXX remove this unecessary limitation.
         raise error.Abort(_('cannot amend merge changesets'))
     base = old.p1()
@@ -126,29 +131,35 @@
 
         if commitopts.get('edit'):
             new._text = cmdutil.commitforceeditor(repo, new, [])
+        revcount = len(repo)
         newid = repo.commitctx(new)
         new = repo[newid]
-
-        # update the bookmark
-        if bm:
-            repo._bookmarks[bm] = newid
-            bookmarks.write(repo)
+        created = len(repo) != revcount
+        if created:
+            # update the bookmark
+            if bm:
+                repo._bookmarks[bm] = newid
+                bookmarks.write(repo)
 
-        # add evolution metadata
-        repo.addobsolete(new.node(), old.node())
-        for u in updates:
-            repo.addobsolete(u.node(), old.node())
-            repo.addobsolete(new.node(), u.node())
-        oldbookmarks = repo.nodebookmarks(old.node())
-        for book in oldbookmarks:
-            repo._bookmarks[book] = new.node()
-        if oldbookmarks:
-            bookmarks.write(repo)
-
+            # add evolution metadata
+            repo.addobsolete(new.node(), old.node())
+            for u in updates:
+                repo.addobsolete(u.node(), old.node())
+                repo.addobsolete(new.node(), u.node())
+            oldbookmarks = repo.nodebookmarks(old.node())
+            for book in oldbookmarks:
+                repo._bookmarks[book] = new.node()
+            if oldbookmarks:
+                bookmarks.write(repo)
+        else:
+            # newid is an existing revision. It could make sense to
+            # replace revisions with existing ones but probably not by
+            # default.
+            pass
     finally:
         wlock.release()
 
-    return newid
+    return newid, created
 
 def relocate(repo, orig, dest):
     """rewrite <rev> on dest"""
@@ -407,7 +418,11 @@
             def commitfunc(ui, repo, message, match, opts):
                 return repo.commit(message, opts.get('user'), opts.get('date'), match,
                                    editor=e)
-            cmdutil.commit(ui, repo, commitfunc, pats, ciopts)
+            revcount = len(repo)
+            tempid = cmdutil.commit(ui, repo, commitfunc, pats, ciopts)
+            if len(repo) == revcount:
+                # No revision created
+                tempid = None
 
             # find all changesets to be considered updates
             cl = repo.changelog
@@ -429,13 +444,20 @@
             # perform amend
             if opts.get('edit'):
                 opts['force_editor'] = True
-            newid = rewrite(repo, old, updates, head,
-                            [old.p1().node(), old.p2().node()], opts)
-
-            # reroute the working copy parent to the new changeset
-            phases.retractboundary(repo, oldphase, [newid])
-            repo.dirstate.setparents(newid, node.nullid)
-
+            newid, created = rewrite(repo, old, updates, head,
+                                     [old.p1().node(), old.p2().node()], opts)
+            if created:
+                # reroute the working copy parent to the new changeset
+                phases.retractboundary(repo, oldphase, [newid])
+                repo.dirstate.setparents(newid, node.nullid)
+            else:
+                # rewrite() recreated an existing revision, discard
+                # the intermediate revision if any. No need to update
+                # phases or parents.
+                if tempid is not None:
+                    repo.addobsolete(node.nullid, tempid)
+                # XXX: need another message in collapse case.
+                raise error.Abort(_('no updates found'))
         finally:
             wlock.release()
     finally:
--- a/tests/test-amend.t	Tue Jun 12 11:53:02 2012 +0200
+++ b/tests/test-amend.t	Tue Jun 12 13:28:39 2012 +0200
@@ -8,6 +8,10 @@
   $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
 
+  $ glog() {
+  >   hg glog --template '{rev}@{branch}({phase}) {desc|firstline}\n' "$@"
+  > }
+
   $ hg init repo
   $ cd repo
   $ echo a > a
@@ -25,7 +29,67 @@
   $ hg branches
   foo                            2:a34b93d251e4
   default                        0:07f494440405 (inactive)
-  $ hg glog --template '{rev}@{branch} {desc|firstline}\n'
-  @  2@foo adda
+  $ glog
+  @  2@foo(draft) adda
+  
+Test no-op
+
+  $ hg amend
+  abort: no updates found
+  [255]
+  $ glog
+  @  2@foo(draft) adda
+  
+
+Test forcing the message to the same value, no intermediate revision.
+
+  $ hg amend -m 'adda'
+  abort: no updates found
+  [255]
+  $ glog
+  @  2@foo(draft) adda
   
 
+Test collapsing into an existing revision, no intermediate revision.
+
+  $ echo a >> a
+  $ hg ci -m changea
+  $ echo a > a
+  $ hg ci -m reseta
+  $ hg amend --change 2
+  abort: no updates found
+  [255]
+  $ hg phase 2
+  2: draft
+  $ glog
+  @  4@foo(draft) reseta
+  |
+  o  3@foo(draft) changea
+  |
+  o  2@foo(draft) adda
+  
+
+Test collapsing into an existing rev, with an intermediate revision.
+
+  $ hg branch --force default
+  marked working directory as branch default
+  (branches are permanent and global, did you want a bookmark?)
+  $ hg ci -m resetbranch
+  created new head
+  $ hg branch --force foo
+  marked working directory as branch foo
+  (branches are permanent and global, did you want a bookmark?)
+  $ hg amend --change 2
+  abort: no updates found
+  [255]
+  $ glog
+  @  6@foo(secret) amends a34b93d251e49c93d5685ebacad785c73a7e8605
+  |
+  o  5@default(draft) resetbranch
+  |
+  o  4@foo(draft) reseta
+  |
+  o  3@foo(draft) changea
+  |
+  o  2@foo(draft) adda
+