##// END OF EJS Templates
added basic comparision of two repositories using bundles...
marcink -
r2355:29a80968 codereview
parent child Browse files
Show More
@@ -1,148 +1,159 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.compare
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 compare controller for pylons showoing differences between two
7 7 repos, branches, bookmarks or tips
8 8
9 9 :created_on: May 6, 2012
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26 import logging
27 27 import traceback
28 28 import binascii
29 29
30 30 from webob.exc import HTTPNotFound
31 31 from pylons import request, response, session, tmpl_context as c, url
32 32 from pylons.controllers.util import abort, redirect
33 33
34 34 from rhodecode.lib import helpers as h
35 35 from rhodecode.lib.base import BaseRepoController, render
36 36 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
37 37 from rhodecode.lib import diffs
38 38
39 39 from rhodecode.model.db import Repository
40 40
41 41 log = logging.getLogger(__name__)
42 42
43 43
44 44 class CompareController(BaseRepoController):
45 45
46 46 @LoginRequired()
47 47 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
48 48 'repository.admin')
49 49 def __before__(self):
50 50 super(CompareController, self).__before__()
51 51
52 52 def _handle_ref(self, ref):
53 53 """
54 54 Parse the org...other string
55 55 Possible formats are
56 56 `(branch|book|tag):<name>...(branch|book|tag):<othername>`
57 57
58 58 :param ref: <orginal_reference>...<other_reference>
59 59 :type ref: str
60 60 """
61 61 org_repo = c.rhodecode_repo.name
62 62
63 63 def org_parser(org):
64 64 _repo = org_repo
65 65 name, val = org.split(':')
66 66 return _repo, (name, val)
67 67
68 68 def other_parser(other):
69 69 _other_repo = request.GET.get('repo')
70 70 _repo = org_repo
71 71 name, val = other.split(':')
72 72 if _other_repo:
73 73 #TODO: do an actual repo loookup within rhodecode
74 74 _repo = _other_repo
75 75
76 76 return _repo, (name, val)
77 77
78 78 if '...' in ref:
79 79 try:
80 80 org, other = ref.split('...')
81 81 org_repo, org_ref = org_parser(org)
82 82 other_repo, other_ref = other_parser(other)
83 83 return org_repo, org_ref, other_repo, other_ref
84 84 except:
85 85 log.error(traceback.format_exc())
86 86
87 87 raise HTTPNotFound
88 88
89 def _get_changesets(self, org_repo, org_ref, other_repo, other_ref):
90 changesets = []
91 #case two independent repos
92 if org_repo != other_repo:
89 def _get_discovery(self,org_repo, org_ref, other_repo, other_ref):
93 90 from mercurial import discovery
94 91 other = org_repo._repo
95 92 repo = other_repo._repo
96 onlyheads = None
97 tmp = discovery.findcommonincoming(repo=repo,
98 remote=other,
99 heads=onlyheads, force=False)
93 tmp = discovery.findcommonincoming(
94 repo=repo, # other_repo we check for incoming
95 remote=other, # org_repo source for incoming
96 heads=[other[org_ref[1]].node()],
97 force=False
98 )
99 return tmp
100
101 def _get_changesets(self, org_repo, org_ref, other_repo, other_ref, tmp):
102 changesets = []
103 #case two independent repos
104 if org_repo != other_repo:
100 105 common, incoming, rheads = tmp
106
101 107 if not incoming:
102 108 revs = []
103 109 else:
104 revs = other.changelog.findmissing(common, rheads)
110 revs = org_repo._repo.changelog.findmissing(common, rheads)
105 111
106 for cs in map(binascii.hexlify, revs):
112 for cs in reversed(map(binascii.hexlify, revs)):
107 113 changesets.append(org_repo.get_changeset(cs))
108 114 else:
109 115 revs = ['ancestors(%s) and not ancestors(%s)' % (org_ref[1],
110 116 other_ref[1])]
111 117 from mercurial import scmutil
112 118 out = scmutil.revrange(org_repo._repo, revs)
113 119 for cs in reversed(out):
114 120 changesets.append(org_repo.get_changeset(cs))
115 121
116 122 return changesets
117 123
118 124 def index(self, ref):
119 125 org_repo, org_ref, other_repo, other_ref = self._handle_ref(ref)
120 c.swap_url = h.url('compare_home', repo_name=c.repo_name,
126 c.swap_url = h.url('compare_home', repo_name=other_repo,
121 127 ref='%s...%s' % (':'.join(other_ref),
122 ':'.join(org_ref)))
128 ':'.join(org_ref)),
129 repo=org_repo)
123 130 c.org_repo = org_repo = Repository.get_by_repo_name(org_repo)
124 131 c.other_repo = other_repo = Repository.get_by_repo_name(other_repo)
125
132 tmp = self._get_discovery(org_repo.scm_instance,
133 org_ref,
134 other_repo.scm_instance,
135 other_ref)
126 136 c.cs_ranges = self._get_changesets(org_repo.scm_instance,
127 137 org_ref,
128 138 other_repo.scm_instance,
129 other_ref)
139 other_ref,
140 tmp)
130 141
131 142 c.org_ref = org_ref[1]
132 143 c.other_ref = other_ref[1]
133 144 # diff needs to have swapped org with other to generate proper diff
134 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref)
145 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref, tmp)
135 146 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
136 147 _parsed = diff_processor.prepare()
137 148
138 149 c.files = []
139 150 c.changes = {}
140 151 # sort Added first then Modified last Deleted files
141 152 sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
142 153 for f in sorted(_parsed, key=sorter):
143 154 fid = h.FID('', f['filename'])
144 155 c.files.append([fid, f['operation'], f['filename'], f['stats']])
145 156 diff = diff_processor.as_html(enable_comments=False, diff_lines=[f])
146 157 c.changes[fid] = [f['operation'], f['filename'], diff]
147 158
148 159 return render('compare/compare_diff.html')
@@ -1,565 +1,609 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 import io
29 30 import difflib
30 31 import markupsafe
32
31 33 from itertools import tee, imap
32 34
35 from mercurial import patch
36 from mercurial.mdiff import diffopts
37 from mercurial.bundlerepo import bundlerepository
38 from mercurial import localrepo
39
33 40 from pylons.i18n.translation import _
34 41
35 42 from rhodecode.lib.vcs.exceptions import VCSError
36 43 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
37 44 from rhodecode.lib.helpers import escape
38 from rhodecode.lib.utils import EmptyChangeset
45 from rhodecode.lib.utils import EmptyChangeset, make_ui
39 46
40 47
41 48 def wrap_to_table(str_):
42 49 return '''<table class="code-difftable">
43 50 <tr class="line no-comment">
44 51 <td class="lineno new"></td>
45 52 <td class="code no-comment"><pre>%s</pre></td>
46 53 </tr>
47 54 </table>''' % str_
48 55
49 56
50 57 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
51 58 ignore_whitespace=True, line_context=3,
52 59 enable_comments=False):
53 60 """
54 61 returns a wrapped diff into a table, checks for cut_off_limit and presents
55 62 proper message
56 63 """
57 64
58 65 if filenode_old is None:
59 66 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
60 67
61 68 if filenode_old.is_binary or filenode_new.is_binary:
62 69 diff = wrap_to_table(_('binary file'))
63 70 stats = (0, 0)
64 71 size = 0
65 72
66 73 elif cut_off_limit != -1 and (cut_off_limit is None or
67 74 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
68 75
69 76 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
70 77 ignore_whitespace=ignore_whitespace,
71 78 context=line_context)
72 79 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
73 80
74 81 diff = diff_processor.as_html(enable_comments=enable_comments)
75 82 stats = diff_processor.stat()
76 83 size = len(diff or '')
77 84 else:
78 85 diff = wrap_to_table(_('Changeset was too big and was cut off, use '
79 86 'diff menu to display this diff'))
80 87 stats = (0, 0)
81 88 size = 0
82 89 if not diff:
83 90 submodules = filter(lambda o: isinstance(o, SubModuleNode),
84 91 [filenode_new, filenode_old])
85 92 if submodules:
86 93 diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
87 94 else:
88 95 diff = wrap_to_table(_('No changes detected'))
89 96
90 97 cs1 = filenode_old.changeset.raw_id
91 98 cs2 = filenode_new.changeset.raw_id
92 99
93 100 return size, cs1, cs2, diff, stats
94 101
95 102
96 103 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
97 104 """
98 105 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
99 106
100 107 :param ignore_whitespace: ignore whitespaces in diff
101 108 """
102 109 # make sure we pass in default context
103 110 context = context or 3
104 111 submodules = filter(lambda o: isinstance(o, SubModuleNode),
105 112 [filenode_new, filenode_old])
106 113 if submodules:
107 114 return ''
108 115
109 116 for filenode in (filenode_old, filenode_new):
110 117 if not isinstance(filenode, FileNode):
111 118 raise VCSError("Given object should be FileNode object, not %s"
112 119 % filenode.__class__)
113 120
114 121 repo = filenode_new.changeset.repository
115 122 old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
116 123 new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
117 124
118 125 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
119 126 ignore_whitespace, context)
120 127 return vcs_gitdiff
121 128
122 129
123 130 class DiffProcessor(object):
124 131 """
125 132 Give it a unified diff and it returns a list of the files that were
126 133 mentioned in the diff together with a dict of meta information that
127 134 can be used to render it in a HTML template.
128 135 """
129 136 _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
130 137
131 138 def __init__(self, diff, differ='diff', format='udiff'):
132 139 """
133 140 :param diff: a text in diff format or generator
134 141 :param format: format of diff passed, `udiff` or `gitdiff`
135 142 """
136 143 if isinstance(diff, basestring):
137 144 diff = [diff]
138 145
139 146 self.__udiff = diff
140 147 self.__format = format
141 148 self.adds = 0
142 149 self.removes = 0
143 150
144 151 if isinstance(self.__udiff, basestring):
145 152 self.lines = iter(self.__udiff.splitlines(1))
146 153
147 154 elif self.__format == 'gitdiff':
148 155 udiff_copy = self.copy_iterator()
149 156 self.lines = imap(self.escaper, self._parse_gitdiff(udiff_copy))
150 157 else:
151 158 udiff_copy = self.copy_iterator()
152 159 self.lines = imap(self.escaper, udiff_copy)
153 160
154 161 # Select a differ.
155 162 if differ == 'difflib':
156 163 self.differ = self._highlight_line_difflib
157 164 else:
158 165 self.differ = self._highlight_line_udiff
159 166
160 167 def escaper(self, string):
161 168 return markupsafe.escape(string)
162 169
163 170 def copy_iterator(self):
164 171 """
165 172 make a fresh copy of generator, we should not iterate thru
166 173 an original as it's needed for repeating operations on
167 174 this instance of DiffProcessor
168 175 """
169 176 self.__udiff, iterator_copy = tee(self.__udiff)
170 177 return iterator_copy
171 178
172 179 def _extract_rev(self, line1, line2):
173 180 """
174 181 Extract the operation (A/M/D), filename and revision hint from a line.
175 182 """
176 183
177 184 try:
178 185 if line1.startswith('--- ') and line2.startswith('+++ '):
179 186 l1 = line1[4:].split(None, 1)
180 187 old_filename = (l1[0].replace('a/', '', 1)
181 188 if len(l1) >= 1 else None)
182 189 old_rev = l1[1] if len(l1) == 2 else 'old'
183 190
184 191 l2 = line2[4:].split(None, 1)
185 192 new_filename = (l2[0].replace('b/', '', 1)
186 193 if len(l1) >= 1 else None)
187 194 new_rev = l2[1] if len(l2) == 2 else 'new'
188 195
189 196 filename = (old_filename
190 197 if old_filename != '/dev/null' else new_filename)
191 198
192 199 operation = 'D' if new_filename == '/dev/null' else None
193 200 if not operation:
194 201 operation = 'M' if old_filename != '/dev/null' else 'A'
195 202
196 203 return operation, filename, new_rev, old_rev
197 204 except (ValueError, IndexError):
198 205 pass
199 206
200 207 return None, None, None, None
201 208
202 209 def _parse_gitdiff(self, diffiterator):
203 210 def line_decoder(l):
204 211 if l.startswith('+') and not l.startswith('+++'):
205 212 self.adds += 1
206 213 elif l.startswith('-') and not l.startswith('---'):
207 214 self.removes += 1
208 215 return l.decode('utf8', 'replace')
209 216
210 217 output = list(diffiterator)
211 218 size = len(output)
212 219
213 220 if size == 2:
214 221 l = []
215 222 l.extend([output[0]])
216 223 l.extend(output[1].splitlines(1))
217 224 return map(line_decoder, l)
218 225 elif size == 1:
219 226 return map(line_decoder, output[0].splitlines(1))
220 227 elif size == 0:
221 228 return []
222 229
223 230 raise Exception('wrong size of diff %s' % size)
224 231
225 232 def _highlight_line_difflib(self, line, next_):
226 233 """
227 234 Highlight inline changes in both lines.
228 235 """
229 236
230 237 if line['action'] == 'del':
231 238 old, new = line, next_
232 239 else:
233 240 old, new = next_, line
234 241
235 242 oldwords = re.split(r'(\W)', old['line'])
236 243 newwords = re.split(r'(\W)', new['line'])
237 244
238 245 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
239 246
240 247 oldfragments, newfragments = [], []
241 248 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
242 249 oldfrag = ''.join(oldwords[i1:i2])
243 250 newfrag = ''.join(newwords[j1:j2])
244 251 if tag != 'equal':
245 252 if oldfrag:
246 253 oldfrag = '<del>%s</del>' % oldfrag
247 254 if newfrag:
248 255 newfrag = '<ins>%s</ins>' % newfrag
249 256 oldfragments.append(oldfrag)
250 257 newfragments.append(newfrag)
251 258
252 259 old['line'] = "".join(oldfragments)
253 260 new['line'] = "".join(newfragments)
254 261
255 262 def _highlight_line_udiff(self, line, next_):
256 263 """
257 264 Highlight inline changes in both lines.
258 265 """
259 266 start = 0
260 267 limit = min(len(line['line']), len(next_['line']))
261 268 while start < limit and line['line'][start] == next_['line'][start]:
262 269 start += 1
263 270 end = -1
264 271 limit -= start
265 272 while -end <= limit and line['line'][end] == next_['line'][end]:
266 273 end -= 1
267 274 end += 1
268 275 if start or end:
269 276 def do(l):
270 277 last = end + len(l['line'])
271 278 if l['action'] == 'add':
272 279 tag = 'ins'
273 280 else:
274 281 tag = 'del'
275 282 l['line'] = '%s<%s>%s</%s>%s' % (
276 283 l['line'][:start],
277 284 tag,
278 285 l['line'][start:last],
279 286 tag,
280 287 l['line'][last:]
281 288 )
282 289
283 290 do(line)
284 291 do(next_)
285 292
286 293 def _parse_udiff(self):
287 294 """
288 295 Parse the diff an return data for the template.
289 296 """
290 297 lineiter = self.lines
291 298 files = []
292 299 try:
293 300 line = lineiter.next()
294 301 # skip first context
295 302 skipfirst = True
296 303
297 304 while 1:
298 305 # continue until we found the old file
299 306 if not line.startswith('--- '):
300 307 line = lineiter.next()
301 308 continue
302 309
303 310 chunks = []
304 311 stats = [0, 0]
305 312 operation, filename, old_rev, new_rev = \
306 313 self._extract_rev(line, lineiter.next())
307 314 files.append({
308 315 'filename': filename,
309 316 'old_revision': old_rev,
310 317 'new_revision': new_rev,
311 318 'chunks': chunks,
312 319 'operation': operation,
313 320 'stats': stats,
314 321 })
315 322
316 323 line = lineiter.next()
317 324 while line:
318 325
319 326 match = self._chunk_re.match(line)
320 327 if not match:
321 328 break
322 329
323 330 lines = []
324 331 chunks.append(lines)
325 332
326 333 old_line, old_end, new_line, new_end = \
327 334 [int(x or 1) for x in match.groups()[:-1]]
328 335 old_line -= 1
329 336 new_line -= 1
330 337 context = len(match.groups()) == 5
331 338 old_end += old_line
332 339 new_end += new_line
333 340
334 341 if context:
335 342 if not skipfirst:
336 343 lines.append({
337 344 'old_lineno': '...',
338 345 'new_lineno': '...',
339 346 'action': 'context',
340 347 'line': line,
341 348 })
342 349 else:
343 350 skipfirst = False
344 351
345 352 line = lineiter.next()
346 353 while old_line < old_end or new_line < new_end:
347 354 if line:
348 355 command, line = line[0], line[1:]
349 356 else:
350 357 command = ' '
351 358 affects_old = affects_new = False
352 359
353 360 # ignore those if we don't expect them
354 361 if command in '#@':
355 362 continue
356 363 elif command == '+':
357 364 affects_new = True
358 365 action = 'add'
359 366 stats[0] += 1
360 367 elif command == '-':
361 368 affects_old = True
362 369 action = 'del'
363 370 stats[1] += 1
364 371 else:
365 372 affects_old = affects_new = True
366 373 action = 'unmod'
367 374
368 375 old_line += affects_old
369 376 new_line += affects_new
370 377 lines.append({
371 378 'old_lineno': affects_old and old_line or '',
372 379 'new_lineno': affects_new and new_line or '',
373 380 'action': action,
374 381 'line': line
375 382 })
376 383 line = lineiter.next()
377 384 except StopIteration:
378 385 pass
379 386
380 387 # highlight inline changes
381 388 for diff_data in files:
382 389 for chunk in diff_data['chunks']:
383 390 lineiter = iter(chunk)
384 391 try:
385 392 while 1:
386 393 line = lineiter.next()
387 394 if line['action'] != 'unmod':
388 395 nextline = lineiter.next()
389 396 if nextline['action'] == 'unmod' or \
390 397 nextline['action'] == line['action']:
391 398 continue
392 399 self.differ(line, nextline)
393 400 except StopIteration:
394 401 pass
395 402 return files
396 403
397 404 def prepare(self):
398 405 """
399 406 Prepare the passed udiff for HTML rendering. It'l return a list
400 407 of dicts
401 408 """
402 409 return self._parse_udiff()
403 410
404 411 def _safe_id(self, idstring):
405 412 """Make a string safe for including in an id attribute.
406 413
407 414 The HTML spec says that id attributes 'must begin with
408 415 a letter ([A-Za-z]) and may be followed by any number
409 416 of letters, digits ([0-9]), hyphens ("-"), underscores
410 417 ("_"), colons (":"), and periods (".")'. These regexps
411 418 are slightly over-zealous, in that they remove colons
412 419 and periods unnecessarily.
413 420
414 421 Whitespace is transformed into underscores, and then
415 422 anything which is not a hyphen or a character that
416 423 matches \w (alphanumerics and underscore) is removed.
417 424
418 425 """
419 426 # Transform all whitespace to underscore
420 427 idstring = re.sub(r'\s', "_", '%s' % idstring)
421 428 # Remove everything that is not a hyphen or a member of \w
422 429 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
423 430 return idstring
424 431
425 432 def raw_diff(self):
426 433 """
427 434 Returns raw string as udiff
428 435 """
429 436 udiff_copy = self.copy_iterator()
430 437 if self.__format == 'gitdiff':
431 438 udiff_copy = self._parse_gitdiff(udiff_copy)
432 439 return u''.join(udiff_copy)
433 440
434 441 def as_html(self, table_class='code-difftable', line_class='line',
435 442 new_lineno_class='lineno old', old_lineno_class='lineno new',
436 443 code_class='code', enable_comments=False, diff_lines=None):
437 444 """
438 445 Return given diff as html table with customized css classes
439 446 """
440 447 def _link_to_if(condition, label, url):
441 448 """
442 449 Generates a link if condition is meet or just the label if not.
443 450 """
444 451
445 452 if condition:
446 453 return '''<a href="%(url)s">%(label)s</a>''' % {
447 454 'url': url,
448 455 'label': label
449 456 }
450 457 else:
451 458 return label
452 459 if diff_lines is None:
453 460 diff_lines = self.prepare()
454 461 _html_empty = True
455 462 _html = []
456 463 _html.append('''<table class="%(table_class)s">\n''' % {
457 464 'table_class': table_class
458 465 })
459 466 for diff in diff_lines:
460 467 for line in diff['chunks']:
461 468 _html_empty = False
462 469 for change in line:
463 470 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
464 471 'lc': line_class,
465 472 'action': change['action']
466 473 })
467 474 anchor_old_id = ''
468 475 anchor_new_id = ''
469 476 anchor_old = "%(filename)s_o%(oldline_no)s" % {
470 477 'filename': self._safe_id(diff['filename']),
471 478 'oldline_no': change['old_lineno']
472 479 }
473 480 anchor_new = "%(filename)s_n%(oldline_no)s" % {
474 481 'filename': self._safe_id(diff['filename']),
475 482 'oldline_no': change['new_lineno']
476 483 }
477 484 cond_old = (change['old_lineno'] != '...' and
478 485 change['old_lineno'])
479 486 cond_new = (change['new_lineno'] != '...' and
480 487 change['new_lineno'])
481 488 if cond_old:
482 489 anchor_old_id = 'id="%s"' % anchor_old
483 490 if cond_new:
484 491 anchor_new_id = 'id="%s"' % anchor_new
485 492 ###########################################################
486 493 # OLD LINE NUMBER
487 494 ###########################################################
488 495 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
489 496 'a_id': anchor_old_id,
490 497 'olc': old_lineno_class
491 498 })
492 499
493 500 _html.append('''%(link)s''' % {
494 501 'link': _link_to_if(True, change['old_lineno'],
495 502 '#%s' % anchor_old)
496 503 })
497 504 _html.append('''</td>\n''')
498 505 ###########################################################
499 506 # NEW LINE NUMBER
500 507 ###########################################################
501 508
502 509 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
503 510 'a_id': anchor_new_id,
504 511 'nlc': new_lineno_class
505 512 })
506 513
507 514 _html.append('''%(link)s''' % {
508 515 'link': _link_to_if(True, change['new_lineno'],
509 516 '#%s' % anchor_new)
510 517 })
511 518 _html.append('''</td>\n''')
512 519 ###########################################################
513 520 # CODE
514 521 ###########################################################
515 522 comments = '' if enable_comments else 'no-comment'
516 523 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
517 524 'cc': code_class,
518 525 'inc': comments
519 526 })
520 527 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
521 528 'code': change['line']
522 529 })
523 530 _html.append('''\t</td>''')
524 531 _html.append('''\n</tr>\n''')
525 532 _html.append('''</table>''')
526 533 if _html_empty:
527 534 return None
528 535 return ''.join(_html)
529 536
530 537 def stat(self):
531 538 """
532 539 Returns tuple of added, and removed lines for this instance
533 540 """
534 541 return self.adds, self.removes
535 542
536 543
537 def differ(org_repo, org_ref, other_repo, other_ref):
544 def differ(org_repo, org_ref, other_repo, other_ref, discovery_data=None):
538 545 """
539 546 General differ between branches, bookmarks or separate but releated
540 547 repositories
541 548
542 549 :param org_repo:
543 550 :type org_repo:
544 551 :param org_ref:
545 552 :type org_ref:
546 553 :param other_repo:
547 554 :type other_repo:
548 555 :param other_ref:
549 556 :type other_ref:
550 557 """
558
551 559 ignore_whitespace = False
552 560 context = 3
553 from mercurial import patch
554 from mercurial.mdiff import diffopts
555
556 561 org_repo = org_repo.scm_instance._repo
557 562 other_repo = other_repo.scm_instance._repo
558
563 opts = diffopts(git=True, ignorews=ignore_whitespace, context=context)
559 564 org_ref = org_ref[1]
560 565 other_ref = other_ref[1]
561 566
562 opts = diffopts(git=True, ignorews=ignore_whitespace, context=context)
567 if org_repo != other_repo:
568
569 common, incoming, rheads = discovery_data
570 # create a bundle (uncompressed if other repo is not local)
571 if other_repo.capable('getbundle'):
572 # disable repo hooks here since it's just bundle !
573 # patch and reset hooks section of UI config to not run any
574 # hooks on fetching archives with subrepos
575 for k, _ in other_repo.ui.configitems('hooks'):
576 other_repo.ui.setconfig('hooks', k, None)
577
578 unbundle = other_repo.getbundle('incoming', common=common,
579 heads=rheads)
580
581 buf = io.BytesIO()
582 while True:
583 chunk = unbundle._stream.read(1024*4)
584 if not chunk:
585 break
586 buf.write(chunk)
563 587
588 buf.seek(0)
589 unbundle._stream = buf
590
591 class InMemoryBundleRepo(bundlerepository):
592 def __init__(self, ui, path, bundlestream):
593 self._tempparent = None
594 localrepo.localrepository.__init__(self, ui, path)
595 self.ui.setconfig('phases', 'publish', False)
596
597 self.bundle = bundlestream
598
599 # dict with the mapping 'filename' -> position in the bundle
600 self.bundlefilespos = {}
601
602 ui = make_ui('db')
603 bundlerepo = InMemoryBundleRepo(ui, path=other_repo.root,
604 bundlestream=unbundle)
605 return ''.join(patch.diff(bundlerepo, node1=org_ref, node2=other_ref,
606 opts=opts))
607 else:
564 608 return ''.join(patch.diff(org_repo, node1=org_ref, node2=other_ref,
565 609 opts=opts))
General Comments 0
You need to be logged in to leave comments. Login now