03fded64e34e

api: add support for editing comments
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Sat, 19 Jun 2010 17:39:57 -0400
parents 2310846513b8
children dff2e396a30f
branches/tags (none)
files review/api.py review/messages.py

Changes

--- 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.
--- 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
+"""
+