Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(107)

Side by Side Diff: codereview/engine.py

Issue 974: accept patches from mercurial SVN Base: http://rietveld.googlecode.com/svn/trunk
Patch Set: Created 3 months, 3 weeks ago
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright 2008 Google Inc. 1 # Copyright 2008 Google Inc.
2 # 2 #
3 # Licensed under the Apache License, Version 2.0 (the "License"); 3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License. 4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at 5 # You may obtain a copy of the License at
6 # 6 #
7 # http://www.apache.org/licenses/LICENSE-2.0 7 # http://www.apache.org/licenses/LICENSE-2.0
8 # 8 #
9 # Unless required by applicable law or agreed to in writing, software 9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, 10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and 12 # See the License for the specific language governing permissions and
13 # limitations under the License. 13 # limitations under the License.
14 14
15 """Diff rendering in HTML for Rietveld.""" 15 """Diff rendering in HTML for Rietveld."""
16 16
17 # Python imports 17 # Python imports
18 import re 18 import re
19 import cgi 19 import cgi
20 import difflib 20 import difflib
21 import logging 21 import logging
22 import urlparse 22 import urlparse
23 23
24 # AppEngine imports 24 # AppEngine imports
25 from google.appengine.api import urlfetch 25 from google.appengine.api import urlfetch
26 from google.appengine.api import users 26 from google.appengine.api import users
27 from google.appengine.ext import db 27 from google.appengine.ext import db
28 28
29 # Django imports 29 # Django imports
30 from django.template import loader 30 from django.template import loader
31 31
32 # Local imports 32 # Local imports
33 import models 33 import models
34 import patching 34 import patching
35 import intra_region_diff 35 import intra_region_diff
36 36
37 37
38 class FetchError(Exception): 38 class FetchError(Exception):
39 """Exception raised by FetchBase() when a URL problem occurs.""" 39 """Exception raised by FetchBase() when a URL problem occurs."""
40 40
41 41
42 def ParsePatchSet(patchset): 42 def ParsePatchSet(patchset):
43 """Patch a patch set into individual patches. 43 """Patch a patch set into individual patches.
44 44
45 Args: 45 Args:
46 patchset: a models.PatchSet instance. 46 patchset: a models.PatchSet instance.
47 47
48 Returns: 48 Returns:
49 A list of models.Patch instances. 49 A list of models.Patch instances.
50 """ 50 """
51 patches = [] 51 patches = []
52 filename = lines = None 52 filename = lines = None
53 for line in patchset.data.splitlines(True): 53 for line in patchset.data.splitlines(True):
54 if line.startswith('Index:'): 54 if line.startswith('Index:'):
55 if filename and lines: 55 if filename and lines:
56 patch = models.Patch(patchset=patchset, text=_ToText(lines), 56 patch = models.Patch(patchset=patchset, text=_ToText(lines),
57 filename=filename, parent=patchset) 57 filename=filename, parent=patchset)
58 patches.append(patch) 58 patches.append(patch)
59 unused, filename = line.split(':', 1) 59 unused, filename = line.split(':', 1)
60 filename = filename.strip() 60 filename = filename.strip()
61 lines = [line] 61 lines = [line]
62 continue 62 continue
63 if lines is not None: 63 if lines is not None:
64 lines.append(line) 64 lines.append(line)
65 if filename and lines: 65 if filename and lines:
66 patch = models.Patch(patchset=patchset, text=_ToText(lines), 66 patch = models.Patch(patchset=patchset, text=_ToText(lines),
67 filename=filename, parent=patchset) 67 filename=filename, parent=patchset)
68 patches.append(patch) 68 patches.append(patch)
69 if not patches:
70 # let's try to convert Mercurial
71 logging.info(patchset.data)
72 lines = patchset.data.splitlines(True)
73 filename = "README"
74 if filename and lines:
75 patch = models.Patch(patchset=patchset, text=_ToText(lines),
76 filename=filename, parent=patchset)
77 patches.append(patch)
69 return patches 78 return patches
70 79
71 80
72 def FetchBase(base, patch): 81 def FetchBase(base, patch):
73 """Fetch the content of the file to which the file is relative. 82 """Fetch the content of the file to which the file is relative.
74 83
75 Args: 84 Args:
76 base: the base property of the Issue to which the Patch belongs. 85 base: the base property of the Issue to which the Patch belongs.
77 patch: a models.Patch instance. 86 patch: a models.Patch instance.
78 87
79 Returns: 88 Returns:
80 A models.Content instance. 89 A models.Content instance.
81 90
82 Raises: 91 Raises:
83 FetchError: For any kind of problem fetching the content. 92 FetchError: For any kind of problem fetching the content.
84 """ 93 """
85 filename, lines = patch.filename, patch.lines 94 filename, lines = patch.filename, patch.lines
86 rev = patching.ParseRevision(lines) 95 rev = patching.ParseRevision(lines)
87 if rev is not None: 96 if rev is not None:
88 if rev == 0: 97 if rev == 0:
89 # rev=0 means it's a new file. 98 # rev=0 means it's a new file.
90 return models.Content(text=db.Text(u''), parent=patch) 99 return models.Content(text=db.Text(u''), parent=patch)
91 url = _MakeUrl(base, filename, rev) 100 url = _MakeUrl(base, filename, rev)
92 logging.info('Fetching %s', url) 101 logging.info('Fetching %s', url)
93 try: 102 try:
94 result = urlfetch.fetch(url) 103 result = urlfetch.fetch(url)
95 except Exception, err: 104 except Exception, err:
96 msg = 'Error fetching %s: %s: %s' % (url, err.__class__.__name__, err) 105 msg = 'Error fetching %s: %s: %s' % (url, err.__class__.__name__, err)
97 logging.error(msg) 106 logging.error(msg)
98 raise FetchError(msg) 107 raise FetchError(msg)
99 if result.status_code != 200: 108 if result.status_code != 200:
100 msg = 'Error fetching %s: HTTP status %s' % (url, result.status_code) 109 msg = 'Error fetching %s: HTTP status %s' % (url, result.status_code)
101 logging.error(msg) 110 logging.error(msg)
102 raise FetchError(msg) 111 raise FetchError(msg)
103 lines = result.content.splitlines(True) 112 lines = result.content.splitlines(True)
104 # TODO(guido): Handle non-ASCII text better. 113 # TODO(guido): Handle non-ASCII text better.
105 for i, line in enumerate(lines): 114 for i, line in enumerate(lines):
106 try: 115 try:
107 line.decode('ascii') 116 line.decode('ascii')
108 except UnicodeError, err: 117 except UnicodeError, err:
109 logging.warn('Line %d: %r is not ASCII', i+1, line) 118 logging.warn('Line %d: %r is not ASCII', i+1, line)
110 uni = line.decode('ascii', 'replace') 119 uni = line.decode('ascii', 'replace')
111 lines[i] = uni.encode('ascii', 'replace') 120 lines[i] = uni.encode('ascii', 'replace')
112 return models.Content(text=_ToText(lines), parent=patch) 121 return models.Content(text=_ToText(lines), parent=patch)
113 122
114 123
115 def _MakeUrl(base, filename, rev): 124 def _MakeUrl(base, filename, rev):
116 """Helper for FetchBase() to construct the URL to fetch. 125 """Helper for FetchBase() to construct the URL to fetch.
117 126
118 Args: 127 Args:
119 base: The base property of the Issue to which the Patch belongs. 128 base: The base property of the Issue to which the Patch belongs.
120 filename: The filename property of the Patch instance. 129 filename: The filename property of the Patch instance.
121 rev: Revision number, or None for head revision. 130 rev: Revision number, or None for head revision.
122 131
123 Returns: 132 Returns:
124 A URL referring to the given revision of the file. 133 A URL referring to the given revision of the file.
125 """ 134 """
135 return "http://hg.sympy.org/sympy/raw-file/tip/" + filename
126 scheme, netloc, path, params, query, fragment = urlparse.urlparse(base) 136 scheme, netloc, path, params, query, fragment = urlparse.urlparse(base)
127 if netloc.endswith(".googlecode.com"): 137 if netloc.endswith(".googlecode.com"):
128 # Handle Google code repositories 138 # Handle Google code repositories
129 assert rev is not None, "Can't access googlecode.com without a revision" 139 assert rev is not None, "Can't access googlecode.com without a revision"
130 assert path.startswith("/svn/"), "Malformed googlecode.com URL" 140 assert path.startswith("/svn/"), "Malformed googlecode.com URL"
131 path = path[5:] # Strip "/svn/" 141 path = path[5:] # Strip "/svn/"
132 url = "%s://%s/svn-history/r%d/%s/%s" % (scheme, netloc, rev, 142 url = "%s://%s/svn-history/r%d/%s/%s" % (scheme, netloc, rev,
133 path, filename) 143 path, filename)
134 return url 144 return url
135 # Default for viewvc-based URLs (svn.python.org) 145 # Default for viewvc-based URLs (svn.python.org)
136 url = base 146 url = base
137 if not url.endswith('/'): 147 if not url.endswith('/'):
138 url += '/' 148 url += '/'
139 url += filename 149 url += filename
140 if rev is not None: 150 if rev is not None:
141 url += '?rev=%s' % rev 151 url += '?rev=%s' % rev
142 return url 152 return url
143 153
144 154
145 def RenderDiffTableRows(request, old_lines, chunks, patch, 155 def RenderDiffTableRows(request, old_lines, chunks, patch,
146 colwidth=80, debug=False): 156 colwidth=80, debug=False):
147 """Render the HTML table rows for a side-by-side diff for a patch. 157 """Render the HTML table rows for a side-by-side diff for a patch.
148 158
149 Args: 159 Args:
150 request: Django Request object. 160 request: Django Request object.
151 old_lines: List of lines representing the original file. 161 old_lines: List of lines representing the original file.
152 chunks: List of chunks as returned by patching.ParsePatch(). 162 chunks: List of chunks as returned by patching.ParsePatch().
153 patch: A models.Patch instance. 163 patch: A models.Patch instance.
154 colwidth: Optional column width (default 80). 164 colwidth: Optional column width (default 80).
155 debug: Optional debugging flag (default False). 165 debug: Optional debugging flag (default False).
156 166
157 Yields: 167 Yields:
158 Strings, each of which represents the text rendering one complete 168 Strings, each of which represents the text rendering one complete
159 pair of lines of the side-by-side diff, possibly including comments. 169 pair of lines of the side-by-side diff, possibly including comments.
160 Each yielded string may consist of several <tr> elements. 170 Each yielded string may consist of several <tr> elements.
161 """ 171 """
162 buffer = [] 172 buffer = []
163 for tag, text in _RenderDiffTableRows(request, old_lines, chunks, patch, 173 for tag, text in _RenderDiffTableRows(request, old_lines, chunks, patch,
164 colwidth, debug): 174 colwidth, debug):
165 if tag == 'equal': 175 if tag == 'equal':
166 buffer.append(text) 176 buffer.append(text)
167 continue 177 continue
168 else: 178 else:
169 for t in _ShortenBuffer(buffer): 179 for t in _ShortenBuffer(buffer):
170 yield t 180 yield t
171 buffer = [] 181 buffer = []
172 yield text 182 yield text
173 if tag == 'error': 183 if tag == 'error':
174 yield None 184 yield None
175 break 185 break
(...skipping 402 matching lines...) Show 10 above Show 10 below
578 Returns: 588 Returns:
579 A tuple (old_len, new_len) representing len(old_lines) and 589 A tuple (old_len, new_len) representing len(old_lines) and
580 len(new_lines), where new_lines is the list representing the 590 len(new_lines), where new_lines is the list representing the
581 result of applying the patch chunks to old_lines, however, without 591 result of applying the patch chunks to old_lines, however, without
582 actually computing new_lines. 592 actually computing new_lines.
583 """ 593 """
584 old_len = len(old_lines) 594 old_len = len(old_lines)
585 new_len = old_len 595 new_len = old_len
586 if chunks: 596 if chunks:
587 (old_a, old_b), (new_a, new_b), old_lines, new_lines = chunks[-1] 597 (old_a, old_b), (new_a, new_b), old_lines, new_lines = chunks[-1]
588 new_len += new_b - old_b 598 new_len += new_b - old_b
589 return old_len, new_len 599 return old_len, new_len
590 600
591 601
592 def _MarkupNumber(ndigits, number, tag): 602 def _MarkupNumber(ndigits, number, tag):
593 """Format a number in HTML in a given width with extra markup. 603 """Format a number in HTML in a given width with extra markup.
594 604
595 Args: 605 Args:
596 ndigits: the total width available for formatting 606 ndigits: the total width available for formatting
597 number: the number to be formatted 607 number: the number to be formatted
598 tag: HTML tag name, e.g. 'u' 608 tag: HTML tag name, e.g. 'u'
599 609
600 Returns: 610 Returns:
601 An HTML string that displays as ndigits wide, with the 611 An HTML string that displays as ndigits wide, with the
602 number right-aligned and surrounded by an HTML tag; for example, 612 number right-aligned and surrounded by an HTML tag; for example,
603 _MarkupNumber(42, 4, 'u') returns ' <u>42</u>'. 613 _MarkupNumber(42, 4, 'u') returns ' <u>42</u>'.
604 """ 614 """
605 formatted_number = str(number) 615 formatted_number = str(number)
606 space_prefix = ' ' * (ndigits - len(formatted_number)) 616 space_prefix = ' ' * (ndigits - len(formatted_number))
607 return '%s<%s>%s</%s>' % (space_prefix, tag, formatted_number, tag) 617 return '%s<%s>%s</%s>' % (space_prefix, tag, formatted_number, tag)
608 618
609 619
610 def _ExpandTemplate(name, **params): 620 def _ExpandTemplate(name, **params):
611 """Wrapper around django.template.loader.render_to_string(). 621 """Wrapper around django.template.loader.render_to_string().
612 622
613 For convenience, this takes keyword arguments instead of a dict. 623 For convenience, this takes keyword arguments instead of a dict.
614 """ 624 """
615 return loader.render_to_string(name, params) 625 return loader.render_to_string(name, params)
616 626
617 627
618 def _ToText(lines): 628 def _ToText(lines):
619 """Helper to turn a list of lines into a db.Text instance. 629 """Helper to turn a list of lines into a db.Text instance.
620 630
621 Args: 631 Args:
622 lines: list of strings. 632 lines: list of strings.
623 633
624 Returns: 634 Returns:
625 A db.Text instance. 635 A db.Text instance.
626 """ 636 """
627 return db.Text(''.join(lines), encoding='utf-8') 637 return db.Text(''.join(lines), encoding='utf-8')
OLDNEW

Powered by Google App Engine
This is Rietveld r305