--- 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)