# HG changeset patch # User Marcin Kuzminski # Date 2017-07-24 15:04:14 # Node ID 965019b0e8ff1651404b66f1c20bcd29027592d1 # Parent 0694aced988814d69ff4bb3ce87fef06ee1f9e1a repo-commits: ported changeset code into pyramid views. - refactored part of naming to use commit_id instead of revision - renamed some parts to repo_commits instead of changeset without breaking backward compat - small improvements and fixes for tests diff --git a/configs/development.ini b/configs/development.ini --- a/configs/development.ini +++ b/configs/development.ini @@ -210,11 +210,12 @@ gist_alias_url = ## The list should be "," separated and on a single line. ## ## Most common views to enable: -# ChangesetController:changeset_patch -# ChangesetController:changeset_raw -# RepoFilesView.repo_files_diff -# RepoFilesView.repo_archivefile -# RepoFilesView.repo_file_raw +# RepoCommitsView:repo_commit_download +# RepoCommitsView:repo_commit_patch +# RepoCommitsView:repo_commit_raw +# RepoFilesView:repo_files_diff +# RepoFilesView:repo_archivefile +# RepoFilesView:repo_file_raw # GistView:* api_access_controllers_whitelist = diff --git a/configs/production.ini b/configs/production.ini --- a/configs/production.ini +++ b/configs/production.ini @@ -184,11 +184,12 @@ gist_alias_url = ## The list should be "," separated and on a single line. ## ## Most common views to enable: -# ChangesetController:changeset_patch -# ChangesetController:changeset_raw -# RepoFilesView.repo_files_diff -# RepoFilesView.repo_archivefile -# RepoFilesView.repo_file_raw +# RepoCommitsView:repo_commit_download +# RepoCommitsView:repo_commit_patch +# RepoCommitsView:repo_commit_raw +# RepoFilesView:repo_files_diff +# RepoFilesView:repo_archivefile +# RepoFilesView:repo_file_raw # GistView:* api_access_controllers_whitelist = diff --git a/docs/api/api.rst b/docs/api/api.rst --- a/docs/api/api.rst +++ b/docs/api/api.rst @@ -42,7 +42,7 @@ archive. ## Syntax is :. ## The list should be "," separated and on a single line. ## - api_access_controllers_whitelist = ChangesetController:changeset_patch,ChangesetController:changeset_raw,ilesController:raw,FilesController:archivefile, + api_access_controllers_whitelist = RepoCommitsView:repo_commit_raw,RepoCommitsView:repo_commit_patch,RepoCommitsView:repo_commit_download After this change, a |RCE| view can be accessed without login by adding a GET parameter ``?auth_token=`` to a url. For example to diff --git a/rhodecode/apps/home/views.py b/rhodecode/apps/home/views.py --- a/rhodecode/apps/home/views.py +++ b/rhodecode/apps/home/views.py @@ -172,9 +172,9 @@ class HomeView(BaseAppView): 'text': entry['commit_id'], 'type': 'commit', 'obj': {'repo': entry['repository']}, - 'url': h.url('changeset_home', + 'url': h.route_path('repo_commit', repo_name=entry['repository'], - revision=entry['commit_id']) + commit_id=entry['commit_id']) } for entry in result['results']] diff --git a/rhodecode/apps/login/tests/test_login.py b/rhodecode/apps/login/tests/test_login.py --- a/rhodecode/apps/login/tests/test_login.py +++ b/rhodecode/apps/login/tests/test_login.py @@ -36,6 +36,8 @@ from rhodecode.model.meta import Session fixture = Fixture() +whitelist_view = ['RepoCommitsView:repo_commit_raw'] + def route_path(name, params=None, **kwargs): import urllib @@ -474,11 +476,10 @@ class TestLoginController(object): def test_access_whitelisted_page_via_auth_token( self, test_name, auth_token, code, user_admin): - whitelist_entry = ['ChangesetController:changeset_raw'] - whitelist = self._get_api_whitelist(whitelist_entry) + whitelist = self._get_api_whitelist(whitelist_view) with mock.patch.dict('rhodecode.CONFIG', whitelist): - assert whitelist_entry == whitelist['api_access_controllers_whitelist'] + assert whitelist_view == whitelist['api_access_controllers_whitelist'] if test_name == 'proper_auth_token': auth_token = user_admin.api_key @@ -492,10 +493,9 @@ class TestLoginController(object): status=code) def test_access_page_via_extra_auth_token(self): - whitelist = self._get_api_whitelist( - ['ChangesetController:changeset_raw']) + whitelist = self._get_api_whitelist(whitelist_view) with mock.patch.dict('rhodecode.CONFIG', whitelist): - assert ['ChangesetController:changeset_raw'] == \ + assert whitelist_view == \ whitelist['api_access_controllers_whitelist'] new_auth_token = AuthTokenModel().create( @@ -509,10 +509,9 @@ class TestLoginController(object): status=200) def test_access_page_via_expired_auth_token(self): - whitelist = self._get_api_whitelist( - ['ChangesetController:changeset_raw']) + whitelist = self._get_api_whitelist(whitelist_view) with mock.patch.dict('rhodecode.CONFIG', whitelist): - assert ['ChangesetController:changeset_raw'] == \ + assert whitelist_view == \ whitelist['api_access_controllers_whitelist'] new_auth_token = AuthTokenModel().create( diff --git a/rhodecode/apps/repository/__init__.py b/rhodecode/apps/repository/__init__.py --- a/rhodecode/apps/repository/__init__.py +++ b/rhodecode/apps/repository/__init__.py @@ -33,10 +33,52 @@ def includeme(config): pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True) # repo commits + config.add_route( name='repo_commit', pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True) + config.add_route( + name='repo_commit_children', + pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True) + + config.add_route( + name='repo_commit_parents', + pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True) + + # still working url for backward compat. + config.add_route( + name='repo_commit_raw_deprecated', + pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True) + + config.add_route( + name='repo_commit_raw', + pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True) + + config.add_route( + name='repo_commit_patch', + pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True) + + config.add_route( + name='repo_commit_download', + pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True) + + config.add_route( + name='repo_commit_data', + pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True) + + config.add_route( + name='repo_commit_comment_create', + pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True) + + config.add_route( + name='repo_commit_comment_preview', + pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True) + + config.add_route( + name='repo_commit_comment_delete', + pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True) + # repo files config.add_route( name='repo_archivefile', @@ -180,21 +222,6 @@ def includeme(config): pattern='/{repo_name:.*?[^/]}/pull-request-data', repo_route=True, repo_accepted_types=['hg', 'git']) - # commits aka changesets - # TODO(dan): handle default landing revision ? - config.add_route( - name='changeset_home', - pattern='/{repo_name:.*?[^/]}/changeset/{revision}', - repo_route=True) - config.add_route( - name='changeset_children', - pattern='/{repo_name:.*?[^/]}/changeset_children/{revision}', - repo_route=True) - config.add_route( - name='changeset_parents', - pattern='/{repo_name:.*?[^/]}/changeset_parents/{revision}', - repo_route=True) - # Settings config.add_route( name='edit_repo', diff --git a/rhodecode/tests/functional/test_commit_comments.py b/rhodecode/apps/repository/tests/test_repo_commit_comments.py rename from rhodecode/tests/functional/test_commit_comments.py rename to rhodecode/apps/repository/tests/test_repo_commit_comments.py --- a/rhodecode/tests/functional/test_commit_comments.py +++ b/rhodecode/apps/repository/tests/test_repo_commit_comments.py @@ -18,18 +18,33 @@ # RhodeCode Enterprise Edition, including its added features, Support services, # and proprietary license terms, please see https://rhodecode.com/licenses/ -from pylons.i18n import ungettext import pytest -from rhodecode.tests import * +from rhodecode.tests import TestController + from rhodecode.model.db import ( ChangesetComment, Notification, UserNotification) from rhodecode.model.meta import Session from rhodecode.lib import helpers as h +def route_path(name, params=None, **kwargs): + import urllib + + base_url = { + 'repo_commit': '/{repo_name}/changeset/{commit_id}', + 'repo_commit_comment_create': '/{repo_name}/changeset/{commit_id}/comment/create', + 'repo_commit_comment_preview': '/{repo_name}/changeset/{commit_id}/comment/preview', + 'repo_commit_comment_delete': '/{repo_name}/changeset/{commit_id}/comment/{comment_id}/delete', + }[name].format(**kwargs) + + if params: + base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) + return base_url + + @pytest.mark.backends("git", "hg", "svn") -class TestCommitCommentsController(TestController): +class TestRepoCommitCommentsView(TestController): @pytest.fixture(autouse=True) def prepare(self, request, pylonsapp): @@ -62,12 +77,13 @@ class TestCommitCommentsController(TestC params = {'text': text, 'csrf_token': self.csrf_token, 'comment_type': comment_type} self.app.post( - url(controller='changeset', action='comment', - repo_name=backend.repo_name, revision=commit_id), params=params) + route_path('repo_commit_comment_create', + repo_name=backend.repo_name, commit_id=commit_id), + params=params) response = self.app.get( - url(controller='changeset', action='index', - repo_name=backend.repo_name, revision=commit_id)) + route_path('repo_commit', + repo_name=backend.repo_name, commit_id=commit_id)) # test DB assert ChangesetComment.query().count() == 1 @@ -103,12 +119,13 @@ class TestCommitCommentsController(TestC 'csrf_token': self.csrf_token} self.app.post( - url(controller='changeset', action='comment', - repo_name=backend.repo_name, revision=commit_id), params=params) + route_path('repo_commit_comment_create', + repo_name=backend.repo_name, commit_id=commit_id), + params=params) response = self.app.get( - url(controller='changeset', action='index', - repo_name=backend.repo_name, revision=commit_id)) + route_path('repo_commit', + repo_name=backend.repo_name, commit_id=commit_id)) # test DB assert ChangesetComment.query().count() == 1 @@ -153,12 +170,13 @@ class TestCommitCommentsController(TestC params = {'text': text, 'csrf_token': self.csrf_token} self.app.post( - url(controller='changeset', action='comment', - repo_name=backend.repo_name, revision=commit_id), params=params) + route_path('repo_commit_comment_create', + repo_name=backend.repo_name, commit_id=commit_id), + params=params) response = self.app.get( - url(controller='changeset', action='index', - repo_name=backend.repo_name, revision=commit_id)) + route_path('repo_commit', + repo_name=backend.repo_name, commit_id=commit_id)) # test DB assert ChangesetComment.query().count() == 1 assert_comment_links(response, ChangesetComment.query().count(), 0) @@ -183,12 +201,14 @@ class TestCommitCommentsController(TestC 'csrf_token': self.csrf_token} self.app.post( - url(controller='changeset', action='comment', - repo_name=backend.repo_name, revision=commit_id), params=params) + route_path( + 'repo_commit_comment_create', + repo_name=backend.repo_name, commit_id=commit_id), + params=params) response = self.app.get( - url(controller='changeset', action='index', - repo_name=backend.repo_name, revision=commit_id)) + route_path('repo_commit', + repo_name=backend.repo_name, commit_id=commit_id)) # test DB assert ChangesetComment.query().count() == 1 @@ -218,9 +238,9 @@ class TestCommitCommentsController(TestC params = {'text': text, 'csrf_token': self.csrf_token} self.app.post( - url( - controller='changeset', action='comment', - repo_name=backend.repo_name, revision=commit_id), + route_path( + 'repo_commit_comment_create', + repo_name=backend.repo_name, commit_id=commit_id), params=params) comments = ChangesetComment.query().all() @@ -228,16 +248,18 @@ class TestCommitCommentsController(TestC comment_id = comments[0].comment_id self.app.post( - url(controller='changeset', action='delete_comment', - repo_name=backend.repo_name, comment_id=comment_id), - params={'_method': 'delete', 'csrf_token': self.csrf_token}) + route_path('repo_commit_comment_delete', + repo_name=backend.repo_name, + commit_id=commit_id, + comment_id=comment_id), + params={'csrf_token': self.csrf_token}) comments = ChangesetComment.query().all() assert len(comments) == 0 response = self.app.get( - url(controller='changeset', action='index', - repo_name=backend.repo_name, revision=commit_id)) + route_path('repo_commit', + repo_name=backend.repo_name, commit_id=commit_id)) assert_comment_links(response, 0, 0) @pytest.mark.parametrize('renderer, input, output', [ @@ -251,36 +273,39 @@ class TestCommitCommentsController(TestC ('markdown', '**bold**', 'bold'), ], ids=['rst-plain', 'rst-header', 'rst-italics', 'rst-bold', 'md-plain', 'md-header', 'md-italics', 'md-bold', ]) - def test_preview(self, renderer, input, output, backend): + def test_preview(self, renderer, input, output, backend, xhr_header): self.log_user() params = { 'renderer': renderer, 'text': input, 'csrf_token': self.csrf_token } - environ = { - 'HTTP_X_PARTIAL_XHR': 'true' - } + commit_id = '0' * 16 # fake this for tests response = self.app.post( - url(controller='changeset', - action='preview_comment', - repo_name=backend.repo_name), + route_path('repo_commit_comment_preview', + repo_name=backend.repo_name, commit_id=commit_id,), params=params, - extra_environ=environ) + extra_environ=xhr_header) response.mustcontain(output) def assert_comment_links(response, comments, inline_comments): - comments_text = ungettext("%d Commit comment", - "%d Commit comments", comments) % comments + if comments == 1: + comments_text = "%d Commit comment" % comments + else: + comments_text = "%d Commit comments" % comments + + if inline_comments == 1: + inline_comments_text = "%d Inline Comment" % inline_comments + else: + inline_comments_text = "%d Inline Comments" % inline_comments + if comments: response.mustcontain('%s,' % comments_text) else: response.mustcontain(comments_text) - inline_comments_text = ungettext("%d Inline Comment", "%d Inline Comments", - inline_comments) % inline_comments if inline_comments: response.mustcontain( 'id="inline-comments-counter">%s. +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ + + +import logging +import collections + +from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound +from pyramid.view import view_config +from pyramid.renderers import render +from pyramid.response import Response + +from rhodecode.apps._base import RepoAppView + +from rhodecode.lib import diffs, codeblocks +from rhodecode.lib.auth import ( + LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired) + +from rhodecode.lib.compat import OrderedDict +from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError +import rhodecode.lib.helpers as h +from rhodecode.lib.utils2 import safe_unicode, safe_int +from rhodecode.lib.vcs.backends.base import EmptyCommit +from rhodecode.lib.vcs.exceptions import ( + RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError) +from rhodecode.model.db import ChangesetComment, ChangesetStatus +from rhodecode.model.changeset_status import ChangesetStatusModel +from rhodecode.model.comment import CommentsModel +from rhodecode.model.meta import Session + + +log = logging.getLogger(__name__) + + +def _update_with_GET(params, request): + for k in ['diff1', 'diff2', 'diff']: + params[k] += request.GET.getall(k) + + +def get_ignore_ws(fid, request): + ig_ws_global = request.GET.get('ignorews') + ig_ws = filter(lambda k: k.startswith('WS'), request.GET.getall(fid)) + if ig_ws: + try: + return int(ig_ws[0].split(':')[-1]) + except Exception: + pass + return ig_ws_global + + +def _ignorews_url(request, fileid=None): + _ = request.translate + fileid = str(fileid) if fileid else None + params = collections.defaultdict(list) + _update_with_GET(params, request) + label = _('Show whitespace') + tooltiplbl = _('Show whitespace for all diffs') + ig_ws = get_ignore_ws(fileid, request) + ln_ctx = get_line_ctx(fileid, request) + + if ig_ws is None: + params['ignorews'] += [1] + label = _('Ignore whitespace') + tooltiplbl = _('Ignore whitespace for all diffs') + ctx_key = 'context' + ctx_val = ln_ctx + + # if we have passed in ln_ctx pass it along to our params + if ln_ctx: + params[ctx_key] += [ctx_val] + + if fileid: + params['anchor'] = 'a_' + fileid + return h.link_to(label, request.current_route_path(_query=params), + title=tooltiplbl, class_='tooltip') + + +def get_line_ctx(fid, request): + ln_ctx_global = request.GET.get('context') + if fid: + ln_ctx = filter(lambda k: k.startswith('C'), request.GET.getall(fid)) + else: + _ln_ctx = filter(lambda k: k.startswith('C'), request.GET) + ln_ctx = request.GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global + if ln_ctx: + ln_ctx = [ln_ctx] + + if ln_ctx: + retval = ln_ctx[0].split(':')[-1] + else: + retval = ln_ctx_global + + try: + return int(retval) + except Exception: + return 3 + + +def _context_url(request, fileid=None): + """ + Generates a url for context lines. + + :param fileid: + """ + + _ = request.translate + fileid = str(fileid) if fileid else None + ig_ws = get_ignore_ws(fileid, request) + ln_ctx = (get_line_ctx(fileid, request) or 3) * 2 + + params = collections.defaultdict(list) + _update_with_GET(params, request) + + if ln_ctx > 0: + params['context'] += [ln_ctx] + + if ig_ws: + ig_ws_key = 'ignorews' + ig_ws_val = 1 + params[ig_ws_key] += [ig_ws_val] + + lbl = _('Increase context') + tooltiplbl = _('Increase context for all diffs') + + if fileid: + params['anchor'] = 'a_' + fileid + return h.link_to(lbl, request.current_route_path(_query=params), + title=tooltiplbl, class_='tooltip') + + +class RepoCommitsView(RepoAppView): + def load_default_context(self): + c = self._get_local_tmpl_context(include_app_defaults=True) + + # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead + c.repo_info = self.db_repo + c.rhodecode_repo = self.rhodecode_vcs_repo + + self._register_global_c(c) + return c + + def _commit(self, commit_id_range, method): + _ = self.request.translate + c = self.load_default_context() + c.ignorews_url = _ignorews_url + c.context_url = _context_url + c.fulldiff = self.request.GET.get('fulldiff') + + # fetch global flags of ignore ws or context lines + context_lcl = get_line_ctx('', self.request) + ign_whitespace_lcl = get_ignore_ws('', self.request) + + # diff_limit will cut off the whole diff if the limit is applied + # otherwise it will just hide the big files from the front-end + diff_limit = c.visual.cut_off_limit_diff + file_limit = c.visual.cut_off_limit_file + + # get ranges of commit ids if preset + commit_range = commit_id_range.split('...')[:2] + + try: + pre_load = ['affected_files', 'author', 'branch', 'date', + 'message', 'parents'] + + if len(commit_range) == 2: + commits = self.rhodecode_vcs_repo.get_commits( + start_id=commit_range[0], end_id=commit_range[1], + pre_load=pre_load) + commits = list(commits) + else: + commits = [self.rhodecode_vcs_repo.get_commit( + commit_id=commit_id_range, pre_load=pre_load)] + + c.commit_ranges = commits + if not c.commit_ranges: + raise RepositoryError( + 'The commit range returned an empty result') + except CommitDoesNotExistError: + msg = _('No such commit exists for this repository') + h.flash(msg, category='error') + raise HTTPNotFound() + except Exception: + log.exception("General failure") + raise HTTPNotFound() + + c.changes = OrderedDict() + c.lines_added = 0 + c.lines_deleted = 0 + + # auto collapse if we have more than limit + collapse_limit = diffs.DiffProcessor._collapse_commits_over + c.collapse_all_commits = len(c.commit_ranges) > collapse_limit + + c.commit_statuses = ChangesetStatus.STATUSES + c.inline_comments = [] + c.files = [] + + c.statuses = [] + c.comments = [] + c.unresolved_comments = [] + if len(c.commit_ranges) == 1: + commit = c.commit_ranges[0] + c.comments = CommentsModel().get_comments( + self.db_repo.repo_id, + revision=commit.raw_id) + c.statuses.append(ChangesetStatusModel().get_status( + self.db_repo.repo_id, commit.raw_id)) + # comments from PR + statuses = ChangesetStatusModel().get_statuses( + self.db_repo.repo_id, commit.raw_id, + with_revisions=True) + prs = set(st.pull_request for st in statuses + if st.pull_request is not None) + # from associated statuses, check the pull requests, and + # show comments from them + for pr in prs: + c.comments.extend(pr.comments) + + c.unresolved_comments = CommentsModel()\ + .get_commit_unresolved_todos(commit.raw_id) + + diff = None + # Iterate over ranges (default commit view is always one commit) + for commit in c.commit_ranges: + c.changes[commit.raw_id] = [] + + commit2 = commit + commit1 = commit.parents[0] if commit.parents else EmptyCommit() + + _diff = self.rhodecode_vcs_repo.get_diff( + commit1, commit2, + ignore_whitespace=ign_whitespace_lcl, context=context_lcl) + diff_processor = diffs.DiffProcessor( + _diff, format='newdiff', diff_limit=diff_limit, + file_limit=file_limit, show_full_diff=c.fulldiff) + + commit_changes = OrderedDict() + if method == 'show': + _parsed = diff_processor.prepare() + c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer) + + _parsed = diff_processor.prepare() + + def _node_getter(commit): + def get_node(fname): + try: + return commit.get_node(fname) + except NodeDoesNotExistError: + return None + return get_node + + inline_comments = CommentsModel().get_inline_comments( + self.db_repo.repo_id, revision=commit.raw_id) + c.inline_cnt = CommentsModel().get_inline_comments_count( + inline_comments) + + diffset = codeblocks.DiffSet( + repo_name=self.db_repo_name, + source_node_getter=_node_getter(commit1), + target_node_getter=_node_getter(commit2), + comments=inline_comments) + diffset = diffset.render_patchset( + _parsed, commit1.raw_id, commit2.raw_id) + + c.changes[commit.raw_id] = diffset + else: + # downloads/raw we only need RAW diff nothing else + diff = diff_processor.as_raw() + c.changes[commit.raw_id] = [None, None, None, None, diff, None, None] + + # sort comments by how they were generated + c.comments = sorted(c.comments, key=lambda x: x.comment_id) + + if len(c.commit_ranges) == 1: + c.commit = c.commit_ranges[0] + c.parent_tmpl = ''.join( + '# Parent %s\n' % x.raw_id for x in c.commit.parents) + + if method == 'download': + response = Response(diff) + response.content_type = 'text/plain' + response.content_disposition = ( + 'attachment; filename=%s.diff' % commit_id_range[:12]) + return response + elif method == 'patch': + c.diff = safe_unicode(diff) + patch = render( + 'rhodecode:templates/changeset/patch_changeset.mako', + self._get_template_context(c), self.request) + response = Response(patch) + response.content_type = 'text/plain' + return response + elif method == 'raw': + response = Response(diff) + response.content_type = 'text/plain' + return response + elif method == 'show': + if len(c.commit_ranges) == 1: + html = render( + 'rhodecode:templates/changeset/changeset.mako', + self._get_template_context(c), self.request) + return Response(html) + else: + c.ancestor = None + c.target_repo = self.db_repo + html = render( + 'rhodecode:templates/changeset/changeset_range.mako', + self._get_template_context(c), self.request) + return Response(html) + + raise HTTPBadRequest() + + @LoginRequired() + @HasRepoPermissionAnyDecorator( + 'repository.read', 'repository.write', 'repository.admin') + @view_config( + route_name='repo_commit', request_method='GET', + renderer=None) + def repo_commit_show(self): + commit_id = self.request.matchdict['commit_id'] + return self._commit(commit_id, method='show') + + @LoginRequired() + @HasRepoPermissionAnyDecorator( + 'repository.read', 'repository.write', 'repository.admin') + @view_config( + route_name='repo_commit_raw', request_method='GET', + renderer=None) + @view_config( + route_name='repo_commit_raw_deprecated', request_method='GET', + renderer=None) + def repo_commit_raw(self): + commit_id = self.request.matchdict['commit_id'] + return self._commit(commit_id, method='raw') + + @LoginRequired() + @HasRepoPermissionAnyDecorator( + 'repository.read', 'repository.write', 'repository.admin') + @view_config( + route_name='repo_commit_patch', request_method='GET', + renderer=None) + def repo_commit_patch(self): + commit_id = self.request.matchdict['commit_id'] + return self._commit(commit_id, method='patch') + + @LoginRequired() + @HasRepoPermissionAnyDecorator( + 'repository.read', 'repository.write', 'repository.admin') + @view_config( + route_name='repo_commit_download', request_method='GET', + renderer=None) + def repo_commit_download(self): + commit_id = self.request.matchdict['commit_id'] + return self._commit(commit_id, method='download') + + @LoginRequired() + @NotAnonymous() + @HasRepoPermissionAnyDecorator( + 'repository.read', 'repository.write', 'repository.admin') + @CSRFRequired() + @view_config( + route_name='repo_commit_comment_create', request_method='POST', + renderer='json_ext') + def repo_commit_comment_create(self): + _ = self.request.translate + commit_id = self.request.matchdict['commit_id'] + + c = self.load_default_context() + status = self.request.POST.get('changeset_status', None) + text = self.request.POST.get('text') + comment_type = self.request.POST.get('comment_type') + resolves_comment_id = self.request.POST.get('resolves_comment_id', None) + + if status: + text = text or (_('Status change %(transition_icon)s %(status)s') + % {'transition_icon': '>', + 'status': ChangesetStatus.get_status_lbl(status)}) + + multi_commit_ids = [] + for _commit_id in self.request.POST.get('commit_ids', '').split(','): + if _commit_id not in ['', None, EmptyCommit.raw_id]: + if _commit_id not in multi_commit_ids: + multi_commit_ids.append(_commit_id) + + commit_ids = multi_commit_ids or [commit_id] + + comment = None + for current_id in filter(None, commit_ids): + comment = CommentsModel().create( + text=text, + repo=self.db_repo.repo_id, + user=self._rhodecode_db_user.user_id, + commit_id=current_id, + f_path=self.request.POST.get('f_path'), + line_no=self.request.POST.get('line'), + status_change=(ChangesetStatus.get_status_lbl(status) + if status else None), + status_change_type=status, + comment_type=comment_type, + resolves_comment_id=resolves_comment_id + ) + + # get status if set ! + if status: + # if latest status was from pull request and it's closed + # disallow changing status ! + # dont_allow_on_closed_pull_request = True ! + + try: + ChangesetStatusModel().set_status( + self.db_repo.repo_id, + status, + self._rhodecode_db_user.user_id, + comment, + revision=current_id, + dont_allow_on_closed_pull_request=True + ) + except StatusChangeOnClosedPullRequestError: + msg = _('Changing the status of a commit associated with ' + 'a closed pull request is not allowed') + log.exception(msg) + h.flash(msg, category='warning') + raise HTTPFound(h.route_path( + 'repo_commit', repo_name=self.db_repo_name, + commit_id=current_id)) + + # finalize, commit and redirect + Session().commit() + + data = { + 'target_id': h.safeid(h.safe_unicode( + self.request.POST.get('f_path'))), + } + if comment: + c.co = comment + rendered_comment = render( + 'rhodecode:templates/changeset/changeset_comment_block.mako', + self._get_template_context(c), self.request) + + data.update(comment.get_dict()) + data.update({'rendered_text': rendered_comment}) + + return data + + @LoginRequired() + @NotAnonymous() + @HasRepoPermissionAnyDecorator( + 'repository.read', 'repository.write', 'repository.admin') + @CSRFRequired() + @view_config( + route_name='repo_commit_comment_preview', request_method='POST', + renderer='string', xhr=True) + def repo_commit_comment_preview(self): + # Technically a CSRF token is not needed as no state changes with this + # call. However, as this is a POST is better to have it, so automated + # tools don't flag it as potential CSRF. + # Post is required because the payload could be bigger than the maximum + # allowed by GET. + + text = self.request.POST.get('text') + renderer = self.request.POST.get('renderer') or 'rst' + if text: + return h.render(text, renderer=renderer, mentions=True) + return '' + + @LoginRequired() + @NotAnonymous() + @HasRepoPermissionAnyDecorator( + 'repository.read', 'repository.write', 'repository.admin') + @CSRFRequired() + @view_config( + route_name='repo_commit_comment_delete', request_method='POST', + renderer='json_ext') + def repo_commit_comment_delete(self): + commit_id = self.request.matchdict['commit_id'] + comment_id = self.request.matchdict['comment_id'] + + comment = ChangesetComment.get_or_404(safe_int(comment_id)) + if not comment: + log.debug('Comment with id:%s not found, skipping', comment_id) + # comment already deleted in another call probably + return True + + is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name) + super_admin = h.HasPermissionAny('hg.admin')() + comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id) + is_repo_comment = comment.repo.repo_name == self.db_repo_name + comment_repo_admin = is_repo_admin and is_repo_comment + + if super_admin or comment_owner or comment_repo_admin: + CommentsModel().delete(comment=comment, user=self._rhodecode_db_user) + Session().commit() + return True + else: + log.warning('No permissions for user %s to delete comment_id: %s', + self._rhodecode_db_user, comment_id) + raise HTTPNotFound() + + @LoginRequired() + @HasRepoPermissionAnyDecorator( + 'repository.read', 'repository.write', 'repository.admin') + @view_config( + route_name='repo_commit_data', request_method='GET', + renderer='json_ext', xhr=True) + def repo_commit_data(self): + commit_id = self.request.matchdict['commit_id'] + self.load_default_context() + + try: + return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id) + except CommitDoesNotExistError as e: + return EmptyCommit(message=str(e)) + + @LoginRequired() + @HasRepoPermissionAnyDecorator( + 'repository.read', 'repository.write', 'repository.admin') + @view_config( + route_name='repo_commit_children', request_method='GET', + renderer='json_ext', xhr=True) + def repo_commit_children(self): + commit_id = self.request.matchdict['commit_id'] + self.load_default_context() + + commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id) + result = {"results": commit.children} + return result + + @LoginRequired() + @HasRepoPermissionAnyDecorator( + 'repository.read', 'repository.write', 'repository.admin') + @view_config( + route_name='repo_commit_parents', request_method='GET', + renderer='json_ext') + def repo_commit_parents(self): + commit_id = self.request.matchdict['commit_id'] + self.load_default_context() + + commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id) + result = {"results": commit.parents} + return result diff --git a/rhodecode/apps/repository/views/repo_feed.py b/rhodecode/apps/repository/views/repo_feed.py --- a/rhodecode/apps/repository/views/repo_feed.py +++ b/rhodecode/apps/repository/views/repo_feed.py @@ -139,8 +139,8 @@ class RepoFeedView(RepoAppView): author_name=commit.author, description=self._get_description(commit), link=h.route_url( - 'changeset_home', repo_name=self.db_repo_name, - revision=commit.raw_id), + 'repo_commit', repo_name=self.db_repo_name, + commit_id=commit.raw_id), pubdate=date,) return feed.mime_type, feed.writeString('utf-8') @@ -185,8 +185,8 @@ class RepoFeedView(RepoAppView): author_name=commit.author, description=self._get_description(commit), link=h.route_url( - 'changeset_home', repo_name=self.db_repo_name, - revision=commit.raw_id), + 'repo_commit', repo_name=self.db_repo_name, + commit_id=commit.raw_id), pubdate=date,) return feed.mime_type, feed.writeString('utf-8') diff --git a/rhodecode/apps/repository/views/repo_files.py b/rhodecode/apps/repository/views/repo_files.py --- a/rhodecode/apps/repository/views/repo_files.py +++ b/rhodecode/apps/repository/views/repo_files.py @@ -1043,8 +1043,8 @@ class RepoFilesView(RepoAppView): log.exception('Error during commit operation') h.flash(_('Error occurred during commit'), category='error') raise HTTPFound( - h.route_path('changeset_home', repo_name=self.db_repo_name, - revision='tip')) + h.route_path('repo_commit', repo_name=self.db_repo_name, + commit_id='tip')) @LoginRequired() @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin') @@ -1133,8 +1133,8 @@ class RepoFilesView(RepoAppView): if content == old_content and filename == org_filename: h.flash(_('No changes'), category='warning') raise HTTPFound( - h.route_path('changeset_home', repo_name=self.db_repo_name, - revision='tip')) + h.route_path('repo_commit', repo_name=self.db_repo_name, + commit_id='tip')) try: mapping = { org_f_path: { @@ -1161,8 +1161,8 @@ class RepoFilesView(RepoAppView): log.exception('Error occurred during commit') h.flash(_('Error occurred during commit'), category='error') raise HTTPFound( - h.route_path('changeset_home', repo_name=self.db_repo_name, - revision='tip')) + h.route_path('repo_commit', repo_name=self.db_repo_name, + commit_id='tip')) @LoginRequired() @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin') @@ -1222,7 +1222,7 @@ class RepoFilesView(RepoAppView): content = content.file default_redirect_url = h.route_path( - 'changeset_home', repo_name=self.db_repo_name, revision='tip') + 'repo_commit', repo_name=self.db_repo_name, commit_id='tip') # If there's no commit, redirect to repo summary if type(c.commit) is EmptyCommit: diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py --- a/rhodecode/config/routing.py +++ b/rhodecode/config/routing.py @@ -429,19 +429,6 @@ def make_map(config): controller='admin/repos', action='repo_check', requirements=URL_NAME_REQUIREMENTS) - rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}', - controller='changeset', revision='tip', - conditions={'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS, jsroute=True) - rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}', - controller='changeset', revision='tip', action='changeset_children', - conditions={'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}', - controller='changeset', revision='tip', action='changeset_parents', - conditions={'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - # repo edit options rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields', controller='admin/repos', action='edit_fields', @@ -515,54 +502,6 @@ def make_map(config): conditions={'method': ['GET', 'POST'], 'function': check_repo}, requirements=URL_NAME_REQUIREMENTS) - # still working url for backward compat. - rmap.connect('raw_changeset_home_depraced', - '/{repo_name}/raw-changeset/{revision}', - controller='changeset', action='changeset_raw', - revision='tip', conditions={'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - - # new URLs - rmap.connect('changeset_raw_home', - '/{repo_name}/changeset-diff/{revision}', - controller='changeset', action='changeset_raw', - revision='tip', conditions={'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - - rmap.connect('changeset_patch_home', - '/{repo_name}/changeset-patch/{revision}', - controller='changeset', action='changeset_patch', - revision='tip', conditions={'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - - rmap.connect('changeset_download_home', - '/{repo_name}/changeset-download/{revision}', - controller='changeset', action='changeset_download', - revision='tip', conditions={'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - - rmap.connect('changeset_comment', - '/{repo_name}/changeset/{revision}/comment', jsroute=True, - controller='changeset', revision='tip', action='comment', - conditions={'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - - rmap.connect('changeset_comment_preview', - '/{repo_name}/changeset/comment/preview', jsroute=True, - controller='changeset', action='preview_comment', - conditions={'function': check_repo, 'method': ['POST']}, - requirements=URL_NAME_REQUIREMENTS) - - rmap.connect('changeset_comment_delete', - '/{repo_name}/changeset/comment/{comment_id}/delete', - controller='changeset', action='delete_comment', - conditions={'function': check_repo, 'method': ['DELETE']}, - requirements=URL_NAME_REQUIREMENTS, jsroute=True) - - rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}', - controller='changeset', action='changeset_info', - requirements=URL_NAME_REQUIREMENTS, jsroute=True) - rmap.connect('compare_home', '/{repo_name}/compare', controller='compare', action='index', diff --git a/rhodecode/controllers/changeset.py b/rhodecode/controllers/changeset.py deleted file mode 100644 --- a/rhodecode/controllers/changeset.py +++ /dev/null @@ -1,491 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2010-2017 RhodeCode GmbH -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License, version 3 -# (only), as published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -# This program is dual-licensed. If you wish to learn more about the -# RhodeCode Enterprise Edition, including its added features, Support services, -# and proprietary license terms, please see https://rhodecode.com/licenses/ - -""" -commit controller for RhodeCode showing changes between commits -""" - -import logging - -from collections import defaultdict -from webob.exc import HTTPForbidden, HTTPBadRequest, HTTPNotFound - -from pylons import tmpl_context as c, request, response -from pylons.i18n.translation import _ -from pylons.controllers.util import redirect - -from rhodecode.lib import auth -from rhodecode.lib import diffs, codeblocks -from rhodecode.lib.auth import ( - LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous) -from rhodecode.lib.base import BaseRepoController, render -from rhodecode.lib.compat import OrderedDict -from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError -import rhodecode.lib.helpers as h -from rhodecode.lib.utils import jsonify -from rhodecode.lib.utils2 import safe_unicode, safe_int -from rhodecode.lib.vcs.backends.base import EmptyCommit -from rhodecode.lib.vcs.exceptions import ( - RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError) -from rhodecode.model.db import ChangesetComment, ChangesetStatus -from rhodecode.model.changeset_status import ChangesetStatusModel -from rhodecode.model.comment import CommentsModel -from rhodecode.model.meta import Session - - -log = logging.getLogger(__name__) - - -def _update_with_GET(params, GET): - for k in ['diff1', 'diff2', 'diff']: - params[k] += GET.getall(k) - - -def get_ignore_ws(fid, GET): - ig_ws_global = GET.get('ignorews') - ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid)) - if ig_ws: - try: - return int(ig_ws[0].split(':')[-1]) - except Exception: - pass - return ig_ws_global - - -def _ignorews_url(GET, fileid=None): - fileid = str(fileid) if fileid else None - params = defaultdict(list) - _update_with_GET(params, GET) - label = _('Show whitespace') - tooltiplbl = _('Show whitespace for all diffs') - ig_ws = get_ignore_ws(fileid, GET) - ln_ctx = get_line_ctx(fileid, GET) - - if ig_ws is None: - params['ignorews'] += [1] - label = _('Ignore whitespace') - tooltiplbl = _('Ignore whitespace for all diffs') - ctx_key = 'context' - ctx_val = ln_ctx - - # if we have passed in ln_ctx pass it along to our params - if ln_ctx: - params[ctx_key] += [ctx_val] - - if fileid: - params['anchor'] = 'a_' + fileid - return h.link_to(label, h.url.current(**params), title=tooltiplbl, class_='tooltip') - - -def get_line_ctx(fid, GET): - ln_ctx_global = GET.get('context') - if fid: - ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid)) - else: - _ln_ctx = filter(lambda k: k.startswith('C'), GET) - ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global - if ln_ctx: - ln_ctx = [ln_ctx] - - if ln_ctx: - retval = ln_ctx[0].split(':')[-1] - else: - retval = ln_ctx_global - - try: - return int(retval) - except Exception: - return 3 - - -def _context_url(GET, fileid=None): - """ - Generates a url for context lines. - - :param fileid: - """ - - fileid = str(fileid) if fileid else None - ig_ws = get_ignore_ws(fileid, GET) - ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2 - - params = defaultdict(list) - _update_with_GET(params, GET) - - if ln_ctx > 0: - params['context'] += [ln_ctx] - - if ig_ws: - ig_ws_key = 'ignorews' - ig_ws_val = 1 - params[ig_ws_key] += [ig_ws_val] - - lbl = _('Increase context') - tooltiplbl = _('Increase context for all diffs') - - if fileid: - params['anchor'] = 'a_' + fileid - return h.link_to(lbl, h.url.current(**params), title=tooltiplbl, class_='tooltip') - - -class ChangesetController(BaseRepoController): - - def __before__(self): - super(ChangesetController, self).__before__() - - def _index(self, commit_id_range, method): - c.ignorews_url = _ignorews_url - c.context_url = _context_url - c.fulldiff = fulldiff = request.GET.get('fulldiff') - - # fetch global flags of ignore ws or context lines - context_lcl = get_line_ctx('', request.GET) - ign_whitespace_lcl = get_ignore_ws('', request.GET) - - # diff_limit will cut off the whole diff if the limit is applied - # otherwise it will just hide the big files from the front-end - diff_limit = self.cut_off_limit_diff - file_limit = self.cut_off_limit_file - - # get ranges of commit ids if preset - commit_range = commit_id_range.split('...')[:2] - - try: - pre_load = ['affected_files', 'author', 'branch', 'date', - 'message', 'parents'] - - if len(commit_range) == 2: - commits = c.rhodecode_repo.get_commits( - start_id=commit_range[0], end_id=commit_range[1], - pre_load=pre_load) - commits = list(commits) - else: - commits = [c.rhodecode_repo.get_commit( - commit_id=commit_id_range, pre_load=pre_load)] - - c.commit_ranges = commits - if not c.commit_ranges: - raise RepositoryError( - 'The commit range returned an empty result') - except CommitDoesNotExistError: - msg = _('No such commit exists for this repository') - h.flash(msg, category='error') - raise HTTPNotFound() - except Exception: - log.exception("General failure") - raise HTTPNotFound() - - c.changes = OrderedDict() - c.lines_added = 0 - c.lines_deleted = 0 - - # auto collapse if we have more than limit - collapse_limit = diffs.DiffProcessor._collapse_commits_over - c.collapse_all_commits = len(c.commit_ranges) > collapse_limit - - c.commit_statuses = ChangesetStatus.STATUSES - c.inline_comments = [] - c.files = [] - - c.statuses = [] - c.comments = [] - c.unresolved_comments = [] - if len(c.commit_ranges) == 1: - commit = c.commit_ranges[0] - c.comments = CommentsModel().get_comments( - c.rhodecode_db_repo.repo_id, - revision=commit.raw_id) - c.statuses.append(ChangesetStatusModel().get_status( - c.rhodecode_db_repo.repo_id, commit.raw_id)) - # comments from PR - statuses = ChangesetStatusModel().get_statuses( - c.rhodecode_db_repo.repo_id, commit.raw_id, - with_revisions=True) - prs = set(st.pull_request for st in statuses - if st.pull_request is not None) - # from associated statuses, check the pull requests, and - # show comments from them - for pr in prs: - c.comments.extend(pr.comments) - - c.unresolved_comments = CommentsModel()\ - .get_commit_unresolved_todos(commit.raw_id) - - # Iterate over ranges (default commit view is always one commit) - for commit in c.commit_ranges: - c.changes[commit.raw_id] = [] - - commit2 = commit - commit1 = commit.parents[0] if commit.parents else EmptyCommit() - - _diff = c.rhodecode_repo.get_diff( - commit1, commit2, - ignore_whitespace=ign_whitespace_lcl, context=context_lcl) - diff_processor = diffs.DiffProcessor( - _diff, format='newdiff', diff_limit=diff_limit, - file_limit=file_limit, show_full_diff=fulldiff) - - commit_changes = OrderedDict() - if method == 'show': - _parsed = diff_processor.prepare() - c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer) - - _parsed = diff_processor.prepare() - - def _node_getter(commit): - def get_node(fname): - try: - return commit.get_node(fname) - except NodeDoesNotExistError: - return None - return get_node - - inline_comments = CommentsModel().get_inline_comments( - c.rhodecode_db_repo.repo_id, revision=commit.raw_id) - c.inline_cnt = CommentsModel().get_inline_comments_count( - inline_comments) - - diffset = codeblocks.DiffSet( - repo_name=c.repo_name, - source_node_getter=_node_getter(commit1), - target_node_getter=_node_getter(commit2), - comments=inline_comments) - diffset = diffset.render_patchset( - _parsed, commit1.raw_id, commit2.raw_id) - - c.changes[commit.raw_id] = diffset - else: - # downloads/raw we only need RAW diff nothing else - diff = diff_processor.as_raw() - c.changes[commit.raw_id] = [None, None, None, None, diff, None, None] - - # sort comments by how they were generated - c.comments = sorted(c.comments, key=lambda x: x.comment_id) - - if len(c.commit_ranges) == 1: - c.commit = c.commit_ranges[0] - c.parent_tmpl = ''.join( - '# Parent %s\n' % x.raw_id for x in c.commit.parents) - if method == 'download': - response.content_type = 'text/plain' - response.content_disposition = ( - 'attachment; filename=%s.diff' % commit_id_range[:12]) - return diff - elif method == 'patch': - response.content_type = 'text/plain' - c.diff = safe_unicode(diff) - return render('changeset/patch_changeset.mako') - elif method == 'raw': - response.content_type = 'text/plain' - return diff - elif method == 'show': - if len(c.commit_ranges) == 1: - return render('changeset/changeset.mako') - else: - c.ancestor = None - c.target_repo = c.rhodecode_db_repo - return render('changeset/changeset_range.mako') - - @LoginRequired() - @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') - def index(self, revision, method='show'): - return self._index(revision, method=method) - - @LoginRequired() - @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') - def changeset_raw(self, revision): - return self._index(revision, method='raw') - - @LoginRequired() - @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') - def changeset_patch(self, revision): - return self._index(revision, method='patch') - - @LoginRequired() - @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') - def changeset_download(self, revision): - return self._index(revision, method='download') - - @LoginRequired() - @NotAnonymous() - @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') - @auth.CSRFRequired() - @jsonify - def comment(self, repo_name, revision): - commit_id = revision - status = request.POST.get('changeset_status', None) - text = request.POST.get('text') - comment_type = request.POST.get('comment_type') - resolves_comment_id = request.POST.get('resolves_comment_id', None) - - if status: - text = text or (_('Status change %(transition_icon)s %(status)s') - % {'transition_icon': '>', - 'status': ChangesetStatus.get_status_lbl(status)}) - - multi_commit_ids = [] - for _commit_id in request.POST.get('commit_ids', '').split(','): - if _commit_id not in ['', None, EmptyCommit.raw_id]: - if _commit_id not in multi_commit_ids: - multi_commit_ids.append(_commit_id) - - commit_ids = multi_commit_ids or [commit_id] - - comment = None - for current_id in filter(None, commit_ids): - c.co = comment = CommentsModel().create( - text=text, - repo=c.rhodecode_db_repo.repo_id, - user=c.rhodecode_user.user_id, - commit_id=current_id, - f_path=request.POST.get('f_path'), - line_no=request.POST.get('line'), - status_change=(ChangesetStatus.get_status_lbl(status) - if status else None), - status_change_type=status, - comment_type=comment_type, - resolves_comment_id=resolves_comment_id - ) - - # get status if set ! - if status: - # if latest status was from pull request and it's closed - # disallow changing status ! - # dont_allow_on_closed_pull_request = True ! - - try: - ChangesetStatusModel().set_status( - c.rhodecode_db_repo.repo_id, - status, - c.rhodecode_user.user_id, - comment, - revision=current_id, - dont_allow_on_closed_pull_request=True - ) - except StatusChangeOnClosedPullRequestError: - msg = _('Changing the status of a commit associated with ' - 'a closed pull request is not allowed') - log.exception(msg) - h.flash(msg, category='warning') - return redirect(h.url( - 'changeset_home', repo_name=repo_name, - revision=current_id)) - - # finalize, commit and redirect - Session().commit() - - data = { - 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))), - } - if comment: - data.update(comment.get_dict()) - data.update({'rendered_text': - render('changeset/changeset_comment_block.mako')}) - - return data - - @LoginRequired() - @NotAnonymous() - @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') - @auth.CSRFRequired() - def preview_comment(self): - # Technically a CSRF token is not needed as no state changes with this - # call. However, as this is a POST is better to have it, so automated - # tools don't flag it as potential CSRF. - # Post is required because the payload could be bigger than the maximum - # allowed by GET. - if not request.environ.get('HTTP_X_PARTIAL_XHR'): - raise HTTPBadRequest() - text = request.POST.get('text') - renderer = request.POST.get('renderer') or 'rst' - if text: - return h.render(text, renderer=renderer, mentions=True) - return '' - - @LoginRequired() - @NotAnonymous() - @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') - @auth.CSRFRequired() - @jsonify - def delete_comment(self, repo_name, comment_id): - comment = ChangesetComment.get_or_404(safe_int(comment_id)) - if not comment: - log.debug('Comment with id:%s not found, skipping', comment_id) - # comment already deleted in another call probably - return True - - is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name) - super_admin = h.HasPermissionAny('hg.admin')() - comment_owner = (comment.author.user_id == c.rhodecode_user.user_id) - is_repo_comment = comment.repo.repo_name == c.repo_name - comment_repo_admin = is_repo_admin and is_repo_comment - - if super_admin or comment_owner or comment_repo_admin: - CommentsModel().delete(comment=comment, user=c.rhodecode_user) - Session().commit() - return True - else: - log.warning('No permissions for user %s to delete comment_id: %s', - c.rhodecode_user, comment_id) - raise HTTPNotFound() - - @LoginRequired() - @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') - @jsonify - def changeset_info(self, repo_name, revision): - if request.is_xhr: - try: - return c.rhodecode_repo.get_commit(commit_id=revision) - except CommitDoesNotExistError as e: - return EmptyCommit(message=str(e)) - else: - raise HTTPBadRequest() - - @LoginRequired() - @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') - @jsonify - def changeset_children(self, repo_name, revision): - if request.is_xhr: - commit = c.rhodecode_repo.get_commit(commit_id=revision) - result = {"results": commit.children} - return result - else: - raise HTTPBadRequest() - - @LoginRequired() - @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') - @jsonify - def changeset_parents(self, repo_name, revision): - if request.is_xhr: - commit = c.rhodecode_repo.get_commit(commit_id=revision) - result = {"results": commit.parents} - return result - else: - raise HTTPBadRequest() diff --git a/rhodecode/lib/action_parser.py b/rhodecode/lib/action_parser.py --- a/rhodecode/lib/action_parser.py +++ b/rhodecode/lib/action_parser.py @@ -20,7 +20,6 @@ import logging -from pylons import url from pylons.i18n.translation import _ from webhelpers.html.builder import literal from webhelpers.html.tags import link_to @@ -201,6 +200,7 @@ class ActionParser(object): return literal(tmpl % (ico, self.action)) def get_cs_links(self): + from rhodecode.lib import helpers as h if self.is_deleted(): return self.action_params @@ -223,8 +223,9 @@ class ActionParser(object): _('Show all combined commits %s->%s') % ( commit_ids[0][:12], commit_ids[-1][:12] ), - url('changeset_home', repo_name=repo_name, - revision=commit_id_range), _('compare view') + h.route_path( + 'repo_commit', repo_name=repo_name, + commit_id=commit_id_range), _('compare view') ) ) @@ -275,6 +276,7 @@ class ActionParser(object): def lnk(self, commit_or_id, repo_name): from rhodecode.lib.helpers import tooltip + from rhodecode.lib import helpers as h if isinstance(commit_or_id, (BaseCommit, AttributeDict)): lazy_cs = True @@ -292,8 +294,8 @@ class ActionParser(object): else: lbl = '%s' % (commit_or_id.short_id[:8]) - _url = url('changeset_home', repo_name=repo_name, - revision=commit_or_id.raw_id) + _url = h.route_path('repo_commit', repo_name=repo_name, + commit_id=commit_or_id.raw_id) title = tooltip(commit_or_id.message) else: # commit cannot be found/striped/removed etc. diff --git a/rhodecode/lib/auth.py b/rhodecode/lib/auth.py --- a/rhodecode/lib/auth.py +++ b/rhodecode/lib/auth.py @@ -754,7 +754,7 @@ class PermissionCalculator(object): } -def allowed_auth_token_access(controller_name, whitelist=None, auth_token=None): +def allowed_auth_token_access(view_name, whitelist=None, auth_token=None): """ Check if given controller_name is in whitelist of auth token access """ @@ -767,16 +767,16 @@ def allowed_auth_token_access(controller auth_token_access_valid = False for entry in whitelist: - if fnmatch.fnmatch(controller_name, entry): + if fnmatch.fnmatch(view_name, entry): auth_token_access_valid = True break if auth_token_access_valid: - log.debug('controller:%s matches entry in whitelist' - % (controller_name,)) + log.debug('view: `%s` matches entry in whitelist: %s' + % (view_name, whitelist)) else: - msg = ('controller: %s does *NOT* match any entry in whitelist' - % (controller_name,)) + msg = ('view: `%s` does *NOT* match any entry in whitelist: %s' + % (view_name, whitelist)) if auth_token: # if we use auth token key and don't have access it's a warning log.warning(msg) diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py --- a/rhodecode/lib/helpers.py +++ b/rhodecode/lib/helpers.py @@ -1575,7 +1575,7 @@ def urlify_commits(text_, repository): :param text_: :param repository: repo name to build the URL with """ - from pylons import url # doh, we need to re-import url to mock it later + URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)') def url_func(match_obj): @@ -1590,8 +1590,8 @@ def urlify_commits(text_, repository): return tmpl % { 'pref': pref, 'cls': 'revision-link', - 'url': url('changeset_home', repo_name=repository, - revision=commit_id, qualified=True), + 'url': route_url('repo_commit', repo_name=repository, + commit_id=commit_id), 'commit_id': commit_id, 'suf': suf } diff --git a/rhodecode/public/js/rhodecode/routes.js b/rhodecode/public/js/rhodecode/routes.js --- a/rhodecode/public/js/rhodecode/routes.js +++ b/rhodecode/public/js/rhodecode/routes.js @@ -15,11 +15,6 @@ function registerRCRoutes() { pyroutes.register('new_repo', '/_admin/create_repository', []); pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']); pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']); - pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']); - pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']); - pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']); - pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']); - pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']); 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']); pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']); pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']); @@ -111,6 +106,16 @@ function registerRCRoutes() { pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']); pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']); pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']); + pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']); + pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']); + pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']); + pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']); + pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']); + pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']); + pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']); + pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']); + pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']); + pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']); pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']); pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']); pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']); @@ -146,9 +151,6 @@ function registerRCRoutes() { pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']); pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']); pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']); - pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']); - pyroutes.register('changeset_children', '/%(repo_name)s/changeset_children/%(revision)s', ['repo_name', 'revision']); - pyroutes.register('changeset_parents', '/%(repo_name)s/changeset_parents/%(revision)s', ['repo_name', 'revision']); pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']); pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']); pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']); diff --git a/rhodecode/public/js/src/rhodecode/comments.js b/rhodecode/public/js/src/rhodecode/comments.js --- a/rhodecode/public/js/src/rhodecode/comments.js +++ b/rhodecode/public/js/src/rhodecode/comments.js @@ -103,8 +103,9 @@ var bindToggleButtons = function() { this.submitButton = $(this.submitForm).find('input[type="submit"]'); this.submitButtonText = this.submitButton.val(); - this.previewUrl = pyroutes.url('changeset_comment_preview', - {'repo_name': templateContext.repo_name}); + this.previewUrl = pyroutes.url('repo_commit_comment_preview', + {'repo_name': templateContext.repo_name, + 'commit_id': templateContext.commit_data.commit_id}); if (resolvesCommentId){ this.resolvesId = '#resolve_comment_{0}'.format(resolvesCommentId); @@ -129,12 +130,12 @@ var bindToggleButtons = function() { // based on commitId, or pullRequestId decide where do we submit // out data if (this.commitId){ - this.submitUrl = pyroutes.url('changeset_comment', + this.submitUrl = pyroutes.url('repo_commit_comment_create', {'repo_name': templateContext.repo_name, - 'revision': this.commitId}); - this.selfUrl = pyroutes.url('changeset_home', + 'commit_id': this.commitId}); + this.selfUrl = pyroutes.url('repo_commit', {'repo_name': templateContext.repo_name, - 'revision': this.commitId}); + 'commit_id': this.commitId}); } else if (this.pullRequestId) { this.submitUrl = pyroutes.url('pullrequest_comment', diff --git a/rhodecode/templates/admin/repos/repo_edit_advanced.mako b/rhodecode/templates/admin/repos/repo_edit_advanced.mako --- a/rhodecode/templates/admin/repos/repo_edit_advanced.mako +++ b/rhodecode/templates/admin/repos/repo_edit_advanced.mako @@ -5,7 +5,7 @@ (_('Owner'), lambda:base.gravatar_with_user(c.repo_info.user.email), '', ''), (_('Created on'), h.format_date(c.repo_info.created_on), '', ''), (_('Updated on'), h.format_date(c.repo_info.updated_on), '', ''), - (_('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'))), '', ''), + (_('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'))), '', ''), ] %> diff --git a/rhodecode/templates/changelog/changelog.mako b/rhodecode/templates/changelog/changelog.mako --- a/rhodecode/templates/changelog/changelog.mako +++ b/rhodecode/templates/changelog/changelog.mako @@ -161,9 +161,9 @@ if (selectedCheckboxes.length>0){ var revEnd = selectedCheckboxes[0].name; var revStart = selectedCheckboxes[selectedCheckboxes.length-1].name; - var url = pyroutes.url('changeset_home', + var url = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}', - 'revision': revStart+'...'+revEnd}); + 'commit_id': revStart+'...'+revEnd}); var link = (revStart == revEnd) ? _gettext('Show selected commit __S') diff --git a/rhodecode/templates/changelog/changelog_elements.mako b/rhodecode/templates/changelog/changelog_elements.mako --- a/rhodecode/templates/changelog/changelog_elements.mako +++ b/rhodecode/templates/changelog/changelog_elements.mako @@ -24,11 +24,11 @@
%if c.statuses.get(commit.raw_id)[2]: -
+
%else: - -
+
+
%endif
@@ -38,7 +38,7 @@ %if c.comments.get(commit.raw_id): - + ${len(c.comments[commit.raw_id])} %endif @@ -46,7 +46,7 @@ - + ${h.show_id(commit)} diff --git a/rhodecode/templates/changelog/changelog_file_history.mako b/rhodecode/templates/changelog/changelog_file_history.mako --- a/rhodecode/templates/changelog/changelog_file_history.mako +++ b/rhodecode/templates/changelog/changelog_file_history.mako @@ -15,7 +15,7 @@
@@ -23,7 +23,7 @@ - + ${h.show_id(cs)} diff --git a/rhodecode/templates/changeset/changeset.mako b/rhodecode/templates/changeset/changeset.mako --- a/rhodecode/templates/changeset/changeset.mako +++ b/rhodecode/templates/changeset/changeset.mako @@ -21,7 +21,7 @@ <%def name="main()">
@@ -137,21 +137,21 @@
- + ${_('Raw Diff')} | - + ${_('Patch Diff')} | - + ${_('Download Diff')} | - ${c.ignorews_url(request.GET)} + ${c.ignorews_url(request)} | - ${c.context_url(request.GET)} + ${c.context_url(request)}
@@ -221,7 +221,7 @@ ${comment.generate_comments(c.comments)} ## main comment form and it status - ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.commit.raw_id), + ${comment.comments(h.route_path('repo_commit_comment_create', repo_name=c.repo_name, commit_id=c.commit.raw_id), h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))} @@ -264,14 +264,14 @@ // >1 links show them to user to choose if(!$('#child_link').hasClass('disabled')){ $.ajax({ - url: '${h.url('changeset_children',repo_name=c.repo_name, revision=c.commit.raw_id)}', + url: '${h.route_path('repo_commit_children',repo_name=c.repo_name, commit_id=c.commit.raw_id)}', success: function(data) { if(data.results.length === 0){ $('#child_link').html("${_('No Child Commits')}").addClass('disabled'); } if(data.results.length === 1){ var commit = data.results[0]; - window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id}); + window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id}); } else if(data.results.length === 2){ $('#child_link').addClass('disabled'); @@ -280,12 +280,12 @@ _html +='__rev__ ' .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6))) .replace('__title__', data.results[0].message) - .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id})); + .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id})); _html +=' | '; _html +='__rev__ ' .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6))) .replace('__title__', data.results[1].message) - .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id})); + .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id})); $('#child_link').html(_html); } } @@ -300,14 +300,14 @@ // >1 links show them to user to choose if(!$('#parent_link').hasClass('disabled')){ $.ajax({ - url: '${h.url("changeset_parents",repo_name=c.repo_name, revision=c.commit.raw_id)}', + url: '${h.route_path("repo_commit_parents",repo_name=c.repo_name, commit_id=c.commit.raw_id)}', success: function(data) { if(data.results.length === 0){ $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled'); } if(data.results.length === 1){ var commit = data.results[0]; - window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id}); + window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id}); } else if(data.results.length === 2){ $('#parent_link').addClass('disabled'); @@ -316,12 +316,12 @@ _html +='Parent __rev__' .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6))) .replace('__title__', data.results[0].message) - .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id})); + .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id})); _html +=' | '; _html +='Parent __rev__' .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6))) .replace('__title__', data.results[1].message) - .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id})); + .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id})); $('#parent_link').html(_html); } } diff --git a/rhodecode/templates/changeset/changeset_file_comment.mako b/rhodecode/templates/changeset/changeset_file_comment.mako --- a/rhodecode/templates/changeset/changeset_file_comment.mako +++ b/rhodecode/templates/changeset/changeset_file_comment.mako @@ -100,7 +100,7 @@ % endif % if inline:
diff --git a/rhodecode/templates/codeblocks/diffs.mako b/rhodecode/templates/codeblocks/diffs.mako --- a/rhodecode/templates/codeblocks/diffs.mako +++ b/rhodecode/templates/codeblocks/diffs.mako @@ -121,7 +121,7 @@ collapse_all = len(diffset.files) > coll %endif

