##// END OF EJS Templates
compare and diff: remove unused "bundle" functionality...
Mads Kiilerich -
r3304:70309536 beta
parent child Browse files
Show More
@@ -1,183 +1,180 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.compare
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 compare controller for pylons showoing differences between two
6 compare controller for pylons showing 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
29 29 from webob.exc import HTTPNotFound
30 30 from pylons import request, response, session, tmpl_context as c, url
31 31 from pylons.controllers.util import abort, redirect
32 32 from pylons.i18n.translation import _
33 33
34 34 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
35 35 from rhodecode.lib import helpers as h
36 36 from rhodecode.lib.base import BaseRepoController, render
37 37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
38 38 from rhodecode.lib import diffs
39 39
40 40 from rhodecode.model.db import Repository
41 41 from rhodecode.model.pull_request import PullRequestModel
42 42 from webob.exc import HTTPBadRequest
43 43 from rhodecode.lib.utils2 import str2bool
44 44 from rhodecode.lib.diffs import LimitedDiffContainer
45 45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class CompareController(BaseRepoController):
51 51
52 52 @LoginRequired()
53 53 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
54 54 'repository.admin')
55 55 def __before__(self):
56 56 super(CompareController, self).__before__()
57 57
58 58 def __get_cs_or_redirect(self, rev, repo, redirect_after=True,
59 59 partial=False):
60 60 """
61 61 Safe way to get changeset if error occur it redirects to changeset with
62 62 proper message. If partial is set then don't do redirect raise Exception
63 63 instead
64 64
65 65 :param rev: revision to fetch
66 66 :param repo: repo instance
67 67 """
68 68
69 69 try:
70 70 type_, rev = rev
71 71 return repo.scm_instance.get_changeset(rev)
72 72 except EmptyRepositoryError, e:
73 73 if not redirect_after:
74 74 return None
75 75 h.flash(h.literal(_('There are no changesets yet')),
76 76 category='warning')
77 77 redirect(url('summary_home', repo_name=repo.repo_name))
78 78
79 79 except RepositoryError, e:
80 80 log.error(traceback.format_exc())
81 81 h.flash(str(e), category='warning')
82 82 if not partial:
83 83 redirect(h.url('summary_home', repo_name=repo.repo_name))
84 84 raise HTTPBadRequest()
85 85
86 86 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
87 87
88 88 org_repo = c.rhodecode_db_repo.repo_name
89 89 org_ref = (org_ref_type, org_ref)
90 90 other_ref = (other_ref_type, other_ref)
91 91 other_repo = request.GET.get('repo', org_repo)
92 incoming_changesets = str2bool(request.GET.get('bundle', False))
93 92 c.fulldiff = fulldiff = request.GET.get('fulldiff')
94 93 rev_start = request.GET.get('rev_start')
95 94 rev_end = request.GET.get('rev_end')
96 95
97 96 c.swap_url = h.url('compare_url', repo_name=other_repo,
98 97 org_ref_type=other_ref[0], org_ref=other_ref[1],
99 98 other_ref_type=org_ref[0], other_ref=org_ref[1],
100 repo=org_repo, as_form=request.GET.get('as_form'),
101 bundle=incoming_changesets)
99 repo=org_repo, as_form=request.GET.get('as_form'))
102 100
103 101 c.org_repo = org_repo = Repository.get_by_repo_name(org_repo)
104 102 c.other_repo = other_repo = Repository.get_by_repo_name(other_repo)
105 103
106 104 if c.org_repo is None:
107 105 log.error('Could not find org repo %s' % org_repo)
108 106 raise HTTPNotFound
109 107 if c.other_repo is None:
110 108 log.error('Could not find other repo %s' % other_repo)
111 109 raise HTTPNotFound
112 110
113 111 if c.org_repo != c.other_repo and h.is_git(c.rhodecode_repo):
114 112 log.error('compare of two remote repos not available for GIT REPOS')
115 113 raise HTTPNotFound
116 114
117 115 if c.org_repo.scm_instance.alias != c.other_repo.scm_instance.alias:
118 116 log.error('compare of two different kind of remote repos not available')
119 117 raise HTTPNotFound
120 118
121 119 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
122 120 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
123 121 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
124 122
125 123 if rev_start and rev_end:
126 124 #replace our org_ref with given CS
127 125 org_ref = ('rev', rev_start)
128 126 other_ref = ('rev', rev_end)
129 127
130 128 c.cs_ranges = PullRequestModel().get_compare_data(
131 129 org_repo, org_ref, other_repo, other_ref,
132 130 )
133 131
134 132 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
135 133 c.cs_ranges])
136 134 c.target_repo = c.repo_name
137 135 # defines that we need hidden inputs with changesets
138 136 c.as_form = request.GET.get('as_form', False)
139 137 if partial:
140 138 return render('compare/compare_cs.html')
141 139
142 140 c.org_ref = org_ref[1]
143 141 c.other_ref = other_ref[1]
144 142
145 if not incoming_changesets and c.cs_ranges and c.org_repo != c.other_repo:
143 if c.cs_ranges and c.org_repo != c.other_repo:
146 144 # case we want a simple diff without incoming changesets, just
147 145 # for review purposes. Make the diff on the forked repo, with
148 146 # revision that is common ancestor
149 147 _org_ref = org_ref
150 148 org_ref = ('rev', getattr(c.cs_ranges[0].parents[0]
151 149 if c.cs_ranges[0].parents
152 150 else EmptyChangeset(), 'raw_id'))
153 151 log.debug('Changed org_ref from %s to %s' % (_org_ref, org_ref))
154 152 other_repo = org_repo
155 153
156 154 diff_limit = self.cut_off_limit if not fulldiff else None
157 155
158 _diff = diffs.differ(org_repo, org_ref, other_repo, other_ref,
159 remote_compare=incoming_changesets)
156 _diff = diffs.differ(org_repo, org_ref, other_repo, other_ref)
160 157
161 158 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
162 159 diff_limit=diff_limit)
163 160 _parsed = diff_processor.prepare()
164 161
165 162 c.limited_diff = False
166 163 if isinstance(_parsed, LimitedDiffContainer):
167 164 c.limited_diff = True
168 165
169 166 c.files = []
170 167 c.changes = {}
171 168 c.lines_added = 0
172 169 c.lines_deleted = 0
173 170 for f in _parsed:
174 171 st = f['stats']
175 172 if st[0] != 'b':
176 173 c.lines_added += st[0]
177 174 c.lines_deleted += st[1]
178 175 fid = h.FID('', f['filename'])
179 176 c.files.append([fid, f['operation'], f['filename'], f['stats']])
180 177 diff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
181 178 c.changes[fid] = [f['operation'], f['filename'], diff]
182 179
183 180 return render('compare/compare_diff.html')
@@ -1,769 +1,716 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.diffs
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Set of diffing helpers, previously part of vcs
7 7
8 8
9 9 :created_on: Dec 4, 2011
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :original copyright: 2007-2008 by Armin Ronacher
13 13 :license: GPLv3, see COPYING for more details.
14 14 """
15 15 # This program is free software: you can redistribute it and/or modify
16 16 # it under the terms of the GNU General Public License as published by
17 17 # the Free Software Foundation, either version 3 of the License, or
18 18 # (at your option) any later version.
19 19 #
20 20 # This program is distributed in the hope that it will be useful,
21 21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 23 # GNU General Public License for more details.
24 24 #
25 25 # You should have received a copy of the GNU General Public License
26 26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 27
28 28 import re
29 29 import difflib
30 30 import logging
31 import traceback
32 31
33 32 from itertools import tee, imap
34 33
35 from mercurial import patch
36 from mercurial.mdiff import diffopts
37 from mercurial.bundlerepo import bundlerepository
38
39 34 from pylons.i18n.translation import _
40 35
41 from rhodecode.lib.compat import BytesIO
42 from rhodecode.lib.vcs.utils.hgcompat import localrepo
43 36 from rhodecode.lib.vcs.exceptions import VCSError
44 37 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
45 38 from rhodecode.lib.vcs.backends.base import EmptyChangeset
46 39 from rhodecode.lib.helpers import escape
47 from rhodecode.lib.utils import make_ui
48 40 from rhodecode.lib.utils2 import safe_unicode
49 41
50 42 log = logging.getLogger(__name__)
51 43
52 44
53 45 def wrap_to_table(str_):
54 46 return '''<table class="code-difftable">
55 47 <tr class="line no-comment">
56 48 <td class="lineno new"></td>
57 49 <td class="code no-comment"><pre>%s</pre></td>
58 50 </tr>
59 51 </table>''' % str_
60 52
61 53
62 54 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
63 55 ignore_whitespace=True, line_context=3,
64 56 enable_comments=False):
65 57 """
66 58 returns a wrapped diff into a table, checks for cut_off_limit and presents
67 59 proper message
68 60 """
69 61
70 62 if filenode_old is None:
71 63 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
72 64
73 65 if filenode_old.is_binary or filenode_new.is_binary:
74 66 diff = wrap_to_table(_('binary file'))
75 67 stats = (0, 0)
76 68 size = 0
77 69
78 70 elif cut_off_limit != -1 and (cut_off_limit is None or
79 71 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
80 72
81 73 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
82 74 ignore_whitespace=ignore_whitespace,
83 75 context=line_context)
84 76 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
85 77
86 78 diff = diff_processor.as_html(enable_comments=enable_comments)
87 79 stats = diff_processor.stat()
88 80 size = len(diff or '')
89 81 else:
90 82 diff = wrap_to_table(_('Changeset was too big and was cut off, use '
91 83 'diff menu to display this diff'))
92 84 stats = (0, 0)
93 85 size = 0
94 86 if not diff:
95 87 submodules = filter(lambda o: isinstance(o, SubModuleNode),
96 88 [filenode_new, filenode_old])
97 89 if submodules:
98 90 diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
99 91 else:
100 92 diff = wrap_to_table(_('No changes detected'))
101 93
102 94 cs1 = filenode_old.changeset.raw_id
103 95 cs2 = filenode_new.changeset.raw_id
104 96
105 97 return size, cs1, cs2, diff, stats
106 98
107 99
108 100 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
109 101 """
110 102 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
111 103
112 104 :param ignore_whitespace: ignore whitespaces in diff
113 105 """
114 106 # make sure we pass in default context
115 107 context = context or 3
116 108 submodules = filter(lambda o: isinstance(o, SubModuleNode),
117 109 [filenode_new, filenode_old])
118 110 if submodules:
119 111 return ''
120 112
121 113 for filenode in (filenode_old, filenode_new):
122 114 if not isinstance(filenode, FileNode):
123 115 raise VCSError("Given object should be FileNode object, not %s"
124 116 % filenode.__class__)
125 117
126 118 repo = filenode_new.changeset.repository
127 119 old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
128 120 new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
129 121
130 122 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
131 123 ignore_whitespace, context)
132 124 return vcs_gitdiff
133 125
134 126 NEW_FILENODE = 1
135 127 DEL_FILENODE = 2
136 128 MOD_FILENODE = 3
137 129 RENAMED_FILENODE = 4
138 130 CHMOD_FILENODE = 5
139 131
140 132
141 133 class DiffLimitExceeded(Exception):
142 134 pass
143 135
144 136
145 137 class LimitedDiffContainer(object):
146 138
147 139 def __init__(self, diff_limit, cur_diff_size, diff):
148 140 self.diff = diff
149 141 self.diff_limit = diff_limit
150 142 self.cur_diff_size = cur_diff_size
151 143
152 144 def __iter__(self):
153 145 for l in self.diff:
154 146 yield l
155 147
156 148
157 149 class DiffProcessor(object):
158 150 """
159 151 Give it a unified or git diff and it returns a list of the files that were
160 152 mentioned in the diff together with a dict of meta information that
161 153 can be used to render it in a HTML template.
162 154 """
163 155 _chunk_re = re.compile(r'^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
164 156 _newline_marker = re.compile(r'^\\ No newline at end of file')
165 157 _git_header_re = re.compile(r"""
166 158 #^diff[ ]--git
167 159 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
168 160 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%\n
169 161 ^rename[ ]from[ ](?P<rename_from>\S+)\n
170 162 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
171 163 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
172 164 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
173 165 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
174 166 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
175 167 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
176 168 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
177 169 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
178 170 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
179 171 """, re.VERBOSE | re.MULTILINE)
180 172 _hg_header_re = re.compile(r"""
181 173 #^diff[ ]--git
182 174 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
183 175 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%(?:\n|$))?
184 176 (?:^rename[ ]from[ ](?P<rename_from>\S+)\n
185 177 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
186 178 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
187 179 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
188 180 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
189 181 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
190 182 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
191 183 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
192 184 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
193 185 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
194 186 """, re.VERBOSE | re.MULTILINE)
195 187
196 188 #used for inline highlighter word split
197 189 _token_re = re.compile(r'()(&gt;|&lt;|&amp;|\W+?)')
198 190
199 191 def __init__(self, diff, vcs='hg', format='gitdiff', diff_limit=None):
200 192 """
201 193 :param diff: a text in diff format
202 194 :param vcs: type of version controll hg or git
203 195 :param format: format of diff passed, `udiff` or `gitdiff`
204 196 :param diff_limit: define the size of diff that is considered "big"
205 197 based on that parameter cut off will be triggered, set to None
206 198 to show full diff
207 199 """
208 200 if not isinstance(diff, basestring):
209 201 raise Exception('Diff must be a basestring got %s instead' % type(diff))
210 202
211 203 self._diff = diff
212 204 self._format = format
213 205 self.adds = 0
214 206 self.removes = 0
215 207 # calculate diff size
216 208 self.diff_size = len(diff)
217 209 self.diff_limit = diff_limit
218 210 self.cur_diff_size = 0
219 211 self.parsed = False
220 212 self.parsed_diff = []
221 213 self.vcs = vcs
222 214
223 215 if format == 'gitdiff':
224 216 self.differ = self._highlight_line_difflib
225 217 self._parser = self._parse_gitdiff
226 218 else:
227 219 self.differ = self._highlight_line_udiff
228 220 self._parser = self._parse_udiff
229 221
230 222 def _copy_iterator(self):
231 223 """
232 224 make a fresh copy of generator, we should not iterate thru
233 225 an original as it's needed for repeating operations on
234 226 this instance of DiffProcessor
235 227 """
236 228 self.__udiff, iterator_copy = tee(self.__udiff)
237 229 return iterator_copy
238 230
239 231 def _escaper(self, string):
240 232 """
241 233 Escaper for diff escapes special chars and checks the diff limit
242 234
243 235 :param string:
244 236 :type string:
245 237 """
246 238
247 239 self.cur_diff_size += len(string)
248 240
249 241 # escaper get's iterated on each .next() call and it checks if each
250 242 # parsed line doesn't exceed the diff limit
251 243 if self.diff_limit is not None and self.cur_diff_size > self.diff_limit:
252 244 raise DiffLimitExceeded('Diff Limit Exceeded')
253 245
254 246 return safe_unicode(string).replace('&', '&amp;')\
255 247 .replace('<', '&lt;')\
256 248 .replace('>', '&gt;')
257 249
258 250 def _line_counter(self, l):
259 251 """
260 252 Checks each line and bumps total adds/removes for this diff
261 253
262 254 :param l:
263 255 """
264 256 if l.startswith('+') and not l.startswith('+++'):
265 257 self.adds += 1
266 258 elif l.startswith('-') and not l.startswith('---'):
267 259 self.removes += 1
268 260 return safe_unicode(l)
269 261
270 262 def _highlight_line_difflib(self, line, next_):
271 263 """
272 264 Highlight inline changes in both lines.
273 265 """
274 266
275 267 if line['action'] == 'del':
276 268 old, new = line, next_
277 269 else:
278 270 old, new = next_, line
279 271
280 272 oldwords = self._token_re.split(old['line'])
281 273 newwords = self._token_re.split(new['line'])
282 274 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
283 275
284 276 oldfragments, newfragments = [], []
285 277 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
286 278 oldfrag = ''.join(oldwords[i1:i2])
287 279 newfrag = ''.join(newwords[j1:j2])
288 280 if tag != 'equal':
289 281 if oldfrag:
290 282 oldfrag = '<del>%s</del>' % oldfrag
291 283 if newfrag:
292 284 newfrag = '<ins>%s</ins>' % newfrag
293 285 oldfragments.append(oldfrag)
294 286 newfragments.append(newfrag)
295 287
296 288 old['line'] = "".join(oldfragments)
297 289 new['line'] = "".join(newfragments)
298 290
299 291 def _highlight_line_udiff(self, line, next_):
300 292 """
301 293 Highlight inline changes in both lines.
302 294 """
303 295 start = 0
304 296 limit = min(len(line['line']), len(next_['line']))
305 297 while start < limit and line['line'][start] == next_['line'][start]:
306 298 start += 1
307 299 end = -1
308 300 limit -= start
309 301 while -end <= limit and line['line'][end] == next_['line'][end]:
310 302 end -= 1
311 303 end += 1
312 304 if start or end:
313 305 def do(l):
314 306 last = end + len(l['line'])
315 307 if l['action'] == 'add':
316 308 tag = 'ins'
317 309 else:
318 310 tag = 'del'
319 311 l['line'] = '%s<%s>%s</%s>%s' % (
320 312 l['line'][:start],
321 313 tag,
322 314 l['line'][start:last],
323 315 tag,
324 316 l['line'][last:]
325 317 )
326 318 do(line)
327 319 do(next_)
328 320
329 321 def _get_header(self, diff_chunk):
330 322 """
331 323 parses the diff header, and returns parts, and leftover diff
332 324 parts consists of 14 elements::
333 325
334 326 a_path, b_path, similarity_index, rename_from, rename_to,
335 327 old_mode, new_mode, new_file_mode, deleted_file_mode,
336 328 a_blob_id, b_blob_id, b_mode, a_file, b_file
337 329
338 330 :param diff_chunk:
339 331 :type diff_chunk:
340 332 """
341 333
342 334 if self.vcs == 'git':
343 335 match = self._git_header_re.match(diff_chunk)
344 336 diff = diff_chunk[match.end():]
345 337 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
346 338 elif self.vcs == 'hg':
347 339 match = self._hg_header_re.match(diff_chunk)
348 340 diff = diff_chunk[match.end():]
349 341 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
350 342 else:
351 343 raise Exception('VCS type %s is not supported' % self.vcs)
352 344
353 345 def _clean_line(self, line, command):
354 346 if command in ['+', '-', ' ']:
355 347 #only modify the line if it's actually a diff thing
356 348 line = line[1:]
357 349 return line
358 350
359 351 def _parse_gitdiff(self, inline_diff=True):
360 352 _files = []
361 353 diff_container = lambda arg: arg
362 354
363 355 ##split the diff in chunks of separate --git a/file b/file chunks
364 356 for raw_diff in ('\n' + self._diff).split('\ndiff --git')[1:]:
365 357 binary = False
366 358 binary_msg = 'unknown binary'
367 359 head, diff = self._get_header(raw_diff)
368 360
369 361 if not head['a_file'] and head['b_file']:
370 362 op = 'A'
371 363 elif head['a_file'] and head['b_file']:
372 364 op = 'M'
373 365 elif head['a_file'] and not head['b_file']:
374 366 op = 'D'
375 367 else:
376 368 #probably we're dealing with a binary file 1
377 369 binary = True
378 370 if head['deleted_file_mode']:
379 371 op = 'D'
380 372 stats = ['b', DEL_FILENODE]
381 373 binary_msg = 'deleted binary file'
382 374 elif head['new_file_mode']:
383 375 op = 'A'
384 376 stats = ['b', NEW_FILENODE]
385 377 binary_msg = 'new binary file %s' % head['new_file_mode']
386 378 else:
387 379 if head['new_mode'] and head['old_mode']:
388 380 stats = ['b', CHMOD_FILENODE]
389 381 op = 'M'
390 382 binary_msg = ('modified binary file chmod %s => %s'
391 383 % (head['old_mode'], head['new_mode']))
392 384 elif (head['rename_from'] and head['rename_to']
393 385 and head['rename_from'] != head['rename_to']):
394 386 stats = ['b', RENAMED_FILENODE]
395 387 op = 'M'
396 388 binary_msg = ('file renamed from %s to %s'
397 389 % (head['rename_from'], head['rename_to']))
398 390 else:
399 391 stats = ['b', MOD_FILENODE]
400 392 op = 'M'
401 393 binary_msg = 'modified binary file'
402 394
403 395 if not binary:
404 396 try:
405 397 chunks, stats = self._parse_lines(diff)
406 398 except DiffLimitExceeded:
407 399 diff_container = lambda _diff: LimitedDiffContainer(
408 400 self.diff_limit,
409 401 self.cur_diff_size,
410 402 _diff)
411 403 break
412 404 else:
413 405 chunks = []
414 406 chunks.append([{
415 407 'old_lineno': '',
416 408 'new_lineno': '',
417 409 'action': 'binary',
418 410 'line': binary_msg,
419 411 }])
420 412
421 413 _files.append({
422 414 'filename': head['b_path'],
423 415 'old_revision': head['a_blob_id'],
424 416 'new_revision': head['b_blob_id'],
425 417 'chunks': chunks,
426 418 'operation': op,
427 419 'stats': stats,
428 420 })
429 421
430 422 sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
431 423
432 424 if inline_diff is False:
433 425 return diff_container(sorted(_files, key=sorter))
434 426
435 427 # highlight inline changes
436 428 for diff_data in _files:
437 429 for chunk in diff_data['chunks']:
438 430 lineiter = iter(chunk)
439 431 try:
440 432 while 1:
441 433 line = lineiter.next()
442 434 if line['action'] not in ['unmod', 'context']:
443 435 nextline = lineiter.next()
444 436 if nextline['action'] in ['unmod', 'context'] or \
445 437 nextline['action'] == line['action']:
446 438 continue
447 439 self.differ(line, nextline)
448 440 except StopIteration:
449 441 pass
450 442
451 443 return diff_container(sorted(_files, key=sorter))
452 444
453 445 def _parse_udiff(self, inline_diff=True):
454 446 raise NotImplementedError()
455 447
456 448 def _parse_lines(self, diff):
457 449 """
458 450 Parse the diff an return data for the template.
459 451 """
460 452
461 453 lineiter = iter(diff)
462 454 stats = [0, 0]
463 455
464 456 try:
465 457 chunks = []
466 458 line = lineiter.next()
467 459
468 460 while line:
469 461 lines = []
470 462 chunks.append(lines)
471 463
472 464 match = self._chunk_re.match(line)
473 465
474 466 if not match:
475 467 break
476 468
477 469 gr = match.groups()
478 470 (old_line, old_end,
479 471 new_line, new_end) = [int(x or 1) for x in gr[:-1]]
480 472 old_line -= 1
481 473 new_line -= 1
482 474
483 475 context = len(gr) == 5
484 476 old_end += old_line
485 477 new_end += new_line
486 478
487 479 if context:
488 480 # skip context only if it's first line
489 481 if int(gr[0]) > 1:
490 482 lines.append({
491 483 'old_lineno': '...',
492 484 'new_lineno': '...',
493 485 'action': 'context',
494 486 'line': line,
495 487 })
496 488
497 489 line = lineiter.next()
498 490
499 491 while old_line < old_end or new_line < new_end:
500 492 command = ' '
501 493 if line:
502 494 command = line[0]
503 495
504 496 affects_old = affects_new = False
505 497
506 498 # ignore those if we don't expect them
507 499 if command in '#@':
508 500 continue
509 501 elif command == '+':
510 502 affects_new = True
511 503 action = 'add'
512 504 stats[0] += 1
513 505 elif command == '-':
514 506 affects_old = True
515 507 action = 'del'
516 508 stats[1] += 1
517 509 else:
518 510 affects_old = affects_new = True
519 511 action = 'unmod'
520 512
521 513 if not self._newline_marker.match(line):
522 514 old_line += affects_old
523 515 new_line += affects_new
524 516 lines.append({
525 517 'old_lineno': affects_old and old_line or '',
526 518 'new_lineno': affects_new and new_line or '',
527 519 'action': action,
528 520 'line': self._clean_line(line, command)
529 521 })
530 522
531 523 line = lineiter.next()
532 524
533 525 if self._newline_marker.match(line):
534 526 # we need to append to lines, since this is not
535 527 # counted in the line specs of diff
536 528 lines.append({
537 529 'old_lineno': '...',
538 530 'new_lineno': '...',
539 531 'action': 'context',
540 532 'line': self._clean_line(line, command)
541 533 })
542 534
543 535 except StopIteration:
544 536 pass
545 537 return chunks, stats
546 538
547 539 def _safe_id(self, idstring):
548 540 """Make a string safe for including in an id attribute.
549 541
550 542 The HTML spec says that id attributes 'must begin with
551 543 a letter ([A-Za-z]) and may be followed by any number
552 544 of letters, digits ([0-9]), hyphens ("-"), underscores
553 545 ("_"), colons (":"), and periods (".")'. These regexps
554 546 are slightly over-zealous, in that they remove colons
555 547 and periods unnecessarily.
556 548
557 549 Whitespace is transformed into underscores, and then
558 550 anything which is not a hyphen or a character that
559 551 matches \w (alphanumerics and underscore) is removed.
560 552
561 553 """
562 554 # Transform all whitespace to underscore
563 555 idstring = re.sub(r'\s', "_", '%s' % idstring)
564 556 # Remove everything that is not a hyphen or a member of \w
565 557 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
566 558 return idstring
567 559
568 560 def prepare(self, inline_diff=True):
569 561 """
570 562 Prepare the passed udiff for HTML rendering. It'l return a list
571 563 of dicts with diff information
572 564 """
573 565 parsed = self._parser(inline_diff=inline_diff)
574 566 self.parsed = True
575 567 self.parsed_diff = parsed
576 568 return parsed
577 569
578 570 def as_raw(self, diff_lines=None):
579 571 """
580 572 Returns raw string diff
581 573 """
582 574 return self._diff
583 575 #return u''.join(imap(self._line_counter, self._diff.splitlines(1)))
584 576
585 577 def as_html(self, table_class='code-difftable', line_class='line',
586 578 old_lineno_class='lineno old', new_lineno_class='lineno new',
587 579 code_class='code', enable_comments=False, parsed_lines=None):
588 580 """
589 581 Return given diff as html table with customized css classes
590 582 """
591 583 def _link_to_if(condition, label, url):
592 584 """
593 585 Generates a link if condition is meet or just the label if not.
594 586 """
595 587
596 588 if condition:
597 589 return '''<a href="%(url)s">%(label)s</a>''' % {
598 590 'url': url,
599 591 'label': label
600 592 }
601 593 else:
602 594 return label
603 595 if not self.parsed:
604 596 self.prepare()
605 597
606 598 diff_lines = self.parsed_diff
607 599 if parsed_lines:
608 600 diff_lines = parsed_lines
609 601
610 602 _html_empty = True
611 603 _html = []
612 604 _html.append('''<table class="%(table_class)s">\n''' % {
613 605 'table_class': table_class
614 606 })
615 607
616 608 for diff in diff_lines:
617 609 for line in diff['chunks']:
618 610 _html_empty = False
619 611 for change in line:
620 612 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
621 613 'lc': line_class,
622 614 'action': change['action']
623 615 })
624 616 anchor_old_id = ''
625 617 anchor_new_id = ''
626 618 anchor_old = "%(filename)s_o%(oldline_no)s" % {
627 619 'filename': self._safe_id(diff['filename']),
628 620 'oldline_no': change['old_lineno']
629 621 }
630 622 anchor_new = "%(filename)s_n%(oldline_no)s" % {
631 623 'filename': self._safe_id(diff['filename']),
632 624 'oldline_no': change['new_lineno']
633 625 }
634 626 cond_old = (change['old_lineno'] != '...' and
635 627 change['old_lineno'])
636 628 cond_new = (change['new_lineno'] != '...' and
637 629 change['new_lineno'])
638 630 if cond_old:
639 631 anchor_old_id = 'id="%s"' % anchor_old
640 632 if cond_new:
641 633 anchor_new_id = 'id="%s"' % anchor_new
642 634 ###########################################################
643 635 # OLD LINE NUMBER
644 636 ###########################################################
645 637 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
646 638 'a_id': anchor_old_id,
647 639 'olc': old_lineno_class
648 640 })
649 641
650 642 _html.append('''%(link)s''' % {
651 643 'link': _link_to_if(True, change['old_lineno'],
652 644 '#%s' % anchor_old)
653 645 })
654 646 _html.append('''</td>\n''')
655 647 ###########################################################
656 648 # NEW LINE NUMBER
657 649 ###########################################################
658 650
659 651 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
660 652 'a_id': anchor_new_id,
661 653 'nlc': new_lineno_class
662 654 })
663 655
664 656 _html.append('''%(link)s''' % {
665 657 'link': _link_to_if(True, change['new_lineno'],
666 658 '#%s' % anchor_new)
667 659 })
668 660 _html.append('''</td>\n''')
669 661 ###########################################################
670 662 # CODE
671 663 ###########################################################
672 664 comments = '' if enable_comments else 'no-comment'
673 665 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
674 666 'cc': code_class,
675 667 'inc': comments
676 668 })
677 669 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
678 670 'code': change['line']
679 671 })
680 672
681 673 _html.append('''\t</td>''')
682 674 _html.append('''\n</tr>\n''')
683 675 _html.append('''</table>''')
684 676 if _html_empty:
685 677 return None
686 678 return ''.join(_html)
687 679
688 680 def stat(self):
689 681 """
690 682 Returns tuple of added, and removed lines for this instance
691 683 """
692 684 return self.adds, self.removes
693 685
694 686
695 class InMemoryBundleRepo(bundlerepository):
696 def __init__(self, ui, path, bundlestream):
697 self._tempparent = None
698 localrepo.localrepository.__init__(self, ui, path)
699 self.ui.setconfig('phases', 'publish', False)
700
701 self.bundle = bundlestream
702
703 # dict with the mapping 'filename' -> position in the bundle
704 self.bundlefilespos = {}
705
706
707 687 def differ(org_repo, org_ref, other_repo, other_ref,
708 remote_compare=False, context=3, ignore_whitespace=False):
688 context=3, ignore_whitespace=False):
709 689 """
710 690 General differ between branches, bookmarks, revisions of two remote or
711 691 local but related repositories
712 692
713 693 :param org_repo:
714 694 :param org_ref:
715 695 :param other_repo:
716 696 :type other_repo:
717 697 :type other_ref:
718 698 """
719 699
720 700 org_repo_scm = org_repo.scm_instance
721 701 other_repo_scm = other_repo.scm_instance
722 702
723 703 org_repo = org_repo_scm._repo
724 704 other_repo = other_repo_scm._repo
725 705
726 706 org_ref = org_ref[1]
727 707 other_ref = other_ref[1]
728 708
729 709 if org_repo_scm == other_repo_scm:
730 710 log.debug('running diff between %s@%s and %s@%s'
731 711 % (org_repo.path, org_ref, other_repo.path, other_ref))
732 712 _diff = org_repo_scm.get_diff(rev1=org_ref, rev2=other_ref,
733 713 ignore_whitespace=ignore_whitespace, context=context)
734 714 return _diff
735 715
736 elif remote_compare:
737 opts = diffopts(git=True, ignorews=ignore_whitespace, context=context)
738 org_repo_peer = localrepo.locallegacypeer(org_repo.local())
739 # create a bundle (uncompressed if other repo is not local)
740 if org_repo_peer.capable('getbundle'):
741 # disable repo hooks here since it's just bundle !
742 # patch and reset hooks section of UI config to not run any
743 # hooks on fetching archives with subrepos
744 for k, _ in org_repo.ui.configitems('hooks'):
745 org_repo.ui.setconfig('hooks', k, None)
746 unbundle = org_repo.getbundle('incoming', common=None,
747 heads=None)
748
749 buf = BytesIO()
750 while True:
751 chunk = unbundle._stream.read(1024 * 4)
752 if not chunk:
753 break
754 buf.write(chunk)
755
756 buf.seek(0)
757 # replace chunked _stream with data that can do tell() and seek()
758 unbundle._stream = buf
759
760 ui = make_ui('db')
761 bundlerepo = InMemoryBundleRepo(ui, path=org_repo.root,
762 bundlestream=unbundle)
763
764 return ''.join(patch.diff(bundlerepo,
765 node1=other_repo[other_ref].node(),
766 node2=org_repo[org_ref].node(),
767 opts=opts))
768
769 716 return ''
@@ -1,204 +1,204 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${c.repo_name} ${_('New pull request')}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(_(u'Home'),h.url('/'))}
9 9 &raquo;
10 10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('New pull request')}
13 13 </%def>
14 14
15 15 <%def name="main()">
16 16
17 17 <div class="box">
18 18 <!-- box / title -->
19 19 <div class="title">
20 20 ${self.breadcrumbs()}
21 21 </div>
22 22 ${h.form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
23 23 <div style="float:left;padding:0px 30px 30px 30px">
24 24 <input type="hidden" name="rev_start" value="${request.GET.get('rev_start')}" />
25 25 <input type="hidden" name="rev_end" value="${request.GET.get('rev_end')}" />
26 26
27 27 ##ORG
28 28 <div style="float:left">
29 29 <div class="fork_user">
30 30 <div class="gravatar">
31 31 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_db_repo.user.email,24)}"/>
32 32 </div>
33 33 <span style="font-size: 20px">
34 34 ${h.select('org_repo','',c.org_repos,class_='refs')}:${h.select('org_ref','',c.org_refs,class_='refs')}
35 35 </span>
36 36 <div style="padding:5px 3px 3px 42px;">${c.rhodecode_db_repo.description}</div>
37 37 </div>
38 38 <div style="clear:both;padding-top: 10px"></div>
39 39 </div>
40 40 <div style="float:left;font-size:24px;padding:0px 20px">
41 41 <img height=32 width=32 src="${h.url('/images/arrow_right_64.png')}"/>
42 42 </div>
43 43
44 44 ##OTHER, most Probably the PARENT OF THIS FORK
45 45 <div style="float:left">
46 46 <div class="fork_user">
47 47 <div class="gravatar">
48 48 <img id="other_repo_gravatar" alt="gravatar" src=""/>
49 49 </div>
50 50 <span style="font-size: 20px">
51 51 ${h.select('other_repo',c.default_pull_request ,c.other_repos,class_='refs')}:${h.select('other_ref',c.default_pull_request_rev,c.default_revs,class_='refs')}
52 52 </span>
53 53 <span style="padding:3px">
54 54 <a id="refresh" href="#" class="tooltip" title="${h.tooltip(_('refresh overview'))}">
55 55 <img style="margin:3px" class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/>
56 56 </a>
57 57 </span>
58 58 <div id="other_repo_desc" style="padding:5px 3px 3px 42px;"></div>
59 59 </div>
60 60 <div style="clear:both;padding-top: 10px"></div>
61 61 </div>
62 62 <div style="clear:both;padding-top: 10px"></div>
63 63 ## overview pulled by ajax
64 64 <div style="float:left" id="pull_request_overview"></div>
65 65 <div style="float:left;clear:both;padding:10px 10px 10px 0px;display:none">
66 66 <a id="pull_request_overview_url" href="#">${_('Detailed compare view')}</a>
67 67 </div>
68 68 </div>
69 69 <div style="float:left; border-left:1px dashed #eee">
70 70 <h4>${_('Pull request reviewers')}</h4>
71 71 <div id="reviewers" style="padding:0px 0px 0px 15px">
72 72 ## members goes here !
73 73 <div class="group_members_wrap">
74 74 <ul id="review_members" class="group_members">
75 75 %for member in c.review_members:
76 76 <li id="reviewer_${member.user_id}">
77 77 <div class="reviewers_member">
78 78 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
79 79 <div style="float:left">${member.full_name} (${_('owner')})</div>
80 80 <input type="hidden" value="${member.user_id}" name="review_members" />
81 81 <span class="delete_icon action_button" onclick="removeReviewer(${member.user_id})"></span>
82 82 </div>
83 83 </li>
84 84 %endfor
85 85 </ul>
86 86 </div>
87 87
88 88 <div class='ac'>
89 89 <div class="reviewer_ac">
90 90 ${h.text('user', class_='yui-ac-input')}
91 91 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
92 92 <div id="reviewers_container"></div>
93 93 </div>
94 94 </div>
95 95 </div>
96 96 </div>
97 97 <h3>${_('Create new pull request')}</h3>
98 98
99 99 <div class="form">
100 100 <!-- fields -->
101 101
102 102 <div class="fields">
103 103
104 104 <div class="field">
105 105 <div class="label">
106 106 <label for="pullrequest_title">${_('Title')}:</label>
107 107 </div>
108 108 <div class="input">
109 109 ${h.text('pullrequest_title',size=30)}
110 110 </div>
111 111 </div>
112 112
113 113 <div class="field">
114 114 <div class="label label-textarea">
115 115 <label for="pullrequest_desc">${_('description')}:</label>
116 116 </div>
117 117 <div class="textarea text-area editor">
118 118 ${h.textarea('pullrequest_desc',size=30)}
119 119 </div>
120 120 </div>
121 121
122 122 <div class="buttons">
123 123 ${h.submit('save',_('Send pull request'),class_="ui-btn large")}
124 124 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
125 125 </div>
126 126 </div>
127 127 </div>
128 128 ${h.end_form()}
129 129
130 130 </div>
131 131
132 132 <script type="text/javascript">
133 133 var _USERS_AC_DATA = ${c.users_array|n};
134 134 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
135 135 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
136 136
137 137 var other_repos_info = ${c.other_repos_info|n};
138 138
139 139 var loadPreview = function(){
140 140 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','none');
141 141 var url = "${h.url('compare_url',
142 142 repo_name='org_repo',
143 143 org_ref_type='org_ref_type', org_ref='org_ref',
144 144 other_ref_type='other_ref_type', other_ref='other_ref',
145 145 repo='other_repo',
146 as_form=True, bundle=False,
146 as_form=True,
147 147 rev_start=request.GET.get('rev_start',''),
148 148 rev_end=request.GET.get('rev_end',''))}";
149 149
150 150 var select_refs = YUQ('#pull_request_form select.refs')
151 151 var rev_data = {}; // gather the org/other ref and repo here
152 152 for(var i=0;i<select_refs.length;i++){
153 153 var select_ref = select_refs[i];
154 154 var select_ref_data = select_ref.value.split(':');
155 155 var key = null;
156 156 var val = null;
157 157
158 158 if(select_ref_data.length>1){
159 159 key = select_ref.name+"_type";
160 160 val = select_ref_data[0];
161 161 url = url.replace(key,val);
162 162 rev_data[key] = val;
163 163
164 164 key = select_ref.name;
165 165 val = select_ref_data[1];
166 166 url = url.replace(key,val);
167 167 rev_data[key] = val;
168 168
169 169 }else{
170 170 key = select_ref.name;
171 171 val = select_ref.value;
172 172 url = url.replace(key,val);
173 173 rev_data[key] = val;
174 174 }
175 175 }
176 176
177 177 YUE.on('other_repo', 'change', function(e){
178 178 var repo_name = e.currentTarget.value;
179 179 // replace the <select> of changed repo
180 180 YUD.get('other_ref').innerHTML = other_repos_info[repo_name]['revs'];
181 181 });
182 182
183 183 ypjax(url,'pull_request_overview', function(data){
184 184 var sel_box = YUQ('#pull_request_form #other_repo')[0];
185 185 var repo_name = sel_box.options[sel_box.selectedIndex].value;
186 186 YUD.get('pull_request_overview_url').href = url;
187 187 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','');
188 188 YUD.get('other_repo_gravatar').src = other_repos_info[repo_name]['gravatar'];
189 189 YUD.get('other_repo_desc').innerHTML = other_repos_info[repo_name]['description'];
190 190 YUD.get('other_ref').innerHTML = other_repos_info[repo_name]['revs'];
191 191 // select back the revision that was just compared
192 192 setSelectValue(YUD.get('other_ref'), rev_data['other_ref']);
193 193 })
194 194 }
195 195 YUE.on('refresh','click',function(e){
196 196 loadPreview()
197 197 })
198 198
199 199 //lazy load overview after 0.5s
200 200 setTimeout(loadPreview, 500)
201 201
202 202 </script>
203 203
204 204 </%def>
@@ -1,153 +1,152 b''
1 1 from rhodecode.tests import *
2 2 from rhodecode.model.repo import RepoModel
3 3 from rhodecode.model.meta import Session
4 4 from rhodecode.model.db import Repository
5 5 from rhodecode.model.scm import ScmModel
6 6 from rhodecode.lib.vcs.backends.base import EmptyChangeset
7 7
8 8
9 9 class TestCompareController(TestController):
10 10
11 11 def test_compare_tag_hg(self):
12 12 self.log_user()
13 13 tag1 = '0.1.2'
14 14 tag2 = '0.1.3'
15 15 response = self.app.get(url(controller='compare', action='index',
16 16 repo_name=HG_REPO,
17 17 org_ref_type="tag",
18 18 org_ref=tag1,
19 19 other_ref_type="tag",
20 20 other_ref=tag2,
21 21 ))
22 22 response.mustcontain('%s@%s -&gt; %s@%s' % (HG_REPO, tag1, HG_REPO, tag2))
23 23 ## outgoing changesets between tags
24 24 response.mustcontain('''<a href="/%s/changeset/c5ddebc06eaaba3010c2d66ea6ec9d074eb0f678">r112:c5ddebc06eaa</a>''' % HG_REPO)
25 25 response.mustcontain('''<a href="/%s/changeset/70d4cef8a37657ee4cf5aabb3bd9f68879769816">r115:70d4cef8a376</a>''' % HG_REPO)
26 26 response.mustcontain('''<a href="/%s/changeset/9749bfbfc0d2eba208d7947de266303b67c87cda">r116:9749bfbfc0d2</a>''' % HG_REPO)
27 27 response.mustcontain('''<a href="/%s/changeset/41fda979f02fda216374bf8edac4e83f69e7581c">r117:41fda979f02f</a>''' % HG_REPO)
28 28 response.mustcontain('''<a href="/%s/changeset/bb1a3ab98cc45cb934a77dcabf87a5a598b59e97">r118:bb1a3ab98cc4</a>''' % HG_REPO)
29 29 response.mustcontain('''<a href="/%s/changeset/36e0fc9d2808c5022a24f49d6658330383ed8666">r119:36e0fc9d2808</a>''' % HG_REPO)
30 30 response.mustcontain('''<a href="/%s/changeset/17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">r120:17544fbfcd33</a>''' % HG_REPO)
31 31
32 32 response.mustcontain('11 files changed with 94 insertions and 64 deletions')
33 33
34 34 ## files diff
35 35 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--1c5cf9e91c12">docs/api/utils/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
36 36 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--e3305437df55">test_and_report.sh</a></div>''' % (HG_REPO, tag1, tag2))
37 37 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--c8e92ef85cd1">.hgignore</a></div>''' % (HG_REPO, tag1, tag2))
38 38 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--6e08b694d687">.hgtags</a></div>''' % (HG_REPO, tag1, tag2))
39 39 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2c14b00f3393">docs/api/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
40 40 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--430ccbc82bdf">vcs/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
41 41 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--9c390eb52cd6">vcs/backends/hg.py</a></div>''' % (HG_REPO, tag1, tag2))
42 42 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--ebb592c595c0">vcs/utils/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
43 43 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--7abc741b5052">vcs/utils/annotate.py</a></div>''' % (HG_REPO, tag1, tag2))
44 44 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2ef0ef106c56">vcs/utils/diffs.py</a></div>''' % (HG_REPO, tag1, tag2))
45 45 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--3150cb87d4b7">vcs/utils/lazy.py</a></div>''' % (HG_REPO, tag1, tag2))
46 46
47 47 def test_compare_tag_git(self):
48 48 self.log_user()
49 49 tag1 = 'v0.1.2'
50 50 tag2 = 'v0.1.3'
51 51 response = self.app.get(url(controller='compare', action='index',
52 52 repo_name=GIT_REPO,
53 53 org_ref_type="tag",
54 54 org_ref=tag1,
55 55 other_ref_type="tag",
56 56 other_ref=tag2,
57 bundle=False
58 57 ))
59 58 response.mustcontain('%s@%s -&gt; %s@%s' % (GIT_REPO, tag1, GIT_REPO, tag2))
60 59
61 60 ## outgoing changesets between tags
62 61 response.mustcontain('''<a href="/%s/changeset/794bbdd31545c199f74912709ea350dedcd189a2">r113:794bbdd31545</a>''' % GIT_REPO)
63 62 response.mustcontain('''<a href="/%s/changeset/e36d8c5025329bdd4212bd53d4ed8a70ff44985f">r115:e36d8c502532</a>''' % GIT_REPO)
64 63 response.mustcontain('''<a href="/%s/changeset/5c9ff4f6d7508db0e72b1d2991c357d0d8e07af2">r116:5c9ff4f6d750</a>''' % GIT_REPO)
65 64 response.mustcontain('''<a href="/%s/changeset/b7187fa2b8c1d773ec35e9dee12f01f74808c879">r117:b7187fa2b8c1</a>''' % GIT_REPO)
66 65 response.mustcontain('''<a href="/%s/changeset/5f3b74262014a8de2dc7dade1152de9fd0c8efef">r118:5f3b74262014</a>''' % GIT_REPO)
67 66 response.mustcontain('''<a href="/%s/changeset/17438a11f72b93f56d0e08e7d1fa79a378578a82">r119:17438a11f72b</a>''' % GIT_REPO)
68 67 response.mustcontain('''<a href="/%s/changeset/5a3a8fb005554692b16e21dee62bf02667d8dc3e">r120:5a3a8fb00555</a>''' % GIT_REPO)
69 68
70 69 response.mustcontain('11 files changed with 94 insertions and 64 deletions')
71 70
72 71 #files
73 72 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--1c5cf9e91c12">docs/api/utils/index.rst</a>''' % (GIT_REPO, tag1, tag2))
74 73 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--e3305437df55">test_and_report.sh</a>''' % (GIT_REPO, tag1, tag2))
75 74 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--c8e92ef85cd1">.hgignore</a>''' % (GIT_REPO, tag1, tag2))
76 75 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--6e08b694d687">.hgtags</a>''' % (GIT_REPO, tag1, tag2))
77 76 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--2c14b00f3393">docs/api/index.rst</a>''' % (GIT_REPO, tag1, tag2))
78 77 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--430ccbc82bdf">vcs/__init__.py</a>''' % (GIT_REPO, tag1, tag2))
79 78 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--9c390eb52cd6">vcs/backends/hg.py</a>''' % (GIT_REPO, tag1, tag2))
80 79 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--ebb592c595c0">vcs/utils/__init__.py</a>''' % (GIT_REPO, tag1, tag2))
81 80 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--7abc741b5052">vcs/utils/annotate.py</a>''' % (GIT_REPO, tag1, tag2))
82 81 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--2ef0ef106c56">vcs/utils/diffs.py</a>''' % (GIT_REPO, tag1, tag2))
83 82 response.mustcontain('''<a href="/%s/compare/tag@%s...tag@%s#C--3150cb87d4b7">vcs/utils/lazy.py</a>''' % (GIT_REPO, tag1, tag2))
84 83
85 84 def test_index_branch_hg(self):
86 85 self.log_user()
87 86 response = self.app.get(url(controller='compare', action='index',
88 87 repo_name=HG_REPO,
89 88 org_ref_type="branch",
90 89 org_ref='default',
91 90 other_ref_type="branch",
92 91 other_ref='default',
93 92 ))
94 93
95 94 response.mustcontain('%s@default -&gt; %s@default' % (HG_REPO, HG_REPO))
96 95 # branch are equal
97 96 response.mustcontain('<span class="empty_data">No files</span>')
98 97 response.mustcontain('<span class="empty_data">No changesets</span>')
99 98
100 99 def test_index_branch_git(self):
101 100 self.log_user()
102 101 response = self.app.get(url(controller='compare', action='index',
103 102 repo_name=GIT_REPO,
104 103 org_ref_type="branch",
105 104 org_ref='master',
106 105 other_ref_type="branch",
107 106 other_ref='master',
108 107 ))
109 108
110 109 response.mustcontain('%s@master -&gt; %s@master' % (GIT_REPO, GIT_REPO))
111 110 # branch are equal
112 111 response.mustcontain('<span class="empty_data">No files</span>')
113 112 response.mustcontain('<span class="empty_data">No changesets</span>')
114 113
115 114 def test_compare_revisions_hg(self):
116 115 self.log_user()
117 116 rev1 = 'b986218ba1c9'
118 117 rev2 = '3d8f361e72ab'
119 118
120 119 response = self.app.get(url(controller='compare', action='index',
121 120 repo_name=HG_REPO,
122 121 org_ref_type="rev",
123 122 org_ref=rev1,
124 123 other_ref_type="rev",
125 124 other_ref=rev2,
126 125 ))
127 126 response.mustcontain('%s@%s -&gt; %s@%s' % (HG_REPO, rev1, HG_REPO, rev2))
128 127 ## outgoing changesets between those revisions
129 128 response.mustcontain("""<a href="/%s/changeset/3d8f361e72ab303da48d799ff1ac40d5ac37c67e">r1:%s</a>""" % (HG_REPO, rev2))
130 129
131 130 response.mustcontain('1 file changed with 7 insertions and 0 deletions')
132 131 ## files
133 132 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--c8e92ef85cd1">.hgignore</a>""" % (HG_REPO, rev1, rev2))
134 133
135 134 def test_compare_revisions_git(self):
136 135 self.log_user()
137 136 rev1 = 'c1214f7e79e02fc37156ff215cd71275450cffc3'
138 137 rev2 = '38b5fe81f109cb111f549bfe9bb6b267e10bc557'
139 138
140 139 response = self.app.get(url(controller='compare', action='index',
141 140 repo_name=GIT_REPO,
142 141 org_ref_type="rev",
143 142 org_ref=rev1,
144 143 other_ref_type="rev",
145 144 other_ref=rev2,
146 145 ))
147 146 response.mustcontain('%s@%s -&gt; %s@%s' % (GIT_REPO, rev1, GIT_REPO, rev2))
148 147 ## outgoing changesets between those revisions
149 148 response.mustcontain("""<a href="/%s/changeset/38b5fe81f109cb111f549bfe9bb6b267e10bc557">r1:%s</a>""" % (GIT_REPO, rev2[:12]))
150 149 response.mustcontain('1 file changed with 7 insertions and 0 deletions')
151 150
152 151 ## files
153 152 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--c8e92ef85cd1">.hgignore</a>""" % (GIT_REPO, rev1, rev2))
General Comments 0
You need to be logged in to leave comments. Login now