# HG changeset patch # User Steve Losh # Date 1278784647 14400 # Node ID 80d2d7acbd6cc4cbe953fbef2785faef921a8e73 # Parent 8308374acdb24a2f4a9310d0d92abe5e31b102f0# Parent 82d7322620316286c4fdf5768e600661588b85e8 Merge with default. diff -r 8308374acdb2 -r 80d2d7acbd6c bundled/markdown2/lib/markdown2.py --- a/bundled/markdown2/lib/markdown2.py Tue Jul 06 22:58:02 2010 -0400 +++ b/bundled/markdown2/lib/markdown2.py Sat Jul 10 13:57:27 2010 -0400 @@ -953,7 +953,10 @@ # We've got to encode these to avoid conflicting # with italics/bold. url = url.replace('*', g_escape_table['*']) \ - .replace('_', g_escape_table['_']) + .replace('_', g_escape_table['_']) \ + .replace('"', '&quo;') + if url.startswith('javascript:'): + url = url.replace('javascript:', '', 1) if title: title_str = ' title="%s"' \ % title.replace('*', g_escape_table['*']) \ @@ -1003,7 +1006,10 @@ # We've got to encode these to avoid conflicting # with italics/bold. url = url.replace('*', g_escape_table['*']) \ - .replace('_', g_escape_table['_']) + .replace('_', g_escape_table['_']) \ + .replace('"', '&quo;') + if url.startswith('javascript:'): + url = url.replace('javascript:', '', 1) title = self.titles.get(link_id) if title: title = title.replace('*', g_escape_table['*']) \ diff -r 8308374acdb2 -r 80d2d7acbd6c review/api.py --- a/review/api.py Tue Jul 06 22:58:02 2010 -0400 +++ b/review/api.py Sat Jul 10 13:57:27 2010 -0400 @@ -2,8 +2,9 @@ """The API for interacting with code review data.""" -import datetime, operator, os -import files, messages +import base64, datetime, operator, os +import messages +from rutil import fromlocal from mercurial import cmdutil, error, hg, patch, util from mercurial.node import hex from mercurial import ui as _ui @@ -148,10 +149,10 @@ for k, v in json.loads(data).iteritems(): result[k.encode('UTF-8')] = v - if u'filename' in result: - result['filename'] = result['filename'].encode('UTF-8') result['node'] = result['node'].encode('UTF-8') result['style'] = result['style'].encode('UTF-8') + if 'lines' in result: + result['lines'] = map(int, result['lines']) return result @@ -310,7 +311,8 @@ else: items[0]._delete(self.ui, self.repo) - def edit_comment(self, identifier, message=None, filename=None, lines=None, style=None): + def edit_comment(self, identifier, message=None, ufilename=None, filename=None, + lines=None, style=None): olds = self.get_items(identifier) if len(olds) == 0: @@ -327,6 +329,7 @@ raise FileNotInChangeset(filename) old.hgdate = util.makedate() + old.ufilename = ufilename if ufilename is not None else fromlocal(filename) 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 @@ -374,6 +377,25 @@ and full_diffs methods. See the docs of those methods for more info. """ + def _load_comment_file(self, filename): + data = _parse_data(self.repo['tip'][filename].data()) + + data['hgdate'] = util.parsedate(data['hgdate']) + data['identifier'] = _split_path_dammit(filename)[-1] + data['ufilename'] = data['file'][0] + data['filename'] = base64.b64decode(data['file'][1]) if data['file'] else '' + + return ReviewComment(**data) + + def _load_signoff_file(self, filename): + data = _parse_data(self.repo['tip'][filename].data()) + + data['hgdate'] = util.parsedate(data['hgdate']) + data['identifier'] = _split_path_dammit(filename)[-1] + + return ReviewSignoff(**data) + + def __init__(self, ui, repo, target, node): """Initialize a ReviewChangeset. @@ -396,20 +418,10 @@ commentfns = filter(_match('%s/comments' % node), relevant) signofffns = filter(_match('%s/signoffs' % node), relevant) - self.comments = [] - for fn in commentfns: - data = _parse_data(self.repo['tip'][fn].data()) - data['hgdate'] = util.parsedate(data['hgdate']) - data['identifier'] = _split_path_dammit(fn)[-1] - self.comments.append(ReviewComment(**data)) + self.comments = [self._load_comment_file(fn) for fn in commentfns] self.comments.sort(key=operator.attrgetter('local_datetime')) - self.signoffs = [] - for fn in signofffns: - data = _parse_data(self.repo['tip'][fn].data()) - data['hgdate'] = util.parsedate(data['hgdate']) - data['identifier'] = _split_path_dammit(fn)[-1] - self.signoffs.append(ReviewSignoff(**data)) + self.signoffs = [self._load_signoff_file(fn) for fn in signofffns] self.signoffs.sort(key=operator.attrgetter('local_datetime')) else: self.comments = [] @@ -450,7 +462,7 @@ self.node, opinion, message, style) signoff._commit(self.ui, self.repo) - def add_comment(self, message, filename='', lines=[], style=''): + def add_comment(self, message, ufilename=u'', filename='', lines=[], style=''): """Add (and commit) a comment for the given file and lines. The filename should be normalized to the format Mercurial expects, @@ -466,9 +478,11 @@ """ if filename and filename not in self.target[self.node].files(): raise FileNotInChangeset(filename) + if filename and not ufilename: + ufilename = fromlocal(filename) comment = ReviewComment(self.ui.username(), util.makedate(), - self.node, filename, lines, message, style) + self.node, ufilename, filename, map(int, lines), message, style) comment._commit(self.ui, self.repo) @@ -660,6 +674,16 @@ """Return the list of files in the revision for this ReviewChangeset.""" return self.target[self.node].files() + def unicode_files(self): + """Returns a tuple of files in the revision for this ReviewChangeset. + + Each element is a pair of strings, the first is a unicode string of the + filename, the second is a bytestring of the filename (which Mercurial + will want). + + """ + return [(fromlocal(f), f) for f in self.target[self.node].files()] + def review_level_comments(self): """Comments on this changeset which aren't on a particular file.""" @@ -775,13 +799,14 @@ The following pieces of information are stored for comments: comment = rcset.comments[0] - comment.author + comment.author U comment.hgdate comment.node + comment.ufilename U comment.filename comment.lines comment.local_datetime - comment.message + comment.message U comment.style comment.identifier comment.itemtype @@ -797,9 +822,13 @@ next to the current computer would have read at the instant the comment was added. + ufilename is a unicode string representing the filename. + + filename is a byte string representing the filename. + """ - def __init__(self, author, hgdate, node, filename, lines, message, - style='', identifier=None, **extra): + def __init__(self, author, hgdate, node, ufilename, filename, lines, + message, style='', identifier=None, **extra): """Initialize a ReviewComment. You shouldn't need to create these directly -- use a ReviewChangeset @@ -818,6 +847,7 @@ self.author = author self.hgdate = hgdate self.node = node + self.ufilename = ufilename self.filename = filename self.lines = lines self.message = message @@ -834,7 +864,8 @@ """ return json.dumps({ 'author': self.author, 'node': self.node, 'hgdate': util.datestr(self.hgdate), - 'filename': self.filename, 'lines': self.lines, + 'file': [self.ufilename, base64.b64encode(self.filename)], + 'lines': self.lines, 'style': self.style, 'message': self.message }, indent=4, sort_keys=True) diff -r 8308374acdb2 -r 80d2d7acbd6c review/cli.py --- a/review/cli.py Tue Jul 06 22:58:02 2010 -0400 +++ b/review/cli.py Sat Jul 10 13:57:27 2010 -0400 @@ -6,10 +6,10 @@ """ import api, helps, messages +from rutil import fromlocal, tolocal from mercurial import help, templatefilters, util from mercurial.node import short - def _get_datastore(ui, repo): try: return api.ReviewDatastore(ui, repo) @@ -46,7 +46,8 @@ style = 'markdown' if mdown or comment.style == 'markdown' else '' try: - rd.edit_comment(comment.identifier, message, fnames[0], lines, style) + rd.edit_comment(comment.identifier, fromlocal(message), + filename=fnames[0], lines=lines, style=style) except api.FileNotInChangeset: raise util.Abort(messages.COMMENT_FILE_DOES_NOT_EXIST % ( fnames[0], rd.target[comment.node].rev())) @@ -65,7 +66,7 @@ style = 'markdown' if mdown or signoff.style == 'markdown' else '' - rd.edit_signoff(signoff.identifier, message, opinion, style) + rd.edit_signoff(signoff.identifier, fromlocal(message), opinion, style) def _web_command(ui, repo, **opts): @@ -126,7 +127,7 @@ for fn in fnames: try: - rcset.add_comment(message=message, filename=fn, lines=lines, style=style) + rcset.add_comment(message=fromlocal(message), filename=fn, lines=lines, style=style) except api.FileNotInChangeset: raise util.Abort(messages.COMMENT_FILE_DOES_NOT_EXIST % ( fn, repo[rev].rev())) @@ -153,7 +154,7 @@ style = mdown and 'markdown' or '' - rcset.add_signoff(message=message, opinion=opinion, style=style) + rcset.add_signoff(message=fromlocal(message), opinion=opinion, style=style) def _check_command(ui, repo, **opts): rd = _get_datastore(ui, repo) @@ -231,7 +232,7 @@ label='review.comment') for line in comment.message.splitlines(): - ui.write(messages.REVIEW_LOG_COMMENT_LINE % line, label='review.comment') + ui.write(messages.REVIEW_LOG_COMMENT_LINE % tolocal(line), label='review.comment') ui.write(after) @@ -244,7 +245,7 @@ ui.write(header, label=label) for line in signoff.message.splitlines(): - ui.write(messages.REVIEW_LOG_SIGNOFF_LINE % line, label=label) + ui.write(messages.REVIEW_LOG_SIGNOFF_LINE % tolocal(line), label=label) ui.write(after) diff -r 8308374acdb2 -r 80d2d7acbd6c review/rutil.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/review/rutil.py Sat Jul 10 13:57:27 2010 -0400 @@ -0,0 +1,8 @@ +from mercurial import encoding + +def tolocal(s): + return encoding.tolocal(s.encode('UTF-8')) + +def fromlocal(s): + return encoding.fromlocal(s).decode('UTF-8') + diff -r 8308374acdb2 -r 80d2d7acbd6c review/static/comments.js --- a/review/static/comments.js Tue Jul 06 22:58:02 2010 -0400 +++ b/review/static/comments.js Sat Jul 10 13:57:27 2010 -0400 @@ -23,7 +23,8 @@ 'Post Comment' + 'Cancel' + - '' + + '' + + '' + '' + '' + '' + @@ -32,10 +33,12 @@ ); function RenderLineCommentForm(line, currNum, body, markdown, identifier) { - var filename = line.closest(".file").find(".filename h3 a .name").html(); + var filename_u = line.closest(".file").find(".filename .filename-u").text(); + var filename_b64 = line.closest(".file").find(".filename .filename-b64").text(); markdown_checked = markdown ? 'checked="checked"' : ''; - return comment_form({ filename: filename, currNum: currNum, body: body, - markdown_checked: markdown_checked, identifier: identifier }); + return comment_form({ filename_u: filename_u, filename_b64: filename_b64, + currNum: currNum, body: body, identifier: identifier, + markdown_checked: markdown_checked }); } $(function() { diff -r 8308374acdb2 -r 80d2d7acbd6c review/static/style.css --- a/review/static/style.css Tue Jul 06 22:58:02 2010 -0400 +++ b/review/static/style.css Sat Jul 10 13:57:27 2010 -0400 @@ -393,6 +393,12 @@ -moz-border-radius-bottomleft: 3px; border-bottom-left-radius: 3px; } +#changeset .content .head .patch { + position: absolute; + top: -18px; + right: -80px; + width: 50px; +} #changeset .content .head .fulldesc { width: 660px; font-size: 12px; diff -r 8308374acdb2 -r 80d2d7acbd6c review/static/style.less --- a/review/static/style.less Tue Jul 06 22:58:02 2010 -0400 +++ b/review/static/style.less Sat Jul 10 13:57:27 2010 -0400 @@ -381,6 +381,12 @@ -moz-border-radius-bottomleft: 3px; border-bottom-left-radius: 3px; } + .patch { + position: absolute; + top: -18px; + right: -80px; + width: 50px; + } .fulldesc { width: 660px; font-size: 12px; diff -r 8308374acdb2 -r 80d2d7acbd6c review/templates/changeset.html --- a/review/templates/changeset.html Tue Jul 06 22:58:02 2010 -0400 +++ b/review/templates/changeset.html Sat Jul 10 13:57:27 2010 -0400 @@ -25,6 +25,7 @@ {% block content %}
 
