Show More
@@ -0,0 +1,302 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 | ||
|
24 | from pyramid.httpexceptions import HTTPNotFound, HTTPFound | |
|
25 | from pyramid.view import view_config | |
|
26 | from pyramid.renderers import render | |
|
27 | from pyramid.response import Response | |
|
28 | ||
|
29 | from rhodecode.apps._base import RepoAppView | |
|
30 | import rhodecode.lib.helpers as h | |
|
31 | from rhodecode.lib.auth import ( | |
|
32 | LoginRequired, HasRepoPermissionAnyDecorator) | |
|
33 | ||
|
34 | from rhodecode.lib.ext_json import json | |
|
35 | from rhodecode.lib.graphmod import _colored, _dagwalker | |
|
36 | from rhodecode.lib.helpers import RepoPage | |
|
37 | from rhodecode.lib.utils2 import safe_int, safe_str | |
|
38 | from rhodecode.lib.vcs.exceptions import ( | |
|
39 | RepositoryError, CommitDoesNotExistError, | |
|
40 | CommitError, NodeDoesNotExistError, EmptyRepositoryError) | |
|
41 | ||
|
42 | log = logging.getLogger(__name__) | |
|
43 | ||
|
44 | DEFAULT_CHANGELOG_SIZE = 20 | |
|
45 | ||
|
46 | ||
|
47 | class RepoChangelogView(RepoAppView): | |
|
48 | ||
|
49 | def _get_commit_or_redirect(self, commit_id, redirect_after=True): | |
|
50 | """ | |
|
51 | This is a safe way to get commit. If an error occurs it redirects to | |
|
52 | tip with proper message | |
|
53 | ||
|
54 | :param commit_id: id of commit to fetch | |
|
55 | :param redirect_after: toggle redirection | |
|
56 | """ | |
|
57 | _ = self.request.translate | |
|
58 | ||
|
59 | try: | |
|
60 | return self.rhodecode_vcs_repo.get_commit(commit_id) | |
|
61 | except EmptyRepositoryError: | |
|
62 | if not redirect_after: | |
|
63 | return None | |
|
64 | ||
|
65 | h.flash(h.literal( | |
|
66 | _('There are no commits yet')), category='warning') | |
|
67 | raise HTTPFound( | |
|
68 | h.route_path('repo_summary', repo_name=self.db_repo_name)) | |
|
69 | ||
|
70 | except (CommitDoesNotExistError, LookupError): | |
|
71 | msg = _('No such commit exists for this repository') | |
|
72 | h.flash(msg, category='error') | |
|
73 | raise HTTPNotFound() | |
|
74 | except RepositoryError as e: | |
|
75 | h.flash(safe_str(h.escape(e)), category='error') | |
|
76 | raise HTTPNotFound() | |
|
77 | ||
|
78 | def _graph(self, repo, commits, prev_data=None, next_data=None): | |
|
79 | """ | |
|
80 | Generates a DAG graph for repo | |
|
81 | ||
|
82 | :param repo: repo instance | |
|
83 | :param commits: list of commits | |
|
84 | """ | |
|
85 | if not commits: | |
|
86 | return json.dumps([]) | |
|
87 | ||
|
88 | def serialize(commit, parents=True): | |
|
89 | data = dict( | |
|
90 | raw_id=commit.raw_id, | |
|
91 | idx=commit.idx, | |
|
92 | branch=commit.branch, | |
|
93 | ) | |
|
94 | if parents: | |
|
95 | data['parents'] = [ | |
|
96 | serialize(x, parents=False) for x in commit.parents] | |
|
97 | return data | |
|
98 | ||
|
99 | prev_data = prev_data or [] | |
|
100 | next_data = next_data or [] | |
|
101 | ||
|
102 | current = [serialize(x) for x in commits] | |
|
103 | commits = prev_data + current + next_data | |
|
104 | ||
|
105 | dag = _dagwalker(repo, commits) | |
|
106 | ||
|
107 | data = [[commit_id, vtx, edges, branch] | |
|
108 | for commit_id, vtx, edges, branch in _colored(dag)] | |
|
109 | return json.dumps(data), json.dumps(current) | |
|
110 | ||
|
111 | def _check_if_valid_branch(self, branch_name, repo_name, f_path): | |
|
112 | if branch_name not in self.rhodecode_vcs_repo.branches_all: | |
|
113 | h.flash('Branch {} is not found.'.format(h.escape(branch_name)), | |
|
114 | category='warning') | |
|
115 | redirect_url = h.route_path( | |
|
116 | 'repo_changelog_file', repo_name=repo_name, | |
|
117 | commit_id=branch_name, f_path=f_path or '') | |
|
118 | raise HTTPFound(redirect_url) | |
|
119 | ||
|
120 | def _load_changelog_data( | |
|
121 | self, c, collection, page, chunk_size, branch_name=None, | |
|
122 | dynamic=False): | |
|
123 | ||
|
124 | def url_generator(**kw): | |
|
125 | query_params = {} | |
|
126 | query_params.update(kw) | |
|
127 | return h.route_path( | |
|
128 | 'repo_changelog', | |
|
129 | repo_name=c.rhodecode_db_repo.repo_name, _query=query_params) | |
|
130 | ||
|
131 | c.total_cs = len(collection) | |
|
132 | c.showing_commits = min(chunk_size, c.total_cs) | |
|
133 | c.pagination = RepoPage(collection, page=page, item_count=c.total_cs, | |
|
134 | items_per_page=chunk_size, branch=branch_name, | |
|
135 | url=url_generator) | |
|
136 | ||
|
137 | c.next_page = c.pagination.next_page | |
|
138 | c.prev_page = c.pagination.previous_page | |
|
139 | ||
|
140 | if dynamic: | |
|
141 | if self.request.GET.get('chunk') != 'next': | |
|
142 | c.next_page = None | |
|
143 | if self.request.GET.get('chunk') != 'prev': | |
|
144 | c.prev_page = None | |
|
145 | ||
|
146 | page_commit_ids = [x.raw_id for x in c.pagination] | |
|
147 | c.comments = c.rhodecode_db_repo.get_comments(page_commit_ids) | |
|
148 | c.statuses = c.rhodecode_db_repo.statuses(page_commit_ids) | |
|
149 | ||
|
150 | def load_default_context(self): | |
|
151 | c = self._get_local_tmpl_context(include_app_defaults=True) | |
|
152 | ||
|
153 | # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead | |
|
154 | c.repo_info = self.db_repo | |
|
155 | c.rhodecode_repo = self.rhodecode_vcs_repo | |
|
156 | ||
|
157 | self._register_global_c(c) | |
|
158 | return c | |
|
159 | ||
|
160 | @LoginRequired() | |
|
161 | @HasRepoPermissionAnyDecorator( | |
|
162 | 'repository.read', 'repository.write', 'repository.admin') | |
|
163 | @view_config( | |
|
164 | route_name='repo_changelog', request_method='GET', | |
|
165 | renderer='rhodecode:templates/changelog/changelog.mako') | |
|
166 | @view_config( | |
|
167 | route_name='repo_changelog_file', request_method='GET', | |
|
168 | renderer='rhodecode:templates/changelog/changelog.mako') | |
|
169 | def repo_changelog(self): | |
|
170 | c = self.load_default_context() | |
|
171 | ||
|
172 | commit_id = self.request.matchdict.get('commit_id') | |
|
173 | f_path = self._get_f_path(self.request.matchdict) | |
|
174 | ||
|
175 | chunk_size = 20 | |
|
176 | ||
|
177 | c.branch_name = branch_name = self.request.GET.get('branch') or '' | |
|
178 | c.book_name = book_name = self.request.GET.get('bookmark') or '' | |
|
179 | hist_limit = safe_int(self.request.GET.get('limit')) or None | |
|
180 | ||
|
181 | p = safe_int(self.request.GET.get('page', 1), 1) | |
|
182 | ||
|
183 | c.selected_name = branch_name or book_name | |
|
184 | if not commit_id and branch_name: | |
|
185 | self._check_if_valid_branch(branch_name, self.db_repo_name, f_path) | |
|
186 | ||
|
187 | c.changelog_for_path = f_path | |
|
188 | pre_load = ['author', 'branch', 'date', 'message', 'parents'] | |
|
189 | commit_ids = [] | |
|
190 | ||
|
191 | partial_xhr = self.request.environ.get('HTTP_X_PARTIAL_XHR') | |
|
192 | ||
|
193 | try: | |
|
194 | if f_path: | |
|
195 | log.debug('generating changelog for path %s', f_path) | |
|
196 | # get the history for the file ! | |
|
197 | base_commit = self.rhodecode_vcs_repo.get_commit(commit_id) | |
|
198 | try: | |
|
199 | collection = base_commit.get_file_history( | |
|
200 | f_path, limit=hist_limit, pre_load=pre_load) | |
|
201 | if collection and partial_xhr: | |
|
202 | # for ajax call we remove first one since we're looking | |
|
203 | # at it right now in the context of a file commit | |
|
204 | collection.pop(0) | |
|
205 | except (NodeDoesNotExistError, CommitError): | |
|
206 | # this node is not present at tip! | |
|
207 | try: | |
|
208 | commit = self._get_commit_or_redirect(commit_id) | |
|
209 | collection = commit.get_file_history(f_path) | |
|
210 | except RepositoryError as e: | |
|
211 | h.flash(safe_str(e), category='warning') | |
|
212 | redirect_url = h.route_path( | |
|
213 | 'repo_changelog', repo_name=self.db_repo_name) | |
|
214 | raise HTTPFound(redirect_url) | |
|
215 | collection = list(reversed(collection)) | |
|
216 | else: | |
|
217 | collection = self.rhodecode_vcs_repo.get_commits( | |
|
218 | branch_name=branch_name, pre_load=pre_load) | |
|
219 | ||
|
220 | self._load_changelog_data( | |
|
221 | c, collection, p, chunk_size, c.branch_name, dynamic=f_path) | |
|
222 | ||
|
223 | except EmptyRepositoryError as e: | |
|
224 | h.flash(safe_str(h.escape(e)), category='warning') | |
|
225 | raise HTTPFound( | |
|
226 | h.route_path('repo_summary', repo_name=self.db_repo_name)) | |
|
227 | except (RepositoryError, CommitDoesNotExistError, Exception) as e: | |
|
228 | log.exception(safe_str(e)) | |
|
229 | h.flash(safe_str(h.escape(e)), category='error') | |
|
230 | raise HTTPFound( | |
|
231 | h.route_path('repo_changelog', repo_name=self.db_repo_name)) | |
|
232 | ||
|
233 | if partial_xhr or self.request.environ.get('HTTP_X_PJAX'): | |
|
234 | # loading from ajax, we don't want the first result, it's popped | |
|
235 | # in the code above | |
|
236 | html = render( | |
|
237 | 'rhodecode:templates/changelog/changelog_file_history.mako', | |
|
238 | self._get_template_context(c), self.request) | |
|
239 | return Response(html) | |
|
240 | ||
|
241 | if not f_path: | |
|
242 | commit_ids = c.pagination | |
|
243 | ||
|
244 | c.graph_data, c.graph_commits = self._graph( | |
|
245 | self.rhodecode_vcs_repo, commit_ids) | |
|
246 | ||
|
247 | return self._get_template_context(c) | |
|
248 | ||
|
249 | @LoginRequired() | |
|
250 | @HasRepoPermissionAnyDecorator( | |
|
251 | 'repository.read', 'repository.write', 'repository.admin') | |
|
252 | @view_config( | |
|
253 | route_name='repo_changelog_elements', request_method=('GET', 'POST'), | |
|
254 | renderer='rhodecode:templates/changelog/changelog_elements.mako', | |
|
255 | xhr=True) | |
|
256 | def repo_changelog_elements(self): | |
|
257 | c = self.load_default_context() | |
|
258 | chunk_size = 20 | |
|
259 | ||
|
260 | def wrap_for_error(err): | |
|
261 | html = '<tr>' \ | |
|
262 | '<td colspan="9" class="alert alert-error">ERROR: {}</td>' \ | |
|
263 | '</tr>'.format(err) | |
|
264 | return Response(html) | |
|
265 | ||
|
266 | c.branch_name = branch_name = self.request.GET.get('branch') or '' | |
|
267 | c.book_name = book_name = self.request.GET.get('bookmark') or '' | |
|
268 | ||
|
269 | c.selected_name = branch_name or book_name | |
|
270 | if branch_name and branch_name not in self.rhodecode_vcs_repo.branches_all: | |
|
271 | return wrap_for_error( | |
|
272 | safe_str('Branch: {} is not valid'.format(branch_name))) | |
|
273 | ||
|
274 | pre_load = ['author', 'branch', 'date', 'message', 'parents'] | |
|
275 | collection = self.rhodecode_vcs_repo.get_commits( | |
|
276 | branch_name=branch_name, pre_load=pre_load) | |
|
277 | ||
|
278 | p = safe_int(self.request.GET.get('page', 1), 1) | |
|
279 | try: | |
|
280 | self._load_changelog_data( | |
|
281 | c, collection, p, chunk_size, dynamic=True) | |
|
282 | except EmptyRepositoryError as e: | |
|
283 | return wrap_for_error(safe_str(e)) | |
|
284 | except (RepositoryError, CommitDoesNotExistError, Exception) as e: | |
|
285 | log.exception('Failed to fetch commits') | |
|
286 | return wrap_for_error(safe_str(e)) | |
|
287 | ||
|
288 | prev_data = None | |
|
289 | next_data = None | |
|
290 | ||
|
291 | prev_graph = json.loads(self.request.POST.get('graph', '')) | |
|
292 | ||
|
293 | if self.request.GET.get('chunk') == 'prev': | |
|
294 | next_data = prev_graph | |
|
295 | elif self.request.GET.get('chunk') == 'next': | |
|
296 | prev_data = prev_graph | |
|
297 | ||
|
298 | c.graph_data, c.graph_commits = self._graph( | |
|
299 | self.rhodecode_vcs_repo, c.pagination, | |
|
300 | prev_data=prev_data, next_data=next_data) | |
|
301 | ||
|
302 | return self._get_template_context(c) |
@@ -139,6 +139,17 b' def includeme(config):' | |||
|
139 | 139 | name='repo_stats', |
|
140 | 140 | pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True) |
|
141 | 141 | |
|
142 | # Changelog | |
|
143 | config.add_route( | |
|
144 | name='repo_changelog', | |
|
145 | pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True) | |
|
146 | config.add_route( | |
|
147 | name='repo_changelog_file', | |
|
148 | pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True) | |
|
149 | config.add_route( | |
|
150 | name='repo_changelog_elements', | |
|
151 | pattern='/{repo_name:.*?[^/]}/changelog_elements', repo_route=True) | |
|
152 | ||
|
142 | 153 | # Tags |
|
143 | 154 | config.add_route( |
|
144 | 155 | name='tags_home', |
@@ -22,20 +22,32 b' import re' | |||
|
22 | 22 | |
|
23 | 23 | import pytest |
|
24 | 24 | |
|
25 |
from rhodecode. |
|
|
26 |
from rhodecode.tests import |
|
|
27 | from rhodecode.tests.utils import AssertResponse | |
|
28 | ||
|
25 | from rhodecode.apps.repository.views.repo_changelog import DEFAULT_CHANGELOG_SIZE | |
|
26 | from rhodecode.tests import TestController | |
|
29 | 27 | |
|
30 | 28 | MATCH_HASH = re.compile(r'<span class="commit_hash">r(\d+):[\da-f]+</span>') |
|
31 | 29 | |
|
32 | 30 | |
|
31 | def route_path(name, params=None, **kwargs): | |
|
32 | import urllib | |
|
33 | ||
|
34 | base_url = { | |
|
35 | 'repo_changelog':'/{repo_name}/changelog', | |
|
36 | 'repo_changelog_file':'/{repo_name}/changelog/{commit_id}/{f_path}', | |
|
37 | 'repo_changelog_elements':'/{repo_name}/changelog_elements', | |
|
38 | }[name].format(**kwargs) | |
|
39 | ||
|
40 | if params: | |
|
41 | base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) | |
|
42 | return base_url | |
|
43 | ||
|
44 | ||
|
33 | 45 | class TestChangelogController(TestController): |
|
34 | 46 | |
|
35 |
def test_ |
|
|
47 | def test_changelog(self, backend): | |
|
36 | 48 | self.log_user() |
|
37 | response = self.app.get(url(controller='changelog', action='index', | |
|
38 |
|
|
|
49 | response = self.app.get( | |
|
50 | route_path('repo_changelog', repo_name=backend.repo_name)) | |
|
39 | 51 | |
|
40 | 52 | first_idx = -1 |
|
41 | 53 | last_idx = -DEFAULT_CHANGELOG_SIZE |
@@ -43,39 +55,30 b' class TestChangelogController(TestContro' | |||
|
43 | 55 | response, first_idx, last_idx, backend) |
|
44 | 56 | |
|
45 | 57 | @pytest.mark.backends("hg", "git") |
|
46 |
def test_ |
|
|
58 | def test_changelog_filtered_by_branch(self, backend): | |
|
47 | 59 | self.log_user() |
|
48 | 60 | self.app.get( |
|
49 | url( | |
|
50 | controller='changelog', | |
|
51 | action='index', | |
|
52 | repo_name=backend.repo_name, | |
|
53 | branch=backend.default_branch_name), | |
|
61 | route_path('repo_changelog', repo_name=backend.repo_name, | |
|
62 | params=dict(branch=backend.default_branch_name)), | |
|
54 | 63 | status=200) |
|
55 | 64 | |
|
56 | 65 | @pytest.mark.backends("svn") |
|
57 |
def test_ |
|
|
66 | def test_changelog_filtered_by_branch_svn(self, autologin_user, backend): | |
|
58 | 67 | repo = backend['svn-simple-layout'] |
|
59 | 68 | response = self.app.get( |
|
60 | url( | |
|
61 | controller='changelog', | |
|
62 | action='index', | |
|
63 | repo_name=repo.repo_name, | |
|
64 | branch='trunk'), | |
|
69 | route_path('repo_changelog', repo_name=repo.repo_name, | |
|
70 | params=dict(branch='trunk')), | |
|
65 | 71 | status=200) |
|
66 | 72 | |
|
67 | 73 | self.assert_commits_on_page( |
|
68 | 74 | response, indexes=[15, 12, 7, 3, 2, 1]) |
|
69 | 75 | |
|
70 |
def test_ |
|
|
76 | def test_changelog_filtered_by_wrong_branch(self, backend): | |
|
71 | 77 | self.log_user() |
|
72 | 78 | branch = 'wrong-branch-name' |
|
73 | 79 | response = self.app.get( |
|
74 | url( | |
|
75 | controller='changelog', | |
|
76 | action='index', | |
|
77 | repo_name=backend.repo_name, | |
|
78 | branch=branch), | |
|
80 | route_path('repo_changelog', repo_name=backend.repo_name, | |
|
81 | params=dict(branch=branch)), | |
|
79 | 82 | status=302) |
|
80 | 83 | expected_url = '/{repo}/changelog/{branch}'.format( |
|
81 | 84 | repo=backend.repo_name, branch=branch) |
@@ -89,7 +92,7 b' class TestChangelogController(TestContro' | |||
|
89 | 92 | assert found_indexes == indexes |
|
90 | 93 | |
|
91 | 94 | @pytest.mark.xfail_backends("svn", reason="Depends on branch support") |
|
92 |
def test_ |
|
|
95 | def test_changelog_filtered_by_branch_with_merges( | |
|
93 | 96 | self, autologin_user, backend): |
|
94 | 97 | |
|
95 | 98 | # Note: The changelog of branch "b" does not contain the commit "a1" |
@@ -104,33 +107,27 b' class TestChangelogController(TestContro' | |||
|
104 | 107 | backend.create_repo(commits) |
|
105 | 108 | |
|
106 | 109 | self.app.get( |
|
107 | url('changelog_home', | |
|
108 | controller='changelog', | |
|
109 | action='index', | |
|
110 | repo_name=backend.repo_name, | |
|
111 | branch='b'), | |
|
110 | route_path('repo_changelog', repo_name=backend.repo_name, | |
|
111 | params=dict(branch='b')), | |
|
112 | 112 | status=200) |
|
113 | 113 | |
|
114 | 114 | @pytest.mark.backends("hg") |
|
115 |
def test_ |
|
|
115 | def test_changelog_closed_branches(self, autologin_user, backend): | |
|
116 | 116 | repo = backend['closed_branch'] |
|
117 | 117 | response = self.app.get( |
|
118 | url( | |
|
119 | controller='changelog', | |
|
120 | action='index', | |
|
121 | repo_name=repo.repo_name, | |
|
122 | branch='experimental'), | |
|
118 | route_path('repo_changelog', repo_name=repo.repo_name, | |
|
119 | params=dict(branch='experimental')), | |
|
123 | 120 | status=200) |
|
124 | 121 | |
|
125 | 122 | self.assert_commits_on_page( |
|
126 | 123 | response, indexes=[3, 1]) |
|
127 | 124 | |
|
128 |
def test_ |
|
|
125 | def test_changelog_pagination(self, backend): | |
|
129 | 126 | self.log_user() |
|
130 | 127 | # pagination, walk up to page 6 |
|
131 |
changelog_url = |
|
|
132 | controller='changelog', action='index', | |
|
133 | repo_name=backend.repo_name) | |
|
128 | changelog_url = route_path( | |
|
129 | 'repo_changelog', repo_name=backend.repo_name) | |
|
130 | ||
|
134 | 131 | for page in range(1, 7): |
|
135 | 132 | response = self.app.get(changelog_url, {'page': page}) |
|
136 | 133 | |
@@ -166,27 +163,33 b' class TestChangelogController(TestContro' | |||
|
166 | 163 | first_commit_of_next_page.idx, first_commit_of_next_page.short_id) |
|
167 | 164 | assert first_span_of_next_page not in response |
|
168 | 165 | |
|
169 | def test_index_with_filenode(self, backend): | |
|
166 | @pytest.mark.parametrize('test_path', [ | |
|
167 | 'vcs/exceptions.py', | |
|
168 | '/vcs/exceptions.py', | |
|
169 | '//vcs/exceptions.py' | |
|
170 | ]) | |
|
171 | def test_changelog_with_filenode(self, backend, test_path): | |
|
170 | 172 | self.log_user() |
|
171 |
response = self.app.get( |
|
|
172 | controller='changelog', action='index', revision='tip', | |
|
173 | f_path='/vcs/exceptions.py', repo_name=backend.repo_name)) | |
|
173 | response = self.app.get( | |
|
174 | route_path('repo_changelog_file', repo_name=backend.repo_name, | |
|
175 | commit_id='tip', f_path=test_path), | |
|
176 | ) | |
|
174 | 177 | |
|
175 | 178 | # history commits messages |
|
176 | 179 | response.mustcontain('Added exceptions module, this time for real') |
|
177 | 180 | response.mustcontain('Added not implemented hg backend test case') |
|
178 | 181 | response.mustcontain('Added BaseChangeset class') |
|
179 | 182 | |
|
180 |
def test_ |
|
|
183 | def test_changelog_with_filenode_that_is_dirnode(self, backend): | |
|
181 | 184 | self.log_user() |
|
182 | response = self.app.get(url(controller='changelog', action='index', | |
|
183 | revision='tip', f_path='/tests', | |
|
184 | repo_name=backend.repo_name)) | |
|
185 | assert response.status == '302 Found' | |
|
185 | self.app.get( | |
|
186 | route_path('repo_changelog_file', repo_name=backend.repo_name, | |
|
187 | commit_id='tip', f_path='/tests'), | |
|
188 | status=302) | |
|
186 | 189 | |
|
187 |
def test_ |
|
|
190 | def test_changelog_with_filenode_not_existing(self, backend): | |
|
188 | 191 | self.log_user() |
|
189 | response = self.app.get(url(controller='changelog', action='index', | |
|
190 | revision='tip', f_path='/wrong_path', | |
|
191 | repo_name=backend.repo_name)) | |
|
192 | assert response.status == '302 Found' | |
|
192 | self.app.get( | |
|
193 | route_path('repo_changelog_file', repo_name=backend.repo_name, | |
|
194 | commit_id='tip', f_path='wrong_path'), | |
|
195 | status=302) |
@@ -274,7 +274,6 b' def make_map(config):' | |||
|
274 | 274 | m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary', |
|
275 | 275 | action='edit_perms_summary', conditions={'method': ['GET']}) |
|
276 | 276 | |
|
277 | ||
|
278 | 277 | # ADMIN USER GROUPS REST ROUTES |
|
279 | 278 | with rmap.submapper(path_prefix=ADMIN_PREFIX, |
|
280 | 279 | controller='admin/user_groups') as m: |
@@ -698,21 +697,6 b' def make_map(config):' | |||
|
698 | 697 | conditions={'function': check_repo, 'method': ['DELETE']}, |
|
699 | 698 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
700 | 699 | |
|
701 | rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True, | |
|
702 | controller='changelog', conditions={'function': check_repo}, | |
|
703 | requirements=URL_NAME_REQUIREMENTS) | |
|
704 | ||
|
705 | rmap.connect('changelog_file_home', | |
|
706 | '/{repo_name}/changelog/{revision}/{f_path}', | |
|
707 | controller='changelog', f_path=None, | |
|
708 | conditions={'function': check_repo}, | |
|
709 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
|
710 | ||
|
711 | rmap.connect('changelog_elements', '/{repo_name}/changelog_details', | |
|
712 | controller='changelog', action='changelog_elements', | |
|
713 | conditions={'function': check_repo}, | |
|
714 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
|
715 | ||
|
716 | 700 | rmap.connect('repo_fork_create_home', '/{repo_name}/fork', |
|
717 | 701 | controller='forks', action='fork_create', |
|
718 | 702 | conditions={'function': check_repo, 'method': ['POST']}, |
@@ -149,7 +149,6 b' class ChangesetController(BaseRepoContro' | |||
|
149 | 149 | |
|
150 | 150 | def __before__(self): |
|
151 | 151 | super(ChangesetController, self).__before__() |
|
152 | c.affected_files_cut_off = 60 | |
|
153 | 152 | |
|
154 | 153 | def _index(self, commit_id_range, method): |
|
155 | 154 | c.ignorews_url = _ignorews_url |
@@ -129,9 +129,8 b' class JournalController(BaseController):' | |||
|
129 | 129 | desc = action_extra() |
|
130 | 130 | _url = None |
|
131 | 131 | if entry.repository is not None: |
|
132 |
_url = url('changelog |
|
|
133 |
repo_name=entry.repository.repo_name |
|
|
134 | qualified=True) | |
|
132 | _url = h.route_url('repo_changelog', | |
|
133 | repo_name=entry.repository.repo_name) | |
|
135 | 134 | |
|
136 | 135 | feed.add_item(title=title, |
|
137 | 136 | pubdate=entry.action_date, |
@@ -172,9 +171,8 b' class JournalController(BaseController):' | |||
|
172 | 171 | desc = action_extra() |
|
173 | 172 | _url = None |
|
174 | 173 | if entry.repository is not None: |
|
175 |
_url = url('changelog |
|
|
176 |
repo_name=entry.repository.repo_name |
|
|
177 | qualified=True) | |
|
174 | _url = h.route_url('repo_changelog', | |
|
175 | repo_name=entry.repository.repo_name) | |
|
178 | 176 | |
|
179 | 177 | feed.add_item(title=title, |
|
180 | 178 | pubdate=entry.action_date, |
@@ -324,6 +324,8 b' def attach_context_attributes(context, r' | |||
|
324 | 324 | context.visual.rhodecode_support_url = \ |
|
325 | 325 | rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support') |
|
326 | 326 | |
|
327 | context.visual.affected_files_cut_off = 60 | |
|
328 | ||
|
327 | 329 | context.pre_code = rc_config.get('rhodecode_pre_code') |
|
328 | 330 | context.post_code = rc_config.get('rhodecode_post_code') |
|
329 | 331 | context.rhodecode_name = rc_config.get('rhodecode_title') |
@@ -66,7 +66,7 b' function setRCMouseBindings(repoName, re' | |||
|
66 | 66 | }); |
|
67 | 67 | Mousetrap.bind(['g c'], function(e) { |
|
68 | 68 | window.location = pyroutes.url( |
|
69 |
'changelog |
|
|
69 | 'repo_changelog', {'repo_name': repoName}); | |
|
70 | 70 | }); |
|
71 | 71 | Mousetrap.bind(['g F'], function(e) { |
|
72 | 72 | window.location = pyroutes.url( |
@@ -30,9 +30,6 b' function registerRCRoutes() {' | |||
|
30 | 30 | pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']); |
|
31 | 31 | pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']); |
|
32 | 32 | pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']); |
|
33 | pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']); | |
|
34 | pyroutes.register('changelog_file_home', '/%(repo_name)s/changelog/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']); | |
|
35 | pyroutes.register('changelog_elements', '/%(repo_name)s/changelog_details', ['repo_name']); | |
|
36 | 33 | pyroutes.register('favicon', '/favicon.ico', []); |
|
37 | 34 | pyroutes.register('robots', '/robots.txt', []); |
|
38 | 35 | pyroutes.register('auth_home', '/_admin/auth*traverse', []); |
@@ -124,6 +121,9 b' function registerRCRoutes() {' | |||
|
124 | 121 | pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']); |
|
125 | 122 | pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']); |
|
126 | 123 | pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']); |
|
124 | pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']); | |
|
125 | pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); | |
|
126 | pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']); | |
|
127 | 127 | pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']); |
|
128 | 128 | pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']); |
|
129 | 129 | pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']); |
@@ -105,7 +105,7 b' var CommitsController = function () {' | |||
|
105 | 105 | urlData['branch'] = branch; |
|
106 | 106 | } |
|
107 | 107 | |
|
108 | return pyroutes.url('changelog_elements', urlData); | |
|
108 | return pyroutes.url('repo_changelog_elements', urlData); | |
|
109 | 109 | }; |
|
110 | 110 | |
|
111 | 111 | this.loadNext = function (node, page, branch) { |
@@ -226,7 +226,7 b'' | |||
|
226 | 226 | <div class="wrapper"> |
|
227 | 227 | <ul id="context-pages" class="horizontal-list navigation"> |
|
228 | 228 | <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li> |
|
229 |
<li class="${is_active('changelog')}"><a class="menulink" href="${h. |
|
|
229 | <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li> | |
|
230 | 230 | <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li> |
|
231 | 231 | <li class="${is_active('compare')}"> |
|
232 | 232 | <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a> |
@@ -86,7 +86,7 b'' | |||
|
86 | 86 | </div> |
|
87 | 87 | ${self.breadcrumbs('breadcrumbs_light')} |
|
88 | 88 | <div id="commit-counter" data-total=${c.total_cs} class="pull-right"> |
|
89 | ${ungettext('showing %d out of %d commit', 'showing %d out of %d commits', c.showing_commits) % (c.showing_commits, c.total_cs)} | |
|
89 | ${_ungettext('showing %d out of %d commit', 'showing %d out of %d commits', c.showing_commits) % (c.showing_commits, c.total_cs)} | |
|
90 | 90 | </div> |
|
91 | 91 | </div> |
|
92 | 92 | |
@@ -230,7 +230,7 b'' | |||
|
230 | 230 | |
|
231 | 231 | $("#clear_filter").on("click", function() { |
|
232 | 232 | var filter = {'repo_name': '${c.repo_name}'}; |
|
233 |
window.location = pyroutes.url('changelog |
|
|
233 | window.location = pyroutes.url('repo_changelog', filter); | |
|
234 | 234 | }); |
|
235 | 235 | |
|
236 | 236 | $("#branch_filter").select2({ |
@@ -280,7 +280,7 b'' | |||
|
280 | 280 | else if (data.type == 'book'){ |
|
281 | 281 | filter["bookmark"] = selected; |
|
282 | 282 | } |
|
283 |
window.location = pyroutes.url('changelog |
|
|
283 | window.location = pyroutes.url('repo_changelog', filter); | |
|
284 | 284 | }); |
|
285 | 285 | |
|
286 | 286 | commitsController = new CommitsController(); |
@@ -101,7 +101,7 b'' | |||
|
101 | 101 | ## branch |
|
102 | 102 | %if commit.branch: |
|
103 | 103 | <span class="tag branchtag" title="${h.tooltip(_('Branch %s') % commit.branch)}"> |
|
104 |
<a href="${h. |
|
|
104 | <a href="${h.route_path('repo_changelog',repo_name=c.repo_name,_query=dict(branch=commit.branch))}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a> | |
|
105 | 105 | </span> |
|
106 | 106 | %endif |
|
107 | 107 |
@@ -14,7 +14,7 b'' | |||
|
14 | 14 | </a> |
|
15 | 15 | </li> |
|
16 | 16 | <li> |
|
17 |
<a title="${_('Changelog')}" href="${h. |
|
|
17 | <a title="${_('Changelog')}" href="${h.route_path('repo_changelog',repo_name=repo_name)}"> | |
|
18 | 18 | <span>${_('Changelog')}</span> |
|
19 | 19 | </a> |
|
20 | 20 | </li> |
@@ -219,9 +219,9 b'' | |||
|
219 | 219 | if (path.indexOf("#") >= 0) { |
|
220 | 220 | path = path.slice(0, path.indexOf("#")); |
|
221 | 221 | } |
|
222 |
var url = pyroutes.url('changelog_fil |
|
|
222 | var url = pyroutes.url('repo_changelog_file', | |
|
223 | 223 | {'repo_name': templateContext.repo_name, |
|
224 |
' |
|
|
224 | 'commit_id': state.rev, 'f_path': path, 'limit': 6}); | |
|
225 | 225 | $('#file_history_container').show(); |
|
226 | 226 | $('#file_history_container').html('<div class="file-history-inner">{0}</div>'.format(_gettext('Loading ...'))); |
|
227 | 227 |
@@ -51,7 +51,7 b'' | |||
|
51 | 51 | <span class="item">${h.format_byte_size_binary(c.file.size)}</span> |
|
52 | 52 | <span class="item last">${c.file.mimetype}</span> |
|
53 | 53 | <div class="buttons"> |
|
54 |
<a class="btn btn-mini" href="${h. |
|
|
54 | <a class="btn btn-mini" href="${h.route_path('repo_changelog_file',repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path)}"> | |
|
55 | 55 | <i class="icon-time"></i> ${_('history')} |
|
56 | 56 | </a> |
|
57 | 57 |
@@ -16,7 +16,7 b'' | |||
|
16 | 16 | <a id="file_history_overview" href="#"> |
|
17 | 17 | ${_('History')} |
|
18 | 18 | </a> |
|
19 |
<a id="file_history_overview_full" style="display: none" href="${h. |
|
|
19 | <a id="file_history_overview_full" style="display: none" href="${h.route_path('repo_changelog_file',repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path)}"> | |
|
20 | 20 | ${_('Show Full History')} |
|
21 | 21 | </a> | |
|
22 | 22 | %if c.annotate: |
@@ -75,7 +75,7 b'' | |||
|
75 | 75 | ## branch link is only valid if it is a branch |
|
76 | 76 | <span class="tag"> |
|
77 | 77 | %if c.pull_request.source_ref_parts.type == 'branch': |
|
78 |
<a href="${h. |
|
|
78 | <a href="${h.route_path('repo_changelog', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a> | |
|
79 | 79 | %else: |
|
80 | 80 | ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name} |
|
81 | 81 | %endif |
@@ -107,7 +107,7 b'' | |||
|
107 | 107 | ## branch link is only valid if it is a branch |
|
108 | 108 | <span class="tag"> |
|
109 | 109 | %if c.pull_request.target_ref_parts.type == 'branch': |
|
110 |
<a href="${h. |
|
|
110 | <a href="${h.route_path('repo_changelog', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a> | |
|
111 | 111 | %else: |
|
112 | 112 | ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name} |
|
113 | 113 | %endif |
@@ -65,7 +65,7 b' for line_number in matching_lines:' | |||
|
65 | 65 | %endif |
|
66 | 66 | </div> |
|
67 | 67 | <div class="buttons"> |
|
68 |
<a id="file_history_overview_full" href="${h. |
|
|
68 | <a id="file_history_overview_full" href="${h.route_path('repo_changelog_file',repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path',''))}"> | |
|
69 | 69 | ${_('Show Full History')} |
|
70 | 70 | </a> |
|
71 | 71 | | ${h.link_to(_('Annotation'), h.route_path('repo_files:annotated', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))} |
@@ -94,7 +94,7 b'' | |||
|
94 | 94 | % if commit_rev == -1: |
|
95 | 95 | ${_ungettext('%(num)s Commit', '%(num)s Commits', 0) % {'num': 0}}, |
|
96 | 96 | % else: |
|
97 |
<a href="${h. |
|
|
97 | <a href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"> | |
|
98 | 98 | ${_ungettext('%(num)s Commit', '%(num)s Commits', commit_rev) % {'num': commit_rev}}</a>, |
|
99 | 99 | % endif |
|
100 | 100 |
@@ -74,7 +74,7 b'' | |||
|
74 | 74 | ## branch |
|
75 | 75 | %if cs.branch: |
|
76 | 76 | <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % cs.branch)}"> |
|
77 |
<a href="${h. |
|
|
77 | <a href="${h.route_path('repo_changelog',repo_name=c.repo_name,_query=dict(branch=cs.branch))}"><i class="icon-code-fork"></i>${h.shorter(cs.branch)}</a> | |
|
78 | 78 | </span> |
|
79 | 79 | %endif |
|
80 | 80 | </div> |
@@ -36,6 +36,19 b' from rhodecode.tests import (' | |||
|
36 | 36 | from rhodecode.tests.utils import AssertResponse |
|
37 | 37 | |
|
38 | 38 | |
|
39 | def route_path(name, params=None, **kwargs): | |
|
40 | import urllib | |
|
41 | ||
|
42 | base_url = { | |
|
43 | 'repo_changelog':'/{repo_name}/changelog', | |
|
44 | 'repo_changelog_file':'/{repo_name}/changelog/{commit_id}/{f_path}', | |
|
45 | }[name].format(**kwargs) | |
|
46 | ||
|
47 | if params: | |
|
48 | base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) | |
|
49 | return base_url | |
|
50 | ||
|
51 | ||
|
39 | 52 | @pytest.mark.usefixtures('app', 'autologin_user') |
|
40 | 53 | @pytest.mark.backends("git", "hg") |
|
41 | 54 | class TestPullrequestsController(object): |
@@ -912,14 +925,14 b' class TestPullrequestsController(object)' | |||
|
912 | 925 | target_children = target.getchildren() |
|
913 | 926 | assert len(target_children) == 1 |
|
914 | 927 | |
|
915 |
expected_origin_link = |
|
|
916 |
'changelog |
|
|
928 | expected_origin_link = route_path( | |
|
929 | 'repo_changelog', | |
|
917 | 930 | repo_name=pull_request.source_repo.scm_instance().name, |
|
918 | branch='origin') | |
|
919 |
expected_target_link = |
|
|
920 |
'changelog |
|
|
931 | params=dict(branch='origin')) | |
|
932 | expected_target_link = route_path( | |
|
933 | 'repo_changelog', | |
|
921 | 934 | repo_name=pull_request.target_repo.scm_instance().name, |
|
922 | branch='target') | |
|
935 | params=dict(branch='target')) | |
|
923 | 936 | assert origin_children[0].attrib['href'] == expected_origin_link |
|
924 | 937 | assert origin_children[0].text == 'branch: origin' |
|
925 | 938 | assert target_children[0].attrib['href'] == expected_target_link |
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now