%if commit: - ${'r%s:%s' % (commit.revision,h.short_id(commit.raw_id))} - + ${'r%s:%s' % (commit.revision,h.short_id(commit.raw_id))} - ${h.age_component(commit.date)} - %endif %if diffset.limited_diff: @@ -459,10 +459,10 @@ from rhodecode.lib.diffs import NEW_FILE ## 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) %if hasattr(c, 'ignorews_url'): - ${c.ignorews_url(request.GET, h.FID('', filediff.patch['filename']))} + ${c.ignorews_url(request, h.FID('', filediff.patch['filename']))} %endif %if hasattr(c, 'context_url'): - ${c.context_url(request.GET, h.FID('', filediff.patch['filename']))} + ${c.context_url(request, h.FID('', filediff.patch['filename']))} %endif %if use_comments: diff --git a/rhodecode/templates/codeblocks/source.mako b/rhodecode/templates/codeblocks/source.mako --- a/rhodecode/templates/codeblocks/source.mako +++ b/rhodecode/templates/codeblocks/source.mako @@ -31,7 +31,7 @@ data-revision="${annotation.revision}" onclick="$('[data-revision=${annotation.revision}]').toggleClass('cb-line-fresh')" style="background: ${bgcolor}"> - + r${annotation.revision} diff --git a/rhodecode/templates/compare/compare_commits.mako b/rhodecode/templates/compare/compare_commits.mako --- a/rhodecode/templates/compare/compare_commits.mako +++ b/rhodecode/templates/compare/compare_commits.mako @@ -3,7 +3,7 @@ %if c.ancestor:
${_('Common Ancestor Commit')}: - + ${h.short_id(c.ancestor)} . ${_('Compare was calculated based on this shared commit.')} @@ -34,9 +34,7 @@ - + r${commit.revision}:${h.short_id(commit.raw_id)} ${h.hidden('revisions',commit.raw_id)} diff --git a/rhodecode/templates/compare/compare_diff.mako b/rhodecode/templates/compare/compare_diff.mako --- a/rhodecode/templates/compare/compare_diff.mako +++ b/rhodecode/templates/compare/compare_diff.mako @@ -137,15 +137,15 @@
@@ -170,7 +170,7 @@ return form_inputs %>
- ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision='0'*16), None, is_compare=True, form_extras=revs(c.commit_ranges))} + ${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))}

