Show More
This diff has been collapsed as it changes many lines, (557 lines changed) Show them Hide them | |||||
@@ -0,0 +1,557 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # Copyright (C) 2010-2017 RhodeCode GmbH | |||
|
4 | # | |||
|
5 | # This program is free software: you can redistribute it and/or modify | |||
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |||
|
7 | # (only), as published by the Free Software Foundation. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU Affero General Public License | |||
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
16 | # | |||
|
17 | # This program is dual-licensed. If you wish to learn more about the | |||
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
20 | ||||
|
21 | ||||
|
22 | import logging | |||
|
23 | import collections | |||
|
24 | ||||
|
25 | from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound | |||
|
26 | from pyramid.view import view_config | |||
|
27 | from pyramid.renderers import render | |||
|
28 | from pyramid.response import Response | |||
|
29 | ||||
|
30 | from rhodecode.apps._base import RepoAppView | |||
|
31 | ||||
|
32 | from rhodecode.lib import diffs, codeblocks | |||
|
33 | from rhodecode.lib.auth import ( | |||
|
34 | LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired) | |||
|
35 | ||||
|
36 | from rhodecode.lib.compat import OrderedDict | |||
|
37 | from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError | |||
|
38 | import rhodecode.lib.helpers as h | |||
|
39 | from rhodecode.lib.utils2 import safe_unicode, safe_int | |||
|
40 | from rhodecode.lib.vcs.backends.base import EmptyCommit | |||
|
41 | from rhodecode.lib.vcs.exceptions import ( | |||
|
42 | RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError) | |||
|
43 | from rhodecode.model.db import ChangesetComment, ChangesetStatus | |||
|
44 | from rhodecode.model.changeset_status import ChangesetStatusModel | |||
|
45 | from rhodecode.model.comment import CommentsModel | |||
|
46 | from rhodecode.model.meta import Session | |||
|
47 | ||||
|
48 | ||||
|
49 | log = logging.getLogger(__name__) | |||
|
50 | ||||
|
51 | ||||
|
52 | def _update_with_GET(params, request): | |||
|
53 | for k in ['diff1', 'diff2', 'diff']: | |||
|
54 | params[k] += request.GET.getall(k) | |||
|
55 | ||||
|
56 | ||||
|
57 | def get_ignore_ws(fid, request): | |||
|
58 | ig_ws_global = request.GET.get('ignorews') | |||
|
59 | ig_ws = filter(lambda k: k.startswith('WS'), request.GET.getall(fid)) | |||
|
60 | if ig_ws: | |||
|
61 | try: | |||
|
62 | return int(ig_ws[0].split(':')[-1]) | |||
|
63 | except Exception: | |||
|
64 | pass | |||
|
65 | return ig_ws_global | |||
|
66 | ||||
|
67 | ||||
|
68 | def _ignorews_url(request, fileid=None): | |||
|
69 | _ = request.translate | |||
|
70 | fileid = str(fileid) if fileid else None | |||
|
71 | params = collections.defaultdict(list) | |||
|
72 | _update_with_GET(params, request) | |||
|
73 | label = _('Show whitespace') | |||
|
74 | tooltiplbl = _('Show whitespace for all diffs') | |||
|
75 | ig_ws = get_ignore_ws(fileid, request) | |||
|
76 | ln_ctx = get_line_ctx(fileid, request) | |||
|
77 | ||||
|
78 | if ig_ws is None: | |||
|
79 | params['ignorews'] += [1] | |||
|
80 | label = _('Ignore whitespace') | |||
|
81 | tooltiplbl = _('Ignore whitespace for all diffs') | |||
|
82 | ctx_key = 'context' | |||
|
83 | ctx_val = ln_ctx | |||
|
84 | ||||
|
85 | # if we have passed in ln_ctx pass it along to our params | |||
|
86 | if ln_ctx: | |||
|
87 | params[ctx_key] += [ctx_val] | |||
|
88 | ||||
|
89 | if fileid: | |||
|
90 | params['anchor'] = 'a_' + fileid | |||
|
91 | return h.link_to(label, request.current_route_path(_query=params), | |||
|
92 | title=tooltiplbl, class_='tooltip') | |||
|
93 | ||||
|
94 | ||||
|
95 | def get_line_ctx(fid, request): | |||
|
96 | ln_ctx_global = request.GET.get('context') | |||
|
97 | if fid: | |||
|
98 | ln_ctx = filter(lambda k: k.startswith('C'), request.GET.getall(fid)) | |||
|
99 | else: | |||
|
100 | _ln_ctx = filter(lambda k: k.startswith('C'), request.GET) | |||
|
101 | ln_ctx = request.GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global | |||
|
102 | if ln_ctx: | |||
|
103 | ln_ctx = [ln_ctx] | |||
|
104 | ||||
|
105 | if ln_ctx: | |||
|
106 | retval = ln_ctx[0].split(':')[-1] | |||
|
107 | else: | |||
|
108 | retval = ln_ctx_global | |||
|
109 | ||||
|
110 | try: | |||
|
111 | return int(retval) | |||
|
112 | except Exception: | |||
|
113 | return 3 | |||
|
114 | ||||
|
115 | ||||
|
116 | def _context_url(request, fileid=None): | |||
|
117 | """ | |||
|
118 | Generates a url for context lines. | |||
|
119 | ||||
|
120 | :param fileid: | |||
|
121 | """ | |||
|
122 | ||||
|
123 | _ = request.translate | |||
|
124 | fileid = str(fileid) if fileid else None | |||
|
125 | ig_ws = get_ignore_ws(fileid, request) | |||
|
126 | ln_ctx = (get_line_ctx(fileid, request) or 3) * 2 | |||
|
127 | ||||
|
128 | params = collections.defaultdict(list) | |||
|
129 | _update_with_GET(params, request) | |||
|
130 | ||||
|
131 | if ln_ctx > 0: | |||
|
132 | params['context'] += [ln_ctx] | |||
|
133 | ||||
|
134 | if ig_ws: | |||
|
135 | ig_ws_key = 'ignorews' | |||
|
136 | ig_ws_val = 1 | |||
|
137 | params[ig_ws_key] += [ig_ws_val] | |||
|
138 | ||||
|
139 | lbl = _('Increase context') | |||
|
140 | tooltiplbl = _('Increase context for all diffs') | |||
|
141 | ||||
|
142 | if fileid: | |||
|
143 | params['anchor'] = 'a_' + fileid | |||
|
144 | return h.link_to(lbl, request.current_route_path(_query=params), | |||
|
145 | title=tooltiplbl, class_='tooltip') | |||
|
146 | ||||
|
147 | ||||
|
148 | class RepoCommitsView(RepoAppView): | |||
|
149 | def load_default_context(self): | |||
|
150 | c = self._get_local_tmpl_context(include_app_defaults=True) | |||
|
151 | ||||
|
152 | # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead | |||
|
153 | c.repo_info = self.db_repo | |||
|
154 | c.rhodecode_repo = self.rhodecode_vcs_repo | |||
|
155 | ||||
|
156 | self._register_global_c(c) | |||
|
157 | return c | |||
|
158 | ||||
|
159 | def _commit(self, commit_id_range, method): | |||
|
160 | _ = self.request.translate | |||
|
161 | c = self.load_default_context() | |||
|
162 | c.ignorews_url = _ignorews_url | |||
|
163 | c.context_url = _context_url | |||
|
164 | c.fulldiff = self.request.GET.get('fulldiff') | |||
|
165 | ||||
|
166 | # fetch global flags of ignore ws or context lines | |||
|
167 | context_lcl = get_line_ctx('', self.request) | |||
|
168 | ign_whitespace_lcl = get_ignore_ws('', self.request) | |||
|
169 | ||||
|
170 | # diff_limit will cut off the whole diff if the limit is applied | |||
|
171 | # otherwise it will just hide the big files from the front-end | |||
|
172 | diff_limit = c.visual.cut_off_limit_diff | |||
|
173 | file_limit = c.visual.cut_off_limit_file | |||
|
174 | ||||
|
175 | # get ranges of commit ids if preset | |||
|
176 | commit_range = commit_id_range.split('...')[:2] | |||
|
177 | ||||
|
178 | try: | |||
|
179 | pre_load = ['affected_files', 'author', 'branch', 'date', | |||
|
180 | 'message', 'parents'] | |||
|
181 | ||||
|
182 | if len(commit_range) == 2: | |||
|
183 | commits = self.rhodecode_vcs_repo.get_commits( | |||
|
184 | start_id=commit_range[0], end_id=commit_range[1], | |||
|
185 | pre_load=pre_load) | |||
|
186 | commits = list(commits) | |||
|
187 | else: | |||
|
188 | commits = [self.rhodecode_vcs_repo.get_commit( | |||
|
189 | commit_id=commit_id_range, pre_load=pre_load)] | |||
|
190 | ||||
|
191 | c.commit_ranges = commits | |||
|
192 | if not c.commit_ranges: | |||
|
193 | raise RepositoryError( | |||
|
194 | 'The commit range returned an empty result') | |||
|
195 | except CommitDoesNotExistError: | |||
|
196 | msg = _('No such commit exists for this repository') | |||
|
197 | h.flash(msg, category='error') | |||
|
198 | raise HTTPNotFound() | |||
|
199 | except Exception: | |||
|
200 | log.exception("General failure") | |||
|
201 | raise HTTPNotFound() | |||
|
202 | ||||
|
203 | c.changes = OrderedDict() | |||
|
204 | c.lines_added = 0 | |||
|
205 | c.lines_deleted = 0 | |||
|
206 | ||||
|
207 | # auto collapse if we have more than limit | |||
|
208 | collapse_limit = diffs.DiffProcessor._collapse_commits_over | |||
|
209 | c.collapse_all_commits = len(c.commit_ranges) > collapse_limit | |||
|
210 | ||||
|
211 | c.commit_statuses = ChangesetStatus.STATUSES | |||
|
212 | c.inline_comments = [] | |||
|
213 | c.files = [] | |||
|
214 | ||||
|
215 | c.statuses = [] | |||
|
216 | c.comments = [] | |||
|
217 | c.unresolved_comments = [] | |||
|
218 | if len(c.commit_ranges) == 1: | |||
|
219 | commit = c.commit_ranges[0] | |||
|
220 | c.comments = CommentsModel().get_comments( | |||
|
221 | self.db_repo.repo_id, | |||
|
222 | revision=commit.raw_id) | |||
|
223 | c.statuses.append(ChangesetStatusModel().get_status( | |||
|
224 | self.db_repo.repo_id, commit.raw_id)) | |||
|
225 | # comments from PR | |||
|
226 | statuses = ChangesetStatusModel().get_statuses( | |||
|
227 | self.db_repo.repo_id, commit.raw_id, | |||
|
228 | with_revisions=True) | |||
|
229 | prs = set(st.pull_request for st in statuses | |||
|
230 | if st.pull_request is not None) | |||
|
231 | # from associated statuses, check the pull requests, and | |||
|
232 | # show comments from them | |||
|
233 | for pr in prs: | |||
|
234 | c.comments.extend(pr.comments) | |||
|
235 | ||||
|
236 | c.unresolved_comments = CommentsModel()\ | |||
|
237 | .get_commit_unresolved_todos(commit.raw_id) | |||
|
238 | ||||
|
239 | diff = None | |||
|
240 | # Iterate over ranges (default commit view is always one commit) | |||
|
241 | for commit in c.commit_ranges: | |||
|
242 | c.changes[commit.raw_id] = [] | |||
|
243 | ||||
|
244 | commit2 = commit | |||
|
245 | commit1 = commit.parents[0] if commit.parents else EmptyCommit() | |||
|
246 | ||||
|
247 | _diff = self.rhodecode_vcs_repo.get_diff( | |||
|
248 | commit1, commit2, | |||
|
249 | ignore_whitespace=ign_whitespace_lcl, context=context_lcl) | |||
|
250 | diff_processor = diffs.DiffProcessor( | |||
|
251 | _diff, format='newdiff', diff_limit=diff_limit, | |||
|
252 | file_limit=file_limit, show_full_diff=c.fulldiff) | |||
|
253 | ||||
|
254 | commit_changes = OrderedDict() | |||
|
255 | if method == 'show': | |||
|
256 | _parsed = diff_processor.prepare() | |||
|
257 | c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer) | |||
|
258 | ||||
|
259 | _parsed = diff_processor.prepare() | |||
|
260 | ||||
|
261 | def _node_getter(commit): | |||
|
262 | def get_node(fname): | |||
|
263 | try: | |||
|
264 | return commit.get_node(fname) | |||
|
265 | except NodeDoesNotExistError: | |||
|
266 | return None | |||
|
267 | return get_node | |||
|
268 | ||||
|
269 | inline_comments = CommentsModel().get_inline_comments( | |||
|
270 | self.db_repo.repo_id, revision=commit.raw_id) | |||
|
271 | c.inline_cnt = CommentsModel().get_inline_comments_count( | |||
|
272 | inline_comments) | |||
|
273 | ||||
|
274 | diffset = codeblocks.DiffSet( | |||
|
275 | repo_name=self.db_repo_name, | |||
|
276 | source_node_getter=_node_getter(commit1), | |||
|
277 | target_node_getter=_node_getter(commit2), | |||
|
278 | comments=inline_comments) | |||
|
279 | diffset = diffset.render_patchset( | |||
|
280 | _parsed, commit1.raw_id, commit2.raw_id) | |||
|
281 | ||||
|
282 | c.changes[commit.raw_id] = diffset | |||
|
283 | else: | |||
|
284 | # downloads/raw we only need RAW diff nothing else | |||
|
285 | diff = diff_processor.as_raw() | |||
|
286 | c.changes[commit.raw_id] = [None, None, None, None, diff, None, None] | |||
|
287 | ||||
|
288 | # sort comments by how they were generated | |||
|
289 | c.comments = sorted(c.comments, key=lambda x: x.comment_id) | |||
|
290 | ||||
|
291 | if len(c.commit_ranges) == 1: | |||
|
292 | c.commit = c.commit_ranges[0] | |||
|
293 | c.parent_tmpl = ''.join( | |||
|
294 | '# Parent %s\n' % x.raw_id for x in c.commit.parents) | |||
|
295 | ||||
|
296 | if method == 'download': | |||
|
297 | response = Response(diff) | |||
|
298 | response.content_type = 'text/plain' | |||
|
299 | response.content_disposition = ( | |||
|
300 | 'attachment; filename=%s.diff' % commit_id_range[:12]) | |||
|
301 | return response | |||
|
302 | elif method == 'patch': | |||
|
303 | c.diff = safe_unicode(diff) | |||
|
304 | patch = render( | |||
|
305 | 'rhodecode:templates/changeset/patch_changeset.mako', | |||
|
306 | self._get_template_context(c), self.request) | |||
|
307 | response = Response(patch) | |||
|
308 | response.content_type = 'text/plain' | |||
|
309 | return response | |||
|
310 | elif method == 'raw': | |||
|
311 | response = Response(diff) | |||
|
312 | response.content_type = 'text/plain' | |||
|
313 | return response | |||
|
314 | elif method == 'show': | |||
|
315 | if len(c.commit_ranges) == 1: | |||
|
316 | html = render( | |||
|
317 | 'rhodecode:templates/changeset/changeset.mako', | |||
|
318 | self._get_template_context(c), self.request) | |||
|
319 | return Response(html) | |||
|
320 | else: | |||
|
321 | c.ancestor = None | |||
|
322 | c.target_repo = self.db_repo | |||
|
323 | html = render( | |||
|
324 | 'rhodecode:templates/changeset/changeset_range.mako', | |||
|
325 | self._get_template_context(c), self.request) | |||
|
326 | return Response(html) | |||
|
327 | ||||
|
328 | raise HTTPBadRequest() | |||
|
329 | ||||
|
330 | @LoginRequired() | |||
|
331 | @HasRepoPermissionAnyDecorator( | |||
|
332 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
333 | @view_config( | |||
|
334 | route_name='repo_commit', request_method='GET', | |||
|
335 | renderer=None) | |||
|
336 | def repo_commit_show(self): | |||
|
337 | commit_id = self.request.matchdict['commit_id'] | |||
|
338 | return self._commit(commit_id, method='show') | |||
|
339 | ||||
|
340 | @LoginRequired() | |||
|
341 | @HasRepoPermissionAnyDecorator( | |||
|
342 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
343 | @view_config( | |||
|
344 | route_name='repo_commit_raw', request_method='GET', | |||
|
345 | renderer=None) | |||
|
346 | @view_config( | |||
|
347 | route_name='repo_commit_raw_deprecated', request_method='GET', | |||
|
348 | renderer=None) | |||
|
349 | def repo_commit_raw(self): | |||
|
350 | commit_id = self.request.matchdict['commit_id'] | |||
|
351 | return self._commit(commit_id, method='raw') | |||
|
352 | ||||
|
353 | @LoginRequired() | |||
|
354 | @HasRepoPermissionAnyDecorator( | |||
|
355 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
356 | @view_config( | |||
|
357 | route_name='repo_commit_patch', request_method='GET', | |||
|
358 | renderer=None) | |||
|
359 | def repo_commit_patch(self): | |||
|
360 | commit_id = self.request.matchdict['commit_id'] | |||
|
361 | return self._commit(commit_id, method='patch') | |||
|
362 | ||||
|
363 | @LoginRequired() | |||
|
364 | @HasRepoPermissionAnyDecorator( | |||
|
365 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
366 | @view_config( | |||
|
367 | route_name='repo_commit_download', request_method='GET', | |||
|
368 | renderer=None) | |||
|
369 | def repo_commit_download(self): | |||
|
370 | commit_id = self.request.matchdict['commit_id'] | |||
|
371 | return self._commit(commit_id, method='download') | |||
|
372 | ||||
|
373 | @LoginRequired() | |||
|
374 | @NotAnonymous() | |||
|
375 | @HasRepoPermissionAnyDecorator( | |||
|
376 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
377 | @CSRFRequired() | |||
|
378 | @view_config( | |||
|
379 | route_name='repo_commit_comment_create', request_method='POST', | |||
|
380 | renderer='json_ext') | |||
|
381 | def repo_commit_comment_create(self): | |||
|
382 | _ = self.request.translate | |||
|
383 | commit_id = self.request.matchdict['commit_id'] | |||
|
384 | ||||
|
385 | c = self.load_default_context() | |||
|
386 | status = self.request.POST.get('changeset_status', None) | |||
|
387 | text = self.request.POST.get('text') | |||
|
388 | comment_type = self.request.POST.get('comment_type') | |||
|
389 | resolves_comment_id = self.request.POST.get('resolves_comment_id', None) | |||
|
390 | ||||
|
391 | if status: | |||
|
392 | text = text or (_('Status change %(transition_icon)s %(status)s') | |||
|
393 | % {'transition_icon': '>', | |||
|
394 | 'status': ChangesetStatus.get_status_lbl(status)}) | |||
|
395 | ||||
|
396 | multi_commit_ids = [] | |||
|
397 | for _commit_id in self.request.POST.get('commit_ids', '').split(','): | |||
|
398 | if _commit_id not in ['', None, EmptyCommit.raw_id]: | |||
|
399 | if _commit_id not in multi_commit_ids: | |||
|
400 | multi_commit_ids.append(_commit_id) | |||
|
401 | ||||
|
402 | commit_ids = multi_commit_ids or [commit_id] | |||
|
403 | ||||
|
404 | comment = None | |||
|
405 | for current_id in filter(None, commit_ids): | |||
|
406 | comment = CommentsModel().create( | |||
|
407 | text=text, | |||
|
408 | repo=self.db_repo.repo_id, | |||
|
409 | user=self._rhodecode_db_user.user_id, | |||
|
410 | commit_id=current_id, | |||
|
411 | f_path=self.request.POST.get('f_path'), | |||
|
412 | line_no=self.request.POST.get('line'), | |||
|
413 | status_change=(ChangesetStatus.get_status_lbl(status) | |||
|
414 | if status else None), | |||
|
415 | status_change_type=status, | |||
|
416 | comment_type=comment_type, | |||
|
417 | resolves_comment_id=resolves_comment_id | |||
|
418 | ) | |||
|
419 | ||||
|
420 | # get status if set ! | |||
|
421 | if status: | |||
|
422 | # if latest status was from pull request and it's closed | |||
|
423 | # disallow changing status ! | |||
|
424 | # dont_allow_on_closed_pull_request = True ! | |||
|
425 | ||||
|
426 | try: | |||
|
427 | ChangesetStatusModel().set_status( | |||
|
428 | self.db_repo.repo_id, | |||
|
429 | status, | |||
|
430 | self._rhodecode_db_user.user_id, | |||
|
431 | comment, | |||
|
432 | revision=current_id, | |||
|
433 | dont_allow_on_closed_pull_request=True | |||
|
434 | ) | |||
|
435 | except StatusChangeOnClosedPullRequestError: | |||
|
436 | msg = _('Changing the status of a commit associated with ' | |||
|
437 | 'a closed pull request is not allowed') | |||
|
438 | log.exception(msg) | |||
|
439 | h.flash(msg, category='warning') | |||
|
440 | raise HTTPFound(h.route_path( | |||
|
441 | 'repo_commit', repo_name=self.db_repo_name, | |||
|
442 | commit_id=current_id)) | |||
|
443 | ||||
|
444 | # finalize, commit and redirect | |||
|
445 | Session().commit() | |||
|
446 | ||||
|
447 | data = { | |||
|
448 | 'target_id': h.safeid(h.safe_unicode( | |||
|
449 | self.request.POST.get('f_path'))), | |||
|
450 | } | |||
|
451 | if comment: | |||
|
452 | c.co = comment | |||
|
453 | rendered_comment = render( | |||
|
454 | 'rhodecode:templates/changeset/changeset_comment_block.mako', | |||
|
455 | self._get_template_context(c), self.request) | |||
|
456 | ||||
|
457 | data.update(comment.get_dict()) | |||
|
458 | data.update({'rendered_text': rendered_comment}) | |||
|
459 | ||||
|
460 | return data | |||
|
461 | ||||
|
462 | @LoginRequired() | |||
|
463 | @NotAnonymous() | |||
|
464 | @HasRepoPermissionAnyDecorator( | |||
|
465 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
466 | @CSRFRequired() | |||
|
467 | @view_config( | |||
|
468 | route_name='repo_commit_comment_preview', request_method='POST', | |||
|
469 | renderer='string', xhr=True) | |||
|
470 | def repo_commit_comment_preview(self): | |||
|
471 | # Technically a CSRF token is not needed as no state changes with this | |||
|
472 | # call. However, as this is a POST is better to have it, so automated | |||
|
473 | # tools don't flag it as potential CSRF. | |||
|
474 | # Post is required because the payload could be bigger than the maximum | |||
|
475 | # allowed by GET. | |||
|
476 | ||||
|
477 | text = self.request.POST.get('text') | |||
|
478 | renderer = self.request.POST.get('renderer') or 'rst' | |||
|
479 | if text: | |||
|
480 | return h.render(text, renderer=renderer, mentions=True) | |||
|
481 | return '' | |||
|
482 | ||||
|
483 | @LoginRequired() | |||
|
484 | @NotAnonymous() | |||
|
485 | @HasRepoPermissionAnyDecorator( | |||
|
486 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
487 | @CSRFRequired() | |||
|
488 | @view_config( | |||
|
489 | route_name='repo_commit_comment_delete', request_method='POST', | |||
|
490 | renderer='json_ext') | |||
|
491 | def repo_commit_comment_delete(self): | |||
|
492 | commit_id = self.request.matchdict['commit_id'] | |||
|
493 | comment_id = self.request.matchdict['comment_id'] | |||
|
494 | ||||
|
495 | comment = ChangesetComment.get_or_404(safe_int(comment_id)) | |||
|
496 | if not comment: | |||
|
497 | log.debug('Comment with id:%s not found, skipping', comment_id) | |||
|
498 | # comment already deleted in another call probably | |||
|
499 | return True | |||
|
500 | ||||
|
501 | is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name) | |||
|
502 | super_admin = h.HasPermissionAny('hg.admin')() | |||
|
503 | comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id) | |||
|
504 | is_repo_comment = comment.repo.repo_name == self.db_repo_name | |||
|
505 | comment_repo_admin = is_repo_admin and is_repo_comment | |||
|
506 | ||||
|
507 | if super_admin or comment_owner or comment_repo_admin: | |||
|
508 | CommentsModel().delete(comment=comment, user=self._rhodecode_db_user) | |||
|
509 | Session().commit() | |||
|
510 | return True | |||
|
511 | else: | |||
|
512 | log.warning('No permissions for user %s to delete comment_id: %s', | |||
|
513 | self._rhodecode_db_user, comment_id) | |||
|
514 | raise HTTPNotFound() | |||
|
515 | ||||
|
516 | @LoginRequired() | |||
|
517 | @HasRepoPermissionAnyDecorator( | |||
|
518 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
519 | @view_config( | |||
|
520 | route_name='repo_commit_data', request_method='GET', | |||
|
521 | renderer='json_ext', xhr=True) | |||
|
522 | def repo_commit_data(self): | |||
|
523 | commit_id = self.request.matchdict['commit_id'] | |||
|
524 | self.load_default_context() | |||
|
525 | ||||
|
526 | try: | |||
|
527 | return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id) | |||
|
528 | except CommitDoesNotExistError as e: | |||
|
529 | return EmptyCommit(message=str(e)) | |||
|
530 | ||||
|
531 | @LoginRequired() | |||
|
532 | @HasRepoPermissionAnyDecorator( | |||
|
533 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
534 | @view_config( | |||
|
535 | route_name='repo_commit_children', request_method='GET', | |||
|
536 | renderer='json_ext', xhr=True) | |||
|
537 | def repo_commit_children(self): | |||
|
538 | commit_id = self.request.matchdict['commit_id'] | |||
|
539 | self.load_default_context() | |||
|
540 | ||||
|
541 | commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id) | |||
|
542 | result = {"results": commit.children} | |||
|
543 | return result | |||
|
544 | ||||
|
545 | @LoginRequired() | |||
|
546 | @HasRepoPermissionAnyDecorator( | |||
|
547 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
548 | @view_config( | |||
|
549 | route_name='repo_commit_parents', request_method='GET', | |||
|
550 | renderer='json_ext') | |||
|
551 | def repo_commit_parents(self): | |||
|
552 | commit_id = self.request.matchdict['commit_id'] | |||
|
553 | self.load_default_context() | |||
|
554 | ||||
|
555 | commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id) | |||
|
556 | result = {"results": commit.parents} | |||
|
557 | return result |
@@ -210,11 +210,12 b' gist_alias_url =' | |||||
210 | ## The list should be "," separated and on a single line. |
|
210 | ## The list should be "," separated and on a single line. | |
211 | ## |
|
211 | ## | |
212 | ## Most common views to enable: |
|
212 | ## Most common views to enable: | |
213 | # ChangesetController:changeset_patch |
|
213 | # RepoCommitsView:repo_commit_download | |
214 | # ChangesetController:changeset_raw |
|
214 | # RepoCommitsView:repo_commit_patch | |
215 |
# Repo |
|
215 | # RepoCommitsView:repo_commit_raw | |
216 |
# RepoFilesView |
|
216 | # RepoFilesView:repo_files_diff | |
217 |
# RepoFilesView |
|
217 | # RepoFilesView:repo_archivefile | |
|
218 | # RepoFilesView:repo_file_raw | |||
218 | # GistView:* |
|
219 | # GistView:* | |
219 | api_access_controllers_whitelist = |
|
220 | api_access_controllers_whitelist = | |
220 |
|
221 |
@@ -184,11 +184,12 b' gist_alias_url =' | |||||
184 | ## The list should be "," separated and on a single line. |
|
184 | ## The list should be "," separated and on a single line. | |
185 | ## |
|
185 | ## | |
186 | ## Most common views to enable: |
|
186 | ## Most common views to enable: | |
187 | # ChangesetController:changeset_patch |
|
187 | # RepoCommitsView:repo_commit_download | |
188 | # ChangesetController:changeset_raw |
|
188 | # RepoCommitsView:repo_commit_patch | |
189 |
# Repo |
|
189 | # RepoCommitsView:repo_commit_raw | |
190 |
# RepoFilesView |
|
190 | # RepoFilesView:repo_files_diff | |
191 |
# RepoFilesView |
|
191 | # RepoFilesView:repo_archivefile | |
|
192 | # RepoFilesView:repo_file_raw | |||
192 | # GistView:* |
|
193 | # GistView:* | |
193 | api_access_controllers_whitelist = |
|
194 | api_access_controllers_whitelist = | |
194 |
|
195 |
@@ -42,7 +42,7 b' archive.' | |||||
42 | ## Syntax is <ControllerClass>:<function_pattern>. |
|
42 | ## Syntax is <ControllerClass>:<function_pattern>. | |
43 | ## The list should be "," separated and on a single line. |
|
43 | ## The list should be "," separated and on a single line. | |
44 | ## |
|
44 | ## | |
45 | api_access_controllers_whitelist = ChangesetController:changeset_patch,ChangesetController:changeset_raw,ilesController:raw,FilesController:archivefile, |
|
45 | api_access_controllers_whitelist = RepoCommitsView:repo_commit_raw,RepoCommitsView:repo_commit_patch,RepoCommitsView:repo_commit_download | |
46 |
|
46 | |||
47 | After this change, a |RCE| view can be accessed without login by adding a |
|
47 | After this change, a |RCE| view can be accessed without login by adding a | |
48 | GET parameter ``?auth_token=<auth_token>`` to a url. For example to |
|
48 | GET parameter ``?auth_token=<auth_token>`` to a url. For example to |
@@ -172,9 +172,9 b' class HomeView(BaseAppView):' | |||||
172 | 'text': entry['commit_id'], |
|
172 | 'text': entry['commit_id'], | |
173 | 'type': 'commit', |
|
173 | 'type': 'commit', | |
174 | 'obj': {'repo': entry['repository']}, |
|
174 | 'obj': {'repo': entry['repository']}, | |
175 |
'url': h. |
|
175 | 'url': h.route_path('repo_commit', | |
176 | repo_name=entry['repository'], |
|
176 | repo_name=entry['repository'], | |
177 |
|
|
177 | commit_id=entry['commit_id']) | |
178 | } |
|
178 | } | |
179 | for entry in result['results']] |
|
179 | for entry in result['results']] | |
180 |
|
180 |
@@ -36,6 +36,8 b' from rhodecode.model.meta import Session' | |||||
36 |
|
36 | |||
37 | fixture = Fixture() |
|
37 | fixture = Fixture() | |
38 |
|
38 | |||
|
39 | whitelist_view = ['RepoCommitsView:repo_commit_raw'] | |||
|
40 | ||||
39 |
|
41 | |||
40 | def route_path(name, params=None, **kwargs): |
|
42 | def route_path(name, params=None, **kwargs): | |
41 | import urllib |
|
43 | import urllib | |
@@ -474,11 +476,10 b' class TestLoginController(object):' | |||||
474 | def test_access_whitelisted_page_via_auth_token( |
|
476 | def test_access_whitelisted_page_via_auth_token( | |
475 | self, test_name, auth_token, code, user_admin): |
|
477 | self, test_name, auth_token, code, user_admin): | |
476 |
|
478 | |||
477 | whitelist_entry = ['ChangesetController:changeset_raw'] |
|
479 | whitelist = self._get_api_whitelist(whitelist_view) | |
478 | whitelist = self._get_api_whitelist(whitelist_entry) |
|
|||
479 |
|
480 | |||
480 | with mock.patch.dict('rhodecode.CONFIG', whitelist): |
|
481 | with mock.patch.dict('rhodecode.CONFIG', whitelist): | |
481 |
assert whitelist_ |
|
482 | assert whitelist_view == whitelist['api_access_controllers_whitelist'] | |
482 |
|
483 | |||
483 | if test_name == 'proper_auth_token': |
|
484 | if test_name == 'proper_auth_token': | |
484 | auth_token = user_admin.api_key |
|
485 | auth_token = user_admin.api_key | |
@@ -492,10 +493,9 b' class TestLoginController(object):' | |||||
492 | status=code) |
|
493 | status=code) | |
493 |
|
494 | |||
494 | def test_access_page_via_extra_auth_token(self): |
|
495 | def test_access_page_via_extra_auth_token(self): | |
495 | whitelist = self._get_api_whitelist( |
|
496 | whitelist = self._get_api_whitelist(whitelist_view) | |
496 | ['ChangesetController:changeset_raw']) |
|
|||
497 | with mock.patch.dict('rhodecode.CONFIG', whitelist): |
|
497 | with mock.patch.dict('rhodecode.CONFIG', whitelist): | |
498 |
assert |
|
498 | assert whitelist_view == \ | |
499 | whitelist['api_access_controllers_whitelist'] |
|
499 | whitelist['api_access_controllers_whitelist'] | |
500 |
|
500 | |||
501 | new_auth_token = AuthTokenModel().create( |
|
501 | new_auth_token = AuthTokenModel().create( | |
@@ -509,10 +509,9 b' class TestLoginController(object):' | |||||
509 | status=200) |
|
509 | status=200) | |
510 |
|
510 | |||
511 | def test_access_page_via_expired_auth_token(self): |
|
511 | def test_access_page_via_expired_auth_token(self): | |
512 | whitelist = self._get_api_whitelist( |
|
512 | whitelist = self._get_api_whitelist(whitelist_view) | |
513 | ['ChangesetController:changeset_raw']) |
|
|||
514 | with mock.patch.dict('rhodecode.CONFIG', whitelist): |
|
513 | with mock.patch.dict('rhodecode.CONFIG', whitelist): | |
515 |
assert |
|
514 | assert whitelist_view == \ | |
516 | whitelist['api_access_controllers_whitelist'] |
|
515 | whitelist['api_access_controllers_whitelist'] | |
517 |
|
516 | |||
518 | new_auth_token = AuthTokenModel().create( |
|
517 | new_auth_token = AuthTokenModel().create( |
@@ -33,10 +33,52 b' def includeme(config):' | |||||
33 | pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True) |
|
33 | pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True) | |
34 |
|
34 | |||
35 | # repo commits |
|
35 | # repo commits | |
|
36 | ||||
36 | config.add_route( |
|
37 | config.add_route( | |
37 | name='repo_commit', |
|
38 | name='repo_commit', | |
38 | pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True) |
|
39 | pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True) | |
39 |
|
40 | |||
|
41 | config.add_route( | |||
|
42 | name='repo_commit_children', | |||
|
43 | pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True) | |||
|
44 | ||||
|
45 | config.add_route( | |||
|
46 | name='repo_commit_parents', | |||
|
47 | pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True) | |||
|
48 | ||||
|
49 | # still working url for backward compat. | |||
|
50 | config.add_route( | |||
|
51 | name='repo_commit_raw_deprecated', | |||
|
52 | pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True) | |||
|
53 | ||||
|
54 | config.add_route( | |||
|
55 | name='repo_commit_raw', | |||
|
56 | pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True) | |||
|
57 | ||||
|
58 | config.add_route( | |||
|
59 | name='repo_commit_patch', | |||
|
60 | pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True) | |||
|
61 | ||||
|
62 | config.add_route( | |||
|
63 | name='repo_commit_download', | |||
|
64 | pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True) | |||
|
65 | ||||
|
66 | config.add_route( | |||
|
67 | name='repo_commit_data', | |||
|
68 | pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True) | |||
|
69 | ||||
|
70 | config.add_route( | |||
|
71 | name='repo_commit_comment_create', | |||
|
72 | pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True) | |||
|
73 | ||||
|
74 | config.add_route( | |||
|
75 | name='repo_commit_comment_preview', | |||
|
76 | pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True) | |||
|
77 | ||||
|
78 | config.add_route( | |||
|
79 | name='repo_commit_comment_delete', | |||
|
80 | pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True) | |||
|
81 | ||||
40 | # repo files |
|
82 | # repo files | |
41 | config.add_route( |
|
83 | config.add_route( | |
42 | name='repo_archivefile', |
|
84 | name='repo_archivefile', | |
@@ -180,21 +222,6 b' def includeme(config):' | |||||
180 | pattern='/{repo_name:.*?[^/]}/pull-request-data', |
|
222 | pattern='/{repo_name:.*?[^/]}/pull-request-data', | |
181 | repo_route=True, repo_accepted_types=['hg', 'git']) |
|
223 | repo_route=True, repo_accepted_types=['hg', 'git']) | |
182 |
|
224 | |||
183 | # commits aka changesets |
|
|||
184 | # TODO(dan): handle default landing revision ? |
|
|||
185 | config.add_route( |
|
|||
186 | name='changeset_home', |
|
|||
187 | pattern='/{repo_name:.*?[^/]}/changeset/{revision}', |
|
|||
188 | repo_route=True) |
|
|||
189 | config.add_route( |
|
|||
190 | name='changeset_children', |
|
|||
191 | pattern='/{repo_name:.*?[^/]}/changeset_children/{revision}', |
|
|||
192 | repo_route=True) |
|
|||
193 | config.add_route( |
|
|||
194 | name='changeset_parents', |
|
|||
195 | pattern='/{repo_name:.*?[^/]}/changeset_parents/{revision}', |
|
|||
196 | repo_route=True) |
|
|||
197 |
|
||||
198 | # Settings |
|
225 | # Settings | |
199 | config.add_route( |
|
226 | config.add_route( | |
200 | name='edit_repo', |
|
227 | name='edit_repo', |
@@ -18,18 +18,33 b'' | |||||
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 |
|
20 | |||
21 | from pylons.i18n import ungettext |
|
|||
22 | import pytest |
|
21 | import pytest | |
23 |
|
22 | |||
24 |
from rhodecode.tests import |
|
23 | from rhodecode.tests import TestController | |
|
24 | ||||
25 | from rhodecode.model.db import ( |
|
25 | from rhodecode.model.db import ( | |
26 | ChangesetComment, Notification, UserNotification) |
|
26 | ChangesetComment, Notification, UserNotification) | |
27 | from rhodecode.model.meta import Session |
|
27 | from rhodecode.model.meta import Session | |
28 | from rhodecode.lib import helpers as h |
|
28 | from rhodecode.lib import helpers as h | |
29 |
|
29 | |||
30 |
|
30 | |||
|
31 | def route_path(name, params=None, **kwargs): | |||
|
32 | import urllib | |||
|
33 | ||||
|
34 | base_url = { | |||
|
35 | 'repo_commit': '/{repo_name}/changeset/{commit_id}', | |||
|
36 | 'repo_commit_comment_create': '/{repo_name}/changeset/{commit_id}/comment/create', | |||
|
37 | 'repo_commit_comment_preview': '/{repo_name}/changeset/{commit_id}/comment/preview', | |||
|
38 | 'repo_commit_comment_delete': '/{repo_name}/changeset/{commit_id}/comment/{comment_id}/delete', | |||
|
39 | }[name].format(**kwargs) | |||
|
40 | ||||
|
41 | if params: | |||
|
42 | base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) | |||
|
43 | return base_url | |||
|
44 | ||||
|
45 | ||||
31 | @pytest.mark.backends("git", "hg", "svn") |
|
46 | @pytest.mark.backends("git", "hg", "svn") | |
32 |
class TestCommitComments |
|
47 | class TestRepoCommitCommentsView(TestController): | |
33 |
|
48 | |||
34 | @pytest.fixture(autouse=True) |
|
49 | @pytest.fixture(autouse=True) | |
35 | def prepare(self, request, pylonsapp): |
|
50 | def prepare(self, request, pylonsapp): | |
@@ -62,12 +77,13 b' class TestCommitCommentsController(TestC' | |||||
62 | params = {'text': text, 'csrf_token': self.csrf_token, |
|
77 | params = {'text': text, 'csrf_token': self.csrf_token, | |
63 | 'comment_type': comment_type} |
|
78 | 'comment_type': comment_type} | |
64 | self.app.post( |
|
79 | self.app.post( | |
65 | url(controller='changeset', action='comment', |
|
80 | route_path('repo_commit_comment_create', | |
66 |
repo_name=backend.repo_name, |
|
81 | repo_name=backend.repo_name, commit_id=commit_id), | |
|
82 | params=params) | |||
67 |
|
83 | |||
68 | response = self.app.get( |
|
84 | response = self.app.get( | |
69 | url(controller='changeset', action='index', |
|
85 | route_path('repo_commit', | |
70 |
repo_name=backend.repo_name, |
|
86 | repo_name=backend.repo_name, commit_id=commit_id)) | |
71 |
|
87 | |||
72 | # test DB |
|
88 | # test DB | |
73 | assert ChangesetComment.query().count() == 1 |
|
89 | assert ChangesetComment.query().count() == 1 | |
@@ -103,12 +119,13 b' class TestCommitCommentsController(TestC' | |||||
103 | 'csrf_token': self.csrf_token} |
|
119 | 'csrf_token': self.csrf_token} | |
104 |
|
120 | |||
105 | self.app.post( |
|
121 | self.app.post( | |
106 | url(controller='changeset', action='comment', |
|
122 | route_path('repo_commit_comment_create', | |
107 |
repo_name=backend.repo_name, |
|
123 | repo_name=backend.repo_name, commit_id=commit_id), | |
|
124 | params=params) | |||
108 |
|
125 | |||
109 | response = self.app.get( |
|
126 | response = self.app.get( | |
110 | url(controller='changeset', action='index', |
|
127 | route_path('repo_commit', | |
111 |
repo_name=backend.repo_name, |
|
128 | repo_name=backend.repo_name, commit_id=commit_id)) | |
112 |
|
129 | |||
113 | # test DB |
|
130 | # test DB | |
114 | assert ChangesetComment.query().count() == 1 |
|
131 | assert ChangesetComment.query().count() == 1 | |
@@ -153,12 +170,13 b' class TestCommitCommentsController(TestC' | |||||
153 |
|
170 | |||
154 | params = {'text': text, 'csrf_token': self.csrf_token} |
|
171 | params = {'text': text, 'csrf_token': self.csrf_token} | |
155 | self.app.post( |
|
172 | self.app.post( | |
156 | url(controller='changeset', action='comment', |
|
173 | route_path('repo_commit_comment_create', | |
157 |
repo_name=backend.repo_name, |
|
174 | repo_name=backend.repo_name, commit_id=commit_id), | |
|
175 | params=params) | |||
158 |
|
176 | |||
159 | response = self.app.get( |
|
177 | response = self.app.get( | |
160 | url(controller='changeset', action='index', |
|
178 | route_path('repo_commit', | |
161 |
repo_name=backend.repo_name, |
|
179 | repo_name=backend.repo_name, commit_id=commit_id)) | |
162 | # test DB |
|
180 | # test DB | |
163 | assert ChangesetComment.query().count() == 1 |
|
181 | assert ChangesetComment.query().count() == 1 | |
164 | assert_comment_links(response, ChangesetComment.query().count(), 0) |
|
182 | assert_comment_links(response, ChangesetComment.query().count(), 0) | |
@@ -183,12 +201,14 b' class TestCommitCommentsController(TestC' | |||||
183 | 'csrf_token': self.csrf_token} |
|
201 | 'csrf_token': self.csrf_token} | |
184 |
|
202 | |||
185 | self.app.post( |
|
203 | self.app.post( | |
186 | url(controller='changeset', action='comment', |
|
204 | route_path( | |
187 | repo_name=backend.repo_name, revision=commit_id), params=params) |
|
205 | 'repo_commit_comment_create', | |
|
206 | repo_name=backend.repo_name, commit_id=commit_id), | |||
|
207 | params=params) | |||
188 |
|
208 | |||
189 | response = self.app.get( |
|
209 | response = self.app.get( | |
190 | url(controller='changeset', action='index', |
|
210 | route_path('repo_commit', | |
191 |
repo_name=backend.repo_name, |
|
211 | repo_name=backend.repo_name, commit_id=commit_id)) | |
192 |
|
212 | |||
193 | # test DB |
|
213 | # test DB | |
194 | assert ChangesetComment.query().count() == 1 |
|
214 | assert ChangesetComment.query().count() == 1 | |
@@ -218,9 +238,9 b' class TestCommitCommentsController(TestC' | |||||
218 |
|
238 | |||
219 | params = {'text': text, 'csrf_token': self.csrf_token} |
|
239 | params = {'text': text, 'csrf_token': self.csrf_token} | |
220 | self.app.post( |
|
240 | self.app.post( | |
221 |
|
|
241 | route_path( | |
222 | controller='changeset', action='comment', |
|
242 | 'repo_commit_comment_create', | |
223 |
repo_name=backend.repo_name, |
|
243 | repo_name=backend.repo_name, commit_id=commit_id), | |
224 | params=params) |
|
244 | params=params) | |
225 |
|
245 | |||
226 | comments = ChangesetComment.query().all() |
|
246 | comments = ChangesetComment.query().all() | |
@@ -228,16 +248,18 b' class TestCommitCommentsController(TestC' | |||||
228 | comment_id = comments[0].comment_id |
|
248 | comment_id = comments[0].comment_id | |
229 |
|
249 | |||
230 | self.app.post( |
|
250 | self.app.post( | |
231 | url(controller='changeset', action='delete_comment', |
|
251 | route_path('repo_commit_comment_delete', | |
232 |
repo_name=backend.repo_name, |
|
252 | repo_name=backend.repo_name, | |
233 | params={'_method': 'delete', 'csrf_token': self.csrf_token}) |
|
253 | commit_id=commit_id, | |
|
254 | comment_id=comment_id), | |||
|
255 | params={'csrf_token': self.csrf_token}) | |||
234 |
|
256 | |||
235 | comments = ChangesetComment.query().all() |
|
257 | comments = ChangesetComment.query().all() | |
236 | assert len(comments) == 0 |
|
258 | assert len(comments) == 0 | |
237 |
|
259 | |||
238 | response = self.app.get( |
|
260 | response = self.app.get( | |
239 | url(controller='changeset', action='index', |
|
261 | route_path('repo_commit', | |
240 |
repo_name=backend.repo_name, |
|
262 | repo_name=backend.repo_name, commit_id=commit_id)) | |
241 | assert_comment_links(response, 0, 0) |
|
263 | assert_comment_links(response, 0, 0) | |
242 |
|
264 | |||
243 | @pytest.mark.parametrize('renderer, input, output', [ |
|
265 | @pytest.mark.parametrize('renderer, input, output', [ | |
@@ -251,36 +273,39 b' class TestCommitCommentsController(TestC' | |||||
251 | ('markdown', '**bold**', '<strong>bold</strong>'), |
|
273 | ('markdown', '**bold**', '<strong>bold</strong>'), | |
252 | ], ids=['rst-plain', 'rst-header', 'rst-italics', 'rst-bold', 'md-plain', |
|
274 | ], ids=['rst-plain', 'rst-header', 'rst-italics', 'rst-bold', 'md-plain', | |
253 | 'md-header', 'md-italics', 'md-bold', ]) |
|
275 | 'md-header', 'md-italics', 'md-bold', ]) | |
254 | def test_preview(self, renderer, input, output, backend): |
|
276 | def test_preview(self, renderer, input, output, backend, xhr_header): | |
255 | self.log_user() |
|
277 | self.log_user() | |
256 | params = { |
|
278 | params = { | |
257 | 'renderer': renderer, |
|
279 | 'renderer': renderer, | |
258 | 'text': input, |
|
280 | 'text': input, | |
259 | 'csrf_token': self.csrf_token |
|
281 | 'csrf_token': self.csrf_token | |
260 | } |
|
282 | } | |
261 | environ = { |
|
283 | commit_id = '0' * 16 # fake this for tests | |
262 | 'HTTP_X_PARTIAL_XHR': 'true' |
|
|||
263 | } |
|
|||
264 | response = self.app.post( |
|
284 | response = self.app.post( | |
265 | url(controller='changeset', |
|
285 | route_path('repo_commit_comment_preview', | |
266 | action='preview_comment', |
|
286 | repo_name=backend.repo_name, commit_id=commit_id,), | |
267 | repo_name=backend.repo_name), |
|
|||
268 | params=params, |
|
287 | params=params, | |
269 |
extra_environ= |
|
288 | extra_environ=xhr_header) | |
270 |
|
289 | |||
271 | response.mustcontain(output) |
|
290 | response.mustcontain(output) | |
272 |
|
291 | |||
273 |
|
292 | |||
274 | def assert_comment_links(response, comments, inline_comments): |
|
293 | def assert_comment_links(response, comments, inline_comments): | |
275 | comments_text = ungettext("%d Commit comment", |
|
294 | if comments == 1: | |
276 |
|
|
295 | comments_text = "%d Commit comment" % comments | |
|
296 | else: | |||
|
297 | comments_text = "%d Commit comments" % comments | |||
|
298 | ||||
|
299 | if inline_comments == 1: | |||
|
300 | inline_comments_text = "%d Inline Comment" % inline_comments | |||
|
301 | else: | |||
|
302 | inline_comments_text = "%d Inline Comments" % inline_comments | |||
|
303 | ||||
277 | if comments: |
|
304 | if comments: | |
278 | response.mustcontain('<a href="#comments">%s</a>,' % comments_text) |
|
305 | response.mustcontain('<a href="#comments">%s</a>,' % comments_text) | |
279 | else: |
|
306 | else: | |
280 | response.mustcontain(comments_text) |
|
307 | response.mustcontain(comments_text) | |
281 |
|
308 | |||
282 | inline_comments_text = ungettext("%d Inline Comment", "%d Inline Comments", |
|
|||
283 | inline_comments) % inline_comments |
|
|||
284 | if inline_comments: |
|
309 | if inline_comments: | |
285 | response.mustcontain( |
|
310 | response.mustcontain( | |
286 | 'id="inline-comments-counter">%s</' % inline_comments_text) |
|
311 | 'id="inline-comments-counter">%s</' % inline_comments_text) |
@@ -21,40 +21,56 b'' | |||||
21 | import pytest |
|
21 | import pytest | |
22 |
|
22 | |||
23 | from rhodecode.lib.helpers import _shorten_commit_id |
|
23 | from rhodecode.lib.helpers import _shorten_commit_id | |
24 | from rhodecode.tests import url |
|
24 | ||
|
25 | ||||
|
26 | def route_path(name, params=None, **kwargs): | |||
|
27 | import urllib | |||
|
28 | ||||
|
29 | base_url = { | |||
|
30 | 'repo_commit': '/{repo_name}/changeset/{commit_id}', | |||
|
31 | 'repo_commit_children': '/{repo_name}/changeset_children/{commit_id}', | |||
|
32 | 'repo_commit_parents': '/{repo_name}/changeset_parents/{commit_id}', | |||
|
33 | 'repo_commit_raw': '/{repo_name}/changeset-diff/{commit_id}', | |||
|
34 | 'repo_commit_patch': '/{repo_name}/changeset-patch/{commit_id}', | |||
|
35 | 'repo_commit_download': '/{repo_name}/changeset-download/{commit_id}', | |||
|
36 | 'repo_commit_data': '/{repo_name}/changeset-data/{commit_id}', | |||
|
37 | 'repo_compare': '/{repo_name}/compare/{source_ref_type}@{source_ref}...{target_ref_type}@{target_ref}', | |||
|
38 | }[name].format(**kwargs) | |||
|
39 | ||||
|
40 | if params: | |||
|
41 | base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) | |||
|
42 | return base_url | |||
25 |
|
43 | |||
26 |
|
44 | |||
27 | @pytest.mark.usefixtures("app") |
|
45 | @pytest.mark.usefixtures("app") | |
28 |
class Test |
|
46 | class TestRepoCommitView(object): | |
29 |
|
47 | |||
30 |
def test_ |
|
48 | def test_show_commit(self, backend): | |
31 | commit_id = self.commit_id[backend.alias] |
|
49 | commit_id = self.commit_id[backend.alias] | |
32 |
response = self.app.get( |
|
50 | response = self.app.get(route_path( | |
33 | controller='changeset', action='index', |
|
51 | 'repo_commit', repo_name=backend.repo_name, commit_id=commit_id)) | |
34 | repo_name=backend.repo_name, revision=commit_id)) |
|
|||
35 | response.mustcontain('Added a symlink') |
|
52 | response.mustcontain('Added a symlink') | |
36 | response.mustcontain(commit_id) |
|
53 | response.mustcontain(commit_id) | |
37 | response.mustcontain('No newline at end of file') |
|
54 | response.mustcontain('No newline at end of file') | |
38 |
|
55 | |||
39 |
def test_ |
|
56 | def test_show_raw(self, backend): | |
40 | commit_id = self.commit_id[backend.alias] |
|
57 | commit_id = self.commit_id[backend.alias] | |
41 |
response = self.app.get( |
|
58 | response = self.app.get(route_path( | |
42 | controller='changeset', action='changeset_raw', |
|
59 | 'repo_commit_raw', | |
43 |
repo_name=backend.repo_name, |
|
60 | repo_name=backend.repo_name, commit_id=commit_id)) | |
44 | assert response.body == self.diffs[backend.alias] |
|
61 | assert response.body == self.diffs[backend.alias] | |
45 |
|
62 | |||
46 |
def test_ |
|
63 | def test_show_raw_patch(self, backend): | |
47 |
response = self.app.get( |
|
64 | response = self.app.get(route_path( | |
48 | controller='changeset', action='changeset_patch', |
|
65 | 'repo_commit_patch', repo_name=backend.repo_name, | |
49 | repo_name=backend.repo_name, |
|
66 | commit_id=self.commit_id[backend.alias])) | |
50 | revision=self.commit_id[backend.alias])) |
|
|||
51 | assert response.body == self.patches[backend.alias] |
|
67 | assert response.body == self.patches[backend.alias] | |
52 |
|
68 | |||
53 |
def test_i |
|
69 | def test_commit_download(self, backend): | |
54 |
response = self.app.get( |
|
70 | response = self.app.get(route_path( | |
55 | controller='changeset', action='changeset_download', |
|
71 | 'repo_commit_download', | |
56 | repo_name=backend.repo_name, |
|
72 | repo_name=backend.repo_name, | |
57 |
|
|
73 | commit_id=self.commit_id[backend.alias])) | |
58 | assert response.body == self.diffs[backend.alias] |
|
74 | assert response.body == self.diffs[backend.alias] | |
59 |
|
75 | |||
60 | def test_single_commit_page_different_ops(self, backend): |
|
76 | def test_single_commit_page_different_ops(self, backend): | |
@@ -64,9 +80,9 b' class TestChangesetController(object):' | |||||
64 | 'svn': '337', |
|
80 | 'svn': '337', | |
65 | } |
|
81 | } | |
66 | commit_id = commit_id[backend.alias] |
|
82 | commit_id = commit_id[backend.alias] | |
67 |
response = self.app.get( |
|
83 | response = self.app.get(route_path( | |
68 | controller='changeset', action='index', |
|
84 | 'repo_commit', | |
69 |
repo_name=backend.repo_name, |
|
85 | repo_name=backend.repo_name, commit_id=commit_id)) | |
70 |
|
86 | |||
71 | response.mustcontain(_shorten_commit_id(commit_id)) |
|
87 | response.mustcontain(_shorten_commit_id(commit_id)) | |
72 | response.mustcontain('21 files changed: 943 inserted, 288 deleted') |
|
88 | response.mustcontain('21 files changed: 943 inserted, 288 deleted') | |
@@ -98,9 +114,9 b' class TestChangesetController(object):' | |||||
98 | } |
|
114 | } | |
99 | commit_ids = commit_id_range[backend.alias] |
|
115 | commit_ids = commit_id_range[backend.alias] | |
100 | commit_id = '%s...%s' % (commit_ids[0], commit_ids[1]) |
|
116 | commit_id = '%s...%s' % (commit_ids[0], commit_ids[1]) | |
101 |
response = self.app.get( |
|
117 | response = self.app.get(route_path( | |
102 | controller='changeset', action='index', |
|
118 | 'repo_commit', | |
103 |
repo_name=backend.repo_name, |
|
119 | repo_name=backend.repo_name, commit_id=commit_id)) | |
104 |
|
120 | |||
105 | response.mustcontain(_shorten_commit_id(commit_ids[0])) |
|
121 | response.mustcontain(_shorten_commit_id(commit_ids[0])) | |
106 | response.mustcontain(_shorten_commit_id(commit_ids[1])) |
|
122 | response.mustcontain(_shorten_commit_id(commit_ids[1])) | |
@@ -137,8 +153,8 b' class TestChangesetController(object):' | |||||
137 | '337'), |
|
153 | '337'), | |
138 | } |
|
154 | } | |
139 | commit_ids = commit_id_range[backend.alias] |
|
155 | commit_ids = commit_id_range[backend.alias] | |
140 |
response = self.app.get( |
|
156 | response = self.app.get(route_path( | |
141 | controller='compare', action='compare', |
|
157 | 'repo_compare', | |
142 | repo_name=backend.repo_name, |
|
158 | repo_name=backend.repo_name, | |
143 | source_ref_type='rev', source_ref=commit_ids[0], |
|
159 | source_ref_type='rev', source_ref=commit_ids[0], | |
144 | target_ref_type='rev', target_ref=commit_ids[1], )) |
|
160 | target_ref_type='rev', target_ref=commit_ids[1], )) | |
@@ -188,9 +204,10 b' class TestChangesetController(object):' | |||||
188 | def _check_changeset_range( |
|
204 | def _check_changeset_range( | |
189 | self, backend, commit_id_ranges, commit_id_range_result): |
|
205 | self, backend, commit_id_ranges, commit_id_range_result): | |
190 | response = self.app.get( |
|
206 | response = self.app.get( | |
191 | url(controller='changeset', action='index', |
|
207 | route_path('repo_commit', | |
192 | repo_name=backend.repo_name, |
|
208 | repo_name=backend.repo_name, | |
193 |
|
|
209 | commit_id=commit_id_ranges[backend.alias])) | |
|
210 | ||||
194 | expected_result = commit_id_range_result[backend.alias] |
|
211 | expected_result = commit_id_range_result[backend.alias] | |
195 | response.mustcontain('{} commits'.format(len(expected_result))) |
|
212 | response.mustcontain('{} commits'.format(len(expected_result))) | |
196 | for commit_id in expected_result: |
|
213 | for commit_id in expected_result: |
@@ -139,8 +139,8 b' class RepoFeedView(RepoAppView):' | |||||
139 | author_name=commit.author, |
|
139 | author_name=commit.author, | |
140 | description=self._get_description(commit), |
|
140 | description=self._get_description(commit), | |
141 | link=h.route_url( |
|
141 | link=h.route_url( | |
142 |
' |
|
142 | 'repo_commit', repo_name=self.db_repo_name, | |
143 |
|
|
143 | commit_id=commit.raw_id), | |
144 | pubdate=date,) |
|
144 | pubdate=date,) | |
145 |
|
145 | |||
146 | return feed.mime_type, feed.writeString('utf-8') |
|
146 | return feed.mime_type, feed.writeString('utf-8') | |
@@ -185,8 +185,8 b' class RepoFeedView(RepoAppView):' | |||||
185 | author_name=commit.author, |
|
185 | author_name=commit.author, | |
186 | description=self._get_description(commit), |
|
186 | description=self._get_description(commit), | |
187 | link=h.route_url( |
|
187 | link=h.route_url( | |
188 |
' |
|
188 | 'repo_commit', repo_name=self.db_repo_name, | |
189 |
|
|
189 | commit_id=commit.raw_id), | |
190 | pubdate=date,) |
|
190 | pubdate=date,) | |
191 |
|
191 | |||
192 | return feed.mime_type, feed.writeString('utf-8') |
|
192 | return feed.mime_type, feed.writeString('utf-8') |
@@ -1043,8 +1043,8 b' class RepoFilesView(RepoAppView):' | |||||
1043 | log.exception('Error during commit operation') |
|
1043 | log.exception('Error during commit operation') | |
1044 | h.flash(_('Error occurred during commit'), category='error') |
|
1044 | h.flash(_('Error occurred during commit'), category='error') | |
1045 | raise HTTPFound( |
|
1045 | raise HTTPFound( | |
1046 |
h.route_path(' |
|
1046 | h.route_path('repo_commit', repo_name=self.db_repo_name, | |
1047 |
|
|
1047 | commit_id='tip')) | |
1048 |
|
1048 | |||
1049 | @LoginRequired() |
|
1049 | @LoginRequired() | |
1050 | @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin') |
|
1050 | @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin') | |
@@ -1133,8 +1133,8 b' class RepoFilesView(RepoAppView):' | |||||
1133 | if content == old_content and filename == org_filename: |
|
1133 | if content == old_content and filename == org_filename: | |
1134 | h.flash(_('No changes'), category='warning') |
|
1134 | h.flash(_('No changes'), category='warning') | |
1135 | raise HTTPFound( |
|
1135 | raise HTTPFound( | |
1136 |
h.route_path(' |
|
1136 | h.route_path('repo_commit', repo_name=self.db_repo_name, | |
1137 |
|
|
1137 | commit_id='tip')) | |
1138 | try: |
|
1138 | try: | |
1139 | mapping = { |
|
1139 | mapping = { | |
1140 | org_f_path: { |
|
1140 | org_f_path: { | |
@@ -1161,8 +1161,8 b' class RepoFilesView(RepoAppView):' | |||||
1161 | log.exception('Error occurred during commit') |
|
1161 | log.exception('Error occurred during commit') | |
1162 | h.flash(_('Error occurred during commit'), category='error') |
|
1162 | h.flash(_('Error occurred during commit'), category='error') | |
1163 | raise HTTPFound( |
|
1163 | raise HTTPFound( | |
1164 |
h.route_path(' |
|
1164 | h.route_path('repo_commit', repo_name=self.db_repo_name, | |
1165 |
|
|
1165 | commit_id='tip')) | |
1166 |
|
1166 | |||
1167 | @LoginRequired() |
|
1167 | @LoginRequired() | |
1168 | @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin') |
|
1168 | @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin') | |
@@ -1222,7 +1222,7 b' class RepoFilesView(RepoAppView):' | |||||
1222 | content = content.file |
|
1222 | content = content.file | |
1223 |
|
1223 | |||
1224 | default_redirect_url = h.route_path( |
|
1224 | default_redirect_url = h.route_path( | |
1225 |
' |
|
1225 | 'repo_commit', repo_name=self.db_repo_name, commit_id='tip') | |
1226 |
|
1226 | |||
1227 | # If there's no commit, redirect to repo summary |
|
1227 | # If there's no commit, redirect to repo summary | |
1228 | if type(c.commit) is EmptyCommit: |
|
1228 | if type(c.commit) is EmptyCommit: |
@@ -429,19 +429,6 b' def make_map(config):' | |||||
429 | controller='admin/repos', action='repo_check', |
|
429 | controller='admin/repos', action='repo_check', | |
430 | requirements=URL_NAME_REQUIREMENTS) |
|
430 | requirements=URL_NAME_REQUIREMENTS) | |
431 |
|
431 | |||
432 | rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}', |
|
|||
433 | controller='changeset', revision='tip', |
|
|||
434 | conditions={'function': check_repo}, |
|
|||
435 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
|||
436 | rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}', |
|
|||
437 | controller='changeset', revision='tip', action='changeset_children', |
|
|||
438 | conditions={'function': check_repo}, |
|
|||
439 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
440 | rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}', |
|
|||
441 | controller='changeset', revision='tip', action='changeset_parents', |
|
|||
442 | conditions={'function': check_repo}, |
|
|||
443 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
444 |
|
||||
445 | # repo edit options |
|
432 | # repo edit options | |
446 | rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields', |
|
433 | rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields', | |
447 | controller='admin/repos', action='edit_fields', |
|
434 | controller='admin/repos', action='edit_fields', | |
@@ -515,54 +502,6 b' def make_map(config):' | |||||
515 | conditions={'method': ['GET', 'POST'], 'function': check_repo}, |
|
502 | conditions={'method': ['GET', 'POST'], 'function': check_repo}, | |
516 | requirements=URL_NAME_REQUIREMENTS) |
|
503 | requirements=URL_NAME_REQUIREMENTS) | |
517 |
|
504 | |||
518 | # still working url for backward compat. |
|
|||
519 | rmap.connect('raw_changeset_home_depraced', |
|
|||
520 | '/{repo_name}/raw-changeset/{revision}', |
|
|||
521 | controller='changeset', action='changeset_raw', |
|
|||
522 | revision='tip', conditions={'function': check_repo}, |
|
|||
523 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
524 |
|
||||
525 | # new URLs |
|
|||
526 | rmap.connect('changeset_raw_home', |
|
|||
527 | '/{repo_name}/changeset-diff/{revision}', |
|
|||
528 | controller='changeset', action='changeset_raw', |
|
|||
529 | revision='tip', conditions={'function': check_repo}, |
|
|||
530 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
531 |
|
||||
532 | rmap.connect('changeset_patch_home', |
|
|||
533 | '/{repo_name}/changeset-patch/{revision}', |
|
|||
534 | controller='changeset', action='changeset_patch', |
|
|||
535 | revision='tip', conditions={'function': check_repo}, |
|
|||
536 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
537 |
|
||||
538 | rmap.connect('changeset_download_home', |
|
|||
539 | '/{repo_name}/changeset-download/{revision}', |
|
|||
540 | controller='changeset', action='changeset_download', |
|
|||
541 | revision='tip', conditions={'function': check_repo}, |
|
|||
542 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
543 |
|
||||
544 | rmap.connect('changeset_comment', |
|
|||
545 | '/{repo_name}/changeset/{revision}/comment', jsroute=True, |
|
|||
546 | controller='changeset', revision='tip', action='comment', |
|
|||
547 | conditions={'function': check_repo}, |
|
|||
548 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
549 |
|
||||
550 | rmap.connect('changeset_comment_preview', |
|
|||
551 | '/{repo_name}/changeset/comment/preview', jsroute=True, |
|
|||
552 | controller='changeset', action='preview_comment', |
|
|||
553 | conditions={'function': check_repo, 'method': ['POST']}, |
|
|||
554 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
555 |
|
||||
556 | rmap.connect('changeset_comment_delete', |
|
|||
557 | '/{repo_name}/changeset/comment/{comment_id}/delete', |
|
|||
558 | controller='changeset', action='delete_comment', |
|
|||
559 | conditions={'function': check_repo, 'method': ['DELETE']}, |
|
|||
560 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
|||
561 |
|
||||
562 | rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}', |
|
|||
563 | controller='changeset', action='changeset_info', |
|
|||
564 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
|||
565 |
|
||||
566 | rmap.connect('compare_home', |
|
505 | rmap.connect('compare_home', | |
567 | '/{repo_name}/compare', |
|
506 | '/{repo_name}/compare', | |
568 | controller='compare', action='index', |
|
507 | controller='compare', action='index', |
@@ -20,7 +20,6 b'' | |||||
20 |
|
20 | |||
21 | import logging |
|
21 | import logging | |
22 |
|
22 | |||
23 | from pylons import url |
|
|||
24 | from pylons.i18n.translation import _ |
|
23 | from pylons.i18n.translation import _ | |
25 | from webhelpers.html.builder import literal |
|
24 | from webhelpers.html.builder import literal | |
26 | from webhelpers.html.tags import link_to |
|
25 | from webhelpers.html.tags import link_to | |
@@ -201,6 +200,7 b' class ActionParser(object):' | |||||
201 | return literal(tmpl % (ico, self.action)) |
|
200 | return literal(tmpl % (ico, self.action)) | |
202 |
|
201 | |||
203 | def get_cs_links(self): |
|
202 | def get_cs_links(self): | |
|
203 | from rhodecode.lib import helpers as h | |||
204 | if self.is_deleted(): |
|
204 | if self.is_deleted(): | |
205 | return self.action_params |
|
205 | return self.action_params | |
206 |
|
206 | |||
@@ -223,8 +223,9 b' class ActionParser(object):' | |||||
223 | _('Show all combined commits %s->%s') % ( |
|
223 | _('Show all combined commits %s->%s') % ( | |
224 | commit_ids[0][:12], commit_ids[-1][:12] |
|
224 | commit_ids[0][:12], commit_ids[-1][:12] | |
225 | ), |
|
225 | ), | |
226 | url('changeset_home', repo_name=repo_name, |
|
226 | h.route_path( | |
227 | revision=commit_id_range), _('compare view') |
|
227 | 'repo_commit', repo_name=repo_name, | |
|
228 | commit_id=commit_id_range), _('compare view') | |||
228 | ) |
|
229 | ) | |
229 | ) |
|
230 | ) | |
230 |
|
231 | |||
@@ -275,6 +276,7 b' class ActionParser(object):' | |||||
275 |
|
276 | |||
276 | def lnk(self, commit_or_id, repo_name): |
|
277 | def lnk(self, commit_or_id, repo_name): | |
277 | from rhodecode.lib.helpers import tooltip |
|
278 | from rhodecode.lib.helpers import tooltip | |
|
279 | from rhodecode.lib import helpers as h | |||
278 |
|
280 | |||
279 | if isinstance(commit_or_id, (BaseCommit, AttributeDict)): |
|
281 | if isinstance(commit_or_id, (BaseCommit, AttributeDict)): | |
280 | lazy_cs = True |
|
282 | lazy_cs = True | |
@@ -292,8 +294,8 b' class ActionParser(object):' | |||||
292 |
|
294 | |||
293 | else: |
|
295 | else: | |
294 | lbl = '%s' % (commit_or_id.short_id[:8]) |
|
296 | lbl = '%s' % (commit_or_id.short_id[:8]) | |
295 |
_url = |
|
297 | _url = h.route_path('repo_commit', repo_name=repo_name, | |
296 |
|
|
298 | commit_id=commit_or_id.raw_id) | |
297 | title = tooltip(commit_or_id.message) |
|
299 | title = tooltip(commit_or_id.message) | |
298 | else: |
|
300 | else: | |
299 | # commit cannot be found/striped/removed etc. |
|
301 | # commit cannot be found/striped/removed etc. |
@@ -754,7 +754,7 b' class PermissionCalculator(object):' | |||||
754 | } |
|
754 | } | |
755 |
|
755 | |||
756 |
|
756 | |||
757 |
def allowed_auth_token_access( |
|
757 | def allowed_auth_token_access(view_name, whitelist=None, auth_token=None): | |
758 | """ |
|
758 | """ | |
759 | Check if given controller_name is in whitelist of auth token access |
|
759 | Check if given controller_name is in whitelist of auth token access | |
760 | """ |
|
760 | """ | |
@@ -767,16 +767,16 b' def allowed_auth_token_access(controller' | |||||
767 |
|
767 | |||
768 | auth_token_access_valid = False |
|
768 | auth_token_access_valid = False | |
769 | for entry in whitelist: |
|
769 | for entry in whitelist: | |
770 |
if fnmatch.fnmatch( |
|
770 | if fnmatch.fnmatch(view_name, entry): | |
771 | auth_token_access_valid = True |
|
771 | auth_token_access_valid = True | |
772 | break |
|
772 | break | |
773 |
|
773 | |||
774 | if auth_token_access_valid: |
|
774 | if auth_token_access_valid: | |
775 |
log.debug(' |
|
775 | log.debug('view: `%s` matches entry in whitelist: %s' | |
776 |
% ( |
|
776 | % (view_name, whitelist)) | |
777 | else: |
|
777 | else: | |
778 |
msg = (' |
|
778 | msg = ('view: `%s` does *NOT* match any entry in whitelist: %s' | |
779 |
% ( |
|
779 | % (view_name, whitelist)) | |
780 | if auth_token: |
|
780 | if auth_token: | |
781 | # if we use auth token key and don't have access it's a warning |
|
781 | # if we use auth token key and don't have access it's a warning | |
782 | log.warning(msg) |
|
782 | log.warning(msg) |
@@ -1575,7 +1575,7 b' def urlify_commits(text_, repository):' | |||||
1575 | :param text_: |
|
1575 | :param text_: | |
1576 | :param repository: repo name to build the URL with |
|
1576 | :param repository: repo name to build the URL with | |
1577 | """ |
|
1577 | """ | |
1578 | from pylons import url # doh, we need to re-import url to mock it later |
|
1578 | ||
1579 | URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)') |
|
1579 | URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)') | |
1580 |
|
1580 | |||
1581 | def url_func(match_obj): |
|
1581 | def url_func(match_obj): | |
@@ -1590,8 +1590,8 b' def urlify_commits(text_, repository):' | |||||
1590 | return tmpl % { |
|
1590 | return tmpl % { | |
1591 | 'pref': pref, |
|
1591 | 'pref': pref, | |
1592 | 'cls': 'revision-link', |
|
1592 | 'cls': 'revision-link', | |
1593 |
'url': url(' |
|
1593 | 'url': route_url('repo_commit', repo_name=repository, | |
1594 |
|
|
1594 | commit_id=commit_id), | |
1595 | 'commit_id': commit_id, |
|
1595 | 'commit_id': commit_id, | |
1596 | 'suf': suf |
|
1596 | 'suf': suf | |
1597 | } |
|
1597 | } |
@@ -15,11 +15,6 b' function registerRCRoutes() {' | |||||
15 | pyroutes.register('new_repo', '/_admin/create_repository', []); |
|
15 | pyroutes.register('new_repo', '/_admin/create_repository', []); | |
16 | pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']); |
|
16 | pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']); | |
17 | pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']); |
|
17 | pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']); | |
18 | pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']); |
|
|||
19 | pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']); |
|
|||
20 | pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']); |
|
|||
21 | pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']); |
|
|||
22 | pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']); |
|
|||
23 | pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']); |
|
18 | pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']); | |
24 | pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']); |
|
19 | pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']); | |
25 | pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']); |
|
20 | pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']); | |
@@ -111,6 +106,16 b' function registerRCRoutes() {' | |||||
111 | pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']); |
|
106 | pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']); | |
112 | pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']); |
|
107 | pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']); | |
113 | pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']); |
|
108 | pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']); | |
|
109 | pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']); | |||
|
110 | pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']); | |||
|
111 | pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']); | |||
|
112 | pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']); | |||
|
113 | pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']); | |||
|
114 | pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']); | |||
|
115 | pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']); | |||
|
116 | pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']); | |||
|
117 | pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']); | |||
|
118 | pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']); | |||
114 | pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']); |
|
119 | pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']); | |
115 | pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']); |
|
120 | pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']); | |
116 | pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']); |
|
121 | pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']); | |
@@ -146,9 +151,6 b' function registerRCRoutes() {' | |||||
146 | pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']); |
|
151 | pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']); | |
147 | pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']); |
|
152 | pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']); | |
148 | pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']); |
|
153 | pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']); | |
149 | pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']); |
|
|||
150 | pyroutes.register('changeset_children', '/%(repo_name)s/changeset_children/%(revision)s', ['repo_name', 'revision']); |
|
|||
151 | pyroutes.register('changeset_parents', '/%(repo_name)s/changeset_parents/%(revision)s', ['repo_name', 'revision']); |
|
|||
152 | pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']); |
|
154 | pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']); | |
153 | pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']); |
|
155 | pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']); | |
154 | pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']); |
|
156 | pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']); |
@@ -103,8 +103,9 b' var bindToggleButtons = function() {' | |||||
103 | this.submitButton = $(this.submitForm).find('input[type="submit"]'); |
|
103 | this.submitButton = $(this.submitForm).find('input[type="submit"]'); | |
104 | this.submitButtonText = this.submitButton.val(); |
|
104 | this.submitButtonText = this.submitButton.val(); | |
105 |
|
105 | |||
106 |
this.previewUrl = pyroutes.url(' |
|
106 | this.previewUrl = pyroutes.url('repo_commit_comment_preview', | |
107 |
{'repo_name': templateContext.repo_name |
|
107 | {'repo_name': templateContext.repo_name, | |
|
108 | 'commit_id': templateContext.commit_data.commit_id}); | |||
108 |
|
109 | |||
109 | if (resolvesCommentId){ |
|
110 | if (resolvesCommentId){ | |
110 | this.resolvesId = '#resolve_comment_{0}'.format(resolvesCommentId); |
|
111 | this.resolvesId = '#resolve_comment_{0}'.format(resolvesCommentId); | |
@@ -129,12 +130,12 b' var bindToggleButtons = function() {' | |||||
129 | // based on commitId, or pullRequestId decide where do we submit |
|
130 | // based on commitId, or pullRequestId decide where do we submit | |
130 | // out data |
|
131 | // out data | |
131 | if (this.commitId){ |
|
132 | if (this.commitId){ | |
132 |
this.submitUrl = pyroutes.url(' |
|
133 | this.submitUrl = pyroutes.url('repo_commit_comment_create', | |
133 | {'repo_name': templateContext.repo_name, |
|
134 | {'repo_name': templateContext.repo_name, | |
134 |
' |
|
135 | 'commit_id': this.commitId}); | |
135 |
this.selfUrl = pyroutes.url(' |
|
136 | this.selfUrl = pyroutes.url('repo_commit', | |
136 | {'repo_name': templateContext.repo_name, |
|
137 | {'repo_name': templateContext.repo_name, | |
137 |
' |
|
138 | 'commit_id': this.commitId}); | |
138 |
|
139 | |||
139 | } else if (this.pullRequestId) { |
|
140 | } else if (this.pullRequestId) { | |
140 | this.submitUrl = pyroutes.url('pullrequest_comment', |
|
141 | this.submitUrl = pyroutes.url('pullrequest_comment', |
@@ -5,7 +5,7 b'' | |||||
5 | (_('Owner'), lambda:base.gravatar_with_user(c.repo_info.user.email), '', ''), |
|
5 | (_('Owner'), lambda:base.gravatar_with_user(c.repo_info.user.email), '', ''), | |
6 | (_('Created on'), h.format_date(c.repo_info.created_on), '', ''), |
|
6 | (_('Created on'), h.format_date(c.repo_info.created_on), '', ''), | |
7 | (_('Updated on'), h.format_date(c.repo_info.updated_on), '', ''), |
|
7 | (_('Updated on'), h.format_date(c.repo_info.updated_on), '', ''), | |
8 |
(_('Cached Commit id'), lambda: h.link_to(c.repo_info.changeset_cache.get('short_id'), h. |
|
8 | (_('Cached Commit id'), lambda: h.link_to(c.repo_info.changeset_cache.get('short_id'), h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.repo_info.changeset_cache.get('raw_id'))), '', ''), | |
9 | ] |
|
9 | ] | |
10 | %> |
|
10 | %> | |
11 |
|
11 |
@@ -161,9 +161,9 b'' | |||||
161 | if (selectedCheckboxes.length>0){ |
|
161 | if (selectedCheckboxes.length>0){ | |
162 | var revEnd = selectedCheckboxes[0].name; |
|
162 | var revEnd = selectedCheckboxes[0].name; | |
163 | var revStart = selectedCheckboxes[selectedCheckboxes.length-1].name; |
|
163 | var revStart = selectedCheckboxes[selectedCheckboxes.length-1].name; | |
164 |
var url = pyroutes.url(' |
|
164 | var url = pyroutes.url('repo_commit', | |
165 | {'repo_name': '${c.repo_name}', |
|
165 | {'repo_name': '${c.repo_name}', | |
166 |
' |
|
166 | 'commit_id': revStart+'...'+revEnd}); | |
167 |
|
167 | |||
168 | var link = (revStart == revEnd) |
|
168 | var link = (revStart == revEnd) | |
169 | ? _gettext('Show selected commit __S') |
|
169 | ? _gettext('Show selected commit __S') |
@@ -24,11 +24,11 b'' | |||||
24 | <div class="changeset-status-ico"> |
|
24 | <div class="changeset-status-ico"> | |
25 | %if c.statuses.get(commit.raw_id)[2]: |
|
25 | %if c.statuses.get(commit.raw_id)[2]: | |
26 | <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]), c.statuses.get(commit.raw_id)[2])}" href="${h.route_path('pullrequest_show',repo_name=c.statuses.get(commit.raw_id)[3],pull_request_id=c.statuses.get(commit.raw_id)[2])}"> |
|
26 | <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]), c.statuses.get(commit.raw_id)[2])}" href="${h.route_path('pullrequest_show',repo_name=c.statuses.get(commit.raw_id)[3],pull_request_id=c.statuses.get(commit.raw_id)[2])}"> | |
27 |
<div class="${'flag_status |
|
27 | <div class="${'flag_status {}'.format(c.statuses.get(commit.raw_id)[0])}"></div> | |
28 | </a> |
|
28 | </a> | |
29 | %else: |
|
29 | %else: | |
30 |
<a class="tooltip" title="${_('Commit status: |
|
30 | <a class="tooltip" title="${_('Commit status: {}').format(h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]))}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id,_anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}"> | |
31 |
<div class="${'flag_status |
|
31 | <div class="${'flag_status {}'.format(c.statuses.get(commit.raw_id)[0])}"></div> | |
32 | </a> |
|
32 | </a> | |
33 | %endif |
|
33 | %endif | |
34 | </div> |
|
34 | </div> | |
@@ -38,7 +38,7 b'' | |||||
38 | </td> |
|
38 | </td> | |
39 | <td class="td-comments comments-col"> |
|
39 | <td class="td-comments comments-col"> | |
40 | %if c.comments.get(commit.raw_id): |
|
40 | %if c.comments.get(commit.raw_id): | |
41 |
<a title="${_('Commit has comments')}" href="${h. |
|
41 | <a title="${_('Commit has comments')}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id,_anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}"> | |
42 | <i class="icon-comment"></i> ${len(c.comments[commit.raw_id])} |
|
42 | <i class="icon-comment"></i> ${len(c.comments[commit.raw_id])} | |
43 | </a> |
|
43 | </a> | |
44 | %endif |
|
44 | %endif | |
@@ -46,7 +46,7 b'' | |||||
46 | <td class="td-hash"> |
|
46 | <td class="td-hash"> | |
47 | <code> |
|
47 | <code> | |
48 |
|
48 | |||
49 |
<a href="${h. |
|
49 | <a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id)}"> | |
50 | <span class="${'commit_hash obsolete' if getattr(commit, 'obsolete', None) else 'commit_hash'}">${h.show_id(commit)}</span> |
|
50 | <span class="${'commit_hash obsolete' if getattr(commit, 'obsolete', None) else 'commit_hash'}">${h.show_id(commit)}</span> | |
51 | </a> |
|
51 | </a> | |
52 | <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${commit.raw_id}" title="${_('Copy the full commit id')}"></i> |
|
52 | <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${commit.raw_id}" title="${_('Copy the full commit id')}"></i> |
@@ -15,7 +15,7 b'' | |||||
15 | <td class="td-message"> |
|
15 | <td class="td-message"> | |
16 | <div class="log-container"> |
|
16 | <div class="log-container"> | |
17 | <div class="message_history" title="${h.tooltip(cs.message)}"> |
|
17 | <div class="message_history" title="${h.tooltip(cs.message)}"> | |
18 |
<a href="${h. |
|
18 | <a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=cs.raw_id)}"> | |
19 | ${h.shorter(cs.message, 75)} |
|
19 | ${h.shorter(cs.message, 75)} | |
20 | </a> |
|
20 | </a> | |
21 | </div> |
|
21 | </div> | |
@@ -23,7 +23,7 b'' | |||||
23 | </td> |
|
23 | </td> | |
24 | <td class="td-hash"> |
|
24 | <td class="td-hash"> | |
25 | <code> |
|
25 | <code> | |
26 |
<a href="${h. |
|
26 | <a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=cs.raw_id)}"> | |
27 | <span>${h.show_id(cs)}</span> |
|
27 | <span>${h.show_id(cs)}</span> | |
28 | </a> |
|
28 | </a> | |
29 | </code> |
|
29 | </code> |
@@ -21,7 +21,7 b'' | |||||
21 | <%def name="main()"> |
|
21 | <%def name="main()"> | |
22 | <script> |
|
22 | <script> | |
23 | // TODO: marcink switch this to pyroutes |
|
23 | // TODO: marcink switch this to pyroutes | |
24 |
AJAX_COMMENT_DELETE_URL = "${h. |
|
24 | AJAX_COMMENT_DELETE_URL = "${h.route_path('repo_commit_comment_delete',repo_name=c.repo_name,commit_id=c.commit.raw_id,comment_id='__COMMENT_ID__')}"; | |
25 | templateContext.commit_data.commit_id = "${c.commit.raw_id}"; |
|
25 | templateContext.commit_data.commit_id = "${c.commit.raw_id}"; | |
26 | </script> |
|
26 | </script> | |
27 | <div class="box"> |
|
27 | <div class="box"> | |
@@ -137,21 +137,21 b'' | |||||
137 | </div> |
|
137 | </div> | |
138 | <div class="right-content"> |
|
138 | <div class="right-content"> | |
139 | <div class="diff-actions"> |
|
139 | <div class="diff-actions"> | |
140 |
<a href="${h. |
|
140 | <a href="${h.route_path('repo_commit_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Raw diff'))}"> | |
141 | ${_('Raw Diff')} |
|
141 | ${_('Raw Diff')} | |
142 | </a> |
|
142 | </a> | |
143 | | |
|
143 | | | |
144 |
<a href="${h. |
|
144 | <a href="${h.route_path('repo_commit_patch',repo_name=c.repo_name,commit_id=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Patch diff'))}"> | |
145 | ${_('Patch Diff')} |
|
145 | ${_('Patch Diff')} | |
146 | </a> |
|
146 | </a> | |
147 | | |
|
147 | | | |
148 |
<a href="${h. |
|
148 | <a href="${h.route_path('repo_commit_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(diff='download'))}" class="tooltip" title="${h.tooltip(_('Download diff'))}"> | |
149 | ${_('Download Diff')} |
|
149 | ${_('Download Diff')} | |
150 | </a> |
|
150 | </a> | |
151 | | |
|
151 | | | |
152 |
${c.ignorews_url(request |
|
152 | ${c.ignorews_url(request)} | |
153 | | |
|
153 | | | |
154 |
${c.context_url(request |
|
154 | ${c.context_url(request)} | |
155 | </div> |
|
155 | </div> | |
156 | </div> |
|
156 | </div> | |
157 | </div> |
|
157 | </div> | |
@@ -221,7 +221,7 b'' | |||||
221 | ${comment.generate_comments(c.comments)} |
|
221 | ${comment.generate_comments(c.comments)} | |
222 |
|
222 | |||
223 | ## main comment form and it status |
|
223 | ## main comment form and it status | |
224 |
${comment.comments(h. |
|
224 | ${comment.comments(h.route_path('repo_commit_comment_create', repo_name=c.repo_name, commit_id=c.commit.raw_id), | |
225 | h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))} |
|
225 | h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))} | |
226 | </div> |
|
226 | </div> | |
227 |
|
227 | |||
@@ -264,14 +264,14 b'' | |||||
264 | // >1 links show them to user to choose |
|
264 | // >1 links show them to user to choose | |
265 | if(!$('#child_link').hasClass('disabled')){ |
|
265 | if(!$('#child_link').hasClass('disabled')){ | |
266 | $.ajax({ |
|
266 | $.ajax({ | |
267 |
url: '${h. |
|
267 | url: '${h.route_path('repo_commit_children',repo_name=c.repo_name, commit_id=c.commit.raw_id)}', | |
268 | success: function(data) { |
|
268 | success: function(data) { | |
269 | if(data.results.length === 0){ |
|
269 | if(data.results.length === 0){ | |
270 | $('#child_link').html("${_('No Child Commits')}").addClass('disabled'); |
|
270 | $('#child_link').html("${_('No Child Commits')}").addClass('disabled'); | |
271 | } |
|
271 | } | |
272 | if(data.results.length === 1){ |
|
272 | if(data.results.length === 1){ | |
273 | var commit = data.results[0]; |
|
273 | var commit = data.results[0]; | |
274 |
window.location = pyroutes.url(' |
|
274 | window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id}); | |
275 | } |
|
275 | } | |
276 | else if(data.results.length === 2){ |
|
276 | else if(data.results.length === 2){ | |
277 | $('#child_link').addClass('disabled'); |
|
277 | $('#child_link').addClass('disabled'); | |
@@ -280,12 +280,12 b'' | |||||
280 | _html +='<a title="__title__" href="__url__">__rev__</a> ' |
|
280 | _html +='<a title="__title__" href="__url__">__rev__</a> ' | |
281 | .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6))) |
|
281 | .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6))) | |
282 | .replace('__title__', data.results[0].message) |
|
282 | .replace('__title__', data.results[0].message) | |
283 |
.replace('__url__', pyroutes.url(' |
|
283 | .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id})); | |
284 | _html +=' | '; |
|
284 | _html +=' | '; | |
285 | _html +='<a title="__title__" href="__url__">__rev__</a> ' |
|
285 | _html +='<a title="__title__" href="__url__">__rev__</a> ' | |
286 | .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6))) |
|
286 | .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6))) | |
287 | .replace('__title__', data.results[1].message) |
|
287 | .replace('__title__', data.results[1].message) | |
288 |
.replace('__url__', pyroutes.url(' |
|
288 | .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id})); | |
289 | $('#child_link').html(_html); |
|
289 | $('#child_link').html(_html); | |
290 | } |
|
290 | } | |
291 | } |
|
291 | } | |
@@ -300,14 +300,14 b'' | |||||
300 | // >1 links show them to user to choose |
|
300 | // >1 links show them to user to choose | |
301 | if(!$('#parent_link').hasClass('disabled')){ |
|
301 | if(!$('#parent_link').hasClass('disabled')){ | |
302 | $.ajax({ |
|
302 | $.ajax({ | |
303 |
url: '${h. |
|
303 | url: '${h.route_path("repo_commit_parents",repo_name=c.repo_name, commit_id=c.commit.raw_id)}', | |
304 | success: function(data) { |
|
304 | success: function(data) { | |
305 | if(data.results.length === 0){ |
|
305 | if(data.results.length === 0){ | |
306 | $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled'); |
|
306 | $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled'); | |
307 | } |
|
307 | } | |
308 | if(data.results.length === 1){ |
|
308 | if(data.results.length === 1){ | |
309 | var commit = data.results[0]; |
|
309 | var commit = data.results[0]; | |
310 |
window.location = pyroutes.url(' |
|
310 | window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id}); | |
311 | } |
|
311 | } | |
312 | else if(data.results.length === 2){ |
|
312 | else if(data.results.length === 2){ | |
313 | $('#parent_link').addClass('disabled'); |
|
313 | $('#parent_link').addClass('disabled'); | |
@@ -316,12 +316,12 b'' | |||||
316 | _html +='<a title="__title__" href="__url__">Parent __rev__</a>' |
|
316 | _html +='<a title="__title__" href="__url__">Parent __rev__</a>' | |
317 | .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6))) |
|
317 | .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6))) | |
318 | .replace('__title__', data.results[0].message) |
|
318 | .replace('__title__', data.results[0].message) | |
319 |
.replace('__url__', pyroutes.url(' |
|
319 | .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id})); | |
320 | _html +=' | '; |
|
320 | _html +=' | '; | |
321 | _html +='<a title="__title__" href="__url__">Parent __rev__</a>' |
|
321 | _html +='<a title="__title__" href="__url__">Parent __rev__</a>' | |
322 | .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6))) |
|
322 | .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6))) | |
323 | .replace('__title__', data.results[1].message) |
|
323 | .replace('__title__', data.results[1].message) | |
324 |
.replace('__url__', pyroutes.url(' |
|
324 | .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id})); | |
325 | $('#parent_link').html(_html); |
|
325 | $('#parent_link').html(_html); | |
326 | } |
|
326 | } | |
327 | } |
|
327 | } |
@@ -100,7 +100,7 b'' | |||||
100 | % endif |
|
100 | % endif | |
101 | % if inline: |
|
101 | % if inline: | |
102 | <div class="pr-version-inline"> |
|
102 | <div class="pr-version-inline"> | |
103 |
<a href="${ |
|
103 | <a href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}"> | |
104 | % if outdated_at_ver: |
|
104 | % if outdated_at_ver: | |
105 | <code class="pr-version-num" title="${_('Outdated comment from pull request version {0}').format(pr_index_ver)}"> |
|
105 | <code class="pr-version-num" title="${_('Outdated comment from pull request version {0}').format(pr_index_ver)}"> | |
106 | outdated ${'v{}'.format(pr_index_ver)} | |
|
106 | outdated ${'v{}'.format(pr_index_ver)} | |
@@ -70,15 +70,15 b'' | |||||
70 | </div> |
|
70 | </div> | |
71 | <div class="right-content"> |
|
71 | <div class="right-content"> | |
72 | <div class="diff-actions"> |
|
72 | <div class="diff-actions"> | |
73 |
<a href="${h. |
|
73 | <a href="${h.route_path('repo_commit_raw',repo_name=c.repo_name,commit_id='?')}" class="tooltip" title="${h.tooltip(_('Raw diff'))}"> | |
74 | ${_('Raw Diff')} |
|
74 | ${_('Raw Diff')} | |
75 | </a> |
|
75 | </a> | |
76 | | |
|
76 | | | |
77 |
<a href="${h. |
|
77 | <a href="${h.route_path('repo_commit_patch',repo_name=c.repo_name,commit_id='?')}" class="tooltip" title="${h.tooltip(_('Patch diff'))}"> | |
78 | ${_('Patch Diff')} |
|
78 | ${_('Patch Diff')} | |
79 | </a> |
|
79 | </a> | |
80 | | |
|
80 | | | |
81 |
<a href="${h. |
|
81 | <a href="${h.route_path('repo_commit_download',repo_name=c.repo_name,commit_id='?',_query=dict(diff='download'))}" class="tooltip" title="${h.tooltip(_('Download diff'))}"> | |
82 | ${_('Download Diff')} |
|
82 | ${_('Download Diff')} | |
83 | </a> |
|
83 | </a> | |
84 | </div> |
|
84 | </div> |
@@ -121,7 +121,7 b' collapse_all = len(diffset.files) > coll' | |||||
121 | %endif |
|
121 | %endif | |
122 | <h2 class="clearinner"> |
|
122 | <h2 class="clearinner"> | |
123 | %if commit: |
|
123 | %if commit: | |
124 |
<a class="tooltip revision" title="${h.tooltip(commit.message)}" href="${h. |
|
124 | <a class="tooltip revision" title="${h.tooltip(commit.message)}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id)}">${'r%s:%s' % (commit.revision,h.short_id(commit.raw_id))}</a> - | |
125 | ${h.age_component(commit.date)} - |
|
125 | ${h.age_component(commit.date)} - | |
126 | %endif |
|
126 | %endif | |
127 | %if diffset.limited_diff: |
|
127 | %if diffset.limited_diff: | |
@@ -459,10 +459,10 b' from rhodecode.lib.diffs import NEW_FILE' | |||||
459 |
|
459 | |||
460 | ## TODO: dan: refactor ignorews_url and context_url into the diff renderer same as diffmode=unified/sideside. Also use ajax to load more context (by clicking hunks) |
|
460 | ## TODO: dan: refactor ignorews_url and context_url into the diff renderer same as diffmode=unified/sideside. Also use ajax to load more context (by clicking hunks) | |
461 | %if hasattr(c, 'ignorews_url'): |
|
461 | %if hasattr(c, 'ignorews_url'): | |
462 |
${c.ignorews_url(request |
|
462 | ${c.ignorews_url(request, h.FID('', filediff.patch['filename']))} | |
463 | %endif |
|
463 | %endif | |
464 | %if hasattr(c, 'context_url'): |
|
464 | %if hasattr(c, 'context_url'): | |
465 |
${c.context_url(request |
|
465 | ${c.context_url(request, h.FID('', filediff.patch['filename']))} | |
466 | %endif |
|
466 | %endif | |
467 |
|
467 | |||
468 | %if use_comments: |
|
468 | %if use_comments: |
@@ -31,7 +31,7 b'' | |||||
31 | data-revision="${annotation.revision}" |
|
31 | data-revision="${annotation.revision}" | |
32 | onclick="$('[data-revision=${annotation.revision}]').toggleClass('cb-line-fresh')" |
|
32 | onclick="$('[data-revision=${annotation.revision}]').toggleClass('cb-line-fresh')" | |
33 | style="background: ${bgcolor}"> |
|
33 | style="background: ${bgcolor}"> | |
34 |
<a class="cb-annotate" href="${h. |
|
34 | <a class="cb-annotate" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=annotation.raw_id)}"> | |
35 | r${annotation.revision} |
|
35 | r${annotation.revision} | |
36 | </a> |
|
36 | </a> | |
37 | </td> |
|
37 | </td> |
@@ -3,7 +3,7 b'' | |||||
3 |
|
3 | |||
4 | %if c.ancestor: |
|
4 | %if c.ancestor: | |
5 | <div class="ancestor">${_('Common Ancestor Commit')}: |
|
5 | <div class="ancestor">${_('Common Ancestor Commit')}: | |
6 |
<a href="${h. |
|
6 | <a href="${h.route_path('repo_commit', repo_name=c.repo_name, commit_id=c.ancestor)}"> | |
7 | ${h.short_id(c.ancestor)} |
|
7 | ${h.short_id(c.ancestor)} | |
8 | </a>. ${_('Compare was calculated based on this shared commit.')} |
|
8 | </a>. ${_('Compare was calculated based on this shared commit.')} | |
9 | <input id="common_ancestor" type="hidden" name="common_ancestor" value="${c.ancestor}"> |
|
9 | <input id="common_ancestor" type="hidden" name="common_ancestor" value="${c.ancestor}"> | |
@@ -34,9 +34,7 b'' | |||||
34 | </td> |
|
34 | </td> | |
35 | <td class="td-hash"> |
|
35 | <td class="td-hash"> | |
36 | <code> |
|
36 | <code> | |
37 | <a href="${h.url('changeset_home', |
|
37 | <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}"> | |
38 | repo_name=c.target_repo.repo_name, |
|
|||
39 | revision=commit.raw_id)}"> |
|
|||
40 | r${commit.revision}:${h.short_id(commit.raw_id)} |
|
38 | r${commit.revision}:${h.short_id(commit.raw_id)} | |
41 | </a> |
|
39 | </a> | |
42 | ${h.hidden('revisions',commit.raw_id)} |
|
40 | ${h.hidden('revisions',commit.raw_id)} |
@@ -137,15 +137,15 b'' | |||||
137 | </div> |
|
137 | </div> | |
138 | <div class="right-content"> |
|
138 | <div class="right-content"> | |
139 | <div class="diff-actions"> |
|
139 | <div class="diff-actions"> | |
140 |
<a href="${h. |
|
140 | <a href="${h.route_path('repo_commit_raw',repo_name=c.repo_name,commit_id='?')}" class="tooltip" title="${h.tooltip(_('Raw diff'))}"> | |
141 | ${_('Raw Diff')} |
|
141 | ${_('Raw Diff')} | |
142 | </a> |
|
142 | </a> | |
143 | | |
|
143 | | | |
144 |
<a href="${h. |
|
144 | <a href="${h.route_path('repo_commit_patch',repo_name=c.repo_name,commit_id='?')}" class="tooltip" title="${h.tooltip(_('Patch diff'))}"> | |
145 | ${_('Patch Diff')} |
|
145 | ${_('Patch Diff')} | |
146 | </a> |
|
146 | </a> | |
147 | | |
|
147 | | | |
148 |
<a href="${h. |
|
148 | <a href="${h.route_path('repo_commit_download',repo_name=c.repo_name,commit_id='?',_query=dict(diff='download'))}" class="tooltip" title="${h.tooltip(_('Download diff'))}"> | |
149 | ${_('Download Diff')} |
|
149 | ${_('Download Diff')} | |
150 | </a> |
|
150 | </a> | |
151 | </div> |
|
151 | </div> | |
@@ -170,7 +170,7 b'' | |||||
170 | return form_inputs |
|
170 | return form_inputs | |
171 | %> |
|
171 | %> | |
172 | <div> |
|
172 | <div> | |
173 |
${comment.comments(h. |
|
173 | ${comment.comments(h.route_path('repo_commit_comment_create', repo_name=c.repo_name, commit_id='0'*16), None, is_compare=True, form_extras=revs(c.commit_ranges))} | |
174 | </div> |
|
174 | </div> | |
175 | </div> |
|
175 | </div> | |
176 | </div> |
|
176 | </div> |
@@ -83,7 +83,7 b'' | |||||
83 | <%def name="revision(name,rev,tip,author,last_msg)"> |
|
83 | <%def name="revision(name,rev,tip,author,last_msg)"> | |
84 | <div> |
|
84 | <div> | |
85 | %if rev >= 0: |
|
85 | %if rev >= 0: | |
86 |
<code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h. |
|
86 | <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.route_path('repo_commit',repo_name=name,commit_id=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code> | |
87 | %else: |
|
87 | %else: | |
88 | ${_('No commits yet')} |
|
88 | ${_('No commits yet')} | |
89 | %endif |
|
89 | %endif |
@@ -840,7 +840,8 b'' | |||||
840 | $('#edit-container').hide(); |
|
840 | $('#edit-container').hide(); | |
841 | $('#preview-container').show(); |
|
841 | $('#preview-container').show(); | |
842 |
|
842 | |||
843 |
var url = pyroutes.url(' |
|
843 | var url = pyroutes.url('repo_commit_comment_preview', | |
|
844 | {'repo_name': 'rhodecode-momentum', 'commit_id': '000000'}); | |||
844 |
|
845 | |||
845 | ajaxPOST(url, post_data, function(o) { |
|
846 | ajaxPOST(url, post_data, function(o) { | |
846 | previewbox.html(o); |
|
847 | previewbox.html(o); |
@@ -17,7 +17,7 b'' | |||||
17 | tag: ${tag} <br/> |
|
17 | tag: ${tag} <br/> | |
18 | % endfor |
|
18 | % endfor | |
19 |
|
19 | |||
20 |
commit: <a href="${h.url(' |
|
20 | commit: <a href="${h.route_url('repo_commit', repo_name=c.rhodecode_db_repo.repo_name, commit_id=commit.raw_id)}">${h.show_id(commit)}</a> | |
21 | <pre> |
|
21 | <pre> | |
22 | ${h.urlify_commit_message(commit.message)} |
|
22 | ${h.urlify_commit_message(commit.message)} | |
23 |
|
23 |
@@ -23,7 +23,7 b'' | |||||
23 | <div class="right-content"> |
|
23 | <div class="right-content"> | |
24 | <div class="tags"> |
|
24 | <div class="tags"> | |
25 | <code> |
|
25 | <code> | |
26 |
<a href="${h. |
|
26 | <a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">${h.show_id(c.commit)}</a> | |
27 | </code> |
|
27 | </code> | |
28 |
|
28 | |||
29 | ${file_base.refs(c.commit)} |
|
29 | ${file_base.refs(c.commit)} |
@@ -217,7 +217,9 b'' | |||||
217 | var _renderer = possible_renderer || DEFAULT_RENDERER; |
|
217 | var _renderer = possible_renderer || DEFAULT_RENDERER; | |
218 | var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN}; |
|
218 | var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN}; | |
219 | $('#editor_preview').html(_gettext('Loading ...')); |
|
219 | $('#editor_preview').html(_gettext('Loading ...')); | |
220 |
var url = pyroutes.url(' |
|
220 | var url = pyroutes.url('repo_commit_comment_preview', | |
|
221 | {'repo_name': '${c.repo_name}', | |||
|
222 | 'commit_id': '${c.commit.raw_id}'}); | |||
221 |
|
223 | |||
222 | ajaxPOST(url, post_data, function(o){ |
|
224 | ajaxPOST(url, post_data, function(o){ | |
223 | $('#editor_preview').html(o); |
|
225 | $('#editor_preview').html(o); |
@@ -21,7 +21,7 b'' | |||||
21 | </div> |
|
21 | </div> | |
22 | <div class="right-content"> |
|
22 | <div class="right-content"> | |
23 | <div class="tags tags-main"> |
|
23 | <div class="tags tags-main"> | |
24 |
<code><a href="${h. |
|
24 | <code><a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">${h.show_id(c.commit)}</a></code> | |
25 | ${file_base.refs(c.commit)} |
|
25 | ${file_base.refs(c.commit)} | |
26 | </div> |
|
26 | </div> | |
27 | </div> |
|
27 | </div> | |
@@ -33,7 +33,7 b'' | |||||
33 | </div> |
|
33 | </div> | |
34 | <div class="right-content"> |
|
34 | <div class="right-content"> | |
35 | <div class="tags"> |
|
35 | <div class="tags"> | |
36 |
<code><a href="${h. |
|
36 | <code><a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.file_last_commit.raw_id)}">${h.show_id(c.file_last_commit)}</a></code> | |
37 |
|
37 | |||
38 | ${file_base.refs(c.file_last_commit)} |
|
38 | ${file_base.refs(c.file_last_commit)} | |
39 | </div> |
|
39 | </div> |
@@ -47,7 +47,7 b'' | |||||
47 | <div class="code-header"> |
|
47 | <div class="code-header"> | |
48 | <div class="stats"> |
|
48 | <div class="stats"> | |
49 | <i class="icon-file"></i> |
|
49 | <i class="icon-file"></i> | |
50 |
<span class="item">${h.link_to("r%s:%s" % (c.file.commit.idx,h.short_id(c.file.commit.raw_id)),h. |
|
50 | <span class="item">${h.link_to("r%s:%s" % (c.file.commit.idx,h.short_id(c.file.commit.raw_id)),h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.file.commit.raw_id))}</span> | |
51 | <span class="item">${h.format_byte_size_binary(c.file.size)}</span> |
|
51 | <span class="item">${h.format_byte_size_binary(c.file.size)}</span> | |
52 | <span class="item last">${c.file.mimetype}</span> |
|
52 | <span class="item last">${c.file.mimetype}</span> | |
53 | <div class="buttons"> |
|
53 | <div class="buttons"> | |
@@ -177,8 +177,9 b'' | |||||
177 | var _renderer = possible_renderer || DEFAULT_RENDERER; |
|
177 | var _renderer = possible_renderer || DEFAULT_RENDERER; | |
178 | var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN}; |
|
178 | var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN}; | |
179 | $('#editor_preview').html(_gettext('Loading ...')); |
|
179 | $('#editor_preview').html(_gettext('Loading ...')); | |
180 |
var url = pyroutes.url(' |
|
180 | var url = pyroutes.url('repo_commit_comment_preview', | |
181 |
|
181 | {'repo_name': '${c.repo_name}', | ||
|
182 | 'commit_id': '${c.commit.raw_id}'}); | |||
182 | ajaxPOST(url, post_data, function(o){ |
|
183 | ajaxPOST(url, post_data, function(o){ | |
183 | $('#editor_preview').html(o); |
|
184 | $('#editor_preview').html(o); | |
184 | }) |
|
185 | }) |
@@ -86,7 +86,7 b'' | |||||
86 | <br/> |
|
86 | <br/> | |
87 | % if c.ancestor_commit: |
|
87 | % if c.ancestor_commit: | |
88 | ${_('Common ancestor')}: |
|
88 | ${_('Common ancestor')}: | |
89 |
<code><a href="${h. |
|
89 | <code><a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a></code> | |
90 | % endif |
|
90 | % endif | |
91 | </div> |
|
91 | </div> | |
92 | <div class="pr-pullinfo"> |
|
92 | <div class="pr-pullinfo"> | |
@@ -513,7 +513,7 b'' | |||||
513 | </td> |
|
513 | </td> | |
514 | <td class="td-hash"> |
|
514 | <td class="td-hash"> | |
515 | <code> |
|
515 | <code> | |
516 |
<a href="${h. |
|
516 | <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}"> | |
517 | r${commit.revision}:${h.short_id(commit.raw_id)} |
|
517 | r${commit.revision}:${h.short_id(commit.raw_id)} | |
518 | </a> |
|
518 | </a> | |
519 | ${h.hidden('revisions', commit.raw_id)} |
|
519 | ${h.hidden('revisions', commit.raw_id)} |
@@ -31,7 +31,7 b'' | |||||
31 | </td> |
|
31 | </td> | |
32 | <td class="td-commit"> |
|
32 | <td class="td-commit"> | |
33 | ${h.link_to(h._shorten_commit_id(entry['commit_id']), |
|
33 | ${h.link_to(h._shorten_commit_id(entry['commit_id']), | |
34 |
h. |
|
34 | h.route_path('repo_commit',repo_name=entry['repository'],commit_id=entry['commit_id']))} | |
35 | </td> |
|
35 | </td> | |
36 | <td class="td-message expand_commit search open" data-commit-id="${h.md5_safe(entry['repository'])+entry['commit_id']}" id="t-${h.md5_safe(entry['repository'])+entry['commit_id']}" title="${_('Expand commit message')}"> |
|
36 | <td class="td-message expand_commit search open" data-commit-id="${h.md5_safe(entry['repository'])+entry['commit_id']}" id="t-${h.md5_safe(entry['repository'])+entry['commit_id']}" title="${_('Expand commit message')}"> | |
37 | <div class="show_more_col"> |
|
37 | <div class="show_more_col"> |
@@ -19,11 +19,11 b'' | |||||
19 | <div class="changeset-status-ico shortlog"> |
|
19 | <div class="changeset-status-ico shortlog"> | |
20 | %if c.statuses.get(cs.raw_id)[2]: |
|
20 | %if c.statuses.get(cs.raw_id)[2]: | |
21 | <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (c.statuses.get(cs.raw_id)[0], c.statuses.get(cs.raw_id)[2])}" href="${h.route_path('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}"> |
|
21 | <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (c.statuses.get(cs.raw_id)[0], c.statuses.get(cs.raw_id)[2])}" href="${h.route_path('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}"> | |
22 |
<div class="${'flag_status |
|
22 | <div class="${'flag_status {}'.format(c.statuses.get(cs.raw_id)[0])}"></div> | |
23 | </a> |
|
23 | </a> | |
24 | %else: |
|
24 | %else: | |
25 |
<a class="tooltip" title="${_('Commit status: |
|
25 | <a class="tooltip" title="${_('Commit status: {}').format(h.commit_status_lbl(c.statuses.get(cs.raw_id)[0]))}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=cs.raw_id,_anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}"> | |
26 |
<div class="${'flag_status |
|
26 | <div class="${'flag_status {}'.format(c.statuses.get(cs.raw_id)[0])}"></div> | |
27 | </a> |
|
27 | </a> | |
28 | %endif |
|
28 | %endif | |
29 | </div> |
|
29 | </div> | |
@@ -33,13 +33,13 b'' | |||||
33 | </td> |
|
33 | </td> | |
34 | <td class="td-comments"> |
|
34 | <td class="td-comments"> | |
35 | %if c.comments.get(cs.raw_id,[]): |
|
35 | %if c.comments.get(cs.raw_id,[]): | |
36 |
<a title="${_('Commit has comments')}" href="${h. |
|
36 | <a title="${_('Commit has comments')}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=cs.raw_id,_anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}"> | |
37 | <i class="icon-comment"></i> ${len(c.comments[cs.raw_id])} |
|
37 | <i class="icon-comment"></i> ${len(c.comments[cs.raw_id])} | |
38 | </a> |
|
38 | </a> | |
39 | %endif |
|
39 | %endif | |
40 | </td> |
|
40 | </td> | |
41 | <td class="td-commit"> |
|
41 | <td class="td-commit"> | |
42 |
<pre><a href="${h. |
|
42 | <pre><a href="${h.route_path('repo_commit', repo_name=c.repo_name, commit_id=cs.raw_id)}">${h.show_id(cs)}</a></pre> | |
43 | </td> |
|
43 | </td> | |
44 |
|
44 | |||
45 | <td class="td-description mid"> |
|
45 | <td class="td-description mid"> |
@@ -471,7 +471,7 b' class TestCompareController(object):' | |||||
471 | compare_page.contains_change_summary(1, 1, 0) |
|
471 | compare_page.contains_change_summary(1, 1, 0) | |
472 |
|
472 | |||
473 | @pytest.mark.xfail_backends("svn") |
|
473 | @pytest.mark.xfail_backends("svn") | |
474 | def test_compare_commits(self, backend): |
|
474 | def test_compare_commits(self, backend, xhr_header): | |
475 | commit0 = backend.repo.get_commit(commit_idx=0) |
|
475 | commit0 = backend.repo.get_commit(commit_idx=0) | |
476 | commit1 = backend.repo.get_commit(commit_idx=1) |
|
476 | commit1 = backend.repo.get_commit(commit_idx=1) | |
477 |
|
477 | |||
@@ -483,7 +483,7 b' class TestCompareController(object):' | |||||
483 | target_ref_type="rev", |
|
483 | target_ref_type="rev", | |
484 | target_ref=commit1.raw_id, |
|
484 | target_ref=commit1.raw_id, | |
485 | merge='1',), |
|
485 | merge='1',), | |
486 |
extra_environ= |
|
486 | extra_environ=xhr_header,) | |
487 |
|
487 | |||
488 | # outgoing commits between those commits |
|
488 | # outgoing commits between those commits | |
489 | compare_page = ComparePage(response) |
|
489 | compare_page = ComparePage(response) |
@@ -397,7 +397,7 b' def test_urlify_commits(sample, expected' | |||||
397 |
|
397 | |||
398 | expected = _quick_url(expected) |
|
398 | expected = _quick_url(expected) | |
399 |
|
399 | |||
400 |
with mock.patch(' |
|
400 | with mock.patch('rhodecode.lib.helpers.route_url', fake_url): | |
401 | from rhodecode.lib.helpers import urlify_commits |
|
401 | from rhodecode.lib.helpers import urlify_commits | |
402 | assert urlify_commits(sample, 'repo_name') == expected |
|
402 | assert urlify_commits(sample, 'repo_name') == expected | |
403 |
|
403 |
@@ -1,7 +1,7 b'' | |||||
1 |
|
1 | |||
2 |
|
2 | |||
3 | ################################################################################ |
|
3 | ################################################################################ | |
4 |
## |
|
4 | ## RHODECODE COMMUNITY EDITION CONFIGURATION ## | |
5 | # The %(here)s variable will be replaced with the parent directory of this file# |
|
5 | # The %(here)s variable will be replaced with the parent directory of this file# | |
6 | ################################################################################ |
|
6 | ################################################################################ | |
7 |
|
7 | |||
@@ -64,7 +64,7 b' asyncore_use_poll = true' | |||||
64 | ########################## |
|
64 | ########################## | |
65 | ## GUNICORN WSGI SERVER ## |
|
65 | ## GUNICORN WSGI SERVER ## | |
66 | ########################## |
|
66 | ########################## | |
67 |
## run with gunicorn --log-config |
|
67 | ## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini | |
68 |
|
68 | |||
69 | #use = egg:gunicorn#main |
|
69 | #use = egg:gunicorn#main | |
70 | ## Sets the number of process workers. You must set `instance_id = *` |
|
70 | ## Sets the number of process workers. You must set `instance_id = *` | |
@@ -153,8 +153,10 b' asyncore_use_poll = true' | |||||
153 | ## prefix middleware for RhodeCode. |
|
153 | ## prefix middleware for RhodeCode. | |
154 | ## recommended when using proxy setup. |
|
154 | ## recommended when using proxy setup. | |
155 | ## allows to set RhodeCode under a prefix in server. |
|
155 | ## allows to set RhodeCode under a prefix in server. | |
156 |
## eg https://server.com/ |
|
156 | ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well. | |
157 |
## |
|
157 | ## And set your prefix like: `prefix = /custom_prefix` | |
|
158 | ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need | |||
|
159 | ## to make your cookies only work on prefix url | |||
158 | [filter:proxy-prefix] |
|
160 | [filter:proxy-prefix] | |
159 | use = egg:PasteDeploy#prefix |
|
161 | use = egg:PasteDeploy#prefix | |
160 | prefix = / |
|
162 | prefix = / | |
@@ -238,27 +240,27 b' rss_items_per_page = 10' | |||||
238 | rss_include_diff = false |
|
240 | rss_include_diff = false | |
239 |
|
241 | |||
240 | ## gist URL alias, used to create nicer urls for gist. This should be an |
|
242 | ## gist URL alias, used to create nicer urls for gist. This should be an | |
241 |
## url that does rewrites to _admin/gists/ |
|
243 | ## url that does rewrites to _admin/gists/{gistid}. | |
242 | ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal |
|
244 | ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal | |
243 |
## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/ |
|
245 | ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid} | |
244 | gist_alias_url = |
|
246 | gist_alias_url = | |
245 |
|
247 | |||
246 |
## List of |
|
248 | ## List of views (using glob pattern syntax) that AUTH TOKENS could be | |
247 | ## used for access. |
|
249 | ## used for access. | |
248 |
## Adding ?auth_token |
|
250 | ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it | |
249 | ## came from the the logged in user who own this authentication token. |
|
251 | ## came from the the logged in user who own this authentication token. | |
250 | ## |
|
252 | ## | |
251 | ## Syntax is <ControllerClass>:<function_pattern>. |
|
253 | ## list of all views can be found under `_admin/permissions/auth_token_access` | |
252 | ## To enable access to raw_files put `FilesController:raw`. |
|
|||
253 | ## To enable access to patches add `ChangesetController:changeset_patch`. |
|
|||
254 | ## The list should be "," separated and on a single line. |
|
254 | ## The list should be "," separated and on a single line. | |
255 | ## |
|
255 | ## | |
256 |
## |
|
256 | ## Most common views to enable: | |
257 | # ChangesetController:changeset_patch, |
|
257 | # RepoCommitsView:repo_commit_download | |
258 | # ChangesetController:changeset_raw, |
|
258 | # RepoCommitsView:repo_commit_patch | |
259 | # FilesController:raw, |
|
259 | # RepoCommitsView:repo_commit_raw | |
260 | # FilesController:archivefile, |
|
260 | # RepoFilesView:repo_files_diff | |
261 | # GistsController:*, |
|
261 | # RepoFilesView:repo_archivefile | |
|
262 | # RepoFilesView:repo_file_raw | |||
|
263 | # GistView:* | |||
262 | api_access_controllers_whitelist = |
|
264 | api_access_controllers_whitelist = | |
263 |
|
265 | |||
264 | ## default encoding used to convert from and to unicode |
|
266 | ## default encoding used to convert from and to unicode | |
@@ -421,15 +423,15 b' beaker.session.lock_dir = %(here)s/rc/da' | |||||
421 |
|
423 | |||
422 | ## Secure encrypted cookie. Requires AES and AES python libraries |
|
424 | ## Secure encrypted cookie. Requires AES and AES python libraries | |
423 | ## you must disable beaker.session.secret to use this |
|
425 | ## you must disable beaker.session.secret to use this | |
424 |
#beaker.session.encrypt_key = |
|
426 | #beaker.session.encrypt_key = key_for_encryption | |
425 |
#beaker.session.validate_key = |
|
427 | #beaker.session.validate_key = validation_key | |
426 |
|
428 | |||
427 | ## sets session as invalid(also logging out user) if it haven not been |
|
429 | ## sets session as invalid(also logging out user) if it haven not been | |
428 | ## accessed for given amount of time in seconds |
|
430 | ## accessed for given amount of time in seconds | |
429 | beaker.session.timeout = 2592000 |
|
431 | beaker.session.timeout = 2592000 | |
430 | beaker.session.httponly = true |
|
432 | beaker.session.httponly = true | |
431 | ## Path to use for the cookie. |
|
433 | ## Path to use for the cookie. Set to prefix if you use prefix middleware | |
432 |
#beaker.session.cookie_path = / |
|
434 | #beaker.session.cookie_path = /custom_prefix | |
433 |
|
435 | |||
434 | ## uncomment for https secure cookie |
|
436 | ## uncomment for https secure cookie | |
435 | beaker.session.secure = false |
|
437 | beaker.session.secure = false | |
@@ -447,8 +449,8 b' beaker.session.auto = false' | |||||
447 | ## Full text search indexer is available in rhodecode-tools under |
|
449 | ## Full text search indexer is available in rhodecode-tools under | |
448 | ## `rhodecode-tools index` command |
|
450 | ## `rhodecode-tools index` command | |
449 |
|
451 | |||
450 | # WHOOSH Backend, doesn't require additional services to run |
|
452 | ## WHOOSH Backend, doesn't require additional services to run | |
451 | # it works good with few dozen repos |
|
453 | ## it works good with few dozen repos | |
452 | search.module = rhodecode.lib.index.whoosh |
|
454 | search.module = rhodecode.lib.index.whoosh | |
453 | search.location = %(here)s/data/index |
|
455 | search.location = %(here)s/data/index | |
454 |
|
456 | |||
@@ -459,15 +461,21 b' search.location = %(here)s/data/index' | |||||
459 | ## in the system. It's also used by the chat system |
|
461 | ## in the system. It's also used by the chat system | |
460 |
|
462 | |||
461 | channelstream.enabled = false |
|
463 | channelstream.enabled = false | |
462 | # location of channelstream server on the backend |
|
464 | ||
|
465 | ## server address for channelstream server on the backend | |||
463 | channelstream.server = 127.0.0.1:9800 |
|
466 | channelstream.server = 127.0.0.1:9800 | |
464 | ## location of the channelstream server from outside world |
|
467 | ## location of the channelstream server from outside world | |
465 | ## most likely this would be an http server special backend URL, that handles |
|
468 | ## use ws:// for http or wss:// for https. This address needs to be handled | |
466 | ## websocket connections see nginx example for config |
|
469 | ## by external HTTP server such as Nginx or Apache | |
|
470 | ## see nginx/apache configuration examples in our docs | |||
467 | channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream |
|
471 | channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream | |
468 | channelstream.secret = secret |
|
472 | channelstream.secret = secret | |
469 | channelstream.history.location = %(here)s/channelstream_history |
|
473 | channelstream.history.location = %(here)s/channelstream_history | |
470 |
|
474 | |||
|
475 | ## Internal application path that Javascript uses to connect into. | |||
|
476 | ## If you use proxy-prefix the prefix should be added before /_channelstream | |||
|
477 | channelstream.proxy_path = /_channelstream | |||
|
478 | ||||
471 |
|
479 | |||
472 | ################################### |
|
480 | ################################### | |
473 | ## APPENLIGHT CONFIG ## |
|
481 | ## APPENLIGHT CONFIG ## | |
@@ -541,19 +549,19 b' set debug = false' | |||||
541 | ############## |
|
549 | ############## | |
542 | debug_style = false |
|
550 | debug_style = false | |
543 |
|
551 | |||
544 |
########################################### |
|
552 | ########################################### | |
545 | ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ### |
|
553 | ### MAIN RHODECODE DATABASE CONFIG ### | |
546 |
########################################### |
|
554 | ########################################### | |
547 | #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db |
|
555 | #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db?timeout=30 | |
548 | #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode_test |
|
556 | #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode_test | |
549 | #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode_test |
|
557 | #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode_test | |
550 | sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db |
|
558 | sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db?timeout=30 | |
551 |
|
559 | |||
552 | # see sqlalchemy docs for other advanced settings |
|
560 | # see sqlalchemy docs for other advanced settings | |
553 |
|
561 | |||
554 | ## print the sql statements to output |
|
562 | ## print the sql statements to output | |
555 | sqlalchemy.db1.echo = false |
|
563 | sqlalchemy.db1.echo = false | |
556 |
## recycle the connections after this am |
|
564 | ## recycle the connections after this amount of seconds | |
557 | sqlalchemy.db1.pool_recycle = 3600 |
|
565 | sqlalchemy.db1.pool_recycle = 3600 | |
558 | sqlalchemy.db1.convert_unicode = true |
|
566 | sqlalchemy.db1.convert_unicode = true | |
559 |
|
567 | |||
@@ -575,7 +583,7 b' vcs.server = localhost:9901' | |||||
575 |
|
583 | |||
576 | ## Web server connectivity protocol, responsible for web based VCS operatations |
|
584 | ## Web server connectivity protocol, responsible for web based VCS operatations | |
577 | ## Available protocols are: |
|
585 | ## Available protocols are: | |
578 |
## `http` - us |
|
586 | ## `http` - use http-rpc backend (default) | |
579 | vcs.server.protocol = http |
|
587 | vcs.server.protocol = http | |
580 |
|
588 | |||
581 | ## Push/Pull operations protocol, available options are: |
|
589 | ## Push/Pull operations protocol, available options are: | |
@@ -584,7 +592,7 b' vcs.server.protocol = http' | |||||
584 | vcs.scm_app_implementation = http |
|
592 | vcs.scm_app_implementation = http | |
585 |
|
593 | |||
586 | ## Push/Pull operations hooks protocol, available options are: |
|
594 | ## Push/Pull operations hooks protocol, available options are: | |
587 |
## `http` - us |
|
595 | ## `http` - use http-rpc backend (default) | |
588 | vcs.hooks.protocol = http |
|
596 | vcs.hooks.protocol = http | |
589 |
|
597 | |||
590 | vcs.server.log_level = debug |
|
598 | vcs.server.log_level = debug | |
@@ -613,12 +621,19 b' svn.proxy.generate_config = false' | |||||
613 | svn.proxy.list_parent_path = true |
|
621 | svn.proxy.list_parent_path = true | |
614 | ## Set location and file name of generated config file. |
|
622 | ## Set location and file name of generated config file. | |
615 | svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf |
|
623 | svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf | |
616 | ## File system path to the directory containing the repositories served by |
|
624 | ## Used as a prefix to the `Location` block in the generated config file. | |
617 | ## RhodeCode. |
|
625 | ## In most cases it should be set to `/`. | |
618 | svn.proxy.parent_path_root = /path/to/repo_store |
|
|||
619 | ## Used as a prefix to the <Location> block in the generated config file. In |
|
|||
620 | ## most cases it should be set to `/`. |
|
|||
621 | svn.proxy.location_root = / |
|
626 | svn.proxy.location_root = / | |
|
627 | ## Command to reload the mod dav svn configuration on change. | |||
|
628 | ## Example: `/etc/init.d/apache2 reload` | |||
|
629 | #svn.proxy.reload_cmd = /etc/init.d/apache2 reload | |||
|
630 | ## If the timeout expires before the reload command finishes, the command will | |||
|
631 | ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds. | |||
|
632 | #svn.proxy.reload_timeout = 10 | |||
|
633 | ||||
|
634 | ## Dummy marker to add new entries after. | |||
|
635 | ## Add any custom entries below. Please don't remove. | |||
|
636 | custom.conf = 1 | |||
622 |
|
637 | |||
623 |
|
638 | |||
624 | ################################ |
|
639 | ################################ |
@@ -1,491 +0,0 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
|||
2 |
|
||||
3 | # Copyright (C) 2010-2017 RhodeCode GmbH |
|
|||
4 | # |
|
|||
5 | # This program is free software: you can redistribute it and/or modify |
|
|||
6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
|||
7 | # (only), as published by the Free Software Foundation. |
|
|||
8 | # |
|
|||
9 | # This program is distributed in the hope that it will be useful, |
|
|||
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|||
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|||
12 | # GNU General Public License for more details. |
|
|||
13 | # |
|
|||
14 | # You should have received a copy of the GNU Affero General Public License |
|
|||
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
|||
16 | # |
|
|||
17 | # This program is dual-licensed. If you wish to learn more about the |
|
|||
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
|||
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
|||
20 |
|
||||
21 | """ |
|
|||
22 | commit controller for RhodeCode showing changes between commits |
|
|||
23 | """ |
|
|||
24 |
|
||||
25 | import logging |
|
|||
26 |
|
||||
27 | from collections import defaultdict |
|
|||
28 | from webob.exc import HTTPForbidden, HTTPBadRequest, HTTPNotFound |
|
|||
29 |
|
||||
30 | from pylons import tmpl_context as c, request, response |
|
|||
31 | from pylons.i18n.translation import _ |
|
|||
32 | from pylons.controllers.util import redirect |
|
|||
33 |
|
||||
34 | from rhodecode.lib import auth |
|
|||
35 | from rhodecode.lib import diffs, codeblocks |
|
|||
36 | from rhodecode.lib.auth import ( |
|
|||
37 | LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous) |
|
|||
38 | from rhodecode.lib.base import BaseRepoController, render |
|
|||
39 | from rhodecode.lib.compat import OrderedDict |
|
|||
40 | from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError |
|
|||
41 | import rhodecode.lib.helpers as h |
|
|||
42 | from rhodecode.lib.utils import jsonify |
|
|||
43 | from rhodecode.lib.utils2 import safe_unicode, safe_int |
|
|||
44 | from rhodecode.lib.vcs.backends.base import EmptyCommit |
|
|||
45 | from rhodecode.lib.vcs.exceptions import ( |
|
|||
46 | RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError) |
|
|||
47 | from rhodecode.model.db import ChangesetComment, ChangesetStatus |
|
|||
48 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
|||
49 | from rhodecode.model.comment import CommentsModel |
|
|||
50 | from rhodecode.model.meta import Session |
|
|||
51 |
|
||||
52 |
|
||||
53 | log = logging.getLogger(__name__) |
|
|||
54 |
|
||||
55 |
|
||||
56 | def _update_with_GET(params, GET): |
|
|||
57 | for k in ['diff1', 'diff2', 'diff']: |
|
|||
58 | params[k] += GET.getall(k) |
|
|||
59 |
|
||||
60 |
|
||||
61 | def get_ignore_ws(fid, GET): |
|
|||
62 | ig_ws_global = GET.get('ignorews') |
|
|||
63 | ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid)) |
|
|||
64 | if ig_ws: |
|
|||
65 | try: |
|
|||
66 | return int(ig_ws[0].split(':')[-1]) |
|
|||
67 | except Exception: |
|
|||
68 | pass |
|
|||
69 | return ig_ws_global |
|
|||
70 |
|
||||
71 |
|
||||
72 | def _ignorews_url(GET, fileid=None): |
|
|||
73 | fileid = str(fileid) if fileid else None |
|
|||
74 | params = defaultdict(list) |
|
|||
75 | _update_with_GET(params, GET) |
|
|||
76 | label = _('Show whitespace') |
|
|||
77 | tooltiplbl = _('Show whitespace for all diffs') |
|
|||
78 | ig_ws = get_ignore_ws(fileid, GET) |
|
|||
79 | ln_ctx = get_line_ctx(fileid, GET) |
|
|||
80 |
|
||||
81 | if ig_ws is None: |
|
|||
82 | params['ignorews'] += [1] |
|
|||
83 | label = _('Ignore whitespace') |
|
|||
84 | tooltiplbl = _('Ignore whitespace for all diffs') |
|
|||
85 | ctx_key = 'context' |
|
|||
86 | ctx_val = ln_ctx |
|
|||
87 |
|
||||
88 | # if we have passed in ln_ctx pass it along to our params |
|
|||
89 | if ln_ctx: |
|
|||
90 | params[ctx_key] += [ctx_val] |
|
|||
91 |
|
||||
92 | if fileid: |
|
|||
93 | params['anchor'] = 'a_' + fileid |
|
|||
94 | return h.link_to(label, h.url.current(**params), title=tooltiplbl, class_='tooltip') |
|
|||
95 |
|
||||
96 |
|
||||
97 | def get_line_ctx(fid, GET): |
|
|||
98 | ln_ctx_global = GET.get('context') |
|
|||
99 | if fid: |
|
|||
100 | ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid)) |
|
|||
101 | else: |
|
|||
102 | _ln_ctx = filter(lambda k: k.startswith('C'), GET) |
|
|||
103 | ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global |
|
|||
104 | if ln_ctx: |
|
|||
105 | ln_ctx = [ln_ctx] |
|
|||
106 |
|
||||
107 | if ln_ctx: |
|
|||
108 | retval = ln_ctx[0].split(':')[-1] |
|
|||
109 | else: |
|
|||
110 | retval = ln_ctx_global |
|
|||
111 |
|
||||
112 | try: |
|
|||
113 | return int(retval) |
|
|||
114 | except Exception: |
|
|||
115 | return 3 |
|
|||
116 |
|
||||
117 |
|
||||
118 | def _context_url(GET, fileid=None): |
|
|||
119 | """ |
|
|||
120 | Generates a url for context lines. |
|
|||
121 |
|
||||
122 | :param fileid: |
|
|||
123 | """ |
|
|||
124 |
|
||||
125 | fileid = str(fileid) if fileid else None |
|
|||
126 | ig_ws = get_ignore_ws(fileid, GET) |
|
|||
127 | ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2 |
|
|||
128 |
|
||||
129 | params = defaultdict(list) |
|
|||
130 | _update_with_GET(params, GET) |
|
|||
131 |
|
||||
132 | if ln_ctx > 0: |
|
|||
133 | params['context'] += [ln_ctx] |
|
|||
134 |
|
||||
135 | if ig_ws: |
|
|||
136 | ig_ws_key = 'ignorews' |
|
|||
137 | ig_ws_val = 1 |
|
|||
138 | params[ig_ws_key] += [ig_ws_val] |
|
|||
139 |
|
||||
140 | lbl = _('Increase context') |
|
|||
141 | tooltiplbl = _('Increase context for all diffs') |
|
|||
142 |
|
||||
143 | if fileid: |
|
|||
144 | params['anchor'] = 'a_' + fileid |
|
|||
145 | return h.link_to(lbl, h.url.current(**params), title=tooltiplbl, class_='tooltip') |
|
|||
146 |
|
||||
147 |
|
||||
148 | class ChangesetController(BaseRepoController): |
|
|||
149 |
|
||||
150 | def __before__(self): |
|
|||
151 | super(ChangesetController, self).__before__() |
|
|||
152 |
|
||||
153 | def _index(self, commit_id_range, method): |
|
|||
154 | c.ignorews_url = _ignorews_url |
|
|||
155 | c.context_url = _context_url |
|
|||
156 | c.fulldiff = fulldiff = request.GET.get('fulldiff') |
|
|||
157 |
|
||||
158 | # fetch global flags of ignore ws or context lines |
|
|||
159 | context_lcl = get_line_ctx('', request.GET) |
|
|||
160 | ign_whitespace_lcl = get_ignore_ws('', request.GET) |
|
|||
161 |
|
||||
162 | # diff_limit will cut off the whole diff if the limit is applied |
|
|||
163 | # otherwise it will just hide the big files from the front-end |
|
|||
164 | diff_limit = self.cut_off_limit_diff |
|
|||
165 | file_limit = self.cut_off_limit_file |
|
|||
166 |
|
||||
167 | # get ranges of commit ids if preset |
|
|||
168 | commit_range = commit_id_range.split('...')[:2] |
|
|||
169 |
|
||||
170 | try: |
|
|||
171 | pre_load = ['affected_files', 'author', 'branch', 'date', |
|
|||
172 | 'message', 'parents'] |
|
|||
173 |
|
||||
174 | if len(commit_range) == 2: |
|
|||
175 | commits = c.rhodecode_repo.get_commits( |
|
|||
176 | start_id=commit_range[0], end_id=commit_range[1], |
|
|||
177 | pre_load=pre_load) |
|
|||
178 | commits = list(commits) |
|
|||
179 | else: |
|
|||
180 | commits = [c.rhodecode_repo.get_commit( |
|
|||
181 | commit_id=commit_id_range, pre_load=pre_load)] |
|
|||
182 |
|
||||
183 | c.commit_ranges = commits |
|
|||
184 | if not c.commit_ranges: |
|
|||
185 | raise RepositoryError( |
|
|||
186 | 'The commit range returned an empty result') |
|
|||
187 | except CommitDoesNotExistError: |
|
|||
188 | msg = _('No such commit exists for this repository') |
|
|||
189 | h.flash(msg, category='error') |
|
|||
190 | raise HTTPNotFound() |
|
|||
191 | except Exception: |
|
|||
192 | log.exception("General failure") |
|
|||
193 | raise HTTPNotFound() |
|
|||
194 |
|
||||
195 | c.changes = OrderedDict() |
|
|||
196 | c.lines_added = 0 |
|
|||
197 | c.lines_deleted = 0 |
|
|||
198 |
|
||||
199 | # auto collapse if we have more than limit |
|
|||
200 | collapse_limit = diffs.DiffProcessor._collapse_commits_over |
|
|||
201 | c.collapse_all_commits = len(c.commit_ranges) > collapse_limit |
|
|||
202 |
|
||||
203 | c.commit_statuses = ChangesetStatus.STATUSES |
|
|||
204 | c.inline_comments = [] |
|
|||
205 | c.files = [] |
|
|||
206 |
|
||||
207 | c.statuses = [] |
|
|||
208 | c.comments = [] |
|
|||
209 | c.unresolved_comments = [] |
|
|||
210 | if len(c.commit_ranges) == 1: |
|
|||
211 | commit = c.commit_ranges[0] |
|
|||
212 | c.comments = CommentsModel().get_comments( |
|
|||
213 | c.rhodecode_db_repo.repo_id, |
|
|||
214 | revision=commit.raw_id) |
|
|||
215 | c.statuses.append(ChangesetStatusModel().get_status( |
|
|||
216 | c.rhodecode_db_repo.repo_id, commit.raw_id)) |
|
|||
217 | # comments from PR |
|
|||
218 | statuses = ChangesetStatusModel().get_statuses( |
|
|||
219 | c.rhodecode_db_repo.repo_id, commit.raw_id, |
|
|||
220 | with_revisions=True) |
|
|||
221 | prs = set(st.pull_request for st in statuses |
|
|||
222 | if st.pull_request is not None) |
|
|||
223 | # from associated statuses, check the pull requests, and |
|
|||
224 | # show comments from them |
|
|||
225 | for pr in prs: |
|
|||
226 | c.comments.extend(pr.comments) |
|
|||
227 |
|
||||
228 | c.unresolved_comments = CommentsModel()\ |
|
|||
229 | .get_commit_unresolved_todos(commit.raw_id) |
|
|||
230 |
|
||||
231 | # Iterate over ranges (default commit view is always one commit) |
|
|||
232 | for commit in c.commit_ranges: |
|
|||
233 | c.changes[commit.raw_id] = [] |
|
|||
234 |
|
||||
235 | commit2 = commit |
|
|||
236 | commit1 = commit.parents[0] if commit.parents else EmptyCommit() |
|
|||
237 |
|
||||
238 | _diff = c.rhodecode_repo.get_diff( |
|
|||
239 | commit1, commit2, |
|
|||
240 | ignore_whitespace=ign_whitespace_lcl, context=context_lcl) |
|
|||
241 | diff_processor = diffs.DiffProcessor( |
|
|||
242 | _diff, format='newdiff', diff_limit=diff_limit, |
|
|||
243 | file_limit=file_limit, show_full_diff=fulldiff) |
|
|||
244 |
|
||||
245 | commit_changes = OrderedDict() |
|
|||
246 | if method == 'show': |
|
|||
247 | _parsed = diff_processor.prepare() |
|
|||
248 | c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer) |
|
|||
249 |
|
||||
250 | _parsed = diff_processor.prepare() |
|
|||
251 |
|
||||
252 | def _node_getter(commit): |
|
|||
253 | def get_node(fname): |
|
|||
254 | try: |
|
|||
255 | return commit.get_node(fname) |
|
|||
256 | except NodeDoesNotExistError: |
|
|||
257 | return None |
|
|||
258 | return get_node |
|
|||
259 |
|
||||
260 | inline_comments = CommentsModel().get_inline_comments( |
|
|||
261 | c.rhodecode_db_repo.repo_id, revision=commit.raw_id) |
|
|||
262 | c.inline_cnt = CommentsModel().get_inline_comments_count( |
|
|||
263 | inline_comments) |
|
|||
264 |
|
||||
265 | diffset = codeblocks.DiffSet( |
|
|||
266 | repo_name=c.repo_name, |
|
|||
267 | source_node_getter=_node_getter(commit1), |
|
|||
268 | target_node_getter=_node_getter(commit2), |
|
|||
269 | comments=inline_comments) |
|
|||
270 | diffset = diffset.render_patchset( |
|
|||
271 | _parsed, commit1.raw_id, commit2.raw_id) |
|
|||
272 |
|
||||
273 | c.changes[commit.raw_id] = diffset |
|
|||
274 | else: |
|
|||
275 | # downloads/raw we only need RAW diff nothing else |
|
|||
276 | diff = diff_processor.as_raw() |
|
|||
277 | c.changes[commit.raw_id] = [None, None, None, None, diff, None, None] |
|
|||
278 |
|
||||
279 | # sort comments by how they were generated |
|
|||
280 | c.comments = sorted(c.comments, key=lambda x: x.comment_id) |
|
|||
281 |
|
||||
282 | if len(c.commit_ranges) == 1: |
|
|||
283 | c.commit = c.commit_ranges[0] |
|
|||
284 | c.parent_tmpl = ''.join( |
|
|||
285 | '# Parent %s\n' % x.raw_id for x in c.commit.parents) |
|
|||
286 | if method == 'download': |
|
|||
287 | response.content_type = 'text/plain' |
|
|||
288 | response.content_disposition = ( |
|
|||
289 | 'attachment; filename=%s.diff' % commit_id_range[:12]) |
|
|||
290 | return diff |
|
|||
291 | elif method == 'patch': |
|
|||
292 | response.content_type = 'text/plain' |
|
|||
293 | c.diff = safe_unicode(diff) |
|
|||
294 | return render('changeset/patch_changeset.mako') |
|
|||
295 | elif method == 'raw': |
|
|||
296 | response.content_type = 'text/plain' |
|
|||
297 | return diff |
|
|||
298 | elif method == 'show': |
|
|||
299 | if len(c.commit_ranges) == 1: |
|
|||
300 | return render('changeset/changeset.mako') |
|
|||
301 | else: |
|
|||
302 | c.ancestor = None |
|
|||
303 | c.target_repo = c.rhodecode_db_repo |
|
|||
304 | return render('changeset/changeset_range.mako') |
|
|||
305 |
|
||||
306 | @LoginRequired() |
|
|||
307 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
|||
308 | 'repository.admin') |
|
|||
309 | def index(self, revision, method='show'): |
|
|||
310 | return self._index(revision, method=method) |
|
|||
311 |
|
||||
312 | @LoginRequired() |
|
|||
313 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
|||
314 | 'repository.admin') |
|
|||
315 | def changeset_raw(self, revision): |
|
|||
316 | return self._index(revision, method='raw') |
|
|||
317 |
|
||||
318 | @LoginRequired() |
|
|||
319 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
|||
320 | 'repository.admin') |
|
|||
321 | def changeset_patch(self, revision): |
|
|||
322 | return self._index(revision, method='patch') |
|
|||
323 |
|
||||
324 | @LoginRequired() |
|
|||
325 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
|||
326 | 'repository.admin') |
|
|||
327 | def changeset_download(self, revision): |
|
|||
328 | return self._index(revision, method='download') |
|
|||
329 |
|
||||
330 | @LoginRequired() |
|
|||
331 | @NotAnonymous() |
|
|||
332 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
|||
333 | 'repository.admin') |
|
|||
334 | @auth.CSRFRequired() |
|
|||
335 | @jsonify |
|
|||
336 | def comment(self, repo_name, revision): |
|
|||
337 | commit_id = revision |
|
|||
338 | status = request.POST.get('changeset_status', None) |
|
|||
339 | text = request.POST.get('text') |
|
|||
340 | comment_type = request.POST.get('comment_type') |
|
|||
341 | resolves_comment_id = request.POST.get('resolves_comment_id', None) |
|
|||
342 |
|
||||
343 | if status: |
|
|||
344 | text = text or (_('Status change %(transition_icon)s %(status)s') |
|
|||
345 | % {'transition_icon': '>', |
|
|||
346 | 'status': ChangesetStatus.get_status_lbl(status)}) |
|
|||
347 |
|
||||
348 | multi_commit_ids = [] |
|
|||
349 | for _commit_id in request.POST.get('commit_ids', '').split(','): |
|
|||
350 | if _commit_id not in ['', None, EmptyCommit.raw_id]: |
|
|||
351 | if _commit_id not in multi_commit_ids: |
|
|||
352 | multi_commit_ids.append(_commit_id) |
|
|||
353 |
|
||||
354 | commit_ids = multi_commit_ids or [commit_id] |
|
|||
355 |
|
||||
356 | comment = None |
|
|||
357 | for current_id in filter(None, commit_ids): |
|
|||
358 | c.co = comment = CommentsModel().create( |
|
|||
359 | text=text, |
|
|||
360 | repo=c.rhodecode_db_repo.repo_id, |
|
|||
361 | user=c.rhodecode_user.user_id, |
|
|||
362 | commit_id=current_id, |
|
|||
363 | f_path=request.POST.get('f_path'), |
|
|||
364 | line_no=request.POST.get('line'), |
|
|||
365 | status_change=(ChangesetStatus.get_status_lbl(status) |
|
|||
366 | if status else None), |
|
|||
367 | status_change_type=status, |
|
|||
368 | comment_type=comment_type, |
|
|||
369 | resolves_comment_id=resolves_comment_id |
|
|||
370 | ) |
|
|||
371 |
|
||||
372 | # get status if set ! |
|
|||
373 | if status: |
|
|||
374 | # if latest status was from pull request and it's closed |
|
|||
375 | # disallow changing status ! |
|
|||
376 | # dont_allow_on_closed_pull_request = True ! |
|
|||
377 |
|
||||
378 | try: |
|
|||
379 | ChangesetStatusModel().set_status( |
|
|||
380 | c.rhodecode_db_repo.repo_id, |
|
|||
381 | status, |
|
|||
382 | c.rhodecode_user.user_id, |
|
|||
383 | comment, |
|
|||
384 | revision=current_id, |
|
|||
385 | dont_allow_on_closed_pull_request=True |
|
|||
386 | ) |
|
|||
387 | except StatusChangeOnClosedPullRequestError: |
|
|||
388 | msg = _('Changing the status of a commit associated with ' |
|
|||
389 | 'a closed pull request is not allowed') |
|
|||
390 | log.exception(msg) |
|
|||
391 | h.flash(msg, category='warning') |
|
|||
392 | return redirect(h.url( |
|
|||
393 | 'changeset_home', repo_name=repo_name, |
|
|||
394 | revision=current_id)) |
|
|||
395 |
|
||||
396 | # finalize, commit and redirect |
|
|||
397 | Session().commit() |
|
|||
398 |
|
||||
399 | data = { |
|
|||
400 | 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))), |
|
|||
401 | } |
|
|||
402 | if comment: |
|
|||
403 | data.update(comment.get_dict()) |
|
|||
404 | data.update({'rendered_text': |
|
|||
405 | render('changeset/changeset_comment_block.mako')}) |
|
|||
406 |
|
||||
407 | return data |
|
|||
408 |
|
||||
409 | @LoginRequired() |
|
|||
410 | @NotAnonymous() |
|
|||
411 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
|||
412 | 'repository.admin') |
|
|||
413 | @auth.CSRFRequired() |
|
|||
414 | def preview_comment(self): |
|
|||
415 | # Technically a CSRF token is not needed as no state changes with this |
|
|||
416 | # call. However, as this is a POST is better to have it, so automated |
|
|||
417 | # tools don't flag it as potential CSRF. |
|
|||
418 | # Post is required because the payload could be bigger than the maximum |
|
|||
419 | # allowed by GET. |
|
|||
420 | if not request.environ.get('HTTP_X_PARTIAL_XHR'): |
|
|||
421 | raise HTTPBadRequest() |
|
|||
422 | text = request.POST.get('text') |
|
|||
423 | renderer = request.POST.get('renderer') or 'rst' |
|
|||
424 | if text: |
|
|||
425 | return h.render(text, renderer=renderer, mentions=True) |
|
|||
426 | return '' |
|
|||
427 |
|
||||
428 | @LoginRequired() |
|
|||
429 | @NotAnonymous() |
|
|||
430 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
|||
431 | 'repository.admin') |
|
|||
432 | @auth.CSRFRequired() |
|
|||
433 | @jsonify |
|
|||
434 | def delete_comment(self, repo_name, comment_id): |
|
|||
435 | comment = ChangesetComment.get_or_404(safe_int(comment_id)) |
|
|||
436 | if not comment: |
|
|||
437 | log.debug('Comment with id:%s not found, skipping', comment_id) |
|
|||
438 | # comment already deleted in another call probably |
|
|||
439 | return True |
|
|||
440 |
|
||||
441 | is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name) |
|
|||
442 | super_admin = h.HasPermissionAny('hg.admin')() |
|
|||
443 | comment_owner = (comment.author.user_id == c.rhodecode_user.user_id) |
|
|||
444 | is_repo_comment = comment.repo.repo_name == c.repo_name |
|
|||
445 | comment_repo_admin = is_repo_admin and is_repo_comment |
|
|||
446 |
|
||||
447 | if super_admin or comment_owner or comment_repo_admin: |
|
|||
448 | CommentsModel().delete(comment=comment, user=c.rhodecode_user) |
|
|||
449 | Session().commit() |
|
|||
450 | return True |
|
|||
451 | else: |
|
|||
452 | log.warning('No permissions for user %s to delete comment_id: %s', |
|
|||
453 | c.rhodecode_user, comment_id) |
|
|||
454 | raise HTTPNotFound() |
|
|||
455 |
|
||||
456 | @LoginRequired() |
|
|||
457 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
|||
458 | 'repository.admin') |
|
|||
459 | @jsonify |
|
|||
460 | def changeset_info(self, repo_name, revision): |
|
|||
461 | if request.is_xhr: |
|
|||
462 | try: |
|
|||
463 | return c.rhodecode_repo.get_commit(commit_id=revision) |
|
|||
464 | except CommitDoesNotExistError as e: |
|
|||
465 | return EmptyCommit(message=str(e)) |
|
|||
466 | else: |
|
|||
467 | raise HTTPBadRequest() |
|
|||
468 |
|
||||
469 | @LoginRequired() |
|
|||
470 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
|||
471 | 'repository.admin') |
|
|||
472 | @jsonify |
|
|||
473 | def changeset_children(self, repo_name, revision): |
|
|||
474 | if request.is_xhr: |
|
|||
475 | commit = c.rhodecode_repo.get_commit(commit_id=revision) |
|
|||
476 | result = {"results": commit.children} |
|
|||
477 | return result |
|
|||
478 | else: |
|
|||
479 | raise HTTPBadRequest() |
|
|||
480 |
|
||||
481 | @LoginRequired() |
|
|||
482 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
|||
483 | 'repository.admin') |
|
|||
484 | @jsonify |
|
|||
485 | def changeset_parents(self, repo_name, revision): |
|
|||
486 | if request.is_xhr: |
|
|||
487 | commit = c.rhodecode_repo.get_commit(commit_id=revision) |
|
|||
488 | result = {"results": commit.parents} |
|
|||
489 | return result |
|
|||
490 | else: |
|
|||
491 | raise HTTPBadRequest() |
|
General Comments 0
You need to be logged in to leave comments.
Login now