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 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | from rhodecode.apps._base import add_route_with_slash |
|
21 | 21 | |
|
22 | 22 | |
|
23 | 23 | def includeme(config): |
|
24 | 24 | |
|
25 | 25 | # Summary |
|
26 | 26 | # NOTE(marcink): one additional route is defined in very bottom, catch |
|
27 | 27 | # all pattern |
|
28 | 28 | config.add_route( |
|
29 | 29 | name='repo_summary_explicit', |
|
30 | 30 | pattern='/{repo_name:.*?[^/]}/summary', repo_route=True) |
|
31 | 31 | config.add_route( |
|
32 | 32 | name='repo_summary_commits', |
|
33 | 33 | pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True) |
|
34 | 34 | |
|
35 | 35 | # repo commits |
|
36 | 36 | config.add_route( |
|
37 | 37 | name='repo_commit', |
|
38 | 38 | pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True) |
|
39 | 39 | |
|
40 | 40 | # repo files |
|
41 | 41 | config.add_route( |
|
42 | 42 | name='repo_archivefile', |
|
43 | 43 | pattern='/{repo_name:.*?[^/]}/archive/{fname}', repo_route=True) |
|
44 | 44 | |
|
45 | 45 | config.add_route( |
|
46 | 46 | name='repo_files_diff', |
|
47 | 47 | pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True) |
|
48 | 48 | config.add_route( # legacy route to make old links work |
|
49 | 49 | name='repo_files_diff_2way_redirect', |
|
50 | 50 | pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True) |
|
51 | 51 | |
|
52 | 52 | config.add_route( |
|
53 | 53 | name='repo_files', |
|
54 | 54 | pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True) |
|
55 | 55 | config.add_route( |
|
56 | 56 | name='repo_files:default_path', |
|
57 | 57 | pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True) |
|
58 | 58 | config.add_route( |
|
59 | 59 | name='repo_files:default_commit', |
|
60 | 60 | pattern='/{repo_name:.*?[^/]}/files', repo_route=True) |
|
61 | 61 | |
|
62 | 62 | config.add_route( |
|
63 | 63 | name='repo_files:rendered', |
|
64 | 64 | pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True) |
|
65 | 65 | |
|
66 | 66 | config.add_route( |
|
67 | 67 | name='repo_files:annotated', |
|
68 | 68 | pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True) |
|
69 | 69 | config.add_route( |
|
70 | 70 | name='repo_files:annotated_previous', |
|
71 | 71 | pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True) |
|
72 | 72 | |
|
73 | 73 | config.add_route( |
|
74 | 74 | name='repo_nodetree_full', |
|
75 | 75 | pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True) |
|
76 | 76 | config.add_route( |
|
77 | 77 | name='repo_nodetree_full:default_path', |
|
78 | 78 | pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True) |
|
79 | 79 | |
|
80 | 80 | config.add_route( |
|
81 | 81 | name='repo_files_nodelist', |
|
82 | 82 | pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True) |
|
83 | 83 | |
|
84 | 84 | config.add_route( |
|
85 | 85 | name='repo_file_raw', |
|
86 | 86 | pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True) |
|
87 | 87 | |
|
88 | 88 | config.add_route( |
|
89 | 89 | name='repo_file_download', |
|
90 | 90 | pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True) |
|
91 | 91 | config.add_route( # backward compat to keep old links working |
|
92 | 92 | name='repo_file_download:legacy', |
|
93 | 93 | pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}', |
|
94 | 94 | repo_route=True) |
|
95 | 95 | |
|
96 | 96 | config.add_route( |
|
97 | 97 | name='repo_file_history', |
|
98 | 98 | pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True) |
|
99 | 99 | |
|
100 | 100 | config.add_route( |
|
101 | 101 | name='repo_file_authors', |
|
102 | 102 | pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True) |
|
103 | 103 | |
|
104 | 104 | config.add_route( |
|
105 | 105 | name='repo_files_remove_file', |
|
106 | 106 | pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}', |
|
107 | 107 | repo_route=True) |
|
108 | 108 | config.add_route( |
|
109 | 109 | name='repo_files_delete_file', |
|
110 | 110 | pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}', |
|
111 | 111 | repo_route=True) |
|
112 | 112 | config.add_route( |
|
113 | 113 | name='repo_files_edit_file', |
|
114 | 114 | pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}', |
|
115 | 115 | repo_route=True) |
|
116 | 116 | config.add_route( |
|
117 | 117 | name='repo_files_update_file', |
|
118 | 118 | pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}', |
|
119 | 119 | repo_route=True) |
|
120 | 120 | config.add_route( |
|
121 | 121 | name='repo_files_add_file', |
|
122 | 122 | pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}', |
|
123 | 123 | repo_route=True) |
|
124 | 124 | config.add_route( |
|
125 | 125 | name='repo_files_create_file', |
|
126 | 126 | pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}', |
|
127 | 127 | repo_route=True) |
|
128 | 128 | |
|
129 | 129 | # refs data |
|
130 | 130 | config.add_route( |
|
131 | 131 | name='repo_refs_data', |
|
132 | 132 | pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True) |
|
133 | 133 | |
|
134 | 134 | config.add_route( |
|
135 | 135 | name='repo_refs_changelog_data', |
|
136 | 136 | pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True) |
|
137 | 137 | |
|
138 | 138 | config.add_route( |
|
139 | 139 | name='repo_stats', |
|
140 | 140 | pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True) |
|
141 | 141 | |
|
142 | # Changelog | |
|
143 | config.add_route( | |
|
144 | name='repo_changelog', | |
|
145 | pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True) | |
|
146 | config.add_route( | |
|
147 | name='repo_changelog_file', | |
|
148 | pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True) | |
|
149 | config.add_route( | |
|
150 | name='repo_changelog_elements', | |
|
151 | pattern='/{repo_name:.*?[^/]}/changelog_elements', repo_route=True) | |
|
152 | ||
|
142 | 153 | # Tags |
|
143 | 154 | config.add_route( |
|
144 | 155 | name='tags_home', |
|
145 | 156 | pattern='/{repo_name:.*?[^/]}/tags', repo_route=True) |
|
146 | 157 | |
|
147 | 158 | # Branches |
|
148 | 159 | config.add_route( |
|
149 | 160 | name='branches_home', |
|
150 | 161 | pattern='/{repo_name:.*?[^/]}/branches', repo_route=True) |
|
151 | 162 | |
|
152 | 163 | config.add_route( |
|
153 | 164 | name='bookmarks_home', |
|
154 | 165 | pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True) |
|
155 | 166 | |
|
156 | 167 | # Pull Requests |
|
157 | 168 | config.add_route( |
|
158 | 169 | name='pullrequest_show', |
|
159 | 170 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id}', |
|
160 | 171 | repo_route=True) |
|
161 | 172 | |
|
162 | 173 | config.add_route( |
|
163 | 174 | name='pullrequest_show_all', |
|
164 | 175 | pattern='/{repo_name:.*?[^/]}/pull-request', |
|
165 | 176 | repo_route=True, repo_accepted_types=['hg', 'git']) |
|
166 | 177 | |
|
167 | 178 | config.add_route( |
|
168 | 179 | name='pullrequest_show_all_data', |
|
169 | 180 | pattern='/{repo_name:.*?[^/]}/pull-request-data', |
|
170 | 181 | repo_route=True, repo_accepted_types=['hg', 'git']) |
|
171 | 182 | |
|
172 | 183 | # commits aka changesets |
|
173 | 184 | # TODO(dan): handle default landing revision ? |
|
174 | 185 | config.add_route( |
|
175 | 186 | name='changeset_home', |
|
176 | 187 | pattern='/{repo_name:.*?[^/]}/changeset/{revision}', |
|
177 | 188 | repo_route=True) |
|
178 | 189 | config.add_route( |
|
179 | 190 | name='changeset_children', |
|
180 | 191 | pattern='/{repo_name:.*?[^/]}/changeset_children/{revision}', |
|
181 | 192 | repo_route=True) |
|
182 | 193 | config.add_route( |
|
183 | 194 | name='changeset_parents', |
|
184 | 195 | pattern='/{repo_name:.*?[^/]}/changeset_parents/{revision}', |
|
185 | 196 | repo_route=True) |
|
186 | 197 | |
|
187 | 198 | # Settings |
|
188 | 199 | config.add_route( |
|
189 | 200 | name='edit_repo', |
|
190 | 201 | pattern='/{repo_name:.*?[^/]}/settings', repo_route=True) |
|
191 | 202 | |
|
192 | 203 | # Settings advanced |
|
193 | 204 | config.add_route( |
|
194 | 205 | name='edit_repo_advanced', |
|
195 | 206 | pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True) |
|
196 | 207 | config.add_route( |
|
197 | 208 | name='edit_repo_advanced_delete', |
|
198 | 209 | pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True) |
|
199 | 210 | config.add_route( |
|
200 | 211 | name='edit_repo_advanced_locking', |
|
201 | 212 | pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True) |
|
202 | 213 | config.add_route( |
|
203 | 214 | name='edit_repo_advanced_journal', |
|
204 | 215 | pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True) |
|
205 | 216 | config.add_route( |
|
206 | 217 | name='edit_repo_advanced_fork', |
|
207 | 218 | pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True) |
|
208 | 219 | |
|
209 | 220 | # Caches |
|
210 | 221 | config.add_route( |
|
211 | 222 | name='edit_repo_caches', |
|
212 | 223 | pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True) |
|
213 | 224 | |
|
214 | 225 | # Permissions |
|
215 | 226 | config.add_route( |
|
216 | 227 | name='edit_repo_perms', |
|
217 | 228 | pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True) |
|
218 | 229 | |
|
219 | 230 | # Repo Review Rules |
|
220 | 231 | config.add_route( |
|
221 | 232 | name='repo_reviewers', |
|
222 | 233 | pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True) |
|
223 | 234 | |
|
224 | 235 | config.add_route( |
|
225 | 236 | name='repo_default_reviewers_data', |
|
226 | 237 | pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True) |
|
227 | 238 | |
|
228 | 239 | # Maintenance |
|
229 | 240 | config.add_route( |
|
230 | 241 | name='repo_maintenance', |
|
231 | 242 | pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True) |
|
232 | 243 | |
|
233 | 244 | config.add_route( |
|
234 | 245 | name='repo_maintenance_execute', |
|
235 | 246 | pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True) |
|
236 | 247 | |
|
237 | 248 | # Strip |
|
238 | 249 | config.add_route( |
|
239 | 250 | name='strip', |
|
240 | 251 | pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True) |
|
241 | 252 | |
|
242 | 253 | config.add_route( |
|
243 | 254 | name='strip_check', |
|
244 | 255 | pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True) |
|
245 | 256 | |
|
246 | 257 | config.add_route( |
|
247 | 258 | name='strip_execute', |
|
248 | 259 | pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True) |
|
249 | 260 | |
|
250 | 261 | # ATOM/RSS Feed |
|
251 | 262 | config.add_route( |
|
252 | 263 | name='rss_feed_home', |
|
253 | 264 | pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True) |
|
254 | 265 | |
|
255 | 266 | config.add_route( |
|
256 | 267 | name='atom_feed_home', |
|
257 | 268 | pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True) |
|
258 | 269 | |
|
259 | 270 | # NOTE(marcink): needs to be at the end for catch-all |
|
260 | 271 | add_route_with_slash( |
|
261 | 272 | config, |
|
262 | 273 | name='repo_summary', |
|
263 | 274 | pattern='/{repo_name:.*?[^/]}', repo_route=True) |
|
264 | 275 | |
|
265 | 276 | # Scan module for configuration decorators. |
|
266 | 277 | config.scan() |
@@ -1,192 +1,195 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2010-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import re |
|
22 | 22 | |
|
23 | 23 | import pytest |
|
24 | 24 | |
|
25 |
from rhodecode. |
|
|
26 |
from rhodecode.tests import |
|
|
27 | from rhodecode.tests.utils import AssertResponse | |
|
28 | ||
|
25 | from rhodecode.apps.repository.views.repo_changelog import DEFAULT_CHANGELOG_SIZE | |
|
26 | from rhodecode.tests import TestController | |
|
29 | 27 | |
|
30 | 28 | MATCH_HASH = re.compile(r'<span class="commit_hash">r(\d+):[\da-f]+</span>') |
|
31 | 29 | |
|
32 | 30 | |
|
31 | def route_path(name, params=None, **kwargs): | |
|
32 | import urllib | |
|
33 | ||
|
34 | base_url = { | |
|
35 | 'repo_changelog':'/{repo_name}/changelog', | |
|
36 | 'repo_changelog_file':'/{repo_name}/changelog/{commit_id}/{f_path}', | |
|
37 | 'repo_changelog_elements':'/{repo_name}/changelog_elements', | |
|
38 | }[name].format(**kwargs) | |
|
39 | ||
|
40 | if params: | |
|
41 | base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) | |
|
42 | return base_url | |
|
43 | ||
|
44 | ||
|
33 | 45 | class TestChangelogController(TestController): |
|
34 | 46 | |
|
35 |
def test_ |
|
|
47 | def test_changelog(self, backend): | |
|
36 | 48 | self.log_user() |
|
37 | response = self.app.get(url(controller='changelog', action='index', | |
|
38 |
|
|
|
49 | response = self.app.get( | |
|
50 | route_path('repo_changelog', repo_name=backend.repo_name)) | |
|
39 | 51 | |
|
40 | 52 | first_idx = -1 |
|
41 | 53 | last_idx = -DEFAULT_CHANGELOG_SIZE |
|
42 | 54 | self.assert_commit_range_on_page( |
|
43 | 55 | response, first_idx, last_idx, backend) |
|
44 | 56 | |
|
45 | 57 | @pytest.mark.backends("hg", "git") |
|
46 |
def test_ |
|
|
58 | def test_changelog_filtered_by_branch(self, backend): | |
|
47 | 59 | self.log_user() |
|
48 | 60 | self.app.get( |
|
49 | url( | |
|
50 | controller='changelog', | |
|
51 | action='index', | |
|
52 | repo_name=backend.repo_name, | |
|
53 | branch=backend.default_branch_name), | |
|
61 | route_path('repo_changelog', repo_name=backend.repo_name, | |
|
62 | params=dict(branch=backend.default_branch_name)), | |
|
54 | 63 | status=200) |
|
55 | 64 | |
|
56 | 65 | @pytest.mark.backends("svn") |
|
57 |
def test_ |
|
|
66 | def test_changelog_filtered_by_branch_svn(self, autologin_user, backend): | |
|
58 | 67 | repo = backend['svn-simple-layout'] |
|
59 | 68 | response = self.app.get( |
|
60 | url( | |
|
61 | controller='changelog', | |
|
62 | action='index', | |
|
63 | repo_name=repo.repo_name, | |
|
64 | branch='trunk'), | |
|
69 | route_path('repo_changelog', repo_name=repo.repo_name, | |
|
70 | params=dict(branch='trunk')), | |
|
65 | 71 | status=200) |
|
66 | 72 | |
|
67 | 73 | self.assert_commits_on_page( |
|
68 | 74 | response, indexes=[15, 12, 7, 3, 2, 1]) |
|
69 | 75 | |
|
70 |
def test_ |
|
|
76 | def test_changelog_filtered_by_wrong_branch(self, backend): | |
|
71 | 77 | self.log_user() |
|
72 | 78 | branch = 'wrong-branch-name' |
|
73 | 79 | response = self.app.get( |
|
74 | url( | |
|
75 | controller='changelog', | |
|
76 | action='index', | |
|
77 | repo_name=backend.repo_name, | |
|
78 | branch=branch), | |
|
80 | route_path('repo_changelog', repo_name=backend.repo_name, | |
|
81 | params=dict(branch=branch)), | |
|
79 | 82 | status=302) |
|
80 | 83 | expected_url = '/{repo}/changelog/{branch}'.format( |
|
81 | 84 | repo=backend.repo_name, branch=branch) |
|
82 | 85 | assert expected_url in response.location |
|
83 | 86 | response = response.follow() |
|
84 | 87 | expected_warning = 'Branch {} is not found.'.format(branch) |
|
85 | 88 | assert expected_warning in response.body |
|
86 | 89 | |
|
87 | 90 | def assert_commits_on_page(self, response, indexes): |
|
88 | 91 | found_indexes = [int(idx) for idx in MATCH_HASH.findall(response.body)] |
|
89 | 92 | assert found_indexes == indexes |
|
90 | 93 | |
|
91 | 94 | @pytest.mark.xfail_backends("svn", reason="Depends on branch support") |
|
92 |
def test_ |
|
|
95 | def test_changelog_filtered_by_branch_with_merges( | |
|
93 | 96 | self, autologin_user, backend): |
|
94 | 97 | |
|
95 | 98 | # Note: The changelog of branch "b" does not contain the commit "a1" |
|
96 | 99 | # although this is a parent of commit "b1". And branch "b" has commits |
|
97 | 100 | # which have a smaller index than commit "a1". |
|
98 | 101 | commits = [ |
|
99 | 102 | {'message': 'a'}, |
|
100 | 103 | {'message': 'b', 'branch': 'b'}, |
|
101 | 104 | {'message': 'a1', 'parents': ['a']}, |
|
102 | 105 | {'message': 'b1', 'branch': 'b', 'parents': ['b', 'a1']}, |
|
103 | 106 | ] |
|
104 | 107 | backend.create_repo(commits) |
|
105 | 108 | |
|
106 | 109 | self.app.get( |
|
107 | url('changelog_home', | |
|
108 | controller='changelog', | |
|
109 | action='index', | |
|
110 | repo_name=backend.repo_name, | |
|
111 | branch='b'), | |
|
110 | route_path('repo_changelog', repo_name=backend.repo_name, | |
|
111 | params=dict(branch='b')), | |
|
112 | 112 | status=200) |
|
113 | 113 | |
|
114 | 114 | @pytest.mark.backends("hg") |
|
115 |
def test_ |
|
|
115 | def test_changelog_closed_branches(self, autologin_user, backend): | |
|
116 | 116 | repo = backend['closed_branch'] |
|
117 | 117 | response = self.app.get( |
|
118 | url( | |
|
119 | controller='changelog', | |
|
120 | action='index', | |
|
121 | repo_name=repo.repo_name, | |
|
122 | branch='experimental'), | |
|
118 | route_path('repo_changelog', repo_name=repo.repo_name, | |
|
119 | params=dict(branch='experimental')), | |
|
123 | 120 | status=200) |
|
124 | 121 | |
|
125 | 122 | self.assert_commits_on_page( |
|
126 | 123 | response, indexes=[3, 1]) |
|
127 | 124 | |
|
128 |
def test_ |
|
|
125 | def test_changelog_pagination(self, backend): | |
|
129 | 126 | self.log_user() |
|
130 | 127 | # pagination, walk up to page 6 |
|
131 |
changelog_url = |
|
|
132 | controller='changelog', action='index', | |
|
133 | repo_name=backend.repo_name) | |
|
128 | changelog_url = route_path( | |
|
129 | 'repo_changelog', repo_name=backend.repo_name) | |
|
130 | ||
|
134 | 131 | for page in range(1, 7): |
|
135 | 132 | response = self.app.get(changelog_url, {'page': page}) |
|
136 | 133 | |
|
137 | 134 | first_idx = -DEFAULT_CHANGELOG_SIZE * (page - 1) - 1 |
|
138 | 135 | last_idx = -DEFAULT_CHANGELOG_SIZE * page |
|
139 | 136 | self.assert_commit_range_on_page(response, first_idx, last_idx, backend) |
|
140 | 137 | |
|
141 | 138 | def assert_commit_range_on_page( |
|
142 | 139 | self, response, first_idx, last_idx, backend): |
|
143 | 140 | input_template = ( |
|
144 | 141 | """<input class="commit-range" id="%(raw_id)s" """ |
|
145 | 142 | """name="%(raw_id)s" type="checkbox" value="1" />""" |
|
146 | 143 | ) |
|
147 | 144 | commit_span_template = """<span class="commit_hash">r%s:%s</span>""" |
|
148 | 145 | repo = backend.repo |
|
149 | 146 | |
|
150 | 147 | first_commit_on_page = repo.get_commit(commit_idx=first_idx) |
|
151 | 148 | response.mustcontain( |
|
152 | 149 | input_template % {'raw_id': first_commit_on_page.raw_id}) |
|
153 | 150 | response.mustcontain(commit_span_template % ( |
|
154 | 151 | first_commit_on_page.idx, first_commit_on_page.short_id) |
|
155 | 152 | ) |
|
156 | 153 | |
|
157 | 154 | last_commit_on_page = repo.get_commit(commit_idx=last_idx) |
|
158 | 155 | response.mustcontain( |
|
159 | 156 | input_template % {'raw_id': last_commit_on_page.raw_id}) |
|
160 | 157 | response.mustcontain(commit_span_template % ( |
|
161 | 158 | last_commit_on_page.idx, last_commit_on_page.short_id) |
|
162 | 159 | ) |
|
163 | 160 | |
|
164 | 161 | first_commit_of_next_page = repo.get_commit(commit_idx=last_idx - 1) |
|
165 | 162 | first_span_of_next_page = commit_span_template % ( |
|
166 | 163 | first_commit_of_next_page.idx, first_commit_of_next_page.short_id) |
|
167 | 164 | assert first_span_of_next_page not in response |
|
168 | 165 | |
|
169 | def test_index_with_filenode(self, backend): | |
|
166 | @pytest.mark.parametrize('test_path', [ | |
|
167 | 'vcs/exceptions.py', | |
|
168 | '/vcs/exceptions.py', | |
|
169 | '//vcs/exceptions.py' | |
|
170 | ]) | |
|
171 | def test_changelog_with_filenode(self, backend, test_path): | |
|
170 | 172 | self.log_user() |
|
171 |
response = self.app.get( |
|
|
172 | controller='changelog', action='index', revision='tip', | |
|
173 | f_path='/vcs/exceptions.py', repo_name=backend.repo_name)) | |
|
173 | response = self.app.get( | |
|
174 | route_path('repo_changelog_file', repo_name=backend.repo_name, | |
|
175 | commit_id='tip', f_path=test_path), | |
|
176 | ) | |
|
174 | 177 | |
|
175 | 178 | # history commits messages |
|
176 | 179 | response.mustcontain('Added exceptions module, this time for real') |
|
177 | 180 | response.mustcontain('Added not implemented hg backend test case') |
|
178 | 181 | response.mustcontain('Added BaseChangeset class') |
|
179 | 182 | |
|
180 |
def test_ |
|
|
183 | def test_changelog_with_filenode_that_is_dirnode(self, backend): | |
|
181 | 184 | self.log_user() |
|
182 | response = self.app.get(url(controller='changelog', action='index', | |
|
183 | revision='tip', f_path='/tests', | |
|
184 | repo_name=backend.repo_name)) | |
|
185 | assert response.status == '302 Found' | |
|
185 | self.app.get( | |
|
186 | route_path('repo_changelog_file', repo_name=backend.repo_name, | |
|
187 | commit_id='tip', f_path='/tests'), | |
|
188 | status=302) | |
|
186 | 189 | |
|
187 |
def test_ |
|
|
190 | def test_changelog_with_filenode_not_existing(self, backend): | |
|
188 | 191 | self.log_user() |
|
189 | response = self.app.get(url(controller='changelog', action='index', | |
|
190 | revision='tip', f_path='/wrong_path', | |
|
191 | repo_name=backend.repo_name)) | |
|
192 | assert response.status == '302 Found' | |
|
192 | self.app.get( | |
|
193 | route_path('repo_changelog_file', repo_name=backend.repo_name, | |
|
194 | commit_id='tip', f_path='wrong_path'), | |
|
195 | status=302) |
@@ -1,731 +1,715 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2010-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | """ |
|
22 | 22 | Routes configuration |
|
23 | 23 | |
|
24 | 24 | The more specific and detailed routes should be defined first so they |
|
25 | 25 | may take precedent over the more generic routes. For more information |
|
26 | 26 | refer to the routes manual at http://routes.groovie.org/docs/ |
|
27 | 27 | |
|
28 | 28 | IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py |
|
29 | 29 | and _route_name variable which uses some of stored naming here to do redirects. |
|
30 | 30 | """ |
|
31 | 31 | import os |
|
32 | 32 | import re |
|
33 | 33 | from routes import Mapper |
|
34 | 34 | |
|
35 | 35 | # prefix for non repository related links needs to be prefixed with `/` |
|
36 | 36 | ADMIN_PREFIX = '/_admin' |
|
37 | 37 | STATIC_FILE_PREFIX = '/_static' |
|
38 | 38 | |
|
39 | 39 | # Default requirements for URL parts |
|
40 | 40 | URL_NAME_REQUIREMENTS = { |
|
41 | 41 | # group name can have a slash in them, but they must not end with a slash |
|
42 | 42 | 'group_name': r'.*?[^/]', |
|
43 | 43 | 'repo_group_name': r'.*?[^/]', |
|
44 | 44 | # repo names can have a slash in them, but they must not end with a slash |
|
45 | 45 | 'repo_name': r'.*?[^/]', |
|
46 | 46 | # file path eats up everything at the end |
|
47 | 47 | 'f_path': r'.*', |
|
48 | 48 | # reference types |
|
49 | 49 | 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)', |
|
50 | 50 | 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)', |
|
51 | 51 | } |
|
52 | 52 | |
|
53 | 53 | |
|
54 | 54 | class JSRoutesMapper(Mapper): |
|
55 | 55 | """ |
|
56 | 56 | Wrapper for routes.Mapper to make pyroutes compatible url definitions |
|
57 | 57 | """ |
|
58 | 58 | _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$') |
|
59 | 59 | _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)') |
|
60 | 60 | def __init__(self, *args, **kw): |
|
61 | 61 | super(JSRoutesMapper, self).__init__(*args, **kw) |
|
62 | 62 | self._jsroutes = [] |
|
63 | 63 | |
|
64 | 64 | def connect(self, *args, **kw): |
|
65 | 65 | """ |
|
66 | 66 | Wrapper for connect to take an extra argument jsroute=True |
|
67 | 67 | |
|
68 | 68 | :param jsroute: boolean, if True will add the route to the pyroutes list |
|
69 | 69 | """ |
|
70 | 70 | if kw.pop('jsroute', False): |
|
71 | 71 | if not self._named_route_regex.match(args[0]): |
|
72 | 72 | raise Exception('only named routes can be added to pyroutes') |
|
73 | 73 | self._jsroutes.append(args[0]) |
|
74 | 74 | |
|
75 | 75 | super(JSRoutesMapper, self).connect(*args, **kw) |
|
76 | 76 | |
|
77 | 77 | def _extract_route_information(self, route): |
|
78 | 78 | """ |
|
79 | 79 | Convert a route into tuple(name, path, args), eg: |
|
80 | 80 | ('show_user', '/profile/%(username)s', ['username']) |
|
81 | 81 | """ |
|
82 | 82 | routepath = route.routepath |
|
83 | 83 | def replace(matchobj): |
|
84 | 84 | if matchobj.group(1): |
|
85 | 85 | return "%%(%s)s" % matchobj.group(1).split(':')[0] |
|
86 | 86 | else: |
|
87 | 87 | return "%%(%s)s" % matchobj.group(2) |
|
88 | 88 | |
|
89 | 89 | routepath = self._argument_prog.sub(replace, routepath) |
|
90 | 90 | return ( |
|
91 | 91 | route.name, |
|
92 | 92 | routepath, |
|
93 | 93 | [(arg[0].split(':')[0] if arg[0] != '' else arg[1]) |
|
94 | 94 | for arg in self._argument_prog.findall(route.routepath)] |
|
95 | 95 | ) |
|
96 | 96 | |
|
97 | 97 | def jsroutes(self): |
|
98 | 98 | """ |
|
99 | 99 | Return a list of pyroutes.js compatible routes |
|
100 | 100 | """ |
|
101 | 101 | for route_name in self._jsroutes: |
|
102 | 102 | yield self._extract_route_information(self._routenames[route_name]) |
|
103 | 103 | |
|
104 | 104 | |
|
105 | 105 | def make_map(config): |
|
106 | 106 | """Create, configure and return the routes Mapper""" |
|
107 | 107 | rmap = JSRoutesMapper( |
|
108 | 108 | directory=config['pylons.paths']['controllers'], |
|
109 | 109 | always_scan=config['debug']) |
|
110 | 110 | rmap.minimization = False |
|
111 | 111 | rmap.explicit = False |
|
112 | 112 | |
|
113 | 113 | from rhodecode.lib.utils2 import str2bool |
|
114 | 114 | from rhodecode.model import repo, repo_group |
|
115 | 115 | |
|
116 | 116 | def check_repo(environ, match_dict): |
|
117 | 117 | """ |
|
118 | 118 | check for valid repository for proper 404 handling |
|
119 | 119 | |
|
120 | 120 | :param environ: |
|
121 | 121 | :param match_dict: |
|
122 | 122 | """ |
|
123 | 123 | repo_name = match_dict.get('repo_name') |
|
124 | 124 | |
|
125 | 125 | if match_dict.get('f_path'): |
|
126 | 126 | # fix for multiple initial slashes that causes errors |
|
127 | 127 | match_dict['f_path'] = match_dict['f_path'].lstrip('/') |
|
128 | 128 | repo_model = repo.RepoModel() |
|
129 | 129 | by_name_match = repo_model.get_by_repo_name(repo_name) |
|
130 | 130 | # if we match quickly from database, short circuit the operation, |
|
131 | 131 | # and validate repo based on the type. |
|
132 | 132 | if by_name_match: |
|
133 | 133 | return True |
|
134 | 134 | |
|
135 | 135 | by_id_match = repo_model.get_repo_by_id(repo_name) |
|
136 | 136 | if by_id_match: |
|
137 | 137 | repo_name = by_id_match.repo_name |
|
138 | 138 | match_dict['repo_name'] = repo_name |
|
139 | 139 | return True |
|
140 | 140 | |
|
141 | 141 | return False |
|
142 | 142 | |
|
143 | 143 | def check_group(environ, match_dict): |
|
144 | 144 | """ |
|
145 | 145 | check for valid repository group path for proper 404 handling |
|
146 | 146 | |
|
147 | 147 | :param environ: |
|
148 | 148 | :param match_dict: |
|
149 | 149 | """ |
|
150 | 150 | repo_group_name = match_dict.get('group_name') |
|
151 | 151 | repo_group_model = repo_group.RepoGroupModel() |
|
152 | 152 | by_name_match = repo_group_model.get_by_group_name(repo_group_name) |
|
153 | 153 | if by_name_match: |
|
154 | 154 | return True |
|
155 | 155 | |
|
156 | 156 | return False |
|
157 | 157 | |
|
158 | 158 | def check_user_group(environ, match_dict): |
|
159 | 159 | """ |
|
160 | 160 | check for valid user group for proper 404 handling |
|
161 | 161 | |
|
162 | 162 | :param environ: |
|
163 | 163 | :param match_dict: |
|
164 | 164 | """ |
|
165 | 165 | return True |
|
166 | 166 | |
|
167 | 167 | def check_int(environ, match_dict): |
|
168 | 168 | return match_dict.get('id').isdigit() |
|
169 | 169 | |
|
170 | 170 | |
|
171 | 171 | #========================================================================== |
|
172 | 172 | # CUSTOM ROUTES HERE |
|
173 | 173 | #========================================================================== |
|
174 | 174 | |
|
175 | 175 | # ping and pylons error test |
|
176 | 176 | rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping') |
|
177 | 177 | rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test') |
|
178 | 178 | |
|
179 | 179 | # ADMIN REPOSITORY ROUTES |
|
180 | 180 | with rmap.submapper(path_prefix=ADMIN_PREFIX, |
|
181 | 181 | controller='admin/repos') as m: |
|
182 | 182 | m.connect('repos', '/repos', |
|
183 | 183 | action='create', conditions={'method': ['POST']}) |
|
184 | 184 | m.connect('repos', '/repos', |
|
185 | 185 | action='index', conditions={'method': ['GET']}) |
|
186 | 186 | m.connect('new_repo', '/create_repository', jsroute=True, |
|
187 | 187 | action='create_repository', conditions={'method': ['GET']}) |
|
188 | 188 | m.connect('delete_repo', '/repos/{repo_name}', |
|
189 | 189 | action='delete', conditions={'method': ['DELETE']}, |
|
190 | 190 | requirements=URL_NAME_REQUIREMENTS) |
|
191 | 191 | m.connect('repo', '/repos/{repo_name}', |
|
192 | 192 | action='show', conditions={'method': ['GET'], |
|
193 | 193 | 'function': check_repo}, |
|
194 | 194 | requirements=URL_NAME_REQUIREMENTS) |
|
195 | 195 | |
|
196 | 196 | # ADMIN REPOSITORY GROUPS ROUTES |
|
197 | 197 | with rmap.submapper(path_prefix=ADMIN_PREFIX, |
|
198 | 198 | controller='admin/repo_groups') as m: |
|
199 | 199 | m.connect('repo_groups', '/repo_groups', |
|
200 | 200 | action='create', conditions={'method': ['POST']}) |
|
201 | 201 | m.connect('repo_groups', '/repo_groups', |
|
202 | 202 | action='index', conditions={'method': ['GET']}) |
|
203 | 203 | m.connect('new_repo_group', '/repo_groups/new', |
|
204 | 204 | action='new', conditions={'method': ['GET']}) |
|
205 | 205 | m.connect('update_repo_group', '/repo_groups/{group_name}', |
|
206 | 206 | action='update', conditions={'method': ['PUT'], |
|
207 | 207 | 'function': check_group}, |
|
208 | 208 | requirements=URL_NAME_REQUIREMENTS) |
|
209 | 209 | |
|
210 | 210 | # EXTRAS REPO GROUP ROUTES |
|
211 | 211 | m.connect('edit_repo_group', '/repo_groups/{group_name}/edit', |
|
212 | 212 | action='edit', |
|
213 | 213 | conditions={'method': ['GET'], 'function': check_group}, |
|
214 | 214 | requirements=URL_NAME_REQUIREMENTS) |
|
215 | 215 | m.connect('edit_repo_group', '/repo_groups/{group_name}/edit', |
|
216 | 216 | action='edit', |
|
217 | 217 | conditions={'method': ['PUT'], 'function': check_group}, |
|
218 | 218 | requirements=URL_NAME_REQUIREMENTS) |
|
219 | 219 | |
|
220 | 220 | m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced', |
|
221 | 221 | action='edit_repo_group_advanced', |
|
222 | 222 | conditions={'method': ['GET'], 'function': check_group}, |
|
223 | 223 | requirements=URL_NAME_REQUIREMENTS) |
|
224 | 224 | m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced', |
|
225 | 225 | action='edit_repo_group_advanced', |
|
226 | 226 | conditions={'method': ['PUT'], 'function': check_group}, |
|
227 | 227 | requirements=URL_NAME_REQUIREMENTS) |
|
228 | 228 | |
|
229 | 229 | m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions', |
|
230 | 230 | action='edit_repo_group_perms', |
|
231 | 231 | conditions={'method': ['GET'], 'function': check_group}, |
|
232 | 232 | requirements=URL_NAME_REQUIREMENTS) |
|
233 | 233 | m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions', |
|
234 | 234 | action='update_perms', |
|
235 | 235 | conditions={'method': ['PUT'], 'function': check_group}, |
|
236 | 236 | requirements=URL_NAME_REQUIREMENTS) |
|
237 | 237 | |
|
238 | 238 | m.connect('delete_repo_group', '/repo_groups/{group_name}', |
|
239 | 239 | action='delete', conditions={'method': ['DELETE'], |
|
240 | 240 | 'function': check_group}, |
|
241 | 241 | requirements=URL_NAME_REQUIREMENTS) |
|
242 | 242 | |
|
243 | 243 | # ADMIN USER ROUTES |
|
244 | 244 | with rmap.submapper(path_prefix=ADMIN_PREFIX, |
|
245 | 245 | controller='admin/users') as m: |
|
246 | 246 | m.connect('users', '/users', |
|
247 | 247 | action='create', conditions={'method': ['POST']}) |
|
248 | 248 | m.connect('new_user', '/users/new', |
|
249 | 249 | action='new', conditions={'method': ['GET']}) |
|
250 | 250 | m.connect('update_user', '/users/{user_id}', |
|
251 | 251 | action='update', conditions={'method': ['PUT']}) |
|
252 | 252 | m.connect('delete_user', '/users/{user_id}', |
|
253 | 253 | action='delete', conditions={'method': ['DELETE']}) |
|
254 | 254 | m.connect('edit_user', '/users/{user_id}/edit', |
|
255 | 255 | action='edit', conditions={'method': ['GET']}, jsroute=True) |
|
256 | 256 | m.connect('user', '/users/{user_id}', |
|
257 | 257 | action='show', conditions={'method': ['GET']}) |
|
258 | 258 | m.connect('force_password_reset_user', '/users/{user_id}/password_reset', |
|
259 | 259 | action='reset_password', conditions={'method': ['POST']}) |
|
260 | 260 | m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group', |
|
261 | 261 | action='create_personal_repo_group', conditions={'method': ['POST']}) |
|
262 | 262 | |
|
263 | 263 | # EXTRAS USER ROUTES |
|
264 | 264 | m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced', |
|
265 | 265 | action='edit_advanced', conditions={'method': ['GET']}) |
|
266 | 266 | m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced', |
|
267 | 267 | action='update_advanced', conditions={'method': ['PUT']}) |
|
268 | 268 | |
|
269 | 269 | m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions', |
|
270 | 270 | action='edit_global_perms', conditions={'method': ['GET']}) |
|
271 | 271 | m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions', |
|
272 | 272 | action='update_global_perms', conditions={'method': ['PUT']}) |
|
273 | 273 | |
|
274 | 274 | m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary', |
|
275 | 275 | action='edit_perms_summary', conditions={'method': ['GET']}) |
|
276 | 276 | |
|
277 | ||
|
278 | 277 | # ADMIN USER GROUPS REST ROUTES |
|
279 | 278 | with rmap.submapper(path_prefix=ADMIN_PREFIX, |
|
280 | 279 | controller='admin/user_groups') as m: |
|
281 | 280 | m.connect('users_groups', '/user_groups', |
|
282 | 281 | action='create', conditions={'method': ['POST']}) |
|
283 | 282 | m.connect('users_groups', '/user_groups', |
|
284 | 283 | action='index', conditions={'method': ['GET']}) |
|
285 | 284 | m.connect('new_users_group', '/user_groups/new', |
|
286 | 285 | action='new', conditions={'method': ['GET']}) |
|
287 | 286 | m.connect('update_users_group', '/user_groups/{user_group_id}', |
|
288 | 287 | action='update', conditions={'method': ['PUT']}) |
|
289 | 288 | m.connect('delete_users_group', '/user_groups/{user_group_id}', |
|
290 | 289 | action='delete', conditions={'method': ['DELETE']}) |
|
291 | 290 | m.connect('edit_users_group', '/user_groups/{user_group_id}/edit', |
|
292 | 291 | action='edit', conditions={'method': ['GET']}, |
|
293 | 292 | function=check_user_group) |
|
294 | 293 | |
|
295 | 294 | # EXTRAS USER GROUP ROUTES |
|
296 | 295 | m.connect('edit_user_group_global_perms', |
|
297 | 296 | '/user_groups/{user_group_id}/edit/global_permissions', |
|
298 | 297 | action='edit_global_perms', conditions={'method': ['GET']}) |
|
299 | 298 | m.connect('edit_user_group_global_perms', |
|
300 | 299 | '/user_groups/{user_group_id}/edit/global_permissions', |
|
301 | 300 | action='update_global_perms', conditions={'method': ['PUT']}) |
|
302 | 301 | m.connect('edit_user_group_perms_summary', |
|
303 | 302 | '/user_groups/{user_group_id}/edit/permissions_summary', |
|
304 | 303 | action='edit_perms_summary', conditions={'method': ['GET']}) |
|
305 | 304 | |
|
306 | 305 | m.connect('edit_user_group_perms', |
|
307 | 306 | '/user_groups/{user_group_id}/edit/permissions', |
|
308 | 307 | action='edit_perms', conditions={'method': ['GET']}) |
|
309 | 308 | m.connect('edit_user_group_perms', |
|
310 | 309 | '/user_groups/{user_group_id}/edit/permissions', |
|
311 | 310 | action='update_perms', conditions={'method': ['PUT']}) |
|
312 | 311 | |
|
313 | 312 | m.connect('edit_user_group_advanced', |
|
314 | 313 | '/user_groups/{user_group_id}/edit/advanced', |
|
315 | 314 | action='edit_advanced', conditions={'method': ['GET']}) |
|
316 | 315 | |
|
317 | 316 | m.connect('edit_user_group_advanced_sync', |
|
318 | 317 | '/user_groups/{user_group_id}/edit/advanced/sync', |
|
319 | 318 | action='edit_advanced_set_synchronization', conditions={'method': ['POST']}) |
|
320 | 319 | |
|
321 | 320 | m.connect('edit_user_group_members', |
|
322 | 321 | '/user_groups/{user_group_id}/edit/members', jsroute=True, |
|
323 | 322 | action='user_group_members', conditions={'method': ['GET']}) |
|
324 | 323 | |
|
325 | 324 | # ADMIN PERMISSIONS ROUTES |
|
326 | 325 | with rmap.submapper(path_prefix=ADMIN_PREFIX, |
|
327 | 326 | controller='admin/permissions') as m: |
|
328 | 327 | m.connect('admin_permissions_application', '/permissions/application', |
|
329 | 328 | action='permission_application_update', conditions={'method': ['POST']}) |
|
330 | 329 | m.connect('admin_permissions_application', '/permissions/application', |
|
331 | 330 | action='permission_application', conditions={'method': ['GET']}) |
|
332 | 331 | |
|
333 | 332 | m.connect('admin_permissions_global', '/permissions/global', |
|
334 | 333 | action='permission_global_update', conditions={'method': ['POST']}) |
|
335 | 334 | m.connect('admin_permissions_global', '/permissions/global', |
|
336 | 335 | action='permission_global', conditions={'method': ['GET']}) |
|
337 | 336 | |
|
338 | 337 | m.connect('admin_permissions_object', '/permissions/object', |
|
339 | 338 | action='permission_objects_update', conditions={'method': ['POST']}) |
|
340 | 339 | m.connect('admin_permissions_object', '/permissions/object', |
|
341 | 340 | action='permission_objects', conditions={'method': ['GET']}) |
|
342 | 341 | |
|
343 | 342 | m.connect('admin_permissions_ips', '/permissions/ips', |
|
344 | 343 | action='permission_ips', conditions={'method': ['POST']}) |
|
345 | 344 | m.connect('admin_permissions_ips', '/permissions/ips', |
|
346 | 345 | action='permission_ips', conditions={'method': ['GET']}) |
|
347 | 346 | |
|
348 | 347 | m.connect('admin_permissions_overview', '/permissions/overview', |
|
349 | 348 | action='permission_perms', conditions={'method': ['GET']}) |
|
350 | 349 | |
|
351 | 350 | # ADMIN DEFAULTS REST ROUTES |
|
352 | 351 | with rmap.submapper(path_prefix=ADMIN_PREFIX, |
|
353 | 352 | controller='admin/defaults') as m: |
|
354 | 353 | m.connect('admin_defaults_repositories', '/defaults/repositories', |
|
355 | 354 | action='update_repository_defaults', conditions={'method': ['POST']}) |
|
356 | 355 | m.connect('admin_defaults_repositories', '/defaults/repositories', |
|
357 | 356 | action='index', conditions={'method': ['GET']}) |
|
358 | 357 | |
|
359 | 358 | # ADMIN SETTINGS ROUTES |
|
360 | 359 | with rmap.submapper(path_prefix=ADMIN_PREFIX, |
|
361 | 360 | controller='admin/settings') as m: |
|
362 | 361 | |
|
363 | 362 | # default |
|
364 | 363 | m.connect('admin_settings', '/settings', |
|
365 | 364 | action='settings_global_update', |
|
366 | 365 | conditions={'method': ['POST']}) |
|
367 | 366 | m.connect('admin_settings', '/settings', |
|
368 | 367 | action='settings_global', conditions={'method': ['GET']}) |
|
369 | 368 | |
|
370 | 369 | m.connect('admin_settings_vcs', '/settings/vcs', |
|
371 | 370 | action='settings_vcs_update', |
|
372 | 371 | conditions={'method': ['POST']}) |
|
373 | 372 | m.connect('admin_settings_vcs', '/settings/vcs', |
|
374 | 373 | action='settings_vcs', |
|
375 | 374 | conditions={'method': ['GET']}) |
|
376 | 375 | m.connect('admin_settings_vcs', '/settings/vcs', |
|
377 | 376 | action='delete_svn_pattern', |
|
378 | 377 | conditions={'method': ['DELETE']}) |
|
379 | 378 | |
|
380 | 379 | m.connect('admin_settings_mapping', '/settings/mapping', |
|
381 | 380 | action='settings_mapping_update', |
|
382 | 381 | conditions={'method': ['POST']}) |
|
383 | 382 | m.connect('admin_settings_mapping', '/settings/mapping', |
|
384 | 383 | action='settings_mapping', conditions={'method': ['GET']}) |
|
385 | 384 | |
|
386 | 385 | m.connect('admin_settings_global', '/settings/global', |
|
387 | 386 | action='settings_global_update', |
|
388 | 387 | conditions={'method': ['POST']}) |
|
389 | 388 | m.connect('admin_settings_global', '/settings/global', |
|
390 | 389 | action='settings_global', conditions={'method': ['GET']}) |
|
391 | 390 | |
|
392 | 391 | m.connect('admin_settings_visual', '/settings/visual', |
|
393 | 392 | action='settings_visual_update', |
|
394 | 393 | conditions={'method': ['POST']}) |
|
395 | 394 | m.connect('admin_settings_visual', '/settings/visual', |
|
396 | 395 | action='settings_visual', conditions={'method': ['GET']}) |
|
397 | 396 | |
|
398 | 397 | m.connect('admin_settings_issuetracker', |
|
399 | 398 | '/settings/issue-tracker', action='settings_issuetracker', |
|
400 | 399 | conditions={'method': ['GET']}) |
|
401 | 400 | m.connect('admin_settings_issuetracker_save', |
|
402 | 401 | '/settings/issue-tracker/save', |
|
403 | 402 | action='settings_issuetracker_save', |
|
404 | 403 | conditions={'method': ['POST']}) |
|
405 | 404 | m.connect('admin_issuetracker_test', '/settings/issue-tracker/test', |
|
406 | 405 | action='settings_issuetracker_test', |
|
407 | 406 | conditions={'method': ['POST']}) |
|
408 | 407 | m.connect('admin_issuetracker_delete', |
|
409 | 408 | '/settings/issue-tracker/delete', |
|
410 | 409 | action='settings_issuetracker_delete', |
|
411 | 410 | conditions={'method': ['DELETE']}) |
|
412 | 411 | |
|
413 | 412 | m.connect('admin_settings_email', '/settings/email', |
|
414 | 413 | action='settings_email_update', |
|
415 | 414 | conditions={'method': ['POST']}) |
|
416 | 415 | m.connect('admin_settings_email', '/settings/email', |
|
417 | 416 | action='settings_email', conditions={'method': ['GET']}) |
|
418 | 417 | |
|
419 | 418 | m.connect('admin_settings_hooks', '/settings/hooks', |
|
420 | 419 | action='settings_hooks_update', |
|
421 | 420 | conditions={'method': ['POST', 'DELETE']}) |
|
422 | 421 | m.connect('admin_settings_hooks', '/settings/hooks', |
|
423 | 422 | action='settings_hooks', conditions={'method': ['GET']}) |
|
424 | 423 | |
|
425 | 424 | m.connect('admin_settings_search', '/settings/search', |
|
426 | 425 | action='settings_search', conditions={'method': ['GET']}) |
|
427 | 426 | |
|
428 | 427 | m.connect('admin_settings_supervisor', '/settings/supervisor', |
|
429 | 428 | action='settings_supervisor', conditions={'method': ['GET']}) |
|
430 | 429 | m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log', |
|
431 | 430 | action='settings_supervisor_log', conditions={'method': ['GET']}) |
|
432 | 431 | |
|
433 | 432 | m.connect('admin_settings_labs', '/settings/labs', |
|
434 | 433 | action='settings_labs_update', |
|
435 | 434 | conditions={'method': ['POST']}) |
|
436 | 435 | m.connect('admin_settings_labs', '/settings/labs', |
|
437 | 436 | action='settings_labs', conditions={'method': ['GET']}) |
|
438 | 437 | |
|
439 | 438 | # ADMIN MY ACCOUNT |
|
440 | 439 | with rmap.submapper(path_prefix=ADMIN_PREFIX, |
|
441 | 440 | controller='admin/my_account') as m: |
|
442 | 441 | |
|
443 | 442 | # NOTE(marcink): this needs to be kept for password force flag to be |
|
444 | 443 | # handled in pylons controllers, remove after full migration to pyramid |
|
445 | 444 | m.connect('my_account_password', '/my_account/password', |
|
446 | 445 | action='my_account_password', conditions={'method': ['GET']}) |
|
447 | 446 | |
|
448 | 447 | # USER JOURNAL |
|
449 | 448 | rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,), |
|
450 | 449 | controller='journal', action='index') |
|
451 | 450 | rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,), |
|
452 | 451 | controller='journal', action='journal_rss') |
|
453 | 452 | rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,), |
|
454 | 453 | controller='journal', action='journal_atom') |
|
455 | 454 | |
|
456 | 455 | rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,), |
|
457 | 456 | controller='journal', action='public_journal') |
|
458 | 457 | |
|
459 | 458 | rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,), |
|
460 | 459 | controller='journal', action='public_journal_rss') |
|
461 | 460 | |
|
462 | 461 | rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,), |
|
463 | 462 | controller='journal', action='public_journal_rss') |
|
464 | 463 | |
|
465 | 464 | rmap.connect('public_journal_atom', |
|
466 | 465 | '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal', |
|
467 | 466 | action='public_journal_atom') |
|
468 | 467 | |
|
469 | 468 | rmap.connect('public_journal_atom_old', |
|
470 | 469 | '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal', |
|
471 | 470 | action='public_journal_atom') |
|
472 | 471 | |
|
473 | 472 | rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,), |
|
474 | 473 | controller='journal', action='toggle_following', jsroute=True, |
|
475 | 474 | conditions={'method': ['POST']}) |
|
476 | 475 | |
|
477 | 476 | #========================================================================== |
|
478 | 477 | # REPOSITORY ROUTES |
|
479 | 478 | #========================================================================== |
|
480 | 479 | |
|
481 | 480 | rmap.connect('repo_creating_home', '/{repo_name}/repo_creating', |
|
482 | 481 | controller='admin/repos', action='repo_creating', |
|
483 | 482 | requirements=URL_NAME_REQUIREMENTS) |
|
484 | 483 | rmap.connect('repo_check_home', '/{repo_name}/crepo_check', |
|
485 | 484 | controller='admin/repos', action='repo_check', |
|
486 | 485 | requirements=URL_NAME_REQUIREMENTS) |
|
487 | 486 | |
|
488 | 487 | rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}', |
|
489 | 488 | controller='changeset', revision='tip', |
|
490 | 489 | conditions={'function': check_repo}, |
|
491 | 490 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
492 | 491 | rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}', |
|
493 | 492 | controller='changeset', revision='tip', action='changeset_children', |
|
494 | 493 | conditions={'function': check_repo}, |
|
495 | 494 | requirements=URL_NAME_REQUIREMENTS) |
|
496 | 495 | rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}', |
|
497 | 496 | controller='changeset', revision='tip', action='changeset_parents', |
|
498 | 497 | conditions={'function': check_repo}, |
|
499 | 498 | requirements=URL_NAME_REQUIREMENTS) |
|
500 | 499 | |
|
501 | 500 | # repo edit options |
|
502 | 501 | rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields', |
|
503 | 502 | controller='admin/repos', action='edit_fields', |
|
504 | 503 | conditions={'method': ['GET'], 'function': check_repo}, |
|
505 | 504 | requirements=URL_NAME_REQUIREMENTS) |
|
506 | 505 | rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new', |
|
507 | 506 | controller='admin/repos', action='create_repo_field', |
|
508 | 507 | conditions={'method': ['PUT'], 'function': check_repo}, |
|
509 | 508 | requirements=URL_NAME_REQUIREMENTS) |
|
510 | 509 | rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}', |
|
511 | 510 | controller='admin/repos', action='delete_repo_field', |
|
512 | 511 | conditions={'method': ['DELETE'], 'function': check_repo}, |
|
513 | 512 | requirements=URL_NAME_REQUIREMENTS) |
|
514 | 513 | |
|
515 | 514 | rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle', |
|
516 | 515 | controller='admin/repos', action='toggle_locking', |
|
517 | 516 | conditions={'method': ['GET'], 'function': check_repo}, |
|
518 | 517 | requirements=URL_NAME_REQUIREMENTS) |
|
519 | 518 | |
|
520 | 519 | rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote', |
|
521 | 520 | controller='admin/repos', action='edit_remote_form', |
|
522 | 521 | conditions={'method': ['GET'], 'function': check_repo}, |
|
523 | 522 | requirements=URL_NAME_REQUIREMENTS) |
|
524 | 523 | rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote', |
|
525 | 524 | controller='admin/repos', action='edit_remote', |
|
526 | 525 | conditions={'method': ['PUT'], 'function': check_repo}, |
|
527 | 526 | requirements=URL_NAME_REQUIREMENTS) |
|
528 | 527 | |
|
529 | 528 | rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics', |
|
530 | 529 | controller='admin/repos', action='edit_statistics_form', |
|
531 | 530 | conditions={'method': ['GET'], 'function': check_repo}, |
|
532 | 531 | requirements=URL_NAME_REQUIREMENTS) |
|
533 | 532 | rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics', |
|
534 | 533 | controller='admin/repos', action='edit_statistics', |
|
535 | 534 | conditions={'method': ['PUT'], 'function': check_repo}, |
|
536 | 535 | requirements=URL_NAME_REQUIREMENTS) |
|
537 | 536 | rmap.connect('repo_settings_issuetracker', |
|
538 | 537 | '/{repo_name}/settings/issue-tracker', |
|
539 | 538 | controller='admin/repos', action='repo_issuetracker', |
|
540 | 539 | conditions={'method': ['GET'], 'function': check_repo}, |
|
541 | 540 | requirements=URL_NAME_REQUIREMENTS) |
|
542 | 541 | rmap.connect('repo_issuetracker_test', |
|
543 | 542 | '/{repo_name}/settings/issue-tracker/test', |
|
544 | 543 | controller='admin/repos', action='repo_issuetracker_test', |
|
545 | 544 | conditions={'method': ['POST'], 'function': check_repo}, |
|
546 | 545 | requirements=URL_NAME_REQUIREMENTS) |
|
547 | 546 | rmap.connect('repo_issuetracker_delete', |
|
548 | 547 | '/{repo_name}/settings/issue-tracker/delete', |
|
549 | 548 | controller='admin/repos', action='repo_issuetracker_delete', |
|
550 | 549 | conditions={'method': ['DELETE'], 'function': check_repo}, |
|
551 | 550 | requirements=URL_NAME_REQUIREMENTS) |
|
552 | 551 | rmap.connect('repo_issuetracker_save', |
|
553 | 552 | '/{repo_name}/settings/issue-tracker/save', |
|
554 | 553 | controller='admin/repos', action='repo_issuetracker_save', |
|
555 | 554 | conditions={'method': ['POST'], 'function': check_repo}, |
|
556 | 555 | requirements=URL_NAME_REQUIREMENTS) |
|
557 | 556 | rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs', |
|
558 | 557 | controller='admin/repos', action='repo_settings_vcs_update', |
|
559 | 558 | conditions={'method': ['POST'], 'function': check_repo}, |
|
560 | 559 | requirements=URL_NAME_REQUIREMENTS) |
|
561 | 560 | rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs', |
|
562 | 561 | controller='admin/repos', action='repo_settings_vcs', |
|
563 | 562 | conditions={'method': ['GET'], 'function': check_repo}, |
|
564 | 563 | requirements=URL_NAME_REQUIREMENTS) |
|
565 | 564 | rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs', |
|
566 | 565 | controller='admin/repos', action='repo_delete_svn_pattern', |
|
567 | 566 | conditions={'method': ['DELETE'], 'function': check_repo}, |
|
568 | 567 | requirements=URL_NAME_REQUIREMENTS) |
|
569 | 568 | rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest', |
|
570 | 569 | controller='admin/repos', action='repo_settings_pullrequest', |
|
571 | 570 | conditions={'method': ['GET', 'POST'], 'function': check_repo}, |
|
572 | 571 | requirements=URL_NAME_REQUIREMENTS) |
|
573 | 572 | |
|
574 | 573 | # still working url for backward compat. |
|
575 | 574 | rmap.connect('raw_changeset_home_depraced', |
|
576 | 575 | '/{repo_name}/raw-changeset/{revision}', |
|
577 | 576 | controller='changeset', action='changeset_raw', |
|
578 | 577 | revision='tip', conditions={'function': check_repo}, |
|
579 | 578 | requirements=URL_NAME_REQUIREMENTS) |
|
580 | 579 | |
|
581 | 580 | # new URLs |
|
582 | 581 | rmap.connect('changeset_raw_home', |
|
583 | 582 | '/{repo_name}/changeset-diff/{revision}', |
|
584 | 583 | controller='changeset', action='changeset_raw', |
|
585 | 584 | revision='tip', conditions={'function': check_repo}, |
|
586 | 585 | requirements=URL_NAME_REQUIREMENTS) |
|
587 | 586 | |
|
588 | 587 | rmap.connect('changeset_patch_home', |
|
589 | 588 | '/{repo_name}/changeset-patch/{revision}', |
|
590 | 589 | controller='changeset', action='changeset_patch', |
|
591 | 590 | revision='tip', conditions={'function': check_repo}, |
|
592 | 591 | requirements=URL_NAME_REQUIREMENTS) |
|
593 | 592 | |
|
594 | 593 | rmap.connect('changeset_download_home', |
|
595 | 594 | '/{repo_name}/changeset-download/{revision}', |
|
596 | 595 | controller='changeset', action='changeset_download', |
|
597 | 596 | revision='tip', conditions={'function': check_repo}, |
|
598 | 597 | requirements=URL_NAME_REQUIREMENTS) |
|
599 | 598 | |
|
600 | 599 | rmap.connect('changeset_comment', |
|
601 | 600 | '/{repo_name}/changeset/{revision}/comment', jsroute=True, |
|
602 | 601 | controller='changeset', revision='tip', action='comment', |
|
603 | 602 | conditions={'function': check_repo}, |
|
604 | 603 | requirements=URL_NAME_REQUIREMENTS) |
|
605 | 604 | |
|
606 | 605 | rmap.connect('changeset_comment_preview', |
|
607 | 606 | '/{repo_name}/changeset/comment/preview', jsroute=True, |
|
608 | 607 | controller='changeset', action='preview_comment', |
|
609 | 608 | conditions={'function': check_repo, 'method': ['POST']}, |
|
610 | 609 | requirements=URL_NAME_REQUIREMENTS) |
|
611 | 610 | |
|
612 | 611 | rmap.connect('changeset_comment_delete', |
|
613 | 612 | '/{repo_name}/changeset/comment/{comment_id}/delete', |
|
614 | 613 | controller='changeset', action='delete_comment', |
|
615 | 614 | conditions={'function': check_repo, 'method': ['DELETE']}, |
|
616 | 615 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
617 | 616 | |
|
618 | 617 | rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}', |
|
619 | 618 | controller='changeset', action='changeset_info', |
|
620 | 619 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
621 | 620 | |
|
622 | 621 | rmap.connect('compare_home', |
|
623 | 622 | '/{repo_name}/compare', |
|
624 | 623 | controller='compare', action='index', |
|
625 | 624 | conditions={'function': check_repo}, |
|
626 | 625 | requirements=URL_NAME_REQUIREMENTS) |
|
627 | 626 | |
|
628 | 627 | rmap.connect('compare_url', |
|
629 | 628 | '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', |
|
630 | 629 | controller='compare', action='compare', |
|
631 | 630 | conditions={'function': check_repo}, |
|
632 | 631 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
633 | 632 | |
|
634 | 633 | rmap.connect('pullrequest_home', |
|
635 | 634 | '/{repo_name}/pull-request/new', controller='pullrequests', |
|
636 | 635 | action='index', conditions={'function': check_repo, |
|
637 | 636 | 'method': ['GET']}, |
|
638 | 637 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
639 | 638 | |
|
640 | 639 | rmap.connect('pullrequest', |
|
641 | 640 | '/{repo_name}/pull-request/new', controller='pullrequests', |
|
642 | 641 | action='create', conditions={'function': check_repo, |
|
643 | 642 | 'method': ['POST']}, |
|
644 | 643 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
645 | 644 | |
|
646 | 645 | rmap.connect('pullrequest_repo_refs', |
|
647 | 646 | '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}', |
|
648 | 647 | controller='pullrequests', |
|
649 | 648 | action='get_repo_refs', |
|
650 | 649 | conditions={'function': check_repo, 'method': ['GET']}, |
|
651 | 650 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
652 | 651 | |
|
653 | 652 | rmap.connect('pullrequest_repo_destinations', |
|
654 | 653 | '/{repo_name}/pull-request/repo-destinations', |
|
655 | 654 | controller='pullrequests', |
|
656 | 655 | action='get_repo_destinations', |
|
657 | 656 | conditions={'function': check_repo, 'method': ['GET']}, |
|
658 | 657 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
659 | 658 | |
|
660 | 659 | rmap.connect('pullrequest_show', |
|
661 | 660 | '/{repo_name}/pull-request/{pull_request_id}', |
|
662 | 661 | controller='pullrequests', |
|
663 | 662 | action='show', conditions={'function': check_repo, |
|
664 | 663 | 'method': ['GET']}, |
|
665 | 664 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
666 | 665 | |
|
667 | 666 | rmap.connect('pullrequest_update', |
|
668 | 667 | '/{repo_name}/pull-request/{pull_request_id}', |
|
669 | 668 | controller='pullrequests', |
|
670 | 669 | action='update', conditions={'function': check_repo, |
|
671 | 670 | 'method': ['PUT']}, |
|
672 | 671 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
673 | 672 | |
|
674 | 673 | rmap.connect('pullrequest_merge', |
|
675 | 674 | '/{repo_name}/pull-request/{pull_request_id}', |
|
676 | 675 | controller='pullrequests', |
|
677 | 676 | action='merge', conditions={'function': check_repo, |
|
678 | 677 | 'method': ['POST']}, |
|
679 | 678 | requirements=URL_NAME_REQUIREMENTS) |
|
680 | 679 | |
|
681 | 680 | rmap.connect('pullrequest_delete', |
|
682 | 681 | '/{repo_name}/pull-request/{pull_request_id}', |
|
683 | 682 | controller='pullrequests', |
|
684 | 683 | action='delete', conditions={'function': check_repo, |
|
685 | 684 | 'method': ['DELETE']}, |
|
686 | 685 | requirements=URL_NAME_REQUIREMENTS) |
|
687 | 686 | |
|
688 | 687 | rmap.connect('pullrequest_comment', |
|
689 | 688 | '/{repo_name}/pull-request-comment/{pull_request_id}', |
|
690 | 689 | controller='pullrequests', |
|
691 | 690 | action='comment', conditions={'function': check_repo, |
|
692 | 691 | 'method': ['POST']}, |
|
693 | 692 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
694 | 693 | |
|
695 | 694 | rmap.connect('pullrequest_comment_delete', |
|
696 | 695 | '/{repo_name}/pull-request-comment/{comment_id}/delete', |
|
697 | 696 | controller='pullrequests', action='delete_comment', |
|
698 | 697 | conditions={'function': check_repo, 'method': ['DELETE']}, |
|
699 | 698 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
700 | 699 | |
|
701 | rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True, | |
|
702 | controller='changelog', conditions={'function': check_repo}, | |
|
703 | requirements=URL_NAME_REQUIREMENTS) | |
|
704 | ||
|
705 | rmap.connect('changelog_file_home', | |
|
706 | '/{repo_name}/changelog/{revision}/{f_path}', | |
|
707 | controller='changelog', f_path=None, | |
|
708 | conditions={'function': check_repo}, | |
|
709 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
|
710 | ||
|
711 | rmap.connect('changelog_elements', '/{repo_name}/changelog_details', | |
|
712 | controller='changelog', action='changelog_elements', | |
|
713 | conditions={'function': check_repo}, | |
|
714 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
|
715 | ||
|
716 | 700 | rmap.connect('repo_fork_create_home', '/{repo_name}/fork', |
|
717 | 701 | controller='forks', action='fork_create', |
|
718 | 702 | conditions={'function': check_repo, 'method': ['POST']}, |
|
719 | 703 | requirements=URL_NAME_REQUIREMENTS) |
|
720 | 704 | |
|
721 | 705 | rmap.connect('repo_fork_home', '/{repo_name}/fork', |
|
722 | 706 | controller='forks', action='fork', |
|
723 | 707 | conditions={'function': check_repo}, |
|
724 | 708 | requirements=URL_NAME_REQUIREMENTS) |
|
725 | 709 | |
|
726 | 710 | rmap.connect('repo_forks_home', '/{repo_name}/forks', |
|
727 | 711 | controller='forks', action='forks', |
|
728 | 712 | conditions={'function': check_repo}, |
|
729 | 713 | requirements=URL_NAME_REQUIREMENTS) |
|
730 | 714 | |
|
731 | 715 | return rmap |
@@ -1,492 +1,491 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2010-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | """ |
|
22 | 22 | commit controller for RhodeCode showing changes between commits |
|
23 | 23 | """ |
|
24 | 24 | |
|
25 | 25 | import logging |
|
26 | 26 | |
|
27 | 27 | from collections import defaultdict |
|
28 | 28 | from webob.exc import HTTPForbidden, HTTPBadRequest, HTTPNotFound |
|
29 | 29 | |
|
30 | 30 | from pylons import tmpl_context as c, request, response |
|
31 | 31 | from pylons.i18n.translation import _ |
|
32 | 32 | from pylons.controllers.util import redirect |
|
33 | 33 | |
|
34 | 34 | from rhodecode.lib import auth |
|
35 | 35 | from rhodecode.lib import diffs, codeblocks |
|
36 | 36 | from rhodecode.lib.auth import ( |
|
37 | 37 | LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous) |
|
38 | 38 | from rhodecode.lib.base import BaseRepoController, render |
|
39 | 39 | from rhodecode.lib.compat import OrderedDict |
|
40 | 40 | from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError |
|
41 | 41 | import rhodecode.lib.helpers as h |
|
42 | 42 | from rhodecode.lib.utils import jsonify |
|
43 | 43 | from rhodecode.lib.utils2 import safe_unicode, safe_int |
|
44 | 44 | from rhodecode.lib.vcs.backends.base import EmptyCommit |
|
45 | 45 | from rhodecode.lib.vcs.exceptions import ( |
|
46 | 46 | RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError) |
|
47 | 47 | from rhodecode.model.db import ChangesetComment, ChangesetStatus |
|
48 | 48 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
49 | 49 | from rhodecode.model.comment import CommentsModel |
|
50 | 50 | from rhodecode.model.meta import Session |
|
51 | 51 | |
|
52 | 52 | |
|
53 | 53 | log = logging.getLogger(__name__) |
|
54 | 54 | |
|
55 | 55 | |
|
56 | 56 | def _update_with_GET(params, GET): |
|
57 | 57 | for k in ['diff1', 'diff2', 'diff']: |
|
58 | 58 | params[k] += GET.getall(k) |
|
59 | 59 | |
|
60 | 60 | |
|
61 | 61 | def get_ignore_ws(fid, GET): |
|
62 | 62 | ig_ws_global = GET.get('ignorews') |
|
63 | 63 | ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid)) |
|
64 | 64 | if ig_ws: |
|
65 | 65 | try: |
|
66 | 66 | return int(ig_ws[0].split(':')[-1]) |
|
67 | 67 | except Exception: |
|
68 | 68 | pass |
|
69 | 69 | return ig_ws_global |
|
70 | 70 | |
|
71 | 71 | |
|
72 | 72 | def _ignorews_url(GET, fileid=None): |
|
73 | 73 | fileid = str(fileid) if fileid else None |
|
74 | 74 | params = defaultdict(list) |
|
75 | 75 | _update_with_GET(params, GET) |
|
76 | 76 | label = _('Show whitespace') |
|
77 | 77 | tooltiplbl = _('Show whitespace for all diffs') |
|
78 | 78 | ig_ws = get_ignore_ws(fileid, GET) |
|
79 | 79 | ln_ctx = get_line_ctx(fileid, GET) |
|
80 | 80 | |
|
81 | 81 | if ig_ws is None: |
|
82 | 82 | params['ignorews'] += [1] |
|
83 | 83 | label = _('Ignore whitespace') |
|
84 | 84 | tooltiplbl = _('Ignore whitespace for all diffs') |
|
85 | 85 | ctx_key = 'context' |
|
86 | 86 | ctx_val = ln_ctx |
|
87 | 87 | |
|
88 | 88 | # if we have passed in ln_ctx pass it along to our params |
|
89 | 89 | if ln_ctx: |
|
90 | 90 | params[ctx_key] += [ctx_val] |
|
91 | 91 | |
|
92 | 92 | if fileid: |
|
93 | 93 | params['anchor'] = 'a_' + fileid |
|
94 | 94 | return h.link_to(label, h.url.current(**params), title=tooltiplbl, class_='tooltip') |
|
95 | 95 | |
|
96 | 96 | |
|
97 | 97 | def get_line_ctx(fid, GET): |
|
98 | 98 | ln_ctx_global = GET.get('context') |
|
99 | 99 | if fid: |
|
100 | 100 | ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid)) |
|
101 | 101 | else: |
|
102 | 102 | _ln_ctx = filter(lambda k: k.startswith('C'), GET) |
|
103 | 103 | ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global |
|
104 | 104 | if ln_ctx: |
|
105 | 105 | ln_ctx = [ln_ctx] |
|
106 | 106 | |
|
107 | 107 | if ln_ctx: |
|
108 | 108 | retval = ln_ctx[0].split(':')[-1] |
|
109 | 109 | else: |
|
110 | 110 | retval = ln_ctx_global |
|
111 | 111 | |
|
112 | 112 | try: |
|
113 | 113 | return int(retval) |
|
114 | 114 | except Exception: |
|
115 | 115 | return 3 |
|
116 | 116 | |
|
117 | 117 | |
|
118 | 118 | def _context_url(GET, fileid=None): |
|
119 | 119 | """ |
|
120 | 120 | Generates a url for context lines. |
|
121 | 121 | |
|
122 | 122 | :param fileid: |
|
123 | 123 | """ |
|
124 | 124 | |
|
125 | 125 | fileid = str(fileid) if fileid else None |
|
126 | 126 | ig_ws = get_ignore_ws(fileid, GET) |
|
127 | 127 | ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2 |
|
128 | 128 | |
|
129 | 129 | params = defaultdict(list) |
|
130 | 130 | _update_with_GET(params, GET) |
|
131 | 131 | |
|
132 | 132 | if ln_ctx > 0: |
|
133 | 133 | params['context'] += [ln_ctx] |
|
134 | 134 | |
|
135 | 135 | if ig_ws: |
|
136 | 136 | ig_ws_key = 'ignorews' |
|
137 | 137 | ig_ws_val = 1 |
|
138 | 138 | params[ig_ws_key] += [ig_ws_val] |
|
139 | 139 | |
|
140 | 140 | lbl = _('Increase context') |
|
141 | 141 | tooltiplbl = _('Increase context for all diffs') |
|
142 | 142 | |
|
143 | 143 | if fileid: |
|
144 | 144 | params['anchor'] = 'a_' + fileid |
|
145 | 145 | return h.link_to(lbl, h.url.current(**params), title=tooltiplbl, class_='tooltip') |
|
146 | 146 | |
|
147 | 147 | |
|
148 | 148 | class ChangesetController(BaseRepoController): |
|
149 | 149 | |
|
150 | 150 | def __before__(self): |
|
151 | 151 | super(ChangesetController, self).__before__() |
|
152 | c.affected_files_cut_off = 60 | |
|
153 | 152 | |
|
154 | 153 | def _index(self, commit_id_range, method): |
|
155 | 154 | c.ignorews_url = _ignorews_url |
|
156 | 155 | c.context_url = _context_url |
|
157 | 156 | c.fulldiff = fulldiff = request.GET.get('fulldiff') |
|
158 | 157 | |
|
159 | 158 | # fetch global flags of ignore ws or context lines |
|
160 | 159 | context_lcl = get_line_ctx('', request.GET) |
|
161 | 160 | ign_whitespace_lcl = get_ignore_ws('', request.GET) |
|
162 | 161 | |
|
163 | 162 | # diff_limit will cut off the whole diff if the limit is applied |
|
164 | 163 | # otherwise it will just hide the big files from the front-end |
|
165 | 164 | diff_limit = self.cut_off_limit_diff |
|
166 | 165 | file_limit = self.cut_off_limit_file |
|
167 | 166 | |
|
168 | 167 | # get ranges of commit ids if preset |
|
169 | 168 | commit_range = commit_id_range.split('...')[:2] |
|
170 | 169 | |
|
171 | 170 | try: |
|
172 | 171 | pre_load = ['affected_files', 'author', 'branch', 'date', |
|
173 | 172 | 'message', 'parents'] |
|
174 | 173 | |
|
175 | 174 | if len(commit_range) == 2: |
|
176 | 175 | commits = c.rhodecode_repo.get_commits( |
|
177 | 176 | start_id=commit_range[0], end_id=commit_range[1], |
|
178 | 177 | pre_load=pre_load) |
|
179 | 178 | commits = list(commits) |
|
180 | 179 | else: |
|
181 | 180 | commits = [c.rhodecode_repo.get_commit( |
|
182 | 181 | commit_id=commit_id_range, pre_load=pre_load)] |
|
183 | 182 | |
|
184 | 183 | c.commit_ranges = commits |
|
185 | 184 | if not c.commit_ranges: |
|
186 | 185 | raise RepositoryError( |
|
187 | 186 | 'The commit range returned an empty result') |
|
188 | 187 | except CommitDoesNotExistError: |
|
189 | 188 | msg = _('No such commit exists for this repository') |
|
190 | 189 | h.flash(msg, category='error') |
|
191 | 190 | raise HTTPNotFound() |
|
192 | 191 | except Exception: |
|
193 | 192 | log.exception("General failure") |
|
194 | 193 | raise HTTPNotFound() |
|
195 | 194 | |
|
196 | 195 | c.changes = OrderedDict() |
|
197 | 196 | c.lines_added = 0 |
|
198 | 197 | c.lines_deleted = 0 |
|
199 | 198 | |
|
200 | 199 | # auto collapse if we have more than limit |
|
201 | 200 | collapse_limit = diffs.DiffProcessor._collapse_commits_over |
|
202 | 201 | c.collapse_all_commits = len(c.commit_ranges) > collapse_limit |
|
203 | 202 | |
|
204 | 203 | c.commit_statuses = ChangesetStatus.STATUSES |
|
205 | 204 | c.inline_comments = [] |
|
206 | 205 | c.files = [] |
|
207 | 206 | |
|
208 | 207 | c.statuses = [] |
|
209 | 208 | c.comments = [] |
|
210 | 209 | c.unresolved_comments = [] |
|
211 | 210 | if len(c.commit_ranges) == 1: |
|
212 | 211 | commit = c.commit_ranges[0] |
|
213 | 212 | c.comments = CommentsModel().get_comments( |
|
214 | 213 | c.rhodecode_db_repo.repo_id, |
|
215 | 214 | revision=commit.raw_id) |
|
216 | 215 | c.statuses.append(ChangesetStatusModel().get_status( |
|
217 | 216 | c.rhodecode_db_repo.repo_id, commit.raw_id)) |
|
218 | 217 | # comments from PR |
|
219 | 218 | statuses = ChangesetStatusModel().get_statuses( |
|
220 | 219 | c.rhodecode_db_repo.repo_id, commit.raw_id, |
|
221 | 220 | with_revisions=True) |
|
222 | 221 | prs = set(st.pull_request for st in statuses |
|
223 | 222 | if st.pull_request is not None) |
|
224 | 223 | # from associated statuses, check the pull requests, and |
|
225 | 224 | # show comments from them |
|
226 | 225 | for pr in prs: |
|
227 | 226 | c.comments.extend(pr.comments) |
|
228 | 227 | |
|
229 | 228 | c.unresolved_comments = CommentsModel()\ |
|
230 | 229 | .get_commit_unresolved_todos(commit.raw_id) |
|
231 | 230 | |
|
232 | 231 | # Iterate over ranges (default commit view is always one commit) |
|
233 | 232 | for commit in c.commit_ranges: |
|
234 | 233 | c.changes[commit.raw_id] = [] |
|
235 | 234 | |
|
236 | 235 | commit2 = commit |
|
237 | 236 | commit1 = commit.parents[0] if commit.parents else EmptyCommit() |
|
238 | 237 | |
|
239 | 238 | _diff = c.rhodecode_repo.get_diff( |
|
240 | 239 | commit1, commit2, |
|
241 | 240 | ignore_whitespace=ign_whitespace_lcl, context=context_lcl) |
|
242 | 241 | diff_processor = diffs.DiffProcessor( |
|
243 | 242 | _diff, format='newdiff', diff_limit=diff_limit, |
|
244 | 243 | file_limit=file_limit, show_full_diff=fulldiff) |
|
245 | 244 | |
|
246 | 245 | commit_changes = OrderedDict() |
|
247 | 246 | if method == 'show': |
|
248 | 247 | _parsed = diff_processor.prepare() |
|
249 | 248 | c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer) |
|
250 | 249 | |
|
251 | 250 | _parsed = diff_processor.prepare() |
|
252 | 251 | |
|
253 | 252 | def _node_getter(commit): |
|
254 | 253 | def get_node(fname): |
|
255 | 254 | try: |
|
256 | 255 | return commit.get_node(fname) |
|
257 | 256 | except NodeDoesNotExistError: |
|
258 | 257 | return None |
|
259 | 258 | return get_node |
|
260 | 259 | |
|
261 | 260 | inline_comments = CommentsModel().get_inline_comments( |
|
262 | 261 | c.rhodecode_db_repo.repo_id, revision=commit.raw_id) |
|
263 | 262 | c.inline_cnt = CommentsModel().get_inline_comments_count( |
|
264 | 263 | inline_comments) |
|
265 | 264 | |
|
266 | 265 | diffset = codeblocks.DiffSet( |
|
267 | 266 | repo_name=c.repo_name, |
|
268 | 267 | source_node_getter=_node_getter(commit1), |
|
269 | 268 | target_node_getter=_node_getter(commit2), |
|
270 | 269 | comments=inline_comments) |
|
271 | 270 | diffset = diffset.render_patchset( |
|
272 | 271 | _parsed, commit1.raw_id, commit2.raw_id) |
|
273 | 272 | |
|
274 | 273 | c.changes[commit.raw_id] = diffset |
|
275 | 274 | else: |
|
276 | 275 | # downloads/raw we only need RAW diff nothing else |
|
277 | 276 | diff = diff_processor.as_raw() |
|
278 | 277 | c.changes[commit.raw_id] = [None, None, None, None, diff, None, None] |
|
279 | 278 | |
|
280 | 279 | # sort comments by how they were generated |
|
281 | 280 | c.comments = sorted(c.comments, key=lambda x: x.comment_id) |
|
282 | 281 | |
|
283 | 282 | if len(c.commit_ranges) == 1: |
|
284 | 283 | c.commit = c.commit_ranges[0] |
|
285 | 284 | c.parent_tmpl = ''.join( |
|
286 | 285 | '# Parent %s\n' % x.raw_id for x in c.commit.parents) |
|
287 | 286 | if method == 'download': |
|
288 | 287 | response.content_type = 'text/plain' |
|
289 | 288 | response.content_disposition = ( |
|
290 | 289 | 'attachment; filename=%s.diff' % commit_id_range[:12]) |
|
291 | 290 | return diff |
|
292 | 291 | elif method == 'patch': |
|
293 | 292 | response.content_type = 'text/plain' |
|
294 | 293 | c.diff = safe_unicode(diff) |
|
295 | 294 | return render('changeset/patch_changeset.mako') |
|
296 | 295 | elif method == 'raw': |
|
297 | 296 | response.content_type = 'text/plain' |
|
298 | 297 | return diff |
|
299 | 298 | elif method == 'show': |
|
300 | 299 | if len(c.commit_ranges) == 1: |
|
301 | 300 | return render('changeset/changeset.mako') |
|
302 | 301 | else: |
|
303 | 302 | c.ancestor = None |
|
304 | 303 | c.target_repo = c.rhodecode_db_repo |
|
305 | 304 | return render('changeset/changeset_range.mako') |
|
306 | 305 | |
|
307 | 306 | @LoginRequired() |
|
308 | 307 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
309 | 308 | 'repository.admin') |
|
310 | 309 | def index(self, revision, method='show'): |
|
311 | 310 | return self._index(revision, method=method) |
|
312 | 311 | |
|
313 | 312 | @LoginRequired() |
|
314 | 313 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
315 | 314 | 'repository.admin') |
|
316 | 315 | def changeset_raw(self, revision): |
|
317 | 316 | return self._index(revision, method='raw') |
|
318 | 317 | |
|
319 | 318 | @LoginRequired() |
|
320 | 319 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
321 | 320 | 'repository.admin') |
|
322 | 321 | def changeset_patch(self, revision): |
|
323 | 322 | return self._index(revision, method='patch') |
|
324 | 323 | |
|
325 | 324 | @LoginRequired() |
|
326 | 325 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
327 | 326 | 'repository.admin') |
|
328 | 327 | def changeset_download(self, revision): |
|
329 | 328 | return self._index(revision, method='download') |
|
330 | 329 | |
|
331 | 330 | @LoginRequired() |
|
332 | 331 | @NotAnonymous() |
|
333 | 332 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
334 | 333 | 'repository.admin') |
|
335 | 334 | @auth.CSRFRequired() |
|
336 | 335 | @jsonify |
|
337 | 336 | def comment(self, repo_name, revision): |
|
338 | 337 | commit_id = revision |
|
339 | 338 | status = request.POST.get('changeset_status', None) |
|
340 | 339 | text = request.POST.get('text') |
|
341 | 340 | comment_type = request.POST.get('comment_type') |
|
342 | 341 | resolves_comment_id = request.POST.get('resolves_comment_id', None) |
|
343 | 342 | |
|
344 | 343 | if status: |
|
345 | 344 | text = text or (_('Status change %(transition_icon)s %(status)s') |
|
346 | 345 | % {'transition_icon': '>', |
|
347 | 346 | 'status': ChangesetStatus.get_status_lbl(status)}) |
|
348 | 347 | |
|
349 | 348 | multi_commit_ids = [] |
|
350 | 349 | for _commit_id in request.POST.get('commit_ids', '').split(','): |
|
351 | 350 | if _commit_id not in ['', None, EmptyCommit.raw_id]: |
|
352 | 351 | if _commit_id not in multi_commit_ids: |
|
353 | 352 | multi_commit_ids.append(_commit_id) |
|
354 | 353 | |
|
355 | 354 | commit_ids = multi_commit_ids or [commit_id] |
|
356 | 355 | |
|
357 | 356 | comment = None |
|
358 | 357 | for current_id in filter(None, commit_ids): |
|
359 | 358 | c.co = comment = CommentsModel().create( |
|
360 | 359 | text=text, |
|
361 | 360 | repo=c.rhodecode_db_repo.repo_id, |
|
362 | 361 | user=c.rhodecode_user.user_id, |
|
363 | 362 | commit_id=current_id, |
|
364 | 363 | f_path=request.POST.get('f_path'), |
|
365 | 364 | line_no=request.POST.get('line'), |
|
366 | 365 | status_change=(ChangesetStatus.get_status_lbl(status) |
|
367 | 366 | if status else None), |
|
368 | 367 | status_change_type=status, |
|
369 | 368 | comment_type=comment_type, |
|
370 | 369 | resolves_comment_id=resolves_comment_id |
|
371 | 370 | ) |
|
372 | 371 | |
|
373 | 372 | # get status if set ! |
|
374 | 373 | if status: |
|
375 | 374 | # if latest status was from pull request and it's closed |
|
376 | 375 | # disallow changing status ! |
|
377 | 376 | # dont_allow_on_closed_pull_request = True ! |
|
378 | 377 | |
|
379 | 378 | try: |
|
380 | 379 | ChangesetStatusModel().set_status( |
|
381 | 380 | c.rhodecode_db_repo.repo_id, |
|
382 | 381 | status, |
|
383 | 382 | c.rhodecode_user.user_id, |
|
384 | 383 | comment, |
|
385 | 384 | revision=current_id, |
|
386 | 385 | dont_allow_on_closed_pull_request=True |
|
387 | 386 | ) |
|
388 | 387 | except StatusChangeOnClosedPullRequestError: |
|
389 | 388 | msg = _('Changing the status of a commit associated with ' |
|
390 | 389 | 'a closed pull request is not allowed') |
|
391 | 390 | log.exception(msg) |
|
392 | 391 | h.flash(msg, category='warning') |
|
393 | 392 | return redirect(h.url( |
|
394 | 393 | 'changeset_home', repo_name=repo_name, |
|
395 | 394 | revision=current_id)) |
|
396 | 395 | |
|
397 | 396 | # finalize, commit and redirect |
|
398 | 397 | Session().commit() |
|
399 | 398 | |
|
400 | 399 | data = { |
|
401 | 400 | 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))), |
|
402 | 401 | } |
|
403 | 402 | if comment: |
|
404 | 403 | data.update(comment.get_dict()) |
|
405 | 404 | data.update({'rendered_text': |
|
406 | 405 | render('changeset/changeset_comment_block.mako')}) |
|
407 | 406 | |
|
408 | 407 | return data |
|
409 | 408 | |
|
410 | 409 | @LoginRequired() |
|
411 | 410 | @NotAnonymous() |
|
412 | 411 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
413 | 412 | 'repository.admin') |
|
414 | 413 | @auth.CSRFRequired() |
|
415 | 414 | def preview_comment(self): |
|
416 | 415 | # Technically a CSRF token is not needed as no state changes with this |
|
417 | 416 | # call. However, as this is a POST is better to have it, so automated |
|
418 | 417 | # tools don't flag it as potential CSRF. |
|
419 | 418 | # Post is required because the payload could be bigger than the maximum |
|
420 | 419 | # allowed by GET. |
|
421 | 420 | if not request.environ.get('HTTP_X_PARTIAL_XHR'): |
|
422 | 421 | raise HTTPBadRequest() |
|
423 | 422 | text = request.POST.get('text') |
|
424 | 423 | renderer = request.POST.get('renderer') or 'rst' |
|
425 | 424 | if text: |
|
426 | 425 | return h.render(text, renderer=renderer, mentions=True) |
|
427 | 426 | return '' |
|
428 | 427 | |
|
429 | 428 | @LoginRequired() |
|
430 | 429 | @NotAnonymous() |
|
431 | 430 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
432 | 431 | 'repository.admin') |
|
433 | 432 | @auth.CSRFRequired() |
|
434 | 433 | @jsonify |
|
435 | 434 | def delete_comment(self, repo_name, comment_id): |
|
436 | 435 | comment = ChangesetComment.get_or_404(safe_int(comment_id)) |
|
437 | 436 | if not comment: |
|
438 | 437 | log.debug('Comment with id:%s not found, skipping', comment_id) |
|
439 | 438 | # comment already deleted in another call probably |
|
440 | 439 | return True |
|
441 | 440 | |
|
442 | 441 | is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name) |
|
443 | 442 | super_admin = h.HasPermissionAny('hg.admin')() |
|
444 | 443 | comment_owner = (comment.author.user_id == c.rhodecode_user.user_id) |
|
445 | 444 | is_repo_comment = comment.repo.repo_name == c.repo_name |
|
446 | 445 | comment_repo_admin = is_repo_admin and is_repo_comment |
|
447 | 446 | |
|
448 | 447 | if super_admin or comment_owner or comment_repo_admin: |
|
449 | 448 | CommentsModel().delete(comment=comment, user=c.rhodecode_user) |
|
450 | 449 | Session().commit() |
|
451 | 450 | return True |
|
452 | 451 | else: |
|
453 | 452 | log.warning('No permissions for user %s to delete comment_id: %s', |
|
454 | 453 | c.rhodecode_user, comment_id) |
|
455 | 454 | raise HTTPNotFound() |
|
456 | 455 | |
|
457 | 456 | @LoginRequired() |
|
458 | 457 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
459 | 458 | 'repository.admin') |
|
460 | 459 | @jsonify |
|
461 | 460 | def changeset_info(self, repo_name, revision): |
|
462 | 461 | if request.is_xhr: |
|
463 | 462 | try: |
|
464 | 463 | return c.rhodecode_repo.get_commit(commit_id=revision) |
|
465 | 464 | except CommitDoesNotExistError as e: |
|
466 | 465 | return EmptyCommit(message=str(e)) |
|
467 | 466 | else: |
|
468 | 467 | raise HTTPBadRequest() |
|
469 | 468 | |
|
470 | 469 | @LoginRequired() |
|
471 | 470 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
472 | 471 | 'repository.admin') |
|
473 | 472 | @jsonify |
|
474 | 473 | def changeset_children(self, repo_name, revision): |
|
475 | 474 | if request.is_xhr: |
|
476 | 475 | commit = c.rhodecode_repo.get_commit(commit_id=revision) |
|
477 | 476 | result = {"results": commit.children} |
|
478 | 477 | return result |
|
479 | 478 | else: |
|
480 | 479 | raise HTTPBadRequest() |
|
481 | 480 | |
|
482 | 481 | @LoginRequired() |
|
483 | 482 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
484 | 483 | 'repository.admin') |
|
485 | 484 | @jsonify |
|
486 | 485 | def changeset_parents(self, repo_name, revision): |
|
487 | 486 | if request.is_xhr: |
|
488 | 487 | commit = c.rhodecode_repo.get_commit(commit_id=revision) |
|
489 | 488 | result = {"results": commit.parents} |
|
490 | 489 | return result |
|
491 | 490 | else: |
|
492 | 491 | raise HTTPBadRequest() |
@@ -1,306 +1,304 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2010-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | """ |
|
22 | 22 | Journal / user event log controller for rhodecode |
|
23 | 23 | """ |
|
24 | 24 | |
|
25 | 25 | import logging |
|
26 | 26 | from itertools import groupby |
|
27 | 27 | |
|
28 | 28 | from sqlalchemy import or_ |
|
29 | 29 | from sqlalchemy.orm import joinedload |
|
30 | 30 | |
|
31 | 31 | from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed |
|
32 | 32 | |
|
33 | 33 | from webob.exc import HTTPBadRequest |
|
34 | 34 | from pylons import request, tmpl_context as c, response, url |
|
35 | 35 | from pylons.i18n.translation import _ |
|
36 | 36 | |
|
37 | 37 | from rhodecode.model.db import UserLog, UserFollowing, User, UserApiKeys |
|
38 | 38 | from rhodecode.model.meta import Session |
|
39 | 39 | import rhodecode.lib.helpers as h |
|
40 | 40 | from rhodecode.lib.helpers import Page |
|
41 | 41 | from rhodecode.lib.user_log_filter import user_log_filter |
|
42 | 42 | from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired |
|
43 | 43 | from rhodecode.lib.base import BaseController, render |
|
44 | 44 | from rhodecode.lib.utils2 import safe_int, AttributeDict |
|
45 | 45 | |
|
46 | 46 | log = logging.getLogger(__name__) |
|
47 | 47 | |
|
48 | 48 | |
|
49 | 49 | class JournalController(BaseController): |
|
50 | 50 | |
|
51 | 51 | def __before__(self): |
|
52 | 52 | super(JournalController, self).__before__() |
|
53 | 53 | self.language = 'en-us' |
|
54 | 54 | self.ttl = "5" |
|
55 | 55 | self.feed_nr = 20 |
|
56 | 56 | c.search_term = request.GET.get('filter') |
|
57 | 57 | |
|
58 | 58 | def _get_daily_aggregate(self, journal): |
|
59 | 59 | groups = [] |
|
60 | 60 | for k, g in groupby(journal, lambda x: x.action_as_day): |
|
61 | 61 | user_group = [] |
|
62 | 62 | #groupby username if it's a present value, else fallback to journal username |
|
63 | 63 | for _, g2 in groupby(list(g), lambda x: x.user.username if x.user else x.username): |
|
64 | 64 | l = list(g2) |
|
65 | 65 | user_group.append((l[0].user, l)) |
|
66 | 66 | |
|
67 | 67 | groups.append((k, user_group,)) |
|
68 | 68 | |
|
69 | 69 | return groups |
|
70 | 70 | |
|
71 | 71 | def _get_journal_data(self, following_repos): |
|
72 | 72 | repo_ids = [x.follows_repository.repo_id for x in following_repos |
|
73 | 73 | if x.follows_repository is not None] |
|
74 | 74 | user_ids = [x.follows_user.user_id for x in following_repos |
|
75 | 75 | if x.follows_user is not None] |
|
76 | 76 | |
|
77 | 77 | filtering_criterion = None |
|
78 | 78 | |
|
79 | 79 | if repo_ids and user_ids: |
|
80 | 80 | filtering_criterion = or_(UserLog.repository_id.in_(repo_ids), |
|
81 | 81 | UserLog.user_id.in_(user_ids)) |
|
82 | 82 | if repo_ids and not user_ids: |
|
83 | 83 | filtering_criterion = UserLog.repository_id.in_(repo_ids) |
|
84 | 84 | if not repo_ids and user_ids: |
|
85 | 85 | filtering_criterion = UserLog.user_id.in_(user_ids) |
|
86 | 86 | if filtering_criterion is not None: |
|
87 | 87 | journal = self.sa.query(UserLog)\ |
|
88 | 88 | .options(joinedload(UserLog.user))\ |
|
89 | 89 | .options(joinedload(UserLog.repository)) |
|
90 | 90 | #filter |
|
91 | 91 | try: |
|
92 | 92 | journal = user_log_filter(journal, c.search_term) |
|
93 | 93 | except Exception: |
|
94 | 94 | # we want this to crash for now |
|
95 | 95 | raise |
|
96 | 96 | journal = journal.filter(filtering_criterion)\ |
|
97 | 97 | .order_by(UserLog.action_date.desc()) |
|
98 | 98 | else: |
|
99 | 99 | journal = [] |
|
100 | 100 | |
|
101 | 101 | return journal |
|
102 | 102 | |
|
103 | 103 | def _atom_feed(self, repos, public=True): |
|
104 | 104 | journal = self._get_journal_data(repos) |
|
105 | 105 | if public: |
|
106 | 106 | _link = url('public_journal_atom', qualified=True) |
|
107 | 107 | _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'), |
|
108 | 108 | 'atom feed') |
|
109 | 109 | else: |
|
110 | 110 | _link = url('journal_atom', qualified=True) |
|
111 | 111 | _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed') |
|
112 | 112 | |
|
113 | 113 | feed = Atom1Feed(title=_desc, |
|
114 | 114 | link=_link, |
|
115 | 115 | description=_desc, |
|
116 | 116 | language=self.language, |
|
117 | 117 | ttl=self.ttl) |
|
118 | 118 | |
|
119 | 119 | for entry in journal[:self.feed_nr]: |
|
120 | 120 | user = entry.user |
|
121 | 121 | if user is None: |
|
122 | 122 | #fix deleted users |
|
123 | 123 | user = AttributeDict({'short_contact': entry.username, |
|
124 | 124 | 'email': '', |
|
125 | 125 | 'full_contact': ''}) |
|
126 | 126 | action, action_extra, ico = h.action_parser(entry, feed=True) |
|
127 | 127 | title = "%s - %s %s" % (user.short_contact, action(), |
|
128 | 128 | entry.repository.repo_name) |
|
129 | 129 | desc = action_extra() |
|
130 | 130 | _url = None |
|
131 | 131 | if entry.repository is not None: |
|
132 |
_url = url('changelog |
|
|
133 |
repo_name=entry.repository.repo_name |
|
|
134 | qualified=True) | |
|
132 | _url = h.route_url('repo_changelog', | |
|
133 | repo_name=entry.repository.repo_name) | |
|
135 | 134 | |
|
136 | 135 | feed.add_item(title=title, |
|
137 | 136 | pubdate=entry.action_date, |
|
138 | 137 | link=_url or url('', qualified=True), |
|
139 | 138 | author_email=user.email, |
|
140 | 139 | author_name=user.full_contact, |
|
141 | 140 | description=desc) |
|
142 | 141 | |
|
143 | 142 | response.content_type = feed.mime_type |
|
144 | 143 | return feed.writeString('utf-8') |
|
145 | 144 | |
|
146 | 145 | def _rss_feed(self, repos, public=True): |
|
147 | 146 | journal = self._get_journal_data(repos) |
|
148 | 147 | if public: |
|
149 | 148 | _link = url('public_journal_atom', qualified=True) |
|
150 | 149 | _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'), |
|
151 | 150 | 'rss feed') |
|
152 | 151 | else: |
|
153 | 152 | _link = url('journal_atom', qualified=True) |
|
154 | 153 | _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'rss feed') |
|
155 | 154 | |
|
156 | 155 | feed = Rss201rev2Feed(title=_desc, |
|
157 | 156 | link=_link, |
|
158 | 157 | description=_desc, |
|
159 | 158 | language=self.language, |
|
160 | 159 | ttl=self.ttl) |
|
161 | 160 | |
|
162 | 161 | for entry in journal[:self.feed_nr]: |
|
163 | 162 | user = entry.user |
|
164 | 163 | if user is None: |
|
165 | 164 | #fix deleted users |
|
166 | 165 | user = AttributeDict({'short_contact': entry.username, |
|
167 | 166 | 'email': '', |
|
168 | 167 | 'full_contact': ''}) |
|
169 | 168 | action, action_extra, ico = h.action_parser(entry, feed=True) |
|
170 | 169 | title = "%s - %s %s" % (user.short_contact, action(), |
|
171 | 170 | entry.repository.repo_name) |
|
172 | 171 | desc = action_extra() |
|
173 | 172 | _url = None |
|
174 | 173 | if entry.repository is not None: |
|
175 |
_url = url('changelog |
|
|
176 |
repo_name=entry.repository.repo_name |
|
|
177 | qualified=True) | |
|
174 | _url = h.route_url('repo_changelog', | |
|
175 | repo_name=entry.repository.repo_name) | |
|
178 | 176 | |
|
179 | 177 | feed.add_item(title=title, |
|
180 | 178 | pubdate=entry.action_date, |
|
181 | 179 | link=_url or url('', qualified=True), |
|
182 | 180 | author_email=user.email, |
|
183 | 181 | author_name=user.full_contact, |
|
184 | 182 | description=desc) |
|
185 | 183 | |
|
186 | 184 | response.content_type = feed.mime_type |
|
187 | 185 | return feed.writeString('utf-8') |
|
188 | 186 | |
|
189 | 187 | @LoginRequired() |
|
190 | 188 | @NotAnonymous() |
|
191 | 189 | def index(self): |
|
192 | 190 | # Return a rendered template |
|
193 | 191 | p = safe_int(request.GET.get('page', 1), 1) |
|
194 | 192 | c.user = User.get(c.rhodecode_user.user_id) |
|
195 | 193 | following = self.sa.query(UserFollowing)\ |
|
196 | 194 | .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\ |
|
197 | 195 | .options(joinedload(UserFollowing.follows_repository))\ |
|
198 | 196 | .all() |
|
199 | 197 | |
|
200 | 198 | journal = self._get_journal_data(following) |
|
201 | 199 | |
|
202 | 200 | def url_generator(**kw): |
|
203 | 201 | return url.current(filter=c.search_term, **kw) |
|
204 | 202 | |
|
205 | 203 | c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator) |
|
206 | 204 | c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager) |
|
207 | 205 | |
|
208 | 206 | c.journal_data = render('journal/journal_data.mako') |
|
209 | 207 | if request.is_xhr: |
|
210 | 208 | return c.journal_data |
|
211 | 209 | |
|
212 | 210 | return render('journal/journal.mako') |
|
213 | 211 | |
|
214 | 212 | @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED]) |
|
215 | 213 | @NotAnonymous() |
|
216 | 214 | def journal_atom(self): |
|
217 | 215 | """ |
|
218 | 216 | Produce an atom-1.0 feed via feedgenerator module |
|
219 | 217 | """ |
|
220 | 218 | following = self.sa.query(UserFollowing)\ |
|
221 | 219 | .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\ |
|
222 | 220 | .options(joinedload(UserFollowing.follows_repository))\ |
|
223 | 221 | .all() |
|
224 | 222 | return self._atom_feed(following, public=False) |
|
225 | 223 | |
|
226 | 224 | @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED]) |
|
227 | 225 | @NotAnonymous() |
|
228 | 226 | def journal_rss(self): |
|
229 | 227 | """ |
|
230 | 228 | Produce an rss feed via feedgenerator module |
|
231 | 229 | """ |
|
232 | 230 | following = self.sa.query(UserFollowing)\ |
|
233 | 231 | .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\ |
|
234 | 232 | .options(joinedload(UserFollowing.follows_repository))\ |
|
235 | 233 | .all() |
|
236 | 234 | return self._rss_feed(following, public=False) |
|
237 | 235 | |
|
238 | 236 | @CSRFRequired() |
|
239 | 237 | @LoginRequired() |
|
240 | 238 | @NotAnonymous() |
|
241 | 239 | def toggle_following(self): |
|
242 | 240 | user_id = request.POST.get('follows_user_id') |
|
243 | 241 | if user_id: |
|
244 | 242 | try: |
|
245 | 243 | self.scm_model.toggle_following_user( |
|
246 | 244 | user_id, c.rhodecode_user.user_id) |
|
247 | 245 | Session().commit() |
|
248 | 246 | return 'ok' |
|
249 | 247 | except Exception: |
|
250 | 248 | raise HTTPBadRequest() |
|
251 | 249 | |
|
252 | 250 | repo_id = request.POST.get('follows_repo_id') |
|
253 | 251 | if repo_id: |
|
254 | 252 | try: |
|
255 | 253 | self.scm_model.toggle_following_repo( |
|
256 | 254 | repo_id, c.rhodecode_user.user_id) |
|
257 | 255 | Session().commit() |
|
258 | 256 | return 'ok' |
|
259 | 257 | except Exception: |
|
260 | 258 | raise HTTPBadRequest() |
|
261 | 259 | |
|
262 | 260 | |
|
263 | 261 | @LoginRequired() |
|
264 | 262 | def public_journal(self): |
|
265 | 263 | # Return a rendered template |
|
266 | 264 | p = safe_int(request.GET.get('page', 1), 1) |
|
267 | 265 | |
|
268 | 266 | c.following = self.sa.query(UserFollowing)\ |
|
269 | 267 | .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\ |
|
270 | 268 | .options(joinedload(UserFollowing.follows_repository))\ |
|
271 | 269 | .all() |
|
272 | 270 | |
|
273 | 271 | journal = self._get_journal_data(c.following) |
|
274 | 272 | |
|
275 | 273 | c.journal_pager = Page(journal, page=p, items_per_page=20) |
|
276 | 274 | |
|
277 | 275 | c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager) |
|
278 | 276 | |
|
279 | 277 | c.journal_data = render('journal/journal_data.mako') |
|
280 | 278 | if request.is_xhr: |
|
281 | 279 | return c.journal_data |
|
282 | 280 | return render('journal/public_journal.mako') |
|
283 | 281 | |
|
284 | 282 | @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED]) |
|
285 | 283 | def public_journal_atom(self): |
|
286 | 284 | """ |
|
287 | 285 | Produce an atom-1.0 feed via feedgenerator module |
|
288 | 286 | """ |
|
289 | 287 | c.following = self.sa.query(UserFollowing)\ |
|
290 | 288 | .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\ |
|
291 | 289 | .options(joinedload(UserFollowing.follows_repository))\ |
|
292 | 290 | .all() |
|
293 | 291 | |
|
294 | 292 | return self._atom_feed(c.following) |
|
295 | 293 | |
|
296 | 294 | @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED]) |
|
297 | 295 | def public_journal_rss(self): |
|
298 | 296 | """ |
|
299 | 297 | Produce an rss2 feed via feedgenerator module |
|
300 | 298 | """ |
|
301 | 299 | c.following = self.sa.query(UserFollowing)\ |
|
302 | 300 | .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\ |
|
303 | 301 | .options(joinedload(UserFollowing.follows_repository))\ |
|
304 | 302 | .all() |
|
305 | 303 | |
|
306 | 304 | return self._rss_feed(c.following) |
@@ -1,632 +1,634 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2010-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | """ |
|
22 | 22 | The base Controller API |
|
23 | 23 | Provides the BaseController class for subclassing. And usage in different |
|
24 | 24 | controllers |
|
25 | 25 | """ |
|
26 | 26 | |
|
27 | 27 | import logging |
|
28 | 28 | import socket |
|
29 | 29 | |
|
30 | 30 | import ipaddress |
|
31 | 31 | import pyramid.threadlocal |
|
32 | 32 | |
|
33 | 33 | from paste.auth.basic import AuthBasicAuthenticator |
|
34 | 34 | from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception |
|
35 | 35 | from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION |
|
36 | 36 | from pylons import config, tmpl_context as c, request, url |
|
37 | 37 | from pylons.controllers import WSGIController |
|
38 | 38 | from pylons.controllers.util import redirect |
|
39 | 39 | from pylons.i18n import translation |
|
40 | 40 | # marcink: don't remove this import |
|
41 | 41 | from pylons.templating import render_mako as render # noqa |
|
42 | 42 | from pylons.i18n.translation import _ |
|
43 | 43 | from webob.exc import HTTPFound |
|
44 | 44 | |
|
45 | 45 | |
|
46 | 46 | import rhodecode |
|
47 | 47 | from rhodecode.authentication.base import VCS_TYPE |
|
48 | 48 | from rhodecode.lib import auth, utils2 |
|
49 | 49 | from rhodecode.lib import helpers as h |
|
50 | 50 | from rhodecode.lib.auth import AuthUser, CookieStoreWrapper |
|
51 | 51 | from rhodecode.lib.exceptions import UserCreationError |
|
52 | 52 | from rhodecode.lib.utils import ( |
|
53 | 53 | get_repo_slug, set_rhodecode_config, password_changed, |
|
54 | 54 | get_enabled_hook_classes) |
|
55 | 55 | from rhodecode.lib.utils2 import ( |
|
56 | 56 | str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist) |
|
57 | 57 | from rhodecode.lib.vcs.exceptions import RepositoryRequirementError |
|
58 | 58 | from rhodecode.model import meta |
|
59 | 59 | from rhodecode.model.db import Repository, User, ChangesetComment |
|
60 | 60 | from rhodecode.model.notification import NotificationModel |
|
61 | 61 | from rhodecode.model.scm import ScmModel |
|
62 | 62 | from rhodecode.model.settings import VcsSettingsModel, SettingsModel |
|
63 | 63 | |
|
64 | 64 | |
|
65 | 65 | log = logging.getLogger(__name__) |
|
66 | 66 | |
|
67 | 67 | |
|
68 | 68 | def _filter_proxy(ip): |
|
69 | 69 | """ |
|
70 | 70 | Passed in IP addresses in HEADERS can be in a special format of multiple |
|
71 | 71 | ips. Those comma separated IPs are passed from various proxies in the |
|
72 | 72 | chain of request processing. The left-most being the original client. |
|
73 | 73 | We only care about the first IP which came from the org. client. |
|
74 | 74 | |
|
75 | 75 | :param ip: ip string from headers |
|
76 | 76 | """ |
|
77 | 77 | if ',' in ip: |
|
78 | 78 | _ips = ip.split(',') |
|
79 | 79 | _first_ip = _ips[0].strip() |
|
80 | 80 | log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip) |
|
81 | 81 | return _first_ip |
|
82 | 82 | return ip |
|
83 | 83 | |
|
84 | 84 | |
|
85 | 85 | def _filter_port(ip): |
|
86 | 86 | """ |
|
87 | 87 | Removes a port from ip, there are 4 main cases to handle here. |
|
88 | 88 | - ipv4 eg. 127.0.0.1 |
|
89 | 89 | - ipv6 eg. ::1 |
|
90 | 90 | - ipv4+port eg. 127.0.0.1:8080 |
|
91 | 91 | - ipv6+port eg. [::1]:8080 |
|
92 | 92 | |
|
93 | 93 | :param ip: |
|
94 | 94 | """ |
|
95 | 95 | def is_ipv6(ip_addr): |
|
96 | 96 | if hasattr(socket, 'inet_pton'): |
|
97 | 97 | try: |
|
98 | 98 | socket.inet_pton(socket.AF_INET6, ip_addr) |
|
99 | 99 | except socket.error: |
|
100 | 100 | return False |
|
101 | 101 | else: |
|
102 | 102 | # fallback to ipaddress |
|
103 | 103 | try: |
|
104 | 104 | ipaddress.IPv6Address(safe_unicode(ip_addr)) |
|
105 | 105 | except Exception: |
|
106 | 106 | return False |
|
107 | 107 | return True |
|
108 | 108 | |
|
109 | 109 | if ':' not in ip: # must be ipv4 pure ip |
|
110 | 110 | return ip |
|
111 | 111 | |
|
112 | 112 | if '[' in ip and ']' in ip: # ipv6 with port |
|
113 | 113 | return ip.split(']')[0][1:].lower() |
|
114 | 114 | |
|
115 | 115 | # must be ipv6 or ipv4 with port |
|
116 | 116 | if is_ipv6(ip): |
|
117 | 117 | return ip |
|
118 | 118 | else: |
|
119 | 119 | ip, _port = ip.split(':')[:2] # means ipv4+port |
|
120 | 120 | return ip |
|
121 | 121 | |
|
122 | 122 | |
|
123 | 123 | def get_ip_addr(environ): |
|
124 | 124 | proxy_key = 'HTTP_X_REAL_IP' |
|
125 | 125 | proxy_key2 = 'HTTP_X_FORWARDED_FOR' |
|
126 | 126 | def_key = 'REMOTE_ADDR' |
|
127 | 127 | _filters = lambda x: _filter_port(_filter_proxy(x)) |
|
128 | 128 | |
|
129 | 129 | ip = environ.get(proxy_key) |
|
130 | 130 | if ip: |
|
131 | 131 | return _filters(ip) |
|
132 | 132 | |
|
133 | 133 | ip = environ.get(proxy_key2) |
|
134 | 134 | if ip: |
|
135 | 135 | return _filters(ip) |
|
136 | 136 | |
|
137 | 137 | ip = environ.get(def_key, '0.0.0.0') |
|
138 | 138 | return _filters(ip) |
|
139 | 139 | |
|
140 | 140 | |
|
141 | 141 | def get_server_ip_addr(environ, log_errors=True): |
|
142 | 142 | hostname = environ.get('SERVER_NAME') |
|
143 | 143 | try: |
|
144 | 144 | return socket.gethostbyname(hostname) |
|
145 | 145 | except Exception as e: |
|
146 | 146 | if log_errors: |
|
147 | 147 | # in some cases this lookup is not possible, and we don't want to |
|
148 | 148 | # make it an exception in logs |
|
149 | 149 | log.exception('Could not retrieve server ip address: %s', e) |
|
150 | 150 | return hostname |
|
151 | 151 | |
|
152 | 152 | |
|
153 | 153 | def get_server_port(environ): |
|
154 | 154 | return environ.get('SERVER_PORT') |
|
155 | 155 | |
|
156 | 156 | |
|
157 | 157 | def get_access_path(environ): |
|
158 | 158 | path = environ.get('PATH_INFO') |
|
159 | 159 | org_req = environ.get('pylons.original_request') |
|
160 | 160 | if org_req: |
|
161 | 161 | path = org_req.environ.get('PATH_INFO') |
|
162 | 162 | return path |
|
163 | 163 | |
|
164 | 164 | |
|
165 | 165 | def get_user_agent(environ): |
|
166 | 166 | return environ.get('HTTP_USER_AGENT') |
|
167 | 167 | |
|
168 | 168 | |
|
169 | 169 | def vcs_operation_context( |
|
170 | 170 | environ, repo_name, username, action, scm, check_locking=True, |
|
171 | 171 | is_shadow_repo=False): |
|
172 | 172 | """ |
|
173 | 173 | Generate the context for a vcs operation, e.g. push or pull. |
|
174 | 174 | |
|
175 | 175 | This context is passed over the layers so that hooks triggered by the |
|
176 | 176 | vcs operation know details like the user, the user's IP address etc. |
|
177 | 177 | |
|
178 | 178 | :param check_locking: Allows to switch of the computation of the locking |
|
179 | 179 | data. This serves mainly the need of the simplevcs middleware to be |
|
180 | 180 | able to disable this for certain operations. |
|
181 | 181 | |
|
182 | 182 | """ |
|
183 | 183 | # Tri-state value: False: unlock, None: nothing, True: lock |
|
184 | 184 | make_lock = None |
|
185 | 185 | locked_by = [None, None, None] |
|
186 | 186 | is_anonymous = username == User.DEFAULT_USER |
|
187 | 187 | if not is_anonymous and check_locking: |
|
188 | 188 | log.debug('Checking locking on repository "%s"', repo_name) |
|
189 | 189 | user = User.get_by_username(username) |
|
190 | 190 | repo = Repository.get_by_repo_name(repo_name) |
|
191 | 191 | make_lock, __, locked_by = repo.get_locking_state( |
|
192 | 192 | action, user.user_id) |
|
193 | 193 | |
|
194 | 194 | settings_model = VcsSettingsModel(repo=repo_name) |
|
195 | 195 | ui_settings = settings_model.get_ui_settings() |
|
196 | 196 | |
|
197 | 197 | extras = { |
|
198 | 198 | 'ip': get_ip_addr(environ), |
|
199 | 199 | 'username': username, |
|
200 | 200 | 'action': action, |
|
201 | 201 | 'repository': repo_name, |
|
202 | 202 | 'scm': scm, |
|
203 | 203 | 'config': rhodecode.CONFIG['__file__'], |
|
204 | 204 | 'make_lock': make_lock, |
|
205 | 205 | 'locked_by': locked_by, |
|
206 | 206 | 'server_url': utils2.get_server_url(environ), |
|
207 | 207 | 'user_agent': get_user_agent(environ), |
|
208 | 208 | 'hooks': get_enabled_hook_classes(ui_settings), |
|
209 | 209 | 'is_shadow_repo': is_shadow_repo, |
|
210 | 210 | } |
|
211 | 211 | return extras |
|
212 | 212 | |
|
213 | 213 | |
|
214 | 214 | class BasicAuth(AuthBasicAuthenticator): |
|
215 | 215 | |
|
216 | 216 | def __init__(self, realm, authfunc, registry, auth_http_code=None, |
|
217 | 217 | initial_call_detection=False, acl_repo_name=None): |
|
218 | 218 | self.realm = realm |
|
219 | 219 | self.initial_call = initial_call_detection |
|
220 | 220 | self.authfunc = authfunc |
|
221 | 221 | self.registry = registry |
|
222 | 222 | self.acl_repo_name = acl_repo_name |
|
223 | 223 | self._rc_auth_http_code = auth_http_code |
|
224 | 224 | |
|
225 | 225 | def _get_response_from_code(self, http_code): |
|
226 | 226 | try: |
|
227 | 227 | return get_exception(safe_int(http_code)) |
|
228 | 228 | except Exception: |
|
229 | 229 | log.exception('Failed to fetch response for code %s' % http_code) |
|
230 | 230 | return HTTPForbidden |
|
231 | 231 | |
|
232 | 232 | def build_authentication(self): |
|
233 | 233 | head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm) |
|
234 | 234 | if self._rc_auth_http_code and not self.initial_call: |
|
235 | 235 | # return alternative HTTP code if alternative http return code |
|
236 | 236 | # is specified in RhodeCode config, but ONLY if it's not the |
|
237 | 237 | # FIRST call |
|
238 | 238 | custom_response_klass = self._get_response_from_code( |
|
239 | 239 | self._rc_auth_http_code) |
|
240 | 240 | return custom_response_klass(headers=head) |
|
241 | 241 | return HTTPUnauthorized(headers=head) |
|
242 | 242 | |
|
243 | 243 | def authenticate(self, environ): |
|
244 | 244 | authorization = AUTHORIZATION(environ) |
|
245 | 245 | if not authorization: |
|
246 | 246 | return self.build_authentication() |
|
247 | 247 | (authmeth, auth) = authorization.split(' ', 1) |
|
248 | 248 | if 'basic' != authmeth.lower(): |
|
249 | 249 | return self.build_authentication() |
|
250 | 250 | auth = auth.strip().decode('base64') |
|
251 | 251 | _parts = auth.split(':', 1) |
|
252 | 252 | if len(_parts) == 2: |
|
253 | 253 | username, password = _parts |
|
254 | 254 | if self.authfunc( |
|
255 | 255 | username, password, environ, VCS_TYPE, |
|
256 | 256 | registry=self.registry, acl_repo_name=self.acl_repo_name): |
|
257 | 257 | return username |
|
258 | 258 | if username and password: |
|
259 | 259 | # we mark that we actually executed authentication once, at |
|
260 | 260 | # that point we can use the alternative auth code |
|
261 | 261 | self.initial_call = False |
|
262 | 262 | |
|
263 | 263 | return self.build_authentication() |
|
264 | 264 | |
|
265 | 265 | __call__ = authenticate |
|
266 | 266 | |
|
267 | 267 | |
|
268 | 268 | def calculate_version_hash(): |
|
269 | 269 | return md5( |
|
270 | 270 | config.get('beaker.session.secret', '') + |
|
271 | 271 | rhodecode.__version__)[:8] |
|
272 | 272 | |
|
273 | 273 | |
|
274 | 274 | def get_current_lang(request): |
|
275 | 275 | # NOTE(marcink): remove after pyramid move |
|
276 | 276 | try: |
|
277 | 277 | return translation.get_lang()[0] |
|
278 | 278 | except: |
|
279 | 279 | pass |
|
280 | 280 | |
|
281 | 281 | return getattr(request, '_LOCALE_', request.locale_name) |
|
282 | 282 | |
|
283 | 283 | |
|
284 | 284 | def attach_context_attributes(context, request, user_id): |
|
285 | 285 | """ |
|
286 | 286 | Attach variables into template context called `c`, please note that |
|
287 | 287 | request could be pylons or pyramid request in here. |
|
288 | 288 | """ |
|
289 | 289 | |
|
290 | 290 | rc_config = SettingsModel().get_all_settings(cache=True) |
|
291 | 291 | |
|
292 | 292 | context.rhodecode_version = rhodecode.__version__ |
|
293 | 293 | context.rhodecode_edition = config.get('rhodecode.edition') |
|
294 | 294 | # unique secret + version does not leak the version but keep consistency |
|
295 | 295 | context.rhodecode_version_hash = calculate_version_hash() |
|
296 | 296 | |
|
297 | 297 | # Default language set for the incoming request |
|
298 | 298 | context.language = get_current_lang(request) |
|
299 | 299 | |
|
300 | 300 | # Visual options |
|
301 | 301 | context.visual = AttributeDict({}) |
|
302 | 302 | |
|
303 | 303 | # DB stored Visual Items |
|
304 | 304 | context.visual.show_public_icon = str2bool( |
|
305 | 305 | rc_config.get('rhodecode_show_public_icon')) |
|
306 | 306 | context.visual.show_private_icon = str2bool( |
|
307 | 307 | rc_config.get('rhodecode_show_private_icon')) |
|
308 | 308 | context.visual.stylify_metatags = str2bool( |
|
309 | 309 | rc_config.get('rhodecode_stylify_metatags')) |
|
310 | 310 | context.visual.dashboard_items = safe_int( |
|
311 | 311 | rc_config.get('rhodecode_dashboard_items', 100)) |
|
312 | 312 | context.visual.admin_grid_items = safe_int( |
|
313 | 313 | rc_config.get('rhodecode_admin_grid_items', 100)) |
|
314 | 314 | context.visual.repository_fields = str2bool( |
|
315 | 315 | rc_config.get('rhodecode_repository_fields')) |
|
316 | 316 | context.visual.show_version = str2bool( |
|
317 | 317 | rc_config.get('rhodecode_show_version')) |
|
318 | 318 | context.visual.use_gravatar = str2bool( |
|
319 | 319 | rc_config.get('rhodecode_use_gravatar')) |
|
320 | 320 | context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url') |
|
321 | 321 | context.visual.default_renderer = rc_config.get( |
|
322 | 322 | 'rhodecode_markup_renderer', 'rst') |
|
323 | 323 | context.visual.comment_types = ChangesetComment.COMMENT_TYPES |
|
324 | 324 | context.visual.rhodecode_support_url = \ |
|
325 | 325 | rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support') |
|
326 | 326 | |
|
327 | context.visual.affected_files_cut_off = 60 | |
|
328 | ||
|
327 | 329 | context.pre_code = rc_config.get('rhodecode_pre_code') |
|
328 | 330 | context.post_code = rc_config.get('rhodecode_post_code') |
|
329 | 331 | context.rhodecode_name = rc_config.get('rhodecode_title') |
|
330 | 332 | context.default_encodings = aslist(config.get('default_encoding'), sep=',') |
|
331 | 333 | # if we have specified default_encoding in the request, it has more |
|
332 | 334 | # priority |
|
333 | 335 | if request.GET.get('default_encoding'): |
|
334 | 336 | context.default_encodings.insert(0, request.GET.get('default_encoding')) |
|
335 | 337 | context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl') |
|
336 | 338 | |
|
337 | 339 | # INI stored |
|
338 | 340 | context.labs_active = str2bool( |
|
339 | 341 | config.get('labs_settings_active', 'false')) |
|
340 | 342 | context.visual.allow_repo_location_change = str2bool( |
|
341 | 343 | config.get('allow_repo_location_change', True)) |
|
342 | 344 | context.visual.allow_custom_hooks_settings = str2bool( |
|
343 | 345 | config.get('allow_custom_hooks_settings', True)) |
|
344 | 346 | context.debug_style = str2bool(config.get('debug_style', False)) |
|
345 | 347 | |
|
346 | 348 | context.rhodecode_instanceid = config.get('instance_id') |
|
347 | 349 | |
|
348 | 350 | context.visual.cut_off_limit_diff = safe_int( |
|
349 | 351 | config.get('cut_off_limit_diff')) |
|
350 | 352 | context.visual.cut_off_limit_file = safe_int( |
|
351 | 353 | config.get('cut_off_limit_file')) |
|
352 | 354 | |
|
353 | 355 | # AppEnlight |
|
354 | 356 | context.appenlight_enabled = str2bool(config.get('appenlight', 'false')) |
|
355 | 357 | context.appenlight_api_public_key = config.get( |
|
356 | 358 | 'appenlight.api_public_key', '') |
|
357 | 359 | context.appenlight_server_url = config.get('appenlight.server_url', '') |
|
358 | 360 | |
|
359 | 361 | # JS template context |
|
360 | 362 | context.template_context = { |
|
361 | 363 | 'repo_name': None, |
|
362 | 364 | 'repo_type': None, |
|
363 | 365 | 'repo_landing_commit': None, |
|
364 | 366 | 'rhodecode_user': { |
|
365 | 367 | 'username': None, |
|
366 | 368 | 'email': None, |
|
367 | 369 | 'notification_status': False |
|
368 | 370 | }, |
|
369 | 371 | 'visual': { |
|
370 | 372 | 'default_renderer': None |
|
371 | 373 | }, |
|
372 | 374 | 'commit_data': { |
|
373 | 375 | 'commit_id': None |
|
374 | 376 | }, |
|
375 | 377 | 'pull_request_data': {'pull_request_id': None}, |
|
376 | 378 | 'timeago': { |
|
377 | 379 | 'refresh_time': 120 * 1000, |
|
378 | 380 | 'cutoff_limit': 1000 * 60 * 60 * 24 * 7 |
|
379 | 381 | }, |
|
380 | 382 | 'pylons_dispatch': { |
|
381 | 383 | # 'controller': request.environ['pylons.routes_dict']['controller'], |
|
382 | 384 | # 'action': request.environ['pylons.routes_dict']['action'], |
|
383 | 385 | }, |
|
384 | 386 | 'pyramid_dispatch': { |
|
385 | 387 | |
|
386 | 388 | }, |
|
387 | 389 | 'extra': {'plugins': {}} |
|
388 | 390 | } |
|
389 | 391 | # END CONFIG VARS |
|
390 | 392 | |
|
391 | 393 | # TODO: This dosn't work when called from pylons compatibility tween. |
|
392 | 394 | # Fix this and remove it from base controller. |
|
393 | 395 | # context.repo_name = get_repo_slug(request) # can be empty |
|
394 | 396 | |
|
395 | 397 | diffmode = 'sideside' |
|
396 | 398 | if request.GET.get('diffmode'): |
|
397 | 399 | if request.GET['diffmode'] == 'unified': |
|
398 | 400 | diffmode = 'unified' |
|
399 | 401 | elif request.session.get('diffmode'): |
|
400 | 402 | diffmode = request.session['diffmode'] |
|
401 | 403 | |
|
402 | 404 | context.diffmode = diffmode |
|
403 | 405 | |
|
404 | 406 | if request.session.get('diffmode') != diffmode: |
|
405 | 407 | request.session['diffmode'] = diffmode |
|
406 | 408 | |
|
407 | 409 | context.csrf_token = auth.get_csrf_token(session=request.session) |
|
408 | 410 | context.backends = rhodecode.BACKENDS.keys() |
|
409 | 411 | context.backends.sort() |
|
410 | 412 | context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id) |
|
411 | 413 | |
|
412 | 414 | # NOTE(marcink): when migrated to pyramid we don't need to set this anymore, |
|
413 | 415 | # given request will ALWAYS be pyramid one |
|
414 | 416 | pyramid_request = pyramid.threadlocal.get_current_request() |
|
415 | 417 | context.pyramid_request = pyramid_request |
|
416 | 418 | |
|
417 | 419 | # web case |
|
418 | 420 | if hasattr(pyramid_request, 'user'): |
|
419 | 421 | context.auth_user = pyramid_request.user |
|
420 | 422 | context.rhodecode_user = pyramid_request.user |
|
421 | 423 | |
|
422 | 424 | # api case |
|
423 | 425 | if hasattr(pyramid_request, 'rpc_user'): |
|
424 | 426 | context.auth_user = pyramid_request.rpc_user |
|
425 | 427 | context.rhodecode_user = pyramid_request.rpc_user |
|
426 | 428 | |
|
427 | 429 | # attach the whole call context to the request |
|
428 | 430 | request.call_context = context |
|
429 | 431 | |
|
430 | 432 | |
|
431 | 433 | def get_auth_user(request): |
|
432 | 434 | environ = request.environ |
|
433 | 435 | session = request.session |
|
434 | 436 | |
|
435 | 437 | ip_addr = get_ip_addr(environ) |
|
436 | 438 | # make sure that we update permissions each time we call controller |
|
437 | 439 | _auth_token = (request.GET.get('auth_token', '') or |
|
438 | 440 | request.GET.get('api_key', '')) |
|
439 | 441 | |
|
440 | 442 | if _auth_token: |
|
441 | 443 | # when using API_KEY we assume user exists, and |
|
442 | 444 | # doesn't need auth based on cookies. |
|
443 | 445 | auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr) |
|
444 | 446 | authenticated = False |
|
445 | 447 | else: |
|
446 | 448 | cookie_store = CookieStoreWrapper(session.get('rhodecode_user')) |
|
447 | 449 | try: |
|
448 | 450 | auth_user = AuthUser(user_id=cookie_store.get('user_id', None), |
|
449 | 451 | ip_addr=ip_addr) |
|
450 | 452 | except UserCreationError as e: |
|
451 | 453 | h.flash(e, 'error') |
|
452 | 454 | # container auth or other auth functions that create users |
|
453 | 455 | # on the fly can throw this exception signaling that there's |
|
454 | 456 | # issue with user creation, explanation should be provided |
|
455 | 457 | # in Exception itself. We then create a simple blank |
|
456 | 458 | # AuthUser |
|
457 | 459 | auth_user = AuthUser(ip_addr=ip_addr) |
|
458 | 460 | |
|
459 | 461 | if password_changed(auth_user, session): |
|
460 | 462 | session.invalidate() |
|
461 | 463 | cookie_store = CookieStoreWrapper(session.get('rhodecode_user')) |
|
462 | 464 | auth_user = AuthUser(ip_addr=ip_addr) |
|
463 | 465 | |
|
464 | 466 | authenticated = cookie_store.get('is_authenticated') |
|
465 | 467 | |
|
466 | 468 | if not auth_user.is_authenticated and auth_user.is_user_object: |
|
467 | 469 | # user is not authenticated and not empty |
|
468 | 470 | auth_user.set_authenticated(authenticated) |
|
469 | 471 | |
|
470 | 472 | return auth_user |
|
471 | 473 | |
|
472 | 474 | |
|
473 | 475 | class BaseController(WSGIController): |
|
474 | 476 | |
|
475 | 477 | def __before__(self): |
|
476 | 478 | """ |
|
477 | 479 | __before__ is called before controller methods and after __call__ |
|
478 | 480 | """ |
|
479 | 481 | # on each call propagate settings calls into global settings. |
|
480 | 482 | set_rhodecode_config(config) |
|
481 | 483 | attach_context_attributes(c, request, self._rhodecode_user.user_id) |
|
482 | 484 | |
|
483 | 485 | # TODO: Remove this when fixed in attach_context_attributes() |
|
484 | 486 | c.repo_name = get_repo_slug(request) # can be empty |
|
485 | 487 | |
|
486 | 488 | self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff')) |
|
487 | 489 | self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file')) |
|
488 | 490 | self.sa = meta.Session |
|
489 | 491 | self.scm_model = ScmModel(self.sa) |
|
490 | 492 | |
|
491 | 493 | # set user language |
|
492 | 494 | user_lang = getattr(c.pyramid_request, '_LOCALE_', None) |
|
493 | 495 | if user_lang: |
|
494 | 496 | translation.set_lang(user_lang) |
|
495 | 497 | log.debug('set language to %s for user %s', |
|
496 | 498 | user_lang, self._rhodecode_user) |
|
497 | 499 | |
|
498 | 500 | def _dispatch_redirect(self, with_url, environ, start_response): |
|
499 | 501 | resp = HTTPFound(with_url) |
|
500 | 502 | environ['SCRIPT_NAME'] = '' # handle prefix middleware |
|
501 | 503 | environ['PATH_INFO'] = with_url |
|
502 | 504 | return resp(environ, start_response) |
|
503 | 505 | |
|
504 | 506 | def __call__(self, environ, start_response): |
|
505 | 507 | """Invoke the Controller""" |
|
506 | 508 | # WSGIController.__call__ dispatches to the Controller method |
|
507 | 509 | # the request is routed to. This routing information is |
|
508 | 510 | # available in environ['pylons.routes_dict'] |
|
509 | 511 | from rhodecode.lib import helpers as h |
|
510 | 512 | |
|
511 | 513 | # Provide the Pylons context to Pyramid's debugtoolbar if it asks |
|
512 | 514 | if environ.get('debugtoolbar.wants_pylons_context', False): |
|
513 | 515 | environ['debugtoolbar.pylons_context'] = c._current_obj() |
|
514 | 516 | |
|
515 | 517 | _route_name = '.'.join([environ['pylons.routes_dict']['controller'], |
|
516 | 518 | environ['pylons.routes_dict']['action']]) |
|
517 | 519 | |
|
518 | 520 | self.rc_config = SettingsModel().get_all_settings(cache=True) |
|
519 | 521 | self.ip_addr = get_ip_addr(environ) |
|
520 | 522 | |
|
521 | 523 | # The rhodecode auth user is looked up and passed through the |
|
522 | 524 | # environ by the pylons compatibility tween in pyramid. |
|
523 | 525 | # So we can just grab it from there. |
|
524 | 526 | auth_user = environ['rc_auth_user'] |
|
525 | 527 | |
|
526 | 528 | # set globals for auth user |
|
527 | 529 | request.user = auth_user |
|
528 | 530 | self._rhodecode_user = auth_user |
|
529 | 531 | |
|
530 | 532 | log.info('IP: %s User: %s accessed %s [%s]' % ( |
|
531 | 533 | self.ip_addr, auth_user, safe_unicode(get_access_path(environ)), |
|
532 | 534 | _route_name) |
|
533 | 535 | ) |
|
534 | 536 | |
|
535 | 537 | user_obj = auth_user.get_instance() |
|
536 | 538 | if user_obj and user_obj.user_data.get('force_password_change'): |
|
537 | 539 | h.flash('You are required to change your password', 'warning', |
|
538 | 540 | ignore_duplicate=True) |
|
539 | 541 | return self._dispatch_redirect( |
|
540 | 542 | url('my_account_password'), environ, start_response) |
|
541 | 543 | |
|
542 | 544 | return WSGIController.__call__(self, environ, start_response) |
|
543 | 545 | |
|
544 | 546 | |
|
545 | 547 | class BaseRepoController(BaseController): |
|
546 | 548 | """ |
|
547 | 549 | Base class for controllers responsible for loading all needed data for |
|
548 | 550 | repository loaded items are |
|
549 | 551 | |
|
550 | 552 | c.rhodecode_repo: instance of scm repository |
|
551 | 553 | c.rhodecode_db_repo: instance of db |
|
552 | 554 | c.repository_requirements_missing: shows that repository specific data |
|
553 | 555 | could not be displayed due to the missing requirements |
|
554 | 556 | c.repository_pull_requests: show number of open pull requests |
|
555 | 557 | """ |
|
556 | 558 | |
|
557 | 559 | def __before__(self): |
|
558 | 560 | super(BaseRepoController, self).__before__() |
|
559 | 561 | if c.repo_name: # extracted from routes |
|
560 | 562 | db_repo = Repository.get_by_repo_name(c.repo_name) |
|
561 | 563 | if not db_repo: |
|
562 | 564 | return |
|
563 | 565 | |
|
564 | 566 | log.debug( |
|
565 | 567 | 'Found repository in database %s with state `%s`', |
|
566 | 568 | safe_unicode(db_repo), safe_unicode(db_repo.repo_state)) |
|
567 | 569 | route = getattr(request.environ.get('routes.route'), 'name', '') |
|
568 | 570 | |
|
569 | 571 | # allow to delete repos that are somehow damages in filesystem |
|
570 | 572 | if route in ['delete_repo']: |
|
571 | 573 | return |
|
572 | 574 | |
|
573 | 575 | if db_repo.repo_state in [Repository.STATE_PENDING]: |
|
574 | 576 | if route in ['repo_creating_home']: |
|
575 | 577 | return |
|
576 | 578 | check_url = url('repo_creating_home', repo_name=c.repo_name) |
|
577 | 579 | return redirect(check_url) |
|
578 | 580 | |
|
579 | 581 | self.rhodecode_db_repo = db_repo |
|
580 | 582 | |
|
581 | 583 | missing_requirements = False |
|
582 | 584 | try: |
|
583 | 585 | self.rhodecode_repo = self.rhodecode_db_repo.scm_instance() |
|
584 | 586 | except RepositoryRequirementError as e: |
|
585 | 587 | missing_requirements = True |
|
586 | 588 | self._handle_missing_requirements(e) |
|
587 | 589 | |
|
588 | 590 | if self.rhodecode_repo is None and not missing_requirements: |
|
589 | 591 | log.error('%s this repository is present in database but it ' |
|
590 | 592 | 'cannot be created as an scm instance', c.repo_name) |
|
591 | 593 | |
|
592 | 594 | h.flash(_( |
|
593 | 595 | "The repository at %(repo_name)s cannot be located.") % |
|
594 | 596 | {'repo_name': c.repo_name}, |
|
595 | 597 | category='error', ignore_duplicate=True) |
|
596 | 598 | redirect(h.route_path('home')) |
|
597 | 599 | |
|
598 | 600 | # update last change according to VCS data |
|
599 | 601 | if not missing_requirements: |
|
600 | 602 | commit = db_repo.get_commit( |
|
601 | 603 | pre_load=["author", "date", "message", "parents"]) |
|
602 | 604 | db_repo.update_commit_cache(commit) |
|
603 | 605 | |
|
604 | 606 | # Prepare context |
|
605 | 607 | c.rhodecode_db_repo = db_repo |
|
606 | 608 | c.rhodecode_repo = self.rhodecode_repo |
|
607 | 609 | c.repository_requirements_missing = missing_requirements |
|
608 | 610 | |
|
609 | 611 | self._update_global_counters(self.scm_model, db_repo) |
|
610 | 612 | |
|
611 | 613 | def _update_global_counters(self, scm_model, db_repo): |
|
612 | 614 | """ |
|
613 | 615 | Base variables that are exposed to every page of repository |
|
614 | 616 | """ |
|
615 | 617 | c.repository_pull_requests = scm_model.get_pull_requests(db_repo) |
|
616 | 618 | |
|
617 | 619 | def _handle_missing_requirements(self, error): |
|
618 | 620 | self.rhodecode_repo = None |
|
619 | 621 | log.error( |
|
620 | 622 | 'Requirements are missing for repository %s: %s', |
|
621 | 623 | c.repo_name, error.message) |
|
622 | 624 | |
|
623 | 625 | summary_url = h.route_path('repo_summary', repo_name=c.repo_name) |
|
624 | 626 | statistics_url = url('edit_repo_statistics', repo_name=c.repo_name) |
|
625 | 627 | settings_update_url = url('repo', repo_name=c.repo_name) |
|
626 | 628 | path = request.path |
|
627 | 629 | should_redirect = ( |
|
628 | 630 | path not in (summary_url, settings_update_url) |
|
629 | 631 | and '/settings' not in path or path == statistics_url |
|
630 | 632 | ) |
|
631 | 633 | if should_redirect: |
|
632 | 634 | redirect(summary_url) |
@@ -1,101 +1,101 b'' | |||
|
1 | 1 | // Global keyboard bindings |
|
2 | 2 | |
|
3 | 3 | function setRCMouseBindings(repoName, repoLandingRev) { |
|
4 | 4 | |
|
5 | 5 | /** custom callback for supressing mousetrap from firing */ |
|
6 | 6 | Mousetrap.stopCallback = function(e, element) { |
|
7 | 7 | // if the element has the class "mousetrap" then no need to stop |
|
8 | 8 | if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) { |
|
9 | 9 | return false; |
|
10 | 10 | } |
|
11 | 11 | |
|
12 | 12 | // stop for input, select, and textarea |
|
13 | 13 | return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable; |
|
14 | 14 | }; |
|
15 | 15 | |
|
16 | 16 | // general help "?" |
|
17 | 17 | Mousetrap.bind(['?'], function(e) { |
|
18 | 18 | $('#help_kb').modal({}); |
|
19 | 19 | }); |
|
20 | 20 | |
|
21 | 21 | // / open the quick filter |
|
22 | 22 | Mousetrap.bind(['/'], function(e) { |
|
23 | 23 | $('#repo_switcher').select2('open'); |
|
24 | 24 | |
|
25 | 25 | // return false to prevent default browser behavior |
|
26 | 26 | // and stop event from bubbling |
|
27 | 27 | return false; |
|
28 | 28 | }); |
|
29 | 29 | |
|
30 | 30 | // ctrl/command+b, show the the main bar |
|
31 | 31 | Mousetrap.bind(['command+b', 'ctrl+b'], function(e) { |
|
32 | 32 | var $headerInner = $('#header-inner'), |
|
33 | 33 | $content = $('#content'); |
|
34 | 34 | if ($headerInner.hasClass('hover') && $content.hasClass('hover')) { |
|
35 | 35 | $headerInner.removeClass('hover'); |
|
36 | 36 | $content.removeClass('hover'); |
|
37 | 37 | } else { |
|
38 | 38 | $headerInner.addClass('hover'); |
|
39 | 39 | $content.addClass('hover'); |
|
40 | 40 | } |
|
41 | 41 | return false; |
|
42 | 42 | }); |
|
43 | 43 | |
|
44 | 44 | // general nav g + action |
|
45 | 45 | Mousetrap.bind(['g h'], function(e) { |
|
46 | 46 | window.location = pyroutes.url('home'); |
|
47 | 47 | }); |
|
48 | 48 | Mousetrap.bind(['g g'], function(e) { |
|
49 | 49 | window.location = pyroutes.url('gists_show', {'private': 1}); |
|
50 | 50 | }); |
|
51 | 51 | Mousetrap.bind(['g G'], function(e) { |
|
52 | 52 | window.location = pyroutes.url('gists_show', {'public': 1}); |
|
53 | 53 | }); |
|
54 | 54 | Mousetrap.bind(['n g'], function(e) { |
|
55 | 55 | window.location = pyroutes.url('gists_new'); |
|
56 | 56 | }); |
|
57 | 57 | Mousetrap.bind(['n r'], function(e) { |
|
58 | 58 | window.location = pyroutes.url('new_repo'); |
|
59 | 59 | }); |
|
60 | 60 | |
|
61 | 61 | if (repoName && repoName != '') { |
|
62 | 62 | // nav in repo context |
|
63 | 63 | Mousetrap.bind(['g s'], function(e) { |
|
64 | 64 | window.location = pyroutes.url( |
|
65 | 65 | 'repo_summary', {'repo_name': repoName}); |
|
66 | 66 | }); |
|
67 | 67 | Mousetrap.bind(['g c'], function(e) { |
|
68 | 68 | window.location = pyroutes.url( |
|
69 |
'changelog |
|
|
69 | 'repo_changelog', {'repo_name': repoName}); | |
|
70 | 70 | }); |
|
71 | 71 | Mousetrap.bind(['g F'], function(e) { |
|
72 | 72 | window.location = pyroutes.url( |
|
73 | 73 | 'repo_files', |
|
74 | 74 | { |
|
75 | 75 | 'repo_name': repoName, |
|
76 | 76 | 'commit_id': repoLandingRev, |
|
77 | 77 | 'f_path': '', |
|
78 | 78 | 'search': '1' |
|
79 | 79 | }); |
|
80 | 80 | }); |
|
81 | 81 | Mousetrap.bind(['g f'], function(e) { |
|
82 | 82 | window.location = pyroutes.url( |
|
83 | 83 | 'repo_files', |
|
84 | 84 | { |
|
85 | 85 | 'repo_name': repoName, |
|
86 | 86 | 'commit_id': repoLandingRev, |
|
87 | 87 | 'f_path': '' |
|
88 | 88 | }); |
|
89 | 89 | }); |
|
90 | 90 | Mousetrap.bind(['g o'], function(e) { |
|
91 | 91 | window.location = pyroutes.url( |
|
92 | 92 | 'edit_repo', {'repo_name': repoName}); |
|
93 | 93 | }); |
|
94 | 94 | Mousetrap.bind(['g O'], function(e) { |
|
95 | 95 | window.location = pyroutes.url( |
|
96 | 96 | 'edit_repo_perms', {'repo_name': repoName}); |
|
97 | 97 | }); |
|
98 | 98 | } |
|
99 | 99 | } |
|
100 | 100 | |
|
101 | 101 | setRCMouseBindings(templateContext.repo_name, templateContext.repo_landing_commit); |
@@ -1,198 +1,198 b'' | |||
|
1 | 1 | |
|
2 | 2 | /****************************************************************************** |
|
3 | 3 | * * |
|
4 | 4 | * DO NOT CHANGE THIS FILE MANUALLY * |
|
5 | 5 | * * |
|
6 | 6 | * * |
|
7 | 7 | * This file is automatically generated when the app starts up with * |
|
8 | 8 | * generate_js_files = true * |
|
9 | 9 | * * |
|
10 | 10 | * To add a route here pass jsroute=True to the route definition in the app * |
|
11 | 11 | * * |
|
12 | 12 | ******************************************************************************/ |
|
13 | 13 | function registerRCRoutes() { |
|
14 | 14 | // routes registration |
|
15 | 15 | pyroutes.register('new_repo', '/_admin/create_repository', []); |
|
16 | 16 | pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']); |
|
17 | 17 | pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']); |
|
18 | 18 | pyroutes.register('toggle_following', '/_admin/toggle_following', []); |
|
19 | 19 | pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']); |
|
20 | 20 | pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']); |
|
21 | 21 | pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']); |
|
22 | 22 | pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']); |
|
23 | 23 | pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']); |
|
24 | 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 | 25 | pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']); |
|
26 | 26 | pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']); |
|
27 | 27 | pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']); |
|
28 | 28 | pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']); |
|
29 | 29 | pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']); |
|
30 | 30 | pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']); |
|
31 | 31 | pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']); |
|
32 | 32 | pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']); |
|
33 | pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']); | |
|
34 | pyroutes.register('changelog_file_home', '/%(repo_name)s/changelog/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']); | |
|
35 | pyroutes.register('changelog_elements', '/%(repo_name)s/changelog_details', ['repo_name']); | |
|
36 | 33 | pyroutes.register('favicon', '/favicon.ico', []); |
|
37 | 34 | pyroutes.register('robots', '/robots.txt', []); |
|
38 | 35 | pyroutes.register('auth_home', '/_admin/auth*traverse', []); |
|
39 | 36 | pyroutes.register('global_integrations_new', '/_admin/integrations/new', []); |
|
40 | 37 | pyroutes.register('global_integrations_home', '/_admin/integrations', []); |
|
41 | 38 | pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']); |
|
42 | 39 | pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']); |
|
43 | 40 | pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']); |
|
44 | 41 | pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']); |
|
45 | 42 | pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']); |
|
46 | 43 | pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']); |
|
47 | 44 | pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']); |
|
48 | 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 | 46 | pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']); |
|
50 | 47 | pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']); |
|
51 | 48 | pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']); |
|
52 | 49 | pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']); |
|
53 | 50 | pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']); |
|
54 | 51 | pyroutes.register('ops_ping', '/_admin/ops/ping', []); |
|
55 | 52 | pyroutes.register('ops_error_test', '/_admin/ops/error', []); |
|
56 | 53 | pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []); |
|
57 | 54 | pyroutes.register('admin_home', '/_admin', []); |
|
58 | 55 | pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []); |
|
59 | 56 | pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']); |
|
60 | 57 | pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']); |
|
61 | 58 | pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']); |
|
62 | 59 | pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []); |
|
63 | 60 | pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []); |
|
64 | 61 | pyroutes.register('admin_settings_system', '/_admin/settings/system', []); |
|
65 | 62 | pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []); |
|
66 | 63 | pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []); |
|
67 | 64 | pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []); |
|
68 | 65 | pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []); |
|
69 | 66 | pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []); |
|
70 | 67 | pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []); |
|
71 | 68 | pyroutes.register('users', '/_admin/users', []); |
|
72 | 69 | pyroutes.register('users_data', '/_admin/users_data', []); |
|
73 | 70 | pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']); |
|
74 | 71 | pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']); |
|
75 | 72 | pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']); |
|
76 | 73 | pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']); |
|
77 | 74 | pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']); |
|
78 | 75 | pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']); |
|
79 | 76 | pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']); |
|
80 | 77 | pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']); |
|
81 | 78 | pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']); |
|
82 | 79 | pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']); |
|
83 | 80 | pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']); |
|
84 | 81 | pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']); |
|
85 | 82 | pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []); |
|
86 | 83 | pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []); |
|
87 | 84 | pyroutes.register('channelstream_proxy', '/_channelstream', []); |
|
88 | 85 | pyroutes.register('login', '/_admin/login', []); |
|
89 | 86 | pyroutes.register('logout', '/_admin/logout', []); |
|
90 | 87 | pyroutes.register('register', '/_admin/register', []); |
|
91 | 88 | pyroutes.register('reset_password', '/_admin/password_reset', []); |
|
92 | 89 | pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []); |
|
93 | 90 | pyroutes.register('home', '/', []); |
|
94 | 91 | pyroutes.register('user_autocomplete_data', '/_users', []); |
|
95 | 92 | pyroutes.register('user_group_autocomplete_data', '/_user_groups', []); |
|
96 | 93 | pyroutes.register('repo_list_data', '/_repos', []); |
|
97 | 94 | pyroutes.register('goto_switcher_data', '/_goto_data', []); |
|
98 | 95 | pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']); |
|
99 | 96 | pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']); |
|
100 | 97 | pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']); |
|
101 | 98 | pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']); |
|
102 | 99 | pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']); |
|
103 | 100 | pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']); |
|
104 | 101 | pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
105 | 102 | pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']); |
|
106 | 103 | pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']); |
|
107 | 104 | pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
108 | 105 | pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
109 | 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 | 107 | pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
111 | 108 | pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']); |
|
112 | 109 | pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
113 | 110 | pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
114 | 111 | pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
115 | 112 | pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
116 | 113 | pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
117 | 114 | pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
118 | 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 | 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 | 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 | 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 | 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 | 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 | 121 | pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']); |
|
125 | 122 | pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']); |
|
126 | 123 | pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']); |
|
124 | pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']); | |
|
125 | pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); | |
|
126 | pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']); | |
|
127 | 127 | pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']); |
|
128 | 128 | pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']); |
|
129 | 129 | pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']); |
|
130 | 130 | pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']); |
|
131 | 131 | pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']); |
|
132 | 132 | pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']); |
|
133 | 133 | pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']); |
|
134 | 134 | pyroutes.register('changeset_children', '/%(repo_name)s/changeset_children/%(revision)s', ['repo_name', 'revision']); |
|
135 | 135 | pyroutes.register('changeset_parents', '/%(repo_name)s/changeset_parents/%(revision)s', ['repo_name', 'revision']); |
|
136 | 136 | pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']); |
|
137 | 137 | pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']); |
|
138 | 138 | pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']); |
|
139 | 139 | pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']); |
|
140 | 140 | pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']); |
|
141 | 141 | pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']); |
|
142 | 142 | pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']); |
|
143 | 143 | pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']); |
|
144 | 144 | pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']); |
|
145 | 145 | pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']); |
|
146 | 146 | pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']); |
|
147 | 147 | pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']); |
|
148 | 148 | pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']); |
|
149 | 149 | pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']); |
|
150 | 150 | pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']); |
|
151 | 151 | pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']); |
|
152 | 152 | pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']); |
|
153 | 153 | pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']); |
|
154 | 154 | pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']); |
|
155 | 155 | pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']); |
|
156 | 156 | pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']); |
|
157 | 157 | pyroutes.register('search', '/_admin/search', []); |
|
158 | 158 | pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']); |
|
159 | 159 | pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']); |
|
160 | 160 | pyroutes.register('my_account_profile', '/_admin/my_account/profile', []); |
|
161 | 161 | pyroutes.register('my_account_edit', '/_admin/my_account/edit', []); |
|
162 | 162 | pyroutes.register('my_account_update', '/_admin/my_account/update', []); |
|
163 | 163 | pyroutes.register('my_account_password', '/_admin/my_account/password', []); |
|
164 | 164 | pyroutes.register('my_account_password_update', '/_admin/my_account/password', []); |
|
165 | 165 | pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []); |
|
166 | 166 | pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []); |
|
167 | 167 | pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []); |
|
168 | 168 | pyroutes.register('my_account_emails', '/_admin/my_account/emails', []); |
|
169 | 169 | pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []); |
|
170 | 170 | pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []); |
|
171 | 171 | pyroutes.register('my_account_repos', '/_admin/my_account/repos', []); |
|
172 | 172 | pyroutes.register('my_account_watched', '/_admin/my_account/watched', []); |
|
173 | 173 | pyroutes.register('my_account_perms', '/_admin/my_account/perms', []); |
|
174 | 174 | pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []); |
|
175 | 175 | pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []); |
|
176 | 176 | pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []); |
|
177 | 177 | pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []); |
|
178 | 178 | pyroutes.register('notifications_show_all', '/_admin/notifications', []); |
|
179 | 179 | pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []); |
|
180 | 180 | pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']); |
|
181 | 181 | pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']); |
|
182 | 182 | pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']); |
|
183 | 183 | pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []); |
|
184 | 184 | pyroutes.register('gists_show', '/_admin/gists', []); |
|
185 | 185 | pyroutes.register('gists_new', '/_admin/gists/new', []); |
|
186 | 186 | pyroutes.register('gists_create', '/_admin/gists/create', []); |
|
187 | 187 | pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']); |
|
188 | 188 | pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']); |
|
189 | 189 | pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']); |
|
190 | 190 | pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']); |
|
191 | 191 | pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']); |
|
192 | 192 | pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']); |
|
193 | 193 | pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']); |
|
194 | 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 | 195 | pyroutes.register('debug_style_home', '/_admin/debug_style', []); |
|
196 | 196 | pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']); |
|
197 | 197 | pyroutes.register('apiv2', '/_admin/api', []); |
|
198 | 198 | } |
@@ -1,173 +1,173 b'' | |||
|
1 | 1 | // # Copyright (C) 2016-2017 RhodeCode GmbH |
|
2 | 2 | // # |
|
3 | 3 | // # This program is free software: you can redistribute it and/or modify |
|
4 | 4 | // # it under the terms of the GNU Affero General Public License, version 3 |
|
5 | 5 | // # (only), as published by the Free Software Foundation. |
|
6 | 6 | // # |
|
7 | 7 | // # This program is distributed in the hope that it will be useful, |
|
8 | 8 | // # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
9 | 9 | // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
10 | 10 | // # GNU General Public License for more details. |
|
11 | 11 | // # |
|
12 | 12 | // # You should have received a copy of the GNU Affero General Public License |
|
13 | 13 | // # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
14 | 14 | // # |
|
15 | 15 | // # This program is dual-licensed. If you wish to learn more about the |
|
16 | 16 | // # RhodeCode Enterprise Edition, including its added features, Support services, |
|
17 | 17 | // # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
18 | 18 | |
|
19 | 19 | |
|
20 | 20 | var CommitsController = function () { |
|
21 | 21 | var self = this; |
|
22 | 22 | this.$graphCanvas = $('#graph_canvas'); |
|
23 | 23 | this.$commitCounter = $('#commit-counter'); |
|
24 | 24 | |
|
25 | 25 | this.getCurrentGraphData = function () { |
|
26 | 26 | // raw form |
|
27 | 27 | return self.$graphCanvas.data('commits'); |
|
28 | 28 | }; |
|
29 | 29 | |
|
30 | 30 | this.setLabelText = function (graphData) { |
|
31 | 31 | var shown = $('.commit_hash').length; |
|
32 | 32 | var total = self.$commitCounter.data('total'); |
|
33 | 33 | |
|
34 | 34 | if (shown == 1) { |
|
35 | 35 | var text = _gettext('showing {0} out of {1} commit').format(shown, total); |
|
36 | 36 | } else { |
|
37 | 37 | var text = _gettext('showing {0} out of {1} commits').format(shown, total); |
|
38 | 38 | } |
|
39 | 39 | self.$commitCounter.html(text) |
|
40 | 40 | }; |
|
41 | 41 | |
|
42 | 42 | this.reloadGraph = function (chunk) { |
|
43 | 43 | chunk = chunk || 'next'; |
|
44 | 44 | |
|
45 | 45 | // reset state on re-render ! |
|
46 | 46 | self.$graphCanvas.html(''); |
|
47 | 47 | |
|
48 | 48 | var edgeData = $("[data-graph]").data('graph') || this.$graphCanvas.data('graph') || []; |
|
49 | 49 | |
|
50 | 50 | // Determine max number of edges per row in graph |
|
51 | 51 | var edgeCount = 1; |
|
52 | 52 | $.each(edgeData, function (i, item) { |
|
53 | 53 | $.each(item[2], function (key, value) { |
|
54 | 54 | if (value[1] > edgeCount) { |
|
55 | 55 | edgeCount = value[1]; |
|
56 | 56 | } |
|
57 | 57 | }); |
|
58 | 58 | }); |
|
59 | 59 | |
|
60 | 60 | var x_step = Math.min(10, Math.floor(86 / edgeCount)); |
|
61 | 61 | var graph_options = { |
|
62 | 62 | width: 100, |
|
63 | 63 | height: $('#changesets').find('.commits-range').height(), |
|
64 | 64 | x_step: x_step, |
|
65 | 65 | y_step: 42, |
|
66 | 66 | dotRadius: 3.5, |
|
67 | 67 | lineWidth: 2.5 |
|
68 | 68 | }; |
|
69 | 69 | |
|
70 | 70 | var prevCommitsData = this.$graphCanvas.data('commits') || []; |
|
71 | 71 | var nextCommitsData = $("[data-graph]").data('commits') || []; |
|
72 | 72 | |
|
73 | 73 | if (chunk == 'next') { |
|
74 | 74 | var commitData = $.merge(prevCommitsData, nextCommitsData); |
|
75 | 75 | } else { |
|
76 | 76 | var commitData = $.merge(nextCommitsData, prevCommitsData); |
|
77 | 77 | } |
|
78 | 78 | |
|
79 | 79 | this.$graphCanvas.data('graph', edgeData); |
|
80 | 80 | this.$graphCanvas.data('commits', commitData); |
|
81 | 81 | |
|
82 | 82 | // destroy dynamic loaded graph |
|
83 | 83 | $("[data-graph]").remove(); |
|
84 | 84 | |
|
85 | 85 | this.$graphCanvas.commits(graph_options); |
|
86 | 86 | |
|
87 | 87 | this.setLabelText(edgeData); |
|
88 | 88 | if ($('.load-more-commits').find('.prev-commits').get(0)) { |
|
89 | 89 | var padding = 75; |
|
90 | 90 | |
|
91 | 91 | } else { |
|
92 | 92 | var padding = 43; |
|
93 | 93 | } |
|
94 | 94 | $('#graph_nodes').css({'padding-top': padding}); |
|
95 | 95 | }; |
|
96 | 96 | |
|
97 | 97 | this.getChunkUrl = function (page, chunk, branch) { |
|
98 | 98 | var urlData = { |
|
99 | 99 | 'repo_name': templateContext.repo_name, |
|
100 | 100 | 'page': page, |
|
101 | 101 | 'chunk': chunk |
|
102 | 102 | }; |
|
103 | 103 | |
|
104 | 104 | if (branch !== undefined && branch !== '') { |
|
105 | 105 | urlData['branch'] = branch; |
|
106 | 106 | } |
|
107 | 107 | |
|
108 | return pyroutes.url('changelog_elements', urlData); | |
|
108 | return pyroutes.url('repo_changelog_elements', urlData); | |
|
109 | 109 | }; |
|
110 | 110 | |
|
111 | 111 | this.loadNext = function (node, page, branch) { |
|
112 | 112 | var loadUrl = this.getChunkUrl(page, 'next', branch); |
|
113 | 113 | var postData = {'graph': JSON.stringify(this.getCurrentGraphData())}; |
|
114 | 114 | |
|
115 | 115 | $.post(loadUrl, postData, function (data) { |
|
116 | 116 | $(node).closest('tbody').append(data); |
|
117 | 117 | $(node).closest('td').remove(); |
|
118 | 118 | self.reloadGraph('next'); |
|
119 | 119 | }) |
|
120 | 120 | }; |
|
121 | 121 | |
|
122 | 122 | this.loadPrev = function (node, page, branch) { |
|
123 | 123 | var loadUrl = this.getChunkUrl(page, 'prev', branch); |
|
124 | 124 | var postData = {'graph': JSON.stringify(this.getCurrentGraphData())}; |
|
125 | 125 | |
|
126 | 126 | $.post(loadUrl, postData, function (data) { |
|
127 | 127 | $(node).closest('tbody').prepend(data); |
|
128 | 128 | $(node).closest('td').remove(); |
|
129 | 129 | self.reloadGraph('prev'); |
|
130 | 130 | }) |
|
131 | 131 | }; |
|
132 | 132 | |
|
133 | 133 | this.expandCommit = function (node) { |
|
134 | 134 | |
|
135 | 135 | var target_expand = $(node); |
|
136 | 136 | var cid = target_expand.data('commitId'); |
|
137 | 137 | |
|
138 | 138 | if (target_expand.hasClass('open')) { |
|
139 | 139 | $('#c-' + cid).css({ |
|
140 | 140 | 'height': '1.5em', |
|
141 | 141 | 'white-space': 'nowrap', |
|
142 | 142 | 'text-overflow': 'ellipsis', |
|
143 | 143 | 'overflow': 'hidden' |
|
144 | 144 | }); |
|
145 | 145 | $('#t-' + cid).css({ |
|
146 | 146 | 'height': 'auto', |
|
147 | 147 | 'line-height': '.9em', |
|
148 | 148 | 'text-overflow': 'ellipsis', |
|
149 | 149 | 'overflow': 'hidden', |
|
150 | 150 | 'white-space': 'nowrap' |
|
151 | 151 | }); |
|
152 | 152 | target_expand.removeClass('open'); |
|
153 | 153 | } |
|
154 | 154 | else { |
|
155 | 155 | $('#c-' + cid).css({ |
|
156 | 156 | 'height': 'auto', |
|
157 | 157 | 'white-space': 'pre-line', |
|
158 | 158 | 'text-overflow': 'initial', |
|
159 | 159 | 'overflow': 'visible' |
|
160 | 160 | }); |
|
161 | 161 | $('#t-' + cid).css({ |
|
162 | 162 | 'height': 'auto', |
|
163 | 163 | 'max-height': 'none', |
|
164 | 164 | 'text-overflow': 'initial', |
|
165 | 165 | 'overflow': 'visible', |
|
166 | 166 | 'white-space': 'normal' |
|
167 | 167 | }); |
|
168 | 168 | target_expand.addClass('open'); |
|
169 | 169 | } |
|
170 | 170 | // redraw the graph |
|
171 | 171 | self.reloadGraph(); |
|
172 | 172 | } |
|
173 | 173 | }; |
@@ -1,600 +1,600 b'' | |||
|
1 | 1 | ## -*- coding: utf-8 -*- |
|
2 | 2 | <%inherit file="root.mako"/> |
|
3 | 3 | |
|
4 | 4 | <div class="outerwrapper"> |
|
5 | 5 | <!-- HEADER --> |
|
6 | 6 | <div class="header"> |
|
7 | 7 | <div id="header-inner" class="wrapper"> |
|
8 | 8 | <div id="logo"> |
|
9 | 9 | <div class="logo-wrapper"> |
|
10 | 10 | <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a> |
|
11 | 11 | </div> |
|
12 | 12 | %if c.rhodecode_name: |
|
13 | 13 | <div class="branding">- ${h.branding(c.rhodecode_name)}</div> |
|
14 | 14 | %endif |
|
15 | 15 | </div> |
|
16 | 16 | <!-- MENU BAR NAV --> |
|
17 | 17 | ${self.menu_bar_nav()} |
|
18 | 18 | <!-- END MENU BAR NAV --> |
|
19 | 19 | </div> |
|
20 | 20 | </div> |
|
21 | 21 | ${self.menu_bar_subnav()} |
|
22 | 22 | <!-- END HEADER --> |
|
23 | 23 | |
|
24 | 24 | <!-- CONTENT --> |
|
25 | 25 | <div id="content" class="wrapper"> |
|
26 | 26 | |
|
27 | 27 | <rhodecode-toast id="notifications"></rhodecode-toast> |
|
28 | 28 | |
|
29 | 29 | <div class="main"> |
|
30 | 30 | ${next.main()} |
|
31 | 31 | </div> |
|
32 | 32 | </div> |
|
33 | 33 | <!-- END CONTENT --> |
|
34 | 34 | |
|
35 | 35 | </div> |
|
36 | 36 | <!-- FOOTER --> |
|
37 | 37 | <div id="footer"> |
|
38 | 38 | <div id="footer-inner" class="title wrapper"> |
|
39 | 39 | <div> |
|
40 | 40 | <p class="footer-link-right"> |
|
41 | 41 | % if c.visual.show_version: |
|
42 | 42 | RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition} |
|
43 | 43 | % endif |
|
44 | 44 | © 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved. |
|
45 | 45 | % if c.visual.rhodecode_support_url: |
|
46 | 46 | <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a> |
|
47 | 47 | % endif |
|
48 | 48 | </p> |
|
49 | 49 | <% sid = 'block' if request.GET.get('showrcid') else 'none' %> |
|
50 | 50 | <p class="server-instance" style="display:${sid}"> |
|
51 | 51 | ## display hidden instance ID if specially defined |
|
52 | 52 | % if c.rhodecode_instanceid: |
|
53 | 53 | ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid} |
|
54 | 54 | % endif |
|
55 | 55 | </p> |
|
56 | 56 | </div> |
|
57 | 57 | </div> |
|
58 | 58 | </div> |
|
59 | 59 | |
|
60 | 60 | <!-- END FOOTER --> |
|
61 | 61 | |
|
62 | 62 | ### MAKO DEFS ### |
|
63 | 63 | |
|
64 | 64 | <%def name="menu_bar_subnav()"> |
|
65 | 65 | </%def> |
|
66 | 66 | |
|
67 | 67 | <%def name="breadcrumbs(class_='breadcrumbs')"> |
|
68 | 68 | <div class="${class_}"> |
|
69 | 69 | ${self.breadcrumbs_links()} |
|
70 | 70 | </div> |
|
71 | 71 | </%def> |
|
72 | 72 | |
|
73 | 73 | <%def name="admin_menu()"> |
|
74 | 74 | <ul class="admin_menu submenu"> |
|
75 | 75 | <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li> |
|
76 | 76 | <li><a href="${h.url('repos')}">${_('Repositories')}</a></li> |
|
77 | 77 | <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li> |
|
78 | 78 | <li><a href="${h.route_path('users')}">${_('Users')}</a></li> |
|
79 | 79 | <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li> |
|
80 | 80 | <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li> |
|
81 | 81 | <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li> |
|
82 | 82 | <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li> |
|
83 | 83 | <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li> |
|
84 | 84 | <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li> |
|
85 | 85 | </ul> |
|
86 | 86 | </%def> |
|
87 | 87 | |
|
88 | 88 | |
|
89 | 89 | <%def name="dt_info_panel(elements)"> |
|
90 | 90 | <dl class="dl-horizontal"> |
|
91 | 91 | %for dt, dd, title, show_items in elements: |
|
92 | 92 | <dt>${dt}:</dt> |
|
93 | 93 | <dd title="${h.tooltip(title)}"> |
|
94 | 94 | %if callable(dd): |
|
95 | 95 | ## allow lazy evaluation of elements |
|
96 | 96 | ${dd()} |
|
97 | 97 | %else: |
|
98 | 98 | ${dd} |
|
99 | 99 | %endif |
|
100 | 100 | %if show_items: |
|
101 | 101 | <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span> |
|
102 | 102 | %endif |
|
103 | 103 | </dd> |
|
104 | 104 | |
|
105 | 105 | %if show_items: |
|
106 | 106 | <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none"> |
|
107 | 107 | %for item in show_items: |
|
108 | 108 | <dt></dt> |
|
109 | 109 | <dd>${item}</dd> |
|
110 | 110 | %endfor |
|
111 | 111 | </div> |
|
112 | 112 | %endif |
|
113 | 113 | |
|
114 | 114 | %endfor |
|
115 | 115 | </dl> |
|
116 | 116 | </%def> |
|
117 | 117 | |
|
118 | 118 | |
|
119 | 119 | <%def name="gravatar(email, size=16)"> |
|
120 | 120 | <% |
|
121 | 121 | if (size > 16): |
|
122 | 122 | gravatar_class = 'gravatar gravatar-large' |
|
123 | 123 | else: |
|
124 | 124 | gravatar_class = 'gravatar' |
|
125 | 125 | %> |
|
126 | 126 | <%doc> |
|
127 | 127 | TODO: johbo: For now we serve double size images to make it smooth |
|
128 | 128 | for retina. This is how it worked until now. Should be replaced |
|
129 | 129 | with a better solution at some point. |
|
130 | 130 | </%doc> |
|
131 | 131 | <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}"> |
|
132 | 132 | </%def> |
|
133 | 133 | |
|
134 | 134 | |
|
135 | 135 | <%def name="gravatar_with_user(contact, size=16, show_disabled=False)"> |
|
136 | 136 | <% email = h.email_or_none(contact) %> |
|
137 | 137 | <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}"> |
|
138 | 138 | ${self.gravatar(email, size)} |
|
139 | 139 | <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span> |
|
140 | 140 | </div> |
|
141 | 141 | </%def> |
|
142 | 142 | |
|
143 | 143 | |
|
144 | 144 | ## admin menu used for people that have some admin resources |
|
145 | 145 | <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)"> |
|
146 | 146 | <ul class="submenu"> |
|
147 | 147 | %if repositories: |
|
148 | 148 | <li class="local-admin-repos"><a href="${h.url('repos')}">${_('Repositories')}</a></li> |
|
149 | 149 | %endif |
|
150 | 150 | %if repository_groups: |
|
151 | 151 | <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li> |
|
152 | 152 | %endif |
|
153 | 153 | %if user_groups: |
|
154 | 154 | <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li> |
|
155 | 155 | %endif |
|
156 | 156 | </ul> |
|
157 | 157 | </%def> |
|
158 | 158 | |
|
159 | 159 | <%def name="repo_page_title(repo_instance)"> |
|
160 | 160 | <div class="title-content"> |
|
161 | 161 | <div class="title-main"> |
|
162 | 162 | ## SVN/HG/GIT icons |
|
163 | 163 | %if h.is_hg(repo_instance): |
|
164 | 164 | <i class="icon-hg"></i> |
|
165 | 165 | %endif |
|
166 | 166 | %if h.is_git(repo_instance): |
|
167 | 167 | <i class="icon-git"></i> |
|
168 | 168 | %endif |
|
169 | 169 | %if h.is_svn(repo_instance): |
|
170 | 170 | <i class="icon-svn"></i> |
|
171 | 171 | %endif |
|
172 | 172 | |
|
173 | 173 | ## public/private |
|
174 | 174 | %if repo_instance.private: |
|
175 | 175 | <i class="icon-repo-private"></i> |
|
176 | 176 | %else: |
|
177 | 177 | <i class="icon-repo-public"></i> |
|
178 | 178 | %endif |
|
179 | 179 | |
|
180 | 180 | ## repo name with group name |
|
181 | 181 | ${h.breadcrumb_repo_link(c.rhodecode_db_repo)} |
|
182 | 182 | |
|
183 | 183 | </div> |
|
184 | 184 | |
|
185 | 185 | ## FORKED |
|
186 | 186 | %if repo_instance.fork: |
|
187 | 187 | <p> |
|
188 | 188 | <i class="icon-code-fork"></i> ${_('Fork of')} |
|
189 | 189 | <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a> |
|
190 | 190 | </p> |
|
191 | 191 | %endif |
|
192 | 192 | |
|
193 | 193 | ## IMPORTED FROM REMOTE |
|
194 | 194 | %if repo_instance.clone_uri: |
|
195 | 195 | <p> |
|
196 | 196 | <i class="icon-code-fork"></i> ${_('Clone from')} |
|
197 | 197 | <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a> |
|
198 | 198 | </p> |
|
199 | 199 | %endif |
|
200 | 200 | |
|
201 | 201 | ## LOCKING STATUS |
|
202 | 202 | %if repo_instance.locked[0]: |
|
203 | 203 | <p class="locking_locked"> |
|
204 | 204 | <i class="icon-repo-lock"></i> |
|
205 | 205 | ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}} |
|
206 | 206 | </p> |
|
207 | 207 | %elif repo_instance.enable_locking: |
|
208 | 208 | <p class="locking_unlocked"> |
|
209 | 209 | <i class="icon-repo-unlock"></i> |
|
210 | 210 | ${_('Repository not locked. Pull repository to lock it.')} |
|
211 | 211 | </p> |
|
212 | 212 | %endif |
|
213 | 213 | |
|
214 | 214 | </div> |
|
215 | 215 | </%def> |
|
216 | 216 | |
|
217 | 217 | <%def name="repo_menu(active=None)"> |
|
218 | 218 | <% |
|
219 | 219 | def is_active(selected): |
|
220 | 220 | if selected == active: |
|
221 | 221 | return "active" |
|
222 | 222 | %> |
|
223 | 223 | |
|
224 | 224 | <!--- CONTEXT BAR --> |
|
225 | 225 | <div id="context-bar"> |
|
226 | 226 | <div class="wrapper"> |
|
227 | 227 | <ul id="context-pages" class="horizontal-list navigation"> |
|
228 | 228 | <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li> |
|
229 |
<li class="${is_active('changelog')}"><a class="menulink" href="${h. |
|
|
229 | <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li> | |
|
230 | 230 | <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li> |
|
231 | 231 | <li class="${is_active('compare')}"> |
|
232 | 232 | <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a> |
|
233 | 233 | </li> |
|
234 | 234 | ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()" |
|
235 | 235 | %if c.rhodecode_db_repo.repo_type in ['git','hg']: |
|
236 | 236 | <li class="${is_active('showpullrequest')}"> |
|
237 | 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 | 238 | %if c.repository_pull_requests: |
|
239 | 239 | <span class="pr_notifications">${c.repository_pull_requests}</span> |
|
240 | 240 | %endif |
|
241 | 241 | <div class="menulabel">${_('Pull Requests')}</div> |
|
242 | 242 | </a> |
|
243 | 243 | </li> |
|
244 | 244 | %endif |
|
245 | 245 | <li class="${is_active('options')}"> |
|
246 | 246 | <a class="menulink dropdown"> |
|
247 | 247 | <div class="menulabel">${_('Options')} <div class="show_more"></div></div> |
|
248 | 248 | </a> |
|
249 | 249 | <ul class="submenu"> |
|
250 | 250 | %if h.HasRepoPermissionAll('repository.admin')(c.repo_name): |
|
251 | 251 | <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li> |
|
252 | 252 | %endif |
|
253 | 253 | %if c.rhodecode_db_repo.fork: |
|
254 | 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 | 255 | ${_('Compare fork')}</a></li> |
|
256 | 256 | %endif |
|
257 | 257 | |
|
258 | 258 | <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li> |
|
259 | 259 | |
|
260 | 260 | %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking: |
|
261 | 261 | %if c.rhodecode_db_repo.locked[0]: |
|
262 | 262 | <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li> |
|
263 | 263 | %else: |
|
264 | 264 | <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li> |
|
265 | 265 | %endif |
|
266 | 266 | %endif |
|
267 | 267 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
268 | 268 | %if c.rhodecode_db_repo.repo_type in ['git','hg']: |
|
269 | 269 | <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li> |
|
270 | 270 | <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li> |
|
271 | 271 | %endif |
|
272 | 272 | %endif |
|
273 | 273 | </ul> |
|
274 | 274 | </li> |
|
275 | 275 | </ul> |
|
276 | 276 | </div> |
|
277 | 277 | <div class="clear"></div> |
|
278 | 278 | </div> |
|
279 | 279 | <!--- END CONTEXT BAR --> |
|
280 | 280 | |
|
281 | 281 | </%def> |
|
282 | 282 | |
|
283 | 283 | <%def name="usermenu(active=False)"> |
|
284 | 284 | ## USER MENU |
|
285 | 285 | <li id="quick_login_li" class="${'active' if active else ''}"> |
|
286 | 286 | <a id="quick_login_link" class="menulink childs"> |
|
287 | 287 | ${gravatar(c.rhodecode_user.email, 20)} |
|
288 | 288 | <span class="user"> |
|
289 | 289 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
290 | 290 | <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div> |
|
291 | 291 | %else: |
|
292 | 292 | <span>${_('Sign in')}</span> |
|
293 | 293 | %endif |
|
294 | 294 | </span> |
|
295 | 295 | </a> |
|
296 | 296 | |
|
297 | 297 | <div class="user-menu submenu"> |
|
298 | 298 | <div id="quick_login"> |
|
299 | 299 | %if c.rhodecode_user.username == h.DEFAULT_USER: |
|
300 | 300 | <h4>${_('Sign in to your account')}</h4> |
|
301 | 301 | ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)} |
|
302 | 302 | <div class="form form-vertical"> |
|
303 | 303 | <div class="fields"> |
|
304 | 304 | <div class="field"> |
|
305 | 305 | <div class="label"> |
|
306 | 306 | <label for="username">${_('Username')}:</label> |
|
307 | 307 | </div> |
|
308 | 308 | <div class="input"> |
|
309 | 309 | ${h.text('username',class_='focus',tabindex=1)} |
|
310 | 310 | </div> |
|
311 | 311 | |
|
312 | 312 | </div> |
|
313 | 313 | <div class="field"> |
|
314 | 314 | <div class="label"> |
|
315 | 315 | <label for="password">${_('Password')}:</label> |
|
316 | 316 | %if h.HasPermissionAny('hg.password_reset.enabled')(): |
|
317 | 317 | <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span> |
|
318 | 318 | %endif |
|
319 | 319 | </div> |
|
320 | 320 | <div class="input"> |
|
321 | 321 | ${h.password('password',class_='focus',tabindex=2)} |
|
322 | 322 | </div> |
|
323 | 323 | </div> |
|
324 | 324 | <div class="buttons"> |
|
325 | 325 | <div class="register"> |
|
326 | 326 | %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')(): |
|
327 | 327 | ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/> |
|
328 | 328 | %endif |
|
329 | 329 | ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))} |
|
330 | 330 | </div> |
|
331 | 331 | <div class="submit"> |
|
332 | 332 | ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)} |
|
333 | 333 | </div> |
|
334 | 334 | </div> |
|
335 | 335 | </div> |
|
336 | 336 | </div> |
|
337 | 337 | ${h.end_form()} |
|
338 | 338 | %else: |
|
339 | 339 | <div class=""> |
|
340 | 340 | <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div> |
|
341 | 341 | <div class="full_name">${c.rhodecode_user.full_name_or_username}</div> |
|
342 | 342 | <div class="email">${c.rhodecode_user.email}</div> |
|
343 | 343 | </div> |
|
344 | 344 | <div class=""> |
|
345 | 345 | <ol class="links"> |
|
346 | 346 | <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li> |
|
347 | 347 | % if c.rhodecode_user.personal_repo_group: |
|
348 | 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 | 349 | % endif |
|
350 | 350 | <li class="logout"> |
|
351 | 351 | ${h.secure_form(h.route_path('logout'), request=request)} |
|
352 | 352 | ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")} |
|
353 | 353 | ${h.end_form()} |
|
354 | 354 | </li> |
|
355 | 355 | </ol> |
|
356 | 356 | </div> |
|
357 | 357 | %endif |
|
358 | 358 | </div> |
|
359 | 359 | </div> |
|
360 | 360 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
361 | 361 | <div class="pill_container"> |
|
362 | 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 | 363 | </div> |
|
364 | 364 | % endif |
|
365 | 365 | </li> |
|
366 | 366 | </%def> |
|
367 | 367 | |
|
368 | 368 | <%def name="menu_items(active=None)"> |
|
369 | 369 | <% |
|
370 | 370 | def is_active(selected): |
|
371 | 371 | if selected == active: |
|
372 | 372 | return "active" |
|
373 | 373 | return "" |
|
374 | 374 | %> |
|
375 | 375 | <ul id="quick" class="main_nav navigation horizontal-list"> |
|
376 | 376 | <!-- repo switcher --> |
|
377 | 377 | <li class="${is_active('repositories')} repo_switcher_li has_select2"> |
|
378 | 378 | <input id="repo_switcher" name="repo_switcher" type="hidden"> |
|
379 | 379 | </li> |
|
380 | 380 | |
|
381 | 381 | ## ROOT MENU |
|
382 | 382 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
383 | 383 | <li class="${is_active('journal')}"> |
|
384 | 384 | <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}"> |
|
385 | 385 | <div class="menulabel">${_('Journal')}</div> |
|
386 | 386 | </a> |
|
387 | 387 | </li> |
|
388 | 388 | %else: |
|
389 | 389 | <li class="${is_active('journal')}"> |
|
390 | 390 | <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}"> |
|
391 | 391 | <div class="menulabel">${_('Public journal')}</div> |
|
392 | 392 | </a> |
|
393 | 393 | </li> |
|
394 | 394 | %endif |
|
395 | 395 | <li class="${is_active('gists')}"> |
|
396 | 396 | <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}"> |
|
397 | 397 | <div class="menulabel">${_('Gists')}</div> |
|
398 | 398 | </a> |
|
399 | 399 | </li> |
|
400 | 400 | <li class="${is_active('search')}"> |
|
401 | 401 | <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}"> |
|
402 | 402 | <div class="menulabel">${_('Search')}</div> |
|
403 | 403 | </a> |
|
404 | 404 | </li> |
|
405 | 405 | % if h.HasPermissionAll('hg.admin')('access admin main page'): |
|
406 | 406 | <li class="${is_active('admin')}"> |
|
407 | 407 | <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;"> |
|
408 | 408 | <div class="menulabel">${_('Admin')} <div class="show_more"></div></div> |
|
409 | 409 | </a> |
|
410 | 410 | ${admin_menu()} |
|
411 | 411 | </li> |
|
412 | 412 | % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin: |
|
413 | 413 | <li class="${is_active('admin')}"> |
|
414 | 414 | <a class="menulink childs" title="${_('Delegated Admin settings')}"> |
|
415 | 415 | <div class="menulabel">${_('Admin')} <div class="show_more"></div></div> |
|
416 | 416 | </a> |
|
417 | 417 | ${admin_menu_simple(c.rhodecode_user.repositories_admin, |
|
418 | 418 | c.rhodecode_user.repository_groups_admin, |
|
419 | 419 | c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())} |
|
420 | 420 | </li> |
|
421 | 421 | % endif |
|
422 | 422 | % if c.debug_style: |
|
423 | 423 | <li class="${is_active('debug_style')}"> |
|
424 | 424 | <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}"> |
|
425 | 425 | <div class="menulabel">${_('Style')}</div> |
|
426 | 426 | </a> |
|
427 | 427 | </li> |
|
428 | 428 | % endif |
|
429 | 429 | ## render extra user menu |
|
430 | 430 | ${usermenu(active=(active=='my_account'))} |
|
431 | 431 | </ul> |
|
432 | 432 | |
|
433 | 433 | <script type="text/javascript"> |
|
434 | 434 | var visual_show_public_icon = "${c.visual.show_public_icon}" == "True"; |
|
435 | 435 | |
|
436 | 436 | /*format the look of items in the list*/ |
|
437 | 437 | var format = function(state, escapeMarkup){ |
|
438 | 438 | if (!state.id){ |
|
439 | 439 | return state.text; // optgroup |
|
440 | 440 | } |
|
441 | 441 | var obj_dict = state.obj; |
|
442 | 442 | var tmpl = ''; |
|
443 | 443 | |
|
444 | 444 | if(obj_dict && state.type == 'repo'){ |
|
445 | 445 | if(obj_dict['repo_type'] === 'hg'){ |
|
446 | 446 | tmpl += '<i class="icon-hg"></i> '; |
|
447 | 447 | } |
|
448 | 448 | else if(obj_dict['repo_type'] === 'git'){ |
|
449 | 449 | tmpl += '<i class="icon-git"></i> '; |
|
450 | 450 | } |
|
451 | 451 | else if(obj_dict['repo_type'] === 'svn'){ |
|
452 | 452 | tmpl += '<i class="icon-svn"></i> '; |
|
453 | 453 | } |
|
454 | 454 | if(obj_dict['private']){ |
|
455 | 455 | tmpl += '<i class="icon-lock" ></i> '; |
|
456 | 456 | } |
|
457 | 457 | else if(visual_show_public_icon){ |
|
458 | 458 | tmpl += '<i class="icon-unlock-alt"></i> '; |
|
459 | 459 | } |
|
460 | 460 | } |
|
461 | 461 | if(obj_dict && state.type == 'commit') { |
|
462 | 462 | tmpl += '<i class="icon-tag"></i>'; |
|
463 | 463 | } |
|
464 | 464 | if(obj_dict && state.type == 'group'){ |
|
465 | 465 | tmpl += '<i class="icon-folder-close"></i> '; |
|
466 | 466 | } |
|
467 | 467 | tmpl += escapeMarkup(state.text); |
|
468 | 468 | return tmpl; |
|
469 | 469 | }; |
|
470 | 470 | |
|
471 | 471 | var formatResult = function(result, container, query, escapeMarkup) { |
|
472 | 472 | return format(result, escapeMarkup); |
|
473 | 473 | }; |
|
474 | 474 | |
|
475 | 475 | var formatSelection = function(data, container, escapeMarkup) { |
|
476 | 476 | return format(data, escapeMarkup); |
|
477 | 477 | }; |
|
478 | 478 | |
|
479 | 479 | $("#repo_switcher").select2({ |
|
480 | 480 | cachedDataSource: {}, |
|
481 | 481 | minimumInputLength: 2, |
|
482 | 482 | placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>', |
|
483 | 483 | dropdownAutoWidth: true, |
|
484 | 484 | formatResult: formatResult, |
|
485 | 485 | formatSelection: formatSelection, |
|
486 | 486 | containerCssClass: "repo-switcher", |
|
487 | 487 | dropdownCssClass: "repo-switcher-dropdown", |
|
488 | 488 | escapeMarkup: function(m){ |
|
489 | 489 | // don't escape our custom placeholder |
|
490 | 490 | if(m.substr(0,23) == '<div class="menulabel">'){ |
|
491 | 491 | return m; |
|
492 | 492 | } |
|
493 | 493 | |
|
494 | 494 | return Select2.util.escapeMarkup(m); |
|
495 | 495 | }, |
|
496 | 496 | query: $.debounce(250, function(query){ |
|
497 | 497 | self = this; |
|
498 | 498 | var cacheKey = query.term; |
|
499 | 499 | var cachedData = self.cachedDataSource[cacheKey]; |
|
500 | 500 | |
|
501 | 501 | if (cachedData) { |
|
502 | 502 | query.callback({results: cachedData.results}); |
|
503 | 503 | } else { |
|
504 | 504 | $.ajax({ |
|
505 | 505 | url: pyroutes.url('goto_switcher_data'), |
|
506 | 506 | data: {'query': query.term}, |
|
507 | 507 | dataType: 'json', |
|
508 | 508 | type: 'GET', |
|
509 | 509 | success: function(data) { |
|
510 | 510 | self.cachedDataSource[cacheKey] = data; |
|
511 | 511 | query.callback({results: data.results}); |
|
512 | 512 | }, |
|
513 | 513 | error: function(data, textStatus, errorThrown) { |
|
514 | 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 | 521 | $("#repo_switcher").on('select2-selecting', function(e){ |
|
522 | 522 | e.preventDefault(); |
|
523 | 523 | window.location = e.choice.url; |
|
524 | 524 | }); |
|
525 | 525 | |
|
526 | 526 | </script> |
|
527 | 527 | <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script> |
|
528 | 528 | </%def> |
|
529 | 529 | |
|
530 | 530 | <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> |
|
531 | 531 | <div class="modal-dialog"> |
|
532 | 532 | <div class="modal-content"> |
|
533 | 533 | <div class="modal-header"> |
|
534 | 534 | <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> |
|
535 | 535 | <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4> |
|
536 | 536 | </div> |
|
537 | 537 | <div class="modal-body"> |
|
538 | 538 | <div class="block-left"> |
|
539 | 539 | <table class="keyboard-mappings"> |
|
540 | 540 | <tbody> |
|
541 | 541 | <tr> |
|
542 | 542 | <th></th> |
|
543 | 543 | <th>${_('Site-wide shortcuts')}</th> |
|
544 | 544 | </tr> |
|
545 | 545 | <% |
|
546 | 546 | elems = [ |
|
547 | 547 | ('/', 'Open quick search box'), |
|
548 | 548 | ('g h', 'Goto home page'), |
|
549 | 549 | ('g g', 'Goto my private gists page'), |
|
550 | 550 | ('g G', 'Goto my public gists page'), |
|
551 | 551 | ('n r', 'New repository page'), |
|
552 | 552 | ('n g', 'New gist page'), |
|
553 | 553 | ] |
|
554 | 554 | %> |
|
555 | 555 | %for key, desc in elems: |
|
556 | 556 | <tr> |
|
557 | 557 | <td class="keys"> |
|
558 | 558 | <span class="key tag">${key}</span> |
|
559 | 559 | </td> |
|
560 | 560 | <td>${desc}</td> |
|
561 | 561 | </tr> |
|
562 | 562 | %endfor |
|
563 | 563 | </tbody> |
|
564 | 564 | </table> |
|
565 | 565 | </div> |
|
566 | 566 | <div class="block-left"> |
|
567 | 567 | <table class="keyboard-mappings"> |
|
568 | 568 | <tbody> |
|
569 | 569 | <tr> |
|
570 | 570 | <th></th> |
|
571 | 571 | <th>${_('Repositories')}</th> |
|
572 | 572 | </tr> |
|
573 | 573 | <% |
|
574 | 574 | elems = [ |
|
575 | 575 | ('g s', 'Goto summary page'), |
|
576 | 576 | ('g c', 'Goto changelog page'), |
|
577 | 577 | ('g f', 'Goto files page'), |
|
578 | 578 | ('g F', 'Goto files page with file search activated'), |
|
579 | 579 | ('g p', 'Goto pull requests page'), |
|
580 | 580 | ('g o', 'Goto repository settings'), |
|
581 | 581 | ('g O', 'Goto repository permissions settings'), |
|
582 | 582 | ] |
|
583 | 583 | %> |
|
584 | 584 | %for key, desc in elems: |
|
585 | 585 | <tr> |
|
586 | 586 | <td class="keys"> |
|
587 | 587 | <span class="key tag">${key}</span> |
|
588 | 588 | </td> |
|
589 | 589 | <td>${desc}</td> |
|
590 | 590 | </tr> |
|
591 | 591 | %endfor |
|
592 | 592 | </tbody> |
|
593 | 593 | </table> |
|
594 | 594 | </div> |
|
595 | 595 | </div> |
|
596 | 596 | <div class="modal-footer"> |
|
597 | 597 | </div> |
|
598 | 598 | </div><!-- /.modal-content --> |
|
599 | 599 | </div><!-- /.modal-dialog --> |
|
600 | 600 | </div><!-- /.modal --> |
@@ -1,299 +1,299 b'' | |||
|
1 | 1 | ## -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | <%inherit file="/base/base.mako"/> |
|
4 | 4 | |
|
5 | 5 | <%def name="title()"> |
|
6 | 6 | ${_('%s Changelog') % c.repo_name} |
|
7 | 7 | %if c.changelog_for_path: |
|
8 | 8 | /${c.changelog_for_path} |
|
9 | 9 | %endif |
|
10 | 10 | %if c.rhodecode_name: |
|
11 | 11 | · ${h.branding(c.rhodecode_name)} |
|
12 | 12 | %endif |
|
13 | 13 | </%def> |
|
14 | 14 | |
|
15 | 15 | <%def name="breadcrumbs_links()"> |
|
16 | 16 | %if c.changelog_for_path: |
|
17 | 17 | /${c.changelog_for_path} |
|
18 | 18 | %endif |
|
19 | 19 | </%def> |
|
20 | 20 | |
|
21 | 21 | <%def name="menu_bar_nav()"> |
|
22 | 22 | ${self.menu_items(active='repositories')} |
|
23 | 23 | </%def> |
|
24 | 24 | |
|
25 | 25 | <%def name="menu_bar_subnav()"> |
|
26 | 26 | ${self.repo_menu(active='changelog')} |
|
27 | 27 | </%def> |
|
28 | 28 | |
|
29 | 29 | <%def name="main()"> |
|
30 | 30 | |
|
31 | 31 | <div class="box"> |
|
32 | 32 | <div class="title"> |
|
33 | 33 | ${self.repo_page_title(c.rhodecode_db_repo)} |
|
34 | 34 | <ul class="links"> |
|
35 | 35 | <li> |
|
36 | 36 | <a href="#" class="btn btn-small" id="rev_range_container" style="display:none;"></a> |
|
37 | 37 | %if c.rhodecode_db_repo.fork: |
|
38 | 38 | <span> |
|
39 | 39 | <a id="compare_fork_button" |
|
40 | 40 | title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}" |
|
41 | 41 | class="btn btn-small" |
|
42 | 42 | href="${h.url('compare_url', |
|
43 | 43 | repo_name=c.rhodecode_db_repo.fork.repo_name, |
|
44 | 44 | source_ref_type=c.rhodecode_db_repo.landing_rev[0], |
|
45 | 45 | source_ref=c.rhodecode_db_repo.landing_rev[1], |
|
46 | 46 | target_repo=c.repo_name, |
|
47 | 47 | target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0], |
|
48 | 48 | target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], |
|
49 | 49 | merge=1)}" |
|
50 | 50 | > |
|
51 | 51 | <i class="icon-loop"></i> |
|
52 | 52 | ${_('Compare fork with Parent (%s)' % c.rhodecode_db_repo.fork.repo_name)} |
|
53 | 53 | </a> |
|
54 | 54 | </span> |
|
55 | 55 | %endif |
|
56 | 56 | |
|
57 | 57 | ## pr open link |
|
58 | 58 | %if h.is_hg(c.rhodecode_repo) or h.is_git(c.rhodecode_repo): |
|
59 | 59 | <span> |
|
60 | 60 | <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.url('pullrequest_home',repo_name=c.repo_name)}"> |
|
61 | 61 | ${_('Open new pull request')} |
|
62 | 62 | </a> |
|
63 | 63 | </span> |
|
64 | 64 | %endif |
|
65 | 65 | |
|
66 | 66 | ## clear selection |
|
67 | 67 | <div title="${_('Clear selection')}" class="btn" id="rev_range_clear" style="display:none"> |
|
68 | 68 | ${_('Clear selection')} |
|
69 | 69 | </div> |
|
70 | 70 | |
|
71 | 71 | </li> |
|
72 | 72 | </ul> |
|
73 | 73 | </div> |
|
74 | 74 | |
|
75 | 75 | % if c.pagination: |
|
76 | 76 | <script type="text/javascript" src="${h.asset('js/jquery.commits-graph.js')}"></script> |
|
77 | 77 | |
|
78 | 78 | <div class="graph-header"> |
|
79 | 79 | <div id="filter_changelog"> |
|
80 | 80 | ${h.hidden('branch_filter')} |
|
81 | 81 | %if c.selected_name: |
|
82 | 82 | <div class="btn btn-default" id="clear_filter" > |
|
83 | 83 | ${_('Clear filter')} |
|
84 | 84 | </div> |
|
85 | 85 | %endif |
|
86 | 86 | </div> |
|
87 | 87 | ${self.breadcrumbs('breadcrumbs_light')} |
|
88 | 88 | <div id="commit-counter" data-total=${c.total_cs} class="pull-right"> |
|
89 | ${ungettext('showing %d out of %d commit', 'showing %d out of %d commits', c.showing_commits) % (c.showing_commits, c.total_cs)} | |
|
89 | ${_ungettext('showing %d out of %d commit', 'showing %d out of %d commits', c.showing_commits) % (c.showing_commits, c.total_cs)} | |
|
90 | 90 | </div> |
|
91 | 91 | </div> |
|
92 | 92 | |
|
93 | 93 | <div id="graph"> |
|
94 | 94 | <div class="graph-col-wrapper"> |
|
95 | 95 | <div id="graph_nodes"> |
|
96 | 96 | <div id="graph_canvas"></div> |
|
97 | 97 | </div> |
|
98 | 98 | <div id="graph_content" class="main-content graph_full_width"> |
|
99 | 99 | |
|
100 | 100 | <div class="table"> |
|
101 | 101 | <table id="changesets" class="rctable"> |
|
102 | 102 | <tr> |
|
103 | 103 | ## checkbox |
|
104 | 104 | <th></th> |
|
105 | 105 | <th colspan="2"></th> |
|
106 | 106 | |
|
107 | 107 | <th>${_('Commit')}</th> |
|
108 | 108 | ## commit message expand arrow |
|
109 | 109 | <th></th> |
|
110 | 110 | <th>${_('Commit Message')}</th> |
|
111 | 111 | |
|
112 | 112 | <th>${_('Age')}</th> |
|
113 | 113 | <th>${_('Author')}</th> |
|
114 | 114 | |
|
115 | 115 | <th>${_('Refs')}</th> |
|
116 | 116 | </tr> |
|
117 | 117 | |
|
118 | 118 | <tbody class="commits-range"> |
|
119 | 119 | <%include file='changelog_elements.mako'/> |
|
120 | 120 | </tbody> |
|
121 | 121 | </table> |
|
122 | 122 | </div> |
|
123 | 123 | </div> |
|
124 | 124 | <div class="pagination-wh pagination-left"> |
|
125 | 125 | ${c.pagination.pager('$link_previous ~2~ $link_next')} |
|
126 | 126 | </div> |
|
127 | 127 | </div> |
|
128 | 128 | |
|
129 | 129 | <script type="text/javascript"> |
|
130 | 130 | var cache = {}; |
|
131 | 131 | $(function(){ |
|
132 | 132 | |
|
133 | 133 | // Create links to commit ranges when range checkboxes are selected |
|
134 | 134 | var $commitCheckboxes = $('.commit-range'); |
|
135 | 135 | // cache elements |
|
136 | 136 | var $commitRangeContainer = $('#rev_range_container'); |
|
137 | 137 | var $commitRangeClear = $('#rev_range_clear'); |
|
138 | 138 | |
|
139 | 139 | var checkboxRangeSelector = function(e){ |
|
140 | 140 | var selectedCheckboxes = []; |
|
141 | 141 | for (pos in $commitCheckboxes){ |
|
142 | 142 | if($commitCheckboxes[pos].checked){ |
|
143 | 143 | selectedCheckboxes.push($commitCheckboxes[pos]); |
|
144 | 144 | } |
|
145 | 145 | } |
|
146 | 146 | var open_new_pull_request = $('#open_new_pull_request'); |
|
147 | 147 | if(open_new_pull_request){ |
|
148 | 148 | var selected_changes = selectedCheckboxes.length; |
|
149 | 149 | if (selected_changes > 1 || selected_changes == 1 && templateContext.repo_type != 'hg') { |
|
150 | 150 | open_new_pull_request.hide(); |
|
151 | 151 | } else { |
|
152 | 152 | if (selected_changes == 1) { |
|
153 | 153 | open_new_pull_request.html(_gettext('Open new pull request for selected commit')); |
|
154 | 154 | } else if (selected_changes == 0) { |
|
155 | 155 | open_new_pull_request.html(_gettext('Open new pull request')); |
|
156 | 156 | } |
|
157 | 157 | open_new_pull_request.show(); |
|
158 | 158 | } |
|
159 | 159 | } |
|
160 | 160 | |
|
161 | 161 | if (selectedCheckboxes.length>0){ |
|
162 | 162 | var revEnd = selectedCheckboxes[0].name; |
|
163 | 163 | var revStart = selectedCheckboxes[selectedCheckboxes.length-1].name; |
|
164 | 164 | var url = pyroutes.url('changeset_home', |
|
165 | 165 | {'repo_name': '${c.repo_name}', |
|
166 | 166 | 'revision': revStart+'...'+revEnd}); |
|
167 | 167 | |
|
168 | 168 | var link = (revStart == revEnd) |
|
169 | 169 | ? _gettext('Show selected commit __S') |
|
170 | 170 | : _gettext('Show selected commits __S ... __E'); |
|
171 | 171 | |
|
172 | 172 | link = link.replace('__S', revStart.substr(0,6)); |
|
173 | 173 | link = link.replace('__E', revEnd.substr(0,6)); |
|
174 | 174 | |
|
175 | 175 | $commitRangeContainer |
|
176 | 176 | .attr('href',url) |
|
177 | 177 | .html(link) |
|
178 | 178 | .show(); |
|
179 | 179 | |
|
180 | 180 | $commitRangeClear.show(); |
|
181 | 181 | var _url = pyroutes.url('pullrequest_home', |
|
182 | 182 | {'repo_name': '${c.repo_name}', |
|
183 | 183 | 'commit': revEnd}); |
|
184 | 184 | open_new_pull_request.attr('href', _url); |
|
185 | 185 | $('#compare_fork_button').hide(); |
|
186 | 186 | } else { |
|
187 | 187 | $commitRangeContainer.hide(); |
|
188 | 188 | $commitRangeClear.hide(); |
|
189 | 189 | |
|
190 | 190 | %if c.branch_name: |
|
191 | 191 | var _url = pyroutes.url('pullrequest_home', |
|
192 | 192 | {'repo_name': '${c.repo_name}', |
|
193 | 193 | 'branch':'${c.branch_name}'}); |
|
194 | 194 | open_new_pull_request.attr('href', _url); |
|
195 | 195 | %else: |
|
196 | 196 | var _url = pyroutes.url('pullrequest_home', |
|
197 | 197 | {'repo_name': '${c.repo_name}'}); |
|
198 | 198 | open_new_pull_request.attr('href', _url); |
|
199 | 199 | %endif |
|
200 | 200 | $('#compare_fork_button').show(); |
|
201 | 201 | } |
|
202 | 202 | }; |
|
203 | 203 | |
|
204 | 204 | $commitCheckboxes.on('click', checkboxRangeSelector); |
|
205 | 205 | |
|
206 | 206 | $commitRangeClear.on('click',function(e) { |
|
207 | 207 | $commitCheckboxes.attr('checked', false); |
|
208 | 208 | checkboxRangeSelector(); |
|
209 | 209 | e.preventDefault(); |
|
210 | 210 | }); |
|
211 | 211 | |
|
212 | 212 | // make sure the buttons are consistent when navigate back and forth |
|
213 | 213 | checkboxRangeSelector(); |
|
214 | 214 | |
|
215 | 215 | var msgs = $('.message'); |
|
216 | 216 | // get first element height |
|
217 | 217 | var el = $('#graph_content .container')[0]; |
|
218 | 218 | var row_h = el.clientHeight; |
|
219 | 219 | for (var i=0; i < msgs.length; i++) { |
|
220 | 220 | var m = msgs[i]; |
|
221 | 221 | |
|
222 | 222 | var h = m.clientHeight; |
|
223 | 223 | var pad = $(m).css('padding'); |
|
224 | 224 | if (h > row_h) { |
|
225 | 225 | var offset = row_h - (h+12); |
|
226 | 226 | $(m.nextElementSibling).css('display','block'); |
|
227 | 227 | $(m.nextElementSibling).css('margin-top',offset+'px'); |
|
228 | 228 | } |
|
229 | 229 | } |
|
230 | 230 | |
|
231 | 231 | $("#clear_filter").on("click", function() { |
|
232 | 232 | var filter = {'repo_name': '${c.repo_name}'}; |
|
233 |
window.location = pyroutes.url('changelog |
|
|
233 | window.location = pyroutes.url('repo_changelog', filter); | |
|
234 | 234 | }); |
|
235 | 235 | |
|
236 | 236 | $("#branch_filter").select2({ |
|
237 | 237 | 'dropdownAutoWidth': true, |
|
238 | 238 | 'width': 'resolve', |
|
239 | 239 | 'placeholder': "${c.selected_name or _('Filter changelog')}", |
|
240 | 240 | containerCssClass: "drop-menu", |
|
241 | 241 | dropdownCssClass: "drop-menu-dropdown", |
|
242 | 242 | query: function(query){ |
|
243 | 243 | var key = 'cache'; |
|
244 | 244 | var cached = cache[key] ; |
|
245 | 245 | if(cached) { |
|
246 | 246 | var data = {results: []}; |
|
247 | 247 | //filter results |
|
248 | 248 | $.each(cached.results, function(){ |
|
249 | 249 | var section = this.text; |
|
250 | 250 | var children = []; |
|
251 | 251 | $.each(this.children, function(){ |
|
252 | 252 | if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){ |
|
253 | 253 | children.push({'id': this.id, 'text': this.text, 'type': this.type}) |
|
254 | 254 | } |
|
255 | 255 | }); |
|
256 | 256 | data.results.push({'text': section, 'children': children}); |
|
257 | 257 | query.callback({results: data.results}); |
|
258 | 258 | }); |
|
259 | 259 | }else{ |
|
260 | 260 | $.ajax({ |
|
261 | 261 | url: pyroutes.url('repo_refs_changelog_data', {'repo_name': '${c.repo_name}'}), |
|
262 | 262 | data: {}, |
|
263 | 263 | dataType: 'json', |
|
264 | 264 | type: 'GET', |
|
265 | 265 | success: function(data) { |
|
266 | 266 | cache[key] = data; |
|
267 | 267 | query.callback({results: data.results}); |
|
268 | 268 | } |
|
269 | 269 | }) |
|
270 | 270 | } |
|
271 | 271 | } |
|
272 | 272 | }); |
|
273 | 273 | $('#branch_filter').on('change', function(e){ |
|
274 | 274 | var data = $('#branch_filter').select2('data'); |
|
275 | 275 | var selected = data.text; |
|
276 | 276 | var filter = {'repo_name': '${c.repo_name}'}; |
|
277 | 277 | if(data.type == 'branch' || data.type == 'branch_closed'){ |
|
278 | 278 | filter["branch"] = selected; |
|
279 | 279 | } |
|
280 | 280 | else if (data.type == 'book'){ |
|
281 | 281 | filter["bookmark"] = selected; |
|
282 | 282 | } |
|
283 |
window.location = pyroutes.url('changelog |
|
|
283 | window.location = pyroutes.url('repo_changelog', filter); | |
|
284 | 284 | }); |
|
285 | 285 | |
|
286 | 286 | commitsController = new CommitsController(); |
|
287 | 287 | % if not c.changelog_for_path: |
|
288 | 288 | commitsController.reloadGraph(); |
|
289 | 289 | % endif |
|
290 | 290 | |
|
291 | 291 | }); |
|
292 | 292 | |
|
293 | 293 | </script> |
|
294 | 294 | </div> |
|
295 | 295 | % else: |
|
296 | 296 | ${_('There are no changes yet')} |
|
297 | 297 | % endif |
|
298 | 298 | </div> |
|
299 | 299 | </%def> |
@@ -1,142 +1,142 b'' | |||
|
1 | 1 | ## small box that displays changed/added/removed details fetched by AJAX |
|
2 | 2 | <%namespace name="base" file="/base/base.mako"/> |
|
3 | 3 | |
|
4 | 4 | |
|
5 | 5 | % if c.prev_page: |
|
6 | 6 | <tr> |
|
7 | 7 | <td colspan="9" class="load-more-commits"> |
|
8 | 8 | <a class="prev-commits" href="#loadPrevCommits" onclick="commitsController.loadPrev(this, ${c.prev_page}, '${c.branch_name}');return false"> |
|
9 | 9 | ${_('load previous')} |
|
10 | 10 | </a> |
|
11 | 11 | </td> |
|
12 | 12 | </tr> |
|
13 | 13 | % endif |
|
14 | 14 | |
|
15 | 15 | % for cnt,commit in enumerate(c.pagination): |
|
16 | 16 | <tr id="sha_${commit.raw_id}" class="changelogRow container ${'tablerow%s' % (cnt%2)}"> |
|
17 | 17 | |
|
18 | 18 | <td class="td-checkbox"> |
|
19 | 19 | ${h.checkbox(commit.raw_id,class_="commit-range")} |
|
20 | 20 | </td> |
|
21 | 21 | <td class="td-status"> |
|
22 | 22 | |
|
23 | 23 | %if c.statuses.get(commit.raw_id): |
|
24 | 24 | <div class="changeset-status-ico"> |
|
25 | 25 | %if c.statuses.get(commit.raw_id)[2]: |
|
26 | 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 | 27 | <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div> |
|
28 | 28 | </a> |
|
29 | 29 | %else: |
|
30 | 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 | 31 | <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div> |
|
32 | 32 | </a> |
|
33 | 33 | %endif |
|
34 | 34 | </div> |
|
35 | 35 | %else: |
|
36 | 36 | <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div> |
|
37 | 37 | %endif |
|
38 | 38 | </td> |
|
39 | 39 | <td class="td-comments comments-col"> |
|
40 | 40 | %if c.comments.get(commit.raw_id): |
|
41 | 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 | 42 | <i class="icon-comment"></i> ${len(c.comments[commit.raw_id])} |
|
43 | 43 | </a> |
|
44 | 44 | %endif |
|
45 | 45 | </td> |
|
46 | 46 | <td class="td-hash"> |
|
47 | 47 | <code> |
|
48 | 48 | <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id)}"> |
|
49 | 49 | <span class="${'commit_hash obsolete' if getattr(commit, 'obsolete', None) else 'commit_hash'}">${h.show_id(commit)}</span> |
|
50 | 50 | </a> |
|
51 | 51 | % if hasattr(commit, 'phase'): |
|
52 | 52 | % if commit.phase != 'public': |
|
53 | 53 | <span class="tag phase-${commit.phase} tooltip" title="${_('Commit phase')}">${commit.phase}</span> |
|
54 | 54 | % endif |
|
55 | 55 | % endif |
|
56 | 56 | |
|
57 | 57 | ## obsolete commits |
|
58 | 58 | % if hasattr(commit, 'obsolete'): |
|
59 | 59 | % if commit.obsolete: |
|
60 | 60 | <span class="tag obsolete-${commit.obsolete} tooltip" title="${_('Evolve State')}">${_('obsolete')}</span> |
|
61 | 61 | % endif |
|
62 | 62 | % endif |
|
63 | 63 | |
|
64 | 64 | ## hidden commits |
|
65 | 65 | % if hasattr(commit, 'hidden'): |
|
66 | 66 | % if commit.hidden: |
|
67 | 67 | <span class="tag obsolete-${commit.hidden} tooltip" title="${_('Evolve State')}">${_('hidden')}</span> |
|
68 | 68 | % endif |
|
69 | 69 | % endif |
|
70 | 70 | |
|
71 | 71 | </code> |
|
72 | 72 | </td> |
|
73 | 73 | <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_('Expand commit message')}" onclick="commitsController.expandCommit(this); return false"> |
|
74 | 74 | <div class="show_more_col"> |
|
75 | 75 | <i class="show_more"></i> |
|
76 | 76 | </div> |
|
77 | 77 | </td> |
|
78 | 78 | <td class="td-description mid"> |
|
79 | 79 | <div class="log-container truncate-wrap"> |
|
80 | 80 | <div class="message truncate" id="c-${commit.raw_id}">${h.urlify_commit_message(commit.message, c.repo_name)}</div> |
|
81 | 81 | </div> |
|
82 | 82 | </td> |
|
83 | 83 | |
|
84 | 84 | <td class="td-time"> |
|
85 | 85 | ${h.age_component(commit.date)} |
|
86 | 86 | </td> |
|
87 | 87 | <td class="td-user"> |
|
88 | 88 | ${base.gravatar_with_user(commit.author)} |
|
89 | 89 | </td> |
|
90 | 90 | |
|
91 | 91 | <td class="td-tags tags-col"> |
|
92 | 92 | <div id="t-${commit.raw_id}"> |
|
93 | 93 | |
|
94 | 94 | ## merge |
|
95 | 95 | %if commit.merge: |
|
96 | 96 | <span class="tag mergetag"> |
|
97 | 97 | <i class="icon-merge"></i>${_('merge')} |
|
98 | 98 | </span> |
|
99 | 99 | %endif |
|
100 | 100 | |
|
101 | 101 | ## branch |
|
102 | 102 | %if commit.branch: |
|
103 | 103 | <span class="tag branchtag" title="${h.tooltip(_('Branch %s') % commit.branch)}"> |
|
104 |
<a href="${h. |
|
|
104 | <a href="${h.route_path('repo_changelog',repo_name=c.repo_name,_query=dict(branch=commit.branch))}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a> | |
|
105 | 105 | </span> |
|
106 | 106 | %endif |
|
107 | 107 | |
|
108 | 108 | ## bookmarks |
|
109 | 109 | %if h.is_hg(c.rhodecode_repo): |
|
110 | 110 | %for book in commit.bookmarks: |
|
111 | 111 | <span class="tag booktag" title="${h.tooltip(_('Bookmark %s') % book)}"> |
|
112 | 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 | 113 | </span> |
|
114 | 114 | %endfor |
|
115 | 115 | %endif |
|
116 | 116 | |
|
117 | 117 | ## tags |
|
118 | 118 | %for tag in commit.tags: |
|
119 | 119 | <span class="tag tagtag" title="${h.tooltip(_('Tag %s') % tag)}"> |
|
120 | 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 | 121 | </span> |
|
122 | 122 | %endfor |
|
123 | 123 | |
|
124 | 124 | </div> |
|
125 | 125 | </td> |
|
126 | 126 | </tr> |
|
127 | 127 | % endfor |
|
128 | 128 | |
|
129 | 129 | % if c.next_page: |
|
130 | 130 | <tr> |
|
131 | 131 | <td colspan="9" class="load-more-commits"> |
|
132 | 132 | <a class="next-commits" href="#loadNextCommits" onclick="commitsController.loadNext(this, ${c.next_page}, '${c.branch_name}');return false"> |
|
133 | 133 | ${_('load next')} |
|
134 | 134 | </a> |
|
135 | 135 | </td> |
|
136 | 136 | </tr> |
|
137 | 137 | % endif |
|
138 | 138 | <tr class="chunk-graph-data" style="display:none" |
|
139 | 139 | data-graph='${c.graph_data|n}' |
|
140 | 140 | data-node='${c.prev_page}:${c.next_page}' |
|
141 | 141 | data-commits='${c.graph_commits|n}'> |
|
142 | 142 | </tr> No newline at end of file |
@@ -1,317 +1,317 b'' | |||
|
1 | 1 | ## DATA TABLE RE USABLE ELEMENTS |
|
2 | 2 | ## usage: |
|
3 | 3 | ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/> |
|
4 | 4 | <%namespace name="base" file="/base/base.mako"/> |
|
5 | 5 | |
|
6 | 6 | ## REPOSITORY RENDERERS |
|
7 | 7 | <%def name="quick_menu(repo_name)"> |
|
8 | 8 | <i class="pointer icon-more"></i> |
|
9 | 9 | <div class="menu_items_container hidden"> |
|
10 | 10 | <ul class="menu_items"> |
|
11 | 11 | <li> |
|
12 | 12 | <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}"> |
|
13 | 13 | <span>${_('Summary')}</span> |
|
14 | 14 | </a> |
|
15 | 15 | </li> |
|
16 | 16 | <li> |
|
17 |
<a title="${_('Changelog')}" href="${h. |
|
|
17 | <a title="${_('Changelog')}" href="${h.route_path('repo_changelog',repo_name=repo_name)}"> | |
|
18 | 18 | <span>${_('Changelog')}</span> |
|
19 | 19 | </a> |
|
20 | 20 | </li> |
|
21 | 21 | <li> |
|
22 | 22 | <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}"> |
|
23 | 23 | <span>${_('Files')}</span> |
|
24 | 24 | </a> |
|
25 | 25 | </li> |
|
26 | 26 | <li> |
|
27 | 27 | <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}"> |
|
28 | 28 | <span>${_('Fork')}</span> |
|
29 | 29 | </a> |
|
30 | 30 | </li> |
|
31 | 31 | </ul> |
|
32 | 32 | </div> |
|
33 | 33 | </%def> |
|
34 | 34 | |
|
35 | 35 | <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)"> |
|
36 | 36 | <% |
|
37 | 37 | def get_name(name,short_name=short_name): |
|
38 | 38 | if short_name: |
|
39 | 39 | return name.split('/')[-1] |
|
40 | 40 | else: |
|
41 | 41 | return name |
|
42 | 42 | %> |
|
43 | 43 | <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate"> |
|
44 | 44 | ##NAME |
|
45 | 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 | 47 | ##TYPE OF REPO |
|
48 | 48 | %if h.is_hg(rtype): |
|
49 | 49 | <span title="${_('Mercurial repository')}"><i class="icon-hg"></i></span> |
|
50 | 50 | %elif h.is_git(rtype): |
|
51 | 51 | <span title="${_('Git repository')}"><i class="icon-git"></i></span> |
|
52 | 52 | %elif h.is_svn(rtype): |
|
53 | 53 | <span title="${_('Subversion repository')}"><i class="icon-svn"></i></span> |
|
54 | 54 | %endif |
|
55 | 55 | |
|
56 | 56 | ##PRIVATE/PUBLIC |
|
57 | 57 | %if private and c.visual.show_private_icon: |
|
58 | 58 | <i class="icon-lock" title="${_('Private repository')}"></i> |
|
59 | 59 | %elif not private and c.visual.show_public_icon: |
|
60 | 60 | <i class="icon-unlock-alt" title="${_('Public repository')}"></i> |
|
61 | 61 | %else: |
|
62 | 62 | <span></span> |
|
63 | 63 | %endif |
|
64 | 64 | ${get_name(name)} |
|
65 | 65 | </a> |
|
66 | 66 | %if fork_of: |
|
67 | 67 | <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a> |
|
68 | 68 | %endif |
|
69 | 69 | %if rstate == 'repo_state_pending': |
|
70 | 70 | <i class="icon-cogs" title="${_('Repository creating in progress...')}"></i> |
|
71 | 71 | %endif |
|
72 | 72 | </div> |
|
73 | 73 | </%def> |
|
74 | 74 | |
|
75 | 75 | <%def name="repo_desc(description)"> |
|
76 | 76 | <div class="truncate-wrap">${description}</div> |
|
77 | 77 | </%def> |
|
78 | 78 | |
|
79 | 79 | <%def name="last_change(last_change)"> |
|
80 | 80 | ${h.age_component(last_change)} |
|
81 | 81 | </%def> |
|
82 | 82 | |
|
83 | 83 | <%def name="revision(name,rev,tip,author,last_msg)"> |
|
84 | 84 | <div> |
|
85 | 85 | %if rev >= 0: |
|
86 | 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 | 87 | %else: |
|
88 | 88 | ${_('No commits yet')} |
|
89 | 89 | %endif |
|
90 | 90 | </div> |
|
91 | 91 | </%def> |
|
92 | 92 | |
|
93 | 93 | <%def name="rss(name)"> |
|
94 | 94 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
95 | 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 | 96 | %else: |
|
97 | 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 | 98 | %endif |
|
99 | 99 | </%def> |
|
100 | 100 | |
|
101 | 101 | <%def name="atom(name)"> |
|
102 | 102 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
103 | 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 | 104 | %else: |
|
105 | 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 | 106 | %endif |
|
107 | 107 | </%def> |
|
108 | 108 | |
|
109 | 109 | <%def name="user_gravatar(email, size=16)"> |
|
110 | 110 | <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}"> |
|
111 | 111 | ${base.gravatar(email, 16)} |
|
112 | 112 | </div> |
|
113 | 113 | </%def> |
|
114 | 114 | |
|
115 | 115 | <%def name="repo_actions(repo_name, super_user=True)"> |
|
116 | 116 | <div> |
|
117 | 117 | <div class="grid_edit"> |
|
118 | 118 | <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}"> |
|
119 | 119 | <i class="icon-pencil"></i>Edit</a> |
|
120 | 120 | </div> |
|
121 | 121 | <div class="grid_delete"> |
|
122 | 122 | ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), method='POST', request=request)} |
|
123 | 123 | ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger", |
|
124 | 124 | onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")} |
|
125 | 125 | ${h.end_form()} |
|
126 | 126 | </div> |
|
127 | 127 | </div> |
|
128 | 128 | </%def> |
|
129 | 129 | |
|
130 | 130 | <%def name="repo_state(repo_state)"> |
|
131 | 131 | <div> |
|
132 | 132 | %if repo_state == 'repo_state_pending': |
|
133 | 133 | <div class="tag tag4">${_('Creating')}</div> |
|
134 | 134 | %elif repo_state == 'repo_state_created': |
|
135 | 135 | <div class="tag tag1">${_('Created')}</div> |
|
136 | 136 | %else: |
|
137 | 137 | <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div> |
|
138 | 138 | %endif |
|
139 | 139 | </div> |
|
140 | 140 | </%def> |
|
141 | 141 | |
|
142 | 142 | |
|
143 | 143 | ## REPO GROUP RENDERERS |
|
144 | 144 | <%def name="quick_repo_group_menu(repo_group_name)"> |
|
145 | 145 | <i class="pointer icon-more"></i> |
|
146 | 146 | <div class="menu_items_container hidden"> |
|
147 | 147 | <ul class="menu_items"> |
|
148 | 148 | <li> |
|
149 | 149 | <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}"> |
|
150 | 150 | <span class="icon"> |
|
151 | 151 | <i class="icon-file-text"></i> |
|
152 | 152 | </span> |
|
153 | 153 | <span>${_('Summary')}</span> |
|
154 | 154 | </a> |
|
155 | 155 | </li> |
|
156 | 156 | |
|
157 | 157 | </ul> |
|
158 | 158 | </div> |
|
159 | 159 | </%def> |
|
160 | 160 | |
|
161 | 161 | <%def name="repo_group_name(repo_group_name, children_groups=None)"> |
|
162 | 162 | <div> |
|
163 | 163 | <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}"> |
|
164 | 164 | <i class="icon-folder-close" title="${_('Repository group')}"></i> |
|
165 | 165 | %if children_groups: |
|
166 | 166 | ${h.literal(' » '.join(children_groups))} |
|
167 | 167 | %else: |
|
168 | 168 | ${repo_group_name} |
|
169 | 169 | %endif |
|
170 | 170 | </a> |
|
171 | 171 | </div> |
|
172 | 172 | </%def> |
|
173 | 173 | |
|
174 | 174 | <%def name="repo_group_desc(description)"> |
|
175 | 175 | <div class="truncate-wrap">${description}</div> |
|
176 | 176 | </%def> |
|
177 | 177 | |
|
178 | 178 | <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)"> |
|
179 | 179 | <div class="grid_edit"> |
|
180 | 180 | <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">Edit</a> |
|
181 | 181 | </div> |
|
182 | 182 | <div class="grid_delete"> |
|
183 | 183 | ${h.secure_form(h.url('delete_repo_group', group_name=repo_group_name),method='delete')} |
|
184 | 184 | ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger", |
|
185 | 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 | 186 | ${h.end_form()} |
|
187 | 187 | </div> |
|
188 | 188 | </%def> |
|
189 | 189 | |
|
190 | 190 | |
|
191 | 191 | <%def name="user_actions(user_id, username)"> |
|
192 | 192 | <div class="grid_edit"> |
|
193 | 193 | <a href="${h.url('edit_user',user_id=user_id)}" title="${_('Edit')}"> |
|
194 | 194 | <i class="icon-pencil"></i>Edit</a> |
|
195 | 195 | </div> |
|
196 | 196 | <div class="grid_delete"> |
|
197 | 197 | ${h.secure_form(h.url('delete_user', user_id=user_id),method='delete')} |
|
198 | 198 | ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger", |
|
199 | 199 | onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")} |
|
200 | 200 | ${h.end_form()} |
|
201 | 201 | </div> |
|
202 | 202 | </%def> |
|
203 | 203 | |
|
204 | 204 | <%def name="user_group_actions(user_group_id, user_group_name)"> |
|
205 | 205 | <div class="grid_edit"> |
|
206 | 206 | <a href="${h.url('edit_users_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a> |
|
207 | 207 | </div> |
|
208 | 208 | <div class="grid_delete"> |
|
209 | 209 | ${h.secure_form(h.url('delete_users_group', user_group_id=user_group_id),method='delete')} |
|
210 | 210 | ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger", |
|
211 | 211 | onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")} |
|
212 | 212 | ${h.end_form()} |
|
213 | 213 | </div> |
|
214 | 214 | </%def> |
|
215 | 215 | |
|
216 | 216 | |
|
217 | 217 | <%def name="user_name(user_id, username)"> |
|
218 | 218 | ${h.link_to(h.person(username, 'username_or_name_or_email'), h.url('edit_user', user_id=user_id))} |
|
219 | 219 | </%def> |
|
220 | 220 | |
|
221 | 221 | <%def name="user_profile(username)"> |
|
222 | 222 | ${base.gravatar_with_user(username, 16)} |
|
223 | 223 | </%def> |
|
224 | 224 | |
|
225 | 225 | <%def name="user_group_name(user_group_id, user_group_name)"> |
|
226 | 226 | <div> |
|
227 | 227 | <a href="${h.url('edit_users_group', user_group_id=user_group_id)}"> |
|
228 | 228 | <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a> |
|
229 | 229 | </div> |
|
230 | 230 | </%def> |
|
231 | 231 | |
|
232 | 232 | |
|
233 | 233 | ## GISTS |
|
234 | 234 | |
|
235 | 235 | <%def name="gist_gravatar(full_contact)"> |
|
236 | 236 | <div class="gist_gravatar"> |
|
237 | 237 | ${base.gravatar(full_contact, 30)} |
|
238 | 238 | </div> |
|
239 | 239 | </%def> |
|
240 | 240 | |
|
241 | 241 | <%def name="gist_access_id(gist_access_id, full_contact)"> |
|
242 | 242 | <div> |
|
243 | 243 | <b> |
|
244 | 244 | <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a> |
|
245 | 245 | </b> |
|
246 | 246 | </div> |
|
247 | 247 | </%def> |
|
248 | 248 | |
|
249 | 249 | <%def name="gist_author(full_contact, created_on, expires)"> |
|
250 | 250 | ${base.gravatar_with_user(full_contact, 16)} |
|
251 | 251 | </%def> |
|
252 | 252 | |
|
253 | 253 | |
|
254 | 254 | <%def name="gist_created(created_on)"> |
|
255 | 255 | <div class="created"> |
|
256 | 256 | ${h.age_component(created_on, time_is_local=True)} |
|
257 | 257 | </div> |
|
258 | 258 | </%def> |
|
259 | 259 | |
|
260 | 260 | <%def name="gist_expires(expires)"> |
|
261 | 261 | <div class="created"> |
|
262 | 262 | %if expires == -1: |
|
263 | 263 | ${_('never')} |
|
264 | 264 | %else: |
|
265 | 265 | ${h.age_component(h.time_to_utcdatetime(expires))} |
|
266 | 266 | %endif |
|
267 | 267 | </div> |
|
268 | 268 | </%def> |
|
269 | 269 | |
|
270 | 270 | <%def name="gist_type(gist_type)"> |
|
271 | 271 | %if gist_type != 'public': |
|
272 | 272 | <div class="tag">${_('Private')}</div> |
|
273 | 273 | %endif |
|
274 | 274 | </%def> |
|
275 | 275 | |
|
276 | 276 | <%def name="gist_description(gist_description)"> |
|
277 | 277 | ${gist_description} |
|
278 | 278 | </%def> |
|
279 | 279 | |
|
280 | 280 | |
|
281 | 281 | ## PULL REQUESTS GRID RENDERERS |
|
282 | 282 | |
|
283 | 283 | <%def name="pullrequest_target_repo(repo_name)"> |
|
284 | 284 | <div class="truncate"> |
|
285 | 285 | ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))} |
|
286 | 286 | </div> |
|
287 | 287 | </%def> |
|
288 | 288 | <%def name="pullrequest_status(status)"> |
|
289 | 289 | <div class="${'flag_status %s' % status} pull-left"></div> |
|
290 | 290 | </%def> |
|
291 | 291 | |
|
292 | 292 | <%def name="pullrequest_title(title, description)"> |
|
293 | 293 | ${title} <br/> |
|
294 | 294 | ${h.shorter(description, 40)} |
|
295 | 295 | </%def> |
|
296 | 296 | |
|
297 | 297 | <%def name="pullrequest_comments(comments_nr)"> |
|
298 | 298 | <i class="icon-comment"></i> ${comments_nr} |
|
299 | 299 | </%def> |
|
300 | 300 | |
|
301 | 301 | <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)"> |
|
302 | 302 | <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}"> |
|
303 | 303 | % if short: |
|
304 | 304 | #${pull_request_id} |
|
305 | 305 | % else: |
|
306 | 306 | ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}} |
|
307 | 307 | % endif |
|
308 | 308 | </a> |
|
309 | 309 | </%def> |
|
310 | 310 | |
|
311 | 311 | <%def name="pullrequest_updated_on(updated_on)"> |
|
312 | 312 | ${h.age_component(h.time_to_utcdatetime(updated_on))} |
|
313 | 313 | </%def> |
|
314 | 314 | |
|
315 | 315 | <%def name="pullrequest_author(full_contact)"> |
|
316 | 316 | ${base.gravatar_with_user(full_contact, 16)} |
|
317 | 317 | </%def> |
@@ -1,324 +1,324 b'' | |||
|
1 | 1 | <%inherit file="/base/base.mako"/> |
|
2 | 2 | |
|
3 | 3 | <%def name="title(*args)"> |
|
4 | 4 | ${_('%s Files') % c.repo_name} |
|
5 | 5 | %if hasattr(c,'file'): |
|
6 | 6 | · ${h.safe_unicode(c.file.path) or '\\'} |
|
7 | 7 | %endif |
|
8 | 8 | |
|
9 | 9 | %if c.rhodecode_name: |
|
10 | 10 | · ${h.branding(c.rhodecode_name)} |
|
11 | 11 | %endif |
|
12 | 12 | </%def> |
|
13 | 13 | |
|
14 | 14 | <%def name="breadcrumbs_links()"> |
|
15 | 15 | ${_('Files')} |
|
16 | 16 | %if c.file: |
|
17 | 17 | @ ${h.show_id(c.commit)} |
|
18 | 18 | %endif |
|
19 | 19 | </%def> |
|
20 | 20 | |
|
21 | 21 | <%def name="menu_bar_nav()"> |
|
22 | 22 | ${self.menu_items(active='repositories')} |
|
23 | 23 | </%def> |
|
24 | 24 | |
|
25 | 25 | <%def name="menu_bar_subnav()"> |
|
26 | 26 | ${self.repo_menu(active='files')} |
|
27 | 27 | </%def> |
|
28 | 28 | |
|
29 | 29 | <%def name="main()"> |
|
30 | 30 | <div class="title"> |
|
31 | 31 | ${self.repo_page_title(c.rhodecode_db_repo)} |
|
32 | 32 | </div> |
|
33 | 33 | |
|
34 | 34 | <div id="pjax-container" class="summary"> |
|
35 | 35 | <div id="files_data"> |
|
36 | 36 | <%include file='files_pjax.mako'/> |
|
37 | 37 | </div> |
|
38 | 38 | </div> |
|
39 | 39 | <script> |
|
40 | 40 | var curState = { |
|
41 | 41 | commit_id: "${c.commit.raw_id}" |
|
42 | 42 | }; |
|
43 | 43 | |
|
44 | 44 | var getState = function(context) { |
|
45 | 45 | var url = $(location).attr('href'); |
|
46 | 46 | var _base_url = '${h.route_path("repo_files",repo_name=c.repo_name,commit_id='',f_path='')}'; |
|
47 | 47 | var _annotate_url = '${h.route_path("repo_files:annotated",repo_name=c.repo_name,commit_id='',f_path='')}'; |
|
48 | 48 | _base_url = _base_url.replace('//', '/'); |
|
49 | 49 | _annotate_url = _annotate_url.replace('//', '/'); |
|
50 | 50 | |
|
51 | 51 | //extract f_path from url. |
|
52 | 52 | var parts = url.split(_base_url); |
|
53 | 53 | if (parts.length != 2) { |
|
54 | 54 | parts = url.split(_annotate_url); |
|
55 | 55 | if (parts.length != 2) { |
|
56 | 56 | var rev = "tip"; |
|
57 | 57 | var f_path = ""; |
|
58 | 58 | } else { |
|
59 | 59 | var parts2 = parts[1].split('/'); |
|
60 | 60 | var rev = parts2.shift(); // pop the first element which is the revision |
|
61 | 61 | var f_path = parts2.join('/'); |
|
62 | 62 | } |
|
63 | 63 | |
|
64 | 64 | } else { |
|
65 | 65 | var parts2 = parts[1].split('/'); |
|
66 | 66 | var rev = parts2.shift(); // pop the first element which is the revision |
|
67 | 67 | var f_path = parts2.join('/'); |
|
68 | 68 | } |
|
69 | 69 | |
|
70 | 70 | var _node_list_url = pyroutes.url('repo_files_nodelist', |
|
71 | 71 | {repo_name: templateContext.repo_name, |
|
72 | 72 | commit_id: rev, f_path: f_path}); |
|
73 | 73 | var _url_base = pyroutes.url('repo_files', |
|
74 | 74 | {repo_name: templateContext.repo_name, |
|
75 | 75 | commit_id: rev, f_path:'__FPATH__'}); |
|
76 | 76 | return { |
|
77 | 77 | url: url, |
|
78 | 78 | f_path: f_path, |
|
79 | 79 | rev: rev, |
|
80 | 80 | commit_id: curState.commit_id, |
|
81 | 81 | node_list_url: _node_list_url, |
|
82 | 82 | url_base: _url_base |
|
83 | 83 | }; |
|
84 | 84 | }; |
|
85 | 85 | |
|
86 | 86 | var metadataRequest = null; |
|
87 | 87 | var getFilesMetadata = function() { |
|
88 | 88 | if (metadataRequest && metadataRequest.readyState != 4) { |
|
89 | 89 | metadataRequest.abort(); |
|
90 | 90 | } |
|
91 | 91 | if (fileSourcePage) { |
|
92 | 92 | return false; |
|
93 | 93 | } |
|
94 | 94 | |
|
95 | 95 | if ($('#file-tree-wrapper').hasClass('full-load')) { |
|
96 | 96 | // in case our HTML wrapper has full-load class we don't |
|
97 | 97 | // trigger the async load of metadata |
|
98 | 98 | return false; |
|
99 | 99 | } |
|
100 | 100 | |
|
101 | 101 | var state = getState('metadata'); |
|
102 | 102 | var url_data = { |
|
103 | 103 | 'repo_name': templateContext.repo_name, |
|
104 | 104 | 'commit_id': state.commit_id, |
|
105 | 105 | 'f_path': state.f_path |
|
106 | 106 | }; |
|
107 | 107 | |
|
108 | 108 | var url = pyroutes.url('repo_nodetree_full', url_data); |
|
109 | 109 | |
|
110 | 110 | metadataRequest = $.ajax({url: url}); |
|
111 | 111 | |
|
112 | 112 | metadataRequest.done(function(data) { |
|
113 | 113 | $('#file-tree').html(data); |
|
114 | 114 | timeagoActivate(); |
|
115 | 115 | }); |
|
116 | 116 | metadataRequest.fail(function (data, textStatus, errorThrown) { |
|
117 | 117 | console.log(data); |
|
118 | 118 | if (data.status != 0) { |
|
119 | 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 | 124 | var callbacks = function() { |
|
125 | 125 | var state = getState('callbacks'); |
|
126 | 126 | timeagoActivate(); |
|
127 | 127 | |
|
128 | 128 | // used for history, and switch to |
|
129 | 129 | var initialCommitData = { |
|
130 | 130 | id: null, |
|
131 | 131 | text: '${_("Pick Commit")}', |
|
132 | 132 | type: 'sha', |
|
133 | 133 | raw_id: null, |
|
134 | 134 | files_url: null |
|
135 | 135 | }; |
|
136 | 136 | |
|
137 | 137 | if ($('#trimmed_message_box').height() < 50) { |
|
138 | 138 | $('#message_expand').hide(); |
|
139 | 139 | } |
|
140 | 140 | |
|
141 | 141 | $('#message_expand').on('click', function(e) { |
|
142 | 142 | $('#trimmed_message_box').css('max-height', 'none'); |
|
143 | 143 | $(this).hide(); |
|
144 | 144 | }); |
|
145 | 145 | |
|
146 | 146 | if (fileSourcePage) { |
|
147 | 147 | // variants for with source code, not tree view |
|
148 | 148 | |
|
149 | 149 | // select code link event |
|
150 | 150 | $("#hlcode").mouseup(getSelectionLink); |
|
151 | 151 | |
|
152 | 152 | // file history select2 |
|
153 | 153 | select2FileHistorySwitcher('#diff1', initialCommitData, state); |
|
154 | 154 | |
|
155 | 155 | // show at, diff to actions handlers |
|
156 | 156 | $('#diff1').on('change', function(e) { |
|
157 | 157 | $('#diff_to_commit').removeClass('disabled').removeAttr("disabled"); |
|
158 | 158 | $('#diff_to_commit').val(_gettext('Diff to Commit ') + e.val.truncateAfter(8, '...')); |
|
159 | 159 | |
|
160 | 160 | $('#show_at_commit').removeClass('disabled').removeAttr("disabled"); |
|
161 | 161 | $('#show_at_commit').val(_gettext('Show at Commit ') + e.val.truncateAfter(8, '...')); |
|
162 | 162 | }); |
|
163 | 163 | |
|
164 | 164 | $('#diff_to_commit').on('click', function(e) { |
|
165 | 165 | var diff1 = $('#diff1').val(); |
|
166 | 166 | var diff2 = $('#diff2').val(); |
|
167 | 167 | |
|
168 | 168 | var url_data = { |
|
169 | 169 | repo_name: templateContext.repo_name, |
|
170 | 170 | source_ref: diff1, |
|
171 | 171 | source_ref_type: 'rev', |
|
172 | 172 | target_ref: diff2, |
|
173 | 173 | target_ref_type: 'rev', |
|
174 | 174 | merge: 1, |
|
175 | 175 | f_path: state.f_path |
|
176 | 176 | }; |
|
177 | 177 | window.location = pyroutes.url('compare_url', url_data); |
|
178 | 178 | }); |
|
179 | 179 | |
|
180 | 180 | $('#show_at_commit').on('click', function(e) { |
|
181 | 181 | var diff1 = $('#diff1').val(); |
|
182 | 182 | |
|
183 | 183 | var annotate = $('#annotate').val(); |
|
184 | 184 | if (annotate === "True") { |
|
185 | 185 | var url = pyroutes.url('repo_files:annotated', |
|
186 | 186 | {'repo_name': templateContext.repo_name, |
|
187 | 187 | 'commit_id': diff1, 'f_path': state.f_path}); |
|
188 | 188 | } else { |
|
189 | 189 | var url = pyroutes.url('repo_files', |
|
190 | 190 | {'repo_name': templateContext.repo_name, |
|
191 | 191 | 'commit_id': diff1, 'f_path': state.f_path}); |
|
192 | 192 | } |
|
193 | 193 | window.location = url; |
|
194 | 194 | |
|
195 | 195 | }); |
|
196 | 196 | |
|
197 | 197 | // show more authors |
|
198 | 198 | $('#show_authors').on('click', function(e) { |
|
199 | 199 | e.preventDefault(); |
|
200 | 200 | var url = pyroutes.url('repo_file_authors', |
|
201 | 201 | {'repo_name': templateContext.repo_name, |
|
202 | 202 | 'commit_id': state.rev, 'f_path': state.f_path}); |
|
203 | 203 | |
|
204 | 204 | $.pjax({ |
|
205 | 205 | url: url, |
|
206 | 206 | data: 'annotate=${"1" if c.annotate else "0"}', |
|
207 | 207 | container: '#file_authors', |
|
208 | 208 | push: false, |
|
209 | 209 | timeout: pjaxTimeout |
|
210 | 210 | }).complete(function(){ |
|
211 | 211 | $('#show_authors').hide(); |
|
212 | 212 | }) |
|
213 | 213 | }); |
|
214 | 214 | |
|
215 | 215 | // load file short history |
|
216 | 216 | $('#file_history_overview').on('click', function(e) { |
|
217 | 217 | e.preventDefault(); |
|
218 | 218 | path = state.f_path; |
|
219 | 219 | if (path.indexOf("#") >= 0) { |
|
220 | 220 | path = path.slice(0, path.indexOf("#")); |
|
221 | 221 | } |
|
222 |
var url = pyroutes.url('changelog_fil |
|
|
222 | var url = pyroutes.url('repo_changelog_file', | |
|
223 | 223 | {'repo_name': templateContext.repo_name, |
|
224 |
' |
|
|
224 | 'commit_id': state.rev, 'f_path': path, 'limit': 6}); | |
|
225 | 225 | $('#file_history_container').show(); |
|
226 | 226 | $('#file_history_container').html('<div class="file-history-inner">{0}</div>'.format(_gettext('Loading ...'))); |
|
227 | 227 | |
|
228 | 228 | $.pjax({ |
|
229 | 229 | url: url, |
|
230 | 230 | container: '#file_history_container', |
|
231 | 231 | push: false, |
|
232 | 232 | timeout: pjaxTimeout |
|
233 | 233 | }) |
|
234 | 234 | }); |
|
235 | 235 | |
|
236 | 236 | } |
|
237 | 237 | else { |
|
238 | 238 | getFilesMetadata(); |
|
239 | 239 | |
|
240 | 240 | // fuzzy file filter |
|
241 | 241 | fileBrowserListeners(state.node_list_url, state.url_base); |
|
242 | 242 | |
|
243 | 243 | // switch to widget |
|
244 | 244 | select2RefSwitcher('#refs_filter', initialCommitData); |
|
245 | 245 | $('#refs_filter').on('change', function(e) { |
|
246 | 246 | var data = $('#refs_filter').select2('data'); |
|
247 | 247 | curState.commit_id = data.raw_id; |
|
248 | 248 | $.pjax({url: data.files_url, container: '#pjax-container', timeout: pjaxTimeout}); |
|
249 | 249 | }); |
|
250 | 250 | |
|
251 | 251 | $("#prev_commit_link").on('click', function(e) { |
|
252 | 252 | var data = $(this).data(); |
|
253 | 253 | curState.commit_id = data.commitId; |
|
254 | 254 | }); |
|
255 | 255 | |
|
256 | 256 | $("#next_commit_link").on('click', function(e) { |
|
257 | 257 | var data = $(this).data(); |
|
258 | 258 | curState.commit_id = data.commitId; |
|
259 | 259 | }); |
|
260 | 260 | |
|
261 | 261 | $('#at_rev').on("keypress", function(e) { |
|
262 | 262 | /* ENTER PRESSED */ |
|
263 | 263 | if (e.keyCode === 13) { |
|
264 | 264 | var rev = $('#at_rev').val(); |
|
265 | 265 | // explicit reload page here. with pjax entering bad input |
|
266 | 266 | // produces not so nice results |
|
267 | 267 | window.location = pyroutes.url('repo_files', |
|
268 | 268 | {'repo_name': templateContext.repo_name, |
|
269 | 269 | 'commit_id': rev, 'f_path': state.f_path}); |
|
270 | 270 | } |
|
271 | 271 | }); |
|
272 | 272 | } |
|
273 | 273 | }; |
|
274 | 274 | |
|
275 | 275 | var pjaxTimeout = 5000; |
|
276 | 276 | |
|
277 | 277 | $(document).pjax(".pjax-link", "#pjax-container", { |
|
278 | 278 | "fragment": "#pjax-content", |
|
279 | 279 | "maxCacheLength": 1000, |
|
280 | 280 | "timeout": pjaxTimeout |
|
281 | 281 | }); |
|
282 | 282 | |
|
283 | 283 | // define global back/forward states |
|
284 | 284 | var isPjaxPopState = false; |
|
285 | 285 | $(document).on('pjax:popstate', function() { |
|
286 | 286 | isPjaxPopState = true; |
|
287 | 287 | }); |
|
288 | 288 | |
|
289 | 289 | $(document).on('pjax:end', function(xhr, options) { |
|
290 | 290 | if (isPjaxPopState) { |
|
291 | 291 | isPjaxPopState = false; |
|
292 | 292 | callbacks(); |
|
293 | 293 | _NODEFILTER.resetFilter(); |
|
294 | 294 | } |
|
295 | 295 | |
|
296 | 296 | // run callback for tracking if defined for google analytics etc. |
|
297 | 297 | // this is used to trigger tracking on pjax |
|
298 | 298 | if (typeof window.rhodecode_statechange_callback !== 'undefined') { |
|
299 | 299 | var state = getState('statechange'); |
|
300 | 300 | rhodecode_statechange_callback(state.url, null) |
|
301 | 301 | } |
|
302 | 302 | }); |
|
303 | 303 | |
|
304 | 304 | $(document).on('pjax:success', function(event, xhr, options) { |
|
305 | 305 | if (event.target.id == "file_history_container") { |
|
306 | 306 | $('#file_history_overview').hide(); |
|
307 | 307 | $('#file_history_overview_full').show(); |
|
308 | 308 | timeagoActivate(); |
|
309 | 309 | } else { |
|
310 | 310 | callbacks(); |
|
311 | 311 | } |
|
312 | 312 | }); |
|
313 | 313 | |
|
314 | 314 | $(document).ready(function() { |
|
315 | 315 | callbacks(); |
|
316 | 316 | var search_GET = "${request.GET.get('search','')}"; |
|
317 | 317 | if (search_GET == "1") { |
|
318 | 318 | _NODEFILTER.initFilter(); |
|
319 | 319 | } |
|
320 | 320 | }); |
|
321 | 321 | |
|
322 | 322 | </script> |
|
323 | 323 | |
|
324 | 324 | </%def> |
@@ -1,196 +1,196 b'' | |||
|
1 | 1 | <%inherit file="/base/base.mako"/> |
|
2 | 2 | |
|
3 | 3 | <%def name="title()"> |
|
4 | 4 | ${_('%s File Edit') % c.repo_name} |
|
5 | 5 | %if c.rhodecode_name: |
|
6 | 6 | · ${h.branding(c.rhodecode_name)} |
|
7 | 7 | %endif |
|
8 | 8 | </%def> |
|
9 | 9 | |
|
10 | 10 | <%def name="menu_bar_nav()"> |
|
11 | 11 | ${self.menu_items(active='repositories')} |
|
12 | 12 | </%def> |
|
13 | 13 | |
|
14 | 14 | <%def name="breadcrumbs_links()"> |
|
15 | 15 | ${_('Edit file')} @ ${h.show_id(c.commit)} |
|
16 | 16 | </%def> |
|
17 | 17 | |
|
18 | 18 | <%def name="menu_bar_subnav()"> |
|
19 | 19 | ${self.repo_menu(active='files')} |
|
20 | 20 | </%def> |
|
21 | 21 | |
|
22 | 22 | <%def name="main()"> |
|
23 | 23 | <% renderer = h.renderer_from_filename(c.f_path)%> |
|
24 | 24 | <div class="box"> |
|
25 | 25 | <div class="title"> |
|
26 | 26 | ${self.repo_page_title(c.rhodecode_db_repo)} |
|
27 | 27 | </div> |
|
28 | 28 | <div class="edit-file-title"> |
|
29 | 29 | ${self.breadcrumbs()} |
|
30 | 30 | </div> |
|
31 | 31 | <div class="edit-file-fieldset"> |
|
32 | 32 | <div class="fieldset"> |
|
33 | 33 | <div id="destination-label" class="left-label"> |
|
34 | 34 | ${_('Path')}: |
|
35 | 35 | </div> |
|
36 | 36 | <div class="right-content"> |
|
37 | 37 | <div id="specify-custom-path-container"> |
|
38 | 38 | <span id="path-breadcrumbs">${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path)}</span> |
|
39 | 39 | </div> |
|
40 | 40 | </div> |
|
41 | 41 | </div> |
|
42 | 42 | </div> |
|
43 | 43 | |
|
44 | 44 | <div class="table"> |
|
45 | 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 | 46 | <div id="codeblock" class="codeblock" > |
|
47 | 47 | <div class="code-header"> |
|
48 | 48 | <div class="stats"> |
|
49 | 49 | <i class="icon-file"></i> |
|
50 | 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 | 51 | <span class="item">${h.format_byte_size_binary(c.file.size)}</span> |
|
52 | 52 | <span class="item last">${c.file.mimetype}</span> |
|
53 | 53 | <div class="buttons"> |
|
54 |
<a class="btn btn-mini" href="${h. |
|
|
54 | <a class="btn btn-mini" href="${h.route_path('repo_changelog_file',repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path)}"> | |
|
55 | 55 | <i class="icon-time"></i> ${_('history')} |
|
56 | 56 | </a> |
|
57 | 57 | |
|
58 | 58 | % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name): |
|
59 | 59 | % if not c.file.is_binary: |
|
60 | 60 | %if True: |
|
61 | 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 | 62 | %else: |
|
63 | 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 | 64 | %endif |
|
65 | 65 | |
|
66 | 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 | 67 | ${_('raw')} |
|
68 | 68 | </a> |
|
69 | 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 | 70 | <i class="icon-archive"></i> ${_('download')} |
|
71 | 71 | </a> |
|
72 | 72 | % endif |
|
73 | 73 | % endif |
|
74 | 74 | </div> |
|
75 | 75 | </div> |
|
76 | 76 | <div class="form"> |
|
77 | 77 | <label for="set_mode">${_('Editing file')}:</label> |
|
78 | 78 | ${'%s /' % c.file.dir_path if c.file.dir_path else c.file.dir_path} |
|
79 | 79 | <input id="filename" type="text" name="filename" value="${c.file.name}"> |
|
80 | 80 | |
|
81 | 81 | ${h.dropdownmenu('set_mode','plain',[('plain',_('plain'))],enable_filter=True)} |
|
82 | 82 | <label for="line_wrap">${_('line wraps')}</label> |
|
83 | 83 | ${h.dropdownmenu('line_wrap', 'off', [('on', _('on')), ('off', _('off')),])} |
|
84 | 84 | |
|
85 | 85 | <div id="render_preview" class="btn btn-small preview hidden">${_('Preview')}</div> |
|
86 | 86 | </div> |
|
87 | 87 | </div> |
|
88 | 88 | <div id="editor_container"> |
|
89 | 89 | <pre id="editor_pre"></pre> |
|
90 | 90 | <textarea id="editor" name="content" >${h.escape(c.file.content)|n}</textarea> |
|
91 | 91 | <div id="editor_preview" ></div> |
|
92 | 92 | </div> |
|
93 | 93 | </div> |
|
94 | 94 | </div> |
|
95 | 95 | |
|
96 | 96 | <div class="edit-file-fieldset"> |
|
97 | 97 | <div class="fieldset"> |
|
98 | 98 | <div id="commit-message-label" class="commit-message-label left-label"> |
|
99 | 99 | ${_('Commit Message')}: |
|
100 | 100 | </div> |
|
101 | 101 | <div class="right-content"> |
|
102 | 102 | <div class="message"> |
|
103 | 103 | <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea> |
|
104 | 104 | </div> |
|
105 | 105 | </div> |
|
106 | 106 | </div> |
|
107 | 107 | <div class="pull-right"> |
|
108 | 108 | ${h.reset('reset',_('Cancel'),class_="btn btn-small")} |
|
109 | 109 | ${h.submit('commit',_('Commit changes'),class_="btn btn-small btn-success")} |
|
110 | 110 | </div> |
|
111 | 111 | </div> |
|
112 | 112 | ${h.end_form()} |
|
113 | 113 | </div> |
|
114 | 114 | |
|
115 | 115 | <script type="text/javascript"> |
|
116 | 116 | $(document).ready(function(){ |
|
117 | 117 | var renderer = "${renderer}"; |
|
118 | 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 | 119 | var myCodeMirror = initCodeMirror('editor', reset_url); |
|
120 | 120 | |
|
121 | 121 | var modes_select = $('#set_mode'); |
|
122 | 122 | fillCodeMirrorOptions(modes_select); |
|
123 | 123 | |
|
124 | 124 | // try to detect the mode based on the file we edit |
|
125 | 125 | var mimetype = "${c.file.mimetype}"; |
|
126 | 126 | var detected_mode = detectCodeMirrorMode( |
|
127 | 127 | "${c.file.name}", mimetype); |
|
128 | 128 | |
|
129 | 129 | if(detected_mode){ |
|
130 | 130 | setCodeMirrorMode(myCodeMirror, detected_mode); |
|
131 | 131 | $(modes_select).select2("val", mimetype); |
|
132 | 132 | $(modes_select).change(); |
|
133 | 133 | setCodeMirrorMode(myCodeMirror, detected_mode); |
|
134 | 134 | } |
|
135 | 135 | |
|
136 | 136 | var filename_selector = '#filename'; |
|
137 | 137 | var callback = function(filename, mimetype, mode){ |
|
138 | 138 | CodeMirrorPreviewEnable(mode); |
|
139 | 139 | }; |
|
140 | 140 | // on change of select field set mode |
|
141 | 141 | setCodeMirrorModeFromSelect( |
|
142 | 142 | modes_select, filename_selector, myCodeMirror, callback); |
|
143 | 143 | |
|
144 | 144 | // on entering the new filename set mode, from given extension |
|
145 | 145 | setCodeMirrorModeFromInput( |
|
146 | 146 | modes_select, filename_selector, myCodeMirror, callback); |
|
147 | 147 | |
|
148 | 148 | // if the file is renderable set line wraps automatically |
|
149 | 149 | if (renderer !== ""){ |
|
150 | 150 | var line_wrap = 'on'; |
|
151 | 151 | $($('#line_wrap option[value="'+line_wrap+'"]')[0]).attr("selected", "selected"); |
|
152 | 152 | setCodeMirrorLineWrap(myCodeMirror, true); |
|
153 | 153 | } |
|
154 | 154 | // on select line wraps change the editor |
|
155 | 155 | $('#line_wrap').on('change', function(e){ |
|
156 | 156 | var selected = e.currentTarget; |
|
157 | 157 | var line_wraps = {'on': true, 'off': false}[selected.value]; |
|
158 | 158 | setCodeMirrorLineWrap(myCodeMirror, line_wraps) |
|
159 | 159 | }); |
|
160 | 160 | |
|
161 | 161 | // render preview/edit button |
|
162 | 162 | if (mimetype === 'text/x-rst' || mimetype === 'text/plain') { |
|
163 | 163 | $('#render_preview').removeClass('hidden'); |
|
164 | 164 | } |
|
165 | 165 | $('#render_preview').on('click', function(e){ |
|
166 | 166 | if($(this).hasClass('preview')){ |
|
167 | 167 | $(this).removeClass('preview'); |
|
168 | 168 | $(this).html("${_('Edit')}"); |
|
169 | 169 | $('#editor_preview').show(); |
|
170 | 170 | $(myCodeMirror.getWrapperElement()).hide(); |
|
171 | 171 | |
|
172 | 172 | var possible_renderer = { |
|
173 | 173 | 'rst':'rst', |
|
174 | 174 | 'markdown':'markdown', |
|
175 | 175 | 'gfm': 'markdown'}[myCodeMirror.getMode().name]; |
|
176 | 176 | var _text = myCodeMirror.getValue(); |
|
177 | 177 | var _renderer = possible_renderer || DEFAULT_RENDERER; |
|
178 | 178 | var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN}; |
|
179 | 179 | $('#editor_preview').html(_gettext('Loading ...')); |
|
180 | 180 | var url = pyroutes.url('changeset_comment_preview', {'repo_name': '${c.repo_name}'}); |
|
181 | 181 | |
|
182 | 182 | ajaxPOST(url, post_data, function(o){ |
|
183 | 183 | $('#editor_preview').html(o); |
|
184 | 184 | }) |
|
185 | 185 | } |
|
186 | 186 | else{ |
|
187 | 187 | $(this).addClass('preview'); |
|
188 | 188 | $(this).html("${_('Preview')}"); |
|
189 | 189 | $('#editor_preview').hide(); |
|
190 | 190 | $(myCodeMirror.getWrapperElement()).show(); |
|
191 | 191 | } |
|
192 | 192 | }); |
|
193 | 193 | |
|
194 | 194 | }) |
|
195 | 195 | </script> |
|
196 | 196 | </%def> |
@@ -1,92 +1,92 b'' | |||
|
1 | 1 | <%namespace name="sourceblock" file="/codeblocks/source.mako"/> |
|
2 | 2 | |
|
3 | 3 | <div id="codeblock" class="codeblock"> |
|
4 | 4 | <div class="codeblock-header"> |
|
5 | 5 | <div class="stats"> |
|
6 | 6 | <span> <strong>${c.file}</strong></span> |
|
7 | 7 | % if c.lf_node: |
|
8 | 8 | <span title="${_('This file is a pointer to large binary file')}"> | ${_('LargeFile')} ${h.format_byte_size_binary(c.lf_node.size)} </span> |
|
9 | 9 | % endif |
|
10 | 10 | <span> | ${c.file.lines()[0]} ${_ungettext('line', 'lines', c.file.lines()[0])}</span> |
|
11 | 11 | <span> | ${h.format_byte_size_binary(c.file.size)}</span> |
|
12 | 12 | <span> | ${c.file.mimetype} </span> |
|
13 | 13 | <span class="item last"> | ${h.get_lexer_for_filenode(c.file).__class__.__name__}</span> |
|
14 | 14 | </div> |
|
15 | 15 | <div class="buttons"> |
|
16 | 16 | <a id="file_history_overview" href="#"> |
|
17 | 17 | ${_('History')} |
|
18 | 18 | </a> |
|
19 |
<a id="file_history_overview_full" style="display: none" href="${h. |
|
|
19 | <a id="file_history_overview_full" style="display: none" href="${h.route_path('repo_changelog_file',repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path)}"> | |
|
20 | 20 | ${_('Show Full History')} |
|
21 | 21 | </a> | |
|
22 | 22 | %if c.annotate: |
|
23 | 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 | 24 | %else: |
|
25 | 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 | 26 | %endif |
|
27 | 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 | 29 | % if c.lf_node: |
|
30 | 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 | 31 | ${_('Download largefile')} |
|
32 | 32 | </a> |
|
33 | 33 | % else: |
|
34 | 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 | 35 | ${_('Download')} |
|
36 | 36 | </a> |
|
37 | 37 | % endif |
|
38 | 38 | |
|
39 | 39 | %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name): |
|
40 | 40 | | |
|
41 | 41 | %if c.on_branch_head and c.branch_or_raw_id and not c.file.is_binary: |
|
42 | 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 | 43 | ${_('Edit on Branch:{}').format(c.branch_name)} |
|
44 | 44 | </a> |
|
45 | 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 | 46 | </a> |
|
47 | 47 | %elif c.on_branch_head and c.branch_or_raw_id and c.file.is_binary: |
|
48 | 48 | ${h.link_to(_('Edit'), '#', class_="btn btn-link disabled tooltip", title=_('Editing binary files not allowed'))} |
|
49 | 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 | 50 | %else: |
|
51 | 51 | ${h.link_to(_('Edit'), '#', class_="btn btn-link disabled tooltip", title=_('Editing files allowed only when on branch head commit'))} |
|
52 | 52 | | ${h.link_to(_('Delete'), '#', class_="btn btn-danger btn-link disabled tooltip", title=_('Deleting files allowed only when on branch head commit'))} |
|
53 | 53 | %endif |
|
54 | 54 | %endif |
|
55 | 55 | </div> |
|
56 | 56 | </div> |
|
57 | 57 | <div id="file_history_container"></div> |
|
58 | 58 | <div class="code-body"> |
|
59 | 59 | %if c.file.is_binary: |
|
60 | 60 | <% rendered_binary = h.render_binary(c.repo_name, c.file)%> |
|
61 | 61 | % if rendered_binary: |
|
62 | 62 | ${rendered_binary} |
|
63 | 63 | % else: |
|
64 | 64 | <div> |
|
65 | 65 | ${_('Binary file (%s)') % c.file.mimetype} |
|
66 | 66 | </div> |
|
67 | 67 | % endif |
|
68 | 68 | %else: |
|
69 | 69 | % if c.file.size < c.visual.cut_off_limit_file: |
|
70 | 70 | %if c.renderer and not c.annotate: |
|
71 | 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 | 72 | %else: |
|
73 | 73 | <table class="cb codehilite"> |
|
74 | 74 | %if c.annotate: |
|
75 | 75 | <% color_hasher = h.color_hasher() %> |
|
76 | 76 | %for annotation, lines in c.annotated_lines: |
|
77 | 77 | ${sourceblock.render_annotation_lines(annotation, lines, color_hasher)} |
|
78 | 78 | %endfor |
|
79 | 79 | %else: |
|
80 | 80 | %for line_num, tokens in enumerate(c.lines, 1): |
|
81 | 81 | ${sourceblock.render_line(line_num, tokens)} |
|
82 | 82 | %endfor |
|
83 | 83 | %endif |
|
84 | 84 | </table> |
|
85 | 85 | %endif |
|
86 | 86 | %else: |
|
87 | 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 | 88 | h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))} |
|
89 | 89 | %endif |
|
90 | 90 | %endif |
|
91 | 91 | </div> |
|
92 | 92 | </div> No newline at end of file |
@@ -1,860 +1,860 b'' | |||
|
1 | 1 | <%inherit file="/base/base.mako"/> |
|
2 | 2 | <%namespace name="base" file="/base/base.mako"/> |
|
3 | 3 | |
|
4 | 4 | <%def name="title()"> |
|
5 | 5 | ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)} |
|
6 | 6 | %if c.rhodecode_name: |
|
7 | 7 | · ${h.branding(c.rhodecode_name)} |
|
8 | 8 | %endif |
|
9 | 9 | </%def> |
|
10 | 10 | |
|
11 | 11 | <%def name="breadcrumbs_links()"> |
|
12 | 12 | <span id="pr-title"> |
|
13 | 13 | ${c.pull_request.title} |
|
14 | 14 | %if c.pull_request.is_closed(): |
|
15 | 15 | (${_('Closed')}) |
|
16 | 16 | %endif |
|
17 | 17 | </span> |
|
18 | 18 | <div id="pr-title-edit" class="input" style="display: none;"> |
|
19 | 19 | ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)} |
|
20 | 20 | </div> |
|
21 | 21 | </%def> |
|
22 | 22 | |
|
23 | 23 | <%def name="menu_bar_nav()"> |
|
24 | 24 | ${self.menu_items(active='repositories')} |
|
25 | 25 | </%def> |
|
26 | 26 | |
|
27 | 27 | <%def name="menu_bar_subnav()"> |
|
28 | 28 | ${self.repo_menu(active='showpullrequest')} |
|
29 | 29 | </%def> |
|
30 | 30 | |
|
31 | 31 | <%def name="main()"> |
|
32 | 32 | |
|
33 | 33 | <script type="text/javascript"> |
|
34 | 34 | // TODO: marcink switch this to pyroutes |
|
35 | 35 | AJAX_COMMENT_DELETE_URL = "${h.url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}"; |
|
36 | 36 | templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id}; |
|
37 | 37 | </script> |
|
38 | 38 | <div class="box"> |
|
39 | 39 | |
|
40 | 40 | <div class="title"> |
|
41 | 41 | ${self.repo_page_title(c.rhodecode_db_repo)} |
|
42 | 42 | </div> |
|
43 | 43 | |
|
44 | 44 | ${self.breadcrumbs()} |
|
45 | 45 | |
|
46 | 46 | <div class="box pr-summary"> |
|
47 | 47 | |
|
48 | 48 | <div class="summary-details block-left"> |
|
49 | 49 | <% summary = lambda n:{False:'summary-short'}.get(n) %> |
|
50 | 50 | <div class="pr-details-title"> |
|
51 | 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 | 52 | %if c.allowed_to_update: |
|
53 | 53 | <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0"> |
|
54 | 54 | % if c.allowed_to_delete: |
|
55 | 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 | 56 | ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'), |
|
57 | 57 | class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")} |
|
58 | 58 | ${h.end_form()} |
|
59 | 59 | % else: |
|
60 | 60 | ${_('Delete')} |
|
61 | 61 | % endif |
|
62 | 62 | </div> |
|
63 | 63 | <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div> |
|
64 | 64 | <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div> |
|
65 | 65 | %endif |
|
66 | 66 | </div> |
|
67 | 67 | |
|
68 | 68 | <div id="summary" class="fields pr-details-content"> |
|
69 | 69 | <div class="field"> |
|
70 | 70 | <div class="label-summary"> |
|
71 | 71 | <label>${_('Source')}:</label> |
|
72 | 72 | </div> |
|
73 | 73 | <div class="input"> |
|
74 | 74 | <div class="pr-origininfo"> |
|
75 | 75 | ## branch link is only valid if it is a branch |
|
76 | 76 | <span class="tag"> |
|
77 | 77 | %if c.pull_request.source_ref_parts.type == 'branch': |
|
78 |
<a href="${h. |
|
|
78 | <a href="${h.route_path('repo_changelog', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a> | |
|
79 | 79 | %else: |
|
80 | 80 | ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name} |
|
81 | 81 | %endif |
|
82 | 82 | </span> |
|
83 | 83 | <span class="clone-url"> |
|
84 | 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 | 85 | </span> |
|
86 | 86 | <br/> |
|
87 | 87 | % if c.ancestor_commit: |
|
88 | 88 | ${_('Common ancestor')}: |
|
89 | 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 | 90 | % endif |
|
91 | 91 | </div> |
|
92 | 92 | <div class="pr-pullinfo"> |
|
93 | 93 | %if h.is_hg(c.pull_request.source_repo): |
|
94 | 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 | 95 | %elif h.is_git(c.pull_request.source_repo): |
|
96 | 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 | 97 | %endif |
|
98 | 98 | </div> |
|
99 | 99 | </div> |
|
100 | 100 | </div> |
|
101 | 101 | <div class="field"> |
|
102 | 102 | <div class="label-summary"> |
|
103 | 103 | <label>${_('Target')}:</label> |
|
104 | 104 | </div> |
|
105 | 105 | <div class="input"> |
|
106 | 106 | <div class="pr-targetinfo"> |
|
107 | 107 | ## branch link is only valid if it is a branch |
|
108 | 108 | <span class="tag"> |
|
109 | 109 | %if c.pull_request.target_ref_parts.type == 'branch': |
|
110 |
<a href="${h. |
|
|
110 | <a href="${h.route_path('repo_changelog', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a> | |
|
111 | 111 | %else: |
|
112 | 112 | ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name} |
|
113 | 113 | %endif |
|
114 | 114 | </span> |
|
115 | 115 | <span class="clone-url"> |
|
116 | 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 | 117 | </span> |
|
118 | 118 | </div> |
|
119 | 119 | </div> |
|
120 | 120 | </div> |
|
121 | 121 | |
|
122 | 122 | ## Link to the shadow repository. |
|
123 | 123 | <div class="field"> |
|
124 | 124 | <div class="label-summary"> |
|
125 | 125 | <label>${_('Merge')}:</label> |
|
126 | 126 | </div> |
|
127 | 127 | <div class="input"> |
|
128 | 128 | % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref: |
|
129 | 129 | <div class="pr-mergeinfo"> |
|
130 | 130 | %if h.is_hg(c.pull_request.target_repo): |
|
131 | 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 | 132 | %elif h.is_git(c.pull_request.target_repo): |
|
133 | 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 | 134 | %endif |
|
135 | 135 | </div> |
|
136 | 136 | % else: |
|
137 | 137 | <div class=""> |
|
138 | 138 | ${_('Shadow repository data not available')}. |
|
139 | 139 | </div> |
|
140 | 140 | % endif |
|
141 | 141 | </div> |
|
142 | 142 | </div> |
|
143 | 143 | |
|
144 | 144 | <div class="field"> |
|
145 | 145 | <div class="label-summary"> |
|
146 | 146 | <label>${_('Review')}:</label> |
|
147 | 147 | </div> |
|
148 | 148 | <div class="input"> |
|
149 | 149 | %if c.pull_request_review_status: |
|
150 | 150 | <div class="${'flag_status %s' % c.pull_request_review_status} tooltip pull-left"></div> |
|
151 | 151 | <span class="changeset-status-lbl tooltip"> |
|
152 | 152 | %if c.pull_request.is_closed(): |
|
153 | 153 | ${_('Closed')}, |
|
154 | 154 | %endif |
|
155 | 155 | ${h.commit_status_lbl(c.pull_request_review_status)} |
|
156 | 156 | </span> |
|
157 | 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 | 158 | %endif |
|
159 | 159 | </div> |
|
160 | 160 | </div> |
|
161 | 161 | <div class="field"> |
|
162 | 162 | <div class="pr-description-label label-summary"> |
|
163 | 163 | <label>${_('Description')}:</label> |
|
164 | 164 | </div> |
|
165 | 165 | <div id="pr-desc" class="input"> |
|
166 | 166 | <div class="pr-description">${h.urlify_commit_message(c.pull_request.description, c.repo_name)}</div> |
|
167 | 167 | </div> |
|
168 | 168 | <div id="pr-desc-edit" class="input textarea editor" style="display: none;"> |
|
169 | 169 | <textarea id="pr-description-input" size="30">${c.pull_request.description}</textarea> |
|
170 | 170 | </div> |
|
171 | 171 | </div> |
|
172 | 172 | |
|
173 | 173 | <div class="field"> |
|
174 | 174 | <div class="label-summary"> |
|
175 | 175 | <label>${_('Versions')}:</label> |
|
176 | 176 | </div> |
|
177 | 177 | |
|
178 | 178 | <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %> |
|
179 | 179 | <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %> |
|
180 | 180 | |
|
181 | 181 | <div class="pr-versions"> |
|
182 | 182 | % if c.show_version_changes: |
|
183 | 183 | <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %> |
|
184 | 184 | <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %> |
|
185 | 185 | <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions" |
|
186 | 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 | 187 | data-toggle-off="${_('Hide all versions of this pull request')}"> |
|
188 | 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 | 189 | </a> |
|
190 | 190 | <table> |
|
191 | 191 | ## SHOW ALL VERSIONS OF PR |
|
192 | 192 | <% ver_pr = None %> |
|
193 | 193 | |
|
194 | 194 | % for data in reversed(list(enumerate(c.versions, 1))): |
|
195 | 195 | <% ver_pos = data[0] %> |
|
196 | 196 | <% ver = data[1] %> |
|
197 | 197 | <% ver_pr = ver.pull_request_version_id %> |
|
198 | 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 | 200 | <tr class="version-pr" style="display: ${display_row}"> |
|
201 | 201 | <td> |
|
202 | 202 | <code> |
|
203 | 203 | <a href="${h.url.current(version=ver_pr or 'latest')}">v${ver_pos}</a> |
|
204 | 204 | </code> |
|
205 | 205 | </td> |
|
206 | 206 | <td> |
|
207 | 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 | 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 | 209 | </td> |
|
210 | 210 | <td> |
|
211 | 211 | <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %> |
|
212 | 212 | <div class="${'flag_status %s' % review_status} tooltip pull-left" title="${_('Your review status at this version')}"> |
|
213 | 213 | </div> |
|
214 | 214 | </td> |
|
215 | 215 | <td> |
|
216 | 216 | % if c.at_version_num != ver_pr: |
|
217 | 217 | <i class="icon-comment"></i> |
|
218 | 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 | 219 | G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])} |
|
220 | 220 | </code> |
|
221 | 221 | % endif |
|
222 | 222 | </td> |
|
223 | 223 | <td> |
|
224 | 224 | ##<code>${ver.source_ref_parts.commit_id[:6]}</code> |
|
225 | 225 | </td> |
|
226 | 226 | <td> |
|
227 | 227 | ${h.age_component(ver.updated_on, time_is_local=True)} |
|
228 | 228 | </td> |
|
229 | 229 | </tr> |
|
230 | 230 | % endfor |
|
231 | 231 | |
|
232 | 232 | <tr> |
|
233 | 233 | <td colspan="6"> |
|
234 | 234 | <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none" |
|
235 | 235 | data-label-text-locked="${_('select versions to show changes')}" |
|
236 | 236 | data-label-text-diff="${_('show changes between versions')}" |
|
237 | 237 | data-label-text-show="${_('show pull request for this version')}" |
|
238 | 238 | > |
|
239 | 239 | ${_('select versions to show changes')} |
|
240 | 240 | </button> |
|
241 | 241 | </td> |
|
242 | 242 | </tr> |
|
243 | 243 | |
|
244 | 244 | ## show comment/inline comments summary |
|
245 | 245 | <%def name="comments_summary()"> |
|
246 | 246 | <tr> |
|
247 | 247 | <td colspan="6" class="comments-summary-td"> |
|
248 | 248 | |
|
249 | 249 | % if c.at_version: |
|
250 | 250 | <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['display']) %> |
|
251 | 251 | <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['display']) %> |
|
252 | 252 | ${_('Comments at this version')}: |
|
253 | 253 | % else: |
|
254 | 254 | <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['until']) %> |
|
255 | 255 | <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['until']) %> |
|
256 | 256 | ${_('Comments for this pull request')}: |
|
257 | 257 | % endif |
|
258 | 258 | |
|
259 | 259 | |
|
260 | 260 | %if general_comm_count_ver: |
|
261 | 261 | <a href="#comments">${_("%d General ") % general_comm_count_ver}</a> |
|
262 | 262 | %else: |
|
263 | 263 | ${_("%d General ") % general_comm_count_ver} |
|
264 | 264 | %endif |
|
265 | 265 | |
|
266 | 266 | %if inline_comm_count_ver: |
|
267 | 267 | , <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${_("%d Inline") % inline_comm_count_ver}</a> |
|
268 | 268 | %else: |
|
269 | 269 | , ${_("%d Inline") % inline_comm_count_ver} |
|
270 | 270 | %endif |
|
271 | 271 | |
|
272 | 272 | %if outdated_comm_count_ver: |
|
273 | 273 | , <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">${_("%d Outdated") % outdated_comm_count_ver}</a> |
|
274 | 274 | <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated comments')}</a> |
|
275 | 275 | <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated comments')}</a> |
|
276 | 276 | %else: |
|
277 | 277 | , ${_("%d Outdated") % outdated_comm_count_ver} |
|
278 | 278 | %endif |
|
279 | 279 | </td> |
|
280 | 280 | </tr> |
|
281 | 281 | </%def> |
|
282 | 282 | ${comments_summary()} |
|
283 | 283 | </table> |
|
284 | 284 | % else: |
|
285 | 285 | <div class="input"> |
|
286 | 286 | ${_('Pull request versions not available')}. |
|
287 | 287 | </div> |
|
288 | 288 | <div> |
|
289 | 289 | <table> |
|
290 | 290 | ${comments_summary()} |
|
291 | 291 | </table> |
|
292 | 292 | </div> |
|
293 | 293 | % endif |
|
294 | 294 | </div> |
|
295 | 295 | </div> |
|
296 | 296 | |
|
297 | 297 | <div id="pr-save" class="field" style="display: none;"> |
|
298 | 298 | <div class="label-summary"></div> |
|
299 | 299 | <div class="input"> |
|
300 | 300 | <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span> |
|
301 | 301 | </div> |
|
302 | 302 | </div> |
|
303 | 303 | </div> |
|
304 | 304 | </div> |
|
305 | 305 | <div> |
|
306 | 306 | ## AUTHOR |
|
307 | 307 | <div class="reviewers-title block-right"> |
|
308 | 308 | <div class="pr-details-title"> |
|
309 | 309 | ${_('Author of this pull request')} |
|
310 | 310 | </div> |
|
311 | 311 | </div> |
|
312 | 312 | <div class="block-right pr-details-content reviewers"> |
|
313 | 313 | <ul class="group_members"> |
|
314 | 314 | <li> |
|
315 | 315 | ${self.gravatar_with_user(c.pull_request.author.email, 16)} |
|
316 | 316 | </li> |
|
317 | 317 | </ul> |
|
318 | 318 | </div> |
|
319 | 319 | |
|
320 | 320 | ## REVIEW RULES |
|
321 | 321 | <div id="review_rules" style="display: none" class="reviewers-title block-right"> |
|
322 | 322 | <div class="pr-details-title"> |
|
323 | 323 | ${_('Reviewer rules')} |
|
324 | 324 | %if c.allowed_to_update: |
|
325 | 325 | <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span> |
|
326 | 326 | %endif |
|
327 | 327 | </div> |
|
328 | 328 | <div class="pr-reviewer-rules"> |
|
329 | 329 | ## review rules will be appended here, by default reviewers logic |
|
330 | 330 | </div> |
|
331 | 331 | <input id="review_data" type="hidden" name="review_data" value=""> |
|
332 | 332 | </div> |
|
333 | 333 | |
|
334 | 334 | ## REVIEWERS |
|
335 | 335 | <div class="reviewers-title block-right"> |
|
336 | 336 | <div class="pr-details-title"> |
|
337 | 337 | ${_('Pull request reviewers')} |
|
338 | 338 | %if c.allowed_to_update: |
|
339 | 339 | <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span> |
|
340 | 340 | %endif |
|
341 | 341 | </div> |
|
342 | 342 | </div> |
|
343 | 343 | <div id="reviewers" class="block-right pr-details-content reviewers"> |
|
344 | 344 | ## members goes here ! |
|
345 | 345 | <input type="hidden" name="__start__" value="review_members:sequence"> |
|
346 | 346 | <ul id="review_members" class="group_members"> |
|
347 | 347 | %for member,reasons,mandatory,status in c.pull_request_reviewers: |
|
348 | 348 | <li id="reviewer_${member.user_id}" class="reviewer_entry"> |
|
349 | 349 | <div class="reviewers_member"> |
|
350 | 350 | <div class="reviewer_status tooltip" title="${h.tooltip(h.commit_status_lbl(status[0][1].status if status else 'not_reviewed'))}"> |
|
351 | 351 | <div class="${'flag_status %s' % (status[0][1].status if status else 'not_reviewed')} pull-left reviewer_member_status"></div> |
|
352 | 352 | </div> |
|
353 | 353 | <div id="reviewer_${member.user_id}_name" class="reviewer_name"> |
|
354 | 354 | ${self.gravatar_with_user(member.email, 16)} |
|
355 | 355 | </div> |
|
356 | 356 | <input type="hidden" name="__start__" value="reviewer:mapping"> |
|
357 | 357 | <input type="hidden" name="__start__" value="reasons:sequence"> |
|
358 | 358 | %for reason in reasons: |
|
359 | 359 | <div class="reviewer_reason">- ${reason}</div> |
|
360 | 360 | <input type="hidden" name="reason" value="${reason}"> |
|
361 | 361 | |
|
362 | 362 | %endfor |
|
363 | 363 | <input type="hidden" name="__end__" value="reasons:sequence"> |
|
364 | 364 | <input id="reviewer_${member.user_id}_input" type="hidden" value="${member.user_id}" name="user_id" /> |
|
365 | 365 | <input type="hidden" name="mandatory" value="${mandatory}"/> |
|
366 | 366 | <input type="hidden" name="__end__" value="reviewer:mapping"> |
|
367 | 367 | % if mandatory: |
|
368 | 368 | <div class="reviewer_member_mandatory_remove"> |
|
369 | 369 | <i class="icon-remove-sign"></i> |
|
370 | 370 | </div> |
|
371 | 371 | <div class="reviewer_member_mandatory"> |
|
372 | 372 | <i class="icon-lock" title="${h.tooltip(_('Mandatory reviewer'))}"></i> |
|
373 | 373 | </div> |
|
374 | 374 | % else: |
|
375 | 375 | %if c.allowed_to_update: |
|
376 | 376 | <div class="reviewer_member_remove action_button" onclick="reviewersController.removeReviewMember(${member.user_id}, true)" style="visibility: hidden;"> |
|
377 | 377 | <i class="icon-remove-sign" ></i> |
|
378 | 378 | </div> |
|
379 | 379 | %endif |
|
380 | 380 | % endif |
|
381 | 381 | </div> |
|
382 | 382 | </li> |
|
383 | 383 | %endfor |
|
384 | 384 | </ul> |
|
385 | 385 | <input type="hidden" name="__end__" value="review_members:sequence"> |
|
386 | 386 | |
|
387 | 387 | %if not c.pull_request.is_closed(): |
|
388 | 388 | <div id="add_reviewer" class="ac" style="display: none;"> |
|
389 | 389 | %if c.allowed_to_update: |
|
390 | 390 | % if not c.forbid_adding_reviewers: |
|
391 | 391 | <div id="add_reviewer_input" class="reviewer_ac"> |
|
392 | 392 | ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))} |
|
393 | 393 | <div id="reviewers_container"></div> |
|
394 | 394 | </div> |
|
395 | 395 | % endif |
|
396 | 396 | <div class="pull-right"> |
|
397 | 397 | <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button> |
|
398 | 398 | </div> |
|
399 | 399 | %endif |
|
400 | 400 | </div> |
|
401 | 401 | %endif |
|
402 | 402 | </div> |
|
403 | 403 | </div> |
|
404 | 404 | </div> |
|
405 | 405 | <div class="box"> |
|
406 | 406 | ##DIFF |
|
407 | 407 | <div class="table" > |
|
408 | 408 | <div id="changeset_compare_view_content"> |
|
409 | 409 | ##CS |
|
410 | 410 | % if c.missing_requirements: |
|
411 | 411 | <div class="box"> |
|
412 | 412 | <div class="alert alert-warning"> |
|
413 | 413 | <div> |
|
414 | 414 | <strong>${_('Missing requirements:')}</strong> |
|
415 | 415 | ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')} |
|
416 | 416 | </div> |
|
417 | 417 | </div> |
|
418 | 418 | </div> |
|
419 | 419 | % elif c.missing_commits: |
|
420 | 420 | <div class="box"> |
|
421 | 421 | <div class="alert alert-warning"> |
|
422 | 422 | <div> |
|
423 | 423 | <strong>${_('Missing commits')}:</strong> |
|
424 | 424 | ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')} |
|
425 | 425 | ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')} |
|
426 | 426 | </div> |
|
427 | 427 | </div> |
|
428 | 428 | </div> |
|
429 | 429 | % endif |
|
430 | 430 | |
|
431 | 431 | <div class="compare_view_commits_title"> |
|
432 | 432 | % if not c.compare_mode: |
|
433 | 433 | |
|
434 | 434 | % if c.at_version_pos: |
|
435 | 435 | <h4> |
|
436 | 436 | ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos} |
|
437 | 437 | </h4> |
|
438 | 438 | % endif |
|
439 | 439 | |
|
440 | 440 | <div class="pull-left"> |
|
441 | 441 | <div class="btn-group"> |
|
442 | 442 | <a |
|
443 | 443 | class="btn" |
|
444 | 444 | href="#" |
|
445 | 445 | onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false"> |
|
446 | 446 | ${ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)} |
|
447 | 447 | </a> |
|
448 | 448 | <a |
|
449 | 449 | class="btn" |
|
450 | 450 | href="#" |
|
451 | 451 | onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false"> |
|
452 | 452 | ${ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)} |
|
453 | 453 | </a> |
|
454 | 454 | </div> |
|
455 | 455 | </div> |
|
456 | 456 | |
|
457 | 457 | <div class="pull-right"> |
|
458 | 458 | % if c.allowed_to_update and not c.pull_request.is_closed(): |
|
459 | 459 | <a id="update_commits" class="btn btn-primary no-margin pull-right">${_('Update commits')}</a> |
|
460 | 460 | % else: |
|
461 | 461 | <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a> |
|
462 | 462 | % endif |
|
463 | 463 | |
|
464 | 464 | </div> |
|
465 | 465 | % endif |
|
466 | 466 | </div> |
|
467 | 467 | |
|
468 | 468 | % if not c.missing_commits: |
|
469 | 469 | % if c.compare_mode: |
|
470 | 470 | % if c.at_version: |
|
471 | 471 | <h4> |
|
472 | 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 | 473 | </h4> |
|
474 | 474 | |
|
475 | 475 | <div class="subtitle-compare"> |
|
476 | 476 | ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))} |
|
477 | 477 | </div> |
|
478 | 478 | |
|
479 | 479 | <div class="container"> |
|
480 | 480 | <table class="rctable compare_view_commits"> |
|
481 | 481 | <tr> |
|
482 | 482 | <th></th> |
|
483 | 483 | <th>${_('Time')}</th> |
|
484 | 484 | <th>${_('Author')}</th> |
|
485 | 485 | <th>${_('Commit')}</th> |
|
486 | 486 | <th></th> |
|
487 | 487 | <th>${_('Description')}</th> |
|
488 | 488 | </tr> |
|
489 | 489 | |
|
490 | 490 | % for c_type, commit in c.commit_changes: |
|
491 | 491 | % if c_type in ['a', 'r']: |
|
492 | 492 | <% |
|
493 | 493 | if c_type == 'a': |
|
494 | 494 | cc_title = _('Commit added in displayed changes') |
|
495 | 495 | elif c_type == 'r': |
|
496 | 496 | cc_title = _('Commit removed in displayed changes') |
|
497 | 497 | else: |
|
498 | 498 | cc_title = '' |
|
499 | 499 | %> |
|
500 | 500 | <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select"> |
|
501 | 501 | <td> |
|
502 | 502 | <div class="commit-change-indicator color-${c_type}-border"> |
|
503 | 503 | <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}"> |
|
504 | 504 | ${c_type.upper()} |
|
505 | 505 | </div> |
|
506 | 506 | </div> |
|
507 | 507 | </td> |
|
508 | 508 | <td class="td-time"> |
|
509 | 509 | ${h.age_component(commit.date)} |
|
510 | 510 | </td> |
|
511 | 511 | <td class="td-user"> |
|
512 | 512 | ${base.gravatar_with_user(commit.author, 16)} |
|
513 | 513 | </td> |
|
514 | 514 | <td class="td-hash"> |
|
515 | 515 | <code> |
|
516 | 516 | <a href="${h.url('changeset_home', repo_name=c.target_repo.repo_name, revision=commit.raw_id)}"> |
|
517 | 517 | r${commit.revision}:${h.short_id(commit.raw_id)} |
|
518 | 518 | </a> |
|
519 | 519 | ${h.hidden('revisions', commit.raw_id)} |
|
520 | 520 | </code> |
|
521 | 521 | </td> |
|
522 | 522 | <td class="expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}"> |
|
523 | 523 | <div class="show_more_col"> |
|
524 | 524 | <i class="show_more"></i> |
|
525 | 525 | </div> |
|
526 | 526 | </td> |
|
527 | 527 | <td class="mid td-description"> |
|
528 | 528 | <div class="log-container truncate-wrap"> |
|
529 | 529 | <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}"> |
|
530 | 530 | ${h.urlify_commit_message(commit.message, c.repo_name)} |
|
531 | 531 | </div> |
|
532 | 532 | </div> |
|
533 | 533 | </td> |
|
534 | 534 | </tr> |
|
535 | 535 | % endif |
|
536 | 536 | % endfor |
|
537 | 537 | </table> |
|
538 | 538 | </div> |
|
539 | 539 | |
|
540 | 540 | <script> |
|
541 | 541 | $('.expand_commit').on('click',function(e){ |
|
542 | 542 | var target_expand = $(this); |
|
543 | 543 | var cid = target_expand.data('commitId'); |
|
544 | 544 | |
|
545 | 545 | if (target_expand.hasClass('open')){ |
|
546 | 546 | $('#c-'+cid).css({ |
|
547 | 547 | 'height': '1.5em', |
|
548 | 548 | 'white-space': 'nowrap', |
|
549 | 549 | 'text-overflow': 'ellipsis', |
|
550 | 550 | 'overflow':'hidden' |
|
551 | 551 | }); |
|
552 | 552 | target_expand.removeClass('open'); |
|
553 | 553 | } |
|
554 | 554 | else { |
|
555 | 555 | $('#c-'+cid).css({ |
|
556 | 556 | 'height': 'auto', |
|
557 | 557 | 'white-space': 'pre-line', |
|
558 | 558 | 'text-overflow': 'initial', |
|
559 | 559 | 'overflow':'visible' |
|
560 | 560 | }); |
|
561 | 561 | target_expand.addClass('open'); |
|
562 | 562 | } |
|
563 | 563 | }); |
|
564 | 564 | </script> |
|
565 | 565 | |
|
566 | 566 | % endif |
|
567 | 567 | |
|
568 | 568 | % else: |
|
569 | 569 | <%include file="/compare/compare_commits.mako" /> |
|
570 | 570 | % endif |
|
571 | 571 | |
|
572 | 572 | <div class="cs_files"> |
|
573 | 573 | <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/> |
|
574 | 574 | ${cbdiffs.render_diffset_menu()} |
|
575 | 575 | ${cbdiffs.render_diffset( |
|
576 | 576 | c.diffset, use_comments=True, |
|
577 | 577 | collapse_when_files_over=30, |
|
578 | 578 | disable_new_comments=not c.allowed_to_comment, |
|
579 | 579 | deleted_files_comments=c.deleted_files_comments)} |
|
580 | 580 | </div> |
|
581 | 581 | % else: |
|
582 | 582 | ## skipping commits we need to clear the view for missing commits |
|
583 | 583 | <div style="clear:both;"></div> |
|
584 | 584 | % endif |
|
585 | 585 | |
|
586 | 586 | </div> |
|
587 | 587 | </div> |
|
588 | 588 | |
|
589 | 589 | ## template for inline comment form |
|
590 | 590 | <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/> |
|
591 | 591 | |
|
592 | 592 | ## render general comments |
|
593 | 593 | |
|
594 | 594 | <div id="comment-tr-show"> |
|
595 | 595 | <div class="comment"> |
|
596 | 596 | % if general_outdated_comm_count_ver: |
|
597 | 597 | <div class="meta"> |
|
598 | 598 | % if general_outdated_comm_count_ver == 1: |
|
599 | 599 | ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)}, |
|
600 | 600 | <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a> |
|
601 | 601 | % else: |
|
602 | 602 | ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)}, |
|
603 | 603 | <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a> |
|
604 | 604 | % endif |
|
605 | 605 | </div> |
|
606 | 606 | % endif |
|
607 | 607 | </div> |
|
608 | 608 | </div> |
|
609 | 609 | |
|
610 | 610 | ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)} |
|
611 | 611 | |
|
612 | 612 | % if not c.pull_request.is_closed(): |
|
613 | 613 | ## merge status, and merge action |
|
614 | 614 | <div class="pull-request-merge"> |
|
615 | 615 | <%include file="/pullrequests/pullrequest_merge_checks.mako"/> |
|
616 | 616 | </div> |
|
617 | 617 | |
|
618 | 618 | ## main comment form and it status |
|
619 | 619 | ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name, |
|
620 | 620 | pull_request_id=c.pull_request.pull_request_id), |
|
621 | 621 | c.pull_request_review_status, |
|
622 | 622 | is_pull_request=True, change_status=c.allowed_to_change_status)} |
|
623 | 623 | %endif |
|
624 | 624 | |
|
625 | 625 | <script type="text/javascript"> |
|
626 | 626 | if (location.hash) { |
|
627 | 627 | var result = splitDelimitedHash(location.hash); |
|
628 | 628 | var line = $('html').find(result.loc); |
|
629 | 629 | // show hidden comments if we use location.hash |
|
630 | 630 | if (line.hasClass('comment-general')) { |
|
631 | 631 | $(line).show(); |
|
632 | 632 | } else if (line.hasClass('comment-inline')) { |
|
633 | 633 | $(line).show(); |
|
634 | 634 | var $cb = $(line).closest('.cb'); |
|
635 | 635 | $cb.removeClass('cb-collapsed') |
|
636 | 636 | } |
|
637 | 637 | if (line.length > 0){ |
|
638 | 638 | offsetScroll(line, 70); |
|
639 | 639 | } |
|
640 | 640 | } |
|
641 | 641 | |
|
642 | 642 | versionController = new VersionController(); |
|
643 | 643 | versionController.init(); |
|
644 | 644 | |
|
645 | 645 | reviewersController = new ReviewersController(); |
|
646 | 646 | |
|
647 | 647 | $(function(){ |
|
648 | 648 | |
|
649 | 649 | // custom code mirror |
|
650 | 650 | var codeMirrorInstance = initPullRequestsCodeMirror('#pr-description-input'); |
|
651 | 651 | |
|
652 | 652 | var PRDetails = { |
|
653 | 653 | editButton: $('#open_edit_pullrequest'), |
|
654 | 654 | closeButton: $('#close_edit_pullrequest'), |
|
655 | 655 | deleteButton: $('#delete_pullrequest'), |
|
656 | 656 | viewFields: $('#pr-desc, #pr-title'), |
|
657 | 657 | editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'), |
|
658 | 658 | |
|
659 | 659 | init: function() { |
|
660 | 660 | var that = this; |
|
661 | 661 | this.editButton.on('click', function(e) { that.edit(); }); |
|
662 | 662 | this.closeButton.on('click', function(e) { that.view(); }); |
|
663 | 663 | }, |
|
664 | 664 | |
|
665 | 665 | edit: function(event) { |
|
666 | 666 | this.viewFields.hide(); |
|
667 | 667 | this.editButton.hide(); |
|
668 | 668 | this.deleteButton.hide(); |
|
669 | 669 | this.closeButton.show(); |
|
670 | 670 | this.editFields.show(); |
|
671 | 671 | codeMirrorInstance.refresh(); |
|
672 | 672 | }, |
|
673 | 673 | |
|
674 | 674 | view: function(event) { |
|
675 | 675 | this.editButton.show(); |
|
676 | 676 | this.deleteButton.show(); |
|
677 | 677 | this.editFields.hide(); |
|
678 | 678 | this.closeButton.hide(); |
|
679 | 679 | this.viewFields.show(); |
|
680 | 680 | } |
|
681 | 681 | }; |
|
682 | 682 | |
|
683 | 683 | var ReviewersPanel = { |
|
684 | 684 | editButton: $('#open_edit_reviewers'), |
|
685 | 685 | closeButton: $('#close_edit_reviewers'), |
|
686 | 686 | addButton: $('#add_reviewer'), |
|
687 | 687 | removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove,.reviewer_member_mandatory'), |
|
688 | 688 | |
|
689 | 689 | init: function() { |
|
690 | 690 | var self = this; |
|
691 | 691 | this.editButton.on('click', function(e) { self.edit(); }); |
|
692 | 692 | this.closeButton.on('click', function(e) { self.close(); }); |
|
693 | 693 | }, |
|
694 | 694 | |
|
695 | 695 | edit: function(event) { |
|
696 | 696 | this.editButton.hide(); |
|
697 | 697 | this.closeButton.show(); |
|
698 | 698 | this.addButton.show(); |
|
699 | 699 | this.removeButtons.css('visibility', 'visible'); |
|
700 | 700 | // review rules |
|
701 | 701 | reviewersController.loadReviewRules( |
|
702 | 702 | ${c.pull_request.reviewer_data_json | n}); |
|
703 | 703 | }, |
|
704 | 704 | |
|
705 | 705 | close: function(event) { |
|
706 | 706 | this.editButton.show(); |
|
707 | 707 | this.closeButton.hide(); |
|
708 | 708 | this.addButton.hide(); |
|
709 | 709 | this.removeButtons.css('visibility', 'hidden'); |
|
710 | 710 | // hide review rules |
|
711 | 711 | reviewersController.hideReviewRules() |
|
712 | 712 | } |
|
713 | 713 | }; |
|
714 | 714 | |
|
715 | 715 | PRDetails.init(); |
|
716 | 716 | ReviewersPanel.init(); |
|
717 | 717 | |
|
718 | 718 | showOutdated = function(self){ |
|
719 | 719 | $('.comment-inline.comment-outdated').show(); |
|
720 | 720 | $('.filediff-outdated').show(); |
|
721 | 721 | $('.showOutdatedComments').hide(); |
|
722 | 722 | $('.hideOutdatedComments').show(); |
|
723 | 723 | }; |
|
724 | 724 | |
|
725 | 725 | hideOutdated = function(self){ |
|
726 | 726 | $('.comment-inline.comment-outdated').hide(); |
|
727 | 727 | $('.filediff-outdated').hide(); |
|
728 | 728 | $('.hideOutdatedComments').hide(); |
|
729 | 729 | $('.showOutdatedComments').show(); |
|
730 | 730 | }; |
|
731 | 731 | |
|
732 | 732 | refreshMergeChecks = function(){ |
|
733 | 733 | var loadUrl = "${h.url.current(merge_checks=1)}"; |
|
734 | 734 | $('.pull-request-merge').css('opacity', 0.3); |
|
735 | 735 | $('.action-buttons-extra').css('opacity', 0.3); |
|
736 | 736 | |
|
737 | 737 | $('.pull-request-merge').load( |
|
738 | 738 | loadUrl, function() { |
|
739 | 739 | $('.pull-request-merge').css('opacity', 1); |
|
740 | 740 | |
|
741 | 741 | $('.action-buttons-extra').css('opacity', 1); |
|
742 | 742 | injectCloseAction(); |
|
743 | 743 | } |
|
744 | 744 | ); |
|
745 | 745 | }; |
|
746 | 746 | |
|
747 | 747 | injectCloseAction = function() { |
|
748 | 748 | var closeAction = $('#close-pull-request-action').html(); |
|
749 | 749 | var $actionButtons = $('.action-buttons-extra'); |
|
750 | 750 | // clear the action before |
|
751 | 751 | $actionButtons.html(""); |
|
752 | 752 | $actionButtons.html(closeAction); |
|
753 | 753 | }; |
|
754 | 754 | |
|
755 | 755 | closePullRequest = function (status) { |
|
756 | 756 | // inject closing flag |
|
757 | 757 | $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">'); |
|
758 | 758 | $(generalCommentForm.statusChange).select2("val", status).trigger('change'); |
|
759 | 759 | $(generalCommentForm.submitForm).submit(); |
|
760 | 760 | }; |
|
761 | 761 | |
|
762 | 762 | $('#show-outdated-comments').on('click', function(e){ |
|
763 | 763 | var button = $(this); |
|
764 | 764 | var outdated = $('.comment-outdated'); |
|
765 | 765 | |
|
766 | 766 | if (button.html() === "(Show)") { |
|
767 | 767 | button.html("(Hide)"); |
|
768 | 768 | outdated.show(); |
|
769 | 769 | } else { |
|
770 | 770 | button.html("(Show)"); |
|
771 | 771 | outdated.hide(); |
|
772 | 772 | } |
|
773 | 773 | }); |
|
774 | 774 | |
|
775 | 775 | $('.show-inline-comments').on('change', function(e){ |
|
776 | 776 | var show = 'none'; |
|
777 | 777 | var target = e.currentTarget; |
|
778 | 778 | if(target.checked){ |
|
779 | 779 | show = '' |
|
780 | 780 | } |
|
781 | 781 | var boxid = $(target).attr('id_for'); |
|
782 | 782 | var comments = $('#{0} .inline-comments'.format(boxid)); |
|
783 | 783 | var fn_display = function(idx){ |
|
784 | 784 | $(this).css('display', show); |
|
785 | 785 | }; |
|
786 | 786 | $(comments).each(fn_display); |
|
787 | 787 | var btns = $('#{0} .inline-comments-button'.format(boxid)); |
|
788 | 788 | $(btns).each(fn_display); |
|
789 | 789 | }); |
|
790 | 790 | |
|
791 | 791 | $('#merge_pull_request_form').submit(function() { |
|
792 | 792 | if (!$('#merge_pull_request').attr('disabled')) { |
|
793 | 793 | $('#merge_pull_request').attr('disabled', 'disabled'); |
|
794 | 794 | } |
|
795 | 795 | return true; |
|
796 | 796 | }); |
|
797 | 797 | |
|
798 | 798 | $('#edit_pull_request').on('click', function(e){ |
|
799 | 799 | var title = $('#pr-title-input').val(); |
|
800 | 800 | var description = codeMirrorInstance.getValue(); |
|
801 | 801 | editPullRequest( |
|
802 | 802 | "${c.repo_name}", "${c.pull_request.pull_request_id}", |
|
803 | 803 | title, description); |
|
804 | 804 | }); |
|
805 | 805 | |
|
806 | 806 | $('#update_pull_request').on('click', function(e){ |
|
807 | 807 | $(this).attr('disabled', 'disabled'); |
|
808 | 808 | $(this).addClass('disabled'); |
|
809 | 809 | $(this).html(_gettext('Saving...')); |
|
810 | 810 | reviewersController.updateReviewers( |
|
811 | 811 | "${c.repo_name}", "${c.pull_request.pull_request_id}"); |
|
812 | 812 | }); |
|
813 | 813 | |
|
814 | 814 | $('#update_commits').on('click', function(e){ |
|
815 | 815 | var isDisabled = !$(e.currentTarget).attr('disabled'); |
|
816 | 816 | $(e.currentTarget).attr('disabled', 'disabled'); |
|
817 | 817 | $(e.currentTarget).addClass('disabled'); |
|
818 | 818 | $(e.currentTarget).removeClass('btn-primary'); |
|
819 | 819 | $(e.currentTarget).text(_gettext('Updating...')); |
|
820 | 820 | if(isDisabled){ |
|
821 | 821 | updateCommits( |
|
822 | 822 | "${c.repo_name}", "${c.pull_request.pull_request_id}"); |
|
823 | 823 | } |
|
824 | 824 | }); |
|
825 | 825 | // fixing issue with caches on firefox |
|
826 | 826 | $('#update_commits').removeAttr("disabled"); |
|
827 | 827 | |
|
828 | 828 | $('.show-inline-comments').on('click', function(e){ |
|
829 | 829 | var boxid = $(this).attr('data-comment-id'); |
|
830 | 830 | var button = $(this); |
|
831 | 831 | |
|
832 | 832 | if(button.hasClass("comments-visible")) { |
|
833 | 833 | $('#{0} .inline-comments'.format(boxid)).each(function(index){ |
|
834 | 834 | $(this).hide(); |
|
835 | 835 | }); |
|
836 | 836 | button.removeClass("comments-visible"); |
|
837 | 837 | } else { |
|
838 | 838 | $('#{0} .inline-comments'.format(boxid)).each(function(index){ |
|
839 | 839 | $(this).show(); |
|
840 | 840 | }); |
|
841 | 841 | button.addClass("comments-visible"); |
|
842 | 842 | } |
|
843 | 843 | }); |
|
844 | 844 | |
|
845 | 845 | // register submit callback on commentForm form to track TODOs |
|
846 | 846 | window.commentFormGlobalSubmitSuccessCallback = function(){ |
|
847 | 847 | refreshMergeChecks(); |
|
848 | 848 | }; |
|
849 | 849 | // initial injection |
|
850 | 850 | injectCloseAction(); |
|
851 | 851 | |
|
852 | 852 | ReviewerAutoComplete('#user'); |
|
853 | 853 | |
|
854 | 854 | }) |
|
855 | 855 | </script> |
|
856 | 856 | |
|
857 | 857 | </div> |
|
858 | 858 | </div> |
|
859 | 859 | |
|
860 | 860 | </%def> |
@@ -1,100 +1,100 b'' | |||
|
1 | 1 | <%def name="highlight_text_file(terms, text, url, line_context=3, |
|
2 | 2 | max_lines=10, |
|
3 | 3 | mimetype=None, filepath=None)"> |
|
4 | 4 | <% |
|
5 | 5 | lines = text.split('\n') |
|
6 | 6 | lines_of_interest = set() |
|
7 | 7 | matching_lines = h.get_matching_line_offsets(lines, terms) |
|
8 | 8 | shown_matching_lines = 0 |
|
9 | 9 | |
|
10 | 10 | for line_number in matching_lines: |
|
11 | 11 | if len(lines_of_interest) < max_lines: |
|
12 | 12 | lines_of_interest |= set(range( |
|
13 | 13 | max(line_number - line_context, 0), |
|
14 | 14 | min(line_number + line_context, len(lines) + 1))) |
|
15 | 15 | shown_matching_lines += 1 |
|
16 | 16 | |
|
17 | 17 | %> |
|
18 | 18 | ${h.code_highlight( |
|
19 | 19 | text, |
|
20 | 20 | h.get_lexer_safe( |
|
21 | 21 | mimetype=mimetype, |
|
22 | 22 | filepath=filepath, |
|
23 | 23 | ), |
|
24 | 24 | h.SearchContentCodeHtmlFormatter( |
|
25 | 25 | linenos=True, |
|
26 | 26 | cssclass="code-highlight", |
|
27 | 27 | url=url, |
|
28 | 28 | query_terms=terms, |
|
29 | 29 | only_line_numbers=lines_of_interest |
|
30 | 30 | ))|n} |
|
31 | 31 | |
|
32 | 32 | %if len(matching_lines) > shown_matching_lines: |
|
33 | 33 | <a href="${url}"> |
|
34 | 34 | ${len(matching_lines) - shown_matching_lines} ${_('more matches in this file')} |
|
35 | 35 | </a> |
|
36 | 36 | %endif |
|
37 | 37 | </%def> |
|
38 | 38 | |
|
39 | 39 | <div class="search-results"> |
|
40 | 40 | %for entry in c.formatted_results: |
|
41 | 41 | ## search results are additionally filtered, and this check is just a safe gate |
|
42 | 42 | % if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results content check'): |
|
43 | 43 | <div id="codeblock" class="codeblock"> |
|
44 | 44 | <div class="codeblock-header"> |
|
45 | 45 | <h2> |
|
46 | 46 | %if h.get_repo_type_by_name(entry.get('repository')) == 'hg': |
|
47 | 47 | <i class="icon-hg"></i> |
|
48 | 48 | %elif h.get_repo_type_by_name(entry.get('repository')) == 'git': |
|
49 | 49 | <i class="icon-git"></i> |
|
50 | 50 | %elif h.get_repo_type_by_name(entry.get('repository')) == 'svn': |
|
51 | 51 | <i class="icon-svn"></i> |
|
52 | 52 | %endif |
|
53 | 53 | ${h.link_to(entry['repository'], h.route_path('repo_summary',repo_name=entry['repository']))} |
|
54 | 54 | </h2> |
|
55 | 55 | <div class="stats"> |
|
56 | 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 | 57 | %if entry.get('lines'): |
|
58 | 58 | | ${entry.get('lines', 0.)} ${_ungettext('line', 'lines', entry.get('lines', 0.))} |
|
59 | 59 | %endif |
|
60 | 60 | %if entry.get('size'): |
|
61 | 61 | | ${h.format_byte_size_binary(entry['size'])} |
|
62 | 62 | %endif |
|
63 | 63 | %if entry.get('mimetype'): |
|
64 | 64 | | ${entry.get('mimetype', "unknown mimetype")} |
|
65 | 65 | %endif |
|
66 | 66 | </div> |
|
67 | 67 | <div class="buttons"> |
|
68 |
<a id="file_history_overview_full" href="${h. |
|
|
68 | <a id="file_history_overview_full" href="${h.route_path('repo_changelog_file',repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path',''))}"> | |
|
69 | 69 | ${_('Show Full History')} |
|
70 | 70 | </a> |
|
71 | 71 | | ${h.link_to(_('Annotation'), h.route_path('repo_files:annotated', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))} |
|
72 | 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 | 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 | 74 | </div> |
|
75 | 75 | </div> |
|
76 | 76 | <div class="code-body search-code-body"> |
|
77 | 77 | ${highlight_text_file(c.cur_query, entry['content'], |
|
78 | 78 | url=h.route_path('repo_files',repo_name=entry['repository'],commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path']), |
|
79 | 79 | mimetype=entry.get('mimetype'), filepath=entry.get('path'))} |
|
80 | 80 | </div> |
|
81 | 81 | </div> |
|
82 | 82 | % endif |
|
83 | 83 | %endfor |
|
84 | 84 | </div> |
|
85 | 85 | %if c.cur_query and c.formatted_results: |
|
86 | 86 | <div class="pagination-wh pagination-left" > |
|
87 | 87 | ${c.formatted_results.pager('$link_previous ~2~ $link_next')} |
|
88 | 88 | </div> |
|
89 | 89 | %endif |
|
90 | 90 | |
|
91 | 91 | %if c.cur_query: |
|
92 | 92 | <script type="text/javascript"> |
|
93 | 93 | $(function(){ |
|
94 | 94 | $(".code").mark( |
|
95 | 95 | '${' '.join(h.normalize_text_for_matching(c.cur_query).split())}', |
|
96 | 96 | {"className": 'match', |
|
97 | 97 | }); |
|
98 | 98 | }) |
|
99 | 99 | </script> |
|
100 | 100 | %endif No newline at end of file |
@@ -1,202 +1,202 b'' | |||
|
1 | 1 | <%def name="refs_counters(branches, closed_branches, tags, bookmarks)"> |
|
2 | 2 | <span class="branchtag tag"> |
|
3 | 3 | <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs"> |
|
4 | 4 | <i class="icon-branch"></i>${_ungettext( |
|
5 | 5 | '%(num)s Branch','%(num)s Branches', len(branches)) % {'num': len(branches)}}</a> |
|
6 | 6 | </span> |
|
7 | 7 | |
|
8 | 8 | %if closed_branches: |
|
9 | 9 | <span class="branchtag tag"> |
|
10 | 10 | <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs"> |
|
11 | 11 | <i class="icon-branch"></i>${_ungettext( |
|
12 | 12 | '%(num)s Closed Branch', '%(num)s Closed Branches', len(closed_branches)) % {'num': len(closed_branches)}}</a> |
|
13 | 13 | </span> |
|
14 | 14 | %endif |
|
15 | 15 | |
|
16 | 16 | <span class="tagtag tag"> |
|
17 | 17 | <a href="${h.route_path('tags_home',repo_name=c.repo_name)}" class="childs"> |
|
18 | 18 | <i class="icon-tag"></i>${_ungettext( |
|
19 | 19 | '%(num)s Tag', '%(num)s Tags', len(tags)) % {'num': len(tags)}}</a> |
|
20 | 20 | </span> |
|
21 | 21 | |
|
22 | 22 | %if bookmarks: |
|
23 | 23 | <span class="booktag tag"> |
|
24 | 24 | <a href="${h.route_path('bookmarks_home',repo_name=c.repo_name)}" class="childs"> |
|
25 | 25 | <i class="icon-bookmark"></i>${_ungettext( |
|
26 | 26 | '%(num)s Bookmark', '%(num)s Bookmarks', len(bookmarks)) % {'num': len(bookmarks)}}</a> |
|
27 | 27 | </span> |
|
28 | 28 | %endif |
|
29 | 29 | </%def> |
|
30 | 30 | |
|
31 | 31 | <%def name="summary_detail(breadcrumbs_links, show_downloads=True)"> |
|
32 | 32 | <% summary = lambda n:{False:'summary-short'}.get(n) %> |
|
33 | 33 | |
|
34 | 34 | <div id="summary-menu-stats" class="summary-detail"> |
|
35 | 35 | <div class="summary-detail-header"> |
|
36 | 36 | <div class="breadcrumbs files_location"> |
|
37 | 37 | <h4> |
|
38 | 38 | ${breadcrumbs_links} |
|
39 | 39 | </h4> |
|
40 | 40 | </div> |
|
41 | 41 | <div id="summary_details_expand" class="btn-collapse" data-toggle="summary-details"> |
|
42 | 42 | ${_('Show More')} |
|
43 | 43 | </div> |
|
44 | 44 | </div> |
|
45 | 45 | |
|
46 | 46 | <div class="fieldset"> |
|
47 | 47 | %if h.is_svn_without_proxy(c.rhodecode_db_repo): |
|
48 | 48 | <div class="left-label disabled"> |
|
49 | 49 | ${_('Read-only url')}: |
|
50 | 50 | </div> |
|
51 | 51 | <div class="right-content disabled"> |
|
52 | 52 | <input type="text" class="input-monospace" id="clone_url" disabled value="${c.clone_repo_url}"/> |
|
53 | 53 | <input type="text" class="input-monospace" id="clone_url_id" disabled value="${c.clone_repo_url_id}" style="display: none;"/> |
|
54 | 54 | <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a> |
|
55 | 55 | <a id="clone_by_id" class="clone">${_('Show by ID')}</a> |
|
56 | 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 | 57 | </div> |
|
58 | 58 | %else: |
|
59 | 59 | <div class="left-label"> |
|
60 | 60 | ${_('Clone url')}: |
|
61 | 61 | </div> |
|
62 | 62 | <div class="right-content"> |
|
63 | 63 | <input type="text" class="input-monospace" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/> |
|
64 | 64 | <input type="text" class="input-monospace" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}" style="display: none;"/> |
|
65 | 65 | <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a> |
|
66 | 66 | <a id="clone_by_id" class="clone">${_('Show by ID')}</a> |
|
67 | 67 | </div> |
|
68 | 68 | %endif |
|
69 | 69 | </div> |
|
70 | 70 | |
|
71 | 71 | <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;"> |
|
72 | 72 | <div class="left-label"> |
|
73 | 73 | ${_('Description')}: |
|
74 | 74 | </div> |
|
75 | 75 | <div class="right-content"> |
|
76 | 76 | %if c.visual.stylify_metatags: |
|
77 | 77 | <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.escaped_stylize(c.rhodecode_db_repo.description))}</div> |
|
78 | 78 | %else: |
|
79 | 79 | <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.html_escape(c.rhodecode_db_repo.description))}</div> |
|
80 | 80 | %endif |
|
81 | 81 | </div> |
|
82 | 82 | </div> |
|
83 | 83 | |
|
84 | 84 | <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;"> |
|
85 | 85 | <div class="left-label"> |
|
86 | 86 | ${_('Information')}: |
|
87 | 87 | </div> |
|
88 | 88 | <div class="right-content"> |
|
89 | 89 | |
|
90 | 90 | <div class="repo-size"> |
|
91 | 91 | <% commit_rev = c.rhodecode_db_repo.changeset_cache.get('revision') %> |
|
92 | 92 | |
|
93 | 93 | ## commits |
|
94 | 94 | % if commit_rev == -1: |
|
95 | 95 | ${_ungettext('%(num)s Commit', '%(num)s Commits', 0) % {'num': 0}}, |
|
96 | 96 | % else: |
|
97 |
<a href="${h. |
|
|
97 | <a href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"> | |
|
98 | 98 | ${_ungettext('%(num)s Commit', '%(num)s Commits', commit_rev) % {'num': commit_rev}}</a>, |
|
99 | 99 | % endif |
|
100 | 100 | |
|
101 | 101 | ## forks |
|
102 | 102 | <a title="${_('Number of Repository Forks')}" href="${h.url('repo_forks_home', repo_name=c.repo_name)}"> |
|
103 | 103 | ${c.repository_forks} ${_ungettext('Fork', 'Forks', c.repository_forks)}</a>, |
|
104 | 104 | |
|
105 | 105 | ## repo size |
|
106 | 106 | % if commit_rev == -1: |
|
107 | 107 | <span class="stats-bullet">0 B</span> |
|
108 | 108 | % else: |
|
109 | 109 | <span class="stats-bullet" id="repo_size_container"> |
|
110 | 110 | ${_('Calculating Repository Size...')} |
|
111 | 111 | </span> |
|
112 | 112 | % endif |
|
113 | 113 | </div> |
|
114 | 114 | |
|
115 | 115 | <div class="commit-info"> |
|
116 | 116 | <div class="tags"> |
|
117 | 117 | % if c.rhodecode_repo: |
|
118 | 118 | ${refs_counters( |
|
119 | 119 | c.rhodecode_repo.branches, |
|
120 | 120 | c.rhodecode_repo.branches_closed, |
|
121 | 121 | c.rhodecode_repo.tags, |
|
122 | 122 | c.rhodecode_repo.bookmarks)} |
|
123 | 123 | % else: |
|
124 | 124 | ## missing requirements can make c.rhodecode_repo None |
|
125 | 125 | ${refs_counters([], [], [], [])} |
|
126 | 126 | % endif |
|
127 | 127 | </div> |
|
128 | 128 | </div> |
|
129 | 129 | |
|
130 | 130 | </div> |
|
131 | 131 | </div> |
|
132 | 132 | |
|
133 | 133 | <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;"> |
|
134 | 134 | <div class="left-label"> |
|
135 | 135 | ${_('Statistics')}: |
|
136 | 136 | </div> |
|
137 | 137 | <div class="right-content"> |
|
138 | 138 | <div class="input ${summary(c.show_stats)} statistics"> |
|
139 | 139 | % if c.show_stats: |
|
140 | 140 | <div id="lang_stats" class="enabled"> |
|
141 | 141 | ${_('Calculating Code Statistics...')} |
|
142 | 142 | </div> |
|
143 | 143 | % else: |
|
144 | 144 | <span class="disabled"> |
|
145 | 145 | ${_('Statistics are disabled for this repository')} |
|
146 | 146 | </span> |
|
147 | 147 | % if h.HasPermissionAll('hg.admin')('enable stats on from summary'): |
|
148 | 148 | , ${h.link_to(_('enable statistics'),h.route_path('edit_repo',repo_name=c.repo_name, anchor='repo_enable_statistics'))} |
|
149 | 149 | % endif |
|
150 | 150 | % endif |
|
151 | 151 | </div> |
|
152 | 152 | |
|
153 | 153 | </div> |
|
154 | 154 | </div> |
|
155 | 155 | |
|
156 | 156 | % if show_downloads: |
|
157 | 157 | <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;"> |
|
158 | 158 | <div class="left-label"> |
|
159 | 159 | ${_('Downloads')}: |
|
160 | 160 | </div> |
|
161 | 161 | <div class="right-content"> |
|
162 | 162 | <div class="input ${summary(c.show_stats)} downloads"> |
|
163 | 163 | % if c.rhodecode_repo and len(c.rhodecode_repo.revisions) == 0: |
|
164 | 164 | <span class="disabled"> |
|
165 | 165 | ${_('There are no downloads yet')} |
|
166 | 166 | </span> |
|
167 | 167 | % elif not c.enable_downloads: |
|
168 | 168 | <span class="disabled"> |
|
169 | 169 | ${_('Downloads are disabled for this repository')} |
|
170 | 170 | </span> |
|
171 | 171 | % if h.HasPermissionAll('hg.admin')('enable downloads on from summary'): |
|
172 | 172 | , ${h.link_to(_('enable downloads'),h.route_path('edit_repo',repo_name=c.repo_name, anchor='repo_enable_downloads'))} |
|
173 | 173 | % endif |
|
174 | 174 | % else: |
|
175 | 175 | <span class="enabled"> |
|
176 | 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 | 177 | <i class="icon-archive"></i> tip.zip |
|
178 | 178 | ## replaced by some JS on select |
|
179 | 179 | </a> |
|
180 | 180 | </span> |
|
181 | 181 | ${h.hidden('download_options')} |
|
182 | 182 | % endif |
|
183 | 183 | </div> |
|
184 | 184 | </div> |
|
185 | 185 | </div> |
|
186 | 186 | % endif |
|
187 | 187 | |
|
188 | 188 | </div><!--end summary-detail--> |
|
189 | 189 | </%def> |
|
190 | 190 | |
|
191 | 191 | <%def name="summary_stats(gravatar_function)"> |
|
192 | 192 | <div class="sidebar-right"> |
|
193 | 193 | <div class="summary-detail-header"> |
|
194 | 194 | <h4 class="item"> |
|
195 | 195 | ${_('Owner')} |
|
196 | 196 | </h4> |
|
197 | 197 | </div> |
|
198 | 198 | <div class="sidebar-right-content"> |
|
199 | 199 | ${gravatar_function(c.rhodecode_db_repo.user.email, 16)} |
|
200 | 200 | </div> |
|
201 | 201 | </div><!--end sidebar-right--> |
|
202 | 202 | </%def> |
@@ -1,136 +1,136 b'' | |||
|
1 | 1 | ## -*- coding: utf-8 -*- |
|
2 | 2 | <%namespace name="base" file="/base/base.mako"/> |
|
3 | 3 | %if c.repo_commits: |
|
4 | 4 | <table class="rctable repo_summary table_disp"> |
|
5 | 5 | <tr> |
|
6 | 6 | |
|
7 | 7 | <th class="status" colspan="2"></th> |
|
8 | 8 | <th>${_('Commit')}</th> |
|
9 | 9 | <th>${_('Commit message')}</th> |
|
10 | 10 | <th>${_('Age')}</th> |
|
11 | 11 | <th>${_('Author')}</th> |
|
12 | 12 | <th>${_('Refs')}</th> |
|
13 | 13 | </tr> |
|
14 | 14 | %for cnt,cs in enumerate(c.repo_commits): |
|
15 | 15 | <tr class="parity${cnt%2}"> |
|
16 | 16 | |
|
17 | 17 | <td class="td-status"> |
|
18 | 18 | %if c.statuses.get(cs.raw_id): |
|
19 | 19 | <div class="changeset-status-ico shortlog"> |
|
20 | 20 | %if c.statuses.get(cs.raw_id)[2]: |
|
21 | 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 | 22 | <div class="${'flag_status %s' % c.statuses.get(cs.raw_id)[0]}"></div> |
|
23 | 23 | </a> |
|
24 | 24 | %else: |
|
25 | 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 | 26 | <div class="${'flag_status %s' % c.statuses.get(cs.raw_id)[0]}"></div> |
|
27 | 27 | </a> |
|
28 | 28 | %endif |
|
29 | 29 | </div> |
|
30 | 30 | %else: |
|
31 | 31 | <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div> |
|
32 | 32 | %endif |
|
33 | 33 | </td> |
|
34 | 34 | <td class="td-comments"> |
|
35 | 35 | %if c.comments.get(cs.raw_id,[]): |
|
36 | 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 | 37 | <i class="icon-comment"></i> ${len(c.comments[cs.raw_id])} |
|
38 | 38 | </a> |
|
39 | 39 | %endif |
|
40 | 40 | </td> |
|
41 | 41 | <td class="td-commit"> |
|
42 | 42 | <pre><a href="${h.url('changeset_home', repo_name=c.repo_name, revision=cs.raw_id)}">${h.show_id(cs)}</a></pre> |
|
43 | 43 | </td> |
|
44 | 44 | |
|
45 | 45 | <td class="td-description mid"> |
|
46 | 46 | <div class="log-container truncate-wrap"> |
|
47 | 47 | <div class="message truncate" id="c-${cs.raw_id}">${h.urlify_commit_message(cs.message, c.repo_name)}</div> |
|
48 | 48 | </div> |
|
49 | 49 | </td> |
|
50 | 50 | |
|
51 | 51 | <td class="td-time"> |
|
52 | 52 | ${h.age_component(cs.date)} |
|
53 | 53 | </td> |
|
54 | 54 | <td class="td-user author"> |
|
55 | 55 | ${base.gravatar_with_user(cs.author)} |
|
56 | 56 | </td> |
|
57 | 57 | |
|
58 | 58 | <td class="td-tags"> |
|
59 | 59 | <div class="autoexpand"> |
|
60 | 60 | %if h.is_hg(c.rhodecode_repo): |
|
61 | 61 | %for book in cs.bookmarks: |
|
62 | 62 | <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}"> |
|
63 | 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 | 64 | </span> |
|
65 | 65 | %endfor |
|
66 | 66 | %endif |
|
67 | 67 | ## tags |
|
68 | 68 | %for tag in cs.tags: |
|
69 | 69 | <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}"> |
|
70 | 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 | 71 | </span> |
|
72 | 72 | %endfor |
|
73 | 73 | |
|
74 | 74 | ## branch |
|
75 | 75 | %if cs.branch: |
|
76 | 76 | <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % cs.branch)}"> |
|
77 |
<a href="${h. |
|
|
77 | <a href="${h.route_path('repo_changelog',repo_name=c.repo_name,_query=dict(branch=cs.branch))}"><i class="icon-code-fork"></i>${h.shorter(cs.branch)}</a> | |
|
78 | 78 | </span> |
|
79 | 79 | %endif |
|
80 | 80 | </div> |
|
81 | 81 | </td> |
|
82 | 82 | </tr> |
|
83 | 83 | %endfor |
|
84 | 84 | |
|
85 | 85 | </table> |
|
86 | 86 | |
|
87 | 87 | <script type="text/javascript"> |
|
88 | 88 | $(document).pjax('#shortlog_data .pager_link','#shortlog_data', {timeout: 2000, scrollTo: false }); |
|
89 | 89 | $(document).on('pjax:success', function(){ timeagoActivate(); }); |
|
90 | 90 | </script> |
|
91 | 91 | |
|
92 | 92 | <div class="pagination-wh pagination-left"> |
|
93 | 93 | ${c.repo_commits.pager('$link_previous ~2~ $link_next')} |
|
94 | 94 | </div> |
|
95 | 95 | %else: |
|
96 | 96 | |
|
97 | 97 | %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name): |
|
98 | 98 | <div class="quick_start"> |
|
99 | 99 | <div class="fieldset"> |
|
100 | 100 | <div class="left-label">${_('Add or upload files directly via RhodeCode:')}</div> |
|
101 | 101 | <div class="right-content"> |
|
102 | 102 | <div id="add_node_id" class="add_node"> |
|
103 | 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 | 104 | </div> |
|
105 | 105 | </div> |
|
106 | 106 | %endif |
|
107 | 107 | </div> |
|
108 | 108 | |
|
109 | 109 | %if not h.is_svn(c.rhodecode_repo): |
|
110 | 110 | <div class="fieldset"> |
|
111 | 111 | <div class="left-label">${_('Push new repo:')}</div> |
|
112 | 112 | <div class="right-content"> |
|
113 | 113 | <pre> |
|
114 | 114 | ${c.rhodecode_repo.alias} clone ${c.clone_repo_url} |
|
115 | 115 | ${c.rhodecode_repo.alias} add README # add first file |
|
116 | 116 | ${c.rhodecode_repo.alias} commit -m "Initial" # commit with message |
|
117 | 117 | ${c.rhodecode_repo.alias} push ${'origin master' if h.is_git(c.rhodecode_repo) else ''} # push changes back |
|
118 | 118 | </pre> |
|
119 | 119 | </div> |
|
120 | 120 | </div> |
|
121 | 121 | <div class="fieldset"> |
|
122 | 122 | <div class="left-label">${_('Existing repository?')}</div> |
|
123 | 123 | <div class="right-content"> |
|
124 | 124 | <pre> |
|
125 | 125 | %if h.is_git(c.rhodecode_repo): |
|
126 | 126 | git remote add origin ${c.clone_repo_url} |
|
127 | 127 | git push -u origin master |
|
128 | 128 | %else: |
|
129 | 129 | hg push ${c.clone_repo_url} |
|
130 | 130 | %endif |
|
131 | 131 | </pre> |
|
132 | 132 | </div> |
|
133 | 133 | </div> |
|
134 | 134 | %endif |
|
135 | 135 | </div> |
|
136 | 136 | %endif |
@@ -1,1094 +1,1107 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2010-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import mock |
|
22 | 22 | import pytest |
|
23 | 23 | from webob.exc import HTTPNotFound |
|
24 | 24 | |
|
25 | 25 | import rhodecode |
|
26 | 26 | from rhodecode.lib.vcs.nodes import FileNode |
|
27 | 27 | from rhodecode.lib import helpers as h |
|
28 | 28 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
29 | 29 | from rhodecode.model.db import ( |
|
30 | 30 | PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment) |
|
31 | 31 | from rhodecode.model.meta import Session |
|
32 | 32 | from rhodecode.model.pull_request import PullRequestModel |
|
33 | 33 | from rhodecode.model.user import UserModel |
|
34 | 34 | from rhodecode.tests import ( |
|
35 | 35 | assert_session_flash, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN) |
|
36 | 36 | from rhodecode.tests.utils import AssertResponse |
|
37 | 37 | |
|
38 | 38 | |
|
39 | def route_path(name, params=None, **kwargs): | |
|
40 | import urllib | |
|
41 | ||
|
42 | base_url = { | |
|
43 | 'repo_changelog':'/{repo_name}/changelog', | |
|
44 | 'repo_changelog_file':'/{repo_name}/changelog/{commit_id}/{f_path}', | |
|
45 | }[name].format(**kwargs) | |
|
46 | ||
|
47 | if params: | |
|
48 | base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) | |
|
49 | return base_url | |
|
50 | ||
|
51 | ||
|
39 | 52 | @pytest.mark.usefixtures('app', 'autologin_user') |
|
40 | 53 | @pytest.mark.backends("git", "hg") |
|
41 | 54 | class TestPullrequestsController(object): |
|
42 | 55 | |
|
43 | 56 | def test_index(self, backend): |
|
44 | 57 | self.app.get(url( |
|
45 | 58 | controller='pullrequests', action='index', |
|
46 | 59 | repo_name=backend.repo_name)) |
|
47 | 60 | |
|
48 | 61 | def test_option_menu_create_pull_request_exists(self, backend): |
|
49 | 62 | repo_name = backend.repo_name |
|
50 | 63 | response = self.app.get(h.route_path('repo_summary', repo_name=repo_name)) |
|
51 | 64 | |
|
52 | 65 | create_pr_link = '<a href="%s">Create Pull Request</a>' % url( |
|
53 | 66 | 'pullrequest', repo_name=repo_name) |
|
54 | 67 | response.mustcontain(create_pr_link) |
|
55 | 68 | |
|
56 | 69 | def test_create_pr_form_with_raw_commit_id(self, backend): |
|
57 | 70 | repo = backend.repo |
|
58 | 71 | |
|
59 | 72 | self.app.get( |
|
60 | 73 | url(controller='pullrequests', action='index', |
|
61 | 74 | repo_name=repo.repo_name, |
|
62 | 75 | commit=repo.get_commit().raw_id), |
|
63 | 76 | status=200) |
|
64 | 77 | |
|
65 | 78 | @pytest.mark.parametrize('pr_merge_enabled', [True, False]) |
|
66 | 79 | def test_show(self, pr_util, pr_merge_enabled): |
|
67 | 80 | pull_request = pr_util.create_pull_request( |
|
68 | 81 | mergeable=pr_merge_enabled, enable_notifications=False) |
|
69 | 82 | |
|
70 | 83 | response = self.app.get(url( |
|
71 | 84 | controller='pullrequests', action='show', |
|
72 | 85 | repo_name=pull_request.target_repo.scm_instance().name, |
|
73 | 86 | pull_request_id=str(pull_request.pull_request_id))) |
|
74 | 87 | |
|
75 | 88 | for commit_id in pull_request.revisions: |
|
76 | 89 | response.mustcontain(commit_id) |
|
77 | 90 | |
|
78 | 91 | assert pull_request.target_ref_parts.type in response |
|
79 | 92 | assert pull_request.target_ref_parts.name in response |
|
80 | 93 | target_clone_url = pull_request.target_repo.clone_url() |
|
81 | 94 | assert target_clone_url in response |
|
82 | 95 | |
|
83 | 96 | assert 'class="pull-request-merge"' in response |
|
84 | 97 | assert ( |
|
85 | 98 | 'Server-side pull request merging is disabled.' |
|
86 | 99 | in response) != pr_merge_enabled |
|
87 | 100 | |
|
88 | 101 | def test_close_status_visibility(self, pr_util, user_util, csrf_token): |
|
89 | 102 | from rhodecode.tests.functional.test_login import login_url, logut_url |
|
90 | 103 | # Logout |
|
91 | 104 | response = self.app.post( |
|
92 | 105 | logut_url, |
|
93 | 106 | params={'csrf_token': csrf_token}) |
|
94 | 107 | # Login as regular user |
|
95 | 108 | response = self.app.post(login_url, |
|
96 | 109 | {'username': TEST_USER_REGULAR_LOGIN, |
|
97 | 110 | 'password': 'test12'}) |
|
98 | 111 | |
|
99 | 112 | pull_request = pr_util.create_pull_request( |
|
100 | 113 | author=TEST_USER_REGULAR_LOGIN) |
|
101 | 114 | |
|
102 | 115 | response = self.app.get(url( |
|
103 | 116 | controller='pullrequests', action='show', |
|
104 | 117 | repo_name=pull_request.target_repo.scm_instance().name, |
|
105 | 118 | pull_request_id=str(pull_request.pull_request_id))) |
|
106 | 119 | |
|
107 | 120 | response.mustcontain('Server-side pull request merging is disabled.') |
|
108 | 121 | |
|
109 | 122 | assert_response = response.assert_response() |
|
110 | 123 | # for regular user without a merge permissions, we don't see it |
|
111 | 124 | assert_response.no_element_exists('#close-pull-request-action') |
|
112 | 125 | |
|
113 | 126 | user_util.grant_user_permission_to_repo( |
|
114 | 127 | pull_request.target_repo, |
|
115 | 128 | UserModel().get_by_username(TEST_USER_REGULAR_LOGIN), |
|
116 | 129 | 'repository.write') |
|
117 | 130 | response = self.app.get(url( |
|
118 | 131 | controller='pullrequests', action='show', |
|
119 | 132 | repo_name=pull_request.target_repo.scm_instance().name, |
|
120 | 133 | pull_request_id=str(pull_request.pull_request_id))) |
|
121 | 134 | |
|
122 | 135 | response.mustcontain('Server-side pull request merging is disabled.') |
|
123 | 136 | |
|
124 | 137 | assert_response = response.assert_response() |
|
125 | 138 | # now regular user has a merge permissions, we have CLOSE button |
|
126 | 139 | assert_response.one_element_exists('#close-pull-request-action') |
|
127 | 140 | |
|
128 | 141 | def test_show_invalid_commit_id(self, pr_util): |
|
129 | 142 | # Simulating invalid revisions which will cause a lookup error |
|
130 | 143 | pull_request = pr_util.create_pull_request() |
|
131 | 144 | pull_request.revisions = ['invalid'] |
|
132 | 145 | Session().add(pull_request) |
|
133 | 146 | Session().commit() |
|
134 | 147 | |
|
135 | 148 | response = self.app.get(url( |
|
136 | 149 | controller='pullrequests', action='show', |
|
137 | 150 | repo_name=pull_request.target_repo.scm_instance().name, |
|
138 | 151 | pull_request_id=str(pull_request.pull_request_id))) |
|
139 | 152 | |
|
140 | 153 | for commit_id in pull_request.revisions: |
|
141 | 154 | response.mustcontain(commit_id) |
|
142 | 155 | |
|
143 | 156 | def test_show_invalid_source_reference(self, pr_util): |
|
144 | 157 | pull_request = pr_util.create_pull_request() |
|
145 | 158 | pull_request.source_ref = 'branch:b:invalid' |
|
146 | 159 | Session().add(pull_request) |
|
147 | 160 | Session().commit() |
|
148 | 161 | |
|
149 | 162 | self.app.get(url( |
|
150 | 163 | controller='pullrequests', action='show', |
|
151 | 164 | repo_name=pull_request.target_repo.scm_instance().name, |
|
152 | 165 | pull_request_id=str(pull_request.pull_request_id))) |
|
153 | 166 | |
|
154 | 167 | def test_edit_title_description(self, pr_util, csrf_token): |
|
155 | 168 | pull_request = pr_util.create_pull_request() |
|
156 | 169 | pull_request_id = pull_request.pull_request_id |
|
157 | 170 | |
|
158 | 171 | response = self.app.post( |
|
159 | 172 | url(controller='pullrequests', action='update', |
|
160 | 173 | repo_name=pull_request.target_repo.repo_name, |
|
161 | 174 | pull_request_id=str(pull_request_id)), |
|
162 | 175 | params={ |
|
163 | 176 | 'edit_pull_request': 'true', |
|
164 | 177 | '_method': 'put', |
|
165 | 178 | 'title': 'New title', |
|
166 | 179 | 'description': 'New description', |
|
167 | 180 | 'csrf_token': csrf_token}) |
|
168 | 181 | |
|
169 | 182 | assert_session_flash( |
|
170 | 183 | response, u'Pull request title & description updated.', |
|
171 | 184 | category='success') |
|
172 | 185 | |
|
173 | 186 | pull_request = PullRequest.get(pull_request_id) |
|
174 | 187 | assert pull_request.title == 'New title' |
|
175 | 188 | assert pull_request.description == 'New description' |
|
176 | 189 | |
|
177 | 190 | def test_edit_title_description_closed(self, pr_util, csrf_token): |
|
178 | 191 | pull_request = pr_util.create_pull_request() |
|
179 | 192 | pull_request_id = pull_request.pull_request_id |
|
180 | 193 | pr_util.close() |
|
181 | 194 | |
|
182 | 195 | response = self.app.post( |
|
183 | 196 | url(controller='pullrequests', action='update', |
|
184 | 197 | repo_name=pull_request.target_repo.repo_name, |
|
185 | 198 | pull_request_id=str(pull_request_id)), |
|
186 | 199 | params={ |
|
187 | 200 | 'edit_pull_request': 'true', |
|
188 | 201 | '_method': 'put', |
|
189 | 202 | 'title': 'New title', |
|
190 | 203 | 'description': 'New description', |
|
191 | 204 | 'csrf_token': csrf_token}) |
|
192 | 205 | |
|
193 | 206 | assert_session_flash( |
|
194 | 207 | response, u'Cannot update closed pull requests.', |
|
195 | 208 | category='error') |
|
196 | 209 | |
|
197 | 210 | def test_update_invalid_source_reference(self, pr_util, csrf_token): |
|
198 | 211 | from rhodecode.lib.vcs.backends.base import UpdateFailureReason |
|
199 | 212 | |
|
200 | 213 | pull_request = pr_util.create_pull_request() |
|
201 | 214 | pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id' |
|
202 | 215 | Session().add(pull_request) |
|
203 | 216 | Session().commit() |
|
204 | 217 | |
|
205 | 218 | pull_request_id = pull_request.pull_request_id |
|
206 | 219 | |
|
207 | 220 | response = self.app.post( |
|
208 | 221 | url(controller='pullrequests', action='update', |
|
209 | 222 | repo_name=pull_request.target_repo.repo_name, |
|
210 | 223 | pull_request_id=str(pull_request_id)), |
|
211 | 224 | params={'update_commits': 'true', '_method': 'put', |
|
212 | 225 | 'csrf_token': csrf_token}) |
|
213 | 226 | |
|
214 | 227 | expected_msg = PullRequestModel.UPDATE_STATUS_MESSAGES[ |
|
215 | 228 | UpdateFailureReason.MISSING_SOURCE_REF] |
|
216 | 229 | assert_session_flash(response, expected_msg, category='error') |
|
217 | 230 | |
|
218 | 231 | def test_missing_target_reference(self, pr_util, csrf_token): |
|
219 | 232 | from rhodecode.lib.vcs.backends.base import MergeFailureReason |
|
220 | 233 | pull_request = pr_util.create_pull_request( |
|
221 | 234 | approved=True, mergeable=True) |
|
222 | 235 | pull_request.target_ref = 'branch:invalid-branch:invalid-commit-id' |
|
223 | 236 | Session().add(pull_request) |
|
224 | 237 | Session().commit() |
|
225 | 238 | |
|
226 | 239 | pull_request_id = pull_request.pull_request_id |
|
227 | 240 | pull_request_url = url( |
|
228 | 241 | controller='pullrequests', action='show', |
|
229 | 242 | repo_name=pull_request.target_repo.repo_name, |
|
230 | 243 | pull_request_id=str(pull_request_id)) |
|
231 | 244 | |
|
232 | 245 | response = self.app.get(pull_request_url) |
|
233 | 246 | |
|
234 | 247 | assertr = AssertResponse(response) |
|
235 | 248 | expected_msg = PullRequestModel.MERGE_STATUS_MESSAGES[ |
|
236 | 249 | MergeFailureReason.MISSING_TARGET_REF] |
|
237 | 250 | assertr.element_contains( |
|
238 | 251 | 'span[data-role="merge-message"]', str(expected_msg)) |
|
239 | 252 | |
|
240 | 253 | def test_comment_and_close_pull_request_custom_message_approved( |
|
241 | 254 | self, pr_util, csrf_token, xhr_header): |
|
242 | 255 | |
|
243 | 256 | pull_request = pr_util.create_pull_request(approved=True) |
|
244 | 257 | pull_request_id = pull_request.pull_request_id |
|
245 | 258 | author = pull_request.user_id |
|
246 | 259 | repo = pull_request.target_repo.repo_id |
|
247 | 260 | |
|
248 | 261 | self.app.post( |
|
249 | 262 | url(controller='pullrequests', |
|
250 | 263 | action='comment', |
|
251 | 264 | repo_name=pull_request.target_repo.scm_instance().name, |
|
252 | 265 | pull_request_id=str(pull_request_id)), |
|
253 | 266 | params={ |
|
254 | 267 | 'close_pull_request': '1', |
|
255 | 268 | 'text': 'Closing a PR', |
|
256 | 269 | 'csrf_token': csrf_token}, |
|
257 | 270 | extra_environ=xhr_header,) |
|
258 | 271 | |
|
259 | 272 | journal = UserLog.query()\ |
|
260 | 273 | .filter(UserLog.user_id == author)\ |
|
261 | 274 | .filter(UserLog.repository_id == repo) \ |
|
262 | 275 | .order_by('user_log_id') \ |
|
263 | 276 | .all() |
|
264 | 277 | assert journal[-1].action == 'repo.pull_request.close' |
|
265 | 278 | |
|
266 | 279 | pull_request = PullRequest.get(pull_request_id) |
|
267 | 280 | assert pull_request.is_closed() |
|
268 | 281 | |
|
269 | 282 | status = ChangesetStatusModel().get_status( |
|
270 | 283 | pull_request.source_repo, pull_request=pull_request) |
|
271 | 284 | assert status == ChangesetStatus.STATUS_APPROVED |
|
272 | 285 | comments = ChangesetComment().query() \ |
|
273 | 286 | .filter(ChangesetComment.pull_request == pull_request) \ |
|
274 | 287 | .order_by(ChangesetComment.comment_id.asc())\ |
|
275 | 288 | .all() |
|
276 | 289 | assert comments[-1].text == 'Closing a PR' |
|
277 | 290 | |
|
278 | 291 | def test_comment_force_close_pull_request_rejected( |
|
279 | 292 | self, pr_util, csrf_token, xhr_header): |
|
280 | 293 | pull_request = pr_util.create_pull_request() |
|
281 | 294 | pull_request_id = pull_request.pull_request_id |
|
282 | 295 | PullRequestModel().update_reviewers( |
|
283 | 296 | pull_request_id, [(1, ['reason'], False), (2, ['reason2'], False)], |
|
284 | 297 | pull_request.author) |
|
285 | 298 | author = pull_request.user_id |
|
286 | 299 | repo = pull_request.target_repo.repo_id |
|
287 | 300 | |
|
288 | 301 | self.app.post( |
|
289 | 302 | url(controller='pullrequests', |
|
290 | 303 | action='comment', |
|
291 | 304 | repo_name=pull_request.target_repo.scm_instance().name, |
|
292 | 305 | pull_request_id=str(pull_request_id)), |
|
293 | 306 | params={ |
|
294 | 307 | 'close_pull_request': '1', |
|
295 | 308 | 'csrf_token': csrf_token}, |
|
296 | 309 | extra_environ=xhr_header) |
|
297 | 310 | |
|
298 | 311 | pull_request = PullRequest.get(pull_request_id) |
|
299 | 312 | |
|
300 | 313 | journal = UserLog.query()\ |
|
301 | 314 | .filter(UserLog.user_id == author, UserLog.repository_id == repo) \ |
|
302 | 315 | .order_by('user_log_id') \ |
|
303 | 316 | .all() |
|
304 | 317 | assert journal[-1].action == 'repo.pull_request.close' |
|
305 | 318 | |
|
306 | 319 | # check only the latest status, not the review status |
|
307 | 320 | status = ChangesetStatusModel().get_status( |
|
308 | 321 | pull_request.source_repo, pull_request=pull_request) |
|
309 | 322 | assert status == ChangesetStatus.STATUS_REJECTED |
|
310 | 323 | |
|
311 | 324 | def test_comment_and_close_pull_request( |
|
312 | 325 | self, pr_util, csrf_token, xhr_header): |
|
313 | 326 | pull_request = pr_util.create_pull_request() |
|
314 | 327 | pull_request_id = pull_request.pull_request_id |
|
315 | 328 | |
|
316 | 329 | response = self.app.post( |
|
317 | 330 | url(controller='pullrequests', |
|
318 | 331 | action='comment', |
|
319 | 332 | repo_name=pull_request.target_repo.scm_instance().name, |
|
320 | 333 | pull_request_id=str(pull_request.pull_request_id)), |
|
321 | 334 | params={ |
|
322 | 335 | 'close_pull_request': 'true', |
|
323 | 336 | 'csrf_token': csrf_token}, |
|
324 | 337 | extra_environ=xhr_header) |
|
325 | 338 | |
|
326 | 339 | assert response.json |
|
327 | 340 | |
|
328 | 341 | pull_request = PullRequest.get(pull_request_id) |
|
329 | 342 | assert pull_request.is_closed() |
|
330 | 343 | |
|
331 | 344 | # check only the latest status, not the review status |
|
332 | 345 | status = ChangesetStatusModel().get_status( |
|
333 | 346 | pull_request.source_repo, pull_request=pull_request) |
|
334 | 347 | assert status == ChangesetStatus.STATUS_REJECTED |
|
335 | 348 | |
|
336 | 349 | def test_create_pull_request(self, backend, csrf_token): |
|
337 | 350 | commits = [ |
|
338 | 351 | {'message': 'ancestor'}, |
|
339 | 352 | {'message': 'change'}, |
|
340 | 353 | {'message': 'change2'}, |
|
341 | 354 | ] |
|
342 | 355 | commit_ids = backend.create_master_repo(commits) |
|
343 | 356 | target = backend.create_repo(heads=['ancestor']) |
|
344 | 357 | source = backend.create_repo(heads=['change2']) |
|
345 | 358 | |
|
346 | 359 | response = self.app.post( |
|
347 | 360 | url( |
|
348 | 361 | controller='pullrequests', |
|
349 | 362 | action='create', |
|
350 | 363 | repo_name=source.repo_name |
|
351 | 364 | ), |
|
352 | 365 | [ |
|
353 | 366 | ('source_repo', source.repo_name), |
|
354 | 367 | ('source_ref', 'branch:default:' + commit_ids['change2']), |
|
355 | 368 | ('target_repo', target.repo_name), |
|
356 | 369 | ('target_ref', 'branch:default:' + commit_ids['ancestor']), |
|
357 | 370 | ('common_ancestor', commit_ids['ancestor']), |
|
358 | 371 | ('pullrequest_desc', 'Description'), |
|
359 | 372 | ('pullrequest_title', 'Title'), |
|
360 | 373 | ('__start__', 'review_members:sequence'), |
|
361 | 374 | ('__start__', 'reviewer:mapping'), |
|
362 | 375 | ('user_id', '1'), |
|
363 | 376 | ('__start__', 'reasons:sequence'), |
|
364 | 377 | ('reason', 'Some reason'), |
|
365 | 378 | ('__end__', 'reasons:sequence'), |
|
366 | 379 | ('mandatory', 'False'), |
|
367 | 380 | ('__end__', 'reviewer:mapping'), |
|
368 | 381 | ('__end__', 'review_members:sequence'), |
|
369 | 382 | ('__start__', 'revisions:sequence'), |
|
370 | 383 | ('revisions', commit_ids['change']), |
|
371 | 384 | ('revisions', commit_ids['change2']), |
|
372 | 385 | ('__end__', 'revisions:sequence'), |
|
373 | 386 | ('user', ''), |
|
374 | 387 | ('csrf_token', csrf_token), |
|
375 | 388 | ], |
|
376 | 389 | status=302) |
|
377 | 390 | |
|
378 | 391 | location = response.headers['Location'] |
|
379 | 392 | pull_request_id = location.rsplit('/', 1)[1] |
|
380 | 393 | assert pull_request_id != 'new' |
|
381 | 394 | pull_request = PullRequest.get(int(pull_request_id)) |
|
382 | 395 | |
|
383 | 396 | # check that we have now both revisions |
|
384 | 397 | assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']] |
|
385 | 398 | assert pull_request.source_ref == 'branch:default:' + commit_ids['change2'] |
|
386 | 399 | expected_target_ref = 'branch:default:' + commit_ids['ancestor'] |
|
387 | 400 | assert pull_request.target_ref == expected_target_ref |
|
388 | 401 | |
|
389 | 402 | def test_reviewer_notifications(self, backend, csrf_token): |
|
390 | 403 | # We have to use the app.post for this test so it will create the |
|
391 | 404 | # notifications properly with the new PR |
|
392 | 405 | commits = [ |
|
393 | 406 | {'message': 'ancestor', |
|
394 | 407 | 'added': [FileNode('file_A', content='content_of_ancestor')]}, |
|
395 | 408 | {'message': 'change', |
|
396 | 409 | 'added': [FileNode('file_a', content='content_of_change')]}, |
|
397 | 410 | {'message': 'change-child'}, |
|
398 | 411 | {'message': 'ancestor-child', 'parents': ['ancestor'], |
|
399 | 412 | 'added': [ |
|
400 | 413 | FileNode('file_B', content='content_of_ancestor_child')]}, |
|
401 | 414 | {'message': 'ancestor-child-2'}, |
|
402 | 415 | ] |
|
403 | 416 | commit_ids = backend.create_master_repo(commits) |
|
404 | 417 | target = backend.create_repo(heads=['ancestor-child']) |
|
405 | 418 | source = backend.create_repo(heads=['change']) |
|
406 | 419 | |
|
407 | 420 | response = self.app.post( |
|
408 | 421 | url( |
|
409 | 422 | controller='pullrequests', |
|
410 | 423 | action='create', |
|
411 | 424 | repo_name=source.repo_name |
|
412 | 425 | ), |
|
413 | 426 | [ |
|
414 | 427 | ('source_repo', source.repo_name), |
|
415 | 428 | ('source_ref', 'branch:default:' + commit_ids['change']), |
|
416 | 429 | ('target_repo', target.repo_name), |
|
417 | 430 | ('target_ref', 'branch:default:' + commit_ids['ancestor-child']), |
|
418 | 431 | ('common_ancestor', commit_ids['ancestor']), |
|
419 | 432 | ('pullrequest_desc', 'Description'), |
|
420 | 433 | ('pullrequest_title', 'Title'), |
|
421 | 434 | ('__start__', 'review_members:sequence'), |
|
422 | 435 | ('__start__', 'reviewer:mapping'), |
|
423 | 436 | ('user_id', '2'), |
|
424 | 437 | ('__start__', 'reasons:sequence'), |
|
425 | 438 | ('reason', 'Some reason'), |
|
426 | 439 | ('__end__', 'reasons:sequence'), |
|
427 | 440 | ('mandatory', 'False'), |
|
428 | 441 | ('__end__', 'reviewer:mapping'), |
|
429 | 442 | ('__end__', 'review_members:sequence'), |
|
430 | 443 | ('__start__', 'revisions:sequence'), |
|
431 | 444 | ('revisions', commit_ids['change']), |
|
432 | 445 | ('__end__', 'revisions:sequence'), |
|
433 | 446 | ('user', ''), |
|
434 | 447 | ('csrf_token', csrf_token), |
|
435 | 448 | ], |
|
436 | 449 | status=302) |
|
437 | 450 | |
|
438 | 451 | location = response.headers['Location'] |
|
439 | 452 | |
|
440 | 453 | pull_request_id = location.rsplit('/', 1)[1] |
|
441 | 454 | assert pull_request_id != 'new' |
|
442 | 455 | pull_request = PullRequest.get(int(pull_request_id)) |
|
443 | 456 | |
|
444 | 457 | # Check that a notification was made |
|
445 | 458 | notifications = Notification.query()\ |
|
446 | 459 | .filter(Notification.created_by == pull_request.author.user_id, |
|
447 | 460 | Notification.type_ == Notification.TYPE_PULL_REQUEST, |
|
448 | 461 | Notification.subject.contains( |
|
449 | 462 | "wants you to review pull request #%s" % pull_request_id)) |
|
450 | 463 | assert len(notifications.all()) == 1 |
|
451 | 464 | |
|
452 | 465 | # Change reviewers and check that a notification was made |
|
453 | 466 | PullRequestModel().update_reviewers( |
|
454 | 467 | pull_request.pull_request_id, [(1, [], False)], |
|
455 | 468 | pull_request.author) |
|
456 | 469 | assert len(notifications.all()) == 2 |
|
457 | 470 | |
|
458 | 471 | def test_create_pull_request_stores_ancestor_commit_id(self, backend, |
|
459 | 472 | csrf_token): |
|
460 | 473 | commits = [ |
|
461 | 474 | {'message': 'ancestor', |
|
462 | 475 | 'added': [FileNode('file_A', content='content_of_ancestor')]}, |
|
463 | 476 | {'message': 'change', |
|
464 | 477 | 'added': [FileNode('file_a', content='content_of_change')]}, |
|
465 | 478 | {'message': 'change-child'}, |
|
466 | 479 | {'message': 'ancestor-child', 'parents': ['ancestor'], |
|
467 | 480 | 'added': [ |
|
468 | 481 | FileNode('file_B', content='content_of_ancestor_child')]}, |
|
469 | 482 | {'message': 'ancestor-child-2'}, |
|
470 | 483 | ] |
|
471 | 484 | commit_ids = backend.create_master_repo(commits) |
|
472 | 485 | target = backend.create_repo(heads=['ancestor-child']) |
|
473 | 486 | source = backend.create_repo(heads=['change']) |
|
474 | 487 | |
|
475 | 488 | response = self.app.post( |
|
476 | 489 | url( |
|
477 | 490 | controller='pullrequests', |
|
478 | 491 | action='create', |
|
479 | 492 | repo_name=source.repo_name |
|
480 | 493 | ), |
|
481 | 494 | [ |
|
482 | 495 | ('source_repo', source.repo_name), |
|
483 | 496 | ('source_ref', 'branch:default:' + commit_ids['change']), |
|
484 | 497 | ('target_repo', target.repo_name), |
|
485 | 498 | ('target_ref', 'branch:default:' + commit_ids['ancestor-child']), |
|
486 | 499 | ('common_ancestor', commit_ids['ancestor']), |
|
487 | 500 | ('pullrequest_desc', 'Description'), |
|
488 | 501 | ('pullrequest_title', 'Title'), |
|
489 | 502 | ('__start__', 'review_members:sequence'), |
|
490 | 503 | ('__start__', 'reviewer:mapping'), |
|
491 | 504 | ('user_id', '1'), |
|
492 | 505 | ('__start__', 'reasons:sequence'), |
|
493 | 506 | ('reason', 'Some reason'), |
|
494 | 507 | ('__end__', 'reasons:sequence'), |
|
495 | 508 | ('mandatory', 'False'), |
|
496 | 509 | ('__end__', 'reviewer:mapping'), |
|
497 | 510 | ('__end__', 'review_members:sequence'), |
|
498 | 511 | ('__start__', 'revisions:sequence'), |
|
499 | 512 | ('revisions', commit_ids['change']), |
|
500 | 513 | ('__end__', 'revisions:sequence'), |
|
501 | 514 | ('user', ''), |
|
502 | 515 | ('csrf_token', csrf_token), |
|
503 | 516 | ], |
|
504 | 517 | status=302) |
|
505 | 518 | |
|
506 | 519 | location = response.headers['Location'] |
|
507 | 520 | |
|
508 | 521 | pull_request_id = location.rsplit('/', 1)[1] |
|
509 | 522 | assert pull_request_id != 'new' |
|
510 | 523 | pull_request = PullRequest.get(int(pull_request_id)) |
|
511 | 524 | |
|
512 | 525 | # target_ref has to point to the ancestor's commit_id in order to |
|
513 | 526 | # show the correct diff |
|
514 | 527 | expected_target_ref = 'branch:default:' + commit_ids['ancestor'] |
|
515 | 528 | assert pull_request.target_ref == expected_target_ref |
|
516 | 529 | |
|
517 | 530 | # Check generated diff contents |
|
518 | 531 | response = response.follow() |
|
519 | 532 | assert 'content_of_ancestor' not in response.body |
|
520 | 533 | assert 'content_of_ancestor-child' not in response.body |
|
521 | 534 | assert 'content_of_change' in response.body |
|
522 | 535 | |
|
523 | 536 | def test_merge_pull_request_enabled(self, pr_util, csrf_token): |
|
524 | 537 | # Clear any previous calls to rcextensions |
|
525 | 538 | rhodecode.EXTENSIONS.calls.clear() |
|
526 | 539 | |
|
527 | 540 | pull_request = pr_util.create_pull_request( |
|
528 | 541 | approved=True, mergeable=True) |
|
529 | 542 | pull_request_id = pull_request.pull_request_id |
|
530 | 543 | repo_name = pull_request.target_repo.scm_instance().name, |
|
531 | 544 | |
|
532 | 545 | response = self.app.post( |
|
533 | 546 | url(controller='pullrequests', |
|
534 | 547 | action='merge', |
|
535 | 548 | repo_name=str(repo_name[0]), |
|
536 | 549 | pull_request_id=str(pull_request_id)), |
|
537 | 550 | params={'csrf_token': csrf_token}).follow() |
|
538 | 551 | |
|
539 | 552 | pull_request = PullRequest.get(pull_request_id) |
|
540 | 553 | |
|
541 | 554 | assert response.status_int == 200 |
|
542 | 555 | assert pull_request.is_closed() |
|
543 | 556 | assert_pull_request_status( |
|
544 | 557 | pull_request, ChangesetStatus.STATUS_APPROVED) |
|
545 | 558 | |
|
546 | 559 | # Check the relevant log entries were added |
|
547 | 560 | user_logs = UserLog.query().order_by('-user_log_id').limit(3) |
|
548 | 561 | actions = [log.action for log in user_logs] |
|
549 | 562 | pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request) |
|
550 | 563 | expected_actions = [ |
|
551 | 564 | u'repo.pull_request.close', |
|
552 | 565 | u'repo.pull_request.merge', |
|
553 | 566 | u'repo.pull_request.comment.create' |
|
554 | 567 | ] |
|
555 | 568 | assert actions == expected_actions |
|
556 | 569 | |
|
557 | 570 | user_logs = UserLog.query().order_by('-user_log_id').limit(4) |
|
558 | 571 | actions = [log for log in user_logs] |
|
559 | 572 | assert actions[-1].action == 'user.push' |
|
560 | 573 | assert actions[-1].action_data['commit_ids'] == pr_commit_ids |
|
561 | 574 | |
|
562 | 575 | # Check post_push rcextension was really executed |
|
563 | 576 | push_calls = rhodecode.EXTENSIONS.calls['post_push'] |
|
564 | 577 | assert len(push_calls) == 1 |
|
565 | 578 | unused_last_call_args, last_call_kwargs = push_calls[0] |
|
566 | 579 | assert last_call_kwargs['action'] == 'push' |
|
567 | 580 | assert last_call_kwargs['pushed_revs'] == pr_commit_ids |
|
568 | 581 | |
|
569 | 582 | def test_merge_pull_request_disabled(self, pr_util, csrf_token): |
|
570 | 583 | pull_request = pr_util.create_pull_request(mergeable=False) |
|
571 | 584 | pull_request_id = pull_request.pull_request_id |
|
572 | 585 | pull_request = PullRequest.get(pull_request_id) |
|
573 | 586 | |
|
574 | 587 | response = self.app.post( |
|
575 | 588 | url(controller='pullrequests', |
|
576 | 589 | action='merge', |
|
577 | 590 | repo_name=pull_request.target_repo.scm_instance().name, |
|
578 | 591 | pull_request_id=str(pull_request.pull_request_id)), |
|
579 | 592 | params={'csrf_token': csrf_token}).follow() |
|
580 | 593 | |
|
581 | 594 | assert response.status_int == 200 |
|
582 | 595 | response.mustcontain( |
|
583 | 596 | 'Merge is not currently possible because of below failed checks.') |
|
584 | 597 | response.mustcontain('Server-side pull request merging is disabled.') |
|
585 | 598 | |
|
586 | 599 | @pytest.mark.skip_backends('svn') |
|
587 | 600 | def test_merge_pull_request_not_approved(self, pr_util, csrf_token): |
|
588 | 601 | pull_request = pr_util.create_pull_request(mergeable=True) |
|
589 | 602 | pull_request_id = pull_request.pull_request_id |
|
590 | 603 | repo_name = pull_request.target_repo.scm_instance().name, |
|
591 | 604 | |
|
592 | 605 | response = self.app.post( |
|
593 | 606 | url(controller='pullrequests', |
|
594 | 607 | action='merge', |
|
595 | 608 | repo_name=str(repo_name[0]), |
|
596 | 609 | pull_request_id=str(pull_request_id)), |
|
597 | 610 | params={'csrf_token': csrf_token}).follow() |
|
598 | 611 | |
|
599 | 612 | assert response.status_int == 200 |
|
600 | 613 | |
|
601 | 614 | response.mustcontain( |
|
602 | 615 | 'Merge is not currently possible because of below failed checks.') |
|
603 | 616 | response.mustcontain('Pull request reviewer approval is pending.') |
|
604 | 617 | |
|
605 | 618 | def test_update_source_revision(self, backend, csrf_token): |
|
606 | 619 | commits = [ |
|
607 | 620 | {'message': 'ancestor'}, |
|
608 | 621 | {'message': 'change'}, |
|
609 | 622 | {'message': 'change-2'}, |
|
610 | 623 | ] |
|
611 | 624 | commit_ids = backend.create_master_repo(commits) |
|
612 | 625 | target = backend.create_repo(heads=['ancestor']) |
|
613 | 626 | source = backend.create_repo(heads=['change']) |
|
614 | 627 | |
|
615 | 628 | # create pr from a in source to A in target |
|
616 | 629 | pull_request = PullRequest() |
|
617 | 630 | pull_request.source_repo = source |
|
618 | 631 | # TODO: johbo: Make sure that we write the source ref this way! |
|
619 | 632 | pull_request.source_ref = 'branch:{branch}:{commit_id}'.format( |
|
620 | 633 | branch=backend.default_branch_name, commit_id=commit_ids['change']) |
|
621 | 634 | pull_request.target_repo = target |
|
622 | 635 | |
|
623 | 636 | pull_request.target_ref = 'branch:{branch}:{commit_id}'.format( |
|
624 | 637 | branch=backend.default_branch_name, |
|
625 | 638 | commit_id=commit_ids['ancestor']) |
|
626 | 639 | pull_request.revisions = [commit_ids['change']] |
|
627 | 640 | pull_request.title = u"Test" |
|
628 | 641 | pull_request.description = u"Description" |
|
629 | 642 | pull_request.author = UserModel().get_by_username( |
|
630 | 643 | TEST_USER_ADMIN_LOGIN) |
|
631 | 644 | Session().add(pull_request) |
|
632 | 645 | Session().commit() |
|
633 | 646 | pull_request_id = pull_request.pull_request_id |
|
634 | 647 | |
|
635 | 648 | # source has ancestor - change - change-2 |
|
636 | 649 | backend.pull_heads(source, heads=['change-2']) |
|
637 | 650 | |
|
638 | 651 | # update PR |
|
639 | 652 | self.app.post( |
|
640 | 653 | url(controller='pullrequests', action='update', |
|
641 | 654 | repo_name=target.repo_name, |
|
642 | 655 | pull_request_id=str(pull_request_id)), |
|
643 | 656 | params={'update_commits': 'true', '_method': 'put', |
|
644 | 657 | 'csrf_token': csrf_token}) |
|
645 | 658 | |
|
646 | 659 | # check that we have now both revisions |
|
647 | 660 | pull_request = PullRequest.get(pull_request_id) |
|
648 | 661 | assert pull_request.revisions == [ |
|
649 | 662 | commit_ids['change-2'], commit_ids['change']] |
|
650 | 663 | |
|
651 | 664 | # TODO: johbo: this should be a test on its own |
|
652 | 665 | response = self.app.get(url( |
|
653 | 666 | controller='pullrequests', action='index', |
|
654 | 667 | repo_name=target.repo_name)) |
|
655 | 668 | assert response.status_int == 200 |
|
656 | 669 | assert 'Pull request updated to' in response.body |
|
657 | 670 | assert 'with 1 added, 0 removed commits.' in response.body |
|
658 | 671 | |
|
659 | 672 | def test_update_target_revision(self, backend, csrf_token): |
|
660 | 673 | commits = [ |
|
661 | 674 | {'message': 'ancestor'}, |
|
662 | 675 | {'message': 'change'}, |
|
663 | 676 | {'message': 'ancestor-new', 'parents': ['ancestor']}, |
|
664 | 677 | {'message': 'change-rebased'}, |
|
665 | 678 | ] |
|
666 | 679 | commit_ids = backend.create_master_repo(commits) |
|
667 | 680 | target = backend.create_repo(heads=['ancestor']) |
|
668 | 681 | source = backend.create_repo(heads=['change']) |
|
669 | 682 | |
|
670 | 683 | # create pr from a in source to A in target |
|
671 | 684 | pull_request = PullRequest() |
|
672 | 685 | pull_request.source_repo = source |
|
673 | 686 | # TODO: johbo: Make sure that we write the source ref this way! |
|
674 | 687 | pull_request.source_ref = 'branch:{branch}:{commit_id}'.format( |
|
675 | 688 | branch=backend.default_branch_name, commit_id=commit_ids['change']) |
|
676 | 689 | pull_request.target_repo = target |
|
677 | 690 | # TODO: johbo: Target ref should be branch based, since tip can jump |
|
678 | 691 | # from branch to branch |
|
679 | 692 | pull_request.target_ref = 'branch:{branch}:{commit_id}'.format( |
|
680 | 693 | branch=backend.default_branch_name, |
|
681 | 694 | commit_id=commit_ids['ancestor']) |
|
682 | 695 | pull_request.revisions = [commit_ids['change']] |
|
683 | 696 | pull_request.title = u"Test" |
|
684 | 697 | pull_request.description = u"Description" |
|
685 | 698 | pull_request.author = UserModel().get_by_username( |
|
686 | 699 | TEST_USER_ADMIN_LOGIN) |
|
687 | 700 | Session().add(pull_request) |
|
688 | 701 | Session().commit() |
|
689 | 702 | pull_request_id = pull_request.pull_request_id |
|
690 | 703 | |
|
691 | 704 | # target has ancestor - ancestor-new |
|
692 | 705 | # source has ancestor - ancestor-new - change-rebased |
|
693 | 706 | backend.pull_heads(target, heads=['ancestor-new']) |
|
694 | 707 | backend.pull_heads(source, heads=['change-rebased']) |
|
695 | 708 | |
|
696 | 709 | # update PR |
|
697 | 710 | self.app.post( |
|
698 | 711 | url(controller='pullrequests', action='update', |
|
699 | 712 | repo_name=target.repo_name, |
|
700 | 713 | pull_request_id=str(pull_request_id)), |
|
701 | 714 | params={'update_commits': 'true', '_method': 'put', |
|
702 | 715 | 'csrf_token': csrf_token}, |
|
703 | 716 | status=200) |
|
704 | 717 | |
|
705 | 718 | # check that we have now both revisions |
|
706 | 719 | pull_request = PullRequest.get(pull_request_id) |
|
707 | 720 | assert pull_request.revisions == [commit_ids['change-rebased']] |
|
708 | 721 | assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format( |
|
709 | 722 | branch=backend.default_branch_name, |
|
710 | 723 | commit_id=commit_ids['ancestor-new']) |
|
711 | 724 | |
|
712 | 725 | # TODO: johbo: This should be a test on its own |
|
713 | 726 | response = self.app.get(url( |
|
714 | 727 | controller='pullrequests', action='index', |
|
715 | 728 | repo_name=target.repo_name)) |
|
716 | 729 | assert response.status_int == 200 |
|
717 | 730 | assert 'Pull request updated to' in response.body |
|
718 | 731 | assert 'with 1 added, 1 removed commits.' in response.body |
|
719 | 732 | |
|
720 | 733 | def test_update_of_ancestor_reference(self, backend, csrf_token): |
|
721 | 734 | commits = [ |
|
722 | 735 | {'message': 'ancestor'}, |
|
723 | 736 | {'message': 'change'}, |
|
724 | 737 | {'message': 'change-2'}, |
|
725 | 738 | {'message': 'ancestor-new', 'parents': ['ancestor']}, |
|
726 | 739 | {'message': 'change-rebased'}, |
|
727 | 740 | ] |
|
728 | 741 | commit_ids = backend.create_master_repo(commits) |
|
729 | 742 | target = backend.create_repo(heads=['ancestor']) |
|
730 | 743 | source = backend.create_repo(heads=['change']) |
|
731 | 744 | |
|
732 | 745 | # create pr from a in source to A in target |
|
733 | 746 | pull_request = PullRequest() |
|
734 | 747 | pull_request.source_repo = source |
|
735 | 748 | # TODO: johbo: Make sure that we write the source ref this way! |
|
736 | 749 | pull_request.source_ref = 'branch:{branch}:{commit_id}'.format( |
|
737 | 750 | branch=backend.default_branch_name, |
|
738 | 751 | commit_id=commit_ids['change']) |
|
739 | 752 | pull_request.target_repo = target |
|
740 | 753 | # TODO: johbo: Target ref should be branch based, since tip can jump |
|
741 | 754 | # from branch to branch |
|
742 | 755 | pull_request.target_ref = 'branch:{branch}:{commit_id}'.format( |
|
743 | 756 | branch=backend.default_branch_name, |
|
744 | 757 | commit_id=commit_ids['ancestor']) |
|
745 | 758 | pull_request.revisions = [commit_ids['change']] |
|
746 | 759 | pull_request.title = u"Test" |
|
747 | 760 | pull_request.description = u"Description" |
|
748 | 761 | pull_request.author = UserModel().get_by_username( |
|
749 | 762 | TEST_USER_ADMIN_LOGIN) |
|
750 | 763 | Session().add(pull_request) |
|
751 | 764 | Session().commit() |
|
752 | 765 | pull_request_id = pull_request.pull_request_id |
|
753 | 766 | |
|
754 | 767 | # target has ancestor - ancestor-new |
|
755 | 768 | # source has ancestor - ancestor-new - change-rebased |
|
756 | 769 | backend.pull_heads(target, heads=['ancestor-new']) |
|
757 | 770 | backend.pull_heads(source, heads=['change-rebased']) |
|
758 | 771 | |
|
759 | 772 | # update PR |
|
760 | 773 | self.app.post( |
|
761 | 774 | url(controller='pullrequests', action='update', |
|
762 | 775 | repo_name=target.repo_name, |
|
763 | 776 | pull_request_id=str(pull_request_id)), |
|
764 | 777 | params={'update_commits': 'true', '_method': 'put', |
|
765 | 778 | 'csrf_token': csrf_token}, |
|
766 | 779 | status=200) |
|
767 | 780 | |
|
768 | 781 | # Expect the target reference to be updated correctly |
|
769 | 782 | pull_request = PullRequest.get(pull_request_id) |
|
770 | 783 | assert pull_request.revisions == [commit_ids['change-rebased']] |
|
771 | 784 | expected_target_ref = 'branch:{branch}:{commit_id}'.format( |
|
772 | 785 | branch=backend.default_branch_name, |
|
773 | 786 | commit_id=commit_ids['ancestor-new']) |
|
774 | 787 | assert pull_request.target_ref == expected_target_ref |
|
775 | 788 | |
|
776 | 789 | def test_remove_pull_request_branch(self, backend_git, csrf_token): |
|
777 | 790 | branch_name = 'development' |
|
778 | 791 | commits = [ |
|
779 | 792 | {'message': 'initial-commit'}, |
|
780 | 793 | {'message': 'old-feature'}, |
|
781 | 794 | {'message': 'new-feature', 'branch': branch_name}, |
|
782 | 795 | ] |
|
783 | 796 | repo = backend_git.create_repo(commits) |
|
784 | 797 | commit_ids = backend_git.commit_ids |
|
785 | 798 | |
|
786 | 799 | pull_request = PullRequest() |
|
787 | 800 | pull_request.source_repo = repo |
|
788 | 801 | pull_request.target_repo = repo |
|
789 | 802 | pull_request.source_ref = 'branch:{branch}:{commit_id}'.format( |
|
790 | 803 | branch=branch_name, commit_id=commit_ids['new-feature']) |
|
791 | 804 | pull_request.target_ref = 'branch:{branch}:{commit_id}'.format( |
|
792 | 805 | branch=backend_git.default_branch_name, |
|
793 | 806 | commit_id=commit_ids['old-feature']) |
|
794 | 807 | pull_request.revisions = [commit_ids['new-feature']] |
|
795 | 808 | pull_request.title = u"Test" |
|
796 | 809 | pull_request.description = u"Description" |
|
797 | 810 | pull_request.author = UserModel().get_by_username( |
|
798 | 811 | TEST_USER_ADMIN_LOGIN) |
|
799 | 812 | Session().add(pull_request) |
|
800 | 813 | Session().commit() |
|
801 | 814 | |
|
802 | 815 | vcs = repo.scm_instance() |
|
803 | 816 | vcs.remove_ref('refs/heads/{}'.format(branch_name)) |
|
804 | 817 | |
|
805 | 818 | response = self.app.get(url( |
|
806 | 819 | controller='pullrequests', action='show', |
|
807 | 820 | repo_name=repo.repo_name, |
|
808 | 821 | pull_request_id=str(pull_request.pull_request_id))) |
|
809 | 822 | |
|
810 | 823 | assert response.status_int == 200 |
|
811 | 824 | assert_response = AssertResponse(response) |
|
812 | 825 | assert_response.element_contains( |
|
813 | 826 | '#changeset_compare_view_content .alert strong', |
|
814 | 827 | 'Missing commits') |
|
815 | 828 | assert_response.element_contains( |
|
816 | 829 | '#changeset_compare_view_content .alert', |
|
817 | 830 | 'This pull request cannot be displayed, because one or more' |
|
818 | 831 | ' commits no longer exist in the source repository.') |
|
819 | 832 | |
|
820 | 833 | def test_strip_commits_from_pull_request( |
|
821 | 834 | self, backend, pr_util, csrf_token): |
|
822 | 835 | commits = [ |
|
823 | 836 | {'message': 'initial-commit'}, |
|
824 | 837 | {'message': 'old-feature'}, |
|
825 | 838 | {'message': 'new-feature', 'parents': ['initial-commit']}, |
|
826 | 839 | ] |
|
827 | 840 | pull_request = pr_util.create_pull_request( |
|
828 | 841 | commits, target_head='initial-commit', source_head='new-feature', |
|
829 | 842 | revisions=['new-feature']) |
|
830 | 843 | |
|
831 | 844 | vcs = pr_util.source_repository.scm_instance() |
|
832 | 845 | if backend.alias == 'git': |
|
833 | 846 | vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master') |
|
834 | 847 | else: |
|
835 | 848 | vcs.strip(pr_util.commit_ids['new-feature']) |
|
836 | 849 | |
|
837 | 850 | response = self.app.get(url( |
|
838 | 851 | controller='pullrequests', action='show', |
|
839 | 852 | repo_name=pr_util.target_repository.repo_name, |
|
840 | 853 | pull_request_id=str(pull_request.pull_request_id))) |
|
841 | 854 | |
|
842 | 855 | assert response.status_int == 200 |
|
843 | 856 | assert_response = AssertResponse(response) |
|
844 | 857 | assert_response.element_contains( |
|
845 | 858 | '#changeset_compare_view_content .alert strong', |
|
846 | 859 | 'Missing commits') |
|
847 | 860 | assert_response.element_contains( |
|
848 | 861 | '#changeset_compare_view_content .alert', |
|
849 | 862 | 'This pull request cannot be displayed, because one or more' |
|
850 | 863 | ' commits no longer exist in the source repository.') |
|
851 | 864 | assert_response.element_contains( |
|
852 | 865 | '#update_commits', |
|
853 | 866 | 'Update commits') |
|
854 | 867 | |
|
855 | 868 | def test_strip_commits_and_update( |
|
856 | 869 | self, backend, pr_util, csrf_token): |
|
857 | 870 | commits = [ |
|
858 | 871 | {'message': 'initial-commit'}, |
|
859 | 872 | {'message': 'old-feature'}, |
|
860 | 873 | {'message': 'new-feature', 'parents': ['old-feature']}, |
|
861 | 874 | ] |
|
862 | 875 | pull_request = pr_util.create_pull_request( |
|
863 | 876 | commits, target_head='old-feature', source_head='new-feature', |
|
864 | 877 | revisions=['new-feature'], mergeable=True) |
|
865 | 878 | |
|
866 | 879 | vcs = pr_util.source_repository.scm_instance() |
|
867 | 880 | if backend.alias == 'git': |
|
868 | 881 | vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master') |
|
869 | 882 | else: |
|
870 | 883 | vcs.strip(pr_util.commit_ids['new-feature']) |
|
871 | 884 | |
|
872 | 885 | response = self.app.post( |
|
873 | 886 | url(controller='pullrequests', action='update', |
|
874 | 887 | repo_name=pull_request.target_repo.repo_name, |
|
875 | 888 | pull_request_id=str(pull_request.pull_request_id)), |
|
876 | 889 | params={'update_commits': 'true', '_method': 'put', |
|
877 | 890 | 'csrf_token': csrf_token}) |
|
878 | 891 | |
|
879 | 892 | assert response.status_int == 200 |
|
880 | 893 | assert response.body == 'true' |
|
881 | 894 | |
|
882 | 895 | # Make sure that after update, it won't raise 500 errors |
|
883 | 896 | response = self.app.get(url( |
|
884 | 897 | controller='pullrequests', action='show', |
|
885 | 898 | repo_name=pr_util.target_repository.repo_name, |
|
886 | 899 | pull_request_id=str(pull_request.pull_request_id))) |
|
887 | 900 | |
|
888 | 901 | assert response.status_int == 200 |
|
889 | 902 | assert_response = AssertResponse(response) |
|
890 | 903 | assert_response.element_contains( |
|
891 | 904 | '#changeset_compare_view_content .alert strong', |
|
892 | 905 | 'Missing commits') |
|
893 | 906 | |
|
894 | 907 | def test_branch_is_a_link(self, pr_util): |
|
895 | 908 | pull_request = pr_util.create_pull_request() |
|
896 | 909 | pull_request.source_ref = 'branch:origin:1234567890abcdef' |
|
897 | 910 | pull_request.target_ref = 'branch:target:abcdef1234567890' |
|
898 | 911 | Session().add(pull_request) |
|
899 | 912 | Session().commit() |
|
900 | 913 | |
|
901 | 914 | response = self.app.get(url( |
|
902 | 915 | controller='pullrequests', action='show', |
|
903 | 916 | repo_name=pull_request.target_repo.scm_instance().name, |
|
904 | 917 | pull_request_id=str(pull_request.pull_request_id))) |
|
905 | 918 | assert response.status_int == 200 |
|
906 | 919 | assert_response = AssertResponse(response) |
|
907 | 920 | |
|
908 | 921 | origin = assert_response.get_element('.pr-origininfo .tag') |
|
909 | 922 | origin_children = origin.getchildren() |
|
910 | 923 | assert len(origin_children) == 1 |
|
911 | 924 | target = assert_response.get_element('.pr-targetinfo .tag') |
|
912 | 925 | target_children = target.getchildren() |
|
913 | 926 | assert len(target_children) == 1 |
|
914 | 927 | |
|
915 |
expected_origin_link = |
|
|
916 |
'changelog |
|
|
928 | expected_origin_link = route_path( | |
|
929 | 'repo_changelog', | |
|
917 | 930 | repo_name=pull_request.source_repo.scm_instance().name, |
|
918 | branch='origin') | |
|
919 |
expected_target_link = |
|
|
920 |
'changelog |
|
|
931 | params=dict(branch='origin')) | |
|
932 | expected_target_link = route_path( | |
|
933 | 'repo_changelog', | |
|
921 | 934 | repo_name=pull_request.target_repo.scm_instance().name, |
|
922 | branch='target') | |
|
935 | params=dict(branch='target')) | |
|
923 | 936 | assert origin_children[0].attrib['href'] == expected_origin_link |
|
924 | 937 | assert origin_children[0].text == 'branch: origin' |
|
925 | 938 | assert target_children[0].attrib['href'] == expected_target_link |
|
926 | 939 | assert target_children[0].text == 'branch: target' |
|
927 | 940 | |
|
928 | 941 | def test_bookmark_is_not_a_link(self, pr_util): |
|
929 | 942 | pull_request = pr_util.create_pull_request() |
|
930 | 943 | pull_request.source_ref = 'bookmark:origin:1234567890abcdef' |
|
931 | 944 | pull_request.target_ref = 'bookmark:target:abcdef1234567890' |
|
932 | 945 | Session().add(pull_request) |
|
933 | 946 | Session().commit() |
|
934 | 947 | |
|
935 | 948 | response = self.app.get(url( |
|
936 | 949 | controller='pullrequests', action='show', |
|
937 | 950 | repo_name=pull_request.target_repo.scm_instance().name, |
|
938 | 951 | pull_request_id=str(pull_request.pull_request_id))) |
|
939 | 952 | assert response.status_int == 200 |
|
940 | 953 | assert_response = AssertResponse(response) |
|
941 | 954 | |
|
942 | 955 | origin = assert_response.get_element('.pr-origininfo .tag') |
|
943 | 956 | assert origin.text.strip() == 'bookmark: origin' |
|
944 | 957 | assert origin.getchildren() == [] |
|
945 | 958 | |
|
946 | 959 | target = assert_response.get_element('.pr-targetinfo .tag') |
|
947 | 960 | assert target.text.strip() == 'bookmark: target' |
|
948 | 961 | assert target.getchildren() == [] |
|
949 | 962 | |
|
950 | 963 | def test_tag_is_not_a_link(self, pr_util): |
|
951 | 964 | pull_request = pr_util.create_pull_request() |
|
952 | 965 | pull_request.source_ref = 'tag:origin:1234567890abcdef' |
|
953 | 966 | pull_request.target_ref = 'tag:target:abcdef1234567890' |
|
954 | 967 | Session().add(pull_request) |
|
955 | 968 | Session().commit() |
|
956 | 969 | |
|
957 | 970 | response = self.app.get(url( |
|
958 | 971 | controller='pullrequests', action='show', |
|
959 | 972 | repo_name=pull_request.target_repo.scm_instance().name, |
|
960 | 973 | pull_request_id=str(pull_request.pull_request_id))) |
|
961 | 974 | assert response.status_int == 200 |
|
962 | 975 | assert_response = AssertResponse(response) |
|
963 | 976 | |
|
964 | 977 | origin = assert_response.get_element('.pr-origininfo .tag') |
|
965 | 978 | assert origin.text.strip() == 'tag: origin' |
|
966 | 979 | assert origin.getchildren() == [] |
|
967 | 980 | |
|
968 | 981 | target = assert_response.get_element('.pr-targetinfo .tag') |
|
969 | 982 | assert target.text.strip() == 'tag: target' |
|
970 | 983 | assert target.getchildren() == [] |
|
971 | 984 | |
|
972 | 985 | @pytest.mark.parametrize('mergeable', [True, False]) |
|
973 | 986 | def test_shadow_repository_link( |
|
974 | 987 | self, mergeable, pr_util, http_host_only_stub): |
|
975 | 988 | """ |
|
976 | 989 | Check that the pull request summary page displays a link to the shadow |
|
977 | 990 | repository if the pull request is mergeable. If it is not mergeable |
|
978 | 991 | the link should not be displayed. |
|
979 | 992 | """ |
|
980 | 993 | pull_request = pr_util.create_pull_request( |
|
981 | 994 | mergeable=mergeable, enable_notifications=False) |
|
982 | 995 | target_repo = pull_request.target_repo.scm_instance() |
|
983 | 996 | pr_id = pull_request.pull_request_id |
|
984 | 997 | shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format( |
|
985 | 998 | host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id) |
|
986 | 999 | |
|
987 | 1000 | response = self.app.get(url( |
|
988 | 1001 | controller='pullrequests', action='show', |
|
989 | 1002 | repo_name=target_repo.name, |
|
990 | 1003 | pull_request_id=str(pr_id))) |
|
991 | 1004 | |
|
992 | 1005 | assertr = AssertResponse(response) |
|
993 | 1006 | if mergeable: |
|
994 | 1007 | assertr.element_value_contains( |
|
995 | 1008 | 'div.pr-mergeinfo input', shadow_url) |
|
996 | 1009 | assertr.element_value_contains( |
|
997 | 1010 | 'div.pr-mergeinfo input', 'pr-merge') |
|
998 | 1011 | else: |
|
999 | 1012 | assertr.no_element_exists('div.pr-mergeinfo') |
|
1000 | 1013 | |
|
1001 | 1014 | |
|
1002 | 1015 | @pytest.mark.usefixtures('app') |
|
1003 | 1016 | @pytest.mark.backends("git", "hg") |
|
1004 | 1017 | class TestPullrequestsControllerDelete(object): |
|
1005 | 1018 | def test_pull_request_delete_button_permissions_admin( |
|
1006 | 1019 | self, autologin_user, user_admin, pr_util): |
|
1007 | 1020 | pull_request = pr_util.create_pull_request( |
|
1008 | 1021 | author=user_admin.username, enable_notifications=False) |
|
1009 | 1022 | |
|
1010 | 1023 | response = self.app.get(url( |
|
1011 | 1024 | controller='pullrequests', action='show', |
|
1012 | 1025 | repo_name=pull_request.target_repo.scm_instance().name, |
|
1013 | 1026 | pull_request_id=str(pull_request.pull_request_id))) |
|
1014 | 1027 | |
|
1015 | 1028 | response.mustcontain('id="delete_pullrequest"') |
|
1016 | 1029 | response.mustcontain('Confirm to delete this pull request') |
|
1017 | 1030 | |
|
1018 | 1031 | def test_pull_request_delete_button_permissions_owner( |
|
1019 | 1032 | self, autologin_regular_user, user_regular, pr_util): |
|
1020 | 1033 | pull_request = pr_util.create_pull_request( |
|
1021 | 1034 | author=user_regular.username, enable_notifications=False) |
|
1022 | 1035 | |
|
1023 | 1036 | response = self.app.get(url( |
|
1024 | 1037 | controller='pullrequests', action='show', |
|
1025 | 1038 | repo_name=pull_request.target_repo.scm_instance().name, |
|
1026 | 1039 | pull_request_id=str(pull_request.pull_request_id))) |
|
1027 | 1040 | |
|
1028 | 1041 | response.mustcontain('id="delete_pullrequest"') |
|
1029 | 1042 | response.mustcontain('Confirm to delete this pull request') |
|
1030 | 1043 | |
|
1031 | 1044 | def test_pull_request_delete_button_permissions_forbidden( |
|
1032 | 1045 | self, autologin_regular_user, user_regular, user_admin, pr_util): |
|
1033 | 1046 | pull_request = pr_util.create_pull_request( |
|
1034 | 1047 | author=user_admin.username, enable_notifications=False) |
|
1035 | 1048 | |
|
1036 | 1049 | response = self.app.get(url( |
|
1037 | 1050 | controller='pullrequests', action='show', |
|
1038 | 1051 | repo_name=pull_request.target_repo.scm_instance().name, |
|
1039 | 1052 | pull_request_id=str(pull_request.pull_request_id))) |
|
1040 | 1053 | response.mustcontain(no=['id="delete_pullrequest"']) |
|
1041 | 1054 | response.mustcontain(no=['Confirm to delete this pull request']) |
|
1042 | 1055 | |
|
1043 | 1056 | def test_pull_request_delete_button_permissions_can_update_cannot_delete( |
|
1044 | 1057 | self, autologin_regular_user, user_regular, user_admin, pr_util, |
|
1045 | 1058 | user_util): |
|
1046 | 1059 | |
|
1047 | 1060 | pull_request = pr_util.create_pull_request( |
|
1048 | 1061 | author=user_admin.username, enable_notifications=False) |
|
1049 | 1062 | |
|
1050 | 1063 | user_util.grant_user_permission_to_repo( |
|
1051 | 1064 | pull_request.target_repo, user_regular, |
|
1052 | 1065 | 'repository.write') |
|
1053 | 1066 | |
|
1054 | 1067 | response = self.app.get(url( |
|
1055 | 1068 | controller='pullrequests', action='show', |
|
1056 | 1069 | repo_name=pull_request.target_repo.scm_instance().name, |
|
1057 | 1070 | pull_request_id=str(pull_request.pull_request_id))) |
|
1058 | 1071 | |
|
1059 | 1072 | response.mustcontain('id="open_edit_pullrequest"') |
|
1060 | 1073 | response.mustcontain('id="delete_pullrequest"') |
|
1061 | 1074 | response.mustcontain(no=['Confirm to delete this pull request']) |
|
1062 | 1075 | |
|
1063 | 1076 | def test_delete_comment_returns_404_if_comment_does_not_exist( |
|
1064 | 1077 | self, autologin_user, pr_util, user_admin): |
|
1065 | 1078 | |
|
1066 | 1079 | pull_request = pr_util.create_pull_request( |
|
1067 | 1080 | author=user_admin.username, enable_notifications=False) |
|
1068 | 1081 | |
|
1069 | 1082 | self.app.get(url( |
|
1070 | 1083 | controller='pullrequests', action='delete_comment', |
|
1071 | 1084 | repo_name=pull_request.target_repo.scm_instance().name, |
|
1072 | 1085 | comment_id=1024404), status=404) |
|
1073 | 1086 | |
|
1074 | 1087 | |
|
1075 | 1088 | def assert_pull_request_status(pull_request, expected_status): |
|
1076 | 1089 | status = ChangesetStatusModel().calculated_review_status( |
|
1077 | 1090 | pull_request=pull_request) |
|
1078 | 1091 | assert status == expected_status |
|
1079 | 1092 | |
|
1080 | 1093 | |
|
1081 | 1094 | @pytest.mark.parametrize('action', ['index', 'create']) |
|
1082 | 1095 | @pytest.mark.usefixtures("autologin_user") |
|
1083 | 1096 | def test_redirects_to_repo_summary_for_svn_repositories(backend_svn, app, action): |
|
1084 | 1097 | response = app.get(url( |
|
1085 | 1098 | controller='pullrequests', action=action, |
|
1086 | 1099 | repo_name=backend_svn.repo_name)) |
|
1087 | 1100 | assert response.status_int == 302 |
|
1088 | 1101 | |
|
1089 | 1102 | # Not allowed, redirect to the summary |
|
1090 | 1103 | redirected = response.follow() |
|
1091 | 1104 | summary_url = h.route_path('repo_summary', repo_name=backend_svn.repo_name) |
|
1092 | 1105 | |
|
1093 | 1106 | # URL adds leading slash and path doesn't have it |
|
1094 | 1107 | assert redirected.request.path == summary_url |
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now