# HG changeset patch # User Steve Losh # Date 1276983597 14400 # Node ID 03fded64e34ee471f36f48e958e83b07312c3cc6 # Parent 2310846513b88a30f4f37574992afb65a7f9a175 api: add support for editing comments diff -r 2310846513b8 -r 03fded64e34e review/api.py --- a/review/api.py Sat Jun 19 15:43:22 2010 -0400 +++ b/review/api.py Sat Jun 19 17:39:57 2010 -0400 @@ -79,6 +79,10 @@ """Raised when trying to specify an item with an identifier which does not match any items.""" pass +class WrongEditItemType(Exception): + """Raised when calling edit_comment with a signoff, or vice versa.""" + pass + def _split_path_dammit(p): """Take a file path (from the current platform) and split it. Really. @@ -261,7 +265,7 @@ def __getitem__(self, rev): """Return a ReviewChangeset for the given revision.""" node = hex(self.target[str(rev)].node()) - return ReviewChangeset(self.ui, self.repo, self.target, node) + return ReviewChangeset(self.ui, self.repo, self.target, node, self) def reviewed_changesets(self): @@ -298,6 +302,28 @@ else: items[0]._delete(self.ui, self.repo) + def edit_comment(self, identifier, message=None, filename=None, lines=None, style=None): + olds = self.get_items(identifier) + + if len(olds) == 0: + raise UnknownIdentifier + elif len(olds) > 1: + raise AmbiguousIdentifier + + old = olds[0] + if old.itemtype != 'comment': + raise WrongEditItemType() + + filename = filename if filename is not None else old.filename + if filename and filename not in self.target[old.node].files(): + raise FileNotInChangeset(filename) + + old.hgdate = util.makedate() + old.filename = filename + old.lines = lines if lines is not None else old.lines + old.message = message if message is not None else old.message + old.style = style if style is not None else old.style + old._rename(self.ui, self.repo, old.identifier) class ReviewChangeset(object): """The review data about one changeset in the target repository. @@ -322,7 +348,7 @@ and full_diffs methods. See the docs of those methods for more info. """ - def __init__(self, ui, repo, target, node): + def __init__(self, ui, repo, target, node, datastore): """Initialize a ReviewChangeset. You shouldn't need to create these directly -- use a ReviewDatastore @@ -336,6 +362,7 @@ self.target = target self.ui = ui self.node = node + self.datastore = datastore if '%s/.exists' % self.node in self.repo['tip']: _match = lambda p: lambda fn: fn.startswith(p) @@ -381,6 +408,7 @@ def signoffs_for_current_user(self): return self.signoffs_for_user(self.ui.username()) + def add_signoff(self, message, opinion='', force=False, style=''): """Add (and commit) a signoff for the given revision. @@ -649,10 +677,12 @@ class _ReviewObject(object): """A base object for some kind of review data (a signoff or comment).""" - def __init__(self, container, commit_message, delete_message=None): + def __init__(self, container, commit_message, delete_message, rename_message): self.container = container self.commit_message = commit_message self.delete_message = delete_message + self.rename_message = rename_message + def _commit(self, ui, repo): """Write and commit this object to the given repo.""" @@ -683,6 +713,34 @@ cmdutil.commit(ui, repo, _commitfunc, [objectpath], { 'message': self.delete_message % self.node, 'addremove': True, }) + def _rename(self, ui, repo, identifier): + """Commit this object in the given repo and mark it as a rename of identifier.""" + + data = self._render_data() + newidentifier = util.sha1(data).hexdigest() + newpath = os.path.join(repo.root, self.node, self.container, newidentifier) + + oldpath = os.path.join(repo.root, self.node, self.container, identifier) + + if oldpath == newpath: + # Nothing has changed. This is probably from a "touch" edit made + # within the same second as the previous modification time. + return + + wlock = repo.wlock(False) + try: + cmdutil.copy(ui, repo, [oldpath, newpath], {'force': True}, rename=True) + finally: + wlock.release() + + with open(newpath, 'w') as objectfile: + objectfile.write(data) + + cmdutil.commit(ui, repo, _commitfunc, [oldpath, newpath], + { 'message': self.rename_message % self.node }) + + self.identifier = newidentifier + @property def local_datetime(self): @@ -705,6 +763,7 @@ comment.message comment.style comment.identifier + comment.itemtype Each item is a string, except for lines, hgdate, and local_datetime. @@ -731,10 +790,10 @@ tip_comments = tip_review.comments """ - super(ReviewComment, self).__init__( - container='comments', commit_message=messages.COMMIT_COMMENT, - delete_message=messages.DELETE_COMMENT - ) + super(ReviewComment, self).__init__(container='comments', + commit_message=messages.COMMIT_COMMENT, + delete_message=messages.DELETE_COMMENT, + rename_message=messages.RENAME_COMMENT) self.author = author self.hgdate = hgdate self.node = node @@ -743,6 +802,7 @@ self.message = message self.style = style self.identifier = identifier + self.itemtype = 'comment' def _render_data(self): """Render the data of this comment into a string for writing to disk. @@ -752,7 +812,7 @@ """ rendered_date = util.datestr(self.hgdate) - lines = ','.join(self.lines) + lines = ','.join(map(str, self.lines)) return files.COMMENT_FILE_TEMPLATE % ( self.author, rendered_date, self.node, self.filename, lines, self.style, self.message ) @@ -765,6 +825,7 @@ self.node, self.filename, self.lines, + self.style, self.message, '\n', ])) @@ -785,6 +846,7 @@ signoff.message signoff.style signoff.identifier + signoff.itemtype Each item is a string, except for hgdate and local_datetime. @@ -809,10 +871,10 @@ tip_signoffs = tip_review.signoffs """ - super(ReviewSignoff, self).__init__( - container='signoffs', commit_message=messages.COMMIT_SIGNOFF, + super(ReviewSignoff, self).__init__(container='signoffs', + commit_message=messages.COMMIT_SIGNOFF, delete_message=messages.DELETE_SIGNOFF, - ) + rename_message=messages.RENAME_SIGNOFF) self.author = author self.hgdate = hgdate self.node = node @@ -820,6 +882,7 @@ self.message = message self.style = style self.identifier = identifier + self.itemtype = 'signoff' def _render_data(self): """Render the data of this signoff into a string for writing to disk. diff -r 2310846513b8 -r 03fded64e34e review/messages.py --- a/review/messages.py Sat Jun 19 15:43:22 2010 -0400 +++ b/review/messages.py Sat Jun 19 17:39:57 2010 -0400 @@ -137,9 +137,11 @@ COMMIT_COMMENT = """Add a comment on changeset %s""" DELETE_COMMENT = """Remove comment off from changeset %s""" +RENAME_COMMENT = """Rename comment on changeset %s""" COMMIT_SIGNOFF = """Sign off on changeset %s""" -DELETE_SIGNOFF = """Remove sign off on changeset %s""" +DELETE_SIGNOFF = """Remove signoff on changeset %s""" +RENAME_SIGNOFF = """Rename signoff on changeset %s""" FETCH = """Automated merge of review data.""" @@ -163,14 +165,23 @@ changeset has no comments or signoffs """ -DELETE_AMBIGUOUS_ID = """\ +AMBIGUOUS_ID = """\ the identifier '%s' matches more than one item! """ -DELETE_UNKNOWN_ID = """\ +UNKNOWN_ID = """\ unknown item '%s'! """ -DELETE_REQUIRES_IDS = """\ +REQUIRES_IDS = """\ no items specified """ + +EDIT_REQUIRES_SINGLE_ID = """\ +cannot edit multiple items +""" + +EDIT_REQUIRES_ONE_OR_LESS_FILES = """\ +cannot edit a comment to be on multiple files +""" +