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