+

{{ rev.rev() }}: @@ -78,14 +79,15 @@

Files

- {% for filename in rcset.files() %} + {% for ufilename, filename in rcset.unicode_files() %} {% set comments = rcset.file_level_comments(filename) %} {% set commentcount = utils['len'](comments) + utils['len'](rcset.line_level_comments(filename)) %}
diff -r 8308374acdb2 -r 80d2d7acbd6c review/templates/pieces/comment.html --- a/review/templates/pieces/comment.html Tue Jul 06 22:58:02 2010 -0400 +++ b/review/templates/pieces/comment.html Sat Jul 10 13:57:27 2010 -0400 @@ -36,8 +36,8 @@ said:
- {% if comment.filename %} -
on {{ comment.filename }}
+ {% if comment.ufilename %} +
on {{ comment.ufilename }}
{% else %}
on changeset {{ rev.rev() }}:{{ utils['node_short'](rev.node()) }}
{% endif %} diff -r 8308374acdb2 -r 80d2d7acbd6c review/templates/pieces/forms/comment-change.html --- a/review/templates/pieces/forms/comment-change.html Tue Jul 06 22:58:02 2010 -0400 +++ b/review/templates/pieces/forms/comment-change.html Sat Jul 10 13:57:27 2010 -0400 @@ -22,7 +22,12 @@
- + + {% if filename %} + + {% else %} + + {% endif %} Edit Comment Cancel diff -r 8308374acdb2 -r 80d2d7acbd6c review/templates/pieces/forms/file-comment.html --- a/review/templates/pieces/forms/file-comment.html Tue Jul 06 22:58:02 2010 -0400 +++ b/review/templates/pieces/forms/file-comment.html Sat Jul 10 13:57:27 2010 -0400 @@ -15,6 +15,7 @@ Post Comment Cancel - + +
diff -r 8308374acdb2 -r 80d2d7acbd6c review/templates/pieces/linecomment.html --- a/review/templates/pieces/linecomment.html Tue Jul 06 22:58:02 2010 -0400 +++ b/review/templates/pieces/linecomment.html Sat Jul 10 13:57:27 2010 -0400 @@ -48,7 +48,7 @@ said:
-
in {{ comment.filename }}
+
in {{ comment.ufilename }}
{{ comment.message }}
diff -r 8308374acdb2 -r 80d2d7acbd6c review/tests/test_encoding.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/review/tests/test_encoding.py Sat Jul 10 13:57:27 2010 -0400 @@ -0,0 +1,25 @@ +# coding=utf-8 +# +import os + +from nose import with_setup +from util import setup_reviewed_sandbox, teardown_sandbox, review, should_fail_with +from util import get_datastore_repo, get_sandbox_repo, get_ui +from util import check_comment_exists_on_line + +from .. import api, messages, rutil + +from mercurial.node import hex + + +@with_setup(setup_reviewed_sandbox, teardown_sandbox) +def test_comment_encoding(): + review(comment=True, message=rutil.tolocal(u'Téstíng.')) + output = review() + assert messages.REVIEW_LOG_COMMENT_LINE % rutil.tolocal(u'Téstíng.') in output + +@with_setup(setup_reviewed_sandbox, teardown_sandbox) +def test_signoff_encoding(): + review(signoff=True, message=rutil.tolocal(u'Téstíng.')) + output = review() + assert messages.REVIEW_LOG_SIGNOFF_LINE % rutil.tolocal(u'Téstíng.') in output diff -r 8308374acdb2 -r 80d2d7acbd6c review/web.py --- a/review/web.py Tue Jul 06 22:58:02 2010 -0400 +++ b/review/web.py Sat Jul 10 13:57:27 2010 -0400 @@ -2,10 +2,10 @@ """The review extension's web UI.""" -import sys, os +import base64, sys, os, StringIO from hashlib import md5 -from mercurial import commands, hg, templatefilters +from mercurial import cmdutil, commands, error, hg, templatefilters from mercurial.node import short from mercurial.util import email @@ -33,7 +33,7 @@ import markdown2 from flask import Flask -from flask import abort, g, redirect, render_template, request +from flask import abort, g, redirect, render_template, request, Response app = Flask(__name__) LOG_PAGE_LEN = 15 @@ -53,6 +53,7 @@ 'no': len(filter(lambda s: s.opinion == 'no', signoffs)), 'neutral': len(filter(lambda s: s.opinion == '', signoffs)),} + markdowner = markdown2.Markdown(safe_mode='escape', extras=['code-friendly', 'pyshell', 'imgless']) utils = { 'node_short': short, @@ -68,6 +69,7 @@ 'str': str, 'decode': lambda s: s.decode('utf-8'), 'markdown': markdowner.convert, + 'b64': base64.b64encode, } def _render(template, **kwargs): @@ -116,17 +118,22 @@ body = request.form.get('new-signoff-body', '') style = 'markdown' if request.form.get('signoff-markdown') else '' - current = request.form.get('current') - if current: - g.datastore.edit_signoff(current, body, signoff, style=style) - else: - rcset = g.datastore[revhash] - rcset.add_signoff(body, signoff, style=style) + try: + current = request.form.get('current') + if current: + g.datastore.edit_signoff(current, body, signoff, style=style) + else: + rcset = g.datastore[revhash] + rcset.add_signoff(body, signoff, style=style) + except error.RepoLookupError: + abort(404) return redirect("%s/changeset/%s/" % (app.site_root, revhash)) def _handle_comment(revhash): - filename = request.form.get('filename', '') + filename = base64.b64decode(request.form.get('filename-b64', u'')) + ufilename = request.form.get('filename-u', u'') + print repr(filename), repr(ufilename) lines = str(request.form.get('lines', '')) if lines: @@ -135,13 +142,16 @@ body = request.form['new-comment-body'] style = 'markdown' if request.form.get('comment-markdown') else '' - if body: - current = request.form.get('current') - if current: - g.datastore.edit_comment(current, body, filename, lines, style) - else: - rcset = g.datastore[revhash] - rcset.add_comment(body, filename, lines, style) + try: + if body: + current = request.form.get('current') + if current: + g.datastore.edit_comment(current, body, ufilename, filename, lines, style) + else: + rcset = g.datastore[revhash] + rcset.add_comment(body, ufilename, filename, lines, style) + except error.RepoLookupError: + abort(404) return redirect("%s/changeset/%s/" % (app.site_root, revhash)) @@ -154,7 +164,10 @@ elif not app.read_only or app.allow_anon: return _handle_comment(revhash) - rcset = g.datastore[revhash] + try: + rcset = g.datastore[revhash] + except error.RepoLookupError: + abort(404) rev = rcset.target[revhash] cu_signoffs = rcset.signoffs_for_current_user() @@ -168,6 +181,15 @@ newer=newer, older=older) +@app.route('/changeset//patch/') +def patch(revhash): + result = StringIO.StringIO() + try: + cmdutil.export(g.datastore.target, [revhash], fp=result) + except error.RepoLookupError: + abort(404) + return Response(result.getvalue(), content_type="text/plain") + @app.route('/pull/', methods=['POST']) def pull(): if not app.read_only: