##// END OF EJS Templates
changelog: ported to pyramid views.
marcink -
r1931:f3f88cc8 default
parent child Browse files
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)
@@ -1,266 +1,277 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 from rhodecode.apps._base import add_route_with_slash
20 from rhodecode.apps._base import add_route_with_slash
21
21
22
22
23 def includeme(config):
23 def includeme(config):
24
24
25 # Summary
25 # Summary
26 # NOTE(marcink): one additional route is defined in very bottom, catch
26 # NOTE(marcink): one additional route is defined in very bottom, catch
27 # all pattern
27 # all pattern
28 config.add_route(
28 config.add_route(
29 name='repo_summary_explicit',
29 name='repo_summary_explicit',
30 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
30 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
31 config.add_route(
31 config.add_route(
32 name='repo_summary_commits',
32 name='repo_summary_commits',
33 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
33 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
34
34
35 # repo commits
35 # repo commits
36 config.add_route(
36 config.add_route(
37 name='repo_commit',
37 name='repo_commit',
38 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
38 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
39
39
40 # repo files
40 # repo files
41 config.add_route(
41 config.add_route(
42 name='repo_archivefile',
42 name='repo_archivefile',
43 pattern='/{repo_name:.*?[^/]}/archive/{fname}', repo_route=True)
43 pattern='/{repo_name:.*?[^/]}/archive/{fname}', repo_route=True)
44
44
45 config.add_route(
45 config.add_route(
46 name='repo_files_diff',
46 name='repo_files_diff',
47 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
47 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
48 config.add_route( # legacy route to make old links work
48 config.add_route( # legacy route to make old links work
49 name='repo_files_diff_2way_redirect',
49 name='repo_files_diff_2way_redirect',
50 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
50 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
51
51
52 config.add_route(
52 config.add_route(
53 name='repo_files',
53 name='repo_files',
54 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
54 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
55 config.add_route(
55 config.add_route(
56 name='repo_files:default_path',
56 name='repo_files:default_path',
57 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
57 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
58 config.add_route(
58 config.add_route(
59 name='repo_files:default_commit',
59 name='repo_files:default_commit',
60 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
60 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
61
61
62 config.add_route(
62 config.add_route(
63 name='repo_files:rendered',
63 name='repo_files:rendered',
64 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
64 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
65
65
66 config.add_route(
66 config.add_route(
67 name='repo_files:annotated',
67 name='repo_files:annotated',
68 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
68 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
69 config.add_route(
69 config.add_route(
70 name='repo_files:annotated_previous',
70 name='repo_files:annotated_previous',
71 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
71 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
72
72
73 config.add_route(
73 config.add_route(
74 name='repo_nodetree_full',
74 name='repo_nodetree_full',
75 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
75 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
76 config.add_route(
76 config.add_route(
77 name='repo_nodetree_full:default_path',
77 name='repo_nodetree_full:default_path',
78 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
78 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
79
79
80 config.add_route(
80 config.add_route(
81 name='repo_files_nodelist',
81 name='repo_files_nodelist',
82 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
82 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
83
83
84 config.add_route(
84 config.add_route(
85 name='repo_file_raw',
85 name='repo_file_raw',
86 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
86 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
87
87
88 config.add_route(
88 config.add_route(
89 name='repo_file_download',
89 name='repo_file_download',
90 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
90 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
91 config.add_route( # backward compat to keep old links working
91 config.add_route( # backward compat to keep old links working
92 name='repo_file_download:legacy',
92 name='repo_file_download:legacy',
93 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
93 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
94 repo_route=True)
94 repo_route=True)
95
95
96 config.add_route(
96 config.add_route(
97 name='repo_file_history',
97 name='repo_file_history',
98 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
98 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
99
99
100 config.add_route(
100 config.add_route(
101 name='repo_file_authors',
101 name='repo_file_authors',
102 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
102 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
103
103
104 config.add_route(
104 config.add_route(
105 name='repo_files_remove_file',
105 name='repo_files_remove_file',
106 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
106 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
107 repo_route=True)
107 repo_route=True)
108 config.add_route(
108 config.add_route(
109 name='repo_files_delete_file',
109 name='repo_files_delete_file',
110 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
110 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
111 repo_route=True)
111 repo_route=True)
112 config.add_route(
112 config.add_route(
113 name='repo_files_edit_file',
113 name='repo_files_edit_file',
114 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
114 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
115 repo_route=True)
115 repo_route=True)
116 config.add_route(
116 config.add_route(
117 name='repo_files_update_file',
117 name='repo_files_update_file',
118 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
118 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
119 repo_route=True)
119 repo_route=True)
120 config.add_route(
120 config.add_route(
121 name='repo_files_add_file',
121 name='repo_files_add_file',
122 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
122 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
123 repo_route=True)
123 repo_route=True)
124 config.add_route(
124 config.add_route(
125 name='repo_files_create_file',
125 name='repo_files_create_file',
126 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
126 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
127 repo_route=True)
127 repo_route=True)
128
128
129 # refs data
129 # refs data
130 config.add_route(
130 config.add_route(
131 name='repo_refs_data',
131 name='repo_refs_data',
132 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
132 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
133
133
134 config.add_route(
134 config.add_route(
135 name='repo_refs_changelog_data',
135 name='repo_refs_changelog_data',
136 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
136 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
137
137
138 config.add_route(
138 config.add_route(
139 name='repo_stats',
139 name='repo_stats',
140 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
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 # Tags
153 # Tags
143 config.add_route(
154 config.add_route(
144 name='tags_home',
155 name='tags_home',
145 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
156 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
146
157
147 # Branches
158 # Branches
148 config.add_route(
159 config.add_route(
149 name='branches_home',
160 name='branches_home',
150 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
161 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
151
162
152 config.add_route(
163 config.add_route(
153 name='bookmarks_home',
164 name='bookmarks_home',
154 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
165 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
155
166
156 # Pull Requests
167 # Pull Requests
157 config.add_route(
168 config.add_route(
158 name='pullrequest_show',
169 name='pullrequest_show',
159 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id}',
170 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id}',
160 repo_route=True)
171 repo_route=True)
161
172
162 config.add_route(
173 config.add_route(
163 name='pullrequest_show_all',
174 name='pullrequest_show_all',
164 pattern='/{repo_name:.*?[^/]}/pull-request',
175 pattern='/{repo_name:.*?[^/]}/pull-request',
165 repo_route=True, repo_accepted_types=['hg', 'git'])
176 repo_route=True, repo_accepted_types=['hg', 'git'])
166
177
167 config.add_route(
178 config.add_route(
168 name='pullrequest_show_all_data',
179 name='pullrequest_show_all_data',
169 pattern='/{repo_name:.*?[^/]}/pull-request-data',
180 pattern='/{repo_name:.*?[^/]}/pull-request-data',
170 repo_route=True, repo_accepted_types=['hg', 'git'])
181 repo_route=True, repo_accepted_types=['hg', 'git'])
171
182
172 # commits aka changesets
183 # commits aka changesets
173 # TODO(dan): handle default landing revision ?
184 # TODO(dan): handle default landing revision ?
174 config.add_route(
185 config.add_route(
175 name='changeset_home',
186 name='changeset_home',
176 pattern='/{repo_name:.*?[^/]}/changeset/{revision}',
187 pattern='/{repo_name:.*?[^/]}/changeset/{revision}',
177 repo_route=True)
188 repo_route=True)
178 config.add_route(
189 config.add_route(
179 name='changeset_children',
190 name='changeset_children',
180 pattern='/{repo_name:.*?[^/]}/changeset_children/{revision}',
191 pattern='/{repo_name:.*?[^/]}/changeset_children/{revision}',
181 repo_route=True)
192 repo_route=True)
182 config.add_route(
193 config.add_route(
183 name='changeset_parents',
194 name='changeset_parents',
184 pattern='/{repo_name:.*?[^/]}/changeset_parents/{revision}',
195 pattern='/{repo_name:.*?[^/]}/changeset_parents/{revision}',
185 repo_route=True)
196 repo_route=True)
186
197
187 # Settings
198 # Settings
188 config.add_route(
199 config.add_route(
189 name='edit_repo',
200 name='edit_repo',
190 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
201 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
191
202
192 # Settings advanced
203 # Settings advanced
193 config.add_route(
204 config.add_route(
194 name='edit_repo_advanced',
205 name='edit_repo_advanced',
195 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
206 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
196 config.add_route(
207 config.add_route(
197 name='edit_repo_advanced_delete',
208 name='edit_repo_advanced_delete',
198 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
209 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
199 config.add_route(
210 config.add_route(
200 name='edit_repo_advanced_locking',
211 name='edit_repo_advanced_locking',
201 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
212 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
202 config.add_route(
213 config.add_route(
203 name='edit_repo_advanced_journal',
214 name='edit_repo_advanced_journal',
204 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
215 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
205 config.add_route(
216 config.add_route(
206 name='edit_repo_advanced_fork',
217 name='edit_repo_advanced_fork',
207 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
218 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
208
219
209 # Caches
220 # Caches
210 config.add_route(
221 config.add_route(
211 name='edit_repo_caches',
222 name='edit_repo_caches',
212 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
223 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
213
224
214 # Permissions
225 # Permissions
215 config.add_route(
226 config.add_route(
216 name='edit_repo_perms',
227 name='edit_repo_perms',
217 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
228 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
218
229
219 # Repo Review Rules
230 # Repo Review Rules
220 config.add_route(
231 config.add_route(
221 name='repo_reviewers',
232 name='repo_reviewers',
222 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
233 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
223
234
224 config.add_route(
235 config.add_route(
225 name='repo_default_reviewers_data',
236 name='repo_default_reviewers_data',
226 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
237 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
227
238
228 # Maintenance
239 # Maintenance
229 config.add_route(
240 config.add_route(
230 name='repo_maintenance',
241 name='repo_maintenance',
231 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
242 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
232
243
233 config.add_route(
244 config.add_route(
234 name='repo_maintenance_execute',
245 name='repo_maintenance_execute',
235 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
246 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
236
247
237 # Strip
248 # Strip
238 config.add_route(
249 config.add_route(
239 name='strip',
250 name='strip',
240 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
251 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
241
252
242 config.add_route(
253 config.add_route(
243 name='strip_check',
254 name='strip_check',
244 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
255 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
245
256
246 config.add_route(
257 config.add_route(
247 name='strip_execute',
258 name='strip_execute',
248 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
259 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
249
260
250 # ATOM/RSS Feed
261 # ATOM/RSS Feed
251 config.add_route(
262 config.add_route(
252 name='rss_feed_home',
263 name='rss_feed_home',
253 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
264 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
254
265
255 config.add_route(
266 config.add_route(
256 name='atom_feed_home',
267 name='atom_feed_home',
257 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
268 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
258
269
259 # NOTE(marcink): needs to be at the end for catch-all
270 # NOTE(marcink): needs to be at the end for catch-all
260 add_route_with_slash(
271 add_route_with_slash(
261 config,
272 config,
262 name='repo_summary',
273 name='repo_summary',
263 pattern='/{repo_name:.*?[^/]}', repo_route=True)
274 pattern='/{repo_name:.*?[^/]}', repo_route=True)
264
275
265 # Scan module for configuration decorators.
276 # Scan module for configuration decorators.
266 config.scan()
277 config.scan()
@@ -1,192 +1,195 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import re
21 import re
22
22
23 import pytest
23 import pytest
24
24
25 from rhodecode.controllers.changelog import DEFAULT_CHANGELOG_SIZE
25 from rhodecode.apps.repository.views.repo_changelog import DEFAULT_CHANGELOG_SIZE
26 from rhodecode.tests import url, TestController
26 from rhodecode.tests import TestController
27 from rhodecode.tests.utils import AssertResponse
28
29
27
30 MATCH_HASH = re.compile(r'<span class="commit_hash">r(\d+):[\da-f]+</span>')
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 class TestChangelogController(TestController):
45 class TestChangelogController(TestController):
34
46
35 def test_index(self, backend):
47 def test_changelog(self, backend):
36 self.log_user()
48 self.log_user()
37 response = self.app.get(url(controller='changelog', action='index',
49 response = self.app.get(
38 repo_name=backend.repo_name))
50 route_path('repo_changelog', repo_name=backend.repo_name))
39
51
40 first_idx = -1
52 first_idx = -1
41 last_idx = -DEFAULT_CHANGELOG_SIZE
53 last_idx = -DEFAULT_CHANGELOG_SIZE
42 self.assert_commit_range_on_page(
54 self.assert_commit_range_on_page(
43 response, first_idx, last_idx, backend)
55 response, first_idx, last_idx, backend)
44
56
45 @pytest.mark.backends("hg", "git")
57 @pytest.mark.backends("hg", "git")
46 def test_index_filtered_by_branch(self, backend):
58 def test_changelog_filtered_by_branch(self, backend):
47 self.log_user()
59 self.log_user()
48 self.app.get(
60 self.app.get(
49 url(
61 route_path('repo_changelog', repo_name=backend.repo_name,
50 controller='changelog',
62 params=dict(branch=backend.default_branch_name)),
51 action='index',
52 repo_name=backend.repo_name,
53 branch=backend.default_branch_name),
54 status=200)
63 status=200)
55
64
56 @pytest.mark.backends("svn")
65 @pytest.mark.backends("svn")
57 def test_index_filtered_by_branch_svn(self, autologin_user, backend):
66 def test_changelog_filtered_by_branch_svn(self, autologin_user, backend):
58 repo = backend['svn-simple-layout']
67 repo = backend['svn-simple-layout']
59 response = self.app.get(
68 response = self.app.get(
60 url(
69 route_path('repo_changelog', repo_name=repo.repo_name,
61 controller='changelog',
70 params=dict(branch='trunk')),
62 action='index',
63 repo_name=repo.repo_name,
64 branch='trunk'),
65 status=200)
71 status=200)
66
72
67 self.assert_commits_on_page(
73 self.assert_commits_on_page(
68 response, indexes=[15, 12, 7, 3, 2, 1])
74 response, indexes=[15, 12, 7, 3, 2, 1])
69
75
70 def test_index_filtered_by_wrong_branch(self, backend):
76 def test_changelog_filtered_by_wrong_branch(self, backend):
71 self.log_user()
77 self.log_user()
72 branch = 'wrong-branch-name'
78 branch = 'wrong-branch-name'
73 response = self.app.get(
79 response = self.app.get(
74 url(
80 route_path('repo_changelog', repo_name=backend.repo_name,
75 controller='changelog',
81 params=dict(branch=branch)),
76 action='index',
77 repo_name=backend.repo_name,
78 branch=branch),
79 status=302)
82 status=302)
80 expected_url = '/{repo}/changelog/{branch}'.format(
83 expected_url = '/{repo}/changelog/{branch}'.format(
81 repo=backend.repo_name, branch=branch)
84 repo=backend.repo_name, branch=branch)
82 assert expected_url in response.location
85 assert expected_url in response.location
83 response = response.follow()
86 response = response.follow()
84 expected_warning = 'Branch {} is not found.'.format(branch)
87 expected_warning = 'Branch {} is not found.'.format(branch)
85 assert expected_warning in response.body
88 assert expected_warning in response.body
86
89
87 def assert_commits_on_page(self, response, indexes):
90 def assert_commits_on_page(self, response, indexes):
88 found_indexes = [int(idx) for idx in MATCH_HASH.findall(response.body)]
91 found_indexes = [int(idx) for idx in MATCH_HASH.findall(response.body)]
89 assert found_indexes == indexes
92 assert found_indexes == indexes
90
93
91 @pytest.mark.xfail_backends("svn", reason="Depends on branch support")
94 @pytest.mark.xfail_backends("svn", reason="Depends on branch support")
92 def test_index_filtered_by_branch_with_merges(
95 def test_changelog_filtered_by_branch_with_merges(
93 self, autologin_user, backend):
96 self, autologin_user, backend):
94
97
95 # Note: The changelog of branch "b" does not contain the commit "a1"
98 # Note: The changelog of branch "b" does not contain the commit "a1"
96 # although this is a parent of commit "b1". And branch "b" has commits
99 # although this is a parent of commit "b1". And branch "b" has commits
97 # which have a smaller index than commit "a1".
100 # which have a smaller index than commit "a1".
98 commits = [
101 commits = [
99 {'message': 'a'},
102 {'message': 'a'},
100 {'message': 'b', 'branch': 'b'},
103 {'message': 'b', 'branch': 'b'},
101 {'message': 'a1', 'parents': ['a']},
104 {'message': 'a1', 'parents': ['a']},
102 {'message': 'b1', 'branch': 'b', 'parents': ['b', 'a1']},
105 {'message': 'b1', 'branch': 'b', 'parents': ['b', 'a1']},
103 ]
106 ]
104 backend.create_repo(commits)
107 backend.create_repo(commits)
105
108
106 self.app.get(
109 self.app.get(
107 url('changelog_home',
110 route_path('repo_changelog', repo_name=backend.repo_name,
108 controller='changelog',
111 params=dict(branch='b')),
109 action='index',
110 repo_name=backend.repo_name,
111 branch='b'),
112 status=200)
112 status=200)
113
113
114 @pytest.mark.backends("hg")
114 @pytest.mark.backends("hg")
115 def test_index_closed_branches(self, autologin_user, backend):
115 def test_changelog_closed_branches(self, autologin_user, backend):
116 repo = backend['closed_branch']
116 repo = backend['closed_branch']
117 response = self.app.get(
117 response = self.app.get(
118 url(
118 route_path('repo_changelog', repo_name=repo.repo_name,
119 controller='changelog',
119 params=dict(branch='experimental')),
120 action='index',
121 repo_name=repo.repo_name,
122 branch='experimental'),
123 status=200)
120 status=200)
124
121
125 self.assert_commits_on_page(
122 self.assert_commits_on_page(
126 response, indexes=[3, 1])
123 response, indexes=[3, 1])
127
124
128 def test_index_pagination(self, backend):
125 def test_changelog_pagination(self, backend):
129 self.log_user()
126 self.log_user()
130 # pagination, walk up to page 6
127 # pagination, walk up to page 6
131 changelog_url = url(
128 changelog_url = route_path(
132 controller='changelog', action='index',
129 'repo_changelog', repo_name=backend.repo_name)
133 repo_name=backend.repo_name)
130
134 for page in range(1, 7):
131 for page in range(1, 7):
135 response = self.app.get(changelog_url, {'page': page})
132 response = self.app.get(changelog_url, {'page': page})
136
133
137 first_idx = -DEFAULT_CHANGELOG_SIZE * (page - 1) - 1
134 first_idx = -DEFAULT_CHANGELOG_SIZE * (page - 1) - 1
138 last_idx = -DEFAULT_CHANGELOG_SIZE * page
135 last_idx = -DEFAULT_CHANGELOG_SIZE * page
139 self.assert_commit_range_on_page(response, first_idx, last_idx, backend)
136 self.assert_commit_range_on_page(response, first_idx, last_idx, backend)
140
137
141 def assert_commit_range_on_page(
138 def assert_commit_range_on_page(
142 self, response, first_idx, last_idx, backend):
139 self, response, first_idx, last_idx, backend):
143 input_template = (
140 input_template = (
144 """<input class="commit-range" id="%(raw_id)s" """
141 """<input class="commit-range" id="%(raw_id)s" """
145 """name="%(raw_id)s" type="checkbox" value="1" />"""
142 """name="%(raw_id)s" type="checkbox" value="1" />"""
146 )
143 )
147 commit_span_template = """<span class="commit_hash">r%s:%s</span>"""
144 commit_span_template = """<span class="commit_hash">r%s:%s</span>"""
148 repo = backend.repo
145 repo = backend.repo
149
146
150 first_commit_on_page = repo.get_commit(commit_idx=first_idx)
147 first_commit_on_page = repo.get_commit(commit_idx=first_idx)
151 response.mustcontain(
148 response.mustcontain(
152 input_template % {'raw_id': first_commit_on_page.raw_id})
149 input_template % {'raw_id': first_commit_on_page.raw_id})
153 response.mustcontain(commit_span_template % (
150 response.mustcontain(commit_span_template % (
154 first_commit_on_page.idx, first_commit_on_page.short_id)
151 first_commit_on_page.idx, first_commit_on_page.short_id)
155 )
152 )
156
153
157 last_commit_on_page = repo.get_commit(commit_idx=last_idx)
154 last_commit_on_page = repo.get_commit(commit_idx=last_idx)
158 response.mustcontain(
155 response.mustcontain(
159 input_template % {'raw_id': last_commit_on_page.raw_id})
156 input_template % {'raw_id': last_commit_on_page.raw_id})
160 response.mustcontain(commit_span_template % (
157 response.mustcontain(commit_span_template % (
161 last_commit_on_page.idx, last_commit_on_page.short_id)
158 last_commit_on_page.idx, last_commit_on_page.short_id)
162 )
159 )
163
160
164 first_commit_of_next_page = repo.get_commit(commit_idx=last_idx - 1)
161 first_commit_of_next_page = repo.get_commit(commit_idx=last_idx - 1)
165 first_span_of_next_page = commit_span_template % (
162 first_span_of_next_page = commit_span_template % (
166 first_commit_of_next_page.idx, first_commit_of_next_page.short_id)
163 first_commit_of_next_page.idx, first_commit_of_next_page.short_id)
167 assert first_span_of_next_page not in response
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 self.log_user()
172 self.log_user()
171 response = self.app.get(url(
173 response = self.app.get(
172 controller='changelog', action='index', revision='tip',
174 route_path('repo_changelog_file', repo_name=backend.repo_name,
173 f_path='/vcs/exceptions.py', repo_name=backend.repo_name))
175 commit_id='tip', f_path=test_path),
176 )
174
177
175 # history commits messages
178 # history commits messages
176 response.mustcontain('Added exceptions module, this time for real')
179 response.mustcontain('Added exceptions module, this time for real')
177 response.mustcontain('Added not implemented hg backend test case')
180 response.mustcontain('Added not implemented hg backend test case')
178 response.mustcontain('Added BaseChangeset class')
181 response.mustcontain('Added BaseChangeset class')
179
182
180 def test_index_with_filenode_that_is_dirnode(self, backend):
183 def test_changelog_with_filenode_that_is_dirnode(self, backend):
181 self.log_user()
184 self.log_user()
182 response = self.app.get(url(controller='changelog', action='index',
185 self.app.get(
183 revision='tip', f_path='/tests',
186 route_path('repo_changelog_file', repo_name=backend.repo_name,
184 repo_name=backend.repo_name))
187 commit_id='tip', f_path='/tests'),
185 assert response.status == '302 Found'
188 status=302)
186
189
187 def test_index_with_filenode_not_existing(self, backend):
190 def test_changelog_with_filenode_not_existing(self, backend):
188 self.log_user()
191 self.log_user()
189 response = self.app.get(url(controller='changelog', action='index',
192 self.app.get(
190 revision='tip', f_path='/wrong_path',
193 route_path('repo_changelog_file', repo_name=backend.repo_name,
191 repo_name=backend.repo_name))
194 commit_id='tip', f_path='wrong_path'),
192 assert response.status == '302 Found'
195 status=302)
@@ -1,731 +1,715 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Routes configuration
22 Routes configuration
23
23
24 The more specific and detailed routes should be defined first so they
24 The more specific and detailed routes should be defined first so they
25 may take precedent over the more generic routes. For more information
25 may take precedent over the more generic routes. For more information
26 refer to the routes manual at http://routes.groovie.org/docs/
26 refer to the routes manual at http://routes.groovie.org/docs/
27
27
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 and _route_name variable which uses some of stored naming here to do redirects.
29 and _route_name variable which uses some of stored naming here to do redirects.
30 """
30 """
31 import os
31 import os
32 import re
32 import re
33 from routes import Mapper
33 from routes import Mapper
34
34
35 # prefix for non repository related links needs to be prefixed with `/`
35 # prefix for non repository related links needs to be prefixed with `/`
36 ADMIN_PREFIX = '/_admin'
36 ADMIN_PREFIX = '/_admin'
37 STATIC_FILE_PREFIX = '/_static'
37 STATIC_FILE_PREFIX = '/_static'
38
38
39 # Default requirements for URL parts
39 # Default requirements for URL parts
40 URL_NAME_REQUIREMENTS = {
40 URL_NAME_REQUIREMENTS = {
41 # group name can have a slash in them, but they must not end with a slash
41 # group name can have a slash in them, but they must not end with a slash
42 'group_name': r'.*?[^/]',
42 'group_name': r'.*?[^/]',
43 'repo_group_name': r'.*?[^/]',
43 'repo_group_name': r'.*?[^/]',
44 # repo names can have a slash in them, but they must not end with a slash
44 # repo names can have a slash in them, but they must not end with a slash
45 'repo_name': r'.*?[^/]',
45 'repo_name': r'.*?[^/]',
46 # file path eats up everything at the end
46 # file path eats up everything at the end
47 'f_path': r'.*',
47 'f_path': r'.*',
48 # reference types
48 # reference types
49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 }
51 }
52
52
53
53
54 class JSRoutesMapper(Mapper):
54 class JSRoutesMapper(Mapper):
55 """
55 """
56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
57 """
57 """
58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
60 def __init__(self, *args, **kw):
60 def __init__(self, *args, **kw):
61 super(JSRoutesMapper, self).__init__(*args, **kw)
61 super(JSRoutesMapper, self).__init__(*args, **kw)
62 self._jsroutes = []
62 self._jsroutes = []
63
63
64 def connect(self, *args, **kw):
64 def connect(self, *args, **kw):
65 """
65 """
66 Wrapper for connect to take an extra argument jsroute=True
66 Wrapper for connect to take an extra argument jsroute=True
67
67
68 :param jsroute: boolean, if True will add the route to the pyroutes list
68 :param jsroute: boolean, if True will add the route to the pyroutes list
69 """
69 """
70 if kw.pop('jsroute', False):
70 if kw.pop('jsroute', False):
71 if not self._named_route_regex.match(args[0]):
71 if not self._named_route_regex.match(args[0]):
72 raise Exception('only named routes can be added to pyroutes')
72 raise Exception('only named routes can be added to pyroutes')
73 self._jsroutes.append(args[0])
73 self._jsroutes.append(args[0])
74
74
75 super(JSRoutesMapper, self).connect(*args, **kw)
75 super(JSRoutesMapper, self).connect(*args, **kw)
76
76
77 def _extract_route_information(self, route):
77 def _extract_route_information(self, route):
78 """
78 """
79 Convert a route into tuple(name, path, args), eg:
79 Convert a route into tuple(name, path, args), eg:
80 ('show_user', '/profile/%(username)s', ['username'])
80 ('show_user', '/profile/%(username)s', ['username'])
81 """
81 """
82 routepath = route.routepath
82 routepath = route.routepath
83 def replace(matchobj):
83 def replace(matchobj):
84 if matchobj.group(1):
84 if matchobj.group(1):
85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
86 else:
86 else:
87 return "%%(%s)s" % matchobj.group(2)
87 return "%%(%s)s" % matchobj.group(2)
88
88
89 routepath = self._argument_prog.sub(replace, routepath)
89 routepath = self._argument_prog.sub(replace, routepath)
90 return (
90 return (
91 route.name,
91 route.name,
92 routepath,
92 routepath,
93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
94 for arg in self._argument_prog.findall(route.routepath)]
94 for arg in self._argument_prog.findall(route.routepath)]
95 )
95 )
96
96
97 def jsroutes(self):
97 def jsroutes(self):
98 """
98 """
99 Return a list of pyroutes.js compatible routes
99 Return a list of pyroutes.js compatible routes
100 """
100 """
101 for route_name in self._jsroutes:
101 for route_name in self._jsroutes:
102 yield self._extract_route_information(self._routenames[route_name])
102 yield self._extract_route_information(self._routenames[route_name])
103
103
104
104
105 def make_map(config):
105 def make_map(config):
106 """Create, configure and return the routes Mapper"""
106 """Create, configure and return the routes Mapper"""
107 rmap = JSRoutesMapper(
107 rmap = JSRoutesMapper(
108 directory=config['pylons.paths']['controllers'],
108 directory=config['pylons.paths']['controllers'],
109 always_scan=config['debug'])
109 always_scan=config['debug'])
110 rmap.minimization = False
110 rmap.minimization = False
111 rmap.explicit = False
111 rmap.explicit = False
112
112
113 from rhodecode.lib.utils2 import str2bool
113 from rhodecode.lib.utils2 import str2bool
114 from rhodecode.model import repo, repo_group
114 from rhodecode.model import repo, repo_group
115
115
116 def check_repo(environ, match_dict):
116 def check_repo(environ, match_dict):
117 """
117 """
118 check for valid repository for proper 404 handling
118 check for valid repository for proper 404 handling
119
119
120 :param environ:
120 :param environ:
121 :param match_dict:
121 :param match_dict:
122 """
122 """
123 repo_name = match_dict.get('repo_name')
123 repo_name = match_dict.get('repo_name')
124
124
125 if match_dict.get('f_path'):
125 if match_dict.get('f_path'):
126 # fix for multiple initial slashes that causes errors
126 # fix for multiple initial slashes that causes errors
127 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
127 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
128 repo_model = repo.RepoModel()
128 repo_model = repo.RepoModel()
129 by_name_match = repo_model.get_by_repo_name(repo_name)
129 by_name_match = repo_model.get_by_repo_name(repo_name)
130 # if we match quickly from database, short circuit the operation,
130 # if we match quickly from database, short circuit the operation,
131 # and validate repo based on the type.
131 # and validate repo based on the type.
132 if by_name_match:
132 if by_name_match:
133 return True
133 return True
134
134
135 by_id_match = repo_model.get_repo_by_id(repo_name)
135 by_id_match = repo_model.get_repo_by_id(repo_name)
136 if by_id_match:
136 if by_id_match:
137 repo_name = by_id_match.repo_name
137 repo_name = by_id_match.repo_name
138 match_dict['repo_name'] = repo_name
138 match_dict['repo_name'] = repo_name
139 return True
139 return True
140
140
141 return False
141 return False
142
142
143 def check_group(environ, match_dict):
143 def check_group(environ, match_dict):
144 """
144 """
145 check for valid repository group path for proper 404 handling
145 check for valid repository group path for proper 404 handling
146
146
147 :param environ:
147 :param environ:
148 :param match_dict:
148 :param match_dict:
149 """
149 """
150 repo_group_name = match_dict.get('group_name')
150 repo_group_name = match_dict.get('group_name')
151 repo_group_model = repo_group.RepoGroupModel()
151 repo_group_model = repo_group.RepoGroupModel()
152 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
152 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
153 if by_name_match:
153 if by_name_match:
154 return True
154 return True
155
155
156 return False
156 return False
157
157
158 def check_user_group(environ, match_dict):
158 def check_user_group(environ, match_dict):
159 """
159 """
160 check for valid user group for proper 404 handling
160 check for valid user group for proper 404 handling
161
161
162 :param environ:
162 :param environ:
163 :param match_dict:
163 :param match_dict:
164 """
164 """
165 return True
165 return True
166
166
167 def check_int(environ, match_dict):
167 def check_int(environ, match_dict):
168 return match_dict.get('id').isdigit()
168 return match_dict.get('id').isdigit()
169
169
170
170
171 #==========================================================================
171 #==========================================================================
172 # CUSTOM ROUTES HERE
172 # CUSTOM ROUTES HERE
173 #==========================================================================
173 #==========================================================================
174
174
175 # ping and pylons error test
175 # ping and pylons error test
176 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
176 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
177 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
177 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
178
178
179 # ADMIN REPOSITORY ROUTES
179 # ADMIN REPOSITORY ROUTES
180 with rmap.submapper(path_prefix=ADMIN_PREFIX,
180 with rmap.submapper(path_prefix=ADMIN_PREFIX,
181 controller='admin/repos') as m:
181 controller='admin/repos') as m:
182 m.connect('repos', '/repos',
182 m.connect('repos', '/repos',
183 action='create', conditions={'method': ['POST']})
183 action='create', conditions={'method': ['POST']})
184 m.connect('repos', '/repos',
184 m.connect('repos', '/repos',
185 action='index', conditions={'method': ['GET']})
185 action='index', conditions={'method': ['GET']})
186 m.connect('new_repo', '/create_repository', jsroute=True,
186 m.connect('new_repo', '/create_repository', jsroute=True,
187 action='create_repository', conditions={'method': ['GET']})
187 action='create_repository', conditions={'method': ['GET']})
188 m.connect('delete_repo', '/repos/{repo_name}',
188 m.connect('delete_repo', '/repos/{repo_name}',
189 action='delete', conditions={'method': ['DELETE']},
189 action='delete', conditions={'method': ['DELETE']},
190 requirements=URL_NAME_REQUIREMENTS)
190 requirements=URL_NAME_REQUIREMENTS)
191 m.connect('repo', '/repos/{repo_name}',
191 m.connect('repo', '/repos/{repo_name}',
192 action='show', conditions={'method': ['GET'],
192 action='show', conditions={'method': ['GET'],
193 'function': check_repo},
193 'function': check_repo},
194 requirements=URL_NAME_REQUIREMENTS)
194 requirements=URL_NAME_REQUIREMENTS)
195
195
196 # ADMIN REPOSITORY GROUPS ROUTES
196 # ADMIN REPOSITORY GROUPS ROUTES
197 with rmap.submapper(path_prefix=ADMIN_PREFIX,
197 with rmap.submapper(path_prefix=ADMIN_PREFIX,
198 controller='admin/repo_groups') as m:
198 controller='admin/repo_groups') as m:
199 m.connect('repo_groups', '/repo_groups',
199 m.connect('repo_groups', '/repo_groups',
200 action='create', conditions={'method': ['POST']})
200 action='create', conditions={'method': ['POST']})
201 m.connect('repo_groups', '/repo_groups',
201 m.connect('repo_groups', '/repo_groups',
202 action='index', conditions={'method': ['GET']})
202 action='index', conditions={'method': ['GET']})
203 m.connect('new_repo_group', '/repo_groups/new',
203 m.connect('new_repo_group', '/repo_groups/new',
204 action='new', conditions={'method': ['GET']})
204 action='new', conditions={'method': ['GET']})
205 m.connect('update_repo_group', '/repo_groups/{group_name}',
205 m.connect('update_repo_group', '/repo_groups/{group_name}',
206 action='update', conditions={'method': ['PUT'],
206 action='update', conditions={'method': ['PUT'],
207 'function': check_group},
207 'function': check_group},
208 requirements=URL_NAME_REQUIREMENTS)
208 requirements=URL_NAME_REQUIREMENTS)
209
209
210 # EXTRAS REPO GROUP ROUTES
210 # EXTRAS REPO GROUP ROUTES
211 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
211 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
212 action='edit',
212 action='edit',
213 conditions={'method': ['GET'], 'function': check_group},
213 conditions={'method': ['GET'], 'function': check_group},
214 requirements=URL_NAME_REQUIREMENTS)
214 requirements=URL_NAME_REQUIREMENTS)
215 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
215 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
216 action='edit',
216 action='edit',
217 conditions={'method': ['PUT'], 'function': check_group},
217 conditions={'method': ['PUT'], 'function': check_group},
218 requirements=URL_NAME_REQUIREMENTS)
218 requirements=URL_NAME_REQUIREMENTS)
219
219
220 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
220 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
221 action='edit_repo_group_advanced',
221 action='edit_repo_group_advanced',
222 conditions={'method': ['GET'], 'function': check_group},
222 conditions={'method': ['GET'], 'function': check_group},
223 requirements=URL_NAME_REQUIREMENTS)
223 requirements=URL_NAME_REQUIREMENTS)
224 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
224 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
225 action='edit_repo_group_advanced',
225 action='edit_repo_group_advanced',
226 conditions={'method': ['PUT'], 'function': check_group},
226 conditions={'method': ['PUT'], 'function': check_group},
227 requirements=URL_NAME_REQUIREMENTS)
227 requirements=URL_NAME_REQUIREMENTS)
228
228
229 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
229 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
230 action='edit_repo_group_perms',
230 action='edit_repo_group_perms',
231 conditions={'method': ['GET'], 'function': check_group},
231 conditions={'method': ['GET'], 'function': check_group},
232 requirements=URL_NAME_REQUIREMENTS)
232 requirements=URL_NAME_REQUIREMENTS)
233 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
233 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
234 action='update_perms',
234 action='update_perms',
235 conditions={'method': ['PUT'], 'function': check_group},
235 conditions={'method': ['PUT'], 'function': check_group},
236 requirements=URL_NAME_REQUIREMENTS)
236 requirements=URL_NAME_REQUIREMENTS)
237
237
238 m.connect('delete_repo_group', '/repo_groups/{group_name}',
238 m.connect('delete_repo_group', '/repo_groups/{group_name}',
239 action='delete', conditions={'method': ['DELETE'],
239 action='delete', conditions={'method': ['DELETE'],
240 'function': check_group},
240 'function': check_group},
241 requirements=URL_NAME_REQUIREMENTS)
241 requirements=URL_NAME_REQUIREMENTS)
242
242
243 # ADMIN USER ROUTES
243 # ADMIN USER ROUTES
244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
245 controller='admin/users') as m:
245 controller='admin/users') as m:
246 m.connect('users', '/users',
246 m.connect('users', '/users',
247 action='create', conditions={'method': ['POST']})
247 action='create', conditions={'method': ['POST']})
248 m.connect('new_user', '/users/new',
248 m.connect('new_user', '/users/new',
249 action='new', conditions={'method': ['GET']})
249 action='new', conditions={'method': ['GET']})
250 m.connect('update_user', '/users/{user_id}',
250 m.connect('update_user', '/users/{user_id}',
251 action='update', conditions={'method': ['PUT']})
251 action='update', conditions={'method': ['PUT']})
252 m.connect('delete_user', '/users/{user_id}',
252 m.connect('delete_user', '/users/{user_id}',
253 action='delete', conditions={'method': ['DELETE']})
253 action='delete', conditions={'method': ['DELETE']})
254 m.connect('edit_user', '/users/{user_id}/edit',
254 m.connect('edit_user', '/users/{user_id}/edit',
255 action='edit', conditions={'method': ['GET']}, jsroute=True)
255 action='edit', conditions={'method': ['GET']}, jsroute=True)
256 m.connect('user', '/users/{user_id}',
256 m.connect('user', '/users/{user_id}',
257 action='show', conditions={'method': ['GET']})
257 action='show', conditions={'method': ['GET']})
258 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
258 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
259 action='reset_password', conditions={'method': ['POST']})
259 action='reset_password', conditions={'method': ['POST']})
260 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
260 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
261 action='create_personal_repo_group', conditions={'method': ['POST']})
261 action='create_personal_repo_group', conditions={'method': ['POST']})
262
262
263 # EXTRAS USER ROUTES
263 # EXTRAS USER ROUTES
264 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
264 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
265 action='edit_advanced', conditions={'method': ['GET']})
265 action='edit_advanced', conditions={'method': ['GET']})
266 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
266 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
267 action='update_advanced', conditions={'method': ['PUT']})
267 action='update_advanced', conditions={'method': ['PUT']})
268
268
269 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
269 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
270 action='edit_global_perms', conditions={'method': ['GET']})
270 action='edit_global_perms', conditions={'method': ['GET']})
271 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
271 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
272 action='update_global_perms', conditions={'method': ['PUT']})
272 action='update_global_perms', conditions={'method': ['PUT']})
273
273
274 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
274 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
275 action='edit_perms_summary', conditions={'method': ['GET']})
275 action='edit_perms_summary', conditions={'method': ['GET']})
276
276
277
278 # ADMIN USER GROUPS REST ROUTES
277 # ADMIN USER GROUPS REST ROUTES
279 with rmap.submapper(path_prefix=ADMIN_PREFIX,
278 with rmap.submapper(path_prefix=ADMIN_PREFIX,
280 controller='admin/user_groups') as m:
279 controller='admin/user_groups') as m:
281 m.connect('users_groups', '/user_groups',
280 m.connect('users_groups', '/user_groups',
282 action='create', conditions={'method': ['POST']})
281 action='create', conditions={'method': ['POST']})
283 m.connect('users_groups', '/user_groups',
282 m.connect('users_groups', '/user_groups',
284 action='index', conditions={'method': ['GET']})
283 action='index', conditions={'method': ['GET']})
285 m.connect('new_users_group', '/user_groups/new',
284 m.connect('new_users_group', '/user_groups/new',
286 action='new', conditions={'method': ['GET']})
285 action='new', conditions={'method': ['GET']})
287 m.connect('update_users_group', '/user_groups/{user_group_id}',
286 m.connect('update_users_group', '/user_groups/{user_group_id}',
288 action='update', conditions={'method': ['PUT']})
287 action='update', conditions={'method': ['PUT']})
289 m.connect('delete_users_group', '/user_groups/{user_group_id}',
288 m.connect('delete_users_group', '/user_groups/{user_group_id}',
290 action='delete', conditions={'method': ['DELETE']})
289 action='delete', conditions={'method': ['DELETE']})
291 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
290 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
292 action='edit', conditions={'method': ['GET']},
291 action='edit', conditions={'method': ['GET']},
293 function=check_user_group)
292 function=check_user_group)
294
293
295 # EXTRAS USER GROUP ROUTES
294 # EXTRAS USER GROUP ROUTES
296 m.connect('edit_user_group_global_perms',
295 m.connect('edit_user_group_global_perms',
297 '/user_groups/{user_group_id}/edit/global_permissions',
296 '/user_groups/{user_group_id}/edit/global_permissions',
298 action='edit_global_perms', conditions={'method': ['GET']})
297 action='edit_global_perms', conditions={'method': ['GET']})
299 m.connect('edit_user_group_global_perms',
298 m.connect('edit_user_group_global_perms',
300 '/user_groups/{user_group_id}/edit/global_permissions',
299 '/user_groups/{user_group_id}/edit/global_permissions',
301 action='update_global_perms', conditions={'method': ['PUT']})
300 action='update_global_perms', conditions={'method': ['PUT']})
302 m.connect('edit_user_group_perms_summary',
301 m.connect('edit_user_group_perms_summary',
303 '/user_groups/{user_group_id}/edit/permissions_summary',
302 '/user_groups/{user_group_id}/edit/permissions_summary',
304 action='edit_perms_summary', conditions={'method': ['GET']})
303 action='edit_perms_summary', conditions={'method': ['GET']})
305
304
306 m.connect('edit_user_group_perms',
305 m.connect('edit_user_group_perms',
307 '/user_groups/{user_group_id}/edit/permissions',
306 '/user_groups/{user_group_id}/edit/permissions',
308 action='edit_perms', conditions={'method': ['GET']})
307 action='edit_perms', conditions={'method': ['GET']})
309 m.connect('edit_user_group_perms',
308 m.connect('edit_user_group_perms',
310 '/user_groups/{user_group_id}/edit/permissions',
309 '/user_groups/{user_group_id}/edit/permissions',
311 action='update_perms', conditions={'method': ['PUT']})
310 action='update_perms', conditions={'method': ['PUT']})
312
311
313 m.connect('edit_user_group_advanced',
312 m.connect('edit_user_group_advanced',
314 '/user_groups/{user_group_id}/edit/advanced',
313 '/user_groups/{user_group_id}/edit/advanced',
315 action='edit_advanced', conditions={'method': ['GET']})
314 action='edit_advanced', conditions={'method': ['GET']})
316
315
317 m.connect('edit_user_group_advanced_sync',
316 m.connect('edit_user_group_advanced_sync',
318 '/user_groups/{user_group_id}/edit/advanced/sync',
317 '/user_groups/{user_group_id}/edit/advanced/sync',
319 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
318 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
320
319
321 m.connect('edit_user_group_members',
320 m.connect('edit_user_group_members',
322 '/user_groups/{user_group_id}/edit/members', jsroute=True,
321 '/user_groups/{user_group_id}/edit/members', jsroute=True,
323 action='user_group_members', conditions={'method': ['GET']})
322 action='user_group_members', conditions={'method': ['GET']})
324
323
325 # ADMIN PERMISSIONS ROUTES
324 # ADMIN PERMISSIONS ROUTES
326 with rmap.submapper(path_prefix=ADMIN_PREFIX,
325 with rmap.submapper(path_prefix=ADMIN_PREFIX,
327 controller='admin/permissions') as m:
326 controller='admin/permissions') as m:
328 m.connect('admin_permissions_application', '/permissions/application',
327 m.connect('admin_permissions_application', '/permissions/application',
329 action='permission_application_update', conditions={'method': ['POST']})
328 action='permission_application_update', conditions={'method': ['POST']})
330 m.connect('admin_permissions_application', '/permissions/application',
329 m.connect('admin_permissions_application', '/permissions/application',
331 action='permission_application', conditions={'method': ['GET']})
330 action='permission_application', conditions={'method': ['GET']})
332
331
333 m.connect('admin_permissions_global', '/permissions/global',
332 m.connect('admin_permissions_global', '/permissions/global',
334 action='permission_global_update', conditions={'method': ['POST']})
333 action='permission_global_update', conditions={'method': ['POST']})
335 m.connect('admin_permissions_global', '/permissions/global',
334 m.connect('admin_permissions_global', '/permissions/global',
336 action='permission_global', conditions={'method': ['GET']})
335 action='permission_global', conditions={'method': ['GET']})
337
336
338 m.connect('admin_permissions_object', '/permissions/object',
337 m.connect('admin_permissions_object', '/permissions/object',
339 action='permission_objects_update', conditions={'method': ['POST']})
338 action='permission_objects_update', conditions={'method': ['POST']})
340 m.connect('admin_permissions_object', '/permissions/object',
339 m.connect('admin_permissions_object', '/permissions/object',
341 action='permission_objects', conditions={'method': ['GET']})
340 action='permission_objects', conditions={'method': ['GET']})
342
341
343 m.connect('admin_permissions_ips', '/permissions/ips',
342 m.connect('admin_permissions_ips', '/permissions/ips',
344 action='permission_ips', conditions={'method': ['POST']})
343 action='permission_ips', conditions={'method': ['POST']})
345 m.connect('admin_permissions_ips', '/permissions/ips',
344 m.connect('admin_permissions_ips', '/permissions/ips',
346 action='permission_ips', conditions={'method': ['GET']})
345 action='permission_ips', conditions={'method': ['GET']})
347
346
348 m.connect('admin_permissions_overview', '/permissions/overview',
347 m.connect('admin_permissions_overview', '/permissions/overview',
349 action='permission_perms', conditions={'method': ['GET']})
348 action='permission_perms', conditions={'method': ['GET']})
350
349
351 # ADMIN DEFAULTS REST ROUTES
350 # ADMIN DEFAULTS REST ROUTES
352 with rmap.submapper(path_prefix=ADMIN_PREFIX,
351 with rmap.submapper(path_prefix=ADMIN_PREFIX,
353 controller='admin/defaults') as m:
352 controller='admin/defaults') as m:
354 m.connect('admin_defaults_repositories', '/defaults/repositories',
353 m.connect('admin_defaults_repositories', '/defaults/repositories',
355 action='update_repository_defaults', conditions={'method': ['POST']})
354 action='update_repository_defaults', conditions={'method': ['POST']})
356 m.connect('admin_defaults_repositories', '/defaults/repositories',
355 m.connect('admin_defaults_repositories', '/defaults/repositories',
357 action='index', conditions={'method': ['GET']})
356 action='index', conditions={'method': ['GET']})
358
357
359 # ADMIN SETTINGS ROUTES
358 # ADMIN SETTINGS ROUTES
360 with rmap.submapper(path_prefix=ADMIN_PREFIX,
359 with rmap.submapper(path_prefix=ADMIN_PREFIX,
361 controller='admin/settings') as m:
360 controller='admin/settings') as m:
362
361
363 # default
362 # default
364 m.connect('admin_settings', '/settings',
363 m.connect('admin_settings', '/settings',
365 action='settings_global_update',
364 action='settings_global_update',
366 conditions={'method': ['POST']})
365 conditions={'method': ['POST']})
367 m.connect('admin_settings', '/settings',
366 m.connect('admin_settings', '/settings',
368 action='settings_global', conditions={'method': ['GET']})
367 action='settings_global', conditions={'method': ['GET']})
369
368
370 m.connect('admin_settings_vcs', '/settings/vcs',
369 m.connect('admin_settings_vcs', '/settings/vcs',
371 action='settings_vcs_update',
370 action='settings_vcs_update',
372 conditions={'method': ['POST']})
371 conditions={'method': ['POST']})
373 m.connect('admin_settings_vcs', '/settings/vcs',
372 m.connect('admin_settings_vcs', '/settings/vcs',
374 action='settings_vcs',
373 action='settings_vcs',
375 conditions={'method': ['GET']})
374 conditions={'method': ['GET']})
376 m.connect('admin_settings_vcs', '/settings/vcs',
375 m.connect('admin_settings_vcs', '/settings/vcs',
377 action='delete_svn_pattern',
376 action='delete_svn_pattern',
378 conditions={'method': ['DELETE']})
377 conditions={'method': ['DELETE']})
379
378
380 m.connect('admin_settings_mapping', '/settings/mapping',
379 m.connect('admin_settings_mapping', '/settings/mapping',
381 action='settings_mapping_update',
380 action='settings_mapping_update',
382 conditions={'method': ['POST']})
381 conditions={'method': ['POST']})
383 m.connect('admin_settings_mapping', '/settings/mapping',
382 m.connect('admin_settings_mapping', '/settings/mapping',
384 action='settings_mapping', conditions={'method': ['GET']})
383 action='settings_mapping', conditions={'method': ['GET']})
385
384
386 m.connect('admin_settings_global', '/settings/global',
385 m.connect('admin_settings_global', '/settings/global',
387 action='settings_global_update',
386 action='settings_global_update',
388 conditions={'method': ['POST']})
387 conditions={'method': ['POST']})
389 m.connect('admin_settings_global', '/settings/global',
388 m.connect('admin_settings_global', '/settings/global',
390 action='settings_global', conditions={'method': ['GET']})
389 action='settings_global', conditions={'method': ['GET']})
391
390
392 m.connect('admin_settings_visual', '/settings/visual',
391 m.connect('admin_settings_visual', '/settings/visual',
393 action='settings_visual_update',
392 action='settings_visual_update',
394 conditions={'method': ['POST']})
393 conditions={'method': ['POST']})
395 m.connect('admin_settings_visual', '/settings/visual',
394 m.connect('admin_settings_visual', '/settings/visual',
396 action='settings_visual', conditions={'method': ['GET']})
395 action='settings_visual', conditions={'method': ['GET']})
397
396
398 m.connect('admin_settings_issuetracker',
397 m.connect('admin_settings_issuetracker',
399 '/settings/issue-tracker', action='settings_issuetracker',
398 '/settings/issue-tracker', action='settings_issuetracker',
400 conditions={'method': ['GET']})
399 conditions={'method': ['GET']})
401 m.connect('admin_settings_issuetracker_save',
400 m.connect('admin_settings_issuetracker_save',
402 '/settings/issue-tracker/save',
401 '/settings/issue-tracker/save',
403 action='settings_issuetracker_save',
402 action='settings_issuetracker_save',
404 conditions={'method': ['POST']})
403 conditions={'method': ['POST']})
405 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
404 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
406 action='settings_issuetracker_test',
405 action='settings_issuetracker_test',
407 conditions={'method': ['POST']})
406 conditions={'method': ['POST']})
408 m.connect('admin_issuetracker_delete',
407 m.connect('admin_issuetracker_delete',
409 '/settings/issue-tracker/delete',
408 '/settings/issue-tracker/delete',
410 action='settings_issuetracker_delete',
409 action='settings_issuetracker_delete',
411 conditions={'method': ['DELETE']})
410 conditions={'method': ['DELETE']})
412
411
413 m.connect('admin_settings_email', '/settings/email',
412 m.connect('admin_settings_email', '/settings/email',
414 action='settings_email_update',
413 action='settings_email_update',
415 conditions={'method': ['POST']})
414 conditions={'method': ['POST']})
416 m.connect('admin_settings_email', '/settings/email',
415 m.connect('admin_settings_email', '/settings/email',
417 action='settings_email', conditions={'method': ['GET']})
416 action='settings_email', conditions={'method': ['GET']})
418
417
419 m.connect('admin_settings_hooks', '/settings/hooks',
418 m.connect('admin_settings_hooks', '/settings/hooks',
420 action='settings_hooks_update',
419 action='settings_hooks_update',
421 conditions={'method': ['POST', 'DELETE']})
420 conditions={'method': ['POST', 'DELETE']})
422 m.connect('admin_settings_hooks', '/settings/hooks',
421 m.connect('admin_settings_hooks', '/settings/hooks',
423 action='settings_hooks', conditions={'method': ['GET']})
422 action='settings_hooks', conditions={'method': ['GET']})
424
423
425 m.connect('admin_settings_search', '/settings/search',
424 m.connect('admin_settings_search', '/settings/search',
426 action='settings_search', conditions={'method': ['GET']})
425 action='settings_search', conditions={'method': ['GET']})
427
426
428 m.connect('admin_settings_supervisor', '/settings/supervisor',
427 m.connect('admin_settings_supervisor', '/settings/supervisor',
429 action='settings_supervisor', conditions={'method': ['GET']})
428 action='settings_supervisor', conditions={'method': ['GET']})
430 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
429 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
431 action='settings_supervisor_log', conditions={'method': ['GET']})
430 action='settings_supervisor_log', conditions={'method': ['GET']})
432
431
433 m.connect('admin_settings_labs', '/settings/labs',
432 m.connect('admin_settings_labs', '/settings/labs',
434 action='settings_labs_update',
433 action='settings_labs_update',
435 conditions={'method': ['POST']})
434 conditions={'method': ['POST']})
436 m.connect('admin_settings_labs', '/settings/labs',
435 m.connect('admin_settings_labs', '/settings/labs',
437 action='settings_labs', conditions={'method': ['GET']})
436 action='settings_labs', conditions={'method': ['GET']})
438
437
439 # ADMIN MY ACCOUNT
438 # ADMIN MY ACCOUNT
440 with rmap.submapper(path_prefix=ADMIN_PREFIX,
439 with rmap.submapper(path_prefix=ADMIN_PREFIX,
441 controller='admin/my_account') as m:
440 controller='admin/my_account') as m:
442
441
443 # NOTE(marcink): this needs to be kept for password force flag to be
442 # NOTE(marcink): this needs to be kept for password force flag to be
444 # handled in pylons controllers, remove after full migration to pyramid
443 # handled in pylons controllers, remove after full migration to pyramid
445 m.connect('my_account_password', '/my_account/password',
444 m.connect('my_account_password', '/my_account/password',
446 action='my_account_password', conditions={'method': ['GET']})
445 action='my_account_password', conditions={'method': ['GET']})
447
446
448 # USER JOURNAL
447 # USER JOURNAL
449 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
448 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
450 controller='journal', action='index')
449 controller='journal', action='index')
451 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
450 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
452 controller='journal', action='journal_rss')
451 controller='journal', action='journal_rss')
453 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
452 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
454 controller='journal', action='journal_atom')
453 controller='journal', action='journal_atom')
455
454
456 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
455 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
457 controller='journal', action='public_journal')
456 controller='journal', action='public_journal')
458
457
459 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
458 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
460 controller='journal', action='public_journal_rss')
459 controller='journal', action='public_journal_rss')
461
460
462 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
461 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
463 controller='journal', action='public_journal_rss')
462 controller='journal', action='public_journal_rss')
464
463
465 rmap.connect('public_journal_atom',
464 rmap.connect('public_journal_atom',
466 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
465 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
467 action='public_journal_atom')
466 action='public_journal_atom')
468
467
469 rmap.connect('public_journal_atom_old',
468 rmap.connect('public_journal_atom_old',
470 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
469 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
471 action='public_journal_atom')
470 action='public_journal_atom')
472
471
473 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
472 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
474 controller='journal', action='toggle_following', jsroute=True,
473 controller='journal', action='toggle_following', jsroute=True,
475 conditions={'method': ['POST']})
474 conditions={'method': ['POST']})
476
475
477 #==========================================================================
476 #==========================================================================
478 # REPOSITORY ROUTES
477 # REPOSITORY ROUTES
479 #==========================================================================
478 #==========================================================================
480
479
481 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
480 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
482 controller='admin/repos', action='repo_creating',
481 controller='admin/repos', action='repo_creating',
483 requirements=URL_NAME_REQUIREMENTS)
482 requirements=URL_NAME_REQUIREMENTS)
484 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
483 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
485 controller='admin/repos', action='repo_check',
484 controller='admin/repos', action='repo_check',
486 requirements=URL_NAME_REQUIREMENTS)
485 requirements=URL_NAME_REQUIREMENTS)
487
486
488 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
487 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
489 controller='changeset', revision='tip',
488 controller='changeset', revision='tip',
490 conditions={'function': check_repo},
489 conditions={'function': check_repo},
491 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
490 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
492 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
491 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
493 controller='changeset', revision='tip', action='changeset_children',
492 controller='changeset', revision='tip', action='changeset_children',
494 conditions={'function': check_repo},
493 conditions={'function': check_repo},
495 requirements=URL_NAME_REQUIREMENTS)
494 requirements=URL_NAME_REQUIREMENTS)
496 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
495 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
497 controller='changeset', revision='tip', action='changeset_parents',
496 controller='changeset', revision='tip', action='changeset_parents',
498 conditions={'function': check_repo},
497 conditions={'function': check_repo},
499 requirements=URL_NAME_REQUIREMENTS)
498 requirements=URL_NAME_REQUIREMENTS)
500
499
501 # repo edit options
500 # repo edit options
502 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
501 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
503 controller='admin/repos', action='edit_fields',
502 controller='admin/repos', action='edit_fields',
504 conditions={'method': ['GET'], 'function': check_repo},
503 conditions={'method': ['GET'], 'function': check_repo},
505 requirements=URL_NAME_REQUIREMENTS)
504 requirements=URL_NAME_REQUIREMENTS)
506 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
505 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
507 controller='admin/repos', action='create_repo_field',
506 controller='admin/repos', action='create_repo_field',
508 conditions={'method': ['PUT'], 'function': check_repo},
507 conditions={'method': ['PUT'], 'function': check_repo},
509 requirements=URL_NAME_REQUIREMENTS)
508 requirements=URL_NAME_REQUIREMENTS)
510 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
509 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
511 controller='admin/repos', action='delete_repo_field',
510 controller='admin/repos', action='delete_repo_field',
512 conditions={'method': ['DELETE'], 'function': check_repo},
511 conditions={'method': ['DELETE'], 'function': check_repo},
513 requirements=URL_NAME_REQUIREMENTS)
512 requirements=URL_NAME_REQUIREMENTS)
514
513
515 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
514 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
516 controller='admin/repos', action='toggle_locking',
515 controller='admin/repos', action='toggle_locking',
517 conditions={'method': ['GET'], 'function': check_repo},
516 conditions={'method': ['GET'], 'function': check_repo},
518 requirements=URL_NAME_REQUIREMENTS)
517 requirements=URL_NAME_REQUIREMENTS)
519
518
520 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
519 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
521 controller='admin/repos', action='edit_remote_form',
520 controller='admin/repos', action='edit_remote_form',
522 conditions={'method': ['GET'], 'function': check_repo},
521 conditions={'method': ['GET'], 'function': check_repo},
523 requirements=URL_NAME_REQUIREMENTS)
522 requirements=URL_NAME_REQUIREMENTS)
524 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
523 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
525 controller='admin/repos', action='edit_remote',
524 controller='admin/repos', action='edit_remote',
526 conditions={'method': ['PUT'], 'function': check_repo},
525 conditions={'method': ['PUT'], 'function': check_repo},
527 requirements=URL_NAME_REQUIREMENTS)
526 requirements=URL_NAME_REQUIREMENTS)
528
527
529 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
528 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
530 controller='admin/repos', action='edit_statistics_form',
529 controller='admin/repos', action='edit_statistics_form',
531 conditions={'method': ['GET'], 'function': check_repo},
530 conditions={'method': ['GET'], 'function': check_repo},
532 requirements=URL_NAME_REQUIREMENTS)
531 requirements=URL_NAME_REQUIREMENTS)
533 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
532 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
534 controller='admin/repos', action='edit_statistics',
533 controller='admin/repos', action='edit_statistics',
535 conditions={'method': ['PUT'], 'function': check_repo},
534 conditions={'method': ['PUT'], 'function': check_repo},
536 requirements=URL_NAME_REQUIREMENTS)
535 requirements=URL_NAME_REQUIREMENTS)
537 rmap.connect('repo_settings_issuetracker',
536 rmap.connect('repo_settings_issuetracker',
538 '/{repo_name}/settings/issue-tracker',
537 '/{repo_name}/settings/issue-tracker',
539 controller='admin/repos', action='repo_issuetracker',
538 controller='admin/repos', action='repo_issuetracker',
540 conditions={'method': ['GET'], 'function': check_repo},
539 conditions={'method': ['GET'], 'function': check_repo},
541 requirements=URL_NAME_REQUIREMENTS)
540 requirements=URL_NAME_REQUIREMENTS)
542 rmap.connect('repo_issuetracker_test',
541 rmap.connect('repo_issuetracker_test',
543 '/{repo_name}/settings/issue-tracker/test',
542 '/{repo_name}/settings/issue-tracker/test',
544 controller='admin/repos', action='repo_issuetracker_test',
543 controller='admin/repos', action='repo_issuetracker_test',
545 conditions={'method': ['POST'], 'function': check_repo},
544 conditions={'method': ['POST'], 'function': check_repo},
546 requirements=URL_NAME_REQUIREMENTS)
545 requirements=URL_NAME_REQUIREMENTS)
547 rmap.connect('repo_issuetracker_delete',
546 rmap.connect('repo_issuetracker_delete',
548 '/{repo_name}/settings/issue-tracker/delete',
547 '/{repo_name}/settings/issue-tracker/delete',
549 controller='admin/repos', action='repo_issuetracker_delete',
548 controller='admin/repos', action='repo_issuetracker_delete',
550 conditions={'method': ['DELETE'], 'function': check_repo},
549 conditions={'method': ['DELETE'], 'function': check_repo},
551 requirements=URL_NAME_REQUIREMENTS)
550 requirements=URL_NAME_REQUIREMENTS)
552 rmap.connect('repo_issuetracker_save',
551 rmap.connect('repo_issuetracker_save',
553 '/{repo_name}/settings/issue-tracker/save',
552 '/{repo_name}/settings/issue-tracker/save',
554 controller='admin/repos', action='repo_issuetracker_save',
553 controller='admin/repos', action='repo_issuetracker_save',
555 conditions={'method': ['POST'], 'function': check_repo},
554 conditions={'method': ['POST'], 'function': check_repo},
556 requirements=URL_NAME_REQUIREMENTS)
555 requirements=URL_NAME_REQUIREMENTS)
557 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
556 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
558 controller='admin/repos', action='repo_settings_vcs_update',
557 controller='admin/repos', action='repo_settings_vcs_update',
559 conditions={'method': ['POST'], 'function': check_repo},
558 conditions={'method': ['POST'], 'function': check_repo},
560 requirements=URL_NAME_REQUIREMENTS)
559 requirements=URL_NAME_REQUIREMENTS)
561 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
560 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
562 controller='admin/repos', action='repo_settings_vcs',
561 controller='admin/repos', action='repo_settings_vcs',
563 conditions={'method': ['GET'], 'function': check_repo},
562 conditions={'method': ['GET'], 'function': check_repo},
564 requirements=URL_NAME_REQUIREMENTS)
563 requirements=URL_NAME_REQUIREMENTS)
565 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
564 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
566 controller='admin/repos', action='repo_delete_svn_pattern',
565 controller='admin/repos', action='repo_delete_svn_pattern',
567 conditions={'method': ['DELETE'], 'function': check_repo},
566 conditions={'method': ['DELETE'], 'function': check_repo},
568 requirements=URL_NAME_REQUIREMENTS)
567 requirements=URL_NAME_REQUIREMENTS)
569 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
568 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
570 controller='admin/repos', action='repo_settings_pullrequest',
569 controller='admin/repos', action='repo_settings_pullrequest',
571 conditions={'method': ['GET', 'POST'], 'function': check_repo},
570 conditions={'method': ['GET', 'POST'], 'function': check_repo},
572 requirements=URL_NAME_REQUIREMENTS)
571 requirements=URL_NAME_REQUIREMENTS)
573
572
574 # still working url for backward compat.
573 # still working url for backward compat.
575 rmap.connect('raw_changeset_home_depraced',
574 rmap.connect('raw_changeset_home_depraced',
576 '/{repo_name}/raw-changeset/{revision}',
575 '/{repo_name}/raw-changeset/{revision}',
577 controller='changeset', action='changeset_raw',
576 controller='changeset', action='changeset_raw',
578 revision='tip', conditions={'function': check_repo},
577 revision='tip', conditions={'function': check_repo},
579 requirements=URL_NAME_REQUIREMENTS)
578 requirements=URL_NAME_REQUIREMENTS)
580
579
581 # new URLs
580 # new URLs
582 rmap.connect('changeset_raw_home',
581 rmap.connect('changeset_raw_home',
583 '/{repo_name}/changeset-diff/{revision}',
582 '/{repo_name}/changeset-diff/{revision}',
584 controller='changeset', action='changeset_raw',
583 controller='changeset', action='changeset_raw',
585 revision='tip', conditions={'function': check_repo},
584 revision='tip', conditions={'function': check_repo},
586 requirements=URL_NAME_REQUIREMENTS)
585 requirements=URL_NAME_REQUIREMENTS)
587
586
588 rmap.connect('changeset_patch_home',
587 rmap.connect('changeset_patch_home',
589 '/{repo_name}/changeset-patch/{revision}',
588 '/{repo_name}/changeset-patch/{revision}',
590 controller='changeset', action='changeset_patch',
589 controller='changeset', action='changeset_patch',
591 revision='tip', conditions={'function': check_repo},
590 revision='tip', conditions={'function': check_repo},
592 requirements=URL_NAME_REQUIREMENTS)
591 requirements=URL_NAME_REQUIREMENTS)
593
592
594 rmap.connect('changeset_download_home',
593 rmap.connect('changeset_download_home',
595 '/{repo_name}/changeset-download/{revision}',
594 '/{repo_name}/changeset-download/{revision}',
596 controller='changeset', action='changeset_download',
595 controller='changeset', action='changeset_download',
597 revision='tip', conditions={'function': check_repo},
596 revision='tip', conditions={'function': check_repo},
598 requirements=URL_NAME_REQUIREMENTS)
597 requirements=URL_NAME_REQUIREMENTS)
599
598
600 rmap.connect('changeset_comment',
599 rmap.connect('changeset_comment',
601 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
600 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
602 controller='changeset', revision='tip', action='comment',
601 controller='changeset', revision='tip', action='comment',
603 conditions={'function': check_repo},
602 conditions={'function': check_repo},
604 requirements=URL_NAME_REQUIREMENTS)
603 requirements=URL_NAME_REQUIREMENTS)
605
604
606 rmap.connect('changeset_comment_preview',
605 rmap.connect('changeset_comment_preview',
607 '/{repo_name}/changeset/comment/preview', jsroute=True,
606 '/{repo_name}/changeset/comment/preview', jsroute=True,
608 controller='changeset', action='preview_comment',
607 controller='changeset', action='preview_comment',
609 conditions={'function': check_repo, 'method': ['POST']},
608 conditions={'function': check_repo, 'method': ['POST']},
610 requirements=URL_NAME_REQUIREMENTS)
609 requirements=URL_NAME_REQUIREMENTS)
611
610
612 rmap.connect('changeset_comment_delete',
611 rmap.connect('changeset_comment_delete',
613 '/{repo_name}/changeset/comment/{comment_id}/delete',
612 '/{repo_name}/changeset/comment/{comment_id}/delete',
614 controller='changeset', action='delete_comment',
613 controller='changeset', action='delete_comment',
615 conditions={'function': check_repo, 'method': ['DELETE']},
614 conditions={'function': check_repo, 'method': ['DELETE']},
616 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
615 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
617
616
618 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
617 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
619 controller='changeset', action='changeset_info',
618 controller='changeset', action='changeset_info',
620 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
619 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
621
620
622 rmap.connect('compare_home',
621 rmap.connect('compare_home',
623 '/{repo_name}/compare',
622 '/{repo_name}/compare',
624 controller='compare', action='index',
623 controller='compare', action='index',
625 conditions={'function': check_repo},
624 conditions={'function': check_repo},
626 requirements=URL_NAME_REQUIREMENTS)
625 requirements=URL_NAME_REQUIREMENTS)
627
626
628 rmap.connect('compare_url',
627 rmap.connect('compare_url',
629 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
628 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
630 controller='compare', action='compare',
629 controller='compare', action='compare',
631 conditions={'function': check_repo},
630 conditions={'function': check_repo},
632 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
631 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
633
632
634 rmap.connect('pullrequest_home',
633 rmap.connect('pullrequest_home',
635 '/{repo_name}/pull-request/new', controller='pullrequests',
634 '/{repo_name}/pull-request/new', controller='pullrequests',
636 action='index', conditions={'function': check_repo,
635 action='index', conditions={'function': check_repo,
637 'method': ['GET']},
636 'method': ['GET']},
638 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
637 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
639
638
640 rmap.connect('pullrequest',
639 rmap.connect('pullrequest',
641 '/{repo_name}/pull-request/new', controller='pullrequests',
640 '/{repo_name}/pull-request/new', controller='pullrequests',
642 action='create', conditions={'function': check_repo,
641 action='create', conditions={'function': check_repo,
643 'method': ['POST']},
642 'method': ['POST']},
644 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
643 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
645
644
646 rmap.connect('pullrequest_repo_refs',
645 rmap.connect('pullrequest_repo_refs',
647 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
646 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
648 controller='pullrequests',
647 controller='pullrequests',
649 action='get_repo_refs',
648 action='get_repo_refs',
650 conditions={'function': check_repo, 'method': ['GET']},
649 conditions={'function': check_repo, 'method': ['GET']},
651 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
650 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
652
651
653 rmap.connect('pullrequest_repo_destinations',
652 rmap.connect('pullrequest_repo_destinations',
654 '/{repo_name}/pull-request/repo-destinations',
653 '/{repo_name}/pull-request/repo-destinations',
655 controller='pullrequests',
654 controller='pullrequests',
656 action='get_repo_destinations',
655 action='get_repo_destinations',
657 conditions={'function': check_repo, 'method': ['GET']},
656 conditions={'function': check_repo, 'method': ['GET']},
658 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
657 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
659
658
660 rmap.connect('pullrequest_show',
659 rmap.connect('pullrequest_show',
661 '/{repo_name}/pull-request/{pull_request_id}',
660 '/{repo_name}/pull-request/{pull_request_id}',
662 controller='pullrequests',
661 controller='pullrequests',
663 action='show', conditions={'function': check_repo,
662 action='show', conditions={'function': check_repo,
664 'method': ['GET']},
663 'method': ['GET']},
665 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
664 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
666
665
667 rmap.connect('pullrequest_update',
666 rmap.connect('pullrequest_update',
668 '/{repo_name}/pull-request/{pull_request_id}',
667 '/{repo_name}/pull-request/{pull_request_id}',
669 controller='pullrequests',
668 controller='pullrequests',
670 action='update', conditions={'function': check_repo,
669 action='update', conditions={'function': check_repo,
671 'method': ['PUT']},
670 'method': ['PUT']},
672 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
671 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
673
672
674 rmap.connect('pullrequest_merge',
673 rmap.connect('pullrequest_merge',
675 '/{repo_name}/pull-request/{pull_request_id}',
674 '/{repo_name}/pull-request/{pull_request_id}',
676 controller='pullrequests',
675 controller='pullrequests',
677 action='merge', conditions={'function': check_repo,
676 action='merge', conditions={'function': check_repo,
678 'method': ['POST']},
677 'method': ['POST']},
679 requirements=URL_NAME_REQUIREMENTS)
678 requirements=URL_NAME_REQUIREMENTS)
680
679
681 rmap.connect('pullrequest_delete',
680 rmap.connect('pullrequest_delete',
682 '/{repo_name}/pull-request/{pull_request_id}',
681 '/{repo_name}/pull-request/{pull_request_id}',
683 controller='pullrequests',
682 controller='pullrequests',
684 action='delete', conditions={'function': check_repo,
683 action='delete', conditions={'function': check_repo,
685 'method': ['DELETE']},
684 'method': ['DELETE']},
686 requirements=URL_NAME_REQUIREMENTS)
685 requirements=URL_NAME_REQUIREMENTS)
687
686
688 rmap.connect('pullrequest_comment',
687 rmap.connect('pullrequest_comment',
689 '/{repo_name}/pull-request-comment/{pull_request_id}',
688 '/{repo_name}/pull-request-comment/{pull_request_id}',
690 controller='pullrequests',
689 controller='pullrequests',
691 action='comment', conditions={'function': check_repo,
690 action='comment', conditions={'function': check_repo,
692 'method': ['POST']},
691 'method': ['POST']},
693 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
692 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
694
693
695 rmap.connect('pullrequest_comment_delete',
694 rmap.connect('pullrequest_comment_delete',
696 '/{repo_name}/pull-request-comment/{comment_id}/delete',
695 '/{repo_name}/pull-request-comment/{comment_id}/delete',
697 controller='pullrequests', action='delete_comment',
696 controller='pullrequests', action='delete_comment',
698 conditions={'function': check_repo, 'method': ['DELETE']},
697 conditions={'function': check_repo, 'method': ['DELETE']},
699 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
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 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
700 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
717 controller='forks', action='fork_create',
701 controller='forks', action='fork_create',
718 conditions={'function': check_repo, 'method': ['POST']},
702 conditions={'function': check_repo, 'method': ['POST']},
719 requirements=URL_NAME_REQUIREMENTS)
703 requirements=URL_NAME_REQUIREMENTS)
720
704
721 rmap.connect('repo_fork_home', '/{repo_name}/fork',
705 rmap.connect('repo_fork_home', '/{repo_name}/fork',
722 controller='forks', action='fork',
706 controller='forks', action='fork',
723 conditions={'function': check_repo},
707 conditions={'function': check_repo},
724 requirements=URL_NAME_REQUIREMENTS)
708 requirements=URL_NAME_REQUIREMENTS)
725
709
726 rmap.connect('repo_forks_home', '/{repo_name}/forks',
710 rmap.connect('repo_forks_home', '/{repo_name}/forks',
727 controller='forks', action='forks',
711 controller='forks', action='forks',
728 conditions={'function': check_repo},
712 conditions={'function': check_repo},
729 requirements=URL_NAME_REQUIREMENTS)
713 requirements=URL_NAME_REQUIREMENTS)
730
714
731 return rmap
715 return rmap
@@ -1,492 +1,491 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 commit controller for RhodeCode showing changes between commits
22 commit controller for RhodeCode showing changes between commits
23 """
23 """
24
24
25 import logging
25 import logging
26
26
27 from collections import defaultdict
27 from collections import defaultdict
28 from webob.exc import HTTPForbidden, HTTPBadRequest, HTTPNotFound
28 from webob.exc import HTTPForbidden, HTTPBadRequest, HTTPNotFound
29
29
30 from pylons import tmpl_context as c, request, response
30 from pylons import tmpl_context as c, request, response
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32 from pylons.controllers.util import redirect
32 from pylons.controllers.util import redirect
33
33
34 from rhodecode.lib import auth
34 from rhodecode.lib import auth
35 from rhodecode.lib import diffs, codeblocks
35 from rhodecode.lib import diffs, codeblocks
36 from rhodecode.lib.auth import (
36 from rhodecode.lib.auth import (
37 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous)
37 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous)
38 from rhodecode.lib.base import BaseRepoController, render
38 from rhodecode.lib.base import BaseRepoController, render
39 from rhodecode.lib.compat import OrderedDict
39 from rhodecode.lib.compat import OrderedDict
40 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
40 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
41 import rhodecode.lib.helpers as h
41 import rhodecode.lib.helpers as h
42 from rhodecode.lib.utils import jsonify
42 from rhodecode.lib.utils import jsonify
43 from rhodecode.lib.utils2 import safe_unicode, safe_int
43 from rhodecode.lib.utils2 import safe_unicode, safe_int
44 from rhodecode.lib.vcs.backends.base import EmptyCommit
44 from rhodecode.lib.vcs.backends.base import EmptyCommit
45 from rhodecode.lib.vcs.exceptions import (
45 from rhodecode.lib.vcs.exceptions import (
46 RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError)
46 RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError)
47 from rhodecode.model.db import ChangesetComment, ChangesetStatus
47 from rhodecode.model.db import ChangesetComment, ChangesetStatus
48 from rhodecode.model.changeset_status import ChangesetStatusModel
48 from rhodecode.model.changeset_status import ChangesetStatusModel
49 from rhodecode.model.comment import CommentsModel
49 from rhodecode.model.comment import CommentsModel
50 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
51
51
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 def _update_with_GET(params, GET):
56 def _update_with_GET(params, GET):
57 for k in ['diff1', 'diff2', 'diff']:
57 for k in ['diff1', 'diff2', 'diff']:
58 params[k] += GET.getall(k)
58 params[k] += GET.getall(k)
59
59
60
60
61 def get_ignore_ws(fid, GET):
61 def get_ignore_ws(fid, GET):
62 ig_ws_global = GET.get('ignorews')
62 ig_ws_global = GET.get('ignorews')
63 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
63 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
64 if ig_ws:
64 if ig_ws:
65 try:
65 try:
66 return int(ig_ws[0].split(':')[-1])
66 return int(ig_ws[0].split(':')[-1])
67 except Exception:
67 except Exception:
68 pass
68 pass
69 return ig_ws_global
69 return ig_ws_global
70
70
71
71
72 def _ignorews_url(GET, fileid=None):
72 def _ignorews_url(GET, fileid=None):
73 fileid = str(fileid) if fileid else None
73 fileid = str(fileid) if fileid else None
74 params = defaultdict(list)
74 params = defaultdict(list)
75 _update_with_GET(params, GET)
75 _update_with_GET(params, GET)
76 label = _('Show whitespace')
76 label = _('Show whitespace')
77 tooltiplbl = _('Show whitespace for all diffs')
77 tooltiplbl = _('Show whitespace for all diffs')
78 ig_ws = get_ignore_ws(fileid, GET)
78 ig_ws = get_ignore_ws(fileid, GET)
79 ln_ctx = get_line_ctx(fileid, GET)
79 ln_ctx = get_line_ctx(fileid, GET)
80
80
81 if ig_ws is None:
81 if ig_ws is None:
82 params['ignorews'] += [1]
82 params['ignorews'] += [1]
83 label = _('Ignore whitespace')
83 label = _('Ignore whitespace')
84 tooltiplbl = _('Ignore whitespace for all diffs')
84 tooltiplbl = _('Ignore whitespace for all diffs')
85 ctx_key = 'context'
85 ctx_key = 'context'
86 ctx_val = ln_ctx
86 ctx_val = ln_ctx
87
87
88 # if we have passed in ln_ctx pass it along to our params
88 # if we have passed in ln_ctx pass it along to our params
89 if ln_ctx:
89 if ln_ctx:
90 params[ctx_key] += [ctx_val]
90 params[ctx_key] += [ctx_val]
91
91
92 if fileid:
92 if fileid:
93 params['anchor'] = 'a_' + fileid
93 params['anchor'] = 'a_' + fileid
94 return h.link_to(label, h.url.current(**params), title=tooltiplbl, class_='tooltip')
94 return h.link_to(label, h.url.current(**params), title=tooltiplbl, class_='tooltip')
95
95
96
96
97 def get_line_ctx(fid, GET):
97 def get_line_ctx(fid, GET):
98 ln_ctx_global = GET.get('context')
98 ln_ctx_global = GET.get('context')
99 if fid:
99 if fid:
100 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
100 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
101 else:
101 else:
102 _ln_ctx = filter(lambda k: k.startswith('C'), GET)
102 _ln_ctx = filter(lambda k: k.startswith('C'), GET)
103 ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
103 ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
104 if ln_ctx:
104 if ln_ctx:
105 ln_ctx = [ln_ctx]
105 ln_ctx = [ln_ctx]
106
106
107 if ln_ctx:
107 if ln_ctx:
108 retval = ln_ctx[0].split(':')[-1]
108 retval = ln_ctx[0].split(':')[-1]
109 else:
109 else:
110 retval = ln_ctx_global
110 retval = ln_ctx_global
111
111
112 try:
112 try:
113 return int(retval)
113 return int(retval)
114 except Exception:
114 except Exception:
115 return 3
115 return 3
116
116
117
117
118 def _context_url(GET, fileid=None):
118 def _context_url(GET, fileid=None):
119 """
119 """
120 Generates a url for context lines.
120 Generates a url for context lines.
121
121
122 :param fileid:
122 :param fileid:
123 """
123 """
124
124
125 fileid = str(fileid) if fileid else None
125 fileid = str(fileid) if fileid else None
126 ig_ws = get_ignore_ws(fileid, GET)
126 ig_ws = get_ignore_ws(fileid, GET)
127 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
127 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
128
128
129 params = defaultdict(list)
129 params = defaultdict(list)
130 _update_with_GET(params, GET)
130 _update_with_GET(params, GET)
131
131
132 if ln_ctx > 0:
132 if ln_ctx > 0:
133 params['context'] += [ln_ctx]
133 params['context'] += [ln_ctx]
134
134
135 if ig_ws:
135 if ig_ws:
136 ig_ws_key = 'ignorews'
136 ig_ws_key = 'ignorews'
137 ig_ws_val = 1
137 ig_ws_val = 1
138 params[ig_ws_key] += [ig_ws_val]
138 params[ig_ws_key] += [ig_ws_val]
139
139
140 lbl = _('Increase context')
140 lbl = _('Increase context')
141 tooltiplbl = _('Increase context for all diffs')
141 tooltiplbl = _('Increase context for all diffs')
142
142
143 if fileid:
143 if fileid:
144 params['anchor'] = 'a_' + fileid
144 params['anchor'] = 'a_' + fileid
145 return h.link_to(lbl, h.url.current(**params), title=tooltiplbl, class_='tooltip')
145 return h.link_to(lbl, h.url.current(**params), title=tooltiplbl, class_='tooltip')
146
146
147
147
148 class ChangesetController(BaseRepoController):
148 class ChangesetController(BaseRepoController):
149
149
150 def __before__(self):
150 def __before__(self):
151 super(ChangesetController, self).__before__()
151 super(ChangesetController, self).__before__()
152 c.affected_files_cut_off = 60
153
152
154 def _index(self, commit_id_range, method):
153 def _index(self, commit_id_range, method):
155 c.ignorews_url = _ignorews_url
154 c.ignorews_url = _ignorews_url
156 c.context_url = _context_url
155 c.context_url = _context_url
157 c.fulldiff = fulldiff = request.GET.get('fulldiff')
156 c.fulldiff = fulldiff = request.GET.get('fulldiff')
158
157
159 # fetch global flags of ignore ws or context lines
158 # fetch global flags of ignore ws or context lines
160 context_lcl = get_line_ctx('', request.GET)
159 context_lcl = get_line_ctx('', request.GET)
161 ign_whitespace_lcl = get_ignore_ws('', request.GET)
160 ign_whitespace_lcl = get_ignore_ws('', request.GET)
162
161
163 # diff_limit will cut off the whole diff if the limit is applied
162 # diff_limit will cut off the whole diff if the limit is applied
164 # otherwise it will just hide the big files from the front-end
163 # otherwise it will just hide the big files from the front-end
165 diff_limit = self.cut_off_limit_diff
164 diff_limit = self.cut_off_limit_diff
166 file_limit = self.cut_off_limit_file
165 file_limit = self.cut_off_limit_file
167
166
168 # get ranges of commit ids if preset
167 # get ranges of commit ids if preset
169 commit_range = commit_id_range.split('...')[:2]
168 commit_range = commit_id_range.split('...')[:2]
170
169
171 try:
170 try:
172 pre_load = ['affected_files', 'author', 'branch', 'date',
171 pre_load = ['affected_files', 'author', 'branch', 'date',
173 'message', 'parents']
172 'message', 'parents']
174
173
175 if len(commit_range) == 2:
174 if len(commit_range) == 2:
176 commits = c.rhodecode_repo.get_commits(
175 commits = c.rhodecode_repo.get_commits(
177 start_id=commit_range[0], end_id=commit_range[1],
176 start_id=commit_range[0], end_id=commit_range[1],
178 pre_load=pre_load)
177 pre_load=pre_load)
179 commits = list(commits)
178 commits = list(commits)
180 else:
179 else:
181 commits = [c.rhodecode_repo.get_commit(
180 commits = [c.rhodecode_repo.get_commit(
182 commit_id=commit_id_range, pre_load=pre_load)]
181 commit_id=commit_id_range, pre_load=pre_load)]
183
182
184 c.commit_ranges = commits
183 c.commit_ranges = commits
185 if not c.commit_ranges:
184 if not c.commit_ranges:
186 raise RepositoryError(
185 raise RepositoryError(
187 'The commit range returned an empty result')
186 'The commit range returned an empty result')
188 except CommitDoesNotExistError:
187 except CommitDoesNotExistError:
189 msg = _('No such commit exists for this repository')
188 msg = _('No such commit exists for this repository')
190 h.flash(msg, category='error')
189 h.flash(msg, category='error')
191 raise HTTPNotFound()
190 raise HTTPNotFound()
192 except Exception:
191 except Exception:
193 log.exception("General failure")
192 log.exception("General failure")
194 raise HTTPNotFound()
193 raise HTTPNotFound()
195
194
196 c.changes = OrderedDict()
195 c.changes = OrderedDict()
197 c.lines_added = 0
196 c.lines_added = 0
198 c.lines_deleted = 0
197 c.lines_deleted = 0
199
198
200 # auto collapse if we have more than limit
199 # auto collapse if we have more than limit
201 collapse_limit = diffs.DiffProcessor._collapse_commits_over
200 collapse_limit = diffs.DiffProcessor._collapse_commits_over
202 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
201 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
203
202
204 c.commit_statuses = ChangesetStatus.STATUSES
203 c.commit_statuses = ChangesetStatus.STATUSES
205 c.inline_comments = []
204 c.inline_comments = []
206 c.files = []
205 c.files = []
207
206
208 c.statuses = []
207 c.statuses = []
209 c.comments = []
208 c.comments = []
210 c.unresolved_comments = []
209 c.unresolved_comments = []
211 if len(c.commit_ranges) == 1:
210 if len(c.commit_ranges) == 1:
212 commit = c.commit_ranges[0]
211 commit = c.commit_ranges[0]
213 c.comments = CommentsModel().get_comments(
212 c.comments = CommentsModel().get_comments(
214 c.rhodecode_db_repo.repo_id,
213 c.rhodecode_db_repo.repo_id,
215 revision=commit.raw_id)
214 revision=commit.raw_id)
216 c.statuses.append(ChangesetStatusModel().get_status(
215 c.statuses.append(ChangesetStatusModel().get_status(
217 c.rhodecode_db_repo.repo_id, commit.raw_id))
216 c.rhodecode_db_repo.repo_id, commit.raw_id))
218 # comments from PR
217 # comments from PR
219 statuses = ChangesetStatusModel().get_statuses(
218 statuses = ChangesetStatusModel().get_statuses(
220 c.rhodecode_db_repo.repo_id, commit.raw_id,
219 c.rhodecode_db_repo.repo_id, commit.raw_id,
221 with_revisions=True)
220 with_revisions=True)
222 prs = set(st.pull_request for st in statuses
221 prs = set(st.pull_request for st in statuses
223 if st.pull_request is not None)
222 if st.pull_request is not None)
224 # from associated statuses, check the pull requests, and
223 # from associated statuses, check the pull requests, and
225 # show comments from them
224 # show comments from them
226 for pr in prs:
225 for pr in prs:
227 c.comments.extend(pr.comments)
226 c.comments.extend(pr.comments)
228
227
229 c.unresolved_comments = CommentsModel()\
228 c.unresolved_comments = CommentsModel()\
230 .get_commit_unresolved_todos(commit.raw_id)
229 .get_commit_unresolved_todos(commit.raw_id)
231
230
232 # Iterate over ranges (default commit view is always one commit)
231 # Iterate over ranges (default commit view is always one commit)
233 for commit in c.commit_ranges:
232 for commit in c.commit_ranges:
234 c.changes[commit.raw_id] = []
233 c.changes[commit.raw_id] = []
235
234
236 commit2 = commit
235 commit2 = commit
237 commit1 = commit.parents[0] if commit.parents else EmptyCommit()
236 commit1 = commit.parents[0] if commit.parents else EmptyCommit()
238
237
239 _diff = c.rhodecode_repo.get_diff(
238 _diff = c.rhodecode_repo.get_diff(
240 commit1, commit2,
239 commit1, commit2,
241 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
240 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
242 diff_processor = diffs.DiffProcessor(
241 diff_processor = diffs.DiffProcessor(
243 _diff, format='newdiff', diff_limit=diff_limit,
242 _diff, format='newdiff', diff_limit=diff_limit,
244 file_limit=file_limit, show_full_diff=fulldiff)
243 file_limit=file_limit, show_full_diff=fulldiff)
245
244
246 commit_changes = OrderedDict()
245 commit_changes = OrderedDict()
247 if method == 'show':
246 if method == 'show':
248 _parsed = diff_processor.prepare()
247 _parsed = diff_processor.prepare()
249 c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer)
248 c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer)
250
249
251 _parsed = diff_processor.prepare()
250 _parsed = diff_processor.prepare()
252
251
253 def _node_getter(commit):
252 def _node_getter(commit):
254 def get_node(fname):
253 def get_node(fname):
255 try:
254 try:
256 return commit.get_node(fname)
255 return commit.get_node(fname)
257 except NodeDoesNotExistError:
256 except NodeDoesNotExistError:
258 return None
257 return None
259 return get_node
258 return get_node
260
259
261 inline_comments = CommentsModel().get_inline_comments(
260 inline_comments = CommentsModel().get_inline_comments(
262 c.rhodecode_db_repo.repo_id, revision=commit.raw_id)
261 c.rhodecode_db_repo.repo_id, revision=commit.raw_id)
263 c.inline_cnt = CommentsModel().get_inline_comments_count(
262 c.inline_cnt = CommentsModel().get_inline_comments_count(
264 inline_comments)
263 inline_comments)
265
264
266 diffset = codeblocks.DiffSet(
265 diffset = codeblocks.DiffSet(
267 repo_name=c.repo_name,
266 repo_name=c.repo_name,
268 source_node_getter=_node_getter(commit1),
267 source_node_getter=_node_getter(commit1),
269 target_node_getter=_node_getter(commit2),
268 target_node_getter=_node_getter(commit2),
270 comments=inline_comments)
269 comments=inline_comments)
271 diffset = diffset.render_patchset(
270 diffset = diffset.render_patchset(
272 _parsed, commit1.raw_id, commit2.raw_id)
271 _parsed, commit1.raw_id, commit2.raw_id)
273
272
274 c.changes[commit.raw_id] = diffset
273 c.changes[commit.raw_id] = diffset
275 else:
274 else:
276 # downloads/raw we only need RAW diff nothing else
275 # downloads/raw we only need RAW diff nothing else
277 diff = diff_processor.as_raw()
276 diff = diff_processor.as_raw()
278 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
277 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
279
278
280 # sort comments by how they were generated
279 # sort comments by how they were generated
281 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
280 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
282
281
283 if len(c.commit_ranges) == 1:
282 if len(c.commit_ranges) == 1:
284 c.commit = c.commit_ranges[0]
283 c.commit = c.commit_ranges[0]
285 c.parent_tmpl = ''.join(
284 c.parent_tmpl = ''.join(
286 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
285 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
287 if method == 'download':
286 if method == 'download':
288 response.content_type = 'text/plain'
287 response.content_type = 'text/plain'
289 response.content_disposition = (
288 response.content_disposition = (
290 'attachment; filename=%s.diff' % commit_id_range[:12])
289 'attachment; filename=%s.diff' % commit_id_range[:12])
291 return diff
290 return diff
292 elif method == 'patch':
291 elif method == 'patch':
293 response.content_type = 'text/plain'
292 response.content_type = 'text/plain'
294 c.diff = safe_unicode(diff)
293 c.diff = safe_unicode(diff)
295 return render('changeset/patch_changeset.mako')
294 return render('changeset/patch_changeset.mako')
296 elif method == 'raw':
295 elif method == 'raw':
297 response.content_type = 'text/plain'
296 response.content_type = 'text/plain'
298 return diff
297 return diff
299 elif method == 'show':
298 elif method == 'show':
300 if len(c.commit_ranges) == 1:
299 if len(c.commit_ranges) == 1:
301 return render('changeset/changeset.mako')
300 return render('changeset/changeset.mako')
302 else:
301 else:
303 c.ancestor = None
302 c.ancestor = None
304 c.target_repo = c.rhodecode_db_repo
303 c.target_repo = c.rhodecode_db_repo
305 return render('changeset/changeset_range.mako')
304 return render('changeset/changeset_range.mako')
306
305
307 @LoginRequired()
306 @LoginRequired()
308 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
307 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
309 'repository.admin')
308 'repository.admin')
310 def index(self, revision, method='show'):
309 def index(self, revision, method='show'):
311 return self._index(revision, method=method)
310 return self._index(revision, method=method)
312
311
313 @LoginRequired()
312 @LoginRequired()
314 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
313 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
315 'repository.admin')
314 'repository.admin')
316 def changeset_raw(self, revision):
315 def changeset_raw(self, revision):
317 return self._index(revision, method='raw')
316 return self._index(revision, method='raw')
318
317
319 @LoginRequired()
318 @LoginRequired()
320 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
319 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
321 'repository.admin')
320 'repository.admin')
322 def changeset_patch(self, revision):
321 def changeset_patch(self, revision):
323 return self._index(revision, method='patch')
322 return self._index(revision, method='patch')
324
323
325 @LoginRequired()
324 @LoginRequired()
326 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
325 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
327 'repository.admin')
326 'repository.admin')
328 def changeset_download(self, revision):
327 def changeset_download(self, revision):
329 return self._index(revision, method='download')
328 return self._index(revision, method='download')
330
329
331 @LoginRequired()
330 @LoginRequired()
332 @NotAnonymous()
331 @NotAnonymous()
333 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
332 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
334 'repository.admin')
333 'repository.admin')
335 @auth.CSRFRequired()
334 @auth.CSRFRequired()
336 @jsonify
335 @jsonify
337 def comment(self, repo_name, revision):
336 def comment(self, repo_name, revision):
338 commit_id = revision
337 commit_id = revision
339 status = request.POST.get('changeset_status', None)
338 status = request.POST.get('changeset_status', None)
340 text = request.POST.get('text')
339 text = request.POST.get('text')
341 comment_type = request.POST.get('comment_type')
340 comment_type = request.POST.get('comment_type')
342 resolves_comment_id = request.POST.get('resolves_comment_id', None)
341 resolves_comment_id = request.POST.get('resolves_comment_id', None)
343
342
344 if status:
343 if status:
345 text = text or (_('Status change %(transition_icon)s %(status)s')
344 text = text or (_('Status change %(transition_icon)s %(status)s')
346 % {'transition_icon': '>',
345 % {'transition_icon': '>',
347 'status': ChangesetStatus.get_status_lbl(status)})
346 'status': ChangesetStatus.get_status_lbl(status)})
348
347
349 multi_commit_ids = []
348 multi_commit_ids = []
350 for _commit_id in request.POST.get('commit_ids', '').split(','):
349 for _commit_id in request.POST.get('commit_ids', '').split(','):
351 if _commit_id not in ['', None, EmptyCommit.raw_id]:
350 if _commit_id not in ['', None, EmptyCommit.raw_id]:
352 if _commit_id not in multi_commit_ids:
351 if _commit_id not in multi_commit_ids:
353 multi_commit_ids.append(_commit_id)
352 multi_commit_ids.append(_commit_id)
354
353
355 commit_ids = multi_commit_ids or [commit_id]
354 commit_ids = multi_commit_ids or [commit_id]
356
355
357 comment = None
356 comment = None
358 for current_id in filter(None, commit_ids):
357 for current_id in filter(None, commit_ids):
359 c.co = comment = CommentsModel().create(
358 c.co = comment = CommentsModel().create(
360 text=text,
359 text=text,
361 repo=c.rhodecode_db_repo.repo_id,
360 repo=c.rhodecode_db_repo.repo_id,
362 user=c.rhodecode_user.user_id,
361 user=c.rhodecode_user.user_id,
363 commit_id=current_id,
362 commit_id=current_id,
364 f_path=request.POST.get('f_path'),
363 f_path=request.POST.get('f_path'),
365 line_no=request.POST.get('line'),
364 line_no=request.POST.get('line'),
366 status_change=(ChangesetStatus.get_status_lbl(status)
365 status_change=(ChangesetStatus.get_status_lbl(status)
367 if status else None),
366 if status else None),
368 status_change_type=status,
367 status_change_type=status,
369 comment_type=comment_type,
368 comment_type=comment_type,
370 resolves_comment_id=resolves_comment_id
369 resolves_comment_id=resolves_comment_id
371 )
370 )
372
371
373 # get status if set !
372 # get status if set !
374 if status:
373 if status:
375 # if latest status was from pull request and it's closed
374 # if latest status was from pull request and it's closed
376 # disallow changing status !
375 # disallow changing status !
377 # dont_allow_on_closed_pull_request = True !
376 # dont_allow_on_closed_pull_request = True !
378
377
379 try:
378 try:
380 ChangesetStatusModel().set_status(
379 ChangesetStatusModel().set_status(
381 c.rhodecode_db_repo.repo_id,
380 c.rhodecode_db_repo.repo_id,
382 status,
381 status,
383 c.rhodecode_user.user_id,
382 c.rhodecode_user.user_id,
384 comment,
383 comment,
385 revision=current_id,
384 revision=current_id,
386 dont_allow_on_closed_pull_request=True
385 dont_allow_on_closed_pull_request=True
387 )
386 )
388 except StatusChangeOnClosedPullRequestError:
387 except StatusChangeOnClosedPullRequestError:
389 msg = _('Changing the status of a commit associated with '
388 msg = _('Changing the status of a commit associated with '
390 'a closed pull request is not allowed')
389 'a closed pull request is not allowed')
391 log.exception(msg)
390 log.exception(msg)
392 h.flash(msg, category='warning')
391 h.flash(msg, category='warning')
393 return redirect(h.url(
392 return redirect(h.url(
394 'changeset_home', repo_name=repo_name,
393 'changeset_home', repo_name=repo_name,
395 revision=current_id))
394 revision=current_id))
396
395
397 # finalize, commit and redirect
396 # finalize, commit and redirect
398 Session().commit()
397 Session().commit()
399
398
400 data = {
399 data = {
401 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
400 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
402 }
401 }
403 if comment:
402 if comment:
404 data.update(comment.get_dict())
403 data.update(comment.get_dict())
405 data.update({'rendered_text':
404 data.update({'rendered_text':
406 render('changeset/changeset_comment_block.mako')})
405 render('changeset/changeset_comment_block.mako')})
407
406
408 return data
407 return data
409
408
410 @LoginRequired()
409 @LoginRequired()
411 @NotAnonymous()
410 @NotAnonymous()
412 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
411 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
413 'repository.admin')
412 'repository.admin')
414 @auth.CSRFRequired()
413 @auth.CSRFRequired()
415 def preview_comment(self):
414 def preview_comment(self):
416 # Technically a CSRF token is not needed as no state changes with this
415 # Technically a CSRF token is not needed as no state changes with this
417 # call. However, as this is a POST is better to have it, so automated
416 # call. However, as this is a POST is better to have it, so automated
418 # tools don't flag it as potential CSRF.
417 # tools don't flag it as potential CSRF.
419 # Post is required because the payload could be bigger than the maximum
418 # Post is required because the payload could be bigger than the maximum
420 # allowed by GET.
419 # allowed by GET.
421 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
420 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
422 raise HTTPBadRequest()
421 raise HTTPBadRequest()
423 text = request.POST.get('text')
422 text = request.POST.get('text')
424 renderer = request.POST.get('renderer') or 'rst'
423 renderer = request.POST.get('renderer') or 'rst'
425 if text:
424 if text:
426 return h.render(text, renderer=renderer, mentions=True)
425 return h.render(text, renderer=renderer, mentions=True)
427 return ''
426 return ''
428
427
429 @LoginRequired()
428 @LoginRequired()
430 @NotAnonymous()
429 @NotAnonymous()
431 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
430 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
432 'repository.admin')
431 'repository.admin')
433 @auth.CSRFRequired()
432 @auth.CSRFRequired()
434 @jsonify
433 @jsonify
435 def delete_comment(self, repo_name, comment_id):
434 def delete_comment(self, repo_name, comment_id):
436 comment = ChangesetComment.get_or_404(safe_int(comment_id))
435 comment = ChangesetComment.get_or_404(safe_int(comment_id))
437 if not comment:
436 if not comment:
438 log.debug('Comment with id:%s not found, skipping', comment_id)
437 log.debug('Comment with id:%s not found, skipping', comment_id)
439 # comment already deleted in another call probably
438 # comment already deleted in another call probably
440 return True
439 return True
441
440
442 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
441 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
443 super_admin = h.HasPermissionAny('hg.admin')()
442 super_admin = h.HasPermissionAny('hg.admin')()
444 comment_owner = (comment.author.user_id == c.rhodecode_user.user_id)
443 comment_owner = (comment.author.user_id == c.rhodecode_user.user_id)
445 is_repo_comment = comment.repo.repo_name == c.repo_name
444 is_repo_comment = comment.repo.repo_name == c.repo_name
446 comment_repo_admin = is_repo_admin and is_repo_comment
445 comment_repo_admin = is_repo_admin and is_repo_comment
447
446
448 if super_admin or comment_owner or comment_repo_admin:
447 if super_admin or comment_owner or comment_repo_admin:
449 CommentsModel().delete(comment=comment, user=c.rhodecode_user)
448 CommentsModel().delete(comment=comment, user=c.rhodecode_user)
450 Session().commit()
449 Session().commit()
451 return True
450 return True
452 else:
451 else:
453 log.warning('No permissions for user %s to delete comment_id: %s',
452 log.warning('No permissions for user %s to delete comment_id: %s',
454 c.rhodecode_user, comment_id)
453 c.rhodecode_user, comment_id)
455 raise HTTPNotFound()
454 raise HTTPNotFound()
456
455
457 @LoginRequired()
456 @LoginRequired()
458 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
457 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
459 'repository.admin')
458 'repository.admin')
460 @jsonify
459 @jsonify
461 def changeset_info(self, repo_name, revision):
460 def changeset_info(self, repo_name, revision):
462 if request.is_xhr:
461 if request.is_xhr:
463 try:
462 try:
464 return c.rhodecode_repo.get_commit(commit_id=revision)
463 return c.rhodecode_repo.get_commit(commit_id=revision)
465 except CommitDoesNotExistError as e:
464 except CommitDoesNotExistError as e:
466 return EmptyCommit(message=str(e))
465 return EmptyCommit(message=str(e))
467 else:
466 else:
468 raise HTTPBadRequest()
467 raise HTTPBadRequest()
469
468
470 @LoginRequired()
469 @LoginRequired()
471 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
470 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
472 'repository.admin')
471 'repository.admin')
473 @jsonify
472 @jsonify
474 def changeset_children(self, repo_name, revision):
473 def changeset_children(self, repo_name, revision):
475 if request.is_xhr:
474 if request.is_xhr:
476 commit = c.rhodecode_repo.get_commit(commit_id=revision)
475 commit = c.rhodecode_repo.get_commit(commit_id=revision)
477 result = {"results": commit.children}
476 result = {"results": commit.children}
478 return result
477 return result
479 else:
478 else:
480 raise HTTPBadRequest()
479 raise HTTPBadRequest()
481
480
482 @LoginRequired()
481 @LoginRequired()
483 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
482 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
484 'repository.admin')
483 'repository.admin')
485 @jsonify
484 @jsonify
486 def changeset_parents(self, repo_name, revision):
485 def changeset_parents(self, repo_name, revision):
487 if request.is_xhr:
486 if request.is_xhr:
488 commit = c.rhodecode_repo.get_commit(commit_id=revision)
487 commit = c.rhodecode_repo.get_commit(commit_id=revision)
489 result = {"results": commit.parents}
488 result = {"results": commit.parents}
490 return result
489 return result
491 else:
490 else:
492 raise HTTPBadRequest()
491 raise HTTPBadRequest()
@@ -1,306 +1,304 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Journal / user event log controller for rhodecode
22 Journal / user event log controller for rhodecode
23 """
23 """
24
24
25 import logging
25 import logging
26 from itertools import groupby
26 from itertools import groupby
27
27
28 from sqlalchemy import or_
28 from sqlalchemy import or_
29 from sqlalchemy.orm import joinedload
29 from sqlalchemy.orm import joinedload
30
30
31 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
31 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
32
32
33 from webob.exc import HTTPBadRequest
33 from webob.exc import HTTPBadRequest
34 from pylons import request, tmpl_context as c, response, url
34 from pylons import request, tmpl_context as c, response, url
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 from rhodecode.model.db import UserLog, UserFollowing, User, UserApiKeys
37 from rhodecode.model.db import UserLog, UserFollowing, User, UserApiKeys
38 from rhodecode.model.meta import Session
38 from rhodecode.model.meta import Session
39 import rhodecode.lib.helpers as h
39 import rhodecode.lib.helpers as h
40 from rhodecode.lib.helpers import Page
40 from rhodecode.lib.helpers import Page
41 from rhodecode.lib.user_log_filter import user_log_filter
41 from rhodecode.lib.user_log_filter import user_log_filter
42 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
42 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
43 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.base import BaseController, render
44 from rhodecode.lib.utils2 import safe_int, AttributeDict
44 from rhodecode.lib.utils2 import safe_int, AttributeDict
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class JournalController(BaseController):
49 class JournalController(BaseController):
50
50
51 def __before__(self):
51 def __before__(self):
52 super(JournalController, self).__before__()
52 super(JournalController, self).__before__()
53 self.language = 'en-us'
53 self.language = 'en-us'
54 self.ttl = "5"
54 self.ttl = "5"
55 self.feed_nr = 20
55 self.feed_nr = 20
56 c.search_term = request.GET.get('filter')
56 c.search_term = request.GET.get('filter')
57
57
58 def _get_daily_aggregate(self, journal):
58 def _get_daily_aggregate(self, journal):
59 groups = []
59 groups = []
60 for k, g in groupby(journal, lambda x: x.action_as_day):
60 for k, g in groupby(journal, lambda x: x.action_as_day):
61 user_group = []
61 user_group = []
62 #groupby username if it's a present value, else fallback to journal username
62 #groupby username if it's a present value, else fallback to journal username
63 for _, g2 in groupby(list(g), lambda x: x.user.username if x.user else x.username):
63 for _, g2 in groupby(list(g), lambda x: x.user.username if x.user else x.username):
64 l = list(g2)
64 l = list(g2)
65 user_group.append((l[0].user, l))
65 user_group.append((l[0].user, l))
66
66
67 groups.append((k, user_group,))
67 groups.append((k, user_group,))
68
68
69 return groups
69 return groups
70
70
71 def _get_journal_data(self, following_repos):
71 def _get_journal_data(self, following_repos):
72 repo_ids = [x.follows_repository.repo_id for x in following_repos
72 repo_ids = [x.follows_repository.repo_id for x in following_repos
73 if x.follows_repository is not None]
73 if x.follows_repository is not None]
74 user_ids = [x.follows_user.user_id for x in following_repos
74 user_ids = [x.follows_user.user_id for x in following_repos
75 if x.follows_user is not None]
75 if x.follows_user is not None]
76
76
77 filtering_criterion = None
77 filtering_criterion = None
78
78
79 if repo_ids and user_ids:
79 if repo_ids and user_ids:
80 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
80 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
81 UserLog.user_id.in_(user_ids))
81 UserLog.user_id.in_(user_ids))
82 if repo_ids and not user_ids:
82 if repo_ids and not user_ids:
83 filtering_criterion = UserLog.repository_id.in_(repo_ids)
83 filtering_criterion = UserLog.repository_id.in_(repo_ids)
84 if not repo_ids and user_ids:
84 if not repo_ids and user_ids:
85 filtering_criterion = UserLog.user_id.in_(user_ids)
85 filtering_criterion = UserLog.user_id.in_(user_ids)
86 if filtering_criterion is not None:
86 if filtering_criterion is not None:
87 journal = self.sa.query(UserLog)\
87 journal = self.sa.query(UserLog)\
88 .options(joinedload(UserLog.user))\
88 .options(joinedload(UserLog.user))\
89 .options(joinedload(UserLog.repository))
89 .options(joinedload(UserLog.repository))
90 #filter
90 #filter
91 try:
91 try:
92 journal = user_log_filter(journal, c.search_term)
92 journal = user_log_filter(journal, c.search_term)
93 except Exception:
93 except Exception:
94 # we want this to crash for now
94 # we want this to crash for now
95 raise
95 raise
96 journal = journal.filter(filtering_criterion)\
96 journal = journal.filter(filtering_criterion)\
97 .order_by(UserLog.action_date.desc())
97 .order_by(UserLog.action_date.desc())
98 else:
98 else:
99 journal = []
99 journal = []
100
100
101 return journal
101 return journal
102
102
103 def _atom_feed(self, repos, public=True):
103 def _atom_feed(self, repos, public=True):
104 journal = self._get_journal_data(repos)
104 journal = self._get_journal_data(repos)
105 if public:
105 if public:
106 _link = url('public_journal_atom', qualified=True)
106 _link = url('public_journal_atom', qualified=True)
107 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
107 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
108 'atom feed')
108 'atom feed')
109 else:
109 else:
110 _link = url('journal_atom', qualified=True)
110 _link = url('journal_atom', qualified=True)
111 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed')
111 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed')
112
112
113 feed = Atom1Feed(title=_desc,
113 feed = Atom1Feed(title=_desc,
114 link=_link,
114 link=_link,
115 description=_desc,
115 description=_desc,
116 language=self.language,
116 language=self.language,
117 ttl=self.ttl)
117 ttl=self.ttl)
118
118
119 for entry in journal[:self.feed_nr]:
119 for entry in journal[:self.feed_nr]:
120 user = entry.user
120 user = entry.user
121 if user is None:
121 if user is None:
122 #fix deleted users
122 #fix deleted users
123 user = AttributeDict({'short_contact': entry.username,
123 user = AttributeDict({'short_contact': entry.username,
124 'email': '',
124 'email': '',
125 'full_contact': ''})
125 'full_contact': ''})
126 action, action_extra, ico = h.action_parser(entry, feed=True)
126 action, action_extra, ico = h.action_parser(entry, feed=True)
127 title = "%s - %s %s" % (user.short_contact, action(),
127 title = "%s - %s %s" % (user.short_contact, action(),
128 entry.repository.repo_name)
128 entry.repository.repo_name)
129 desc = action_extra()
129 desc = action_extra()
130 _url = None
130 _url = None
131 if entry.repository is not None:
131 if entry.repository is not None:
132 _url = url('changelog_home',
132 _url = h.route_url('repo_changelog',
133 repo_name=entry.repository.repo_name,
133 repo_name=entry.repository.repo_name)
134 qualified=True)
135
134
136 feed.add_item(title=title,
135 feed.add_item(title=title,
137 pubdate=entry.action_date,
136 pubdate=entry.action_date,
138 link=_url or url('', qualified=True),
137 link=_url or url('', qualified=True),
139 author_email=user.email,
138 author_email=user.email,
140 author_name=user.full_contact,
139 author_name=user.full_contact,
141 description=desc)
140 description=desc)
142
141
143 response.content_type = feed.mime_type
142 response.content_type = feed.mime_type
144 return feed.writeString('utf-8')
143 return feed.writeString('utf-8')
145
144
146 def _rss_feed(self, repos, public=True):
145 def _rss_feed(self, repos, public=True):
147 journal = self._get_journal_data(repos)
146 journal = self._get_journal_data(repos)
148 if public:
147 if public:
149 _link = url('public_journal_atom', qualified=True)
148 _link = url('public_journal_atom', qualified=True)
150 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
149 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
151 'rss feed')
150 'rss feed')
152 else:
151 else:
153 _link = url('journal_atom', qualified=True)
152 _link = url('journal_atom', qualified=True)
154 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'rss feed')
153 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'rss feed')
155
154
156 feed = Rss201rev2Feed(title=_desc,
155 feed = Rss201rev2Feed(title=_desc,
157 link=_link,
156 link=_link,
158 description=_desc,
157 description=_desc,
159 language=self.language,
158 language=self.language,
160 ttl=self.ttl)
159 ttl=self.ttl)
161
160
162 for entry in journal[:self.feed_nr]:
161 for entry in journal[:self.feed_nr]:
163 user = entry.user
162 user = entry.user
164 if user is None:
163 if user is None:
165 #fix deleted users
164 #fix deleted users
166 user = AttributeDict({'short_contact': entry.username,
165 user = AttributeDict({'short_contact': entry.username,
167 'email': '',
166 'email': '',
168 'full_contact': ''})
167 'full_contact': ''})
169 action, action_extra, ico = h.action_parser(entry, feed=True)
168 action, action_extra, ico = h.action_parser(entry, feed=True)
170 title = "%s - %s %s" % (user.short_contact, action(),
169 title = "%s - %s %s" % (user.short_contact, action(),
171 entry.repository.repo_name)
170 entry.repository.repo_name)
172 desc = action_extra()
171 desc = action_extra()
173 _url = None
172 _url = None
174 if entry.repository is not None:
173 if entry.repository is not None:
175 _url = url('changelog_home',
174 _url = h.route_url('repo_changelog',
176 repo_name=entry.repository.repo_name,
175 repo_name=entry.repository.repo_name)
177 qualified=True)
178
176
179 feed.add_item(title=title,
177 feed.add_item(title=title,
180 pubdate=entry.action_date,
178 pubdate=entry.action_date,
181 link=_url or url('', qualified=True),
179 link=_url or url('', qualified=True),
182 author_email=user.email,
180 author_email=user.email,
183 author_name=user.full_contact,
181 author_name=user.full_contact,
184 description=desc)
182 description=desc)
185
183
186 response.content_type = feed.mime_type
184 response.content_type = feed.mime_type
187 return feed.writeString('utf-8')
185 return feed.writeString('utf-8')
188
186
189 @LoginRequired()
187 @LoginRequired()
190 @NotAnonymous()
188 @NotAnonymous()
191 def index(self):
189 def index(self):
192 # Return a rendered template
190 # Return a rendered template
193 p = safe_int(request.GET.get('page', 1), 1)
191 p = safe_int(request.GET.get('page', 1), 1)
194 c.user = User.get(c.rhodecode_user.user_id)
192 c.user = User.get(c.rhodecode_user.user_id)
195 following = self.sa.query(UserFollowing)\
193 following = self.sa.query(UserFollowing)\
196 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
194 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
197 .options(joinedload(UserFollowing.follows_repository))\
195 .options(joinedload(UserFollowing.follows_repository))\
198 .all()
196 .all()
199
197
200 journal = self._get_journal_data(following)
198 journal = self._get_journal_data(following)
201
199
202 def url_generator(**kw):
200 def url_generator(**kw):
203 return url.current(filter=c.search_term, **kw)
201 return url.current(filter=c.search_term, **kw)
204
202
205 c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator)
203 c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator)
206 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
204 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
207
205
208 c.journal_data = render('journal/journal_data.mako')
206 c.journal_data = render('journal/journal_data.mako')
209 if request.is_xhr:
207 if request.is_xhr:
210 return c.journal_data
208 return c.journal_data
211
209
212 return render('journal/journal.mako')
210 return render('journal/journal.mako')
213
211
214 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
212 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
215 @NotAnonymous()
213 @NotAnonymous()
216 def journal_atom(self):
214 def journal_atom(self):
217 """
215 """
218 Produce an atom-1.0 feed via feedgenerator module
216 Produce an atom-1.0 feed via feedgenerator module
219 """
217 """
220 following = self.sa.query(UserFollowing)\
218 following = self.sa.query(UserFollowing)\
221 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
219 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
222 .options(joinedload(UserFollowing.follows_repository))\
220 .options(joinedload(UserFollowing.follows_repository))\
223 .all()
221 .all()
224 return self._atom_feed(following, public=False)
222 return self._atom_feed(following, public=False)
225
223
226 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
224 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
227 @NotAnonymous()
225 @NotAnonymous()
228 def journal_rss(self):
226 def journal_rss(self):
229 """
227 """
230 Produce an rss feed via feedgenerator module
228 Produce an rss feed via feedgenerator module
231 """
229 """
232 following = self.sa.query(UserFollowing)\
230 following = self.sa.query(UserFollowing)\
233 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
231 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
234 .options(joinedload(UserFollowing.follows_repository))\
232 .options(joinedload(UserFollowing.follows_repository))\
235 .all()
233 .all()
236 return self._rss_feed(following, public=False)
234 return self._rss_feed(following, public=False)
237
235
238 @CSRFRequired()
236 @CSRFRequired()
239 @LoginRequired()
237 @LoginRequired()
240 @NotAnonymous()
238 @NotAnonymous()
241 def toggle_following(self):
239 def toggle_following(self):
242 user_id = request.POST.get('follows_user_id')
240 user_id = request.POST.get('follows_user_id')
243 if user_id:
241 if user_id:
244 try:
242 try:
245 self.scm_model.toggle_following_user(
243 self.scm_model.toggle_following_user(
246 user_id, c.rhodecode_user.user_id)
244 user_id, c.rhodecode_user.user_id)
247 Session().commit()
245 Session().commit()
248 return 'ok'
246 return 'ok'
249 except Exception:
247 except Exception:
250 raise HTTPBadRequest()
248 raise HTTPBadRequest()
251
249
252 repo_id = request.POST.get('follows_repo_id')
250 repo_id = request.POST.get('follows_repo_id')
253 if repo_id:
251 if repo_id:
254 try:
252 try:
255 self.scm_model.toggle_following_repo(
253 self.scm_model.toggle_following_repo(
256 repo_id, c.rhodecode_user.user_id)
254 repo_id, c.rhodecode_user.user_id)
257 Session().commit()
255 Session().commit()
258 return 'ok'
256 return 'ok'
259 except Exception:
257 except Exception:
260 raise HTTPBadRequest()
258 raise HTTPBadRequest()
261
259
262
260
263 @LoginRequired()
261 @LoginRequired()
264 def public_journal(self):
262 def public_journal(self):
265 # Return a rendered template
263 # Return a rendered template
266 p = safe_int(request.GET.get('page', 1), 1)
264 p = safe_int(request.GET.get('page', 1), 1)
267
265
268 c.following = self.sa.query(UserFollowing)\
266 c.following = self.sa.query(UserFollowing)\
269 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
267 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
270 .options(joinedload(UserFollowing.follows_repository))\
268 .options(joinedload(UserFollowing.follows_repository))\
271 .all()
269 .all()
272
270
273 journal = self._get_journal_data(c.following)
271 journal = self._get_journal_data(c.following)
274
272
275 c.journal_pager = Page(journal, page=p, items_per_page=20)
273 c.journal_pager = Page(journal, page=p, items_per_page=20)
276
274
277 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
275 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
278
276
279 c.journal_data = render('journal/journal_data.mako')
277 c.journal_data = render('journal/journal_data.mako')
280 if request.is_xhr:
278 if request.is_xhr:
281 return c.journal_data
279 return c.journal_data
282 return render('journal/public_journal.mako')
280 return render('journal/public_journal.mako')
283
281
284 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
282 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
285 def public_journal_atom(self):
283 def public_journal_atom(self):
286 """
284 """
287 Produce an atom-1.0 feed via feedgenerator module
285 Produce an atom-1.0 feed via feedgenerator module
288 """
286 """
289 c.following = self.sa.query(UserFollowing)\
287 c.following = self.sa.query(UserFollowing)\
290 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
288 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
291 .options(joinedload(UserFollowing.follows_repository))\
289 .options(joinedload(UserFollowing.follows_repository))\
292 .all()
290 .all()
293
291
294 return self._atom_feed(c.following)
292 return self._atom_feed(c.following)
295
293
296 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
294 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
297 def public_journal_rss(self):
295 def public_journal_rss(self):
298 """
296 """
299 Produce an rss2 feed via feedgenerator module
297 Produce an rss2 feed via feedgenerator module
300 """
298 """
301 c.following = self.sa.query(UserFollowing)\
299 c.following = self.sa.query(UserFollowing)\
302 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
300 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
303 .options(joinedload(UserFollowing.follows_repository))\
301 .options(joinedload(UserFollowing.follows_repository))\
304 .all()
302 .all()
305
303
306 return self._rss_feed(c.following)
304 return self._rss_feed(c.following)
@@ -1,632 +1,634 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 The base Controller API
22 The base Controller API
23 Provides the BaseController class for subclassing. And usage in different
23 Provides the BaseController class for subclassing. And usage in different
24 controllers
24 controllers
25 """
25 """
26
26
27 import logging
27 import logging
28 import socket
28 import socket
29
29
30 import ipaddress
30 import ipaddress
31 import pyramid.threadlocal
31 import pyramid.threadlocal
32
32
33 from paste.auth.basic import AuthBasicAuthenticator
33 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36 from pylons import config, tmpl_context as c, request, url
36 from pylons import config, tmpl_context as c, request, url
37 from pylons.controllers import WSGIController
37 from pylons.controllers import WSGIController
38 from pylons.controllers.util import redirect
38 from pylons.controllers.util import redirect
39 from pylons.i18n import translation
39 from pylons.i18n import translation
40 # marcink: don't remove this import
40 # marcink: don't remove this import
41 from pylons.templating import render_mako as render # noqa
41 from pylons.templating import render_mako as render # noqa
42 from pylons.i18n.translation import _
42 from pylons.i18n.translation import _
43 from webob.exc import HTTPFound
43 from webob.exc import HTTPFound
44
44
45
45
46 import rhodecode
46 import rhodecode
47 from rhodecode.authentication.base import VCS_TYPE
47 from rhodecode.authentication.base import VCS_TYPE
48 from rhodecode.lib import auth, utils2
48 from rhodecode.lib import auth, utils2
49 from rhodecode.lib import helpers as h
49 from rhodecode.lib import helpers as h
50 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
50 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
51 from rhodecode.lib.exceptions import UserCreationError
51 from rhodecode.lib.exceptions import UserCreationError
52 from rhodecode.lib.utils import (
52 from rhodecode.lib.utils import (
53 get_repo_slug, set_rhodecode_config, password_changed,
53 get_repo_slug, set_rhodecode_config, password_changed,
54 get_enabled_hook_classes)
54 get_enabled_hook_classes)
55 from rhodecode.lib.utils2 import (
55 from rhodecode.lib.utils2 import (
56 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
56 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
57 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
57 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
58 from rhodecode.model import meta
58 from rhodecode.model import meta
59 from rhodecode.model.db import Repository, User, ChangesetComment
59 from rhodecode.model.db import Repository, User, ChangesetComment
60 from rhodecode.model.notification import NotificationModel
60 from rhodecode.model.notification import NotificationModel
61 from rhodecode.model.scm import ScmModel
61 from rhodecode.model.scm import ScmModel
62 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
62 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
63
63
64
64
65 log = logging.getLogger(__name__)
65 log = logging.getLogger(__name__)
66
66
67
67
68 def _filter_proxy(ip):
68 def _filter_proxy(ip):
69 """
69 """
70 Passed in IP addresses in HEADERS can be in a special format of multiple
70 Passed in IP addresses in HEADERS can be in a special format of multiple
71 ips. Those comma separated IPs are passed from various proxies in the
71 ips. Those comma separated IPs are passed from various proxies in the
72 chain of request processing. The left-most being the original client.
72 chain of request processing. The left-most being the original client.
73 We only care about the first IP which came from the org. client.
73 We only care about the first IP which came from the org. client.
74
74
75 :param ip: ip string from headers
75 :param ip: ip string from headers
76 """
76 """
77 if ',' in ip:
77 if ',' in ip:
78 _ips = ip.split(',')
78 _ips = ip.split(',')
79 _first_ip = _ips[0].strip()
79 _first_ip = _ips[0].strip()
80 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
80 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
81 return _first_ip
81 return _first_ip
82 return ip
82 return ip
83
83
84
84
85 def _filter_port(ip):
85 def _filter_port(ip):
86 """
86 """
87 Removes a port from ip, there are 4 main cases to handle here.
87 Removes a port from ip, there are 4 main cases to handle here.
88 - ipv4 eg. 127.0.0.1
88 - ipv4 eg. 127.0.0.1
89 - ipv6 eg. ::1
89 - ipv6 eg. ::1
90 - ipv4+port eg. 127.0.0.1:8080
90 - ipv4+port eg. 127.0.0.1:8080
91 - ipv6+port eg. [::1]:8080
91 - ipv6+port eg. [::1]:8080
92
92
93 :param ip:
93 :param ip:
94 """
94 """
95 def is_ipv6(ip_addr):
95 def is_ipv6(ip_addr):
96 if hasattr(socket, 'inet_pton'):
96 if hasattr(socket, 'inet_pton'):
97 try:
97 try:
98 socket.inet_pton(socket.AF_INET6, ip_addr)
98 socket.inet_pton(socket.AF_INET6, ip_addr)
99 except socket.error:
99 except socket.error:
100 return False
100 return False
101 else:
101 else:
102 # fallback to ipaddress
102 # fallback to ipaddress
103 try:
103 try:
104 ipaddress.IPv6Address(safe_unicode(ip_addr))
104 ipaddress.IPv6Address(safe_unicode(ip_addr))
105 except Exception:
105 except Exception:
106 return False
106 return False
107 return True
107 return True
108
108
109 if ':' not in ip: # must be ipv4 pure ip
109 if ':' not in ip: # must be ipv4 pure ip
110 return ip
110 return ip
111
111
112 if '[' in ip and ']' in ip: # ipv6 with port
112 if '[' in ip and ']' in ip: # ipv6 with port
113 return ip.split(']')[0][1:].lower()
113 return ip.split(']')[0][1:].lower()
114
114
115 # must be ipv6 or ipv4 with port
115 # must be ipv6 or ipv4 with port
116 if is_ipv6(ip):
116 if is_ipv6(ip):
117 return ip
117 return ip
118 else:
118 else:
119 ip, _port = ip.split(':')[:2] # means ipv4+port
119 ip, _port = ip.split(':')[:2] # means ipv4+port
120 return ip
120 return ip
121
121
122
122
123 def get_ip_addr(environ):
123 def get_ip_addr(environ):
124 proxy_key = 'HTTP_X_REAL_IP'
124 proxy_key = 'HTTP_X_REAL_IP'
125 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
125 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
126 def_key = 'REMOTE_ADDR'
126 def_key = 'REMOTE_ADDR'
127 _filters = lambda x: _filter_port(_filter_proxy(x))
127 _filters = lambda x: _filter_port(_filter_proxy(x))
128
128
129 ip = environ.get(proxy_key)
129 ip = environ.get(proxy_key)
130 if ip:
130 if ip:
131 return _filters(ip)
131 return _filters(ip)
132
132
133 ip = environ.get(proxy_key2)
133 ip = environ.get(proxy_key2)
134 if ip:
134 if ip:
135 return _filters(ip)
135 return _filters(ip)
136
136
137 ip = environ.get(def_key, '0.0.0.0')
137 ip = environ.get(def_key, '0.0.0.0')
138 return _filters(ip)
138 return _filters(ip)
139
139
140
140
141 def get_server_ip_addr(environ, log_errors=True):
141 def get_server_ip_addr(environ, log_errors=True):
142 hostname = environ.get('SERVER_NAME')
142 hostname = environ.get('SERVER_NAME')
143 try:
143 try:
144 return socket.gethostbyname(hostname)
144 return socket.gethostbyname(hostname)
145 except Exception as e:
145 except Exception as e:
146 if log_errors:
146 if log_errors:
147 # in some cases this lookup is not possible, and we don't want to
147 # in some cases this lookup is not possible, and we don't want to
148 # make it an exception in logs
148 # make it an exception in logs
149 log.exception('Could not retrieve server ip address: %s', e)
149 log.exception('Could not retrieve server ip address: %s', e)
150 return hostname
150 return hostname
151
151
152
152
153 def get_server_port(environ):
153 def get_server_port(environ):
154 return environ.get('SERVER_PORT')
154 return environ.get('SERVER_PORT')
155
155
156
156
157 def get_access_path(environ):
157 def get_access_path(environ):
158 path = environ.get('PATH_INFO')
158 path = environ.get('PATH_INFO')
159 org_req = environ.get('pylons.original_request')
159 org_req = environ.get('pylons.original_request')
160 if org_req:
160 if org_req:
161 path = org_req.environ.get('PATH_INFO')
161 path = org_req.environ.get('PATH_INFO')
162 return path
162 return path
163
163
164
164
165 def get_user_agent(environ):
165 def get_user_agent(environ):
166 return environ.get('HTTP_USER_AGENT')
166 return environ.get('HTTP_USER_AGENT')
167
167
168
168
169 def vcs_operation_context(
169 def vcs_operation_context(
170 environ, repo_name, username, action, scm, check_locking=True,
170 environ, repo_name, username, action, scm, check_locking=True,
171 is_shadow_repo=False):
171 is_shadow_repo=False):
172 """
172 """
173 Generate the context for a vcs operation, e.g. push or pull.
173 Generate the context for a vcs operation, e.g. push or pull.
174
174
175 This context is passed over the layers so that hooks triggered by the
175 This context is passed over the layers so that hooks triggered by the
176 vcs operation know details like the user, the user's IP address etc.
176 vcs operation know details like the user, the user's IP address etc.
177
177
178 :param check_locking: Allows to switch of the computation of the locking
178 :param check_locking: Allows to switch of the computation of the locking
179 data. This serves mainly the need of the simplevcs middleware to be
179 data. This serves mainly the need of the simplevcs middleware to be
180 able to disable this for certain operations.
180 able to disable this for certain operations.
181
181
182 """
182 """
183 # Tri-state value: False: unlock, None: nothing, True: lock
183 # Tri-state value: False: unlock, None: nothing, True: lock
184 make_lock = None
184 make_lock = None
185 locked_by = [None, None, None]
185 locked_by = [None, None, None]
186 is_anonymous = username == User.DEFAULT_USER
186 is_anonymous = username == User.DEFAULT_USER
187 if not is_anonymous and check_locking:
187 if not is_anonymous and check_locking:
188 log.debug('Checking locking on repository "%s"', repo_name)
188 log.debug('Checking locking on repository "%s"', repo_name)
189 user = User.get_by_username(username)
189 user = User.get_by_username(username)
190 repo = Repository.get_by_repo_name(repo_name)
190 repo = Repository.get_by_repo_name(repo_name)
191 make_lock, __, locked_by = repo.get_locking_state(
191 make_lock, __, locked_by = repo.get_locking_state(
192 action, user.user_id)
192 action, user.user_id)
193
193
194 settings_model = VcsSettingsModel(repo=repo_name)
194 settings_model = VcsSettingsModel(repo=repo_name)
195 ui_settings = settings_model.get_ui_settings()
195 ui_settings = settings_model.get_ui_settings()
196
196
197 extras = {
197 extras = {
198 'ip': get_ip_addr(environ),
198 'ip': get_ip_addr(environ),
199 'username': username,
199 'username': username,
200 'action': action,
200 'action': action,
201 'repository': repo_name,
201 'repository': repo_name,
202 'scm': scm,
202 'scm': scm,
203 'config': rhodecode.CONFIG['__file__'],
203 'config': rhodecode.CONFIG['__file__'],
204 'make_lock': make_lock,
204 'make_lock': make_lock,
205 'locked_by': locked_by,
205 'locked_by': locked_by,
206 'server_url': utils2.get_server_url(environ),
206 'server_url': utils2.get_server_url(environ),
207 'user_agent': get_user_agent(environ),
207 'user_agent': get_user_agent(environ),
208 'hooks': get_enabled_hook_classes(ui_settings),
208 'hooks': get_enabled_hook_classes(ui_settings),
209 'is_shadow_repo': is_shadow_repo,
209 'is_shadow_repo': is_shadow_repo,
210 }
210 }
211 return extras
211 return extras
212
212
213
213
214 class BasicAuth(AuthBasicAuthenticator):
214 class BasicAuth(AuthBasicAuthenticator):
215
215
216 def __init__(self, realm, authfunc, registry, auth_http_code=None,
216 def __init__(self, realm, authfunc, registry, auth_http_code=None,
217 initial_call_detection=False, acl_repo_name=None):
217 initial_call_detection=False, acl_repo_name=None):
218 self.realm = realm
218 self.realm = realm
219 self.initial_call = initial_call_detection
219 self.initial_call = initial_call_detection
220 self.authfunc = authfunc
220 self.authfunc = authfunc
221 self.registry = registry
221 self.registry = registry
222 self.acl_repo_name = acl_repo_name
222 self.acl_repo_name = acl_repo_name
223 self._rc_auth_http_code = auth_http_code
223 self._rc_auth_http_code = auth_http_code
224
224
225 def _get_response_from_code(self, http_code):
225 def _get_response_from_code(self, http_code):
226 try:
226 try:
227 return get_exception(safe_int(http_code))
227 return get_exception(safe_int(http_code))
228 except Exception:
228 except Exception:
229 log.exception('Failed to fetch response for code %s' % http_code)
229 log.exception('Failed to fetch response for code %s' % http_code)
230 return HTTPForbidden
230 return HTTPForbidden
231
231
232 def build_authentication(self):
232 def build_authentication(self):
233 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
233 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
234 if self._rc_auth_http_code and not self.initial_call:
234 if self._rc_auth_http_code and not self.initial_call:
235 # return alternative HTTP code if alternative http return code
235 # return alternative HTTP code if alternative http return code
236 # is specified in RhodeCode config, but ONLY if it's not the
236 # is specified in RhodeCode config, but ONLY if it's not the
237 # FIRST call
237 # FIRST call
238 custom_response_klass = self._get_response_from_code(
238 custom_response_klass = self._get_response_from_code(
239 self._rc_auth_http_code)
239 self._rc_auth_http_code)
240 return custom_response_klass(headers=head)
240 return custom_response_klass(headers=head)
241 return HTTPUnauthorized(headers=head)
241 return HTTPUnauthorized(headers=head)
242
242
243 def authenticate(self, environ):
243 def authenticate(self, environ):
244 authorization = AUTHORIZATION(environ)
244 authorization = AUTHORIZATION(environ)
245 if not authorization:
245 if not authorization:
246 return self.build_authentication()
246 return self.build_authentication()
247 (authmeth, auth) = authorization.split(' ', 1)
247 (authmeth, auth) = authorization.split(' ', 1)
248 if 'basic' != authmeth.lower():
248 if 'basic' != authmeth.lower():
249 return self.build_authentication()
249 return self.build_authentication()
250 auth = auth.strip().decode('base64')
250 auth = auth.strip().decode('base64')
251 _parts = auth.split(':', 1)
251 _parts = auth.split(':', 1)
252 if len(_parts) == 2:
252 if len(_parts) == 2:
253 username, password = _parts
253 username, password = _parts
254 if self.authfunc(
254 if self.authfunc(
255 username, password, environ, VCS_TYPE,
255 username, password, environ, VCS_TYPE,
256 registry=self.registry, acl_repo_name=self.acl_repo_name):
256 registry=self.registry, acl_repo_name=self.acl_repo_name):
257 return username
257 return username
258 if username and password:
258 if username and password:
259 # we mark that we actually executed authentication once, at
259 # we mark that we actually executed authentication once, at
260 # that point we can use the alternative auth code
260 # that point we can use the alternative auth code
261 self.initial_call = False
261 self.initial_call = False
262
262
263 return self.build_authentication()
263 return self.build_authentication()
264
264
265 __call__ = authenticate
265 __call__ = authenticate
266
266
267
267
268 def calculate_version_hash():
268 def calculate_version_hash():
269 return md5(
269 return md5(
270 config.get('beaker.session.secret', '') +
270 config.get('beaker.session.secret', '') +
271 rhodecode.__version__)[:8]
271 rhodecode.__version__)[:8]
272
272
273
273
274 def get_current_lang(request):
274 def get_current_lang(request):
275 # NOTE(marcink): remove after pyramid move
275 # NOTE(marcink): remove after pyramid move
276 try:
276 try:
277 return translation.get_lang()[0]
277 return translation.get_lang()[0]
278 except:
278 except:
279 pass
279 pass
280
280
281 return getattr(request, '_LOCALE_', request.locale_name)
281 return getattr(request, '_LOCALE_', request.locale_name)
282
282
283
283
284 def attach_context_attributes(context, request, user_id):
284 def attach_context_attributes(context, request, user_id):
285 """
285 """
286 Attach variables into template context called `c`, please note that
286 Attach variables into template context called `c`, please note that
287 request could be pylons or pyramid request in here.
287 request could be pylons or pyramid request in here.
288 """
288 """
289
289
290 rc_config = SettingsModel().get_all_settings(cache=True)
290 rc_config = SettingsModel().get_all_settings(cache=True)
291
291
292 context.rhodecode_version = rhodecode.__version__
292 context.rhodecode_version = rhodecode.__version__
293 context.rhodecode_edition = config.get('rhodecode.edition')
293 context.rhodecode_edition = config.get('rhodecode.edition')
294 # unique secret + version does not leak the version but keep consistency
294 # unique secret + version does not leak the version but keep consistency
295 context.rhodecode_version_hash = calculate_version_hash()
295 context.rhodecode_version_hash = calculate_version_hash()
296
296
297 # Default language set for the incoming request
297 # Default language set for the incoming request
298 context.language = get_current_lang(request)
298 context.language = get_current_lang(request)
299
299
300 # Visual options
300 # Visual options
301 context.visual = AttributeDict({})
301 context.visual = AttributeDict({})
302
302
303 # DB stored Visual Items
303 # DB stored Visual Items
304 context.visual.show_public_icon = str2bool(
304 context.visual.show_public_icon = str2bool(
305 rc_config.get('rhodecode_show_public_icon'))
305 rc_config.get('rhodecode_show_public_icon'))
306 context.visual.show_private_icon = str2bool(
306 context.visual.show_private_icon = str2bool(
307 rc_config.get('rhodecode_show_private_icon'))
307 rc_config.get('rhodecode_show_private_icon'))
308 context.visual.stylify_metatags = str2bool(
308 context.visual.stylify_metatags = str2bool(
309 rc_config.get('rhodecode_stylify_metatags'))
309 rc_config.get('rhodecode_stylify_metatags'))
310 context.visual.dashboard_items = safe_int(
310 context.visual.dashboard_items = safe_int(
311 rc_config.get('rhodecode_dashboard_items', 100))
311 rc_config.get('rhodecode_dashboard_items', 100))
312 context.visual.admin_grid_items = safe_int(
312 context.visual.admin_grid_items = safe_int(
313 rc_config.get('rhodecode_admin_grid_items', 100))
313 rc_config.get('rhodecode_admin_grid_items', 100))
314 context.visual.repository_fields = str2bool(
314 context.visual.repository_fields = str2bool(
315 rc_config.get('rhodecode_repository_fields'))
315 rc_config.get('rhodecode_repository_fields'))
316 context.visual.show_version = str2bool(
316 context.visual.show_version = str2bool(
317 rc_config.get('rhodecode_show_version'))
317 rc_config.get('rhodecode_show_version'))
318 context.visual.use_gravatar = str2bool(
318 context.visual.use_gravatar = str2bool(
319 rc_config.get('rhodecode_use_gravatar'))
319 rc_config.get('rhodecode_use_gravatar'))
320 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
320 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
321 context.visual.default_renderer = rc_config.get(
321 context.visual.default_renderer = rc_config.get(
322 'rhodecode_markup_renderer', 'rst')
322 'rhodecode_markup_renderer', 'rst')
323 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
323 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
324 context.visual.rhodecode_support_url = \
324 context.visual.rhodecode_support_url = \
325 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
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 context.pre_code = rc_config.get('rhodecode_pre_code')
329 context.pre_code = rc_config.get('rhodecode_pre_code')
328 context.post_code = rc_config.get('rhodecode_post_code')
330 context.post_code = rc_config.get('rhodecode_post_code')
329 context.rhodecode_name = rc_config.get('rhodecode_title')
331 context.rhodecode_name = rc_config.get('rhodecode_title')
330 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
332 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
331 # if we have specified default_encoding in the request, it has more
333 # if we have specified default_encoding in the request, it has more
332 # priority
334 # priority
333 if request.GET.get('default_encoding'):
335 if request.GET.get('default_encoding'):
334 context.default_encodings.insert(0, request.GET.get('default_encoding'))
336 context.default_encodings.insert(0, request.GET.get('default_encoding'))
335 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
337 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
336
338
337 # INI stored
339 # INI stored
338 context.labs_active = str2bool(
340 context.labs_active = str2bool(
339 config.get('labs_settings_active', 'false'))
341 config.get('labs_settings_active', 'false'))
340 context.visual.allow_repo_location_change = str2bool(
342 context.visual.allow_repo_location_change = str2bool(
341 config.get('allow_repo_location_change', True))
343 config.get('allow_repo_location_change', True))
342 context.visual.allow_custom_hooks_settings = str2bool(
344 context.visual.allow_custom_hooks_settings = str2bool(
343 config.get('allow_custom_hooks_settings', True))
345 config.get('allow_custom_hooks_settings', True))
344 context.debug_style = str2bool(config.get('debug_style', False))
346 context.debug_style = str2bool(config.get('debug_style', False))
345
347
346 context.rhodecode_instanceid = config.get('instance_id')
348 context.rhodecode_instanceid = config.get('instance_id')
347
349
348 context.visual.cut_off_limit_diff = safe_int(
350 context.visual.cut_off_limit_diff = safe_int(
349 config.get('cut_off_limit_diff'))
351 config.get('cut_off_limit_diff'))
350 context.visual.cut_off_limit_file = safe_int(
352 context.visual.cut_off_limit_file = safe_int(
351 config.get('cut_off_limit_file'))
353 config.get('cut_off_limit_file'))
352
354
353 # AppEnlight
355 # AppEnlight
354 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
356 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
355 context.appenlight_api_public_key = config.get(
357 context.appenlight_api_public_key = config.get(
356 'appenlight.api_public_key', '')
358 'appenlight.api_public_key', '')
357 context.appenlight_server_url = config.get('appenlight.server_url', '')
359 context.appenlight_server_url = config.get('appenlight.server_url', '')
358
360
359 # JS template context
361 # JS template context
360 context.template_context = {
362 context.template_context = {
361 'repo_name': None,
363 'repo_name': None,
362 'repo_type': None,
364 'repo_type': None,
363 'repo_landing_commit': None,
365 'repo_landing_commit': None,
364 'rhodecode_user': {
366 'rhodecode_user': {
365 'username': None,
367 'username': None,
366 'email': None,
368 'email': None,
367 'notification_status': False
369 'notification_status': False
368 },
370 },
369 'visual': {
371 'visual': {
370 'default_renderer': None
372 'default_renderer': None
371 },
373 },
372 'commit_data': {
374 'commit_data': {
373 'commit_id': None
375 'commit_id': None
374 },
376 },
375 'pull_request_data': {'pull_request_id': None},
377 'pull_request_data': {'pull_request_id': None},
376 'timeago': {
378 'timeago': {
377 'refresh_time': 120 * 1000,
379 'refresh_time': 120 * 1000,
378 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
380 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
379 },
381 },
380 'pylons_dispatch': {
382 'pylons_dispatch': {
381 # 'controller': request.environ['pylons.routes_dict']['controller'],
383 # 'controller': request.environ['pylons.routes_dict']['controller'],
382 # 'action': request.environ['pylons.routes_dict']['action'],
384 # 'action': request.environ['pylons.routes_dict']['action'],
383 },
385 },
384 'pyramid_dispatch': {
386 'pyramid_dispatch': {
385
387
386 },
388 },
387 'extra': {'plugins': {}}
389 'extra': {'plugins': {}}
388 }
390 }
389 # END CONFIG VARS
391 # END CONFIG VARS
390
392
391 # TODO: This dosn't work when called from pylons compatibility tween.
393 # TODO: This dosn't work when called from pylons compatibility tween.
392 # Fix this and remove it from base controller.
394 # Fix this and remove it from base controller.
393 # context.repo_name = get_repo_slug(request) # can be empty
395 # context.repo_name = get_repo_slug(request) # can be empty
394
396
395 diffmode = 'sideside'
397 diffmode = 'sideside'
396 if request.GET.get('diffmode'):
398 if request.GET.get('diffmode'):
397 if request.GET['diffmode'] == 'unified':
399 if request.GET['diffmode'] == 'unified':
398 diffmode = 'unified'
400 diffmode = 'unified'
399 elif request.session.get('diffmode'):
401 elif request.session.get('diffmode'):
400 diffmode = request.session['diffmode']
402 diffmode = request.session['diffmode']
401
403
402 context.diffmode = diffmode
404 context.diffmode = diffmode
403
405
404 if request.session.get('diffmode') != diffmode:
406 if request.session.get('diffmode') != diffmode:
405 request.session['diffmode'] = diffmode
407 request.session['diffmode'] = diffmode
406
408
407 context.csrf_token = auth.get_csrf_token(session=request.session)
409 context.csrf_token = auth.get_csrf_token(session=request.session)
408 context.backends = rhodecode.BACKENDS.keys()
410 context.backends = rhodecode.BACKENDS.keys()
409 context.backends.sort()
411 context.backends.sort()
410 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
412 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
411
413
412 # NOTE(marcink): when migrated to pyramid we don't need to set this anymore,
414 # NOTE(marcink): when migrated to pyramid we don't need to set this anymore,
413 # given request will ALWAYS be pyramid one
415 # given request will ALWAYS be pyramid one
414 pyramid_request = pyramid.threadlocal.get_current_request()
416 pyramid_request = pyramid.threadlocal.get_current_request()
415 context.pyramid_request = pyramid_request
417 context.pyramid_request = pyramid_request
416
418
417 # web case
419 # web case
418 if hasattr(pyramid_request, 'user'):
420 if hasattr(pyramid_request, 'user'):
419 context.auth_user = pyramid_request.user
421 context.auth_user = pyramid_request.user
420 context.rhodecode_user = pyramid_request.user
422 context.rhodecode_user = pyramid_request.user
421
423
422 # api case
424 # api case
423 if hasattr(pyramid_request, 'rpc_user'):
425 if hasattr(pyramid_request, 'rpc_user'):
424 context.auth_user = pyramid_request.rpc_user
426 context.auth_user = pyramid_request.rpc_user
425 context.rhodecode_user = pyramid_request.rpc_user
427 context.rhodecode_user = pyramid_request.rpc_user
426
428
427 # attach the whole call context to the request
429 # attach the whole call context to the request
428 request.call_context = context
430 request.call_context = context
429
431
430
432
431 def get_auth_user(request):
433 def get_auth_user(request):
432 environ = request.environ
434 environ = request.environ
433 session = request.session
435 session = request.session
434
436
435 ip_addr = get_ip_addr(environ)
437 ip_addr = get_ip_addr(environ)
436 # make sure that we update permissions each time we call controller
438 # make sure that we update permissions each time we call controller
437 _auth_token = (request.GET.get('auth_token', '') or
439 _auth_token = (request.GET.get('auth_token', '') or
438 request.GET.get('api_key', ''))
440 request.GET.get('api_key', ''))
439
441
440 if _auth_token:
442 if _auth_token:
441 # when using API_KEY we assume user exists, and
443 # when using API_KEY we assume user exists, and
442 # doesn't need auth based on cookies.
444 # doesn't need auth based on cookies.
443 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
445 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
444 authenticated = False
446 authenticated = False
445 else:
447 else:
446 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
448 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
447 try:
449 try:
448 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
450 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
449 ip_addr=ip_addr)
451 ip_addr=ip_addr)
450 except UserCreationError as e:
452 except UserCreationError as e:
451 h.flash(e, 'error')
453 h.flash(e, 'error')
452 # container auth or other auth functions that create users
454 # container auth or other auth functions that create users
453 # on the fly can throw this exception signaling that there's
455 # on the fly can throw this exception signaling that there's
454 # issue with user creation, explanation should be provided
456 # issue with user creation, explanation should be provided
455 # in Exception itself. We then create a simple blank
457 # in Exception itself. We then create a simple blank
456 # AuthUser
458 # AuthUser
457 auth_user = AuthUser(ip_addr=ip_addr)
459 auth_user = AuthUser(ip_addr=ip_addr)
458
460
459 if password_changed(auth_user, session):
461 if password_changed(auth_user, session):
460 session.invalidate()
462 session.invalidate()
461 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
463 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
462 auth_user = AuthUser(ip_addr=ip_addr)
464 auth_user = AuthUser(ip_addr=ip_addr)
463
465
464 authenticated = cookie_store.get('is_authenticated')
466 authenticated = cookie_store.get('is_authenticated')
465
467
466 if not auth_user.is_authenticated and auth_user.is_user_object:
468 if not auth_user.is_authenticated and auth_user.is_user_object:
467 # user is not authenticated and not empty
469 # user is not authenticated and not empty
468 auth_user.set_authenticated(authenticated)
470 auth_user.set_authenticated(authenticated)
469
471
470 return auth_user
472 return auth_user
471
473
472
474
473 class BaseController(WSGIController):
475 class BaseController(WSGIController):
474
476
475 def __before__(self):
477 def __before__(self):
476 """
478 """
477 __before__ is called before controller methods and after __call__
479 __before__ is called before controller methods and after __call__
478 """
480 """
479 # on each call propagate settings calls into global settings.
481 # on each call propagate settings calls into global settings.
480 set_rhodecode_config(config)
482 set_rhodecode_config(config)
481 attach_context_attributes(c, request, self._rhodecode_user.user_id)
483 attach_context_attributes(c, request, self._rhodecode_user.user_id)
482
484
483 # TODO: Remove this when fixed in attach_context_attributes()
485 # TODO: Remove this when fixed in attach_context_attributes()
484 c.repo_name = get_repo_slug(request) # can be empty
486 c.repo_name = get_repo_slug(request) # can be empty
485
487
486 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
488 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
487 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
489 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
488 self.sa = meta.Session
490 self.sa = meta.Session
489 self.scm_model = ScmModel(self.sa)
491 self.scm_model = ScmModel(self.sa)
490
492
491 # set user language
493 # set user language
492 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
494 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
493 if user_lang:
495 if user_lang:
494 translation.set_lang(user_lang)
496 translation.set_lang(user_lang)
495 log.debug('set language to %s for user %s',
497 log.debug('set language to %s for user %s',
496 user_lang, self._rhodecode_user)
498 user_lang, self._rhodecode_user)
497
499
498 def _dispatch_redirect(self, with_url, environ, start_response):
500 def _dispatch_redirect(self, with_url, environ, start_response):
499 resp = HTTPFound(with_url)
501 resp = HTTPFound(with_url)
500 environ['SCRIPT_NAME'] = '' # handle prefix middleware
502 environ['SCRIPT_NAME'] = '' # handle prefix middleware
501 environ['PATH_INFO'] = with_url
503 environ['PATH_INFO'] = with_url
502 return resp(environ, start_response)
504 return resp(environ, start_response)
503
505
504 def __call__(self, environ, start_response):
506 def __call__(self, environ, start_response):
505 """Invoke the Controller"""
507 """Invoke the Controller"""
506 # WSGIController.__call__ dispatches to the Controller method
508 # WSGIController.__call__ dispatches to the Controller method
507 # the request is routed to. This routing information is
509 # the request is routed to. This routing information is
508 # available in environ['pylons.routes_dict']
510 # available in environ['pylons.routes_dict']
509 from rhodecode.lib import helpers as h
511 from rhodecode.lib import helpers as h
510
512
511 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
513 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
512 if environ.get('debugtoolbar.wants_pylons_context', False):
514 if environ.get('debugtoolbar.wants_pylons_context', False):
513 environ['debugtoolbar.pylons_context'] = c._current_obj()
515 environ['debugtoolbar.pylons_context'] = c._current_obj()
514
516
515 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
517 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
516 environ['pylons.routes_dict']['action']])
518 environ['pylons.routes_dict']['action']])
517
519
518 self.rc_config = SettingsModel().get_all_settings(cache=True)
520 self.rc_config = SettingsModel().get_all_settings(cache=True)
519 self.ip_addr = get_ip_addr(environ)
521 self.ip_addr = get_ip_addr(environ)
520
522
521 # The rhodecode auth user is looked up and passed through the
523 # The rhodecode auth user is looked up and passed through the
522 # environ by the pylons compatibility tween in pyramid.
524 # environ by the pylons compatibility tween in pyramid.
523 # So we can just grab it from there.
525 # So we can just grab it from there.
524 auth_user = environ['rc_auth_user']
526 auth_user = environ['rc_auth_user']
525
527
526 # set globals for auth user
528 # set globals for auth user
527 request.user = auth_user
529 request.user = auth_user
528 self._rhodecode_user = auth_user
530 self._rhodecode_user = auth_user
529
531
530 log.info('IP: %s User: %s accessed %s [%s]' % (
532 log.info('IP: %s User: %s accessed %s [%s]' % (
531 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
533 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
532 _route_name)
534 _route_name)
533 )
535 )
534
536
535 user_obj = auth_user.get_instance()
537 user_obj = auth_user.get_instance()
536 if user_obj and user_obj.user_data.get('force_password_change'):
538 if user_obj and user_obj.user_data.get('force_password_change'):
537 h.flash('You are required to change your password', 'warning',
539 h.flash('You are required to change your password', 'warning',
538 ignore_duplicate=True)
540 ignore_duplicate=True)
539 return self._dispatch_redirect(
541 return self._dispatch_redirect(
540 url('my_account_password'), environ, start_response)
542 url('my_account_password'), environ, start_response)
541
543
542 return WSGIController.__call__(self, environ, start_response)
544 return WSGIController.__call__(self, environ, start_response)
543
545
544
546
545 class BaseRepoController(BaseController):
547 class BaseRepoController(BaseController):
546 """
548 """
547 Base class for controllers responsible for loading all needed data for
549 Base class for controllers responsible for loading all needed data for
548 repository loaded items are
550 repository loaded items are
549
551
550 c.rhodecode_repo: instance of scm repository
552 c.rhodecode_repo: instance of scm repository
551 c.rhodecode_db_repo: instance of db
553 c.rhodecode_db_repo: instance of db
552 c.repository_requirements_missing: shows that repository specific data
554 c.repository_requirements_missing: shows that repository specific data
553 could not be displayed due to the missing requirements
555 could not be displayed due to the missing requirements
554 c.repository_pull_requests: show number of open pull requests
556 c.repository_pull_requests: show number of open pull requests
555 """
557 """
556
558
557 def __before__(self):
559 def __before__(self):
558 super(BaseRepoController, self).__before__()
560 super(BaseRepoController, self).__before__()
559 if c.repo_name: # extracted from routes
561 if c.repo_name: # extracted from routes
560 db_repo = Repository.get_by_repo_name(c.repo_name)
562 db_repo = Repository.get_by_repo_name(c.repo_name)
561 if not db_repo:
563 if not db_repo:
562 return
564 return
563
565
564 log.debug(
566 log.debug(
565 'Found repository in database %s with state `%s`',
567 'Found repository in database %s with state `%s`',
566 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
568 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
567 route = getattr(request.environ.get('routes.route'), 'name', '')
569 route = getattr(request.environ.get('routes.route'), 'name', '')
568
570
569 # allow to delete repos that are somehow damages in filesystem
571 # allow to delete repos that are somehow damages in filesystem
570 if route in ['delete_repo']:
572 if route in ['delete_repo']:
571 return
573 return
572
574
573 if db_repo.repo_state in [Repository.STATE_PENDING]:
575 if db_repo.repo_state in [Repository.STATE_PENDING]:
574 if route in ['repo_creating_home']:
576 if route in ['repo_creating_home']:
575 return
577 return
576 check_url = url('repo_creating_home', repo_name=c.repo_name)
578 check_url = url('repo_creating_home', repo_name=c.repo_name)
577 return redirect(check_url)
579 return redirect(check_url)
578
580
579 self.rhodecode_db_repo = db_repo
581 self.rhodecode_db_repo = db_repo
580
582
581 missing_requirements = False
583 missing_requirements = False
582 try:
584 try:
583 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
585 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
584 except RepositoryRequirementError as e:
586 except RepositoryRequirementError as e:
585 missing_requirements = True
587 missing_requirements = True
586 self._handle_missing_requirements(e)
588 self._handle_missing_requirements(e)
587
589
588 if self.rhodecode_repo is None and not missing_requirements:
590 if self.rhodecode_repo is None and not missing_requirements:
589 log.error('%s this repository is present in database but it '
591 log.error('%s this repository is present in database but it '
590 'cannot be created as an scm instance', c.repo_name)
592 'cannot be created as an scm instance', c.repo_name)
591
593
592 h.flash(_(
594 h.flash(_(
593 "The repository at %(repo_name)s cannot be located.") %
595 "The repository at %(repo_name)s cannot be located.") %
594 {'repo_name': c.repo_name},
596 {'repo_name': c.repo_name},
595 category='error', ignore_duplicate=True)
597 category='error', ignore_duplicate=True)
596 redirect(h.route_path('home'))
598 redirect(h.route_path('home'))
597
599
598 # update last change according to VCS data
600 # update last change according to VCS data
599 if not missing_requirements:
601 if not missing_requirements:
600 commit = db_repo.get_commit(
602 commit = db_repo.get_commit(
601 pre_load=["author", "date", "message", "parents"])
603 pre_load=["author", "date", "message", "parents"])
602 db_repo.update_commit_cache(commit)
604 db_repo.update_commit_cache(commit)
603
605
604 # Prepare context
606 # Prepare context
605 c.rhodecode_db_repo = db_repo
607 c.rhodecode_db_repo = db_repo
606 c.rhodecode_repo = self.rhodecode_repo
608 c.rhodecode_repo = self.rhodecode_repo
607 c.repository_requirements_missing = missing_requirements
609 c.repository_requirements_missing = missing_requirements
608
610
609 self._update_global_counters(self.scm_model, db_repo)
611 self._update_global_counters(self.scm_model, db_repo)
610
612
611 def _update_global_counters(self, scm_model, db_repo):
613 def _update_global_counters(self, scm_model, db_repo):
612 """
614 """
613 Base variables that are exposed to every page of repository
615 Base variables that are exposed to every page of repository
614 """
616 """
615 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
617 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
616
618
617 def _handle_missing_requirements(self, error):
619 def _handle_missing_requirements(self, error):
618 self.rhodecode_repo = None
620 self.rhodecode_repo = None
619 log.error(
621 log.error(
620 'Requirements are missing for repository %s: %s',
622 'Requirements are missing for repository %s: %s',
621 c.repo_name, error.message)
623 c.repo_name, error.message)
622
624
623 summary_url = h.route_path('repo_summary', repo_name=c.repo_name)
625 summary_url = h.route_path('repo_summary', repo_name=c.repo_name)
624 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
626 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
625 settings_update_url = url('repo', repo_name=c.repo_name)
627 settings_update_url = url('repo', repo_name=c.repo_name)
626 path = request.path
628 path = request.path
627 should_redirect = (
629 should_redirect = (
628 path not in (summary_url, settings_update_url)
630 path not in (summary_url, settings_update_url)
629 and '/settings' not in path or path == statistics_url
631 and '/settings' not in path or path == statistics_url
630 )
632 )
631 if should_redirect:
633 if should_redirect:
632 redirect(summary_url)
634 redirect(summary_url)
@@ -1,101 +1,101 b''
1 // Global keyboard bindings
1 // Global keyboard bindings
2
2
3 function setRCMouseBindings(repoName, repoLandingRev) {
3 function setRCMouseBindings(repoName, repoLandingRev) {
4
4
5 /** custom callback for supressing mousetrap from firing */
5 /** custom callback for supressing mousetrap from firing */
6 Mousetrap.stopCallback = function(e, element) {
6 Mousetrap.stopCallback = function(e, element) {
7 // if the element has the class "mousetrap" then no need to stop
7 // if the element has the class "mousetrap" then no need to stop
8 if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
8 if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
9 return false;
9 return false;
10 }
10 }
11
11
12 // stop for input, select, and textarea
12 // stop for input, select, and textarea
13 return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
13 return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
14 };
14 };
15
15
16 // general help "?"
16 // general help "?"
17 Mousetrap.bind(['?'], function(e) {
17 Mousetrap.bind(['?'], function(e) {
18 $('#help_kb').modal({});
18 $('#help_kb').modal({});
19 });
19 });
20
20
21 // / open the quick filter
21 // / open the quick filter
22 Mousetrap.bind(['/'], function(e) {
22 Mousetrap.bind(['/'], function(e) {
23 $('#repo_switcher').select2('open');
23 $('#repo_switcher').select2('open');
24
24
25 // return false to prevent default browser behavior
25 // return false to prevent default browser behavior
26 // and stop event from bubbling
26 // and stop event from bubbling
27 return false;
27 return false;
28 });
28 });
29
29
30 // ctrl/command+b, show the the main bar
30 // ctrl/command+b, show the the main bar
31 Mousetrap.bind(['command+b', 'ctrl+b'], function(e) {
31 Mousetrap.bind(['command+b', 'ctrl+b'], function(e) {
32 var $headerInner = $('#header-inner'),
32 var $headerInner = $('#header-inner'),
33 $content = $('#content');
33 $content = $('#content');
34 if ($headerInner.hasClass('hover') && $content.hasClass('hover')) {
34 if ($headerInner.hasClass('hover') && $content.hasClass('hover')) {
35 $headerInner.removeClass('hover');
35 $headerInner.removeClass('hover');
36 $content.removeClass('hover');
36 $content.removeClass('hover');
37 } else {
37 } else {
38 $headerInner.addClass('hover');
38 $headerInner.addClass('hover');
39 $content.addClass('hover');
39 $content.addClass('hover');
40 }
40 }
41 return false;
41 return false;
42 });
42 });
43
43
44 // general nav g + action
44 // general nav g + action
45 Mousetrap.bind(['g h'], function(e) {
45 Mousetrap.bind(['g h'], function(e) {
46 window.location = pyroutes.url('home');
46 window.location = pyroutes.url('home');
47 });
47 });
48 Mousetrap.bind(['g g'], function(e) {
48 Mousetrap.bind(['g g'], function(e) {
49 window.location = pyroutes.url('gists_show', {'private': 1});
49 window.location = pyroutes.url('gists_show', {'private': 1});
50 });
50 });
51 Mousetrap.bind(['g G'], function(e) {
51 Mousetrap.bind(['g G'], function(e) {
52 window.location = pyroutes.url('gists_show', {'public': 1});
52 window.location = pyroutes.url('gists_show', {'public': 1});
53 });
53 });
54 Mousetrap.bind(['n g'], function(e) {
54 Mousetrap.bind(['n g'], function(e) {
55 window.location = pyroutes.url('gists_new');
55 window.location = pyroutes.url('gists_new');
56 });
56 });
57 Mousetrap.bind(['n r'], function(e) {
57 Mousetrap.bind(['n r'], function(e) {
58 window.location = pyroutes.url('new_repo');
58 window.location = pyroutes.url('new_repo');
59 });
59 });
60
60
61 if (repoName && repoName != '') {
61 if (repoName && repoName != '') {
62 // nav in repo context
62 // nav in repo context
63 Mousetrap.bind(['g s'], function(e) {
63 Mousetrap.bind(['g s'], function(e) {
64 window.location = pyroutes.url(
64 window.location = pyroutes.url(
65 'repo_summary', {'repo_name': repoName});
65 'repo_summary', {'repo_name': repoName});
66 });
66 });
67 Mousetrap.bind(['g c'], function(e) {
67 Mousetrap.bind(['g c'], function(e) {
68 window.location = pyroutes.url(
68 window.location = pyroutes.url(
69 'changelog_home', {'repo_name': repoName});
69 'repo_changelog', {'repo_name': repoName});
70 });
70 });
71 Mousetrap.bind(['g F'], function(e) {
71 Mousetrap.bind(['g F'], function(e) {
72 window.location = pyroutes.url(
72 window.location = pyroutes.url(
73 'repo_files',
73 'repo_files',
74 {
74 {
75 'repo_name': repoName,
75 'repo_name': repoName,
76 'commit_id': repoLandingRev,
76 'commit_id': repoLandingRev,
77 'f_path': '',
77 'f_path': '',
78 'search': '1'
78 'search': '1'
79 });
79 });
80 });
80 });
81 Mousetrap.bind(['g f'], function(e) {
81 Mousetrap.bind(['g f'], function(e) {
82 window.location = pyroutes.url(
82 window.location = pyroutes.url(
83 'repo_files',
83 'repo_files',
84 {
84 {
85 'repo_name': repoName,
85 'repo_name': repoName,
86 'commit_id': repoLandingRev,
86 'commit_id': repoLandingRev,
87 'f_path': ''
87 'f_path': ''
88 });
88 });
89 });
89 });
90 Mousetrap.bind(['g o'], function(e) {
90 Mousetrap.bind(['g o'], function(e) {
91 window.location = pyroutes.url(
91 window.location = pyroutes.url(
92 'edit_repo', {'repo_name': repoName});
92 'edit_repo', {'repo_name': repoName});
93 });
93 });
94 Mousetrap.bind(['g O'], function(e) {
94 Mousetrap.bind(['g O'], function(e) {
95 window.location = pyroutes.url(
95 window.location = pyroutes.url(
96 'edit_repo_perms', {'repo_name': repoName});
96 'edit_repo_perms', {'repo_name': repoName});
97 });
97 });
98 }
98 }
99 }
99 }
100
100
101 setRCMouseBindings(templateContext.repo_name, templateContext.repo_landing_commit);
101 setRCMouseBindings(templateContext.repo_name, templateContext.repo_landing_commit);
@@ -1,198 +1,198 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('new_repo', '/_admin/create_repository', []);
15 pyroutes.register('new_repo', '/_admin/create_repository', []);
16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
17 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
17 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
18 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
18 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
19 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
19 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
20 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
20 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
21 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
21 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
22 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
22 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
23 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
23 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
24 pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
24 pyroutes.register('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']);
25 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
25 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
26 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
26 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
27 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
27 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
28 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
28 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
29 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
29 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
30 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
30 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
31 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
31 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
32 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
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 pyroutes.register('favicon', '/favicon.ico', []);
33 pyroutes.register('favicon', '/favicon.ico', []);
37 pyroutes.register('robots', '/robots.txt', []);
34 pyroutes.register('robots', '/robots.txt', []);
38 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
35 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
39 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
36 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
40 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
37 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
41 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
38 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
42 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
39 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
43 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
40 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
44 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
41 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
45 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
42 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
46 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
43 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
47 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
44 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
48 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
45 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
49 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
46 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
50 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
47 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
51 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
48 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
52 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
49 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
53 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
50 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
54 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
51 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
55 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
52 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
56 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
53 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
57 pyroutes.register('admin_home', '/_admin', []);
54 pyroutes.register('admin_home', '/_admin', []);
58 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
55 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
59 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
56 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
60 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
57 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
61 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
58 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
62 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
59 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
63 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
60 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
64 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
61 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
65 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
62 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
66 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
63 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
67 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
64 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
68 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
65 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
69 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
66 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
70 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
67 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
71 pyroutes.register('users', '/_admin/users', []);
68 pyroutes.register('users', '/_admin/users', []);
72 pyroutes.register('users_data', '/_admin/users_data', []);
69 pyroutes.register('users_data', '/_admin/users_data', []);
73 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
70 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
74 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
71 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
75 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
72 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
76 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
73 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
77 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
74 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
78 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
75 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
79 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
76 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
80 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
77 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
81 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
78 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
82 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
79 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
83 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
80 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
84 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
81 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
85 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
82 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
86 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
83 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
87 pyroutes.register('channelstream_proxy', '/_channelstream', []);
84 pyroutes.register('channelstream_proxy', '/_channelstream', []);
88 pyroutes.register('login', '/_admin/login', []);
85 pyroutes.register('login', '/_admin/login', []);
89 pyroutes.register('logout', '/_admin/logout', []);
86 pyroutes.register('logout', '/_admin/logout', []);
90 pyroutes.register('register', '/_admin/register', []);
87 pyroutes.register('register', '/_admin/register', []);
91 pyroutes.register('reset_password', '/_admin/password_reset', []);
88 pyroutes.register('reset_password', '/_admin/password_reset', []);
92 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
89 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
93 pyroutes.register('home', '/', []);
90 pyroutes.register('home', '/', []);
94 pyroutes.register('user_autocomplete_data', '/_users', []);
91 pyroutes.register('user_autocomplete_data', '/_users', []);
95 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
92 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
96 pyroutes.register('repo_list_data', '/_repos', []);
93 pyroutes.register('repo_list_data', '/_repos', []);
97 pyroutes.register('goto_switcher_data', '/_goto_data', []);
94 pyroutes.register('goto_switcher_data', '/_goto_data', []);
98 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
95 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
99 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
96 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
100 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
97 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
101 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
98 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
102 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
99 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
103 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
100 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
104 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
101 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
105 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
102 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
106 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
103 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
107 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
104 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
108 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
105 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
109 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
106 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
110 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
107 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
111 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
108 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
112 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
109 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
113 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
110 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
114 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
111 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
115 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
112 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
116 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
113 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
117 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
114 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
118 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
115 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
119 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
116 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
120 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
117 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
121 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
118 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
122 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
119 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
123 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
120 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
124 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
121 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
125 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
122 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
126 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
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 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
127 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
128 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
128 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
129 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
129 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
130 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
130 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
131 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
131 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
132 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
132 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
133 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
133 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
134 pyroutes.register('changeset_children', '/%(repo_name)s/changeset_children/%(revision)s', ['repo_name', 'revision']);
134 pyroutes.register('changeset_children', '/%(repo_name)s/changeset_children/%(revision)s', ['repo_name', 'revision']);
135 pyroutes.register('changeset_parents', '/%(repo_name)s/changeset_parents/%(revision)s', ['repo_name', 'revision']);
135 pyroutes.register('changeset_parents', '/%(repo_name)s/changeset_parents/%(revision)s', ['repo_name', 'revision']);
136 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
136 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
137 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
137 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
138 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
138 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
139 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
139 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
140 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
140 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
141 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
141 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
142 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
142 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
143 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
143 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
144 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
144 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
145 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
145 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
146 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
146 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
147 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
147 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
148 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
148 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
149 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
149 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
150 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
150 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
151 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
151 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
152 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
152 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
153 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
153 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
154 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
154 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
155 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
155 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
156 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
156 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
157 pyroutes.register('search', '/_admin/search', []);
157 pyroutes.register('search', '/_admin/search', []);
158 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
158 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
159 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
159 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
160 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
160 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
161 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
161 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
162 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
162 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
163 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
163 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
164 pyroutes.register('my_account_password_update', '/_admin/my_account/password', []);
164 pyroutes.register('my_account_password_update', '/_admin/my_account/password', []);
165 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
165 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
166 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
166 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
167 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
167 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
168 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
168 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
169 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
169 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
170 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
170 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
171 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
171 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
172 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
172 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
173 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
173 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
174 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
174 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
175 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
175 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
176 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
176 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
177 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
177 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
178 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
178 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
179 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
179 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
180 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
180 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
181 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
181 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
182 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
182 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
183 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
183 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
184 pyroutes.register('gists_show', '/_admin/gists', []);
184 pyroutes.register('gists_show', '/_admin/gists', []);
185 pyroutes.register('gists_new', '/_admin/gists/new', []);
185 pyroutes.register('gists_new', '/_admin/gists/new', []);
186 pyroutes.register('gists_create', '/_admin/gists/create', []);
186 pyroutes.register('gists_create', '/_admin/gists/create', []);
187 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
187 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
188 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
188 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
189 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
189 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
190 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
190 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
191 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
191 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
192 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
192 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
193 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
193 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
194 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
194 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
195 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
195 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
196 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
196 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
197 pyroutes.register('apiv2', '/_admin/api', []);
197 pyroutes.register('apiv2', '/_admin/api', []);
198 }
198 }
@@ -1,173 +1,173 b''
1 // # Copyright (C) 2016-2017 RhodeCode GmbH
1 // # Copyright (C) 2016-2017 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19
19
20 var CommitsController = function () {
20 var CommitsController = function () {
21 var self = this;
21 var self = this;
22 this.$graphCanvas = $('#graph_canvas');
22 this.$graphCanvas = $('#graph_canvas');
23 this.$commitCounter = $('#commit-counter');
23 this.$commitCounter = $('#commit-counter');
24
24
25 this.getCurrentGraphData = function () {
25 this.getCurrentGraphData = function () {
26 // raw form
26 // raw form
27 return self.$graphCanvas.data('commits');
27 return self.$graphCanvas.data('commits');
28 };
28 };
29
29
30 this.setLabelText = function (graphData) {
30 this.setLabelText = function (graphData) {
31 var shown = $('.commit_hash').length;
31 var shown = $('.commit_hash').length;
32 var total = self.$commitCounter.data('total');
32 var total = self.$commitCounter.data('total');
33
33
34 if (shown == 1) {
34 if (shown == 1) {
35 var text = _gettext('showing {0} out of {1} commit').format(shown, total);
35 var text = _gettext('showing {0} out of {1} commit').format(shown, total);
36 } else {
36 } else {
37 var text = _gettext('showing {0} out of {1} commits').format(shown, total);
37 var text = _gettext('showing {0} out of {1} commits').format(shown, total);
38 }
38 }
39 self.$commitCounter.html(text)
39 self.$commitCounter.html(text)
40 };
40 };
41
41
42 this.reloadGraph = function (chunk) {
42 this.reloadGraph = function (chunk) {
43 chunk = chunk || 'next';
43 chunk = chunk || 'next';
44
44
45 // reset state on re-render !
45 // reset state on re-render !
46 self.$graphCanvas.html('');
46 self.$graphCanvas.html('');
47
47
48 var edgeData = $("[data-graph]").data('graph') || this.$graphCanvas.data('graph') || [];
48 var edgeData = $("[data-graph]").data('graph') || this.$graphCanvas.data('graph') || [];
49
49
50 // Determine max number of edges per row in graph
50 // Determine max number of edges per row in graph
51 var edgeCount = 1;
51 var edgeCount = 1;
52 $.each(edgeData, function (i, item) {
52 $.each(edgeData, function (i, item) {
53 $.each(item[2], function (key, value) {
53 $.each(item[2], function (key, value) {
54 if (value[1] > edgeCount) {
54 if (value[1] > edgeCount) {
55 edgeCount = value[1];
55 edgeCount = value[1];
56 }
56 }
57 });
57 });
58 });
58 });
59
59
60 var x_step = Math.min(10, Math.floor(86 / edgeCount));
60 var x_step = Math.min(10, Math.floor(86 / edgeCount));
61 var graph_options = {
61 var graph_options = {
62 width: 100,
62 width: 100,
63 height: $('#changesets').find('.commits-range').height(),
63 height: $('#changesets').find('.commits-range').height(),
64 x_step: x_step,
64 x_step: x_step,
65 y_step: 42,
65 y_step: 42,
66 dotRadius: 3.5,
66 dotRadius: 3.5,
67 lineWidth: 2.5
67 lineWidth: 2.5
68 };
68 };
69
69
70 var prevCommitsData = this.$graphCanvas.data('commits') || [];
70 var prevCommitsData = this.$graphCanvas.data('commits') || [];
71 var nextCommitsData = $("[data-graph]").data('commits') || [];
71 var nextCommitsData = $("[data-graph]").data('commits') || [];
72
72
73 if (chunk == 'next') {
73 if (chunk == 'next') {
74 var commitData = $.merge(prevCommitsData, nextCommitsData);
74 var commitData = $.merge(prevCommitsData, nextCommitsData);
75 } else {
75 } else {
76 var commitData = $.merge(nextCommitsData, prevCommitsData);
76 var commitData = $.merge(nextCommitsData, prevCommitsData);
77 }
77 }
78
78
79 this.$graphCanvas.data('graph', edgeData);
79 this.$graphCanvas.data('graph', edgeData);
80 this.$graphCanvas.data('commits', commitData);
80 this.$graphCanvas.data('commits', commitData);
81
81
82 // destroy dynamic loaded graph
82 // destroy dynamic loaded graph
83 $("[data-graph]").remove();
83 $("[data-graph]").remove();
84
84
85 this.$graphCanvas.commits(graph_options);
85 this.$graphCanvas.commits(graph_options);
86
86
87 this.setLabelText(edgeData);
87 this.setLabelText(edgeData);
88 if ($('.load-more-commits').find('.prev-commits').get(0)) {
88 if ($('.load-more-commits').find('.prev-commits').get(0)) {
89 var padding = 75;
89 var padding = 75;
90
90
91 } else {
91 } else {
92 var padding = 43;
92 var padding = 43;
93 }
93 }
94 $('#graph_nodes').css({'padding-top': padding});
94 $('#graph_nodes').css({'padding-top': padding});
95 };
95 };
96
96
97 this.getChunkUrl = function (page, chunk, branch) {
97 this.getChunkUrl = function (page, chunk, branch) {
98 var urlData = {
98 var urlData = {
99 'repo_name': templateContext.repo_name,
99 'repo_name': templateContext.repo_name,
100 'page': page,
100 'page': page,
101 'chunk': chunk
101 'chunk': chunk
102 };
102 };
103
103
104 if (branch !== undefined && branch !== '') {
104 if (branch !== undefined && branch !== '') {
105 urlData['branch'] = branch;
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 this.loadNext = function (node, page, branch) {
111 this.loadNext = function (node, page, branch) {
112 var loadUrl = this.getChunkUrl(page, 'next', branch);
112 var loadUrl = this.getChunkUrl(page, 'next', branch);
113 var postData = {'graph': JSON.stringify(this.getCurrentGraphData())};
113 var postData = {'graph': JSON.stringify(this.getCurrentGraphData())};
114
114
115 $.post(loadUrl, postData, function (data) {
115 $.post(loadUrl, postData, function (data) {
116 $(node).closest('tbody').append(data);
116 $(node).closest('tbody').append(data);
117 $(node).closest('td').remove();
117 $(node).closest('td').remove();
118 self.reloadGraph('next');
118 self.reloadGraph('next');
119 })
119 })
120 };
120 };
121
121
122 this.loadPrev = function (node, page, branch) {
122 this.loadPrev = function (node, page, branch) {
123 var loadUrl = this.getChunkUrl(page, 'prev', branch);
123 var loadUrl = this.getChunkUrl(page, 'prev', branch);
124 var postData = {'graph': JSON.stringify(this.getCurrentGraphData())};
124 var postData = {'graph': JSON.stringify(this.getCurrentGraphData())};
125
125
126 $.post(loadUrl, postData, function (data) {
126 $.post(loadUrl, postData, function (data) {
127 $(node).closest('tbody').prepend(data);
127 $(node).closest('tbody').prepend(data);
128 $(node).closest('td').remove();
128 $(node).closest('td').remove();
129 self.reloadGraph('prev');
129 self.reloadGraph('prev');
130 })
130 })
131 };
131 };
132
132
133 this.expandCommit = function (node) {
133 this.expandCommit = function (node) {
134
134
135 var target_expand = $(node);
135 var target_expand = $(node);
136 var cid = target_expand.data('commitId');
136 var cid = target_expand.data('commitId');
137
137
138 if (target_expand.hasClass('open')) {
138 if (target_expand.hasClass('open')) {
139 $('#c-' + cid).css({
139 $('#c-' + cid).css({
140 'height': '1.5em',
140 'height': '1.5em',
141 'white-space': 'nowrap',
141 'white-space': 'nowrap',
142 'text-overflow': 'ellipsis',
142 'text-overflow': 'ellipsis',
143 'overflow': 'hidden'
143 'overflow': 'hidden'
144 });
144 });
145 $('#t-' + cid).css({
145 $('#t-' + cid).css({
146 'height': 'auto',
146 'height': 'auto',
147 'line-height': '.9em',
147 'line-height': '.9em',
148 'text-overflow': 'ellipsis',
148 'text-overflow': 'ellipsis',
149 'overflow': 'hidden',
149 'overflow': 'hidden',
150 'white-space': 'nowrap'
150 'white-space': 'nowrap'
151 });
151 });
152 target_expand.removeClass('open');
152 target_expand.removeClass('open');
153 }
153 }
154 else {
154 else {
155 $('#c-' + cid).css({
155 $('#c-' + cid).css({
156 'height': 'auto',
156 'height': 'auto',
157 'white-space': 'pre-line',
157 'white-space': 'pre-line',
158 'text-overflow': 'initial',
158 'text-overflow': 'initial',
159 'overflow': 'visible'
159 'overflow': 'visible'
160 });
160 });
161 $('#t-' + cid).css({
161 $('#t-' + cid).css({
162 'height': 'auto',
162 'height': 'auto',
163 'max-height': 'none',
163 'max-height': 'none',
164 'text-overflow': 'initial',
164 'text-overflow': 'initial',
165 'overflow': 'visible',
165 'overflow': 'visible',
166 'white-space': 'normal'
166 'white-space': 'normal'
167 });
167 });
168 target_expand.addClass('open');
168 target_expand.addClass('open');
169 }
169 }
170 // redraw the graph
170 // redraw the graph
171 self.reloadGraph();
171 self.reloadGraph();
172 }
172 }
173 };
173 };
@@ -1,600 +1,600 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.mako"/>
2 <%inherit file="root.mako"/>
3
3
4 <div class="outerwrapper">
4 <div class="outerwrapper">
5 <!-- HEADER -->
5 <!-- HEADER -->
6 <div class="header">
6 <div class="header">
7 <div id="header-inner" class="wrapper">
7 <div id="header-inner" class="wrapper">
8 <div id="logo">
8 <div id="logo">
9 <div class="logo-wrapper">
9 <div class="logo-wrapper">
10 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
10 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 </div>
11 </div>
12 %if c.rhodecode_name:
12 %if c.rhodecode_name:
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 %endif
14 %endif
15 </div>
15 </div>
16 <!-- MENU BAR NAV -->
16 <!-- MENU BAR NAV -->
17 ${self.menu_bar_nav()}
17 ${self.menu_bar_nav()}
18 <!-- END MENU BAR NAV -->
18 <!-- END MENU BAR NAV -->
19 </div>
19 </div>
20 </div>
20 </div>
21 ${self.menu_bar_subnav()}
21 ${self.menu_bar_subnav()}
22 <!-- END HEADER -->
22 <!-- END HEADER -->
23
23
24 <!-- CONTENT -->
24 <!-- CONTENT -->
25 <div id="content" class="wrapper">
25 <div id="content" class="wrapper">
26
26
27 <rhodecode-toast id="notifications"></rhodecode-toast>
27 <rhodecode-toast id="notifications"></rhodecode-toast>
28
28
29 <div class="main">
29 <div class="main">
30 ${next.main()}
30 ${next.main()}
31 </div>
31 </div>
32 </div>
32 </div>
33 <!-- END CONTENT -->
33 <!-- END CONTENT -->
34
34
35 </div>
35 </div>
36 <!-- FOOTER -->
36 <!-- FOOTER -->
37 <div id="footer">
37 <div id="footer">
38 <div id="footer-inner" class="title wrapper">
38 <div id="footer-inner" class="title wrapper">
39 <div>
39 <div>
40 <p class="footer-link-right">
40 <p class="footer-link-right">
41 % if c.visual.show_version:
41 % if c.visual.show_version:
42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
43 % endif
43 % endif
44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
45 % if c.visual.rhodecode_support_url:
45 % if c.visual.rhodecode_support_url:
46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
47 % endif
47 % endif
48 </p>
48 </p>
49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
50 <p class="server-instance" style="display:${sid}">
50 <p class="server-instance" style="display:${sid}">
51 ## display hidden instance ID if specially defined
51 ## display hidden instance ID if specially defined
52 % if c.rhodecode_instanceid:
52 % if c.rhodecode_instanceid:
53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
54 % endif
54 % endif
55 </p>
55 </p>
56 </div>
56 </div>
57 </div>
57 </div>
58 </div>
58 </div>
59
59
60 <!-- END FOOTER -->
60 <!-- END FOOTER -->
61
61
62 ### MAKO DEFS ###
62 ### MAKO DEFS ###
63
63
64 <%def name="menu_bar_subnav()">
64 <%def name="menu_bar_subnav()">
65 </%def>
65 </%def>
66
66
67 <%def name="breadcrumbs(class_='breadcrumbs')">
67 <%def name="breadcrumbs(class_='breadcrumbs')">
68 <div class="${class_}">
68 <div class="${class_}">
69 ${self.breadcrumbs_links()}
69 ${self.breadcrumbs_links()}
70 </div>
70 </div>
71 </%def>
71 </%def>
72
72
73 <%def name="admin_menu()">
73 <%def name="admin_menu()">
74 <ul class="admin_menu submenu">
74 <ul class="admin_menu submenu">
75 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
75 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
76 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
76 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
79 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
79 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
80 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
80 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
85 </ul>
85 </ul>
86 </%def>
86 </%def>
87
87
88
88
89 <%def name="dt_info_panel(elements)">
89 <%def name="dt_info_panel(elements)">
90 <dl class="dl-horizontal">
90 <dl class="dl-horizontal">
91 %for dt, dd, title, show_items in elements:
91 %for dt, dd, title, show_items in elements:
92 <dt>${dt}:</dt>
92 <dt>${dt}:</dt>
93 <dd title="${h.tooltip(title)}">
93 <dd title="${h.tooltip(title)}">
94 %if callable(dd):
94 %if callable(dd):
95 ## allow lazy evaluation of elements
95 ## allow lazy evaluation of elements
96 ${dd()}
96 ${dd()}
97 %else:
97 %else:
98 ${dd}
98 ${dd}
99 %endif
99 %endif
100 %if show_items:
100 %if show_items:
101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
102 %endif
102 %endif
103 </dd>
103 </dd>
104
104
105 %if show_items:
105 %if show_items:
106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
107 %for item in show_items:
107 %for item in show_items:
108 <dt></dt>
108 <dt></dt>
109 <dd>${item}</dd>
109 <dd>${item}</dd>
110 %endfor
110 %endfor
111 </div>
111 </div>
112 %endif
112 %endif
113
113
114 %endfor
114 %endfor
115 </dl>
115 </dl>
116 </%def>
116 </%def>
117
117
118
118
119 <%def name="gravatar(email, size=16)">
119 <%def name="gravatar(email, size=16)">
120 <%
120 <%
121 if (size > 16):
121 if (size > 16):
122 gravatar_class = 'gravatar gravatar-large'
122 gravatar_class = 'gravatar gravatar-large'
123 else:
123 else:
124 gravatar_class = 'gravatar'
124 gravatar_class = 'gravatar'
125 %>
125 %>
126 <%doc>
126 <%doc>
127 TODO: johbo: For now we serve double size images to make it smooth
127 TODO: johbo: For now we serve double size images to make it smooth
128 for retina. This is how it worked until now. Should be replaced
128 for retina. This is how it worked until now. Should be replaced
129 with a better solution at some point.
129 with a better solution at some point.
130 </%doc>
130 </%doc>
131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
132 </%def>
132 </%def>
133
133
134
134
135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
136 <% email = h.email_or_none(contact) %>
136 <% email = h.email_or_none(contact) %>
137 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
137 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
138 ${self.gravatar(email, size)}
138 ${self.gravatar(email, size)}
139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
140 </div>
140 </div>
141 </%def>
141 </%def>
142
142
143
143
144 ## admin menu used for people that have some admin resources
144 ## admin menu used for people that have some admin resources
145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
146 <ul class="submenu">
146 <ul class="submenu">
147 %if repositories:
147 %if repositories:
148 <li class="local-admin-repos"><a href="${h.url('repos')}">${_('Repositories')}</a></li>
148 <li class="local-admin-repos"><a href="${h.url('repos')}">${_('Repositories')}</a></li>
149 %endif
149 %endif
150 %if repository_groups:
150 %if repository_groups:
151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
152 %endif
152 %endif
153 %if user_groups:
153 %if user_groups:
154 <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
154 <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
155 %endif
155 %endif
156 </ul>
156 </ul>
157 </%def>
157 </%def>
158
158
159 <%def name="repo_page_title(repo_instance)">
159 <%def name="repo_page_title(repo_instance)">
160 <div class="title-content">
160 <div class="title-content">
161 <div class="title-main">
161 <div class="title-main">
162 ## SVN/HG/GIT icons
162 ## SVN/HG/GIT icons
163 %if h.is_hg(repo_instance):
163 %if h.is_hg(repo_instance):
164 <i class="icon-hg"></i>
164 <i class="icon-hg"></i>
165 %endif
165 %endif
166 %if h.is_git(repo_instance):
166 %if h.is_git(repo_instance):
167 <i class="icon-git"></i>
167 <i class="icon-git"></i>
168 %endif
168 %endif
169 %if h.is_svn(repo_instance):
169 %if h.is_svn(repo_instance):
170 <i class="icon-svn"></i>
170 <i class="icon-svn"></i>
171 %endif
171 %endif
172
172
173 ## public/private
173 ## public/private
174 %if repo_instance.private:
174 %if repo_instance.private:
175 <i class="icon-repo-private"></i>
175 <i class="icon-repo-private"></i>
176 %else:
176 %else:
177 <i class="icon-repo-public"></i>
177 <i class="icon-repo-public"></i>
178 %endif
178 %endif
179
179
180 ## repo name with group name
180 ## repo name with group name
181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
182
182
183 </div>
183 </div>
184
184
185 ## FORKED
185 ## FORKED
186 %if repo_instance.fork:
186 %if repo_instance.fork:
187 <p>
187 <p>
188 <i class="icon-code-fork"></i> ${_('Fork of')}
188 <i class="icon-code-fork"></i> ${_('Fork of')}
189 <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
189 <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
190 </p>
190 </p>
191 %endif
191 %endif
192
192
193 ## IMPORTED FROM REMOTE
193 ## IMPORTED FROM REMOTE
194 %if repo_instance.clone_uri:
194 %if repo_instance.clone_uri:
195 <p>
195 <p>
196 <i class="icon-code-fork"></i> ${_('Clone from')}
196 <i class="icon-code-fork"></i> ${_('Clone from')}
197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
198 </p>
198 </p>
199 %endif
199 %endif
200
200
201 ## LOCKING STATUS
201 ## LOCKING STATUS
202 %if repo_instance.locked[0]:
202 %if repo_instance.locked[0]:
203 <p class="locking_locked">
203 <p class="locking_locked">
204 <i class="icon-repo-lock"></i>
204 <i class="icon-repo-lock"></i>
205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
206 </p>
206 </p>
207 %elif repo_instance.enable_locking:
207 %elif repo_instance.enable_locking:
208 <p class="locking_unlocked">
208 <p class="locking_unlocked">
209 <i class="icon-repo-unlock"></i>
209 <i class="icon-repo-unlock"></i>
210 ${_('Repository not locked. Pull repository to lock it.')}
210 ${_('Repository not locked. Pull repository to lock it.')}
211 </p>
211 </p>
212 %endif
212 %endif
213
213
214 </div>
214 </div>
215 </%def>
215 </%def>
216
216
217 <%def name="repo_menu(active=None)">
217 <%def name="repo_menu(active=None)">
218 <%
218 <%
219 def is_active(selected):
219 def is_active(selected):
220 if selected == active:
220 if selected == active:
221 return "active"
221 return "active"
222 %>
222 %>
223
223
224 <!--- CONTEXT BAR -->
224 <!--- CONTEXT BAR -->
225 <div id="context-bar">
225 <div id="context-bar">
226 <div class="wrapper">
226 <div class="wrapper">
227 <ul id="context-pages" class="horizontal-list navigation">
227 <ul id="context-pages" class="horizontal-list navigation">
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>
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.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
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 <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>
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 <li class="${is_active('compare')}">
231 <li class="${is_active('compare')}">
232 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
232 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
233 </li>
233 </li>
234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
236 <li class="${is_active('showpullrequest')}">
236 <li class="${is_active('showpullrequest')}">
237 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
237 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
238 %if c.repository_pull_requests:
238 %if c.repository_pull_requests:
239 <span class="pr_notifications">${c.repository_pull_requests}</span>
239 <span class="pr_notifications">${c.repository_pull_requests}</span>
240 %endif
240 %endif
241 <div class="menulabel">${_('Pull Requests')}</div>
241 <div class="menulabel">${_('Pull Requests')}</div>
242 </a>
242 </a>
243 </li>
243 </li>
244 %endif
244 %endif
245 <li class="${is_active('options')}">
245 <li class="${is_active('options')}">
246 <a class="menulink dropdown">
246 <a class="menulink dropdown">
247 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
247 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
248 </a>
248 </a>
249 <ul class="submenu">
249 <ul class="submenu">
250 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
250 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
251 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
251 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
252 %endif
252 %endif
253 %if c.rhodecode_db_repo.fork:
253 %if c.rhodecode_db_repo.fork:
254 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
254 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
255 ${_('Compare fork')}</a></li>
255 ${_('Compare fork')}</a></li>
256 %endif
256 %endif
257
257
258 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
258 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
259
259
260 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
260 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
261 %if c.rhodecode_db_repo.locked[0]:
261 %if c.rhodecode_db_repo.locked[0]:
262 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
262 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
263 %else:
263 %else:
264 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
264 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
265 %endif
265 %endif
266 %endif
266 %endif
267 %if c.rhodecode_user.username != h.DEFAULT_USER:
267 %if c.rhodecode_user.username != h.DEFAULT_USER:
268 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
268 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
269 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
269 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
270 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
270 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
271 %endif
271 %endif
272 %endif
272 %endif
273 </ul>
273 </ul>
274 </li>
274 </li>
275 </ul>
275 </ul>
276 </div>
276 </div>
277 <div class="clear"></div>
277 <div class="clear"></div>
278 </div>
278 </div>
279 <!--- END CONTEXT BAR -->
279 <!--- END CONTEXT BAR -->
280
280
281 </%def>
281 </%def>
282
282
283 <%def name="usermenu(active=False)">
283 <%def name="usermenu(active=False)">
284 ## USER MENU
284 ## USER MENU
285 <li id="quick_login_li" class="${'active' if active else ''}">
285 <li id="quick_login_li" class="${'active' if active else ''}">
286 <a id="quick_login_link" class="menulink childs">
286 <a id="quick_login_link" class="menulink childs">
287 ${gravatar(c.rhodecode_user.email, 20)}
287 ${gravatar(c.rhodecode_user.email, 20)}
288 <span class="user">
288 <span class="user">
289 %if c.rhodecode_user.username != h.DEFAULT_USER:
289 %if c.rhodecode_user.username != h.DEFAULT_USER:
290 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
290 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
291 %else:
291 %else:
292 <span>${_('Sign in')}</span>
292 <span>${_('Sign in')}</span>
293 %endif
293 %endif
294 </span>
294 </span>
295 </a>
295 </a>
296
296
297 <div class="user-menu submenu">
297 <div class="user-menu submenu">
298 <div id="quick_login">
298 <div id="quick_login">
299 %if c.rhodecode_user.username == h.DEFAULT_USER:
299 %if c.rhodecode_user.username == h.DEFAULT_USER:
300 <h4>${_('Sign in to your account')}</h4>
300 <h4>${_('Sign in to your account')}</h4>
301 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
301 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
302 <div class="form form-vertical">
302 <div class="form form-vertical">
303 <div class="fields">
303 <div class="fields">
304 <div class="field">
304 <div class="field">
305 <div class="label">
305 <div class="label">
306 <label for="username">${_('Username')}:</label>
306 <label for="username">${_('Username')}:</label>
307 </div>
307 </div>
308 <div class="input">
308 <div class="input">
309 ${h.text('username',class_='focus',tabindex=1)}
309 ${h.text('username',class_='focus',tabindex=1)}
310 </div>
310 </div>
311
311
312 </div>
312 </div>
313 <div class="field">
313 <div class="field">
314 <div class="label">
314 <div class="label">
315 <label for="password">${_('Password')}:</label>
315 <label for="password">${_('Password')}:</label>
316 %if h.HasPermissionAny('hg.password_reset.enabled')():
316 %if h.HasPermissionAny('hg.password_reset.enabled')():
317 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
317 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
318 %endif
318 %endif
319 </div>
319 </div>
320 <div class="input">
320 <div class="input">
321 ${h.password('password',class_='focus',tabindex=2)}
321 ${h.password('password',class_='focus',tabindex=2)}
322 </div>
322 </div>
323 </div>
323 </div>
324 <div class="buttons">
324 <div class="buttons">
325 <div class="register">
325 <div class="register">
326 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
326 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
327 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
327 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
328 %endif
328 %endif
329 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
329 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
330 </div>
330 </div>
331 <div class="submit">
331 <div class="submit">
332 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
332 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
333 </div>
333 </div>
334 </div>
334 </div>
335 </div>
335 </div>
336 </div>
336 </div>
337 ${h.end_form()}
337 ${h.end_form()}
338 %else:
338 %else:
339 <div class="">
339 <div class="">
340 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
340 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
341 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
341 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
342 <div class="email">${c.rhodecode_user.email}</div>
342 <div class="email">${c.rhodecode_user.email}</div>
343 </div>
343 </div>
344 <div class="">
344 <div class="">
345 <ol class="links">
345 <ol class="links">
346 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
346 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
347 % if c.rhodecode_user.personal_repo_group:
347 % if c.rhodecode_user.personal_repo_group:
348 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
348 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
349 % endif
349 % endif
350 <li class="logout">
350 <li class="logout">
351 ${h.secure_form(h.route_path('logout'), request=request)}
351 ${h.secure_form(h.route_path('logout'), request=request)}
352 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
352 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
353 ${h.end_form()}
353 ${h.end_form()}
354 </li>
354 </li>
355 </ol>
355 </ol>
356 </div>
356 </div>
357 %endif
357 %endif
358 </div>
358 </div>
359 </div>
359 </div>
360 %if c.rhodecode_user.username != h.DEFAULT_USER:
360 %if c.rhodecode_user.username != h.DEFAULT_USER:
361 <div class="pill_container">
361 <div class="pill_container">
362 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
362 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
363 </div>
363 </div>
364 % endif
364 % endif
365 </li>
365 </li>
366 </%def>
366 </%def>
367
367
368 <%def name="menu_items(active=None)">
368 <%def name="menu_items(active=None)">
369 <%
369 <%
370 def is_active(selected):
370 def is_active(selected):
371 if selected == active:
371 if selected == active:
372 return "active"
372 return "active"
373 return ""
373 return ""
374 %>
374 %>
375 <ul id="quick" class="main_nav navigation horizontal-list">
375 <ul id="quick" class="main_nav navigation horizontal-list">
376 <!-- repo switcher -->
376 <!-- repo switcher -->
377 <li class="${is_active('repositories')} repo_switcher_li has_select2">
377 <li class="${is_active('repositories')} repo_switcher_li has_select2">
378 <input id="repo_switcher" name="repo_switcher" type="hidden">
378 <input id="repo_switcher" name="repo_switcher" type="hidden">
379 </li>
379 </li>
380
380
381 ## ROOT MENU
381 ## ROOT MENU
382 %if c.rhodecode_user.username != h.DEFAULT_USER:
382 %if c.rhodecode_user.username != h.DEFAULT_USER:
383 <li class="${is_active('journal')}">
383 <li class="${is_active('journal')}">
384 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
384 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
385 <div class="menulabel">${_('Journal')}</div>
385 <div class="menulabel">${_('Journal')}</div>
386 </a>
386 </a>
387 </li>
387 </li>
388 %else:
388 %else:
389 <li class="${is_active('journal')}">
389 <li class="${is_active('journal')}">
390 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
390 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
391 <div class="menulabel">${_('Public journal')}</div>
391 <div class="menulabel">${_('Public journal')}</div>
392 </a>
392 </a>
393 </li>
393 </li>
394 %endif
394 %endif
395 <li class="${is_active('gists')}">
395 <li class="${is_active('gists')}">
396 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
396 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
397 <div class="menulabel">${_('Gists')}</div>
397 <div class="menulabel">${_('Gists')}</div>
398 </a>
398 </a>
399 </li>
399 </li>
400 <li class="${is_active('search')}">
400 <li class="${is_active('search')}">
401 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
401 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
402 <div class="menulabel">${_('Search')}</div>
402 <div class="menulabel">${_('Search')}</div>
403 </a>
403 </a>
404 </li>
404 </li>
405 % if h.HasPermissionAll('hg.admin')('access admin main page'):
405 % if h.HasPermissionAll('hg.admin')('access admin main page'):
406 <li class="${is_active('admin')}">
406 <li class="${is_active('admin')}">
407 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
407 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
408 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
408 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
409 </a>
409 </a>
410 ${admin_menu()}
410 ${admin_menu()}
411 </li>
411 </li>
412 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
412 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
413 <li class="${is_active('admin')}">
413 <li class="${is_active('admin')}">
414 <a class="menulink childs" title="${_('Delegated Admin settings')}">
414 <a class="menulink childs" title="${_('Delegated Admin settings')}">
415 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
415 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
416 </a>
416 </a>
417 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
417 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
418 c.rhodecode_user.repository_groups_admin,
418 c.rhodecode_user.repository_groups_admin,
419 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
419 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
420 </li>
420 </li>
421 % endif
421 % endif
422 % if c.debug_style:
422 % if c.debug_style:
423 <li class="${is_active('debug_style')}">
423 <li class="${is_active('debug_style')}">
424 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
424 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
425 <div class="menulabel">${_('Style')}</div>
425 <div class="menulabel">${_('Style')}</div>
426 </a>
426 </a>
427 </li>
427 </li>
428 % endif
428 % endif
429 ## render extra user menu
429 ## render extra user menu
430 ${usermenu(active=(active=='my_account'))}
430 ${usermenu(active=(active=='my_account'))}
431 </ul>
431 </ul>
432
432
433 <script type="text/javascript">
433 <script type="text/javascript">
434 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
434 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
435
435
436 /*format the look of items in the list*/
436 /*format the look of items in the list*/
437 var format = function(state, escapeMarkup){
437 var format = function(state, escapeMarkup){
438 if (!state.id){
438 if (!state.id){
439 return state.text; // optgroup
439 return state.text; // optgroup
440 }
440 }
441 var obj_dict = state.obj;
441 var obj_dict = state.obj;
442 var tmpl = '';
442 var tmpl = '';
443
443
444 if(obj_dict && state.type == 'repo'){
444 if(obj_dict && state.type == 'repo'){
445 if(obj_dict['repo_type'] === 'hg'){
445 if(obj_dict['repo_type'] === 'hg'){
446 tmpl += '<i class="icon-hg"></i> ';
446 tmpl += '<i class="icon-hg"></i> ';
447 }
447 }
448 else if(obj_dict['repo_type'] === 'git'){
448 else if(obj_dict['repo_type'] === 'git'){
449 tmpl += '<i class="icon-git"></i> ';
449 tmpl += '<i class="icon-git"></i> ';
450 }
450 }
451 else if(obj_dict['repo_type'] === 'svn'){
451 else if(obj_dict['repo_type'] === 'svn'){
452 tmpl += '<i class="icon-svn"></i> ';
452 tmpl += '<i class="icon-svn"></i> ';
453 }
453 }
454 if(obj_dict['private']){
454 if(obj_dict['private']){
455 tmpl += '<i class="icon-lock" ></i> ';
455 tmpl += '<i class="icon-lock" ></i> ';
456 }
456 }
457 else if(visual_show_public_icon){
457 else if(visual_show_public_icon){
458 tmpl += '<i class="icon-unlock-alt"></i> ';
458 tmpl += '<i class="icon-unlock-alt"></i> ';
459 }
459 }
460 }
460 }
461 if(obj_dict && state.type == 'commit') {
461 if(obj_dict && state.type == 'commit') {
462 tmpl += '<i class="icon-tag"></i>';
462 tmpl += '<i class="icon-tag"></i>';
463 }
463 }
464 if(obj_dict && state.type == 'group'){
464 if(obj_dict && state.type == 'group'){
465 tmpl += '<i class="icon-folder-close"></i> ';
465 tmpl += '<i class="icon-folder-close"></i> ';
466 }
466 }
467 tmpl += escapeMarkup(state.text);
467 tmpl += escapeMarkup(state.text);
468 return tmpl;
468 return tmpl;
469 };
469 };
470
470
471 var formatResult = function(result, container, query, escapeMarkup) {
471 var formatResult = function(result, container, query, escapeMarkup) {
472 return format(result, escapeMarkup);
472 return format(result, escapeMarkup);
473 };
473 };
474
474
475 var formatSelection = function(data, container, escapeMarkup) {
475 var formatSelection = function(data, container, escapeMarkup) {
476 return format(data, escapeMarkup);
476 return format(data, escapeMarkup);
477 };
477 };
478
478
479 $("#repo_switcher").select2({
479 $("#repo_switcher").select2({
480 cachedDataSource: {},
480 cachedDataSource: {},
481 minimumInputLength: 2,
481 minimumInputLength: 2,
482 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
482 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
483 dropdownAutoWidth: true,
483 dropdownAutoWidth: true,
484 formatResult: formatResult,
484 formatResult: formatResult,
485 formatSelection: formatSelection,
485 formatSelection: formatSelection,
486 containerCssClass: "repo-switcher",
486 containerCssClass: "repo-switcher",
487 dropdownCssClass: "repo-switcher-dropdown",
487 dropdownCssClass: "repo-switcher-dropdown",
488 escapeMarkup: function(m){
488 escapeMarkup: function(m){
489 // don't escape our custom placeholder
489 // don't escape our custom placeholder
490 if(m.substr(0,23) == '<div class="menulabel">'){
490 if(m.substr(0,23) == '<div class="menulabel">'){
491 return m;
491 return m;
492 }
492 }
493
493
494 return Select2.util.escapeMarkup(m);
494 return Select2.util.escapeMarkup(m);
495 },
495 },
496 query: $.debounce(250, function(query){
496 query: $.debounce(250, function(query){
497 self = this;
497 self = this;
498 var cacheKey = query.term;
498 var cacheKey = query.term;
499 var cachedData = self.cachedDataSource[cacheKey];
499 var cachedData = self.cachedDataSource[cacheKey];
500
500
501 if (cachedData) {
501 if (cachedData) {
502 query.callback({results: cachedData.results});
502 query.callback({results: cachedData.results});
503 } else {
503 } else {
504 $.ajax({
504 $.ajax({
505 url: pyroutes.url('goto_switcher_data'),
505 url: pyroutes.url('goto_switcher_data'),
506 data: {'query': query.term},
506 data: {'query': query.term},
507 dataType: 'json',
507 dataType: 'json',
508 type: 'GET',
508 type: 'GET',
509 success: function(data) {
509 success: function(data) {
510 self.cachedDataSource[cacheKey] = data;
510 self.cachedDataSource[cacheKey] = data;
511 query.callback({results: data.results});
511 query.callback({results: data.results});
512 },
512 },
513 error: function(data, textStatus, errorThrown) {
513 error: function(data, textStatus, errorThrown) {
514 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
514 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
515 }
515 }
516 })
516 })
517 }
517 }
518 })
518 })
519 });
519 });
520
520
521 $("#repo_switcher").on('select2-selecting', function(e){
521 $("#repo_switcher").on('select2-selecting', function(e){
522 e.preventDefault();
522 e.preventDefault();
523 window.location = e.choice.url;
523 window.location = e.choice.url;
524 });
524 });
525
525
526 </script>
526 </script>
527 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
527 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
528 </%def>
528 </%def>
529
529
530 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
530 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
531 <div class="modal-dialog">
531 <div class="modal-dialog">
532 <div class="modal-content">
532 <div class="modal-content">
533 <div class="modal-header">
533 <div class="modal-header">
534 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
534 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
535 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
535 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
536 </div>
536 </div>
537 <div class="modal-body">
537 <div class="modal-body">
538 <div class="block-left">
538 <div class="block-left">
539 <table class="keyboard-mappings">
539 <table class="keyboard-mappings">
540 <tbody>
540 <tbody>
541 <tr>
541 <tr>
542 <th></th>
542 <th></th>
543 <th>${_('Site-wide shortcuts')}</th>
543 <th>${_('Site-wide shortcuts')}</th>
544 </tr>
544 </tr>
545 <%
545 <%
546 elems = [
546 elems = [
547 ('/', 'Open quick search box'),
547 ('/', 'Open quick search box'),
548 ('g h', 'Goto home page'),
548 ('g h', 'Goto home page'),
549 ('g g', 'Goto my private gists page'),
549 ('g g', 'Goto my private gists page'),
550 ('g G', 'Goto my public gists page'),
550 ('g G', 'Goto my public gists page'),
551 ('n r', 'New repository page'),
551 ('n r', 'New repository page'),
552 ('n g', 'New gist page'),
552 ('n g', 'New gist page'),
553 ]
553 ]
554 %>
554 %>
555 %for key, desc in elems:
555 %for key, desc in elems:
556 <tr>
556 <tr>
557 <td class="keys">
557 <td class="keys">
558 <span class="key tag">${key}</span>
558 <span class="key tag">${key}</span>
559 </td>
559 </td>
560 <td>${desc}</td>
560 <td>${desc}</td>
561 </tr>
561 </tr>
562 %endfor
562 %endfor
563 </tbody>
563 </tbody>
564 </table>
564 </table>
565 </div>
565 </div>
566 <div class="block-left">
566 <div class="block-left">
567 <table class="keyboard-mappings">
567 <table class="keyboard-mappings">
568 <tbody>
568 <tbody>
569 <tr>
569 <tr>
570 <th></th>
570 <th></th>
571 <th>${_('Repositories')}</th>
571 <th>${_('Repositories')}</th>
572 </tr>
572 </tr>
573 <%
573 <%
574 elems = [
574 elems = [
575 ('g s', 'Goto summary page'),
575 ('g s', 'Goto summary page'),
576 ('g c', 'Goto changelog page'),
576 ('g c', 'Goto changelog page'),
577 ('g f', 'Goto files page'),
577 ('g f', 'Goto files page'),
578 ('g F', 'Goto files page with file search activated'),
578 ('g F', 'Goto files page with file search activated'),
579 ('g p', 'Goto pull requests page'),
579 ('g p', 'Goto pull requests page'),
580 ('g o', 'Goto repository settings'),
580 ('g o', 'Goto repository settings'),
581 ('g O', 'Goto repository permissions settings'),
581 ('g O', 'Goto repository permissions settings'),
582 ]
582 ]
583 %>
583 %>
584 %for key, desc in elems:
584 %for key, desc in elems:
585 <tr>
585 <tr>
586 <td class="keys">
586 <td class="keys">
587 <span class="key tag">${key}</span>
587 <span class="key tag">${key}</span>
588 </td>
588 </td>
589 <td>${desc}</td>
589 <td>${desc}</td>
590 </tr>
590 </tr>
591 %endfor
591 %endfor
592 </tbody>
592 </tbody>
593 </table>
593 </table>
594 </div>
594 </div>
595 </div>
595 </div>
596 <div class="modal-footer">
596 <div class="modal-footer">
597 </div>
597 </div>
598 </div><!-- /.modal-content -->
598 </div><!-- /.modal-content -->
599 </div><!-- /.modal-dialog -->
599 </div><!-- /.modal-dialog -->
600 </div><!-- /.modal -->
600 </div><!-- /.modal -->
@@ -1,299 +1,299 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.mako"/>
3 <%inherit file="/base/base.mako"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${_('%s Changelog') % c.repo_name}
6 ${_('%s Changelog') % c.repo_name}
7 %if c.changelog_for_path:
7 %if c.changelog_for_path:
8 /${c.changelog_for_path}
8 /${c.changelog_for_path}
9 %endif
9 %endif
10 %if c.rhodecode_name:
10 %if c.rhodecode_name:
11 &middot; ${h.branding(c.rhodecode_name)}
11 &middot; ${h.branding(c.rhodecode_name)}
12 %endif
12 %endif
13 </%def>
13 </%def>
14
14
15 <%def name="breadcrumbs_links()">
15 <%def name="breadcrumbs_links()">
16 %if c.changelog_for_path:
16 %if c.changelog_for_path:
17 /${c.changelog_for_path}
17 /${c.changelog_for_path}
18 %endif
18 %endif
19 </%def>
19 </%def>
20
20
21 <%def name="menu_bar_nav()">
21 <%def name="menu_bar_nav()">
22 ${self.menu_items(active='repositories')}
22 ${self.menu_items(active='repositories')}
23 </%def>
23 </%def>
24
24
25 <%def name="menu_bar_subnav()">
25 <%def name="menu_bar_subnav()">
26 ${self.repo_menu(active='changelog')}
26 ${self.repo_menu(active='changelog')}
27 </%def>
27 </%def>
28
28
29 <%def name="main()">
29 <%def name="main()">
30
30
31 <div class="box">
31 <div class="box">
32 <div class="title">
32 <div class="title">
33 ${self.repo_page_title(c.rhodecode_db_repo)}
33 ${self.repo_page_title(c.rhodecode_db_repo)}
34 <ul class="links">
34 <ul class="links">
35 <li>
35 <li>
36 <a href="#" class="btn btn-small" id="rev_range_container" style="display:none;"></a>
36 <a href="#" class="btn btn-small" id="rev_range_container" style="display:none;"></a>
37 %if c.rhodecode_db_repo.fork:
37 %if c.rhodecode_db_repo.fork:
38 <span>
38 <span>
39 <a id="compare_fork_button"
39 <a id="compare_fork_button"
40 title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
40 title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
41 class="btn btn-small"
41 class="btn btn-small"
42 href="${h.url('compare_url',
42 href="${h.url('compare_url',
43 repo_name=c.rhodecode_db_repo.fork.repo_name,
43 repo_name=c.rhodecode_db_repo.fork.repo_name,
44 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
44 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
45 source_ref=c.rhodecode_db_repo.landing_rev[1],
45 source_ref=c.rhodecode_db_repo.landing_rev[1],
46 target_repo=c.repo_name,
46 target_repo=c.repo_name,
47 target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
47 target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
48 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
48 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
49 merge=1)}"
49 merge=1)}"
50 >
50 >
51 <i class="icon-loop"></i>
51 <i class="icon-loop"></i>
52 ${_('Compare fork with Parent (%s)' % c.rhodecode_db_repo.fork.repo_name)}
52 ${_('Compare fork with Parent (%s)' % c.rhodecode_db_repo.fork.repo_name)}
53 </a>
53 </a>
54 </span>
54 </span>
55 %endif
55 %endif
56
56
57 ## pr open link
57 ## pr open link
58 %if h.is_hg(c.rhodecode_repo) or h.is_git(c.rhodecode_repo):
58 %if h.is_hg(c.rhodecode_repo) or h.is_git(c.rhodecode_repo):
59 <span>
59 <span>
60 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.url('pullrequest_home',repo_name=c.repo_name)}">
60 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.url('pullrequest_home',repo_name=c.repo_name)}">
61 ${_('Open new pull request')}
61 ${_('Open new pull request')}
62 </a>
62 </a>
63 </span>
63 </span>
64 %endif
64 %endif
65
65
66 ## clear selection
66 ## clear selection
67 <div title="${_('Clear selection')}" class="btn" id="rev_range_clear" style="display:none">
67 <div title="${_('Clear selection')}" class="btn" id="rev_range_clear" style="display:none">
68 ${_('Clear selection')}
68 ${_('Clear selection')}
69 </div>
69 </div>
70
70
71 </li>
71 </li>
72 </ul>
72 </ul>
73 </div>
73 </div>
74
74
75 % if c.pagination:
75 % if c.pagination:
76 <script type="text/javascript" src="${h.asset('js/jquery.commits-graph.js')}"></script>
76 <script type="text/javascript" src="${h.asset('js/jquery.commits-graph.js')}"></script>
77
77
78 <div class="graph-header">
78 <div class="graph-header">
79 <div id="filter_changelog">
79 <div id="filter_changelog">
80 ${h.hidden('branch_filter')}
80 ${h.hidden('branch_filter')}
81 %if c.selected_name:
81 %if c.selected_name:
82 <div class="btn btn-default" id="clear_filter" >
82 <div class="btn btn-default" id="clear_filter" >
83 ${_('Clear filter')}
83 ${_('Clear filter')}
84 </div>
84 </div>
85 %endif
85 %endif
86 </div>
86 </div>
87 ${self.breadcrumbs('breadcrumbs_light')}
87 ${self.breadcrumbs('breadcrumbs_light')}
88 <div id="commit-counter" data-total=${c.total_cs} class="pull-right">
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 </div>
90 </div>
91 </div>
91 </div>
92
92
93 <div id="graph">
93 <div id="graph">
94 <div class="graph-col-wrapper">
94 <div class="graph-col-wrapper">
95 <div id="graph_nodes">
95 <div id="graph_nodes">
96 <div id="graph_canvas"></div>
96 <div id="graph_canvas"></div>
97 </div>
97 </div>
98 <div id="graph_content" class="main-content graph_full_width">
98 <div id="graph_content" class="main-content graph_full_width">
99
99
100 <div class="table">
100 <div class="table">
101 <table id="changesets" class="rctable">
101 <table id="changesets" class="rctable">
102 <tr>
102 <tr>
103 ## checkbox
103 ## checkbox
104 <th></th>
104 <th></th>
105 <th colspan="2"></th>
105 <th colspan="2"></th>
106
106
107 <th>${_('Commit')}</th>
107 <th>${_('Commit')}</th>
108 ## commit message expand arrow
108 ## commit message expand arrow
109 <th></th>
109 <th></th>
110 <th>${_('Commit Message')}</th>
110 <th>${_('Commit Message')}</th>
111
111
112 <th>${_('Age')}</th>
112 <th>${_('Age')}</th>
113 <th>${_('Author')}</th>
113 <th>${_('Author')}</th>
114
114
115 <th>${_('Refs')}</th>
115 <th>${_('Refs')}</th>
116 </tr>
116 </tr>
117
117
118 <tbody class="commits-range">
118 <tbody class="commits-range">
119 <%include file='changelog_elements.mako'/>
119 <%include file='changelog_elements.mako'/>
120 </tbody>
120 </tbody>
121 </table>
121 </table>
122 </div>
122 </div>
123 </div>
123 </div>
124 <div class="pagination-wh pagination-left">
124 <div class="pagination-wh pagination-left">
125 ${c.pagination.pager('$link_previous ~2~ $link_next')}
125 ${c.pagination.pager('$link_previous ~2~ $link_next')}
126 </div>
126 </div>
127 </div>
127 </div>
128
128
129 <script type="text/javascript">
129 <script type="text/javascript">
130 var cache = {};
130 var cache = {};
131 $(function(){
131 $(function(){
132
132
133 // Create links to commit ranges when range checkboxes are selected
133 // Create links to commit ranges when range checkboxes are selected
134 var $commitCheckboxes = $('.commit-range');
134 var $commitCheckboxes = $('.commit-range');
135 // cache elements
135 // cache elements
136 var $commitRangeContainer = $('#rev_range_container');
136 var $commitRangeContainer = $('#rev_range_container');
137 var $commitRangeClear = $('#rev_range_clear');
137 var $commitRangeClear = $('#rev_range_clear');
138
138
139 var checkboxRangeSelector = function(e){
139 var checkboxRangeSelector = function(e){
140 var selectedCheckboxes = [];
140 var selectedCheckboxes = [];
141 for (pos in $commitCheckboxes){
141 for (pos in $commitCheckboxes){
142 if($commitCheckboxes[pos].checked){
142 if($commitCheckboxes[pos].checked){
143 selectedCheckboxes.push($commitCheckboxes[pos]);
143 selectedCheckboxes.push($commitCheckboxes[pos]);
144 }
144 }
145 }
145 }
146 var open_new_pull_request = $('#open_new_pull_request');
146 var open_new_pull_request = $('#open_new_pull_request');
147 if(open_new_pull_request){
147 if(open_new_pull_request){
148 var selected_changes = selectedCheckboxes.length;
148 var selected_changes = selectedCheckboxes.length;
149 if (selected_changes > 1 || selected_changes == 1 && templateContext.repo_type != 'hg') {
149 if (selected_changes > 1 || selected_changes == 1 && templateContext.repo_type != 'hg') {
150 open_new_pull_request.hide();
150 open_new_pull_request.hide();
151 } else {
151 } else {
152 if (selected_changes == 1) {
152 if (selected_changes == 1) {
153 open_new_pull_request.html(_gettext('Open new pull request for selected commit'));
153 open_new_pull_request.html(_gettext('Open new pull request for selected commit'));
154 } else if (selected_changes == 0) {
154 } else if (selected_changes == 0) {
155 open_new_pull_request.html(_gettext('Open new pull request'));
155 open_new_pull_request.html(_gettext('Open new pull request'));
156 }
156 }
157 open_new_pull_request.show();
157 open_new_pull_request.show();
158 }
158 }
159 }
159 }
160
160
161 if (selectedCheckboxes.length>0){
161 if (selectedCheckboxes.length>0){
162 var revEnd = selectedCheckboxes[0].name;
162 var revEnd = selectedCheckboxes[0].name;
163 var revStart = selectedCheckboxes[selectedCheckboxes.length-1].name;
163 var revStart = selectedCheckboxes[selectedCheckboxes.length-1].name;
164 var url = pyroutes.url('changeset_home',
164 var url = pyroutes.url('changeset_home',
165 {'repo_name': '${c.repo_name}',
165 {'repo_name': '${c.repo_name}',
166 'revision': revStart+'...'+revEnd});
166 'revision': revStart+'...'+revEnd});
167
167
168 var link = (revStart == revEnd)
168 var link = (revStart == revEnd)
169 ? _gettext('Show selected commit __S')
169 ? _gettext('Show selected commit __S')
170 : _gettext('Show selected commits __S ... __E');
170 : _gettext('Show selected commits __S ... __E');
171
171
172 link = link.replace('__S', revStart.substr(0,6));
172 link = link.replace('__S', revStart.substr(0,6));
173 link = link.replace('__E', revEnd.substr(0,6));
173 link = link.replace('__E', revEnd.substr(0,6));
174
174
175 $commitRangeContainer
175 $commitRangeContainer
176 .attr('href',url)
176 .attr('href',url)
177 .html(link)
177 .html(link)
178 .show();
178 .show();
179
179
180 $commitRangeClear.show();
180 $commitRangeClear.show();
181 var _url = pyroutes.url('pullrequest_home',
181 var _url = pyroutes.url('pullrequest_home',
182 {'repo_name': '${c.repo_name}',
182 {'repo_name': '${c.repo_name}',
183 'commit': revEnd});
183 'commit': revEnd});
184 open_new_pull_request.attr('href', _url);
184 open_new_pull_request.attr('href', _url);
185 $('#compare_fork_button').hide();
185 $('#compare_fork_button').hide();
186 } else {
186 } else {
187 $commitRangeContainer.hide();
187 $commitRangeContainer.hide();
188 $commitRangeClear.hide();
188 $commitRangeClear.hide();
189
189
190 %if c.branch_name:
190 %if c.branch_name:
191 var _url = pyroutes.url('pullrequest_home',
191 var _url = pyroutes.url('pullrequest_home',
192 {'repo_name': '${c.repo_name}',
192 {'repo_name': '${c.repo_name}',
193 'branch':'${c.branch_name}'});
193 'branch':'${c.branch_name}'});
194 open_new_pull_request.attr('href', _url);
194 open_new_pull_request.attr('href', _url);
195 %else:
195 %else:
196 var _url = pyroutes.url('pullrequest_home',
196 var _url = pyroutes.url('pullrequest_home',
197 {'repo_name': '${c.repo_name}'});
197 {'repo_name': '${c.repo_name}'});
198 open_new_pull_request.attr('href', _url);
198 open_new_pull_request.attr('href', _url);
199 %endif
199 %endif
200 $('#compare_fork_button').show();
200 $('#compare_fork_button').show();
201 }
201 }
202 };
202 };
203
203
204 $commitCheckboxes.on('click', checkboxRangeSelector);
204 $commitCheckboxes.on('click', checkboxRangeSelector);
205
205
206 $commitRangeClear.on('click',function(e) {
206 $commitRangeClear.on('click',function(e) {
207 $commitCheckboxes.attr('checked', false);
207 $commitCheckboxes.attr('checked', false);
208 checkboxRangeSelector();
208 checkboxRangeSelector();
209 e.preventDefault();
209 e.preventDefault();
210 });
210 });
211
211
212 // make sure the buttons are consistent when navigate back and forth
212 // make sure the buttons are consistent when navigate back and forth
213 checkboxRangeSelector();
213 checkboxRangeSelector();
214
214
215 var msgs = $('.message');
215 var msgs = $('.message');
216 // get first element height
216 // get first element height
217 var el = $('#graph_content .container')[0];
217 var el = $('#graph_content .container')[0];
218 var row_h = el.clientHeight;
218 var row_h = el.clientHeight;
219 for (var i=0; i < msgs.length; i++) {
219 for (var i=0; i < msgs.length; i++) {
220 var m = msgs[i];
220 var m = msgs[i];
221
221
222 var h = m.clientHeight;
222 var h = m.clientHeight;
223 var pad = $(m).css('padding');
223 var pad = $(m).css('padding');
224 if (h > row_h) {
224 if (h > row_h) {
225 var offset = row_h - (h+12);
225 var offset = row_h - (h+12);
226 $(m.nextElementSibling).css('display','block');
226 $(m.nextElementSibling).css('display','block');
227 $(m.nextElementSibling).css('margin-top',offset+'px');
227 $(m.nextElementSibling).css('margin-top',offset+'px');
228 }
228 }
229 }
229 }
230
230
231 $("#clear_filter").on("click", function() {
231 $("#clear_filter").on("click", function() {
232 var filter = {'repo_name': '${c.repo_name}'};
232 var filter = {'repo_name': '${c.repo_name}'};
233 window.location = pyroutes.url('changelog_home', filter);
233 window.location = pyroutes.url('repo_changelog', filter);
234 });
234 });
235
235
236 $("#branch_filter").select2({
236 $("#branch_filter").select2({
237 'dropdownAutoWidth': true,
237 'dropdownAutoWidth': true,
238 'width': 'resolve',
238 'width': 'resolve',
239 'placeholder': "${c.selected_name or _('Filter changelog')}",
239 'placeholder': "${c.selected_name or _('Filter changelog')}",
240 containerCssClass: "drop-menu",
240 containerCssClass: "drop-menu",
241 dropdownCssClass: "drop-menu-dropdown",
241 dropdownCssClass: "drop-menu-dropdown",
242 query: function(query){
242 query: function(query){
243 var key = 'cache';
243 var key = 'cache';
244 var cached = cache[key] ;
244 var cached = cache[key] ;
245 if(cached) {
245 if(cached) {
246 var data = {results: []};
246 var data = {results: []};
247 //filter results
247 //filter results
248 $.each(cached.results, function(){
248 $.each(cached.results, function(){
249 var section = this.text;
249 var section = this.text;
250 var children = [];
250 var children = [];
251 $.each(this.children, function(){
251 $.each(this.children, function(){
252 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
252 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
253 children.push({'id': this.id, 'text': this.text, 'type': this.type})
253 children.push({'id': this.id, 'text': this.text, 'type': this.type})
254 }
254 }
255 });
255 });
256 data.results.push({'text': section, 'children': children});
256 data.results.push({'text': section, 'children': children});
257 query.callback({results: data.results});
257 query.callback({results: data.results});
258 });
258 });
259 }else{
259 }else{
260 $.ajax({
260 $.ajax({
261 url: pyroutes.url('repo_refs_changelog_data', {'repo_name': '${c.repo_name}'}),
261 url: pyroutes.url('repo_refs_changelog_data', {'repo_name': '${c.repo_name}'}),
262 data: {},
262 data: {},
263 dataType: 'json',
263 dataType: 'json',
264 type: 'GET',
264 type: 'GET',
265 success: function(data) {
265 success: function(data) {
266 cache[key] = data;
266 cache[key] = data;
267 query.callback({results: data.results});
267 query.callback({results: data.results});
268 }
268 }
269 })
269 })
270 }
270 }
271 }
271 }
272 });
272 });
273 $('#branch_filter').on('change', function(e){
273 $('#branch_filter').on('change', function(e){
274 var data = $('#branch_filter').select2('data');
274 var data = $('#branch_filter').select2('data');
275 var selected = data.text;
275 var selected = data.text;
276 var filter = {'repo_name': '${c.repo_name}'};
276 var filter = {'repo_name': '${c.repo_name}'};
277 if(data.type == 'branch' || data.type == 'branch_closed'){
277 if(data.type == 'branch' || data.type == 'branch_closed'){
278 filter["branch"] = selected;
278 filter["branch"] = selected;
279 }
279 }
280 else if (data.type == 'book'){
280 else if (data.type == 'book'){
281 filter["bookmark"] = selected;
281 filter["bookmark"] = selected;
282 }
282 }
283 window.location = pyroutes.url('changelog_home', filter);
283 window.location = pyroutes.url('repo_changelog', filter);
284 });
284 });
285
285
286 commitsController = new CommitsController();
286 commitsController = new CommitsController();
287 % if not c.changelog_for_path:
287 % if not c.changelog_for_path:
288 commitsController.reloadGraph();
288 commitsController.reloadGraph();
289 % endif
289 % endif
290
290
291 });
291 });
292
292
293 </script>
293 </script>
294 </div>
294 </div>
295 % else:
295 % else:
296 ${_('There are no changes yet')}
296 ${_('There are no changes yet')}
297 % endif
297 % endif
298 </div>
298 </div>
299 </%def>
299 </%def>
@@ -1,142 +1,142 b''
1 ## small box that displays changed/added/removed details fetched by AJAX
1 ## small box that displays changed/added/removed details fetched by AJAX
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3
3
4
4
5 % if c.prev_page:
5 % if c.prev_page:
6 <tr>
6 <tr>
7 <td colspan="9" class="load-more-commits">
7 <td colspan="9" class="load-more-commits">
8 <a class="prev-commits" href="#loadPrevCommits" onclick="commitsController.loadPrev(this, ${c.prev_page}, '${c.branch_name}');return false">
8 <a class="prev-commits" href="#loadPrevCommits" onclick="commitsController.loadPrev(this, ${c.prev_page}, '${c.branch_name}');return false">
9 ${_('load previous')}
9 ${_('load previous')}
10 </a>
10 </a>
11 </td>
11 </td>
12 </tr>
12 </tr>
13 % endif
13 % endif
14
14
15 % for cnt,commit in enumerate(c.pagination):
15 % for cnt,commit in enumerate(c.pagination):
16 <tr id="sha_${commit.raw_id}" class="changelogRow container ${'tablerow%s' % (cnt%2)}">
16 <tr id="sha_${commit.raw_id}" class="changelogRow container ${'tablerow%s' % (cnt%2)}">
17
17
18 <td class="td-checkbox">
18 <td class="td-checkbox">
19 ${h.checkbox(commit.raw_id,class_="commit-range")}
19 ${h.checkbox(commit.raw_id,class_="commit-range")}
20 </td>
20 </td>
21 <td class="td-status">
21 <td class="td-status">
22
22
23 %if c.statuses.get(commit.raw_id):
23 %if c.statuses.get(commit.raw_id):
24 <div class="changeset-status-ico">
24 <div class="changeset-status-ico">
25 %if c.statuses.get(commit.raw_id)[2]:
25 %if c.statuses.get(commit.raw_id)[2]:
26 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]), c.statuses.get(commit.raw_id)[2])}" href="${h.route_path('pullrequest_show',repo_name=c.statuses.get(commit.raw_id)[3],pull_request_id=c.statuses.get(commit.raw_id)[2])}">
26 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]), c.statuses.get(commit.raw_id)[2])}" href="${h.route_path('pullrequest_show',repo_name=c.statuses.get(commit.raw_id)[3],pull_request_id=c.statuses.get(commit.raw_id)[2])}">
27 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
27 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
28 </a>
28 </a>
29 %else:
29 %else:
30 <a class="tooltip" title="${_('Commit status: %s') % h.commit_status_lbl(c.statuses.get(commit.raw_id)[0])}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
30 <a class="tooltip" title="${_('Commit status: %s') % h.commit_status_lbl(c.statuses.get(commit.raw_id)[0])}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
31 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
31 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
32 </a>
32 </a>
33 %endif
33 %endif
34 </div>
34 </div>
35 %else:
35 %else:
36 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
36 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
37 %endif
37 %endif
38 </td>
38 </td>
39 <td class="td-comments comments-col">
39 <td class="td-comments comments-col">
40 %if c.comments.get(commit.raw_id):
40 %if c.comments.get(commit.raw_id):
41 <a title="${_('Commit has comments')}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
41 <a title="${_('Commit has comments')}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
42 <i class="icon-comment"></i> ${len(c.comments[commit.raw_id])}
42 <i class="icon-comment"></i> ${len(c.comments[commit.raw_id])}
43 </a>
43 </a>
44 %endif
44 %endif
45 </td>
45 </td>
46 <td class="td-hash">
46 <td class="td-hash">
47 <code>
47 <code>
48 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id)}">
48 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id)}">
49 <span class="${'commit_hash obsolete' if getattr(commit, 'obsolete', None) else 'commit_hash'}">${h.show_id(commit)}</span>
49 <span class="${'commit_hash obsolete' if getattr(commit, 'obsolete', None) else 'commit_hash'}">${h.show_id(commit)}</span>
50 </a>
50 </a>
51 % if hasattr(commit, 'phase'):
51 % if hasattr(commit, 'phase'):
52 % if commit.phase != 'public':
52 % if commit.phase != 'public':
53 <span class="tag phase-${commit.phase} tooltip" title="${_('Commit phase')}">${commit.phase}</span>
53 <span class="tag phase-${commit.phase} tooltip" title="${_('Commit phase')}">${commit.phase}</span>
54 % endif
54 % endif
55 % endif
55 % endif
56
56
57 ## obsolete commits
57 ## obsolete commits
58 % if hasattr(commit, 'obsolete'):
58 % if hasattr(commit, 'obsolete'):
59 % if commit.obsolete:
59 % if commit.obsolete:
60 <span class="tag obsolete-${commit.obsolete} tooltip" title="${_('Evolve State')}">${_('obsolete')}</span>
60 <span class="tag obsolete-${commit.obsolete} tooltip" title="${_('Evolve State')}">${_('obsolete')}</span>
61 % endif
61 % endif
62 % endif
62 % endif
63
63
64 ## hidden commits
64 ## hidden commits
65 % if hasattr(commit, 'hidden'):
65 % if hasattr(commit, 'hidden'):
66 % if commit.hidden:
66 % if commit.hidden:
67 <span class="tag obsolete-${commit.hidden} tooltip" title="${_('Evolve State')}">${_('hidden')}</span>
67 <span class="tag obsolete-${commit.hidden} tooltip" title="${_('Evolve State')}">${_('hidden')}</span>
68 % endif
68 % endif
69 % endif
69 % endif
70
70
71 </code>
71 </code>
72 </td>
72 </td>
73 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_('Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
73 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_('Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
74 <div class="show_more_col">
74 <div class="show_more_col">
75 <i class="show_more"></i>&nbsp;
75 <i class="show_more"></i>&nbsp;
76 </div>
76 </div>
77 </td>
77 </td>
78 <td class="td-description mid">
78 <td class="td-description mid">
79 <div class="log-container truncate-wrap">
79 <div class="log-container truncate-wrap">
80 <div class="message truncate" id="c-${commit.raw_id}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
80 <div class="message truncate" id="c-${commit.raw_id}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
81 </div>
81 </div>
82 </td>
82 </td>
83
83
84 <td class="td-time">
84 <td class="td-time">
85 ${h.age_component(commit.date)}
85 ${h.age_component(commit.date)}
86 </td>
86 </td>
87 <td class="td-user">
87 <td class="td-user">
88 ${base.gravatar_with_user(commit.author)}
88 ${base.gravatar_with_user(commit.author)}
89 </td>
89 </td>
90
90
91 <td class="td-tags tags-col">
91 <td class="td-tags tags-col">
92 <div id="t-${commit.raw_id}">
92 <div id="t-${commit.raw_id}">
93
93
94 ## merge
94 ## merge
95 %if commit.merge:
95 %if commit.merge:
96 <span class="tag mergetag">
96 <span class="tag mergetag">
97 <i class="icon-merge"></i>${_('merge')}
97 <i class="icon-merge"></i>${_('merge')}
98 </span>
98 </span>
99 %endif
99 %endif
100
100
101 ## branch
101 ## branch
102 %if commit.branch:
102 %if commit.branch:
103 <span class="tag branchtag" title="${h.tooltip(_('Branch %s') % commit.branch)}">
103 <span class="tag branchtag" title="${h.tooltip(_('Branch %s') % commit.branch)}">
104 <a href="${h.url('changelog_home',repo_name=c.repo_name,branch=commit.branch)}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a>
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 </span>
105 </span>
106 %endif
106 %endif
107
107
108 ## bookmarks
108 ## bookmarks
109 %if h.is_hg(c.rhodecode_repo):
109 %if h.is_hg(c.rhodecode_repo):
110 %for book in commit.bookmarks:
110 %for book in commit.bookmarks:
111 <span class="tag booktag" title="${h.tooltip(_('Bookmark %s') % book)}">
111 <span class="tag booktag" title="${h.tooltip(_('Bookmark %s') % book)}">
112 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id, _query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
112 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id, _query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
113 </span>
113 </span>
114 %endfor
114 %endfor
115 %endif
115 %endif
116
116
117 ## tags
117 ## tags
118 %for tag in commit.tags:
118 %for tag in commit.tags:
119 <span class="tag tagtag" title="${h.tooltip(_('Tag %s') % tag)}">
119 <span class="tag tagtag" title="${h.tooltip(_('Tag %s') % tag)}">
120 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id, _query=dict(at=tag))}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
120 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id, _query=dict(at=tag))}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
121 </span>
121 </span>
122 %endfor
122 %endfor
123
123
124 </div>
124 </div>
125 </td>
125 </td>
126 </tr>
126 </tr>
127 % endfor
127 % endfor
128
128
129 % if c.next_page:
129 % if c.next_page:
130 <tr>
130 <tr>
131 <td colspan="9" class="load-more-commits">
131 <td colspan="9" class="load-more-commits">
132 <a class="next-commits" href="#loadNextCommits" onclick="commitsController.loadNext(this, ${c.next_page}, '${c.branch_name}');return false">
132 <a class="next-commits" href="#loadNextCommits" onclick="commitsController.loadNext(this, ${c.next_page}, '${c.branch_name}');return false">
133 ${_('load next')}
133 ${_('load next')}
134 </a>
134 </a>
135 </td>
135 </td>
136 </tr>
136 </tr>
137 % endif
137 % endif
138 <tr class="chunk-graph-data" style="display:none"
138 <tr class="chunk-graph-data" style="display:none"
139 data-graph='${c.graph_data|n}'
139 data-graph='${c.graph_data|n}'
140 data-node='${c.prev_page}:${c.next_page}'
140 data-node='${c.prev_page}:${c.next_page}'
141 data-commits='${c.graph_commits|n}'>
141 data-commits='${c.graph_commits|n}'>
142 </tr> No newline at end of file
142 </tr>
@@ -1,317 +1,317 b''
1 ## DATA TABLE RE USABLE ELEMENTS
1 ## DATA TABLE RE USABLE ELEMENTS
2 ## usage:
2 ## usage:
3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
5
5
6 ## REPOSITORY RENDERERS
6 ## REPOSITORY RENDERERS
7 <%def name="quick_menu(repo_name)">
7 <%def name="quick_menu(repo_name)">
8 <i class="pointer icon-more"></i>
8 <i class="pointer icon-more"></i>
9 <div class="menu_items_container hidden">
9 <div class="menu_items_container hidden">
10 <ul class="menu_items">
10 <ul class="menu_items">
11 <li>
11 <li>
12 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
12 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
13 <span>${_('Summary')}</span>
13 <span>${_('Summary')}</span>
14 </a>
14 </a>
15 </li>
15 </li>
16 <li>
16 <li>
17 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo_name)}">
17 <a title="${_('Changelog')}" href="${h.route_path('repo_changelog',repo_name=repo_name)}">
18 <span>${_('Changelog')}</span>
18 <span>${_('Changelog')}</span>
19 </a>
19 </a>
20 </li>
20 </li>
21 <li>
21 <li>
22 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
22 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
23 <span>${_('Files')}</span>
23 <span>${_('Files')}</span>
24 </a>
24 </a>
25 </li>
25 </li>
26 <li>
26 <li>
27 <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
27 <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
28 <span>${_('Fork')}</span>
28 <span>${_('Fork')}</span>
29 </a>
29 </a>
30 </li>
30 </li>
31 </ul>
31 </ul>
32 </div>
32 </div>
33 </%def>
33 </%def>
34
34
35 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
35 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
36 <%
36 <%
37 def get_name(name,short_name=short_name):
37 def get_name(name,short_name=short_name):
38 if short_name:
38 if short_name:
39 return name.split('/')[-1]
39 return name.split('/')[-1]
40 else:
40 else:
41 return name
41 return name
42 %>
42 %>
43 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
43 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
44 ##NAME
44 ##NAME
45 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
45 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
46
46
47 ##TYPE OF REPO
47 ##TYPE OF REPO
48 %if h.is_hg(rtype):
48 %if h.is_hg(rtype):
49 <span title="${_('Mercurial repository')}"><i class="icon-hg"></i></span>
49 <span title="${_('Mercurial repository')}"><i class="icon-hg"></i></span>
50 %elif h.is_git(rtype):
50 %elif h.is_git(rtype):
51 <span title="${_('Git repository')}"><i class="icon-git"></i></span>
51 <span title="${_('Git repository')}"><i class="icon-git"></i></span>
52 %elif h.is_svn(rtype):
52 %elif h.is_svn(rtype):
53 <span title="${_('Subversion repository')}"><i class="icon-svn"></i></span>
53 <span title="${_('Subversion repository')}"><i class="icon-svn"></i></span>
54 %endif
54 %endif
55
55
56 ##PRIVATE/PUBLIC
56 ##PRIVATE/PUBLIC
57 %if private and c.visual.show_private_icon:
57 %if private and c.visual.show_private_icon:
58 <i class="icon-lock" title="${_('Private repository')}"></i>
58 <i class="icon-lock" title="${_('Private repository')}"></i>
59 %elif not private and c.visual.show_public_icon:
59 %elif not private and c.visual.show_public_icon:
60 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
60 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
61 %else:
61 %else:
62 <span></span>
62 <span></span>
63 %endif
63 %endif
64 ${get_name(name)}
64 ${get_name(name)}
65 </a>
65 </a>
66 %if fork_of:
66 %if fork_of:
67 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
67 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
68 %endif
68 %endif
69 %if rstate == 'repo_state_pending':
69 %if rstate == 'repo_state_pending':
70 <i class="icon-cogs" title="${_('Repository creating in progress...')}"></i>
70 <i class="icon-cogs" title="${_('Repository creating in progress...')}"></i>
71 %endif
71 %endif
72 </div>
72 </div>
73 </%def>
73 </%def>
74
74
75 <%def name="repo_desc(description)">
75 <%def name="repo_desc(description)">
76 <div class="truncate-wrap">${description}</div>
76 <div class="truncate-wrap">${description}</div>
77 </%def>
77 </%def>
78
78
79 <%def name="last_change(last_change)">
79 <%def name="last_change(last_change)">
80 ${h.age_component(last_change)}
80 ${h.age_component(last_change)}
81 </%def>
81 </%def>
82
82
83 <%def name="revision(name,rev,tip,author,last_msg)">
83 <%def name="revision(name,rev,tip,author,last_msg)">
84 <div>
84 <div>
85 %if rev >= 0:
85 %if rev >= 0:
86 <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
86 <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
87 %else:
87 %else:
88 ${_('No commits yet')}
88 ${_('No commits yet')}
89 %endif
89 %endif
90 </div>
90 </div>
91 </%def>
91 </%def>
92
92
93 <%def name="rss(name)">
93 <%def name="rss(name)">
94 %if c.rhodecode_user.username != h.DEFAULT_USER:
94 %if c.rhodecode_user.username != h.DEFAULT_USER:
95 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
95 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
96 %else:
96 %else:
97 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
97 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
98 %endif
98 %endif
99 </%def>
99 </%def>
100
100
101 <%def name="atom(name)">
101 <%def name="atom(name)">
102 %if c.rhodecode_user.username != h.DEFAULT_USER:
102 %if c.rhodecode_user.username != h.DEFAULT_USER:
103 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
103 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
104 %else:
104 %else:
105 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
105 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
106 %endif
106 %endif
107 </%def>
107 </%def>
108
108
109 <%def name="user_gravatar(email, size=16)">
109 <%def name="user_gravatar(email, size=16)">
110 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
110 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
111 ${base.gravatar(email, 16)}
111 ${base.gravatar(email, 16)}
112 </div>
112 </div>
113 </%def>
113 </%def>
114
114
115 <%def name="repo_actions(repo_name, super_user=True)">
115 <%def name="repo_actions(repo_name, super_user=True)">
116 <div>
116 <div>
117 <div class="grid_edit">
117 <div class="grid_edit">
118 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
118 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
119 <i class="icon-pencil"></i>Edit</a>
119 <i class="icon-pencil"></i>Edit</a>
120 </div>
120 </div>
121 <div class="grid_delete">
121 <div class="grid_delete">
122 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), method='POST', request=request)}
122 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), method='POST', request=request)}
123 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
123 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
124 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
124 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
125 ${h.end_form()}
125 ${h.end_form()}
126 </div>
126 </div>
127 </div>
127 </div>
128 </%def>
128 </%def>
129
129
130 <%def name="repo_state(repo_state)">
130 <%def name="repo_state(repo_state)">
131 <div>
131 <div>
132 %if repo_state == 'repo_state_pending':
132 %if repo_state == 'repo_state_pending':
133 <div class="tag tag4">${_('Creating')}</div>
133 <div class="tag tag4">${_('Creating')}</div>
134 %elif repo_state == 'repo_state_created':
134 %elif repo_state == 'repo_state_created':
135 <div class="tag tag1">${_('Created')}</div>
135 <div class="tag tag1">${_('Created')}</div>
136 %else:
136 %else:
137 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
137 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
138 %endif
138 %endif
139 </div>
139 </div>
140 </%def>
140 </%def>
141
141
142
142
143 ## REPO GROUP RENDERERS
143 ## REPO GROUP RENDERERS
144 <%def name="quick_repo_group_menu(repo_group_name)">
144 <%def name="quick_repo_group_menu(repo_group_name)">
145 <i class="pointer icon-more"></i>
145 <i class="pointer icon-more"></i>
146 <div class="menu_items_container hidden">
146 <div class="menu_items_container hidden">
147 <ul class="menu_items">
147 <ul class="menu_items">
148 <li>
148 <li>
149 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
149 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
150 <span class="icon">
150 <span class="icon">
151 <i class="icon-file-text"></i>
151 <i class="icon-file-text"></i>
152 </span>
152 </span>
153 <span>${_('Summary')}</span>
153 <span>${_('Summary')}</span>
154 </a>
154 </a>
155 </li>
155 </li>
156
156
157 </ul>
157 </ul>
158 </div>
158 </div>
159 </%def>
159 </%def>
160
160
161 <%def name="repo_group_name(repo_group_name, children_groups=None)">
161 <%def name="repo_group_name(repo_group_name, children_groups=None)">
162 <div>
162 <div>
163 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
163 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
164 <i class="icon-folder-close" title="${_('Repository group')}"></i>
164 <i class="icon-folder-close" title="${_('Repository group')}"></i>
165 %if children_groups:
165 %if children_groups:
166 ${h.literal(' &raquo; '.join(children_groups))}
166 ${h.literal(' &raquo; '.join(children_groups))}
167 %else:
167 %else:
168 ${repo_group_name}
168 ${repo_group_name}
169 %endif
169 %endif
170 </a>
170 </a>
171 </div>
171 </div>
172 </%def>
172 </%def>
173
173
174 <%def name="repo_group_desc(description)">
174 <%def name="repo_group_desc(description)">
175 <div class="truncate-wrap">${description}</div>
175 <div class="truncate-wrap">${description}</div>
176 </%def>
176 </%def>
177
177
178 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
178 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
179 <div class="grid_edit">
179 <div class="grid_edit">
180 <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
180 <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
181 </div>
181 </div>
182 <div class="grid_delete">
182 <div class="grid_delete">
183 ${h.secure_form(h.url('delete_repo_group', group_name=repo_group_name),method='delete')}
183 ${h.secure_form(h.url('delete_repo_group', group_name=repo_group_name),method='delete')}
184 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
184 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
185 onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
185 onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
186 ${h.end_form()}
186 ${h.end_form()}
187 </div>
187 </div>
188 </%def>
188 </%def>
189
189
190
190
191 <%def name="user_actions(user_id, username)">
191 <%def name="user_actions(user_id, username)">
192 <div class="grid_edit">
192 <div class="grid_edit">
193 <a href="${h.url('edit_user',user_id=user_id)}" title="${_('Edit')}">
193 <a href="${h.url('edit_user',user_id=user_id)}" title="${_('Edit')}">
194 <i class="icon-pencil"></i>Edit</a>
194 <i class="icon-pencil"></i>Edit</a>
195 </div>
195 </div>
196 <div class="grid_delete">
196 <div class="grid_delete">
197 ${h.secure_form(h.url('delete_user', user_id=user_id),method='delete')}
197 ${h.secure_form(h.url('delete_user', user_id=user_id),method='delete')}
198 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
198 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
199 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
199 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
200 ${h.end_form()}
200 ${h.end_form()}
201 </div>
201 </div>
202 </%def>
202 </%def>
203
203
204 <%def name="user_group_actions(user_group_id, user_group_name)">
204 <%def name="user_group_actions(user_group_id, user_group_name)">
205 <div class="grid_edit">
205 <div class="grid_edit">
206 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
206 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
207 </div>
207 </div>
208 <div class="grid_delete">
208 <div class="grid_delete">
209 ${h.secure_form(h.url('delete_users_group', user_group_id=user_group_id),method='delete')}
209 ${h.secure_form(h.url('delete_users_group', user_group_id=user_group_id),method='delete')}
210 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
210 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
211 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
211 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
212 ${h.end_form()}
212 ${h.end_form()}
213 </div>
213 </div>
214 </%def>
214 </%def>
215
215
216
216
217 <%def name="user_name(user_id, username)">
217 <%def name="user_name(user_id, username)">
218 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.url('edit_user', user_id=user_id))}
218 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.url('edit_user', user_id=user_id))}
219 </%def>
219 </%def>
220
220
221 <%def name="user_profile(username)">
221 <%def name="user_profile(username)">
222 ${base.gravatar_with_user(username, 16)}
222 ${base.gravatar_with_user(username, 16)}
223 </%def>
223 </%def>
224
224
225 <%def name="user_group_name(user_group_id, user_group_name)">
225 <%def name="user_group_name(user_group_id, user_group_name)">
226 <div>
226 <div>
227 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}">
227 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}">
228 <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a>
228 <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a>
229 </div>
229 </div>
230 </%def>
230 </%def>
231
231
232
232
233 ## GISTS
233 ## GISTS
234
234
235 <%def name="gist_gravatar(full_contact)">
235 <%def name="gist_gravatar(full_contact)">
236 <div class="gist_gravatar">
236 <div class="gist_gravatar">
237 ${base.gravatar(full_contact, 30)}
237 ${base.gravatar(full_contact, 30)}
238 </div>
238 </div>
239 </%def>
239 </%def>
240
240
241 <%def name="gist_access_id(gist_access_id, full_contact)">
241 <%def name="gist_access_id(gist_access_id, full_contact)">
242 <div>
242 <div>
243 <b>
243 <b>
244 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
244 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
245 </b>
245 </b>
246 </div>
246 </div>
247 </%def>
247 </%def>
248
248
249 <%def name="gist_author(full_contact, created_on, expires)">
249 <%def name="gist_author(full_contact, created_on, expires)">
250 ${base.gravatar_with_user(full_contact, 16)}
250 ${base.gravatar_with_user(full_contact, 16)}
251 </%def>
251 </%def>
252
252
253
253
254 <%def name="gist_created(created_on)">
254 <%def name="gist_created(created_on)">
255 <div class="created">
255 <div class="created">
256 ${h.age_component(created_on, time_is_local=True)}
256 ${h.age_component(created_on, time_is_local=True)}
257 </div>
257 </div>
258 </%def>
258 </%def>
259
259
260 <%def name="gist_expires(expires)">
260 <%def name="gist_expires(expires)">
261 <div class="created">
261 <div class="created">
262 %if expires == -1:
262 %if expires == -1:
263 ${_('never')}
263 ${_('never')}
264 %else:
264 %else:
265 ${h.age_component(h.time_to_utcdatetime(expires))}
265 ${h.age_component(h.time_to_utcdatetime(expires))}
266 %endif
266 %endif
267 </div>
267 </div>
268 </%def>
268 </%def>
269
269
270 <%def name="gist_type(gist_type)">
270 <%def name="gist_type(gist_type)">
271 %if gist_type != 'public':
271 %if gist_type != 'public':
272 <div class="tag">${_('Private')}</div>
272 <div class="tag">${_('Private')}</div>
273 %endif
273 %endif
274 </%def>
274 </%def>
275
275
276 <%def name="gist_description(gist_description)">
276 <%def name="gist_description(gist_description)">
277 ${gist_description}
277 ${gist_description}
278 </%def>
278 </%def>
279
279
280
280
281 ## PULL REQUESTS GRID RENDERERS
281 ## PULL REQUESTS GRID RENDERERS
282
282
283 <%def name="pullrequest_target_repo(repo_name)">
283 <%def name="pullrequest_target_repo(repo_name)">
284 <div class="truncate">
284 <div class="truncate">
285 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
285 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
286 </div>
286 </div>
287 </%def>
287 </%def>
288 <%def name="pullrequest_status(status)">
288 <%def name="pullrequest_status(status)">
289 <div class="${'flag_status %s' % status} pull-left"></div>
289 <div class="${'flag_status %s' % status} pull-left"></div>
290 </%def>
290 </%def>
291
291
292 <%def name="pullrequest_title(title, description)">
292 <%def name="pullrequest_title(title, description)">
293 ${title} <br/>
293 ${title} <br/>
294 ${h.shorter(description, 40)}
294 ${h.shorter(description, 40)}
295 </%def>
295 </%def>
296
296
297 <%def name="pullrequest_comments(comments_nr)">
297 <%def name="pullrequest_comments(comments_nr)">
298 <i class="icon-comment"></i> ${comments_nr}
298 <i class="icon-comment"></i> ${comments_nr}
299 </%def>
299 </%def>
300
300
301 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
301 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
302 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
302 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
303 % if short:
303 % if short:
304 #${pull_request_id}
304 #${pull_request_id}
305 % else:
305 % else:
306 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
306 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
307 % endif
307 % endif
308 </a>
308 </a>
309 </%def>
309 </%def>
310
310
311 <%def name="pullrequest_updated_on(updated_on)">
311 <%def name="pullrequest_updated_on(updated_on)">
312 ${h.age_component(h.time_to_utcdatetime(updated_on))}
312 ${h.age_component(h.time_to_utcdatetime(updated_on))}
313 </%def>
313 </%def>
314
314
315 <%def name="pullrequest_author(full_contact)">
315 <%def name="pullrequest_author(full_contact)">
316 ${base.gravatar_with_user(full_contact, 16)}
316 ${base.gravatar_with_user(full_contact, 16)}
317 </%def>
317 </%def>
@@ -1,324 +1,324 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title(*args)">
3 <%def name="title(*args)">
4 ${_('%s Files') % c.repo_name}
4 ${_('%s Files') % c.repo_name}
5 %if hasattr(c,'file'):
5 %if hasattr(c,'file'):
6 &middot; ${h.safe_unicode(c.file.path) or '\\'}
6 &middot; ${h.safe_unicode(c.file.path) or '\\'}
7 %endif
7 %endif
8
8
9 %if c.rhodecode_name:
9 %if c.rhodecode_name:
10 &middot; ${h.branding(c.rhodecode_name)}
10 &middot; ${h.branding(c.rhodecode_name)}
11 %endif
11 %endif
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()">
15 ${_('Files')}
15 ${_('Files')}
16 %if c.file:
16 %if c.file:
17 @ ${h.show_id(c.commit)}
17 @ ${h.show_id(c.commit)}
18 %endif
18 %endif
19 </%def>
19 </%def>
20
20
21 <%def name="menu_bar_nav()">
21 <%def name="menu_bar_nav()">
22 ${self.menu_items(active='repositories')}
22 ${self.menu_items(active='repositories')}
23 </%def>
23 </%def>
24
24
25 <%def name="menu_bar_subnav()">
25 <%def name="menu_bar_subnav()">
26 ${self.repo_menu(active='files')}
26 ${self.repo_menu(active='files')}
27 </%def>
27 </%def>
28
28
29 <%def name="main()">
29 <%def name="main()">
30 <div class="title">
30 <div class="title">
31 ${self.repo_page_title(c.rhodecode_db_repo)}
31 ${self.repo_page_title(c.rhodecode_db_repo)}
32 </div>
32 </div>
33
33
34 <div id="pjax-container" class="summary">
34 <div id="pjax-container" class="summary">
35 <div id="files_data">
35 <div id="files_data">
36 <%include file='files_pjax.mako'/>
36 <%include file='files_pjax.mako'/>
37 </div>
37 </div>
38 </div>
38 </div>
39 <script>
39 <script>
40 var curState = {
40 var curState = {
41 commit_id: "${c.commit.raw_id}"
41 commit_id: "${c.commit.raw_id}"
42 };
42 };
43
43
44 var getState = function(context) {
44 var getState = function(context) {
45 var url = $(location).attr('href');
45 var url = $(location).attr('href');
46 var _base_url = '${h.route_path("repo_files",repo_name=c.repo_name,commit_id='',f_path='')}';
46 var _base_url = '${h.route_path("repo_files",repo_name=c.repo_name,commit_id='',f_path='')}';
47 var _annotate_url = '${h.route_path("repo_files:annotated",repo_name=c.repo_name,commit_id='',f_path='')}';
47 var _annotate_url = '${h.route_path("repo_files:annotated",repo_name=c.repo_name,commit_id='',f_path='')}';
48 _base_url = _base_url.replace('//', '/');
48 _base_url = _base_url.replace('//', '/');
49 _annotate_url = _annotate_url.replace('//', '/');
49 _annotate_url = _annotate_url.replace('//', '/');
50
50
51 //extract f_path from url.
51 //extract f_path from url.
52 var parts = url.split(_base_url);
52 var parts = url.split(_base_url);
53 if (parts.length != 2) {
53 if (parts.length != 2) {
54 parts = url.split(_annotate_url);
54 parts = url.split(_annotate_url);
55 if (parts.length != 2) {
55 if (parts.length != 2) {
56 var rev = "tip";
56 var rev = "tip";
57 var f_path = "";
57 var f_path = "";
58 } else {
58 } else {
59 var parts2 = parts[1].split('/');
59 var parts2 = parts[1].split('/');
60 var rev = parts2.shift(); // pop the first element which is the revision
60 var rev = parts2.shift(); // pop the first element which is the revision
61 var f_path = parts2.join('/');
61 var f_path = parts2.join('/');
62 }
62 }
63
63
64 } else {
64 } else {
65 var parts2 = parts[1].split('/');
65 var parts2 = parts[1].split('/');
66 var rev = parts2.shift(); // pop the first element which is the revision
66 var rev = parts2.shift(); // pop the first element which is the revision
67 var f_path = parts2.join('/');
67 var f_path = parts2.join('/');
68 }
68 }
69
69
70 var _node_list_url = pyroutes.url('repo_files_nodelist',
70 var _node_list_url = pyroutes.url('repo_files_nodelist',
71 {repo_name: templateContext.repo_name,
71 {repo_name: templateContext.repo_name,
72 commit_id: rev, f_path: f_path});
72 commit_id: rev, f_path: f_path});
73 var _url_base = pyroutes.url('repo_files',
73 var _url_base = pyroutes.url('repo_files',
74 {repo_name: templateContext.repo_name,
74 {repo_name: templateContext.repo_name,
75 commit_id: rev, f_path:'__FPATH__'});
75 commit_id: rev, f_path:'__FPATH__'});
76 return {
76 return {
77 url: url,
77 url: url,
78 f_path: f_path,
78 f_path: f_path,
79 rev: rev,
79 rev: rev,
80 commit_id: curState.commit_id,
80 commit_id: curState.commit_id,
81 node_list_url: _node_list_url,
81 node_list_url: _node_list_url,
82 url_base: _url_base
82 url_base: _url_base
83 };
83 };
84 };
84 };
85
85
86 var metadataRequest = null;
86 var metadataRequest = null;
87 var getFilesMetadata = function() {
87 var getFilesMetadata = function() {
88 if (metadataRequest && metadataRequest.readyState != 4) {
88 if (metadataRequest && metadataRequest.readyState != 4) {
89 metadataRequest.abort();
89 metadataRequest.abort();
90 }
90 }
91 if (fileSourcePage) {
91 if (fileSourcePage) {
92 return false;
92 return false;
93 }
93 }
94
94
95 if ($('#file-tree-wrapper').hasClass('full-load')) {
95 if ($('#file-tree-wrapper').hasClass('full-load')) {
96 // in case our HTML wrapper has full-load class we don't
96 // in case our HTML wrapper has full-load class we don't
97 // trigger the async load of metadata
97 // trigger the async load of metadata
98 return false;
98 return false;
99 }
99 }
100
100
101 var state = getState('metadata');
101 var state = getState('metadata');
102 var url_data = {
102 var url_data = {
103 'repo_name': templateContext.repo_name,
103 'repo_name': templateContext.repo_name,
104 'commit_id': state.commit_id,
104 'commit_id': state.commit_id,
105 'f_path': state.f_path
105 'f_path': state.f_path
106 };
106 };
107
107
108 var url = pyroutes.url('repo_nodetree_full', url_data);
108 var url = pyroutes.url('repo_nodetree_full', url_data);
109
109
110 metadataRequest = $.ajax({url: url});
110 metadataRequest = $.ajax({url: url});
111
111
112 metadataRequest.done(function(data) {
112 metadataRequest.done(function(data) {
113 $('#file-tree').html(data);
113 $('#file-tree').html(data);
114 timeagoActivate();
114 timeagoActivate();
115 });
115 });
116 metadataRequest.fail(function (data, textStatus, errorThrown) {
116 metadataRequest.fail(function (data, textStatus, errorThrown) {
117 console.log(data);
117 console.log(data);
118 if (data.status != 0) {
118 if (data.status != 0) {
119 alert("Error while fetching metadata.\nError code {0} ({1}).Please consider reloading the page".format(data.status,data.statusText));
119 alert("Error while fetching metadata.\nError code {0} ({1}).Please consider reloading the page".format(data.status,data.statusText));
120 }
120 }
121 });
121 });
122 };
122 };
123
123
124 var callbacks = function() {
124 var callbacks = function() {
125 var state = getState('callbacks');
125 var state = getState('callbacks');
126 timeagoActivate();
126 timeagoActivate();
127
127
128 // used for history, and switch to
128 // used for history, and switch to
129 var initialCommitData = {
129 var initialCommitData = {
130 id: null,
130 id: null,
131 text: '${_("Pick Commit")}',
131 text: '${_("Pick Commit")}',
132 type: 'sha',
132 type: 'sha',
133 raw_id: null,
133 raw_id: null,
134 files_url: null
134 files_url: null
135 };
135 };
136
136
137 if ($('#trimmed_message_box').height() < 50) {
137 if ($('#trimmed_message_box').height() < 50) {
138 $('#message_expand').hide();
138 $('#message_expand').hide();
139 }
139 }
140
140
141 $('#message_expand').on('click', function(e) {
141 $('#message_expand').on('click', function(e) {
142 $('#trimmed_message_box').css('max-height', 'none');
142 $('#trimmed_message_box').css('max-height', 'none');
143 $(this).hide();
143 $(this).hide();
144 });
144 });
145
145
146 if (fileSourcePage) {
146 if (fileSourcePage) {
147 // variants for with source code, not tree view
147 // variants for with source code, not tree view
148
148
149 // select code link event
149 // select code link event
150 $("#hlcode").mouseup(getSelectionLink);
150 $("#hlcode").mouseup(getSelectionLink);
151
151
152 // file history select2
152 // file history select2
153 select2FileHistorySwitcher('#diff1', initialCommitData, state);
153 select2FileHistorySwitcher('#diff1', initialCommitData, state);
154
154
155 // show at, diff to actions handlers
155 // show at, diff to actions handlers
156 $('#diff1').on('change', function(e) {
156 $('#diff1').on('change', function(e) {
157 $('#diff_to_commit').removeClass('disabled').removeAttr("disabled");
157 $('#diff_to_commit').removeClass('disabled').removeAttr("disabled");
158 $('#diff_to_commit').val(_gettext('Diff to Commit ') + e.val.truncateAfter(8, '...'));
158 $('#diff_to_commit').val(_gettext('Diff to Commit ') + e.val.truncateAfter(8, '...'));
159
159
160 $('#show_at_commit').removeClass('disabled').removeAttr("disabled");
160 $('#show_at_commit').removeClass('disabled').removeAttr("disabled");
161 $('#show_at_commit').val(_gettext('Show at Commit ') + e.val.truncateAfter(8, '...'));
161 $('#show_at_commit').val(_gettext('Show at Commit ') + e.val.truncateAfter(8, '...'));
162 });
162 });
163
163
164 $('#diff_to_commit').on('click', function(e) {
164 $('#diff_to_commit').on('click', function(e) {
165 var diff1 = $('#diff1').val();
165 var diff1 = $('#diff1').val();
166 var diff2 = $('#diff2').val();
166 var diff2 = $('#diff2').val();
167
167
168 var url_data = {
168 var url_data = {
169 repo_name: templateContext.repo_name,
169 repo_name: templateContext.repo_name,
170 source_ref: diff1,
170 source_ref: diff1,
171 source_ref_type: 'rev',
171 source_ref_type: 'rev',
172 target_ref: diff2,
172 target_ref: diff2,
173 target_ref_type: 'rev',
173 target_ref_type: 'rev',
174 merge: 1,
174 merge: 1,
175 f_path: state.f_path
175 f_path: state.f_path
176 };
176 };
177 window.location = pyroutes.url('compare_url', url_data);
177 window.location = pyroutes.url('compare_url', url_data);
178 });
178 });
179
179
180 $('#show_at_commit').on('click', function(e) {
180 $('#show_at_commit').on('click', function(e) {
181 var diff1 = $('#diff1').val();
181 var diff1 = $('#diff1').val();
182
182
183 var annotate = $('#annotate').val();
183 var annotate = $('#annotate').val();
184 if (annotate === "True") {
184 if (annotate === "True") {
185 var url = pyroutes.url('repo_files:annotated',
185 var url = pyroutes.url('repo_files:annotated',
186 {'repo_name': templateContext.repo_name,
186 {'repo_name': templateContext.repo_name,
187 'commit_id': diff1, 'f_path': state.f_path});
187 'commit_id': diff1, 'f_path': state.f_path});
188 } else {
188 } else {
189 var url = pyroutes.url('repo_files',
189 var url = pyroutes.url('repo_files',
190 {'repo_name': templateContext.repo_name,
190 {'repo_name': templateContext.repo_name,
191 'commit_id': diff1, 'f_path': state.f_path});
191 'commit_id': diff1, 'f_path': state.f_path});
192 }
192 }
193 window.location = url;
193 window.location = url;
194
194
195 });
195 });
196
196
197 // show more authors
197 // show more authors
198 $('#show_authors').on('click', function(e) {
198 $('#show_authors').on('click', function(e) {
199 e.preventDefault();
199 e.preventDefault();
200 var url = pyroutes.url('repo_file_authors',
200 var url = pyroutes.url('repo_file_authors',
201 {'repo_name': templateContext.repo_name,
201 {'repo_name': templateContext.repo_name,
202 'commit_id': state.rev, 'f_path': state.f_path});
202 'commit_id': state.rev, 'f_path': state.f_path});
203
203
204 $.pjax({
204 $.pjax({
205 url: url,
205 url: url,
206 data: 'annotate=${"1" if c.annotate else "0"}',
206 data: 'annotate=${"1" if c.annotate else "0"}',
207 container: '#file_authors',
207 container: '#file_authors',
208 push: false,
208 push: false,
209 timeout: pjaxTimeout
209 timeout: pjaxTimeout
210 }).complete(function(){
210 }).complete(function(){
211 $('#show_authors').hide();
211 $('#show_authors').hide();
212 })
212 })
213 });
213 });
214
214
215 // load file short history
215 // load file short history
216 $('#file_history_overview').on('click', function(e) {
216 $('#file_history_overview').on('click', function(e) {
217 e.preventDefault();
217 e.preventDefault();
218 path = state.f_path;
218 path = state.f_path;
219 if (path.indexOf("#") >= 0) {
219 if (path.indexOf("#") >= 0) {
220 path = path.slice(0, path.indexOf("#"));
220 path = path.slice(0, path.indexOf("#"));
221 }
221 }
222 var url = pyroutes.url('changelog_file_home',
222 var url = pyroutes.url('repo_changelog_file',
223 {'repo_name': templateContext.repo_name,
223 {'repo_name': templateContext.repo_name,
224 'revision': state.rev, 'f_path': path, 'limit': 6});
224 'commit_id': state.rev, 'f_path': path, 'limit': 6});
225 $('#file_history_container').show();
225 $('#file_history_container').show();
226 $('#file_history_container').html('<div class="file-history-inner">{0}</div>'.format(_gettext('Loading ...')));
226 $('#file_history_container').html('<div class="file-history-inner">{0}</div>'.format(_gettext('Loading ...')));
227
227
228 $.pjax({
228 $.pjax({
229 url: url,
229 url: url,
230 container: '#file_history_container',
230 container: '#file_history_container',
231 push: false,
231 push: false,
232 timeout: pjaxTimeout
232 timeout: pjaxTimeout
233 })
233 })
234 });
234 });
235
235
236 }
236 }
237 else {
237 else {
238 getFilesMetadata();
238 getFilesMetadata();
239
239
240 // fuzzy file filter
240 // fuzzy file filter
241 fileBrowserListeners(state.node_list_url, state.url_base);
241 fileBrowserListeners(state.node_list_url, state.url_base);
242
242
243 // switch to widget
243 // switch to widget
244 select2RefSwitcher('#refs_filter', initialCommitData);
244 select2RefSwitcher('#refs_filter', initialCommitData);
245 $('#refs_filter').on('change', function(e) {
245 $('#refs_filter').on('change', function(e) {
246 var data = $('#refs_filter').select2('data');
246 var data = $('#refs_filter').select2('data');
247 curState.commit_id = data.raw_id;
247 curState.commit_id = data.raw_id;
248 $.pjax({url: data.files_url, container: '#pjax-container', timeout: pjaxTimeout});
248 $.pjax({url: data.files_url, container: '#pjax-container', timeout: pjaxTimeout});
249 });
249 });
250
250
251 $("#prev_commit_link").on('click', function(e) {
251 $("#prev_commit_link").on('click', function(e) {
252 var data = $(this).data();
252 var data = $(this).data();
253 curState.commit_id = data.commitId;
253 curState.commit_id = data.commitId;
254 });
254 });
255
255
256 $("#next_commit_link").on('click', function(e) {
256 $("#next_commit_link").on('click', function(e) {
257 var data = $(this).data();
257 var data = $(this).data();
258 curState.commit_id = data.commitId;
258 curState.commit_id = data.commitId;
259 });
259 });
260
260
261 $('#at_rev').on("keypress", function(e) {
261 $('#at_rev').on("keypress", function(e) {
262 /* ENTER PRESSED */
262 /* ENTER PRESSED */
263 if (e.keyCode === 13) {
263 if (e.keyCode === 13) {
264 var rev = $('#at_rev').val();
264 var rev = $('#at_rev').val();
265 // explicit reload page here. with pjax entering bad input
265 // explicit reload page here. with pjax entering bad input
266 // produces not so nice results
266 // produces not so nice results
267 window.location = pyroutes.url('repo_files',
267 window.location = pyroutes.url('repo_files',
268 {'repo_name': templateContext.repo_name,
268 {'repo_name': templateContext.repo_name,
269 'commit_id': rev, 'f_path': state.f_path});
269 'commit_id': rev, 'f_path': state.f_path});
270 }
270 }
271 });
271 });
272 }
272 }
273 };
273 };
274
274
275 var pjaxTimeout = 5000;
275 var pjaxTimeout = 5000;
276
276
277 $(document).pjax(".pjax-link", "#pjax-container", {
277 $(document).pjax(".pjax-link", "#pjax-container", {
278 "fragment": "#pjax-content",
278 "fragment": "#pjax-content",
279 "maxCacheLength": 1000,
279 "maxCacheLength": 1000,
280 "timeout": pjaxTimeout
280 "timeout": pjaxTimeout
281 });
281 });
282
282
283 // define global back/forward states
283 // define global back/forward states
284 var isPjaxPopState = false;
284 var isPjaxPopState = false;
285 $(document).on('pjax:popstate', function() {
285 $(document).on('pjax:popstate', function() {
286 isPjaxPopState = true;
286 isPjaxPopState = true;
287 });
287 });
288
288
289 $(document).on('pjax:end', function(xhr, options) {
289 $(document).on('pjax:end', function(xhr, options) {
290 if (isPjaxPopState) {
290 if (isPjaxPopState) {
291 isPjaxPopState = false;
291 isPjaxPopState = false;
292 callbacks();
292 callbacks();
293 _NODEFILTER.resetFilter();
293 _NODEFILTER.resetFilter();
294 }
294 }
295
295
296 // run callback for tracking if defined for google analytics etc.
296 // run callback for tracking if defined for google analytics etc.
297 // this is used to trigger tracking on pjax
297 // this is used to trigger tracking on pjax
298 if (typeof window.rhodecode_statechange_callback !== 'undefined') {
298 if (typeof window.rhodecode_statechange_callback !== 'undefined') {
299 var state = getState('statechange');
299 var state = getState('statechange');
300 rhodecode_statechange_callback(state.url, null)
300 rhodecode_statechange_callback(state.url, null)
301 }
301 }
302 });
302 });
303
303
304 $(document).on('pjax:success', function(event, xhr, options) {
304 $(document).on('pjax:success', function(event, xhr, options) {
305 if (event.target.id == "file_history_container") {
305 if (event.target.id == "file_history_container") {
306 $('#file_history_overview').hide();
306 $('#file_history_overview').hide();
307 $('#file_history_overview_full').show();
307 $('#file_history_overview_full').show();
308 timeagoActivate();
308 timeagoActivate();
309 } else {
309 } else {
310 callbacks();
310 callbacks();
311 }
311 }
312 });
312 });
313
313
314 $(document).ready(function() {
314 $(document).ready(function() {
315 callbacks();
315 callbacks();
316 var search_GET = "${request.GET.get('search','')}";
316 var search_GET = "${request.GET.get('search','')}";
317 if (search_GET == "1") {
317 if (search_GET == "1") {
318 _NODEFILTER.initFilter();
318 _NODEFILTER.initFilter();
319 }
319 }
320 });
320 });
321
321
322 </script>
322 </script>
323
323
324 </%def>
324 </%def>
@@ -1,196 +1,196 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('%s File Edit') % c.repo_name}
4 ${_('%s File Edit') % c.repo_name}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="menu_bar_nav()">
10 <%def name="menu_bar_nav()">
11 ${self.menu_items(active='repositories')}
11 ${self.menu_items(active='repositories')}
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()">
15 ${_('Edit file')} @ ${h.show_id(c.commit)}
15 ${_('Edit file')} @ ${h.show_id(c.commit)}
16 </%def>
16 </%def>
17
17
18 <%def name="menu_bar_subnav()">
18 <%def name="menu_bar_subnav()">
19 ${self.repo_menu(active='files')}
19 ${self.repo_menu(active='files')}
20 </%def>
20 </%def>
21
21
22 <%def name="main()">
22 <%def name="main()">
23 <% renderer = h.renderer_from_filename(c.f_path)%>
23 <% renderer = h.renderer_from_filename(c.f_path)%>
24 <div class="box">
24 <div class="box">
25 <div class="title">
25 <div class="title">
26 ${self.repo_page_title(c.rhodecode_db_repo)}
26 ${self.repo_page_title(c.rhodecode_db_repo)}
27 </div>
27 </div>
28 <div class="edit-file-title">
28 <div class="edit-file-title">
29 ${self.breadcrumbs()}
29 ${self.breadcrumbs()}
30 </div>
30 </div>
31 <div class="edit-file-fieldset">
31 <div class="edit-file-fieldset">
32 <div class="fieldset">
32 <div class="fieldset">
33 <div id="destination-label" class="left-label">
33 <div id="destination-label" class="left-label">
34 ${_('Path')}:
34 ${_('Path')}:
35 </div>
35 </div>
36 <div class="right-content">
36 <div class="right-content">
37 <div id="specify-custom-path-container">
37 <div id="specify-custom-path-container">
38 <span id="path-breadcrumbs">${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path)}</span>
38 <span id="path-breadcrumbs">${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path)}</span>
39 </div>
39 </div>
40 </div>
40 </div>
41 </div>
41 </div>
42 </div>
42 </div>
43
43
44 <div class="table">
44 <div class="table">
45 ${h.secure_form(h.route_path('repo_files_update_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', method='POST')}
45 ${h.secure_form(h.route_path('repo_files_update_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', method='POST')}
46 <div id="codeblock" class="codeblock" >
46 <div id="codeblock" class="codeblock" >
47 <div class="code-header">
47 <div class="code-header">
48 <div class="stats">
48 <div class="stats">
49 <i class="icon-file"></i>
49 <i class="icon-file"></i>
50 <span class="item">${h.link_to("r%s:%s" % (c.file.commit.idx,h.short_id(c.file.commit.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.commit.raw_id))}</span>
50 <span class="item">${h.link_to("r%s:%s" % (c.file.commit.idx,h.short_id(c.file.commit.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.commit.raw_id))}</span>
51 <span class="item">${h.format_byte_size_binary(c.file.size)}</span>
51 <span class="item">${h.format_byte_size_binary(c.file.size)}</span>
52 <span class="item last">${c.file.mimetype}</span>
52 <span class="item last">${c.file.mimetype}</span>
53 <div class="buttons">
53 <div class="buttons">
54 <a class="btn btn-mini" href="${h.url('changelog_file_home',repo_name=c.repo_name, revision=c.commit.raw_id, f_path=c.f_path)}">
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 <i class="icon-time"></i> ${_('history')}
55 <i class="icon-time"></i> ${_('history')}
56 </a>
56 </a>
57
57
58 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
58 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
59 % if not c.file.is_binary:
59 % if not c.file.is_binary:
60 %if True:
60 %if True:
61 ${h.link_to(_('source'), h.route_path('repo_files', repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),class_="btn btn-mini")}
61 ${h.link_to(_('source'), h.route_path('repo_files', repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),class_="btn btn-mini")}
62 %else:
62 %else:
63 ${h.link_to(_('annotation'),h.route_path('repo_files:annotated',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),class_="btn btn-mini")}
63 ${h.link_to(_('annotation'),h.route_path('repo_files:annotated',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),class_="btn btn-mini")}
64 %endif
64 %endif
65
65
66 <a class="btn btn-mini" href="${h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
66 <a class="btn btn-mini" href="${h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
67 ${_('raw')}
67 ${_('raw')}
68 </a>
68 </a>
69 <a class="btn btn-mini" href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
69 <a class="btn btn-mini" href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
70 <i class="icon-archive"></i> ${_('download')}
70 <i class="icon-archive"></i> ${_('download')}
71 </a>
71 </a>
72 % endif
72 % endif
73 % endif
73 % endif
74 </div>
74 </div>
75 </div>
75 </div>
76 <div class="form">
76 <div class="form">
77 <label for="set_mode">${_('Editing file')}:</label>
77 <label for="set_mode">${_('Editing file')}:</label>
78 ${'%s /' % c.file.dir_path if c.file.dir_path else c.file.dir_path}
78 ${'%s /' % c.file.dir_path if c.file.dir_path else c.file.dir_path}
79 <input id="filename" type="text" name="filename" value="${c.file.name}">
79 <input id="filename" type="text" name="filename" value="${c.file.name}">
80
80
81 ${h.dropdownmenu('set_mode','plain',[('plain',_('plain'))],enable_filter=True)}
81 ${h.dropdownmenu('set_mode','plain',[('plain',_('plain'))],enable_filter=True)}
82 <label for="line_wrap">${_('line wraps')}</label>
82 <label for="line_wrap">${_('line wraps')}</label>
83 ${h.dropdownmenu('line_wrap', 'off', [('on', _('on')), ('off', _('off')),])}
83 ${h.dropdownmenu('line_wrap', 'off', [('on', _('on')), ('off', _('off')),])}
84
84
85 <div id="render_preview" class="btn btn-small preview hidden">${_('Preview')}</div>
85 <div id="render_preview" class="btn btn-small preview hidden">${_('Preview')}</div>
86 </div>
86 </div>
87 </div>
87 </div>
88 <div id="editor_container">
88 <div id="editor_container">
89 <pre id="editor_pre"></pre>
89 <pre id="editor_pre"></pre>
90 <textarea id="editor" name="content" >${h.escape(c.file.content)|n}</textarea>
90 <textarea id="editor" name="content" >${h.escape(c.file.content)|n}</textarea>
91 <div id="editor_preview" ></div>
91 <div id="editor_preview" ></div>
92 </div>
92 </div>
93 </div>
93 </div>
94 </div>
94 </div>
95
95
96 <div class="edit-file-fieldset">
96 <div class="edit-file-fieldset">
97 <div class="fieldset">
97 <div class="fieldset">
98 <div id="commit-message-label" class="commit-message-label left-label">
98 <div id="commit-message-label" class="commit-message-label left-label">
99 ${_('Commit Message')}:
99 ${_('Commit Message')}:
100 </div>
100 </div>
101 <div class="right-content">
101 <div class="right-content">
102 <div class="message">
102 <div class="message">
103 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
103 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
104 </div>
104 </div>
105 </div>
105 </div>
106 </div>
106 </div>
107 <div class="pull-right">
107 <div class="pull-right">
108 ${h.reset('reset',_('Cancel'),class_="btn btn-small")}
108 ${h.reset('reset',_('Cancel'),class_="btn btn-small")}
109 ${h.submit('commit',_('Commit changes'),class_="btn btn-small btn-success")}
109 ${h.submit('commit',_('Commit changes'),class_="btn btn-small btn-success")}
110 </div>
110 </div>
111 </div>
111 </div>
112 ${h.end_form()}
112 ${h.end_form()}
113 </div>
113 </div>
114
114
115 <script type="text/javascript">
115 <script type="text/javascript">
116 $(document).ready(function(){
116 $(document).ready(function(){
117 var renderer = "${renderer}";
117 var renderer = "${renderer}";
118 var reset_url = "${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.file.path)}";
118 var reset_url = "${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.file.path)}";
119 var myCodeMirror = initCodeMirror('editor', reset_url);
119 var myCodeMirror = initCodeMirror('editor', reset_url);
120
120
121 var modes_select = $('#set_mode');
121 var modes_select = $('#set_mode');
122 fillCodeMirrorOptions(modes_select);
122 fillCodeMirrorOptions(modes_select);
123
123
124 // try to detect the mode based on the file we edit
124 // try to detect the mode based on the file we edit
125 var mimetype = "${c.file.mimetype}";
125 var mimetype = "${c.file.mimetype}";
126 var detected_mode = detectCodeMirrorMode(
126 var detected_mode = detectCodeMirrorMode(
127 "${c.file.name}", mimetype);
127 "${c.file.name}", mimetype);
128
128
129 if(detected_mode){
129 if(detected_mode){
130 setCodeMirrorMode(myCodeMirror, detected_mode);
130 setCodeMirrorMode(myCodeMirror, detected_mode);
131 $(modes_select).select2("val", mimetype);
131 $(modes_select).select2("val", mimetype);
132 $(modes_select).change();
132 $(modes_select).change();
133 setCodeMirrorMode(myCodeMirror, detected_mode);
133 setCodeMirrorMode(myCodeMirror, detected_mode);
134 }
134 }
135
135
136 var filename_selector = '#filename';
136 var filename_selector = '#filename';
137 var callback = function(filename, mimetype, mode){
137 var callback = function(filename, mimetype, mode){
138 CodeMirrorPreviewEnable(mode);
138 CodeMirrorPreviewEnable(mode);
139 };
139 };
140 // on change of select field set mode
140 // on change of select field set mode
141 setCodeMirrorModeFromSelect(
141 setCodeMirrorModeFromSelect(
142 modes_select, filename_selector, myCodeMirror, callback);
142 modes_select, filename_selector, myCodeMirror, callback);
143
143
144 // on entering the new filename set mode, from given extension
144 // on entering the new filename set mode, from given extension
145 setCodeMirrorModeFromInput(
145 setCodeMirrorModeFromInput(
146 modes_select, filename_selector, myCodeMirror, callback);
146 modes_select, filename_selector, myCodeMirror, callback);
147
147
148 // if the file is renderable set line wraps automatically
148 // if the file is renderable set line wraps automatically
149 if (renderer !== ""){
149 if (renderer !== ""){
150 var line_wrap = 'on';
150 var line_wrap = 'on';
151 $($('#line_wrap option[value="'+line_wrap+'"]')[0]).attr("selected", "selected");
151 $($('#line_wrap option[value="'+line_wrap+'"]')[0]).attr("selected", "selected");
152 setCodeMirrorLineWrap(myCodeMirror, true);
152 setCodeMirrorLineWrap(myCodeMirror, true);
153 }
153 }
154 // on select line wraps change the editor
154 // on select line wraps change the editor
155 $('#line_wrap').on('change', function(e){
155 $('#line_wrap').on('change', function(e){
156 var selected = e.currentTarget;
156 var selected = e.currentTarget;
157 var line_wraps = {'on': true, 'off': false}[selected.value];
157 var line_wraps = {'on': true, 'off': false}[selected.value];
158 setCodeMirrorLineWrap(myCodeMirror, line_wraps)
158 setCodeMirrorLineWrap(myCodeMirror, line_wraps)
159 });
159 });
160
160
161 // render preview/edit button
161 // render preview/edit button
162 if (mimetype === 'text/x-rst' || mimetype === 'text/plain') {
162 if (mimetype === 'text/x-rst' || mimetype === 'text/plain') {
163 $('#render_preview').removeClass('hidden');
163 $('#render_preview').removeClass('hidden');
164 }
164 }
165 $('#render_preview').on('click', function(e){
165 $('#render_preview').on('click', function(e){
166 if($(this).hasClass('preview')){
166 if($(this).hasClass('preview')){
167 $(this).removeClass('preview');
167 $(this).removeClass('preview');
168 $(this).html("${_('Edit')}");
168 $(this).html("${_('Edit')}");
169 $('#editor_preview').show();
169 $('#editor_preview').show();
170 $(myCodeMirror.getWrapperElement()).hide();
170 $(myCodeMirror.getWrapperElement()).hide();
171
171
172 var possible_renderer = {
172 var possible_renderer = {
173 'rst':'rst',
173 'rst':'rst',
174 'markdown':'markdown',
174 'markdown':'markdown',
175 'gfm': 'markdown'}[myCodeMirror.getMode().name];
175 'gfm': 'markdown'}[myCodeMirror.getMode().name];
176 var _text = myCodeMirror.getValue();
176 var _text = myCodeMirror.getValue();
177 var _renderer = possible_renderer || DEFAULT_RENDERER;
177 var _renderer = possible_renderer || DEFAULT_RENDERER;
178 var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN};
178 var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN};
179 $('#editor_preview').html(_gettext('Loading ...'));
179 $('#editor_preview').html(_gettext('Loading ...'));
180 var url = pyroutes.url('changeset_comment_preview', {'repo_name': '${c.repo_name}'});
180 var url = pyroutes.url('changeset_comment_preview', {'repo_name': '${c.repo_name}'});
181
181
182 ajaxPOST(url, post_data, function(o){
182 ajaxPOST(url, post_data, function(o){
183 $('#editor_preview').html(o);
183 $('#editor_preview').html(o);
184 })
184 })
185 }
185 }
186 else{
186 else{
187 $(this).addClass('preview');
187 $(this).addClass('preview');
188 $(this).html("${_('Preview')}");
188 $(this).html("${_('Preview')}");
189 $('#editor_preview').hide();
189 $('#editor_preview').hide();
190 $(myCodeMirror.getWrapperElement()).show();
190 $(myCodeMirror.getWrapperElement()).show();
191 }
191 }
192 });
192 });
193
193
194 })
194 })
195 </script>
195 </script>
196 </%def>
196 </%def>
@@ -1,92 +1,92 b''
1 <%namespace name="sourceblock" file="/codeblocks/source.mako"/>
1 <%namespace name="sourceblock" file="/codeblocks/source.mako"/>
2
2
3 <div id="codeblock" class="codeblock">
3 <div id="codeblock" class="codeblock">
4 <div class="codeblock-header">
4 <div class="codeblock-header">
5 <div class="stats">
5 <div class="stats">
6 <span> <strong>${c.file}</strong></span>
6 <span> <strong>${c.file}</strong></span>
7 % if c.lf_node:
7 % if c.lf_node:
8 <span title="${_('This file is a pointer to large binary file')}"> | ${_('LargeFile')} ${h.format_byte_size_binary(c.lf_node.size)} </span>
8 <span title="${_('This file is a pointer to large binary file')}"> | ${_('LargeFile')} ${h.format_byte_size_binary(c.lf_node.size)} </span>
9 % endif
9 % endif
10 <span> | ${c.file.lines()[0]} ${_ungettext('line', 'lines', c.file.lines()[0])}</span>
10 <span> | ${c.file.lines()[0]} ${_ungettext('line', 'lines', c.file.lines()[0])}</span>
11 <span> | ${h.format_byte_size_binary(c.file.size)}</span>
11 <span> | ${h.format_byte_size_binary(c.file.size)}</span>
12 <span> | ${c.file.mimetype} </span>
12 <span> | ${c.file.mimetype} </span>
13 <span class="item last"> | ${h.get_lexer_for_filenode(c.file).__class__.__name__}</span>
13 <span class="item last"> | ${h.get_lexer_for_filenode(c.file).__class__.__name__}</span>
14 </div>
14 </div>
15 <div class="buttons">
15 <div class="buttons">
16 <a id="file_history_overview" href="#">
16 <a id="file_history_overview" href="#">
17 ${_('History')}
17 ${_('History')}
18 </a>
18 </a>
19 <a id="file_history_overview_full" style="display: none" href="${h.url('changelog_file_home',repo_name=c.repo_name, revision=c.commit.raw_id, f_path=c.f_path)}">
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 ${_('Show Full History')}
20 ${_('Show Full History')}
21 </a> |
21 </a> |
22 %if c.annotate:
22 %if c.annotate:
23 ${h.link_to(_('Source'), h.route_path('repo_files', repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
23 ${h.link_to(_('Source'), h.route_path('repo_files', repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
24 %else:
24 %else:
25 ${h.link_to(_('Annotation'), h.route_path('repo_files:annotated',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
25 ${h.link_to(_('Annotation'), h.route_path('repo_files:annotated',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
26 %endif
26 %endif
27 | ${h.link_to(_('Raw'), h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
27 | ${h.link_to(_('Raw'), h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
28 |
28 |
29 % if c.lf_node:
29 % if c.lf_node:
30 <a href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path, _query=dict(lf=1))}">
30 <a href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path, _query=dict(lf=1))}">
31 ${_('Download largefile')}
31 ${_('Download largefile')}
32 </a>
32 </a>
33 % else:
33 % else:
34 <a href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
34 <a href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
35 ${_('Download')}
35 ${_('Download')}
36 </a>
36 </a>
37 % endif
37 % endif
38
38
39 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
39 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
40 |
40 |
41 %if c.on_branch_head and c.branch_or_raw_id and not c.file.is_binary:
41 %if c.on_branch_head and c.branch_or_raw_id and not c.file.is_binary:
42 <a href="${h.route_path('repo_files_edit_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _anchor='edit')}">
42 <a href="${h.route_path('repo_files_edit_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _anchor='edit')}">
43 ${_('Edit on Branch:{}').format(c.branch_name)}
43 ${_('Edit on Branch:{}').format(c.branch_name)}
44 </a>
44 </a>
45 | <a class="btn-danger btn-link" href="${h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _anchor='edit')}">${_('Delete')}
45 | <a class="btn-danger btn-link" href="${h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _anchor='edit')}">${_('Delete')}
46 </a>
46 </a>
47 %elif c.on_branch_head and c.branch_or_raw_id and c.file.is_binary:
47 %elif c.on_branch_head and c.branch_or_raw_id and c.file.is_binary:
48 ${h.link_to(_('Edit'), '#', class_="btn btn-link disabled tooltip", title=_('Editing binary files not allowed'))}
48 ${h.link_to(_('Edit'), '#', class_="btn btn-link disabled tooltip", title=_('Editing binary files not allowed'))}
49 | ${h.link_to(_('Delete'), h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _anchor='edit'),class_="btn-danger btn-link")}
49 | ${h.link_to(_('Delete'), h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _anchor='edit'),class_="btn-danger btn-link")}
50 %else:
50 %else:
51 ${h.link_to(_('Edit'), '#', class_="btn btn-link disabled tooltip", title=_('Editing files allowed only when on branch head commit'))}
51 ${h.link_to(_('Edit'), '#', class_="btn btn-link disabled tooltip", title=_('Editing files allowed only when on branch head commit'))}
52 | ${h.link_to(_('Delete'), '#', class_="btn btn-danger btn-link disabled tooltip", title=_('Deleting files allowed only when on branch head commit'))}
52 | ${h.link_to(_('Delete'), '#', class_="btn btn-danger btn-link disabled tooltip", title=_('Deleting files allowed only when on branch head commit'))}
53 %endif
53 %endif
54 %endif
54 %endif
55 </div>
55 </div>
56 </div>
56 </div>
57 <div id="file_history_container"></div>
57 <div id="file_history_container"></div>
58 <div class="code-body">
58 <div class="code-body">
59 %if c.file.is_binary:
59 %if c.file.is_binary:
60 <% rendered_binary = h.render_binary(c.repo_name, c.file)%>
60 <% rendered_binary = h.render_binary(c.repo_name, c.file)%>
61 % if rendered_binary:
61 % if rendered_binary:
62 ${rendered_binary}
62 ${rendered_binary}
63 % else:
63 % else:
64 <div>
64 <div>
65 ${_('Binary file (%s)') % c.file.mimetype}
65 ${_('Binary file (%s)') % c.file.mimetype}
66 </div>
66 </div>
67 % endif
67 % endif
68 %else:
68 %else:
69 % if c.file.size < c.visual.cut_off_limit_file:
69 % if c.file.size < c.visual.cut_off_limit_file:
70 %if c.renderer and not c.annotate:
70 %if c.renderer and not c.annotate:
71 ${h.render(c.file.content, renderer=c.renderer, relative_url=h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
71 ${h.render(c.file.content, renderer=c.renderer, relative_url=h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
72 %else:
72 %else:
73 <table class="cb codehilite">
73 <table class="cb codehilite">
74 %if c.annotate:
74 %if c.annotate:
75 <% color_hasher = h.color_hasher() %>
75 <% color_hasher = h.color_hasher() %>
76 %for annotation, lines in c.annotated_lines:
76 %for annotation, lines in c.annotated_lines:
77 ${sourceblock.render_annotation_lines(annotation, lines, color_hasher)}
77 ${sourceblock.render_annotation_lines(annotation, lines, color_hasher)}
78 %endfor
78 %endfor
79 %else:
79 %else:
80 %for line_num, tokens in enumerate(c.lines, 1):
80 %for line_num, tokens in enumerate(c.lines, 1):
81 ${sourceblock.render_line(line_num, tokens)}
81 ${sourceblock.render_line(line_num, tokens)}
82 %endfor
82 %endfor
83 %endif
83 %endif
84 </table>
84 </table>
85 %endif
85 %endif
86 %else:
86 %else:
87 ${_('File size {} is bigger then allowed limit {}. ').format(h.format_byte_size_binary(c.file.size), h.format_byte_size_binary(c.visual.cut_off_limit_file))} ${h.link_to(_('Show as raw'),
87 ${_('File size {} is bigger then allowed limit {}. ').format(h.format_byte_size_binary(c.file.size), h.format_byte_size_binary(c.visual.cut_off_limit_file))} ${h.link_to(_('Show as raw'),
88 h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
88 h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
89 %endif
89 %endif
90 %endif
90 %endif
91 </div>
91 </div>
92 </div> No newline at end of file
92 </div>
@@ -1,860 +1,860 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
5 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 <span id="pr-title">
12 <span id="pr-title">
13 ${c.pull_request.title}
13 ${c.pull_request.title}
14 %if c.pull_request.is_closed():
14 %if c.pull_request.is_closed():
15 (${_('Closed')})
15 (${_('Closed')})
16 %endif
16 %endif
17 </span>
17 </span>
18 <div id="pr-title-edit" class="input" style="display: none;">
18 <div id="pr-title-edit" class="input" style="display: none;">
19 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
19 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
20 </div>
20 </div>
21 </%def>
21 </%def>
22
22
23 <%def name="menu_bar_nav()">
23 <%def name="menu_bar_nav()">
24 ${self.menu_items(active='repositories')}
24 ${self.menu_items(active='repositories')}
25 </%def>
25 </%def>
26
26
27 <%def name="menu_bar_subnav()">
27 <%def name="menu_bar_subnav()">
28 ${self.repo_menu(active='showpullrequest')}
28 ${self.repo_menu(active='showpullrequest')}
29 </%def>
29 </%def>
30
30
31 <%def name="main()">
31 <%def name="main()">
32
32
33 <script type="text/javascript">
33 <script type="text/javascript">
34 // TODO: marcink switch this to pyroutes
34 // TODO: marcink switch this to pyroutes
35 AJAX_COMMENT_DELETE_URL = "${h.url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
35 AJAX_COMMENT_DELETE_URL = "${h.url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
36 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
36 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
37 </script>
37 </script>
38 <div class="box">
38 <div class="box">
39
39
40 <div class="title">
40 <div class="title">
41 ${self.repo_page_title(c.rhodecode_db_repo)}
41 ${self.repo_page_title(c.rhodecode_db_repo)}
42 </div>
42 </div>
43
43
44 ${self.breadcrumbs()}
44 ${self.breadcrumbs()}
45
45
46 <div class="box pr-summary">
46 <div class="box pr-summary">
47
47
48 <div class="summary-details block-left">
48 <div class="summary-details block-left">
49 <% summary = lambda n:{False:'summary-short'}.get(n) %>
49 <% summary = lambda n:{False:'summary-short'}.get(n) %>
50 <div class="pr-details-title">
50 <div class="pr-details-title">
51 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request #%s') % c.pull_request.pull_request_id}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
51 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request #%s') % c.pull_request.pull_request_id}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
52 %if c.allowed_to_update:
52 %if c.allowed_to_update:
53 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
53 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
54 % if c.allowed_to_delete:
54 % if c.allowed_to_delete:
55 ${h.secure_form(h.url('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id),method='delete')}
55 ${h.secure_form(h.url('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id),method='delete')}
56 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
56 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
57 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
57 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
58 ${h.end_form()}
58 ${h.end_form()}
59 % else:
59 % else:
60 ${_('Delete')}
60 ${_('Delete')}
61 % endif
61 % endif
62 </div>
62 </div>
63 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
63 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
64 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
64 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
65 %endif
65 %endif
66 </div>
66 </div>
67
67
68 <div id="summary" class="fields pr-details-content">
68 <div id="summary" class="fields pr-details-content">
69 <div class="field">
69 <div class="field">
70 <div class="label-summary">
70 <div class="label-summary">
71 <label>${_('Source')}:</label>
71 <label>${_('Source')}:</label>
72 </div>
72 </div>
73 <div class="input">
73 <div class="input">
74 <div class="pr-origininfo">
74 <div class="pr-origininfo">
75 ## branch link is only valid if it is a branch
75 ## branch link is only valid if it is a branch
76 <span class="tag">
76 <span class="tag">
77 %if c.pull_request.source_ref_parts.type == 'branch':
77 %if c.pull_request.source_ref_parts.type == 'branch':
78 <a href="${h.url('changelog_home', repo_name=c.pull_request.source_repo.repo_name, branch=c.pull_request.source_ref_parts.name)}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
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 %else:
79 %else:
80 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
80 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
81 %endif
81 %endif
82 </span>
82 </span>
83 <span class="clone-url">
83 <span class="clone-url">
84 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
84 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
85 </span>
85 </span>
86 <br/>
86 <br/>
87 % if c.ancestor_commit:
87 % if c.ancestor_commit:
88 ${_('Common ancestor')}:
88 ${_('Common ancestor')}:
89 <code><a href="${h.url('changeset_home', repo_name=c.target_repo.repo_name, revision=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a></code>
89 <code><a href="${h.url('changeset_home', repo_name=c.target_repo.repo_name, revision=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a></code>
90 % endif
90 % endif
91 </div>
91 </div>
92 <div class="pr-pullinfo">
92 <div class="pr-pullinfo">
93 %if h.is_hg(c.pull_request.source_repo):
93 %if h.is_hg(c.pull_request.source_repo):
94 <input type="text" class="input-monospace" value="hg pull -r ${h.short_id(c.source_ref)} ${c.pull_request.source_repo.clone_url()}" readonly="readonly">
94 <input type="text" class="input-monospace" value="hg pull -r ${h.short_id(c.source_ref)} ${c.pull_request.source_repo.clone_url()}" readonly="readonly">
95 %elif h.is_git(c.pull_request.source_repo):
95 %elif h.is_git(c.pull_request.source_repo):
96 <input type="text" class="input-monospace" value="git pull ${c.pull_request.source_repo.clone_url()} ${c.pull_request.source_ref_parts.name}" readonly="readonly">
96 <input type="text" class="input-monospace" value="git pull ${c.pull_request.source_repo.clone_url()} ${c.pull_request.source_ref_parts.name}" readonly="readonly">
97 %endif
97 %endif
98 </div>
98 </div>
99 </div>
99 </div>
100 </div>
100 </div>
101 <div class="field">
101 <div class="field">
102 <div class="label-summary">
102 <div class="label-summary">
103 <label>${_('Target')}:</label>
103 <label>${_('Target')}:</label>
104 </div>
104 </div>
105 <div class="input">
105 <div class="input">
106 <div class="pr-targetinfo">
106 <div class="pr-targetinfo">
107 ## branch link is only valid if it is a branch
107 ## branch link is only valid if it is a branch
108 <span class="tag">
108 <span class="tag">
109 %if c.pull_request.target_ref_parts.type == 'branch':
109 %if c.pull_request.target_ref_parts.type == 'branch':
110 <a href="${h.url('changelog_home', repo_name=c.pull_request.target_repo.repo_name, branch=c.pull_request.target_ref_parts.name)}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
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 %else:
111 %else:
112 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
112 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
113 %endif
113 %endif
114 </span>
114 </span>
115 <span class="clone-url">
115 <span class="clone-url">
116 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
116 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
117 </span>
117 </span>
118 </div>
118 </div>
119 </div>
119 </div>
120 </div>
120 </div>
121
121
122 ## Link to the shadow repository.
122 ## Link to the shadow repository.
123 <div class="field">
123 <div class="field">
124 <div class="label-summary">
124 <div class="label-summary">
125 <label>${_('Merge')}:</label>
125 <label>${_('Merge')}:</label>
126 </div>
126 </div>
127 <div class="input">
127 <div class="input">
128 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
128 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
129 <div class="pr-mergeinfo">
129 <div class="pr-mergeinfo">
130 %if h.is_hg(c.pull_request.target_repo):
130 %if h.is_hg(c.pull_request.target_repo):
131 <input type="text" class="input-monospace" value="hg clone -u ${c.pull_request.shadow_merge_ref.name} ${c.shadow_clone_url} pull-request-${c.pull_request.pull_request_id}" readonly="readonly">
131 <input type="text" class="input-monospace" value="hg clone -u ${c.pull_request.shadow_merge_ref.name} ${c.shadow_clone_url} pull-request-${c.pull_request.pull_request_id}" readonly="readonly">
132 %elif h.is_git(c.pull_request.target_repo):
132 %elif h.is_git(c.pull_request.target_repo):
133 <input type="text" class="input-monospace" value="git clone --branch ${c.pull_request.shadow_merge_ref.name} ${c.shadow_clone_url} pull-request-${c.pull_request.pull_request_id}" readonly="readonly">
133 <input type="text" class="input-monospace" value="git clone --branch ${c.pull_request.shadow_merge_ref.name} ${c.shadow_clone_url} pull-request-${c.pull_request.pull_request_id}" readonly="readonly">
134 %endif
134 %endif
135 </div>
135 </div>
136 % else:
136 % else:
137 <div class="">
137 <div class="">
138 ${_('Shadow repository data not available')}.
138 ${_('Shadow repository data not available')}.
139 </div>
139 </div>
140 % endif
140 % endif
141 </div>
141 </div>
142 </div>
142 </div>
143
143
144 <div class="field">
144 <div class="field">
145 <div class="label-summary">
145 <div class="label-summary">
146 <label>${_('Review')}:</label>
146 <label>${_('Review')}:</label>
147 </div>
147 </div>
148 <div class="input">
148 <div class="input">
149 %if c.pull_request_review_status:
149 %if c.pull_request_review_status:
150 <div class="${'flag_status %s' % c.pull_request_review_status} tooltip pull-left"></div>
150 <div class="${'flag_status %s' % c.pull_request_review_status} tooltip pull-left"></div>
151 <span class="changeset-status-lbl tooltip">
151 <span class="changeset-status-lbl tooltip">
152 %if c.pull_request.is_closed():
152 %if c.pull_request.is_closed():
153 ${_('Closed')},
153 ${_('Closed')},
154 %endif
154 %endif
155 ${h.commit_status_lbl(c.pull_request_review_status)}
155 ${h.commit_status_lbl(c.pull_request_review_status)}
156 </span>
156 </span>
157 - ${ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
157 - ${ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
158 %endif
158 %endif
159 </div>
159 </div>
160 </div>
160 </div>
161 <div class="field">
161 <div class="field">
162 <div class="pr-description-label label-summary">
162 <div class="pr-description-label label-summary">
163 <label>${_('Description')}:</label>
163 <label>${_('Description')}:</label>
164 </div>
164 </div>
165 <div id="pr-desc" class="input">
165 <div id="pr-desc" class="input">
166 <div class="pr-description">${h.urlify_commit_message(c.pull_request.description, c.repo_name)}</div>
166 <div class="pr-description">${h.urlify_commit_message(c.pull_request.description, c.repo_name)}</div>
167 </div>
167 </div>
168 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
168 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
169 <textarea id="pr-description-input" size="30">${c.pull_request.description}</textarea>
169 <textarea id="pr-description-input" size="30">${c.pull_request.description}</textarea>
170 </div>
170 </div>
171 </div>
171 </div>
172
172
173 <div class="field">
173 <div class="field">
174 <div class="label-summary">
174 <div class="label-summary">
175 <label>${_('Versions')}:</label>
175 <label>${_('Versions')}:</label>
176 </div>
176 </div>
177
177
178 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
178 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
179 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
179 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
180
180
181 <div class="pr-versions">
181 <div class="pr-versions">
182 % if c.show_version_changes:
182 % if c.show_version_changes:
183 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
183 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
184 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
184 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
185 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
185 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
186 data-toggle-on="${ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
186 data-toggle-on="${ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
187 data-toggle-off="${_('Hide all versions of this pull request')}">
187 data-toggle-off="${_('Hide all versions of this pull request')}">
188 ${ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
188 ${ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
189 </a>
189 </a>
190 <table>
190 <table>
191 ## SHOW ALL VERSIONS OF PR
191 ## SHOW ALL VERSIONS OF PR
192 <% ver_pr = None %>
192 <% ver_pr = None %>
193
193
194 % for data in reversed(list(enumerate(c.versions, 1))):
194 % for data in reversed(list(enumerate(c.versions, 1))):
195 <% ver_pos = data[0] %>
195 <% ver_pos = data[0] %>
196 <% ver = data[1] %>
196 <% ver = data[1] %>
197 <% ver_pr = ver.pull_request_version_id %>
197 <% ver_pr = ver.pull_request_version_id %>
198 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
198 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
199
199
200 <tr class="version-pr" style="display: ${display_row}">
200 <tr class="version-pr" style="display: ${display_row}">
201 <td>
201 <td>
202 <code>
202 <code>
203 <a href="${h.url.current(version=ver_pr or 'latest')}">v${ver_pos}</a>
203 <a href="${h.url.current(version=ver_pr or 'latest')}">v${ver_pos}</a>
204 </code>
204 </code>
205 </td>
205 </td>
206 <td>
206 <td>
207 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
207 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
208 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
208 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
209 </td>
209 </td>
210 <td>
210 <td>
211 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
211 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
212 <div class="${'flag_status %s' % review_status} tooltip pull-left" title="${_('Your review status at this version')}">
212 <div class="${'flag_status %s' % review_status} tooltip pull-left" title="${_('Your review status at this version')}">
213 </div>
213 </div>
214 </td>
214 </td>
215 <td>
215 <td>
216 % if c.at_version_num != ver_pr:
216 % if c.at_version_num != ver_pr:
217 <i class="icon-comment"></i>
217 <i class="icon-comment"></i>
218 <code class="tooltip" title="${_('Comment from pull request version {0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
218 <code class="tooltip" title="${_('Comment from pull request version {0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
219 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
219 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
220 </code>
220 </code>
221 % endif
221 % endif
222 </td>
222 </td>
223 <td>
223 <td>
224 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
224 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
225 </td>
225 </td>
226 <td>
226 <td>
227 ${h.age_component(ver.updated_on, time_is_local=True)}
227 ${h.age_component(ver.updated_on, time_is_local=True)}
228 </td>
228 </td>
229 </tr>
229 </tr>
230 % endfor
230 % endfor
231
231
232 <tr>
232 <tr>
233 <td colspan="6">
233 <td colspan="6">
234 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
234 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
235 data-label-text-locked="${_('select versions to show changes')}"
235 data-label-text-locked="${_('select versions to show changes')}"
236 data-label-text-diff="${_('show changes between versions')}"
236 data-label-text-diff="${_('show changes between versions')}"
237 data-label-text-show="${_('show pull request for this version')}"
237 data-label-text-show="${_('show pull request for this version')}"
238 >
238 >
239 ${_('select versions to show changes')}
239 ${_('select versions to show changes')}
240 </button>
240 </button>
241 </td>
241 </td>
242 </tr>
242 </tr>
243
243
244 ## show comment/inline comments summary
244 ## show comment/inline comments summary
245 <%def name="comments_summary()">
245 <%def name="comments_summary()">
246 <tr>
246 <tr>
247 <td colspan="6" class="comments-summary-td">
247 <td colspan="6" class="comments-summary-td">
248
248
249 % if c.at_version:
249 % if c.at_version:
250 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['display']) %>
250 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['display']) %>
251 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['display']) %>
251 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['display']) %>
252 ${_('Comments at this version')}:
252 ${_('Comments at this version')}:
253 % else:
253 % else:
254 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['until']) %>
254 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['until']) %>
255 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['until']) %>
255 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['until']) %>
256 ${_('Comments for this pull request')}:
256 ${_('Comments for this pull request')}:
257 % endif
257 % endif
258
258
259
259
260 %if general_comm_count_ver:
260 %if general_comm_count_ver:
261 <a href="#comments">${_("%d General ") % general_comm_count_ver}</a>
261 <a href="#comments">${_("%d General ") % general_comm_count_ver}</a>
262 %else:
262 %else:
263 ${_("%d General ") % general_comm_count_ver}
263 ${_("%d General ") % general_comm_count_ver}
264 %endif
264 %endif
265
265
266 %if inline_comm_count_ver:
266 %if inline_comm_count_ver:
267 , <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${_("%d Inline") % inline_comm_count_ver}</a>
267 , <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${_("%d Inline") % inline_comm_count_ver}</a>
268 %else:
268 %else:
269 , ${_("%d Inline") % inline_comm_count_ver}
269 , ${_("%d Inline") % inline_comm_count_ver}
270 %endif
270 %endif
271
271
272 %if outdated_comm_count_ver:
272 %if outdated_comm_count_ver:
273 , <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">${_("%d Outdated") % outdated_comm_count_ver}</a>
273 , <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">${_("%d Outdated") % outdated_comm_count_ver}</a>
274 <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated comments')}</a>
274 <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated comments')}</a>
275 <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated comments')}</a>
275 <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated comments')}</a>
276 %else:
276 %else:
277 , ${_("%d Outdated") % outdated_comm_count_ver}
277 , ${_("%d Outdated") % outdated_comm_count_ver}
278 %endif
278 %endif
279 </td>
279 </td>
280 </tr>
280 </tr>
281 </%def>
281 </%def>
282 ${comments_summary()}
282 ${comments_summary()}
283 </table>
283 </table>
284 % else:
284 % else:
285 <div class="input">
285 <div class="input">
286 ${_('Pull request versions not available')}.
286 ${_('Pull request versions not available')}.
287 </div>
287 </div>
288 <div>
288 <div>
289 <table>
289 <table>
290 ${comments_summary()}
290 ${comments_summary()}
291 </table>
291 </table>
292 </div>
292 </div>
293 % endif
293 % endif
294 </div>
294 </div>
295 </div>
295 </div>
296
296
297 <div id="pr-save" class="field" style="display: none;">
297 <div id="pr-save" class="field" style="display: none;">
298 <div class="label-summary"></div>
298 <div class="label-summary"></div>
299 <div class="input">
299 <div class="input">
300 <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span>
300 <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span>
301 </div>
301 </div>
302 </div>
302 </div>
303 </div>
303 </div>
304 </div>
304 </div>
305 <div>
305 <div>
306 ## AUTHOR
306 ## AUTHOR
307 <div class="reviewers-title block-right">
307 <div class="reviewers-title block-right">
308 <div class="pr-details-title">
308 <div class="pr-details-title">
309 ${_('Author of this pull request')}
309 ${_('Author of this pull request')}
310 </div>
310 </div>
311 </div>
311 </div>
312 <div class="block-right pr-details-content reviewers">
312 <div class="block-right pr-details-content reviewers">
313 <ul class="group_members">
313 <ul class="group_members">
314 <li>
314 <li>
315 ${self.gravatar_with_user(c.pull_request.author.email, 16)}
315 ${self.gravatar_with_user(c.pull_request.author.email, 16)}
316 </li>
316 </li>
317 </ul>
317 </ul>
318 </div>
318 </div>
319
319
320 ## REVIEW RULES
320 ## REVIEW RULES
321 <div id="review_rules" style="display: none" class="reviewers-title block-right">
321 <div id="review_rules" style="display: none" class="reviewers-title block-right">
322 <div class="pr-details-title">
322 <div class="pr-details-title">
323 ${_('Reviewer rules')}
323 ${_('Reviewer rules')}
324 %if c.allowed_to_update:
324 %if c.allowed_to_update:
325 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
325 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
326 %endif
326 %endif
327 </div>
327 </div>
328 <div class="pr-reviewer-rules">
328 <div class="pr-reviewer-rules">
329 ## review rules will be appended here, by default reviewers logic
329 ## review rules will be appended here, by default reviewers logic
330 </div>
330 </div>
331 <input id="review_data" type="hidden" name="review_data" value="">
331 <input id="review_data" type="hidden" name="review_data" value="">
332 </div>
332 </div>
333
333
334 ## REVIEWERS
334 ## REVIEWERS
335 <div class="reviewers-title block-right">
335 <div class="reviewers-title block-right">
336 <div class="pr-details-title">
336 <div class="pr-details-title">
337 ${_('Pull request reviewers')}
337 ${_('Pull request reviewers')}
338 %if c.allowed_to_update:
338 %if c.allowed_to_update:
339 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
339 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
340 %endif
340 %endif
341 </div>
341 </div>
342 </div>
342 </div>
343 <div id="reviewers" class="block-right pr-details-content reviewers">
343 <div id="reviewers" class="block-right pr-details-content reviewers">
344 ## members goes here !
344 ## members goes here !
345 <input type="hidden" name="__start__" value="review_members:sequence">
345 <input type="hidden" name="__start__" value="review_members:sequence">
346 <ul id="review_members" class="group_members">
346 <ul id="review_members" class="group_members">
347 %for member,reasons,mandatory,status in c.pull_request_reviewers:
347 %for member,reasons,mandatory,status in c.pull_request_reviewers:
348 <li id="reviewer_${member.user_id}" class="reviewer_entry">
348 <li id="reviewer_${member.user_id}" class="reviewer_entry">
349 <div class="reviewers_member">
349 <div class="reviewers_member">
350 <div class="reviewer_status tooltip" title="${h.tooltip(h.commit_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
350 <div class="reviewer_status tooltip" title="${h.tooltip(h.commit_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
351 <div class="${'flag_status %s' % (status[0][1].status if status else 'not_reviewed')} pull-left reviewer_member_status"></div>
351 <div class="${'flag_status %s' % (status[0][1].status if status else 'not_reviewed')} pull-left reviewer_member_status"></div>
352 </div>
352 </div>
353 <div id="reviewer_${member.user_id}_name" class="reviewer_name">
353 <div id="reviewer_${member.user_id}_name" class="reviewer_name">
354 ${self.gravatar_with_user(member.email, 16)}
354 ${self.gravatar_with_user(member.email, 16)}
355 </div>
355 </div>
356 <input type="hidden" name="__start__" value="reviewer:mapping">
356 <input type="hidden" name="__start__" value="reviewer:mapping">
357 <input type="hidden" name="__start__" value="reasons:sequence">
357 <input type="hidden" name="__start__" value="reasons:sequence">
358 %for reason in reasons:
358 %for reason in reasons:
359 <div class="reviewer_reason">- ${reason}</div>
359 <div class="reviewer_reason">- ${reason}</div>
360 <input type="hidden" name="reason" value="${reason}">
360 <input type="hidden" name="reason" value="${reason}">
361
361
362 %endfor
362 %endfor
363 <input type="hidden" name="__end__" value="reasons:sequence">
363 <input type="hidden" name="__end__" value="reasons:sequence">
364 <input id="reviewer_${member.user_id}_input" type="hidden" value="${member.user_id}" name="user_id" />
364 <input id="reviewer_${member.user_id}_input" type="hidden" value="${member.user_id}" name="user_id" />
365 <input type="hidden" name="mandatory" value="${mandatory}"/>
365 <input type="hidden" name="mandatory" value="${mandatory}"/>
366 <input type="hidden" name="__end__" value="reviewer:mapping">
366 <input type="hidden" name="__end__" value="reviewer:mapping">
367 % if mandatory:
367 % if mandatory:
368 <div class="reviewer_member_mandatory_remove">
368 <div class="reviewer_member_mandatory_remove">
369 <i class="icon-remove-sign"></i>
369 <i class="icon-remove-sign"></i>
370 </div>
370 </div>
371 <div class="reviewer_member_mandatory">
371 <div class="reviewer_member_mandatory">
372 <i class="icon-lock" title="${h.tooltip(_('Mandatory reviewer'))}"></i>
372 <i class="icon-lock" title="${h.tooltip(_('Mandatory reviewer'))}"></i>
373 </div>
373 </div>
374 % else:
374 % else:
375 %if c.allowed_to_update:
375 %if c.allowed_to_update:
376 <div class="reviewer_member_remove action_button" onclick="reviewersController.removeReviewMember(${member.user_id}, true)" style="visibility: hidden;">
376 <div class="reviewer_member_remove action_button" onclick="reviewersController.removeReviewMember(${member.user_id}, true)" style="visibility: hidden;">
377 <i class="icon-remove-sign" ></i>
377 <i class="icon-remove-sign" ></i>
378 </div>
378 </div>
379 %endif
379 %endif
380 % endif
380 % endif
381 </div>
381 </div>
382 </li>
382 </li>
383 %endfor
383 %endfor
384 </ul>
384 </ul>
385 <input type="hidden" name="__end__" value="review_members:sequence">
385 <input type="hidden" name="__end__" value="review_members:sequence">
386
386
387 %if not c.pull_request.is_closed():
387 %if not c.pull_request.is_closed():
388 <div id="add_reviewer" class="ac" style="display: none;">
388 <div id="add_reviewer" class="ac" style="display: none;">
389 %if c.allowed_to_update:
389 %if c.allowed_to_update:
390 % if not c.forbid_adding_reviewers:
390 % if not c.forbid_adding_reviewers:
391 <div id="add_reviewer_input" class="reviewer_ac">
391 <div id="add_reviewer_input" class="reviewer_ac">
392 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
392 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
393 <div id="reviewers_container"></div>
393 <div id="reviewers_container"></div>
394 </div>
394 </div>
395 % endif
395 % endif
396 <div class="pull-right">
396 <div class="pull-right">
397 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
397 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
398 </div>
398 </div>
399 %endif
399 %endif
400 </div>
400 </div>
401 %endif
401 %endif
402 </div>
402 </div>
403 </div>
403 </div>
404 </div>
404 </div>
405 <div class="box">
405 <div class="box">
406 ##DIFF
406 ##DIFF
407 <div class="table" >
407 <div class="table" >
408 <div id="changeset_compare_view_content">
408 <div id="changeset_compare_view_content">
409 ##CS
409 ##CS
410 % if c.missing_requirements:
410 % if c.missing_requirements:
411 <div class="box">
411 <div class="box">
412 <div class="alert alert-warning">
412 <div class="alert alert-warning">
413 <div>
413 <div>
414 <strong>${_('Missing requirements:')}</strong>
414 <strong>${_('Missing requirements:')}</strong>
415 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
415 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
416 </div>
416 </div>
417 </div>
417 </div>
418 </div>
418 </div>
419 % elif c.missing_commits:
419 % elif c.missing_commits:
420 <div class="box">
420 <div class="box">
421 <div class="alert alert-warning">
421 <div class="alert alert-warning">
422 <div>
422 <div>
423 <strong>${_('Missing commits')}:</strong>
423 <strong>${_('Missing commits')}:</strong>
424 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
424 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
425 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
425 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
426 </div>
426 </div>
427 </div>
427 </div>
428 </div>
428 </div>
429 % endif
429 % endif
430
430
431 <div class="compare_view_commits_title">
431 <div class="compare_view_commits_title">
432 % if not c.compare_mode:
432 % if not c.compare_mode:
433
433
434 % if c.at_version_pos:
434 % if c.at_version_pos:
435 <h4>
435 <h4>
436 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
436 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
437 </h4>
437 </h4>
438 % endif
438 % endif
439
439
440 <div class="pull-left">
440 <div class="pull-left">
441 <div class="btn-group">
441 <div class="btn-group">
442 <a
442 <a
443 class="btn"
443 class="btn"
444 href="#"
444 href="#"
445 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
445 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
446 ${ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
446 ${ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
447 </a>
447 </a>
448 <a
448 <a
449 class="btn"
449 class="btn"
450 href="#"
450 href="#"
451 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
451 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
452 ${ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
452 ${ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
453 </a>
453 </a>
454 </div>
454 </div>
455 </div>
455 </div>
456
456
457 <div class="pull-right">
457 <div class="pull-right">
458 % if c.allowed_to_update and not c.pull_request.is_closed():
458 % if c.allowed_to_update and not c.pull_request.is_closed():
459 <a id="update_commits" class="btn btn-primary no-margin pull-right">${_('Update commits')}</a>
459 <a id="update_commits" class="btn btn-primary no-margin pull-right">${_('Update commits')}</a>
460 % else:
460 % else:
461 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
461 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
462 % endif
462 % endif
463
463
464 </div>
464 </div>
465 % endif
465 % endif
466 </div>
466 </div>
467
467
468 % if not c.missing_commits:
468 % if not c.missing_commits:
469 % if c.compare_mode:
469 % if c.compare_mode:
470 % if c.at_version:
470 % if c.at_version:
471 <h4>
471 <h4>
472 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
472 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
473 </h4>
473 </h4>
474
474
475 <div class="subtitle-compare">
475 <div class="subtitle-compare">
476 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
476 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
477 </div>
477 </div>
478
478
479 <div class="container">
479 <div class="container">
480 <table class="rctable compare_view_commits">
480 <table class="rctable compare_view_commits">
481 <tr>
481 <tr>
482 <th></th>
482 <th></th>
483 <th>${_('Time')}</th>
483 <th>${_('Time')}</th>
484 <th>${_('Author')}</th>
484 <th>${_('Author')}</th>
485 <th>${_('Commit')}</th>
485 <th>${_('Commit')}</th>
486 <th></th>
486 <th></th>
487 <th>${_('Description')}</th>
487 <th>${_('Description')}</th>
488 </tr>
488 </tr>
489
489
490 % for c_type, commit in c.commit_changes:
490 % for c_type, commit in c.commit_changes:
491 % if c_type in ['a', 'r']:
491 % if c_type in ['a', 'r']:
492 <%
492 <%
493 if c_type == 'a':
493 if c_type == 'a':
494 cc_title = _('Commit added in displayed changes')
494 cc_title = _('Commit added in displayed changes')
495 elif c_type == 'r':
495 elif c_type == 'r':
496 cc_title = _('Commit removed in displayed changes')
496 cc_title = _('Commit removed in displayed changes')
497 else:
497 else:
498 cc_title = ''
498 cc_title = ''
499 %>
499 %>
500 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
500 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
501 <td>
501 <td>
502 <div class="commit-change-indicator color-${c_type}-border">
502 <div class="commit-change-indicator color-${c_type}-border">
503 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
503 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
504 ${c_type.upper()}
504 ${c_type.upper()}
505 </div>
505 </div>
506 </div>
506 </div>
507 </td>
507 </td>
508 <td class="td-time">
508 <td class="td-time">
509 ${h.age_component(commit.date)}
509 ${h.age_component(commit.date)}
510 </td>
510 </td>
511 <td class="td-user">
511 <td class="td-user">
512 ${base.gravatar_with_user(commit.author, 16)}
512 ${base.gravatar_with_user(commit.author, 16)}
513 </td>
513 </td>
514 <td class="td-hash">
514 <td class="td-hash">
515 <code>
515 <code>
516 <a href="${h.url('changeset_home', repo_name=c.target_repo.repo_name, revision=commit.raw_id)}">
516 <a href="${h.url('changeset_home', repo_name=c.target_repo.repo_name, revision=commit.raw_id)}">
517 r${commit.revision}:${h.short_id(commit.raw_id)}
517 r${commit.revision}:${h.short_id(commit.raw_id)}
518 </a>
518 </a>
519 ${h.hidden('revisions', commit.raw_id)}
519 ${h.hidden('revisions', commit.raw_id)}
520 </code>
520 </code>
521 </td>
521 </td>
522 <td class="expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}">
522 <td class="expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}">
523 <div class="show_more_col">
523 <div class="show_more_col">
524 <i class="show_more"></i>
524 <i class="show_more"></i>
525 </div>
525 </div>
526 </td>
526 </td>
527 <td class="mid td-description">
527 <td class="mid td-description">
528 <div class="log-container truncate-wrap">
528 <div class="log-container truncate-wrap">
529 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">
529 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">
530 ${h.urlify_commit_message(commit.message, c.repo_name)}
530 ${h.urlify_commit_message(commit.message, c.repo_name)}
531 </div>
531 </div>
532 </div>
532 </div>
533 </td>
533 </td>
534 </tr>
534 </tr>
535 % endif
535 % endif
536 % endfor
536 % endfor
537 </table>
537 </table>
538 </div>
538 </div>
539
539
540 <script>
540 <script>
541 $('.expand_commit').on('click',function(e){
541 $('.expand_commit').on('click',function(e){
542 var target_expand = $(this);
542 var target_expand = $(this);
543 var cid = target_expand.data('commitId');
543 var cid = target_expand.data('commitId');
544
544
545 if (target_expand.hasClass('open')){
545 if (target_expand.hasClass('open')){
546 $('#c-'+cid).css({
546 $('#c-'+cid).css({
547 'height': '1.5em',
547 'height': '1.5em',
548 'white-space': 'nowrap',
548 'white-space': 'nowrap',
549 'text-overflow': 'ellipsis',
549 'text-overflow': 'ellipsis',
550 'overflow':'hidden'
550 'overflow':'hidden'
551 });
551 });
552 target_expand.removeClass('open');
552 target_expand.removeClass('open');
553 }
553 }
554 else {
554 else {
555 $('#c-'+cid).css({
555 $('#c-'+cid).css({
556 'height': 'auto',
556 'height': 'auto',
557 'white-space': 'pre-line',
557 'white-space': 'pre-line',
558 'text-overflow': 'initial',
558 'text-overflow': 'initial',
559 'overflow':'visible'
559 'overflow':'visible'
560 });
560 });
561 target_expand.addClass('open');
561 target_expand.addClass('open');
562 }
562 }
563 });
563 });
564 </script>
564 </script>
565
565
566 % endif
566 % endif
567
567
568 % else:
568 % else:
569 <%include file="/compare/compare_commits.mako" />
569 <%include file="/compare/compare_commits.mako" />
570 % endif
570 % endif
571
571
572 <div class="cs_files">
572 <div class="cs_files">
573 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
573 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
574 ${cbdiffs.render_diffset_menu()}
574 ${cbdiffs.render_diffset_menu()}
575 ${cbdiffs.render_diffset(
575 ${cbdiffs.render_diffset(
576 c.diffset, use_comments=True,
576 c.diffset, use_comments=True,
577 collapse_when_files_over=30,
577 collapse_when_files_over=30,
578 disable_new_comments=not c.allowed_to_comment,
578 disable_new_comments=not c.allowed_to_comment,
579 deleted_files_comments=c.deleted_files_comments)}
579 deleted_files_comments=c.deleted_files_comments)}
580 </div>
580 </div>
581 % else:
581 % else:
582 ## skipping commits we need to clear the view for missing commits
582 ## skipping commits we need to clear the view for missing commits
583 <div style="clear:both;"></div>
583 <div style="clear:both;"></div>
584 % endif
584 % endif
585
585
586 </div>
586 </div>
587 </div>
587 </div>
588
588
589 ## template for inline comment form
589 ## template for inline comment form
590 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
590 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
591
591
592 ## render general comments
592 ## render general comments
593
593
594 <div id="comment-tr-show">
594 <div id="comment-tr-show">
595 <div class="comment">
595 <div class="comment">
596 % if general_outdated_comm_count_ver:
596 % if general_outdated_comm_count_ver:
597 <div class="meta">
597 <div class="meta">
598 % if general_outdated_comm_count_ver == 1:
598 % if general_outdated_comm_count_ver == 1:
599 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
599 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
600 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
600 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
601 % else:
601 % else:
602 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
602 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
603 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
603 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
604 % endif
604 % endif
605 </div>
605 </div>
606 % endif
606 % endif
607 </div>
607 </div>
608 </div>
608 </div>
609
609
610 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
610 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
611
611
612 % if not c.pull_request.is_closed():
612 % if not c.pull_request.is_closed():
613 ## merge status, and merge action
613 ## merge status, and merge action
614 <div class="pull-request-merge">
614 <div class="pull-request-merge">
615 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
615 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
616 </div>
616 </div>
617
617
618 ## main comment form and it status
618 ## main comment form and it status
619 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
619 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
620 pull_request_id=c.pull_request.pull_request_id),
620 pull_request_id=c.pull_request.pull_request_id),
621 c.pull_request_review_status,
621 c.pull_request_review_status,
622 is_pull_request=True, change_status=c.allowed_to_change_status)}
622 is_pull_request=True, change_status=c.allowed_to_change_status)}
623 %endif
623 %endif
624
624
625 <script type="text/javascript">
625 <script type="text/javascript">
626 if (location.hash) {
626 if (location.hash) {
627 var result = splitDelimitedHash(location.hash);
627 var result = splitDelimitedHash(location.hash);
628 var line = $('html').find(result.loc);
628 var line = $('html').find(result.loc);
629 // show hidden comments if we use location.hash
629 // show hidden comments if we use location.hash
630 if (line.hasClass('comment-general')) {
630 if (line.hasClass('comment-general')) {
631 $(line).show();
631 $(line).show();
632 } else if (line.hasClass('comment-inline')) {
632 } else if (line.hasClass('comment-inline')) {
633 $(line).show();
633 $(line).show();
634 var $cb = $(line).closest('.cb');
634 var $cb = $(line).closest('.cb');
635 $cb.removeClass('cb-collapsed')
635 $cb.removeClass('cb-collapsed')
636 }
636 }
637 if (line.length > 0){
637 if (line.length > 0){
638 offsetScroll(line, 70);
638 offsetScroll(line, 70);
639 }
639 }
640 }
640 }
641
641
642 versionController = new VersionController();
642 versionController = new VersionController();
643 versionController.init();
643 versionController.init();
644
644
645 reviewersController = new ReviewersController();
645 reviewersController = new ReviewersController();
646
646
647 $(function(){
647 $(function(){
648
648
649 // custom code mirror
649 // custom code mirror
650 var codeMirrorInstance = initPullRequestsCodeMirror('#pr-description-input');
650 var codeMirrorInstance = initPullRequestsCodeMirror('#pr-description-input');
651
651
652 var PRDetails = {
652 var PRDetails = {
653 editButton: $('#open_edit_pullrequest'),
653 editButton: $('#open_edit_pullrequest'),
654 closeButton: $('#close_edit_pullrequest'),
654 closeButton: $('#close_edit_pullrequest'),
655 deleteButton: $('#delete_pullrequest'),
655 deleteButton: $('#delete_pullrequest'),
656 viewFields: $('#pr-desc, #pr-title'),
656 viewFields: $('#pr-desc, #pr-title'),
657 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
657 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
658
658
659 init: function() {
659 init: function() {
660 var that = this;
660 var that = this;
661 this.editButton.on('click', function(e) { that.edit(); });
661 this.editButton.on('click', function(e) { that.edit(); });
662 this.closeButton.on('click', function(e) { that.view(); });
662 this.closeButton.on('click', function(e) { that.view(); });
663 },
663 },
664
664
665 edit: function(event) {
665 edit: function(event) {
666 this.viewFields.hide();
666 this.viewFields.hide();
667 this.editButton.hide();
667 this.editButton.hide();
668 this.deleteButton.hide();
668 this.deleteButton.hide();
669 this.closeButton.show();
669 this.closeButton.show();
670 this.editFields.show();
670 this.editFields.show();
671 codeMirrorInstance.refresh();
671 codeMirrorInstance.refresh();
672 },
672 },
673
673
674 view: function(event) {
674 view: function(event) {
675 this.editButton.show();
675 this.editButton.show();
676 this.deleteButton.show();
676 this.deleteButton.show();
677 this.editFields.hide();
677 this.editFields.hide();
678 this.closeButton.hide();
678 this.closeButton.hide();
679 this.viewFields.show();
679 this.viewFields.show();
680 }
680 }
681 };
681 };
682
682
683 var ReviewersPanel = {
683 var ReviewersPanel = {
684 editButton: $('#open_edit_reviewers'),
684 editButton: $('#open_edit_reviewers'),
685 closeButton: $('#close_edit_reviewers'),
685 closeButton: $('#close_edit_reviewers'),
686 addButton: $('#add_reviewer'),
686 addButton: $('#add_reviewer'),
687 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove,.reviewer_member_mandatory'),
687 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove,.reviewer_member_mandatory'),
688
688
689 init: function() {
689 init: function() {
690 var self = this;
690 var self = this;
691 this.editButton.on('click', function(e) { self.edit(); });
691 this.editButton.on('click', function(e) { self.edit(); });
692 this.closeButton.on('click', function(e) { self.close(); });
692 this.closeButton.on('click', function(e) { self.close(); });
693 },
693 },
694
694
695 edit: function(event) {
695 edit: function(event) {
696 this.editButton.hide();
696 this.editButton.hide();
697 this.closeButton.show();
697 this.closeButton.show();
698 this.addButton.show();
698 this.addButton.show();
699 this.removeButtons.css('visibility', 'visible');
699 this.removeButtons.css('visibility', 'visible');
700 // review rules
700 // review rules
701 reviewersController.loadReviewRules(
701 reviewersController.loadReviewRules(
702 ${c.pull_request.reviewer_data_json | n});
702 ${c.pull_request.reviewer_data_json | n});
703 },
703 },
704
704
705 close: function(event) {
705 close: function(event) {
706 this.editButton.show();
706 this.editButton.show();
707 this.closeButton.hide();
707 this.closeButton.hide();
708 this.addButton.hide();
708 this.addButton.hide();
709 this.removeButtons.css('visibility', 'hidden');
709 this.removeButtons.css('visibility', 'hidden');
710 // hide review rules
710 // hide review rules
711 reviewersController.hideReviewRules()
711 reviewersController.hideReviewRules()
712 }
712 }
713 };
713 };
714
714
715 PRDetails.init();
715 PRDetails.init();
716 ReviewersPanel.init();
716 ReviewersPanel.init();
717
717
718 showOutdated = function(self){
718 showOutdated = function(self){
719 $('.comment-inline.comment-outdated').show();
719 $('.comment-inline.comment-outdated').show();
720 $('.filediff-outdated').show();
720 $('.filediff-outdated').show();
721 $('.showOutdatedComments').hide();
721 $('.showOutdatedComments').hide();
722 $('.hideOutdatedComments').show();
722 $('.hideOutdatedComments').show();
723 };
723 };
724
724
725 hideOutdated = function(self){
725 hideOutdated = function(self){
726 $('.comment-inline.comment-outdated').hide();
726 $('.comment-inline.comment-outdated').hide();
727 $('.filediff-outdated').hide();
727 $('.filediff-outdated').hide();
728 $('.hideOutdatedComments').hide();
728 $('.hideOutdatedComments').hide();
729 $('.showOutdatedComments').show();
729 $('.showOutdatedComments').show();
730 };
730 };
731
731
732 refreshMergeChecks = function(){
732 refreshMergeChecks = function(){
733 var loadUrl = "${h.url.current(merge_checks=1)}";
733 var loadUrl = "${h.url.current(merge_checks=1)}";
734 $('.pull-request-merge').css('opacity', 0.3);
734 $('.pull-request-merge').css('opacity', 0.3);
735 $('.action-buttons-extra').css('opacity', 0.3);
735 $('.action-buttons-extra').css('opacity', 0.3);
736
736
737 $('.pull-request-merge').load(
737 $('.pull-request-merge').load(
738 loadUrl, function() {
738 loadUrl, function() {
739 $('.pull-request-merge').css('opacity', 1);
739 $('.pull-request-merge').css('opacity', 1);
740
740
741 $('.action-buttons-extra').css('opacity', 1);
741 $('.action-buttons-extra').css('opacity', 1);
742 injectCloseAction();
742 injectCloseAction();
743 }
743 }
744 );
744 );
745 };
745 };
746
746
747 injectCloseAction = function() {
747 injectCloseAction = function() {
748 var closeAction = $('#close-pull-request-action').html();
748 var closeAction = $('#close-pull-request-action').html();
749 var $actionButtons = $('.action-buttons-extra');
749 var $actionButtons = $('.action-buttons-extra');
750 // clear the action before
750 // clear the action before
751 $actionButtons.html("");
751 $actionButtons.html("");
752 $actionButtons.html(closeAction);
752 $actionButtons.html(closeAction);
753 };
753 };
754
754
755 closePullRequest = function (status) {
755 closePullRequest = function (status) {
756 // inject closing flag
756 // inject closing flag
757 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
757 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
758 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
758 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
759 $(generalCommentForm.submitForm).submit();
759 $(generalCommentForm.submitForm).submit();
760 };
760 };
761
761
762 $('#show-outdated-comments').on('click', function(e){
762 $('#show-outdated-comments').on('click', function(e){
763 var button = $(this);
763 var button = $(this);
764 var outdated = $('.comment-outdated');
764 var outdated = $('.comment-outdated');
765
765
766 if (button.html() === "(Show)") {
766 if (button.html() === "(Show)") {
767 button.html("(Hide)");
767 button.html("(Hide)");
768 outdated.show();
768 outdated.show();
769 } else {
769 } else {
770 button.html("(Show)");
770 button.html("(Show)");
771 outdated.hide();
771 outdated.hide();
772 }
772 }
773 });
773 });
774
774
775 $('.show-inline-comments').on('change', function(e){
775 $('.show-inline-comments').on('change', function(e){
776 var show = 'none';
776 var show = 'none';
777 var target = e.currentTarget;
777 var target = e.currentTarget;
778 if(target.checked){
778 if(target.checked){
779 show = ''
779 show = ''
780 }
780 }
781 var boxid = $(target).attr('id_for');
781 var boxid = $(target).attr('id_for');
782 var comments = $('#{0} .inline-comments'.format(boxid));
782 var comments = $('#{0} .inline-comments'.format(boxid));
783 var fn_display = function(idx){
783 var fn_display = function(idx){
784 $(this).css('display', show);
784 $(this).css('display', show);
785 };
785 };
786 $(comments).each(fn_display);
786 $(comments).each(fn_display);
787 var btns = $('#{0} .inline-comments-button'.format(boxid));
787 var btns = $('#{0} .inline-comments-button'.format(boxid));
788 $(btns).each(fn_display);
788 $(btns).each(fn_display);
789 });
789 });
790
790
791 $('#merge_pull_request_form').submit(function() {
791 $('#merge_pull_request_form').submit(function() {
792 if (!$('#merge_pull_request').attr('disabled')) {
792 if (!$('#merge_pull_request').attr('disabled')) {
793 $('#merge_pull_request').attr('disabled', 'disabled');
793 $('#merge_pull_request').attr('disabled', 'disabled');
794 }
794 }
795 return true;
795 return true;
796 });
796 });
797
797
798 $('#edit_pull_request').on('click', function(e){
798 $('#edit_pull_request').on('click', function(e){
799 var title = $('#pr-title-input').val();
799 var title = $('#pr-title-input').val();
800 var description = codeMirrorInstance.getValue();
800 var description = codeMirrorInstance.getValue();
801 editPullRequest(
801 editPullRequest(
802 "${c.repo_name}", "${c.pull_request.pull_request_id}",
802 "${c.repo_name}", "${c.pull_request.pull_request_id}",
803 title, description);
803 title, description);
804 });
804 });
805
805
806 $('#update_pull_request').on('click', function(e){
806 $('#update_pull_request').on('click', function(e){
807 $(this).attr('disabled', 'disabled');
807 $(this).attr('disabled', 'disabled');
808 $(this).addClass('disabled');
808 $(this).addClass('disabled');
809 $(this).html(_gettext('Saving...'));
809 $(this).html(_gettext('Saving...'));
810 reviewersController.updateReviewers(
810 reviewersController.updateReviewers(
811 "${c.repo_name}", "${c.pull_request.pull_request_id}");
811 "${c.repo_name}", "${c.pull_request.pull_request_id}");
812 });
812 });
813
813
814 $('#update_commits').on('click', function(e){
814 $('#update_commits').on('click', function(e){
815 var isDisabled = !$(e.currentTarget).attr('disabled');
815 var isDisabled = !$(e.currentTarget).attr('disabled');
816 $(e.currentTarget).attr('disabled', 'disabled');
816 $(e.currentTarget).attr('disabled', 'disabled');
817 $(e.currentTarget).addClass('disabled');
817 $(e.currentTarget).addClass('disabled');
818 $(e.currentTarget).removeClass('btn-primary');
818 $(e.currentTarget).removeClass('btn-primary');
819 $(e.currentTarget).text(_gettext('Updating...'));
819 $(e.currentTarget).text(_gettext('Updating...'));
820 if(isDisabled){
820 if(isDisabled){
821 updateCommits(
821 updateCommits(
822 "${c.repo_name}", "${c.pull_request.pull_request_id}");
822 "${c.repo_name}", "${c.pull_request.pull_request_id}");
823 }
823 }
824 });
824 });
825 // fixing issue with caches on firefox
825 // fixing issue with caches on firefox
826 $('#update_commits').removeAttr("disabled");
826 $('#update_commits').removeAttr("disabled");
827
827
828 $('.show-inline-comments').on('click', function(e){
828 $('.show-inline-comments').on('click', function(e){
829 var boxid = $(this).attr('data-comment-id');
829 var boxid = $(this).attr('data-comment-id');
830 var button = $(this);
830 var button = $(this);
831
831
832 if(button.hasClass("comments-visible")) {
832 if(button.hasClass("comments-visible")) {
833 $('#{0} .inline-comments'.format(boxid)).each(function(index){
833 $('#{0} .inline-comments'.format(boxid)).each(function(index){
834 $(this).hide();
834 $(this).hide();
835 });
835 });
836 button.removeClass("comments-visible");
836 button.removeClass("comments-visible");
837 } else {
837 } else {
838 $('#{0} .inline-comments'.format(boxid)).each(function(index){
838 $('#{0} .inline-comments'.format(boxid)).each(function(index){
839 $(this).show();
839 $(this).show();
840 });
840 });
841 button.addClass("comments-visible");
841 button.addClass("comments-visible");
842 }
842 }
843 });
843 });
844
844
845 // register submit callback on commentForm form to track TODOs
845 // register submit callback on commentForm form to track TODOs
846 window.commentFormGlobalSubmitSuccessCallback = function(){
846 window.commentFormGlobalSubmitSuccessCallback = function(){
847 refreshMergeChecks();
847 refreshMergeChecks();
848 };
848 };
849 // initial injection
849 // initial injection
850 injectCloseAction();
850 injectCloseAction();
851
851
852 ReviewerAutoComplete('#user');
852 ReviewerAutoComplete('#user');
853
853
854 })
854 })
855 </script>
855 </script>
856
856
857 </div>
857 </div>
858 </div>
858 </div>
859
859
860 </%def>
860 </%def>
@@ -1,100 +1,100 b''
1 <%def name="highlight_text_file(terms, text, url, line_context=3,
1 <%def name="highlight_text_file(terms, text, url, line_context=3,
2 max_lines=10,
2 max_lines=10,
3 mimetype=None, filepath=None)">
3 mimetype=None, filepath=None)">
4 <%
4 <%
5 lines = text.split('\n')
5 lines = text.split('\n')
6 lines_of_interest = set()
6 lines_of_interest = set()
7 matching_lines = h.get_matching_line_offsets(lines, terms)
7 matching_lines = h.get_matching_line_offsets(lines, terms)
8 shown_matching_lines = 0
8 shown_matching_lines = 0
9
9
10 for line_number in matching_lines:
10 for line_number in matching_lines:
11 if len(lines_of_interest) < max_lines:
11 if len(lines_of_interest) < max_lines:
12 lines_of_interest |= set(range(
12 lines_of_interest |= set(range(
13 max(line_number - line_context, 0),
13 max(line_number - line_context, 0),
14 min(line_number + line_context, len(lines) + 1)))
14 min(line_number + line_context, len(lines) + 1)))
15 shown_matching_lines += 1
15 shown_matching_lines += 1
16
16
17 %>
17 %>
18 ${h.code_highlight(
18 ${h.code_highlight(
19 text,
19 text,
20 h.get_lexer_safe(
20 h.get_lexer_safe(
21 mimetype=mimetype,
21 mimetype=mimetype,
22 filepath=filepath,
22 filepath=filepath,
23 ),
23 ),
24 h.SearchContentCodeHtmlFormatter(
24 h.SearchContentCodeHtmlFormatter(
25 linenos=True,
25 linenos=True,
26 cssclass="code-highlight",
26 cssclass="code-highlight",
27 url=url,
27 url=url,
28 query_terms=terms,
28 query_terms=terms,
29 only_line_numbers=lines_of_interest
29 only_line_numbers=lines_of_interest
30 ))|n}
30 ))|n}
31
31
32 %if len(matching_lines) > shown_matching_lines:
32 %if len(matching_lines) > shown_matching_lines:
33 <a href="${url}">
33 <a href="${url}">
34 ${len(matching_lines) - shown_matching_lines} ${_('more matches in this file')}
34 ${len(matching_lines) - shown_matching_lines} ${_('more matches in this file')}
35 </a>
35 </a>
36 %endif
36 %endif
37 </%def>
37 </%def>
38
38
39 <div class="search-results">
39 <div class="search-results">
40 %for entry in c.formatted_results:
40 %for entry in c.formatted_results:
41 ## search results are additionally filtered, and this check is just a safe gate
41 ## search results are additionally filtered, and this check is just a safe gate
42 % if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results content check'):
42 % if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results content check'):
43 <div id="codeblock" class="codeblock">
43 <div id="codeblock" class="codeblock">
44 <div class="codeblock-header">
44 <div class="codeblock-header">
45 <h2>
45 <h2>
46 %if h.get_repo_type_by_name(entry.get('repository')) == 'hg':
46 %if h.get_repo_type_by_name(entry.get('repository')) == 'hg':
47 <i class="icon-hg"></i>
47 <i class="icon-hg"></i>
48 %elif h.get_repo_type_by_name(entry.get('repository')) == 'git':
48 %elif h.get_repo_type_by_name(entry.get('repository')) == 'git':
49 <i class="icon-git"></i>
49 <i class="icon-git"></i>
50 %elif h.get_repo_type_by_name(entry.get('repository')) == 'svn':
50 %elif h.get_repo_type_by_name(entry.get('repository')) == 'svn':
51 <i class="icon-svn"></i>
51 <i class="icon-svn"></i>
52 %endif
52 %endif
53 ${h.link_to(entry['repository'], h.route_path('repo_summary',repo_name=entry['repository']))}
53 ${h.link_to(entry['repository'], h.route_path('repo_summary',repo_name=entry['repository']))}
54 </h2>
54 </h2>
55 <div class="stats">
55 <div class="stats">
56 ${h.link_to(h.literal(entry['f_path']), h.route_path('repo_files',repo_name=entry['repository'],commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path']))}
56 ${h.link_to(h.literal(entry['f_path']), h.route_path('repo_files',repo_name=entry['repository'],commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path']))}
57 %if entry.get('lines'):
57 %if entry.get('lines'):
58 | ${entry.get('lines', 0.)} ${_ungettext('line', 'lines', entry.get('lines', 0.))}
58 | ${entry.get('lines', 0.)} ${_ungettext('line', 'lines', entry.get('lines', 0.))}
59 %endif
59 %endif
60 %if entry.get('size'):
60 %if entry.get('size'):
61 | ${h.format_byte_size_binary(entry['size'])}
61 | ${h.format_byte_size_binary(entry['size'])}
62 %endif
62 %endif
63 %if entry.get('mimetype'):
63 %if entry.get('mimetype'):
64 | ${entry.get('mimetype', "unknown mimetype")}
64 | ${entry.get('mimetype', "unknown mimetype")}
65 %endif
65 %endif
66 </div>
66 </div>
67 <div class="buttons">
67 <div class="buttons">
68 <a id="file_history_overview_full" href="${h.url('changelog_file_home',repo_name=entry.get('repository',''),revision=entry.get('commit_id', 'tip'),f_path=entry.get('f_path',''))}">
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 ${_('Show Full History')}
69 ${_('Show Full History')}
70 </a>
70 </a>
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','')))}
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','')))}
72 | ${h.link_to(_('Raw'), h.route_path('repo_file_raw', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
72 | ${h.link_to(_('Raw'), h.route_path('repo_file_raw', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
73 | ${h.link_to(_('Download'), h.route_path('repo_file_download',repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
73 | ${h.link_to(_('Download'), h.route_path('repo_file_download',repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
74 </div>
74 </div>
75 </div>
75 </div>
76 <div class="code-body search-code-body">
76 <div class="code-body search-code-body">
77 ${highlight_text_file(c.cur_query, entry['content'],
77 ${highlight_text_file(c.cur_query, entry['content'],
78 url=h.route_path('repo_files',repo_name=entry['repository'],commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path']),
78 url=h.route_path('repo_files',repo_name=entry['repository'],commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path']),
79 mimetype=entry.get('mimetype'), filepath=entry.get('path'))}
79 mimetype=entry.get('mimetype'), filepath=entry.get('path'))}
80 </div>
80 </div>
81 </div>
81 </div>
82 % endif
82 % endif
83 %endfor
83 %endfor
84 </div>
84 </div>
85 %if c.cur_query and c.formatted_results:
85 %if c.cur_query and c.formatted_results:
86 <div class="pagination-wh pagination-left" >
86 <div class="pagination-wh pagination-left" >
87 ${c.formatted_results.pager('$link_previous ~2~ $link_next')}
87 ${c.formatted_results.pager('$link_previous ~2~ $link_next')}
88 </div>
88 </div>
89 %endif
89 %endif
90
90
91 %if c.cur_query:
91 %if c.cur_query:
92 <script type="text/javascript">
92 <script type="text/javascript">
93 $(function(){
93 $(function(){
94 $(".code").mark(
94 $(".code").mark(
95 '${' '.join(h.normalize_text_for_matching(c.cur_query).split())}',
95 '${' '.join(h.normalize_text_for_matching(c.cur_query).split())}',
96 {"className": 'match',
96 {"className": 'match',
97 });
97 });
98 })
98 })
99 </script>
99 </script>
100 %endif No newline at end of file
100 %endif
@@ -1,202 +1,202 b''
1 <%def name="refs_counters(branches, closed_branches, tags, bookmarks)">
1 <%def name="refs_counters(branches, closed_branches, tags, bookmarks)">
2 <span class="branchtag tag">
2 <span class="branchtag tag">
3 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
3 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
4 <i class="icon-branch"></i>${_ungettext(
4 <i class="icon-branch"></i>${_ungettext(
5 '%(num)s Branch','%(num)s Branches', len(branches)) % {'num': len(branches)}}</a>
5 '%(num)s Branch','%(num)s Branches', len(branches)) % {'num': len(branches)}}</a>
6 </span>
6 </span>
7
7
8 %if closed_branches:
8 %if closed_branches:
9 <span class="branchtag tag">
9 <span class="branchtag tag">
10 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
10 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
11 <i class="icon-branch"></i>${_ungettext(
11 <i class="icon-branch"></i>${_ungettext(
12 '%(num)s Closed Branch', '%(num)s Closed Branches', len(closed_branches)) % {'num': len(closed_branches)}}</a>
12 '%(num)s Closed Branch', '%(num)s Closed Branches', len(closed_branches)) % {'num': len(closed_branches)}}</a>
13 </span>
13 </span>
14 %endif
14 %endif
15
15
16 <span class="tagtag tag">
16 <span class="tagtag tag">
17 <a href="${h.route_path('tags_home',repo_name=c.repo_name)}" class="childs">
17 <a href="${h.route_path('tags_home',repo_name=c.repo_name)}" class="childs">
18 <i class="icon-tag"></i>${_ungettext(
18 <i class="icon-tag"></i>${_ungettext(
19 '%(num)s Tag', '%(num)s Tags', len(tags)) % {'num': len(tags)}}</a>
19 '%(num)s Tag', '%(num)s Tags', len(tags)) % {'num': len(tags)}}</a>
20 </span>
20 </span>
21
21
22 %if bookmarks:
22 %if bookmarks:
23 <span class="booktag tag">
23 <span class="booktag tag">
24 <a href="${h.route_path('bookmarks_home',repo_name=c.repo_name)}" class="childs">
24 <a href="${h.route_path('bookmarks_home',repo_name=c.repo_name)}" class="childs">
25 <i class="icon-bookmark"></i>${_ungettext(
25 <i class="icon-bookmark"></i>${_ungettext(
26 '%(num)s Bookmark', '%(num)s Bookmarks', len(bookmarks)) % {'num': len(bookmarks)}}</a>
26 '%(num)s Bookmark', '%(num)s Bookmarks', len(bookmarks)) % {'num': len(bookmarks)}}</a>
27 </span>
27 </span>
28 %endif
28 %endif
29 </%def>
29 </%def>
30
30
31 <%def name="summary_detail(breadcrumbs_links, show_downloads=True)">
31 <%def name="summary_detail(breadcrumbs_links, show_downloads=True)">
32 <% summary = lambda n:{False:'summary-short'}.get(n) %>
32 <% summary = lambda n:{False:'summary-short'}.get(n) %>
33
33
34 <div id="summary-menu-stats" class="summary-detail">
34 <div id="summary-menu-stats" class="summary-detail">
35 <div class="summary-detail-header">
35 <div class="summary-detail-header">
36 <div class="breadcrumbs files_location">
36 <div class="breadcrumbs files_location">
37 <h4>
37 <h4>
38 ${breadcrumbs_links}
38 ${breadcrumbs_links}
39 </h4>
39 </h4>
40 </div>
40 </div>
41 <div id="summary_details_expand" class="btn-collapse" data-toggle="summary-details">
41 <div id="summary_details_expand" class="btn-collapse" data-toggle="summary-details">
42 ${_('Show More')}
42 ${_('Show More')}
43 </div>
43 </div>
44 </div>
44 </div>
45
45
46 <div class="fieldset">
46 <div class="fieldset">
47 %if h.is_svn_without_proxy(c.rhodecode_db_repo):
47 %if h.is_svn_without_proxy(c.rhodecode_db_repo):
48 <div class="left-label disabled">
48 <div class="left-label disabled">
49 ${_('Read-only url')}:
49 ${_('Read-only url')}:
50 </div>
50 </div>
51 <div class="right-content disabled">
51 <div class="right-content disabled">
52 <input type="text" class="input-monospace" id="clone_url" disabled value="${c.clone_repo_url}"/>
52 <input type="text" class="input-monospace" id="clone_url" disabled value="${c.clone_repo_url}"/>
53 <input type="text" class="input-monospace" id="clone_url_id" disabled value="${c.clone_repo_url_id}" style="display: none;"/>
53 <input type="text" class="input-monospace" id="clone_url_id" disabled value="${c.clone_repo_url_id}" style="display: none;"/>
54 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
54 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
55 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
55 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
56 <p class="help-block">${_('SVN Protocol is disabled. To enable it, see the')} <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('documentation here')}</a>.</p>
56 <p class="help-block">${_('SVN Protocol is disabled. To enable it, see the')} <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('documentation here')}</a>.</p>
57 </div>
57 </div>
58 %else:
58 %else:
59 <div class="left-label">
59 <div class="left-label">
60 ${_('Clone url')}:
60 ${_('Clone url')}:
61 </div>
61 </div>
62 <div class="right-content">
62 <div class="right-content">
63 <input type="text" class="input-monospace" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
63 <input type="text" class="input-monospace" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
64 <input type="text" class="input-monospace" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}" style="display: none;"/>
64 <input type="text" class="input-monospace" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}" style="display: none;"/>
65 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
65 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
66 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
66 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
67 </div>
67 </div>
68 %endif
68 %endif
69 </div>
69 </div>
70
70
71 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
71 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
72 <div class="left-label">
72 <div class="left-label">
73 ${_('Description')}:
73 ${_('Description')}:
74 </div>
74 </div>
75 <div class="right-content">
75 <div class="right-content">
76 %if c.visual.stylify_metatags:
76 %if c.visual.stylify_metatags:
77 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.escaped_stylize(c.rhodecode_db_repo.description))}</div>
77 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.escaped_stylize(c.rhodecode_db_repo.description))}</div>
78 %else:
78 %else:
79 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.html_escape(c.rhodecode_db_repo.description))}</div>
79 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.html_escape(c.rhodecode_db_repo.description))}</div>
80 %endif
80 %endif
81 </div>
81 </div>
82 </div>
82 </div>
83
83
84 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
84 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
85 <div class="left-label">
85 <div class="left-label">
86 ${_('Information')}:
86 ${_('Information')}:
87 </div>
87 </div>
88 <div class="right-content">
88 <div class="right-content">
89
89
90 <div class="repo-size">
90 <div class="repo-size">
91 <% commit_rev = c.rhodecode_db_repo.changeset_cache.get('revision') %>
91 <% commit_rev = c.rhodecode_db_repo.changeset_cache.get('revision') %>
92
92
93 ## commits
93 ## commits
94 % if commit_rev == -1:
94 % if commit_rev == -1:
95 ${_ungettext('%(num)s Commit', '%(num)s Commits', 0) % {'num': 0}},
95 ${_ungettext('%(num)s Commit', '%(num)s Commits', 0) % {'num': 0}},
96 % else:
96 % else:
97 <a href="${h.url('changelog_home', repo_name=c.repo_name)}">
97 <a href="${h.route_path('repo_changelog', repo_name=c.repo_name)}">
98 ${_ungettext('%(num)s Commit', '%(num)s Commits', commit_rev) % {'num': commit_rev}}</a>,
98 ${_ungettext('%(num)s Commit', '%(num)s Commits', commit_rev) % {'num': commit_rev}}</a>,
99 % endif
99 % endif
100
100
101 ## forks
101 ## forks
102 <a title="${_('Number of Repository Forks')}" href="${h.url('repo_forks_home', repo_name=c.repo_name)}">
102 <a title="${_('Number of Repository Forks')}" href="${h.url('repo_forks_home', repo_name=c.repo_name)}">
103 ${c.repository_forks} ${_ungettext('Fork', 'Forks', c.repository_forks)}</a>,
103 ${c.repository_forks} ${_ungettext('Fork', 'Forks', c.repository_forks)}</a>,
104
104
105 ## repo size
105 ## repo size
106 % if commit_rev == -1:
106 % if commit_rev == -1:
107 <span class="stats-bullet">0 B</span>
107 <span class="stats-bullet">0 B</span>
108 % else:
108 % else:
109 <span class="stats-bullet" id="repo_size_container">
109 <span class="stats-bullet" id="repo_size_container">
110 ${_('Calculating Repository Size...')}
110 ${_('Calculating Repository Size...')}
111 </span>
111 </span>
112 % endif
112 % endif
113 </div>
113 </div>
114
114
115 <div class="commit-info">
115 <div class="commit-info">
116 <div class="tags">
116 <div class="tags">
117 % if c.rhodecode_repo:
117 % if c.rhodecode_repo:
118 ${refs_counters(
118 ${refs_counters(
119 c.rhodecode_repo.branches,
119 c.rhodecode_repo.branches,
120 c.rhodecode_repo.branches_closed,
120 c.rhodecode_repo.branches_closed,
121 c.rhodecode_repo.tags,
121 c.rhodecode_repo.tags,
122 c.rhodecode_repo.bookmarks)}
122 c.rhodecode_repo.bookmarks)}
123 % else:
123 % else:
124 ## missing requirements can make c.rhodecode_repo None
124 ## missing requirements can make c.rhodecode_repo None
125 ${refs_counters([], [], [], [])}
125 ${refs_counters([], [], [], [])}
126 % endif
126 % endif
127 </div>
127 </div>
128 </div>
128 </div>
129
129
130 </div>
130 </div>
131 </div>
131 </div>
132
132
133 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
133 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
134 <div class="left-label">
134 <div class="left-label">
135 ${_('Statistics')}:
135 ${_('Statistics')}:
136 </div>
136 </div>
137 <div class="right-content">
137 <div class="right-content">
138 <div class="input ${summary(c.show_stats)} statistics">
138 <div class="input ${summary(c.show_stats)} statistics">
139 % if c.show_stats:
139 % if c.show_stats:
140 <div id="lang_stats" class="enabled">
140 <div id="lang_stats" class="enabled">
141 ${_('Calculating Code Statistics...')}
141 ${_('Calculating Code Statistics...')}
142 </div>
142 </div>
143 % else:
143 % else:
144 <span class="disabled">
144 <span class="disabled">
145 ${_('Statistics are disabled for this repository')}
145 ${_('Statistics are disabled for this repository')}
146 </span>
146 </span>
147 % if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
147 % if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
148 , ${h.link_to(_('enable statistics'),h.route_path('edit_repo',repo_name=c.repo_name, anchor='repo_enable_statistics'))}
148 , ${h.link_to(_('enable statistics'),h.route_path('edit_repo',repo_name=c.repo_name, anchor='repo_enable_statistics'))}
149 % endif
149 % endif
150 % endif
150 % endif
151 </div>
151 </div>
152
152
153 </div>
153 </div>
154 </div>
154 </div>
155
155
156 % if show_downloads:
156 % if show_downloads:
157 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
157 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
158 <div class="left-label">
158 <div class="left-label">
159 ${_('Downloads')}:
159 ${_('Downloads')}:
160 </div>
160 </div>
161 <div class="right-content">
161 <div class="right-content">
162 <div class="input ${summary(c.show_stats)} downloads">
162 <div class="input ${summary(c.show_stats)} downloads">
163 % if c.rhodecode_repo and len(c.rhodecode_repo.revisions) == 0:
163 % if c.rhodecode_repo and len(c.rhodecode_repo.revisions) == 0:
164 <span class="disabled">
164 <span class="disabled">
165 ${_('There are no downloads yet')}
165 ${_('There are no downloads yet')}
166 </span>
166 </span>
167 % elif not c.enable_downloads:
167 % elif not c.enable_downloads:
168 <span class="disabled">
168 <span class="disabled">
169 ${_('Downloads are disabled for this repository')}
169 ${_('Downloads are disabled for this repository')}
170 </span>
170 </span>
171 % if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
171 % if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
172 , ${h.link_to(_('enable downloads'),h.route_path('edit_repo',repo_name=c.repo_name, anchor='repo_enable_downloads'))}
172 , ${h.link_to(_('enable downloads'),h.route_path('edit_repo',repo_name=c.repo_name, anchor='repo_enable_downloads'))}
173 % endif
173 % endif
174 % else:
174 % else:
175 <span class="enabled">
175 <span class="enabled">
176 <a id="archive_link" class="btn btn-small" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name,fname='tip.zip')}">
176 <a id="archive_link" class="btn btn-small" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name,fname='tip.zip')}">
177 <i class="icon-archive"></i> tip.zip
177 <i class="icon-archive"></i> tip.zip
178 ## replaced by some JS on select
178 ## replaced by some JS on select
179 </a>
179 </a>
180 </span>
180 </span>
181 ${h.hidden('download_options')}
181 ${h.hidden('download_options')}
182 % endif
182 % endif
183 </div>
183 </div>
184 </div>
184 </div>
185 </div>
185 </div>
186 % endif
186 % endif
187
187
188 </div><!--end summary-detail-->
188 </div><!--end summary-detail-->
189 </%def>
189 </%def>
190
190
191 <%def name="summary_stats(gravatar_function)">
191 <%def name="summary_stats(gravatar_function)">
192 <div class="sidebar-right">
192 <div class="sidebar-right">
193 <div class="summary-detail-header">
193 <div class="summary-detail-header">
194 <h4 class="item">
194 <h4 class="item">
195 ${_('Owner')}
195 ${_('Owner')}
196 </h4>
196 </h4>
197 </div>
197 </div>
198 <div class="sidebar-right-content">
198 <div class="sidebar-right-content">
199 ${gravatar_function(c.rhodecode_db_repo.user.email, 16)}
199 ${gravatar_function(c.rhodecode_db_repo.user.email, 16)}
200 </div>
200 </div>
201 </div><!--end sidebar-right-->
201 </div><!--end sidebar-right-->
202 </%def>
202 </%def>
@@ -1,136 +1,136 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3 %if c.repo_commits:
3 %if c.repo_commits:
4 <table class="rctable repo_summary table_disp">
4 <table class="rctable repo_summary table_disp">
5 <tr>
5 <tr>
6
6
7 <th class="status" colspan="2"></th>
7 <th class="status" colspan="2"></th>
8 <th>${_('Commit')}</th>
8 <th>${_('Commit')}</th>
9 <th>${_('Commit message')}</th>
9 <th>${_('Commit message')}</th>
10 <th>${_('Age')}</th>
10 <th>${_('Age')}</th>
11 <th>${_('Author')}</th>
11 <th>${_('Author')}</th>
12 <th>${_('Refs')}</th>
12 <th>${_('Refs')}</th>
13 </tr>
13 </tr>
14 %for cnt,cs in enumerate(c.repo_commits):
14 %for cnt,cs in enumerate(c.repo_commits):
15 <tr class="parity${cnt%2}">
15 <tr class="parity${cnt%2}">
16
16
17 <td class="td-status">
17 <td class="td-status">
18 %if c.statuses.get(cs.raw_id):
18 %if c.statuses.get(cs.raw_id):
19 <div class="changeset-status-ico shortlog">
19 <div class="changeset-status-ico shortlog">
20 %if c.statuses.get(cs.raw_id)[2]:
20 %if c.statuses.get(cs.raw_id)[2]:
21 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (c.statuses.get(cs.raw_id)[0], c.statuses.get(cs.raw_id)[2])}" href="${h.route_path('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
21 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (c.statuses.get(cs.raw_id)[0], c.statuses.get(cs.raw_id)[2])}" href="${h.route_path('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
22 <div class="${'flag_status %s' % c.statuses.get(cs.raw_id)[0]}"></div>
22 <div class="${'flag_status %s' % c.statuses.get(cs.raw_id)[0]}"></div>
23 </a>
23 </a>
24 %else:
24 %else:
25 <a class="tooltip" title="${_('Commit status: %s') % h.commit_status_lbl(c.statuses.get(cs.raw_id)[0])}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
25 <a class="tooltip" title="${_('Commit status: %s') % h.commit_status_lbl(c.statuses.get(cs.raw_id)[0])}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
26 <div class="${'flag_status %s' % c.statuses.get(cs.raw_id)[0]}"></div>
26 <div class="${'flag_status %s' % c.statuses.get(cs.raw_id)[0]}"></div>
27 </a>
27 </a>
28 %endif
28 %endif
29 </div>
29 </div>
30 %else:
30 %else:
31 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
31 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
32 %endif
32 %endif
33 </td>
33 </td>
34 <td class="td-comments">
34 <td class="td-comments">
35 %if c.comments.get(cs.raw_id,[]):
35 %if c.comments.get(cs.raw_id,[]):
36 <a title="${_('Commit has comments')}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
36 <a title="${_('Commit has comments')}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
37 <i class="icon-comment"></i> ${len(c.comments[cs.raw_id])}
37 <i class="icon-comment"></i> ${len(c.comments[cs.raw_id])}
38 </a>
38 </a>
39 %endif
39 %endif
40 </td>
40 </td>
41 <td class="td-commit">
41 <td class="td-commit">
42 <pre><a href="${h.url('changeset_home', repo_name=c.repo_name, revision=cs.raw_id)}">${h.show_id(cs)}</a></pre>
42 <pre><a href="${h.url('changeset_home', repo_name=c.repo_name, revision=cs.raw_id)}">${h.show_id(cs)}</a></pre>
43 </td>
43 </td>
44
44
45 <td class="td-description mid">
45 <td class="td-description mid">
46 <div class="log-container truncate-wrap">
46 <div class="log-container truncate-wrap">
47 <div class="message truncate" id="c-${cs.raw_id}">${h.urlify_commit_message(cs.message, c.repo_name)}</div>
47 <div class="message truncate" id="c-${cs.raw_id}">${h.urlify_commit_message(cs.message, c.repo_name)}</div>
48 </div>
48 </div>
49 </td>
49 </td>
50
50
51 <td class="td-time">
51 <td class="td-time">
52 ${h.age_component(cs.date)}
52 ${h.age_component(cs.date)}
53 </td>
53 </td>
54 <td class="td-user author">
54 <td class="td-user author">
55 ${base.gravatar_with_user(cs.author)}
55 ${base.gravatar_with_user(cs.author)}
56 </td>
56 </td>
57
57
58 <td class="td-tags">
58 <td class="td-tags">
59 <div class="autoexpand">
59 <div class="autoexpand">
60 %if h.is_hg(c.rhodecode_repo):
60 %if h.is_hg(c.rhodecode_repo):
61 %for book in cs.bookmarks:
61 %for book in cs.bookmarks:
62 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
62 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
63 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=cs.raw_id, _query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
63 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=cs.raw_id, _query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
64 </span>
64 </span>
65 %endfor
65 %endfor
66 %endif
66 %endif
67 ## tags
67 ## tags
68 %for tag in cs.tags:
68 %for tag in cs.tags:
69 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
69 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
70 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=cs.raw_id, _query=dict(at=tag))}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
70 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=cs.raw_id, _query=dict(at=tag))}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
71 </span>
71 </span>
72 %endfor
72 %endfor
73
73
74 ## branch
74 ## branch
75 %if cs.branch:
75 %if cs.branch:
76 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % cs.branch)}">
76 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % cs.branch)}">
77 <a href="${h.url('changelog_home',repo_name=c.repo_name,branch=cs.branch)}"><i class="icon-code-fork"></i>${h.shorter(cs.branch)}</a>
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 </span>
78 </span>
79 %endif
79 %endif
80 </div>
80 </div>
81 </td>
81 </td>
82 </tr>
82 </tr>
83 %endfor
83 %endfor
84
84
85 </table>
85 </table>
86
86
87 <script type="text/javascript">
87 <script type="text/javascript">
88 $(document).pjax('#shortlog_data .pager_link','#shortlog_data', {timeout: 2000, scrollTo: false });
88 $(document).pjax('#shortlog_data .pager_link','#shortlog_data', {timeout: 2000, scrollTo: false });
89 $(document).on('pjax:success', function(){ timeagoActivate(); });
89 $(document).on('pjax:success', function(){ timeagoActivate(); });
90 </script>
90 </script>
91
91
92 <div class="pagination-wh pagination-left">
92 <div class="pagination-wh pagination-left">
93 ${c.repo_commits.pager('$link_previous ~2~ $link_next')}
93 ${c.repo_commits.pager('$link_previous ~2~ $link_next')}
94 </div>
94 </div>
95 %else:
95 %else:
96
96
97 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
97 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
98 <div class="quick_start">
98 <div class="quick_start">
99 <div class="fieldset">
99 <div class="fieldset">
100 <div class="left-label">${_('Add or upload files directly via RhodeCode:')}</div>
100 <div class="left-label">${_('Add or upload files directly via RhodeCode:')}</div>
101 <div class="right-content">
101 <div class="right-content">
102 <div id="add_node_id" class="add_node">
102 <div id="add_node_id" class="add_node">
103 <a href="${h.route_path('repo_files_add_file',repo_name=c.repo_name,commit_id=0, f_path='', _anchor='edit')}" class="btn btn-default">${_('Add New File')}</a>
103 <a href="${h.route_path('repo_files_add_file',repo_name=c.repo_name,commit_id=0, f_path='', _anchor='edit')}" class="btn btn-default">${_('Add New File')}</a>
104 </div>
104 </div>
105 </div>
105 </div>
106 %endif
106 %endif
107 </div>
107 </div>
108
108
109 %if not h.is_svn(c.rhodecode_repo):
109 %if not h.is_svn(c.rhodecode_repo):
110 <div class="fieldset">
110 <div class="fieldset">
111 <div class="left-label">${_('Push new repo:')}</div>
111 <div class="left-label">${_('Push new repo:')}</div>
112 <div class="right-content">
112 <div class="right-content">
113 <pre>
113 <pre>
114 ${c.rhodecode_repo.alias} clone ${c.clone_repo_url}
114 ${c.rhodecode_repo.alias} clone ${c.clone_repo_url}
115 ${c.rhodecode_repo.alias} add README # add first file
115 ${c.rhodecode_repo.alias} add README # add first file
116 ${c.rhodecode_repo.alias} commit -m "Initial" # commit with message
116 ${c.rhodecode_repo.alias} commit -m "Initial" # commit with message
117 ${c.rhodecode_repo.alias} push ${'origin master' if h.is_git(c.rhodecode_repo) else ''} # push changes back
117 ${c.rhodecode_repo.alias} push ${'origin master' if h.is_git(c.rhodecode_repo) else ''} # push changes back
118 </pre>
118 </pre>
119 </div>
119 </div>
120 </div>
120 </div>
121 <div class="fieldset">
121 <div class="fieldset">
122 <div class="left-label">${_('Existing repository?')}</div>
122 <div class="left-label">${_('Existing repository?')}</div>
123 <div class="right-content">
123 <div class="right-content">
124 <pre>
124 <pre>
125 %if h.is_git(c.rhodecode_repo):
125 %if h.is_git(c.rhodecode_repo):
126 git remote add origin ${c.clone_repo_url}
126 git remote add origin ${c.clone_repo_url}
127 git push -u origin master
127 git push -u origin master
128 %else:
128 %else:
129 hg push ${c.clone_repo_url}
129 hg push ${c.clone_repo_url}
130 %endif
130 %endif
131 </pre>
131 </pre>
132 </div>
132 </div>
133 </div>
133 </div>
134 %endif
134 %endif
135 </div>
135 </div>
136 %endif
136 %endif
@@ -1,1094 +1,1107 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23 from webob.exc import HTTPNotFound
23 from webob.exc import HTTPNotFound
24
24
25 import rhodecode
25 import rhodecode
26 from rhodecode.lib.vcs.nodes import FileNode
26 from rhodecode.lib.vcs.nodes import FileNode
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.model.changeset_status import ChangesetStatusModel
28 from rhodecode.model.changeset_status import ChangesetStatusModel
29 from rhodecode.model.db import (
29 from rhodecode.model.db import (
30 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment)
30 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment)
31 from rhodecode.model.meta import Session
31 from rhodecode.model.meta import Session
32 from rhodecode.model.pull_request import PullRequestModel
32 from rhodecode.model.pull_request import PullRequestModel
33 from rhodecode.model.user import UserModel
33 from rhodecode.model.user import UserModel
34 from rhodecode.tests import (
34 from rhodecode.tests import (
35 assert_session_flash, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
35 assert_session_flash, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
36 from rhodecode.tests.utils import AssertResponse
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 @pytest.mark.usefixtures('app', 'autologin_user')
52 @pytest.mark.usefixtures('app', 'autologin_user')
40 @pytest.mark.backends("git", "hg")
53 @pytest.mark.backends("git", "hg")
41 class TestPullrequestsController(object):
54 class TestPullrequestsController(object):
42
55
43 def test_index(self, backend):
56 def test_index(self, backend):
44 self.app.get(url(
57 self.app.get(url(
45 controller='pullrequests', action='index',
58 controller='pullrequests', action='index',
46 repo_name=backend.repo_name))
59 repo_name=backend.repo_name))
47
60
48 def test_option_menu_create_pull_request_exists(self, backend):
61 def test_option_menu_create_pull_request_exists(self, backend):
49 repo_name = backend.repo_name
62 repo_name = backend.repo_name
50 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
63 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
51
64
52 create_pr_link = '<a href="%s">Create Pull Request</a>' % url(
65 create_pr_link = '<a href="%s">Create Pull Request</a>' % url(
53 'pullrequest', repo_name=repo_name)
66 'pullrequest', repo_name=repo_name)
54 response.mustcontain(create_pr_link)
67 response.mustcontain(create_pr_link)
55
68
56 def test_create_pr_form_with_raw_commit_id(self, backend):
69 def test_create_pr_form_with_raw_commit_id(self, backend):
57 repo = backend.repo
70 repo = backend.repo
58
71
59 self.app.get(
72 self.app.get(
60 url(controller='pullrequests', action='index',
73 url(controller='pullrequests', action='index',
61 repo_name=repo.repo_name,
74 repo_name=repo.repo_name,
62 commit=repo.get_commit().raw_id),
75 commit=repo.get_commit().raw_id),
63 status=200)
76 status=200)
64
77
65 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
78 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
66 def test_show(self, pr_util, pr_merge_enabled):
79 def test_show(self, pr_util, pr_merge_enabled):
67 pull_request = pr_util.create_pull_request(
80 pull_request = pr_util.create_pull_request(
68 mergeable=pr_merge_enabled, enable_notifications=False)
81 mergeable=pr_merge_enabled, enable_notifications=False)
69
82
70 response = self.app.get(url(
83 response = self.app.get(url(
71 controller='pullrequests', action='show',
84 controller='pullrequests', action='show',
72 repo_name=pull_request.target_repo.scm_instance().name,
85 repo_name=pull_request.target_repo.scm_instance().name,
73 pull_request_id=str(pull_request.pull_request_id)))
86 pull_request_id=str(pull_request.pull_request_id)))
74
87
75 for commit_id in pull_request.revisions:
88 for commit_id in pull_request.revisions:
76 response.mustcontain(commit_id)
89 response.mustcontain(commit_id)
77
90
78 assert pull_request.target_ref_parts.type in response
91 assert pull_request.target_ref_parts.type in response
79 assert pull_request.target_ref_parts.name in response
92 assert pull_request.target_ref_parts.name in response
80 target_clone_url = pull_request.target_repo.clone_url()
93 target_clone_url = pull_request.target_repo.clone_url()
81 assert target_clone_url in response
94 assert target_clone_url in response
82
95
83 assert 'class="pull-request-merge"' in response
96 assert 'class="pull-request-merge"' in response
84 assert (
97 assert (
85 'Server-side pull request merging is disabled.'
98 'Server-side pull request merging is disabled.'
86 in response) != pr_merge_enabled
99 in response) != pr_merge_enabled
87
100
88 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
101 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
89 from rhodecode.tests.functional.test_login import login_url, logut_url
102 from rhodecode.tests.functional.test_login import login_url, logut_url
90 # Logout
103 # Logout
91 response = self.app.post(
104 response = self.app.post(
92 logut_url,
105 logut_url,
93 params={'csrf_token': csrf_token})
106 params={'csrf_token': csrf_token})
94 # Login as regular user
107 # Login as regular user
95 response = self.app.post(login_url,
108 response = self.app.post(login_url,
96 {'username': TEST_USER_REGULAR_LOGIN,
109 {'username': TEST_USER_REGULAR_LOGIN,
97 'password': 'test12'})
110 'password': 'test12'})
98
111
99 pull_request = pr_util.create_pull_request(
112 pull_request = pr_util.create_pull_request(
100 author=TEST_USER_REGULAR_LOGIN)
113 author=TEST_USER_REGULAR_LOGIN)
101
114
102 response = self.app.get(url(
115 response = self.app.get(url(
103 controller='pullrequests', action='show',
116 controller='pullrequests', action='show',
104 repo_name=pull_request.target_repo.scm_instance().name,
117 repo_name=pull_request.target_repo.scm_instance().name,
105 pull_request_id=str(pull_request.pull_request_id)))
118 pull_request_id=str(pull_request.pull_request_id)))
106
119
107 response.mustcontain('Server-side pull request merging is disabled.')
120 response.mustcontain('Server-side pull request merging is disabled.')
108
121
109 assert_response = response.assert_response()
122 assert_response = response.assert_response()
110 # for regular user without a merge permissions, we don't see it
123 # for regular user without a merge permissions, we don't see it
111 assert_response.no_element_exists('#close-pull-request-action')
124 assert_response.no_element_exists('#close-pull-request-action')
112
125
113 user_util.grant_user_permission_to_repo(
126 user_util.grant_user_permission_to_repo(
114 pull_request.target_repo,
127 pull_request.target_repo,
115 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
128 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
116 'repository.write')
129 'repository.write')
117 response = self.app.get(url(
130 response = self.app.get(url(
118 controller='pullrequests', action='show',
131 controller='pullrequests', action='show',
119 repo_name=pull_request.target_repo.scm_instance().name,
132 repo_name=pull_request.target_repo.scm_instance().name,
120 pull_request_id=str(pull_request.pull_request_id)))
133 pull_request_id=str(pull_request.pull_request_id)))
121
134
122 response.mustcontain('Server-side pull request merging is disabled.')
135 response.mustcontain('Server-side pull request merging is disabled.')
123
136
124 assert_response = response.assert_response()
137 assert_response = response.assert_response()
125 # now regular user has a merge permissions, we have CLOSE button
138 # now regular user has a merge permissions, we have CLOSE button
126 assert_response.one_element_exists('#close-pull-request-action')
139 assert_response.one_element_exists('#close-pull-request-action')
127
140
128 def test_show_invalid_commit_id(self, pr_util):
141 def test_show_invalid_commit_id(self, pr_util):
129 # Simulating invalid revisions which will cause a lookup error
142 # Simulating invalid revisions which will cause a lookup error
130 pull_request = pr_util.create_pull_request()
143 pull_request = pr_util.create_pull_request()
131 pull_request.revisions = ['invalid']
144 pull_request.revisions = ['invalid']
132 Session().add(pull_request)
145 Session().add(pull_request)
133 Session().commit()
146 Session().commit()
134
147
135 response = self.app.get(url(
148 response = self.app.get(url(
136 controller='pullrequests', action='show',
149 controller='pullrequests', action='show',
137 repo_name=pull_request.target_repo.scm_instance().name,
150 repo_name=pull_request.target_repo.scm_instance().name,
138 pull_request_id=str(pull_request.pull_request_id)))
151 pull_request_id=str(pull_request.pull_request_id)))
139
152
140 for commit_id in pull_request.revisions:
153 for commit_id in pull_request.revisions:
141 response.mustcontain(commit_id)
154 response.mustcontain(commit_id)
142
155
143 def test_show_invalid_source_reference(self, pr_util):
156 def test_show_invalid_source_reference(self, pr_util):
144 pull_request = pr_util.create_pull_request()
157 pull_request = pr_util.create_pull_request()
145 pull_request.source_ref = 'branch:b:invalid'
158 pull_request.source_ref = 'branch:b:invalid'
146 Session().add(pull_request)
159 Session().add(pull_request)
147 Session().commit()
160 Session().commit()
148
161
149 self.app.get(url(
162 self.app.get(url(
150 controller='pullrequests', action='show',
163 controller='pullrequests', action='show',
151 repo_name=pull_request.target_repo.scm_instance().name,
164 repo_name=pull_request.target_repo.scm_instance().name,
152 pull_request_id=str(pull_request.pull_request_id)))
165 pull_request_id=str(pull_request.pull_request_id)))
153
166
154 def test_edit_title_description(self, pr_util, csrf_token):
167 def test_edit_title_description(self, pr_util, csrf_token):
155 pull_request = pr_util.create_pull_request()
168 pull_request = pr_util.create_pull_request()
156 pull_request_id = pull_request.pull_request_id
169 pull_request_id = pull_request.pull_request_id
157
170
158 response = self.app.post(
171 response = self.app.post(
159 url(controller='pullrequests', action='update',
172 url(controller='pullrequests', action='update',
160 repo_name=pull_request.target_repo.repo_name,
173 repo_name=pull_request.target_repo.repo_name,
161 pull_request_id=str(pull_request_id)),
174 pull_request_id=str(pull_request_id)),
162 params={
175 params={
163 'edit_pull_request': 'true',
176 'edit_pull_request': 'true',
164 '_method': 'put',
177 '_method': 'put',
165 'title': 'New title',
178 'title': 'New title',
166 'description': 'New description',
179 'description': 'New description',
167 'csrf_token': csrf_token})
180 'csrf_token': csrf_token})
168
181
169 assert_session_flash(
182 assert_session_flash(
170 response, u'Pull request title & description updated.',
183 response, u'Pull request title & description updated.',
171 category='success')
184 category='success')
172
185
173 pull_request = PullRequest.get(pull_request_id)
186 pull_request = PullRequest.get(pull_request_id)
174 assert pull_request.title == 'New title'
187 assert pull_request.title == 'New title'
175 assert pull_request.description == 'New description'
188 assert pull_request.description == 'New description'
176
189
177 def test_edit_title_description_closed(self, pr_util, csrf_token):
190 def test_edit_title_description_closed(self, pr_util, csrf_token):
178 pull_request = pr_util.create_pull_request()
191 pull_request = pr_util.create_pull_request()
179 pull_request_id = pull_request.pull_request_id
192 pull_request_id = pull_request.pull_request_id
180 pr_util.close()
193 pr_util.close()
181
194
182 response = self.app.post(
195 response = self.app.post(
183 url(controller='pullrequests', action='update',
196 url(controller='pullrequests', action='update',
184 repo_name=pull_request.target_repo.repo_name,
197 repo_name=pull_request.target_repo.repo_name,
185 pull_request_id=str(pull_request_id)),
198 pull_request_id=str(pull_request_id)),
186 params={
199 params={
187 'edit_pull_request': 'true',
200 'edit_pull_request': 'true',
188 '_method': 'put',
201 '_method': 'put',
189 'title': 'New title',
202 'title': 'New title',
190 'description': 'New description',
203 'description': 'New description',
191 'csrf_token': csrf_token})
204 'csrf_token': csrf_token})
192
205
193 assert_session_flash(
206 assert_session_flash(
194 response, u'Cannot update closed pull requests.',
207 response, u'Cannot update closed pull requests.',
195 category='error')
208 category='error')
196
209
197 def test_update_invalid_source_reference(self, pr_util, csrf_token):
210 def test_update_invalid_source_reference(self, pr_util, csrf_token):
198 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
211 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
199
212
200 pull_request = pr_util.create_pull_request()
213 pull_request = pr_util.create_pull_request()
201 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
214 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
202 Session().add(pull_request)
215 Session().add(pull_request)
203 Session().commit()
216 Session().commit()
204
217
205 pull_request_id = pull_request.pull_request_id
218 pull_request_id = pull_request.pull_request_id
206
219
207 response = self.app.post(
220 response = self.app.post(
208 url(controller='pullrequests', action='update',
221 url(controller='pullrequests', action='update',
209 repo_name=pull_request.target_repo.repo_name,
222 repo_name=pull_request.target_repo.repo_name,
210 pull_request_id=str(pull_request_id)),
223 pull_request_id=str(pull_request_id)),
211 params={'update_commits': 'true', '_method': 'put',
224 params={'update_commits': 'true', '_method': 'put',
212 'csrf_token': csrf_token})
225 'csrf_token': csrf_token})
213
226
214 expected_msg = PullRequestModel.UPDATE_STATUS_MESSAGES[
227 expected_msg = PullRequestModel.UPDATE_STATUS_MESSAGES[
215 UpdateFailureReason.MISSING_SOURCE_REF]
228 UpdateFailureReason.MISSING_SOURCE_REF]
216 assert_session_flash(response, expected_msg, category='error')
229 assert_session_flash(response, expected_msg, category='error')
217
230
218 def test_missing_target_reference(self, pr_util, csrf_token):
231 def test_missing_target_reference(self, pr_util, csrf_token):
219 from rhodecode.lib.vcs.backends.base import MergeFailureReason
232 from rhodecode.lib.vcs.backends.base import MergeFailureReason
220 pull_request = pr_util.create_pull_request(
233 pull_request = pr_util.create_pull_request(
221 approved=True, mergeable=True)
234 approved=True, mergeable=True)
222 pull_request.target_ref = 'branch:invalid-branch:invalid-commit-id'
235 pull_request.target_ref = 'branch:invalid-branch:invalid-commit-id'
223 Session().add(pull_request)
236 Session().add(pull_request)
224 Session().commit()
237 Session().commit()
225
238
226 pull_request_id = pull_request.pull_request_id
239 pull_request_id = pull_request.pull_request_id
227 pull_request_url = url(
240 pull_request_url = url(
228 controller='pullrequests', action='show',
241 controller='pullrequests', action='show',
229 repo_name=pull_request.target_repo.repo_name,
242 repo_name=pull_request.target_repo.repo_name,
230 pull_request_id=str(pull_request_id))
243 pull_request_id=str(pull_request_id))
231
244
232 response = self.app.get(pull_request_url)
245 response = self.app.get(pull_request_url)
233
246
234 assertr = AssertResponse(response)
247 assertr = AssertResponse(response)
235 expected_msg = PullRequestModel.MERGE_STATUS_MESSAGES[
248 expected_msg = PullRequestModel.MERGE_STATUS_MESSAGES[
236 MergeFailureReason.MISSING_TARGET_REF]
249 MergeFailureReason.MISSING_TARGET_REF]
237 assertr.element_contains(
250 assertr.element_contains(
238 'span[data-role="merge-message"]', str(expected_msg))
251 'span[data-role="merge-message"]', str(expected_msg))
239
252
240 def test_comment_and_close_pull_request_custom_message_approved(
253 def test_comment_and_close_pull_request_custom_message_approved(
241 self, pr_util, csrf_token, xhr_header):
254 self, pr_util, csrf_token, xhr_header):
242
255
243 pull_request = pr_util.create_pull_request(approved=True)
256 pull_request = pr_util.create_pull_request(approved=True)
244 pull_request_id = pull_request.pull_request_id
257 pull_request_id = pull_request.pull_request_id
245 author = pull_request.user_id
258 author = pull_request.user_id
246 repo = pull_request.target_repo.repo_id
259 repo = pull_request.target_repo.repo_id
247
260
248 self.app.post(
261 self.app.post(
249 url(controller='pullrequests',
262 url(controller='pullrequests',
250 action='comment',
263 action='comment',
251 repo_name=pull_request.target_repo.scm_instance().name,
264 repo_name=pull_request.target_repo.scm_instance().name,
252 pull_request_id=str(pull_request_id)),
265 pull_request_id=str(pull_request_id)),
253 params={
266 params={
254 'close_pull_request': '1',
267 'close_pull_request': '1',
255 'text': 'Closing a PR',
268 'text': 'Closing a PR',
256 'csrf_token': csrf_token},
269 'csrf_token': csrf_token},
257 extra_environ=xhr_header,)
270 extra_environ=xhr_header,)
258
271
259 journal = UserLog.query()\
272 journal = UserLog.query()\
260 .filter(UserLog.user_id == author)\
273 .filter(UserLog.user_id == author)\
261 .filter(UserLog.repository_id == repo) \
274 .filter(UserLog.repository_id == repo) \
262 .order_by('user_log_id') \
275 .order_by('user_log_id') \
263 .all()
276 .all()
264 assert journal[-1].action == 'repo.pull_request.close'
277 assert journal[-1].action == 'repo.pull_request.close'
265
278
266 pull_request = PullRequest.get(pull_request_id)
279 pull_request = PullRequest.get(pull_request_id)
267 assert pull_request.is_closed()
280 assert pull_request.is_closed()
268
281
269 status = ChangesetStatusModel().get_status(
282 status = ChangesetStatusModel().get_status(
270 pull_request.source_repo, pull_request=pull_request)
283 pull_request.source_repo, pull_request=pull_request)
271 assert status == ChangesetStatus.STATUS_APPROVED
284 assert status == ChangesetStatus.STATUS_APPROVED
272 comments = ChangesetComment().query() \
285 comments = ChangesetComment().query() \
273 .filter(ChangesetComment.pull_request == pull_request) \
286 .filter(ChangesetComment.pull_request == pull_request) \
274 .order_by(ChangesetComment.comment_id.asc())\
287 .order_by(ChangesetComment.comment_id.asc())\
275 .all()
288 .all()
276 assert comments[-1].text == 'Closing a PR'
289 assert comments[-1].text == 'Closing a PR'
277
290
278 def test_comment_force_close_pull_request_rejected(
291 def test_comment_force_close_pull_request_rejected(
279 self, pr_util, csrf_token, xhr_header):
292 self, pr_util, csrf_token, xhr_header):
280 pull_request = pr_util.create_pull_request()
293 pull_request = pr_util.create_pull_request()
281 pull_request_id = pull_request.pull_request_id
294 pull_request_id = pull_request.pull_request_id
282 PullRequestModel().update_reviewers(
295 PullRequestModel().update_reviewers(
283 pull_request_id, [(1, ['reason'], False), (2, ['reason2'], False)],
296 pull_request_id, [(1, ['reason'], False), (2, ['reason2'], False)],
284 pull_request.author)
297 pull_request.author)
285 author = pull_request.user_id
298 author = pull_request.user_id
286 repo = pull_request.target_repo.repo_id
299 repo = pull_request.target_repo.repo_id
287
300
288 self.app.post(
301 self.app.post(
289 url(controller='pullrequests',
302 url(controller='pullrequests',
290 action='comment',
303 action='comment',
291 repo_name=pull_request.target_repo.scm_instance().name,
304 repo_name=pull_request.target_repo.scm_instance().name,
292 pull_request_id=str(pull_request_id)),
305 pull_request_id=str(pull_request_id)),
293 params={
306 params={
294 'close_pull_request': '1',
307 'close_pull_request': '1',
295 'csrf_token': csrf_token},
308 'csrf_token': csrf_token},
296 extra_environ=xhr_header)
309 extra_environ=xhr_header)
297
310
298 pull_request = PullRequest.get(pull_request_id)
311 pull_request = PullRequest.get(pull_request_id)
299
312
300 journal = UserLog.query()\
313 journal = UserLog.query()\
301 .filter(UserLog.user_id == author, UserLog.repository_id == repo) \
314 .filter(UserLog.user_id == author, UserLog.repository_id == repo) \
302 .order_by('user_log_id') \
315 .order_by('user_log_id') \
303 .all()
316 .all()
304 assert journal[-1].action == 'repo.pull_request.close'
317 assert journal[-1].action == 'repo.pull_request.close'
305
318
306 # check only the latest status, not the review status
319 # check only the latest status, not the review status
307 status = ChangesetStatusModel().get_status(
320 status = ChangesetStatusModel().get_status(
308 pull_request.source_repo, pull_request=pull_request)
321 pull_request.source_repo, pull_request=pull_request)
309 assert status == ChangesetStatus.STATUS_REJECTED
322 assert status == ChangesetStatus.STATUS_REJECTED
310
323
311 def test_comment_and_close_pull_request(
324 def test_comment_and_close_pull_request(
312 self, pr_util, csrf_token, xhr_header):
325 self, pr_util, csrf_token, xhr_header):
313 pull_request = pr_util.create_pull_request()
326 pull_request = pr_util.create_pull_request()
314 pull_request_id = pull_request.pull_request_id
327 pull_request_id = pull_request.pull_request_id
315
328
316 response = self.app.post(
329 response = self.app.post(
317 url(controller='pullrequests',
330 url(controller='pullrequests',
318 action='comment',
331 action='comment',
319 repo_name=pull_request.target_repo.scm_instance().name,
332 repo_name=pull_request.target_repo.scm_instance().name,
320 pull_request_id=str(pull_request.pull_request_id)),
333 pull_request_id=str(pull_request.pull_request_id)),
321 params={
334 params={
322 'close_pull_request': 'true',
335 'close_pull_request': 'true',
323 'csrf_token': csrf_token},
336 'csrf_token': csrf_token},
324 extra_environ=xhr_header)
337 extra_environ=xhr_header)
325
338
326 assert response.json
339 assert response.json
327
340
328 pull_request = PullRequest.get(pull_request_id)
341 pull_request = PullRequest.get(pull_request_id)
329 assert pull_request.is_closed()
342 assert pull_request.is_closed()
330
343
331 # check only the latest status, not the review status
344 # check only the latest status, not the review status
332 status = ChangesetStatusModel().get_status(
345 status = ChangesetStatusModel().get_status(
333 pull_request.source_repo, pull_request=pull_request)
346 pull_request.source_repo, pull_request=pull_request)
334 assert status == ChangesetStatus.STATUS_REJECTED
347 assert status == ChangesetStatus.STATUS_REJECTED
335
348
336 def test_create_pull_request(self, backend, csrf_token):
349 def test_create_pull_request(self, backend, csrf_token):
337 commits = [
350 commits = [
338 {'message': 'ancestor'},
351 {'message': 'ancestor'},
339 {'message': 'change'},
352 {'message': 'change'},
340 {'message': 'change2'},
353 {'message': 'change2'},
341 ]
354 ]
342 commit_ids = backend.create_master_repo(commits)
355 commit_ids = backend.create_master_repo(commits)
343 target = backend.create_repo(heads=['ancestor'])
356 target = backend.create_repo(heads=['ancestor'])
344 source = backend.create_repo(heads=['change2'])
357 source = backend.create_repo(heads=['change2'])
345
358
346 response = self.app.post(
359 response = self.app.post(
347 url(
360 url(
348 controller='pullrequests',
361 controller='pullrequests',
349 action='create',
362 action='create',
350 repo_name=source.repo_name
363 repo_name=source.repo_name
351 ),
364 ),
352 [
365 [
353 ('source_repo', source.repo_name),
366 ('source_repo', source.repo_name),
354 ('source_ref', 'branch:default:' + commit_ids['change2']),
367 ('source_ref', 'branch:default:' + commit_ids['change2']),
355 ('target_repo', target.repo_name),
368 ('target_repo', target.repo_name),
356 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
369 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
357 ('common_ancestor', commit_ids['ancestor']),
370 ('common_ancestor', commit_ids['ancestor']),
358 ('pullrequest_desc', 'Description'),
371 ('pullrequest_desc', 'Description'),
359 ('pullrequest_title', 'Title'),
372 ('pullrequest_title', 'Title'),
360 ('__start__', 'review_members:sequence'),
373 ('__start__', 'review_members:sequence'),
361 ('__start__', 'reviewer:mapping'),
374 ('__start__', 'reviewer:mapping'),
362 ('user_id', '1'),
375 ('user_id', '1'),
363 ('__start__', 'reasons:sequence'),
376 ('__start__', 'reasons:sequence'),
364 ('reason', 'Some reason'),
377 ('reason', 'Some reason'),
365 ('__end__', 'reasons:sequence'),
378 ('__end__', 'reasons:sequence'),
366 ('mandatory', 'False'),
379 ('mandatory', 'False'),
367 ('__end__', 'reviewer:mapping'),
380 ('__end__', 'reviewer:mapping'),
368 ('__end__', 'review_members:sequence'),
381 ('__end__', 'review_members:sequence'),
369 ('__start__', 'revisions:sequence'),
382 ('__start__', 'revisions:sequence'),
370 ('revisions', commit_ids['change']),
383 ('revisions', commit_ids['change']),
371 ('revisions', commit_ids['change2']),
384 ('revisions', commit_ids['change2']),
372 ('__end__', 'revisions:sequence'),
385 ('__end__', 'revisions:sequence'),
373 ('user', ''),
386 ('user', ''),
374 ('csrf_token', csrf_token),
387 ('csrf_token', csrf_token),
375 ],
388 ],
376 status=302)
389 status=302)
377
390
378 location = response.headers['Location']
391 location = response.headers['Location']
379 pull_request_id = location.rsplit('/', 1)[1]
392 pull_request_id = location.rsplit('/', 1)[1]
380 assert pull_request_id != 'new'
393 assert pull_request_id != 'new'
381 pull_request = PullRequest.get(int(pull_request_id))
394 pull_request = PullRequest.get(int(pull_request_id))
382
395
383 # check that we have now both revisions
396 # check that we have now both revisions
384 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
397 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
385 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
398 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
386 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
399 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
387 assert pull_request.target_ref == expected_target_ref
400 assert pull_request.target_ref == expected_target_ref
388
401
389 def test_reviewer_notifications(self, backend, csrf_token):
402 def test_reviewer_notifications(self, backend, csrf_token):
390 # We have to use the app.post for this test so it will create the
403 # We have to use the app.post for this test so it will create the
391 # notifications properly with the new PR
404 # notifications properly with the new PR
392 commits = [
405 commits = [
393 {'message': 'ancestor',
406 {'message': 'ancestor',
394 'added': [FileNode('file_A', content='content_of_ancestor')]},
407 'added': [FileNode('file_A', content='content_of_ancestor')]},
395 {'message': 'change',
408 {'message': 'change',
396 'added': [FileNode('file_a', content='content_of_change')]},
409 'added': [FileNode('file_a', content='content_of_change')]},
397 {'message': 'change-child'},
410 {'message': 'change-child'},
398 {'message': 'ancestor-child', 'parents': ['ancestor'],
411 {'message': 'ancestor-child', 'parents': ['ancestor'],
399 'added': [
412 'added': [
400 FileNode('file_B', content='content_of_ancestor_child')]},
413 FileNode('file_B', content='content_of_ancestor_child')]},
401 {'message': 'ancestor-child-2'},
414 {'message': 'ancestor-child-2'},
402 ]
415 ]
403 commit_ids = backend.create_master_repo(commits)
416 commit_ids = backend.create_master_repo(commits)
404 target = backend.create_repo(heads=['ancestor-child'])
417 target = backend.create_repo(heads=['ancestor-child'])
405 source = backend.create_repo(heads=['change'])
418 source = backend.create_repo(heads=['change'])
406
419
407 response = self.app.post(
420 response = self.app.post(
408 url(
421 url(
409 controller='pullrequests',
422 controller='pullrequests',
410 action='create',
423 action='create',
411 repo_name=source.repo_name
424 repo_name=source.repo_name
412 ),
425 ),
413 [
426 [
414 ('source_repo', source.repo_name),
427 ('source_repo', source.repo_name),
415 ('source_ref', 'branch:default:' + commit_ids['change']),
428 ('source_ref', 'branch:default:' + commit_ids['change']),
416 ('target_repo', target.repo_name),
429 ('target_repo', target.repo_name),
417 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
430 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
418 ('common_ancestor', commit_ids['ancestor']),
431 ('common_ancestor', commit_ids['ancestor']),
419 ('pullrequest_desc', 'Description'),
432 ('pullrequest_desc', 'Description'),
420 ('pullrequest_title', 'Title'),
433 ('pullrequest_title', 'Title'),
421 ('__start__', 'review_members:sequence'),
434 ('__start__', 'review_members:sequence'),
422 ('__start__', 'reviewer:mapping'),
435 ('__start__', 'reviewer:mapping'),
423 ('user_id', '2'),
436 ('user_id', '2'),
424 ('__start__', 'reasons:sequence'),
437 ('__start__', 'reasons:sequence'),
425 ('reason', 'Some reason'),
438 ('reason', 'Some reason'),
426 ('__end__', 'reasons:sequence'),
439 ('__end__', 'reasons:sequence'),
427 ('mandatory', 'False'),
440 ('mandatory', 'False'),
428 ('__end__', 'reviewer:mapping'),
441 ('__end__', 'reviewer:mapping'),
429 ('__end__', 'review_members:sequence'),
442 ('__end__', 'review_members:sequence'),
430 ('__start__', 'revisions:sequence'),
443 ('__start__', 'revisions:sequence'),
431 ('revisions', commit_ids['change']),
444 ('revisions', commit_ids['change']),
432 ('__end__', 'revisions:sequence'),
445 ('__end__', 'revisions:sequence'),
433 ('user', ''),
446 ('user', ''),
434 ('csrf_token', csrf_token),
447 ('csrf_token', csrf_token),
435 ],
448 ],
436 status=302)
449 status=302)
437
450
438 location = response.headers['Location']
451 location = response.headers['Location']
439
452
440 pull_request_id = location.rsplit('/', 1)[1]
453 pull_request_id = location.rsplit('/', 1)[1]
441 assert pull_request_id != 'new'
454 assert pull_request_id != 'new'
442 pull_request = PullRequest.get(int(pull_request_id))
455 pull_request = PullRequest.get(int(pull_request_id))
443
456
444 # Check that a notification was made
457 # Check that a notification was made
445 notifications = Notification.query()\
458 notifications = Notification.query()\
446 .filter(Notification.created_by == pull_request.author.user_id,
459 .filter(Notification.created_by == pull_request.author.user_id,
447 Notification.type_ == Notification.TYPE_PULL_REQUEST,
460 Notification.type_ == Notification.TYPE_PULL_REQUEST,
448 Notification.subject.contains(
461 Notification.subject.contains(
449 "wants you to review pull request #%s" % pull_request_id))
462 "wants you to review pull request #%s" % pull_request_id))
450 assert len(notifications.all()) == 1
463 assert len(notifications.all()) == 1
451
464
452 # Change reviewers and check that a notification was made
465 # Change reviewers and check that a notification was made
453 PullRequestModel().update_reviewers(
466 PullRequestModel().update_reviewers(
454 pull_request.pull_request_id, [(1, [], False)],
467 pull_request.pull_request_id, [(1, [], False)],
455 pull_request.author)
468 pull_request.author)
456 assert len(notifications.all()) == 2
469 assert len(notifications.all()) == 2
457
470
458 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
471 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
459 csrf_token):
472 csrf_token):
460 commits = [
473 commits = [
461 {'message': 'ancestor',
474 {'message': 'ancestor',
462 'added': [FileNode('file_A', content='content_of_ancestor')]},
475 'added': [FileNode('file_A', content='content_of_ancestor')]},
463 {'message': 'change',
476 {'message': 'change',
464 'added': [FileNode('file_a', content='content_of_change')]},
477 'added': [FileNode('file_a', content='content_of_change')]},
465 {'message': 'change-child'},
478 {'message': 'change-child'},
466 {'message': 'ancestor-child', 'parents': ['ancestor'],
479 {'message': 'ancestor-child', 'parents': ['ancestor'],
467 'added': [
480 'added': [
468 FileNode('file_B', content='content_of_ancestor_child')]},
481 FileNode('file_B', content='content_of_ancestor_child')]},
469 {'message': 'ancestor-child-2'},
482 {'message': 'ancestor-child-2'},
470 ]
483 ]
471 commit_ids = backend.create_master_repo(commits)
484 commit_ids = backend.create_master_repo(commits)
472 target = backend.create_repo(heads=['ancestor-child'])
485 target = backend.create_repo(heads=['ancestor-child'])
473 source = backend.create_repo(heads=['change'])
486 source = backend.create_repo(heads=['change'])
474
487
475 response = self.app.post(
488 response = self.app.post(
476 url(
489 url(
477 controller='pullrequests',
490 controller='pullrequests',
478 action='create',
491 action='create',
479 repo_name=source.repo_name
492 repo_name=source.repo_name
480 ),
493 ),
481 [
494 [
482 ('source_repo', source.repo_name),
495 ('source_repo', source.repo_name),
483 ('source_ref', 'branch:default:' + commit_ids['change']),
496 ('source_ref', 'branch:default:' + commit_ids['change']),
484 ('target_repo', target.repo_name),
497 ('target_repo', target.repo_name),
485 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
498 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
486 ('common_ancestor', commit_ids['ancestor']),
499 ('common_ancestor', commit_ids['ancestor']),
487 ('pullrequest_desc', 'Description'),
500 ('pullrequest_desc', 'Description'),
488 ('pullrequest_title', 'Title'),
501 ('pullrequest_title', 'Title'),
489 ('__start__', 'review_members:sequence'),
502 ('__start__', 'review_members:sequence'),
490 ('__start__', 'reviewer:mapping'),
503 ('__start__', 'reviewer:mapping'),
491 ('user_id', '1'),
504 ('user_id', '1'),
492 ('__start__', 'reasons:sequence'),
505 ('__start__', 'reasons:sequence'),
493 ('reason', 'Some reason'),
506 ('reason', 'Some reason'),
494 ('__end__', 'reasons:sequence'),
507 ('__end__', 'reasons:sequence'),
495 ('mandatory', 'False'),
508 ('mandatory', 'False'),
496 ('__end__', 'reviewer:mapping'),
509 ('__end__', 'reviewer:mapping'),
497 ('__end__', 'review_members:sequence'),
510 ('__end__', 'review_members:sequence'),
498 ('__start__', 'revisions:sequence'),
511 ('__start__', 'revisions:sequence'),
499 ('revisions', commit_ids['change']),
512 ('revisions', commit_ids['change']),
500 ('__end__', 'revisions:sequence'),
513 ('__end__', 'revisions:sequence'),
501 ('user', ''),
514 ('user', ''),
502 ('csrf_token', csrf_token),
515 ('csrf_token', csrf_token),
503 ],
516 ],
504 status=302)
517 status=302)
505
518
506 location = response.headers['Location']
519 location = response.headers['Location']
507
520
508 pull_request_id = location.rsplit('/', 1)[1]
521 pull_request_id = location.rsplit('/', 1)[1]
509 assert pull_request_id != 'new'
522 assert pull_request_id != 'new'
510 pull_request = PullRequest.get(int(pull_request_id))
523 pull_request = PullRequest.get(int(pull_request_id))
511
524
512 # target_ref has to point to the ancestor's commit_id in order to
525 # target_ref has to point to the ancestor's commit_id in order to
513 # show the correct diff
526 # show the correct diff
514 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
527 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
515 assert pull_request.target_ref == expected_target_ref
528 assert pull_request.target_ref == expected_target_ref
516
529
517 # Check generated diff contents
530 # Check generated diff contents
518 response = response.follow()
531 response = response.follow()
519 assert 'content_of_ancestor' not in response.body
532 assert 'content_of_ancestor' not in response.body
520 assert 'content_of_ancestor-child' not in response.body
533 assert 'content_of_ancestor-child' not in response.body
521 assert 'content_of_change' in response.body
534 assert 'content_of_change' in response.body
522
535
523 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
536 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
524 # Clear any previous calls to rcextensions
537 # Clear any previous calls to rcextensions
525 rhodecode.EXTENSIONS.calls.clear()
538 rhodecode.EXTENSIONS.calls.clear()
526
539
527 pull_request = pr_util.create_pull_request(
540 pull_request = pr_util.create_pull_request(
528 approved=True, mergeable=True)
541 approved=True, mergeable=True)
529 pull_request_id = pull_request.pull_request_id
542 pull_request_id = pull_request.pull_request_id
530 repo_name = pull_request.target_repo.scm_instance().name,
543 repo_name = pull_request.target_repo.scm_instance().name,
531
544
532 response = self.app.post(
545 response = self.app.post(
533 url(controller='pullrequests',
546 url(controller='pullrequests',
534 action='merge',
547 action='merge',
535 repo_name=str(repo_name[0]),
548 repo_name=str(repo_name[0]),
536 pull_request_id=str(pull_request_id)),
549 pull_request_id=str(pull_request_id)),
537 params={'csrf_token': csrf_token}).follow()
550 params={'csrf_token': csrf_token}).follow()
538
551
539 pull_request = PullRequest.get(pull_request_id)
552 pull_request = PullRequest.get(pull_request_id)
540
553
541 assert response.status_int == 200
554 assert response.status_int == 200
542 assert pull_request.is_closed()
555 assert pull_request.is_closed()
543 assert_pull_request_status(
556 assert_pull_request_status(
544 pull_request, ChangesetStatus.STATUS_APPROVED)
557 pull_request, ChangesetStatus.STATUS_APPROVED)
545
558
546 # Check the relevant log entries were added
559 # Check the relevant log entries were added
547 user_logs = UserLog.query().order_by('-user_log_id').limit(3)
560 user_logs = UserLog.query().order_by('-user_log_id').limit(3)
548 actions = [log.action for log in user_logs]
561 actions = [log.action for log in user_logs]
549 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
562 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
550 expected_actions = [
563 expected_actions = [
551 u'repo.pull_request.close',
564 u'repo.pull_request.close',
552 u'repo.pull_request.merge',
565 u'repo.pull_request.merge',
553 u'repo.pull_request.comment.create'
566 u'repo.pull_request.comment.create'
554 ]
567 ]
555 assert actions == expected_actions
568 assert actions == expected_actions
556
569
557 user_logs = UserLog.query().order_by('-user_log_id').limit(4)
570 user_logs = UserLog.query().order_by('-user_log_id').limit(4)
558 actions = [log for log in user_logs]
571 actions = [log for log in user_logs]
559 assert actions[-1].action == 'user.push'
572 assert actions[-1].action == 'user.push'
560 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
573 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
561
574
562 # Check post_push rcextension was really executed
575 # Check post_push rcextension was really executed
563 push_calls = rhodecode.EXTENSIONS.calls['post_push']
576 push_calls = rhodecode.EXTENSIONS.calls['post_push']
564 assert len(push_calls) == 1
577 assert len(push_calls) == 1
565 unused_last_call_args, last_call_kwargs = push_calls[0]
578 unused_last_call_args, last_call_kwargs = push_calls[0]
566 assert last_call_kwargs['action'] == 'push'
579 assert last_call_kwargs['action'] == 'push'
567 assert last_call_kwargs['pushed_revs'] == pr_commit_ids
580 assert last_call_kwargs['pushed_revs'] == pr_commit_ids
568
581
569 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
582 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
570 pull_request = pr_util.create_pull_request(mergeable=False)
583 pull_request = pr_util.create_pull_request(mergeable=False)
571 pull_request_id = pull_request.pull_request_id
584 pull_request_id = pull_request.pull_request_id
572 pull_request = PullRequest.get(pull_request_id)
585 pull_request = PullRequest.get(pull_request_id)
573
586
574 response = self.app.post(
587 response = self.app.post(
575 url(controller='pullrequests',
588 url(controller='pullrequests',
576 action='merge',
589 action='merge',
577 repo_name=pull_request.target_repo.scm_instance().name,
590 repo_name=pull_request.target_repo.scm_instance().name,
578 pull_request_id=str(pull_request.pull_request_id)),
591 pull_request_id=str(pull_request.pull_request_id)),
579 params={'csrf_token': csrf_token}).follow()
592 params={'csrf_token': csrf_token}).follow()
580
593
581 assert response.status_int == 200
594 assert response.status_int == 200
582 response.mustcontain(
595 response.mustcontain(
583 'Merge is not currently possible because of below failed checks.')
596 'Merge is not currently possible because of below failed checks.')
584 response.mustcontain('Server-side pull request merging is disabled.')
597 response.mustcontain('Server-side pull request merging is disabled.')
585
598
586 @pytest.mark.skip_backends('svn')
599 @pytest.mark.skip_backends('svn')
587 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
600 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
588 pull_request = pr_util.create_pull_request(mergeable=True)
601 pull_request = pr_util.create_pull_request(mergeable=True)
589 pull_request_id = pull_request.pull_request_id
602 pull_request_id = pull_request.pull_request_id
590 repo_name = pull_request.target_repo.scm_instance().name,
603 repo_name = pull_request.target_repo.scm_instance().name,
591
604
592 response = self.app.post(
605 response = self.app.post(
593 url(controller='pullrequests',
606 url(controller='pullrequests',
594 action='merge',
607 action='merge',
595 repo_name=str(repo_name[0]),
608 repo_name=str(repo_name[0]),
596 pull_request_id=str(pull_request_id)),
609 pull_request_id=str(pull_request_id)),
597 params={'csrf_token': csrf_token}).follow()
610 params={'csrf_token': csrf_token}).follow()
598
611
599 assert response.status_int == 200
612 assert response.status_int == 200
600
613
601 response.mustcontain(
614 response.mustcontain(
602 'Merge is not currently possible because of below failed checks.')
615 'Merge is not currently possible because of below failed checks.')
603 response.mustcontain('Pull request reviewer approval is pending.')
616 response.mustcontain('Pull request reviewer approval is pending.')
604
617
605 def test_update_source_revision(self, backend, csrf_token):
618 def test_update_source_revision(self, backend, csrf_token):
606 commits = [
619 commits = [
607 {'message': 'ancestor'},
620 {'message': 'ancestor'},
608 {'message': 'change'},
621 {'message': 'change'},
609 {'message': 'change-2'},
622 {'message': 'change-2'},
610 ]
623 ]
611 commit_ids = backend.create_master_repo(commits)
624 commit_ids = backend.create_master_repo(commits)
612 target = backend.create_repo(heads=['ancestor'])
625 target = backend.create_repo(heads=['ancestor'])
613 source = backend.create_repo(heads=['change'])
626 source = backend.create_repo(heads=['change'])
614
627
615 # create pr from a in source to A in target
628 # create pr from a in source to A in target
616 pull_request = PullRequest()
629 pull_request = PullRequest()
617 pull_request.source_repo = source
630 pull_request.source_repo = source
618 # TODO: johbo: Make sure that we write the source ref this way!
631 # TODO: johbo: Make sure that we write the source ref this way!
619 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
632 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
620 branch=backend.default_branch_name, commit_id=commit_ids['change'])
633 branch=backend.default_branch_name, commit_id=commit_ids['change'])
621 pull_request.target_repo = target
634 pull_request.target_repo = target
622
635
623 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
636 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
624 branch=backend.default_branch_name,
637 branch=backend.default_branch_name,
625 commit_id=commit_ids['ancestor'])
638 commit_id=commit_ids['ancestor'])
626 pull_request.revisions = [commit_ids['change']]
639 pull_request.revisions = [commit_ids['change']]
627 pull_request.title = u"Test"
640 pull_request.title = u"Test"
628 pull_request.description = u"Description"
641 pull_request.description = u"Description"
629 pull_request.author = UserModel().get_by_username(
642 pull_request.author = UserModel().get_by_username(
630 TEST_USER_ADMIN_LOGIN)
643 TEST_USER_ADMIN_LOGIN)
631 Session().add(pull_request)
644 Session().add(pull_request)
632 Session().commit()
645 Session().commit()
633 pull_request_id = pull_request.pull_request_id
646 pull_request_id = pull_request.pull_request_id
634
647
635 # source has ancestor - change - change-2
648 # source has ancestor - change - change-2
636 backend.pull_heads(source, heads=['change-2'])
649 backend.pull_heads(source, heads=['change-2'])
637
650
638 # update PR
651 # update PR
639 self.app.post(
652 self.app.post(
640 url(controller='pullrequests', action='update',
653 url(controller='pullrequests', action='update',
641 repo_name=target.repo_name,
654 repo_name=target.repo_name,
642 pull_request_id=str(pull_request_id)),
655 pull_request_id=str(pull_request_id)),
643 params={'update_commits': 'true', '_method': 'put',
656 params={'update_commits': 'true', '_method': 'put',
644 'csrf_token': csrf_token})
657 'csrf_token': csrf_token})
645
658
646 # check that we have now both revisions
659 # check that we have now both revisions
647 pull_request = PullRequest.get(pull_request_id)
660 pull_request = PullRequest.get(pull_request_id)
648 assert pull_request.revisions == [
661 assert pull_request.revisions == [
649 commit_ids['change-2'], commit_ids['change']]
662 commit_ids['change-2'], commit_ids['change']]
650
663
651 # TODO: johbo: this should be a test on its own
664 # TODO: johbo: this should be a test on its own
652 response = self.app.get(url(
665 response = self.app.get(url(
653 controller='pullrequests', action='index',
666 controller='pullrequests', action='index',
654 repo_name=target.repo_name))
667 repo_name=target.repo_name))
655 assert response.status_int == 200
668 assert response.status_int == 200
656 assert 'Pull request updated to' in response.body
669 assert 'Pull request updated to' in response.body
657 assert 'with 1 added, 0 removed commits.' in response.body
670 assert 'with 1 added, 0 removed commits.' in response.body
658
671
659 def test_update_target_revision(self, backend, csrf_token):
672 def test_update_target_revision(self, backend, csrf_token):
660 commits = [
673 commits = [
661 {'message': 'ancestor'},
674 {'message': 'ancestor'},
662 {'message': 'change'},
675 {'message': 'change'},
663 {'message': 'ancestor-new', 'parents': ['ancestor']},
676 {'message': 'ancestor-new', 'parents': ['ancestor']},
664 {'message': 'change-rebased'},
677 {'message': 'change-rebased'},
665 ]
678 ]
666 commit_ids = backend.create_master_repo(commits)
679 commit_ids = backend.create_master_repo(commits)
667 target = backend.create_repo(heads=['ancestor'])
680 target = backend.create_repo(heads=['ancestor'])
668 source = backend.create_repo(heads=['change'])
681 source = backend.create_repo(heads=['change'])
669
682
670 # create pr from a in source to A in target
683 # create pr from a in source to A in target
671 pull_request = PullRequest()
684 pull_request = PullRequest()
672 pull_request.source_repo = source
685 pull_request.source_repo = source
673 # TODO: johbo: Make sure that we write the source ref this way!
686 # TODO: johbo: Make sure that we write the source ref this way!
674 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
687 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
675 branch=backend.default_branch_name, commit_id=commit_ids['change'])
688 branch=backend.default_branch_name, commit_id=commit_ids['change'])
676 pull_request.target_repo = target
689 pull_request.target_repo = target
677 # TODO: johbo: Target ref should be branch based, since tip can jump
690 # TODO: johbo: Target ref should be branch based, since tip can jump
678 # from branch to branch
691 # from branch to branch
679 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
692 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
680 branch=backend.default_branch_name,
693 branch=backend.default_branch_name,
681 commit_id=commit_ids['ancestor'])
694 commit_id=commit_ids['ancestor'])
682 pull_request.revisions = [commit_ids['change']]
695 pull_request.revisions = [commit_ids['change']]
683 pull_request.title = u"Test"
696 pull_request.title = u"Test"
684 pull_request.description = u"Description"
697 pull_request.description = u"Description"
685 pull_request.author = UserModel().get_by_username(
698 pull_request.author = UserModel().get_by_username(
686 TEST_USER_ADMIN_LOGIN)
699 TEST_USER_ADMIN_LOGIN)
687 Session().add(pull_request)
700 Session().add(pull_request)
688 Session().commit()
701 Session().commit()
689 pull_request_id = pull_request.pull_request_id
702 pull_request_id = pull_request.pull_request_id
690
703
691 # target has ancestor - ancestor-new
704 # target has ancestor - ancestor-new
692 # source has ancestor - ancestor-new - change-rebased
705 # source has ancestor - ancestor-new - change-rebased
693 backend.pull_heads(target, heads=['ancestor-new'])
706 backend.pull_heads(target, heads=['ancestor-new'])
694 backend.pull_heads(source, heads=['change-rebased'])
707 backend.pull_heads(source, heads=['change-rebased'])
695
708
696 # update PR
709 # update PR
697 self.app.post(
710 self.app.post(
698 url(controller='pullrequests', action='update',
711 url(controller='pullrequests', action='update',
699 repo_name=target.repo_name,
712 repo_name=target.repo_name,
700 pull_request_id=str(pull_request_id)),
713 pull_request_id=str(pull_request_id)),
701 params={'update_commits': 'true', '_method': 'put',
714 params={'update_commits': 'true', '_method': 'put',
702 'csrf_token': csrf_token},
715 'csrf_token': csrf_token},
703 status=200)
716 status=200)
704
717
705 # check that we have now both revisions
718 # check that we have now both revisions
706 pull_request = PullRequest.get(pull_request_id)
719 pull_request = PullRequest.get(pull_request_id)
707 assert pull_request.revisions == [commit_ids['change-rebased']]
720 assert pull_request.revisions == [commit_ids['change-rebased']]
708 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
721 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
709 branch=backend.default_branch_name,
722 branch=backend.default_branch_name,
710 commit_id=commit_ids['ancestor-new'])
723 commit_id=commit_ids['ancestor-new'])
711
724
712 # TODO: johbo: This should be a test on its own
725 # TODO: johbo: This should be a test on its own
713 response = self.app.get(url(
726 response = self.app.get(url(
714 controller='pullrequests', action='index',
727 controller='pullrequests', action='index',
715 repo_name=target.repo_name))
728 repo_name=target.repo_name))
716 assert response.status_int == 200
729 assert response.status_int == 200
717 assert 'Pull request updated to' in response.body
730 assert 'Pull request updated to' in response.body
718 assert 'with 1 added, 1 removed commits.' in response.body
731 assert 'with 1 added, 1 removed commits.' in response.body
719
732
720 def test_update_of_ancestor_reference(self, backend, csrf_token):
733 def test_update_of_ancestor_reference(self, backend, csrf_token):
721 commits = [
734 commits = [
722 {'message': 'ancestor'},
735 {'message': 'ancestor'},
723 {'message': 'change'},
736 {'message': 'change'},
724 {'message': 'change-2'},
737 {'message': 'change-2'},
725 {'message': 'ancestor-new', 'parents': ['ancestor']},
738 {'message': 'ancestor-new', 'parents': ['ancestor']},
726 {'message': 'change-rebased'},
739 {'message': 'change-rebased'},
727 ]
740 ]
728 commit_ids = backend.create_master_repo(commits)
741 commit_ids = backend.create_master_repo(commits)
729 target = backend.create_repo(heads=['ancestor'])
742 target = backend.create_repo(heads=['ancestor'])
730 source = backend.create_repo(heads=['change'])
743 source = backend.create_repo(heads=['change'])
731
744
732 # create pr from a in source to A in target
745 # create pr from a in source to A in target
733 pull_request = PullRequest()
746 pull_request = PullRequest()
734 pull_request.source_repo = source
747 pull_request.source_repo = source
735 # TODO: johbo: Make sure that we write the source ref this way!
748 # TODO: johbo: Make sure that we write the source ref this way!
736 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
749 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
737 branch=backend.default_branch_name,
750 branch=backend.default_branch_name,
738 commit_id=commit_ids['change'])
751 commit_id=commit_ids['change'])
739 pull_request.target_repo = target
752 pull_request.target_repo = target
740 # TODO: johbo: Target ref should be branch based, since tip can jump
753 # TODO: johbo: Target ref should be branch based, since tip can jump
741 # from branch to branch
754 # from branch to branch
742 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
755 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
743 branch=backend.default_branch_name,
756 branch=backend.default_branch_name,
744 commit_id=commit_ids['ancestor'])
757 commit_id=commit_ids['ancestor'])
745 pull_request.revisions = [commit_ids['change']]
758 pull_request.revisions = [commit_ids['change']]
746 pull_request.title = u"Test"
759 pull_request.title = u"Test"
747 pull_request.description = u"Description"
760 pull_request.description = u"Description"
748 pull_request.author = UserModel().get_by_username(
761 pull_request.author = UserModel().get_by_username(
749 TEST_USER_ADMIN_LOGIN)
762 TEST_USER_ADMIN_LOGIN)
750 Session().add(pull_request)
763 Session().add(pull_request)
751 Session().commit()
764 Session().commit()
752 pull_request_id = pull_request.pull_request_id
765 pull_request_id = pull_request.pull_request_id
753
766
754 # target has ancestor - ancestor-new
767 # target has ancestor - ancestor-new
755 # source has ancestor - ancestor-new - change-rebased
768 # source has ancestor - ancestor-new - change-rebased
756 backend.pull_heads(target, heads=['ancestor-new'])
769 backend.pull_heads(target, heads=['ancestor-new'])
757 backend.pull_heads(source, heads=['change-rebased'])
770 backend.pull_heads(source, heads=['change-rebased'])
758
771
759 # update PR
772 # update PR
760 self.app.post(
773 self.app.post(
761 url(controller='pullrequests', action='update',
774 url(controller='pullrequests', action='update',
762 repo_name=target.repo_name,
775 repo_name=target.repo_name,
763 pull_request_id=str(pull_request_id)),
776 pull_request_id=str(pull_request_id)),
764 params={'update_commits': 'true', '_method': 'put',
777 params={'update_commits': 'true', '_method': 'put',
765 'csrf_token': csrf_token},
778 'csrf_token': csrf_token},
766 status=200)
779 status=200)
767
780
768 # Expect the target reference to be updated correctly
781 # Expect the target reference to be updated correctly
769 pull_request = PullRequest.get(pull_request_id)
782 pull_request = PullRequest.get(pull_request_id)
770 assert pull_request.revisions == [commit_ids['change-rebased']]
783 assert pull_request.revisions == [commit_ids['change-rebased']]
771 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
784 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
772 branch=backend.default_branch_name,
785 branch=backend.default_branch_name,
773 commit_id=commit_ids['ancestor-new'])
786 commit_id=commit_ids['ancestor-new'])
774 assert pull_request.target_ref == expected_target_ref
787 assert pull_request.target_ref == expected_target_ref
775
788
776 def test_remove_pull_request_branch(self, backend_git, csrf_token):
789 def test_remove_pull_request_branch(self, backend_git, csrf_token):
777 branch_name = 'development'
790 branch_name = 'development'
778 commits = [
791 commits = [
779 {'message': 'initial-commit'},
792 {'message': 'initial-commit'},
780 {'message': 'old-feature'},
793 {'message': 'old-feature'},
781 {'message': 'new-feature', 'branch': branch_name},
794 {'message': 'new-feature', 'branch': branch_name},
782 ]
795 ]
783 repo = backend_git.create_repo(commits)
796 repo = backend_git.create_repo(commits)
784 commit_ids = backend_git.commit_ids
797 commit_ids = backend_git.commit_ids
785
798
786 pull_request = PullRequest()
799 pull_request = PullRequest()
787 pull_request.source_repo = repo
800 pull_request.source_repo = repo
788 pull_request.target_repo = repo
801 pull_request.target_repo = repo
789 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
802 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
790 branch=branch_name, commit_id=commit_ids['new-feature'])
803 branch=branch_name, commit_id=commit_ids['new-feature'])
791 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
804 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
792 branch=backend_git.default_branch_name,
805 branch=backend_git.default_branch_name,
793 commit_id=commit_ids['old-feature'])
806 commit_id=commit_ids['old-feature'])
794 pull_request.revisions = [commit_ids['new-feature']]
807 pull_request.revisions = [commit_ids['new-feature']]
795 pull_request.title = u"Test"
808 pull_request.title = u"Test"
796 pull_request.description = u"Description"
809 pull_request.description = u"Description"
797 pull_request.author = UserModel().get_by_username(
810 pull_request.author = UserModel().get_by_username(
798 TEST_USER_ADMIN_LOGIN)
811 TEST_USER_ADMIN_LOGIN)
799 Session().add(pull_request)
812 Session().add(pull_request)
800 Session().commit()
813 Session().commit()
801
814
802 vcs = repo.scm_instance()
815 vcs = repo.scm_instance()
803 vcs.remove_ref('refs/heads/{}'.format(branch_name))
816 vcs.remove_ref('refs/heads/{}'.format(branch_name))
804
817
805 response = self.app.get(url(
818 response = self.app.get(url(
806 controller='pullrequests', action='show',
819 controller='pullrequests', action='show',
807 repo_name=repo.repo_name,
820 repo_name=repo.repo_name,
808 pull_request_id=str(pull_request.pull_request_id)))
821 pull_request_id=str(pull_request.pull_request_id)))
809
822
810 assert response.status_int == 200
823 assert response.status_int == 200
811 assert_response = AssertResponse(response)
824 assert_response = AssertResponse(response)
812 assert_response.element_contains(
825 assert_response.element_contains(
813 '#changeset_compare_view_content .alert strong',
826 '#changeset_compare_view_content .alert strong',
814 'Missing commits')
827 'Missing commits')
815 assert_response.element_contains(
828 assert_response.element_contains(
816 '#changeset_compare_view_content .alert',
829 '#changeset_compare_view_content .alert',
817 'This pull request cannot be displayed, because one or more'
830 'This pull request cannot be displayed, because one or more'
818 ' commits no longer exist in the source repository.')
831 ' commits no longer exist in the source repository.')
819
832
820 def test_strip_commits_from_pull_request(
833 def test_strip_commits_from_pull_request(
821 self, backend, pr_util, csrf_token):
834 self, backend, pr_util, csrf_token):
822 commits = [
835 commits = [
823 {'message': 'initial-commit'},
836 {'message': 'initial-commit'},
824 {'message': 'old-feature'},
837 {'message': 'old-feature'},
825 {'message': 'new-feature', 'parents': ['initial-commit']},
838 {'message': 'new-feature', 'parents': ['initial-commit']},
826 ]
839 ]
827 pull_request = pr_util.create_pull_request(
840 pull_request = pr_util.create_pull_request(
828 commits, target_head='initial-commit', source_head='new-feature',
841 commits, target_head='initial-commit', source_head='new-feature',
829 revisions=['new-feature'])
842 revisions=['new-feature'])
830
843
831 vcs = pr_util.source_repository.scm_instance()
844 vcs = pr_util.source_repository.scm_instance()
832 if backend.alias == 'git':
845 if backend.alias == 'git':
833 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
846 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
834 else:
847 else:
835 vcs.strip(pr_util.commit_ids['new-feature'])
848 vcs.strip(pr_util.commit_ids['new-feature'])
836
849
837 response = self.app.get(url(
850 response = self.app.get(url(
838 controller='pullrequests', action='show',
851 controller='pullrequests', action='show',
839 repo_name=pr_util.target_repository.repo_name,
852 repo_name=pr_util.target_repository.repo_name,
840 pull_request_id=str(pull_request.pull_request_id)))
853 pull_request_id=str(pull_request.pull_request_id)))
841
854
842 assert response.status_int == 200
855 assert response.status_int == 200
843 assert_response = AssertResponse(response)
856 assert_response = AssertResponse(response)
844 assert_response.element_contains(
857 assert_response.element_contains(
845 '#changeset_compare_view_content .alert strong',
858 '#changeset_compare_view_content .alert strong',
846 'Missing commits')
859 'Missing commits')
847 assert_response.element_contains(
860 assert_response.element_contains(
848 '#changeset_compare_view_content .alert',
861 '#changeset_compare_view_content .alert',
849 'This pull request cannot be displayed, because one or more'
862 'This pull request cannot be displayed, because one or more'
850 ' commits no longer exist in the source repository.')
863 ' commits no longer exist in the source repository.')
851 assert_response.element_contains(
864 assert_response.element_contains(
852 '#update_commits',
865 '#update_commits',
853 'Update commits')
866 'Update commits')
854
867
855 def test_strip_commits_and_update(
868 def test_strip_commits_and_update(
856 self, backend, pr_util, csrf_token):
869 self, backend, pr_util, csrf_token):
857 commits = [
870 commits = [
858 {'message': 'initial-commit'},
871 {'message': 'initial-commit'},
859 {'message': 'old-feature'},
872 {'message': 'old-feature'},
860 {'message': 'new-feature', 'parents': ['old-feature']},
873 {'message': 'new-feature', 'parents': ['old-feature']},
861 ]
874 ]
862 pull_request = pr_util.create_pull_request(
875 pull_request = pr_util.create_pull_request(
863 commits, target_head='old-feature', source_head='new-feature',
876 commits, target_head='old-feature', source_head='new-feature',
864 revisions=['new-feature'], mergeable=True)
877 revisions=['new-feature'], mergeable=True)
865
878
866 vcs = pr_util.source_repository.scm_instance()
879 vcs = pr_util.source_repository.scm_instance()
867 if backend.alias == 'git':
880 if backend.alias == 'git':
868 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
881 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
869 else:
882 else:
870 vcs.strip(pr_util.commit_ids['new-feature'])
883 vcs.strip(pr_util.commit_ids['new-feature'])
871
884
872 response = self.app.post(
885 response = self.app.post(
873 url(controller='pullrequests', action='update',
886 url(controller='pullrequests', action='update',
874 repo_name=pull_request.target_repo.repo_name,
887 repo_name=pull_request.target_repo.repo_name,
875 pull_request_id=str(pull_request.pull_request_id)),
888 pull_request_id=str(pull_request.pull_request_id)),
876 params={'update_commits': 'true', '_method': 'put',
889 params={'update_commits': 'true', '_method': 'put',
877 'csrf_token': csrf_token})
890 'csrf_token': csrf_token})
878
891
879 assert response.status_int == 200
892 assert response.status_int == 200
880 assert response.body == 'true'
893 assert response.body == 'true'
881
894
882 # Make sure that after update, it won't raise 500 errors
895 # Make sure that after update, it won't raise 500 errors
883 response = self.app.get(url(
896 response = self.app.get(url(
884 controller='pullrequests', action='show',
897 controller='pullrequests', action='show',
885 repo_name=pr_util.target_repository.repo_name,
898 repo_name=pr_util.target_repository.repo_name,
886 pull_request_id=str(pull_request.pull_request_id)))
899 pull_request_id=str(pull_request.pull_request_id)))
887
900
888 assert response.status_int == 200
901 assert response.status_int == 200
889 assert_response = AssertResponse(response)
902 assert_response = AssertResponse(response)
890 assert_response.element_contains(
903 assert_response.element_contains(
891 '#changeset_compare_view_content .alert strong',
904 '#changeset_compare_view_content .alert strong',
892 'Missing commits')
905 'Missing commits')
893
906
894 def test_branch_is_a_link(self, pr_util):
907 def test_branch_is_a_link(self, pr_util):
895 pull_request = pr_util.create_pull_request()
908 pull_request = pr_util.create_pull_request()
896 pull_request.source_ref = 'branch:origin:1234567890abcdef'
909 pull_request.source_ref = 'branch:origin:1234567890abcdef'
897 pull_request.target_ref = 'branch:target:abcdef1234567890'
910 pull_request.target_ref = 'branch:target:abcdef1234567890'
898 Session().add(pull_request)
911 Session().add(pull_request)
899 Session().commit()
912 Session().commit()
900
913
901 response = self.app.get(url(
914 response = self.app.get(url(
902 controller='pullrequests', action='show',
915 controller='pullrequests', action='show',
903 repo_name=pull_request.target_repo.scm_instance().name,
916 repo_name=pull_request.target_repo.scm_instance().name,
904 pull_request_id=str(pull_request.pull_request_id)))
917 pull_request_id=str(pull_request.pull_request_id)))
905 assert response.status_int == 200
918 assert response.status_int == 200
906 assert_response = AssertResponse(response)
919 assert_response = AssertResponse(response)
907
920
908 origin = assert_response.get_element('.pr-origininfo .tag')
921 origin = assert_response.get_element('.pr-origininfo .tag')
909 origin_children = origin.getchildren()
922 origin_children = origin.getchildren()
910 assert len(origin_children) == 1
923 assert len(origin_children) == 1
911 target = assert_response.get_element('.pr-targetinfo .tag')
924 target = assert_response.get_element('.pr-targetinfo .tag')
912 target_children = target.getchildren()
925 target_children = target.getchildren()
913 assert len(target_children) == 1
926 assert len(target_children) == 1
914
927
915 expected_origin_link = url(
928 expected_origin_link = route_path(
916 'changelog_home',
929 'repo_changelog',
917 repo_name=pull_request.source_repo.scm_instance().name,
930 repo_name=pull_request.source_repo.scm_instance().name,
918 branch='origin')
931 params=dict(branch='origin'))
919 expected_target_link = url(
932 expected_target_link = route_path(
920 'changelog_home',
933 'repo_changelog',
921 repo_name=pull_request.target_repo.scm_instance().name,
934 repo_name=pull_request.target_repo.scm_instance().name,
922 branch='target')
935 params=dict(branch='target'))
923 assert origin_children[0].attrib['href'] == expected_origin_link
936 assert origin_children[0].attrib['href'] == expected_origin_link
924 assert origin_children[0].text == 'branch: origin'
937 assert origin_children[0].text == 'branch: origin'
925 assert target_children[0].attrib['href'] == expected_target_link
938 assert target_children[0].attrib['href'] == expected_target_link
926 assert target_children[0].text == 'branch: target'
939 assert target_children[0].text == 'branch: target'
927
940
928 def test_bookmark_is_not_a_link(self, pr_util):
941 def test_bookmark_is_not_a_link(self, pr_util):
929 pull_request = pr_util.create_pull_request()
942 pull_request = pr_util.create_pull_request()
930 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
943 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
931 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
944 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
932 Session().add(pull_request)
945 Session().add(pull_request)
933 Session().commit()
946 Session().commit()
934
947
935 response = self.app.get(url(
948 response = self.app.get(url(
936 controller='pullrequests', action='show',
949 controller='pullrequests', action='show',
937 repo_name=pull_request.target_repo.scm_instance().name,
950 repo_name=pull_request.target_repo.scm_instance().name,
938 pull_request_id=str(pull_request.pull_request_id)))
951 pull_request_id=str(pull_request.pull_request_id)))
939 assert response.status_int == 200
952 assert response.status_int == 200
940 assert_response = AssertResponse(response)
953 assert_response = AssertResponse(response)
941
954
942 origin = assert_response.get_element('.pr-origininfo .tag')
955 origin = assert_response.get_element('.pr-origininfo .tag')
943 assert origin.text.strip() == 'bookmark: origin'
956 assert origin.text.strip() == 'bookmark: origin'
944 assert origin.getchildren() == []
957 assert origin.getchildren() == []
945
958
946 target = assert_response.get_element('.pr-targetinfo .tag')
959 target = assert_response.get_element('.pr-targetinfo .tag')
947 assert target.text.strip() == 'bookmark: target'
960 assert target.text.strip() == 'bookmark: target'
948 assert target.getchildren() == []
961 assert target.getchildren() == []
949
962
950 def test_tag_is_not_a_link(self, pr_util):
963 def test_tag_is_not_a_link(self, pr_util):
951 pull_request = pr_util.create_pull_request()
964 pull_request = pr_util.create_pull_request()
952 pull_request.source_ref = 'tag:origin:1234567890abcdef'
965 pull_request.source_ref = 'tag:origin:1234567890abcdef'
953 pull_request.target_ref = 'tag:target:abcdef1234567890'
966 pull_request.target_ref = 'tag:target:abcdef1234567890'
954 Session().add(pull_request)
967 Session().add(pull_request)
955 Session().commit()
968 Session().commit()
956
969
957 response = self.app.get(url(
970 response = self.app.get(url(
958 controller='pullrequests', action='show',
971 controller='pullrequests', action='show',
959 repo_name=pull_request.target_repo.scm_instance().name,
972 repo_name=pull_request.target_repo.scm_instance().name,
960 pull_request_id=str(pull_request.pull_request_id)))
973 pull_request_id=str(pull_request.pull_request_id)))
961 assert response.status_int == 200
974 assert response.status_int == 200
962 assert_response = AssertResponse(response)
975 assert_response = AssertResponse(response)
963
976
964 origin = assert_response.get_element('.pr-origininfo .tag')
977 origin = assert_response.get_element('.pr-origininfo .tag')
965 assert origin.text.strip() == 'tag: origin'
978 assert origin.text.strip() == 'tag: origin'
966 assert origin.getchildren() == []
979 assert origin.getchildren() == []
967
980
968 target = assert_response.get_element('.pr-targetinfo .tag')
981 target = assert_response.get_element('.pr-targetinfo .tag')
969 assert target.text.strip() == 'tag: target'
982 assert target.text.strip() == 'tag: target'
970 assert target.getchildren() == []
983 assert target.getchildren() == []
971
984
972 @pytest.mark.parametrize('mergeable', [True, False])
985 @pytest.mark.parametrize('mergeable', [True, False])
973 def test_shadow_repository_link(
986 def test_shadow_repository_link(
974 self, mergeable, pr_util, http_host_only_stub):
987 self, mergeable, pr_util, http_host_only_stub):
975 """
988 """
976 Check that the pull request summary page displays a link to the shadow
989 Check that the pull request summary page displays a link to the shadow
977 repository if the pull request is mergeable. If it is not mergeable
990 repository if the pull request is mergeable. If it is not mergeable
978 the link should not be displayed.
991 the link should not be displayed.
979 """
992 """
980 pull_request = pr_util.create_pull_request(
993 pull_request = pr_util.create_pull_request(
981 mergeable=mergeable, enable_notifications=False)
994 mergeable=mergeable, enable_notifications=False)
982 target_repo = pull_request.target_repo.scm_instance()
995 target_repo = pull_request.target_repo.scm_instance()
983 pr_id = pull_request.pull_request_id
996 pr_id = pull_request.pull_request_id
984 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
997 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
985 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
998 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
986
999
987 response = self.app.get(url(
1000 response = self.app.get(url(
988 controller='pullrequests', action='show',
1001 controller='pullrequests', action='show',
989 repo_name=target_repo.name,
1002 repo_name=target_repo.name,
990 pull_request_id=str(pr_id)))
1003 pull_request_id=str(pr_id)))
991
1004
992 assertr = AssertResponse(response)
1005 assertr = AssertResponse(response)
993 if mergeable:
1006 if mergeable:
994 assertr.element_value_contains(
1007 assertr.element_value_contains(
995 'div.pr-mergeinfo input', shadow_url)
1008 'div.pr-mergeinfo input', shadow_url)
996 assertr.element_value_contains(
1009 assertr.element_value_contains(
997 'div.pr-mergeinfo input', 'pr-merge')
1010 'div.pr-mergeinfo input', 'pr-merge')
998 else:
1011 else:
999 assertr.no_element_exists('div.pr-mergeinfo')
1012 assertr.no_element_exists('div.pr-mergeinfo')
1000
1013
1001
1014
1002 @pytest.mark.usefixtures('app')
1015 @pytest.mark.usefixtures('app')
1003 @pytest.mark.backends("git", "hg")
1016 @pytest.mark.backends("git", "hg")
1004 class TestPullrequestsControllerDelete(object):
1017 class TestPullrequestsControllerDelete(object):
1005 def test_pull_request_delete_button_permissions_admin(
1018 def test_pull_request_delete_button_permissions_admin(
1006 self, autologin_user, user_admin, pr_util):
1019 self, autologin_user, user_admin, pr_util):
1007 pull_request = pr_util.create_pull_request(
1020 pull_request = pr_util.create_pull_request(
1008 author=user_admin.username, enable_notifications=False)
1021 author=user_admin.username, enable_notifications=False)
1009
1022
1010 response = self.app.get(url(
1023 response = self.app.get(url(
1011 controller='pullrequests', action='show',
1024 controller='pullrequests', action='show',
1012 repo_name=pull_request.target_repo.scm_instance().name,
1025 repo_name=pull_request.target_repo.scm_instance().name,
1013 pull_request_id=str(pull_request.pull_request_id)))
1026 pull_request_id=str(pull_request.pull_request_id)))
1014
1027
1015 response.mustcontain('id="delete_pullrequest"')
1028 response.mustcontain('id="delete_pullrequest"')
1016 response.mustcontain('Confirm to delete this pull request')
1029 response.mustcontain('Confirm to delete this pull request')
1017
1030
1018 def test_pull_request_delete_button_permissions_owner(
1031 def test_pull_request_delete_button_permissions_owner(
1019 self, autologin_regular_user, user_regular, pr_util):
1032 self, autologin_regular_user, user_regular, pr_util):
1020 pull_request = pr_util.create_pull_request(
1033 pull_request = pr_util.create_pull_request(
1021 author=user_regular.username, enable_notifications=False)
1034 author=user_regular.username, enable_notifications=False)
1022
1035
1023 response = self.app.get(url(
1036 response = self.app.get(url(
1024 controller='pullrequests', action='show',
1037 controller='pullrequests', action='show',
1025 repo_name=pull_request.target_repo.scm_instance().name,
1038 repo_name=pull_request.target_repo.scm_instance().name,
1026 pull_request_id=str(pull_request.pull_request_id)))
1039 pull_request_id=str(pull_request.pull_request_id)))
1027
1040
1028 response.mustcontain('id="delete_pullrequest"')
1041 response.mustcontain('id="delete_pullrequest"')
1029 response.mustcontain('Confirm to delete this pull request')
1042 response.mustcontain('Confirm to delete this pull request')
1030
1043
1031 def test_pull_request_delete_button_permissions_forbidden(
1044 def test_pull_request_delete_button_permissions_forbidden(
1032 self, autologin_regular_user, user_regular, user_admin, pr_util):
1045 self, autologin_regular_user, user_regular, user_admin, pr_util):
1033 pull_request = pr_util.create_pull_request(
1046 pull_request = pr_util.create_pull_request(
1034 author=user_admin.username, enable_notifications=False)
1047 author=user_admin.username, enable_notifications=False)
1035
1048
1036 response = self.app.get(url(
1049 response = self.app.get(url(
1037 controller='pullrequests', action='show',
1050 controller='pullrequests', action='show',
1038 repo_name=pull_request.target_repo.scm_instance().name,
1051 repo_name=pull_request.target_repo.scm_instance().name,
1039 pull_request_id=str(pull_request.pull_request_id)))
1052 pull_request_id=str(pull_request.pull_request_id)))
1040 response.mustcontain(no=['id="delete_pullrequest"'])
1053 response.mustcontain(no=['id="delete_pullrequest"'])
1041 response.mustcontain(no=['Confirm to delete this pull request'])
1054 response.mustcontain(no=['Confirm to delete this pull request'])
1042
1055
1043 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1056 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1044 self, autologin_regular_user, user_regular, user_admin, pr_util,
1057 self, autologin_regular_user, user_regular, user_admin, pr_util,
1045 user_util):
1058 user_util):
1046
1059
1047 pull_request = pr_util.create_pull_request(
1060 pull_request = pr_util.create_pull_request(
1048 author=user_admin.username, enable_notifications=False)
1061 author=user_admin.username, enable_notifications=False)
1049
1062
1050 user_util.grant_user_permission_to_repo(
1063 user_util.grant_user_permission_to_repo(
1051 pull_request.target_repo, user_regular,
1064 pull_request.target_repo, user_regular,
1052 'repository.write')
1065 'repository.write')
1053
1066
1054 response = self.app.get(url(
1067 response = self.app.get(url(
1055 controller='pullrequests', action='show',
1068 controller='pullrequests', action='show',
1056 repo_name=pull_request.target_repo.scm_instance().name,
1069 repo_name=pull_request.target_repo.scm_instance().name,
1057 pull_request_id=str(pull_request.pull_request_id)))
1070 pull_request_id=str(pull_request.pull_request_id)))
1058
1071
1059 response.mustcontain('id="open_edit_pullrequest"')
1072 response.mustcontain('id="open_edit_pullrequest"')
1060 response.mustcontain('id="delete_pullrequest"')
1073 response.mustcontain('id="delete_pullrequest"')
1061 response.mustcontain(no=['Confirm to delete this pull request'])
1074 response.mustcontain(no=['Confirm to delete this pull request'])
1062
1075
1063 def test_delete_comment_returns_404_if_comment_does_not_exist(
1076 def test_delete_comment_returns_404_if_comment_does_not_exist(
1064 self, autologin_user, pr_util, user_admin):
1077 self, autologin_user, pr_util, user_admin):
1065
1078
1066 pull_request = pr_util.create_pull_request(
1079 pull_request = pr_util.create_pull_request(
1067 author=user_admin.username, enable_notifications=False)
1080 author=user_admin.username, enable_notifications=False)
1068
1081
1069 self.app.get(url(
1082 self.app.get(url(
1070 controller='pullrequests', action='delete_comment',
1083 controller='pullrequests', action='delete_comment',
1071 repo_name=pull_request.target_repo.scm_instance().name,
1084 repo_name=pull_request.target_repo.scm_instance().name,
1072 comment_id=1024404), status=404)
1085 comment_id=1024404), status=404)
1073
1086
1074
1087
1075 def assert_pull_request_status(pull_request, expected_status):
1088 def assert_pull_request_status(pull_request, expected_status):
1076 status = ChangesetStatusModel().calculated_review_status(
1089 status = ChangesetStatusModel().calculated_review_status(
1077 pull_request=pull_request)
1090 pull_request=pull_request)
1078 assert status == expected_status
1091 assert status == expected_status
1079
1092
1080
1093
1081 @pytest.mark.parametrize('action', ['index', 'create'])
1094 @pytest.mark.parametrize('action', ['index', 'create'])
1082 @pytest.mark.usefixtures("autologin_user")
1095 @pytest.mark.usefixtures("autologin_user")
1083 def test_redirects_to_repo_summary_for_svn_repositories(backend_svn, app, action):
1096 def test_redirects_to_repo_summary_for_svn_repositories(backend_svn, app, action):
1084 response = app.get(url(
1097 response = app.get(url(
1085 controller='pullrequests', action=action,
1098 controller='pullrequests', action=action,
1086 repo_name=backend_svn.repo_name))
1099 repo_name=backend_svn.repo_name))
1087 assert response.status_int == 302
1100 assert response.status_int == 302
1088
1101
1089 # Not allowed, redirect to the summary
1102 # Not allowed, redirect to the summary
1090 redirected = response.follow()
1103 redirected = response.follow()
1091 summary_url = h.route_path('repo_summary', repo_name=backend_svn.repo_name)
1104 summary_url = h.route_path('repo_summary', repo_name=backend_svn.repo_name)
1092
1105
1093 # URL adds leading slash and path doesn't have it
1106 # URL adds leading slash and path doesn't have it
1094 assert redirected.request.path == summary_url
1107 assert redirected.request.path == summary_url
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now