e91ac244e5ad

Add the ability to overwrite signoffs.
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Sun, 04 Oct 2009 22:08:06 -0400
parents bc3aa7a206f0
children 12aeab05829a
branches/tags (none)
files review/api.py review/extension_ui.py review/messages.py review/tests/test_signoff.py review/tests/util.py

Changes

--- a/review/api.py	Sun Oct 04 20:47:20 2009 -0400
+++ b/review/api.py	Sun Oct 04 22:08:06 2009 -0400
@@ -18,6 +18,14 @@
         self.committed = committed
     
 
+class SignoffExists(Exception):
+    '''Raised when trying to signoff twice without forcing.'''
+    pass
+
+class CannotDeleteObject(Exception):
+    '''Raised when trying to delete an object that does not support deletion.'''
+    pass
+
 
 def _parse_hgrf(repo):
     """Parse the .hgreview file and return the data inside."""
@@ -119,8 +127,15 @@
                 { 'message': 'Initialize review data for changeset %s' % self.node,
                   'addremove': True, })
     
-    def add_signoff(self, message, opinion=''):
+    def add_signoff(self, message, opinion='', force=False):
         '''Add (and commit) a signoff for the given revision.'''
+        existing = filter(lambda s: s.author == self.ui.username(), self.signoffs)
+        
+        if existing:
+            if not force:
+                raise SignoffExists
+            existing[0].delete(self.ui, self.repo)
+        
         signoff = ReviewSignoff(self.ui.username(), datetime.utcnow(),
             self.node, opinion, message)
         signoff.commit(self.ui, self.repo)
@@ -134,9 +149,10 @@
 
 class _ReviewObject(object):
     '''Some kind of object.'''
-    def __init__(self, container, commit_message):
+    def __init__(self, container, commit_message, delete_message=None):
         self.container = container
         self.commit_message = commit_message
+        self.delete_message = delete_message
     
     def commit(self, ui, repo):
         '''Write and commit this object to the given repo.'''
@@ -152,10 +168,24 @@
         with open(objectpath, 'w') as objectfile:
             objectfile.write(data)
         
-        cmdutil.commit(ui, repo, _commitfunc,
-            [objectpath],
+        cmdutil.commit(ui, repo, _commitfunc, [objectpath],
             { 'message': self.commit_message % self.node, 'addremove': True, })
     
+    def delete(self, ui, repo):
+        '''Delete and commit this object in the given repo.'''
+        
+        if not self.delete_message:
+            raise CannotDeleteObject
+        
+        data = self.render_data()
+        filename = sha1(data).hexdigest()
+        objectpath = os.path.join(repo.root, self.node, self.container, filename)
+        
+        os.remove(objectpath)
+        
+        cmdutil.commit(ui, repo, _commitfunc, [objectpath],
+            { 'message': self.delete_message % self.node, 'addremove': True, })
+    
 
 class ReviewComment(_ReviewObject):
     '''A single review comment.'''
@@ -182,6 +212,7 @@
     def __init__(self, author, datetime, node, opinion, message, **extra):
         super(ReviewSignoff, self).__init__(
             container='signoffs', commit_message=messages.COMMIT_SIGNOFF,
+            delete_message=messages.DELETE_SIGNOFF,
         )
         self.author = author
         self.datetime = datetime
--- a/review/extension_ui.py	Sun Oct 04 20:47:20 2009 -0400
+++ b/review/extension_ui.py	Sun Oct 04 22:08:06 2009 -0400
@@ -43,7 +43,12 @@
         raise util.Abort(messages.SIGNOFF_OPINION_CONFLICT)
     opinion = 'yes' if yes else ('no' if no else '')
     
-    rcset.add_signoff(message=message, opinion=opinion)
+    try:
+        rcset.add_signoff(message=message, opinion=opinion,
+            force=opts.pop('force'))
+    except SignoffExists:
+        raise util.Abort(messages.SIGNOFF_EXISTS)
+    
     return
 
 def _review_command(ui, repo, **opts):
@@ -86,6 +91,7 @@
         ('', 'yes', False, 'sign off as stating the changeset is good'),
         ('', 'no', False, 'sign off as stating the changeset is bad'),
         ('m', 'message', '', 'use <text> as the comment or signoff message'),
+        ('', 'force', False, 'overwrite an existing signoff'),
         ('r', 'rev', '.', 'the revision to review'),
     ],
     'hg review')
--- a/review/messages.py	Sun Oct 04 20:47:20 2009 -0400
+++ b/review/messages.py	Sun Oct 04 22:08:06 2009 -0400
@@ -30,6 +30,10 @@
 cannot sign off as both --yes and --no!
 '''
 
+SIGNOFF_EXISTS = '''\
+you have already signed off on this changeset (use --force to overwrite)!
+'''
+
 REVIEW_LOG_CSET = '''\
 changeset: %d:%s
 '''
@@ -54,4 +58,5 @@
 
 
 COMMIT_COMMENT = '''Add a comment on changeset %s'''
-COMMIT_SIGNOFF = '''Sign off on changeset %s'''
\ No newline at end of file
+COMMIT_SIGNOFF = '''Sign off on changeset %s'''
+DELETE_SIGNOFF = '''Remove sign off on changeset %s'''
\ No newline at end of file
--- a/review/tests/test_signoff.py	Sun Oct 04 20:47:20 2009 -0400
+++ b/review/tests/test_signoff.py	Sun Oct 04 22:08:06 2009 -0400
@@ -57,3 +57,25 @@
     output = grab_output()
     assert messages.REVIEW_LOG_SIGNOFFS % 0 in output
 
+
+@with_setup(setup_reviewed_sandbox, teardown_sandbox)
+def test_multiple_signoffs():
+    sandbox = get_sandbox_repo()
+    
+    review(signoff=True, message='Test signoff one.')
+    
+    try:
+        review(signoff=True, message='Test signoff two.')
+    except hgutil.Abort, e:
+        error = str(e)
+        assert messages.SIGNOFF_EXISTS in error
+    else:
+        assert False, 'The correct error message was not printed.'
+    
+    review(signoff=True, message='Test signoff two.', force=True)
+    
+    gather_output()
+    review()
+    output = grab_output()
+    assert messages.REVIEW_LOG_SIGNOFFS % 1 in output
+
--- a/review/tests/util.py	Sun Oct 04 20:47:20 2009 -0400
+++ b/review/tests/util.py	Sun Oct 04 22:08:06 2009 -0400
@@ -6,10 +6,10 @@
 
 _ui = ui.ui()
 def review(init=False, comment=False, signoff=False, yes=False, no=False,
-    message='', rev='.', local_path='', remote_path=''):
+    force=False, message='', rev='.', local_path='', remote_path=''):
     return extension_ui.review(_ui, get_sandbox_repo(),
         init=init, comment=comment, signoff=signoff, yes=yes, no=no, 
-        message=message, rev=rev, local_path=local_path,
+        force=force, message=message, rev=rev, local_path=local_path,
         remote_path=remote_path)