34caeeba9ae2

Add line-level commenting.
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Tue, 06 Oct 2009 19:27:47 -0400
parents 1ef154bb1a4f
children ab4cc556087d
branches/tags (none)
files review/api.py review/extension_ui.py review/messages.py review/tests/sample_data.py review/tests/test_comment.py review/tests/util.py

Changes

--- a/review/api.py	Mon Oct 05 20:50:06 2009 -0400
+++ b/review/api.py	Tue Oct 06 19:27:47 2009 -0400
@@ -106,14 +106,17 @@
             commentfns = filter(_match('%s/comments' % node), relevant)
             signofffns = filter(_match('%s/signoffs' % node), relevant)
             
-            self.comments = [ 
-                ReviewComment(**_parse_data(self.repo['tip'][fn].data()))
-                              for fn in commentfns
-            ]
-            self.signoffs = [ 
-                ReviewSignoff(**_parse_data(self.repo['tip'][fn].data()))
-                              for fn in signofffns
-            ]
+            self.comments = []
+            for fn in commentfns:
+                data = _parse_data(self.repo['tip'][fn].data())
+                data['lines'] = data['lines'].split(',')
+                data['lines'] = map(int, filter(None, data['lines']))
+                self.comments.append(ReviewComment(**data))
+            
+            self.signoffs = []
+            for fn in signofffns:
+                data = _parse_data(self.repo['tip'][fn].data())
+                self.signoffs.append(ReviewSignoff(**data))
         else:
             self.comments = []
             self.signoffs = []
@@ -235,6 +238,17 @@
         return templates.COMMENT_FILE_TEMPLATE % ( self.author, datetime,
             self.node, self.filename, lines, self.message )
     
+    def __str__(self):
+        return '\n'.join(map(str, [
+            self.author,
+            self.datetime,
+            self.node,
+            self.filename,
+            self.lines,
+            self.message,
+            '\n',
+        ]))
+    
 
 class ReviewSignoff(_ReviewObject):
     '''A single review signoff.'''
--- a/review/extension_ui.py	Mon Oct 05 20:50:06 2009 -0400
+++ b/review/extension_ui.py	Tue Oct 06 19:27:47 2009 -0400
@@ -6,6 +6,7 @@
 from mercurial import util
 from mercurial.node import short
 
+
 def _init_command(ui, repo, **opts):
     ui.note(messages.INIT_START)
     try:
@@ -23,6 +24,7 @@
     rev = opts.pop('rev')
     filename = fnames[0] if fnames else ''
     message = opts.pop('message')
+    lines = opts.pop('lines')
     
     rd = ReviewDatastore(ui, repo)
     rcset = rd[rev]
@@ -35,10 +37,13 @@
                 messages.COMMENT_FILE_DOES_NOT_EXIST % (filename, repo[rev].rev())
             )
     
+    if lines and not filename:
+        raise util.Abort(messages.COMMENT_LINES_REQUIRE_FILE)
+    
     if not message:
         raise util.Abort(messages.COMMENT_REQUIRES_MESSAGE)
     
-    rcset.add_comment(message=message, filename=filename)
+    rcset.add_comment(message=message, filename=filename, lines=lines.split(','))
 
 def _signoff_command(ui, repo, **opts):
     rd = ReviewDatastore(ui, repo)
@@ -76,13 +81,18 @@
     ui.write(messages.REVIEW_LOG_SIGNOFFS % len(rcset.signoffs))
     ui.write(messages.REVIEW_LOG_COMMENTS % (comment_count, author_count))
     
+    def _print_comment(comment, before='', after=''):
+        ui.write(before)
+        ui.write(messages.REVIEW_LOG_COMMENT_AUTHOR % comment.author)
+        for line in comment.message.splitlines():
+            ui.write(messages.REVIEW_LOG_COMMENT_LINE % line)
+        ui.write(after)
+    
     review_level_comments = filter(lambda c: not c.filename, rcset.comments)
     if review_level_comments:
         ui.write('\n')
     for comment in review_level_comments:
-        ui.write('\n' + messages.REVIEW_LOG_COMMENT_AUTHOR % comment.author)
-        for line in comment.message.splitlines():
-            ui.write(messages.REVIEW_LOG_COMMENT_LINE % line)
+        _print_comment(comment, before='\n')
     
     diffs = rcset.full_diffs(fnames, opts)
     
@@ -90,18 +100,26 @@
         header = messages.REVIEW_LOG_FILE_HEADER % filename
         print '\n\n%s %s' % (header, '-'*(80-(len(header)+1)))
         
-        file_level_comments = filter(lambda c: filename == c.filename, rcset.comments)
+        file_level_comments = filter(
+            lambda c: filename == c.filename and not c.lines, rcset.comments
+        )
         for comment in file_level_comments:
-            ui.write(messages.REVIEW_LOG_COMMENT_AUTHOR % comment.author)
-            for line in comment.message.splitlines():
-                ui.write(messages.REVIEW_LOG_COMMENT_LINE % line)
-            
+            _print_comment(comment)
         
         content = diffs[filename].splitlines()[2:]
         
