# HG changeset patch # User Steve Losh # Date 1254708486 14400 # Node ID e91ac244e5adcaf1e800779585733c3e836ae6b0 # Parent bc3aa7a206f0991d33eba82185816c2f250ed1ef Add the ability to overwrite signoffs. diff -r bc3aa7a206f0 -r e91ac244e5ad review/api.py --- 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 diff -r bc3aa7a206f0 -r e91ac244e5ad review/extension_ui.py --- 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 as the comment or signoff message'), + ('', 'force', False, 'overwrite an existing signoff'), ('r', 'rev', '.', 'the revision to review'), ], 'hg review') diff -r bc3aa7a206f0 -r e91ac244e5ad review/messages.py --- 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 diff -r bc3aa7a206f0 -r e91ac244e5ad review/tests/test_signoff.py --- 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 + diff -r bc3aa7a206f0 -r e91ac244e5ad review/tests/util.py --- 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)