##// END OF EJS Templates
fix some small unicode issues with logging on differ
marcink -
r3366:c72dbcad beta
parent child Browse files
Show More
@@ -1,716 +1,717 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.diffs
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Set of diffing helpers, previously part of vcs
7 7
8 8
9 9 :created_on: Dec 4, 2011
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :original copyright: 2007-2008 by Armin Ronacher
13 13 :license: GPLv3, see COPYING for more details.
14 14 """
15 15 # This program is free software: you can redistribute it and/or modify
16 16 # it under the terms of the GNU General Public License as published by
17 17 # the Free Software Foundation, either version 3 of the License, or
18 18 # (at your option) any later version.
19 19 #
20 20 # This program is distributed in the hope that it will be useful,
21 21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 23 # GNU General Public License for more details.
24 24 #
25 25 # You should have received a copy of the GNU General Public License
26 26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 27
28 28 import re
29 29 import difflib
30 30 import logging
31 31
32 32 from itertools import tee, imap
33 33
34 34 from pylons.i18n.translation import _
35 35
36 36 from rhodecode.lib.vcs.exceptions import VCSError
37 37 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
38 38 from rhodecode.lib.vcs.backends.base import EmptyChangeset
39 39 from rhodecode.lib.helpers import escape
40 from rhodecode.lib.utils2 import safe_unicode
40 from rhodecode.lib.utils2 import safe_unicode, safe_str
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44
45 45 def wrap_to_table(str_):
46 46 return '''<table class="code-difftable">
47 47 <tr class="line no-comment">
48 48 <td class="lineno new"></td>
49 49 <td class="code no-comment"><pre>%s</pre></td>
50 50 </tr>
51 51 </table>''' % str_
52 52
53 53
54 54 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
55 55 ignore_whitespace=True, line_context=3,
56 56 enable_comments=False):
57 57 """
58 58 returns a wrapped diff into a table, checks for cut_off_limit and presents
59 59 proper message
60 60 """
61 61
62 62 if filenode_old is None:
63 63 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
64 64
65 65 if filenode_old.is_binary or filenode_new.is_binary:
66 66 diff = wrap_to_table(_('binary file'))
67 67 stats = (0, 0)
68 68 size = 0
69 69
70 70 elif cut_off_limit != -1 and (cut_off_limit is None or
71 71 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
72 72
73 73 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
74 74 ignore_whitespace=ignore_whitespace,
75 75 context=line_context)
76 76 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
77 77
78 78 diff = diff_processor.as_html(enable_comments=enable_comments)
79 79 stats = diff_processor.stat()
80 80 size = len(diff or '')
81 81 else:
82 82 diff = wrap_to_table(_('Changeset was too big and was cut off, use '
83 83 'diff menu to display this diff'))
84 84 stats = (0, 0)
85 85 size = 0
86 86 if not diff:
87 87 submodules = filter(lambda o: isinstance(o, SubModuleNode),
88 88 [filenode_new, filenode_old])
89 89 if submodules:
90 90 diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
91 91 else:
92 92 diff = wrap_to_table(_('No changes detected'))
93 93
94 94 cs1 = filenode_old.changeset.raw_id
95 95 cs2 = filenode_new.changeset.raw_id
96 96
97 97 return size, cs1, cs2, diff, stats
98 98
99 99
100 100 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
101 101 """
102 102 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
103 103
104 104 :param ignore_whitespace: ignore whitespaces in diff
105 105 """
106 106 # make sure we pass in default context
107 107 context = context or 3
108 108 submodules = filter(lambda o: isinstance(o, SubModuleNode),
109 109 [filenode_new, filenode_old])
110 110 if submodules:
111 111 return ''
112 112
113 113 for filenode in (filenode_old, filenode_new):
114 114 if not isinstance(filenode, FileNode):
115 115 raise VCSError("Given object should be FileNode object, not %s"
116 116 % filenode.__class__)
117 117
118 118 repo = filenode_new.changeset.repository
119 119 old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
120 120 new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
121 121
122 122 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
123 123 ignore_whitespace, context)
124 124 return vcs_gitdiff
125 125
126 126 NEW_FILENODE = 1
127 127 DEL_FILENODE = 2
128 128 MOD_FILENODE = 3
129 129 RENAMED_FILENODE = 4
130 130 CHMOD_FILENODE = 5
131 131
132 132
133 133 class DiffLimitExceeded(Exception):
134 134 pass
135 135
136 136
137 137 class LimitedDiffContainer(object):
138 138
139 139 def __init__(self, diff_limit, cur_diff_size, diff):
140 140 self.diff = diff
141 141 self.diff_limit = diff_limit
142 142 self.cur_diff_size = cur_diff_size
143 143
144 144 def __iter__(self):
145 145 for l in self.diff:
146 146 yield l
147 147
148 148
149 149 class DiffProcessor(object):
150 150 """
151 151 Give it a unified or git diff and it returns a list of the files that were
152 152 mentioned in the diff together with a dict of meta information that
153 153 can be used to render it in a HTML template.
154 154 """
155 155 _chunk_re = re.compile(r'^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
156 156 _newline_marker = re.compile(r'^\\ No newline at end of file')
157 157 _git_header_re = re.compile(r"""
158 158 #^diff[ ]--git
159 159 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
160 160 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%\n
161 161 ^rename[ ]from[ ](?P<rename_from>\S+)\n
162 162 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
163 163 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
164 164 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
165 165 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
166 166 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
167 167 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
168 168 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
169 169 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
170 170 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
171 171 """, re.VERBOSE | re.MULTILINE)
172 172 _hg_header_re = re.compile(r"""
173 173 #^diff[ ]--git
174 174 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
175 175 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%(?:\n|$))?
176 176 (?:^rename[ ]from[ ](?P<rename_from>\S+)\n
177 177 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
178 178 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
179 179 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
180 180 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
181 181 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
182 182 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
183 183 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
184 184 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
185 185 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
186 186 """, re.VERBOSE | re.MULTILINE)
187 187
188 188 #used for inline highlighter word split
189 189 _token_re = re.compile(r'()(&gt;|&lt;|&amp;|\W+?)')
190 190
191 191 def __init__(self, diff, vcs='hg', format='gitdiff', diff_limit=None):
192 192 """
193 193 :param diff: a text in diff format
194 194 :param vcs: type of version controll hg or git
195 195 :param format: format of diff passed, `udiff` or `gitdiff`
196 196 :param diff_limit: define the size of diff that is considered "big"
197 197 based on that parameter cut off will be triggered, set to None
198 198 to show full diff
199 199 """
200 200 if not isinstance(diff, basestring):
201 201 raise Exception('Diff must be a basestring got %s instead' % type(diff))
202 202
203 203 self._diff = diff
204 204 self._format = format
205 205 self.adds = 0
206 206 self.removes = 0
207 207 # calculate diff size
208 208 self.diff_size = len(diff)
209 209 self.diff_limit = diff_limit
210 210 self.cur_diff_size = 0
211 211 self.parsed = False
212 212 self.parsed_diff = []
213 213 self.vcs = vcs
214 214
215 215 if format == 'gitdiff':
216 216 self.differ = self._highlight_line_difflib
217 217 self._parser = self._parse_gitdiff
218 218 else:
219 219 self.differ = self._highlight_line_udiff
220 220 self._parser = self._parse_udiff
221 221
222 222 def _copy_iterator(self):
223 223 """
224 224 make a fresh copy of generator, we should not iterate thru
225 225 an original as it's needed for repeating operations on
226 226 this instance of DiffProcessor
227 227 """
228 228 self.__udiff, iterator_copy = tee(self.__udiff)
229 229 return iterator_copy
230 230
231 231 def _escaper(self, string):
232 232 """
233 233 Escaper for diff escapes special chars and checks the diff limit
234 234
235 235 :param string:
236 236 :type string:
237 237 """
238 238
239 239 self.cur_diff_size += len(string)
240 240
241 241 # escaper get's iterated on each .next() call and it checks if each
242 242 # parsed line doesn't exceed the diff limit
243 243 if self.diff_limit is not None and self.cur_diff_size > self.diff_limit:
244 244 raise DiffLimitExceeded('Diff Limit Exceeded')
245 245
246 246 return safe_unicode(string).replace('&', '&amp;')\
247 247 .replace('<', '&lt;')\
248 248 .replace('>', '&gt;')
249 249
250 250 def _line_counter(self, l):
251 251 """
252 252 Checks each line and bumps total adds/removes for this diff
253 253
254 254 :param l:
255 255 """
256 256 if l.startswith('+') and not l.startswith('+++'):
257 257 self.adds += 1
258 258 elif l.startswith('-') and not l.startswith('---'):
259 259 self.removes += 1
260 260 return safe_unicode(l)
261 261
262 262 def _highlight_line_difflib(self, line, next_):
263 263 """
264 264 Highlight inline changes in both lines.
265 265 """
266 266
267 267 if line['action'] == 'del':
268 268 old, new = line, next_
269 269 else:
270 270 old, new = next_, line
271 271
272 272 oldwords = self._token_re.split(old['line'])
273 273 newwords = self._token_re.split(new['line'])
274 274 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
275 275
276 276 oldfragments, newfragments = [], []
277 277 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
278 278 oldfrag = ''.join(oldwords[i1:i2])
279 279 newfrag = ''.join(newwords[j1:j2])
280 280 if tag != 'equal':
281 281 if oldfrag:
282 282 oldfrag = '<del>%s</del>' % oldfrag
283 283 if newfrag:
284 284 newfrag = '<ins>%s</ins>' % newfrag
285 285 oldfragments.append(oldfrag)
286 286 newfragments.append(newfrag)
287 287
288 288 old['line'] = "".join(oldfragments)
289 289 new['line'] = "".join(newfragments)
290 290
291 291 def _highlight_line_udiff(self, line, next_):
292 292 """
293 293 Highlight inline changes in both lines.
294 294 """
295 295 start = 0
296 296 limit = min(len(line['line']), len(next_['line']))
297 297 while start < limit and line['line'][start] == next_['line'][start]:
298 298 start += 1
299 299 end = -1
300 300 limit -= start
301 301 while -end <= limit and line['line'][end] == next_['line'][end]:
302 302 end -= 1
303 303 end += 1
304 304 if start or end:
305 305 def do(l):
306 306 last = end + len(l['line'])
307 307 if l['action'] == 'add':
308 308 tag = 'ins'
309 309 else:
310 310 tag = 'del'
311 311 l['line'] = '%s<%s>%s</%s>%s' % (
312 312 l['line'][:start],
313 313 tag,
314 314 l['line'][start:last],
315 315 tag,
316 316 l['line'][last:]
317 317 )
318 318 do(line)
319 319 do(next_)
320 320
321 321 def _get_header(self, diff_chunk):
322 322 """
323 323 parses the diff header, and returns parts, and leftover diff
324 324 parts consists of 14 elements::
325 325
326 326 a_path, b_path, similarity_index, rename_from, rename_to,
327 327 old_mode, new_mode, new_file_mode, deleted_file_mode,
328 328 a_blob_id, b_blob_id, b_mode, a_file, b_file
329 329
330 330 :param diff_chunk:
331 331 :type diff_chunk:
332 332 """
333 333
334 334 if self.vcs == 'git':
335 335 match = self._git_header_re.match(diff_chunk)
336 336 diff = diff_chunk[match.end():]
337 337 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
338 338 elif self.vcs == 'hg':
339 339 match = self._hg_header_re.match(diff_chunk)
340 340 diff = diff_chunk[match.end():]
341 341 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
342 342 else:
343 343 raise Exception('VCS type %s is not supported' % self.vcs)
344 344
345 345 def _clean_line(self, line, command):
346 346 if command in ['+', '-', ' ']:
347 347 #only modify the line if it's actually a diff thing
348 348 line = line[1:]
349 349 return line
350 350
351 351 def _parse_gitdiff(self, inline_diff=True):
352 352 _files = []
353 353 diff_container = lambda arg: arg
354 354
355 355 ##split the diff in chunks of separate --git a/file b/file chunks
356 356 for raw_diff in ('\n' + self._diff).split('\ndiff --git')[1:]:
357 357 binary = False
358 358 binary_msg = 'unknown binary'
359 359 head, diff = self._get_header(raw_diff)
360 360
361 361 if not head['a_file'] and head['b_file']:
362 362 op = 'A'
363 363 elif head['a_file'] and head['b_file']:
364 364 op = 'M'
365 365 elif head['a_file'] and not head['b_file']:
366 366 op = 'D'
367 367 else:
368 368 #probably we're dealing with a binary file 1
369 369 binary = True
370 370 if head['deleted_file_mode']:
371 371 op = 'D'
372 372 stats = ['b', DEL_FILENODE]
373 373 binary_msg = 'deleted binary file'
374 374 elif head['new_file_mode']:
375 375 op = 'A'
376 376 stats = ['b', NEW_FILENODE]
377 377 binary_msg = 'new binary file %s' % head['new_file_mode']
378 378 else:
379 379 if head['new_mode'] and head['old_mode']:
380 380 stats = ['b', CHMOD_FILENODE]
381 381 op = 'M'
382 382 binary_msg = ('modified binary file chmod %s => %s'
383 383 % (head['old_mode'], head['new_mode']))
384 384 elif (head['rename_from'] and head['rename_to']
385 385 and head['rename_from'] != head['rename_to']):
386 386 stats = ['b', RENAMED_FILENODE]
387 387 op = 'M'
388 388 binary_msg = ('file renamed from %s to %s'
389 389 % (head['rename_from'], head['rename_to']))
390 390 else:
391 391 stats = ['b', MOD_FILENODE]
392 392 op = 'M'
393 393 binary_msg = 'modified binary file'
394 394
395 395 if not binary:
396 396 try:
397 397 chunks, stats = self._parse_lines(diff)
398 398 except DiffLimitExceeded:
399 399 diff_container = lambda _diff: LimitedDiffContainer(
400 400 self.diff_limit,
401 401 self.cur_diff_size,
402 402 _diff)
403 403 break
404 404 else:
405 405 chunks = []
406 406 chunks.append([{
407 407 'old_lineno': '',
408 408 'new_lineno': '',
409 409 'action': 'binary',
410 410 'line': binary_msg,
411 411 }])
412 412
413 413 _files.append({
414 414 'filename': head['b_path'],
415 415 'old_revision': head['a_blob_id'],
416 416 'new_revision': head['b_blob_id'],
417 417 'chunks': chunks,
418 418 'operation': op,
419 419 'stats': stats,
420 420 })
421 421
422 422 sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
423 423
424 424 if inline_diff is False:
425 425 return diff_container(sorted(_files, key=sorter))
426 426
427 427 # highlight inline changes
428 428 for diff_data in _files:
429 429 for chunk in diff_data['chunks']:
430 430 lineiter = iter(chunk)
431 431 try:
432 432 while 1:
433 433 line = lineiter.next()
434 434 if line['action'] not in ['unmod', 'context']:
435 435 nextline = lineiter.next()
436 436 if nextline['action'] in ['unmod', 'context'] or \
437 437 nextline['action'] == line['action']:
438 438 continue
439 439 self.differ(line, nextline)
440 440 except StopIteration:
441 441 pass
442 442
443 443 return diff_container(sorted(_files, key=sorter))
444 444
445 445 def _parse_udiff(self, inline_diff=True):
446 446 raise NotImplementedError()
447 447
448 448 def _parse_lines(self, diff):
449 449 """
450 450 Parse the diff an return data for the template.
451 451 """
452 452
453 453 lineiter = iter(diff)
454 454 stats = [0, 0]
455 455
456 456 try:
457 457 chunks = []
458 458 line = lineiter.next()
459 459
460 460 while line:
461 461 lines = []
462 462 chunks.append(lines)
463 463
464 464 match = self._chunk_re.match(line)
465 465
466 466 if not match:
467 467 break
468 468
469 469 gr = match.groups()
470 470 (old_line, old_end,
471 471 new_line, new_end) = [int(x or 1) for x in gr[:-1]]
472 472 old_line -= 1
473 473 new_line -= 1
474 474
475 475 context = len(gr) == 5
476 476 old_end += old_line
477 477 new_end += new_line
478 478
479 479 if context:
480 480 # skip context only if it's first line
481 481 if int(gr[0]) > 1:
482 482 lines.append({
483 483 'old_lineno': '...',
484 484 'new_lineno': '...',
485 485 'action': 'context',
486 486 'line': line,
487 487 })
488 488
489 489 line = lineiter.next()
490 490
491 491 while old_line < old_end or new_line < new_end:
492 492 command = ' '
493 493 if line:
494 494 command = line[0]
495 495
496 496 affects_old = affects_new = False
497 497
498 498 # ignore those if we don't expect them
499 499 if command in '#@':
500 500 continue
501 501 elif command == '+':
502 502 affects_new = True
503 503 action = 'add'
504 504 stats[0] += 1
505 505 elif command == '-':
506 506 affects_old = True
507 507 action = 'del'
508 508 stats[1] += 1
509 509 else:
510 510 affects_old = affects_new = True
511 511 action = 'unmod'
512 512
513 513 if not self._newline_marker.match(line):
514 514 old_line += affects_old
515 515 new_line += affects_new
516 516 lines.append({
517 517 'old_lineno': affects_old and old_line or '',
518 518 'new_lineno': affects_new and new_line or '',
519 519 'action': action,
520 520 'line': self._clean_line(line, command)
521 521 })
522 522
523 523 line = lineiter.next()
524 524
525 525 if self._newline_marker.match(line):
526 526 # we need to append to lines, since this is not
527 527 # counted in the line specs of diff
528 528 lines.append({
529 529 'old_lineno': '...',
530 530 'new_lineno': '...',
531 531 'action': 'context',
532 532 'line': self._clean_line(line, command)
533 533 })
534 534
535 535 except StopIteration:
536 536 pass
537 537 return chunks, stats
538 538
539 539 def _safe_id(self, idstring):
540 540 """Make a string safe for including in an id attribute.
541 541
542 542 The HTML spec says that id attributes 'must begin with
543 543 a letter ([A-Za-z]) and may be followed by any number
544 544 of letters, digits ([0-9]), hyphens ("-"), underscores
545 545 ("_"), colons (":"), and periods (".")'. These regexps
546 546 are slightly over-zealous, in that they remove colons
547 547 and periods unnecessarily.
548 548
549 549 Whitespace is transformed into underscores, and then
550 550 anything which is not a hyphen or a character that
551 551 matches \w (alphanumerics and underscore) is removed.
552 552
553 553 """
554 554 # Transform all whitespace to underscore
555 555 idstring = re.sub(r'\s', "_", '%s' % idstring)
556 556 # Remove everything that is not a hyphen or a member of \w
557 557 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
558 558 return idstring
559 559
560 560 def prepare(self, inline_diff=True):
561 561 """
562 562 Prepare the passed udiff for HTML rendering. It'l return a list
563 563 of dicts with diff information
564 564 """
565 565 parsed = self._parser(inline_diff=inline_diff)
566 566 self.parsed = True
567 567 self.parsed_diff = parsed
568 568 return parsed
569 569
570 570 def as_raw(self, diff_lines=None):
571 571 """
572 572 Returns raw string diff
573 573 """
574 574 return self._diff
575 575 #return u''.join(imap(self._line_counter, self._diff.splitlines(1)))
576 576
577 577 def as_html(self, table_class='code-difftable', line_class='line',
578 578 old_lineno_class='lineno old', new_lineno_class='lineno new',
579 579 code_class='code', enable_comments=False, parsed_lines=None):
580 580 """
581 581 Return given diff as html table with customized css classes
582 582 """
583 583 def _link_to_if(condition, label, url):
584 584 """
585 585 Generates a link if condition is meet or just the label if not.
586 586 """
587 587
588 588 if condition:
589 589 return '''<a href="%(url)s">%(label)s</a>''' % {
590 590 'url': url,
591 591 'label': label
592 592 }
593 593 else:
594 594 return label
595 595 if not self.parsed:
596 596 self.prepare()
597 597
598 598 diff_lines = self.parsed_diff
599 599 if parsed_lines:
600 600 diff_lines = parsed_lines
601 601
602 602 _html_empty = True
603 603 _html = []
604 604 _html.append('''<table class="%(table_class)s">\n''' % {
605 605 'table_class': table_class
606 606 })
607 607
608 608 for diff in diff_lines:
609 609 for line in diff['chunks']:
610 610 _html_empty = False
611 611 for change in line:
612 612 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
613 613 'lc': line_class,
614 614 'action': change['action']
615 615 })
616 616 anchor_old_id = ''
617 617 anchor_new_id = ''
618 618 anchor_old = "%(filename)s_o%(oldline_no)s" % {
619 619 'filename': self._safe_id(diff['filename']),
620 620 'oldline_no': change['old_lineno']
621 621 }
622 622 anchor_new = "%(filename)s_n%(oldline_no)s" % {
623 623 'filename': self._safe_id(diff['filename']),
624 624 'oldline_no': change['new_lineno']
625 625 }
626 626 cond_old = (change['old_lineno'] != '...' and
627 627 change['old_lineno'])
628 628 cond_new = (change['new_lineno'] != '...' and
629 629 change['new_lineno'])
630 630 if cond_old:
631 631 anchor_old_id = 'id="%s"' % anchor_old
632 632 if cond_new:
633 633 anchor_new_id = 'id="%s"' % anchor_new
634 634 ###########################################################
635 635 # OLD LINE NUMBER
636 636 ###########################################################
637 637 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
638 638 'a_id': anchor_old_id,
639 639 'olc': old_lineno_class
640 640 })
641 641
642 642 _html.append('''%(link)s''' % {
643 643 'link': _link_to_if(True, change['old_lineno'],
644 644 '#%s' % anchor_old)
645 645 })
646 646 _html.append('''</td>\n''')
647 647 ###########################################################
648 648 # NEW LINE NUMBER
649 649 ###########################################################
650 650
651 651 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
652 652 'a_id': anchor_new_id,
653 653 'nlc': new_lineno_class
654 654 })
655 655
656 656 _html.append('''%(link)s''' % {
657 657 'link': _link_to_if(True, change['new_lineno'],
658 658 '#%s' % anchor_new)
659 659 })
660 660 _html.append('''</td>\n''')
661 661 ###########################################################
662 662 # CODE
663 663 ###########################################################
664 664 comments = '' if enable_comments else 'no-comment'
665 665 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
666 666 'cc': code_class,
667 667 'inc': comments
668 668 })
669 669 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
670 670 'code': change['line']
671 671 })
672 672
673 673 _html.append('''\t</td>''')
674 674 _html.append('''\n</tr>\n''')
675 675 _html.append('''</table>''')
676 676 if _html_empty:
677 677 return None
678 678 return ''.join(_html)
679 679
680 680 def stat(self):
681 681 """
682 682 Returns tuple of added, and removed lines for this instance
683 683 """
684 684 return self.adds, self.removes
685 685
686 686
687 687 def differ(org_repo, org_ref, other_repo, other_ref,
688 688 context=3, ignore_whitespace=False):
689 689 """
690 690 General differ between branches, bookmarks, revisions of two remote or
691 691 local but related repositories
692 692
693 693 :param org_repo:
694 694 :param org_ref:
695 695 :param other_repo:
696 696 :type other_repo:
697 697 :type other_ref:
698 698 """
699 699
700 700 org_repo_scm = org_repo.scm_instance
701 701 other_repo_scm = other_repo.scm_instance
702 702
703 703 org_repo = org_repo_scm._repo
704 704 other_repo = other_repo_scm._repo
705 705
706 org_ref = org_ref[1]
707 other_ref = other_ref[1]
706 org_ref = safe_str(org_ref[1])
707 other_ref = safe_str(other_ref[1])
708 708
709 709 if org_repo_scm == other_repo_scm:
710 710 log.debug('running diff between %s@%s and %s@%s'
711 % (org_repo.path, org_ref, other_repo.path, other_ref))
711 % (org_repo.path, org_ref,
712 other_repo.path, other_ref))
712 713 _diff = org_repo_scm.get_diff(rev1=org_ref, rev2=other_ref,
713 714 ignore_whitespace=ignore_whitespace, context=context)
714 715 return _diff
715 716
716 717 return '' # FIXME: when is it ever relevant to return nothing?
General Comments 0
You need to be logged in to leave comments. Login now