##// END OF EJS Templates
repo-commits: ported changeset code into pyramid views....
marcink -
r1951:965019b0 default
parent child Browse files
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 # RepoFilesView.repo_files_diff
215 # RepoCommitsView:repo_commit_raw
216 # RepoFilesView.repo_archivefile
216 # RepoFilesView:repo_files_diff
217 # RepoFilesView.repo_file_raw
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 # RepoFilesView.repo_files_diff
189 # RepoCommitsView:repo_commit_raw
190 # RepoFilesView.repo_archivefile
190 # RepoFilesView:repo_files_diff
191 # RepoFilesView.repo_file_raw
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.url('changeset_home',
175 'url': h.route_path('repo_commit',
176 repo_name=entry['repository'],
176 repo_name=entry['repository'],
177 revision=entry['commit_id'])
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_entry == whitelist['api_access_controllers_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 ['ChangesetController:changeset_raw'] == \
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 ['ChangesetController:changeset_raw'] == \
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 TestCommitCommentsController(TestController):
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, revision=commit_id), params=params)
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, revision=commit_id))
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, revision=commit_id), params=params)
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, revision=commit_id))
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, revision=commit_id), params=params)
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, revision=commit_id))
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, revision=commit_id))
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 url(
241 route_path(
222 controller='changeset', action='comment',
242 'repo_commit_comment_create',
223 repo_name=backend.repo_name, revision=commit_id),
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, comment_id=comment_id),
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, revision=commit_id))
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=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 "%d Commit comments", comments) % comments
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 TestChangesetController(object):
46 class TestRepoCommitView(object):
29
47
30 def test_index(self, backend):
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(url(
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_index_raw(self, backend):
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(url(
58 response = self.app.get(route_path(
42 controller='changeset', action='changeset_raw',
59 'repo_commit_raw',
43 repo_name=backend.repo_name, revision=commit_id))
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_index_raw_patch(self, backend):
63 def test_show_raw_patch(self, backend):
47 response = self.app.get(url(
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_index_changeset_download(self, backend):
69 def test_commit_download(self, backend):
54 response = self.app.get(url(
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 revision=self.commit_id[backend.alias]))
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(url(
83 response = self.app.get(route_path(
68 controller='changeset', action='index',
84 'repo_commit',
69 repo_name=backend.repo_name, revision=commit_id))
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(url(
117 response = self.app.get(route_path(
102 controller='changeset', action='index',
118 'repo_commit',
103 repo_name=backend.repo_name, revision=commit_id))
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(url(
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 revision=commit_id_ranges[backend.alias]))
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 'changeset_home', repo_name=self.db_repo_name,
142 'repo_commit', repo_name=self.db_repo_name,
143 revision=commit.raw_id),
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 'changeset_home', repo_name=self.db_repo_name,
188 'repo_commit', repo_name=self.db_repo_name,
189 revision=commit.raw_id),
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('changeset_home', repo_name=self.db_repo_name,
1046 h.route_path('repo_commit', repo_name=self.db_repo_name,
1047 revision='tip'))
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('changeset_home', repo_name=self.db_repo_name,
1136 h.route_path('repo_commit', repo_name=self.db_repo_name,
1137 revision='tip'))
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('changeset_home', repo_name=self.db_repo_name,
1164 h.route_path('repo_commit', repo_name=self.db_repo_name,
1165 revision='tip'))
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 'changeset_home', repo_name=self.db_repo_name, revision='tip')
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 = url('changeset_home', repo_name=repo_name,
297 _url = h.route_path('repo_commit', repo_name=repo_name,
296 revision=commit_or_id.raw_id)
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(controller_name, whitelist=None, auth_token=None):
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(controller_name, entry):
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('controller:%s matches entry in whitelist'
775 log.debug('view: `%s` matches entry in whitelist: %s'
776 % (controller_name,))
776 % (view_name, whitelist))
777 else:
777 else:
778 msg = ('controller: %s does *NOT* match any entry in whitelist'
778 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
779 % (controller_name,))
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('changeset_home', repo_name=repository,
1593 'url': route_url('repo_commit', repo_name=repository,
1594 revision=commit_id, qualified=True),
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('changeset_comment_preview',
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('changeset_comment',
133 this.submitUrl = pyroutes.url('repo_commit_comment_create',
133 {'repo_name': templateContext.repo_name,
134 {'repo_name': templateContext.repo_name,
134 'revision': this.commitId});
135 'commit_id': this.commitId});
135 this.selfUrl = pyroutes.url('changeset_home',
136 this.selfUrl = pyroutes.url('repo_commit',
136 {'repo_name': templateContext.repo_name,
137 {'repo_name': templateContext.repo_name,
137 'revision': this.commitId});
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.url('changeset_home',repo_name=c.repo_name,revision=c.repo_info.changeset_cache.get('raw_id'))), '', ''),
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('changeset_home',
164 var url = pyroutes.url('repo_commit',
165 {'repo_name': '${c.repo_name}',
165 {'repo_name': '${c.repo_name}',
166 'revision': revStart+'...'+revEnd});
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 %s' % c.statuses.get(commit.raw_id)[0]}"></div>
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: %s') % h.commit_status_lbl(c.statuses.get(commit.raw_id)[0])}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
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 %s' % c.statuses.get(commit.raw_id)[0]}"></div>
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.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
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.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id)}">
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.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}">
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.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}">
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.url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
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.url('changeset_raw_home',repo_name=c.repo_name,revision=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
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.url('changeset_patch_home',repo_name=c.repo_name,revision=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Patch diff'))}">
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.url('changeset_download_home',repo_name=c.repo_name,revision=c.commit.raw_id,diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
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.GET)}
152 ${c.ignorews_url(request)}
153 |
153 |
154 ${c.context_url(request.GET)}
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.url('changeset_comment', repo_name=c.repo_name, revision=c.commit.raw_id),
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.url('changeset_children',repo_name=c.repo_name, revision=c.commit.raw_id)}',
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('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
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('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
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('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
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.url("changeset_parents",repo_name=c.repo_name, revision=c.commit.raw_id)}',
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('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
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('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
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('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
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="${h.url.current(version=comment.pull_request_version_id, anchor='comment-{}'.format(comment.comment_id))}">
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.url('changeset_raw_home',repo_name=c.repo_name,revision='?')}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
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.url('changeset_patch_home',repo_name=c.repo_name,revision='?')}" class="tooltip" title="${h.tooltip(_('Patch diff'))}">
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.url('changeset_download_home',repo_name=c.repo_name,revision='?',diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
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.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id)}">${'r%s:%s' % (commit.revision,h.short_id(commit.raw_id))}</a> -
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.GET, h.FID('', filediff.patch['filename']))}
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.GET, h.FID('', filediff.patch['filename']))}
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.url('changeset_home',repo_name=c.repo_name,revision=annotation.raw_id)}">
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.url('changeset_home', repo_name=c.repo_name, revision=c.ancestor)}">
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.url('changeset_raw_home',repo_name=c.repo_name,revision='?')}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
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.url('changeset_patch_home',repo_name=c.repo_name,revision='?')}" class="tooltip" title="${h.tooltip(_('Patch diff'))}">
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.url('changeset_download_home',repo_name=c.repo_name,revision='?',diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
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.url('changeset_comment', repo_name=c.repo_name, revision='0'*16), None, is_compare=True, form_extras=revs(c.commit_ranges))}
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.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
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('changeset_comment_preview', {'repo_name': 'rhodecode-momentum'});
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('changeset_home', repo_name=c.rhodecode_db_repo.repo_name, revision=commit.raw_id, qualified=True)}">${h.show_id(commit)}</a>
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.url('changeset_home',repo_name=c.repo_name,revision=c.commit.raw_id)}">${h.show_id(c.commit)}</a>
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('changeset_comment_preview', {'repo_name': '${c.repo_name}'});
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.url('changeset_home',repo_name=c.repo_name,revision=c.commit.raw_id)}">${h.show_id(c.commit)}</a></code>
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.url('changeset_home',repo_name=c.repo_name,revision=c.file_last_commit.raw_id)}">${h.show_id(c.file_last_commit)}</a></code>
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.url('changeset_home',repo_name=c.repo_name,revision=c.file.commit.raw_id))}</span>
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('changeset_comment_preview', {'repo_name': '${c.repo_name}'});
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.url('changeset_home', repo_name=c.target_repo.repo_name, revision=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a></code>
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.url('changeset_home', repo_name=c.target_repo.repo_name, revision=commit.raw_id)}">
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.url('changeset_home',repo_name=entry['repository'],revision=entry['commit_id']))}
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 %s' % c.statuses.get(cs.raw_id)[0]}"></div>
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: %s') % h.commit_status_lbl(c.statuses.get(cs.raw_id)[0])}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
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 %s' % c.statuses.get(cs.raw_id)[0]}"></div>
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.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
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.url('changeset_home', repo_name=c.repo_name, revision=cs.raw_id)}">${h.show_id(cs)}</a></pre>
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={'HTTP_X_PARTIAL_XHR': '1'},)
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('pylons.url', fake_url):
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 ## RHODECODE ENTERPRISE CONFIGURATION ##
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 <inifile.ini> --paste <inifile.ini>
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/<prefix>. Enable `filter-with =` option below as well.
156 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
157 ## optionally set prefix like: `prefix = /<your-prefix>`
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/<gistid>.
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/<gistid>
245 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
244 gist_alias_url =
246 gist_alias_url =
245
247
246 ## List of controllers (using glob pattern syntax) that AUTH TOKENS could be
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 = <token> to the url authenticates this request as if it
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 ## Recommended controllers to enable:
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 = <key_for_encryption>
426 #beaker.session.encrypt_key = key_for_encryption
425 #beaker.session.validate_key = <validation_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 = /<your-prefix>
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 ammount of seconds
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` - using http-rpc backend
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` - using http-rpc backend
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