+        line_level_comments = filter(
+            lambda c: filename == c.filename and c.lines, rcset.comments
+        )
         prefix = '%%%dd: ' % len(str(len(content)-1))
         for n, line in enumerate(content):
             ui.write('%s %s\n' % (prefix % n, line))
+            line_comments = filter(
+                lambda c: max(c.lines) == n, line_level_comments
+            )
+            for comment in line_comments:
+                _print_comment(comment)
+        
 
 
 def review(ui, repo, *fnames, **opts):
@@ -130,6 +148,7 @@
         ('',  'force',       False, 'overwrite an existing signoff'),
         ('f', 'file',        '',    'comment on <file>'),
         ('r', 'rev',         '.',   'the revision to review'),
+        ('l', 'lines',       '',    'the line(s) of the file to comment on'),
     ],
     'hg review')
 }
\ No newline at end of file
--- a/review/messages.py	Mon Oct 05 20:50:06 2009 -0400
+++ b/review/messages.py	Tue Oct 06 19:27:47 2009 -0400
@@ -26,6 +26,10 @@
 file %s was not changed in revision %s!
 '''
 
+COMMENT_LINES_REQUIRE_FILE = '''\
+you must give a filename to comment on specific lines!
+'''
+
 SIGNOFF_REQUIRES_MESSAGE = '''\
 a message must be provided to sign off!
 '''
--- a/review/tests/sample_data.py	Mon Oct 05 20:50:06 2009 -0400
+++ b/review/tests/sample_data.py	Tue Oct 06 19:27:47 2009 -0400
@@ -2,7 +2,7 @@
     { 'file_one': 'hello\nworld',
       'file_two': 'this is another test file',
     },
-    { 'file_one': 'hello again\nworld',
+    { 'file_one': 'hello again\nworld\nfoo\nbar',
       'file_three': 'this is a new file\nfor testing',
     },
 ]
\ No newline at end of file
--- a/review/tests/test_comment.py	Mon Oct 05 20:50:06 2009 -0400
+++ b/review/tests/test_comment.py	Tue Oct 06 19:27:47 2009 -0400
@@ -115,3 +115,61 @@
         assert False, 'The correct error message was not printed.'
     
 
+
+@with_setup(setup_reviewed_sandbox, teardown_sandbox)
+def test_add_comments_to_file_line():
+    sandbox = get_sandbox_repo()
+    
+    try:
+        review(comment=True, rev='1', message='Test bad comment.', lines='1')
+    except hgutil.Abort, e:
+        error = str(e)
+        assert messages.COMMENT_LINES_REQUIRE_FILE in error
+    else:
+        assert False, 'The correct error message was not printed.'
+    
+    author_line = messages.REVIEW_LOG_COMMENT_AUTHOR % '|'
+    a1, _, a2 = author_line.partition('|')
+    
+    review(comment=True, rev='1', message='Test comment one.',
+        files=['file_one'], lines='1')
+    
+    output = review(rev='1', files=['file_one'])
+    
+    # Make sure the comment is present at all.
+    assert a1 in output
+    assert a2 in output
+    assert messages.REVIEW_LOG_COMMENT_LINE % 'Test comment one.' in output
+    
+    # Make sure it's in the correct place
+    output = output.splitlines()
+    for n, line in enumerate(output):
+        if line.startswith('#'):
+            assert output[n-1].strip().startswith('1')
+            break
+
+
+@with_setup(setup_reviewed_sandbox, teardown_sandbox)
+def test_add_comments_to_file_lines():
+    sandbox = get_sandbox_repo()
+    
+    author_line = messages.REVIEW_LOG_COMMENT_AUTHOR % '|'
+    a1, _, a2 = author_line.partition('|')
+    
+    review(comment=True, rev='1', message='Test comment one.',
+        files=['file_one'], lines='1,2')
+    
+    output = review(rev='1', files=['file_one'])
+    
+    # Make sure the comment is present at all.
+    assert a1 in output
+    assert a2 in output
+    assert messages.REVIEW_LOG_COMMENT_LINE % 'Test comment one.' in output
+    
+    # Make sure it's in the correct place
+    output = output.splitlines()
+    for n, line in enumerate(output):
+        if line.startswith('#'):
+            assert output[n-1].strip().startswith('2')
+            break
+
--- a/review/tests/util.py	Mon Oct 05 20:50:06 2009 -0400
+++ b/review/tests/util.py	Tue Oct 06 19:27:47 2009 -0400
@@ -6,7 +6,8 @@
 
 _ui = ui.ui()
 def review(init=False, comment=False, signoff=False, yes=False, no=False,
-    force=False, message='', rev='.', local_path='', remote_path='', files=None):
+    force=False, message='', rev='.', local_path='', remote_path='', lines='',
+    files=None):
     
     files = files if files else []
     
@@ -14,7 +15,7 @@
     extension_ui.review(_ui, get_sandbox_repo(), *files,
         init=init, comment=comment, signoff=signoff, yes=yes, no=no, 
         force=force, message=message, rev=rev,
-        local_path=local_path, remote_path=remote_path )
+        local_path=local_path, remote_path=remote_path, lines=lines )
     output = _ui.popbuffer()
     
     print output