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