diff --git a/rhodecode/templates/data_table/_dt_elements.mako b/rhodecode/templates/data_table/_dt_elements.mako --- a/rhodecode/templates/data_table/_dt_elements.mako +++ b/rhodecode/templates/data_table/_dt_elements.mako @@ -83,7 +83,7 @@ <%def name="revision(name,rev,tip,author,last_msg)">
%if rev >= 0: - ${'r%s:%s' % (rev,h.short_id(tip))} + ${'r%s:%s' % (rev,h.short_id(tip))} %else: ${_('No commits yet')} %endif diff --git a/rhodecode/templates/debug_style/collapsable-content.html b/rhodecode/templates/debug_style/collapsable-content.html --- a/rhodecode/templates/debug_style/collapsable-content.html +++ b/rhodecode/templates/debug_style/collapsable-content.html @@ -840,7 +840,8 @@ $('#edit-container').hide(); $('#preview-container').show(); - var url = pyroutes.url('changeset_comment_preview', {'repo_name': 'rhodecode-momentum'}); + var url = pyroutes.url('repo_commit_comment_preview', + {'repo_name': 'rhodecode-momentum', 'commit_id': '000000'}); ajaxPOST(url, post_data, function(o) { previewbox.html(o); diff --git a/rhodecode/templates/feed/atom_feed_entry.mako b/rhodecode/templates/feed/atom_feed_entry.mako --- a/rhodecode/templates/feed/atom_feed_entry.mako +++ b/rhodecode/templates/feed/atom_feed_entry.mako @@ -17,7 +17,7 @@ tag: ${tag}
% endfor -commit: ${h.show_id(commit)} +commit: ${h.show_id(commit)}
 ${h.urlify_commit_message(commit.message)}
 
diff --git a/rhodecode/templates/files/file_tree_detail.mako b/rhodecode/templates/files/file_tree_detail.mako
--- a/rhodecode/templates/files/file_tree_detail.mako
+++ b/rhodecode/templates/files/file_tree_detail.mako
@@ -23,7 +23,7 @@
   
- ${h.show_id(c.commit)} + ${h.show_id(c.commit)} ${file_base.refs(c.commit)} diff --git a/rhodecode/templates/files/files_add.mako b/rhodecode/templates/files/files_add.mako --- a/rhodecode/templates/files/files_add.mako +++ b/rhodecode/templates/files/files_add.mako @@ -217,7 +217,9 @@ var _renderer = possible_renderer || DEFAULT_RENDERER; var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN}; $('#editor_preview').html(_gettext('Loading ...')); - var url = pyroutes.url('changeset_comment_preview', {'repo_name': '${c.repo_name}'}); + var url = pyroutes.url('repo_commit_comment_preview', + {'repo_name': '${c.repo_name}', + 'commit_id': '${c.commit.raw_id}'}); ajaxPOST(url, post_data, function(o){ $('#editor_preview').html(o); diff --git a/rhodecode/templates/files/files_detail.mako b/rhodecode/templates/files/files_detail.mako --- a/rhodecode/templates/files/files_detail.mako +++ b/rhodecode/templates/files/files_detail.mako @@ -21,7 +21,7 @@
- ${h.show_id(c.commit)} + ${h.show_id(c.commit)} ${file_base.refs(c.commit)}
@@ -33,7 +33,7 @@
- ${h.show_id(c.file_last_commit)} + ${h.show_id(c.file_last_commit)} ${file_base.refs(c.file_last_commit)}
diff --git a/rhodecode/templates/files/files_edit.mako b/rhodecode/templates/files/files_edit.mako --- a/rhodecode/templates/files/files_edit.mako +++ b/rhodecode/templates/files/files_edit.mako @@ -47,7 +47,7 @@
- ${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))} + ${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))} ${h.format_byte_size_binary(c.file.size)} ${c.file.mimetype}
@@ -177,8 +177,9 @@ var _renderer = possible_renderer || DEFAULT_RENDERER; var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN}; $('#editor_preview').html(_gettext('Loading ...')); - var url = pyroutes.url('changeset_comment_preview', {'repo_name': '${c.repo_name}'}); - + var url = pyroutes.url('repo_commit_comment_preview', + {'repo_name': '${c.repo_name}', + 'commit_id': '${c.commit.raw_id}'}); ajaxPOST(url, post_data, function(o){ $('#editor_preview').html(o); }) diff --git a/rhodecode/templates/pullrequests/pullrequest_show.mako b/rhodecode/templates/pullrequests/pullrequest_show.mako --- a/rhodecode/templates/pullrequests/pullrequest_show.mako +++ b/rhodecode/templates/pullrequests/pullrequest_show.mako @@ -86,7 +86,7 @@
% if c.ancestor_commit: ${_('Common ancestor')}: - ${h.show_id(c.ancestor_commit)} + ${h.show_id(c.ancestor_commit)} % endif
@@ -513,7 +513,7 @@ - + r${commit.revision}:${h.short_id(commit.raw_id)} ${h.hidden('revisions', commit.raw_id)} diff --git a/rhodecode/templates/search/search_commit.mako b/rhodecode/templates/search/search_commit.mako --- a/rhodecode/templates/search/search_commit.mako +++ b/rhodecode/templates/search/search_commit.mako @@ -31,7 +31,7 @@ ${h.link_to(h._shorten_commit_id(entry['commit_id']), - h.url('changeset_home',repo_name=entry['repository'],revision=entry['commit_id']))} + h.route_path('repo_commit',repo_name=entry['repository'],commit_id=entry['commit_id']))}
diff --git a/rhodecode/templates/summary/summary_commits.mako b/rhodecode/templates/summary/summary_commits.mako --- a/rhodecode/templates/summary/summary_commits.mako +++ b/rhodecode/templates/summary/summary_commits.mako @@ -19,11 +19,11 @@
%if c.statuses.get(cs.raw_id)[2]: -
+
%else: - -
+
+
%endif
@@ -33,13 +33,13 @@ %if c.comments.get(cs.raw_id,[]): - + ${len(c.comments[cs.raw_id])} %endif -
${h.show_id(cs)}
+
${h.show_id(cs)}
diff --git a/rhodecode/tests/functional/test_compare.py b/rhodecode/tests/functional/test_compare.py --- a/rhodecode/tests/functional/test_compare.py +++ b/rhodecode/tests/functional/test_compare.py @@ -471,7 +471,7 @@ class TestCompareController(object): compare_page.contains_change_summary(1, 1, 0) @pytest.mark.xfail_backends("svn") - def test_compare_commits(self, backend): + def test_compare_commits(self, backend, xhr_header): commit0 = backend.repo.get_commit(commit_idx=0) commit1 = backend.repo.get_commit(commit_idx=1) @@ -483,7 +483,7 @@ class TestCompareController(object): target_ref_type="rev", target_ref=commit1.raw_id, merge='1',), - extra_environ={'HTTP_X_PARTIAL_XHR': '1'},) + extra_environ=xhr_header,) # outgoing commits between those commits compare_page = ComparePage(response) diff --git a/rhodecode/tests/other/test_libs.py b/rhodecode/tests/other/test_libs.py --- a/rhodecode/tests/other/test_libs.py +++ b/rhodecode/tests/other/test_libs.py @@ -397,7 +397,7 @@ def test_urlify_commits(sample, expected expected = _quick_url(expected) - with mock.patch('pylons.url', fake_url): + with mock.patch('rhodecode.lib.helpers.route_url', fake_url): from rhodecode.lib.helpers import urlify_commits assert urlify_commits(sample, 'repo_name') == expected diff --git a/rhodecode/tests/rhodecode.ini b/rhodecode/tests/rhodecode.ini --- a/rhodecode/tests/rhodecode.ini +++ b/rhodecode/tests/rhodecode.ini @@ -1,7 +1,7 @@ ################################################################################ -## RHODECODE ENTERPRISE CONFIGURATION ## +## RHODECODE COMMUNITY EDITION CONFIGURATION ## # The %(here)s variable will be replaced with the parent directory of this file# ################################################################################ @@ -64,7 +64,7 @@ asyncore_use_poll = true ########################## ## GUNICORN WSGI SERVER ## ########################## -## run with gunicorn --log-config --paste +## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini #use = egg:gunicorn#main ## Sets the number of process workers. You must set `instance_id = *` @@ -153,8 +153,10 @@ asyncore_use_poll = true ## prefix middleware for RhodeCode. ## recommended when using proxy setup. ## allows to set RhodeCode under a prefix in server. -## eg https://server.com/. Enable `filter-with =` option below as well. -## optionally set prefix like: `prefix = /` +## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well. +## And set your prefix like: `prefix = /custom_prefix` +## be sure to also set beaker.session.cookie_path = /custom_prefix if you need +## to make your cookies only work on prefix url [filter:proxy-prefix] use = egg:PasteDeploy#prefix prefix = / @@ -238,27 +240,27 @@ rss_items_per_page = 10 rss_include_diff = false ## gist URL alias, used to create nicer urls for gist. This should be an -## url that does rewrites to _admin/gists/. +## url that does rewrites to _admin/gists/{gistid}. ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal -## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/ +## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid} gist_alias_url = -## List of controllers (using glob pattern syntax) that AUTH TOKENS could be +## List of views (using glob pattern syntax) that AUTH TOKENS could be ## used for access. -## Adding ?auth_token = to the url authenticates this request as if it +## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it ## came from the the logged in user who own this authentication token. ## -## Syntax is :. -## To enable access to raw_files put `FilesController:raw`. -## To enable access to patches add `ChangesetController:changeset_patch`. +## list of all views can be found under `_admin/permissions/auth_token_access` ## The list should be "," separated and on a single line. ## -## Recommended controllers to enable: -# ChangesetController:changeset_patch, -# ChangesetController:changeset_raw, -# FilesController:raw, -# FilesController:archivefile, -# GistsController:*, +## Most common views to enable: +# RepoCommitsView:repo_commit_download +# RepoCommitsView:repo_commit_patch +# RepoCommitsView:repo_commit_raw +# RepoFilesView:repo_files_diff +# RepoFilesView:repo_archivefile +# RepoFilesView:repo_file_raw +# GistView:* api_access_controllers_whitelist = ## default encoding used to convert from and to unicode @@ -421,15 +423,15 @@ beaker.session.lock_dir = %(here)s/rc/da ## Secure encrypted cookie. Requires AES and AES python libraries ## you must disable beaker.session.secret to use this -#beaker.session.encrypt_key = -#beaker.session.validate_key = +#beaker.session.encrypt_key = key_for_encryption +#beaker.session.validate_key = validation_key ## sets session as invalid(also logging out user) if it haven not been ## accessed for given amount of time in seconds beaker.session.timeout = 2592000 beaker.session.httponly = true -## Path to use for the cookie. -#beaker.session.cookie_path = / +## Path to use for the cookie. Set to prefix if you use prefix middleware +#beaker.session.cookie_path = /custom_prefix ## uncomment for https secure cookie beaker.session.secure = false @@ -447,8 +449,8 @@ beaker.session.auto = false ## Full text search indexer is available in rhodecode-tools under ## `rhodecode-tools index` command -# WHOOSH Backend, doesn't require additional services to run -# it works good with few dozen repos +## WHOOSH Backend, doesn't require additional services to run +## it works good with few dozen repos search.module = rhodecode.lib.index.whoosh search.location = %(here)s/data/index @@ -459,15 +461,21 @@ search.location = %(here)s/data/index ## in the system. It's also used by the chat system channelstream.enabled = false -# location of channelstream server on the backend + +## server address for channelstream server on the backend channelstream.server = 127.0.0.1:9800 ## location of the channelstream server from outside world -## most likely this would be an http server special backend URL, that handles -## websocket connections see nginx example for config +## use ws:// for http or wss:// for https. This address needs to be handled +## by external HTTP server such as Nginx or Apache +## see nginx/apache configuration examples in our docs channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream channelstream.secret = secret channelstream.history.location = %(here)s/channelstream_history +## Internal application path that Javascript uses to connect into. +## If you use proxy-prefix the prefix should be added before /_channelstream +channelstream.proxy_path = /_channelstream + ################################### ## APPENLIGHT CONFIG ## @@ -541,19 +549,19 @@ set debug = false ############## debug_style = false -######################################################### -### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ### -######################################################### -#sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db +########################################### +### MAIN RHODECODE DATABASE CONFIG ### +########################################### +#sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db?timeout=30 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode_test #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode_test -sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db +sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db?timeout=30 # see sqlalchemy docs for other advanced settings ## print the sql statements to output sqlalchemy.db1.echo = false -## recycle the connections after this ammount of seconds +## recycle the connections after this amount of seconds sqlalchemy.db1.pool_recycle = 3600 sqlalchemy.db1.convert_unicode = true @@ -575,7 +583,7 @@ vcs.server = localhost:9901 ## Web server connectivity protocol, responsible for web based VCS operatations ## Available protocols are: -## `http` - using http-rpc backend +## `http` - use http-rpc backend (default) vcs.server.protocol = http ## Push/Pull operations protocol, available options are: @@ -584,7 +592,7 @@ vcs.server.protocol = http vcs.scm_app_implementation = http ## Push/Pull operations hooks protocol, available options are: -## `http` - using http-rpc backend +## `http` - use http-rpc backend (default) vcs.hooks.protocol = http vcs.server.log_level = debug @@ -613,12 +621,19 @@ svn.proxy.generate_config = false svn.proxy.list_parent_path = true ## Set location and file name of generated config file. svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf -## File system path to the directory containing the repositories served by -## RhodeCode. -svn.proxy.parent_path_root = /path/to/repo_store -## Used as a prefix to the block in the generated config file. In -## most cases it should be set to `/`. +## Used as a prefix to the `Location` block in the generated config file. +## In most cases it should be set to `/`. svn.proxy.location_root = / +## Command to reload the mod dav svn configuration on change. +## Example: `/etc/init.d/apache2 reload` +#svn.proxy.reload_cmd = /etc/init.d/apache2 reload +## If the timeout expires before the reload command finishes, the command will +## be killed. Setting it to zero means no timeout. Defaults to 10 seconds. +#svn.proxy.reload_timeout = 10 + +## Dummy marker to add new entries after. +## Add any custom entries below. Please don't remove. +custom.conf = 1 ################################