Show More
The requested changes are too big and content was truncated. Show full diff
This diff has been collapsed as it changes many lines, (1278 lines changed) Show them Hide them | |||||
@@ -0,0 +1,1278 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # Copyright (C) 2011-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 | import itertools | |||
|
22 | import logging | |||
|
23 | import os | |||
|
24 | import shutil | |||
|
25 | import tempfile | |||
|
26 | import collections | |||
|
27 | ||||
|
28 | from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound | |||
|
29 | from pyramid.view import view_config | |||
|
30 | from pyramid.renderers import render | |||
|
31 | from pyramid.response import Response | |||
|
32 | ||||
|
33 | from rhodecode.apps._base import RepoAppView | |||
|
34 | ||||
|
35 | from rhodecode.controllers.utils import parse_path_ref | |||
|
36 | from rhodecode.lib import diffs, helpers as h, caches | |||
|
37 | from rhodecode.lib import audit_logger | |||
|
38 | from rhodecode.lib.exceptions import NonRelativePathError | |||
|
39 | from rhodecode.lib.codeblocks import ( | |||
|
40 | filenode_as_lines_tokens, filenode_as_annotated_lines_tokens) | |||
|
41 | from rhodecode.lib.utils2 import ( | |||
|
42 | convert_line_endings, detect_mode, safe_str, str2bool) | |||
|
43 | from rhodecode.lib.auth import ( | |||
|
44 | LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired) | |||
|
45 | from rhodecode.lib.vcs import path as vcspath | |||
|
46 | from rhodecode.lib.vcs.backends.base import EmptyCommit | |||
|
47 | from rhodecode.lib.vcs.conf import settings | |||
|
48 | from rhodecode.lib.vcs.nodes import FileNode | |||
|
49 | from rhodecode.lib.vcs.exceptions import ( | |||
|
50 | RepositoryError, CommitDoesNotExistError, EmptyRepositoryError, | |||
|
51 | ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError, | |||
|
52 | NodeDoesNotExistError, CommitError, NodeError) | |||
|
53 | ||||
|
54 | from rhodecode.model.scm import ScmModel | |||
|
55 | from rhodecode.model.db import Repository | |||
|
56 | ||||
|
57 | log = logging.getLogger(__name__) | |||
|
58 | ||||
|
59 | ||||
|
60 | class RepoFilesView(RepoAppView): | |||
|
61 | ||||
|
62 | @staticmethod | |||
|
63 | def adjust_file_path_for_svn(f_path, repo): | |||
|
64 | """ | |||
|
65 | Computes the relative path of `f_path`. | |||
|
66 | ||||
|
67 | This is mainly based on prefix matching of the recognized tags and | |||
|
68 | branches in the underlying repository. | |||
|
69 | """ | |||
|
70 | tags_and_branches = itertools.chain( | |||
|
71 | repo.branches.iterkeys(), | |||
|
72 | repo.tags.iterkeys()) | |||
|
73 | tags_and_branches = sorted(tags_and_branches, key=len, reverse=True) | |||
|
74 | ||||
|
75 | for name in tags_and_branches: | |||
|
76 | if f_path.startswith('{}/'.format(name)): | |||
|
77 | f_path = vcspath.relpath(f_path, name) | |||
|
78 | break | |||
|
79 | return f_path | |||
|
80 | ||||
|
81 | def load_default_context(self): | |||
|
82 | c = self._get_local_tmpl_context(include_app_defaults=True) | |||
|
83 | ||||
|
84 | # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead | |||
|
85 | c.repo_info = self.db_repo | |||
|
86 | c.rhodecode_repo = self.rhodecode_vcs_repo | |||
|
87 | ||||
|
88 | self._register_global_c(c) | |||
|
89 | return c | |||
|
90 | ||||
|
91 | def _ensure_not_locked(self): | |||
|
92 | _ = self.request.translate | |||
|
93 | ||||
|
94 | repo = self.db_repo | |||
|
95 | if repo.enable_locking and repo.locked[0]: | |||
|
96 | h.flash(_('This repository has been locked by %s on %s') | |||
|
97 | % (h.person_by_id(repo.locked[0]), | |||
|
98 | h.format_date(h.time_to_datetime(repo.locked[1]))), | |||
|
99 | 'warning') | |||
|
100 | files_url = h.route_path( | |||
|
101 | 'repo_files:default_path', | |||
|
102 | repo_name=self.db_repo_name, commit_id='tip') | |||
|
103 | raise HTTPFound(files_url) | |||
|
104 | ||||
|
105 | def _get_commit_and_path(self): | |||
|
106 | default_commit_id = self.db_repo.landing_rev[1] | |||
|
107 | default_f_path = '/' | |||
|
108 | ||||
|
109 | commit_id = self.request.matchdict.get( | |||
|
110 | 'commit_id', default_commit_id) | |||
|
111 | f_path = self.request.matchdict.get('f_path', default_f_path) | |||
|
112 | return commit_id, f_path | |||
|
113 | ||||
|
114 | def _get_default_encoding(self, c): | |||
|
115 | enc_list = getattr(c, 'default_encodings', []) | |||
|
116 | return enc_list[0] if enc_list else 'UTF-8' | |||
|
117 | ||||
|
118 | def _get_commit_or_redirect(self, commit_id, redirect_after=True): | |||
|
119 | """ | |||
|
120 | This is a safe way to get commit. If an error occurs it redirects to | |||
|
121 | tip with proper message | |||
|
122 | ||||
|
123 | :param commit_id: id of commit to fetch | |||
|
124 | :param redirect_after: toggle redirection | |||
|
125 | """ | |||
|
126 | _ = self.request.translate | |||
|
127 | ||||
|
128 | try: | |||
|
129 | return self.rhodecode_vcs_repo.get_commit(commit_id) | |||
|
130 | except EmptyRepositoryError: | |||
|
131 | if not redirect_after: | |||
|
132 | return None | |||
|
133 | ||||
|
134 | _url = h.route_path( | |||
|
135 | 'repo_files_add_file', | |||
|
136 | repo_name=self.db_repo_name, commit_id=0, f_path='', | |||
|
137 | _anchor='edit') | |||
|
138 | ||||
|
139 | if h.HasRepoPermissionAny( | |||
|
140 | 'repository.write', 'repository.admin')(self.db_repo_name): | |||
|
141 | add_new = h.link_to( | |||
|
142 | _('Click here to add a new file.'), _url, class_="alert-link") | |||
|
143 | else: | |||
|
144 | add_new = "" | |||
|
145 | ||||
|
146 | h.flash(h.literal( | |||
|
147 | _('There are no files yet. %s') % add_new), category='warning') | |||
|
148 | raise HTTPFound( | |||
|
149 | h.route_path('repo_summary', repo_name=self.db_repo_name)) | |||
|
150 | ||||
|
151 | except (CommitDoesNotExistError, LookupError): | |||
|
152 | msg = _('No such commit exists for this repository') | |||
|
153 | h.flash(msg, category='error') | |||
|
154 | raise HTTPNotFound() | |||
|
155 | except RepositoryError as e: | |||
|
156 | h.flash(safe_str(h.escape(e)), category='error') | |||
|
157 | raise HTTPNotFound() | |||
|
158 | ||||
|
159 | def _get_filenode_or_redirect(self, commit_obj, path): | |||
|
160 | """ | |||
|
161 | Returns file_node, if error occurs or given path is directory, | |||
|
162 | it'll redirect to top level path | |||
|
163 | """ | |||
|
164 | _ = self.request.translate | |||
|
165 | ||||
|
166 | try: | |||
|
167 | file_node = commit_obj.get_node(path) | |||
|
168 | if file_node.is_dir(): | |||
|
169 | raise RepositoryError('The given path is a directory') | |||
|
170 | except CommitDoesNotExistError: | |||
|
171 | log.exception('No such commit exists for this repository') | |||
|
172 | h.flash(_('No such commit exists for this repository'), category='error') | |||
|
173 | raise HTTPNotFound() | |||
|
174 | except RepositoryError as e: | |||
|
175 | log.warning('Repository error while fetching ' | |||
|
176 | 'filenode `%s`. Err:%s', path, e) | |||
|
177 | h.flash(safe_str(h.escape(e)), category='error') | |||
|
178 | raise HTTPNotFound() | |||
|
179 | ||||
|
180 | return file_node | |||
|
181 | ||||
|
182 | def _is_valid_head(self, commit_id, repo): | |||
|
183 | # check if commit is a branch identifier- basically we cannot | |||
|
184 | # create multiple heads via file editing | |||
|
185 | valid_heads = repo.branches.keys() + repo.branches.values() | |||
|
186 | ||||
|
187 | if h.is_svn(repo) and not repo.is_empty(): | |||
|
188 | # Note: Subversion only has one head, we add it here in case there | |||
|
189 | # is no branch matched. | |||
|
190 | valid_heads.append(repo.get_commit(commit_idx=-1).raw_id) | |||
|
191 | ||||
|
192 | # check if commit is a branch name or branch hash | |||
|
193 | return commit_id in valid_heads | |||
|
194 | ||||
|
195 | def _get_tree_cache_manager(self, namespace_type): | |||
|
196 | _namespace = caches.get_repo_namespace_key( | |||
|
197 | namespace_type, self.db_repo_name) | |||
|
198 | return caches.get_cache_manager('repo_cache_long', _namespace) | |||
|
199 | ||||
|
200 | def _get_tree_at_commit( | |||
|
201 | self, c, commit_id, f_path, full_load=False, force=False): | |||
|
202 | def _cached_tree(): | |||
|
203 | log.debug('Generating cached file tree for %s, %s, %s', | |||
|
204 | self.db_repo_name, commit_id, f_path) | |||
|
205 | ||||
|
206 | c.full_load = full_load | |||
|
207 | return render( | |||
|
208 | 'rhodecode:templates/files/files_browser_tree.mako', | |||
|
209 | self._get_template_context(c), self.request) | |||
|
210 | ||||
|
211 | cache_manager = self._get_tree_cache_manager(caches.FILE_TREE) | |||
|
212 | ||||
|
213 | cache_key = caches.compute_key_from_params( | |||
|
214 | self.db_repo_name, commit_id, f_path) | |||
|
215 | ||||
|
216 | if force: | |||
|
217 | # we want to force recompute of caches | |||
|
218 | cache_manager.remove_value(cache_key) | |||
|
219 | ||||
|
220 | return cache_manager.get(cache_key, createfunc=_cached_tree) | |||
|
221 | ||||
|
222 | def _get_archive_spec(self, fname): | |||
|
223 | log.debug('Detecting archive spec for: `%s`', fname) | |||
|
224 | ||||
|
225 | fileformat = None | |||
|
226 | ext = None | |||
|
227 | content_type = None | |||
|
228 | for a_type, ext_data in settings.ARCHIVE_SPECS.items(): | |||
|
229 | content_type, extension = ext_data | |||
|
230 | ||||
|
231 | if fname.endswith(extension): | |||
|
232 | fileformat = a_type | |||
|
233 | log.debug('archive is of type: %s', fileformat) | |||
|
234 | ext = extension | |||
|
235 | break | |||
|
236 | ||||
|
237 | if not fileformat: | |||
|
238 | raise ValueError() | |||
|
239 | ||||
|
240 | # left over part of whole fname is the commit | |||
|
241 | commit_id = fname[:-len(ext)] | |||
|
242 | ||||
|
243 | return commit_id, ext, fileformat, content_type | |||
|
244 | ||||
|
245 | @LoginRequired() | |||
|
246 | @HasRepoPermissionAnyDecorator( | |||
|
247 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
248 | @view_config( | |||
|
249 | route_name='repo_archivefile', request_method='GET', | |||
|
250 | renderer=None) | |||
|
251 | def repo_archivefile(self): | |||
|
252 | # archive cache config | |||
|
253 | from rhodecode import CONFIG | |||
|
254 | _ = self.request.translate | |||
|
255 | self.load_default_context() | |||
|
256 | ||||
|
257 | fname = self.request.matchdict['fname'] | |||
|
258 | subrepos = self.request.GET.get('subrepos') == 'true' | |||
|
259 | ||||
|
260 | if not self.db_repo.enable_downloads: | |||
|
261 | return Response(_('Downloads disabled')) | |||
|
262 | ||||
|
263 | try: | |||
|
264 | commit_id, ext, fileformat, content_type = \ | |||
|
265 | self._get_archive_spec(fname) | |||
|
266 | except ValueError: | |||
|
267 | return Response(_('Unknown archive type for: `{}`').format(fname)) | |||
|
268 | ||||
|
269 | try: | |||
|
270 | commit = self.rhodecode_vcs_repo.get_commit(commit_id) | |||
|
271 | except CommitDoesNotExistError: | |||
|
272 | return Response(_('Unknown commit_id %s') % commit_id) | |||
|
273 | except EmptyRepositoryError: | |||
|
274 | return Response(_('Empty repository')) | |||
|
275 | ||||
|
276 | archive_name = '%s-%s%s%s' % ( | |||
|
277 | safe_str(self.db_repo_name.replace('/', '_')), | |||
|
278 | '-sub' if subrepos else '', | |||
|
279 | safe_str(commit.short_id), ext) | |||
|
280 | ||||
|
281 | use_cached_archive = False | |||
|
282 | archive_cache_enabled = CONFIG.get( | |||
|
283 | 'archive_cache_dir') and not self.request.GET.get('no_cache') | |||
|
284 | ||||
|
285 | if archive_cache_enabled: | |||
|
286 | # check if we it's ok to write | |||
|
287 | if not os.path.isdir(CONFIG['archive_cache_dir']): | |||
|
288 | os.makedirs(CONFIG['archive_cache_dir']) | |||
|
289 | cached_archive_path = os.path.join( | |||
|
290 | CONFIG['archive_cache_dir'], archive_name) | |||
|
291 | if os.path.isfile(cached_archive_path): | |||
|
292 | log.debug('Found cached archive in %s', cached_archive_path) | |||
|
293 | fd, archive = None, cached_archive_path | |||
|
294 | use_cached_archive = True | |||
|
295 | else: | |||
|
296 | log.debug('Archive %s is not yet cached', archive_name) | |||
|
297 | ||||
|
298 | if not use_cached_archive: | |||
|
299 | # generate new archive | |||
|
300 | fd, archive = tempfile.mkstemp() | |||
|
301 | log.debug('Creating new temp archive in %s', archive) | |||
|
302 | try: | |||
|
303 | commit.archive_repo(archive, kind=fileformat, subrepos=subrepos) | |||
|
304 | except ImproperArchiveTypeError: | |||
|
305 | return _('Unknown archive type') | |||
|
306 | if archive_cache_enabled: | |||
|
307 | # if we generated the archive and we have cache enabled | |||
|
308 | # let's use this for future | |||
|
309 | log.debug('Storing new archive in %s', cached_archive_path) | |||
|
310 | shutil.move(archive, cached_archive_path) | |||
|
311 | archive = cached_archive_path | |||
|
312 | ||||
|
313 | # store download action | |||
|
314 | audit_logger.store_web( | |||
|
315 | 'repo.archive.download', action_data={ | |||
|
316 | 'user_agent': self.request.user_agent, | |||
|
317 | 'archive_name': archive_name, | |||
|
318 | 'archive_spec': fname, | |||
|
319 | 'archive_cached': use_cached_archive}, | |||
|
320 | user=self._rhodecode_user, | |||
|
321 | repo=self.db_repo, | |||
|
322 | commit=True | |||
|
323 | ) | |||
|
324 | ||||
|
325 | def get_chunked_archive(archive): | |||
|
326 | with open(archive, 'rb') as stream: | |||
|
327 | while True: | |||
|
328 | data = stream.read(16 * 1024) | |||
|
329 | if not data: | |||
|
330 | if fd: # fd means we used temporary file | |||
|
331 | os.close(fd) | |||
|
332 | if not archive_cache_enabled: | |||
|
333 | log.debug('Destroying temp archive %s', archive) | |||
|
334 | os.remove(archive) | |||
|
335 | break | |||
|
336 | yield data | |||
|
337 | ||||
|
338 | response = Response(app_iter=get_chunked_archive(archive)) | |||
|
339 | response.content_disposition = str( | |||
|
340 | 'attachment; filename=%s' % archive_name) | |||
|
341 | response.content_type = str(content_type) | |||
|
342 | ||||
|
343 | return response | |||
|
344 | ||||
|
345 | def _get_file_node(self, commit_id, f_path): | |||
|
346 | if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]: | |||
|
347 | commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id) | |||
|
348 | try: | |||
|
349 | node = commit.get_node(f_path) | |||
|
350 | if node.is_dir(): | |||
|
351 | raise NodeError('%s path is a %s not a file' | |||
|
352 | % (node, type(node))) | |||
|
353 | except NodeDoesNotExistError: | |||
|
354 | commit = EmptyCommit( | |||
|
355 | commit_id=commit_id, | |||
|
356 | idx=commit.idx, | |||
|
357 | repo=commit.repository, | |||
|
358 | alias=commit.repository.alias, | |||
|
359 | message=commit.message, | |||
|
360 | author=commit.author, | |||
|
361 | date=commit.date) | |||
|
362 | node = FileNode(f_path, '', commit=commit) | |||
|
363 | else: | |||
|
364 | commit = EmptyCommit( | |||
|
365 | repo=self.rhodecode_vcs_repo, | |||
|
366 | alias=self.rhodecode_vcs_repo.alias) | |||
|
367 | node = FileNode(f_path, '', commit=commit) | |||
|
368 | return node | |||
|
369 | ||||
|
370 | @LoginRequired() | |||
|
371 | @HasRepoPermissionAnyDecorator( | |||
|
372 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
373 | @view_config( | |||
|
374 | route_name='repo_files_diff', request_method='GET', | |||
|
375 | renderer=None) | |||
|
376 | def repo_files_diff(self): | |||
|
377 | c = self.load_default_context() | |||
|
378 | diff1 = self.request.GET.get('diff1', '') | |||
|
379 | diff2 = self.request.GET.get('diff2', '') | |||
|
380 | f_path = self.request.matchdict['f_path'] | |||
|
381 | ||||
|
382 | path1, diff1 = parse_path_ref(diff1, default_path=f_path) | |||
|
383 | ||||
|
384 | ignore_whitespace = str2bool(self.request.GET.get('ignorews')) | |||
|
385 | line_context = self.request.GET.get('context', 3) | |||
|
386 | ||||
|
387 | if not any((diff1, diff2)): | |||
|
388 | h.flash( | |||
|
389 | 'Need query parameter "diff1" or "diff2" to generate a diff.', | |||
|
390 | category='error') | |||
|
391 | raise HTTPBadRequest() | |||
|
392 | ||||
|
393 | c.action = self.request.GET.get('diff') | |||
|
394 | if c.action not in ['download', 'raw']: | |||
|
395 | compare_url = h.url( | |||
|
396 | 'compare_url', repo_name=self.db_repo_name, | |||
|
397 | source_ref_type='rev', | |||
|
398 | source_ref=diff1, | |||
|
399 | target_repo=self.db_repo_name, | |||
|
400 | target_ref_type='rev', | |||
|
401 | target_ref=diff2, | |||
|
402 | f_path=f_path) | |||
|
403 | # redirect to new view if we render diff | |||
|
404 | raise HTTPFound(compare_url) | |||
|
405 | ||||
|
406 | try: | |||
|
407 | node1 = self._get_file_node(diff1, path1) | |||
|
408 | node2 = self._get_file_node(diff2, f_path) | |||
|
409 | except (RepositoryError, NodeError): | |||
|
410 | log.exception("Exception while trying to get node from repository") | |||
|
411 | raise HTTPFound( | |||
|
412 | h.route_path('repo_files', repo_name=self.db_repo_name, | |||
|
413 | commit_id='tip', f_path=f_path)) | |||
|
414 | ||||
|
415 | if all(isinstance(node.commit, EmptyCommit) | |||
|
416 | for node in (node1, node2)): | |||
|
417 | raise HTTPNotFound() | |||
|
418 | ||||
|
419 | c.commit_1 = node1.commit | |||
|
420 | c.commit_2 = node2.commit | |||
|
421 | ||||
|
422 | if c.action == 'download': | |||
|
423 | _diff = diffs.get_gitdiff(node1, node2, | |||
|
424 | ignore_whitespace=ignore_whitespace, | |||
|
425 | context=line_context) | |||
|
426 | diff = diffs.DiffProcessor(_diff, format='gitdiff') | |||
|
427 | ||||
|
428 | response = Response(diff.as_raw()) | |||
|
429 | response.content_type = 'text/plain' | |||
|
430 | response.content_disposition = ( | |||
|
431 | 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2) | |||
|
432 | ) | |||
|
433 | charset = self._get_default_encoding(c) | |||
|
434 | if charset: | |||
|
435 | response.charset = charset | |||
|
436 | return response | |||
|
437 | ||||
|
438 | elif c.action == 'raw': | |||
|
439 | _diff = diffs.get_gitdiff(node1, node2, | |||
|
440 | ignore_whitespace=ignore_whitespace, | |||
|
441 | context=line_context) | |||
|
442 | diff = diffs.DiffProcessor(_diff, format='gitdiff') | |||
|
443 | ||||
|
444 | response = Response(diff.as_raw()) | |||
|
445 | response.content_type = 'text/plain' | |||
|
446 | charset = self._get_default_encoding(c) | |||
|
447 | if charset: | |||
|
448 | response.charset = charset | |||
|
449 | return response | |||
|
450 | ||||
|
451 | # in case we ever end up here | |||
|
452 | raise HTTPNotFound() | |||
|
453 | ||||
|
454 | @LoginRequired() | |||
|
455 | @HasRepoPermissionAnyDecorator( | |||
|
456 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
457 | @view_config( | |||
|
458 | route_name='repo_files_diff_2way_redirect', request_method='GET', | |||
|
459 | renderer=None) | |||
|
460 | def repo_files_diff_2way_redirect(self): | |||
|
461 | """ | |||
|
462 | Kept only to make OLD links work | |||
|
463 | """ | |||
|
464 | diff1 = self.request.GET.get('diff1', '') | |||
|
465 | diff2 = self.request.GET.get('diff2', '') | |||
|
466 | f_path = self.request.matchdict['f_path'] | |||
|
467 | ||||
|
468 | if not any((diff1, diff2)): | |||
|
469 | h.flash( | |||
|
470 | 'Need query parameter "diff1" or "diff2" to generate a diff.', | |||
|
471 | category='error') | |||
|
472 | raise HTTPBadRequest() | |||
|
473 | ||||
|
474 | compare_url = h.url( | |||
|
475 | 'compare_url', repo_name=self.db_repo_name, | |||
|
476 | source_ref_type='rev', | |||
|
477 | source_ref=diff1, | |||
|
478 | target_repo=self.db_repo_name, | |||
|
479 | target_ref_type='rev', | |||
|
480 | target_ref=diff2, | |||
|
481 | f_path=f_path, | |||
|
482 | diffmode='sideside') | |||
|
483 | raise HTTPFound(compare_url) | |||
|
484 | ||||
|
485 | @LoginRequired() | |||
|
486 | @HasRepoPermissionAnyDecorator( | |||
|
487 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
488 | @view_config( | |||
|
489 | route_name='repo_files', request_method='GET', | |||
|
490 | renderer=None) | |||
|
491 | @view_config( | |||
|
492 | route_name='repo_files:default_path', request_method='GET', | |||
|
493 | renderer=None) | |||
|
494 | @view_config( | |||
|
495 | route_name='repo_files:default_commit', request_method='GET', | |||
|
496 | renderer=None) | |||
|
497 | @view_config( | |||
|
498 | route_name='repo_files:rendered', request_method='GET', | |||
|
499 | renderer=None) | |||
|
500 | @view_config( | |||
|
501 | route_name='repo_files:annotated', request_method='GET', | |||
|
502 | renderer=None) | |||
|
503 | def repo_files(self): | |||
|
504 | c = self.load_default_context() | |||
|
505 | ||||
|
506 | view_name = getattr(self.request.matched_route, 'name', None) | |||
|
507 | ||||
|
508 | c.annotate = view_name == 'repo_files:annotated' | |||
|
509 | # default is false, but .rst/.md files later are auto rendered, we can | |||
|
510 | # overwrite auto rendering by setting this GET flag | |||
|
511 | c.renderer = view_name == 'repo_files:rendered' or \ | |||
|
512 | not self.request.GET.get('no-render', False) | |||
|
513 | ||||
|
514 | # redirect to given commit_id from form if given | |||
|
515 | get_commit_id = self.request.GET.get('at_rev', None) | |||
|
516 | if get_commit_id: | |||
|
517 | self._get_commit_or_redirect(get_commit_id) | |||
|
518 | ||||
|
519 | commit_id, f_path = self._get_commit_and_path() | |||
|
520 | c.commit = self._get_commit_or_redirect(commit_id) | |||
|
521 | c.branch = self.request.GET.get('branch', None) | |||
|
522 | c.f_path = f_path | |||
|
523 | ||||
|
524 | # prev link | |||
|
525 | try: | |||
|
526 | prev_commit = c.commit.prev(c.branch) | |||
|
527 | c.prev_commit = prev_commit | |||
|
528 | c.url_prev = h.route_path( | |||
|
529 | 'repo_files', repo_name=self.db_repo_name, | |||
|
530 | commit_id=prev_commit.raw_id, f_path=f_path) | |||
|
531 | if c.branch: | |||
|
532 | c.url_prev += '?branch=%s' % c.branch | |||
|
533 | except (CommitDoesNotExistError, VCSError): | |||
|
534 | c.url_prev = '#' | |||
|
535 | c.prev_commit = EmptyCommit() | |||
|
536 | ||||
|
537 | # next link | |||
|
538 | try: | |||
|
539 | next_commit = c.commit.next(c.branch) | |||
|
540 | c.next_commit = next_commit | |||
|
541 | c.url_next = h.route_path( | |||
|
542 | 'repo_files', repo_name=self.db_repo_name, | |||
|
543 | commit_id=next_commit.raw_id, f_path=f_path) | |||
|
544 | if c.branch: | |||
|
545 | c.url_next += '?branch=%s' % c.branch | |||
|
546 | except (CommitDoesNotExistError, VCSError): | |||
|
547 | c.url_next = '#' | |||
|
548 | c.next_commit = EmptyCommit() | |||
|
549 | ||||
|
550 | # files or dirs | |||
|
551 | try: | |||
|
552 | c.file = c.commit.get_node(f_path) | |||
|
553 | c.file_author = True | |||
|
554 | c.file_tree = '' | |||
|
555 | ||||
|
556 | # load file content | |||
|
557 | if c.file.is_file(): | |||
|
558 | c.lf_node = c.file.get_largefile_node() | |||
|
559 | ||||
|
560 | c.file_source_page = 'true' | |||
|
561 | c.file_last_commit = c.file.last_commit | |||
|
562 | if c.file.size < c.visual.cut_off_limit_diff: | |||
|
563 | if c.annotate: # annotation has precedence over renderer | |||
|
564 | c.annotated_lines = filenode_as_annotated_lines_tokens( | |||
|
565 | c.file | |||
|
566 | ) | |||
|
567 | else: | |||
|
568 | c.renderer = ( | |||
|
569 | c.renderer and h.renderer_from_filename(c.file.path) | |||
|
570 | ) | |||
|
571 | if not c.renderer: | |||
|
572 | c.lines = filenode_as_lines_tokens(c.file) | |||
|
573 | ||||
|
574 | c.on_branch_head = self._is_valid_head( | |||
|
575 | commit_id, self.rhodecode_vcs_repo) | |||
|
576 | ||||
|
577 | branch = c.commit.branch if ( | |||
|
578 | c.commit.branch and '/' not in c.commit.branch) else None | |||
|
579 | c.branch_or_raw_id = branch or c.commit.raw_id | |||
|
580 | c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id) | |||
|
581 | ||||
|
582 | author = c.file_last_commit.author | |||
|
583 | c.authors = [[ | |||
|
584 | h.email(author), | |||
|
585 | h.person(author, 'username_or_name_or_email'), | |||
|
586 | 1 | |||
|
587 | ]] | |||
|
588 | ||||
|
589 | else: # load tree content at path | |||
|
590 | c.file_source_page = 'false' | |||
|
591 | c.authors = [] | |||
|
592 | # this loads a simple tree without metadata to speed things up | |||
|
593 | # later via ajax we call repo_nodetree_full and fetch whole | |||
|
594 | c.file_tree = self._get_tree_at_commit( | |||
|
595 | c, c.commit.raw_id, f_path) | |||
|
596 | ||||
|
597 | except RepositoryError as e: | |||
|
598 | h.flash(safe_str(h.escape(e)), category='error') | |||
|
599 | raise HTTPNotFound() | |||
|
600 | ||||
|
601 | if self.request.environ.get('HTTP_X_PJAX'): | |||
|
602 | html = render('rhodecode:templates/files/files_pjax.mako', | |||
|
603 | self._get_template_context(c), self.request) | |||
|
604 | else: | |||
|
605 | html = render('rhodecode:templates/files/files.mako', | |||
|
606 | self._get_template_context(c), self.request) | |||
|
607 | return Response(html) | |||
|
608 | ||||
|
609 | @HasRepoPermissionAnyDecorator( | |||
|
610 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
611 | @view_config( | |||
|
612 | route_name='repo_files:annotated_previous', request_method='GET', | |||
|
613 | renderer=None) | |||
|
614 | def repo_files_annotated_previous(self): | |||
|
615 | self.load_default_context() | |||
|
616 | ||||
|
617 | commit_id, f_path = self._get_commit_and_path() | |||
|
618 | commit = self._get_commit_or_redirect(commit_id) | |||
|
619 | prev_commit_id = commit.raw_id | |||
|
620 | line_anchor = self.request.GET.get('line_anchor') | |||
|
621 | is_file = False | |||
|
622 | try: | |||
|
623 | _file = commit.get_node(f_path) | |||
|
624 | is_file = _file.is_file() | |||
|
625 | except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError): | |||
|
626 | pass | |||
|
627 | ||||
|
628 | if is_file: | |||
|
629 | history = commit.get_file_history(f_path) | |||
|
630 | prev_commit_id = history[1].raw_id \ | |||
|
631 | if len(history) > 1 else prev_commit_id | |||
|
632 | prev_url = h.route_path( | |||
|
633 | 'repo_files:annotated', repo_name=self.db_repo_name, | |||
|
634 | commit_id=prev_commit_id, f_path=f_path, | |||
|
635 | _anchor='L{}'.format(line_anchor)) | |||
|
636 | ||||
|
637 | raise HTTPFound(prev_url) | |||
|
638 | ||||
|
639 | @LoginRequired() | |||
|
640 | @HasRepoPermissionAnyDecorator( | |||
|
641 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
642 | @view_config( | |||
|
643 | route_name='repo_nodetree_full', request_method='GET', | |||
|
644 | renderer=None, xhr=True) | |||
|
645 | @view_config( | |||
|
646 | route_name='repo_nodetree_full:default_path', request_method='GET', | |||
|
647 | renderer=None, xhr=True) | |||
|
648 | def repo_nodetree_full(self): | |||
|
649 | """ | |||
|
650 | Returns rendered html of file tree that contains commit date, | |||
|
651 | author, commit_id for the specified combination of | |||
|
652 | repo, commit_id and file path | |||
|
653 | """ | |||
|
654 | c = self.load_default_context() | |||
|
655 | ||||
|
656 | commit_id, f_path = self._get_commit_and_path() | |||
|
657 | commit = self._get_commit_or_redirect(commit_id) | |||
|
658 | try: | |||
|
659 | dir_node = commit.get_node(f_path) | |||
|
660 | except RepositoryError as e: | |||
|
661 | return Response('error: {}'.format(safe_str(e))) | |||
|
662 | ||||
|
663 | if dir_node.is_file(): | |||
|
664 | return Response('') | |||
|
665 | ||||
|
666 | c.file = dir_node | |||
|
667 | c.commit = commit | |||
|
668 | ||||
|
669 | # using force=True here, make a little trick. We flush the cache and | |||
|
670 | # compute it using the same key as without previous full_load, so now | |||
|
671 | # the fully loaded tree is now returned instead of partial, | |||
|
672 | # and we store this in caches | |||
|
673 | html = self._get_tree_at_commit( | |||
|
674 | c, commit.raw_id, dir_node.path, full_load=True, force=True) | |||
|
675 | ||||
|
676 | return Response(html) | |||
|
677 | ||||
|
678 | def _get_attachement_disposition(self, f_path): | |||
|
679 | return 'attachment; filename=%s' % \ | |||
|
680 | safe_str(f_path.split(Repository.NAME_SEP)[-1]) | |||
|
681 | ||||
|
682 | @LoginRequired() | |||
|
683 | @HasRepoPermissionAnyDecorator( | |||
|
684 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
685 | @view_config( | |||
|
686 | route_name='repo_file_raw', request_method='GET', | |||
|
687 | renderer=None) | |||
|
688 | def repo_file_raw(self): | |||
|
689 | """ | |||
|
690 | Action for show as raw, some mimetypes are "rendered", | |||
|
691 | those include images, icons. | |||
|
692 | """ | |||
|
693 | c = self.load_default_context() | |||
|
694 | ||||
|
695 | commit_id, f_path = self._get_commit_and_path() | |||
|
696 | commit = self._get_commit_or_redirect(commit_id) | |||
|
697 | file_node = self._get_filenode_or_redirect(commit, f_path) | |||
|
698 | ||||
|
699 | raw_mimetype_mapping = { | |||
|
700 | # map original mimetype to a mimetype used for "show as raw" | |||
|
701 | # you can also provide a content-disposition to override the | |||
|
702 | # default "attachment" disposition. | |||
|
703 | # orig_type: (new_type, new_dispo) | |||
|
704 | ||||
|
705 | # show images inline: | |||
|
706 | # Do not re-add SVG: it is unsafe and permits XSS attacks. One can | |||
|
707 | # for example render an SVG with javascript inside or even render | |||
|
708 | # HTML. | |||
|
709 | 'image/x-icon': ('image/x-icon', 'inline'), | |||
|
710 | 'image/png': ('image/png', 'inline'), | |||
|
711 | 'image/gif': ('image/gif', 'inline'), | |||
|
712 | 'image/jpeg': ('image/jpeg', 'inline'), | |||
|
713 | 'application/pdf': ('application/pdf', 'inline'), | |||
|
714 | } | |||
|
715 | ||||
|
716 | mimetype = file_node.mimetype | |||
|
717 | try: | |||
|
718 | mimetype, disposition = raw_mimetype_mapping[mimetype] | |||
|
719 | except KeyError: | |||
|
720 | # we don't know anything special about this, handle it safely | |||
|
721 | if file_node.is_binary: | |||
|
722 | # do same as download raw for binary files | |||
|
723 | mimetype, disposition = 'application/octet-stream', 'attachment' | |||
|
724 | else: | |||
|
725 | # do not just use the original mimetype, but force text/plain, | |||
|
726 | # otherwise it would serve text/html and that might be unsafe. | |||
|
727 | # Note: underlying vcs library fakes text/plain mimetype if the | |||
|
728 | # mimetype can not be determined and it thinks it is not | |||
|
729 | # binary.This might lead to erroneous text display in some | |||
|
730 | # cases, but helps in other cases, like with text files | |||
|
731 | # without extension. | |||
|
732 | mimetype, disposition = 'text/plain', 'inline' | |||
|
733 | ||||
|
734 | if disposition == 'attachment': | |||
|
735 | disposition = self._get_attachement_disposition(f_path) | |||
|
736 | ||||
|
737 | def stream_node(): | |||
|
738 | yield file_node.raw_bytes | |||
|
739 | ||||
|
740 | response = Response(app_iter=stream_node()) | |||
|
741 | response.content_disposition = disposition | |||
|
742 | response.content_type = mimetype | |||
|
743 | ||||
|
744 | charset = self._get_default_encoding(c) | |||
|
745 | if charset: | |||
|
746 | response.charset = charset | |||
|
747 | ||||
|
748 | return response | |||
|
749 | ||||
|
750 | @LoginRequired() | |||
|
751 | @HasRepoPermissionAnyDecorator( | |||
|
752 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
753 | @view_config( | |||
|
754 | route_name='repo_file_download', request_method='GET', | |||
|
755 | renderer=None) | |||
|
756 | @view_config( | |||
|
757 | route_name='repo_file_download:legacy', request_method='GET', | |||
|
758 | renderer=None) | |||
|
759 | def repo_file_download(self): | |||
|
760 | c = self.load_default_context() | |||
|
761 | ||||
|
762 | commit_id, f_path = self._get_commit_and_path() | |||
|
763 | commit = self._get_commit_or_redirect(commit_id) | |||
|
764 | file_node = self._get_filenode_or_redirect(commit, f_path) | |||
|
765 | ||||
|
766 | if self.request.GET.get('lf'): | |||
|
767 | # only if lf get flag is passed, we download this file | |||
|
768 | # as LFS/Largefile | |||
|
769 | lf_node = file_node.get_largefile_node() | |||
|
770 | if lf_node: | |||
|
771 | # overwrite our pointer with the REAL large-file | |||
|
772 | file_node = lf_node | |||
|
773 | ||||
|
774 | disposition = self._get_attachement_disposition(f_path) | |||
|
775 | ||||
|
776 | def stream_node(): | |||
|
777 | yield file_node.raw_bytes | |||
|
778 | ||||
|
779 | response = Response(app_iter=stream_node()) | |||
|
780 | response.content_disposition = disposition | |||
|
781 | response.content_type = file_node.mimetype | |||
|
782 | ||||
|
783 | charset = self._get_default_encoding(c) | |||
|
784 | if charset: | |||
|
785 | response.charset = charset | |||
|
786 | ||||
|
787 | return response | |||
|
788 | ||||
|
789 | def _get_nodelist_at_commit(self, repo_name, commit_id, f_path): | |||
|
790 | def _cached_nodes(): | |||
|
791 | log.debug('Generating cached nodelist for %s, %s, %s', | |||
|
792 | repo_name, commit_id, f_path) | |||
|
793 | _d, _f = ScmModel().get_nodes( | |||
|
794 | repo_name, commit_id, f_path, flat=False) | |||
|
795 | return _d + _f | |||
|
796 | ||||
|
797 | cache_manager = self._get_tree_cache_manager(caches.FILE_SEARCH_TREE_META) | |||
|
798 | ||||
|
799 | cache_key = caches.compute_key_from_params( | |||
|
800 | repo_name, commit_id, f_path) | |||
|
801 | return cache_manager.get(cache_key, createfunc=_cached_nodes) | |||
|
802 | ||||
|
803 | @LoginRequired() | |||
|
804 | @HasRepoPermissionAnyDecorator( | |||
|
805 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
806 | @view_config( | |||
|
807 | route_name='repo_files_nodelist', request_method='GET', | |||
|
808 | renderer='json_ext', xhr=True) | |||
|
809 | def repo_nodelist(self): | |||
|
810 | self.load_default_context() | |||
|
811 | ||||
|
812 | commit_id, f_path = self._get_commit_and_path() | |||
|
813 | commit = self._get_commit_or_redirect(commit_id) | |||
|
814 | ||||
|
815 | metadata = self._get_nodelist_at_commit( | |||
|
816 | self.db_repo_name, commit.raw_id, f_path) | |||
|
817 | return {'nodes': metadata} | |||
|
818 | ||||
|
819 | def _create_references( | |||
|
820 | self, branches_or_tags, symbolic_reference, f_path): | |||
|
821 | items = [] | |||
|
822 | for name, commit_id in branches_or_tags.items(): | |||
|
823 | sym_ref = symbolic_reference(commit_id, name, f_path) | |||
|
824 | items.append((sym_ref, name)) | |||
|
825 | return items | |||
|
826 | ||||
|
827 | def _symbolic_reference(self, commit_id, name, f_path): | |||
|
828 | return commit_id | |||
|
829 | ||||
|
830 | def _symbolic_reference_svn(self, commit_id, name, f_path): | |||
|
831 | new_f_path = vcspath.join(name, f_path) | |||
|
832 | return u'%s@%s' % (new_f_path, commit_id) | |||
|
833 | ||||
|
834 | def _get_node_history(self, commit_obj, f_path, commits=None): | |||
|
835 | """ | |||
|
836 | get commit history for given node | |||
|
837 | ||||
|
838 | :param commit_obj: commit to calculate history | |||
|
839 | :param f_path: path for node to calculate history for | |||
|
840 | :param commits: if passed don't calculate history and take | |||
|
841 | commits defined in this list | |||
|
842 | """ | |||
|
843 | _ = self.request.translate | |||
|
844 | ||||
|
845 | # calculate history based on tip | |||
|
846 | tip = self.rhodecode_vcs_repo.get_commit() | |||
|
847 | if commits is None: | |||
|
848 | pre_load = ["author", "branch"] | |||
|
849 | try: | |||
|
850 | commits = tip.get_file_history(f_path, pre_load=pre_load) | |||
|
851 | except (NodeDoesNotExistError, CommitError): | |||
|
852 | # this node is not present at tip! | |||
|
853 | commits = commit_obj.get_file_history(f_path, pre_load=pre_load) | |||
|
854 | ||||
|
855 | history = [] | |||
|
856 | commits_group = ([], _("Changesets")) | |||
|
857 | for commit in commits: | |||
|
858 | branch = ' (%s)' % commit.branch if commit.branch else '' | |||
|
859 | n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch) | |||
|
860 | commits_group[0].append((commit.raw_id, n_desc,)) | |||
|
861 | history.append(commits_group) | |||
|
862 | ||||
|
863 | symbolic_reference = self._symbolic_reference | |||
|
864 | ||||
|
865 | if self.rhodecode_vcs_repo.alias == 'svn': | |||
|
866 | adjusted_f_path = RepoFilesView.adjust_file_path_for_svn( | |||
|
867 | f_path, self.rhodecode_vcs_repo) | |||
|
868 | if adjusted_f_path != f_path: | |||
|
869 | log.debug( | |||
|
870 | 'Recognized svn tag or branch in file "%s", using svn ' | |||
|
871 | 'specific symbolic references', f_path) | |||
|
872 | f_path = adjusted_f_path | |||
|
873 | symbolic_reference = self._symbolic_reference_svn | |||
|
874 | ||||
|
875 | branches = self._create_references( | |||
|
876 | self.rhodecode_vcs_repo.branches, symbolic_reference, f_path) | |||
|
877 | branches_group = (branches, _("Branches")) | |||
|
878 | ||||
|
879 | tags = self._create_references( | |||
|
880 | self.rhodecode_vcs_repo.tags, symbolic_reference, f_path) | |||
|
881 | tags_group = (tags, _("Tags")) | |||
|
882 | ||||
|
883 | history.append(branches_group) | |||
|
884 | history.append(tags_group) | |||
|
885 | ||||
|
886 | return history, commits | |||
|
887 | ||||
|
888 | @LoginRequired() | |||
|
889 | @HasRepoPermissionAnyDecorator( | |||
|
890 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
891 | @view_config( | |||
|
892 | route_name='repo_file_history', request_method='GET', | |||
|
893 | renderer='json_ext') | |||
|
894 | def repo_file_history(self): | |||
|
895 | self.load_default_context() | |||
|
896 | ||||
|
897 | commit_id, f_path = self._get_commit_and_path() | |||
|
898 | commit = self._get_commit_or_redirect(commit_id) | |||
|
899 | file_node = self._get_filenode_or_redirect(commit, f_path) | |||
|
900 | ||||
|
901 | if file_node.is_file(): | |||
|
902 | file_history, _hist = self._get_node_history(commit, f_path) | |||
|
903 | ||||
|
904 | res = [] | |||
|
905 | for obj in file_history: | |||
|
906 | res.append({ | |||
|
907 | 'text': obj[1], | |||
|
908 | 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]] | |||
|
909 | }) | |||
|
910 | ||||
|
911 | data = { | |||
|
912 | 'more': False, | |||
|
913 | 'results': res | |||
|
914 | } | |||
|
915 | return data | |||
|
916 | ||||
|
917 | log.warning('Cannot fetch history for directory') | |||
|
918 | raise HTTPBadRequest() | |||
|
919 | ||||
|
920 | @LoginRequired() | |||
|
921 | @HasRepoPermissionAnyDecorator( | |||
|
922 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
923 | @view_config( | |||
|
924 | route_name='repo_file_authors', request_method='GET', | |||
|
925 | renderer='rhodecode:templates/files/file_authors_box.mako') | |||
|
926 | def repo_file_authors(self): | |||
|
927 | c = self.load_default_context() | |||
|
928 | ||||
|
929 | commit_id, f_path = self._get_commit_and_path() | |||
|
930 | commit = self._get_commit_or_redirect(commit_id) | |||
|
931 | file_node = self._get_filenode_or_redirect(commit, f_path) | |||
|
932 | ||||
|
933 | if not file_node.is_file(): | |||
|
934 | raise HTTPBadRequest() | |||
|
935 | ||||
|
936 | c.file_last_commit = file_node.last_commit | |||
|
937 | if self.request.GET.get('annotate') == '1': | |||
|
938 | # use _hist from annotation if annotation mode is on | |||
|
939 | commit_ids = set(x[1] for x in file_node.annotate) | |||
|
940 | _hist = ( | |||
|
941 | self.rhodecode_vcs_repo.get_commit(commit_id) | |||
|
942 | for commit_id in commit_ids) | |||
|
943 | else: | |||
|
944 | _f_history, _hist = self._get_node_history(commit, f_path) | |||
|
945 | c.file_author = False | |||
|
946 | ||||
|
947 | unique = collections.OrderedDict() | |||
|
948 | for commit in _hist: | |||
|
949 | author = commit.author | |||
|
950 | if author not in unique: | |||
|
951 | unique[commit.author] = [ | |||
|
952 | h.email(author), | |||
|
953 | h.person(author, 'username_or_name_or_email'), | |||
|
954 | 1 # counter | |||
|
955 | ] | |||
|
956 | ||||
|
957 | else: | |||
|
958 | # increase counter | |||
|
959 | unique[commit.author][2] += 1 | |||
|
960 | ||||
|
961 | c.authors = [val for val in unique.values()] | |||
|
962 | ||||
|
963 | return self._get_template_context(c) | |||
|
964 | ||||
|
965 | @LoginRequired() | |||
|
966 | @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin') | |||
|
967 | @view_config( | |||
|
968 | route_name='repo_files_remove_file', request_method='GET', | |||
|
969 | renderer='rhodecode:templates/files/files_delete.mako') | |||
|
970 | def repo_files_remove_file(self): | |||
|
971 | _ = self.request.translate | |||
|
972 | c = self.load_default_context() | |||
|
973 | commit_id, f_path = self._get_commit_and_path() | |||
|
974 | ||||
|
975 | self._ensure_not_locked() | |||
|
976 | ||||
|
977 | if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo): | |||
|
978 | h.flash(_('You can only delete files with commit ' | |||
|
979 | 'being a valid branch '), category='warning') | |||
|
980 | raise HTTPFound( | |||
|
981 | h.route_path('repo_files', | |||
|
982 | repo_name=self.db_repo_name, commit_id='tip', | |||
|
983 | f_path=f_path)) | |||
|
984 | ||||
|
985 | c.commit = self._get_commit_or_redirect(commit_id) | |||
|
986 | c.file = self._get_filenode_or_redirect(c.commit, f_path) | |||
|
987 | ||||
|
988 | c.default_message = _( | |||
|
989 | 'Deleted file {} via RhodeCode Enterprise').format(f_path) | |||
|
990 | c.f_path = f_path | |||
|
991 | ||||
|
992 | return self._get_template_context(c) | |||
|
993 | ||||
|
994 | @LoginRequired() | |||
|
995 | @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin') | |||
|
996 | @CSRFRequired() | |||
|
997 | @view_config( | |||
|
998 | route_name='repo_files_delete_file', request_method='POST', | |||
|
999 | renderer=None) | |||
|
1000 | def repo_files_delete_file(self): | |||
|
1001 | _ = self.request.translate | |||
|
1002 | ||||
|
1003 | c = self.load_default_context() | |||
|
1004 | commit_id, f_path = self._get_commit_and_path() | |||
|
1005 | ||||
|
1006 | self._ensure_not_locked() | |||
|
1007 | ||||
|
1008 | if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo): | |||
|
1009 | h.flash(_('You can only delete files with commit ' | |||
|
1010 | 'being a valid branch '), category='warning') | |||
|
1011 | raise HTTPFound( | |||
|
1012 | h.route_path('repo_files', | |||
|
1013 | repo_name=self.db_repo_name, commit_id='tip', | |||
|
1014 | f_path=f_path)) | |||
|
1015 | ||||
|
1016 | c.commit = self._get_commit_or_redirect(commit_id) | |||
|
1017 | c.file = self._get_filenode_or_redirect(c.commit, f_path) | |||
|
1018 | ||||
|
1019 | c.default_message = _( | |||
|
1020 | 'Deleted file {} via RhodeCode Enterprise').format(f_path) | |||
|
1021 | c.f_path = f_path | |||
|
1022 | node_path = f_path | |||
|
1023 | author = self._rhodecode_db_user.full_contact | |||
|
1024 | message = self.request.POST.get('message') or c.default_message | |||
|
1025 | try: | |||
|
1026 | nodes = { | |||
|
1027 | node_path: { | |||
|
1028 | 'content': '' | |||
|
1029 | } | |||
|
1030 | } | |||
|
1031 | ScmModel().delete_nodes( | |||
|
1032 | user=self._rhodecode_db_user.user_id, repo=self.db_repo, | |||
|
1033 | message=message, | |||
|
1034 | nodes=nodes, | |||
|
1035 | parent_commit=c.commit, | |||
|
1036 | author=author, | |||
|
1037 | ) | |||
|
1038 | ||||
|
1039 | h.flash( | |||
|
1040 | _('Successfully deleted file `{}`').format( | |||
|
1041 | h.escape(f_path)), category='success') | |||
|
1042 | except Exception: | |||
|
1043 | log.exception('Error during commit operation') | |||
|
1044 | h.flash(_('Error occurred during commit'), category='error') | |||
|
1045 | raise HTTPFound( | |||
|
1046 | h.route_path('changeset_home', repo_name=self.db_repo_name, | |||
|
1047 | revision='tip')) | |||
|
1048 | ||||
|
1049 | @LoginRequired() | |||
|
1050 | @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin') | |||
|
1051 | @view_config( | |||
|
1052 | route_name='repo_files_edit_file', request_method='GET', | |||
|
1053 | renderer='rhodecode:templates/files/files_edit.mako') | |||
|
1054 | def repo_files_edit_file(self): | |||
|
1055 | _ = self.request.translate | |||
|
1056 | c = self.load_default_context() | |||
|
1057 | commit_id, f_path = self._get_commit_and_path() | |||
|
1058 | ||||
|
1059 | self._ensure_not_locked() | |||
|
1060 | ||||
|
1061 | if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo): | |||
|
1062 | h.flash(_('You can only edit files with commit ' | |||
|
1063 | 'being a valid branch '), category='warning') | |||
|
1064 | raise HTTPFound( | |||
|
1065 | h.route_path('repo_files', | |||
|
1066 | repo_name=self.db_repo_name, commit_id='tip', | |||
|
1067 | f_path=f_path)) | |||
|
1068 | ||||
|
1069 | c.commit = self._get_commit_or_redirect(commit_id) | |||
|
1070 | c.file = self._get_filenode_or_redirect(c.commit, f_path) | |||
|
1071 | ||||
|
1072 | if c.file.is_binary: | |||
|
1073 | files_url = h.route_path( | |||
|
1074 | 'repo_files', | |||
|
1075 | repo_name=self.db_repo_name, | |||
|
1076 | commit_id=c.commit.raw_id, f_path=f_path) | |||
|
1077 | raise HTTPFound(files_url) | |||
|
1078 | ||||
|
1079 | c.default_message = _( | |||
|
1080 | 'Edited file {} via RhodeCode Enterprise').format(f_path) | |||
|
1081 | c.f_path = f_path | |||
|
1082 | ||||
|
1083 | return self._get_template_context(c) | |||
|
1084 | ||||
|
1085 | @LoginRequired() | |||
|
1086 | @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin') | |||
|
1087 | @CSRFRequired() | |||
|
1088 | @view_config( | |||
|
1089 | route_name='repo_files_update_file', request_method='POST', | |||
|
1090 | renderer=None) | |||
|
1091 | def repo_files_update_file(self): | |||
|
1092 | _ = self.request.translate | |||
|
1093 | c = self.load_default_context() | |||
|
1094 | commit_id, f_path = self._get_commit_and_path() | |||
|
1095 | ||||
|
1096 | self._ensure_not_locked() | |||
|
1097 | ||||
|
1098 | if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo): | |||
|
1099 | h.flash(_('You can only edit files with commit ' | |||
|
1100 | 'being a valid branch '), category='warning') | |||
|
1101 | raise HTTPFound( | |||
|
1102 | h.route_path('repo_files', | |||
|
1103 | repo_name=self.db_repo_name, commit_id='tip', | |||
|
1104 | f_path=f_path)) | |||
|
1105 | ||||
|
1106 | c.commit = self._get_commit_or_redirect(commit_id) | |||
|
1107 | c.file = self._get_filenode_or_redirect(c.commit, f_path) | |||
|
1108 | ||||
|
1109 | if c.file.is_binary: | |||
|
1110 | raise HTTPFound( | |||
|
1111 | h.route_path('repo_files', | |||
|
1112 | repo_name=self.db_repo_name, | |||
|
1113 | commit_id=c.commit.raw_id, | |||
|
1114 | f_path=f_path)) | |||
|
1115 | ||||
|
1116 | c.default_message = _( | |||
|
1117 | 'Edited file {} via RhodeCode Enterprise').format(f_path) | |||
|
1118 | c.f_path = f_path | |||
|
1119 | old_content = c.file.content | |||
|
1120 | sl = old_content.splitlines(1) | |||
|
1121 | first_line = sl[0] if sl else '' | |||
|
1122 | ||||
|
1123 | r_post = self.request.POST | |||
|
1124 | # modes: 0 - Unix, 1 - Mac, 2 - DOS | |||
|
1125 | mode = detect_mode(first_line, 0) | |||
|
1126 | content = convert_line_endings(r_post.get('content', ''), mode) | |||
|
1127 | ||||
|
1128 | message = r_post.get('message') or c.default_message | |||
|
1129 | org_f_path = c.file.unicode_path | |||
|
1130 | filename = r_post['filename'] | |||
|
1131 | org_filename = c.file.name | |||
|
1132 | ||||
|
1133 | if content == old_content and filename == org_filename: | |||
|
1134 | h.flash(_('No changes'), category='warning') | |||
|
1135 | raise HTTPFound( | |||
|
1136 | h.route_path('changeset_home', repo_name=self.db_repo_name, | |||
|
1137 | revision='tip')) | |||
|
1138 | try: | |||
|
1139 | mapping = { | |||
|
1140 | org_f_path: { | |||
|
1141 | 'org_filename': org_f_path, | |||
|
1142 | 'filename': os.path.join(c.file.dir_path, filename), | |||
|
1143 | 'content': content, | |||
|
1144 | 'lexer': '', | |||
|
1145 | 'op': 'mod', | |||
|
1146 | } | |||
|
1147 | } | |||
|
1148 | ||||
|
1149 | ScmModel().update_nodes( | |||
|
1150 | user=self._rhodecode_db_user.user_id, | |||
|
1151 | repo=self.db_repo, | |||
|
1152 | message=message, | |||
|
1153 | nodes=mapping, | |||
|
1154 | parent_commit=c.commit, | |||
|
1155 | ) | |||
|
1156 | ||||
|
1157 | h.flash( | |||
|
1158 | _('Successfully committed changes to file `{}`').format( | |||
|
1159 | h.escape(f_path)), category='success') | |||
|
1160 | except Exception: | |||
|
1161 | log.exception('Error occurred during commit') | |||
|
1162 | h.flash(_('Error occurred during commit'), category='error') | |||
|
1163 | raise HTTPFound( | |||
|
1164 | h.route_path('changeset_home', repo_name=self.db_repo_name, | |||
|
1165 | revision='tip')) | |||
|
1166 | ||||
|
1167 | @LoginRequired() | |||
|
1168 | @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin') | |||
|
1169 | @view_config( | |||
|
1170 | route_name='repo_files_add_file', request_method='GET', | |||
|
1171 | renderer='rhodecode:templates/files/files_add.mako') | |||
|
1172 | def repo_files_add_file(self): | |||
|
1173 | _ = self.request.translate | |||
|
1174 | c = self.load_default_context() | |||
|
1175 | commit_id, f_path = self._get_commit_and_path() | |||
|
1176 | ||||
|
1177 | self._ensure_not_locked() | |||
|
1178 | ||||
|
1179 | c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False) | |||
|
1180 | if c.commit is None: | |||
|
1181 | c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias) | |||
|
1182 | c.default_message = (_('Added file via RhodeCode Enterprise')) | |||
|
1183 | c.f_path = f_path | |||
|
1184 | ||||
|
1185 | return self._get_template_context(c) | |||
|
1186 | ||||
|
1187 | @LoginRequired() | |||
|
1188 | @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin') | |||
|
1189 | @CSRFRequired() | |||
|
1190 | @view_config( | |||
|
1191 | route_name='repo_files_create_file', request_method='POST', | |||
|
1192 | renderer=None) | |||
|
1193 | def repo_files_create_file(self): | |||
|
1194 | _ = self.request.translate | |||
|
1195 | c = self.load_default_context() | |||
|
1196 | commit_id, f_path = self._get_commit_and_path() | |||
|
1197 | ||||
|
1198 | self._ensure_not_locked() | |||
|
1199 | ||||
|
1200 | r_post = self.request.POST | |||
|
1201 | ||||
|
1202 | c.commit = self._get_commit_or_redirect( | |||
|
1203 | commit_id, redirect_after=False) | |||
|
1204 | if c.commit is None: | |||
|
1205 | c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias) | |||
|
1206 | c.default_message = (_('Added file via RhodeCode Enterprise')) | |||
|
1207 | c.f_path = f_path | |||
|
1208 | unix_mode = 0 | |||
|
1209 | content = convert_line_endings(r_post.get('content', ''), unix_mode) | |||
|
1210 | ||||
|
1211 | message = r_post.get('message') or c.default_message | |||
|
1212 | filename = r_post.get('filename') | |||
|
1213 | location = r_post.get('location', '') # dir location | |||
|
1214 | file_obj = r_post.get('upload_file', None) | |||
|
1215 | ||||
|
1216 | if file_obj is not None and hasattr(file_obj, 'filename'): | |||
|
1217 | filename = r_post.get('filename_upload') | |||
|
1218 | content = file_obj.file | |||
|
1219 | ||||
|
1220 | if hasattr(content, 'file'): | |||
|
1221 | # non posix systems store real file under file attr | |||
|
1222 | content = content.file | |||
|
1223 | ||||
|
1224 | default_redirect_url = h.route_path( | |||
|
1225 | 'changeset_home', repo_name=self.db_repo_name, revision='tip') | |||
|
1226 | ||||
|
1227 | # If there's no commit, redirect to repo summary | |||
|
1228 | if type(c.commit) is EmptyCommit: | |||
|
1229 | redirect_url = h.route_path( | |||
|
1230 | 'repo_summary', repo_name=self.db_repo_name) | |||
|
1231 | else: | |||
|
1232 | redirect_url = default_redirect_url | |||
|
1233 | ||||
|
1234 | if not filename: | |||
|
1235 | h.flash(_('No filename'), category='warning') | |||
|
1236 | raise HTTPFound(redirect_url) | |||
|
1237 | ||||
|
1238 | # extract the location from filename, | |||
|
1239 | # allows using foo/bar.txt syntax to create subdirectories | |||
|
1240 | subdir_loc = filename.rsplit('/', 1) | |||
|
1241 | if len(subdir_loc) == 2: | |||
|
1242 | location = os.path.join(location, subdir_loc[0]) | |||
|
1243 | ||||
|
1244 | # strip all crap out of file, just leave the basename | |||
|
1245 | filename = os.path.basename(filename) | |||
|
1246 | node_path = os.path.join(location, filename) | |||
|
1247 | author = self._rhodecode_db_user.full_contact | |||
|
1248 | ||||
|
1249 | try: | |||
|
1250 | nodes = { | |||
|
1251 | node_path: { | |||
|
1252 | 'content': content | |||
|
1253 | } | |||
|
1254 | } | |||
|
1255 | ScmModel().create_nodes( | |||
|
1256 | user=self._rhodecode_db_user.user_id, | |||
|
1257 | repo=self.db_repo, | |||
|
1258 | message=message, | |||
|
1259 | nodes=nodes, | |||
|
1260 | parent_commit=c.commit, | |||
|
1261 | author=author, | |||
|
1262 | ) | |||
|
1263 | ||||
|
1264 | h.flash( | |||
|
1265 | _('Successfully committed new file `{}`').format( | |||
|
1266 | h.escape(node_path)), category='success') | |||
|
1267 | except NonRelativePathError: | |||
|
1268 | h.flash(_( | |||
|
1269 | 'The location specified must be a relative path and must not ' | |||
|
1270 | 'contain .. in the path'), category='warning') | |||
|
1271 | raise HTTPFound(default_redirect_url) | |||
|
1272 | except (NodeError, NodeAlreadyExistsError) as e: | |||
|
1273 | h.flash(_(h.escape(e)), category='error') | |||
|
1274 | except Exception: | |||
|
1275 | log.exception('Error occurred during commit') | |||
|
1276 | h.flash(_('Error occurred during commit'), category='error') | |||
|
1277 | ||||
|
1278 | raise HTTPFound(default_redirect_url) |
@@ -1,376 +1,385 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 |
|
2 | |||
3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
3 | # Copyright (C) 2016-2017 RhodeCode GmbH | |
4 | # |
|
4 | # | |
5 | # This program is free software: you can redistribute it and/or modify |
|
5 | # This program is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
7 | # (only), as published by the Free Software Foundation. |
|
7 | # (only), as published by the Free Software Foundation. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | # |
|
16 | # | |
17 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 |
|
20 | |||
21 | import time |
|
21 | import time | |
22 | import logging |
|
22 | import logging | |
23 |
|
23 | |||
24 | from pyramid.httpexceptions import HTTPFound |
|
24 | from pyramid.httpexceptions import HTTPFound | |
25 |
|
25 | |||
26 | from rhodecode.lib import helpers as h |
|
26 | from rhodecode.lib import helpers as h | |
27 | from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time |
|
27 | from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time | |
28 | from rhodecode.lib.vcs.exceptions import RepositoryRequirementError |
|
28 | from rhodecode.lib.vcs.exceptions import RepositoryRequirementError | |
29 | from rhodecode.model import repo |
|
29 | from rhodecode.model import repo | |
30 | from rhodecode.model import repo_group |
|
30 | from rhodecode.model import repo_group | |
31 | from rhodecode.model.db import User |
|
31 | from rhodecode.model.db import User | |
32 | from rhodecode.model.scm import ScmModel |
|
32 | from rhodecode.model.scm import ScmModel | |
33 |
|
33 | |||
34 | log = logging.getLogger(__name__) |
|
34 | log = logging.getLogger(__name__) | |
35 |
|
35 | |||
36 |
|
36 | |||
37 | ADMIN_PREFIX = '/_admin' |
|
37 | ADMIN_PREFIX = '/_admin' | |
38 | STATIC_FILE_PREFIX = '/_static' |
|
38 | STATIC_FILE_PREFIX = '/_static' | |
39 |
|
39 | |||
40 |
|
40 | |||
41 | def add_route_with_slash(config,name, pattern, **kw): |
|
41 | def add_route_with_slash(config,name, pattern, **kw): | |
42 | config.add_route(name, pattern, **kw) |
|
42 | config.add_route(name, pattern, **kw) | |
43 | if not pattern.endswith('/'): |
|
43 | if not pattern.endswith('/'): | |
44 | config.add_route(name + '_slash', pattern + '/', **kw) |
|
44 | config.add_route(name + '_slash', pattern + '/', **kw) | |
45 |
|
45 | |||
46 |
|
46 | |||
47 | def get_format_ref_id(repo): |
|
47 | def get_format_ref_id(repo): | |
48 | """Returns a `repo` specific reference formatter function""" |
|
48 | """Returns a `repo` specific reference formatter function""" | |
49 | if h.is_svn(repo): |
|
49 | if h.is_svn(repo): | |
50 | return _format_ref_id_svn |
|
50 | return _format_ref_id_svn | |
51 | else: |
|
51 | else: | |
52 | return _format_ref_id |
|
52 | return _format_ref_id | |
53 |
|
53 | |||
54 |
|
54 | |||
55 | def _format_ref_id(name, raw_id): |
|
55 | def _format_ref_id(name, raw_id): | |
56 | """Default formatting of a given reference `name`""" |
|
56 | """Default formatting of a given reference `name`""" | |
57 | return name |
|
57 | return name | |
58 |
|
58 | |||
59 |
|
59 | |||
60 | def _format_ref_id_svn(name, raw_id): |
|
60 | def _format_ref_id_svn(name, raw_id): | |
61 | """Special way of formatting a reference for Subversion including path""" |
|
61 | """Special way of formatting a reference for Subversion including path""" | |
62 | return '%s@%s' % (name, raw_id) |
|
62 | return '%s@%s' % (name, raw_id) | |
63 |
|
63 | |||
64 |
|
64 | |||
65 | class TemplateArgs(StrictAttributeDict): |
|
65 | class TemplateArgs(StrictAttributeDict): | |
66 | pass |
|
66 | pass | |
67 |
|
67 | |||
68 |
|
68 | |||
69 | class BaseAppView(object): |
|
69 | class BaseAppView(object): | |
70 |
|
70 | |||
71 | def __init__(self, context, request): |
|
71 | def __init__(self, context, request): | |
72 | self.request = request |
|
72 | self.request = request | |
73 | self.context = context |
|
73 | self.context = context | |
74 | self.session = request.session |
|
74 | self.session = request.session | |
75 | self._rhodecode_user = request.user # auth user |
|
75 | self._rhodecode_user = request.user # auth user | |
76 | self._rhodecode_db_user = self._rhodecode_user.get_instance() |
|
76 | self._rhodecode_db_user = self._rhodecode_user.get_instance() | |
77 | self._maybe_needs_password_change( |
|
77 | self._maybe_needs_password_change( | |
78 | request.matched_route.name, self._rhodecode_db_user) |
|
78 | request.matched_route.name, self._rhodecode_db_user) | |
79 |
|
79 | |||
80 | def _maybe_needs_password_change(self, view_name, user_obj): |
|
80 | def _maybe_needs_password_change(self, view_name, user_obj): | |
81 | log.debug('Checking if user %s needs password change on view %s', |
|
81 | log.debug('Checking if user %s needs password change on view %s', | |
82 | user_obj, view_name) |
|
82 | user_obj, view_name) | |
83 | skip_user_views = [ |
|
83 | skip_user_views = [ | |
84 | 'logout', 'login', |
|
84 | 'logout', 'login', | |
85 | 'my_account_password', 'my_account_password_update' |
|
85 | 'my_account_password', 'my_account_password_update' | |
86 | ] |
|
86 | ] | |
87 |
|
87 | |||
88 | if not user_obj: |
|
88 | if not user_obj: | |
89 | return |
|
89 | return | |
90 |
|
90 | |||
91 | if user_obj.username == User.DEFAULT_USER: |
|
91 | if user_obj.username == User.DEFAULT_USER: | |
92 | return |
|
92 | return | |
93 |
|
93 | |||
94 | now = time.time() |
|
94 | now = time.time() | |
95 | should_change = user_obj.user_data.get('force_password_change') |
|
95 | should_change = user_obj.user_data.get('force_password_change') | |
96 | change_after = safe_int(should_change) or 0 |
|
96 | change_after = safe_int(should_change) or 0 | |
97 | if should_change and now > change_after: |
|
97 | if should_change and now > change_after: | |
98 | log.debug('User %s requires password change', user_obj) |
|
98 | log.debug('User %s requires password change', user_obj) | |
99 | h.flash('You are required to change your password', 'warning', |
|
99 | h.flash('You are required to change your password', 'warning', | |
100 | ignore_duplicate=True) |
|
100 | ignore_duplicate=True) | |
101 |
|
101 | |||
102 | if view_name not in skip_user_views: |
|
102 | if view_name not in skip_user_views: | |
103 | raise HTTPFound( |
|
103 | raise HTTPFound( | |
104 | self.request.route_path('my_account_password')) |
|
104 | self.request.route_path('my_account_password')) | |
105 |
|
105 | |||
106 | def _get_local_tmpl_context(self, include_app_defaults=False): |
|
106 | def _get_local_tmpl_context(self, include_app_defaults=False): | |
107 | c = TemplateArgs() |
|
107 | c = TemplateArgs() | |
108 | c.auth_user = self.request.user |
|
108 | c.auth_user = self.request.user | |
109 | # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user |
|
109 | # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user | |
110 | c.rhodecode_user = self.request.user |
|
110 | c.rhodecode_user = self.request.user | |
111 |
|
111 | |||
112 | if include_app_defaults: |
|
112 | if include_app_defaults: | |
113 | # NOTE(marcink): after full pyramid migration include_app_defaults |
|
113 | # NOTE(marcink): after full pyramid migration include_app_defaults | |
114 | # should be turned on by default |
|
114 | # should be turned on by default | |
115 | from rhodecode.lib.base import attach_context_attributes |
|
115 | from rhodecode.lib.base import attach_context_attributes | |
116 | attach_context_attributes(c, self.request, self.request.user.user_id) |
|
116 | attach_context_attributes(c, self.request, self.request.user.user_id) | |
117 |
|
117 | |||
118 | return c |
|
118 | return c | |
119 |
|
119 | |||
120 | def _register_global_c(self, tmpl_args): |
|
120 | def _register_global_c(self, tmpl_args): | |
121 | """ |
|
121 | """ | |
122 | Registers attributes to pylons global `c` |
|
122 | Registers attributes to pylons global `c` | |
123 | """ |
|
123 | """ | |
124 |
|
124 | |||
125 | # TODO(marcink): remove once pyramid migration is finished |
|
125 | # TODO(marcink): remove once pyramid migration is finished | |
126 | from pylons import tmpl_context as c |
|
126 | from pylons import tmpl_context as c | |
127 | try: |
|
127 | try: | |
128 | for k, v in tmpl_args.items(): |
|
128 | for k, v in tmpl_args.items(): | |
129 | setattr(c, k, v) |
|
129 | setattr(c, k, v) | |
130 | except TypeError: |
|
130 | except TypeError: | |
131 | log.exception('Failed to register pylons C') |
|
131 | log.exception('Failed to register pylons C') | |
132 | pass |
|
132 | pass | |
133 |
|
133 | |||
134 | def _get_template_context(self, tmpl_args): |
|
134 | def _get_template_context(self, tmpl_args): | |
135 | self._register_global_c(tmpl_args) |
|
135 | self._register_global_c(tmpl_args) | |
136 |
|
136 | |||
137 | local_tmpl_args = { |
|
137 | local_tmpl_args = { | |
138 | 'defaults': {}, |
|
138 | 'defaults': {}, | |
139 | 'errors': {}, |
|
139 | 'errors': {}, | |
140 | # register a fake 'c' to be used in templates instead of global |
|
140 | # register a fake 'c' to be used in templates instead of global | |
141 | # pylons c, after migration to pyramid we should rename it to 'c' |
|
141 | # pylons c, after migration to pyramid we should rename it to 'c' | |
142 | # make sure we replace usage of _c in templates too |
|
142 | # make sure we replace usage of _c in templates too | |
143 | '_c': tmpl_args |
|
143 | '_c': tmpl_args | |
144 | } |
|
144 | } | |
145 | local_tmpl_args.update(tmpl_args) |
|
145 | local_tmpl_args.update(tmpl_args) | |
146 | return local_tmpl_args |
|
146 | return local_tmpl_args | |
147 |
|
147 | |||
148 | def load_default_context(self): |
|
148 | def load_default_context(self): | |
149 | """ |
|
149 | """ | |
150 | example: |
|
150 | example: | |
151 |
|
151 | |||
152 | def load_default_context(self): |
|
152 | def load_default_context(self): | |
153 | c = self._get_local_tmpl_context() |
|
153 | c = self._get_local_tmpl_context() | |
154 | c.custom_var = 'foobar' |
|
154 | c.custom_var = 'foobar' | |
155 | self._register_global_c(c) |
|
155 | self._register_global_c(c) | |
156 | return c |
|
156 | return c | |
157 | """ |
|
157 | """ | |
158 | raise NotImplementedError('Needs implementation in view class') |
|
158 | raise NotImplementedError('Needs implementation in view class') | |
159 |
|
159 | |||
160 |
|
160 | |||
161 | class RepoAppView(BaseAppView): |
|
161 | class RepoAppView(BaseAppView): | |
162 |
|
162 | |||
163 | def __init__(self, context, request): |
|
163 | def __init__(self, context, request): | |
164 | super(RepoAppView, self).__init__(context, request) |
|
164 | super(RepoAppView, self).__init__(context, request) | |
165 | self.db_repo = request.db_repo |
|
165 | self.db_repo = request.db_repo | |
166 | self.db_repo_name = self.db_repo.repo_name |
|
166 | self.db_repo_name = self.db_repo.repo_name | |
167 | self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo) |
|
167 | self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo) | |
168 |
|
168 | |||
169 | def _handle_missing_requirements(self, error): |
|
169 | def _handle_missing_requirements(self, error): | |
170 | log.error( |
|
170 | log.error( | |
171 | 'Requirements are missing for repository %s: %s', |
|
171 | 'Requirements are missing for repository %s: %s', | |
172 | self.db_repo_name, error.message) |
|
172 | self.db_repo_name, error.message) | |
173 |
|
173 | |||
174 | def _get_local_tmpl_context(self, include_app_defaults=False): |
|
174 | def _get_local_tmpl_context(self, include_app_defaults=False): | |
175 | c = super(RepoAppView, self)._get_local_tmpl_context( |
|
175 | c = super(RepoAppView, self)._get_local_tmpl_context( | |
176 | include_app_defaults=include_app_defaults) |
|
176 | include_app_defaults=include_app_defaults) | |
177 |
|
177 | |||
178 | # register common vars for this type of view |
|
178 | # register common vars for this type of view | |
179 | c.rhodecode_db_repo = self.db_repo |
|
179 | c.rhodecode_db_repo = self.db_repo | |
180 | c.repo_name = self.db_repo_name |
|
180 | c.repo_name = self.db_repo_name | |
181 | c.repository_pull_requests = self.db_repo_pull_requests |
|
181 | c.repository_pull_requests = self.db_repo_pull_requests | |
182 |
|
182 | |||
183 | c.repository_requirements_missing = False |
|
183 | c.repository_requirements_missing = False | |
184 | try: |
|
184 | try: | |
185 | self.rhodecode_vcs_repo = self.db_repo.scm_instance() |
|
185 | self.rhodecode_vcs_repo = self.db_repo.scm_instance() | |
186 | except RepositoryRequirementError as e: |
|
186 | except RepositoryRequirementError as e: | |
187 | c.repository_requirements_missing = True |
|
187 | c.repository_requirements_missing = True | |
188 | self._handle_missing_requirements(e) |
|
188 | self._handle_missing_requirements(e) | |
189 |
|
189 | |||
190 | return c |
|
190 | return c | |
191 |
|
191 | |||
192 |
|
192 | |||
193 | class DataGridAppView(object): |
|
193 | class DataGridAppView(object): | |
194 | """ |
|
194 | """ | |
195 | Common class to have re-usable grid rendering components |
|
195 | Common class to have re-usable grid rendering components | |
196 | """ |
|
196 | """ | |
197 |
|
197 | |||
198 | def _extract_ordering(self, request, column_map=None): |
|
198 | def _extract_ordering(self, request, column_map=None): | |
199 | column_map = column_map or {} |
|
199 | column_map = column_map or {} | |
200 | column_index = safe_int(request.GET.get('order[0][column]')) |
|
200 | column_index = safe_int(request.GET.get('order[0][column]')) | |
201 | order_dir = request.GET.get( |
|
201 | order_dir = request.GET.get( | |
202 | 'order[0][dir]', 'desc') |
|
202 | 'order[0][dir]', 'desc') | |
203 | order_by = request.GET.get( |
|
203 | order_by = request.GET.get( | |
204 | 'columns[%s][data][sort]' % column_index, 'name_raw') |
|
204 | 'columns[%s][data][sort]' % column_index, 'name_raw') | |
205 |
|
205 | |||
206 | # translate datatable to DB columns |
|
206 | # translate datatable to DB columns | |
207 | order_by = column_map.get(order_by) or order_by |
|
207 | order_by = column_map.get(order_by) or order_by | |
208 |
|
208 | |||
209 | search_q = request.GET.get('search[value]') |
|
209 | search_q = request.GET.get('search[value]') | |
210 | return search_q, order_by, order_dir |
|
210 | return search_q, order_by, order_dir | |
211 |
|
211 | |||
212 | def _extract_chunk(self, request): |
|
212 | def _extract_chunk(self, request): | |
213 | start = safe_int(request.GET.get('start'), 0) |
|
213 | start = safe_int(request.GET.get('start'), 0) | |
214 | length = safe_int(request.GET.get('length'), 25) |
|
214 | length = safe_int(request.GET.get('length'), 25) | |
215 | draw = safe_int(request.GET.get('draw')) |
|
215 | draw = safe_int(request.GET.get('draw')) | |
216 | return draw, start, length |
|
216 | return draw, start, length | |
217 |
|
217 | |||
218 |
|
218 | |||
219 | class BaseReferencesView(RepoAppView): |
|
219 | class BaseReferencesView(RepoAppView): | |
220 | """ |
|
220 | """ | |
221 | Base for reference view for branches, tags and bookmarks. |
|
221 | Base for reference view for branches, tags and bookmarks. | |
222 | """ |
|
222 | """ | |
223 | def load_default_context(self): |
|
223 | def load_default_context(self): | |
224 | c = self._get_local_tmpl_context() |
|
224 | c = self._get_local_tmpl_context() | |
225 |
|
225 | |||
226 | # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead |
|
226 | # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead | |
227 | c.repo_info = self.db_repo |
|
227 | c.repo_info = self.db_repo | |
228 |
|
228 | |||
229 | self._register_global_c(c) |
|
229 | self._register_global_c(c) | |
230 | return c |
|
230 | return c | |
231 |
|
231 | |||
232 | def load_refs_context(self, ref_items, partials_template): |
|
232 | def load_refs_context(self, ref_items, partials_template): | |
233 | _render = self.request.get_partial_renderer(partials_template) |
|
233 | _render = self.request.get_partial_renderer(partials_template) | |
234 | pre_load = ["author", "date", "message"] |
|
234 | pre_load = ["author", "date", "message"] | |
235 |
|
235 | |||
236 | is_svn = h.is_svn(self.rhodecode_vcs_repo) |
|
236 | is_svn = h.is_svn(self.rhodecode_vcs_repo) | |
237 | is_hg = h.is_hg(self.rhodecode_vcs_repo) |
|
237 | is_hg = h.is_hg(self.rhodecode_vcs_repo) | |
238 |
|
238 | |||
239 | format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo) |
|
239 | format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo) | |
240 |
|
240 | |||
241 | closed_refs = {} |
|
241 | closed_refs = {} | |
242 | if is_hg: |
|
242 | if is_hg: | |
243 | closed_refs = self.rhodecode_vcs_repo.branches_closed |
|
243 | closed_refs = self.rhodecode_vcs_repo.branches_closed | |
244 |
|
244 | |||
245 | data = [] |
|
245 | data = [] | |
246 | for ref_name, commit_id in ref_items: |
|
246 | for ref_name, commit_id in ref_items: | |
247 | commit = self.rhodecode_vcs_repo.get_commit( |
|
247 | commit = self.rhodecode_vcs_repo.get_commit( | |
248 | commit_id=commit_id, pre_load=pre_load) |
|
248 | commit_id=commit_id, pre_load=pre_load) | |
249 | closed = ref_name in closed_refs |
|
249 | closed = ref_name in closed_refs | |
250 |
|
250 | |||
251 | # TODO: johbo: Unify generation of reference links |
|
251 | # TODO: johbo: Unify generation of reference links | |
252 | use_commit_id = '/' in ref_name or is_svn |
|
252 | use_commit_id = '/' in ref_name or is_svn | |
253 | files_url = h.url( |
|
253 | ||
254 | 'files_home', |
|
254 | if use_commit_id: | |
255 | repo_name=self.db_repo_name, |
|
255 | files_url = h.route_path( | |
256 | f_path=ref_name if is_svn else '', |
|
256 | 'repo_files', | |
257 | revision=commit_id if use_commit_id else ref_name, |
|
257 | repo_name=self.db_repo_name, | |
258 |
at=ref_name |
|
258 | f_path=ref_name if is_svn else '', | |
|
259 | commit_id=commit_id) | |||
|
260 | ||||
|
261 | else: | |||
|
262 | files_url = h.route_path( | |||
|
263 | 'repo_files', | |||
|
264 | repo_name=self.db_repo_name, | |||
|
265 | f_path=ref_name if is_svn else '', | |||
|
266 | commit_id=ref_name, | |||
|
267 | _query=dict(at=ref_name)) | |||
259 |
|
268 | |||
260 | data.append({ |
|
269 | data.append({ | |
261 | "name": _render('name', ref_name, files_url, closed), |
|
270 | "name": _render('name', ref_name, files_url, closed), | |
262 | "name_raw": ref_name, |
|
271 | "name_raw": ref_name, | |
263 | "date": _render('date', commit.date), |
|
272 | "date": _render('date', commit.date), | |
264 | "date_raw": datetime_to_time(commit.date), |
|
273 | "date_raw": datetime_to_time(commit.date), | |
265 | "author": _render('author', commit.author), |
|
274 | "author": _render('author', commit.author), | |
266 | "commit": _render( |
|
275 | "commit": _render( | |
267 | 'commit', commit.message, commit.raw_id, commit.idx), |
|
276 | 'commit', commit.message, commit.raw_id, commit.idx), | |
268 | "commit_raw": commit.idx, |
|
277 | "commit_raw": commit.idx, | |
269 | "compare": _render( |
|
278 | "compare": _render( | |
270 | 'compare', format_ref_id(ref_name, commit.raw_id)), |
|
279 | 'compare', format_ref_id(ref_name, commit.raw_id)), | |
271 | }) |
|
280 | }) | |
272 |
|
281 | |||
273 | return data |
|
282 | return data | |
274 |
|
283 | |||
275 |
|
284 | |||
276 | class RepoRoutePredicate(object): |
|
285 | class RepoRoutePredicate(object): | |
277 | def __init__(self, val, config): |
|
286 | def __init__(self, val, config): | |
278 | self.val = val |
|
287 | self.val = val | |
279 |
|
288 | |||
280 | def text(self): |
|
289 | def text(self): | |
281 | return 'repo_route = %s' % self.val |
|
290 | return 'repo_route = %s' % self.val | |
282 |
|
291 | |||
283 | phash = text |
|
292 | phash = text | |
284 |
|
293 | |||
285 | def __call__(self, info, request): |
|
294 | def __call__(self, info, request): | |
286 |
|
295 | |||
287 | if hasattr(request, 'vcs_call'): |
|
296 | if hasattr(request, 'vcs_call'): | |
288 | # skip vcs calls |
|
297 | # skip vcs calls | |
289 | return |
|
298 | return | |
290 |
|
299 | |||
291 | repo_name = info['match']['repo_name'] |
|
300 | repo_name = info['match']['repo_name'] | |
292 | repo_model = repo.RepoModel() |
|
301 | repo_model = repo.RepoModel() | |
293 | by_name_match = repo_model.get_by_repo_name(repo_name, cache=True) |
|
302 | by_name_match = repo_model.get_by_repo_name(repo_name, cache=True) | |
294 |
|
303 | |||
295 | if by_name_match: |
|
304 | if by_name_match: | |
296 | # register this as request object we can re-use later |
|
305 | # register this as request object we can re-use later | |
297 | request.db_repo = by_name_match |
|
306 | request.db_repo = by_name_match | |
298 | return True |
|
307 | return True | |
299 |
|
308 | |||
300 | by_id_match = repo_model.get_repo_by_id(repo_name) |
|
309 | by_id_match = repo_model.get_repo_by_id(repo_name) | |
301 | if by_id_match: |
|
310 | if by_id_match: | |
302 | request.db_repo = by_id_match |
|
311 | request.db_repo = by_id_match | |
303 | return True |
|
312 | return True | |
304 |
|
313 | |||
305 | return False |
|
314 | return False | |
306 |
|
315 | |||
307 |
|
316 | |||
308 | class RepoTypeRoutePredicate(object): |
|
317 | class RepoTypeRoutePredicate(object): | |
309 | def __init__(self, val, config): |
|
318 | def __init__(self, val, config): | |
310 | self.val = val or ['hg', 'git', 'svn'] |
|
319 | self.val = val or ['hg', 'git', 'svn'] | |
311 |
|
320 | |||
312 | def text(self): |
|
321 | def text(self): | |
313 | return 'repo_accepted_type = %s' % self.val |
|
322 | return 'repo_accepted_type = %s' % self.val | |
314 |
|
323 | |||
315 | phash = text |
|
324 | phash = text | |
316 |
|
325 | |||
317 | def __call__(self, info, request): |
|
326 | def __call__(self, info, request): | |
318 | if hasattr(request, 'vcs_call'): |
|
327 | if hasattr(request, 'vcs_call'): | |
319 | # skip vcs calls |
|
328 | # skip vcs calls | |
320 | return |
|
329 | return | |
321 |
|
330 | |||
322 | rhodecode_db_repo = request.db_repo |
|
331 | rhodecode_db_repo = request.db_repo | |
323 |
|
332 | |||
324 | log.debug( |
|
333 | log.debug( | |
325 | '%s checking repo type for %s in %s', |
|
334 | '%s checking repo type for %s in %s', | |
326 | self.__class__.__name__, rhodecode_db_repo.repo_type, self.val) |
|
335 | self.__class__.__name__, rhodecode_db_repo.repo_type, self.val) | |
327 |
|
336 | |||
328 | if rhodecode_db_repo.repo_type in self.val: |
|
337 | if rhodecode_db_repo.repo_type in self.val: | |
329 | return True |
|
338 | return True | |
330 | else: |
|
339 | else: | |
331 | log.warning('Current view is not supported for repo type:%s', |
|
340 | log.warning('Current view is not supported for repo type:%s', | |
332 | rhodecode_db_repo.repo_type) |
|
341 | rhodecode_db_repo.repo_type) | |
333 | # |
|
342 | # | |
334 | # h.flash(h.literal( |
|
343 | # h.flash(h.literal( | |
335 | # _('Action not supported for %s.' % rhodecode_repo.alias)), |
|
344 | # _('Action not supported for %s.' % rhodecode_repo.alias)), | |
336 | # category='warning') |
|
345 | # category='warning') | |
337 | # return redirect( |
|
346 | # return redirect( | |
338 | # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name)) |
|
347 | # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name)) | |
339 |
|
348 | |||
340 | return False |
|
349 | return False | |
341 |
|
350 | |||
342 |
|
351 | |||
343 | class RepoGroupRoutePredicate(object): |
|
352 | class RepoGroupRoutePredicate(object): | |
344 | def __init__(self, val, config): |
|
353 | def __init__(self, val, config): | |
345 | self.val = val |
|
354 | self.val = val | |
346 |
|
355 | |||
347 | def text(self): |
|
356 | def text(self): | |
348 | return 'repo_group_route = %s' % self.val |
|
357 | return 'repo_group_route = %s' % self.val | |
349 |
|
358 | |||
350 | phash = text |
|
359 | phash = text | |
351 |
|
360 | |||
352 | def __call__(self, info, request): |
|
361 | def __call__(self, info, request): | |
353 | if hasattr(request, 'vcs_call'): |
|
362 | if hasattr(request, 'vcs_call'): | |
354 | # skip vcs calls |
|
363 | # skip vcs calls | |
355 | return |
|
364 | return | |
356 |
|
365 | |||
357 | repo_group_name = info['match']['repo_group_name'] |
|
366 | repo_group_name = info['match']['repo_group_name'] | |
358 | repo_group_model = repo_group.RepoGroupModel() |
|
367 | repo_group_model = repo_group.RepoGroupModel() | |
359 | by_name_match = repo_group_model.get_by_group_name( |
|
368 | by_name_match = repo_group_model.get_by_group_name( | |
360 | repo_group_name, cache=True) |
|
369 | repo_group_name, cache=True) | |
361 |
|
370 | |||
362 | if by_name_match: |
|
371 | if by_name_match: | |
363 | # register this as request object we can re-use later |
|
372 | # register this as request object we can re-use later | |
364 | request.db_repo_group = by_name_match |
|
373 | request.db_repo_group = by_name_match | |
365 | return True |
|
374 | return True | |
366 |
|
375 | |||
367 | return False |
|
376 | return False | |
368 |
|
377 | |||
369 |
|
378 | |||
370 | def includeme(config): |
|
379 | def includeme(config): | |
371 | config.add_route_predicate( |
|
380 | config.add_route_predicate( | |
372 | 'repo_route', RepoRoutePredicate) |
|
381 | 'repo_route', RepoRoutePredicate) | |
373 | config.add_route_predicate( |
|
382 | config.add_route_predicate( | |
374 | 'repo_accepted_types', RepoTypeRoutePredicate) |
|
383 | 'repo_accepted_types', RepoTypeRoutePredicate) | |
375 | config.add_route_predicate( |
|
384 | config.add_route_predicate( | |
376 | 'repo_group_route', RepoGroupRoutePredicate) |
|
385 | 'repo_group_route', RepoGroupRoutePredicate) |
@@ -1,177 +1,266 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 |
|
2 | |||
3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
3 | # Copyright (C) 2016-2017 RhodeCode GmbH | |
4 | # |
|
4 | # | |
5 | # This program is free software: you can redistribute it and/or modify |
|
5 | # This program is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
7 | # (only), as published by the Free Software Foundation. |
|
7 | # (only), as published by the Free Software Foundation. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | # |
|
16 | # | |
17 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 | from rhodecode.apps._base import add_route_with_slash |
|
20 | from rhodecode.apps._base import add_route_with_slash | |
21 |
|
21 | |||
22 |
|
22 | |||
23 | def includeme(config): |
|
23 | def includeme(config): | |
24 |
|
24 | |||
25 | # Summary |
|
25 | # Summary | |
26 | # NOTE(marcink): one additional route is defined in very bottom, catch |
|
26 | # NOTE(marcink): one additional route is defined in very bottom, catch | |
27 | # all pattern |
|
27 | # all pattern | |
28 | config.add_route( |
|
28 | config.add_route( | |
29 | name='repo_summary_explicit', |
|
29 | name='repo_summary_explicit', | |
30 | pattern='/{repo_name:.*?[^/]}/summary', repo_route=True) |
|
30 | pattern='/{repo_name:.*?[^/]}/summary', repo_route=True) | |
31 | config.add_route( |
|
31 | config.add_route( | |
32 | name='repo_summary_commits', |
|
32 | name='repo_summary_commits', | |
33 | pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True) |
|
33 | pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True) | |
34 |
|
34 | |||
35 | # repo commits |
|
35 | # repo commits | |
36 | config.add_route( |
|
36 | config.add_route( | |
37 | name='repo_commit', |
|
37 | name='repo_commit', | |
38 | pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True) |
|
38 | pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True) | |
39 |
|
39 | |||
|
40 | # repo files | |||
|
41 | config.add_route( | |||
|
42 | name='repo_archivefile', | |||
|
43 | pattern='/{repo_name:.*?[^/]}/archive/{fname}', repo_route=True) | |||
|
44 | ||||
|
45 | config.add_route( | |||
|
46 | name='repo_files_diff', | |||
|
47 | pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True) | |||
|
48 | config.add_route( # legacy route to make old links work | |||
|
49 | name='repo_files_diff_2way_redirect', | |||
|
50 | pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True) | |||
|
51 | ||||
|
52 | config.add_route( | |||
|
53 | name='repo_files', | |||
|
54 | pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True) | |||
|
55 | config.add_route( | |||
|
56 | name='repo_files:default_path', | |||
|
57 | pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True) | |||
|
58 | config.add_route( | |||
|
59 | name='repo_files:default_commit', | |||
|
60 | pattern='/{repo_name:.*?[^/]}/files', repo_route=True) | |||
|
61 | ||||
|
62 | config.add_route( | |||
|
63 | name='repo_files:rendered', | |||
|
64 | pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True) | |||
|
65 | ||||
|
66 | config.add_route( | |||
|
67 | name='repo_files:annotated', | |||
|
68 | pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True) | |||
|
69 | config.add_route( | |||
|
70 | name='repo_files:annotated_previous', | |||
|
71 | pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True) | |||
|
72 | ||||
|
73 | config.add_route( | |||
|
74 | name='repo_nodetree_full', | |||
|
75 | pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True) | |||
|
76 | config.add_route( | |||
|
77 | name='repo_nodetree_full:default_path', | |||
|
78 | pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True) | |||
|
79 | ||||
|
80 | config.add_route( | |||
|
81 | name='repo_files_nodelist', | |||
|
82 | pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True) | |||
|
83 | ||||
|
84 | config.add_route( | |||
|
85 | name='repo_file_raw', | |||
|
86 | pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True) | |||
|
87 | ||||
|
88 | config.add_route( | |||
|
89 | name='repo_file_download', | |||
|
90 | pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True) | |||
|
91 | config.add_route( # backward compat to keep old links working | |||
|
92 | name='repo_file_download:legacy', | |||
|
93 | pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}', | |||
|
94 | repo_route=True) | |||
|
95 | ||||
|
96 | config.add_route( | |||
|
97 | name='repo_file_history', | |||
|
98 | pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True) | |||
|
99 | ||||
|
100 | config.add_route( | |||
|
101 | name='repo_file_authors', | |||
|
102 | pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True) | |||
|
103 | ||||
|
104 | config.add_route( | |||
|
105 | name='repo_files_remove_file', | |||
|
106 | pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}', | |||
|
107 | repo_route=True) | |||
|
108 | config.add_route( | |||
|
109 | name='repo_files_delete_file', | |||
|
110 | pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}', | |||
|
111 | repo_route=True) | |||
|
112 | config.add_route( | |||
|
113 | name='repo_files_edit_file', | |||
|
114 | pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}', | |||
|
115 | repo_route=True) | |||
|
116 | config.add_route( | |||
|
117 | name='repo_files_update_file', | |||
|
118 | pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}', | |||
|
119 | repo_route=True) | |||
|
120 | config.add_route( | |||
|
121 | name='repo_files_add_file', | |||
|
122 | pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}', | |||
|
123 | repo_route=True) | |||
|
124 | config.add_route( | |||
|
125 | name='repo_files_create_file', | |||
|
126 | pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}', | |||
|
127 | repo_route=True) | |||
|
128 | ||||
40 | # refs data |
|
129 | # refs data | |
41 | config.add_route( |
|
130 | config.add_route( | |
42 | name='repo_refs_data', |
|
131 | name='repo_refs_data', | |
43 | pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True) |
|
132 | pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True) | |
44 |
|
133 | |||
45 | config.add_route( |
|
134 | config.add_route( | |
46 | name='repo_refs_changelog_data', |
|
135 | name='repo_refs_changelog_data', | |
47 | pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True) |
|
136 | pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True) | |
48 |
|
137 | |||
49 | config.add_route( |
|
138 | config.add_route( | |
50 | name='repo_stats', |
|
139 | name='repo_stats', | |
51 | pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True) |
|
140 | pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True) | |
52 |
|
141 | |||
53 | # Tags |
|
142 | # Tags | |
54 | config.add_route( |
|
143 | config.add_route( | |
55 | name='tags_home', |
|
144 | name='tags_home', | |
56 | pattern='/{repo_name:.*?[^/]}/tags', repo_route=True) |
|
145 | pattern='/{repo_name:.*?[^/]}/tags', repo_route=True) | |
57 |
|
146 | |||
58 | # Branches |
|
147 | # Branches | |
59 | config.add_route( |
|
148 | config.add_route( | |
60 | name='branches_home', |
|
149 | name='branches_home', | |
61 | pattern='/{repo_name:.*?[^/]}/branches', repo_route=True) |
|
150 | pattern='/{repo_name:.*?[^/]}/branches', repo_route=True) | |
62 |
|
151 | |||
63 | config.add_route( |
|
152 | config.add_route( | |
64 | name='bookmarks_home', |
|
153 | name='bookmarks_home', | |
65 | pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True) |
|
154 | pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True) | |
66 |
|
155 | |||
67 | # Pull Requests |
|
156 | # Pull Requests | |
68 | config.add_route( |
|
157 | config.add_route( | |
69 | name='pullrequest_show', |
|
158 | name='pullrequest_show', | |
70 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id}', |
|
159 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id}', | |
71 | repo_route=True) |
|
160 | repo_route=True) | |
72 |
|
161 | |||
73 | config.add_route( |
|
162 | config.add_route( | |
74 | name='pullrequest_show_all', |
|
163 | name='pullrequest_show_all', | |
75 | pattern='/{repo_name:.*?[^/]}/pull-request', |
|
164 | pattern='/{repo_name:.*?[^/]}/pull-request', | |
76 | repo_route=True, repo_accepted_types=['hg', 'git']) |
|
165 | repo_route=True, repo_accepted_types=['hg', 'git']) | |
77 |
|
166 | |||
78 | config.add_route( |
|
167 | config.add_route( | |
79 | name='pullrequest_show_all_data', |
|
168 | name='pullrequest_show_all_data', | |
80 | pattern='/{repo_name:.*?[^/]}/pull-request-data', |
|
169 | pattern='/{repo_name:.*?[^/]}/pull-request-data', | |
81 | repo_route=True, repo_accepted_types=['hg', 'git']) |
|
170 | repo_route=True, repo_accepted_types=['hg', 'git']) | |
82 |
|
171 | |||
83 | # commits aka changesets |
|
172 | # commits aka changesets | |
84 | # TODO(dan): handle default landing revision ? |
|
173 | # TODO(dan): handle default landing revision ? | |
85 | config.add_route( |
|
174 | config.add_route( | |
86 | name='changeset_home', |
|
175 | name='changeset_home', | |
87 | pattern='/{repo_name:.*?[^/]}/changeset/{revision}', |
|
176 | pattern='/{repo_name:.*?[^/]}/changeset/{revision}', | |
88 | repo_route=True) |
|
177 | repo_route=True) | |
89 | config.add_route( |
|
178 | config.add_route( | |
90 | name='changeset_children', |
|
179 | name='changeset_children', | |
91 | pattern='/{repo_name:.*?[^/]}/changeset_children/{revision}', |
|
180 | pattern='/{repo_name:.*?[^/]}/changeset_children/{revision}', | |
92 | repo_route=True) |
|
181 | repo_route=True) | |
93 | config.add_route( |
|
182 | config.add_route( | |
94 | name='changeset_parents', |
|
183 | name='changeset_parents', | |
95 | pattern='/{repo_name:.*?[^/]}/changeset_parents/{revision}', |
|
184 | pattern='/{repo_name:.*?[^/]}/changeset_parents/{revision}', | |
96 | repo_route=True) |
|
185 | repo_route=True) | |
97 |
|
186 | |||
98 | # Settings |
|
187 | # Settings | |
99 | config.add_route( |
|
188 | config.add_route( | |
100 | name='edit_repo', |
|
189 | name='edit_repo', | |
101 | pattern='/{repo_name:.*?[^/]}/settings', repo_route=True) |
|
190 | pattern='/{repo_name:.*?[^/]}/settings', repo_route=True) | |
102 |
|
191 | |||
103 | # Settings advanced |
|
192 | # Settings advanced | |
104 | config.add_route( |
|
193 | config.add_route( | |
105 | name='edit_repo_advanced', |
|
194 | name='edit_repo_advanced', | |
106 | pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True) |
|
195 | pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True) | |
107 | config.add_route( |
|
196 | config.add_route( | |
108 | name='edit_repo_advanced_delete', |
|
197 | name='edit_repo_advanced_delete', | |
109 | pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True) |
|
198 | pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True) | |
110 | config.add_route( |
|
199 | config.add_route( | |
111 | name='edit_repo_advanced_locking', |
|
200 | name='edit_repo_advanced_locking', | |
112 | pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True) |
|
201 | pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True) | |
113 | config.add_route( |
|
202 | config.add_route( | |
114 | name='edit_repo_advanced_journal', |
|
203 | name='edit_repo_advanced_journal', | |
115 | pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True) |
|
204 | pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True) | |
116 | config.add_route( |
|
205 | config.add_route( | |
117 | name='edit_repo_advanced_fork', |
|
206 | name='edit_repo_advanced_fork', | |
118 | pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True) |
|
207 | pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True) | |
119 |
|
208 | |||
120 | # Caches |
|
209 | # Caches | |
121 | config.add_route( |
|
210 | config.add_route( | |
122 | name='edit_repo_caches', |
|
211 | name='edit_repo_caches', | |
123 | pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True) |
|
212 | pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True) | |
124 |
|
213 | |||
125 | # Permissions |
|
214 | # Permissions | |
126 | config.add_route( |
|
215 | config.add_route( | |
127 | name='edit_repo_perms', |
|
216 | name='edit_repo_perms', | |
128 | pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True) |
|
217 | pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True) | |
129 |
|
218 | |||
130 | # Repo Review Rules |
|
219 | # Repo Review Rules | |
131 | config.add_route( |
|
220 | config.add_route( | |
132 | name='repo_reviewers', |
|
221 | name='repo_reviewers', | |
133 | pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True) |
|
222 | pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True) | |
134 |
|
223 | |||
135 | config.add_route( |
|
224 | config.add_route( | |
136 | name='repo_default_reviewers_data', |
|
225 | name='repo_default_reviewers_data', | |
137 | pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True) |
|
226 | pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True) | |
138 |
|
227 | |||
139 | # Maintenance |
|
228 | # Maintenance | |
140 | config.add_route( |
|
229 | config.add_route( | |
141 | name='repo_maintenance', |
|
230 | name='repo_maintenance', | |
142 | pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True) |
|
231 | pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True) | |
143 |
|
232 | |||
144 | config.add_route( |
|
233 | config.add_route( | |
145 | name='repo_maintenance_execute', |
|
234 | name='repo_maintenance_execute', | |
146 | pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True) |
|
235 | pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True) | |
147 |
|
236 | |||
148 | # Strip |
|
237 | # Strip | |
149 | config.add_route( |
|
238 | config.add_route( | |
150 | name='strip', |
|
239 | name='strip', | |
151 | pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True) |
|
240 | pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True) | |
152 |
|
241 | |||
153 | config.add_route( |
|
242 | config.add_route( | |
154 | name='strip_check', |
|
243 | name='strip_check', | |
155 | pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True) |
|
244 | pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True) | |
156 |
|
245 | |||
157 | config.add_route( |
|
246 | config.add_route( | |
158 | name='strip_execute', |
|
247 | name='strip_execute', | |
159 | pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True) |
|
248 | pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True) | |
160 |
|
249 | |||
161 | # ATOM/RSS Feed |
|
250 | # ATOM/RSS Feed | |
162 | config.add_route( |
|
251 | config.add_route( | |
163 | name='rss_feed_home', |
|
252 | name='rss_feed_home', | |
164 | pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True) |
|
253 | pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True) | |
165 |
|
254 | |||
166 | config.add_route( |
|
255 | config.add_route( | |
167 | name='atom_feed_home', |
|
256 | name='atom_feed_home', | |
168 | pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True) |
|
257 | pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True) | |
169 |
|
258 | |||
170 | # NOTE(marcink): needs to be at the end for catch-all |
|
259 | # NOTE(marcink): needs to be at the end for catch-all | |
171 | add_route_with_slash( |
|
260 | add_route_with_slash( | |
172 | config, |
|
261 | config, | |
173 | name='repo_summary', |
|
262 | name='repo_summary', | |
174 | pattern='/{repo_name:.*?[^/]}', repo_route=True) |
|
263 | pattern='/{repo_name:.*?[^/]}', repo_route=True) | |
175 |
|
264 | |||
176 | # Scan module for configuration decorators. |
|
265 | # Scan module for configuration decorators. | |
177 | config.scan() |
|
266 | config.scan() |
This diff has been collapsed as it changes many lines, (959 lines changed) Show them Hide them | |||||
@@ -1,987 +1,1062 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 |
|
2 | |||
3 | # Copyright (C) 2010-2017 RhodeCode GmbH |
|
3 | # Copyright (C) 2010-2017 RhodeCode GmbH | |
4 | # |
|
4 | # | |
5 | # This program is free software: you can redistribute it and/or modify |
|
5 | # This program is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
7 | # (only), as published by the Free Software Foundation. |
|
7 | # (only), as published by the Free Software Foundation. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | # |
|
16 | # | |
17 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 |
|
20 | |||
21 | import os |
|
21 | import os | |
22 |
|
22 | |||
23 | import mock |
|
23 | import mock | |
24 | import pytest |
|
24 | import pytest | |
25 |
|
25 | |||
26 | from rhodecode.controllers.files import FilesController |
|
26 | from rhodecode.apps.repository.views.repo_files import RepoFilesView | |
27 | from rhodecode.lib import helpers as h |
|
27 | from rhodecode.lib import helpers as h | |
28 | from rhodecode.lib.compat import OrderedDict |
|
28 | from rhodecode.lib.compat import OrderedDict | |
29 | from rhodecode.lib.ext_json import json |
|
29 | from rhodecode.lib.ext_json import json | |
30 | from rhodecode.lib.vcs import nodes |
|
30 | from rhodecode.lib.vcs import nodes | |
31 |
|
31 | |||
32 | from rhodecode.lib.vcs.conf import settings |
|
32 | from rhodecode.lib.vcs.conf import settings | |
33 |
from rhodecode.tests import |
|
33 | from rhodecode.tests import assert_session_flash | |
34 | url, assert_session_flash, assert_not_in_session_flash) |
|
|||
35 | from rhodecode.tests.fixture import Fixture |
|
34 | from rhodecode.tests.fixture import Fixture | |
36 |
|
35 | |||
37 | fixture = Fixture() |
|
36 | fixture = Fixture() | |
38 |
|
37 | |||
39 | NODE_HISTORY = { |
|
38 | NODE_HISTORY = { | |
40 | 'hg': json.loads(fixture.load_resource('hg_node_history_response.json')), |
|
39 | 'hg': json.loads(fixture.load_resource('hg_node_history_response.json')), | |
41 | 'git': json.loads(fixture.load_resource('git_node_history_response.json')), |
|
40 | 'git': json.loads(fixture.load_resource('git_node_history_response.json')), | |
42 | 'svn': json.loads(fixture.load_resource('svn_node_history_response.json')), |
|
41 | 'svn': json.loads(fixture.load_resource('svn_node_history_response.json')), | |
43 | } |
|
42 | } | |
44 |
|
43 | |||
45 |
|
44 | |||
|
45 | def route_path(name, params=None, **kwargs): | |||
|
46 | import urllib | |||
|
47 | ||||
|
48 | base_url = { | |||
|
49 | 'repo_archivefile': '/{repo_name}/archive/{fname}', | |||
|
50 | 'repo_files_diff': '/{repo_name}/diff/{f_path}', | |||
|
51 | 'repo_files_diff_2way_redirect': '/{repo_name}/diff-2way/{f_path}', | |||
|
52 | 'repo_files': '/{repo_name}/files/{commit_id}/{f_path}', | |||
|
53 | 'repo_files:default_path': '/{repo_name}/files/{commit_id}/', | |||
|
54 | 'repo_files:default_commit': '/{repo_name}/files', | |||
|
55 | 'repo_files:rendered': '/{repo_name}/render/{commit_id}/{f_path}', | |||
|
56 | 'repo_files:annotated': '/{repo_name}/annotate/{commit_id}/{f_path}', | |||
|
57 | 'repo_files:annotated_previous': '/{repo_name}/annotate-previous/{commit_id}/{f_path}', | |||
|
58 | 'repo_files_nodelist': '/{repo_name}/nodelist/{commit_id}/{f_path}', | |||
|
59 | 'repo_file_raw': '/{repo_name}/raw/{commit_id}/{f_path}', | |||
|
60 | 'repo_file_download': '/{repo_name}/download/{commit_id}/{f_path}', | |||
|
61 | 'repo_file_history': '/{repo_name}/history/{commit_id}/{f_path}', | |||
|
62 | 'repo_file_authors': '/{repo_name}/authors/{commit_id}/{f_path}', | |||
|
63 | 'repo_files_remove_file': '/{repo_name}/remove_file/{commit_id}/{f_path}', | |||
|
64 | 'repo_files_delete_file': '/{repo_name}/delete_file/{commit_id}/{f_path}', | |||
|
65 | 'repo_files_edit_file': '/{repo_name}/edit_file/{commit_id}/{f_path}', | |||
|
66 | 'repo_files_update_file': '/{repo_name}/update_file/{commit_id}/{f_path}', | |||
|
67 | 'repo_files_add_file': '/{repo_name}/add_file/{commit_id}/{f_path}', | |||
|
68 | 'repo_files_create_file': '/{repo_name}/create_file/{commit_id}/{f_path}', | |||
|
69 | 'repo_nodetree_full': '/{repo_name}/nodetree_full/{commit_id}/{f_path}', | |||
|
70 | 'repo_nodetree_full:default_path': '/{repo_name}/nodetree_full/{commit_id}/', | |||
|
71 | }[name].format(**kwargs) | |||
|
72 | ||||
|
73 | if params: | |||
|
74 | base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) | |||
|
75 | return base_url | |||
|
76 | ||||
|
77 | ||||
|
78 | def assert_files_in_response(response, files, params): | |||
|
79 | template = ( | |||
|
80 | 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"') | |||
|
81 | _assert_items_in_response(response, files, template, params) | |||
|
82 | ||||
|
83 | ||||
|
84 | def assert_dirs_in_response(response, dirs, params): | |||
|
85 | template = ( | |||
|
86 | 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"') | |||
|
87 | _assert_items_in_response(response, dirs, template, params) | |||
|
88 | ||||
|
89 | ||||
|
90 | def _assert_items_in_response(response, items, template, params): | |||
|
91 | for item in items: | |||
|
92 | item_params = {'name': item} | |||
|
93 | item_params.update(params) | |||
|
94 | response.mustcontain(template % item_params) | |||
|
95 | ||||
|
96 | ||||
|
97 | def assert_timeago_in_response(response, items, params): | |||
|
98 | for item in items: | |||
|
99 | response.mustcontain(h.age_component(params['date'])) | |||
|
100 | ||||
46 |
|
101 | |||
47 | @pytest.mark.usefixtures("app") |
|
102 | @pytest.mark.usefixtures("app") | |
48 |
class TestFiles |
|
103 | class TestFilesViews(object): | |
49 |
|
104 | |||
50 |
def test_ |
|
105 | def test_show_files(self, backend): | |
51 |
response = self.app.get( |
|
106 | response = self.app.get( | |
52 | controller='files', action='index', |
|
107 | route_path('repo_files', | |
53 |
repo_name=backend.repo_name, |
|
108 | repo_name=backend.repo_name, | |
|
109 | commit_id='tip', f_path='/')) | |||
54 | commit = backend.repo.get_commit() |
|
110 | commit = backend.repo.get_commit() | |
55 |
|
111 | |||
56 | params = { |
|
112 | params = { | |
57 | 'repo_name': backend.repo_name, |
|
113 | 'repo_name': backend.repo_name, | |
58 | 'commit_id': commit.raw_id, |
|
114 | 'commit_id': commit.raw_id, | |
59 | 'date': commit.date |
|
115 | 'date': commit.date | |
60 | } |
|
116 | } | |
61 | assert_dirs_in_response(response, ['docs', 'vcs'], params) |
|
117 | assert_dirs_in_response(response, ['docs', 'vcs'], params) | |
62 | files = [ |
|
118 | files = [ | |
63 | '.gitignore', |
|
119 | '.gitignore', | |
64 | '.hgignore', |
|
120 | '.hgignore', | |
65 | '.hgtags', |
|
121 | '.hgtags', | |
66 | # TODO: missing in Git |
|
122 | # TODO: missing in Git | |
67 | # '.travis.yml', |
|
123 | # '.travis.yml', | |
68 | 'MANIFEST.in', |
|
124 | 'MANIFEST.in', | |
69 | 'README.rst', |
|
125 | 'README.rst', | |
70 | # TODO: File is missing in svn repository |
|
126 | # TODO: File is missing in svn repository | |
71 | # 'run_test_and_report.sh', |
|
127 | # 'run_test_and_report.sh', | |
72 | 'setup.cfg', |
|
128 | 'setup.cfg', | |
73 | 'setup.py', |
|
129 | 'setup.py', | |
74 | 'test_and_report.sh', |
|
130 | 'test_and_report.sh', | |
75 | 'tox.ini', |
|
131 | 'tox.ini', | |
76 | ] |
|
132 | ] | |
77 | assert_files_in_response(response, files, params) |
|
133 | assert_files_in_response(response, files, params) | |
78 | assert_timeago_in_response(response, files, params) |
|
134 | assert_timeago_in_response(response, files, params) | |
79 |
|
135 | |||
80 |
def test_ |
|
136 | def test_show_files_links_submodules_with_absolute_url(self, backend_hg): | |
81 | repo = backend_hg['subrepos'] |
|
137 | repo = backend_hg['subrepos'] | |
82 |
response = self.app.get( |
|
138 | response = self.app.get( | |
83 | controller='files', action='index', |
|
139 | route_path('repo_files', | |
84 |
repo_name=repo.repo_name, |
|
140 | repo_name=repo.repo_name, | |
|
141 | commit_id='tip', f_path='/')) | |||
85 | assert_response = response.assert_response() |
|
142 | assert_response = response.assert_response() | |
86 | assert_response.contains_one_link( |
|
143 | assert_response.contains_one_link( | |
87 | 'absolute-path @ 000000000000', 'http://example.com/absolute-path') |
|
144 | 'absolute-path @ 000000000000', 'http://example.com/absolute-path') | |
88 |
|
145 | |||
89 |
def test_ |
|
146 | def test_show_files_links_submodules_with_absolute_url_subpaths( | |
90 | self, backend_hg): |
|
147 | self, backend_hg): | |
91 | repo = backend_hg['subrepos'] |
|
148 | repo = backend_hg['subrepos'] | |
92 |
response = self.app.get( |
|
149 | response = self.app.get( | |
93 | controller='files', action='index', |
|
150 | route_path('repo_files', | |
94 |
repo_name=repo.repo_name, |
|
151 | repo_name=repo.repo_name, | |
|
152 | commit_id='tip', f_path='/')) | |||
95 | assert_response = response.assert_response() |
|
153 | assert_response = response.assert_response() | |
96 | assert_response.contains_one_link( |
|
154 | assert_response.contains_one_link( | |
97 | 'subpaths-path @ 000000000000', |
|
155 | 'subpaths-path @ 000000000000', | |
98 | 'http://sub-base.example.com/subpaths-path') |
|
156 | 'http://sub-base.example.com/subpaths-path') | |
99 |
|
157 | |||
100 | @pytest.mark.xfail_backends("svn", reason="Depends on branch support") |
|
158 | @pytest.mark.xfail_backends("svn", reason="Depends on branch support") | |
101 | def test_files_menu(self, backend): |
|
159 | def test_files_menu(self, backend): | |
102 | new_branch = "temp_branch_name" |
|
160 | new_branch = "temp_branch_name" | |
103 | commits = [ |
|
161 | commits = [ | |
104 | {'message': 'a'}, |
|
162 | {'message': 'a'}, | |
105 | {'message': 'b', 'branch': new_branch} |
|
163 | {'message': 'b', 'branch': new_branch} | |
106 | ] |
|
164 | ] | |
107 | backend.create_repo(commits) |
|
165 | backend.create_repo(commits) | |
108 |
|
166 | |||
109 | backend.repo.landing_rev = "branch:%s" % new_branch |
|
167 | backend.repo.landing_rev = "branch:%s" % new_branch | |
110 |
|
168 | |||
111 |
# get response based on tip and not new |
|
169 | # get response based on tip and not new commit | |
112 |
response = self.app.get( |
|
170 | response = self.app.get( | |
113 | controller='files', action='index', |
|
171 | route_path('repo_files', | |
114 |
repo_name=backend.repo_name, |
|
172 | repo_name=backend.repo_name, | |
115 | status=200) |
|
173 | commit_id='tip', f_path='/')) | |
116 |
|
174 | |||
117 |
# make sure Files menu url is not tip but new |
|
175 | # make sure Files menu url is not tip but new commit | |
118 | landing_rev = backend.repo.landing_rev[1] |
|
176 | landing_rev = backend.repo.landing_rev[1] | |
119 | files_url = url('files_home', repo_name=backend.repo_name, |
|
177 | files_url = route_path('repo_files:default_path', | |
120 |
|
|
178 | repo_name=backend.repo_name, | |
|
179 | commit_id=landing_rev) | |||
121 |
|
180 | |||
122 | assert landing_rev != 'tip' |
|
181 | assert landing_rev != 'tip' | |
123 | response.mustcontain('<li class="active"><a class="menulink" href="%s">' % files_url) |
|
182 | response.mustcontain( | |
|
183 | '<li class="active"><a class="menulink" href="%s">' % files_url) | |||
124 |
|
184 | |||
125 |
def test_ |
|
185 | def test_show_files_commit(self, backend): | |
126 | commit = backend.repo.get_commit(commit_idx=32) |
|
186 | commit = backend.repo.get_commit(commit_idx=32) | |
127 |
|
187 | |||
128 |
response = self.app.get( |
|
188 | response = self.app.get( | |
129 | controller='files', action='index', |
|
189 | route_path('repo_files', | |
130 | repo_name=backend.repo_name, |
|
190 | repo_name=backend.repo_name, | |
131 |
|
|
191 | commit_id=commit.raw_id, f_path='/')) | |
132 | f_path='/') |
|
|||
133 | ) |
|
|||
134 |
|
192 | |||
135 | dirs = ['docs', 'tests'] |
|
193 | dirs = ['docs', 'tests'] | |
136 | files = ['README.rst'] |
|
194 | files = ['README.rst'] | |
137 | params = { |
|
195 | params = { | |
138 | 'repo_name': backend.repo_name, |
|
196 | 'repo_name': backend.repo_name, | |
139 | 'commit_id': commit.raw_id, |
|
197 | 'commit_id': commit.raw_id, | |
140 | } |
|
198 | } | |
141 | assert_dirs_in_response(response, dirs, params) |
|
199 | assert_dirs_in_response(response, dirs, params) | |
142 | assert_files_in_response(response, files, params) |
|
200 | assert_files_in_response(response, files, params) | |
143 |
|
201 | |||
144 |
def test_ |
|
202 | def test_show_files_different_branch(self, backend): | |
145 | branches = dict( |
|
203 | branches = dict( | |
146 | hg=(150, ['git']), |
|
204 | hg=(150, ['git']), | |
147 | # TODO: Git test repository does not contain other branches |
|
205 | # TODO: Git test repository does not contain other branches | |
148 | git=(633, ['master']), |
|
206 | git=(633, ['master']), | |
149 | # TODO: Branch support in Subversion |
|
207 | # TODO: Branch support in Subversion | |
150 | svn=(150, []) |
|
208 | svn=(150, []) | |
151 | ) |
|
209 | ) | |
152 | idx, branches = branches[backend.alias] |
|
210 | idx, branches = branches[backend.alias] | |
153 | commit = backend.repo.get_commit(commit_idx=idx) |
|
211 | commit = backend.repo.get_commit(commit_idx=idx) | |
154 |
response = self.app.get( |
|
212 | response = self.app.get( | |
155 | controller='files', action='index', |
|
213 | route_path('repo_files', | |
156 | repo_name=backend.repo_name, |
|
214 | repo_name=backend.repo_name, | |
157 |
|
|
215 | commit_id=commit.raw_id, f_path='/')) | |
158 | f_path='/')) |
|
216 | ||
159 | assert_response = response.assert_response() |
|
217 | assert_response = response.assert_response() | |
160 | for branch in branches: |
|
218 | for branch in branches: | |
161 | assert_response.element_contains('.tags .branchtag', branch) |
|
219 | assert_response.element_contains('.tags .branchtag', branch) | |
162 |
|
220 | |||
163 |
def test_ |
|
221 | def test_show_files_paging(self, backend): | |
164 | repo = backend.repo |
|
222 | repo = backend.repo | |
165 | indexes = [73, 92, 109, 1, 0] |
|
223 | indexes = [73, 92, 109, 1, 0] | |
166 | idx_map = [(rev, repo.get_commit(commit_idx=rev).raw_id) |
|
224 | idx_map = [(rev, repo.get_commit(commit_idx=rev).raw_id) | |
167 | for rev in indexes] |
|
225 | for rev in indexes] | |
168 |
|
226 | |||
169 | for idx in idx_map: |
|
227 | for idx in idx_map: | |
170 |
response = self.app.get( |
|
228 | response = self.app.get( | |
171 | controller='files', action='index', |
|
229 | route_path('repo_files', | |
172 | repo_name=backend.repo_name, |
|
230 | repo_name=backend.repo_name, | |
173 |
|
|
231 | commit_id=idx[1], f_path='/')) | |
174 | f_path='/')) |
|
|||
175 |
|
232 | |||
176 | response.mustcontain("""r%s:%s""" % (idx[0], idx[1][:8])) |
|
233 | response.mustcontain("""r%s:%s""" % (idx[0], idx[1][:8])) | |
177 |
|
234 | |||
178 | def test_file_source(self, backend): |
|
235 | def test_file_source(self, backend): | |
179 | commit = backend.repo.get_commit(commit_idx=167) |
|
236 | commit = backend.repo.get_commit(commit_idx=167) | |
180 |
response = self.app.get( |
|
237 | response = self.app.get( | |
181 | controller='files', action='index', |
|
238 | route_path('repo_files', | |
182 | repo_name=backend.repo_name, |
|
239 | repo_name=backend.repo_name, | |
183 | revision=commit.raw_id, |
|
240 | commit_id=commit.raw_id, f_path='vcs/nodes.py')) | |
184 | f_path='vcs/nodes.py')) |
|
|||
185 |
|
241 | |||
186 | msgbox = """<div class="commit right-content">%s</div>""" |
|
242 | msgbox = """<div class="commit right-content">%s</div>""" | |
187 | response.mustcontain(msgbox % (commit.message, )) |
|
243 | response.mustcontain(msgbox % (commit.message, )) | |
188 |
|
244 | |||
189 | assert_response = response.assert_response() |
|
245 | assert_response = response.assert_response() | |
190 | if commit.branch: |
|
246 | if commit.branch: | |
191 |
assert_response.element_contains( |
|
247 | assert_response.element_contains( | |
|
248 | '.tags.tags-main .branchtag', commit.branch) | |||
192 | if commit.tags: |
|
249 | if commit.tags: | |
193 | for tag in commit.tags: |
|
250 | for tag in commit.tags: | |
194 | assert_response.element_contains('.tags.tags-main .tagtag', tag) |
|
251 | assert_response.element_contains('.tags.tags-main .tagtag', tag) | |
195 |
|
252 | |||
196 |
def test_file_source_ |
|
253 | def test_file_source_annotated(self, backend): | |
197 | response = self.app.get( |
|
|||
198 | url( |
|
|||
199 | controller='files', action='history', |
|
|||
200 | repo_name=backend.repo_name, |
|
|||
201 | revision='tip', |
|
|||
202 | f_path='vcs/nodes.py'), |
|
|||
203 | extra_environ={'HTTP_X_PARTIAL_XHR': '1'}) |
|
|||
204 | assert NODE_HISTORY[backend.alias] == json.loads(response.body) |
|
|||
205 |
|
||||
206 | def test_file_source_history_svn(self, backend_svn): |
|
|||
207 | simple_repo = backend_svn['svn-simple-layout'] |
|
|||
208 | response = self.app.get( |
|
254 | response = self.app.get( | |
209 | url( |
|
255 | route_path('repo_files:annotated', | |
210 | controller='files', action='history', |
|
256 | repo_name=backend.repo_name, | |
211 | repo_name=simple_repo.repo_name, |
|
257 | commit_id='tip', f_path='vcs/nodes.py')) | |
212 | revision='tip', |
|
258 | expected_commits = { | |
213 | f_path='trunk/example.py'), |
|
|||
214 | extra_environ={'HTTP_X_PARTIAL_XHR': '1'}) |
|
|||
215 |
|
||||
216 | expected_data = json.loads( |
|
|||
217 | fixture.load_resource('svn_node_history_branches.json')) |
|
|||
218 | assert expected_data == response.json |
|
|||
219 |
|
||||
220 | def test_file_annotation_history(self, backend): |
|
|||
221 | response = self.app.get( |
|
|||
222 | url( |
|
|||
223 | controller='files', action='history', |
|
|||
224 | repo_name=backend.repo_name, |
|
|||
225 | revision='tip', |
|
|||
226 | f_path='vcs/nodes.py', |
|
|||
227 | annotate=True), |
|
|||
228 | extra_environ={'HTTP_X_PARTIAL_XHR': '1'}) |
|
|||
229 | assert NODE_HISTORY[backend.alias] == json.loads(response.body) |
|
|||
230 |
|
||||
231 | def test_file_annotation(self, backend): |
|
|||
232 | response = self.app.get(url( |
|
|||
233 | controller='files', action='index', |
|
|||
234 | repo_name=backend.repo_name, revision='tip', f_path='vcs/nodes.py', |
|
|||
235 | annotate=True)) |
|
|||
236 |
|
||||
237 | expected_revisions = { |
|
|||
238 | 'hg': 'r356', |
|
259 | 'hg': 'r356', | |
239 | 'git': 'r345', |
|
260 | 'git': 'r345', | |
240 | 'svn': 'r208', |
|
261 | 'svn': 'r208', | |
241 | } |
|
262 | } | |
242 |
response.mustcontain(expected_ |
|
263 | response.mustcontain(expected_commits[backend.alias]) | |
243 |
|
264 | |||
244 | def test_file_authors(self, backend): |
|
265 | def test_file_source_authors(self, backend): | |
245 |
response = self.app.get( |
|
266 | response = self.app.get( | |
246 |
|
|
267 | route_path('repo_file_authors', | |
247 | repo_name=backend.repo_name, |
|
268 | repo_name=backend.repo_name, | |
248 | revision='tip', |
|
269 | commit_id='tip', f_path='vcs/nodes.py')) | |
249 | f_path='vcs/nodes.py', |
|
270 | expected_authors = { | |
250 | annotate=True)) |
|
271 | 'hg': ('Marcin Kuzminski', 'Lukasz Balcerzak'), | |
|
272 | 'git': ('Marcin Kuzminski', 'Lukasz Balcerzak'), | |||
|
273 | 'svn': ('marcin', 'lukasz'), | |||
|
274 | } | |||
251 |
|
275 | |||
|
276 | for author in expected_authors[backend.alias]: | |||
|
277 | response.mustcontain(author) | |||
|
278 | ||||
|
279 | def test_file_source_authors_with_annotation(self, backend): | |||
|
280 | response = self.app.get( | |||
|
281 | route_path('repo_file_authors', | |||
|
282 | repo_name=backend.repo_name, | |||
|
283 | commit_id='tip', f_path='vcs/nodes.py', | |||
|
284 | params=dict(annotate=1))) | |||
252 | expected_authors = { |
|
285 | expected_authors = { | |
253 | 'hg': ('Marcin Kuzminski', 'Lukasz Balcerzak'), |
|
286 | 'hg': ('Marcin Kuzminski', 'Lukasz Balcerzak'), | |
254 | 'git': ('Marcin Kuzminski', 'Lukasz Balcerzak'), |
|
287 | 'git': ('Marcin Kuzminski', 'Lukasz Balcerzak'), | |
255 | 'svn': ('marcin', 'lukasz'), |
|
288 | 'svn': ('marcin', 'lukasz'), | |
256 | } |
|
289 | } | |
257 |
|
290 | |||
258 | for author in expected_authors[backend.alias]: |
|
291 | for author in expected_authors[backend.alias]: | |
259 | response.mustcontain(author) |
|
292 | response.mustcontain(author) | |
260 |
|
293 | |||
|
294 | def test_file_source_history(self, backend, xhr_header): | |||
|
295 | response = self.app.get( | |||
|
296 | route_path('repo_file_history', | |||
|
297 | repo_name=backend.repo_name, | |||
|
298 | commit_id='tip', f_path='vcs/nodes.py'), | |||
|
299 | extra_environ=xhr_header) | |||
|
300 | assert NODE_HISTORY[backend.alias] == json.loads(response.body) | |||
|
301 | ||||
|
302 | def test_file_source_history_svn(self, backend_svn, xhr_header): | |||
|
303 | simple_repo = backend_svn['svn-simple-layout'] | |||
|
304 | response = self.app.get( | |||
|
305 | route_path('repo_file_history', | |||
|
306 | repo_name=simple_repo.repo_name, | |||
|
307 | commit_id='tip', f_path='trunk/example.py'), | |||
|
308 | extra_environ=xhr_header) | |||
|
309 | ||||
|
310 | expected_data = json.loads( | |||
|
311 | fixture.load_resource('svn_node_history_branches.json')) | |||
|
312 | assert expected_data == response.json | |||
|
313 | ||||
|
314 | def test_file_source_history_with_annotation(self, backend, xhr_header): | |||
|
315 | response = self.app.get( | |||
|
316 | route_path('repo_file_history', | |||
|
317 | repo_name=backend.repo_name, | |||
|
318 | commit_id='tip', f_path='vcs/nodes.py', | |||
|
319 | params=dict(annotate=1)), | |||
|
320 | ||||
|
321 | extra_environ=xhr_header) | |||
|
322 | assert NODE_HISTORY[backend.alias] == json.loads(response.body) | |||
|
323 | ||||
261 | def test_tree_search_top_level(self, backend, xhr_header): |
|
324 | def test_tree_search_top_level(self, backend, xhr_header): | |
262 | commit = backend.repo.get_commit(commit_idx=173) |
|
325 | commit = backend.repo.get_commit(commit_idx=173) | |
263 | response = self.app.get( |
|
326 | response = self.app.get( | |
264 | url('files_nodelist_home', repo_name=backend.repo_name, |
|
327 | route_path('repo_files_nodelist', | |
265 | revision=commit.raw_id, f_path='/'), |
|
328 | repo_name=backend.repo_name, | |
|
329 | commit_id=commit.raw_id, f_path='/'), | |||
266 | extra_environ=xhr_header) |
|
330 | extra_environ=xhr_header) | |
267 | assert 'nodes' in response.json |
|
331 | assert 'nodes' in response.json | |
268 | assert {'name': 'docs', 'type': 'dir'} in response.json['nodes'] |
|
332 | assert {'name': 'docs', 'type': 'dir'} in response.json['nodes'] | |
269 |
|
333 | |||
|
334 | def test_tree_search_missing_xhr(self, backend): | |||
|
335 | self.app.get( | |||
|
336 | route_path('repo_files_nodelist', | |||
|
337 | repo_name=backend.repo_name, | |||
|
338 | commit_id='tip', f_path='/'), | |||
|
339 | status=404) | |||
|
340 | ||||
270 | def test_tree_search_at_path(self, backend, xhr_header): |
|
341 | def test_tree_search_at_path(self, backend, xhr_header): | |
271 | commit = backend.repo.get_commit(commit_idx=173) |
|
342 | commit = backend.repo.get_commit(commit_idx=173) | |
272 | response = self.app.get( |
|
343 | response = self.app.get( | |
273 | url('files_nodelist_home', repo_name=backend.repo_name, |
|
344 | route_path('repo_files_nodelist', | |
274 | revision=commit.raw_id, f_path='/docs'), |
|
345 | repo_name=backend.repo_name, | |
|
346 | commit_id=commit.raw_id, f_path='/docs'), | |||
275 | extra_environ=xhr_header) |
|
347 | extra_environ=xhr_header) | |
276 | assert 'nodes' in response.json |
|
348 | assert 'nodes' in response.json | |
277 | nodes = response.json['nodes'] |
|
349 | nodes = response.json['nodes'] | |
278 | assert {'name': 'docs/api', 'type': 'dir'} in nodes |
|
350 | assert {'name': 'docs/api', 'type': 'dir'} in nodes | |
279 | assert {'name': 'docs/index.rst', 'type': 'file'} in nodes |
|
351 | assert {'name': 'docs/index.rst', 'type': 'file'} in nodes | |
280 |
|
352 | |||
281 |
def test_tree_search_at_path_ |
|
353 | def test_tree_search_at_path_2nd_level(self, backend, xhr_header): | |
282 | self.app.get( |
|
|||
283 | url('files_nodelist_home', repo_name=backend.repo_name, |
|
|||
284 | revision='tip', f_path=''), status=400) |
|
|||
285 |
|
||||
286 | def test_tree_view_list(self, backend, xhr_header): |
|
|||
287 | commit = backend.repo.get_commit(commit_idx=173) |
|
|||
288 | response = self.app.get( |
|
|||
289 | url('files_nodelist_home', repo_name=backend.repo_name, |
|
|||
290 | f_path='/', revision=commit.raw_id), |
|
|||
291 | extra_environ=xhr_header, |
|
|||
292 | ) |
|
|||
293 | response.mustcontain("vcs/web/simplevcs/views/repository.py") |
|
|||
294 |
|
||||
295 | def test_tree_view_list_at_path(self, backend, xhr_header): |
|
|||
296 | commit = backend.repo.get_commit(commit_idx=173) |
|
354 | commit = backend.repo.get_commit(commit_idx=173) | |
297 | response = self.app.get( |
|
355 | response = self.app.get( | |
298 | url('files_nodelist_home', repo_name=backend.repo_name, |
|
356 | route_path('repo_files_nodelist', | |
299 | f_path='/docs', revision=commit.raw_id), |
|
357 | repo_name=backend.repo_name, | |
300 | extra_environ=xhr_header, |
|
358 | commit_id=commit.raw_id, f_path='/docs/api'), | |
301 | ) |
|
359 | extra_environ=xhr_header) | |
302 | response.mustcontain("docs/index.rst") |
|
360 | assert 'nodes' in response.json | |
|
361 | nodes = response.json['nodes'] | |||
|
362 | assert {'name': 'docs/api/index.rst', 'type': 'file'} in nodes | |||
303 |
|
363 | |||
304 |
def test_tree_ |
|
364 | def test_tree_search_at_path_missing_xhr(self, backend): | |
305 | self.app.get( |
|
365 | self.app.get( | |
306 | url('files_nodelist_home', repo_name=backend.repo_name, |
|
366 | route_path('repo_files_nodelist', | |
307 | f_path='/', revision='tip'), status=400) |
|
367 | repo_name=backend.repo_name, | |
|
368 | commit_id='tip', f_path='/docs'), | |||
|
369 | status=404) | |||
308 |
|
370 | |||
309 |
def test_nodetree |
|
371 | def test_nodetree(self, backend, xhr_header): | |
310 | commit = backend.repo.get_commit(commit_idx=173) |
|
372 | commit = backend.repo.get_commit(commit_idx=173) | |
311 | response = self.app.get( |
|
373 | response = self.app.get( | |
312 | url('files_nodetree_full', repo_name=backend.repo_name, |
|
374 | route_path('repo_nodetree_full', | |
313 | f_path='/', commit_id=commit.raw_id), |
|
375 | repo_name=backend.repo_name, | |
|
376 | commit_id=commit.raw_id, f_path='/'), | |||
314 | extra_environ=xhr_header) |
|
377 | extra_environ=xhr_header) | |
315 |
|
378 | |||
316 | assert_response = response.assert_response() |
|
379 | assert_response = response.assert_response() | |
317 |
|
380 | |||
318 | for attr in ['data-commit-id', 'data-date', 'data-author']: |
|
381 | for attr in ['data-commit-id', 'data-date', 'data-author']: | |
319 | elements = assert_response.get_elements('[{}]'.format(attr)) |
|
382 | elements = assert_response.get_elements('[{}]'.format(attr)) | |
320 | assert len(elements) > 1 |
|
383 | assert len(elements) > 1 | |
321 |
|
384 | |||
322 | for element in elements: |
|
385 | for element in elements: | |
323 | assert element.get(attr) |
|
386 | assert element.get(attr) | |
324 |
|
387 | |||
325 |
def test_nodetree_ |
|
388 | def test_nodetree_if_file(self, backend, xhr_header): | |
326 | commit = backend.repo.get_commit(commit_idx=173) |
|
389 | commit = backend.repo.get_commit(commit_idx=173) | |
327 | response = self.app.get( |
|
390 | response = self.app.get( | |
328 | url('files_nodetree_full', repo_name=backend.repo_name, |
|
391 | route_path('repo_nodetree_full', | |
329 | f_path='README.rst', commit_id=commit.raw_id), |
|
392 | repo_name=backend.repo_name, | |
|
393 | commit_id=commit.raw_id, f_path='README.rst'), | |||
330 | extra_environ=xhr_header) |
|
394 | extra_environ=xhr_header) | |
331 | assert response.body == '' |
|
395 | assert response.body == '' | |
332 |
|
396 | |||
333 |
def test_ |
|
397 | def test_nodetree_wrong_path(self, backend, xhr_header): | |
334 | self.app.get( |
|
398 | commit = backend.repo.get_commit(commit_idx=173) | |
335 | url('files_nodetree_full', repo_name=backend.repo_name, |
|
399 | response = self.app.get( | |
336 | f_path='/', commit_id='tip'), status=400) |
|
400 | route_path('repo_nodetree_full', | |
|
401 | repo_name=backend.repo_name, | |||
|
402 | commit_id=commit.raw_id, f_path='/dont-exist'), | |||
|
403 | extra_environ=xhr_header) | |||
337 |
|
404 | |||
338 | def test_access_empty_repo_redirect_to_summary_with_alert_write_perms( |
|
405 | err = 'error: There is no file nor ' \ | |
339 | self, app, backend_stub, autologin_regular_user, user_regular, |
|
406 | 'directory at the given path' | |
340 | user_util): |
|
407 | assert err in response.body | |
341 | repo = backend_stub.create_repo() |
|
|||
342 | user_util.grant_user_permission_to_repo( |
|
|||
343 | repo, user_regular, 'repository.write') |
|
|||
344 | response = self.app.get(url( |
|
|||
345 | controller='files', action='index', |
|
|||
346 | repo_name=repo.repo_name, revision='tip', f_path='/')) |
|
|||
347 | assert_session_flash( |
|
|||
348 | response, |
|
|||
349 | 'There are no files yet. <a class="alert-link" ' |
|
|||
350 | 'href="/%s/add/0/#edit">Click here to add a new file.</a>' |
|
|||
351 | % (repo.repo_name)) |
|
|||
352 |
|
408 | |||
353 | def test_access_empty_repo_redirect_to_summary_with_alert_no_write_perms( |
|
409 | def test_nodetree_missing_xhr(self, backend): | |
354 | self, backend_stub, user_util): |
|
410 | self.app.get( | |
355 | repo = backend_stub.create_repo() |
|
411 | route_path('repo_nodetree_full', | |
356 | repo_file_url = url( |
|
412 | repo_name=backend.repo_name, | |
357 | 'files_add_home', |
|
413 | commit_id='tip', f_path='/'), | |
358 | repo_name=repo.repo_name, |
|
414 | status=404) | |
359 | revision=0, f_path='', anchor='edit') |
|
|||
360 | response = self.app.get(url( |
|
|||
361 | controller='files', action='index', |
|
|||
362 | repo_name=repo.repo_name, revision='tip', f_path='/')) |
|
|||
363 | assert_not_in_session_flash(response, repo_file_url) |
|
|||
364 |
|
415 | |||
365 |
|
416 | |||
366 | # TODO: johbo: Think about a better place for these tests. Either controller |
|
417 | @pytest.mark.usefixtures("app", "autologin_user") | |
367 | # specific unit tests or we move down the whole logic further towards the vcs |
|
418 | class TestRawFileHandling(object): | |
368 | # layer |
|
419 | ||
369 | class TestAdjustFilePathForSvn(object): |
|
420 | def test_download_file(self, backend): | |
370 | """SVN specific adjustments of node history in FileController.""" |
|
421 | commit = backend.repo.get_commit(commit_idx=173) | |
|
422 | response = self.app.get( | |||
|
423 | route_path('repo_file_download', | |||
|
424 | repo_name=backend.repo_name, | |||
|
425 | commit_id=commit.raw_id, f_path='vcs/nodes.py'),) | |||
|
426 | ||||
|
427 | assert response.content_disposition == "attachment; filename=nodes.py" | |||
|
428 | assert response.content_type == "text/x-python" | |||
|
429 | ||||
|
430 | def test_download_file_wrong_cs(self, backend): | |||
|
431 | raw_id = u'ERRORce30c96924232dffcd24178a07ffeb5dfc' | |||
|
432 | ||||
|
433 | response = self.app.get( | |||
|
434 | route_path('repo_file_download', | |||
|
435 | repo_name=backend.repo_name, | |||
|
436 | commit_id=raw_id, f_path='vcs/nodes.svg'), | |||
|
437 | status=404) | |||
371 |
|
438 | |||
372 | def test_returns_path_relative_to_matched_reference(self): |
|
439 | msg = """No such commit exists for this repository""" | |
373 | repo = self._repo(branches=['trunk']) |
|
440 | response.mustcontain(msg) | |
374 | self.assert_file_adjustment('trunk/file', 'file', repo) |
|
441 | ||
|
442 | def test_download_file_wrong_f_path(self, backend): | |||
|
443 | commit = backend.repo.get_commit(commit_idx=173) | |||
|
444 | f_path = 'vcs/ERRORnodes.py' | |||
375 |
|
445 | |||
376 | def test_does_not_modify_file_if_no_reference_matches(self): |
|
446 | response = self.app.get( | |
377 | repo = self._repo(branches=['trunk']) |
|
447 | route_path('repo_file_download', | |
378 | self.assert_file_adjustment('notes/file', 'notes/file', repo) |
|
448 | repo_name=backend.repo_name, | |
|
449 | commit_id=commit.raw_id, f_path=f_path), | |||
|
450 | status=404) | |||
|
451 | ||||
|
452 | msg = ( | |||
|
453 | "There is no file nor directory at the given path: " | |||
|
454 | "`%s` at commit %s" % (f_path, commit.short_id)) | |||
|
455 | response.mustcontain(msg) | |||
|
456 | ||||
|
457 | def test_file_raw(self, backend): | |||
|
458 | commit = backend.repo.get_commit(commit_idx=173) | |||
|
459 | response = self.app.get( | |||
|
460 | route_path('repo_file_raw', | |||
|
461 | repo_name=backend.repo_name, | |||
|
462 | commit_id=commit.raw_id, f_path='vcs/nodes.py'),) | |||
379 |
|
463 | |||
380 | def test_does_not_adjust_partial_directory_names(self): |
|
464 | assert response.content_type == "text/plain" | |
381 | repo = self._repo(branches=['trun']) |
|
465 | ||
382 | self.assert_file_adjustment('trunk/file', 'trunk/file', repo) |
|
466 | def test_file_raw_binary(self, backend): | |
|
467 | commit = backend.repo.get_commit() | |||
|
468 | response = self.app.get( | |||
|
469 | route_path('repo_file_raw', | |||
|
470 | repo_name=backend.repo_name, | |||
|
471 | commit_id=commit.raw_id, | |||
|
472 | f_path='docs/theme/ADC/static/breadcrumb_background.png'),) | |||
|
473 | ||||
|
474 | assert response.content_disposition == 'inline' | |||
383 |
|
475 | |||
384 | def test_is_robust_to_patterns_which_prefix_other_patterns(self): |
|
476 | def test_raw_file_wrong_cs(self, backend): | |
385 | repo = self._repo(branches=['trunk', 'trunk/new', 'trunk/old']) |
|
477 | raw_id = u'ERRORcce30c96924232dffcd24178a07ffeb5dfc' | |
386 | self.assert_file_adjustment('trunk/new/file', 'file', repo) |
|
478 | ||
|
479 | response = self.app.get( | |||
|
480 | route_path('repo_file_raw', | |||
|
481 | repo_name=backend.repo_name, | |||
|
482 | commit_id=raw_id, f_path='vcs/nodes.svg'), | |||
|
483 | status=404) | |||
|
484 | ||||
|
485 | msg = """No such commit exists for this repository""" | |||
|
486 | response.mustcontain(msg) | |||
387 |
|
487 | |||
388 | def assert_file_adjustment(self, f_path, expected, repo): |
|
488 | def test_raw_wrong_f_path(self, backend): | |
389 | controller = FilesController() |
|
489 | commit = backend.repo.get_commit(commit_idx=173) | |
390 | result = controller._adjust_file_path_for_svn(f_path, repo) |
|
490 | f_path = 'vcs/ERRORnodes.py' | |
391 | assert result == expected |
|
491 | response = self.app.get( | |
|
492 | route_path('repo_file_raw', | |||
|
493 | repo_name=backend.repo_name, | |||
|
494 | commit_id=commit.raw_id, f_path=f_path), | |||
|
495 | status=404) | |||
392 |
|
496 | |||
393 | def _repo(self, branches=None): |
|
497 | msg = ( | |
394 | repo = mock.Mock() |
|
498 | "There is no file nor directory at the given path: " | |
395 | repo.branches = OrderedDict((name, '0') for name in branches or []) |
|
499 | "`%s` at commit %s" % (f_path, commit.short_id)) | |
396 | repo.tags = {} |
|
500 | response.mustcontain(msg) | |
397 | return repo |
|
501 | ||
|
502 | def test_raw_svg_should_not_be_rendered(self, backend): | |||
|
503 | backend.create_repo() | |||
|
504 | backend.ensure_file("xss.svg") | |||
|
505 | response = self.app.get( | |||
|
506 | route_path('repo_file_raw', | |||
|
507 | repo_name=backend.repo_name, | |||
|
508 | commit_id='tip', f_path='xss.svg'),) | |||
|
509 | # If the content type is image/svg+xml then it allows to render HTML | |||
|
510 | # and malicious SVG. | |||
|
511 | assert response.content_type == "text/plain" | |||
398 |
|
512 | |||
399 |
|
513 | |||
400 | @pytest.mark.usefixtures("app") |
|
514 | @pytest.mark.usefixtures("app") | |
401 | class TestRepositoryArchival(object): |
|
515 | class TestRepositoryArchival(object): | |
402 |
|
516 | |||
403 | def test_archival(self, backend): |
|
517 | def test_archival(self, backend): | |
404 | backend.enable_downloads() |
|
518 | backend.enable_downloads() | |
405 | commit = backend.repo.get_commit(commit_idx=173) |
|
519 | commit = backend.repo.get_commit(commit_idx=173) | |
406 | for archive, info in settings.ARCHIVE_SPECS.items(): |
|
520 | for archive, info in settings.ARCHIVE_SPECS.items(): | |
407 | mime_type, arch_ext = info |
|
521 | mime_type, arch_ext = info | |
408 | short = commit.short_id + arch_ext |
|
522 | short = commit.short_id + arch_ext | |
409 | fname = commit.raw_id + arch_ext |
|
523 | fname = commit.raw_id + arch_ext | |
410 | filename = '%s-%s' % (backend.repo_name, short) |
|
524 | filename = '%s-%s' % (backend.repo_name, short) | |
411 |
response = self.app.get( |
|
525 | response = self.app.get( | |
412 | action='archivefile', |
|
526 | route_path('repo_archivefile', | |
413 |
|
|
527 | repo_name=backend.repo_name, | |
414 |
|
|
528 | fname=fname)) | |
415 |
|
529 | |||
416 | assert response.status == '200 OK' |
|
530 | assert response.status == '200 OK' | |
417 | headers = [ |
|
531 | headers = [ | |
418 | ('Pragma', 'no-cache'), |
|
|||
419 | ('Cache-Control', 'no-cache'), |
|
|||
420 | ('Content-Disposition', 'attachment; filename=%s' % filename), |
|
532 | ('Content-Disposition', 'attachment; filename=%s' % filename), | |
421 | ('Content-Type', '%s' % mime_type), |
|
533 | ('Content-Type', '%s' % mime_type), | |
422 | ] |
|
534 | ] | |
423 | if 'Set-Cookie' in response.response.headers: |
|
535 | ||
424 | del response.response.headers['Set-Cookie'] |
|
536 | for header in headers: | |
425 |
assert |
|
537 | assert header in response.headers.items() | |
426 |
|
538 | |||
427 | def test_archival_wrong_ext(self, backend): |
|
539 | @pytest.mark.parametrize('arch_ext',[ | |
|
540 | 'tar', 'rar', 'x', '..ax', '.zipz', 'tar.gz.tar']) | |||
|
541 | def test_archival_wrong_ext(self, backend, arch_ext): | |||
428 | backend.enable_downloads() |
|
542 | backend.enable_downloads() | |
429 | commit = backend.repo.get_commit(commit_idx=173) |
|
543 | commit = backend.repo.get_commit(commit_idx=173) | |
430 | for arch_ext in ['tar', 'rar', 'x', '..ax', '.zipz']: |
|
|||
431 | fname = commit.raw_id + arch_ext |
|
|||
432 |
|
544 | |||
433 | response = self.app.get(url(controller='files', |
|
545 | fname = commit.raw_id + '.' + arch_ext | |
434 | action='archivefile', |
|
|||
435 | repo_name=backend.repo_name, |
|
|||
436 | fname=fname)) |
|
|||
437 | response.mustcontain('Unknown archive type') |
|
|||
438 |
|
||||
439 | def test_archival_wrong_commit_id(self, backend): |
|
|||
440 | backend.enable_downloads() |
|
|||
441 | for commit_id in ['00x000000', 'tar', 'wrong', '@##$@$42413232', |
|
|||
442 | '232dffcd']: |
|
|||
443 | fname = '%s.zip' % commit_id |
|
|||
444 |
|
||||
445 | response = self.app.get(url(controller='files', |
|
|||
446 | action='archivefile', |
|
|||
447 | repo_name=backend.repo_name, |
|
|||
448 | fname=fname)) |
|
|||
449 | response.mustcontain('Unknown revision') |
|
|||
450 |
|
||||
451 |
|
546 | |||
452 | @pytest.mark.usefixtures("app", "autologin_user") |
|
547 | response = self.app.get( | |
453 | class TestRawFileHandling(object): |
|
548 | route_path('repo_archivefile', | |
454 |
|
549 | repo_name=backend.repo_name, | ||
455 | def test_raw_file_ok(self, backend): |
|
550 | fname=fname)) | |
456 | commit = backend.repo.get_commit(commit_idx=173) |
|
551 | response.mustcontain( | |
457 | response = self.app.get(url(controller='files', action='rawfile', |
|
552 | 'Unknown archive type for: `{}`'.format(fname)) | |
458 | repo_name=backend.repo_name, |
|
|||
459 | revision=commit.raw_id, |
|
|||
460 | f_path='vcs/nodes.py')) |
|
|||
461 |
|
||||
462 | assert response.content_disposition == "attachment; filename=nodes.py" |
|
|||
463 | assert response.content_type == "text/x-python" |
|
|||
464 |
|
||||
465 | def test_raw_file_wrong_cs(self, backend): |
|
|||
466 | commit_id = u'ERRORce30c96924232dffcd24178a07ffeb5dfc' |
|
|||
467 | f_path = 'vcs/nodes.py' |
|
|||
468 |
|
||||
469 | response = self.app.get(url(controller='files', action='rawfile', |
|
|||
470 | repo_name=backend.repo_name, |
|
|||
471 | revision=commit_id, |
|
|||
472 | f_path=f_path), status=404) |
|
|||
473 |
|
||||
474 | msg = """No such commit exists for this repository""" |
|
|||
475 | response.mustcontain(msg) |
|
|||
476 |
|
553 | |||
477 | def test_raw_file_wrong_f_path(self, backend): |
|
554 | @pytest.mark.parametrize('commit_id', [ | |
478 | commit = backend.repo.get_commit(commit_idx=173) |
|
555 | '00x000000', 'tar', 'wrong', '@$@$42413232', '232dffcd']) | |
479 | f_path = 'vcs/ERRORnodes.py' |
|
556 | def test_archival_wrong_commit_id(self, backend, commit_id): | |
480 | response = self.app.get(url(controller='files', action='rawfile', |
|
557 | backend.enable_downloads() | |
481 | repo_name=backend.repo_name, |
|
558 | fname = '%s.zip' % commit_id | |
482 | revision=commit.raw_id, |
|
|||
483 | f_path=f_path), status=404) |
|
|||
484 |
|
||||
485 | msg = ( |
|
|||
486 | "There is no file nor directory at the given path: " |
|
|||
487 | "`%s` at commit %s" % (f_path, commit.short_id)) |
|
|||
488 | response.mustcontain(msg) |
|
|||
489 |
|
||||
490 | def test_raw_ok(self, backend): |
|
|||
491 | commit = backend.repo.get_commit(commit_idx=173) |
|
|||
492 | response = self.app.get(url(controller='files', action='raw', |
|
|||
493 | repo_name=backend.repo_name, |
|
|||
494 | revision=commit.raw_id, |
|
|||
495 | f_path='vcs/nodes.py')) |
|
|||
496 |
|
||||
497 | assert response.content_type == "text/plain" |
|
|||
498 |
|
||||
499 | def test_raw_wrong_cs(self, backend): |
|
|||
500 | commit_id = u'ERRORcce30c96924232dffcd24178a07ffeb5dfc' |
|
|||
501 | f_path = 'vcs/nodes.py' |
|
|||
502 |
|
559 | |||
503 |
response = self.app.get( |
|
560 | response = self.app.get( | |
504 | repo_name=backend.repo_name, |
|
561 | route_path('repo_archivefile', | |
505 |
|
|
562 | repo_name=backend.repo_name, | |
506 |
|
|
563 | fname=fname)) | |
507 |
|
564 | response.mustcontain('Unknown commit_id') | ||
508 | msg = """No such commit exists for this repository""" |
|
|||
509 | response.mustcontain(msg) |
|
|||
510 |
|
||||
511 | def test_raw_wrong_f_path(self, backend): |
|
|||
512 | commit = backend.repo.get_commit(commit_idx=173) |
|
|||
513 | f_path = 'vcs/ERRORnodes.py' |
|
|||
514 | response = self.app.get(url(controller='files', action='raw', |
|
|||
515 | repo_name=backend.repo_name, |
|
|||
516 | revision=commit.raw_id, |
|
|||
517 | f_path=f_path), status=404) |
|
|||
518 | msg = ( |
|
|||
519 | "There is no file nor directory at the given path: " |
|
|||
520 | "`%s` at commit %s" % (f_path, commit.short_id)) |
|
|||
521 | response.mustcontain(msg) |
|
|||
522 |
|
||||
523 | def test_raw_svg_should_not_be_rendered(self, backend): |
|
|||
524 | backend.create_repo() |
|
|||
525 | backend.ensure_file("xss.svg") |
|
|||
526 | response = self.app.get(url(controller='files', action='raw', |
|
|||
527 | repo_name=backend.repo_name, |
|
|||
528 | revision='tip', |
|
|||
529 | f_path='xss.svg')) |
|
|||
530 |
|
||||
531 | # If the content type is image/svg+xml then it allows to render HTML |
|
|||
532 | # and malicious SVG. |
|
|||
533 | assert response.content_type == "text/plain" |
|
|||
534 |
|
565 | |||
535 |
|
566 | |||
536 | @pytest.mark.usefixtures("app") |
|
567 | @pytest.mark.usefixtures("app") | |
537 | class TestFilesDiff: |
|
568 | class TestFilesDiff(object): | |
538 |
|
569 | |||
539 | @pytest.mark.parametrize("diff", ['diff', 'download', 'raw']) |
|
570 | @pytest.mark.parametrize("diff", ['diff', 'download', 'raw']) | |
540 | def test_file_full_diff(self, backend, diff): |
|
571 | def test_file_full_diff(self, backend, diff): | |
541 | commit1 = backend.repo.get_commit(commit_idx=-1) |
|
572 | commit1 = backend.repo.get_commit(commit_idx=-1) | |
542 | commit2 = backend.repo.get_commit(commit_idx=-2) |
|
573 | commit2 = backend.repo.get_commit(commit_idx=-2) | |
543 |
|
574 | |||
544 | response = self.app.get( |
|
575 | response = self.app.get( | |
545 | url( |
|
576 | route_path('repo_files_diff', | |
546 | controller='files', |
|
577 | repo_name=backend.repo_name, | |
547 |
|
|
578 | f_path='README'), | |
548 | repo_name=backend.repo_name, |
|
|||
549 | f_path='README'), |
|
|||
550 | params={ |
|
579 | params={ | |
551 | 'diff1': commit2.raw_id, |
|
580 | 'diff1': commit2.raw_id, | |
552 | 'diff2': commit1.raw_id, |
|
581 | 'diff2': commit1.raw_id, | |
553 | 'fulldiff': '1', |
|
582 | 'fulldiff': '1', | |
554 | 'diff': diff, |
|
583 | 'diff': diff, | |
555 | }) |
|
584 | }) | |
556 |
|
585 | |||
557 | if diff == 'diff': |
|
586 | if diff == 'diff': | |
558 | # use redirect since this is OLD view redirecting to compare page |
|
587 | # use redirect since this is OLD view redirecting to compare page | |
559 | response = response.follow() |
|
588 | response = response.follow() | |
560 |
|
589 | |||
561 | # It's a symlink to README.rst |
|
590 | # It's a symlink to README.rst | |
562 | response.mustcontain('README.rst') |
|
591 | response.mustcontain('README.rst') | |
563 | response.mustcontain('No newline at end of file') |
|
592 | response.mustcontain('No newline at end of file') | |
564 |
|
593 | |||
565 | def test_file_binary_diff(self, backend): |
|
594 | def test_file_binary_diff(self, backend): | |
566 | commits = [ |
|
595 | commits = [ | |
567 | {'message': 'First commit'}, |
|
596 | {'message': 'First commit'}, | |
568 | {'message': 'Commit with binary', |
|
597 | {'message': 'Commit with binary', | |
569 | 'added': [nodes.FileNode('file.bin', content='\0BINARY\0')]}, |
|
598 | 'added': [nodes.FileNode('file.bin', content='\0BINARY\0')]}, | |
570 | ] |
|
599 | ] | |
571 | repo = backend.create_repo(commits=commits) |
|
600 | repo = backend.create_repo(commits=commits) | |
572 |
|
601 | |||
573 | response = self.app.get( |
|
602 | response = self.app.get( | |
574 | url( |
|
603 | route_path('repo_files_diff', | |
575 | controller='files', |
|
604 | repo_name=backend.repo_name, | |
576 |
|
|
605 | f_path='file.bin'), | |
577 | repo_name=backend.repo_name, |
|
|||
578 | f_path='file.bin'), |
|
|||
579 | params={ |
|
606 | params={ | |
580 | 'diff1': repo.get_commit(commit_idx=0).raw_id, |
|
607 | 'diff1': repo.get_commit(commit_idx=0).raw_id, | |
581 | 'diff2': repo.get_commit(commit_idx=1).raw_id, |
|
608 | 'diff2': repo.get_commit(commit_idx=1).raw_id, | |
582 | 'fulldiff': '1', |
|
609 | 'fulldiff': '1', | |
583 | 'diff': 'diff', |
|
610 | 'diff': 'diff', | |
584 | }) |
|
611 | }) | |
585 | # use redirect since this is OLD view redirecting to compare page |
|
612 | # use redirect since this is OLD view redirecting to compare page | |
586 | response = response.follow() |
|
613 | response = response.follow() | |
587 | response.mustcontain('Expand 1 commit') |
|
614 | response.mustcontain('Expand 1 commit') | |
588 | response.mustcontain('1 file changed: 0 inserted, 0 deleted') |
|
615 | response.mustcontain('1 file changed: 0 inserted, 0 deleted') | |
589 |
|
616 | |||
590 | if backend.alias == 'svn': |
|
617 | if backend.alias == 'svn': | |
591 | response.mustcontain('new file 10644') |
|
618 | response.mustcontain('new file 10644') | |
592 | # TODO(marcink): SVN doesn't yet detect binary changes |
|
619 | # TODO(marcink): SVN doesn't yet detect binary changes | |
593 | else: |
|
620 | else: | |
594 | response.mustcontain('new file 100644') |
|
621 | response.mustcontain('new file 100644') | |
595 | response.mustcontain('binary diff hidden') |
|
622 | response.mustcontain('binary diff hidden') | |
596 |
|
623 | |||
597 | def test_diff_2way(self, backend): |
|
624 | def test_diff_2way(self, backend): | |
598 | commit1 = backend.repo.get_commit(commit_idx=-1) |
|
625 | commit1 = backend.repo.get_commit(commit_idx=-1) | |
599 | commit2 = backend.repo.get_commit(commit_idx=-2) |
|
626 | commit2 = backend.repo.get_commit(commit_idx=-2) | |
600 | response = self.app.get( |
|
627 | response = self.app.get( | |
601 | url( |
|
628 | route_path('repo_files_diff_2way_redirect', | |
602 | controller='files', |
|
629 | repo_name=backend.repo_name, | |
603 | action='diff_2way', |
|
630 | f_path='README'), | |
604 | repo_name=backend.repo_name, |
|
|||
605 | f_path='README'), |
|
|||
606 | params={ |
|
631 | params={ | |
607 | 'diff1': commit2.raw_id, |
|
632 | 'diff1': commit2.raw_id, | |
608 | 'diff2': commit1.raw_id, |
|
633 | 'diff2': commit1.raw_id, | |
609 | }) |
|
634 | }) | |
610 | # use redirect since this is OLD view redirecting to compare page |
|
635 | # use redirect since this is OLD view redirecting to compare page | |
611 | response = response.follow() |
|
636 | response = response.follow() | |
612 |
|
637 | |||
613 | # It's a symlink to README.rst |
|
638 | # It's a symlink to README.rst | |
614 | response.mustcontain('README.rst') |
|
639 | response.mustcontain('README.rst') | |
615 | response.mustcontain('No newline at end of file') |
|
640 | response.mustcontain('No newline at end of file') | |
616 |
|
641 | |||
617 | def test_requires_one_commit_id(self, backend, autologin_user): |
|
642 | def test_requires_one_commit_id(self, backend, autologin_user): | |
618 | response = self.app.get( |
|
643 | response = self.app.get( | |
619 | url( |
|
644 | route_path('repo_files_diff', | |
620 | controller='files', |
|
645 | repo_name=backend.repo_name, | |
621 | action='diff', |
|
646 | f_path='README.rst'), | |
622 | repo_name=backend.repo_name, |
|
|||
623 | f_path='README.rst'), |
|
|||
624 | status=400) |
|
647 | status=400) | |
625 | response.mustcontain( |
|
648 | response.mustcontain( | |
626 | 'Need query parameter', 'diff1', 'diff2', 'to generate a diff.') |
|
649 | 'Need query parameter', 'diff1', 'diff2', 'to generate a diff.') | |
627 |
|
650 | |||
628 | def test_returns_no_files_if_file_does_not_exist(self, vcsbackend): |
|
651 | def test_returns_no_files_if_file_does_not_exist(self, vcsbackend): | |
629 | repo = vcsbackend.repo |
|
652 | repo = vcsbackend.repo | |
630 | response = self.app.get( |
|
653 | response = self.app.get( | |
631 | url( |
|
654 | route_path('repo_files_diff', | |
632 | controller='files', |
|
655 | repo_name=repo.name, | |
633 | action='diff', |
|
656 | f_path='does-not-exist-in-any-commit'), | |
634 | repo_name=repo.name, |
|
657 | params={ | |
635 | f_path='does-not-exist-in-any-commit', |
|
658 | 'diff1': repo[0].raw_id, | |
636 |
|
|
659 | 'diff2': repo[1].raw_id | |
637 | diff2=repo[1].raw_id),) |
|
660 | }) | |
638 |
|
661 | |||
639 | response = response.follow() |
|
662 | response = response.follow() | |
640 | response.mustcontain('No files') |
|
663 | response.mustcontain('No files') | |
641 |
|
664 | |||
642 | def test_returns_redirect_if_file_not_changed(self, backend): |
|
665 | def test_returns_redirect_if_file_not_changed(self, backend): | |
643 | commit = backend.repo.get_commit(commit_idx=-1) |
|
666 | commit = backend.repo.get_commit(commit_idx=-1) | |
644 | f_path = 'README' |
|
|||
645 | response = self.app.get( |
|
667 | response = self.app.get( | |
646 | url( |
|
668 | route_path('repo_files_diff_2way_redirect', | |
647 | controller='files', |
|
669 | repo_name=backend.repo_name, | |
648 | action='diff_2way', |
|
670 | f_path='README'), | |
649 | repo_name=backend.repo_name, |
|
671 | params={ | |
650 | f_path=f_path, |
|
672 | 'diff1': commit.raw_id, | |
651 |
|
|
673 | 'diff2': commit.raw_id, | |
652 | diff2=commit.raw_id, |
|
674 | }) | |
653 | ), |
|
675 | ||
654 | ) |
|
|||
655 | response = response.follow() |
|
676 | response = response.follow() | |
656 | response.mustcontain('No files') |
|
677 | response.mustcontain('No files') | |
657 | response.mustcontain('No commits in this compare') |
|
678 | response.mustcontain('No commits in this compare') | |
658 |
|
679 | |||
659 | def test_supports_diff_to_different_path_svn(self, backend_svn): |
|
680 | def test_supports_diff_to_different_path_svn(self, backend_svn): | |
660 | #TODO: check this case |
|
681 | #TODO: check this case | |
661 | return |
|
682 | return | |
662 |
|
683 | |||
663 | repo = backend_svn['svn-simple-layout'].scm_instance() |
|
684 | repo = backend_svn['svn-simple-layout'].scm_instance() | |
664 | commit_id_1 = '24' |
|
685 | commit_id_1 = '24' | |
665 | commit_id_2 = '26' |
|
686 | commit_id_2 = '26' | |
666 |
|
687 | |||
667 |
|
||||
668 | print( url( |
|
|||
669 | controller='files', |
|
|||
670 | action='diff', |
|
|||
671 | repo_name=repo.name, |
|
|||
672 | f_path='trunk/example.py', |
|
|||
673 | diff1='tags/v0.2/example.py@' + commit_id_1, |
|
|||
674 | diff2=commit_id_2)) |
|
|||
675 |
|
||||
676 | response = self.app.get( |
|
688 | response = self.app.get( | |
677 | url( |
|
689 | route_path('repo_files_diff', | |
678 | controller='files', |
|
690 | repo_name=backend_svn.repo_name, | |
679 | action='diff', |
|
691 | f_path='trunk/example.py'), | |
680 | repo_name=repo.name, |
|
692 | params={ | |
681 |
|
|
693 | 'diff1': 'tags/v0.2/example.py@' + commit_id_1, | |
682 |
|
|
694 | 'diff2': commit_id_2, | |
683 | diff2=commit_id_2)) |
|
695 | }) | |
684 |
|
696 | |||
685 | response = response.follow() |
|
697 | response = response.follow() | |
686 | response.mustcontain( |
|
698 | response.mustcontain( | |
687 | # diff contains this |
|
699 | # diff contains this | |
688 | "Will print out a useful message on invocation.") |
|
700 | "Will print out a useful message on invocation.") | |
689 |
|
701 | |||
690 | # Note: Expecting that we indicate the user what's being compared |
|
702 | # Note: Expecting that we indicate the user what's being compared | |
691 | response.mustcontain("trunk/example.py") |
|
703 | response.mustcontain("trunk/example.py") | |
692 | response.mustcontain("tags/v0.2/example.py") |
|
704 | response.mustcontain("tags/v0.2/example.py") | |
693 |
|
705 | |||
694 | def test_show_rev_redirects_to_svn_path(self, backend_svn): |
|
706 | def test_show_rev_redirects_to_svn_path(self, backend_svn): | |
695 | #TODO: check this case |
|
707 | #TODO: check this case | |
696 | return |
|
708 | return | |
697 |
|
709 | |||
698 | repo = backend_svn['svn-simple-layout'].scm_instance() |
|
710 | repo = backend_svn['svn-simple-layout'].scm_instance() | |
699 | commit_id = repo[-1].raw_id |
|
711 | commit_id = repo[-1].raw_id | |
|
712 | ||||
700 | response = self.app.get( |
|
713 | response = self.app.get( | |
701 | url( |
|
714 | route_path('repo_files_diff', | |
702 | controller='files', |
|
715 | repo_name=backend_svn.repo_name, | |
703 | action='diff', |
|
716 | f_path='trunk/example.py'), | |
704 | repo_name=repo.name, |
|
717 | params={ | |
705 | f_path='trunk/example.py', |
|
718 | 'diff1': 'branches/argparse/example.py@' + commit_id, | |
706 |
|
|
719 | 'diff2': commit_id, | |
707 | diff2=commit_id), |
|
720 | }, | |
708 | params={'show_rev': 'Show at Revision'}, |
|
|||
709 | status=302) |
|
721 | status=302) | |
|
722 | response = response.follow() | |||
710 | assert response.headers['Location'].endswith( |
|
723 | assert response.headers['Location'].endswith( | |
711 | 'svn-svn-simple-layout/files/26/branches/argparse/example.py') |
|
724 | 'svn-svn-simple-layout/files/26/branches/argparse/example.py') | |
712 |
|
725 | |||
713 | def test_show_rev_and_annotate_redirects_to_svn_path(self, backend_svn): |
|
726 | def test_show_rev_and_annotate_redirects_to_svn_path(self, backend_svn): | |
714 | #TODO: check this case |
|
727 | #TODO: check this case | |
715 | return |
|
728 | return | |
716 |
|
729 | |||
717 | repo = backend_svn['svn-simple-layout'].scm_instance() |
|
730 | repo = backend_svn['svn-simple-layout'].scm_instance() | |
718 | commit_id = repo[-1].raw_id |
|
731 | commit_id = repo[-1].raw_id | |
719 | response = self.app.get( |
|
732 | response = self.app.get( | |
720 | url( |
|
733 | route_path('repo_files_diff', | |
721 | controller='files', |
|
734 | repo_name=backend_svn.repo_name, | |
722 | action='diff', |
|
735 | f_path='trunk/example.py'), | |
723 | repo_name=repo.name, |
|
|||
724 | f_path='trunk/example.py', |
|
|||
725 | diff1='branches/argparse/example.py@' + commit_id, |
|
|||
726 | diff2=commit_id), |
|
|||
727 | params={ |
|
736 | params={ | |
|
737 | 'diff1': 'branches/argparse/example.py@' + commit_id, | |||
|
738 | 'diff2': commit_id, | |||
728 | 'show_rev': 'Show at Revision', |
|
739 | 'show_rev': 'Show at Revision', | |
729 | 'annotate': 'true', |
|
740 | 'annotate': 'true', | |
730 | }, |
|
741 | }, | |
731 | status=302) |
|
742 | status=302) | |
|
743 | response = response.follow() | |||
732 | assert response.headers['Location'].endswith( |
|
744 | assert response.headers['Location'].endswith( | |
733 | 'svn-svn-simple-layout/annotate/26/branches/argparse/example.py') |
|
745 | 'svn-svn-simple-layout/annotate/26/branches/argparse/example.py') | |
734 |
|
746 | |||
735 |
|
747 | |||
736 | @pytest.mark.usefixtures("app", "autologin_user") |
|
748 | @pytest.mark.usefixtures("app", "autologin_user") | |
737 | class TestChangingFiles: |
|
749 | class TestModifyFilesWithWebInterface(object): | |
738 |
|
750 | |||
739 | def test_add_file_view(self, backend): |
|
751 | def test_add_file_view(self, backend): | |
740 |
self.app.get( |
|
752 | self.app.get( | |
741 |
'files_add_ |
|
753 | route_path('repo_files_add_file', | |
742 | repo_name=backend.repo_name, |
|
754 | repo_name=backend.repo_name, | |
743 |
|
|
755 | commit_id='tip', f_path='/') | |
|
756 | ) | |||
744 |
|
757 | |||
745 | @pytest.mark.xfail_backends("svn", reason="Depends on online editing") |
|
758 | @pytest.mark.xfail_backends("svn", reason="Depends on online editing") | |
746 | def test_add_file_into_repo_missing_content(self, backend, csrf_token): |
|
759 | def test_add_file_into_repo_missing_content(self, backend, csrf_token): | |
747 | repo = backend.create_repo() |
|
760 | repo = backend.create_repo() | |
748 | filename = 'init.py' |
|
761 | filename = 'init.py' | |
749 | response = self.app.post( |
|
762 | response = self.app.post( | |
750 | url( |
|
763 | route_path('repo_files_create_file', | |
751 | 'files_add', |
|
764 | repo_name=backend.repo_name, | |
752 | repo_name=repo.repo_name, |
|
765 | commit_id='tip', f_path='/'), | |
753 | revision='tip', f_path='/'), |
|
|||
754 | params={ |
|
766 | params={ | |
755 | 'content': "", |
|
767 | 'content': "", | |
756 | 'filename': filename, |
|
768 | 'filename': filename, | |
757 | 'location': "", |
|
769 | 'location': "", | |
758 | 'csrf_token': csrf_token, |
|
770 | 'csrf_token': csrf_token, | |
759 | }, |
|
771 | }, | |
760 | status=302) |
|
772 | status=302) | |
761 | assert_session_flash(response, |
|
773 | assert_session_flash(response, | |
762 |
'Successfully committed new file `{}`'.format( |
|
774 | 'Successfully committed new file `{}`'.format( | |
|
775 | os.path.join(filename))) | |||
763 |
|
776 | |||
764 | def test_add_file_into_repo_missing_filename(self, backend, csrf_token): |
|
777 | def test_add_file_into_repo_missing_filename(self, backend, csrf_token): | |
765 | response = self.app.post( |
|
778 | response = self.app.post( | |
766 | url( |
|
779 | route_path('repo_files_create_file', | |
767 | 'files_add', |
|
780 | repo_name=backend.repo_name, | |
768 | repo_name=backend.repo_name, |
|
781 | commit_id='tip', f_path='/'), | |
769 | revision='tip', f_path='/'), |
|
|||
770 | params={ |
|
782 | params={ | |
771 | 'content': "foo", |
|
783 | 'content': "foo", | |
772 | 'csrf_token': csrf_token, |
|
784 | 'csrf_token': csrf_token, | |
773 | }, |
|
785 | }, | |
774 | status=302) |
|
786 | status=302) | |
775 |
|
787 | |||
776 | assert_session_flash(response, 'No filename') |
|
788 | assert_session_flash(response, 'No filename') | |
777 |
|
789 | |||
778 | def test_add_file_into_repo_errors_and_no_commits( |
|
790 | def test_add_file_into_repo_errors_and_no_commits( | |
779 | self, backend, csrf_token): |
|
791 | self, backend, csrf_token): | |
780 | repo = backend.create_repo() |
|
792 | repo = backend.create_repo() | |
781 | # Create a file with no filename, it will display an error but |
|
793 | # Create a file with no filename, it will display an error but | |
782 | # the repo has no commits yet |
|
794 | # the repo has no commits yet | |
783 | response = self.app.post( |
|
795 | response = self.app.post( | |
784 | url( |
|
796 | route_path('repo_files_create_file', | |
785 | 'files_add', |
|
797 | repo_name=repo.repo_name, | |
786 | repo_name=repo.repo_name, |
|
798 | commit_id='tip', f_path='/'), | |
787 | revision='tip', f_path='/'), |
|
|||
788 | params={ |
|
799 | params={ | |
789 | 'content': "foo", |
|
800 | 'content': "foo", | |
790 | 'csrf_token': csrf_token, |
|
801 | 'csrf_token': csrf_token, | |
791 | }, |
|
802 | }, | |
792 | status=302) |
|
803 | status=302) | |
793 |
|
804 | |||
794 | assert_session_flash(response, 'No filename') |
|
805 | assert_session_flash(response, 'No filename') | |
795 |
|
806 | |||
796 | # Not allowed, redirect to the summary |
|
807 | # Not allowed, redirect to the summary | |
797 | redirected = response.follow() |
|
808 | redirected = response.follow() | |
798 | summary_url = h.route_path('repo_summary', repo_name=repo.repo_name) |
|
809 | summary_url = h.route_path('repo_summary', repo_name=repo.repo_name) | |
799 |
|
810 | |||
800 | # As there are no commits, displays the summary page with the error of |
|
811 | # As there are no commits, displays the summary page with the error of | |
801 | # creating a file with no filename |
|
812 | # creating a file with no filename | |
802 |
|
813 | |||
803 | assert redirected.request.path == summary_url |
|
814 | assert redirected.request.path == summary_url | |
804 |
|
815 | |||
805 | @pytest.mark.parametrize("location, filename", [ |
|
816 | @pytest.mark.parametrize("location, filename", [ | |
806 | ('/abs', 'foo'), |
|
817 | ('/abs', 'foo'), | |
807 | ('../rel', 'foo'), |
|
818 | ('../rel', 'foo'), | |
808 | ('file/../foo', 'foo'), |
|
819 | ('file/../foo', 'foo'), | |
809 | ]) |
|
820 | ]) | |
810 | def test_add_file_into_repo_bad_filenames( |
|
821 | def test_add_file_into_repo_bad_filenames( | |
811 | self, location, filename, backend, csrf_token): |
|
822 | self, location, filename, backend, csrf_token): | |
812 | response = self.app.post( |
|
823 | response = self.app.post( | |
813 | url( |
|
824 | route_path('repo_files_create_file', | |
814 | 'files_add', |
|
825 | repo_name=backend.repo_name, | |
815 | repo_name=backend.repo_name, |
|
826 | commit_id='tip', f_path='/'), | |
816 | revision='tip', f_path='/'), |
|
|||
817 | params={ |
|
827 | params={ | |
818 | 'content': "foo", |
|
828 | 'content': "foo", | |
819 | 'filename': filename, |
|
829 | 'filename': filename, | |
820 | 'location': location, |
|
830 | 'location': location, | |
821 | 'csrf_token': csrf_token, |
|
831 | 'csrf_token': csrf_token, | |
822 | }, |
|
832 | }, | |
823 | status=302) |
|
833 | status=302) | |
824 |
|
834 | |||
825 | assert_session_flash( |
|
835 | assert_session_flash( | |
826 | response, |
|
836 | response, | |
827 | 'The location specified must be a relative path and must not ' |
|
837 | 'The location specified must be a relative path and must not ' | |
828 | 'contain .. in the path') |
|
838 | 'contain .. in the path') | |
829 |
|
839 | |||
830 | @pytest.mark.parametrize("cnt, location, filename", [ |
|
840 | @pytest.mark.parametrize("cnt, location, filename", [ | |
831 | (1, '', 'foo.txt'), |
|
841 | (1, '', 'foo.txt'), | |
832 | (2, 'dir', 'foo.rst'), |
|
842 | (2, 'dir', 'foo.rst'), | |
833 | (3, 'rel/dir', 'foo.bar'), |
|
843 | (3, 'rel/dir', 'foo.bar'), | |
834 | ]) |
|
844 | ]) | |
835 | def test_add_file_into_repo(self, cnt, location, filename, backend, |
|
845 | def test_add_file_into_repo(self, cnt, location, filename, backend, | |
836 | csrf_token): |
|
846 | csrf_token): | |
837 | repo = backend.create_repo() |
|
847 | repo = backend.create_repo() | |
838 | response = self.app.post( |
|
848 | response = self.app.post( | |
839 | url( |
|
849 | route_path('repo_files_create_file', | |
840 | 'files_add', |
|
850 | repo_name=repo.repo_name, | |
841 | repo_name=repo.repo_name, |
|
851 | commit_id='tip', f_path='/'), | |
842 | revision='tip', f_path='/'), |
|
|||
843 | params={ |
|
852 | params={ | |
844 | 'content': "foo", |
|
853 | 'content': "foo", | |
845 | 'filename': filename, |
|
854 | 'filename': filename, | |
846 | 'location': location, |
|
855 | 'location': location, | |
847 | 'csrf_token': csrf_token, |
|
856 | 'csrf_token': csrf_token, | |
848 | }, |
|
857 | }, | |
849 | status=302) |
|
858 | status=302) | |
850 | assert_session_flash(response, |
|
859 | assert_session_flash(response, | |
851 | 'Successfully committed new file `{}`'.format( |
|
860 | 'Successfully committed new file `{}`'.format( | |
852 | os.path.join(location, filename))) |
|
861 | os.path.join(location, filename))) | |
853 |
|
862 | |||
854 | def test_edit_file_view(self, backend): |
|
863 | def test_edit_file_view(self, backend): | |
855 | response = self.app.get( |
|
864 | response = self.app.get( | |
856 | url( |
|
865 | route_path('repo_files_edit_file', | |
857 | 'files_edit_home', |
|
866 | repo_name=backend.repo_name, | |
858 |
|
|
867 | commit_id=backend.default_head_id, | |
859 | revision=backend.default_head_id, |
|
868 | f_path='vcs/nodes.py'), | |
860 | f_path='vcs/nodes.py'), |
|
|||
861 | status=200) |
|
869 | status=200) | |
862 | response.mustcontain("Module holding everything related to vcs nodes.") |
|
870 | response.mustcontain("Module holding everything related to vcs nodes.") | |
863 |
|
871 | |||
864 | def test_edit_file_view_not_on_branch(self, backend): |
|
872 | def test_edit_file_view_not_on_branch(self, backend): | |
865 | repo = backend.create_repo() |
|
873 | repo = backend.create_repo() | |
866 | backend.ensure_file("vcs/nodes.py") |
|
874 | backend.ensure_file("vcs/nodes.py") | |
867 |
|
875 | |||
868 | response = self.app.get( |
|
876 | response = self.app.get( | |
869 | url( |
|
877 | route_path('repo_files_edit_file', | |
870 | 'files_edit_home', |
|
878 | repo_name=repo.repo_name, | |
871 | repo_name=repo.repo_name, |
|
879 | commit_id='tip', | |
872 |
|
|
880 | f_path='vcs/nodes.py'), | |
873 | status=302) |
|
881 | status=302) | |
874 | assert_session_flash( |
|
882 | assert_session_flash( | |
875 | response, |
|
883 | response, | |
876 |
'You can only edit files with |
|
884 | 'You can only edit files with commit being a valid branch') | |
877 |
|
885 | |||
878 | def test_edit_file_view_commit_changes(self, backend, csrf_token): |
|
886 | def test_edit_file_view_commit_changes(self, backend, csrf_token): | |
879 | repo = backend.create_repo() |
|
887 | repo = backend.create_repo() | |
880 | backend.ensure_file("vcs/nodes.py", content="print 'hello'") |
|
888 | backend.ensure_file("vcs/nodes.py", content="print 'hello'") | |
881 |
|
889 | |||
882 | response = self.app.post( |
|
890 | response = self.app.post( | |
883 | url( |
|
891 | route_path('repo_files_update_file', | |
884 | 'files_edit', |
|
892 | repo_name=repo.repo_name, | |
885 | repo_name=repo.repo_name, |
|
893 | commit_id=backend.default_head_id, | |
886 | revision=backend.default_head_id, |
|
894 | f_path='vcs/nodes.py'), | |
887 | f_path='vcs/nodes.py'), |
|
|||
888 | params={ |
|
895 | params={ | |
889 | 'content': "print 'hello world'", |
|
896 | 'content': "print 'hello world'", | |
890 | 'message': 'I committed', |
|
897 | 'message': 'I committed', | |
891 | 'filename': "vcs/nodes.py", |
|
898 | 'filename': "vcs/nodes.py", | |
892 | 'csrf_token': csrf_token, |
|
899 | 'csrf_token': csrf_token, | |
893 | }, |
|
900 | }, | |
894 | status=302) |
|
901 | status=302) | |
895 | assert_session_flash( |
|
902 | assert_session_flash( | |
896 | response, 'Successfully committed changes to file `vcs/nodes.py`') |
|
903 | response, 'Successfully committed changes to file `vcs/nodes.py`') | |
897 | tip = repo.get_commit(commit_idx=-1) |
|
904 | tip = repo.get_commit(commit_idx=-1) | |
898 | assert tip.message == 'I committed' |
|
905 | assert tip.message == 'I committed' | |
899 |
|
906 | |||
900 | def test_edit_file_view_commit_changes_default_message(self, backend, |
|
907 | def test_edit_file_view_commit_changes_default_message(self, backend, | |
901 | csrf_token): |
|
908 | csrf_token): | |
902 | repo = backend.create_repo() |
|
909 | repo = backend.create_repo() | |
903 | backend.ensure_file("vcs/nodes.py", content="print 'hello'") |
|
910 | backend.ensure_file("vcs/nodes.py", content="print 'hello'") | |
904 |
|
911 | |||
905 | commit_id = ( |
|
912 | commit_id = ( | |
906 | backend.default_branch_name or |
|
913 | backend.default_branch_name or | |
907 | backend.repo.scm_instance().commit_ids[-1]) |
|
914 | backend.repo.scm_instance().commit_ids[-1]) | |
908 |
|
915 | |||
909 | response = self.app.post( |
|
916 | response = self.app.post( | |
910 | url( |
|
917 | route_path('repo_files_update_file', | |
911 | 'files_edit', |
|
918 | repo_name=repo.repo_name, | |
912 | repo_name=repo.repo_name, |
|
919 | commit_id=commit_id, | |
913 | revision=commit_id, |
|
920 | f_path='vcs/nodes.py'), | |
914 | f_path='vcs/nodes.py'), |
|
|||
915 | params={ |
|
921 | params={ | |
916 | 'content': "print 'hello world'", |
|
922 | 'content': "print 'hello world'", | |
917 | 'message': '', |
|
923 | 'message': '', | |
918 | 'filename': "vcs/nodes.py", |
|
924 | 'filename': "vcs/nodes.py", | |
919 | 'csrf_token': csrf_token, |
|
925 | 'csrf_token': csrf_token, | |
920 | }, |
|
926 | }, | |
921 | status=302) |
|
927 | status=302) | |
922 | assert_session_flash( |
|
928 | assert_session_flash( | |
923 | response, 'Successfully committed changes to file `vcs/nodes.py`') |
|
929 | response, 'Successfully committed changes to file `vcs/nodes.py`') | |
924 | tip = repo.get_commit(commit_idx=-1) |
|
930 | tip = repo.get_commit(commit_idx=-1) | |
925 | assert tip.message == 'Edited file vcs/nodes.py via RhodeCode Enterprise' |
|
931 | assert tip.message == 'Edited file vcs/nodes.py via RhodeCode Enterprise' | |
926 |
|
932 | |||
927 | def test_delete_file_view(self, backend): |
|
933 | def test_delete_file_view(self, backend): | |
928 |
self.app.get( |
|
934 | self.app.get( | |
929 |
'files_ |
|
935 | route_path('repo_files_remove_file', | |
930 | repo_name=backend.repo_name, |
|
936 | repo_name=backend.repo_name, | |
931 | revision='tip', f_path='vcs/nodes.py')) |
|
937 | commit_id=backend.default_head_id, | |
|
938 | f_path='vcs/nodes.py'), | |||
|
939 | status=200) | |||
932 |
|
940 | |||
933 | def test_delete_file_view_not_on_branch(self, backend): |
|
941 | def test_delete_file_view_not_on_branch(self, backend): | |
934 | repo = backend.create_repo() |
|
942 | repo = backend.create_repo() | |
935 | backend.ensure_file('vcs/nodes.py') |
|
943 | backend.ensure_file('vcs/nodes.py') | |
936 |
|
944 | |||
937 | response = self.app.get( |
|
945 | response = self.app.get( | |
938 | url( |
|
946 | route_path('repo_files_remove_file', | |
939 | 'files_delete_home', |
|
947 | repo_name=repo.repo_name, | |
940 | repo_name=repo.repo_name, |
|
948 | commit_id='tip', | |
941 |
|
|
949 | f_path='vcs/nodes.py'), | |
942 | status=302) |
|
950 | status=302) | |
943 | assert_session_flash( |
|
951 | assert_session_flash( | |
944 | response, |
|
952 | response, | |
945 |
'You can only delete files with |
|
953 | 'You can only delete files with commit being a valid branch') | |
946 |
|
954 | |||
947 | def test_delete_file_view_commit_changes(self, backend, csrf_token): |
|
955 | def test_delete_file_view_commit_changes(self, backend, csrf_token): | |
948 | repo = backend.create_repo() |
|
956 | repo = backend.create_repo() | |
949 | backend.ensure_file("vcs/nodes.py") |
|
957 | backend.ensure_file("vcs/nodes.py") | |
950 |
|
958 | |||
951 | response = self.app.post( |
|
959 | response = self.app.post( | |
952 | url( |
|
960 | route_path('repo_files_delete_file', | |
953 | 'files_delete_home', |
|
961 | repo_name=repo.repo_name, | |
954 | repo_name=repo.repo_name, |
|
962 | commit_id=backend.default_head_id, | |
955 | revision=backend.default_head_id, |
|
963 | f_path='vcs/nodes.py'), | |
956 | f_path='vcs/nodes.py'), |
|
|||
957 | params={ |
|
964 | params={ | |
958 | 'message': 'i commited', |
|
965 | 'message': 'i commited', | |
959 | 'csrf_token': csrf_token, |
|
966 | 'csrf_token': csrf_token, | |
960 | }, |
|
967 | }, | |
961 | status=302) |
|
968 | status=302) | |
962 | assert_session_flash( |
|
969 | assert_session_flash( | |
963 | response, 'Successfully deleted file `vcs/nodes.py`') |
|
970 | response, 'Successfully deleted file `vcs/nodes.py`') | |
964 |
|
971 | |||
965 |
|
972 | |||
966 | def assert_files_in_response(response, files, params): |
|
973 | @pytest.mark.usefixtures("app") | |
967 | template = ( |
|
974 | class TestFilesViewOtherCases(object): | |
968 | 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"') |
|
975 | ||
969 | _assert_items_in_response(response, files, template, params) |
|
976 | def test_access_empty_repo_redirect_to_summary_with_alert_write_perms( | |
|
977 | self, backend_stub, autologin_regular_user, user_regular, | |||
|
978 | user_util): | |||
|
979 | ||||
|
980 | repo = backend_stub.create_repo() | |||
|
981 | user_util.grant_user_permission_to_repo( | |||
|
982 | repo, user_regular, 'repository.write') | |||
|
983 | response = self.app.get( | |||
|
984 | route_path('repo_files', | |||
|
985 | repo_name=repo.repo_name, | |||
|
986 | commit_id='tip', f_path='/')) | |||
|
987 | ||||
|
988 | repo_file_add_url = route_path( | |||
|
989 | 'repo_files_add_file', | |||
|
990 | repo_name=repo.repo_name, | |||
|
991 | commit_id=0, f_path='') + '#edit' | |||
|
992 | ||||
|
993 | assert_session_flash( | |||
|
994 | response, | |||
|
995 | 'There are no files yet. <a class="alert-link" ' | |||
|
996 | 'href="{}">Click here to add a new file.</a>' | |||
|
997 | .format(repo_file_add_url)) | |||
|
998 | ||||
|
999 | def test_access_empty_repo_redirect_to_summary_with_alert_no_write_perms( | |||
|
1000 | self, backend_stub, user_util): | |||
|
1001 | repo = backend_stub.create_repo() | |||
|
1002 | repo_file_add_url = route_path( | |||
|
1003 | 'repo_files_add_file', | |||
|
1004 | repo_name=repo.repo_name, | |||
|
1005 | commit_id=0, f_path='') + '#edit' | |||
|
1006 | ||||
|
1007 | response = self.app.get( | |||
|
1008 | route_path('repo_files', | |||
|
1009 | repo_name=repo.repo_name, | |||
|
1010 | commit_id='tip', f_path='/')) | |||
|
1011 | ||||
|
1012 | assert_session_flash(response, no_=repo_file_add_url) | |||
|
1013 | ||||
|
1014 | @pytest.mark.parametrize('file_node', [ | |||
|
1015 | 'archive/file.zip', | |||
|
1016 | 'diff/my-file.txt', | |||
|
1017 | 'render.py', | |||
|
1018 | 'render', | |||
|
1019 | 'remove_file', | |||
|
1020 | 'remove_file/to-delete.txt', | |||
|
1021 | ]) | |||
|
1022 | def test_file_names_equal_to_routes_parts(self, backend, file_node): | |||
|
1023 | backend.create_repo() | |||
|
1024 | backend.ensure_file(file_node) | |||
|
1025 | ||||
|
1026 | self.app.get( | |||
|
1027 | route_path('repo_files', | |||
|
1028 | repo_name=backend.repo_name, | |||
|
1029 | commit_id='tip', f_path=file_node), | |||
|
1030 | status=200) | |||
970 |
|
1031 | |||
971 |
|
1032 | |||
972 | def assert_dirs_in_response(response, dirs, params): |
|
1033 | class TestAdjustFilePathForSvn(object): | |
973 | template = ( |
|
1034 | """ | |
974 | 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"') |
|
1035 | SVN specific adjustments of node history in RepoFilesView. | |
975 | _assert_items_in_response(response, dirs, template, params) |
|
1036 | """ | |
976 |
|
1037 | |||
|
1038 | def test_returns_path_relative_to_matched_reference(self): | |||
|
1039 | repo = self._repo(branches=['trunk']) | |||
|
1040 | self.assert_file_adjustment('trunk/file', 'file', repo) | |||
|
1041 | ||||
|
1042 | def test_does_not_modify_file_if_no_reference_matches(self): | |||
|
1043 | repo = self._repo(branches=['trunk']) | |||
|
1044 | self.assert_file_adjustment('notes/file', 'notes/file', repo) | |||
977 |
|
1045 | |||
978 | def _assert_items_in_response(response, items, template, params): |
|
1046 | def test_does_not_adjust_partial_directory_names(self): | |
979 | for item in items: |
|
1047 | repo = self._repo(branches=['trun']) | |
980 | item_params = {'name': item} |
|
1048 | self.assert_file_adjustment('trunk/file', 'trunk/file', repo) | |
981 | item_params.update(params) |
|
1049 | ||
982 | response.mustcontain(template % item_params) |
|
1050 | def test_is_robust_to_patterns_which_prefix_other_patterns(self): | |
|
1051 | repo = self._repo(branches=['trunk', 'trunk/new', 'trunk/old']) | |||
|
1052 | self.assert_file_adjustment('trunk/new/file', 'file', repo) | |||
983 |
|
1053 | |||
|
1054 | def assert_file_adjustment(self, f_path, expected, repo): | |||
|
1055 | result = RepoFilesView.adjust_file_path_for_svn(f_path, repo) | |||
|
1056 | assert result == expected | |||
984 |
|
1057 | |||
985 | def assert_timeago_in_response(response, items, params): |
|
1058 | def _repo(self, branches=None): | |
986 | for item in items: |
|
1059 | repo = mock.Mock() | |
987 | response.mustcontain(h.age_component(params['date'])) |
|
1060 | repo.branches = OrderedDict((name, '0') for name in branches or []) | |
|
1061 | repo.tags = {} | |||
|
1062 | return repo |
@@ -1,494 +1,494 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 |
|
2 | |||
3 | # Copyright (C) 2010-2017 RhodeCode GmbH |
|
3 | # Copyright (C) 2010-2017 RhodeCode GmbH | |
4 | # |
|
4 | # | |
5 | # This program is free software: you can redistribute it and/or modify |
|
5 | # This program is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
7 | # (only), as published by the Free Software Foundation. |
|
7 | # (only), as published by the Free Software Foundation. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | # |
|
16 | # | |
17 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 |
|
20 | |||
21 | import re |
|
21 | import re | |
22 |
|
22 | |||
23 | import mock |
|
23 | import mock | |
24 | import pytest |
|
24 | import pytest | |
25 |
|
25 | |||
26 | from rhodecode.apps.repository.views.repo_summary import RepoSummaryView |
|
26 | from rhodecode.apps.repository.views.repo_summary import RepoSummaryView | |
27 | from rhodecode.lib import helpers as h |
|
27 | from rhodecode.lib import helpers as h | |
28 | from rhodecode.lib.compat import OrderedDict |
|
28 | from rhodecode.lib.compat import OrderedDict | |
29 | from rhodecode.lib.utils2 import AttributeDict |
|
29 | from rhodecode.lib.utils2 import AttributeDict | |
30 | from rhodecode.lib.vcs.exceptions import RepositoryRequirementError |
|
30 | from rhodecode.lib.vcs.exceptions import RepositoryRequirementError | |
31 | from rhodecode.model.db import Repository |
|
31 | from rhodecode.model.db import Repository | |
32 | from rhodecode.model.meta import Session |
|
32 | from rhodecode.model.meta import Session | |
33 | from rhodecode.model.repo import RepoModel |
|
33 | from rhodecode.model.repo import RepoModel | |
34 | from rhodecode.model.scm import ScmModel |
|
34 | from rhodecode.model.scm import ScmModel | |
35 | from rhodecode.tests import assert_session_flash |
|
35 | from rhodecode.tests import assert_session_flash | |
36 | from rhodecode.tests.fixture import Fixture |
|
36 | from rhodecode.tests.fixture import Fixture | |
37 | from rhodecode.tests.utils import AssertResponse, repo_on_filesystem |
|
37 | from rhodecode.tests.utils import AssertResponse, repo_on_filesystem | |
38 |
|
38 | |||
39 |
|
39 | |||
40 | fixture = Fixture() |
|
40 | fixture = Fixture() | |
41 |
|
41 | |||
42 |
|
42 | |||
43 | def route_path(name, params=None, **kwargs): |
|
43 | def route_path(name, params=None, **kwargs): | |
44 | import urllib |
|
44 | import urllib | |
45 |
|
45 | |||
46 | base_url = { |
|
46 | base_url = { | |
47 | 'repo_summary': '/{repo_name}', |
|
47 | 'repo_summary': '/{repo_name}', | |
48 | 'repo_stats': '/{repo_name}/repo_stats/{commit_id}', |
|
48 | 'repo_stats': '/{repo_name}/repo_stats/{commit_id}', | |
49 | 'repo_refs_data': '/{repo_name}/refs-data', |
|
49 | 'repo_refs_data': '/{repo_name}/refs-data', | |
50 | 'repo_refs_changelog_data': '/{repo_name}/refs-data-changelog' |
|
50 | 'repo_refs_changelog_data': '/{repo_name}/refs-data-changelog' | |
51 |
|
51 | |||
52 | }[name].format(**kwargs) |
|
52 | }[name].format(**kwargs) | |
53 |
|
53 | |||
54 | if params: |
|
54 | if params: | |
55 | base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) |
|
55 | base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) | |
56 | return base_url |
|
56 | return base_url | |
57 |
|
57 | |||
58 |
|
58 | |||
59 | @pytest.mark.usefixtures('app') |
|
59 | @pytest.mark.usefixtures('app') | |
60 | class TestSummaryView(object): |
|
60 | class TestSummaryView(object): | |
61 | def test_index(self, autologin_user, backend, http_host_only_stub): |
|
61 | def test_index(self, autologin_user, backend, http_host_only_stub): | |
62 | repo_id = backend.repo.repo_id |
|
62 | repo_id = backend.repo.repo_id | |
63 | repo_name = backend.repo_name |
|
63 | repo_name = backend.repo_name | |
64 | with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy', |
|
64 | with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy', | |
65 | return_value=False): |
|
65 | return_value=False): | |
66 | response = self.app.get( |
|
66 | response = self.app.get( | |
67 | route_path('repo_summary', repo_name=repo_name)) |
|
67 | route_path('repo_summary', repo_name=repo_name)) | |
68 |
|
68 | |||
69 | # repo type |
|
69 | # repo type | |
70 | response.mustcontain( |
|
70 | response.mustcontain( | |
71 | '<i class="icon-%s">' % (backend.alias, ) |
|
71 | '<i class="icon-%s">' % (backend.alias, ) | |
72 | ) |
|
72 | ) | |
73 | # public/private |
|
73 | # public/private | |
74 | response.mustcontain( |
|
74 | response.mustcontain( | |
75 | """<i class="icon-unlock-alt">""" |
|
75 | """<i class="icon-unlock-alt">""" | |
76 | ) |
|
76 | ) | |
77 |
|
77 | |||
78 | # clone url... |
|
78 | # clone url... | |
79 | response.mustcontain( |
|
79 | response.mustcontain( | |
80 | 'id="clone_url" readonly="readonly"' |
|
80 | 'id="clone_url" readonly="readonly"' | |
81 | ' value="http://test_admin@%s/%s"' % (http_host_only_stub, repo_name, )) |
|
81 | ' value="http://test_admin@%s/%s"' % (http_host_only_stub, repo_name, )) | |
82 | response.mustcontain( |
|
82 | response.mustcontain( | |
83 | 'id="clone_url_id" readonly="readonly"' |
|
83 | 'id="clone_url_id" readonly="readonly"' | |
84 | ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, )) |
|
84 | ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, )) | |
85 |
|
85 | |||
86 | def test_index_svn_without_proxy( |
|
86 | def test_index_svn_without_proxy( | |
87 | self, autologin_user, backend_svn, http_host_only_stub): |
|
87 | self, autologin_user, backend_svn, http_host_only_stub): | |
88 | repo_id = backend_svn.repo.repo_id |
|
88 | repo_id = backend_svn.repo.repo_id | |
89 | repo_name = backend_svn.repo_name |
|
89 | repo_name = backend_svn.repo_name | |
90 | response = self.app.get(route_path('repo_summary', repo_name=repo_name)) |
|
90 | response = self.app.get(route_path('repo_summary', repo_name=repo_name)) | |
91 | # clone url... |
|
91 | # clone url... | |
92 | response.mustcontain( |
|
92 | response.mustcontain( | |
93 | 'id="clone_url" disabled' |
|
93 | 'id="clone_url" disabled' | |
94 | ' value="http://test_admin@%s/%s"' % (http_host_only_stub, repo_name, )) |
|
94 | ' value="http://test_admin@%s/%s"' % (http_host_only_stub, repo_name, )) | |
95 | response.mustcontain( |
|
95 | response.mustcontain( | |
96 | 'id="clone_url_id" disabled' |
|
96 | 'id="clone_url_id" disabled' | |
97 | ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, )) |
|
97 | ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, )) | |
98 |
|
98 | |||
99 | def test_index_with_trailing_slash( |
|
99 | def test_index_with_trailing_slash( | |
100 | self, autologin_user, backend, http_host_only_stub): |
|
100 | self, autologin_user, backend, http_host_only_stub): | |
101 |
|
101 | |||
102 | repo_id = backend.repo.repo_id |
|
102 | repo_id = backend.repo.repo_id | |
103 | repo_name = backend.repo_name |
|
103 | repo_name = backend.repo_name | |
104 | with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy', |
|
104 | with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy', | |
105 | return_value=False): |
|
105 | return_value=False): | |
106 | response = self.app.get( |
|
106 | response = self.app.get( | |
107 | route_path('repo_summary', repo_name=repo_name) + '/', |
|
107 | route_path('repo_summary', repo_name=repo_name) + '/', | |
108 | status=200) |
|
108 | status=200) | |
109 |
|
109 | |||
110 | # clone url... |
|
110 | # clone url... | |
111 | response.mustcontain( |
|
111 | response.mustcontain( | |
112 | 'id="clone_url" readonly="readonly"' |
|
112 | 'id="clone_url" readonly="readonly"' | |
113 | ' value="http://test_admin@%s/%s"' % (http_host_only_stub, repo_name, )) |
|
113 | ' value="http://test_admin@%s/%s"' % (http_host_only_stub, repo_name, )) | |
114 | response.mustcontain( |
|
114 | response.mustcontain( | |
115 | 'id="clone_url_id" readonly="readonly"' |
|
115 | 'id="clone_url_id" readonly="readonly"' | |
116 | ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, )) |
|
116 | ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, )) | |
117 |
|
117 | |||
118 | def test_index_by_id(self, autologin_user, backend): |
|
118 | def test_index_by_id(self, autologin_user, backend): | |
119 | repo_id = backend.repo.repo_id |
|
119 | repo_id = backend.repo.repo_id | |
120 | response = self.app.get( |
|
120 | response = self.app.get( | |
121 | route_path('repo_summary', repo_name='_%s' % (repo_id,))) |
|
121 | route_path('repo_summary', repo_name='_%s' % (repo_id,))) | |
122 |
|
122 | |||
123 | # repo type |
|
123 | # repo type | |
124 | response.mustcontain( |
|
124 | response.mustcontain( | |
125 | '<i class="icon-%s">' % (backend.alias, ) |
|
125 | '<i class="icon-%s">' % (backend.alias, ) | |
126 | ) |
|
126 | ) | |
127 | # public/private |
|
127 | # public/private | |
128 | response.mustcontain( |
|
128 | response.mustcontain( | |
129 | """<i class="icon-unlock-alt">""" |
|
129 | """<i class="icon-unlock-alt">""" | |
130 | ) |
|
130 | ) | |
131 |
|
131 | |||
132 | def test_index_by_repo_having_id_path_in_name_hg(self, autologin_user): |
|
132 | def test_index_by_repo_having_id_path_in_name_hg(self, autologin_user): | |
133 | fixture.create_repo(name='repo_1') |
|
133 | fixture.create_repo(name='repo_1') | |
134 | response = self.app.get(route_path('repo_summary', repo_name='repo_1')) |
|
134 | response = self.app.get(route_path('repo_summary', repo_name='repo_1')) | |
135 |
|
135 | |||
136 | try: |
|
136 | try: | |
137 | response.mustcontain("repo_1") |
|
137 | response.mustcontain("repo_1") | |
138 | finally: |
|
138 | finally: | |
139 | RepoModel().delete(Repository.get_by_repo_name('repo_1')) |
|
139 | RepoModel().delete(Repository.get_by_repo_name('repo_1')) | |
140 | Session().commit() |
|
140 | Session().commit() | |
141 |
|
141 | |||
142 | def test_index_with_anonymous_access_disabled( |
|
142 | def test_index_with_anonymous_access_disabled( | |
143 | self, backend, disable_anonymous_user): |
|
143 | self, backend, disable_anonymous_user): | |
144 | response = self.app.get( |
|
144 | response = self.app.get( | |
145 | route_path('repo_summary', repo_name=backend.repo_name), status=302) |
|
145 | route_path('repo_summary', repo_name=backend.repo_name), status=302) | |
146 | assert 'login' in response.location |
|
146 | assert 'login' in response.location | |
147 |
|
147 | |||
148 | def _enable_stats(self, repo): |
|
148 | def _enable_stats(self, repo): | |
149 | r = Repository.get_by_repo_name(repo) |
|
149 | r = Repository.get_by_repo_name(repo) | |
150 | r.enable_statistics = True |
|
150 | r.enable_statistics = True | |
151 | Session().add(r) |
|
151 | Session().add(r) | |
152 | Session().commit() |
|
152 | Session().commit() | |
153 |
|
153 | |||
154 | expected_trending = { |
|
154 | expected_trending = { | |
155 | 'hg': { |
|
155 | 'hg': { | |
156 | "py": {"count": 68, "desc": ["Python"]}, |
|
156 | "py": {"count": 68, "desc": ["Python"]}, | |
157 | "rst": {"count": 16, "desc": ["Rst"]}, |
|
157 | "rst": {"count": 16, "desc": ["Rst"]}, | |
158 | "css": {"count": 2, "desc": ["Css"]}, |
|
158 | "css": {"count": 2, "desc": ["Css"]}, | |
159 | "sh": {"count": 2, "desc": ["Bash"]}, |
|
159 | "sh": {"count": 2, "desc": ["Bash"]}, | |
160 | "bat": {"count": 1, "desc": ["Batch"]}, |
|
160 | "bat": {"count": 1, "desc": ["Batch"]}, | |
161 | "cfg": {"count": 1, "desc": ["Ini"]}, |
|
161 | "cfg": {"count": 1, "desc": ["Ini"]}, | |
162 | "html": {"count": 1, "desc": ["EvoqueHtml", "Html"]}, |
|
162 | "html": {"count": 1, "desc": ["EvoqueHtml", "Html"]}, | |
163 | "ini": {"count": 1, "desc": ["Ini"]}, |
|
163 | "ini": {"count": 1, "desc": ["Ini"]}, | |
164 | "js": {"count": 1, "desc": ["Javascript"]}, |
|
164 | "js": {"count": 1, "desc": ["Javascript"]}, | |
165 | "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]} |
|
165 | "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]} | |
166 | }, |
|
166 | }, | |
167 | 'git': { |
|
167 | 'git': { | |
168 | "py": {"count": 68, "desc": ["Python"]}, |
|
168 | "py": {"count": 68, "desc": ["Python"]}, | |
169 | "rst": {"count": 16, "desc": ["Rst"]}, |
|
169 | "rst": {"count": 16, "desc": ["Rst"]}, | |
170 | "css": {"count": 2, "desc": ["Css"]}, |
|
170 | "css": {"count": 2, "desc": ["Css"]}, | |
171 | "sh": {"count": 2, "desc": ["Bash"]}, |
|
171 | "sh": {"count": 2, "desc": ["Bash"]}, | |
172 | "bat": {"count": 1, "desc": ["Batch"]}, |
|
172 | "bat": {"count": 1, "desc": ["Batch"]}, | |
173 | "cfg": {"count": 1, "desc": ["Ini"]}, |
|
173 | "cfg": {"count": 1, "desc": ["Ini"]}, | |
174 | "html": {"count": 1, "desc": ["EvoqueHtml", "Html"]}, |
|
174 | "html": {"count": 1, "desc": ["EvoqueHtml", "Html"]}, | |
175 | "ini": {"count": 1, "desc": ["Ini"]}, |
|
175 | "ini": {"count": 1, "desc": ["Ini"]}, | |
176 | "js": {"count": 1, "desc": ["Javascript"]}, |
|
176 | "js": {"count": 1, "desc": ["Javascript"]}, | |
177 | "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]} |
|
177 | "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]} | |
178 | }, |
|
178 | }, | |
179 | 'svn': { |
|
179 | 'svn': { | |
180 | "py": {"count": 75, "desc": ["Python"]}, |
|
180 | "py": {"count": 75, "desc": ["Python"]}, | |
181 | "rst": {"count": 16, "desc": ["Rst"]}, |
|
181 | "rst": {"count": 16, "desc": ["Rst"]}, | |
182 | "html": {"count": 11, "desc": ["EvoqueHtml", "Html"]}, |
|
182 | "html": {"count": 11, "desc": ["EvoqueHtml", "Html"]}, | |
183 | "css": {"count": 2, "desc": ["Css"]}, |
|
183 | "css": {"count": 2, "desc": ["Css"]}, | |
184 | "bat": {"count": 1, "desc": ["Batch"]}, |
|
184 | "bat": {"count": 1, "desc": ["Batch"]}, | |
185 | "cfg": {"count": 1, "desc": ["Ini"]}, |
|
185 | "cfg": {"count": 1, "desc": ["Ini"]}, | |
186 | "ini": {"count": 1, "desc": ["Ini"]}, |
|
186 | "ini": {"count": 1, "desc": ["Ini"]}, | |
187 | "js": {"count": 1, "desc": ["Javascript"]}, |
|
187 | "js": {"count": 1, "desc": ["Javascript"]}, | |
188 | "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]}, |
|
188 | "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]}, | |
189 | "sh": {"count": 1, "desc": ["Bash"]} |
|
189 | "sh": {"count": 1, "desc": ["Bash"]} | |
190 | }, |
|
190 | }, | |
191 | } |
|
191 | } | |
192 |
|
192 | |||
193 | def test_repo_stats(self, autologin_user, backend, xhr_header): |
|
193 | def test_repo_stats(self, autologin_user, backend, xhr_header): | |
194 | response = self.app.get( |
|
194 | response = self.app.get( | |
195 | route_path( |
|
195 | route_path( | |
196 | 'repo_stats', repo_name=backend.repo_name, commit_id='tip'), |
|
196 | 'repo_stats', repo_name=backend.repo_name, commit_id='tip'), | |
197 | extra_environ=xhr_header, |
|
197 | extra_environ=xhr_header, | |
198 | status=200) |
|
198 | status=200) | |
199 | assert re.match(r'6[\d\.]+ KiB', response.json['size']) |
|
199 | assert re.match(r'6[\d\.]+ KiB', response.json['size']) | |
200 |
|
200 | |||
201 | def test_repo_stats_code_stats_enabled(self, autologin_user, backend, xhr_header): |
|
201 | def test_repo_stats_code_stats_enabled(self, autologin_user, backend, xhr_header): | |
202 | repo_name = backend.repo_name |
|
202 | repo_name = backend.repo_name | |
203 |
|
203 | |||
204 | # codes stats |
|
204 | # codes stats | |
205 | self._enable_stats(repo_name) |
|
205 | self._enable_stats(repo_name) | |
206 | ScmModel().mark_for_invalidation(repo_name) |
|
206 | ScmModel().mark_for_invalidation(repo_name) | |
207 |
|
207 | |||
208 | response = self.app.get( |
|
208 | response = self.app.get( | |
209 | route_path( |
|
209 | route_path( | |
210 | 'repo_stats', repo_name=backend.repo_name, commit_id='tip'), |
|
210 | 'repo_stats', repo_name=backend.repo_name, commit_id='tip'), | |
211 | extra_environ=xhr_header, |
|
211 | extra_environ=xhr_header, | |
212 | status=200) |
|
212 | status=200) | |
213 |
|
213 | |||
214 | expected_data = self.expected_trending[backend.alias] |
|
214 | expected_data = self.expected_trending[backend.alias] | |
215 | returned_stats = response.json['code_stats'] |
|
215 | returned_stats = response.json['code_stats'] | |
216 | for k, v in expected_data.items(): |
|
216 | for k, v in expected_data.items(): | |
217 | assert v == returned_stats[k] |
|
217 | assert v == returned_stats[k] | |
218 |
|
218 | |||
219 | def test_repo_refs_data(self, backend): |
|
219 | def test_repo_refs_data(self, backend): | |
220 | response = self.app.get( |
|
220 | response = self.app.get( | |
221 | route_path('repo_refs_data', repo_name=backend.repo_name), |
|
221 | route_path('repo_refs_data', repo_name=backend.repo_name), | |
222 | status=200) |
|
222 | status=200) | |
223 |
|
223 | |||
224 | # Ensure that there is the correct amount of items in the result |
|
224 | # Ensure that there is the correct amount of items in the result | |
225 | repo = backend.repo.scm_instance() |
|
225 | repo = backend.repo.scm_instance() | |
226 | data = response.json['results'] |
|
226 | data = response.json['results'] | |
227 | items = sum(len(section['children']) for section in data) |
|
227 | items = sum(len(section['children']) for section in data) | |
228 | repo_refs = len(repo.branches) + len(repo.tags) + len(repo.bookmarks) |
|
228 | repo_refs = len(repo.branches) + len(repo.tags) + len(repo.bookmarks) | |
229 | assert items == repo_refs |
|
229 | assert items == repo_refs | |
230 |
|
230 | |||
231 | def test_index_shows_missing_requirements_message( |
|
231 | def test_index_shows_missing_requirements_message( | |
232 | self, backend, autologin_user): |
|
232 | self, backend, autologin_user): | |
233 | repo_name = backend.repo_name |
|
233 | repo_name = backend.repo_name | |
234 | scm_patcher = mock.patch.object( |
|
234 | scm_patcher = mock.patch.object( | |
235 | Repository, 'scm_instance', side_effect=RepositoryRequirementError) |
|
235 | Repository, 'scm_instance', side_effect=RepositoryRequirementError) | |
236 |
|
236 | |||
237 | with scm_patcher: |
|
237 | with scm_patcher: | |
238 | response = self.app.get(route_path('repo_summary', repo_name=repo_name)) |
|
238 | response = self.app.get(route_path('repo_summary', repo_name=repo_name)) | |
239 | assert_response = AssertResponse(response) |
|
239 | assert_response = AssertResponse(response) | |
240 | assert_response.element_contains( |
|
240 | assert_response.element_contains( | |
241 | '.main .alert-warning strong', 'Missing requirements') |
|
241 | '.main .alert-warning strong', 'Missing requirements') | |
242 | assert_response.element_contains( |
|
242 | assert_response.element_contains( | |
243 | '.main .alert-warning', |
|
243 | '.main .alert-warning', | |
244 | 'Commits cannot be displayed, because this repository ' |
|
244 | 'Commits cannot be displayed, because this repository ' | |
245 | 'uses one or more extensions, which was not enabled.') |
|
245 | 'uses one or more extensions, which was not enabled.') | |
246 |
|
246 | |||
247 | def test_missing_requirements_page_does_not_contains_switch_to( |
|
247 | def test_missing_requirements_page_does_not_contains_switch_to( | |
248 | self, autologin_user, backend): |
|
248 | self, autologin_user, backend): | |
249 | repo_name = backend.repo_name |
|
249 | repo_name = backend.repo_name | |
250 | scm_patcher = mock.patch.object( |
|
250 | scm_patcher = mock.patch.object( | |
251 | Repository, 'scm_instance', side_effect=RepositoryRequirementError) |
|
251 | Repository, 'scm_instance', side_effect=RepositoryRequirementError) | |
252 |
|
252 | |||
253 | with scm_patcher: |
|
253 | with scm_patcher: | |
254 | response = self.app.get(route_path('repo_summary', repo_name=repo_name)) |
|
254 | response = self.app.get(route_path('repo_summary', repo_name=repo_name)) | |
255 | response.mustcontain(no='Switch To') |
|
255 | response.mustcontain(no='Switch To') | |
256 |
|
256 | |||
257 |
|
257 | |||
258 | @pytest.mark.usefixtures('app') |
|
258 | @pytest.mark.usefixtures('app') | |
259 | class TestRepoLocation(object): |
|
259 | class TestRepoLocation(object): | |
260 |
|
260 | |||
261 | @pytest.mark.parametrize("suffix", [u'', u'ąęł'], ids=['', 'non-ascii']) |
|
261 | @pytest.mark.parametrize("suffix", [u'', u'ąęł'], ids=['', 'non-ascii']) | |
262 | def test_manual_delete(self, autologin_user, backend, suffix, csrf_token): |
|
262 | def test_manual_delete(self, autologin_user, backend, suffix, csrf_token): | |
263 | repo = backend.create_repo(name_suffix=suffix) |
|
263 | repo = backend.create_repo(name_suffix=suffix) | |
264 | repo_name = repo.repo_name |
|
264 | repo_name = repo.repo_name | |
265 |
|
265 | |||
266 | # delete from file system |
|
266 | # delete from file system | |
267 | RepoModel()._delete_filesystem_repo(repo) |
|
267 | RepoModel()._delete_filesystem_repo(repo) | |
268 |
|
268 | |||
269 | # test if the repo is still in the database |
|
269 | # test if the repo is still in the database | |
270 | new_repo = RepoModel().get_by_repo_name(repo_name) |
|
270 | new_repo = RepoModel().get_by_repo_name(repo_name) | |
271 | assert new_repo.repo_name == repo_name |
|
271 | assert new_repo.repo_name == repo_name | |
272 |
|
272 | |||
273 | # check if repo is not in the filesystem |
|
273 | # check if repo is not in the filesystem | |
274 | assert not repo_on_filesystem(repo_name) |
|
274 | assert not repo_on_filesystem(repo_name) | |
275 | self.assert_repo_not_found_redirect(repo_name) |
|
275 | self.assert_repo_not_found_redirect(repo_name) | |
276 |
|
276 | |||
277 | def assert_repo_not_found_redirect(self, repo_name): |
|
277 | def assert_repo_not_found_redirect(self, repo_name): | |
278 | # run the check page that triggers the other flash message |
|
278 | # run the check page that triggers the other flash message | |
279 | response = self.app.get(h.url('repo_check_home', repo_name=repo_name)) |
|
279 | response = self.app.get(h.url('repo_check_home', repo_name=repo_name)) | |
280 | assert_session_flash( |
|
280 | assert_session_flash( | |
281 | response, 'The repository at %s cannot be located.' % repo_name) |
|
281 | response, 'The repository at %s cannot be located.' % repo_name) | |
282 |
|
282 | |||
283 |
|
283 | |||
284 | @pytest.fixture() |
|
284 | @pytest.fixture() | |
285 | def summary_view(context_stub, request_stub, user_util): |
|
285 | def summary_view(context_stub, request_stub, user_util): | |
286 | """ |
|
286 | """ | |
287 | Bootstrap view to test the view functions |
|
287 | Bootstrap view to test the view functions | |
288 | """ |
|
288 | """ | |
289 | request_stub.matched_route = AttributeDict(name='test_view') |
|
289 | request_stub.matched_route = AttributeDict(name='test_view') | |
290 |
|
290 | |||
291 | request_stub.user = user_util.create_user().AuthUser |
|
291 | request_stub.user = user_util.create_user().AuthUser | |
292 | request_stub.db_repo = user_util.create_repo() |
|
292 | request_stub.db_repo = user_util.create_repo() | |
293 |
|
293 | |||
294 | view = RepoSummaryView(context=context_stub, request=request_stub) |
|
294 | view = RepoSummaryView(context=context_stub, request=request_stub) | |
295 | return view |
|
295 | return view | |
296 |
|
296 | |||
297 |
|
297 | |||
298 | @pytest.mark.usefixtures('app') |
|
298 | @pytest.mark.usefixtures('app') | |
299 | class TestCreateReferenceData(object): |
|
299 | class TestCreateReferenceData(object): | |
300 |
|
300 | |||
301 | @pytest.fixture |
|
301 | @pytest.fixture | |
302 | def example_refs(self): |
|
302 | def example_refs(self): | |
303 | section_1_refs = OrderedDict((('a', 'a_id'), ('b', 'b_id'))) |
|
303 | section_1_refs = OrderedDict((('a', 'a_id'), ('b', 'b_id'))) | |
304 | example_refs = [ |
|
304 | example_refs = [ | |
305 | ('section_1', section_1_refs, 't1'), |
|
305 | ('section_1', section_1_refs, 't1'), | |
306 | ('section_2', {'c': 'c_id'}, 't2'), |
|
306 | ('section_2', {'c': 'c_id'}, 't2'), | |
307 | ] |
|
307 | ] | |
308 | return example_refs |
|
308 | return example_refs | |
309 |
|
309 | |||
310 | def test_generates_refs_based_on_commit_ids(self, example_refs, summary_view): |
|
310 | def test_generates_refs_based_on_commit_ids(self, example_refs, summary_view): | |
311 | repo = mock.Mock() |
|
311 | repo = mock.Mock() | |
312 | repo.name = 'test-repo' |
|
312 | repo.name = 'test-repo' | |
313 | repo.alias = 'git' |
|
313 | repo.alias = 'git' | |
314 | full_repo_name = 'pytest-repo-group/' + repo.name |
|
314 | full_repo_name = 'pytest-repo-group/' + repo.name | |
315 |
|
315 | |||
316 | result = summary_view._create_reference_data( |
|
316 | result = summary_view._create_reference_data( | |
317 | repo, full_repo_name, example_refs) |
|
317 | repo, full_repo_name, example_refs) | |
318 |
|
318 | |||
319 | expected_files_url = '/{}/files/'.format(full_repo_name) |
|
319 | expected_files_url = '/{}/files/'.format(full_repo_name) | |
320 | expected_result = [ |
|
320 | expected_result = [ | |
321 | { |
|
321 | { | |
322 | 'children': [ |
|
322 | 'children': [ | |
323 | { |
|
323 | { | |
324 | 'id': 'a', 'raw_id': 'a_id', 'text': 'a', 'type': 't1', |
|
324 | 'id': 'a', 'raw_id': 'a_id', 'text': 'a', 'type': 't1', | |
325 | 'files_url': expected_files_url + 'a/?at=a', |
|
325 | 'files_url': expected_files_url + 'a/?at=a', | |
326 | }, |
|
326 | }, | |
327 | { |
|
327 | { | |
328 | 'id': 'b', 'raw_id': 'b_id', 'text': 'b', 'type': 't1', |
|
328 | 'id': 'b', 'raw_id': 'b_id', 'text': 'b', 'type': 't1', | |
329 | 'files_url': expected_files_url + 'b/?at=b', |
|
329 | 'files_url': expected_files_url + 'b/?at=b', | |
330 | } |
|
330 | } | |
331 | ], |
|
331 | ], | |
332 | 'text': 'section_1' |
|
332 | 'text': 'section_1' | |
333 | }, |
|
333 | }, | |
334 | { |
|
334 | { | |
335 | 'children': [ |
|
335 | 'children': [ | |
336 | { |
|
336 | { | |
337 | 'id': 'c', 'raw_id': 'c_id', 'text': 'c', 'type': 't2', |
|
337 | 'id': 'c', 'raw_id': 'c_id', 'text': 'c', 'type': 't2', | |
338 | 'files_url': expected_files_url + 'c/?at=c', |
|
338 | 'files_url': expected_files_url + 'c/?at=c', | |
339 | } |
|
339 | } | |
340 | ], |
|
340 | ], | |
341 | 'text': 'section_2' |
|
341 | 'text': 'section_2' | |
342 | }] |
|
342 | }] | |
343 | assert result == expected_result |
|
343 | assert result == expected_result | |
344 |
|
344 | |||
345 | def test_generates_refs_with_path_for_svn(self, example_refs, summary_view): |
|
345 | def test_generates_refs_with_path_for_svn(self, example_refs, summary_view): | |
346 | repo = mock.Mock() |
|
346 | repo = mock.Mock() | |
347 | repo.name = 'test-repo' |
|
347 | repo.name = 'test-repo' | |
348 | repo.alias = 'svn' |
|
348 | repo.alias = 'svn' | |
349 | full_repo_name = 'pytest-repo-group/' + repo.name |
|
349 | full_repo_name = 'pytest-repo-group/' + repo.name | |
350 |
|
350 | |||
351 | result = summary_view._create_reference_data( |
|
351 | result = summary_view._create_reference_data( | |
352 | repo, full_repo_name, example_refs) |
|
352 | repo, full_repo_name, example_refs) | |
353 |
|
353 | |||
354 | expected_files_url = '/{}/files/'.format(full_repo_name) |
|
354 | expected_files_url = '/{}/files/'.format(full_repo_name) | |
355 | expected_result = [ |
|
355 | expected_result = [ | |
356 | { |
|
356 | { | |
357 | 'children': [ |
|
357 | 'children': [ | |
358 | { |
|
358 | { | |
359 | 'id': 'a@a_id', 'raw_id': 'a_id', |
|
359 | 'id': 'a@a_id', 'raw_id': 'a_id', | |
360 | 'text': 'a', 'type': 't1', |
|
360 | 'text': 'a', 'type': 't1', | |
361 | 'files_url': expected_files_url + 'a_id/a?at=a', |
|
361 | 'files_url': expected_files_url + 'a_id/a?at=a', | |
362 | }, |
|
362 | }, | |
363 | { |
|
363 | { | |
364 | 'id': 'b@b_id', 'raw_id': 'b_id', |
|
364 | 'id': 'b@b_id', 'raw_id': 'b_id', | |
365 | 'text': 'b', 'type': 't1', |
|
365 | 'text': 'b', 'type': 't1', | |
366 | 'files_url': expected_files_url + 'b_id/b?at=b', |
|
366 | 'files_url': expected_files_url + 'b_id/b?at=b', | |
367 | } |
|
367 | } | |
368 | ], |
|
368 | ], | |
369 | 'text': 'section_1' |
|
369 | 'text': 'section_1' | |
370 | }, |
|
370 | }, | |
371 | { |
|
371 | { | |
372 | 'children': [ |
|
372 | 'children': [ | |
373 | { |
|
373 | { | |
374 | 'id': 'c@c_id', 'raw_id': 'c_id', |
|
374 | 'id': 'c@c_id', 'raw_id': 'c_id', | |
375 | 'text': 'c', 'type': 't2', |
|
375 | 'text': 'c', 'type': 't2', | |
376 | 'files_url': expected_files_url + 'c_id/c?at=c', |
|
376 | 'files_url': expected_files_url + 'c_id/c?at=c', | |
377 | } |
|
377 | } | |
378 | ], |
|
378 | ], | |
379 | 'text': 'section_2' |
|
379 | 'text': 'section_2' | |
380 | } |
|
380 | } | |
381 | ] |
|
381 | ] | |
382 | assert result == expected_result |
|
382 | assert result == expected_result | |
383 |
|
383 | |||
384 |
|
384 | |||
385 | class TestCreateFilesUrl(object): |
|
385 | class TestCreateFilesUrl(object): | |
386 |
|
386 | |||
387 | def test_creates_non_svn_url(self, summary_view): |
|
387 | def test_creates_non_svn_url(self, app, summary_view): | |
388 | repo = mock.Mock() |
|
388 | repo = mock.Mock() | |
389 | repo.name = 'abcde' |
|
389 | repo.name = 'abcde' | |
390 | full_repo_name = 'test-repo-group/' + repo.name |
|
390 | full_repo_name = 'test-repo-group/' + repo.name | |
391 | ref_name = 'branch1' |
|
391 | ref_name = 'branch1' | |
392 | raw_id = 'deadbeef0123456789' |
|
392 | raw_id = 'deadbeef0123456789' | |
393 | is_svn = False |
|
393 | is_svn = False | |
394 |
|
394 | |||
395 |
with mock.patch('rhodecode.lib.helpers. |
|
395 | with mock.patch('rhodecode.lib.helpers.route_path') as url_mock: | |
396 | result = summary_view._create_files_url( |
|
396 | result = summary_view._create_files_url( | |
397 | repo, full_repo_name, ref_name, raw_id, is_svn) |
|
397 | repo, full_repo_name, ref_name, raw_id, is_svn) | |
398 | url_mock.assert_called_once_with( |
|
398 | url_mock.assert_called_once_with( | |
399 |
'files |
|
399 | 'repo_files', repo_name=full_repo_name, commit_id=ref_name, | |
400 |
|
|
400 | f_path='', _query=dict(at=ref_name)) | |
401 | assert result == url_mock.return_value |
|
401 | assert result == url_mock.return_value | |
402 |
|
402 | |||
403 | def test_creates_svn_url(self, summary_view): |
|
403 | def test_creates_svn_url(self, app, summary_view): | |
404 | repo = mock.Mock() |
|
404 | repo = mock.Mock() | |
405 | repo.name = 'abcde' |
|
405 | repo.name = 'abcde' | |
406 | full_repo_name = 'test-repo-group/' + repo.name |
|
406 | full_repo_name = 'test-repo-group/' + repo.name | |
407 | ref_name = 'branch1' |
|
407 | ref_name = 'branch1' | |
408 | raw_id = 'deadbeef0123456789' |
|
408 | raw_id = 'deadbeef0123456789' | |
409 | is_svn = True |
|
409 | is_svn = True | |
410 |
|
410 | |||
411 |
with mock.patch('rhodecode.lib.helpers. |
|
411 | with mock.patch('rhodecode.lib.helpers.route_path') as url_mock: | |
412 | result = summary_view._create_files_url( |
|
412 | result = summary_view._create_files_url( | |
413 | repo, full_repo_name, ref_name, raw_id, is_svn) |
|
413 | repo, full_repo_name, ref_name, raw_id, is_svn) | |
414 | url_mock.assert_called_once_with( |
|
414 | url_mock.assert_called_once_with( | |
415 |
'files |
|
415 | 'repo_files', repo_name=full_repo_name, f_path=ref_name, | |
416 |
|
|
416 | commit_id=raw_id, _query=dict(at=ref_name)) | |
417 | assert result == url_mock.return_value |
|
417 | assert result == url_mock.return_value | |
418 |
|
418 | |||
419 | def test_name_has_slashes(self, summary_view): |
|
419 | def test_name_has_slashes(self, app, summary_view): | |
420 | repo = mock.Mock() |
|
420 | repo = mock.Mock() | |
421 | repo.name = 'abcde' |
|
421 | repo.name = 'abcde' | |
422 | full_repo_name = 'test-repo-group/' + repo.name |
|
422 | full_repo_name = 'test-repo-group/' + repo.name | |
423 | ref_name = 'branch1/branch2' |
|
423 | ref_name = 'branch1/branch2' | |
424 | raw_id = 'deadbeef0123456789' |
|
424 | raw_id = 'deadbeef0123456789' | |
425 | is_svn = False |
|
425 | is_svn = False | |
426 |
|
426 | |||
427 |
with mock.patch('rhodecode.lib.helpers. |
|
427 | with mock.patch('rhodecode.lib.helpers.route_path') as url_mock: | |
428 | result = summary_view._create_files_url( |
|
428 | result = summary_view._create_files_url( | |
429 | repo, full_repo_name, ref_name, raw_id, is_svn) |
|
429 | repo, full_repo_name, ref_name, raw_id, is_svn) | |
430 | url_mock.assert_called_once_with( |
|
430 | url_mock.assert_called_once_with( | |
431 |
'files |
|
431 | 'repo_files', repo_name=full_repo_name, commit_id=raw_id, | |
432 | at=ref_name) |
|
432 | f_path='', _query=dict(at=ref_name)) | |
433 | assert result == url_mock.return_value |
|
433 | assert result == url_mock.return_value | |
434 |
|
434 | |||
435 |
|
435 | |||
436 | class TestReferenceItems(object): |
|
436 | class TestReferenceItems(object): | |
437 | repo = mock.Mock() |
|
437 | repo = mock.Mock() | |
438 | repo.name = 'pytest-repo' |
|
438 | repo.name = 'pytest-repo' | |
439 | repo_full_name = 'pytest-repo-group/' + repo.name |
|
439 | repo_full_name = 'pytest-repo-group/' + repo.name | |
440 | ref_type = 'branch' |
|
440 | ref_type = 'branch' | |
441 | fake_url = '/abcde/' |
|
441 | fake_url = '/abcde/' | |
442 |
|
442 | |||
443 | @staticmethod |
|
443 | @staticmethod | |
444 | def _format_function(name, id_): |
|
444 | def _format_function(name, id_): | |
445 | return 'format_function_{}_{}'.format(name, id_) |
|
445 | return 'format_function_{}_{}'.format(name, id_) | |
446 |
|
446 | |||
447 | def test_creates_required_amount_of_items(self, summary_view): |
|
447 | def test_creates_required_amount_of_items(self, summary_view): | |
448 | amount = 100 |
|
448 | amount = 100 | |
449 | refs = { |
|
449 | refs = { | |
450 | 'ref{}'.format(i): '{0:040d}'.format(i) |
|
450 | 'ref{}'.format(i): '{0:040d}'.format(i) | |
451 | for i in range(amount) |
|
451 | for i in range(amount) | |
452 | } |
|
452 | } | |
453 |
|
453 | |||
454 | url_patcher = mock.patch.object(summary_view, '_create_files_url') |
|
454 | url_patcher = mock.patch.object(summary_view, '_create_files_url') | |
455 | svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn', |
|
455 | svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn', | |
456 | return_value=False) |
|
456 | return_value=False) | |
457 |
|
457 | |||
458 | with url_patcher as url_mock, svn_patcher: |
|
458 | with url_patcher as url_mock, svn_patcher: | |
459 | result = summary_view._create_reference_items( |
|
459 | result = summary_view._create_reference_items( | |
460 | self.repo, self.repo_full_name, refs, self.ref_type, |
|
460 | self.repo, self.repo_full_name, refs, self.ref_type, | |
461 | self._format_function) |
|
461 | self._format_function) | |
462 | assert len(result) == amount |
|
462 | assert len(result) == amount | |
463 | assert url_mock.call_count == amount |
|
463 | assert url_mock.call_count == amount | |
464 |
|
464 | |||
465 | def test_single_item_details(self, summary_view): |
|
465 | def test_single_item_details(self, summary_view): | |
466 | ref_name = 'ref1' |
|
466 | ref_name = 'ref1' | |
467 | ref_id = 'deadbeef' |
|
467 | ref_id = 'deadbeef' | |
468 | refs = { |
|
468 | refs = { | |
469 | ref_name: ref_id |
|
469 | ref_name: ref_id | |
470 | } |
|
470 | } | |
471 |
|
471 | |||
472 | svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn', |
|
472 | svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn', | |
473 | return_value=False) |
|
473 | return_value=False) | |
474 |
|
474 | |||
475 | url_patcher = mock.patch.object( |
|
475 | url_patcher = mock.patch.object( | |
476 | summary_view, '_create_files_url', return_value=self.fake_url) |
|
476 | summary_view, '_create_files_url', return_value=self.fake_url) | |
477 |
|
477 | |||
478 | with url_patcher as url_mock, svn_patcher: |
|
478 | with url_patcher as url_mock, svn_patcher: | |
479 | result = summary_view._create_reference_items( |
|
479 | result = summary_view._create_reference_items( | |
480 | self.repo, self.repo_full_name, refs, self.ref_type, |
|
480 | self.repo, self.repo_full_name, refs, self.ref_type, | |
481 | self._format_function) |
|
481 | self._format_function) | |
482 |
|
482 | |||
483 | url_mock.assert_called_once_with( |
|
483 | url_mock.assert_called_once_with( | |
484 | self.repo, self.repo_full_name, ref_name, ref_id, False) |
|
484 | self.repo, self.repo_full_name, ref_name, ref_id, False) | |
485 | expected_result = [ |
|
485 | expected_result = [ | |
486 | { |
|
486 | { | |
487 | 'text': ref_name, |
|
487 | 'text': ref_name, | |
488 | 'id': self._format_function(ref_name, ref_id), |
|
488 | 'id': self._format_function(ref_name, ref_id), | |
489 | 'raw_id': ref_id, |
|
489 | 'raw_id': ref_id, | |
490 | 'type': self.ref_type, |
|
490 | 'type': self.ref_type, | |
491 | 'files_url': self.fake_url |
|
491 | 'files_url': self.fake_url | |
492 | } |
|
492 | } | |
493 | ] |
|
493 | ] | |
494 | assert result == expected_result |
|
494 | assert result == expected_result |
@@ -1,368 +1,367 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 |
|
2 | |||
3 | # Copyright (C) 2011-2017 RhodeCode GmbH |
|
3 | # Copyright (C) 2011-2017 RhodeCode GmbH | |
4 | # |
|
4 | # | |
5 | # This program is free software: you can redistribute it and/or modify |
|
5 | # This program is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
7 | # (only), as published by the Free Software Foundation. |
|
7 | # (only), as published by the Free Software Foundation. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | # |
|
16 | # | |
17 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 |
|
20 | |||
21 | import logging |
|
21 | import logging | |
22 | import string |
|
22 | import string | |
23 |
|
23 | |||
24 | from pyramid.view import view_config |
|
24 | from pyramid.view import view_config | |
25 |
|
25 | |||
26 | from beaker.cache import cache_region |
|
26 | from beaker.cache import cache_region | |
27 |
|
27 | |||
28 |
|
28 | |||
29 | from rhodecode.controllers import utils |
|
29 | from rhodecode.controllers import utils | |
30 |
|
30 | |||
31 | from rhodecode.apps._base import RepoAppView |
|
31 | from rhodecode.apps._base import RepoAppView | |
32 | from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP) |
|
32 | from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP) | |
33 | from rhodecode.lib import caches, helpers as h |
|
33 | from rhodecode.lib import caches, helpers as h | |
34 | from rhodecode.lib.helpers import RepoPage |
|
34 | from rhodecode.lib.helpers import RepoPage | |
35 | from rhodecode.lib.utils2 import safe_str, safe_int |
|
35 | from rhodecode.lib.utils2 import safe_str, safe_int | |
36 | from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator |
|
36 | from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator | |
37 | from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links |
|
37 | from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links | |
38 | from rhodecode.lib.ext_json import json |
|
38 | from rhodecode.lib.ext_json import json | |
39 | from rhodecode.lib.vcs.backends.base import EmptyCommit |
|
39 | from rhodecode.lib.vcs.backends.base import EmptyCommit | |
40 | from rhodecode.lib.vcs.exceptions import CommitError, EmptyRepositoryError |
|
40 | from rhodecode.lib.vcs.exceptions import CommitError, EmptyRepositoryError | |
41 | from rhodecode.model.db import Statistics, CacheKey, User |
|
41 | from rhodecode.model.db import Statistics, CacheKey, User | |
42 | from rhodecode.model.meta import Session |
|
42 | from rhodecode.model.meta import Session | |
43 | from rhodecode.model.repo import ReadmeFinder |
|
43 | from rhodecode.model.repo import ReadmeFinder | |
44 | from rhodecode.model.scm import ScmModel |
|
44 | from rhodecode.model.scm import ScmModel | |
45 |
|
45 | |||
46 | log = logging.getLogger(__name__) |
|
46 | log = logging.getLogger(__name__) | |
47 |
|
47 | |||
48 |
|
48 | |||
49 | class RepoSummaryView(RepoAppView): |
|
49 | class RepoSummaryView(RepoAppView): | |
50 |
|
50 | |||
51 | def load_default_context(self): |
|
51 | def load_default_context(self): | |
52 | c = self._get_local_tmpl_context(include_app_defaults=True) |
|
52 | c = self._get_local_tmpl_context(include_app_defaults=True) | |
53 |
|
53 | |||
54 | # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead |
|
54 | # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead | |
55 | c.repo_info = self.db_repo |
|
55 | c.repo_info = self.db_repo | |
56 | c.rhodecode_repo = None |
|
56 | c.rhodecode_repo = None | |
57 | if not c.repository_requirements_missing: |
|
57 | if not c.repository_requirements_missing: | |
58 | c.rhodecode_repo = self.rhodecode_vcs_repo |
|
58 | c.rhodecode_repo = self.rhodecode_vcs_repo | |
59 |
|
59 | |||
60 | self._register_global_c(c) |
|
60 | self._register_global_c(c) | |
61 | return c |
|
61 | return c | |
62 |
|
62 | |||
63 | def _get_readme_data(self, db_repo, default_renderer): |
|
63 | def _get_readme_data(self, db_repo, default_renderer): | |
64 | repo_name = db_repo.repo_name |
|
64 | repo_name = db_repo.repo_name | |
65 | log.debug('Looking for README file') |
|
65 | log.debug('Looking for README file') | |
66 |
|
66 | |||
67 | @cache_region('long_term') |
|
67 | @cache_region('long_term') | |
68 | def _generate_readme(cache_key): |
|
68 | def _generate_readme(cache_key): | |
69 | readme_data = None |
|
69 | readme_data = None | |
70 | readme_node = None |
|
70 | readme_node = None | |
71 | readme_filename = None |
|
71 | readme_filename = None | |
72 | commit = self._get_landing_commit_or_none(db_repo) |
|
72 | commit = self._get_landing_commit_or_none(db_repo) | |
73 | if commit: |
|
73 | if commit: | |
74 | log.debug("Searching for a README file.") |
|
74 | log.debug("Searching for a README file.") | |
75 | readme_node = ReadmeFinder(default_renderer).search(commit) |
|
75 | readme_node = ReadmeFinder(default_renderer).search(commit) | |
76 | if readme_node: |
|
76 | if readme_node: | |
77 |
relative_url = h. |
|
77 | relative_url = h.route_path( | |
78 |
|
|
78 | 'repo_file_raw', repo_name=repo_name, | |
79 | revision=commit.raw_id, |
|
79 | commit_id=commit.raw_id, f_path=readme_node.path) | |
80 | f_path=readme_node.path) |
|
|||
81 | readme_data = self._render_readme_or_none( |
|
80 | readme_data = self._render_readme_or_none( | |
82 | commit, readme_node, relative_url) |
|
81 | commit, readme_node, relative_url) | |
83 | readme_filename = readme_node.path |
|
82 | readme_filename = readme_node.path | |
84 | return readme_data, readme_filename |
|
83 | return readme_data, readme_filename | |
85 |
|
84 | |||
86 | invalidator_context = CacheKey.repo_context_cache( |
|
85 | invalidator_context = CacheKey.repo_context_cache( | |
87 | _generate_readme, repo_name, CacheKey.CACHE_TYPE_README) |
|
86 | _generate_readme, repo_name, CacheKey.CACHE_TYPE_README) | |
88 |
|
87 | |||
89 | with invalidator_context as context: |
|
88 | with invalidator_context as context: | |
90 | context.invalidate() |
|
89 | context.invalidate() | |
91 | computed = context.compute() |
|
90 | computed = context.compute() | |
92 |
|
91 | |||
93 | return computed |
|
92 | return computed | |
94 |
|
93 | |||
95 | def _get_landing_commit_or_none(self, db_repo): |
|
94 | def _get_landing_commit_or_none(self, db_repo): | |
96 | log.debug("Getting the landing commit.") |
|
95 | log.debug("Getting the landing commit.") | |
97 | try: |
|
96 | try: | |
98 | commit = db_repo.get_landing_commit() |
|
97 | commit = db_repo.get_landing_commit() | |
99 | if not isinstance(commit, EmptyCommit): |
|
98 | if not isinstance(commit, EmptyCommit): | |
100 | return commit |
|
99 | return commit | |
101 | else: |
|
100 | else: | |
102 | log.debug("Repository is empty, no README to render.") |
|
101 | log.debug("Repository is empty, no README to render.") | |
103 | except CommitError: |
|
102 | except CommitError: | |
104 | log.exception( |
|
103 | log.exception( | |
105 | "Problem getting commit when trying to render the README.") |
|
104 | "Problem getting commit when trying to render the README.") | |
106 |
|
105 | |||
107 | def _render_readme_or_none(self, commit, readme_node, relative_url): |
|
106 | def _render_readme_or_none(self, commit, readme_node, relative_url): | |
108 | log.debug( |
|
107 | log.debug( | |
109 | 'Found README file `%s` rendering...', readme_node.path) |
|
108 | 'Found README file `%s` rendering...', readme_node.path) | |
110 | renderer = MarkupRenderer() |
|
109 | renderer = MarkupRenderer() | |
111 | try: |
|
110 | try: | |
112 | html_source = renderer.render( |
|
111 | html_source = renderer.render( | |
113 | readme_node.content, filename=readme_node.path) |
|
112 | readme_node.content, filename=readme_node.path) | |
114 | if relative_url: |
|
113 | if relative_url: | |
115 | return relative_links(html_source, relative_url) |
|
114 | return relative_links(html_source, relative_url) | |
116 | return html_source |
|
115 | return html_source | |
117 | except Exception: |
|
116 | except Exception: | |
118 | log.exception( |
|
117 | log.exception( | |
119 | "Exception while trying to render the README") |
|
118 | "Exception while trying to render the README") | |
120 |
|
119 | |||
121 | def _load_commits_context(self, c): |
|
120 | def _load_commits_context(self, c): | |
122 | p = safe_int(self.request.GET.get('page'), 1) |
|
121 | p = safe_int(self.request.GET.get('page'), 1) | |
123 | size = safe_int(self.request.GET.get('size'), 10) |
|
122 | size = safe_int(self.request.GET.get('size'), 10) | |
124 |
|
123 | |||
125 | def url_generator(**kw): |
|
124 | def url_generator(**kw): | |
126 | query_params = { |
|
125 | query_params = { | |
127 | 'size': size |
|
126 | 'size': size | |
128 | } |
|
127 | } | |
129 | query_params.update(kw) |
|
128 | query_params.update(kw) | |
130 | return h.route_path( |
|
129 | return h.route_path( | |
131 | 'repo_summary_commits', |
|
130 | 'repo_summary_commits', | |
132 | repo_name=c.rhodecode_db_repo.repo_name, _query=query_params) |
|
131 | repo_name=c.rhodecode_db_repo.repo_name, _query=query_params) | |
133 |
|
132 | |||
134 | pre_load = ['author', 'branch', 'date', 'message'] |
|
133 | pre_load = ['author', 'branch', 'date', 'message'] | |
135 | try: |
|
134 | try: | |
136 | collection = self.rhodecode_vcs_repo.get_commits(pre_load=pre_load) |
|
135 | collection = self.rhodecode_vcs_repo.get_commits(pre_load=pre_load) | |
137 | except EmptyRepositoryError: |
|
136 | except EmptyRepositoryError: | |
138 | collection = self.rhodecode_vcs_repo |
|
137 | collection = self.rhodecode_vcs_repo | |
139 |
|
138 | |||
140 | c.repo_commits = RepoPage( |
|
139 | c.repo_commits = RepoPage( | |
141 | collection, page=p, items_per_page=size, url=url_generator) |
|
140 | collection, page=p, items_per_page=size, url=url_generator) | |
142 | page_ids = [x.raw_id for x in c.repo_commits] |
|
141 | page_ids = [x.raw_id for x in c.repo_commits] | |
143 | c.comments = self.db_repo.get_comments(page_ids) |
|
142 | c.comments = self.db_repo.get_comments(page_ids) | |
144 | c.statuses = self.db_repo.statuses(page_ids) |
|
143 | c.statuses = self.db_repo.statuses(page_ids) | |
145 |
|
144 | |||
146 | @LoginRequired() |
|
145 | @LoginRequired() | |
147 | @HasRepoPermissionAnyDecorator( |
|
146 | @HasRepoPermissionAnyDecorator( | |
148 | 'repository.read', 'repository.write', 'repository.admin') |
|
147 | 'repository.read', 'repository.write', 'repository.admin') | |
149 | @view_config( |
|
148 | @view_config( | |
150 | route_name='repo_summary_commits', request_method='GET', |
|
149 | route_name='repo_summary_commits', request_method='GET', | |
151 | renderer='rhodecode:templates/summary/summary_commits.mako') |
|
150 | renderer='rhodecode:templates/summary/summary_commits.mako') | |
152 | def summary_commits(self): |
|
151 | def summary_commits(self): | |
153 | c = self.load_default_context() |
|
152 | c = self.load_default_context() | |
154 | self._load_commits_context(c) |
|
153 | self._load_commits_context(c) | |
155 | return self._get_template_context(c) |
|
154 | return self._get_template_context(c) | |
156 |
|
155 | |||
157 | @LoginRequired() |
|
156 | @LoginRequired() | |
158 | @HasRepoPermissionAnyDecorator( |
|
157 | @HasRepoPermissionAnyDecorator( | |
159 | 'repository.read', 'repository.write', 'repository.admin') |
|
158 | 'repository.read', 'repository.write', 'repository.admin') | |
160 | @view_config( |
|
159 | @view_config( | |
161 | route_name='repo_summary', request_method='GET', |
|
160 | route_name='repo_summary', request_method='GET', | |
162 | renderer='rhodecode:templates/summary/summary.mako') |
|
161 | renderer='rhodecode:templates/summary/summary.mako') | |
163 | @view_config( |
|
162 | @view_config( | |
164 | route_name='repo_summary_slash', request_method='GET', |
|
163 | route_name='repo_summary_slash', request_method='GET', | |
165 | renderer='rhodecode:templates/summary/summary.mako') |
|
164 | renderer='rhodecode:templates/summary/summary.mako') | |
166 | def summary(self): |
|
165 | def summary(self): | |
167 | c = self.load_default_context() |
|
166 | c = self.load_default_context() | |
168 |
|
167 | |||
169 | # Prepare the clone URL |
|
168 | # Prepare the clone URL | |
170 | username = '' |
|
169 | username = '' | |
171 | if self._rhodecode_user.username != User.DEFAULT_USER: |
|
170 | if self._rhodecode_user.username != User.DEFAULT_USER: | |
172 | username = safe_str(self._rhodecode_user.username) |
|
171 | username = safe_str(self._rhodecode_user.username) | |
173 |
|
172 | |||
174 | _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl |
|
173 | _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl | |
175 | if '{repo}' in _def_clone_uri: |
|
174 | if '{repo}' in _def_clone_uri: | |
176 | _def_clone_uri_by_id = _def_clone_uri.replace( |
|
175 | _def_clone_uri_by_id = _def_clone_uri.replace( | |
177 | '{repo}', '_{repoid}') |
|
176 | '{repo}', '_{repoid}') | |
178 | elif '{repoid}' in _def_clone_uri: |
|
177 | elif '{repoid}' in _def_clone_uri: | |
179 | _def_clone_uri_by_id = _def_clone_uri.replace( |
|
178 | _def_clone_uri_by_id = _def_clone_uri.replace( | |
180 | '_{repoid}', '{repo}') |
|
179 | '_{repoid}', '{repo}') | |
181 |
|
180 | |||
182 | c.clone_repo_url = self.db_repo.clone_url( |
|
181 | c.clone_repo_url = self.db_repo.clone_url( | |
183 | user=username, uri_tmpl=_def_clone_uri) |
|
182 | user=username, uri_tmpl=_def_clone_uri) | |
184 | c.clone_repo_url_id = self.db_repo.clone_url( |
|
183 | c.clone_repo_url_id = self.db_repo.clone_url( | |
185 | user=username, uri_tmpl=_def_clone_uri_by_id) |
|
184 | user=username, uri_tmpl=_def_clone_uri_by_id) | |
186 |
|
185 | |||
187 | # If enabled, get statistics data |
|
186 | # If enabled, get statistics data | |
188 |
|
187 | |||
189 | c.show_stats = bool(self.db_repo.enable_statistics) |
|
188 | c.show_stats = bool(self.db_repo.enable_statistics) | |
190 |
|
189 | |||
191 | stats = Session().query(Statistics) \ |
|
190 | stats = Session().query(Statistics) \ | |
192 | .filter(Statistics.repository == self.db_repo) \ |
|
191 | .filter(Statistics.repository == self.db_repo) \ | |
193 | .scalar() |
|
192 | .scalar() | |
194 |
|
193 | |||
195 | c.stats_percentage = 0 |
|
194 | c.stats_percentage = 0 | |
196 |
|
195 | |||
197 | if stats and stats.languages: |
|
196 | if stats and stats.languages: | |
198 | c.no_data = False is self.db_repo.enable_statistics |
|
197 | c.no_data = False is self.db_repo.enable_statistics | |
199 | lang_stats_d = json.loads(stats.languages) |
|
198 | lang_stats_d = json.loads(stats.languages) | |
200 |
|
199 | |||
201 | # Sort first by decreasing count and second by the file extension, |
|
200 | # Sort first by decreasing count and second by the file extension, | |
202 | # so we have a consistent output. |
|
201 | # so we have a consistent output. | |
203 | lang_stats_items = sorted(lang_stats_d.iteritems(), |
|
202 | lang_stats_items = sorted(lang_stats_d.iteritems(), | |
204 | key=lambda k: (-k[1], k[0]))[:10] |
|
203 | key=lambda k: (-k[1], k[0]))[:10] | |
205 | lang_stats = [(x, {"count": y, |
|
204 | lang_stats = [(x, {"count": y, | |
206 | "desc": LANGUAGES_EXTENSIONS_MAP.get(x)}) |
|
205 | "desc": LANGUAGES_EXTENSIONS_MAP.get(x)}) | |
207 | for x, y in lang_stats_items] |
|
206 | for x, y in lang_stats_items] | |
208 |
|
207 | |||
209 | c.trending_languages = json.dumps(lang_stats) |
|
208 | c.trending_languages = json.dumps(lang_stats) | |
210 | else: |
|
209 | else: | |
211 | c.no_data = True |
|
210 | c.no_data = True | |
212 | c.trending_languages = json.dumps({}) |
|
211 | c.trending_languages = json.dumps({}) | |
213 |
|
212 | |||
214 | scm_model = ScmModel() |
|
213 | scm_model = ScmModel() | |
215 | c.enable_downloads = self.db_repo.enable_downloads |
|
214 | c.enable_downloads = self.db_repo.enable_downloads | |
216 | c.repository_followers = scm_model.get_followers(self.db_repo) |
|
215 | c.repository_followers = scm_model.get_followers(self.db_repo) | |
217 | c.repository_forks = scm_model.get_forks(self.db_repo) |
|
216 | c.repository_forks = scm_model.get_forks(self.db_repo) | |
218 | c.repository_is_user_following = scm_model.is_following_repo( |
|
217 | c.repository_is_user_following = scm_model.is_following_repo( | |
219 | self.db_repo_name, self._rhodecode_user.user_id) |
|
218 | self.db_repo_name, self._rhodecode_user.user_id) | |
220 |
|
219 | |||
221 | # first interaction with the VCS instance after here... |
|
220 | # first interaction with the VCS instance after here... | |
222 | if c.repository_requirements_missing: |
|
221 | if c.repository_requirements_missing: | |
223 | self.request.override_renderer = \ |
|
222 | self.request.override_renderer = \ | |
224 | 'rhodecode:templates/summary/missing_requirements.mako' |
|
223 | 'rhodecode:templates/summary/missing_requirements.mako' | |
225 | return self._get_template_context(c) |
|
224 | return self._get_template_context(c) | |
226 |
|
225 | |||
227 | c.readme_data, c.readme_file = \ |
|
226 | c.readme_data, c.readme_file = \ | |
228 | self._get_readme_data(self.db_repo, c.visual.default_renderer) |
|
227 | self._get_readme_data(self.db_repo, c.visual.default_renderer) | |
229 |
|
228 | |||
230 | # loads the summary commits template context |
|
229 | # loads the summary commits template context | |
231 | self._load_commits_context(c) |
|
230 | self._load_commits_context(c) | |
232 |
|
231 | |||
233 | return self._get_template_context(c) |
|
232 | return self._get_template_context(c) | |
234 |
|
233 | |||
235 | def get_request_commit_id(self): |
|
234 | def get_request_commit_id(self): | |
236 | return self.request.matchdict['commit_id'] |
|
235 | return self.request.matchdict['commit_id'] | |
237 |
|
236 | |||
238 | @LoginRequired() |
|
237 | @LoginRequired() | |
239 | @HasRepoPermissionAnyDecorator( |
|
238 | @HasRepoPermissionAnyDecorator( | |
240 | 'repository.read', 'repository.write', 'repository.admin') |
|
239 | 'repository.read', 'repository.write', 'repository.admin') | |
241 | @view_config( |
|
240 | @view_config( | |
242 | route_name='repo_stats', request_method='GET', |
|
241 | route_name='repo_stats', request_method='GET', | |
243 | renderer='json_ext') |
|
242 | renderer='json_ext') | |
244 | def repo_stats(self): |
|
243 | def repo_stats(self): | |
245 | commit_id = self.get_request_commit_id() |
|
244 | commit_id = self.get_request_commit_id() | |
246 |
|
245 | |||
247 | _namespace = caches.get_repo_namespace_key( |
|
246 | _namespace = caches.get_repo_namespace_key( | |
248 | caches.SUMMARY_STATS, self.db_repo_name) |
|
247 | caches.SUMMARY_STATS, self.db_repo_name) | |
249 | show_stats = bool(self.db_repo.enable_statistics) |
|
248 | show_stats = bool(self.db_repo.enable_statistics) | |
250 | cache_manager = caches.get_cache_manager( |
|
249 | cache_manager = caches.get_cache_manager( | |
251 | 'repo_cache_long', _namespace) |
|
250 | 'repo_cache_long', _namespace) | |
252 | _cache_key = caches.compute_key_from_params( |
|
251 | _cache_key = caches.compute_key_from_params( | |
253 | self.db_repo_name, commit_id, show_stats) |
|
252 | self.db_repo_name, commit_id, show_stats) | |
254 |
|
253 | |||
255 | def compute_stats(): |
|
254 | def compute_stats(): | |
256 | code_stats = {} |
|
255 | code_stats = {} | |
257 | size = 0 |
|
256 | size = 0 | |
258 | try: |
|
257 | try: | |
259 | scm_instance = self.db_repo.scm_instance() |
|
258 | scm_instance = self.db_repo.scm_instance() | |
260 | commit = scm_instance.get_commit(commit_id) |
|
259 | commit = scm_instance.get_commit(commit_id) | |
261 |
|
260 | |||
262 | for node in commit.get_filenodes_generator(): |
|
261 | for node in commit.get_filenodes_generator(): | |
263 | size += node.size |
|
262 | size += node.size | |
264 | if not show_stats: |
|
263 | if not show_stats: | |
265 | continue |
|
264 | continue | |
266 | ext = string.lower(node.extension) |
|
265 | ext = string.lower(node.extension) | |
267 | ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext) |
|
266 | ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext) | |
268 | if ext_info: |
|
267 | if ext_info: | |
269 | if ext in code_stats: |
|
268 | if ext in code_stats: | |
270 | code_stats[ext]['count'] += 1 |
|
269 | code_stats[ext]['count'] += 1 | |
271 | else: |
|
270 | else: | |
272 | code_stats[ext] = {"count": 1, "desc": ext_info} |
|
271 | code_stats[ext] = {"count": 1, "desc": ext_info} | |
273 | except EmptyRepositoryError: |
|
272 | except EmptyRepositoryError: | |
274 | pass |
|
273 | pass | |
275 | return {'size': h.format_byte_size_binary(size), |
|
274 | return {'size': h.format_byte_size_binary(size), | |
276 | 'code_stats': code_stats} |
|
275 | 'code_stats': code_stats} | |
277 |
|
276 | |||
278 | stats = cache_manager.get(_cache_key, createfunc=compute_stats) |
|
277 | stats = cache_manager.get(_cache_key, createfunc=compute_stats) | |
279 | return stats |
|
278 | return stats | |
280 |
|
279 | |||
281 | @LoginRequired() |
|
280 | @LoginRequired() | |
282 | @HasRepoPermissionAnyDecorator( |
|
281 | @HasRepoPermissionAnyDecorator( | |
283 | 'repository.read', 'repository.write', 'repository.admin') |
|
282 | 'repository.read', 'repository.write', 'repository.admin') | |
284 | @view_config( |
|
283 | @view_config( | |
285 | route_name='repo_refs_data', request_method='GET', |
|
284 | route_name='repo_refs_data', request_method='GET', | |
286 | renderer='json_ext') |
|
285 | renderer='json_ext') | |
287 | def repo_refs_data(self): |
|
286 | def repo_refs_data(self): | |
288 | _ = self.request.translate |
|
287 | _ = self.request.translate | |
289 | self.load_default_context() |
|
288 | self.load_default_context() | |
290 |
|
289 | |||
291 | repo = self.rhodecode_vcs_repo |
|
290 | repo = self.rhodecode_vcs_repo | |
292 | refs_to_create = [ |
|
291 | refs_to_create = [ | |
293 | (_("Branch"), repo.branches, 'branch'), |
|
292 | (_("Branch"), repo.branches, 'branch'), | |
294 | (_("Tag"), repo.tags, 'tag'), |
|
293 | (_("Tag"), repo.tags, 'tag'), | |
295 | (_("Bookmark"), repo.bookmarks, 'book'), |
|
294 | (_("Bookmark"), repo.bookmarks, 'book'), | |
296 | ] |
|
295 | ] | |
297 | res = self._create_reference_data( |
|
296 | res = self._create_reference_data( | |
298 | repo, self.db_repo_name, refs_to_create) |
|
297 | repo, self.db_repo_name, refs_to_create) | |
299 | data = { |
|
298 | data = { | |
300 | 'more': False, |
|
299 | 'more': False, | |
301 | 'results': res |
|
300 | 'results': res | |
302 | } |
|
301 | } | |
303 | return data |
|
302 | return data | |
304 |
|
303 | |||
305 | @LoginRequired() |
|
304 | @LoginRequired() | |
306 | @HasRepoPermissionAnyDecorator( |
|
305 | @HasRepoPermissionAnyDecorator( | |
307 | 'repository.read', 'repository.write', 'repository.admin') |
|
306 | 'repository.read', 'repository.write', 'repository.admin') | |
308 | @view_config( |
|
307 | @view_config( | |
309 | route_name='repo_refs_changelog_data', request_method='GET', |
|
308 | route_name='repo_refs_changelog_data', request_method='GET', | |
310 | renderer='json_ext') |
|
309 | renderer='json_ext') | |
311 | def repo_refs_changelog_data(self): |
|
310 | def repo_refs_changelog_data(self): | |
312 | _ = self.request.translate |
|
311 | _ = self.request.translate | |
313 | self.load_default_context() |
|
312 | self.load_default_context() | |
314 |
|
313 | |||
315 | repo = self.rhodecode_vcs_repo |
|
314 | repo = self.rhodecode_vcs_repo | |
316 |
|
315 | |||
317 | refs_to_create = [ |
|
316 | refs_to_create = [ | |
318 | (_("Branches"), repo.branches, 'branch'), |
|
317 | (_("Branches"), repo.branches, 'branch'), | |
319 | (_("Closed branches"), repo.branches_closed, 'branch_closed'), |
|
318 | (_("Closed branches"), repo.branches_closed, 'branch_closed'), | |
320 | # TODO: enable when vcs can handle bookmarks filters |
|
319 | # TODO: enable when vcs can handle bookmarks filters | |
321 | # (_("Bookmarks"), repo.bookmarks, "book"), |
|
320 | # (_("Bookmarks"), repo.bookmarks, "book"), | |
322 | ] |
|
321 | ] | |
323 | res = self._create_reference_data( |
|
322 | res = self._create_reference_data( | |
324 | repo, self.db_repo_name, refs_to_create) |
|
323 | repo, self.db_repo_name, refs_to_create) | |
325 | data = { |
|
324 | data = { | |
326 | 'more': False, |
|
325 | 'more': False, | |
327 | 'results': res |
|
326 | 'results': res | |
328 | } |
|
327 | } | |
329 | return data |
|
328 | return data | |
330 |
|
329 | |||
331 | def _create_reference_data(self, repo, full_repo_name, refs_to_create): |
|
330 | def _create_reference_data(self, repo, full_repo_name, refs_to_create): | |
332 | format_ref_id = utils.get_format_ref_id(repo) |
|
331 | format_ref_id = utils.get_format_ref_id(repo) | |
333 |
|
332 | |||
334 | result = [] |
|
333 | result = [] | |
335 | for title, refs, ref_type in refs_to_create: |
|
334 | for title, refs, ref_type in refs_to_create: | |
336 | if refs: |
|
335 | if refs: | |
337 | result.append({ |
|
336 | result.append({ | |
338 | 'text': title, |
|
337 | 'text': title, | |
339 | 'children': self._create_reference_items( |
|
338 | 'children': self._create_reference_items( | |
340 | repo, full_repo_name, refs, ref_type, |
|
339 | repo, full_repo_name, refs, ref_type, | |
341 | format_ref_id), |
|
340 | format_ref_id), | |
342 | }) |
|
341 | }) | |
343 | return result |
|
342 | return result | |
344 |
|
343 | |||
345 | def _create_reference_items(self, repo, full_repo_name, refs, ref_type, |
|
344 | def _create_reference_items(self, repo, full_repo_name, refs, ref_type, | |
346 | format_ref_id): |
|
345 | format_ref_id): | |
347 | result = [] |
|
346 | result = [] | |
348 | is_svn = h.is_svn(repo) |
|
347 | is_svn = h.is_svn(repo) | |
349 | for ref_name, raw_id in refs.iteritems(): |
|
348 | for ref_name, raw_id in refs.iteritems(): | |
350 | files_url = self._create_files_url( |
|
349 | files_url = self._create_files_url( | |
351 | repo, full_repo_name, ref_name, raw_id, is_svn) |
|
350 | repo, full_repo_name, ref_name, raw_id, is_svn) | |
352 | result.append({ |
|
351 | result.append({ | |
353 | 'text': ref_name, |
|
352 | 'text': ref_name, | |
354 | 'id': format_ref_id(ref_name, raw_id), |
|
353 | 'id': format_ref_id(ref_name, raw_id), | |
355 | 'raw_id': raw_id, |
|
354 | 'raw_id': raw_id, | |
356 | 'type': ref_type, |
|
355 | 'type': ref_type, | |
357 | 'files_url': files_url, |
|
356 | 'files_url': files_url, | |
358 | }) |
|
357 | }) | |
359 | return result |
|
358 | return result | |
360 |
|
359 | |||
361 | def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn): |
|
360 | def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn): | |
362 | use_commit_id = '/' in ref_name or is_svn |
|
361 | use_commit_id = '/' in ref_name or is_svn | |
363 |
return h. |
|
362 | return h.route_path( | |
364 |
'files |
|
363 | 'repo_files', | |
365 | repo_name=full_repo_name, |
|
364 | repo_name=full_repo_name, | |
366 | f_path=ref_name if is_svn else '', |
|
365 | f_path=ref_name if is_svn else '', | |
367 |
|
|
366 | commit_id=raw_id if use_commit_id else ref_name, | |
368 | at=ref_name) |
|
367 | _query=dict(at=ref_name)) |
@@ -1,527 +1,528 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 |
|
2 | |||
3 | # Copyright (C) 2010-2017 RhodeCode GmbH |
|
3 | # Copyright (C) 2010-2017 RhodeCode GmbH | |
4 | # |
|
4 | # | |
5 | # This program is free software: you can redistribute it and/or modify |
|
5 | # This program is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
7 | # (only), as published by the Free Software Foundation. |
|
7 | # (only), as published by the Free Software Foundation. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | # |
|
16 | # | |
17 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 |
|
20 | |||
21 | """ |
|
21 | """ | |
22 | Pylons middleware initialization |
|
22 | Pylons middleware initialization | |
23 | """ |
|
23 | """ | |
24 | import logging |
|
24 | import logging | |
25 | import traceback |
|
25 | import traceback | |
26 | from collections import OrderedDict |
|
26 | from collections import OrderedDict | |
27 |
|
27 | |||
28 | from paste.registry import RegistryManager |
|
28 | from paste.registry import RegistryManager | |
29 | from paste.gzipper import make_gzip_middleware |
|
29 | from paste.gzipper import make_gzip_middleware | |
30 | from pylons.wsgiapp import PylonsApp |
|
30 | from pylons.wsgiapp import PylonsApp | |
31 | from pyramid.authorization import ACLAuthorizationPolicy |
|
31 | from pyramid.authorization import ACLAuthorizationPolicy | |
32 | from pyramid.config import Configurator |
|
32 | from pyramid.config import Configurator | |
33 | from pyramid.settings import asbool, aslist |
|
33 | from pyramid.settings import asbool, aslist | |
34 | from pyramid.wsgi import wsgiapp |
|
34 | from pyramid.wsgi import wsgiapp | |
35 | from pyramid.httpexceptions import ( |
|
35 | from pyramid.httpexceptions import ( | |
36 | HTTPException, HTTPError, HTTPInternalServerError, HTTPFound) |
|
36 | HTTPException, HTTPError, HTTPInternalServerError, HTTPFound) | |
37 | from pyramid.events import ApplicationCreated |
|
37 | from pyramid.events import ApplicationCreated | |
38 | from pyramid.renderers import render_to_response |
|
38 | from pyramid.renderers import render_to_response | |
39 | from routes.middleware import RoutesMiddleware |
|
39 | from routes.middleware import RoutesMiddleware | |
40 | import rhodecode |
|
40 | import rhodecode | |
41 |
|
41 | |||
42 | from rhodecode.model import meta |
|
42 | from rhodecode.model import meta | |
43 | from rhodecode.config import patches |
|
43 | from rhodecode.config import patches | |
44 | from rhodecode.config.routing import STATIC_FILE_PREFIX |
|
44 | from rhodecode.config.routing import STATIC_FILE_PREFIX | |
45 | from rhodecode.config.environment import ( |
|
45 | from rhodecode.config.environment import ( | |
46 | load_environment, load_pyramid_environment) |
|
46 | load_environment, load_pyramid_environment) | |
47 |
|
47 | |||
48 | from rhodecode.lib.vcs import VCSCommunicationError |
|
48 | from rhodecode.lib.vcs import VCSCommunicationError | |
49 | from rhodecode.lib.exceptions import VCSServerUnavailable |
|
49 | from rhodecode.lib.exceptions import VCSServerUnavailable | |
50 | from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled |
|
50 | from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled | |
51 | from rhodecode.lib.middleware.error_handling import ( |
|
51 | from rhodecode.lib.middleware.error_handling import ( | |
52 | PylonsErrorHandlingMiddleware) |
|
52 | PylonsErrorHandlingMiddleware) | |
53 | from rhodecode.lib.middleware.https_fixup import HttpsFixup |
|
53 | from rhodecode.lib.middleware.https_fixup import HttpsFixup | |
54 | from rhodecode.lib.middleware.vcs import VCSMiddleware |
|
54 | from rhodecode.lib.middleware.vcs import VCSMiddleware | |
55 | from rhodecode.lib.plugins.utils import register_rhodecode_plugin |
|
55 | from rhodecode.lib.plugins.utils import register_rhodecode_plugin | |
56 | from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict |
|
56 | from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict | |
57 | from rhodecode.subscribers import ( |
|
57 | from rhodecode.subscribers import ( | |
58 | scan_repositories_if_enabled, write_js_routes_if_enabled, |
|
58 | scan_repositories_if_enabled, write_js_routes_if_enabled, | |
59 | write_metadata_if_needed) |
|
59 | write_metadata_if_needed) | |
60 |
|
60 | |||
61 |
|
61 | |||
62 | log = logging.getLogger(__name__) |
|
62 | log = logging.getLogger(__name__) | |
63 |
|
63 | |||
64 |
|
64 | |||
65 | # this is used to avoid avoid the route lookup overhead in routesmiddleware |
|
65 | # this is used to avoid avoid the route lookup overhead in routesmiddleware | |
66 | # for certain routes which won't go to pylons to - eg. static files, debugger |
|
66 | # for certain routes which won't go to pylons to - eg. static files, debugger | |
67 | # it is only needed for the pylons migration and can be removed once complete |
|
67 | # it is only needed for the pylons migration and can be removed once complete | |
68 | class SkippableRoutesMiddleware(RoutesMiddleware): |
|
68 | class SkippableRoutesMiddleware(RoutesMiddleware): | |
69 | """ Routes middleware that allows you to skip prefixes """ |
|
69 | """ Routes middleware that allows you to skip prefixes """ | |
70 |
|
70 | |||
71 | def __init__(self, *args, **kw): |
|
71 | def __init__(self, *args, **kw): | |
72 | self.skip_prefixes = kw.pop('skip_prefixes', []) |
|
72 | self.skip_prefixes = kw.pop('skip_prefixes', []) | |
73 | super(SkippableRoutesMiddleware, self).__init__(*args, **kw) |
|
73 | super(SkippableRoutesMiddleware, self).__init__(*args, **kw) | |
74 |
|
74 | |||
75 | def __call__(self, environ, start_response): |
|
75 | def __call__(self, environ, start_response): | |
76 | for prefix in self.skip_prefixes: |
|
76 | for prefix in self.skip_prefixes: | |
77 | if environ['PATH_INFO'].startswith(prefix): |
|
77 | if environ['PATH_INFO'].startswith(prefix): | |
78 | # added to avoid the case when a missing /_static route falls |
|
78 | # added to avoid the case when a missing /_static route falls | |
79 | # through to pylons and causes an exception as pylons is |
|
79 | # through to pylons and causes an exception as pylons is | |
80 | # expecting wsgiorg.routingargs to be set in the environ |
|
80 | # expecting wsgiorg.routingargs to be set in the environ | |
81 | # by RoutesMiddleware. |
|
81 | # by RoutesMiddleware. | |
82 | if 'wsgiorg.routing_args' not in environ: |
|
82 | if 'wsgiorg.routing_args' not in environ: | |
83 | environ['wsgiorg.routing_args'] = (None, {}) |
|
83 | environ['wsgiorg.routing_args'] = (None, {}) | |
84 | return self.app(environ, start_response) |
|
84 | return self.app(environ, start_response) | |
85 |
|
85 | |||
86 | return super(SkippableRoutesMiddleware, self).__call__( |
|
86 | return super(SkippableRoutesMiddleware, self).__call__( | |
87 | environ, start_response) |
|
87 | environ, start_response) | |
88 |
|
88 | |||
89 |
|
89 | |||
90 | def make_app(global_conf, static_files=True, **app_conf): |
|
90 | def make_app(global_conf, static_files=True, **app_conf): | |
91 | """Create a Pylons WSGI application and return it |
|
91 | """Create a Pylons WSGI application and return it | |
92 |
|
92 | |||
93 | ``global_conf`` |
|
93 | ``global_conf`` | |
94 | The inherited configuration for this application. Normally from |
|
94 | The inherited configuration for this application. Normally from | |
95 | the [DEFAULT] section of the Paste ini file. |
|
95 | the [DEFAULT] section of the Paste ini file. | |
96 |
|
96 | |||
97 | ``app_conf`` |
|
97 | ``app_conf`` | |
98 | The application's local configuration. Normally specified in |
|
98 | The application's local configuration. Normally specified in | |
99 | the [app:<name>] section of the Paste ini file (where <name> |
|
99 | the [app:<name>] section of the Paste ini file (where <name> | |
100 | defaults to main). |
|
100 | defaults to main). | |
101 |
|
101 | |||
102 | """ |
|
102 | """ | |
103 | # Apply compatibility patches |
|
103 | # Apply compatibility patches | |
104 | patches.kombu_1_5_1_python_2_7_11() |
|
104 | patches.kombu_1_5_1_python_2_7_11() | |
105 | patches.inspect_getargspec() |
|
105 | patches.inspect_getargspec() | |
106 |
|
106 | |||
107 | # Configure the Pylons environment |
|
107 | # Configure the Pylons environment | |
108 | config = load_environment(global_conf, app_conf) |
|
108 | config = load_environment(global_conf, app_conf) | |
109 |
|
109 | |||
110 | # The Pylons WSGI app |
|
110 | # The Pylons WSGI app | |
111 | app = PylonsApp(config=config) |
|
111 | app = PylonsApp(config=config) | |
112 |
|
112 | |||
113 | # Establish the Registry for this application |
|
113 | # Establish the Registry for this application | |
114 | app = RegistryManager(app) |
|
114 | app = RegistryManager(app) | |
115 |
|
115 | |||
116 | app.config = config |
|
116 | app.config = config | |
117 |
|
117 | |||
118 | return app |
|
118 | return app | |
119 |
|
119 | |||
120 |
|
120 | |||
121 | def make_pyramid_app(global_config, **settings): |
|
121 | def make_pyramid_app(global_config, **settings): | |
122 | """ |
|
122 | """ | |
123 | Constructs the WSGI application based on Pyramid and wraps the Pylons based |
|
123 | Constructs the WSGI application based on Pyramid and wraps the Pylons based | |
124 | application. |
|
124 | application. | |
125 |
|
125 | |||
126 | Specials: |
|
126 | Specials: | |
127 |
|
127 | |||
128 | * We migrate from Pylons to Pyramid. While doing this, we keep both |
|
128 | * We migrate from Pylons to Pyramid. While doing this, we keep both | |
129 | frameworks functional. This involves moving some WSGI middlewares around |
|
129 | frameworks functional. This involves moving some WSGI middlewares around | |
130 | and providing access to some data internals, so that the old code is |
|
130 | and providing access to some data internals, so that the old code is | |
131 | still functional. |
|
131 | still functional. | |
132 |
|
132 | |||
133 | * The application can also be integrated like a plugin via the call to |
|
133 | * The application can also be integrated like a plugin via the call to | |
134 | `includeme`. This is accompanied with the other utility functions which |
|
134 | `includeme`. This is accompanied with the other utility functions which | |
135 | are called. Changing this should be done with great care to not break |
|
135 | are called. Changing this should be done with great care to not break | |
136 | cases when these fragments are assembled from another place. |
|
136 | cases when these fragments are assembled from another place. | |
137 |
|
137 | |||
138 | """ |
|
138 | """ | |
139 | # The edition string should be available in pylons too, so we add it here |
|
139 | # The edition string should be available in pylons too, so we add it here | |
140 | # before copying the settings. |
|
140 | # before copying the settings. | |
141 | settings.setdefault('rhodecode.edition', 'Community Edition') |
|
141 | settings.setdefault('rhodecode.edition', 'Community Edition') | |
142 |
|
142 | |||
143 | # As long as our Pylons application does expect "unprepared" settings, make |
|
143 | # As long as our Pylons application does expect "unprepared" settings, make | |
144 | # sure that we keep an unmodified copy. This avoids unintentional change of |
|
144 | # sure that we keep an unmodified copy. This avoids unintentional change of | |
145 | # behavior in the old application. |
|
145 | # behavior in the old application. | |
146 | settings_pylons = settings.copy() |
|
146 | settings_pylons = settings.copy() | |
147 |
|
147 | |||
148 | sanitize_settings_and_apply_defaults(settings) |
|
148 | sanitize_settings_and_apply_defaults(settings) | |
149 | config = Configurator(settings=settings) |
|
149 | config = Configurator(settings=settings) | |
150 | add_pylons_compat_data(config.registry, global_config, settings_pylons) |
|
150 | add_pylons_compat_data(config.registry, global_config, settings_pylons) | |
151 |
|
151 | |||
152 | load_pyramid_environment(global_config, settings) |
|
152 | load_pyramid_environment(global_config, settings) | |
153 |
|
153 | |||
154 | includeme_first(config) |
|
154 | includeme_first(config) | |
155 | includeme(config) |
|
155 | includeme(config) | |
|
156 | ||||
156 | pyramid_app = config.make_wsgi_app() |
|
157 | pyramid_app = config.make_wsgi_app() | |
157 | pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config) |
|
158 | pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config) | |
158 | pyramid_app.config = config |
|
159 | pyramid_app.config = config | |
159 |
|
160 | |||
160 | # creating the app uses a connection - return it after we are done |
|
161 | # creating the app uses a connection - return it after we are done | |
161 | meta.Session.remove() |
|
162 | meta.Session.remove() | |
162 |
|
163 | |||
163 | return pyramid_app |
|
164 | return pyramid_app | |
164 |
|
165 | |||
165 |
|
166 | |||
166 | def make_not_found_view(config): |
|
167 | def make_not_found_view(config): | |
167 | """ |
|
168 | """ | |
168 | This creates the view which should be registered as not-found-view to |
|
169 | This creates the view which should be registered as not-found-view to | |
169 | pyramid. Basically it contains of the old pylons app, converted to a view. |
|
170 | pyramid. Basically it contains of the old pylons app, converted to a view. | |
170 | Additionally it is wrapped by some other middlewares. |
|
171 | Additionally it is wrapped by some other middlewares. | |
171 | """ |
|
172 | """ | |
172 | settings = config.registry.settings |
|
173 | settings = config.registry.settings | |
173 | vcs_server_enabled = settings['vcs.server.enable'] |
|
174 | vcs_server_enabled = settings['vcs.server.enable'] | |
174 |
|
175 | |||
175 | # Make pylons app from unprepared settings. |
|
176 | # Make pylons app from unprepared settings. | |
176 | pylons_app = make_app( |
|
177 | pylons_app = make_app( | |
177 | config.registry._pylons_compat_global_config, |
|
178 | config.registry._pylons_compat_global_config, | |
178 | **config.registry._pylons_compat_settings) |
|
179 | **config.registry._pylons_compat_settings) | |
179 | config.registry._pylons_compat_config = pylons_app.config |
|
180 | config.registry._pylons_compat_config = pylons_app.config | |
180 |
|
181 | |||
181 | # Appenlight monitoring. |
|
182 | # Appenlight monitoring. | |
182 | pylons_app, appenlight_client = wrap_in_appenlight_if_enabled( |
|
183 | pylons_app, appenlight_client = wrap_in_appenlight_if_enabled( | |
183 | pylons_app, settings) |
|
184 | pylons_app, settings) | |
184 |
|
185 | |||
185 | # The pylons app is executed inside of the pyramid 404 exception handler. |
|
186 | # The pylons app is executed inside of the pyramid 404 exception handler. | |
186 | # Exceptions which are raised inside of it are not handled by pyramid |
|
187 | # Exceptions which are raised inside of it are not handled by pyramid | |
187 | # again. Therefore we add a middleware that invokes the error handler in |
|
188 | # again. Therefore we add a middleware that invokes the error handler in | |
188 | # case of an exception or error response. This way we return proper error |
|
189 | # case of an exception or error response. This way we return proper error | |
189 | # HTML pages in case of an error. |
|
190 | # HTML pages in case of an error. | |
190 | reraise = (settings.get('debugtoolbar.enabled', False) or |
|
191 | reraise = (settings.get('debugtoolbar.enabled', False) or | |
191 | rhodecode.disable_error_handler) |
|
192 | rhodecode.disable_error_handler) | |
192 | pylons_app = PylonsErrorHandlingMiddleware( |
|
193 | pylons_app = PylonsErrorHandlingMiddleware( | |
193 | pylons_app, error_handler, reraise) |
|
194 | pylons_app, error_handler, reraise) | |
194 |
|
195 | |||
195 | # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a |
|
196 | # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a | |
196 | # view to handle the request. Therefore it is wrapped around the pylons |
|
197 | # view to handle the request. Therefore it is wrapped around the pylons | |
197 | # app. It has to be outside of the error handling otherwise error responses |
|
198 | # app. It has to be outside of the error handling otherwise error responses | |
198 | # from the vcsserver are converted to HTML error pages. This confuses the |
|
199 | # from the vcsserver are converted to HTML error pages. This confuses the | |
199 | # command line tools and the user won't get a meaningful error message. |
|
200 | # command line tools and the user won't get a meaningful error message. | |
200 | if vcs_server_enabled: |
|
201 | if vcs_server_enabled: | |
201 | pylons_app = VCSMiddleware( |
|
202 | pylons_app = VCSMiddleware( | |
202 | pylons_app, settings, appenlight_client, registry=config.registry) |
|
203 | pylons_app, settings, appenlight_client, registry=config.registry) | |
203 |
|
204 | |||
204 | # Convert WSGI app to pyramid view and return it. |
|
205 | # Convert WSGI app to pyramid view and return it. | |
205 | return wsgiapp(pylons_app) |
|
206 | return wsgiapp(pylons_app) | |
206 |
|
207 | |||
207 |
|
208 | |||
208 | def add_pylons_compat_data(registry, global_config, settings): |
|
209 | def add_pylons_compat_data(registry, global_config, settings): | |
209 | """ |
|
210 | """ | |
210 | Attach data to the registry to support the Pylons integration. |
|
211 | Attach data to the registry to support the Pylons integration. | |
211 | """ |
|
212 | """ | |
212 | registry._pylons_compat_global_config = global_config |
|
213 | registry._pylons_compat_global_config = global_config | |
213 | registry._pylons_compat_settings = settings |
|
214 | registry._pylons_compat_settings = settings | |
214 |
|
215 | |||
215 |
|
216 | |||
216 | def error_handler(exception, request): |
|
217 | def error_handler(exception, request): | |
217 | import rhodecode |
|
218 | import rhodecode | |
218 | from rhodecode.lib import helpers |
|
219 | from rhodecode.lib import helpers | |
219 |
|
220 | |||
220 | rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode' |
|
221 | rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode' | |
221 |
|
222 | |||
222 | base_response = HTTPInternalServerError() |
|
223 | base_response = HTTPInternalServerError() | |
223 | # prefer original exception for the response since it may have headers set |
|
224 | # prefer original exception for the response since it may have headers set | |
224 | if isinstance(exception, HTTPException): |
|
225 | if isinstance(exception, HTTPException): | |
225 | base_response = exception |
|
226 | base_response = exception | |
226 | elif isinstance(exception, VCSCommunicationError): |
|
227 | elif isinstance(exception, VCSCommunicationError): | |
227 | base_response = VCSServerUnavailable() |
|
228 | base_response = VCSServerUnavailable() | |
228 |
|
229 | |||
229 | def is_http_error(response): |
|
230 | def is_http_error(response): | |
230 | # error which should have traceback |
|
231 | # error which should have traceback | |
231 | return response.status_code > 499 |
|
232 | return response.status_code > 499 | |
232 |
|
233 | |||
233 | if is_http_error(base_response): |
|
234 | if is_http_error(base_response): | |
234 | log.exception( |
|
235 | log.exception( | |
235 | 'error occurred handling this request for path: %s', request.path) |
|
236 | 'error occurred handling this request for path: %s', request.path) | |
236 |
|
237 | |||
237 | c = AttributeDict() |
|
238 | c = AttributeDict() | |
238 | c.error_message = base_response.status |
|
239 | c.error_message = base_response.status | |
239 | c.error_explanation = base_response.explanation or str(base_response) |
|
240 | c.error_explanation = base_response.explanation or str(base_response) | |
240 | c.visual = AttributeDict() |
|
241 | c.visual = AttributeDict() | |
241 |
|
242 | |||
242 | c.visual.rhodecode_support_url = ( |
|
243 | c.visual.rhodecode_support_url = ( | |
243 | request.registry.settings.get('rhodecode_support_url') or |
|
244 | request.registry.settings.get('rhodecode_support_url') or | |
244 | request.route_url('rhodecode_support') |
|
245 | request.route_url('rhodecode_support') | |
245 | ) |
|
246 | ) | |
246 | c.redirect_time = 0 |
|
247 | c.redirect_time = 0 | |
247 | c.rhodecode_name = rhodecode_title |
|
248 | c.rhodecode_name = rhodecode_title | |
248 | if not c.rhodecode_name: |
|
249 | if not c.rhodecode_name: | |
249 | c.rhodecode_name = 'Rhodecode' |
|
250 | c.rhodecode_name = 'Rhodecode' | |
250 |
|
251 | |||
251 | c.causes = [] |
|
252 | c.causes = [] | |
252 | if hasattr(base_response, 'causes'): |
|
253 | if hasattr(base_response, 'causes'): | |
253 | c.causes = base_response.causes |
|
254 | c.causes = base_response.causes | |
254 | c.messages = helpers.flash.pop_messages(request=request) |
|
255 | c.messages = helpers.flash.pop_messages(request=request) | |
255 | c.traceback = traceback.format_exc() |
|
256 | c.traceback = traceback.format_exc() | |
256 | response = render_to_response( |
|
257 | response = render_to_response( | |
257 | '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request, |
|
258 | '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request, | |
258 | response=base_response) |
|
259 | response=base_response) | |
259 |
|
260 | |||
260 | return response |
|
261 | return response | |
261 |
|
262 | |||
262 |
|
263 | |||
263 | def includeme(config): |
|
264 | def includeme(config): | |
264 | settings = config.registry.settings |
|
265 | settings = config.registry.settings | |
265 |
|
266 | |||
266 | # plugin information |
|
267 | # plugin information | |
267 | config.registry.rhodecode_plugins = OrderedDict() |
|
268 | config.registry.rhodecode_plugins = OrderedDict() | |
268 |
|
269 | |||
269 | config.add_directive( |
|
270 | config.add_directive( | |
270 | 'register_rhodecode_plugin', register_rhodecode_plugin) |
|
271 | 'register_rhodecode_plugin', register_rhodecode_plugin) | |
271 |
|
272 | |||
272 | if asbool(settings.get('appenlight', 'false')): |
|
273 | if asbool(settings.get('appenlight', 'false')): | |
273 | config.include('appenlight_client.ext.pyramid_tween') |
|
274 | config.include('appenlight_client.ext.pyramid_tween') | |
274 |
|
275 | |||
275 | # Includes which are required. The application would fail without them. |
|
276 | # Includes which are required. The application would fail without them. | |
276 | config.include('pyramid_mako') |
|
277 | config.include('pyramid_mako') | |
277 | config.include('pyramid_beaker') |
|
278 | config.include('pyramid_beaker') | |
278 |
|
279 | |||
279 | config.include('rhodecode.authentication') |
|
280 | config.include('rhodecode.authentication') | |
280 | config.include('rhodecode.integrations') |
|
281 | config.include('rhodecode.integrations') | |
281 |
|
282 | |||
282 | # apps |
|
283 | # apps | |
283 | config.include('rhodecode.apps._base') |
|
284 | config.include('rhodecode.apps._base') | |
284 | config.include('rhodecode.apps.ops') |
|
285 | config.include('rhodecode.apps.ops') | |
285 |
|
286 | |||
286 | config.include('rhodecode.apps.admin') |
|
287 | config.include('rhodecode.apps.admin') | |
287 | config.include('rhodecode.apps.channelstream') |
|
288 | config.include('rhodecode.apps.channelstream') | |
288 | config.include('rhodecode.apps.login') |
|
289 | config.include('rhodecode.apps.login') | |
289 | config.include('rhodecode.apps.home') |
|
290 | config.include('rhodecode.apps.home') | |
290 | config.include('rhodecode.apps.repository') |
|
291 | config.include('rhodecode.apps.repository') | |
291 | config.include('rhodecode.apps.repo_group') |
|
292 | config.include('rhodecode.apps.repo_group') | |
292 | config.include('rhodecode.apps.search') |
|
293 | config.include('rhodecode.apps.search') | |
293 | config.include('rhodecode.apps.user_profile') |
|
294 | config.include('rhodecode.apps.user_profile') | |
294 | config.include('rhodecode.apps.my_account') |
|
295 | config.include('rhodecode.apps.my_account') | |
295 | config.include('rhodecode.apps.svn_support') |
|
296 | config.include('rhodecode.apps.svn_support') | |
296 | config.include('rhodecode.apps.gist') |
|
297 | config.include('rhodecode.apps.gist') | |
297 |
|
298 | |||
298 | config.include('rhodecode.apps.debug_style') |
|
299 | config.include('rhodecode.apps.debug_style') | |
299 | config.include('rhodecode.tweens') |
|
300 | config.include('rhodecode.tweens') | |
300 | config.include('rhodecode.api') |
|
301 | config.include('rhodecode.api') | |
301 |
|
302 | |||
302 | config.add_route( |
|
303 | config.add_route( | |
303 | 'rhodecode_support', 'https://rhodecode.com/help/', static=True) |
|
304 | 'rhodecode_support', 'https://rhodecode.com/help/', static=True) | |
304 |
|
305 | |||
305 | config.add_translation_dirs('rhodecode:i18n/') |
|
306 | config.add_translation_dirs('rhodecode:i18n/') | |
306 | settings['default_locale_name'] = settings.get('lang', 'en') |
|
307 | settings['default_locale_name'] = settings.get('lang', 'en') | |
307 |
|
308 | |||
308 | # Add subscribers. |
|
309 | # Add subscribers. | |
309 | config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated) |
|
310 | config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated) | |
310 | config.add_subscriber(write_metadata_if_needed, ApplicationCreated) |
|
311 | config.add_subscriber(write_metadata_if_needed, ApplicationCreated) | |
311 | config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated) |
|
312 | config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated) | |
312 |
|
313 | |||
313 | config.add_request_method( |
|
314 | config.add_request_method( | |
314 | 'rhodecode.lib.partial_renderer.get_partial_renderer', |
|
315 | 'rhodecode.lib.partial_renderer.get_partial_renderer', | |
315 | 'get_partial_renderer') |
|
316 | 'get_partial_renderer') | |
316 |
|
317 | |||
317 | # events |
|
318 | # events | |
318 | # TODO(marcink): this should be done when pyramid migration is finished |
|
319 | # TODO(marcink): this should be done when pyramid migration is finished | |
319 | # config.add_subscriber( |
|
320 | # config.add_subscriber( | |
320 | # 'rhodecode.integrations.integrations_event_handler', |
|
321 | # 'rhodecode.integrations.integrations_event_handler', | |
321 | # 'rhodecode.events.RhodecodeEvent') |
|
322 | # 'rhodecode.events.RhodecodeEvent') | |
322 |
|
323 | |||
323 | # Set the authorization policy. |
|
324 | # Set the authorization policy. | |
324 | authz_policy = ACLAuthorizationPolicy() |
|
325 | authz_policy = ACLAuthorizationPolicy() | |
325 | config.set_authorization_policy(authz_policy) |
|
326 | config.set_authorization_policy(authz_policy) | |
326 |
|
327 | |||
327 | # Set the default renderer for HTML templates to mako. |
|
328 | # Set the default renderer for HTML templates to mako. | |
328 | config.add_mako_renderer('.html') |
|
329 | config.add_mako_renderer('.html') | |
329 |
|
330 | |||
330 | config.add_renderer( |
|
331 | config.add_renderer( | |
331 | name='json_ext', |
|
332 | name='json_ext', | |
332 | factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json') |
|
333 | factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json') | |
333 |
|
334 | |||
334 | # include RhodeCode plugins |
|
335 | # include RhodeCode plugins | |
335 | includes = aslist(settings.get('rhodecode.includes', [])) |
|
336 | includes = aslist(settings.get('rhodecode.includes', [])) | |
336 | for inc in includes: |
|
337 | for inc in includes: | |
337 | config.include(inc) |
|
338 | config.include(inc) | |
338 |
|
339 | |||
339 | # This is the glue which allows us to migrate in chunks. By registering the |
|
340 | # This is the glue which allows us to migrate in chunks. By registering the | |
340 | # pylons based application as the "Not Found" view in Pyramid, we will |
|
341 | # pylons based application as the "Not Found" view in Pyramid, we will | |
341 | # fallback to the old application each time the new one does not yet know |
|
342 | # fallback to the old application each time the new one does not yet know | |
342 | # how to handle a request. |
|
343 | # how to handle a request. | |
343 | config.add_notfound_view(make_not_found_view(config)) |
|
344 | config.add_notfound_view(make_not_found_view(config)) | |
344 |
|
345 | |||
345 | if not settings.get('debugtoolbar.enabled', False): |
|
346 | if not settings.get('debugtoolbar.enabled', False): | |
346 | # disabled debugtoolbar handle all exceptions via the error_handlers |
|
347 | # disabled debugtoolbar handle all exceptions via the error_handlers | |
347 | config.add_view(error_handler, context=Exception) |
|
348 | config.add_view(error_handler, context=Exception) | |
348 |
|
349 | |||
349 | config.add_view(error_handler, context=HTTPError) |
|
350 | config.add_view(error_handler, context=HTTPError) | |
350 |
|
351 | |||
351 |
|
352 | |||
352 | def includeme_first(config): |
|
353 | def includeme_first(config): | |
353 | # redirect automatic browser favicon.ico requests to correct place |
|
354 | # redirect automatic browser favicon.ico requests to correct place | |
354 | def favicon_redirect(context, request): |
|
355 | def favicon_redirect(context, request): | |
355 | return HTTPFound( |
|
356 | return HTTPFound( | |
356 | request.static_path('rhodecode:public/images/favicon.ico')) |
|
357 | request.static_path('rhodecode:public/images/favicon.ico')) | |
357 |
|
358 | |||
358 | config.add_view(favicon_redirect, route_name='favicon') |
|
359 | config.add_view(favicon_redirect, route_name='favicon') | |
359 | config.add_route('favicon', '/favicon.ico') |
|
360 | config.add_route('favicon', '/favicon.ico') | |
360 |
|
361 | |||
361 | def robots_redirect(context, request): |
|
362 | def robots_redirect(context, request): | |
362 | return HTTPFound( |
|
363 | return HTTPFound( | |
363 | request.static_path('rhodecode:public/robots.txt')) |
|
364 | request.static_path('rhodecode:public/robots.txt')) | |
364 |
|
365 | |||
365 | config.add_view(robots_redirect, route_name='robots') |
|
366 | config.add_view(robots_redirect, route_name='robots') | |
366 | config.add_route('robots', '/robots.txt') |
|
367 | config.add_route('robots', '/robots.txt') | |
367 |
|
368 | |||
368 | config.add_static_view( |
|
369 | config.add_static_view( | |
369 | '_static/deform', 'deform:static') |
|
370 | '_static/deform', 'deform:static') | |
370 | config.add_static_view( |
|
371 | config.add_static_view( | |
371 | '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24) |
|
372 | '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24) | |
372 |
|
373 | |||
373 |
|
374 | |||
374 | def wrap_app_in_wsgi_middlewares(pyramid_app, config): |
|
375 | def wrap_app_in_wsgi_middlewares(pyramid_app, config): | |
375 | """ |
|
376 | """ | |
376 | Apply outer WSGI middlewares around the application. |
|
377 | Apply outer WSGI middlewares around the application. | |
377 |
|
378 | |||
378 | Part of this has been moved up from the Pylons layer, so that the |
|
379 | Part of this has been moved up from the Pylons layer, so that the | |
379 | data is also available if old Pylons code is hit through an already ported |
|
380 | data is also available if old Pylons code is hit through an already ported | |
380 | view. |
|
381 | view. | |
381 | """ |
|
382 | """ | |
382 | settings = config.registry.settings |
|
383 | settings = config.registry.settings | |
383 |
|
384 | |||
384 | # enable https redirects based on HTTP_X_URL_SCHEME set by proxy |
|
385 | # enable https redirects based on HTTP_X_URL_SCHEME set by proxy | |
385 | pyramid_app = HttpsFixup(pyramid_app, settings) |
|
386 | pyramid_app = HttpsFixup(pyramid_app, settings) | |
386 |
|
387 | |||
387 | # Add RoutesMiddleware to support the pylons compatibility tween during |
|
388 | # Add RoutesMiddleware to support the pylons compatibility tween during | |
388 | # migration to pyramid. |
|
389 | # migration to pyramid. | |
389 |
|
390 | |||
390 | # TODO(marcink): remove after migration to pyramid |
|
391 | # TODO(marcink): remove after migration to pyramid | |
391 | if hasattr(config.registry, '_pylons_compat_config'): |
|
392 | if hasattr(config.registry, '_pylons_compat_config'): | |
392 | routes_map = config.registry._pylons_compat_config['routes.map'] |
|
393 | routes_map = config.registry._pylons_compat_config['routes.map'] | |
393 | pyramid_app = SkippableRoutesMiddleware( |
|
394 | pyramid_app = SkippableRoutesMiddleware( | |
394 | pyramid_app, routes_map, |
|
395 | pyramid_app, routes_map, | |
395 | skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar')) |
|
396 | skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar')) | |
396 |
|
397 | |||
397 | pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings) |
|
398 | pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings) | |
398 |
|
399 | |||
399 | if settings['gzip_responses']: |
|
400 | if settings['gzip_responses']: | |
400 | pyramid_app = make_gzip_middleware( |
|
401 | pyramid_app = make_gzip_middleware( | |
401 | pyramid_app, settings, compress_level=1) |
|
402 | pyramid_app, settings, compress_level=1) | |
402 |
|
403 | |||
403 | # this should be the outer most middleware in the wsgi stack since |
|
404 | # this should be the outer most middleware in the wsgi stack since | |
404 | # middleware like Routes make database calls |
|
405 | # middleware like Routes make database calls | |
405 | def pyramid_app_with_cleanup(environ, start_response): |
|
406 | def pyramid_app_with_cleanup(environ, start_response): | |
406 | try: |
|
407 | try: | |
407 | return pyramid_app(environ, start_response) |
|
408 | return pyramid_app(environ, start_response) | |
408 | finally: |
|
409 | finally: | |
409 | # Dispose current database session and rollback uncommitted |
|
410 | # Dispose current database session and rollback uncommitted | |
410 | # transactions. |
|
411 | # transactions. | |
411 | meta.Session.remove() |
|
412 | meta.Session.remove() | |
412 |
|
413 | |||
413 | # In a single threaded mode server, on non sqlite db we should have |
|
414 | # In a single threaded mode server, on non sqlite db we should have | |
414 | # '0 Current Checked out connections' at the end of a request, |
|
415 | # '0 Current Checked out connections' at the end of a request, | |
415 | # if not, then something, somewhere is leaving a connection open |
|
416 | # if not, then something, somewhere is leaving a connection open | |
416 | pool = meta.Base.metadata.bind.engine.pool |
|
417 | pool = meta.Base.metadata.bind.engine.pool | |
417 | log.debug('sa pool status: %s', pool.status()) |
|
418 | log.debug('sa pool status: %s', pool.status()) | |
418 |
|
419 | |||
419 | return pyramid_app_with_cleanup |
|
420 | return pyramid_app_with_cleanup | |
420 |
|
421 | |||
421 |
|
422 | |||
422 | def sanitize_settings_and_apply_defaults(settings): |
|
423 | def sanitize_settings_and_apply_defaults(settings): | |
423 | """ |
|
424 | """ | |
424 | Applies settings defaults and does all type conversion. |
|
425 | Applies settings defaults and does all type conversion. | |
425 |
|
426 | |||
426 | We would move all settings parsing and preparation into this place, so that |
|
427 | We would move all settings parsing and preparation into this place, so that | |
427 | we have only one place left which deals with this part. The remaining parts |
|
428 | we have only one place left which deals with this part. The remaining parts | |
428 | of the application would start to rely fully on well prepared settings. |
|
429 | of the application would start to rely fully on well prepared settings. | |
429 |
|
430 | |||
430 | This piece would later be split up per topic to avoid a big fat monster |
|
431 | This piece would later be split up per topic to avoid a big fat monster | |
431 | function. |
|
432 | function. | |
432 | """ |
|
433 | """ | |
433 |
|
434 | |||
434 | # Pyramid's mako renderer has to search in the templates folder so that the |
|
435 | # Pyramid's mako renderer has to search in the templates folder so that the | |
435 | # old templates still work. Ported and new templates are expected to use |
|
436 | # old templates still work. Ported and new templates are expected to use | |
436 | # real asset specifications for the includes. |
|
437 | # real asset specifications for the includes. | |
437 | mako_directories = settings.setdefault('mako.directories', [ |
|
438 | mako_directories = settings.setdefault('mako.directories', [ | |
438 | # Base templates of the original Pylons application |
|
439 | # Base templates of the original Pylons application | |
439 | 'rhodecode:templates', |
|
440 | 'rhodecode:templates', | |
440 | ]) |
|
441 | ]) | |
441 | log.debug( |
|
442 | log.debug( | |
442 | "Using the following Mako template directories: %s", |
|
443 | "Using the following Mako template directories: %s", | |
443 | mako_directories) |
|
444 | mako_directories) | |
444 |
|
445 | |||
445 | # Default includes, possible to change as a user |
|
446 | # Default includes, possible to change as a user | |
446 | pyramid_includes = settings.setdefault('pyramid.includes', [ |
|
447 | pyramid_includes = settings.setdefault('pyramid.includes', [ | |
447 | 'rhodecode.lib.middleware.request_wrapper', |
|
448 | 'rhodecode.lib.middleware.request_wrapper', | |
448 | ]) |
|
449 | ]) | |
449 | log.debug( |
|
450 | log.debug( | |
450 | "Using the following pyramid.includes: %s", |
|
451 | "Using the following pyramid.includes: %s", | |
451 | pyramid_includes) |
|
452 | pyramid_includes) | |
452 |
|
453 | |||
453 | # TODO: johbo: Re-think this, usually the call to config.include |
|
454 | # TODO: johbo: Re-think this, usually the call to config.include | |
454 | # should allow to pass in a prefix. |
|
455 | # should allow to pass in a prefix. | |
455 | settings.setdefault('rhodecode.api.url', '/_admin/api') |
|
456 | settings.setdefault('rhodecode.api.url', '/_admin/api') | |
456 |
|
457 | |||
457 | # Sanitize generic settings. |
|
458 | # Sanitize generic settings. | |
458 | _list_setting(settings, 'default_encoding', 'UTF-8') |
|
459 | _list_setting(settings, 'default_encoding', 'UTF-8') | |
459 | _bool_setting(settings, 'is_test', 'false') |
|
460 | _bool_setting(settings, 'is_test', 'false') | |
460 | _bool_setting(settings, 'gzip_responses', 'false') |
|
461 | _bool_setting(settings, 'gzip_responses', 'false') | |
461 |
|
462 | |||
462 | # Call split out functions that sanitize settings for each topic. |
|
463 | # Call split out functions that sanitize settings for each topic. | |
463 | _sanitize_appenlight_settings(settings) |
|
464 | _sanitize_appenlight_settings(settings) | |
464 | _sanitize_vcs_settings(settings) |
|
465 | _sanitize_vcs_settings(settings) | |
465 |
|
466 | |||
466 | return settings |
|
467 | return settings | |
467 |
|
468 | |||
468 |
|
469 | |||
469 | def _sanitize_appenlight_settings(settings): |
|
470 | def _sanitize_appenlight_settings(settings): | |
470 | _bool_setting(settings, 'appenlight', 'false') |
|
471 | _bool_setting(settings, 'appenlight', 'false') | |
471 |
|
472 | |||
472 |
|
473 | |||
473 | def _sanitize_vcs_settings(settings): |
|
474 | def _sanitize_vcs_settings(settings): | |
474 | """ |
|
475 | """ | |
475 | Applies settings defaults and does type conversion for all VCS related |
|
476 | Applies settings defaults and does type conversion for all VCS related | |
476 | settings. |
|
477 | settings. | |
477 | """ |
|
478 | """ | |
478 | _string_setting(settings, 'vcs.svn.compatible_version', '') |
|
479 | _string_setting(settings, 'vcs.svn.compatible_version', '') | |
479 | _string_setting(settings, 'git_rev_filter', '--all') |
|
480 | _string_setting(settings, 'git_rev_filter', '--all') | |
480 | _string_setting(settings, 'vcs.hooks.protocol', 'http') |
|
481 | _string_setting(settings, 'vcs.hooks.protocol', 'http') | |
481 | _string_setting(settings, 'vcs.scm_app_implementation', 'http') |
|
482 | _string_setting(settings, 'vcs.scm_app_implementation', 'http') | |
482 | _string_setting(settings, 'vcs.server', '') |
|
483 | _string_setting(settings, 'vcs.server', '') | |
483 | _string_setting(settings, 'vcs.server.log_level', 'debug') |
|
484 | _string_setting(settings, 'vcs.server.log_level', 'debug') | |
484 | _string_setting(settings, 'vcs.server.protocol', 'http') |
|
485 | _string_setting(settings, 'vcs.server.protocol', 'http') | |
485 | _bool_setting(settings, 'startup.import_repos', 'false') |
|
486 | _bool_setting(settings, 'startup.import_repos', 'false') | |
486 | _bool_setting(settings, 'vcs.hooks.direct_calls', 'false') |
|
487 | _bool_setting(settings, 'vcs.hooks.direct_calls', 'false') | |
487 | _bool_setting(settings, 'vcs.server.enable', 'true') |
|
488 | _bool_setting(settings, 'vcs.server.enable', 'true') | |
488 | _bool_setting(settings, 'vcs.start_server', 'false') |
|
489 | _bool_setting(settings, 'vcs.start_server', 'false') | |
489 | _list_setting(settings, 'vcs.backends', 'hg, git, svn') |
|
490 | _list_setting(settings, 'vcs.backends', 'hg, git, svn') | |
490 | _int_setting(settings, 'vcs.connection_timeout', 3600) |
|
491 | _int_setting(settings, 'vcs.connection_timeout', 3600) | |
491 |
|
492 | |||
492 | # Support legacy values of vcs.scm_app_implementation. Legacy |
|
493 | # Support legacy values of vcs.scm_app_implementation. Legacy | |
493 | # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http' |
|
494 | # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http' | |
494 | # which is now mapped to 'http'. |
|
495 | # which is now mapped to 'http'. | |
495 | scm_app_impl = settings['vcs.scm_app_implementation'] |
|
496 | scm_app_impl = settings['vcs.scm_app_implementation'] | |
496 | if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http': |
|
497 | if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http': | |
497 | settings['vcs.scm_app_implementation'] = 'http' |
|
498 | settings['vcs.scm_app_implementation'] = 'http' | |
498 |
|
499 | |||
499 |
|
500 | |||
500 | def _int_setting(settings, name, default): |
|
501 | def _int_setting(settings, name, default): | |
501 | settings[name] = int(settings.get(name, default)) |
|
502 | settings[name] = int(settings.get(name, default)) | |
502 |
|
503 | |||
503 |
|
504 | |||
504 | def _bool_setting(settings, name, default): |
|
505 | def _bool_setting(settings, name, default): | |
505 | input = settings.get(name, default) |
|
506 | input = settings.get(name, default) | |
506 | if isinstance(input, unicode): |
|
507 | if isinstance(input, unicode): | |
507 | input = input.encode('utf8') |
|
508 | input = input.encode('utf8') | |
508 | settings[name] = asbool(input) |
|
509 | settings[name] = asbool(input) | |
509 |
|
510 | |||
510 |
|
511 | |||
511 | def _list_setting(settings, name, default): |
|
512 | def _list_setting(settings, name, default): | |
512 | raw_value = settings.get(name, default) |
|
513 | raw_value = settings.get(name, default) | |
513 |
|
514 | |||
514 | old_separator = ',' |
|
515 | old_separator = ',' | |
515 | if old_separator in raw_value: |
|
516 | if old_separator in raw_value: | |
516 | # If we get a comma separated list, pass it to our own function. |
|
517 | # If we get a comma separated list, pass it to our own function. | |
517 | settings[name] = rhodecode_aslist(raw_value, sep=old_separator) |
|
518 | settings[name] = rhodecode_aslist(raw_value, sep=old_separator) | |
518 | else: |
|
519 | else: | |
519 | # Otherwise we assume it uses pyramids space/newline separation. |
|
520 | # Otherwise we assume it uses pyramids space/newline separation. | |
520 | settings[name] = aslist(raw_value) |
|
521 | settings[name] = aslist(raw_value) | |
521 |
|
522 | |||
522 |
|
523 | |||
523 | def _string_setting(settings, name, default, lower=True): |
|
524 | def _string_setting(settings, name, default, lower=True): | |
524 | value = settings.get(name, default) |
|
525 | value = settings.get(name, default) | |
525 | if lower: |
|
526 | if lower: | |
526 | value = value.lower() |
|
527 | value = value.lower() | |
527 | settings[name] = value |
|
528 | settings[name] = value |
@@ -1,870 +1,744 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 |
|
2 | |||
3 | # Copyright (C) 2010-2017 RhodeCode GmbH |
|
3 | # Copyright (C) 2010-2017 RhodeCode GmbH | |
4 | # |
|
4 | # | |
5 | # This program is free software: you can redistribute it and/or modify |
|
5 | # This program is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
7 | # (only), as published by the Free Software Foundation. |
|
7 | # (only), as published by the Free Software Foundation. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | # |
|
16 | # | |
17 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 |
|
20 | |||
21 | """ |
|
21 | """ | |
22 | Routes configuration |
|
22 | Routes configuration | |
23 |
|
23 | |||
24 | The more specific and detailed routes should be defined first so they |
|
24 | The more specific and detailed routes should be defined first so they | |
25 | may take precedent over the more generic routes. For more information |
|
25 | may take precedent over the more generic routes. For more information | |
26 | refer to the routes manual at http://routes.groovie.org/docs/ |
|
26 | refer to the routes manual at http://routes.groovie.org/docs/ | |
27 |
|
27 | |||
28 | IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py |
|
28 | IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py | |
29 | and _route_name variable which uses some of stored naming here to do redirects. |
|
29 | and _route_name variable which uses some of stored naming here to do redirects. | |
30 | """ |
|
30 | """ | |
31 | import os |
|
31 | import os | |
32 | import re |
|
32 | import re | |
33 | from routes import Mapper |
|
33 | from routes import Mapper | |
34 |
|
34 | |||
35 | # prefix for non repository related links needs to be prefixed with `/` |
|
35 | # prefix for non repository related links needs to be prefixed with `/` | |
36 | ADMIN_PREFIX = '/_admin' |
|
36 | ADMIN_PREFIX = '/_admin' | |
37 | STATIC_FILE_PREFIX = '/_static' |
|
37 | STATIC_FILE_PREFIX = '/_static' | |
38 |
|
38 | |||
39 | # Default requirements for URL parts |
|
39 | # Default requirements for URL parts | |
40 | URL_NAME_REQUIREMENTS = { |
|
40 | URL_NAME_REQUIREMENTS = { | |
41 | # group name can have a slash in them, but they must not end with a slash |
|
41 | # group name can have a slash in them, but they must not end with a slash | |
42 | 'group_name': r'.*?[^/]', |
|
42 | 'group_name': r'.*?[^/]', | |
43 | 'repo_group_name': r'.*?[^/]', |
|
43 | 'repo_group_name': r'.*?[^/]', | |
44 | # repo names can have a slash in them, but they must not end with a slash |
|
44 | # repo names can have a slash in them, but they must not end with a slash | |
45 | 'repo_name': r'.*?[^/]', |
|
45 | 'repo_name': r'.*?[^/]', | |
46 | # file path eats up everything at the end |
|
46 | # file path eats up everything at the end | |
47 | 'f_path': r'.*', |
|
47 | 'f_path': r'.*', | |
48 | # reference types |
|
48 | # reference types | |
49 | 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)', |
|
49 | 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)', | |
50 | 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)', |
|
50 | 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)', | |
51 | } |
|
51 | } | |
52 |
|
52 | |||
53 |
|
53 | |||
54 | def add_route_requirements(route_path, requirements): |
|
54 | def add_route_requirements(route_path, requirements): | |
55 | """ |
|
55 | """ | |
56 | Adds regex requirements to pyramid routes using a mapping dict |
|
56 | Adds regex requirements to pyramid routes using a mapping dict | |
57 |
|
57 | |||
58 | >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'}) |
|
58 | >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'}) | |
59 | '/{action}/{id:\d+}' |
|
59 | '/{action}/{id:\d+}' | |
60 |
|
60 | |||
61 | """ |
|
61 | """ | |
62 | for key, regex in requirements.items(): |
|
62 | for key, regex in requirements.items(): | |
63 | route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex)) |
|
63 | route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex)) | |
64 | return route_path |
|
64 | return route_path | |
65 |
|
65 | |||
66 |
|
66 | |||
67 | class JSRoutesMapper(Mapper): |
|
67 | class JSRoutesMapper(Mapper): | |
68 | """ |
|
68 | """ | |
69 | Wrapper for routes.Mapper to make pyroutes compatible url definitions |
|
69 | Wrapper for routes.Mapper to make pyroutes compatible url definitions | |
70 | """ |
|
70 | """ | |
71 | _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$') |
|
71 | _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$') | |
72 | _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)') |
|
72 | _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)') | |
73 | def __init__(self, *args, **kw): |
|
73 | def __init__(self, *args, **kw): | |
74 | super(JSRoutesMapper, self).__init__(*args, **kw) |
|
74 | super(JSRoutesMapper, self).__init__(*args, **kw) | |
75 | self._jsroutes = [] |
|
75 | self._jsroutes = [] | |
76 |
|
76 | |||
77 | def connect(self, *args, **kw): |
|
77 | def connect(self, *args, **kw): | |
78 | """ |
|
78 | """ | |
79 | Wrapper for connect to take an extra argument jsroute=True |
|
79 | Wrapper for connect to take an extra argument jsroute=True | |
80 |
|
80 | |||
81 | :param jsroute: boolean, if True will add the route to the pyroutes list |
|
81 | :param jsroute: boolean, if True will add the route to the pyroutes list | |
82 | """ |
|
82 | """ | |
83 | if kw.pop('jsroute', False): |
|
83 | if kw.pop('jsroute', False): | |
84 | if not self._named_route_regex.match(args[0]): |
|
84 | if not self._named_route_regex.match(args[0]): | |
85 | raise Exception('only named routes can be added to pyroutes') |
|
85 | raise Exception('only named routes can be added to pyroutes') | |
86 | self._jsroutes.append(args[0]) |
|
86 | self._jsroutes.append(args[0]) | |
87 |
|
87 | |||
88 | super(JSRoutesMapper, self).connect(*args, **kw) |
|
88 | super(JSRoutesMapper, self).connect(*args, **kw) | |
89 |
|
89 | |||
90 | def _extract_route_information(self, route): |
|
90 | def _extract_route_information(self, route): | |
91 | """ |
|
91 | """ | |
92 | Convert a route into tuple(name, path, args), eg: |
|
92 | Convert a route into tuple(name, path, args), eg: | |
93 | ('show_user', '/profile/%(username)s', ['username']) |
|
93 | ('show_user', '/profile/%(username)s', ['username']) | |
94 | """ |
|
94 | """ | |
95 | routepath = route.routepath |
|
95 | routepath = route.routepath | |
96 | def replace(matchobj): |
|
96 | def replace(matchobj): | |
97 | if matchobj.group(1): |
|
97 | if matchobj.group(1): | |
98 | return "%%(%s)s" % matchobj.group(1).split(':')[0] |
|
98 | return "%%(%s)s" % matchobj.group(1).split(':')[0] | |
99 | else: |
|
99 | else: | |
100 | return "%%(%s)s" % matchobj.group(2) |
|
100 | return "%%(%s)s" % matchobj.group(2) | |
101 |
|
101 | |||
102 | routepath = self._argument_prog.sub(replace, routepath) |
|
102 | routepath = self._argument_prog.sub(replace, routepath) | |
103 | return ( |
|
103 | return ( | |
104 | route.name, |
|
104 | route.name, | |
105 | routepath, |
|
105 | routepath, | |
106 | [(arg[0].split(':')[0] if arg[0] != '' else arg[1]) |
|
106 | [(arg[0].split(':')[0] if arg[0] != '' else arg[1]) | |
107 | for arg in self._argument_prog.findall(route.routepath)] |
|
107 | for arg in self._argument_prog.findall(route.routepath)] | |
108 | ) |
|
108 | ) | |
109 |
|
109 | |||
110 | def jsroutes(self): |
|
110 | def jsroutes(self): | |
111 | """ |
|
111 | """ | |
112 | Return a list of pyroutes.js compatible routes |
|
112 | Return a list of pyroutes.js compatible routes | |
113 | """ |
|
113 | """ | |
114 | for route_name in self._jsroutes: |
|
114 | for route_name in self._jsroutes: | |
115 | yield self._extract_route_information(self._routenames[route_name]) |
|
115 | yield self._extract_route_information(self._routenames[route_name]) | |
116 |
|
116 | |||
117 |
|
117 | |||
118 | def make_map(config): |
|
118 | def make_map(config): | |
119 | """Create, configure and return the routes Mapper""" |
|
119 | """Create, configure and return the routes Mapper""" | |
120 | rmap = JSRoutesMapper( |
|
120 | rmap = JSRoutesMapper( | |
121 | directory=config['pylons.paths']['controllers'], |
|
121 | directory=config['pylons.paths']['controllers'], | |
122 | always_scan=config['debug']) |
|
122 | always_scan=config['debug']) | |
123 | rmap.minimization = False |
|
123 | rmap.minimization = False | |
124 | rmap.explicit = False |
|
124 | rmap.explicit = False | |
125 |
|
125 | |||
126 | from rhodecode.lib.utils2 import str2bool |
|
126 | from rhodecode.lib.utils2 import str2bool | |
127 | from rhodecode.model import repo, repo_group |
|
127 | from rhodecode.model import repo, repo_group | |
128 |
|
128 | |||
129 | def check_repo(environ, match_dict): |
|
129 | def check_repo(environ, match_dict): | |
130 | """ |
|
130 | """ | |
131 | check for valid repository for proper 404 handling |
|
131 | check for valid repository for proper 404 handling | |
132 |
|
132 | |||
133 | :param environ: |
|
133 | :param environ: | |
134 | :param match_dict: |
|
134 | :param match_dict: | |
135 | """ |
|
135 | """ | |
136 | repo_name = match_dict.get('repo_name') |
|
136 | repo_name = match_dict.get('repo_name') | |
137 |
|
137 | |||
138 | if match_dict.get('f_path'): |
|
138 | if match_dict.get('f_path'): | |
139 | # fix for multiple initial slashes that causes errors |
|
139 | # fix for multiple initial slashes that causes errors | |
140 | match_dict['f_path'] = match_dict['f_path'].lstrip('/') |
|
140 | match_dict['f_path'] = match_dict['f_path'].lstrip('/') | |
141 | repo_model = repo.RepoModel() |
|
141 | repo_model = repo.RepoModel() | |
142 | by_name_match = repo_model.get_by_repo_name(repo_name) |
|
142 | by_name_match = repo_model.get_by_repo_name(repo_name) | |
143 | # if we match quickly from database, short circuit the operation, |
|
143 | # if we match quickly from database, short circuit the operation, | |
144 | # and validate repo based on the type. |
|
144 | # and validate repo based on the type. | |
145 | if by_name_match: |
|
145 | if by_name_match: | |
146 | return True |
|
146 | return True | |
147 |
|
147 | |||
148 | by_id_match = repo_model.get_repo_by_id(repo_name) |
|
148 | by_id_match = repo_model.get_repo_by_id(repo_name) | |
149 | if by_id_match: |
|
149 | if by_id_match: | |
150 | repo_name = by_id_match.repo_name |
|
150 | repo_name = by_id_match.repo_name | |
151 | match_dict['repo_name'] = repo_name |
|
151 | match_dict['repo_name'] = repo_name | |
152 | return True |
|
152 | return True | |
153 |
|
153 | |||
154 | return False |
|
154 | return False | |
155 |
|
155 | |||
156 | def check_group(environ, match_dict): |
|
156 | def check_group(environ, match_dict): | |
157 | """ |
|
157 | """ | |
158 | check for valid repository group path for proper 404 handling |
|
158 | check for valid repository group path for proper 404 handling | |
159 |
|
159 | |||
160 | :param environ: |
|
160 | :param environ: | |
161 | :param match_dict: |
|
161 | :param match_dict: | |
162 | """ |
|
162 | """ | |
163 | repo_group_name = match_dict.get('group_name') |
|
163 | repo_group_name = match_dict.get('group_name') | |
164 | repo_group_model = repo_group.RepoGroupModel() |
|
164 | repo_group_model = repo_group.RepoGroupModel() | |
165 | by_name_match = repo_group_model.get_by_group_name(repo_group_name) |
|
165 | by_name_match = repo_group_model.get_by_group_name(repo_group_name) | |
166 | if by_name_match: |
|
166 | if by_name_match: | |
167 | return True |
|
167 | return True | |
168 |
|
168 | |||
169 | return False |
|
169 | return False | |
170 |
|
170 | |||
171 | def check_user_group(environ, match_dict): |
|
171 | def check_user_group(environ, match_dict): | |
172 | """ |
|
172 | """ | |
173 | check for valid user group for proper 404 handling |
|
173 | check for valid user group for proper 404 handling | |
174 |
|
174 | |||
175 | :param environ: |
|
175 | :param environ: | |
176 | :param match_dict: |
|
176 | :param match_dict: | |
177 | """ |
|
177 | """ | |
178 | return True |
|
178 | return True | |
179 |
|
179 | |||
180 | def check_int(environ, match_dict): |
|
180 | def check_int(environ, match_dict): | |
181 | return match_dict.get('id').isdigit() |
|
181 | return match_dict.get('id').isdigit() | |
182 |
|
182 | |||
183 |
|
183 | |||
184 | #========================================================================== |
|
184 | #========================================================================== | |
185 | # CUSTOM ROUTES HERE |
|
185 | # CUSTOM ROUTES HERE | |
186 | #========================================================================== |
|
186 | #========================================================================== | |
187 |
|
187 | |||
188 | # ping and pylons error test |
|
188 | # ping and pylons error test | |
189 | rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping') |
|
189 | rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping') | |
190 | rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test') |
|
190 | rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test') | |
191 |
|
191 | |||
192 | # ADMIN REPOSITORY ROUTES |
|
192 | # ADMIN REPOSITORY ROUTES | |
193 | with rmap.submapper(path_prefix=ADMIN_PREFIX, |
|
193 | with rmap.submapper(path_prefix=ADMIN_PREFIX, | |
194 | controller='admin/repos') as m: |
|
194 | controller='admin/repos') as m: | |
195 | m.connect('repos', '/repos', |
|
195 | m.connect('repos', '/repos', | |
196 | action='create', conditions={'method': ['POST']}) |
|
196 | action='create', conditions={'method': ['POST']}) | |
197 | m.connect('repos', '/repos', |
|
197 | m.connect('repos', '/repos', | |
198 | action='index', conditions={'method': ['GET']}) |
|
198 | action='index', conditions={'method': ['GET']}) | |
199 | m.connect('new_repo', '/create_repository', jsroute=True, |
|
199 | m.connect('new_repo', '/create_repository', jsroute=True, | |
200 | action='create_repository', conditions={'method': ['GET']}) |
|
200 | action='create_repository', conditions={'method': ['GET']}) | |
201 | m.connect('delete_repo', '/repos/{repo_name}', |
|
201 | m.connect('delete_repo', '/repos/{repo_name}', | |
202 | action='delete', conditions={'method': ['DELETE']}, |
|
202 | action='delete', conditions={'method': ['DELETE']}, | |
203 | requirements=URL_NAME_REQUIREMENTS) |
|
203 | requirements=URL_NAME_REQUIREMENTS) | |
204 | m.connect('repo', '/repos/{repo_name}', |
|
204 | m.connect('repo', '/repos/{repo_name}', | |
205 | action='show', conditions={'method': ['GET'], |
|
205 | action='show', conditions={'method': ['GET'], | |
206 | 'function': check_repo}, |
|
206 | 'function': check_repo}, | |
207 | requirements=URL_NAME_REQUIREMENTS) |
|
207 | requirements=URL_NAME_REQUIREMENTS) | |
208 |
|
208 | |||
209 | # ADMIN REPOSITORY GROUPS ROUTES |
|
209 | # ADMIN REPOSITORY GROUPS ROUTES | |
210 | with rmap.submapper(path_prefix=ADMIN_PREFIX, |
|
210 | with rmap.submapper(path_prefix=ADMIN_PREFIX, | |
211 | controller='admin/repo_groups') as m: |
|
211 | controller='admin/repo_groups') as m: | |
212 | m.connect('repo_groups', '/repo_groups', |
|
212 | m.connect('repo_groups', '/repo_groups', | |
213 | action='create', conditions={'method': ['POST']}) |
|
213 | action='create', conditions={'method': ['POST']}) | |
214 | m.connect('repo_groups', '/repo_groups', |
|
214 | m.connect('repo_groups', '/repo_groups', | |
215 | action='index', conditions={'method': ['GET']}) |
|
215 | action='index', conditions={'method': ['GET']}) | |
216 | m.connect('new_repo_group', '/repo_groups/new', |
|
216 | m.connect('new_repo_group', '/repo_groups/new', | |
217 | action='new', conditions={'method': ['GET']}) |
|
217 | action='new', conditions={'method': ['GET']}) | |
218 | m.connect('update_repo_group', '/repo_groups/{group_name}', |
|
218 | m.connect('update_repo_group', '/repo_groups/{group_name}', | |
219 | action='update', conditions={'method': ['PUT'], |
|
219 | action='update', conditions={'method': ['PUT'], | |
220 | 'function': check_group}, |
|
220 | 'function': check_group}, | |
221 | requirements=URL_NAME_REQUIREMENTS) |
|
221 | requirements=URL_NAME_REQUIREMENTS) | |
222 |
|
222 | |||
223 | # EXTRAS REPO GROUP ROUTES |
|
223 | # EXTRAS REPO GROUP ROUTES | |
224 | m.connect('edit_repo_group', '/repo_groups/{group_name}/edit', |
|
224 | m.connect('edit_repo_group', '/repo_groups/{group_name}/edit', | |
225 | action='edit', |
|
225 | action='edit', | |
226 | conditions={'method': ['GET'], 'function': check_group}, |
|
226 | conditions={'method': ['GET'], 'function': check_group}, | |
227 | requirements=URL_NAME_REQUIREMENTS) |
|
227 | requirements=URL_NAME_REQUIREMENTS) | |
228 | m.connect('edit_repo_group', '/repo_groups/{group_name}/edit', |
|
228 | m.connect('edit_repo_group', '/repo_groups/{group_name}/edit', | |
229 | action='edit', |
|
229 | action='edit', | |
230 | conditions={'method': ['PUT'], 'function': check_group}, |
|
230 | conditions={'method': ['PUT'], 'function': check_group}, | |
231 | requirements=URL_NAME_REQUIREMENTS) |
|
231 | requirements=URL_NAME_REQUIREMENTS) | |
232 |
|
232 | |||
233 | m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced', |
|
233 | m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced', | |
234 | action='edit_repo_group_advanced', |
|
234 | action='edit_repo_group_advanced', | |
235 | conditions={'method': ['GET'], 'function': check_group}, |
|
235 | conditions={'method': ['GET'], 'function': check_group}, | |
236 | requirements=URL_NAME_REQUIREMENTS) |
|
236 | requirements=URL_NAME_REQUIREMENTS) | |
237 | m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced', |
|
237 | m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced', | |
238 | action='edit_repo_group_advanced', |
|
238 | action='edit_repo_group_advanced', | |
239 | conditions={'method': ['PUT'], 'function': check_group}, |
|
239 | conditions={'method': ['PUT'], 'function': check_group}, | |
240 | requirements=URL_NAME_REQUIREMENTS) |
|
240 | requirements=URL_NAME_REQUIREMENTS) | |
241 |
|
241 | |||
242 | m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions', |
|
242 | m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions', | |
243 | action='edit_repo_group_perms', |
|
243 | action='edit_repo_group_perms', | |
244 | conditions={'method': ['GET'], 'function': check_group}, |
|
244 | conditions={'method': ['GET'], 'function': check_group}, | |
245 | requirements=URL_NAME_REQUIREMENTS) |
|
245 | requirements=URL_NAME_REQUIREMENTS) | |
246 | m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions', |
|
246 | m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions', | |
247 | action='update_perms', |
|
247 | action='update_perms', | |
248 | conditions={'method': ['PUT'], 'function': check_group}, |
|
248 | conditions={'method': ['PUT'], 'function': check_group}, | |
249 | requirements=URL_NAME_REQUIREMENTS) |
|
249 | requirements=URL_NAME_REQUIREMENTS) | |
250 |
|
250 | |||
251 | m.connect('delete_repo_group', '/repo_groups/{group_name}', |
|
251 | m.connect('delete_repo_group', '/repo_groups/{group_name}', | |
252 | action='delete', conditions={'method': ['DELETE'], |
|
252 | action='delete', conditions={'method': ['DELETE'], | |
253 | 'function': check_group}, |
|
253 | 'function': check_group}, | |
254 | requirements=URL_NAME_REQUIREMENTS) |
|
254 | requirements=URL_NAME_REQUIREMENTS) | |
255 |
|
255 | |||
256 | # ADMIN USER ROUTES |
|
256 | # ADMIN USER ROUTES | |
257 | with rmap.submapper(path_prefix=ADMIN_PREFIX, |
|
257 | with rmap.submapper(path_prefix=ADMIN_PREFIX, | |
258 | controller='admin/users') as m: |
|
258 | controller='admin/users') as m: | |
259 | m.connect('users', '/users', |
|
259 | m.connect('users', '/users', | |
260 | action='create', conditions={'method': ['POST']}) |
|
260 | action='create', conditions={'method': ['POST']}) | |
261 | m.connect('new_user', '/users/new', |
|
261 | m.connect('new_user', '/users/new', | |
262 | action='new', conditions={'method': ['GET']}) |
|
262 | action='new', conditions={'method': ['GET']}) | |
263 | m.connect('update_user', '/users/{user_id}', |
|
263 | m.connect('update_user', '/users/{user_id}', | |
264 | action='update', conditions={'method': ['PUT']}) |
|
264 | action='update', conditions={'method': ['PUT']}) | |
265 | m.connect('delete_user', '/users/{user_id}', |
|
265 | m.connect('delete_user', '/users/{user_id}', | |
266 | action='delete', conditions={'method': ['DELETE']}) |
|
266 | action='delete', conditions={'method': ['DELETE']}) | |
267 | m.connect('edit_user', '/users/{user_id}/edit', |
|
267 | m.connect('edit_user', '/users/{user_id}/edit', | |
268 | action='edit', conditions={'method': ['GET']}, jsroute=True) |
|
268 | action='edit', conditions={'method': ['GET']}, jsroute=True) | |
269 | m.connect('user', '/users/{user_id}', |
|
269 | m.connect('user', '/users/{user_id}', | |
270 | action='show', conditions={'method': ['GET']}) |
|
270 | action='show', conditions={'method': ['GET']}) | |
271 | m.connect('force_password_reset_user', '/users/{user_id}/password_reset', |
|
271 | m.connect('force_password_reset_user', '/users/{user_id}/password_reset', | |
272 | action='reset_password', conditions={'method': ['POST']}) |
|
272 | action='reset_password', conditions={'method': ['POST']}) | |
273 | m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group', |
|
273 | m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group', | |
274 | action='create_personal_repo_group', conditions={'method': ['POST']}) |
|
274 | action='create_personal_repo_group', conditions={'method': ['POST']}) | |
275 |
|
275 | |||
276 | # EXTRAS USER ROUTES |
|
276 | # EXTRAS USER ROUTES | |
277 | m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced', |
|
277 | m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced', | |
278 | action='edit_advanced', conditions={'method': ['GET']}) |
|
278 | action='edit_advanced', conditions={'method': ['GET']}) | |
279 | m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced', |
|
279 | m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced', | |
280 | action='update_advanced', conditions={'method': ['PUT']}) |
|
280 | action='update_advanced', conditions={'method': ['PUT']}) | |
281 |
|
281 | |||
282 | m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions', |
|
282 | m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions', | |
283 | action='edit_global_perms', conditions={'method': ['GET']}) |
|
283 | action='edit_global_perms', conditions={'method': ['GET']}) | |
284 | m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions', |
|
284 | m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions', | |
285 | action='update_global_perms', conditions={'method': ['PUT']}) |
|
285 | action='update_global_perms', conditions={'method': ['PUT']}) | |
286 |
|
286 | |||
287 | m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary', |
|
287 | m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary', | |
288 | action='edit_perms_summary', conditions={'method': ['GET']}) |
|
288 | action='edit_perms_summary', conditions={'method': ['GET']}) | |
289 |
|
289 | |||
290 |
|
290 | |||
291 | # ADMIN USER GROUPS REST ROUTES |
|
291 | # ADMIN USER GROUPS REST ROUTES | |
292 | with rmap.submapper(path_prefix=ADMIN_PREFIX, |
|
292 | with rmap.submapper(path_prefix=ADMIN_PREFIX, | |
293 | controller='admin/user_groups') as m: |
|
293 | controller='admin/user_groups') as m: | |
294 | m.connect('users_groups', '/user_groups', |
|
294 | m.connect('users_groups', '/user_groups', | |
295 | action='create', conditions={'method': ['POST']}) |
|
295 | action='create', conditions={'method': ['POST']}) | |
296 | m.connect('users_groups', '/user_groups', |
|
296 | m.connect('users_groups', '/user_groups', | |
297 | action='index', conditions={'method': ['GET']}) |
|
297 | action='index', conditions={'method': ['GET']}) | |
298 | m.connect('new_users_group', '/user_groups/new', |
|
298 | m.connect('new_users_group', '/user_groups/new', | |
299 | action='new', conditions={'method': ['GET']}) |
|
299 | action='new', conditions={'method': ['GET']}) | |
300 | m.connect('update_users_group', '/user_groups/{user_group_id}', |
|
300 | m.connect('update_users_group', '/user_groups/{user_group_id}', | |
301 | action='update', conditions={'method': ['PUT']}) |
|
301 | action='update', conditions={'method': ['PUT']}) | |
302 | m.connect('delete_users_group', '/user_groups/{user_group_id}', |
|
302 | m.connect('delete_users_group', '/user_groups/{user_group_id}', | |
303 | action='delete', conditions={'method': ['DELETE']}) |
|
303 | action='delete', conditions={'method': ['DELETE']}) | |
304 | m.connect('edit_users_group', '/user_groups/{user_group_id}/edit', |
|
304 | m.connect('edit_users_group', '/user_groups/{user_group_id}/edit', | |
305 | action='edit', conditions={'method': ['GET']}, |
|
305 | action='edit', conditions={'method': ['GET']}, | |
306 | function=check_user_group) |
|
306 | function=check_user_group) | |
307 |
|
307 | |||
308 | # EXTRAS USER GROUP ROUTES |
|
308 | # EXTRAS USER GROUP ROUTES | |
309 | m.connect('edit_user_group_global_perms', |
|
309 | m.connect('edit_user_group_global_perms', | |
310 | '/user_groups/{user_group_id}/edit/global_permissions', |
|
310 | '/user_groups/{user_group_id}/edit/global_permissions', | |
311 | action='edit_global_perms', conditions={'method': ['GET']}) |
|
311 | action='edit_global_perms', conditions={'method': ['GET']}) | |
312 | m.connect('edit_user_group_global_perms', |
|
312 | m.connect('edit_user_group_global_perms', | |
313 | '/user_groups/{user_group_id}/edit/global_permissions', |
|
313 | '/user_groups/{user_group_id}/edit/global_permissions', | |
314 | action='update_global_perms', conditions={'method': ['PUT']}) |
|
314 | action='update_global_perms', conditions={'method': ['PUT']}) | |
315 | m.connect('edit_user_group_perms_summary', |
|
315 | m.connect('edit_user_group_perms_summary', | |
316 | '/user_groups/{user_group_id}/edit/permissions_summary', |
|
316 | '/user_groups/{user_group_id}/edit/permissions_summary', | |
317 | action='edit_perms_summary', conditions={'method': ['GET']}) |
|
317 | action='edit_perms_summary', conditions={'method': ['GET']}) | |
318 |
|
318 | |||
319 | m.connect('edit_user_group_perms', |
|
319 | m.connect('edit_user_group_perms', | |
320 | '/user_groups/{user_group_id}/edit/permissions', |
|
320 | '/user_groups/{user_group_id}/edit/permissions', | |
321 | action='edit_perms', conditions={'method': ['GET']}) |
|
321 | action='edit_perms', conditions={'method': ['GET']}) | |
322 | m.connect('edit_user_group_perms', |
|
322 | m.connect('edit_user_group_perms', | |
323 | '/user_groups/{user_group_id}/edit/permissions', |
|
323 | '/user_groups/{user_group_id}/edit/permissions', | |
324 | action='update_perms', conditions={'method': ['PUT']}) |
|
324 | action='update_perms', conditions={'method': ['PUT']}) | |
325 |
|
325 | |||
326 | m.connect('edit_user_group_advanced', |
|
326 | m.connect('edit_user_group_advanced', | |
327 | '/user_groups/{user_group_id}/edit/advanced', |
|
327 | '/user_groups/{user_group_id}/edit/advanced', | |
328 | action='edit_advanced', conditions={'method': ['GET']}) |
|
328 | action='edit_advanced', conditions={'method': ['GET']}) | |
329 |
|
329 | |||
330 | m.connect('edit_user_group_advanced_sync', |
|
330 | m.connect('edit_user_group_advanced_sync', | |
331 | '/user_groups/{user_group_id}/edit/advanced/sync', |
|
331 | '/user_groups/{user_group_id}/edit/advanced/sync', | |
332 | action='edit_advanced_set_synchronization', conditions={'method': ['POST']}) |
|
332 | action='edit_advanced_set_synchronization', conditions={'method': ['POST']}) | |
333 |
|
333 | |||
334 | m.connect('edit_user_group_members', |
|
334 | m.connect('edit_user_group_members', | |
335 | '/user_groups/{user_group_id}/edit/members', jsroute=True, |
|
335 | '/user_groups/{user_group_id}/edit/members', jsroute=True, | |
336 | action='user_group_members', conditions={'method': ['GET']}) |
|
336 | action='user_group_members', conditions={'method': ['GET']}) | |
337 |
|
337 | |||
338 | # ADMIN PERMISSIONS ROUTES |
|
338 | # ADMIN PERMISSIONS ROUTES | |
339 | with rmap.submapper(path_prefix=ADMIN_PREFIX, |
|
339 | with rmap.submapper(path_prefix=ADMIN_PREFIX, | |
340 | controller='admin/permissions') as m: |
|
340 | controller='admin/permissions') as m: | |
341 | m.connect('admin_permissions_application', '/permissions/application', |
|
341 | m.connect('admin_permissions_application', '/permissions/application', | |
342 | action='permission_application_update', conditions={'method': ['POST']}) |
|
342 | action='permission_application_update', conditions={'method': ['POST']}) | |
343 | m.connect('admin_permissions_application', '/permissions/application', |
|
343 | m.connect('admin_permissions_application', '/permissions/application', | |
344 | action='permission_application', conditions={'method': ['GET']}) |
|
344 | action='permission_application', conditions={'method': ['GET']}) | |
345 |
|
345 | |||
346 | m.connect('admin_permissions_global', '/permissions/global', |
|
346 | m.connect('admin_permissions_global', '/permissions/global', | |
347 | action='permission_global_update', conditions={'method': ['POST']}) |
|
347 | action='permission_global_update', conditions={'method': ['POST']}) | |
348 | m.connect('admin_permissions_global', '/permissions/global', |
|
348 | m.connect('admin_permissions_global', '/permissions/global', | |
349 | action='permission_global', conditions={'method': ['GET']}) |
|
349 | action='permission_global', conditions={'method': ['GET']}) | |
350 |
|
350 | |||
351 | m.connect('admin_permissions_object', '/permissions/object', |
|
351 | m.connect('admin_permissions_object', '/permissions/object', | |
352 | action='permission_objects_update', conditions={'method': ['POST']}) |
|
352 | action='permission_objects_update', conditions={'method': ['POST']}) | |
353 | m.connect('admin_permissions_object', '/permissions/object', |
|
353 | m.connect('admin_permissions_object', '/permissions/object', | |
354 | action='permission_objects', conditions={'method': ['GET']}) |
|
354 | action='permission_objects', conditions={'method': ['GET']}) | |
355 |
|
355 | |||
356 | m.connect('admin_permissions_ips', '/permissions/ips', |
|
356 | m.connect('admin_permissions_ips', '/permissions/ips', | |
357 | action='permission_ips', conditions={'method': ['POST']}) |
|
357 | action='permission_ips', conditions={'method': ['POST']}) | |
358 | m.connect('admin_permissions_ips', '/permissions/ips', |
|
358 | m.connect('admin_permissions_ips', '/permissions/ips', | |
359 | action='permission_ips', conditions={'method': ['GET']}) |
|
359 | action='permission_ips', conditions={'method': ['GET']}) | |
360 |
|
360 | |||
361 | m.connect('admin_permissions_overview', '/permissions/overview', |
|
361 | m.connect('admin_permissions_overview', '/permissions/overview', | |
362 | action='permission_perms', conditions={'method': ['GET']}) |
|
362 | action='permission_perms', conditions={'method': ['GET']}) | |
363 |
|
363 | |||
364 | # ADMIN DEFAULTS REST ROUTES |
|
364 | # ADMIN DEFAULTS REST ROUTES | |
365 | with rmap.submapper(path_prefix=ADMIN_PREFIX, |
|
365 | with rmap.submapper(path_prefix=ADMIN_PREFIX, | |
366 | controller='admin/defaults') as m: |
|
366 | controller='admin/defaults') as m: | |
367 | m.connect('admin_defaults_repositories', '/defaults/repositories', |
|
367 | m.connect('admin_defaults_repositories', '/defaults/repositories', | |
368 | action='update_repository_defaults', conditions={'method': ['POST']}) |
|
368 | action='update_repository_defaults', conditions={'method': ['POST']}) | |
369 | m.connect('admin_defaults_repositories', '/defaults/repositories', |
|
369 | m.connect('admin_defaults_repositories', '/defaults/repositories', | |
370 | action='index', conditions={'method': ['GET']}) |
|
370 | action='index', conditions={'method': ['GET']}) | |
371 |
|
371 | |||
372 | # ADMIN SETTINGS ROUTES |
|
372 | # ADMIN SETTINGS ROUTES | |
373 | with rmap.submapper(path_prefix=ADMIN_PREFIX, |
|
373 | with rmap.submapper(path_prefix=ADMIN_PREFIX, | |
374 | controller='admin/settings') as m: |
|
374 | controller='admin/settings') as m: | |
375 |
|
375 | |||
376 | # default |
|
376 | # default | |
377 | m.connect('admin_settings', '/settings', |
|
377 | m.connect('admin_settings', '/settings', | |
378 | action='settings_global_update', |
|
378 | action='settings_global_update', | |
379 | conditions={'method': ['POST']}) |
|
379 | conditions={'method': ['POST']}) | |
380 | m.connect('admin_settings', '/settings', |
|
380 | m.connect('admin_settings', '/settings', | |
381 | action='settings_global', conditions={'method': ['GET']}) |
|
381 | action='settings_global', conditions={'method': ['GET']}) | |
382 |
|
382 | |||
383 | m.connect('admin_settings_vcs', '/settings/vcs', |
|
383 | m.connect('admin_settings_vcs', '/settings/vcs', | |
384 | action='settings_vcs_update', |
|
384 | action='settings_vcs_update', | |
385 | conditions={'method': ['POST']}) |
|
385 | conditions={'method': ['POST']}) | |
386 | m.connect('admin_settings_vcs', '/settings/vcs', |
|
386 | m.connect('admin_settings_vcs', '/settings/vcs', | |
387 | action='settings_vcs', |
|
387 | action='settings_vcs', | |
388 | conditions={'method': ['GET']}) |
|
388 | conditions={'method': ['GET']}) | |
389 | m.connect('admin_settings_vcs', '/settings/vcs', |
|
389 | m.connect('admin_settings_vcs', '/settings/vcs', | |
390 | action='delete_svn_pattern', |
|
390 | action='delete_svn_pattern', | |
391 | conditions={'method': ['DELETE']}) |
|
391 | conditions={'method': ['DELETE']}) | |
392 |
|
392 | |||
393 | m.connect('admin_settings_mapping', '/settings/mapping', |
|
393 | m.connect('admin_settings_mapping', '/settings/mapping', | |
394 | action='settings_mapping_update', |
|
394 | action='settings_mapping_update', | |
395 | conditions={'method': ['POST']}) |
|
395 | conditions={'method': ['POST']}) | |
396 | m.connect('admin_settings_mapping', '/settings/mapping', |
|
396 | m.connect('admin_settings_mapping', '/settings/mapping', | |
397 | action='settings_mapping', conditions={'method': ['GET']}) |
|
397 | action='settings_mapping', conditions={'method': ['GET']}) | |
398 |
|
398 | |||
399 | m.connect('admin_settings_global', '/settings/global', |
|
399 | m.connect('admin_settings_global', '/settings/global', | |
400 | action='settings_global_update', |
|
400 | action='settings_global_update', | |
401 | conditions={'method': ['POST']}) |
|
401 | conditions={'method': ['POST']}) | |
402 | m.connect('admin_settings_global', '/settings/global', |
|
402 | m.connect('admin_settings_global', '/settings/global', | |
403 | action='settings_global', conditions={'method': ['GET']}) |
|
403 | action='settings_global', conditions={'method': ['GET']}) | |
404 |
|
404 | |||
405 | m.connect('admin_settings_visual', '/settings/visual', |
|
405 | m.connect('admin_settings_visual', '/settings/visual', | |
406 | action='settings_visual_update', |
|
406 | action='settings_visual_update', | |
407 | conditions={'method': ['POST']}) |
|
407 | conditions={'method': ['POST']}) | |
408 | m.connect('admin_settings_visual', '/settings/visual', |
|
408 | m.connect('admin_settings_visual', '/settings/visual', | |
409 | action='settings_visual', conditions={'method': ['GET']}) |
|
409 | action='settings_visual', conditions={'method': ['GET']}) | |
410 |
|
410 | |||
411 | m.connect('admin_settings_issuetracker', |
|
411 | m.connect('admin_settings_issuetracker', | |
412 | '/settings/issue-tracker', action='settings_issuetracker', |
|
412 | '/settings/issue-tracker', action='settings_issuetracker', | |
413 | conditions={'method': ['GET']}) |
|
413 | conditions={'method': ['GET']}) | |
414 | m.connect('admin_settings_issuetracker_save', |
|
414 | m.connect('admin_settings_issuetracker_save', | |
415 | '/settings/issue-tracker/save', |
|
415 | '/settings/issue-tracker/save', | |
416 | action='settings_issuetracker_save', |
|
416 | action='settings_issuetracker_save', | |
417 | conditions={'method': ['POST']}) |
|
417 | conditions={'method': ['POST']}) | |
418 | m.connect('admin_issuetracker_test', '/settings/issue-tracker/test', |
|
418 | m.connect('admin_issuetracker_test', '/settings/issue-tracker/test', | |
419 | action='settings_issuetracker_test', |
|
419 | action='settings_issuetracker_test', | |
420 | conditions={'method': ['POST']}) |
|
420 | conditions={'method': ['POST']}) | |
421 | m.connect('admin_issuetracker_delete', |
|
421 | m.connect('admin_issuetracker_delete', | |
422 | '/settings/issue-tracker/delete', |
|
422 | '/settings/issue-tracker/delete', | |
423 | action='settings_issuetracker_delete', |
|
423 | action='settings_issuetracker_delete', | |
424 | conditions={'method': ['DELETE']}) |
|
424 | conditions={'method': ['DELETE']}) | |
425 |
|
425 | |||
426 | m.connect('admin_settings_email', '/settings/email', |
|
426 | m.connect('admin_settings_email', '/settings/email', | |
427 | action='settings_email_update', |
|
427 | action='settings_email_update', | |
428 | conditions={'method': ['POST']}) |
|
428 | conditions={'method': ['POST']}) | |
429 | m.connect('admin_settings_email', '/settings/email', |
|
429 | m.connect('admin_settings_email', '/settings/email', | |
430 | action='settings_email', conditions={'method': ['GET']}) |
|
430 | action='settings_email', conditions={'method': ['GET']}) | |
431 |
|
431 | |||
432 | m.connect('admin_settings_hooks', '/settings/hooks', |
|
432 | m.connect('admin_settings_hooks', '/settings/hooks', | |
433 | action='settings_hooks_update', |
|
433 | action='settings_hooks_update', | |
434 | conditions={'method': ['POST', 'DELETE']}) |
|
434 | conditions={'method': ['POST', 'DELETE']}) | |
435 | m.connect('admin_settings_hooks', '/settings/hooks', |
|
435 | m.connect('admin_settings_hooks', '/settings/hooks', | |
436 | action='settings_hooks', conditions={'method': ['GET']}) |
|
436 | action='settings_hooks', conditions={'method': ['GET']}) | |
437 |
|
437 | |||
438 | m.connect('admin_settings_search', '/settings/search', |
|
438 | m.connect('admin_settings_search', '/settings/search', | |
439 | action='settings_search', conditions={'method': ['GET']}) |
|
439 | action='settings_search', conditions={'method': ['GET']}) | |
440 |
|
440 | |||
441 | m.connect('admin_settings_supervisor', '/settings/supervisor', |
|
441 | m.connect('admin_settings_supervisor', '/settings/supervisor', | |
442 | action='settings_supervisor', conditions={'method': ['GET']}) |
|
442 | action='settings_supervisor', conditions={'method': ['GET']}) | |
443 | m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log', |
|
443 | m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log', | |
444 | action='settings_supervisor_log', conditions={'method': ['GET']}) |
|
444 | action='settings_supervisor_log', conditions={'method': ['GET']}) | |
445 |
|
445 | |||
446 | m.connect('admin_settings_labs', '/settings/labs', |
|
446 | m.connect('admin_settings_labs', '/settings/labs', | |
447 | action='settings_labs_update', |
|
447 | action='settings_labs_update', | |
448 | conditions={'method': ['POST']}) |
|
448 | conditions={'method': ['POST']}) | |
449 | m.connect('admin_settings_labs', '/settings/labs', |
|
449 | m.connect('admin_settings_labs', '/settings/labs', | |
450 | action='settings_labs', conditions={'method': ['GET']}) |
|
450 | action='settings_labs', conditions={'method': ['GET']}) | |
451 |
|
451 | |||
452 | # ADMIN MY ACCOUNT |
|
452 | # ADMIN MY ACCOUNT | |
453 | with rmap.submapper(path_prefix=ADMIN_PREFIX, |
|
453 | with rmap.submapper(path_prefix=ADMIN_PREFIX, | |
454 | controller='admin/my_account') as m: |
|
454 | controller='admin/my_account') as m: | |
455 |
|
455 | |||
456 | # NOTE(marcink): this needs to be kept for password force flag to be |
|
456 | # NOTE(marcink): this needs to be kept for password force flag to be | |
457 | # handled in pylons controllers, remove after full migration to pyramid |
|
457 | # handled in pylons controllers, remove after full migration to pyramid | |
458 | m.connect('my_account_password', '/my_account/password', |
|
458 | m.connect('my_account_password', '/my_account/password', | |
459 | action='my_account_password', conditions={'method': ['GET']}) |
|
459 | action='my_account_password', conditions={'method': ['GET']}) | |
460 |
|
460 | |||
461 | # USER JOURNAL |
|
461 | # USER JOURNAL | |
462 | rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,), |
|
462 | rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,), | |
463 | controller='journal', action='index') |
|
463 | controller='journal', action='index') | |
464 | rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,), |
|
464 | rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,), | |
465 | controller='journal', action='journal_rss') |
|
465 | controller='journal', action='journal_rss') | |
466 | rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,), |
|
466 | rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,), | |
467 | controller='journal', action='journal_atom') |
|
467 | controller='journal', action='journal_atom') | |
468 |
|
468 | |||
469 | rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,), |
|
469 | rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,), | |
470 | controller='journal', action='public_journal') |
|
470 | controller='journal', action='public_journal') | |
471 |
|
471 | |||
472 | rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,), |
|
472 | rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,), | |
473 | controller='journal', action='public_journal_rss') |
|
473 | controller='journal', action='public_journal_rss') | |
474 |
|
474 | |||
475 | rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,), |
|
475 | rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,), | |
476 | controller='journal', action='public_journal_rss') |
|
476 | controller='journal', action='public_journal_rss') | |
477 |
|
477 | |||
478 | rmap.connect('public_journal_atom', |
|
478 | rmap.connect('public_journal_atom', | |
479 | '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal', |
|
479 | '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal', | |
480 | action='public_journal_atom') |
|
480 | action='public_journal_atom') | |
481 |
|
481 | |||
482 | rmap.connect('public_journal_atom_old', |
|
482 | rmap.connect('public_journal_atom_old', | |
483 | '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal', |
|
483 | '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal', | |
484 | action='public_journal_atom') |
|
484 | action='public_journal_atom') | |
485 |
|
485 | |||
486 | rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,), |
|
486 | rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,), | |
487 | controller='journal', action='toggle_following', jsroute=True, |
|
487 | controller='journal', action='toggle_following', jsroute=True, | |
488 | conditions={'method': ['POST']}) |
|
488 | conditions={'method': ['POST']}) | |
489 |
|
489 | |||
490 | #========================================================================== |
|
490 | #========================================================================== | |
491 | # REPOSITORY ROUTES |
|
491 | # REPOSITORY ROUTES | |
492 | #========================================================================== |
|
492 | #========================================================================== | |
493 |
|
493 | |||
494 | rmap.connect('repo_creating_home', '/{repo_name}/repo_creating', |
|
494 | rmap.connect('repo_creating_home', '/{repo_name}/repo_creating', | |
495 | controller='admin/repos', action='repo_creating', |
|
495 | controller='admin/repos', action='repo_creating', | |
496 | requirements=URL_NAME_REQUIREMENTS) |
|
496 | requirements=URL_NAME_REQUIREMENTS) | |
497 | rmap.connect('repo_check_home', '/{repo_name}/crepo_check', |
|
497 | rmap.connect('repo_check_home', '/{repo_name}/crepo_check', | |
498 | controller='admin/repos', action='repo_check', |
|
498 | controller='admin/repos', action='repo_check', | |
499 | requirements=URL_NAME_REQUIREMENTS) |
|
499 | requirements=URL_NAME_REQUIREMENTS) | |
500 |
|
500 | |||
501 | rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}', |
|
501 | rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}', | |
502 | controller='changeset', revision='tip', |
|
502 | controller='changeset', revision='tip', | |
503 | conditions={'function': check_repo}, |
|
503 | conditions={'function': check_repo}, | |
504 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
504 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
505 | rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}', |
|
505 | rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}', | |
506 | controller='changeset', revision='tip', action='changeset_children', |
|
506 | controller='changeset', revision='tip', action='changeset_children', | |
507 | conditions={'function': check_repo}, |
|
507 | conditions={'function': check_repo}, | |
508 | requirements=URL_NAME_REQUIREMENTS) |
|
508 | requirements=URL_NAME_REQUIREMENTS) | |
509 | rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}', |
|
509 | rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}', | |
510 | controller='changeset', revision='tip', action='changeset_parents', |
|
510 | controller='changeset', revision='tip', action='changeset_parents', | |
511 | conditions={'function': check_repo}, |
|
511 | conditions={'function': check_repo}, | |
512 | requirements=URL_NAME_REQUIREMENTS) |
|
512 | requirements=URL_NAME_REQUIREMENTS) | |
513 |
|
513 | |||
514 | # repo edit options |
|
514 | # repo edit options | |
515 | rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields', |
|
515 | rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields', | |
516 | controller='admin/repos', action='edit_fields', |
|
516 | controller='admin/repos', action='edit_fields', | |
517 | conditions={'method': ['GET'], 'function': check_repo}, |
|
517 | conditions={'method': ['GET'], 'function': check_repo}, | |
518 | requirements=URL_NAME_REQUIREMENTS) |
|
518 | requirements=URL_NAME_REQUIREMENTS) | |
519 | rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new', |
|
519 | rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new', | |
520 | controller='admin/repos', action='create_repo_field', |
|
520 | controller='admin/repos', action='create_repo_field', | |
521 | conditions={'method': ['PUT'], 'function': check_repo}, |
|
521 | conditions={'method': ['PUT'], 'function': check_repo}, | |
522 | requirements=URL_NAME_REQUIREMENTS) |
|
522 | requirements=URL_NAME_REQUIREMENTS) | |
523 | rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}', |
|
523 | rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}', | |
524 | controller='admin/repos', action='delete_repo_field', |
|
524 | controller='admin/repos', action='delete_repo_field', | |
525 | conditions={'method': ['DELETE'], 'function': check_repo}, |
|
525 | conditions={'method': ['DELETE'], 'function': check_repo}, | |
526 | requirements=URL_NAME_REQUIREMENTS) |
|
526 | requirements=URL_NAME_REQUIREMENTS) | |
527 |
|
527 | |||
528 | rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle', |
|
528 | rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle', | |
529 | controller='admin/repos', action='toggle_locking', |
|
529 | controller='admin/repos', action='toggle_locking', | |
530 | conditions={'method': ['GET'], 'function': check_repo}, |
|
530 | conditions={'method': ['GET'], 'function': check_repo}, | |
531 | requirements=URL_NAME_REQUIREMENTS) |
|
531 | requirements=URL_NAME_REQUIREMENTS) | |
532 |
|
532 | |||
533 | rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote', |
|
533 | rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote', | |
534 | controller='admin/repos', action='edit_remote_form', |
|
534 | controller='admin/repos', action='edit_remote_form', | |
535 | conditions={'method': ['GET'], 'function': check_repo}, |
|
535 | conditions={'method': ['GET'], 'function': check_repo}, | |
536 | requirements=URL_NAME_REQUIREMENTS) |
|
536 | requirements=URL_NAME_REQUIREMENTS) | |
537 | rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote', |
|
537 | rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote', | |
538 | controller='admin/repos', action='edit_remote', |
|
538 | controller='admin/repos', action='edit_remote', | |
539 | conditions={'method': ['PUT'], 'function': check_repo}, |
|
539 | conditions={'method': ['PUT'], 'function': check_repo}, | |
540 | requirements=URL_NAME_REQUIREMENTS) |
|
540 | requirements=URL_NAME_REQUIREMENTS) | |
541 |
|
541 | |||
542 | rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics', |
|
542 | rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics', | |
543 | controller='admin/repos', action='edit_statistics_form', |
|
543 | controller='admin/repos', action='edit_statistics_form', | |
544 | conditions={'method': ['GET'], 'function': check_repo}, |
|
544 | conditions={'method': ['GET'], 'function': check_repo}, | |
545 | requirements=URL_NAME_REQUIREMENTS) |
|
545 | requirements=URL_NAME_REQUIREMENTS) | |
546 | rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics', |
|
546 | rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics', | |
547 | controller='admin/repos', action='edit_statistics', |
|
547 | controller='admin/repos', action='edit_statistics', | |
548 | conditions={'method': ['PUT'], 'function': check_repo}, |
|
548 | conditions={'method': ['PUT'], 'function': check_repo}, | |
549 | requirements=URL_NAME_REQUIREMENTS) |
|
549 | requirements=URL_NAME_REQUIREMENTS) | |
550 | rmap.connect('repo_settings_issuetracker', |
|
550 | rmap.connect('repo_settings_issuetracker', | |
551 | '/{repo_name}/settings/issue-tracker', |
|
551 | '/{repo_name}/settings/issue-tracker', | |
552 | controller='admin/repos', action='repo_issuetracker', |
|
552 | controller='admin/repos', action='repo_issuetracker', | |
553 | conditions={'method': ['GET'], 'function': check_repo}, |
|
553 | conditions={'method': ['GET'], 'function': check_repo}, | |
554 | requirements=URL_NAME_REQUIREMENTS) |
|
554 | requirements=URL_NAME_REQUIREMENTS) | |
555 | rmap.connect('repo_issuetracker_test', |
|
555 | rmap.connect('repo_issuetracker_test', | |
556 | '/{repo_name}/settings/issue-tracker/test', |
|
556 | '/{repo_name}/settings/issue-tracker/test', | |
557 | controller='admin/repos', action='repo_issuetracker_test', |
|
557 | controller='admin/repos', action='repo_issuetracker_test', | |
558 | conditions={'method': ['POST'], 'function': check_repo}, |
|
558 | conditions={'method': ['POST'], 'function': check_repo}, | |
559 | requirements=URL_NAME_REQUIREMENTS) |
|
559 | requirements=URL_NAME_REQUIREMENTS) | |
560 | rmap.connect('repo_issuetracker_delete', |
|
560 | rmap.connect('repo_issuetracker_delete', | |
561 | '/{repo_name}/settings/issue-tracker/delete', |
|
561 | '/{repo_name}/settings/issue-tracker/delete', | |
562 | controller='admin/repos', action='repo_issuetracker_delete', |
|
562 | controller='admin/repos', action='repo_issuetracker_delete', | |
563 | conditions={'method': ['DELETE'], 'function': check_repo}, |
|
563 | conditions={'method': ['DELETE'], 'function': check_repo}, | |
564 | requirements=URL_NAME_REQUIREMENTS) |
|
564 | requirements=URL_NAME_REQUIREMENTS) | |
565 | rmap.connect('repo_issuetracker_save', |
|
565 | rmap.connect('repo_issuetracker_save', | |
566 | '/{repo_name}/settings/issue-tracker/save', |
|
566 | '/{repo_name}/settings/issue-tracker/save', | |
567 | controller='admin/repos', action='repo_issuetracker_save', |
|
567 | controller='admin/repos', action='repo_issuetracker_save', | |
568 | conditions={'method': ['POST'], 'function': check_repo}, |
|
568 | conditions={'method': ['POST'], 'function': check_repo}, | |
569 | requirements=URL_NAME_REQUIREMENTS) |
|
569 | requirements=URL_NAME_REQUIREMENTS) | |
570 | rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs', |
|
570 | rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs', | |
571 | controller='admin/repos', action='repo_settings_vcs_update', |
|
571 | controller='admin/repos', action='repo_settings_vcs_update', | |
572 | conditions={'method': ['POST'], 'function': check_repo}, |
|
572 | conditions={'method': ['POST'], 'function': check_repo}, | |
573 | requirements=URL_NAME_REQUIREMENTS) |
|
573 | requirements=URL_NAME_REQUIREMENTS) | |
574 | rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs', |
|
574 | rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs', | |
575 | controller='admin/repos', action='repo_settings_vcs', |
|
575 | controller='admin/repos', action='repo_settings_vcs', | |
576 | conditions={'method': ['GET'], 'function': check_repo}, |
|
576 | conditions={'method': ['GET'], 'function': check_repo}, | |
577 | requirements=URL_NAME_REQUIREMENTS) |
|
577 | requirements=URL_NAME_REQUIREMENTS) | |
578 | rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs', |
|
578 | rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs', | |
579 | controller='admin/repos', action='repo_delete_svn_pattern', |
|
579 | controller='admin/repos', action='repo_delete_svn_pattern', | |
580 | conditions={'method': ['DELETE'], 'function': check_repo}, |
|
580 | conditions={'method': ['DELETE'], 'function': check_repo}, | |
581 | requirements=URL_NAME_REQUIREMENTS) |
|
581 | requirements=URL_NAME_REQUIREMENTS) | |
582 | rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest', |
|
582 | rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest', | |
583 | controller='admin/repos', action='repo_settings_pullrequest', |
|
583 | controller='admin/repos', action='repo_settings_pullrequest', | |
584 | conditions={'method': ['GET', 'POST'], 'function': check_repo}, |
|
584 | conditions={'method': ['GET', 'POST'], 'function': check_repo}, | |
585 | requirements=URL_NAME_REQUIREMENTS) |
|
585 | requirements=URL_NAME_REQUIREMENTS) | |
586 |
|
586 | |||
587 | # still working url for backward compat. |
|
587 | # still working url for backward compat. | |
588 | rmap.connect('raw_changeset_home_depraced', |
|
588 | rmap.connect('raw_changeset_home_depraced', | |
589 | '/{repo_name}/raw-changeset/{revision}', |
|
589 | '/{repo_name}/raw-changeset/{revision}', | |
590 | controller='changeset', action='changeset_raw', |
|
590 | controller='changeset', action='changeset_raw', | |
591 | revision='tip', conditions={'function': check_repo}, |
|
591 | revision='tip', conditions={'function': check_repo}, | |
592 | requirements=URL_NAME_REQUIREMENTS) |
|
592 | requirements=URL_NAME_REQUIREMENTS) | |
593 |
|
593 | |||
594 | # new URLs |
|
594 | # new URLs | |
595 | rmap.connect('changeset_raw_home', |
|
595 | rmap.connect('changeset_raw_home', | |
596 | '/{repo_name}/changeset-diff/{revision}', |
|
596 | '/{repo_name}/changeset-diff/{revision}', | |
597 | controller='changeset', action='changeset_raw', |
|
597 | controller='changeset', action='changeset_raw', | |
598 | revision='tip', conditions={'function': check_repo}, |
|
598 | revision='tip', conditions={'function': check_repo}, | |
599 | requirements=URL_NAME_REQUIREMENTS) |
|
599 | requirements=URL_NAME_REQUIREMENTS) | |
600 |
|
600 | |||
601 | rmap.connect('changeset_patch_home', |
|
601 | rmap.connect('changeset_patch_home', | |
602 | '/{repo_name}/changeset-patch/{revision}', |
|
602 | '/{repo_name}/changeset-patch/{revision}', | |
603 | controller='changeset', action='changeset_patch', |
|
603 | controller='changeset', action='changeset_patch', | |
604 | revision='tip', conditions={'function': check_repo}, |
|
604 | revision='tip', conditions={'function': check_repo}, | |
605 | requirements=URL_NAME_REQUIREMENTS) |
|
605 | requirements=URL_NAME_REQUIREMENTS) | |
606 |
|
606 | |||
607 | rmap.connect('changeset_download_home', |
|
607 | rmap.connect('changeset_download_home', | |
608 | '/{repo_name}/changeset-download/{revision}', |
|
608 | '/{repo_name}/changeset-download/{revision}', | |
609 | controller='changeset', action='changeset_download', |
|
609 | controller='changeset', action='changeset_download', | |
610 | revision='tip', conditions={'function': check_repo}, |
|
610 | revision='tip', conditions={'function': check_repo}, | |
611 | requirements=URL_NAME_REQUIREMENTS) |
|
611 | requirements=URL_NAME_REQUIREMENTS) | |
612 |
|
612 | |||
613 | rmap.connect('changeset_comment', |
|
613 | rmap.connect('changeset_comment', | |
614 | '/{repo_name}/changeset/{revision}/comment', jsroute=True, |
|
614 | '/{repo_name}/changeset/{revision}/comment', jsroute=True, | |
615 | controller='changeset', revision='tip', action='comment', |
|
615 | controller='changeset', revision='tip', action='comment', | |
616 | conditions={'function': check_repo}, |
|
616 | conditions={'function': check_repo}, | |
617 | requirements=URL_NAME_REQUIREMENTS) |
|
617 | requirements=URL_NAME_REQUIREMENTS) | |
618 |
|
618 | |||
619 | rmap.connect('changeset_comment_preview', |
|
619 | rmap.connect('changeset_comment_preview', | |
620 | '/{repo_name}/changeset/comment/preview', jsroute=True, |
|
620 | '/{repo_name}/changeset/comment/preview', jsroute=True, | |
621 | controller='changeset', action='preview_comment', |
|
621 | controller='changeset', action='preview_comment', | |
622 | conditions={'function': check_repo, 'method': ['POST']}, |
|
622 | conditions={'function': check_repo, 'method': ['POST']}, | |
623 | requirements=URL_NAME_REQUIREMENTS) |
|
623 | requirements=URL_NAME_REQUIREMENTS) | |
624 |
|
624 | |||
625 | rmap.connect('changeset_comment_delete', |
|
625 | rmap.connect('changeset_comment_delete', | |
626 | '/{repo_name}/changeset/comment/{comment_id}/delete', |
|
626 | '/{repo_name}/changeset/comment/{comment_id}/delete', | |
627 | controller='changeset', action='delete_comment', |
|
627 | controller='changeset', action='delete_comment', | |
628 | conditions={'function': check_repo, 'method': ['DELETE']}, |
|
628 | conditions={'function': check_repo, 'method': ['DELETE']}, | |
629 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
629 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
630 |
|
630 | |||
631 | rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}', |
|
631 | rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}', | |
632 | controller='changeset', action='changeset_info', |
|
632 | controller='changeset', action='changeset_info', | |
633 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
633 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
634 |
|
634 | |||
635 | rmap.connect('compare_home', |
|
635 | rmap.connect('compare_home', | |
636 | '/{repo_name}/compare', |
|
636 | '/{repo_name}/compare', | |
637 | controller='compare', action='index', |
|
637 | controller='compare', action='index', | |
638 | conditions={'function': check_repo}, |
|
638 | conditions={'function': check_repo}, | |
639 | requirements=URL_NAME_REQUIREMENTS) |
|
639 | requirements=URL_NAME_REQUIREMENTS) | |
640 |
|
640 | |||
641 | rmap.connect('compare_url', |
|
641 | rmap.connect('compare_url', | |
642 | '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', |
|
642 | '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', | |
643 | controller='compare', action='compare', |
|
643 | controller='compare', action='compare', | |
644 | conditions={'function': check_repo}, |
|
644 | conditions={'function': check_repo}, | |
645 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
645 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
646 |
|
646 | |||
647 | rmap.connect('pullrequest_home', |
|
647 | rmap.connect('pullrequest_home', | |
648 | '/{repo_name}/pull-request/new', controller='pullrequests', |
|
648 | '/{repo_name}/pull-request/new', controller='pullrequests', | |
649 | action='index', conditions={'function': check_repo, |
|
649 | action='index', conditions={'function': check_repo, | |
650 | 'method': ['GET']}, |
|
650 | 'method': ['GET']}, | |
651 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
651 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
652 |
|
652 | |||
653 | rmap.connect('pullrequest', |
|
653 | rmap.connect('pullrequest', | |
654 | '/{repo_name}/pull-request/new', controller='pullrequests', |
|
654 | '/{repo_name}/pull-request/new', controller='pullrequests', | |
655 | action='create', conditions={'function': check_repo, |
|
655 | action='create', conditions={'function': check_repo, | |
656 | 'method': ['POST']}, |
|
656 | 'method': ['POST']}, | |
657 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
657 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
658 |
|
658 | |||
659 | rmap.connect('pullrequest_repo_refs', |
|
659 | rmap.connect('pullrequest_repo_refs', | |
660 | '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}', |
|
660 | '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}', | |
661 | controller='pullrequests', |
|
661 | controller='pullrequests', | |
662 | action='get_repo_refs', |
|
662 | action='get_repo_refs', | |
663 | conditions={'function': check_repo, 'method': ['GET']}, |
|
663 | conditions={'function': check_repo, 'method': ['GET']}, | |
664 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
664 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
665 |
|
665 | |||
666 | rmap.connect('pullrequest_repo_destinations', |
|
666 | rmap.connect('pullrequest_repo_destinations', | |
667 | '/{repo_name}/pull-request/repo-destinations', |
|
667 | '/{repo_name}/pull-request/repo-destinations', | |
668 | controller='pullrequests', |
|
668 | controller='pullrequests', | |
669 | action='get_repo_destinations', |
|
669 | action='get_repo_destinations', | |
670 | conditions={'function': check_repo, 'method': ['GET']}, |
|
670 | conditions={'function': check_repo, 'method': ['GET']}, | |
671 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
671 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
672 |
|
672 | |||
673 | rmap.connect('pullrequest_show', |
|
673 | rmap.connect('pullrequest_show', | |
674 | '/{repo_name}/pull-request/{pull_request_id}', |
|
674 | '/{repo_name}/pull-request/{pull_request_id}', | |
675 | controller='pullrequests', |
|
675 | controller='pullrequests', | |
676 | action='show', conditions={'function': check_repo, |
|
676 | action='show', conditions={'function': check_repo, | |
677 | 'method': ['GET']}, |
|
677 | 'method': ['GET']}, | |
678 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
678 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
679 |
|
679 | |||
680 | rmap.connect('pullrequest_update', |
|
680 | rmap.connect('pullrequest_update', | |
681 | '/{repo_name}/pull-request/{pull_request_id}', |
|
681 | '/{repo_name}/pull-request/{pull_request_id}', | |
682 | controller='pullrequests', |
|
682 | controller='pullrequests', | |
683 | action='update', conditions={'function': check_repo, |
|
683 | action='update', conditions={'function': check_repo, | |
684 | 'method': ['PUT']}, |
|
684 | 'method': ['PUT']}, | |
685 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
685 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
686 |
|
686 | |||
687 | rmap.connect('pullrequest_merge', |
|
687 | rmap.connect('pullrequest_merge', | |
688 | '/{repo_name}/pull-request/{pull_request_id}', |
|
688 | '/{repo_name}/pull-request/{pull_request_id}', | |
689 | controller='pullrequests', |
|
689 | controller='pullrequests', | |
690 | action='merge', conditions={'function': check_repo, |
|
690 | action='merge', conditions={'function': check_repo, | |
691 | 'method': ['POST']}, |
|
691 | 'method': ['POST']}, | |
692 | requirements=URL_NAME_REQUIREMENTS) |
|
692 | requirements=URL_NAME_REQUIREMENTS) | |
693 |
|
693 | |||
694 | rmap.connect('pullrequest_delete', |
|
694 | rmap.connect('pullrequest_delete', | |
695 | '/{repo_name}/pull-request/{pull_request_id}', |
|
695 | '/{repo_name}/pull-request/{pull_request_id}', | |
696 | controller='pullrequests', |
|
696 | controller='pullrequests', | |
697 | action='delete', conditions={'function': check_repo, |
|
697 | action='delete', conditions={'function': check_repo, | |
698 | 'method': ['DELETE']}, |
|
698 | 'method': ['DELETE']}, | |
699 | requirements=URL_NAME_REQUIREMENTS) |
|
699 | requirements=URL_NAME_REQUIREMENTS) | |
700 |
|
700 | |||
701 | rmap.connect('pullrequest_comment', |
|
701 | rmap.connect('pullrequest_comment', | |
702 | '/{repo_name}/pull-request-comment/{pull_request_id}', |
|
702 | '/{repo_name}/pull-request-comment/{pull_request_id}', | |
703 | controller='pullrequests', |
|
703 | controller='pullrequests', | |
704 | action='comment', conditions={'function': check_repo, |
|
704 | action='comment', conditions={'function': check_repo, | |
705 | 'method': ['POST']}, |
|
705 | 'method': ['POST']}, | |
706 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
706 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
707 |
|
707 | |||
708 | rmap.connect('pullrequest_comment_delete', |
|
708 | rmap.connect('pullrequest_comment_delete', | |
709 | '/{repo_name}/pull-request-comment/{comment_id}/delete', |
|
709 | '/{repo_name}/pull-request-comment/{comment_id}/delete', | |
710 | controller='pullrequests', action='delete_comment', |
|
710 | controller='pullrequests', action='delete_comment', | |
711 | conditions={'function': check_repo, 'method': ['DELETE']}, |
|
711 | conditions={'function': check_repo, 'method': ['DELETE']}, | |
712 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
712 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
713 |
|
713 | |||
714 | rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True, |
|
714 | rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True, | |
715 | controller='changelog', conditions={'function': check_repo}, |
|
715 | controller='changelog', conditions={'function': check_repo}, | |
716 | requirements=URL_NAME_REQUIREMENTS) |
|
716 | requirements=URL_NAME_REQUIREMENTS) | |
717 |
|
717 | |||
718 | rmap.connect('changelog_file_home', |
|
718 | rmap.connect('changelog_file_home', | |
719 | '/{repo_name}/changelog/{revision}/{f_path}', |
|
719 | '/{repo_name}/changelog/{revision}/{f_path}', | |
720 | controller='changelog', f_path=None, |
|
720 | controller='changelog', f_path=None, | |
721 | conditions={'function': check_repo}, |
|
721 | conditions={'function': check_repo}, | |
722 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
722 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
723 |
|
723 | |||
724 | rmap.connect('changelog_elements', '/{repo_name}/changelog_details', |
|
724 | rmap.connect('changelog_elements', '/{repo_name}/changelog_details', | |
725 | controller='changelog', action='changelog_elements', |
|
725 | controller='changelog', action='changelog_elements', | |
726 | conditions={'function': check_repo}, |
|
726 | conditions={'function': check_repo}, | |
727 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
727 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
728 |
|
728 | |||
729 | rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}', |
|
|||
730 | controller='files', revision='tip', f_path='', |
|
|||
731 | conditions={'function': check_repo}, |
|
|||
732 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
|||
733 |
|
||||
734 | rmap.connect('files_home_simple_catchrev', |
|
|||
735 | '/{repo_name}/files/{revision}', |
|
|||
736 | controller='files', revision='tip', f_path='', |
|
|||
737 | conditions={'function': check_repo}, |
|
|||
738 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
739 |
|
||||
740 | rmap.connect('files_home_simple_catchall', |
|
|||
741 | '/{repo_name}/files', |
|
|||
742 | controller='files', revision='tip', f_path='', |
|
|||
743 | conditions={'function': check_repo}, |
|
|||
744 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
745 |
|
||||
746 | rmap.connect('files_history_home', |
|
|||
747 | '/{repo_name}/history/{revision}/{f_path}', |
|
|||
748 | controller='files', action='history', revision='tip', f_path='', |
|
|||
749 | conditions={'function': check_repo}, |
|
|||
750 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
|||
751 |
|
||||
752 | rmap.connect('files_authors_home', |
|
|||
753 | '/{repo_name}/authors/{revision}/{f_path}', |
|
|||
754 | controller='files', action='authors', revision='tip', f_path='', |
|
|||
755 | conditions={'function': check_repo}, |
|
|||
756 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
|||
757 |
|
||||
758 | rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}', |
|
|||
759 | controller='files', action='diff', f_path='', |
|
|||
760 | conditions={'function': check_repo}, |
|
|||
761 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
762 |
|
||||
763 | rmap.connect('files_diff_2way_home', |
|
|||
764 | '/{repo_name}/diff-2way/{f_path}', |
|
|||
765 | controller='files', action='diff_2way', f_path='', |
|
|||
766 | conditions={'function': check_repo}, |
|
|||
767 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
768 |
|
||||
769 | rmap.connect('files_rawfile_home', |
|
|||
770 | '/{repo_name}/rawfile/{revision}/{f_path}', |
|
|||
771 | controller='files', action='rawfile', revision='tip', |
|
|||
772 | f_path='', conditions={'function': check_repo}, |
|
|||
773 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
774 |
|
||||
775 | rmap.connect('files_raw_home', |
|
|||
776 | '/{repo_name}/raw/{revision}/{f_path}', |
|
|||
777 | controller='files', action='raw', revision='tip', f_path='', |
|
|||
778 | conditions={'function': check_repo}, |
|
|||
779 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
780 |
|
||||
781 | rmap.connect('files_render_home', |
|
|||
782 | '/{repo_name}/render/{revision}/{f_path}', |
|
|||
783 | controller='files', action='index', revision='tip', f_path='', |
|
|||
784 | rendered=True, conditions={'function': check_repo}, |
|
|||
785 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
786 |
|
||||
787 | rmap.connect('files_annotate_home', |
|
|||
788 | '/{repo_name}/annotate/{revision}/{f_path}', |
|
|||
789 | controller='files', action='index', revision='tip', |
|
|||
790 | f_path='', annotate=True, conditions={'function': check_repo}, |
|
|||
791 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
|||
792 |
|
||||
793 | rmap.connect('files_annotate_previous', |
|
|||
794 | '/{repo_name}/annotate-previous/{revision}/{f_path}', |
|
|||
795 | controller='files', action='annotate_previous', revision='tip', |
|
|||
796 | f_path='', annotate=True, conditions={'function': check_repo}, |
|
|||
797 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
|||
798 |
|
||||
799 | rmap.connect('files_edit', |
|
|||
800 | '/{repo_name}/edit/{revision}/{f_path}', |
|
|||
801 | controller='files', action='edit', revision='tip', |
|
|||
802 | f_path='', |
|
|||
803 | conditions={'function': check_repo, 'method': ['POST']}, |
|
|||
804 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
805 |
|
||||
806 | rmap.connect('files_edit_home', |
|
|||
807 | '/{repo_name}/edit/{revision}/{f_path}', |
|
|||
808 | controller='files', action='edit_home', revision='tip', |
|
|||
809 | f_path='', conditions={'function': check_repo}, |
|
|||
810 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
811 |
|
||||
812 | rmap.connect('files_add', |
|
|||
813 | '/{repo_name}/add/{revision}/{f_path}', |
|
|||
814 | controller='files', action='add', revision='tip', |
|
|||
815 | f_path='', |
|
|||
816 | conditions={'function': check_repo, 'method': ['POST']}, |
|
|||
817 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
818 |
|
||||
819 | rmap.connect('files_add_home', |
|
|||
820 | '/{repo_name}/add/{revision}/{f_path}', |
|
|||
821 | controller='files', action='add_home', revision='tip', |
|
|||
822 | f_path='', conditions={'function': check_repo}, |
|
|||
823 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
824 |
|
||||
825 | rmap.connect('files_delete', |
|
|||
826 | '/{repo_name}/delete/{revision}/{f_path}', |
|
|||
827 | controller='files', action='delete', revision='tip', |
|
|||
828 | f_path='', |
|
|||
829 | conditions={'function': check_repo, 'method': ['POST']}, |
|
|||
830 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
831 |
|
||||
832 | rmap.connect('files_delete_home', |
|
|||
833 | '/{repo_name}/delete/{revision}/{f_path}', |
|
|||
834 | controller='files', action='delete_home', revision='tip', |
|
|||
835 | f_path='', conditions={'function': check_repo}, |
|
|||
836 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
837 |
|
||||
838 | rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}', |
|
|||
839 | controller='files', action='archivefile', |
|
|||
840 | conditions={'function': check_repo}, |
|
|||
841 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
|||
842 |
|
||||
843 | rmap.connect('files_nodelist_home', |
|
|||
844 | '/{repo_name}/nodelist/{revision}/{f_path}', |
|
|||
845 | controller='files', action='nodelist', |
|
|||
846 | conditions={'function': check_repo}, |
|
|||
847 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
|||
848 |
|
||||
849 | rmap.connect('files_nodetree_full', |
|
|||
850 | '/{repo_name}/nodetree_full/{commit_id}/{f_path}', |
|
|||
851 | controller='files', action='nodetree_full', |
|
|||
852 | conditions={'function': check_repo}, |
|
|||
853 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
|||
854 |
|
||||
855 | rmap.connect('repo_fork_create_home', '/{repo_name}/fork', |
|
729 | rmap.connect('repo_fork_create_home', '/{repo_name}/fork', | |
856 | controller='forks', action='fork_create', |
|
730 | controller='forks', action='fork_create', | |
857 | conditions={'function': check_repo, 'method': ['POST']}, |
|
731 | conditions={'function': check_repo, 'method': ['POST']}, | |
858 | requirements=URL_NAME_REQUIREMENTS) |
|
732 | requirements=URL_NAME_REQUIREMENTS) | |
859 |
|
733 | |||
860 | rmap.connect('repo_fork_home', '/{repo_name}/fork', |
|
734 | rmap.connect('repo_fork_home', '/{repo_name}/fork', | |
861 | controller='forks', action='fork', |
|
735 | controller='forks', action='fork', | |
862 | conditions={'function': check_repo}, |
|
736 | conditions={'function': check_repo}, | |
863 | requirements=URL_NAME_REQUIREMENTS) |
|
737 | requirements=URL_NAME_REQUIREMENTS) | |
864 |
|
738 | |||
865 | rmap.connect('repo_forks_home', '/{repo_name}/forks', |
|
739 | rmap.connect('repo_forks_home', '/{repo_name}/forks', | |
866 | controller='forks', action='forks', |
|
740 | controller='forks', action='forks', | |
867 | conditions={'function': check_repo}, |
|
741 | conditions={'function': check_repo}, | |
868 | requirements=URL_NAME_REQUIREMENTS) |
|
742 | requirements=URL_NAME_REQUIREMENTS) | |
869 |
|
743 | |||
870 | return rmap |
|
744 | return rmap |
@@ -1,2040 +1,2046 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 |
|
2 | |||
3 | # Copyright (C) 2010-2017 RhodeCode GmbH |
|
3 | # Copyright (C) 2010-2017 RhodeCode GmbH | |
4 | # |
|
4 | # | |
5 | # This program is free software: you can redistribute it and/or modify |
|
5 | # This program is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
7 | # (only), as published by the Free Software Foundation. |
|
7 | # (only), as published by the Free Software Foundation. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | # |
|
16 | # | |
17 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 |
|
20 | |||
21 | """ |
|
21 | """ | |
22 | Helper functions |
|
22 | Helper functions | |
23 |
|
23 | |||
24 | Consists of functions to typically be used within templates, but also |
|
24 | Consists of functions to typically be used within templates, but also | |
25 | available to Controllers. This module is available to both as 'h'. |
|
25 | available to Controllers. This module is available to both as 'h'. | |
26 | """ |
|
26 | """ | |
27 |
|
27 | |||
28 | import random |
|
28 | import random | |
29 | import hashlib |
|
29 | import hashlib | |
30 | import StringIO |
|
30 | import StringIO | |
31 | import urllib |
|
31 | import urllib | |
32 | import math |
|
32 | import math | |
33 | import logging |
|
33 | import logging | |
34 | import re |
|
34 | import re | |
35 | import urlparse |
|
35 | import urlparse | |
36 | import time |
|
36 | import time | |
37 | import string |
|
37 | import string | |
38 | import hashlib |
|
38 | import hashlib | |
39 | from collections import OrderedDict |
|
39 | from collections import OrderedDict | |
40 |
|
40 | |||
41 | import pygments |
|
41 | import pygments | |
42 | import itertools |
|
42 | import itertools | |
43 | import fnmatch |
|
43 | import fnmatch | |
44 |
|
44 | |||
45 | from datetime import datetime |
|
45 | from datetime import datetime | |
46 | from functools import partial |
|
46 | from functools import partial | |
47 | from pygments.formatters.html import HtmlFormatter |
|
47 | from pygments.formatters.html import HtmlFormatter | |
48 | from pygments import highlight as code_highlight |
|
48 | from pygments import highlight as code_highlight | |
49 | from pygments.lexers import ( |
|
49 | from pygments.lexers import ( | |
50 | get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype) |
|
50 | get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype) | |
51 |
|
51 | |||
52 | from pyramid.threadlocal import get_current_request |
|
52 | from pyramid.threadlocal import get_current_request | |
53 |
|
53 | |||
54 | from webhelpers.html import literal, HTML, escape |
|
54 | from webhelpers.html import literal, HTML, escape | |
55 | from webhelpers.html.tools import * |
|
55 | from webhelpers.html.tools import * | |
56 | from webhelpers.html.builder import make_tag |
|
56 | from webhelpers.html.builder import make_tag | |
57 | from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \ |
|
57 | from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \ | |
58 | end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \ |
|
58 | end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \ | |
59 | link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \ |
|
59 | link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \ | |
60 | submit, text, password, textarea, title, ul, xml_declaration, radio |
|
60 | submit, text, password, textarea, title, ul, xml_declaration, radio | |
61 | from webhelpers.html.tools import auto_link, button_to, highlight, \ |
|
61 | from webhelpers.html.tools import auto_link, button_to, highlight, \ | |
62 | js_obfuscate, mail_to, strip_links, strip_tags, tag_re |
|
62 | js_obfuscate, mail_to, strip_links, strip_tags, tag_re | |
63 | from webhelpers.pylonslib import Flash as _Flash |
|
63 | from webhelpers.pylonslib import Flash as _Flash | |
64 | from webhelpers.text import chop_at, collapse, convert_accented_entities, \ |
|
64 | from webhelpers.text import chop_at, collapse, convert_accented_entities, \ | |
65 | convert_misc_entities, lchop, plural, rchop, remove_formatting, \ |
|
65 | convert_misc_entities, lchop, plural, rchop, remove_formatting, \ | |
66 | replace_whitespace, urlify, truncate, wrap_paragraphs |
|
66 | replace_whitespace, urlify, truncate, wrap_paragraphs | |
67 | from webhelpers.date import time_ago_in_words |
|
67 | from webhelpers.date import time_ago_in_words | |
68 | from webhelpers.paginate import Page as _Page |
|
68 | from webhelpers.paginate import Page as _Page | |
69 | from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \ |
|
69 | from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \ | |
70 | convert_boolean_attrs, NotGiven, _make_safe_id_component |
|
70 | convert_boolean_attrs, NotGiven, _make_safe_id_component | |
71 | from webhelpers2.number import format_byte_size |
|
71 | from webhelpers2.number import format_byte_size | |
72 |
|
72 | |||
73 | from rhodecode.lib.action_parser import action_parser |
|
73 | from rhodecode.lib.action_parser import action_parser | |
74 | from rhodecode.lib.ext_json import json |
|
74 | from rhodecode.lib.ext_json import json | |
75 | from rhodecode.lib.utils import repo_name_slug, get_custom_lexer |
|
75 | from rhodecode.lib.utils import repo_name_slug, get_custom_lexer | |
76 | from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \ |
|
76 | from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \ | |
77 | get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \ |
|
77 | get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \ | |
78 | AttributeDict, safe_int, md5, md5_safe |
|
78 | AttributeDict, safe_int, md5, md5_safe | |
79 | from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links |
|
79 | from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links | |
80 | from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError |
|
80 | from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError | |
81 | from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit |
|
81 | from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit | |
82 | from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT |
|
82 | from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT | |
83 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
83 | from rhodecode.model.changeset_status import ChangesetStatusModel | |
84 | from rhodecode.model.db import Permission, User, Repository |
|
84 | from rhodecode.model.db import Permission, User, Repository | |
85 | from rhodecode.model.repo_group import RepoGroupModel |
|
85 | from rhodecode.model.repo_group import RepoGroupModel | |
86 | from rhodecode.model.settings import IssueTrackerSettingsModel |
|
86 | from rhodecode.model.settings import IssueTrackerSettingsModel | |
87 |
|
87 | |||
88 | log = logging.getLogger(__name__) |
|
88 | log = logging.getLogger(__name__) | |
89 |
|
89 | |||
90 |
|
90 | |||
91 | DEFAULT_USER = User.DEFAULT_USER |
|
91 | DEFAULT_USER = User.DEFAULT_USER | |
92 | DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL |
|
92 | DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL | |
93 |
|
93 | |||
94 |
|
94 | |||
95 | def url(*args, **kw): |
|
95 | def url(*args, **kw): | |
96 | from pylons import url as pylons_url |
|
96 | from pylons import url as pylons_url | |
97 | return pylons_url(*args, **kw) |
|
97 | return pylons_url(*args, **kw) | |
98 |
|
98 | |||
99 |
|
99 | |||
100 | def pylons_url_current(*args, **kw): |
|
100 | def pylons_url_current(*args, **kw): | |
101 | """ |
|
101 | """ | |
102 | This function overrides pylons.url.current() which returns the current |
|
102 | This function overrides pylons.url.current() which returns the current | |
103 | path so that it will also work from a pyramid only context. This |
|
103 | path so that it will also work from a pyramid only context. This | |
104 | should be removed once port to pyramid is complete. |
|
104 | should be removed once port to pyramid is complete. | |
105 | """ |
|
105 | """ | |
106 | from pylons import url as pylons_url |
|
106 | from pylons import url as pylons_url | |
107 | if not args and not kw: |
|
107 | if not args and not kw: | |
108 | request = get_current_request() |
|
108 | request = get_current_request() | |
109 | return request.path |
|
109 | return request.path | |
110 | return pylons_url.current(*args, **kw) |
|
110 | return pylons_url.current(*args, **kw) | |
111 |
|
111 | |||
112 | url.current = pylons_url_current |
|
112 | url.current = pylons_url_current | |
113 |
|
113 | |||
114 |
|
114 | |||
115 | def url_replace(**qargs): |
|
115 | def url_replace(**qargs): | |
116 | """ Returns the current request url while replacing query string args """ |
|
116 | """ Returns the current request url while replacing query string args """ | |
117 |
|
117 | |||
118 | request = get_current_request() |
|
118 | request = get_current_request() | |
119 | new_args = request.GET.mixed() |
|
119 | new_args = request.GET.mixed() | |
120 | new_args.update(qargs) |
|
120 | new_args.update(qargs) | |
121 | return url('', **new_args) |
|
121 | return url('', **new_args) | |
122 |
|
122 | |||
123 |
|
123 | |||
124 | def asset(path, ver=None, **kwargs): |
|
124 | def asset(path, ver=None, **kwargs): | |
125 | """ |
|
125 | """ | |
126 | Helper to generate a static asset file path for rhodecode assets |
|
126 | Helper to generate a static asset file path for rhodecode assets | |
127 |
|
127 | |||
128 | eg. h.asset('images/image.png', ver='3923') |
|
128 | eg. h.asset('images/image.png', ver='3923') | |
129 |
|
129 | |||
130 | :param path: path of asset |
|
130 | :param path: path of asset | |
131 | :param ver: optional version query param to append as ?ver= |
|
131 | :param ver: optional version query param to append as ?ver= | |
132 | """ |
|
132 | """ | |
133 | request = get_current_request() |
|
133 | request = get_current_request() | |
134 | query = {} |
|
134 | query = {} | |
135 | query.update(kwargs) |
|
135 | query.update(kwargs) | |
136 | if ver: |
|
136 | if ver: | |
137 | query = {'ver': ver} |
|
137 | query = {'ver': ver} | |
138 | return request.static_path( |
|
138 | return request.static_path( | |
139 | 'rhodecode:public/{}'.format(path), _query=query) |
|
139 | 'rhodecode:public/{}'.format(path), _query=query) | |
140 |
|
140 | |||
141 |
|
141 | |||
142 | default_html_escape_table = { |
|
142 | default_html_escape_table = { | |
143 | ord('&'): u'&', |
|
143 | ord('&'): u'&', | |
144 | ord('<'): u'<', |
|
144 | ord('<'): u'<', | |
145 | ord('>'): u'>', |
|
145 | ord('>'): u'>', | |
146 | ord('"'): u'"', |
|
146 | ord('"'): u'"', | |
147 | ord("'"): u''', |
|
147 | ord("'"): u''', | |
148 | } |
|
148 | } | |
149 |
|
149 | |||
150 |
|
150 | |||
151 | def html_escape(text, html_escape_table=default_html_escape_table): |
|
151 | def html_escape(text, html_escape_table=default_html_escape_table): | |
152 | """Produce entities within text.""" |
|
152 | """Produce entities within text.""" | |
153 | return text.translate(html_escape_table) |
|
153 | return text.translate(html_escape_table) | |
154 |
|
154 | |||
155 |
|
155 | |||
156 | def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None): |
|
156 | def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None): | |
157 | """ |
|
157 | """ | |
158 | Truncate string ``s`` at the first occurrence of ``sub``. |
|
158 | Truncate string ``s`` at the first occurrence of ``sub``. | |
159 |
|
159 | |||
160 | If ``inclusive`` is true, truncate just after ``sub`` rather than at it. |
|
160 | If ``inclusive`` is true, truncate just after ``sub`` rather than at it. | |
161 | """ |
|
161 | """ | |
162 | suffix_if_chopped = suffix_if_chopped or '' |
|
162 | suffix_if_chopped = suffix_if_chopped or '' | |
163 | pos = s.find(sub) |
|
163 | pos = s.find(sub) | |
164 | if pos == -1: |
|
164 | if pos == -1: | |
165 | return s |
|
165 | return s | |
166 |
|
166 | |||
167 | if inclusive: |
|
167 | if inclusive: | |
168 | pos += len(sub) |
|
168 | pos += len(sub) | |
169 |
|
169 | |||
170 | chopped = s[:pos] |
|
170 | chopped = s[:pos] | |
171 | left = s[pos:].strip() |
|
171 | left = s[pos:].strip() | |
172 |
|
172 | |||
173 | if left and suffix_if_chopped: |
|
173 | if left and suffix_if_chopped: | |
174 | chopped += suffix_if_chopped |
|
174 | chopped += suffix_if_chopped | |
175 |
|
175 | |||
176 | return chopped |
|
176 | return chopped | |
177 |
|
177 | |||
178 |
|
178 | |||
179 | def shorter(text, size=20): |
|
179 | def shorter(text, size=20): | |
180 | postfix = '...' |
|
180 | postfix = '...' | |
181 | if len(text) > size: |
|
181 | if len(text) > size: | |
182 | return text[:size - len(postfix)] + postfix |
|
182 | return text[:size - len(postfix)] + postfix | |
183 | return text |
|
183 | return text | |
184 |
|
184 | |||
185 |
|
185 | |||
186 | def _reset(name, value=None, id=NotGiven, type="reset", **attrs): |
|
186 | def _reset(name, value=None, id=NotGiven, type="reset", **attrs): | |
187 | """ |
|
187 | """ | |
188 | Reset button |
|
188 | Reset button | |
189 | """ |
|
189 | """ | |
190 | _set_input_attrs(attrs, type, name, value) |
|
190 | _set_input_attrs(attrs, type, name, value) | |
191 | _set_id_attr(attrs, id, name) |
|
191 | _set_id_attr(attrs, id, name) | |
192 | convert_boolean_attrs(attrs, ["disabled"]) |
|
192 | convert_boolean_attrs(attrs, ["disabled"]) | |
193 | return HTML.input(**attrs) |
|
193 | return HTML.input(**attrs) | |
194 |
|
194 | |||
195 | reset = _reset |
|
195 | reset = _reset | |
196 | safeid = _make_safe_id_component |
|
196 | safeid = _make_safe_id_component | |
197 |
|
197 | |||
198 |
|
198 | |||
199 | def branding(name, length=40): |
|
199 | def branding(name, length=40): | |
200 | return truncate(name, length, indicator="") |
|
200 | return truncate(name, length, indicator="") | |
201 |
|
201 | |||
202 |
|
202 | |||
203 | def FID(raw_id, path): |
|
203 | def FID(raw_id, path): | |
204 | """ |
|
204 | """ | |
205 | Creates a unique ID for filenode based on it's hash of path and commit |
|
205 | Creates a unique ID for filenode based on it's hash of path and commit | |
206 | it's safe to use in urls |
|
206 | it's safe to use in urls | |
207 |
|
207 | |||
208 | :param raw_id: |
|
208 | :param raw_id: | |
209 | :param path: |
|
209 | :param path: | |
210 | """ |
|
210 | """ | |
211 |
|
211 | |||
212 | return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12]) |
|
212 | return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12]) | |
213 |
|
213 | |||
214 |
|
214 | |||
215 | class _GetError(object): |
|
215 | class _GetError(object): | |
216 | """Get error from form_errors, and represent it as span wrapped error |
|
216 | """Get error from form_errors, and represent it as span wrapped error | |
217 | message |
|
217 | message | |
218 |
|
218 | |||
219 | :param field_name: field to fetch errors for |
|
219 | :param field_name: field to fetch errors for | |
220 | :param form_errors: form errors dict |
|
220 | :param form_errors: form errors dict | |
221 | """ |
|
221 | """ | |
222 |
|
222 | |||
223 | def __call__(self, field_name, form_errors): |
|
223 | def __call__(self, field_name, form_errors): | |
224 | tmpl = """<span class="error_msg">%s</span>""" |
|
224 | tmpl = """<span class="error_msg">%s</span>""" | |
225 | if form_errors and field_name in form_errors: |
|
225 | if form_errors and field_name in form_errors: | |
226 | return literal(tmpl % form_errors.get(field_name)) |
|
226 | return literal(tmpl % form_errors.get(field_name)) | |
227 |
|
227 | |||
228 | get_error = _GetError() |
|
228 | get_error = _GetError() | |
229 |
|
229 | |||
230 |
|
230 | |||
231 | class _ToolTip(object): |
|
231 | class _ToolTip(object): | |
232 |
|
232 | |||
233 | def __call__(self, tooltip_title, trim_at=50): |
|
233 | def __call__(self, tooltip_title, trim_at=50): | |
234 | """ |
|
234 | """ | |
235 | Special function just to wrap our text into nice formatted |
|
235 | Special function just to wrap our text into nice formatted | |
236 | autowrapped text |
|
236 | autowrapped text | |
237 |
|
237 | |||
238 | :param tooltip_title: |
|
238 | :param tooltip_title: | |
239 | """ |
|
239 | """ | |
240 | tooltip_title = escape(tooltip_title) |
|
240 | tooltip_title = escape(tooltip_title) | |
241 | tooltip_title = tooltip_title.replace('<', '<').replace('>', '>') |
|
241 | tooltip_title = tooltip_title.replace('<', '<').replace('>', '>') | |
242 | return tooltip_title |
|
242 | return tooltip_title | |
243 | tooltip = _ToolTip() |
|
243 | tooltip = _ToolTip() | |
244 |
|
244 | |||
245 |
|
245 | |||
246 | def files_breadcrumbs(repo_name, commit_id, file_path): |
|
246 | def files_breadcrumbs(repo_name, commit_id, file_path): | |
247 | if isinstance(file_path, str): |
|
247 | if isinstance(file_path, str): | |
248 | file_path = safe_unicode(file_path) |
|
248 | file_path = safe_unicode(file_path) | |
249 |
|
249 | |||
250 | # TODO: johbo: Is this always a url like path, or is this operating |
|
250 | # TODO: johbo: Is this always a url like path, or is this operating | |
251 | # system dependent? |
|
251 | # system dependent? | |
252 | path_segments = file_path.split('/') |
|
252 | path_segments = file_path.split('/') | |
253 |
|
253 | |||
254 | repo_name_html = escape(repo_name) |
|
254 | repo_name_html = escape(repo_name) | |
255 | if len(path_segments) == 1 and path_segments[0] == '': |
|
255 | if len(path_segments) == 1 and path_segments[0] == '': | |
256 | url_segments = [repo_name_html] |
|
256 | url_segments = [repo_name_html] | |
257 | else: |
|
257 | else: | |
258 | url_segments = [ |
|
258 | url_segments = [ | |
259 | link_to( |
|
259 | link_to( | |
260 | repo_name_html, |
|
260 | repo_name_html, | |
261 |
|
|
261 | route_path( | |
|
262 | 'repo_files', | |||
262 | repo_name=repo_name, |
|
263 | repo_name=repo_name, | |
263 |
|
|
264 | commit_id=commit_id, | |
264 | f_path=''), |
|
265 | f_path=''), | |
265 | class_='pjax-link')] |
|
266 | class_='pjax-link')] | |
266 |
|
267 | |||
267 | last_cnt = len(path_segments) - 1 |
|
268 | last_cnt = len(path_segments) - 1 | |
268 | for cnt, segment in enumerate(path_segments): |
|
269 | for cnt, segment in enumerate(path_segments): | |
269 | if not segment: |
|
270 | if not segment: | |
270 | continue |
|
271 | continue | |
271 | segment_html = escape(segment) |
|
272 | segment_html = escape(segment) | |
272 |
|
273 | |||
273 | if cnt != last_cnt: |
|
274 | if cnt != last_cnt: | |
274 | url_segments.append( |
|
275 | url_segments.append( | |
275 | link_to( |
|
276 | link_to( | |
276 | segment_html, |
|
277 | segment_html, | |
277 |
|
|
278 | route_path( | |
|
279 | 'repo_files', | |||
278 | repo_name=repo_name, |
|
280 | repo_name=repo_name, | |
279 |
|
|
281 | commit_id=commit_id, | |
280 | f_path='/'.join(path_segments[:cnt + 1])), |
|
282 | f_path='/'.join(path_segments[:cnt + 1])), | |
281 | class_='pjax-link')) |
|
283 | class_='pjax-link')) | |
282 | else: |
|
284 | else: | |
283 | url_segments.append(segment_html) |
|
285 | url_segments.append(segment_html) | |
284 |
|
286 | |||
285 | return literal('/'.join(url_segments)) |
|
287 | return literal('/'.join(url_segments)) | |
286 |
|
288 | |||
287 |
|
289 | |||
288 | class CodeHtmlFormatter(HtmlFormatter): |
|
290 | class CodeHtmlFormatter(HtmlFormatter): | |
289 | """ |
|
291 | """ | |
290 | My code Html Formatter for source codes |
|
292 | My code Html Formatter for source codes | |
291 | """ |
|
293 | """ | |
292 |
|
294 | |||
293 | def wrap(self, source, outfile): |
|
295 | def wrap(self, source, outfile): | |
294 | return self._wrap_div(self._wrap_pre(self._wrap_code(source))) |
|
296 | return self._wrap_div(self._wrap_pre(self._wrap_code(source))) | |
295 |
|
297 | |||
296 | def _wrap_code(self, source): |
|
298 | def _wrap_code(self, source): | |
297 | for cnt, it in enumerate(source): |
|
299 | for cnt, it in enumerate(source): | |
298 | i, t = it |
|
300 | i, t = it | |
299 | t = '<div id="L%s">%s</div>' % (cnt + 1, t) |
|
301 | t = '<div id="L%s">%s</div>' % (cnt + 1, t) | |
300 | yield i, t |
|
302 | yield i, t | |
301 |
|
303 | |||
302 | def _wrap_tablelinenos(self, inner): |
|
304 | def _wrap_tablelinenos(self, inner): | |
303 | dummyoutfile = StringIO.StringIO() |
|
305 | dummyoutfile = StringIO.StringIO() | |
304 | lncount = 0 |
|
306 | lncount = 0 | |
305 | for t, line in inner: |
|
307 | for t, line in inner: | |
306 | if t: |
|
308 | if t: | |
307 | lncount += 1 |
|
309 | lncount += 1 | |
308 | dummyoutfile.write(line) |
|
310 | dummyoutfile.write(line) | |
309 |
|
311 | |||
310 | fl = self.linenostart |
|
312 | fl = self.linenostart | |
311 | mw = len(str(lncount + fl - 1)) |
|
313 | mw = len(str(lncount + fl - 1)) | |
312 | sp = self.linenospecial |
|
314 | sp = self.linenospecial | |
313 | st = self.linenostep |
|
315 | st = self.linenostep | |
314 | la = self.lineanchors |
|
316 | la = self.lineanchors | |
315 | aln = self.anchorlinenos |
|
317 | aln = self.anchorlinenos | |
316 | nocls = self.noclasses |
|
318 | nocls = self.noclasses | |
317 | if sp: |
|
319 | if sp: | |
318 | lines = [] |
|
320 | lines = [] | |
319 |
|
321 | |||
320 | for i in range(fl, fl + lncount): |
|
322 | for i in range(fl, fl + lncount): | |
321 | if i % st == 0: |
|
323 | if i % st == 0: | |
322 | if i % sp == 0: |
|
324 | if i % sp == 0: | |
323 | if aln: |
|
325 | if aln: | |
324 | lines.append('<a href="#%s%d" class="special">%*d</a>' % |
|
326 | lines.append('<a href="#%s%d" class="special">%*d</a>' % | |
325 | (la, i, mw, i)) |
|
327 | (la, i, mw, i)) | |
326 | else: |
|
328 | else: | |
327 | lines.append('<span class="special">%*d</span>' % (mw, i)) |
|
329 | lines.append('<span class="special">%*d</span>' % (mw, i)) | |
328 | else: |
|
330 | else: | |
329 | if aln: |
|
331 | if aln: | |
330 | lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i)) |
|
332 | lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i)) | |
331 | else: |
|
333 | else: | |
332 | lines.append('%*d' % (mw, i)) |
|
334 | lines.append('%*d' % (mw, i)) | |
333 | else: |
|
335 | else: | |
334 | lines.append('') |
|
336 | lines.append('') | |
335 | ls = '\n'.join(lines) |
|
337 | ls = '\n'.join(lines) | |
336 | else: |
|
338 | else: | |
337 | lines = [] |
|
339 | lines = [] | |
338 | for i in range(fl, fl + lncount): |
|
340 | for i in range(fl, fl + lncount): | |
339 | if i % st == 0: |
|
341 | if i % st == 0: | |
340 | if aln: |
|
342 | if aln: | |
341 | lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i)) |
|
343 | lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i)) | |
342 | else: |
|
344 | else: | |
343 | lines.append('%*d' % (mw, i)) |
|
345 | lines.append('%*d' % (mw, i)) | |
344 | else: |
|
346 | else: | |
345 | lines.append('') |
|
347 | lines.append('') | |
346 | ls = '\n'.join(lines) |
|
348 | ls = '\n'.join(lines) | |
347 |
|
349 | |||
348 | # in case you wonder about the seemingly redundant <div> here: since the |
|
350 | # in case you wonder about the seemingly redundant <div> here: since the | |
349 | # content in the other cell also is wrapped in a div, some browsers in |
|
351 | # content in the other cell also is wrapped in a div, some browsers in | |
350 | # some configurations seem to mess up the formatting... |
|
352 | # some configurations seem to mess up the formatting... | |
351 | if nocls: |
|
353 | if nocls: | |
352 | yield 0, ('<table class="%stable">' % self.cssclass + |
|
354 | yield 0, ('<table class="%stable">' % self.cssclass + | |
353 | '<tr><td><div class="linenodiv" ' |
|
355 | '<tr><td><div class="linenodiv" ' | |
354 | 'style="background-color: #f0f0f0; padding-right: 10px">' |
|
356 | 'style="background-color: #f0f0f0; padding-right: 10px">' | |
355 | '<pre style="line-height: 125%">' + |
|
357 | '<pre style="line-height: 125%">' + | |
356 | ls + '</pre></div></td><td id="hlcode" class="code">') |
|
358 | ls + '</pre></div></td><td id="hlcode" class="code">') | |
357 | else: |
|
359 | else: | |
358 | yield 0, ('<table class="%stable">' % self.cssclass + |
|
360 | yield 0, ('<table class="%stable">' % self.cssclass + | |
359 | '<tr><td class="linenos"><div class="linenodiv"><pre>' + |
|
361 | '<tr><td class="linenos"><div class="linenodiv"><pre>' + | |
360 | ls + '</pre></div></td><td id="hlcode" class="code">') |
|
362 | ls + '</pre></div></td><td id="hlcode" class="code">') | |
361 | yield 0, dummyoutfile.getvalue() |
|
363 | yield 0, dummyoutfile.getvalue() | |
362 | yield 0, '</td></tr></table>' |
|
364 | yield 0, '</td></tr></table>' | |
363 |
|
365 | |||
364 |
|
366 | |||
365 | class SearchContentCodeHtmlFormatter(CodeHtmlFormatter): |
|
367 | class SearchContentCodeHtmlFormatter(CodeHtmlFormatter): | |
366 | def __init__(self, **kw): |
|
368 | def __init__(self, **kw): | |
367 | # only show these line numbers if set |
|
369 | # only show these line numbers if set | |
368 | self.only_lines = kw.pop('only_line_numbers', []) |
|
370 | self.only_lines = kw.pop('only_line_numbers', []) | |
369 | self.query_terms = kw.pop('query_terms', []) |
|
371 | self.query_terms = kw.pop('query_terms', []) | |
370 | self.max_lines = kw.pop('max_lines', 5) |
|
372 | self.max_lines = kw.pop('max_lines', 5) | |
371 | self.line_context = kw.pop('line_context', 3) |
|
373 | self.line_context = kw.pop('line_context', 3) | |
372 | self.url = kw.pop('url', None) |
|
374 | self.url = kw.pop('url', None) | |
373 |
|
375 | |||
374 | super(CodeHtmlFormatter, self).__init__(**kw) |
|
376 | super(CodeHtmlFormatter, self).__init__(**kw) | |
375 |
|
377 | |||
376 | def _wrap_code(self, source): |
|
378 | def _wrap_code(self, source): | |
377 | for cnt, it in enumerate(source): |
|
379 | for cnt, it in enumerate(source): | |
378 | i, t = it |
|
380 | i, t = it | |
379 | t = '<pre>%s</pre>' % t |
|
381 | t = '<pre>%s</pre>' % t | |
380 | yield i, t |
|
382 | yield i, t | |
381 |
|
383 | |||
382 | def _wrap_tablelinenos(self, inner): |
|
384 | def _wrap_tablelinenos(self, inner): | |
383 | yield 0, '<table class="code-highlight %stable">' % self.cssclass |
|
385 | yield 0, '<table class="code-highlight %stable">' % self.cssclass | |
384 |
|
386 | |||
385 | last_shown_line_number = 0 |
|
387 | last_shown_line_number = 0 | |
386 | current_line_number = 1 |
|
388 | current_line_number = 1 | |
387 |
|
389 | |||
388 | for t, line in inner: |
|
390 | for t, line in inner: | |
389 | if not t: |
|
391 | if not t: | |
390 | yield t, line |
|
392 | yield t, line | |
391 | continue |
|
393 | continue | |
392 |
|
394 | |||
393 | if current_line_number in self.only_lines: |
|
395 | if current_line_number in self.only_lines: | |
394 | if last_shown_line_number + 1 != current_line_number: |
|
396 | if last_shown_line_number + 1 != current_line_number: | |
395 | yield 0, '<tr>' |
|
397 | yield 0, '<tr>' | |
396 | yield 0, '<td class="line">...</td>' |
|
398 | yield 0, '<td class="line">...</td>' | |
397 | yield 0, '<td id="hlcode" class="code"></td>' |
|
399 | yield 0, '<td id="hlcode" class="code"></td>' | |
398 | yield 0, '</tr>' |
|
400 | yield 0, '</tr>' | |
399 |
|
401 | |||
400 | yield 0, '<tr>' |
|
402 | yield 0, '<tr>' | |
401 | if self.url: |
|
403 | if self.url: | |
402 | yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % ( |
|
404 | yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % ( | |
403 | self.url, current_line_number, current_line_number) |
|
405 | self.url, current_line_number, current_line_number) | |
404 | else: |
|
406 | else: | |
405 | yield 0, '<td class="line"><a href="">%i</a></td>' % ( |
|
407 | yield 0, '<td class="line"><a href="">%i</a></td>' % ( | |
406 | current_line_number) |
|
408 | current_line_number) | |
407 | yield 0, '<td id="hlcode" class="code">' + line + '</td>' |
|
409 | yield 0, '<td id="hlcode" class="code">' + line + '</td>' | |
408 | yield 0, '</tr>' |
|
410 | yield 0, '</tr>' | |
409 |
|
411 | |||
410 | last_shown_line_number = current_line_number |
|
412 | last_shown_line_number = current_line_number | |
411 |
|
413 | |||
412 | current_line_number += 1 |
|
414 | current_line_number += 1 | |
413 |
|
415 | |||
414 |
|
416 | |||
415 | yield 0, '</table>' |
|
417 | yield 0, '</table>' | |
416 |
|
418 | |||
417 |
|
419 | |||
418 | def extract_phrases(text_query): |
|
420 | def extract_phrases(text_query): | |
419 | """ |
|
421 | """ | |
420 | Extracts phrases from search term string making sure phrases |
|
422 | Extracts phrases from search term string making sure phrases | |
421 | contained in double quotes are kept together - and discarding empty values |
|
423 | contained in double quotes are kept together - and discarding empty values | |
422 | or fully whitespace values eg. |
|
424 | or fully whitespace values eg. | |
423 |
|
425 | |||
424 | 'some text "a phrase" more' => ['some', 'text', 'a phrase', 'more'] |
|
426 | 'some text "a phrase" more' => ['some', 'text', 'a phrase', 'more'] | |
425 |
|
427 | |||
426 | """ |
|
428 | """ | |
427 |
|
429 | |||
428 | in_phrase = False |
|
430 | in_phrase = False | |
429 | buf = '' |
|
431 | buf = '' | |
430 | phrases = [] |
|
432 | phrases = [] | |
431 | for char in text_query: |
|
433 | for char in text_query: | |
432 | if in_phrase: |
|
434 | if in_phrase: | |
433 | if char == '"': # end phrase |
|
435 | if char == '"': # end phrase | |
434 | phrases.append(buf) |
|
436 | phrases.append(buf) | |
435 | buf = '' |
|
437 | buf = '' | |
436 | in_phrase = False |
|
438 | in_phrase = False | |
437 | continue |
|
439 | continue | |
438 | else: |
|
440 | else: | |
439 | buf += char |
|
441 | buf += char | |
440 | continue |
|
442 | continue | |
441 | else: |
|
443 | else: | |
442 | if char == '"': # start phrase |
|
444 | if char == '"': # start phrase | |
443 | in_phrase = True |
|
445 | in_phrase = True | |
444 | phrases.append(buf) |
|
446 | phrases.append(buf) | |
445 | buf = '' |
|
447 | buf = '' | |
446 | continue |
|
448 | continue | |
447 | elif char == ' ': |
|
449 | elif char == ' ': | |
448 | phrases.append(buf) |
|
450 | phrases.append(buf) | |
449 | buf = '' |
|
451 | buf = '' | |
450 | continue |
|
452 | continue | |
451 | else: |
|
453 | else: | |
452 | buf += char |
|
454 | buf += char | |
453 |
|
455 | |||
454 | phrases.append(buf) |
|
456 | phrases.append(buf) | |
455 | phrases = [phrase.strip() for phrase in phrases if phrase.strip()] |
|
457 | phrases = [phrase.strip() for phrase in phrases if phrase.strip()] | |
456 | return phrases |
|
458 | return phrases | |
457 |
|
459 | |||
458 |
|
460 | |||
459 | def get_matching_offsets(text, phrases): |
|
461 | def get_matching_offsets(text, phrases): | |
460 | """ |
|
462 | """ | |
461 | Returns a list of string offsets in `text` that the list of `terms` match |
|
463 | Returns a list of string offsets in `text` that the list of `terms` match | |
462 |
|
464 | |||
463 | >>> get_matching_offsets('some text here', ['some', 'here']) |
|
465 | >>> get_matching_offsets('some text here', ['some', 'here']) | |
464 | [(0, 4), (10, 14)] |
|
466 | [(0, 4), (10, 14)] | |
465 |
|
467 | |||
466 | """ |
|
468 | """ | |
467 | offsets = [] |
|
469 | offsets = [] | |
468 | for phrase in phrases: |
|
470 | for phrase in phrases: | |
469 | for match in re.finditer(phrase, text): |
|
471 | for match in re.finditer(phrase, text): | |
470 | offsets.append((match.start(), match.end())) |
|
472 | offsets.append((match.start(), match.end())) | |
471 |
|
473 | |||
472 | return offsets |
|
474 | return offsets | |
473 |
|
475 | |||
474 |
|
476 | |||
475 | def normalize_text_for_matching(x): |
|
477 | def normalize_text_for_matching(x): | |
476 | """ |
|
478 | """ | |
477 | Replaces all non alnum characters to spaces and lower cases the string, |
|
479 | Replaces all non alnum characters to spaces and lower cases the string, | |
478 | useful for comparing two text strings without punctuation |
|
480 | useful for comparing two text strings without punctuation | |
479 | """ |
|
481 | """ | |
480 | return re.sub(r'[^\w]', ' ', x.lower()) |
|
482 | return re.sub(r'[^\w]', ' ', x.lower()) | |
481 |
|
483 | |||
482 |
|
484 | |||
483 | def get_matching_line_offsets(lines, terms): |
|
485 | def get_matching_line_offsets(lines, terms): | |
484 | """ Return a set of `lines` indices (starting from 1) matching a |
|
486 | """ Return a set of `lines` indices (starting from 1) matching a | |
485 | text search query, along with `context` lines above/below matching lines |
|
487 | text search query, along with `context` lines above/below matching lines | |
486 |
|
488 | |||
487 | :param lines: list of strings representing lines |
|
489 | :param lines: list of strings representing lines | |
488 | :param terms: search term string to match in lines eg. 'some text' |
|
490 | :param terms: search term string to match in lines eg. 'some text' | |
489 | :param context: number of lines above/below a matching line to add to result |
|
491 | :param context: number of lines above/below a matching line to add to result | |
490 | :param max_lines: cut off for lines of interest |
|
492 | :param max_lines: cut off for lines of interest | |
491 | eg. |
|
493 | eg. | |
492 |
|
494 | |||
493 | text = ''' |
|
495 | text = ''' | |
494 | words words words |
|
496 | words words words | |
495 | words words words |
|
497 | words words words | |
496 | some text some |
|
498 | some text some | |
497 | words words words |
|
499 | words words words | |
498 | words words words |
|
500 | words words words | |
499 | text here what |
|
501 | text here what | |
500 | ''' |
|
502 | ''' | |
501 | get_matching_line_offsets(text, 'text', context=1) |
|
503 | get_matching_line_offsets(text, 'text', context=1) | |
502 | {3: [(5, 9)], 6: [(0, 4)]] |
|
504 | {3: [(5, 9)], 6: [(0, 4)]] | |
503 |
|
505 | |||
504 | """ |
|
506 | """ | |
505 | matching_lines = {} |
|
507 | matching_lines = {} | |
506 | phrases = [normalize_text_for_matching(phrase) |
|
508 | phrases = [normalize_text_for_matching(phrase) | |
507 | for phrase in extract_phrases(terms)] |
|
509 | for phrase in extract_phrases(terms)] | |
508 |
|
510 | |||
509 | for line_index, line in enumerate(lines, start=1): |
|
511 | for line_index, line in enumerate(lines, start=1): | |
510 | match_offsets = get_matching_offsets( |
|
512 | match_offsets = get_matching_offsets( | |
511 | normalize_text_for_matching(line), phrases) |
|
513 | normalize_text_for_matching(line), phrases) | |
512 | if match_offsets: |
|
514 | if match_offsets: | |
513 | matching_lines[line_index] = match_offsets |
|
515 | matching_lines[line_index] = match_offsets | |
514 |
|
516 | |||
515 | return matching_lines |
|
517 | return matching_lines | |
516 |
|
518 | |||
517 |
|
519 | |||
518 | def hsv_to_rgb(h, s, v): |
|
520 | def hsv_to_rgb(h, s, v): | |
519 | """ Convert hsv color values to rgb """ |
|
521 | """ Convert hsv color values to rgb """ | |
520 |
|
522 | |||
521 | if s == 0.0: |
|
523 | if s == 0.0: | |
522 | return v, v, v |
|
524 | return v, v, v | |
523 | i = int(h * 6.0) # XXX assume int() truncates! |
|
525 | i = int(h * 6.0) # XXX assume int() truncates! | |
524 | f = (h * 6.0) - i |
|
526 | f = (h * 6.0) - i | |
525 | p = v * (1.0 - s) |
|
527 | p = v * (1.0 - s) | |
526 | q = v * (1.0 - s * f) |
|
528 | q = v * (1.0 - s * f) | |
527 | t = v * (1.0 - s * (1.0 - f)) |
|
529 | t = v * (1.0 - s * (1.0 - f)) | |
528 | i = i % 6 |
|
530 | i = i % 6 | |
529 | if i == 0: |
|
531 | if i == 0: | |
530 | return v, t, p |
|
532 | return v, t, p | |
531 | if i == 1: |
|
533 | if i == 1: | |
532 | return q, v, p |
|
534 | return q, v, p | |
533 | if i == 2: |
|
535 | if i == 2: | |
534 | return p, v, t |
|
536 | return p, v, t | |
535 | if i == 3: |
|
537 | if i == 3: | |
536 | return p, q, v |
|
538 | return p, q, v | |
537 | if i == 4: |
|
539 | if i == 4: | |
538 | return t, p, v |
|
540 | return t, p, v | |
539 | if i == 5: |
|
541 | if i == 5: | |
540 | return v, p, q |
|
542 | return v, p, q | |
541 |
|
543 | |||
542 |
|
544 | |||
543 | def unique_color_generator(n=10000, saturation=0.10, lightness=0.95): |
|
545 | def unique_color_generator(n=10000, saturation=0.10, lightness=0.95): | |
544 | """ |
|
546 | """ | |
545 | Generator for getting n of evenly distributed colors using |
|
547 | Generator for getting n of evenly distributed colors using | |
546 | hsv color and golden ratio. It always return same order of colors |
|
548 | hsv color and golden ratio. It always return same order of colors | |
547 |
|
549 | |||
548 | :param n: number of colors to generate |
|
550 | :param n: number of colors to generate | |
549 | :param saturation: saturation of returned colors |
|
551 | :param saturation: saturation of returned colors | |
550 | :param lightness: lightness of returned colors |
|
552 | :param lightness: lightness of returned colors | |
551 | :returns: RGB tuple |
|
553 | :returns: RGB tuple | |
552 | """ |
|
554 | """ | |
553 |
|
555 | |||
554 | golden_ratio = 0.618033988749895 |
|
556 | golden_ratio = 0.618033988749895 | |
555 | h = 0.22717784590367374 |
|
557 | h = 0.22717784590367374 | |
556 |
|
558 | |||
557 | for _ in xrange(n): |
|
559 | for _ in xrange(n): | |
558 | h += golden_ratio |
|
560 | h += golden_ratio | |
559 | h %= 1 |
|
561 | h %= 1 | |
560 | HSV_tuple = [h, saturation, lightness] |
|
562 | HSV_tuple = [h, saturation, lightness] | |
561 | RGB_tuple = hsv_to_rgb(*HSV_tuple) |
|
563 | RGB_tuple = hsv_to_rgb(*HSV_tuple) | |
562 | yield map(lambda x: str(int(x * 256)), RGB_tuple) |
|
564 | yield map(lambda x: str(int(x * 256)), RGB_tuple) | |
563 |
|
565 | |||
564 |
|
566 | |||
565 | def color_hasher(n=10000, saturation=0.10, lightness=0.95): |
|
567 | def color_hasher(n=10000, saturation=0.10, lightness=0.95): | |
566 | """ |
|
568 | """ | |
567 | Returns a function which when called with an argument returns a unique |
|
569 | Returns a function which when called with an argument returns a unique | |
568 | color for that argument, eg. |
|
570 | color for that argument, eg. | |
569 |
|
571 | |||
570 | :param n: number of colors to generate |
|
572 | :param n: number of colors to generate | |
571 | :param saturation: saturation of returned colors |
|
573 | :param saturation: saturation of returned colors | |
572 | :param lightness: lightness of returned colors |
|
574 | :param lightness: lightness of returned colors | |
573 | :returns: css RGB string |
|
575 | :returns: css RGB string | |
574 |
|
576 | |||
575 | >>> color_hash = color_hasher() |
|
577 | >>> color_hash = color_hasher() | |
576 | >>> color_hash('hello') |
|
578 | >>> color_hash('hello') | |
577 | 'rgb(34, 12, 59)' |
|
579 | 'rgb(34, 12, 59)' | |
578 | >>> color_hash('hello') |
|
580 | >>> color_hash('hello') | |
579 | 'rgb(34, 12, 59)' |
|
581 | 'rgb(34, 12, 59)' | |
580 | >>> color_hash('other') |
|
582 | >>> color_hash('other') | |
581 | 'rgb(90, 224, 159)' |
|
583 | 'rgb(90, 224, 159)' | |
582 | """ |
|
584 | """ | |
583 |
|
585 | |||
584 | color_dict = {} |
|
586 | color_dict = {} | |
585 | cgenerator = unique_color_generator( |
|
587 | cgenerator = unique_color_generator( | |
586 | saturation=saturation, lightness=lightness) |
|
588 | saturation=saturation, lightness=lightness) | |
587 |
|
589 | |||
588 | def get_color_string(thing): |
|
590 | def get_color_string(thing): | |
589 | if thing in color_dict: |
|
591 | if thing in color_dict: | |
590 | col = color_dict[thing] |
|
592 | col = color_dict[thing] | |
591 | else: |
|
593 | else: | |
592 | col = color_dict[thing] = cgenerator.next() |
|
594 | col = color_dict[thing] = cgenerator.next() | |
593 | return "rgb(%s)" % (', '.join(col)) |
|
595 | return "rgb(%s)" % (', '.join(col)) | |
594 |
|
596 | |||
595 | return get_color_string |
|
597 | return get_color_string | |
596 |
|
598 | |||
597 |
|
599 | |||
598 | def get_lexer_safe(mimetype=None, filepath=None): |
|
600 | def get_lexer_safe(mimetype=None, filepath=None): | |
599 | """ |
|
601 | """ | |
600 | Tries to return a relevant pygments lexer using mimetype/filepath name, |
|
602 | Tries to return a relevant pygments lexer using mimetype/filepath name, | |
601 | defaulting to plain text if none could be found |
|
603 | defaulting to plain text if none could be found | |
602 | """ |
|
604 | """ | |
603 | lexer = None |
|
605 | lexer = None | |
604 | try: |
|
606 | try: | |
605 | if mimetype: |
|
607 | if mimetype: | |
606 | lexer = get_lexer_for_mimetype(mimetype) |
|
608 | lexer = get_lexer_for_mimetype(mimetype) | |
607 | if not lexer: |
|
609 | if not lexer: | |
608 | lexer = get_lexer_for_filename(filepath) |
|
610 | lexer = get_lexer_for_filename(filepath) | |
609 | except pygments.util.ClassNotFound: |
|
611 | except pygments.util.ClassNotFound: | |
610 | pass |
|
612 | pass | |
611 |
|
613 | |||
612 | if not lexer: |
|
614 | if not lexer: | |
613 | lexer = get_lexer_by_name('text') |
|
615 | lexer = get_lexer_by_name('text') | |
614 |
|
616 | |||
615 | return lexer |
|
617 | return lexer | |
616 |
|
618 | |||
617 |
|
619 | |||
618 | def get_lexer_for_filenode(filenode): |
|
620 | def get_lexer_for_filenode(filenode): | |
619 | lexer = get_custom_lexer(filenode.extension) or filenode.lexer |
|
621 | lexer = get_custom_lexer(filenode.extension) or filenode.lexer | |
620 | return lexer |
|
622 | return lexer | |
621 |
|
623 | |||
622 |
|
624 | |||
623 | def pygmentize(filenode, **kwargs): |
|
625 | def pygmentize(filenode, **kwargs): | |
624 | """ |
|
626 | """ | |
625 | pygmentize function using pygments |
|
627 | pygmentize function using pygments | |
626 |
|
628 | |||
627 | :param filenode: |
|
629 | :param filenode: | |
628 | """ |
|
630 | """ | |
629 | lexer = get_lexer_for_filenode(filenode) |
|
631 | lexer = get_lexer_for_filenode(filenode) | |
630 | return literal(code_highlight(filenode.content, lexer, |
|
632 | return literal(code_highlight(filenode.content, lexer, | |
631 | CodeHtmlFormatter(**kwargs))) |
|
633 | CodeHtmlFormatter(**kwargs))) | |
632 |
|
634 | |||
633 |
|
635 | |||
634 | def is_following_repo(repo_name, user_id): |
|
636 | def is_following_repo(repo_name, user_id): | |
635 | from rhodecode.model.scm import ScmModel |
|
637 | from rhodecode.model.scm import ScmModel | |
636 | return ScmModel().is_following_repo(repo_name, user_id) |
|
638 | return ScmModel().is_following_repo(repo_name, user_id) | |
637 |
|
639 | |||
638 |
|
640 | |||
639 | class _Message(object): |
|
641 | class _Message(object): | |
640 | """A message returned by ``Flash.pop_messages()``. |
|
642 | """A message returned by ``Flash.pop_messages()``. | |
641 |
|
643 | |||
642 | Converting the message to a string returns the message text. Instances |
|
644 | Converting the message to a string returns the message text. Instances | |
643 | also have the following attributes: |
|
645 | also have the following attributes: | |
644 |
|
646 | |||
645 | * ``message``: the message text. |
|
647 | * ``message``: the message text. | |
646 | * ``category``: the category specified when the message was created. |
|
648 | * ``category``: the category specified when the message was created. | |
647 | """ |
|
649 | """ | |
648 |
|
650 | |||
649 | def __init__(self, category, message): |
|
651 | def __init__(self, category, message): | |
650 | self.category = category |
|
652 | self.category = category | |
651 | self.message = message |
|
653 | self.message = message | |
652 |
|
654 | |||
653 | def __str__(self): |
|
655 | def __str__(self): | |
654 | return self.message |
|
656 | return self.message | |
655 |
|
657 | |||
656 | __unicode__ = __str__ |
|
658 | __unicode__ = __str__ | |
657 |
|
659 | |||
658 | def __html__(self): |
|
660 | def __html__(self): | |
659 | return escape(safe_unicode(self.message)) |
|
661 | return escape(safe_unicode(self.message)) | |
660 |
|
662 | |||
661 |
|
663 | |||
662 | class Flash(_Flash): |
|
664 | class Flash(_Flash): | |
663 |
|
665 | |||
664 | def pop_messages(self, request=None): |
|
666 | def pop_messages(self, request=None): | |
665 | """Return all accumulated messages and delete them from the session. |
|
667 | """Return all accumulated messages and delete them from the session. | |
666 |
|
668 | |||
667 | The return value is a list of ``Message`` objects. |
|
669 | The return value is a list of ``Message`` objects. | |
668 | """ |
|
670 | """ | |
669 | messages = [] |
|
671 | messages = [] | |
670 |
|
672 | |||
671 | if request: |
|
673 | if request: | |
672 | session = request.session |
|
674 | session = request.session | |
673 | else: |
|
675 | else: | |
674 | from pylons import session |
|
676 | from pylons import session | |
675 |
|
677 | |||
676 | # Pop the 'old' pylons flash messages. They are tuples of the form |
|
678 | # Pop the 'old' pylons flash messages. They are tuples of the form | |
677 | # (category, message) |
|
679 | # (category, message) | |
678 | for cat, msg in session.pop(self.session_key, []): |
|
680 | for cat, msg in session.pop(self.session_key, []): | |
679 | messages.append(_Message(cat, msg)) |
|
681 | messages.append(_Message(cat, msg)) | |
680 |
|
682 | |||
681 | # Pop the 'new' pyramid flash messages for each category as list |
|
683 | # Pop the 'new' pyramid flash messages for each category as list | |
682 | # of strings. |
|
684 | # of strings. | |
683 | for cat in self.categories: |
|
685 | for cat in self.categories: | |
684 | for msg in session.pop_flash(queue=cat): |
|
686 | for msg in session.pop_flash(queue=cat): | |
685 | messages.append(_Message(cat, msg)) |
|
687 | messages.append(_Message(cat, msg)) | |
686 | # Map messages from the default queue to the 'notice' category. |
|
688 | # Map messages from the default queue to the 'notice' category. | |
687 | for msg in session.pop_flash(): |
|
689 | for msg in session.pop_flash(): | |
688 | messages.append(_Message('notice', msg)) |
|
690 | messages.append(_Message('notice', msg)) | |
689 |
|
691 | |||
690 | session.save() |
|
692 | session.save() | |
691 | return messages |
|
693 | return messages | |
692 |
|
694 | |||
693 | def json_alerts(self, request=None): |
|
695 | def json_alerts(self, request=None): | |
694 | payloads = [] |
|
696 | payloads = [] | |
695 | messages = flash.pop_messages(request=request) |
|
697 | messages = flash.pop_messages(request=request) | |
696 | if messages: |
|
698 | if messages: | |
697 | for message in messages: |
|
699 | for message in messages: | |
698 | subdata = {} |
|
700 | subdata = {} | |
699 | if hasattr(message.message, 'rsplit'): |
|
701 | if hasattr(message.message, 'rsplit'): | |
700 | flash_data = message.message.rsplit('|DELIM|', 1) |
|
702 | flash_data = message.message.rsplit('|DELIM|', 1) | |
701 | org_message = flash_data[0] |
|
703 | org_message = flash_data[0] | |
702 | if len(flash_data) > 1: |
|
704 | if len(flash_data) > 1: | |
703 | subdata = json.loads(flash_data[1]) |
|
705 | subdata = json.loads(flash_data[1]) | |
704 | else: |
|
706 | else: | |
705 | org_message = message.message |
|
707 | org_message = message.message | |
706 | payloads.append({ |
|
708 | payloads.append({ | |
707 | 'message': { |
|
709 | 'message': { | |
708 | 'message': u'{}'.format(org_message), |
|
710 | 'message': u'{}'.format(org_message), | |
709 | 'level': message.category, |
|
711 | 'level': message.category, | |
710 | 'force': True, |
|
712 | 'force': True, | |
711 | 'subdata': subdata |
|
713 | 'subdata': subdata | |
712 | } |
|
714 | } | |
713 | }) |
|
715 | }) | |
714 | return json.dumps(payloads) |
|
716 | return json.dumps(payloads) | |
715 |
|
717 | |||
716 | flash = Flash() |
|
718 | flash = Flash() | |
717 |
|
719 | |||
718 | #============================================================================== |
|
720 | #============================================================================== | |
719 | # SCM FILTERS available via h. |
|
721 | # SCM FILTERS available via h. | |
720 | #============================================================================== |
|
722 | #============================================================================== | |
721 | from rhodecode.lib.vcs.utils import author_name, author_email |
|
723 | from rhodecode.lib.vcs.utils import author_name, author_email | |
722 | from rhodecode.lib.utils2 import credentials_filter, age as _age |
|
724 | from rhodecode.lib.utils2 import credentials_filter, age as _age | |
723 | from rhodecode.model.db import User, ChangesetStatus |
|
725 | from rhodecode.model.db import User, ChangesetStatus | |
724 |
|
726 | |||
725 | age = _age |
|
727 | age = _age | |
726 | capitalize = lambda x: x.capitalize() |
|
728 | capitalize = lambda x: x.capitalize() | |
727 | email = author_email |
|
729 | email = author_email | |
728 | short_id = lambda x: x[:12] |
|
730 | short_id = lambda x: x[:12] | |
729 | hide_credentials = lambda x: ''.join(credentials_filter(x)) |
|
731 | hide_credentials = lambda x: ''.join(credentials_filter(x)) | |
730 |
|
732 | |||
731 |
|
733 | |||
732 | def age_component(datetime_iso, value=None, time_is_local=False): |
|
734 | def age_component(datetime_iso, value=None, time_is_local=False): | |
733 | title = value or format_date(datetime_iso) |
|
735 | title = value or format_date(datetime_iso) | |
734 | tzinfo = '+00:00' |
|
736 | tzinfo = '+00:00' | |
735 |
|
737 | |||
736 | # detect if we have a timezone info, otherwise, add it |
|
738 | # detect if we have a timezone info, otherwise, add it | |
737 | if isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo: |
|
739 | if isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo: | |
738 | if time_is_local: |
|
740 | if time_is_local: | |
739 | tzinfo = time.strftime("+%H:%M", |
|
741 | tzinfo = time.strftime("+%H:%M", | |
740 | time.gmtime( |
|
742 | time.gmtime( | |
741 | (datetime.now() - datetime.utcnow()).seconds + 1 |
|
743 | (datetime.now() - datetime.utcnow()).seconds + 1 | |
742 | ) |
|
744 | ) | |
743 | ) |
|
745 | ) | |
744 |
|
746 | |||
745 | return literal( |
|
747 | return literal( | |
746 | '<time class="timeago tooltip" ' |
|
748 | '<time class="timeago tooltip" ' | |
747 | 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format( |
|
749 | 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format( | |
748 | datetime_iso, title, tzinfo)) |
|
750 | datetime_iso, title, tzinfo)) | |
749 |
|
751 | |||
750 |
|
752 | |||
751 | def _shorten_commit_id(commit_id): |
|
753 | def _shorten_commit_id(commit_id): | |
752 | from rhodecode import CONFIG |
|
754 | from rhodecode import CONFIG | |
753 | def_len = safe_int(CONFIG.get('rhodecode_show_sha_length', 12)) |
|
755 | def_len = safe_int(CONFIG.get('rhodecode_show_sha_length', 12)) | |
754 | return commit_id[:def_len] |
|
756 | return commit_id[:def_len] | |
755 |
|
757 | |||
756 |
|
758 | |||
757 | def show_id(commit): |
|
759 | def show_id(commit): | |
758 | """ |
|
760 | """ | |
759 | Configurable function that shows ID |
|
761 | Configurable function that shows ID | |
760 | by default it's r123:fffeeefffeee |
|
762 | by default it's r123:fffeeefffeee | |
761 |
|
763 | |||
762 | :param commit: commit instance |
|
764 | :param commit: commit instance | |
763 | """ |
|
765 | """ | |
764 | from rhodecode import CONFIG |
|
766 | from rhodecode import CONFIG | |
765 | show_idx = str2bool(CONFIG.get('rhodecode_show_revision_number', True)) |
|
767 | show_idx = str2bool(CONFIG.get('rhodecode_show_revision_number', True)) | |
766 |
|
768 | |||
767 | raw_id = _shorten_commit_id(commit.raw_id) |
|
769 | raw_id = _shorten_commit_id(commit.raw_id) | |
768 | if show_idx: |
|
770 | if show_idx: | |
769 | return 'r%s:%s' % (commit.idx, raw_id) |
|
771 | return 'r%s:%s' % (commit.idx, raw_id) | |
770 | else: |
|
772 | else: | |
771 | return '%s' % (raw_id, ) |
|
773 | return '%s' % (raw_id, ) | |
772 |
|
774 | |||
773 |
|
775 | |||
774 | def format_date(date): |
|
776 | def format_date(date): | |
775 | """ |
|
777 | """ | |
776 | use a standardized formatting for dates used in RhodeCode |
|
778 | use a standardized formatting for dates used in RhodeCode | |
777 |
|
779 | |||
778 | :param date: date/datetime object |
|
780 | :param date: date/datetime object | |
779 | :return: formatted date |
|
781 | :return: formatted date | |
780 | """ |
|
782 | """ | |
781 |
|
783 | |||
782 | if date: |
|
784 | if date: | |
783 | _fmt = "%a, %d %b %Y %H:%M:%S" |
|
785 | _fmt = "%a, %d %b %Y %H:%M:%S" | |
784 | return safe_unicode(date.strftime(_fmt)) |
|
786 | return safe_unicode(date.strftime(_fmt)) | |
785 |
|
787 | |||
786 | return u"" |
|
788 | return u"" | |
787 |
|
789 | |||
788 |
|
790 | |||
789 | class _RepoChecker(object): |
|
791 | class _RepoChecker(object): | |
790 |
|
792 | |||
791 | def __init__(self, backend_alias): |
|
793 | def __init__(self, backend_alias): | |
792 | self._backend_alias = backend_alias |
|
794 | self._backend_alias = backend_alias | |
793 |
|
795 | |||
794 | def __call__(self, repository): |
|
796 | def __call__(self, repository): | |
795 | if hasattr(repository, 'alias'): |
|
797 | if hasattr(repository, 'alias'): | |
796 | _type = repository.alias |
|
798 | _type = repository.alias | |
797 | elif hasattr(repository, 'repo_type'): |
|
799 | elif hasattr(repository, 'repo_type'): | |
798 | _type = repository.repo_type |
|
800 | _type = repository.repo_type | |
799 | else: |
|
801 | else: | |
800 | _type = repository |
|
802 | _type = repository | |
801 | return _type == self._backend_alias |
|
803 | return _type == self._backend_alias | |
802 |
|
804 | |||
803 | is_git = _RepoChecker('git') |
|
805 | is_git = _RepoChecker('git') | |
804 | is_hg = _RepoChecker('hg') |
|
806 | is_hg = _RepoChecker('hg') | |
805 | is_svn = _RepoChecker('svn') |
|
807 | is_svn = _RepoChecker('svn') | |
806 |
|
808 | |||
807 |
|
809 | |||
808 | def get_repo_type_by_name(repo_name): |
|
810 | def get_repo_type_by_name(repo_name): | |
809 | repo = Repository.get_by_repo_name(repo_name) |
|
811 | repo = Repository.get_by_repo_name(repo_name) | |
810 | return repo.repo_type |
|
812 | return repo.repo_type | |
811 |
|
813 | |||
812 |
|
814 | |||
813 | def is_svn_without_proxy(repository): |
|
815 | def is_svn_without_proxy(repository): | |
814 | if is_svn(repository): |
|
816 | if is_svn(repository): | |
815 | from rhodecode.model.settings import VcsSettingsModel |
|
817 | from rhodecode.model.settings import VcsSettingsModel | |
816 | conf = VcsSettingsModel().get_ui_settings_as_config_obj() |
|
818 | conf = VcsSettingsModel().get_ui_settings_as_config_obj() | |
817 | return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled')) |
|
819 | return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled')) | |
818 | return False |
|
820 | return False | |
819 |
|
821 | |||
820 |
|
822 | |||
821 | def discover_user(author): |
|
823 | def discover_user(author): | |
822 | """ |
|
824 | """ | |
823 | Tries to discover RhodeCode User based on the autho string. Author string |
|
825 | Tries to discover RhodeCode User based on the autho string. Author string | |
824 | is typically `FirstName LastName <email@address.com>` |
|
826 | is typically `FirstName LastName <email@address.com>` | |
825 | """ |
|
827 | """ | |
826 |
|
828 | |||
827 | # if author is already an instance use it for extraction |
|
829 | # if author is already an instance use it for extraction | |
828 | if isinstance(author, User): |
|
830 | if isinstance(author, User): | |
829 | return author |
|
831 | return author | |
830 |
|
832 | |||
831 | # Valid email in the attribute passed, see if they're in the system |
|
833 | # Valid email in the attribute passed, see if they're in the system | |
832 | _email = author_email(author) |
|
834 | _email = author_email(author) | |
833 | if _email != '': |
|
835 | if _email != '': | |
834 | user = User.get_by_email(_email, case_insensitive=True, cache=True) |
|
836 | user = User.get_by_email(_email, case_insensitive=True, cache=True) | |
835 | if user is not None: |
|
837 | if user is not None: | |
836 | return user |
|
838 | return user | |
837 |
|
839 | |||
838 | # Maybe it's a username, we try to extract it and fetch by username ? |
|
840 | # Maybe it's a username, we try to extract it and fetch by username ? | |
839 | _author = author_name(author) |
|
841 | _author = author_name(author) | |
840 | user = User.get_by_username(_author, case_insensitive=True, cache=True) |
|
842 | user = User.get_by_username(_author, case_insensitive=True, cache=True) | |
841 | if user is not None: |
|
843 | if user is not None: | |
842 | return user |
|
844 | return user | |
843 |
|
845 | |||
844 | return None |
|
846 | return None | |
845 |
|
847 | |||
846 |
|
848 | |||
847 | def email_or_none(author): |
|
849 | def email_or_none(author): | |
848 | # extract email from the commit string |
|
850 | # extract email from the commit string | |
849 | _email = author_email(author) |
|
851 | _email = author_email(author) | |
850 |
|
852 | |||
851 | # If we have an email, use it, otherwise |
|
853 | # If we have an email, use it, otherwise | |
852 | # see if it contains a username we can get an email from |
|
854 | # see if it contains a username we can get an email from | |
853 | if _email != '': |
|
855 | if _email != '': | |
854 | return _email |
|
856 | return _email | |
855 | else: |
|
857 | else: | |
856 | user = User.get_by_username( |
|
858 | user = User.get_by_username( | |
857 | author_name(author), case_insensitive=True, cache=True) |
|
859 | author_name(author), case_insensitive=True, cache=True) | |
858 |
|
860 | |||
859 | if user is not None: |
|
861 | if user is not None: | |
860 | return user.email |
|
862 | return user.email | |
861 |
|
863 | |||
862 | # No valid email, not a valid user in the system, none! |
|
864 | # No valid email, not a valid user in the system, none! | |
863 | return None |
|
865 | return None | |
864 |
|
866 | |||
865 |
|
867 | |||
866 | def link_to_user(author, length=0, **kwargs): |
|
868 | def link_to_user(author, length=0, **kwargs): | |
867 | user = discover_user(author) |
|
869 | user = discover_user(author) | |
868 | # user can be None, but if we have it already it means we can re-use it |
|
870 | # user can be None, but if we have it already it means we can re-use it | |
869 | # in the person() function, so we save 1 intensive-query |
|
871 | # in the person() function, so we save 1 intensive-query | |
870 | if user: |
|
872 | if user: | |
871 | author = user |
|
873 | author = user | |
872 |
|
874 | |||
873 | display_person = person(author, 'username_or_name_or_email') |
|
875 | display_person = person(author, 'username_or_name_or_email') | |
874 | if length: |
|
876 | if length: | |
875 | display_person = shorter(display_person, length) |
|
877 | display_person = shorter(display_person, length) | |
876 |
|
878 | |||
877 | if user: |
|
879 | if user: | |
878 | return link_to( |
|
880 | return link_to( | |
879 | escape(display_person), |
|
881 | escape(display_person), | |
880 | route_path('user_profile', username=user.username), |
|
882 | route_path('user_profile', username=user.username), | |
881 | **kwargs) |
|
883 | **kwargs) | |
882 | else: |
|
884 | else: | |
883 | return escape(display_person) |
|
885 | return escape(display_person) | |
884 |
|
886 | |||
885 |
|
887 | |||
886 | def person(author, show_attr="username_and_name"): |
|
888 | def person(author, show_attr="username_and_name"): | |
887 | user = discover_user(author) |
|
889 | user = discover_user(author) | |
888 | if user: |
|
890 | if user: | |
889 | return getattr(user, show_attr) |
|
891 | return getattr(user, show_attr) | |
890 | else: |
|
892 | else: | |
891 | _author = author_name(author) |
|
893 | _author = author_name(author) | |
892 | _email = email(author) |
|
894 | _email = email(author) | |
893 | return _author or _email |
|
895 | return _author or _email | |
894 |
|
896 | |||
895 |
|
897 | |||
896 | def author_string(email): |
|
898 | def author_string(email): | |
897 | if email: |
|
899 | if email: | |
898 | user = User.get_by_email(email, case_insensitive=True, cache=True) |
|
900 | user = User.get_by_email(email, case_insensitive=True, cache=True) | |
899 | if user: |
|
901 | if user: | |
900 | if user.first_name or user.last_name: |
|
902 | if user.first_name or user.last_name: | |
901 | return '%s %s <%s>' % ( |
|
903 | return '%s %s <%s>' % ( | |
902 | user.first_name, user.last_name, email) |
|
904 | user.first_name, user.last_name, email) | |
903 | else: |
|
905 | else: | |
904 | return email |
|
906 | return email | |
905 | else: |
|
907 | else: | |
906 | return email |
|
908 | return email | |
907 | else: |
|
909 | else: | |
908 | return None |
|
910 | return None | |
909 |
|
911 | |||
910 |
|
912 | |||
911 | def person_by_id(id_, show_attr="username_and_name"): |
|
913 | def person_by_id(id_, show_attr="username_and_name"): | |
912 | # attr to return from fetched user |
|
914 | # attr to return from fetched user | |
913 | person_getter = lambda usr: getattr(usr, show_attr) |
|
915 | person_getter = lambda usr: getattr(usr, show_attr) | |
914 |
|
916 | |||
915 | #maybe it's an ID ? |
|
917 | #maybe it's an ID ? | |
916 | if str(id_).isdigit() or isinstance(id_, int): |
|
918 | if str(id_).isdigit() or isinstance(id_, int): | |
917 | id_ = int(id_) |
|
919 | id_ = int(id_) | |
918 | user = User.get(id_) |
|
920 | user = User.get(id_) | |
919 | if user is not None: |
|
921 | if user is not None: | |
920 | return person_getter(user) |
|
922 | return person_getter(user) | |
921 | return id_ |
|
923 | return id_ | |
922 |
|
924 | |||
923 |
|
925 | |||
924 | def gravatar_with_user(author, show_disabled=False): |
|
926 | def gravatar_with_user(author, show_disabled=False): | |
925 | from rhodecode.lib.utils import PartialRenderer |
|
927 | from rhodecode.lib.utils import PartialRenderer | |
926 | _render = PartialRenderer('base/base.mako') |
|
928 | _render = PartialRenderer('base/base.mako') | |
927 | return _render('gravatar_with_user', author, show_disabled=show_disabled) |
|
929 | return _render('gravatar_with_user', author, show_disabled=show_disabled) | |
928 |
|
930 | |||
929 |
|
931 | |||
930 | def desc_stylize(value): |
|
932 | def desc_stylize(value): | |
931 | """ |
|
933 | """ | |
932 | converts tags from value into html equivalent |
|
934 | converts tags from value into html equivalent | |
933 |
|
935 | |||
934 | :param value: |
|
936 | :param value: | |
935 | """ |
|
937 | """ | |
936 | if not value: |
|
938 | if not value: | |
937 | return '' |
|
939 | return '' | |
938 |
|
940 | |||
939 | value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]', |
|
941 | value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]', | |
940 | '<div class="metatag" tag="see">see => \\1 </div>', value) |
|
942 | '<div class="metatag" tag="see">see => \\1 </div>', value) | |
941 | value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]', |
|
943 | value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]', | |
942 | '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value) |
|
944 | '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value) | |
943 | value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]', |
|
945 | value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]', | |
944 | '<div class="metatag" tag="\\1">\\1 => <a href="/\\2">\\2</a></div>', value) |
|
946 | '<div class="metatag" tag="\\1">\\1 => <a href="/\\2">\\2</a></div>', value) | |
945 | value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]', |
|
947 | value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]', | |
946 | '<div class="metatag" tag="lang">\\2</div>', value) |
|
948 | '<div class="metatag" tag="lang">\\2</div>', value) | |
947 | value = re.sub(r'\[([a-z]+)\]', |
|
949 | value = re.sub(r'\[([a-z]+)\]', | |
948 | '<div class="metatag" tag="\\1">\\1</div>', value) |
|
950 | '<div class="metatag" tag="\\1">\\1</div>', value) | |
949 |
|
951 | |||
950 | return value |
|
952 | return value | |
951 |
|
953 | |||
952 |
|
954 | |||
953 | def escaped_stylize(value): |
|
955 | def escaped_stylize(value): | |
954 | """ |
|
956 | """ | |
955 | converts tags from value into html equivalent, but escaping its value first |
|
957 | converts tags from value into html equivalent, but escaping its value first | |
956 | """ |
|
958 | """ | |
957 | if not value: |
|
959 | if not value: | |
958 | return '' |
|
960 | return '' | |
959 |
|
961 | |||
960 | # Using default webhelper escape method, but has to force it as a |
|
962 | # Using default webhelper escape method, but has to force it as a | |
961 | # plain unicode instead of a markup tag to be used in regex expressions |
|
963 | # plain unicode instead of a markup tag to be used in regex expressions | |
962 | value = unicode(escape(safe_unicode(value))) |
|
964 | value = unicode(escape(safe_unicode(value))) | |
963 |
|
965 | |||
964 | value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]', |
|
966 | value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]', | |
965 | '<div class="metatag" tag="see">see => \\1 </div>', value) |
|
967 | '<div class="metatag" tag="see">see => \\1 </div>', value) | |
966 | value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]', |
|
968 | value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]', | |
967 | '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value) |
|
969 | '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value) | |
968 | value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]', |
|
970 | value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]', | |
969 | '<div class="metatag" tag="\\1">\\1 => <a href="/\\2">\\2</a></div>', value) |
|
971 | '<div class="metatag" tag="\\1">\\1 => <a href="/\\2">\\2</a></div>', value) | |
970 | value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]', |
|
972 | value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]', | |
971 | '<div class="metatag" tag="lang">\\2</div>', value) |
|
973 | '<div class="metatag" tag="lang">\\2</div>', value) | |
972 | value = re.sub(r'\[([a-z]+)\]', |
|
974 | value = re.sub(r'\[([a-z]+)\]', | |
973 | '<div class="metatag" tag="\\1">\\1</div>', value) |
|
975 | '<div class="metatag" tag="\\1">\\1</div>', value) | |
974 |
|
976 | |||
975 | return value |
|
977 | return value | |
976 |
|
978 | |||
977 |
|
979 | |||
978 | def bool2icon(value): |
|
980 | def bool2icon(value): | |
979 | """ |
|
981 | """ | |
980 | Returns boolean value of a given value, represented as html element with |
|
982 | Returns boolean value of a given value, represented as html element with | |
981 | classes that will represent icons |
|
983 | classes that will represent icons | |
982 |
|
984 | |||
983 | :param value: given value to convert to html node |
|
985 | :param value: given value to convert to html node | |
984 | """ |
|
986 | """ | |
985 |
|
987 | |||
986 | if value: # does bool conversion |
|
988 | if value: # does bool conversion | |
987 | return HTML.tag('i', class_="icon-true") |
|
989 | return HTML.tag('i', class_="icon-true") | |
988 | else: # not true as bool |
|
990 | else: # not true as bool | |
989 | return HTML.tag('i', class_="icon-false") |
|
991 | return HTML.tag('i', class_="icon-false") | |
990 |
|
992 | |||
991 |
|
993 | |||
992 | #============================================================================== |
|
994 | #============================================================================== | |
993 | # PERMS |
|
995 | # PERMS | |
994 | #============================================================================== |
|
996 | #============================================================================== | |
995 | from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \ |
|
997 | from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \ | |
996 | HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \ |
|
998 | HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \ | |
997 | HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \ |
|
999 | HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \ | |
998 | csrf_token_key |
|
1000 | csrf_token_key | |
999 |
|
1001 | |||
1000 |
|
1002 | |||
1001 | #============================================================================== |
|
1003 | #============================================================================== | |
1002 | # GRAVATAR URL |
|
1004 | # GRAVATAR URL | |
1003 | #============================================================================== |
|
1005 | #============================================================================== | |
1004 | class InitialsGravatar(object): |
|
1006 | class InitialsGravatar(object): | |
1005 | def __init__(self, email_address, first_name, last_name, size=30, |
|
1007 | def __init__(self, email_address, first_name, last_name, size=30, | |
1006 | background=None, text_color='#fff'): |
|
1008 | background=None, text_color='#fff'): | |
1007 | self.size = size |
|
1009 | self.size = size | |
1008 | self.first_name = first_name |
|
1010 | self.first_name = first_name | |
1009 | self.last_name = last_name |
|
1011 | self.last_name = last_name | |
1010 | self.email_address = email_address |
|
1012 | self.email_address = email_address | |
1011 | self.background = background or self.str2color(email_address) |
|
1013 | self.background = background or self.str2color(email_address) | |
1012 | self.text_color = text_color |
|
1014 | self.text_color = text_color | |
1013 |
|
1015 | |||
1014 | def get_color_bank(self): |
|
1016 | def get_color_bank(self): | |
1015 | """ |
|
1017 | """ | |
1016 | returns a predefined list of colors that gravatars can use. |
|
1018 | returns a predefined list of colors that gravatars can use. | |
1017 | Those are randomized distinct colors that guarantee readability and |
|
1019 | Those are randomized distinct colors that guarantee readability and | |
1018 | uniqueness. |
|
1020 | uniqueness. | |
1019 |
|
1021 | |||
1020 | generated with: http://phrogz.net/css/distinct-colors.html |
|
1022 | generated with: http://phrogz.net/css/distinct-colors.html | |
1021 | """ |
|
1023 | """ | |
1022 | return [ |
|
1024 | return [ | |
1023 | '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000', |
|
1025 | '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000', | |
1024 | '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320', |
|
1026 | '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320', | |
1025 | '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300', |
|
1027 | '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300', | |
1026 | '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140', |
|
1028 | '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140', | |
1027 | '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c', |
|
1029 | '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c', | |
1028 | '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020', |
|
1030 | '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020', | |
1029 | '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039', |
|
1031 | '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039', | |
1030 | '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f', |
|
1032 | '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f', | |
1031 | '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340', |
|
1033 | '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340', | |
1032 | '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98', |
|
1034 | '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98', | |
1033 | '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c', |
|
1035 | '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c', | |
1034 | '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200', |
|
1036 | '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200', | |
1035 | '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a', |
|
1037 | '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a', | |
1036 | '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959', |
|
1038 | '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959', | |
1037 | '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3', |
|
1039 | '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3', | |
1038 | '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626', |
|
1040 | '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626', | |
1039 | '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000', |
|
1041 | '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000', | |
1040 | '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362', |
|
1042 | '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362', | |
1041 | '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3', |
|
1043 | '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3', | |
1042 | '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a', |
|
1044 | '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a', | |
1043 | '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939', |
|
1045 | '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939', | |
1044 | '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39', |
|
1046 | '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39', | |
1045 | '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953', |
|
1047 | '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953', | |
1046 | '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9', |
|
1048 | '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9', | |
1047 | '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1', |
|
1049 | '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1', | |
1048 | '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900', |
|
1050 | '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900', | |
1049 | '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00', |
|
1051 | '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00', | |
1050 | '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3', |
|
1052 | '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3', | |
1051 | '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59', |
|
1053 | '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59', | |
1052 | '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079', |
|
1054 | '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079', | |
1053 | '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700', |
|
1055 | '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700', | |
1054 | '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d', |
|
1056 | '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d', | |
1055 | '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2', |
|
1057 | '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2', | |
1056 | '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff', |
|
1058 | '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff', | |
1057 | '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20', |
|
1059 | '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20', | |
1058 | '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626', |
|
1060 | '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626', | |
1059 | '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23', |
|
1061 | '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23', | |
1060 | '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff', |
|
1062 | '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff', | |
1061 | '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6', |
|
1063 | '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6', | |
1062 | '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a', |
|
1064 | '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a', | |
1063 | '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c', |
|
1065 | '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c', | |
1064 | '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600', |
|
1066 | '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600', | |
1065 | '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff', |
|
1067 | '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff', | |
1066 | '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539', |
|
1068 | '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539', | |
1067 | '#4f8c46', '#368dd9', '#5c0073' |
|
1069 | '#4f8c46', '#368dd9', '#5c0073' | |
1068 | ] |
|
1070 | ] | |
1069 |
|
1071 | |||
1070 | def rgb_to_hex_color(self, rgb_tuple): |
|
1072 | def rgb_to_hex_color(self, rgb_tuple): | |
1071 | """ |
|
1073 | """ | |
1072 | Converts an rgb_tuple passed to an hex color. |
|
1074 | Converts an rgb_tuple passed to an hex color. | |
1073 |
|
1075 | |||
1074 | :param rgb_tuple: tuple with 3 ints represents rgb color space |
|
1076 | :param rgb_tuple: tuple with 3 ints represents rgb color space | |
1075 | """ |
|
1077 | """ | |
1076 | return '#' + ("".join(map(chr, rgb_tuple)).encode('hex')) |
|
1078 | return '#' + ("".join(map(chr, rgb_tuple)).encode('hex')) | |
1077 |
|
1079 | |||
1078 | def email_to_int_list(self, email_str): |
|
1080 | def email_to_int_list(self, email_str): | |
1079 | """ |
|
1081 | """ | |
1080 | Get every byte of the hex digest value of email and turn it to integer. |
|
1082 | Get every byte of the hex digest value of email and turn it to integer. | |
1081 | It's going to be always between 0-255 |
|
1083 | It's going to be always between 0-255 | |
1082 | """ |
|
1084 | """ | |
1083 | digest = md5_safe(email_str.lower()) |
|
1085 | digest = md5_safe(email_str.lower()) | |
1084 | return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)] |
|
1086 | return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)] | |
1085 |
|
1087 | |||
1086 | def pick_color_bank_index(self, email_str, color_bank): |
|
1088 | def pick_color_bank_index(self, email_str, color_bank): | |
1087 | return self.email_to_int_list(email_str)[0] % len(color_bank) |
|
1089 | return self.email_to_int_list(email_str)[0] % len(color_bank) | |
1088 |
|
1090 | |||
1089 | def str2color(self, email_str): |
|
1091 | def str2color(self, email_str): | |
1090 | """ |
|
1092 | """ | |
1091 | Tries to map in a stable algorithm an email to color |
|
1093 | Tries to map in a stable algorithm an email to color | |
1092 |
|
1094 | |||
1093 | :param email_str: |
|
1095 | :param email_str: | |
1094 | """ |
|
1096 | """ | |
1095 | color_bank = self.get_color_bank() |
|
1097 | color_bank = self.get_color_bank() | |
1096 | # pick position (module it's length so we always find it in the |
|
1098 | # pick position (module it's length so we always find it in the | |
1097 | # bank even if it's smaller than 256 values |
|
1099 | # bank even if it's smaller than 256 values | |
1098 | pos = self.pick_color_bank_index(email_str, color_bank) |
|
1100 | pos = self.pick_color_bank_index(email_str, color_bank) | |
1099 | return color_bank[pos] |
|
1101 | return color_bank[pos] | |
1100 |
|
1102 | |||
1101 | def normalize_email(self, email_address): |
|
1103 | def normalize_email(self, email_address): | |
1102 | import unicodedata |
|
1104 | import unicodedata | |
1103 | # default host used to fill in the fake/missing email |
|
1105 | # default host used to fill in the fake/missing email | |
1104 | default_host = u'localhost' |
|
1106 | default_host = u'localhost' | |
1105 |
|
1107 | |||
1106 | if not email_address: |
|
1108 | if not email_address: | |
1107 | email_address = u'%s@%s' % (User.DEFAULT_USER, default_host) |
|
1109 | email_address = u'%s@%s' % (User.DEFAULT_USER, default_host) | |
1108 |
|
1110 | |||
1109 | email_address = safe_unicode(email_address) |
|
1111 | email_address = safe_unicode(email_address) | |
1110 |
|
1112 | |||
1111 | if u'@' not in email_address: |
|
1113 | if u'@' not in email_address: | |
1112 | email_address = u'%s@%s' % (email_address, default_host) |
|
1114 | email_address = u'%s@%s' % (email_address, default_host) | |
1113 |
|
1115 | |||
1114 | if email_address.endswith(u'@'): |
|
1116 | if email_address.endswith(u'@'): | |
1115 | email_address = u'%s%s' % (email_address, default_host) |
|
1117 | email_address = u'%s%s' % (email_address, default_host) | |
1116 |
|
1118 | |||
1117 | email_address = unicodedata.normalize('NFKD', email_address)\ |
|
1119 | email_address = unicodedata.normalize('NFKD', email_address)\ | |
1118 | .encode('ascii', 'ignore') |
|
1120 | .encode('ascii', 'ignore') | |
1119 | return email_address |
|
1121 | return email_address | |
1120 |
|
1122 | |||
1121 | def get_initials(self): |
|
1123 | def get_initials(self): | |
1122 | """ |
|
1124 | """ | |
1123 | Returns 2 letter initials calculated based on the input. |
|
1125 | Returns 2 letter initials calculated based on the input. | |
1124 | The algorithm picks first given email address, and takes first letter |
|
1126 | The algorithm picks first given email address, and takes first letter | |
1125 | of part before @, and then the first letter of server name. In case |
|
1127 | of part before @, and then the first letter of server name. In case | |
1126 | the part before @ is in a format of `somestring.somestring2` it replaces |
|
1128 | the part before @ is in a format of `somestring.somestring2` it replaces | |
1127 | the server letter with first letter of somestring2 |
|
1129 | the server letter with first letter of somestring2 | |
1128 |
|
1130 | |||
1129 | In case function was initialized with both first and lastname, this |
|
1131 | In case function was initialized with both first and lastname, this | |
1130 | overrides the extraction from email by first letter of the first and |
|
1132 | overrides the extraction from email by first letter of the first and | |
1131 | last name. We add special logic to that functionality, In case Full name |
|
1133 | last name. We add special logic to that functionality, In case Full name | |
1132 | is compound, like Guido Von Rossum, we use last part of the last name |
|
1134 | is compound, like Guido Von Rossum, we use last part of the last name | |
1133 | (Von Rossum) picking `R`. |
|
1135 | (Von Rossum) picking `R`. | |
1134 |
|
1136 | |||
1135 | Function also normalizes the non-ascii characters to they ascii |
|
1137 | Function also normalizes the non-ascii characters to they ascii | |
1136 | representation, eg Ą => A |
|
1138 | representation, eg Ą => A | |
1137 | """ |
|
1139 | """ | |
1138 | import unicodedata |
|
1140 | import unicodedata | |
1139 | # replace non-ascii to ascii |
|
1141 | # replace non-ascii to ascii | |
1140 | first_name = unicodedata.normalize( |
|
1142 | first_name = unicodedata.normalize( | |
1141 | 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore') |
|
1143 | 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore') | |
1142 | last_name = unicodedata.normalize( |
|
1144 | last_name = unicodedata.normalize( | |
1143 | 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore') |
|
1145 | 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore') | |
1144 |
|
1146 | |||
1145 | # do NFKD encoding, and also make sure email has proper format |
|
1147 | # do NFKD encoding, and also make sure email has proper format | |
1146 | email_address = self.normalize_email(self.email_address) |
|
1148 | email_address = self.normalize_email(self.email_address) | |
1147 |
|
1149 | |||
1148 | # first push the email initials |
|
1150 | # first push the email initials | |
1149 | prefix, server = email_address.split('@', 1) |
|
1151 | prefix, server = email_address.split('@', 1) | |
1150 |
|
1152 | |||
1151 | # check if prefix is maybe a 'first_name.last_name' syntax |
|
1153 | # check if prefix is maybe a 'first_name.last_name' syntax | |
1152 | _dot_split = prefix.rsplit('.', 1) |
|
1154 | _dot_split = prefix.rsplit('.', 1) | |
1153 | if len(_dot_split) == 2: |
|
1155 | if len(_dot_split) == 2: | |
1154 | initials = [_dot_split[0][0], _dot_split[1][0]] |
|
1156 | initials = [_dot_split[0][0], _dot_split[1][0]] | |
1155 | else: |
|
1157 | else: | |
1156 | initials = [prefix[0], server[0]] |
|
1158 | initials = [prefix[0], server[0]] | |
1157 |
|
1159 | |||
1158 | # then try to replace either first_name or last_name |
|
1160 | # then try to replace either first_name or last_name | |
1159 | fn_letter = (first_name or " ")[0].strip() |
|
1161 | fn_letter = (first_name or " ")[0].strip() | |
1160 | ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip() |
|
1162 | ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip() | |
1161 |
|
1163 | |||
1162 | if fn_letter: |
|
1164 | if fn_letter: | |
1163 | initials[0] = fn_letter |
|
1165 | initials[0] = fn_letter | |
1164 |
|
1166 | |||
1165 | if ln_letter: |
|
1167 | if ln_letter: | |
1166 | initials[1] = ln_letter |
|
1168 | initials[1] = ln_letter | |
1167 |
|
1169 | |||
1168 | return ''.join(initials).upper() |
|
1170 | return ''.join(initials).upper() | |
1169 |
|
1171 | |||
1170 | def get_img_data_by_type(self, font_family, img_type): |
|
1172 | def get_img_data_by_type(self, font_family, img_type): | |
1171 | default_user = """ |
|
1173 | default_user = """ | |
1172 | <svg xmlns="http://www.w3.org/2000/svg" |
|
1174 | <svg xmlns="http://www.w3.org/2000/svg" | |
1173 | version="1.1" x="0px" y="0px" width="{size}" height="{size}" |
|
1175 | version="1.1" x="0px" y="0px" width="{size}" height="{size}" | |
1174 | viewBox="-15 -10 439.165 429.164" |
|
1176 | viewBox="-15 -10 439.165 429.164" | |
1175 |
|
1177 | |||
1176 | xml:space="preserve" |
|
1178 | xml:space="preserve" | |
1177 | style="background:{background};" > |
|
1179 | style="background:{background};" > | |
1178 |
|
1180 | |||
1179 | <path d="M204.583,216.671c50.664,0,91.74-48.075, |
|
1181 | <path d="M204.583,216.671c50.664,0,91.74-48.075, | |
1180 | 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377 |
|
1182 | 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377 | |
1181 | c-50.668,0-91.74,25.14-91.74,107.377C112.844, |
|
1183 | c-50.668,0-91.74,25.14-91.74,107.377C112.844, | |
1182 | 168.596,153.916,216.671, |
|
1184 | 168.596,153.916,216.671, | |
1183 | 204.583,216.671z" fill="{text_color}"/> |
|
1185 | 204.583,216.671z" fill="{text_color}"/> | |
1184 | <path d="M407.164,374.717L360.88, |
|
1186 | <path d="M407.164,374.717L360.88, | |
1185 | 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392 |
|
1187 | 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392 | |
1186 | c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316, |
|
1188 | c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316, | |
1187 | 15.366-44.203,23.488-69.076,23.488c-24.877, |
|
1189 | 15.366-44.203,23.488-69.076,23.488c-24.877, | |
1188 | 0-48.762-8.122-69.078-23.488 |
|
1190 | 0-48.762-8.122-69.078-23.488 | |
1189 | c-1.428-1.078-3.346-1.238-4.93-0.415L58.75, |
|
1191 | c-1.428-1.078-3.346-1.238-4.93-0.415L58.75, | |
1190 | 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717 |
|
1192 | 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717 | |
1191 | c-3.191,7.188-2.537,15.412,1.75,22.005c4.285, |
|
1193 | c-3.191,7.188-2.537,15.412,1.75,22.005c4.285, | |
1192 | 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936, |
|
1194 | 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936, | |
1193 | 19.402-10.527 C409.699,390.129, |
|
1195 | 19.402-10.527 C409.699,390.129, | |
1194 | 410.355,381.902,407.164,374.717z" fill="{text_color}"/> |
|
1196 | 410.355,381.902,407.164,374.717z" fill="{text_color}"/> | |
1195 | </svg>""".format( |
|
1197 | </svg>""".format( | |
1196 | size=self.size, |
|
1198 | size=self.size, | |
1197 | background='#979797', # @grey4 |
|
1199 | background='#979797', # @grey4 | |
1198 | text_color=self.text_color, |
|
1200 | text_color=self.text_color, | |
1199 | font_family=font_family) |
|
1201 | font_family=font_family) | |
1200 |
|
1202 | |||
1201 | return { |
|
1203 | return { | |
1202 | "default_user": default_user |
|
1204 | "default_user": default_user | |
1203 | }[img_type] |
|
1205 | }[img_type] | |
1204 |
|
1206 | |||
1205 | def get_img_data(self, svg_type=None): |
|
1207 | def get_img_data(self, svg_type=None): | |
1206 | """ |
|
1208 | """ | |
1207 | generates the svg metadata for image |
|
1209 | generates the svg metadata for image | |
1208 | """ |
|
1210 | """ | |
1209 |
|
1211 | |||
1210 | font_family = ','.join([ |
|
1212 | font_family = ','.join([ | |
1211 | 'proximanovaregular', |
|
1213 | 'proximanovaregular', | |
1212 | 'Proxima Nova Regular', |
|
1214 | 'Proxima Nova Regular', | |
1213 | 'Proxima Nova', |
|
1215 | 'Proxima Nova', | |
1214 | 'Arial', |
|
1216 | 'Arial', | |
1215 | 'Lucida Grande', |
|
1217 | 'Lucida Grande', | |
1216 | 'sans-serif' |
|
1218 | 'sans-serif' | |
1217 | ]) |
|
1219 | ]) | |
1218 | if svg_type: |
|
1220 | if svg_type: | |
1219 | return self.get_img_data_by_type(font_family, svg_type) |
|
1221 | return self.get_img_data_by_type(font_family, svg_type) | |
1220 |
|
1222 | |||
1221 | initials = self.get_initials() |
|
1223 | initials = self.get_initials() | |
1222 | img_data = """ |
|
1224 | img_data = """ | |
1223 | <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none" |
|
1225 | <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none" | |
1224 | width="{size}" height="{size}" |
|
1226 | width="{size}" height="{size}" | |
1225 | style="width: 100%; height: 100%; background-color: {background}" |
|
1227 | style="width: 100%; height: 100%; background-color: {background}" | |
1226 | viewBox="0 0 {size} {size}"> |
|
1228 | viewBox="0 0 {size} {size}"> | |
1227 | <text text-anchor="middle" y="50%" x="50%" dy="0.35em" |
|
1229 | <text text-anchor="middle" y="50%" x="50%" dy="0.35em" | |
1228 | pointer-events="auto" fill="{text_color}" |
|
1230 | pointer-events="auto" fill="{text_color}" | |
1229 | font-family="{font_family}" |
|
1231 | font-family="{font_family}" | |
1230 | style="font-weight: 400; font-size: {f_size}px;">{text} |
|
1232 | style="font-weight: 400; font-size: {f_size}px;">{text} | |
1231 | </text> |
|
1233 | </text> | |
1232 | </svg>""".format( |
|
1234 | </svg>""".format( | |
1233 | size=self.size, |
|
1235 | size=self.size, | |
1234 | f_size=self.size/1.85, # scale the text inside the box nicely |
|
1236 | f_size=self.size/1.85, # scale the text inside the box nicely | |
1235 | background=self.background, |
|
1237 | background=self.background, | |
1236 | text_color=self.text_color, |
|
1238 | text_color=self.text_color, | |
1237 | text=initials.upper(), |
|
1239 | text=initials.upper(), | |
1238 | font_family=font_family) |
|
1240 | font_family=font_family) | |
1239 |
|
1241 | |||
1240 | return img_data |
|
1242 | return img_data | |
1241 |
|
1243 | |||
1242 | def generate_svg(self, svg_type=None): |
|
1244 | def generate_svg(self, svg_type=None): | |
1243 | img_data = self.get_img_data(svg_type) |
|
1245 | img_data = self.get_img_data(svg_type) | |
1244 | return "data:image/svg+xml;base64,%s" % img_data.encode('base64') |
|
1246 | return "data:image/svg+xml;base64,%s" % img_data.encode('base64') | |
1245 |
|
1247 | |||
1246 |
|
1248 | |||
1247 | def initials_gravatar(email_address, first_name, last_name, size=30): |
|
1249 | def initials_gravatar(email_address, first_name, last_name, size=30): | |
1248 | svg_type = None |
|
1250 | svg_type = None | |
1249 | if email_address == User.DEFAULT_USER_EMAIL: |
|
1251 | if email_address == User.DEFAULT_USER_EMAIL: | |
1250 | svg_type = 'default_user' |
|
1252 | svg_type = 'default_user' | |
1251 | klass = InitialsGravatar(email_address, first_name, last_name, size) |
|
1253 | klass = InitialsGravatar(email_address, first_name, last_name, size) | |
1252 | return klass.generate_svg(svg_type=svg_type) |
|
1254 | return klass.generate_svg(svg_type=svg_type) | |
1253 |
|
1255 | |||
1254 |
|
1256 | |||
1255 | def gravatar_url(email_address, size=30, request=None): |
|
1257 | def gravatar_url(email_address, size=30, request=None): | |
1256 | request = get_current_request() |
|
1258 | request = get_current_request() | |
1257 | if request and hasattr(request, 'call_context'): |
|
1259 | if request and hasattr(request, 'call_context'): | |
1258 | _use_gravatar = request.call_context.visual.use_gravatar |
|
1260 | _use_gravatar = request.call_context.visual.use_gravatar | |
1259 | _gravatar_url = request.call_context.visual.gravatar_url |
|
1261 | _gravatar_url = request.call_context.visual.gravatar_url | |
1260 | else: |
|
1262 | else: | |
1261 | # doh, we need to re-import those to mock it later |
|
1263 | # doh, we need to re-import those to mock it later | |
1262 | from pylons import tmpl_context as c |
|
1264 | from pylons import tmpl_context as c | |
1263 |
|
1265 | |||
1264 | _use_gravatar = c.visual.use_gravatar |
|
1266 | _use_gravatar = c.visual.use_gravatar | |
1265 | _gravatar_url = c.visual.gravatar_url |
|
1267 | _gravatar_url = c.visual.gravatar_url | |
1266 |
|
1268 | |||
1267 | _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL |
|
1269 | _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL | |
1268 |
|
1270 | |||
1269 | email_address = email_address or User.DEFAULT_USER_EMAIL |
|
1271 | email_address = email_address or User.DEFAULT_USER_EMAIL | |
1270 | if isinstance(email_address, unicode): |
|
1272 | if isinstance(email_address, unicode): | |
1271 | # hashlib crashes on unicode items |
|
1273 | # hashlib crashes on unicode items | |
1272 | email_address = safe_str(email_address) |
|
1274 | email_address = safe_str(email_address) | |
1273 |
|
1275 | |||
1274 | # empty email or default user |
|
1276 | # empty email or default user | |
1275 | if not email_address or email_address == User.DEFAULT_USER_EMAIL: |
|
1277 | if not email_address or email_address == User.DEFAULT_USER_EMAIL: | |
1276 | return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size) |
|
1278 | return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size) | |
1277 |
|
1279 | |||
1278 | if _use_gravatar: |
|
1280 | if _use_gravatar: | |
1279 | # TODO: Disuse pyramid thread locals. Think about another solution to |
|
1281 | # TODO: Disuse pyramid thread locals. Think about another solution to | |
1280 | # get the host and schema here. |
|
1282 | # get the host and schema here. | |
1281 | request = get_current_request() |
|
1283 | request = get_current_request() | |
1282 | tmpl = safe_str(_gravatar_url) |
|
1284 | tmpl = safe_str(_gravatar_url) | |
1283 | tmpl = tmpl.replace('{email}', email_address)\ |
|
1285 | tmpl = tmpl.replace('{email}', email_address)\ | |
1284 | .replace('{md5email}', md5_safe(email_address.lower())) \ |
|
1286 | .replace('{md5email}', md5_safe(email_address.lower())) \ | |
1285 | .replace('{netloc}', request.host)\ |
|
1287 | .replace('{netloc}', request.host)\ | |
1286 | .replace('{scheme}', request.scheme)\ |
|
1288 | .replace('{scheme}', request.scheme)\ | |
1287 | .replace('{size}', safe_str(size)) |
|
1289 | .replace('{size}', safe_str(size)) | |
1288 | return tmpl |
|
1290 | return tmpl | |
1289 | else: |
|
1291 | else: | |
1290 | return initials_gravatar(email_address, '', '', size=size) |
|
1292 | return initials_gravatar(email_address, '', '', size=size) | |
1291 |
|
1293 | |||
1292 |
|
1294 | |||
1293 | class Page(_Page): |
|
1295 | class Page(_Page): | |
1294 | """ |
|
1296 | """ | |
1295 | Custom pager to match rendering style with paginator |
|
1297 | Custom pager to match rendering style with paginator | |
1296 | """ |
|
1298 | """ | |
1297 |
|
1299 | |||
1298 | def _get_pos(self, cur_page, max_page, items): |
|
1300 | def _get_pos(self, cur_page, max_page, items): | |
1299 | edge = (items / 2) + 1 |
|
1301 | edge = (items / 2) + 1 | |
1300 | if (cur_page <= edge): |
|
1302 | if (cur_page <= edge): | |
1301 | radius = max(items / 2, items - cur_page) |
|
1303 | radius = max(items / 2, items - cur_page) | |
1302 | elif (max_page - cur_page) < edge: |
|
1304 | elif (max_page - cur_page) < edge: | |
1303 | radius = (items - 1) - (max_page - cur_page) |
|
1305 | radius = (items - 1) - (max_page - cur_page) | |
1304 | else: |
|
1306 | else: | |
1305 | radius = items / 2 |
|
1307 | radius = items / 2 | |
1306 |
|
1308 | |||
1307 | left = max(1, (cur_page - (radius))) |
|
1309 | left = max(1, (cur_page - (radius))) | |
1308 | right = min(max_page, cur_page + (radius)) |
|
1310 | right = min(max_page, cur_page + (radius)) | |
1309 | return left, cur_page, right |
|
1311 | return left, cur_page, right | |
1310 |
|
1312 | |||
1311 | def _range(self, regexp_match): |
|
1313 | def _range(self, regexp_match): | |
1312 | """ |
|
1314 | """ | |
1313 | Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8'). |
|
1315 | Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8'). | |
1314 |
|
1316 | |||
1315 | Arguments: |
|
1317 | Arguments: | |
1316 |
|
1318 | |||
1317 | regexp_match |
|
1319 | regexp_match | |
1318 | A "re" (regular expressions) match object containing the |
|
1320 | A "re" (regular expressions) match object containing the | |
1319 | radius of linked pages around the current page in |
|
1321 | radius of linked pages around the current page in | |
1320 | regexp_match.group(1) as a string |
|
1322 | regexp_match.group(1) as a string | |
1321 |
|
1323 | |||
1322 | This function is supposed to be called as a callable in |
|
1324 | This function is supposed to be called as a callable in | |
1323 | re.sub. |
|
1325 | re.sub. | |
1324 |
|
1326 | |||
1325 | """ |
|
1327 | """ | |
1326 | radius = int(regexp_match.group(1)) |
|
1328 | radius = int(regexp_match.group(1)) | |
1327 |
|
1329 | |||
1328 | # Compute the first and last page number within the radius |
|
1330 | # Compute the first and last page number within the radius | |
1329 | # e.g. '1 .. 5 6 [7] 8 9 .. 12' |
|
1331 | # e.g. '1 .. 5 6 [7] 8 9 .. 12' | |
1330 | # -> leftmost_page = 5 |
|
1332 | # -> leftmost_page = 5 | |
1331 | # -> rightmost_page = 9 |
|
1333 | # -> rightmost_page = 9 | |
1332 | leftmost_page, _cur, rightmost_page = self._get_pos(self.page, |
|
1334 | leftmost_page, _cur, rightmost_page = self._get_pos(self.page, | |
1333 | self.last_page, |
|
1335 | self.last_page, | |
1334 | (radius * 2) + 1) |
|
1336 | (radius * 2) + 1) | |
1335 | nav_items = [] |
|
1337 | nav_items = [] | |
1336 |
|
1338 | |||
1337 | # Create a link to the first page (unless we are on the first page |
|
1339 | # Create a link to the first page (unless we are on the first page | |
1338 | # or there would be no need to insert '..' spacers) |
|
1340 | # or there would be no need to insert '..' spacers) | |
1339 | if self.page != self.first_page and self.first_page < leftmost_page: |
|
1341 | if self.page != self.first_page and self.first_page < leftmost_page: | |
1340 | nav_items.append(self._pagerlink(self.first_page, self.first_page)) |
|
1342 | nav_items.append(self._pagerlink(self.first_page, self.first_page)) | |
1341 |
|
1343 | |||
1342 | # Insert dots if there are pages between the first page |
|
1344 | # Insert dots if there are pages between the first page | |
1343 | # and the currently displayed page range |
|
1345 | # and the currently displayed page range | |
1344 | if leftmost_page - self.first_page > 1: |
|
1346 | if leftmost_page - self.first_page > 1: | |
1345 | # Wrap in a SPAN tag if nolink_attr is set |
|
1347 | # Wrap in a SPAN tag if nolink_attr is set | |
1346 | text = '..' |
|
1348 | text = '..' | |
1347 | if self.dotdot_attr: |
|
1349 | if self.dotdot_attr: | |
1348 | text = HTML.span(c=text, **self.dotdot_attr) |
|
1350 | text = HTML.span(c=text, **self.dotdot_attr) | |
1349 | nav_items.append(text) |
|
1351 | nav_items.append(text) | |
1350 |
|
1352 | |||
1351 | for thispage in xrange(leftmost_page, rightmost_page + 1): |
|
1353 | for thispage in xrange(leftmost_page, rightmost_page + 1): | |
1352 | # Hilight the current page number and do not use a link |
|
1354 | # Hilight the current page number and do not use a link | |
1353 | if thispage == self.page: |
|
1355 | if thispage == self.page: | |
1354 | text = '%s' % (thispage,) |
|
1356 | text = '%s' % (thispage,) | |
1355 | # Wrap in a SPAN tag if nolink_attr is set |
|
1357 | # Wrap in a SPAN tag if nolink_attr is set | |
1356 | if self.curpage_attr: |
|
1358 | if self.curpage_attr: | |
1357 | text = HTML.span(c=text, **self.curpage_attr) |
|
1359 | text = HTML.span(c=text, **self.curpage_attr) | |
1358 | nav_items.append(text) |
|
1360 | nav_items.append(text) | |
1359 | # Otherwise create just a link to that page |
|
1361 | # Otherwise create just a link to that page | |
1360 | else: |
|
1362 | else: | |
1361 | text = '%s' % (thispage,) |
|
1363 | text = '%s' % (thispage,) | |
1362 | nav_items.append(self._pagerlink(thispage, text)) |
|
1364 | nav_items.append(self._pagerlink(thispage, text)) | |
1363 |
|
1365 | |||
1364 | # Insert dots if there are pages between the displayed |
|
1366 | # Insert dots if there are pages between the displayed | |
1365 | # page numbers and the end of the page range |
|
1367 | # page numbers and the end of the page range | |
1366 | if self.last_page - rightmost_page > 1: |
|
1368 | if self.last_page - rightmost_page > 1: | |
1367 | text = '..' |
|
1369 | text = '..' | |
1368 | # Wrap in a SPAN tag if nolink_attr is set |
|
1370 | # Wrap in a SPAN tag if nolink_attr is set | |
1369 | if self.dotdot_attr: |
|
1371 | if self.dotdot_attr: | |
1370 | text = HTML.span(c=text, **self.dotdot_attr) |
|
1372 | text = HTML.span(c=text, **self.dotdot_attr) | |
1371 | nav_items.append(text) |
|
1373 | nav_items.append(text) | |
1372 |
|
1374 | |||
1373 | # Create a link to the very last page (unless we are on the last |
|
1375 | # Create a link to the very last page (unless we are on the last | |
1374 | # page or there would be no need to insert '..' spacers) |
|
1376 | # page or there would be no need to insert '..' spacers) | |
1375 | if self.page != self.last_page and rightmost_page < self.last_page: |
|
1377 | if self.page != self.last_page and rightmost_page < self.last_page: | |
1376 | nav_items.append(self._pagerlink(self.last_page, self.last_page)) |
|
1378 | nav_items.append(self._pagerlink(self.last_page, self.last_page)) | |
1377 |
|
1379 | |||
1378 | ## prerender links |
|
1380 | ## prerender links | |
1379 | #_page_link = url.current() |
|
1381 | #_page_link = url.current() | |
1380 | #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1)))) |
|
1382 | #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1)))) | |
1381 | #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1)))) |
|
1383 | #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1)))) | |
1382 | return self.separator.join(nav_items) |
|
1384 | return self.separator.join(nav_items) | |
1383 |
|
1385 | |||
1384 | def pager(self, format='~2~', page_param='page', partial_param='partial', |
|
1386 | def pager(self, format='~2~', page_param='page', partial_param='partial', | |
1385 | show_if_single_page=False, separator=' ', onclick=None, |
|
1387 | show_if_single_page=False, separator=' ', onclick=None, | |
1386 | symbol_first='<<', symbol_last='>>', |
|
1388 | symbol_first='<<', symbol_last='>>', | |
1387 | symbol_previous='<', symbol_next='>', |
|
1389 | symbol_previous='<', symbol_next='>', | |
1388 | link_attr={'class': 'pager_link', 'rel': 'prerender'}, |
|
1390 | link_attr={'class': 'pager_link', 'rel': 'prerender'}, | |
1389 | curpage_attr={'class': 'pager_curpage'}, |
|
1391 | curpage_attr={'class': 'pager_curpage'}, | |
1390 | dotdot_attr={'class': 'pager_dotdot'}, **kwargs): |
|
1392 | dotdot_attr={'class': 'pager_dotdot'}, **kwargs): | |
1391 |
|
1393 | |||
1392 | self.curpage_attr = curpage_attr |
|
1394 | self.curpage_attr = curpage_attr | |
1393 | self.separator = separator |
|
1395 | self.separator = separator | |
1394 | self.pager_kwargs = kwargs |
|
1396 | self.pager_kwargs = kwargs | |
1395 | self.page_param = page_param |
|
1397 | self.page_param = page_param | |
1396 | self.partial_param = partial_param |
|
1398 | self.partial_param = partial_param | |
1397 | self.onclick = onclick |
|
1399 | self.onclick = onclick | |
1398 | self.link_attr = link_attr |
|
1400 | self.link_attr = link_attr | |
1399 | self.dotdot_attr = dotdot_attr |
|
1401 | self.dotdot_attr = dotdot_attr | |
1400 |
|
1402 | |||
1401 | # Don't show navigator if there is no more than one page |
|
1403 | # Don't show navigator if there is no more than one page | |
1402 | if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page): |
|
1404 | if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page): | |
1403 | return '' |
|
1405 | return '' | |
1404 |
|
1406 | |||
1405 | from string import Template |
|
1407 | from string import Template | |
1406 | # Replace ~...~ in token format by range of pages |
|
1408 | # Replace ~...~ in token format by range of pages | |
1407 | result = re.sub(r'~(\d+)~', self._range, format) |
|
1409 | result = re.sub(r'~(\d+)~', self._range, format) | |
1408 |
|
1410 | |||
1409 | # Interpolate '%' variables |
|
1411 | # Interpolate '%' variables | |
1410 | result = Template(result).safe_substitute({ |
|
1412 | result = Template(result).safe_substitute({ | |
1411 | 'first_page': self.first_page, |
|
1413 | 'first_page': self.first_page, | |
1412 | 'last_page': self.last_page, |
|
1414 | 'last_page': self.last_page, | |
1413 | 'page': self.page, |
|
1415 | 'page': self.page, | |
1414 | 'page_count': self.page_count, |
|
1416 | 'page_count': self.page_count, | |
1415 | 'items_per_page': self.items_per_page, |
|
1417 | 'items_per_page': self.items_per_page, | |
1416 | 'first_item': self.first_item, |
|
1418 | 'first_item': self.first_item, | |
1417 | 'last_item': self.last_item, |
|
1419 | 'last_item': self.last_item, | |
1418 | 'item_count': self.item_count, |
|
1420 | 'item_count': self.item_count, | |
1419 | 'link_first': self.page > self.first_page and \ |
|
1421 | 'link_first': self.page > self.first_page and \ | |
1420 | self._pagerlink(self.first_page, symbol_first) or '', |
|
1422 | self._pagerlink(self.first_page, symbol_first) or '', | |
1421 | 'link_last': self.page < self.last_page and \ |
|
1423 | 'link_last': self.page < self.last_page and \ | |
1422 | self._pagerlink(self.last_page, symbol_last) or '', |
|
1424 | self._pagerlink(self.last_page, symbol_last) or '', | |
1423 | 'link_previous': self.previous_page and \ |
|
1425 | 'link_previous': self.previous_page and \ | |
1424 | self._pagerlink(self.previous_page, symbol_previous) \ |
|
1426 | self._pagerlink(self.previous_page, symbol_previous) \ | |
1425 | or HTML.span(symbol_previous, class_="pg-previous disabled"), |
|
1427 | or HTML.span(symbol_previous, class_="pg-previous disabled"), | |
1426 | 'link_next': self.next_page and \ |
|
1428 | 'link_next': self.next_page and \ | |
1427 | self._pagerlink(self.next_page, symbol_next) \ |
|
1429 | self._pagerlink(self.next_page, symbol_next) \ | |
1428 | or HTML.span(symbol_next, class_="pg-next disabled") |
|
1430 | or HTML.span(symbol_next, class_="pg-next disabled") | |
1429 | }) |
|
1431 | }) | |
1430 |
|
1432 | |||
1431 | return literal(result) |
|
1433 | return literal(result) | |
1432 |
|
1434 | |||
1433 |
|
1435 | |||
1434 | #============================================================================== |
|
1436 | #============================================================================== | |
1435 | # REPO PAGER, PAGER FOR REPOSITORY |
|
1437 | # REPO PAGER, PAGER FOR REPOSITORY | |
1436 | #============================================================================== |
|
1438 | #============================================================================== | |
1437 | class RepoPage(Page): |
|
1439 | class RepoPage(Page): | |
1438 |
|
1440 | |||
1439 | def __init__(self, collection, page=1, items_per_page=20, |
|
1441 | def __init__(self, collection, page=1, items_per_page=20, | |
1440 | item_count=None, url=None, **kwargs): |
|
1442 | item_count=None, url=None, **kwargs): | |
1441 |
|
1443 | |||
1442 | """Create a "RepoPage" instance. special pager for paging |
|
1444 | """Create a "RepoPage" instance. special pager for paging | |
1443 | repository |
|
1445 | repository | |
1444 | """ |
|
1446 | """ | |
1445 | self._url_generator = url |
|
1447 | self._url_generator = url | |
1446 |
|
1448 | |||
1447 | # Safe the kwargs class-wide so they can be used in the pager() method |
|
1449 | # Safe the kwargs class-wide so they can be used in the pager() method | |
1448 | self.kwargs = kwargs |
|
1450 | self.kwargs = kwargs | |
1449 |
|
1451 | |||
1450 | # Save a reference to the collection |
|
1452 | # Save a reference to the collection | |
1451 | self.original_collection = collection |
|
1453 | self.original_collection = collection | |
1452 |
|
1454 | |||
1453 | self.collection = collection |
|
1455 | self.collection = collection | |
1454 |
|
1456 | |||
1455 | # The self.page is the number of the current page. |
|
1457 | # The self.page is the number of the current page. | |
1456 | # The first page has the number 1! |
|
1458 | # The first page has the number 1! | |
1457 | try: |
|
1459 | try: | |
1458 | self.page = int(page) # make it int() if we get it as a string |
|
1460 | self.page = int(page) # make it int() if we get it as a string | |
1459 | except (ValueError, TypeError): |
|
1461 | except (ValueError, TypeError): | |
1460 | self.page = 1 |
|
1462 | self.page = 1 | |
1461 |
|
1463 | |||
1462 | self.items_per_page = items_per_page |
|
1464 | self.items_per_page = items_per_page | |
1463 |
|
1465 | |||
1464 | # Unless the user tells us how many items the collections has |
|
1466 | # Unless the user tells us how many items the collections has | |
1465 | # we calculate that ourselves. |
|
1467 | # we calculate that ourselves. | |
1466 | if item_count is not None: |
|
1468 | if item_count is not None: | |
1467 | self.item_count = item_count |
|
1469 | self.item_count = item_count | |
1468 | else: |
|
1470 | else: | |
1469 | self.item_count = len(self.collection) |
|
1471 | self.item_count = len(self.collection) | |
1470 |
|
1472 | |||
1471 | # Compute the number of the first and last available page |
|
1473 | # Compute the number of the first and last available page | |
1472 | if self.item_count > 0: |
|
1474 | if self.item_count > 0: | |
1473 | self.first_page = 1 |
|
1475 | self.first_page = 1 | |
1474 | self.page_count = int(math.ceil(float(self.item_count) / |
|
1476 | self.page_count = int(math.ceil(float(self.item_count) / | |
1475 | self.items_per_page)) |
|
1477 | self.items_per_page)) | |
1476 | self.last_page = self.first_page + self.page_count - 1 |
|
1478 | self.last_page = self.first_page + self.page_count - 1 | |
1477 |
|
1479 | |||
1478 | # Make sure that the requested page number is the range of |
|
1480 | # Make sure that the requested page number is the range of | |
1479 | # valid pages |
|
1481 | # valid pages | |
1480 | if self.page > self.last_page: |
|
1482 | if self.page > self.last_page: | |
1481 | self.page = self.last_page |
|
1483 | self.page = self.last_page | |
1482 | elif self.page < self.first_page: |
|
1484 | elif self.page < self.first_page: | |
1483 | self.page = self.first_page |
|
1485 | self.page = self.first_page | |
1484 |
|
1486 | |||
1485 | # Note: the number of items on this page can be less than |
|
1487 | # Note: the number of items on this page can be less than | |
1486 | # items_per_page if the last page is not full |
|
1488 | # items_per_page if the last page is not full | |
1487 | self.first_item = max(0, (self.item_count) - (self.page * |
|
1489 | self.first_item = max(0, (self.item_count) - (self.page * | |
1488 | items_per_page)) |
|
1490 | items_per_page)) | |
1489 | self.last_item = ((self.item_count - 1) - items_per_page * |
|
1491 | self.last_item = ((self.item_count - 1) - items_per_page * | |
1490 | (self.page - 1)) |
|
1492 | (self.page - 1)) | |
1491 |
|
1493 | |||
1492 | self.items = list(self.collection[self.first_item:self.last_item + 1]) |
|
1494 | self.items = list(self.collection[self.first_item:self.last_item + 1]) | |
1493 |
|
1495 | |||
1494 | # Links to previous and next page |
|
1496 | # Links to previous and next page | |
1495 | if self.page > self.first_page: |
|
1497 | if self.page > self.first_page: | |
1496 | self.previous_page = self.page - 1 |
|
1498 | self.previous_page = self.page - 1 | |
1497 | else: |
|
1499 | else: | |
1498 | self.previous_page = None |
|
1500 | self.previous_page = None | |
1499 |
|
1501 | |||
1500 | if self.page < self.last_page: |
|
1502 | if self.page < self.last_page: | |
1501 | self.next_page = self.page + 1 |
|
1503 | self.next_page = self.page + 1 | |
1502 | else: |
|
1504 | else: | |
1503 | self.next_page = None |
|
1505 | self.next_page = None | |
1504 |
|
1506 | |||
1505 | # No items available |
|
1507 | # No items available | |
1506 | else: |
|
1508 | else: | |
1507 | self.first_page = None |
|
1509 | self.first_page = None | |
1508 | self.page_count = 0 |
|
1510 | self.page_count = 0 | |
1509 | self.last_page = None |
|
1511 | self.last_page = None | |
1510 | self.first_item = None |
|
1512 | self.first_item = None | |
1511 | self.last_item = None |
|
1513 | self.last_item = None | |
1512 | self.previous_page = None |
|
1514 | self.previous_page = None | |
1513 | self.next_page = None |
|
1515 | self.next_page = None | |
1514 | self.items = [] |
|
1516 | self.items = [] | |
1515 |
|
1517 | |||
1516 | # This is a subclass of the 'list' type. Initialise the list now. |
|
1518 | # This is a subclass of the 'list' type. Initialise the list now. | |
1517 | list.__init__(self, reversed(self.items)) |
|
1519 | list.__init__(self, reversed(self.items)) | |
1518 |
|
1520 | |||
1519 |
|
1521 | |||
1520 | def breadcrumb_repo_link(repo): |
|
1522 | def breadcrumb_repo_link(repo): | |
1521 | """ |
|
1523 | """ | |
1522 | Makes a breadcrumbs path link to repo |
|
1524 | Makes a breadcrumbs path link to repo | |
1523 |
|
1525 | |||
1524 | ex:: |
|
1526 | ex:: | |
1525 | group >> subgroup >> repo |
|
1527 | group >> subgroup >> repo | |
1526 |
|
1528 | |||
1527 | :param repo: a Repository instance |
|
1529 | :param repo: a Repository instance | |
1528 | """ |
|
1530 | """ | |
1529 |
|
1531 | |||
1530 | path = [ |
|
1532 | path = [ | |
1531 | link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name)) |
|
1533 | link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name)) | |
1532 | for group in repo.groups_with_parents |
|
1534 | for group in repo.groups_with_parents | |
1533 | ] + [ |
|
1535 | ] + [ | |
1534 | link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name)) |
|
1536 | link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name)) | |
1535 | ] |
|
1537 | ] | |
1536 |
|
1538 | |||
1537 | return literal(' » '.join(path)) |
|
1539 | return literal(' » '.join(path)) | |
1538 |
|
1540 | |||
1539 |
|
1541 | |||
1540 | def format_byte_size_binary(file_size): |
|
1542 | def format_byte_size_binary(file_size): | |
1541 | """ |
|
1543 | """ | |
1542 | Formats file/folder sizes to standard. |
|
1544 | Formats file/folder sizes to standard. | |
1543 | """ |
|
1545 | """ | |
|
1546 | if file_size is None: | |||
|
1547 | file_size = 0 | |||
|
1548 | ||||
1544 | formatted_size = format_byte_size(file_size, binary=True) |
|
1549 | formatted_size = format_byte_size(file_size, binary=True) | |
1545 | return formatted_size |
|
1550 | return formatted_size | |
1546 |
|
1551 | |||
1547 |
|
1552 | |||
1548 | def urlify_text(text_, safe=True): |
|
1553 | def urlify_text(text_, safe=True): | |
1549 | """ |
|
1554 | """ | |
1550 | Extrac urls from text and make html links out of them |
|
1555 | Extrac urls from text and make html links out of them | |
1551 |
|
1556 | |||
1552 | :param text_: |
|
1557 | :param text_: | |
1553 | """ |
|
1558 | """ | |
1554 |
|
1559 | |||
1555 | url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]''' |
|
1560 | url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]''' | |
1556 | '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''') |
|
1561 | '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''') | |
1557 |
|
1562 | |||
1558 | def url_func(match_obj): |
|
1563 | def url_func(match_obj): | |
1559 | url_full = match_obj.groups()[0] |
|
1564 | url_full = match_obj.groups()[0] | |
1560 | return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full}) |
|
1565 | return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full}) | |
1561 | _newtext = url_pat.sub(url_func, text_) |
|
1566 | _newtext = url_pat.sub(url_func, text_) | |
1562 | if safe: |
|
1567 | if safe: | |
1563 | return literal(_newtext) |
|
1568 | return literal(_newtext) | |
1564 | return _newtext |
|
1569 | return _newtext | |
1565 |
|
1570 | |||
1566 |
|
1571 | |||
1567 | def urlify_commits(text_, repository): |
|
1572 | def urlify_commits(text_, repository): | |
1568 | """ |
|
1573 | """ | |
1569 | Extract commit ids from text and make link from them |
|
1574 | Extract commit ids from text and make link from them | |
1570 |
|
1575 | |||
1571 | :param text_: |
|
1576 | :param text_: | |
1572 | :param repository: repo name to build the URL with |
|
1577 | :param repository: repo name to build the URL with | |
1573 | """ |
|
1578 | """ | |
1574 | from pylons import url # doh, we need to re-import url to mock it later |
|
1579 | from pylons import url # doh, we need to re-import url to mock it later | |
1575 | URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)') |
|
1580 | URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)') | |
1576 |
|
1581 | |||
1577 | def url_func(match_obj): |
|
1582 | def url_func(match_obj): | |
1578 | commit_id = match_obj.groups()[1] |
|
1583 | commit_id = match_obj.groups()[1] | |
1579 | pref = match_obj.groups()[0] |
|
1584 | pref = match_obj.groups()[0] | |
1580 | suf = match_obj.groups()[2] |
|
1585 | suf = match_obj.groups()[2] | |
1581 |
|
1586 | |||
1582 | tmpl = ( |
|
1587 | tmpl = ( | |
1583 | '%(pref)s<a class="%(cls)s" href="%(url)s">' |
|
1588 | '%(pref)s<a class="%(cls)s" href="%(url)s">' | |
1584 | '%(commit_id)s</a>%(suf)s' |
|
1589 | '%(commit_id)s</a>%(suf)s' | |
1585 | ) |
|
1590 | ) | |
1586 | return tmpl % { |
|
1591 | return tmpl % { | |
1587 | 'pref': pref, |
|
1592 | 'pref': pref, | |
1588 | 'cls': 'revision-link', |
|
1593 | 'cls': 'revision-link', | |
1589 | 'url': url('changeset_home', repo_name=repository, |
|
1594 | 'url': url('changeset_home', repo_name=repository, | |
1590 | revision=commit_id, qualified=True), |
|
1595 | revision=commit_id, qualified=True), | |
1591 | 'commit_id': commit_id, |
|
1596 | 'commit_id': commit_id, | |
1592 | 'suf': suf |
|
1597 | 'suf': suf | |
1593 | } |
|
1598 | } | |
1594 |
|
1599 | |||
1595 | newtext = URL_PAT.sub(url_func, text_) |
|
1600 | newtext = URL_PAT.sub(url_func, text_) | |
1596 |
|
1601 | |||
1597 | return newtext |
|
1602 | return newtext | |
1598 |
|
1603 | |||
1599 |
|
1604 | |||
1600 | def _process_url_func(match_obj, repo_name, uid, entry, |
|
1605 | def _process_url_func(match_obj, repo_name, uid, entry, | |
1601 | return_raw_data=False, link_format='html'): |
|
1606 | return_raw_data=False, link_format='html'): | |
1602 | pref = '' |
|
1607 | pref = '' | |
1603 | if match_obj.group().startswith(' '): |
|
1608 | if match_obj.group().startswith(' '): | |
1604 | pref = ' ' |
|
1609 | pref = ' ' | |
1605 |
|
1610 | |||
1606 | issue_id = ''.join(match_obj.groups()) |
|
1611 | issue_id = ''.join(match_obj.groups()) | |
1607 |
|
1612 | |||
1608 | if link_format == 'html': |
|
1613 | if link_format == 'html': | |
1609 | tmpl = ( |
|
1614 | tmpl = ( | |
1610 | '%(pref)s<a class="%(cls)s" href="%(url)s">' |
|
1615 | '%(pref)s<a class="%(cls)s" href="%(url)s">' | |
1611 | '%(issue-prefix)s%(id-repr)s' |
|
1616 | '%(issue-prefix)s%(id-repr)s' | |
1612 | '</a>') |
|
1617 | '</a>') | |
1613 | elif link_format == 'rst': |
|
1618 | elif link_format == 'rst': | |
1614 | tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_' |
|
1619 | tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_' | |
1615 | elif link_format == 'markdown': |
|
1620 | elif link_format == 'markdown': | |
1616 | tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)' |
|
1621 | tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)' | |
1617 | else: |
|
1622 | else: | |
1618 | raise ValueError('Bad link_format:{}'.format(link_format)) |
|
1623 | raise ValueError('Bad link_format:{}'.format(link_format)) | |
1619 |
|
1624 | |||
1620 | (repo_name_cleaned, |
|
1625 | (repo_name_cleaned, | |
1621 | parent_group_name) = RepoGroupModel().\ |
|
1626 | parent_group_name) = RepoGroupModel().\ | |
1622 | _get_group_name_and_parent(repo_name) |
|
1627 | _get_group_name_and_parent(repo_name) | |
1623 |
|
1628 | |||
1624 | # variables replacement |
|
1629 | # variables replacement | |
1625 | named_vars = { |
|
1630 | named_vars = { | |
1626 | 'id': issue_id, |
|
1631 | 'id': issue_id, | |
1627 | 'repo': repo_name, |
|
1632 | 'repo': repo_name, | |
1628 | 'repo_name': repo_name_cleaned, |
|
1633 | 'repo_name': repo_name_cleaned, | |
1629 | 'group_name': parent_group_name |
|
1634 | 'group_name': parent_group_name | |
1630 | } |
|
1635 | } | |
1631 | # named regex variables |
|
1636 | # named regex variables | |
1632 | named_vars.update(match_obj.groupdict()) |
|
1637 | named_vars.update(match_obj.groupdict()) | |
1633 | _url = string.Template(entry['url']).safe_substitute(**named_vars) |
|
1638 | _url = string.Template(entry['url']).safe_substitute(**named_vars) | |
1634 |
|
1639 | |||
1635 | data = { |
|
1640 | data = { | |
1636 | 'pref': pref, |
|
1641 | 'pref': pref, | |
1637 | 'cls': 'issue-tracker-link', |
|
1642 | 'cls': 'issue-tracker-link', | |
1638 | 'url': _url, |
|
1643 | 'url': _url, | |
1639 | 'id-repr': issue_id, |
|
1644 | 'id-repr': issue_id, | |
1640 | 'issue-prefix': entry['pref'], |
|
1645 | 'issue-prefix': entry['pref'], | |
1641 | 'serv': entry['url'], |
|
1646 | 'serv': entry['url'], | |
1642 | } |
|
1647 | } | |
1643 | if return_raw_data: |
|
1648 | if return_raw_data: | |
1644 | return { |
|
1649 | return { | |
1645 | 'id': issue_id, |
|
1650 | 'id': issue_id, | |
1646 | 'url': _url |
|
1651 | 'url': _url | |
1647 | } |
|
1652 | } | |
1648 | return tmpl % data |
|
1653 | return tmpl % data | |
1649 |
|
1654 | |||
1650 |
|
1655 | |||
1651 | def process_patterns(text_string, repo_name, link_format='html'): |
|
1656 | def process_patterns(text_string, repo_name, link_format='html'): | |
1652 | allowed_formats = ['html', 'rst', 'markdown'] |
|
1657 | allowed_formats = ['html', 'rst', 'markdown'] | |
1653 | if link_format not in allowed_formats: |
|
1658 | if link_format not in allowed_formats: | |
1654 | raise ValueError('Link format can be only one of:{} got {}'.format( |
|
1659 | raise ValueError('Link format can be only one of:{} got {}'.format( | |
1655 | allowed_formats, link_format)) |
|
1660 | allowed_formats, link_format)) | |
1656 |
|
1661 | |||
1657 | repo = None |
|
1662 | repo = None | |
1658 | if repo_name: |
|
1663 | if repo_name: | |
1659 | # Retrieving repo_name to avoid invalid repo_name to explode on |
|
1664 | # Retrieving repo_name to avoid invalid repo_name to explode on | |
1660 | # IssueTrackerSettingsModel but still passing invalid name further down |
|
1665 | # IssueTrackerSettingsModel but still passing invalid name further down | |
1661 | repo = Repository.get_by_repo_name(repo_name, cache=True) |
|
1666 | repo = Repository.get_by_repo_name(repo_name, cache=True) | |
1662 |
|
1667 | |||
1663 | settings_model = IssueTrackerSettingsModel(repo=repo) |
|
1668 | settings_model = IssueTrackerSettingsModel(repo=repo) | |
1664 | active_entries = settings_model.get_settings(cache=True) |
|
1669 | active_entries = settings_model.get_settings(cache=True) | |
1665 |
|
1670 | |||
1666 | issues_data = [] |
|
1671 | issues_data = [] | |
1667 | newtext = text_string |
|
1672 | newtext = text_string | |
1668 |
|
1673 | |||
1669 | for uid, entry in active_entries.items(): |
|
1674 | for uid, entry in active_entries.items(): | |
1670 | log.debug('found issue tracker entry with uid %s' % (uid,)) |
|
1675 | log.debug('found issue tracker entry with uid %s' % (uid,)) | |
1671 |
|
1676 | |||
1672 | if not (entry['pat'] and entry['url']): |
|
1677 | if not (entry['pat'] and entry['url']): | |
1673 | log.debug('skipping due to missing data') |
|
1678 | log.debug('skipping due to missing data') | |
1674 | continue |
|
1679 | continue | |
1675 |
|
1680 | |||
1676 | log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s' |
|
1681 | log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s' | |
1677 | % (uid, entry['pat'], entry['url'], entry['pref'])) |
|
1682 | % (uid, entry['pat'], entry['url'], entry['pref'])) | |
1678 |
|
1683 | |||
1679 | try: |
|
1684 | try: | |
1680 | pattern = re.compile(r'%s' % entry['pat']) |
|
1685 | pattern = re.compile(r'%s' % entry['pat']) | |
1681 | except re.error: |
|
1686 | except re.error: | |
1682 | log.exception( |
|
1687 | log.exception( | |
1683 | 'issue tracker pattern: `%s` failed to compile', |
|
1688 | 'issue tracker pattern: `%s` failed to compile', | |
1684 | entry['pat']) |
|
1689 | entry['pat']) | |
1685 | continue |
|
1690 | continue | |
1686 |
|
1691 | |||
1687 | data_func = partial( |
|
1692 | data_func = partial( | |
1688 | _process_url_func, repo_name=repo_name, entry=entry, uid=uid, |
|
1693 | _process_url_func, repo_name=repo_name, entry=entry, uid=uid, | |
1689 | return_raw_data=True) |
|
1694 | return_raw_data=True) | |
1690 |
|
1695 | |||
1691 | for match_obj in pattern.finditer(text_string): |
|
1696 | for match_obj in pattern.finditer(text_string): | |
1692 | issues_data.append(data_func(match_obj)) |
|
1697 | issues_data.append(data_func(match_obj)) | |
1693 |
|
1698 | |||
1694 | url_func = partial( |
|
1699 | url_func = partial( | |
1695 | _process_url_func, repo_name=repo_name, entry=entry, uid=uid, |
|
1700 | _process_url_func, repo_name=repo_name, entry=entry, uid=uid, | |
1696 | link_format=link_format) |
|
1701 | link_format=link_format) | |
1697 |
|
1702 | |||
1698 | newtext = pattern.sub(url_func, newtext) |
|
1703 | newtext = pattern.sub(url_func, newtext) | |
1699 | log.debug('processed prefix:uid `%s`' % (uid,)) |
|
1704 | log.debug('processed prefix:uid `%s`' % (uid,)) | |
1700 |
|
1705 | |||
1701 | return newtext, issues_data |
|
1706 | return newtext, issues_data | |
1702 |
|
1707 | |||
1703 |
|
1708 | |||
1704 | def urlify_commit_message(commit_text, repository=None): |
|
1709 | def urlify_commit_message(commit_text, repository=None): | |
1705 | """ |
|
1710 | """ | |
1706 | Parses given text message and makes proper links. |
|
1711 | Parses given text message and makes proper links. | |
1707 | issues are linked to given issue-server, and rest is a commit link |
|
1712 | issues are linked to given issue-server, and rest is a commit link | |
1708 |
|
1713 | |||
1709 | :param commit_text: |
|
1714 | :param commit_text: | |
1710 | :param repository: |
|
1715 | :param repository: | |
1711 | """ |
|
1716 | """ | |
1712 | from pylons import url # doh, we need to re-import url to mock it later |
|
1717 | from pylons import url # doh, we need to re-import url to mock it later | |
1713 |
|
1718 | |||
1714 | def escaper(string): |
|
1719 | def escaper(string): | |
1715 | return string.replace('<', '<').replace('>', '>') |
|
1720 | return string.replace('<', '<').replace('>', '>') | |
1716 |
|
1721 | |||
1717 | newtext = escaper(commit_text) |
|
1722 | newtext = escaper(commit_text) | |
1718 |
|
1723 | |||
1719 | # extract http/https links and make them real urls |
|
1724 | # extract http/https links and make them real urls | |
1720 | newtext = urlify_text(newtext, safe=False) |
|
1725 | newtext = urlify_text(newtext, safe=False) | |
1721 |
|
1726 | |||
1722 | # urlify commits - extract commit ids and make link out of them, if we have |
|
1727 | # urlify commits - extract commit ids and make link out of them, if we have | |
1723 | # the scope of repository present. |
|
1728 | # the scope of repository present. | |
1724 | if repository: |
|
1729 | if repository: | |
1725 | newtext = urlify_commits(newtext, repository) |
|
1730 | newtext = urlify_commits(newtext, repository) | |
1726 |
|
1731 | |||
1727 | # process issue tracker patterns |
|
1732 | # process issue tracker patterns | |
1728 | newtext, issues = process_patterns(newtext, repository or '') |
|
1733 | newtext, issues = process_patterns(newtext, repository or '') | |
1729 |
|
1734 | |||
1730 | return literal(newtext) |
|
1735 | return literal(newtext) | |
1731 |
|
1736 | |||
1732 |
|
1737 | |||
1733 | def render_binary(repo_name, file_obj): |
|
1738 | def render_binary(repo_name, file_obj): | |
1734 | """ |
|
1739 | """ | |
1735 | Choose how to render a binary file |
|
1740 | Choose how to render a binary file | |
1736 | """ |
|
1741 | """ | |
1737 | filename = file_obj.name |
|
1742 | filename = file_obj.name | |
1738 |
|
1743 | |||
1739 | # images |
|
1744 | # images | |
1740 | for ext in ['*.png', '*.jpg', '*.ico', '*.gif']: |
|
1745 | for ext in ['*.png', '*.jpg', '*.ico', '*.gif']: | |
1741 | if fnmatch.fnmatch(filename, pat=ext): |
|
1746 | if fnmatch.fnmatch(filename, pat=ext): | |
1742 | alt = filename |
|
1747 | alt = filename | |
1743 | src = url('files_raw_home', repo_name=repo_name, |
|
1748 | src = route_path( | |
1744 | revision=file_obj.commit.raw_id, f_path=file_obj.path) |
|
1749 | 'repo_file_raw', repo_name=repo_name, | |
|
1750 | commit_id=file_obj.commit.raw_id, f_path=file_obj.path) | |||
1745 | return literal('<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src)) |
|
1751 | return literal('<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src)) | |
1746 |
|
1752 | |||
1747 |
|
1753 | |||
1748 | def renderer_from_filename(filename, exclude=None): |
|
1754 | def renderer_from_filename(filename, exclude=None): | |
1749 | """ |
|
1755 | """ | |
1750 | choose a renderer based on filename, this works only for text based files |
|
1756 | choose a renderer based on filename, this works only for text based files | |
1751 | """ |
|
1757 | """ | |
1752 |
|
1758 | |||
1753 | # ipython |
|
1759 | # ipython | |
1754 | for ext in ['*.ipynb']: |
|
1760 | for ext in ['*.ipynb']: | |
1755 | if fnmatch.fnmatch(filename, pat=ext): |
|
1761 | if fnmatch.fnmatch(filename, pat=ext): | |
1756 | return 'jupyter' |
|
1762 | return 'jupyter' | |
1757 |
|
1763 | |||
1758 | is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude) |
|
1764 | is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude) | |
1759 | if is_markup: |
|
1765 | if is_markup: | |
1760 | return is_markup |
|
1766 | return is_markup | |
1761 | return None |
|
1767 | return None | |
1762 |
|
1768 | |||
1763 |
|
1769 | |||
1764 | def render(source, renderer='rst', mentions=False, relative_url=None, |
|
1770 | def render(source, renderer='rst', mentions=False, relative_url=None, | |
1765 | repo_name=None): |
|
1771 | repo_name=None): | |
1766 |
|
1772 | |||
1767 | def maybe_convert_relative_links(html_source): |
|
1773 | def maybe_convert_relative_links(html_source): | |
1768 | if relative_url: |
|
1774 | if relative_url: | |
1769 | return relative_links(html_source, relative_url) |
|
1775 | return relative_links(html_source, relative_url) | |
1770 | return html_source |
|
1776 | return html_source | |
1771 |
|
1777 | |||
1772 | if renderer == 'rst': |
|
1778 | if renderer == 'rst': | |
1773 | if repo_name: |
|
1779 | if repo_name: | |
1774 | # process patterns on comments if we pass in repo name |
|
1780 | # process patterns on comments if we pass in repo name | |
1775 | source, issues = process_patterns( |
|
1781 | source, issues = process_patterns( | |
1776 | source, repo_name, link_format='rst') |
|
1782 | source, repo_name, link_format='rst') | |
1777 |
|
1783 | |||
1778 | return literal( |
|
1784 | return literal( | |
1779 | '<div class="rst-block">%s</div>' % |
|
1785 | '<div class="rst-block">%s</div>' % | |
1780 | maybe_convert_relative_links( |
|
1786 | maybe_convert_relative_links( | |
1781 | MarkupRenderer.rst(source, mentions=mentions))) |
|
1787 | MarkupRenderer.rst(source, mentions=mentions))) | |
1782 | elif renderer == 'markdown': |
|
1788 | elif renderer == 'markdown': | |
1783 | if repo_name: |
|
1789 | if repo_name: | |
1784 | # process patterns on comments if we pass in repo name |
|
1790 | # process patterns on comments if we pass in repo name | |
1785 | source, issues = process_patterns( |
|
1791 | source, issues = process_patterns( | |
1786 | source, repo_name, link_format='markdown') |
|
1792 | source, repo_name, link_format='markdown') | |
1787 |
|
1793 | |||
1788 | return literal( |
|
1794 | return literal( | |
1789 | '<div class="markdown-block">%s</div>' % |
|
1795 | '<div class="markdown-block">%s</div>' % | |
1790 | maybe_convert_relative_links( |
|
1796 | maybe_convert_relative_links( | |
1791 | MarkupRenderer.markdown(source, flavored=True, |
|
1797 | MarkupRenderer.markdown(source, flavored=True, | |
1792 | mentions=mentions))) |
|
1798 | mentions=mentions))) | |
1793 | elif renderer == 'jupyter': |
|
1799 | elif renderer == 'jupyter': | |
1794 | return literal( |
|
1800 | return literal( | |
1795 | '<div class="ipynb">%s</div>' % |
|
1801 | '<div class="ipynb">%s</div>' % | |
1796 | maybe_convert_relative_links( |
|
1802 | maybe_convert_relative_links( | |
1797 | MarkupRenderer.jupyter(source))) |
|
1803 | MarkupRenderer.jupyter(source))) | |
1798 |
|
1804 | |||
1799 | # None means just show the file-source |
|
1805 | # None means just show the file-source | |
1800 | return None |
|
1806 | return None | |
1801 |
|
1807 | |||
1802 |
|
1808 | |||
1803 | def commit_status(repo, commit_id): |
|
1809 | def commit_status(repo, commit_id): | |
1804 | return ChangesetStatusModel().get_status(repo, commit_id) |
|
1810 | return ChangesetStatusModel().get_status(repo, commit_id) | |
1805 |
|
1811 | |||
1806 |
|
1812 | |||
1807 | def commit_status_lbl(commit_status): |
|
1813 | def commit_status_lbl(commit_status): | |
1808 | return dict(ChangesetStatus.STATUSES).get(commit_status) |
|
1814 | return dict(ChangesetStatus.STATUSES).get(commit_status) | |
1809 |
|
1815 | |||
1810 |
|
1816 | |||
1811 | def commit_time(repo_name, commit_id): |
|
1817 | def commit_time(repo_name, commit_id): | |
1812 | repo = Repository.get_by_repo_name(repo_name) |
|
1818 | repo = Repository.get_by_repo_name(repo_name) | |
1813 | commit = repo.get_commit(commit_id=commit_id) |
|
1819 | commit = repo.get_commit(commit_id=commit_id) | |
1814 | return commit.date |
|
1820 | return commit.date | |
1815 |
|
1821 | |||
1816 |
|
1822 | |||
1817 | def get_permission_name(key): |
|
1823 | def get_permission_name(key): | |
1818 | return dict(Permission.PERMS).get(key) |
|
1824 | return dict(Permission.PERMS).get(key) | |
1819 |
|
1825 | |||
1820 |
|
1826 | |||
1821 | def journal_filter_help(request): |
|
1827 | def journal_filter_help(request): | |
1822 | _ = request.translate |
|
1828 | _ = request.translate | |
1823 |
|
1829 | |||
1824 | return _( |
|
1830 | return _( | |
1825 | 'Example filter terms:\n' + |
|
1831 | 'Example filter terms:\n' + | |
1826 | ' repository:vcs\n' + |
|
1832 | ' repository:vcs\n' + | |
1827 | ' username:marcin\n' + |
|
1833 | ' username:marcin\n' + | |
1828 | ' username:(NOT marcin)\n' + |
|
1834 | ' username:(NOT marcin)\n' + | |
1829 | ' action:*push*\n' + |
|
1835 | ' action:*push*\n' + | |
1830 | ' ip:127.0.0.1\n' + |
|
1836 | ' ip:127.0.0.1\n' + | |
1831 | ' date:20120101\n' + |
|
1837 | ' date:20120101\n' + | |
1832 | ' date:[20120101100000 TO 20120102]\n' + |
|
1838 | ' date:[20120101100000 TO 20120102]\n' + | |
1833 | '\n' + |
|
1839 | '\n' + | |
1834 | 'Generate wildcards using \'*\' character:\n' + |
|
1840 | 'Generate wildcards using \'*\' character:\n' + | |
1835 | ' "repository:vcs*" - search everything starting with \'vcs\'\n' + |
|
1841 | ' "repository:vcs*" - search everything starting with \'vcs\'\n' + | |
1836 | ' "repository:*vcs*" - search for repository containing \'vcs\'\n' + |
|
1842 | ' "repository:*vcs*" - search for repository containing \'vcs\'\n' + | |
1837 | '\n' + |
|
1843 | '\n' + | |
1838 | 'Optional AND / OR operators in queries\n' + |
|
1844 | 'Optional AND / OR operators in queries\n' + | |
1839 | ' "repository:vcs OR repository:test"\n' + |
|
1845 | ' "repository:vcs OR repository:test"\n' + | |
1840 | ' "username:test AND repository:test*"\n' |
|
1846 | ' "username:test AND repository:test*"\n' | |
1841 | ) |
|
1847 | ) | |
1842 |
|
1848 | |||
1843 |
|
1849 | |||
1844 | def search_filter_help(searcher, request): |
|
1850 | def search_filter_help(searcher, request): | |
1845 | _ = request.translate |
|
1851 | _ = request.translate | |
1846 |
|
1852 | |||
1847 | terms = '' |
|
1853 | terms = '' | |
1848 | return _( |
|
1854 | return _( | |
1849 | 'Example filter terms for `{searcher}` search:\n' + |
|
1855 | 'Example filter terms for `{searcher}` search:\n' + | |
1850 | '{terms}\n' + |
|
1856 | '{terms}\n' + | |
1851 | 'Generate wildcards using \'*\' character:\n' + |
|
1857 | 'Generate wildcards using \'*\' character:\n' + | |
1852 | ' "repo_name:vcs*" - search everything starting with \'vcs\'\n' + |
|
1858 | ' "repo_name:vcs*" - search everything starting with \'vcs\'\n' + | |
1853 | ' "repo_name:*vcs*" - search for repository containing \'vcs\'\n' + |
|
1859 | ' "repo_name:*vcs*" - search for repository containing \'vcs\'\n' + | |
1854 | '\n' + |
|
1860 | '\n' + | |
1855 | 'Optional AND / OR operators in queries\n' + |
|
1861 | 'Optional AND / OR operators in queries\n' + | |
1856 | ' "repo_name:vcs OR repo_name:test"\n' + |
|
1862 | ' "repo_name:vcs OR repo_name:test"\n' + | |
1857 | ' "owner:test AND repo_name:test*"\n' + |
|
1863 | ' "owner:test AND repo_name:test*"\n' + | |
1858 | 'More: {search_doc}' |
|
1864 | 'More: {search_doc}' | |
1859 | ).format(searcher=searcher.name, |
|
1865 | ).format(searcher=searcher.name, | |
1860 | terms=terms, search_doc=searcher.query_lang_doc) |
|
1866 | terms=terms, search_doc=searcher.query_lang_doc) | |
1861 |
|
1867 | |||
1862 |
|
1868 | |||
1863 | def not_mapped_error(repo_name): |
|
1869 | def not_mapped_error(repo_name): | |
1864 | from rhodecode.translation import _ |
|
1870 | from rhodecode.translation import _ | |
1865 | flash(_('%s repository is not mapped to db perhaps' |
|
1871 | flash(_('%s repository is not mapped to db perhaps' | |
1866 | ' it was created or renamed from the filesystem' |
|
1872 | ' it was created or renamed from the filesystem' | |
1867 | ' please run the application again' |
|
1873 | ' please run the application again' | |
1868 | ' in order to rescan repositories') % repo_name, category='error') |
|
1874 | ' in order to rescan repositories') % repo_name, category='error') | |
1869 |
|
1875 | |||
1870 |
|
1876 | |||
1871 | def ip_range(ip_addr): |
|
1877 | def ip_range(ip_addr): | |
1872 | from rhodecode.model.db import UserIpMap |
|
1878 | from rhodecode.model.db import UserIpMap | |
1873 | s, e = UserIpMap._get_ip_range(ip_addr) |
|
1879 | s, e = UserIpMap._get_ip_range(ip_addr) | |
1874 | return '%s - %s' % (s, e) |
|
1880 | return '%s - %s' % (s, e) | |
1875 |
|
1881 | |||
1876 |
|
1882 | |||
1877 | def form(url, method='post', needs_csrf_token=True, **attrs): |
|
1883 | def form(url, method='post', needs_csrf_token=True, **attrs): | |
1878 | """Wrapper around webhelpers.tags.form to prevent CSRF attacks.""" |
|
1884 | """Wrapper around webhelpers.tags.form to prevent CSRF attacks.""" | |
1879 | if method.lower() != 'get' and needs_csrf_token: |
|
1885 | if method.lower() != 'get' and needs_csrf_token: | |
1880 | raise Exception( |
|
1886 | raise Exception( | |
1881 | 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' + |
|
1887 | 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' + | |
1882 | 'CSRF token. If the endpoint does not require such token you can ' + |
|
1888 | 'CSRF token. If the endpoint does not require such token you can ' + | |
1883 | 'explicitly set the parameter needs_csrf_token to false.') |
|
1889 | 'explicitly set the parameter needs_csrf_token to false.') | |
1884 |
|
1890 | |||
1885 | return wh_form(url, method=method, **attrs) |
|
1891 | return wh_form(url, method=method, **attrs) | |
1886 |
|
1892 | |||
1887 |
|
1893 | |||
1888 | def secure_form(url, method="POST", multipart=False, **attrs): |
|
1894 | def secure_form(url, method="POST", multipart=False, **attrs): | |
1889 | """Start a form tag that points the action to an url. This |
|
1895 | """Start a form tag that points the action to an url. This | |
1890 | form tag will also include the hidden field containing |
|
1896 | form tag will also include the hidden field containing | |
1891 | the auth token. |
|
1897 | the auth token. | |
1892 |
|
1898 | |||
1893 | The url options should be given either as a string, or as a |
|
1899 | The url options should be given either as a string, or as a | |
1894 | ``url()`` function. The method for the form defaults to POST. |
|
1900 | ``url()`` function. The method for the form defaults to POST. | |
1895 |
|
1901 | |||
1896 | Options: |
|
1902 | Options: | |
1897 |
|
1903 | |||
1898 | ``multipart`` |
|
1904 | ``multipart`` | |
1899 | If set to True, the enctype is set to "multipart/form-data". |
|
1905 | If set to True, the enctype is set to "multipart/form-data". | |
1900 | ``method`` |
|
1906 | ``method`` | |
1901 | The method to use when submitting the form, usually either |
|
1907 | The method to use when submitting the form, usually either | |
1902 | "GET" or "POST". If "PUT", "DELETE", or another verb is used, a |
|
1908 | "GET" or "POST". If "PUT", "DELETE", or another verb is used, a | |
1903 | hidden input with name _method is added to simulate the verb |
|
1909 | hidden input with name _method is added to simulate the verb | |
1904 | over POST. |
|
1910 | over POST. | |
1905 |
|
1911 | |||
1906 | """ |
|
1912 | """ | |
1907 | from webhelpers.pylonslib.secure_form import insecure_form |
|
1913 | from webhelpers.pylonslib.secure_form import insecure_form | |
1908 | form = insecure_form(url, method, multipart, **attrs) |
|
1914 | form = insecure_form(url, method, multipart, **attrs) | |
1909 |
|
1915 | |||
1910 | session = None |
|
1916 | session = None | |
1911 | # TODO(marcink): after pyramid migration require request variable ALWAYS |
|
1917 | # TODO(marcink): after pyramid migration require request variable ALWAYS | |
1912 | if 'request' in attrs: |
|
1918 | if 'request' in attrs: | |
1913 | session = attrs['request'].session |
|
1919 | session = attrs['request'].session | |
1914 |
|
1920 | |||
1915 | token = literal( |
|
1921 | token = literal( | |
1916 | '<input type="hidden" id="{}" name="{}" value="{}">'.format( |
|
1922 | '<input type="hidden" id="{}" name="{}" value="{}">'.format( | |
1917 | csrf_token_key, csrf_token_key, get_csrf_token(session))) |
|
1923 | csrf_token_key, csrf_token_key, get_csrf_token(session))) | |
1918 |
|
1924 | |||
1919 | return literal("%s\n%s" % (form, token)) |
|
1925 | return literal("%s\n%s" % (form, token)) | |
1920 |
|
1926 | |||
1921 |
|
1927 | |||
1922 | def dropdownmenu(name, selected, options, enable_filter=False, **attrs): |
|
1928 | def dropdownmenu(name, selected, options, enable_filter=False, **attrs): | |
1923 | select_html = select(name, selected, options, **attrs) |
|
1929 | select_html = select(name, selected, options, **attrs) | |
1924 | select2 = """ |
|
1930 | select2 = """ | |
1925 | <script> |
|
1931 | <script> | |
1926 | $(document).ready(function() { |
|
1932 | $(document).ready(function() { | |
1927 | $('#%s').select2({ |
|
1933 | $('#%s').select2({ | |
1928 | containerCssClass: 'drop-menu', |
|
1934 | containerCssClass: 'drop-menu', | |
1929 | dropdownCssClass: 'drop-menu-dropdown', |
|
1935 | dropdownCssClass: 'drop-menu-dropdown', | |
1930 | dropdownAutoWidth: true%s |
|
1936 | dropdownAutoWidth: true%s | |
1931 | }); |
|
1937 | }); | |
1932 | }); |
|
1938 | }); | |
1933 | </script> |
|
1939 | </script> | |
1934 | """ |
|
1940 | """ | |
1935 | filter_option = """, |
|
1941 | filter_option = """, | |
1936 | minimumResultsForSearch: -1 |
|
1942 | minimumResultsForSearch: -1 | |
1937 | """ |
|
1943 | """ | |
1938 | input_id = attrs.get('id') or name |
|
1944 | input_id = attrs.get('id') or name | |
1939 | filter_enabled = "" if enable_filter else filter_option |
|
1945 | filter_enabled = "" if enable_filter else filter_option | |
1940 | select_script = literal(select2 % (input_id, filter_enabled)) |
|
1946 | select_script = literal(select2 % (input_id, filter_enabled)) | |
1941 |
|
1947 | |||
1942 | return literal(select_html+select_script) |
|
1948 | return literal(select_html+select_script) | |
1943 |
|
1949 | |||
1944 |
|
1950 | |||
1945 | def get_visual_attr(tmpl_context_var, attr_name): |
|
1951 | def get_visual_attr(tmpl_context_var, attr_name): | |
1946 | """ |
|
1952 | """ | |
1947 | A safe way to get a variable from visual variable of template context |
|
1953 | A safe way to get a variable from visual variable of template context | |
1948 |
|
1954 | |||
1949 | :param tmpl_context_var: instance of tmpl_context, usually present as `c` |
|
1955 | :param tmpl_context_var: instance of tmpl_context, usually present as `c` | |
1950 | :param attr_name: name of the attribute we fetch from the c.visual |
|
1956 | :param attr_name: name of the attribute we fetch from the c.visual | |
1951 | """ |
|
1957 | """ | |
1952 | visual = getattr(tmpl_context_var, 'visual', None) |
|
1958 | visual = getattr(tmpl_context_var, 'visual', None) | |
1953 | if not visual: |
|
1959 | if not visual: | |
1954 | return |
|
1960 | return | |
1955 | else: |
|
1961 | else: | |
1956 | return getattr(visual, attr_name, None) |
|
1962 | return getattr(visual, attr_name, None) | |
1957 |
|
1963 | |||
1958 |
|
1964 | |||
1959 | def get_last_path_part(file_node): |
|
1965 | def get_last_path_part(file_node): | |
1960 | if not file_node.path: |
|
1966 | if not file_node.path: | |
1961 | return u'' |
|
1967 | return u'' | |
1962 |
|
1968 | |||
1963 | path = safe_unicode(file_node.path.split('/')[-1]) |
|
1969 | path = safe_unicode(file_node.path.split('/')[-1]) | |
1964 | return u'../' + path |
|
1970 | return u'../' + path | |
1965 |
|
1971 | |||
1966 |
|
1972 | |||
1967 | def route_url(*args, **kwargs): |
|
1973 | def route_url(*args, **kwargs): | |
1968 | """ |
|
1974 | """ | |
1969 | Wrapper around pyramids `route_url` (fully qualified url) function. |
|
1975 | Wrapper around pyramids `route_url` (fully qualified url) function. | |
1970 | It is used to generate URLs from within pylons views or templates. |
|
1976 | It is used to generate URLs from within pylons views or templates. | |
1971 | This will be removed when pyramid migration if finished. |
|
1977 | This will be removed when pyramid migration if finished. | |
1972 | """ |
|
1978 | """ | |
1973 | req = get_current_request() |
|
1979 | req = get_current_request() | |
1974 | return req.route_url(*args, **kwargs) |
|
1980 | return req.route_url(*args, **kwargs) | |
1975 |
|
1981 | |||
1976 |
|
1982 | |||
1977 | def route_path(*args, **kwargs): |
|
1983 | def route_path(*args, **kwargs): | |
1978 | """ |
|
1984 | """ | |
1979 | Wrapper around pyramids `route_path` function. It is used to generate |
|
1985 | Wrapper around pyramids `route_path` function. It is used to generate | |
1980 | URLs from within pylons views or templates. This will be removed when |
|
1986 | URLs from within pylons views or templates. This will be removed when | |
1981 | pyramid migration if finished. |
|
1987 | pyramid migration if finished. | |
1982 | """ |
|
1988 | """ | |
1983 | req = get_current_request() |
|
1989 | req = get_current_request() | |
1984 | return req.route_path(*args, **kwargs) |
|
1990 | return req.route_path(*args, **kwargs) | |
1985 |
|
1991 | |||
1986 |
|
1992 | |||
1987 | def route_path_or_none(*args, **kwargs): |
|
1993 | def route_path_or_none(*args, **kwargs): | |
1988 | try: |
|
1994 | try: | |
1989 | return route_path(*args, **kwargs) |
|
1995 | return route_path(*args, **kwargs) | |
1990 | except KeyError: |
|
1996 | except KeyError: | |
1991 | return None |
|
1997 | return None | |
1992 |
|
1998 | |||
1993 |
|
1999 | |||
1994 | def static_url(*args, **kwds): |
|
2000 | def static_url(*args, **kwds): | |
1995 | """ |
|
2001 | """ | |
1996 | Wrapper around pyramids `route_path` function. It is used to generate |
|
2002 | Wrapper around pyramids `route_path` function. It is used to generate | |
1997 | URLs from within pylons views or templates. This will be removed when |
|
2003 | URLs from within pylons views or templates. This will be removed when | |
1998 | pyramid migration if finished. |
|
2004 | pyramid migration if finished. | |
1999 | """ |
|
2005 | """ | |
2000 | req = get_current_request() |
|
2006 | req = get_current_request() | |
2001 | return req.static_url(*args, **kwds) |
|
2007 | return req.static_url(*args, **kwds) | |
2002 |
|
2008 | |||
2003 |
|
2009 | |||
2004 | def resource_path(*args, **kwds): |
|
2010 | def resource_path(*args, **kwds): | |
2005 | """ |
|
2011 | """ | |
2006 | Wrapper around pyramids `route_path` function. It is used to generate |
|
2012 | Wrapper around pyramids `route_path` function. It is used to generate | |
2007 | URLs from within pylons views or templates. This will be removed when |
|
2013 | URLs from within pylons views or templates. This will be removed when | |
2008 | pyramid migration if finished. |
|
2014 | pyramid migration if finished. | |
2009 | """ |
|
2015 | """ | |
2010 | req = get_current_request() |
|
2016 | req = get_current_request() | |
2011 | return req.resource_path(*args, **kwds) |
|
2017 | return req.resource_path(*args, **kwds) | |
2012 |
|
2018 | |||
2013 |
|
2019 | |||
2014 | def api_call_example(method, args): |
|
2020 | def api_call_example(method, args): | |
2015 | """ |
|
2021 | """ | |
2016 | Generates an API call example via CURL |
|
2022 | Generates an API call example via CURL | |
2017 | """ |
|
2023 | """ | |
2018 | args_json = json.dumps(OrderedDict([ |
|
2024 | args_json = json.dumps(OrderedDict([ | |
2019 | ('id', 1), |
|
2025 | ('id', 1), | |
2020 | ('auth_token', 'SECRET'), |
|
2026 | ('auth_token', 'SECRET'), | |
2021 | ('method', method), |
|
2027 | ('method', method), | |
2022 | ('args', args) |
|
2028 | ('args', args) | |
2023 | ])) |
|
2029 | ])) | |
2024 | return literal( |
|
2030 | return literal( | |
2025 | "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'" |
|
2031 | "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'" | |
2026 | "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, " |
|
2032 | "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, " | |
2027 | "and needs to be of `api calls` role." |
|
2033 | "and needs to be of `api calls` role." | |
2028 | .format( |
|
2034 | .format( | |
2029 | api_url=route_url('apiv2'), |
|
2035 | api_url=route_url('apiv2'), | |
2030 | token_url=route_url('my_account_auth_tokens'), |
|
2036 | token_url=route_url('my_account_auth_tokens'), | |
2031 | data=args_json)) |
|
2037 | data=args_json)) | |
2032 |
|
2038 | |||
2033 |
|
2039 | |||
2034 | def notification_description(notification, request): |
|
2040 | def notification_description(notification, request): | |
2035 | """ |
|
2041 | """ | |
2036 | Generate notification human readable description based on notification type |
|
2042 | Generate notification human readable description based on notification type | |
2037 | """ |
|
2043 | """ | |
2038 | from rhodecode.model.notification import NotificationModel |
|
2044 | from rhodecode.model.notification import NotificationModel | |
2039 | return NotificationModel().make_description( |
|
2045 | return NotificationModel().make_description( | |
2040 | notification, translate=request.translate) |
|
2046 | notification, translate=request.translate) |
@@ -1,101 +1,101 b'' | |||||
1 | // Global keyboard bindings |
|
1 | // Global keyboard bindings | |
2 |
|
2 | |||
3 | function setRCMouseBindings(repoName, repoLandingRev) { |
|
3 | function setRCMouseBindings(repoName, repoLandingRev) { | |
4 |
|
4 | |||
5 | /** custom callback for supressing mousetrap from firing */ |
|
5 | /** custom callback for supressing mousetrap from firing */ | |
6 | Mousetrap.stopCallback = function(e, element) { |
|
6 | Mousetrap.stopCallback = function(e, element) { | |
7 | // if the element has the class "mousetrap" then no need to stop |
|
7 | // if the element has the class "mousetrap" then no need to stop | |
8 | if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) { |
|
8 | if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) { | |
9 | return false; |
|
9 | return false; | |
10 | } |
|
10 | } | |
11 |
|
11 | |||
12 | // stop for input, select, and textarea |
|
12 | // stop for input, select, and textarea | |
13 | return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable; |
|
13 | return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable; | |
14 | }; |
|
14 | }; | |
15 |
|
15 | |||
16 | // general help "?" |
|
16 | // general help "?" | |
17 | Mousetrap.bind(['?'], function(e) { |
|
17 | Mousetrap.bind(['?'], function(e) { | |
18 | $('#help_kb').modal({}); |
|
18 | $('#help_kb').modal({}); | |
19 | }); |
|
19 | }); | |
20 |
|
20 | |||
21 | // / open the quick filter |
|
21 | // / open the quick filter | |
22 | Mousetrap.bind(['/'], function(e) { |
|
22 | Mousetrap.bind(['/'], function(e) { | |
23 | $('#repo_switcher').select2('open'); |
|
23 | $('#repo_switcher').select2('open'); | |
24 |
|
24 | |||
25 | // return false to prevent default browser behavior |
|
25 | // return false to prevent default browser behavior | |
26 | // and stop event from bubbling |
|
26 | // and stop event from bubbling | |
27 | return false; |
|
27 | return false; | |
28 | }); |
|
28 | }); | |
29 |
|
29 | |||
30 | // ctrl/command+b, show the the main bar |
|
30 | // ctrl/command+b, show the the main bar | |
31 | Mousetrap.bind(['command+b', 'ctrl+b'], function(e) { |
|
31 | Mousetrap.bind(['command+b', 'ctrl+b'], function(e) { | |
32 | var $headerInner = $('#header-inner'), |
|
32 | var $headerInner = $('#header-inner'), | |
33 | $content = $('#content'); |
|
33 | $content = $('#content'); | |
34 | if ($headerInner.hasClass('hover') && $content.hasClass('hover')) { |
|
34 | if ($headerInner.hasClass('hover') && $content.hasClass('hover')) { | |
35 | $headerInner.removeClass('hover'); |
|
35 | $headerInner.removeClass('hover'); | |
36 | $content.removeClass('hover'); |
|
36 | $content.removeClass('hover'); | |
37 | } else { |
|
37 | } else { | |
38 | $headerInner.addClass('hover'); |
|
38 | $headerInner.addClass('hover'); | |
39 | $content.addClass('hover'); |
|
39 | $content.addClass('hover'); | |
40 | } |
|
40 | } | |
41 | return false; |
|
41 | return false; | |
42 | }); |
|
42 | }); | |
43 |
|
43 | |||
44 | // general nav g + action |
|
44 | // general nav g + action | |
45 | Mousetrap.bind(['g h'], function(e) { |
|
45 | Mousetrap.bind(['g h'], function(e) { | |
46 | window.location = pyroutes.url('home'); |
|
46 | window.location = pyroutes.url('home'); | |
47 | }); |
|
47 | }); | |
48 | Mousetrap.bind(['g g'], function(e) { |
|
48 | Mousetrap.bind(['g g'], function(e) { | |
49 | window.location = pyroutes.url('gists_show', {'private': 1}); |
|
49 | window.location = pyroutes.url('gists_show', {'private': 1}); | |
50 | }); |
|
50 | }); | |
51 | Mousetrap.bind(['g G'], function(e) { |
|
51 | Mousetrap.bind(['g G'], function(e) { | |
52 | window.location = pyroutes.url('gists_show', {'public': 1}); |
|
52 | window.location = pyroutes.url('gists_show', {'public': 1}); | |
53 | }); |
|
53 | }); | |
54 | Mousetrap.bind(['n g'], function(e) { |
|
54 | Mousetrap.bind(['n g'], function(e) { | |
55 | window.location = pyroutes.url('gists_new'); |
|
55 | window.location = pyroutes.url('gists_new'); | |
56 | }); |
|
56 | }); | |
57 | Mousetrap.bind(['n r'], function(e) { |
|
57 | Mousetrap.bind(['n r'], function(e) { | |
58 | window.location = pyroutes.url('new_repo'); |
|
58 | window.location = pyroutes.url('new_repo'); | |
59 | }); |
|
59 | }); | |
60 |
|
60 | |||
61 | if (repoName && repoName != '') { |
|
61 | if (repoName && repoName != '') { | |
62 | // nav in repo context |
|
62 | // nav in repo context | |
63 | Mousetrap.bind(['g s'], function(e) { |
|
63 | Mousetrap.bind(['g s'], function(e) { | |
64 | window.location = pyroutes.url( |
|
64 | window.location = pyroutes.url( | |
65 | 'repo_summary', {'repo_name': repoName}); |
|
65 | 'repo_summary', {'repo_name': repoName}); | |
66 | }); |
|
66 | }); | |
67 | Mousetrap.bind(['g c'], function(e) { |
|
67 | Mousetrap.bind(['g c'], function(e) { | |
68 | window.location = pyroutes.url( |
|
68 | window.location = pyroutes.url( | |
69 | 'changelog_home', {'repo_name': repoName}); |
|
69 | 'changelog_home', {'repo_name': repoName}); | |
70 | }); |
|
70 | }); | |
71 | Mousetrap.bind(['g F'], function(e) { |
|
71 | Mousetrap.bind(['g F'], function(e) { | |
72 | window.location = pyroutes.url( |
|
72 | window.location = pyroutes.url( | |
73 |
'files |
|
73 | 'repo_files', | |
74 | { |
|
74 | { | |
75 | 'repo_name': repoName, |
|
75 | 'repo_name': repoName, | |
76 |
' |
|
76 | 'commit_id': repoLandingRev, | |
77 | 'f_path': '', |
|
77 | 'f_path': '', | |
78 | 'search': '1' |
|
78 | 'search': '1' | |
79 | }); |
|
79 | }); | |
80 | }); |
|
80 | }); | |
81 | Mousetrap.bind(['g f'], function(e) { |
|
81 | Mousetrap.bind(['g f'], function(e) { | |
82 | window.location = pyroutes.url( |
|
82 | window.location = pyroutes.url( | |
83 |
'files |
|
83 | 'repo_files', | |
84 | { |
|
84 | { | |
85 | 'repo_name': repoName, |
|
85 | 'repo_name': repoName, | |
86 |
' |
|
86 | 'commit_id': repoLandingRev, | |
87 | 'f_path': '' |
|
87 | 'f_path': '' | |
88 | }); |
|
88 | }); | |
89 | }); |
|
89 | }); | |
90 | Mousetrap.bind(['g o'], function(e) { |
|
90 | Mousetrap.bind(['g o'], function(e) { | |
91 | window.location = pyroutes.url( |
|
91 | window.location = pyroutes.url( | |
92 | 'edit_repo', {'repo_name': repoName}); |
|
92 | 'edit_repo', {'repo_name': repoName}); | |
93 | }); |
|
93 | }); | |
94 | Mousetrap.bind(['g O'], function(e) { |
|
94 | Mousetrap.bind(['g O'], function(e) { | |
95 | window.location = pyroutes.url( |
|
95 | window.location = pyroutes.url( | |
96 | 'edit_repo_perms', {'repo_name': repoName}); |
|
96 | 'edit_repo_perms', {'repo_name': repoName}); | |
97 | }); |
|
97 | }); | |
98 | } |
|
98 | } | |
99 | } |
|
99 | } | |
100 |
|
100 | |||
101 | setRCMouseBindings(templateContext.repo_name, templateContext.repo_landing_commit); |
|
101 | setRCMouseBindings(templateContext.repo_name, templateContext.repo_landing_commit); |
@@ -1,183 +1,198 b'' | |||||
1 |
|
1 | |||
2 | /****************************************************************************** |
|
2 | /****************************************************************************** | |
3 | * * |
|
3 | * * | |
4 | * DO NOT CHANGE THIS FILE MANUALLY * |
|
4 | * DO NOT CHANGE THIS FILE MANUALLY * | |
5 | * * |
|
5 | * * | |
6 | * * |
|
6 | * * | |
7 | * This file is automatically generated when the app starts up with * |
|
7 | * This file is automatically generated when the app starts up with * | |
8 | * generate_js_files = true * |
|
8 | * generate_js_files = true * | |
9 | * * |
|
9 | * * | |
10 | * To add a route here pass jsroute=True to the route definition in the app * |
|
10 | * To add a route here pass jsroute=True to the route definition in the app * | |
11 | * * |
|
11 | * * | |
12 | ******************************************************************************/ |
|
12 | ******************************************************************************/ | |
13 | function registerRCRoutes() { |
|
13 | function registerRCRoutes() { | |
14 | // routes registration |
|
14 | // routes registration | |
15 | pyroutes.register('new_repo', '/_admin/create_repository', []); |
|
15 | pyroutes.register('new_repo', '/_admin/create_repository', []); | |
16 | pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']); |
|
16 | pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']); | |
17 | pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']); |
|
17 | pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']); | |
18 | pyroutes.register('toggle_following', '/_admin/toggle_following', []); |
|
18 | pyroutes.register('toggle_following', '/_admin/toggle_following', []); | |
19 | pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']); |
|
19 | pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']); | |
20 | pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']); |
|
20 | pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']); | |
21 | pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']); |
|
21 | pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']); | |
22 | pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']); |
|
22 | pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']); | |
23 | pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']); |
|
23 | pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']); | |
24 | pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']); |
|
24 | pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']); | |
25 | pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']); |
|
25 | pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']); | |
26 | pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']); |
|
26 | pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']); | |
27 | pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']); |
|
27 | pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']); | |
28 | pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']); |
|
28 | pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']); | |
29 | pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']); |
|
29 | pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']); | |
30 | pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']); |
|
30 | pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']); | |
31 | pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']); |
|
31 | pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']); | |
32 | pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']); |
|
32 | pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']); | |
33 | pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']); |
|
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']); |
|
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']); |
|
35 | pyroutes.register('changelog_elements', '/%(repo_name)s/changelog_details', ['repo_name']); | |
36 | pyroutes.register('files_home', '/%(repo_name)s/files/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']); |
|
|||
37 | pyroutes.register('files_history_home', '/%(repo_name)s/history/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']); |
|
|||
38 | pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']); |
|
|||
39 | pyroutes.register('files_annotate_home', '/%(repo_name)s/annotate/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']); |
|
|||
40 | pyroutes.register('files_annotate_previous', '/%(repo_name)s/annotate-previous/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']); |
|
|||
41 | pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']); |
|
|||
42 | pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']); |
|
|||
43 | pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); |
|
|||
44 | pyroutes.register('favicon', '/favicon.ico', []); |
|
36 | pyroutes.register('favicon', '/favicon.ico', []); | |
45 | pyroutes.register('robots', '/robots.txt', []); |
|
37 | pyroutes.register('robots', '/robots.txt', []); | |
46 | pyroutes.register('auth_home', '/_admin/auth*traverse', []); |
|
38 | pyroutes.register('auth_home', '/_admin/auth*traverse', []); | |
47 | pyroutes.register('global_integrations_new', '/_admin/integrations/new', []); |
|
39 | pyroutes.register('global_integrations_new', '/_admin/integrations/new', []); | |
48 | pyroutes.register('global_integrations_home', '/_admin/integrations', []); |
|
40 | pyroutes.register('global_integrations_home', '/_admin/integrations', []); | |
49 | pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']); |
|
41 | pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']); | |
50 | pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']); |
|
42 | pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']); | |
51 | pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']); |
|
43 | pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']); | |
52 | pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']); |
|
44 | pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']); | |
53 | pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']); |
|
45 | pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']); | |
54 | pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']); |
|
46 | pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']); | |
55 | pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']); |
|
47 | pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']); | |
56 | pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']); |
|
48 | pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']); | |
57 | pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']); |
|
49 | pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']); | |
58 | pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']); |
|
50 | pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']); | |
59 | pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']); |
|
51 | pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']); | |
60 | pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']); |
|
52 | pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']); | |
61 | pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']); |
|
53 | pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']); | |
62 | pyroutes.register('ops_ping', '/_admin/ops/ping', []); |
|
54 | pyroutes.register('ops_ping', '/_admin/ops/ping', []); | |
63 | pyroutes.register('ops_error_test', '/_admin/ops/error', []); |
|
55 | pyroutes.register('ops_error_test', '/_admin/ops/error', []); | |
64 | pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []); |
|
56 | pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []); | |
65 | pyroutes.register('admin_home', '/_admin', []); |
|
57 | pyroutes.register('admin_home', '/_admin', []); | |
66 | pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []); |
|
58 | pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []); | |
67 | pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']); |
|
59 | pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']); | |
68 | pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']); |
|
60 | pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']); | |
69 | pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']); |
|
61 | pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']); | |
70 | pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []); |
|
62 | pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []); | |
71 | pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []); |
|
63 | pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []); | |
72 | pyroutes.register('admin_settings_system', '/_admin/settings/system', []); |
|
64 | pyroutes.register('admin_settings_system', '/_admin/settings/system', []); | |
73 | pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []); |
|
65 | pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []); | |
74 | pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []); |
|
66 | pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []); | |
75 | pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []); |
|
67 | pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []); | |
76 | pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []); |
|
68 | pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []); | |
77 | pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []); |
|
69 | pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []); | |
78 | pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []); |
|
70 | pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []); | |
79 | pyroutes.register('users', '/_admin/users', []); |
|
71 | pyroutes.register('users', '/_admin/users', []); | |
80 | pyroutes.register('users_data', '/_admin/users_data', []); |
|
72 | pyroutes.register('users_data', '/_admin/users_data', []); | |
81 | pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']); |
|
73 | pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']); | |
82 | pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']); |
|
74 | pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']); | |
83 | pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']); |
|
75 | pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']); | |
84 | pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']); |
|
76 | pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']); | |
85 | pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']); |
|
77 | pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']); | |
86 | pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']); |
|
78 | pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']); | |
87 | pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']); |
|
79 | pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']); | |
88 | pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']); |
|
80 | pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']); | |
89 | pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']); |
|
81 | pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']); | |
90 | pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']); |
|
82 | pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']); | |
91 | pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']); |
|
83 | pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']); | |
92 | pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']); |
|
84 | pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']); | |
93 | pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []); |
|
85 | pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []); | |
94 | pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []); |
|
86 | pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []); | |
95 | pyroutes.register('channelstream_proxy', '/_channelstream', []); |
|
87 | pyroutes.register('channelstream_proxy', '/_channelstream', []); | |
96 | pyroutes.register('login', '/_admin/login', []); |
|
88 | pyroutes.register('login', '/_admin/login', []); | |
97 | pyroutes.register('logout', '/_admin/logout', []); |
|
89 | pyroutes.register('logout', '/_admin/logout', []); | |
98 | pyroutes.register('register', '/_admin/register', []); |
|
90 | pyroutes.register('register', '/_admin/register', []); | |
99 | pyroutes.register('reset_password', '/_admin/password_reset', []); |
|
91 | pyroutes.register('reset_password', '/_admin/password_reset', []); | |
100 | pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []); |
|
92 | pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []); | |
101 | pyroutes.register('home', '/', []); |
|
93 | pyroutes.register('home', '/', []); | |
102 | pyroutes.register('user_autocomplete_data', '/_users', []); |
|
94 | pyroutes.register('user_autocomplete_data', '/_users', []); | |
103 | pyroutes.register('user_group_autocomplete_data', '/_user_groups', []); |
|
95 | pyroutes.register('user_group_autocomplete_data', '/_user_groups', []); | |
104 | pyroutes.register('repo_list_data', '/_repos', []); |
|
96 | pyroutes.register('repo_list_data', '/_repos', []); | |
105 | pyroutes.register('goto_switcher_data', '/_goto_data', []); |
|
97 | pyroutes.register('goto_switcher_data', '/_goto_data', []); | |
106 | pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']); |
|
98 | pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']); | |
107 | pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']); |
|
99 | pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']); | |
108 | pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']); |
|
100 | pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']); | |
|
101 | pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']); | |||
|
102 | pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']); | |||
|
103 | pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']); | |||
|
104 | pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); | |||
|
105 | pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']); | |||
|
106 | pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']); | |||
|
107 | pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); | |||
|
108 | pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); | |||
|
109 | pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); | |||
|
110 | pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); | |||
|
111 | pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']); | |||
|
112 | pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); | |||
|
113 | pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); | |||
|
114 | pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); | |||
|
115 | pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); | |||
|
116 | pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); | |||
|
117 | pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); | |||
|
118 | pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); | |||
|
119 | pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); | |||
|
120 | pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); | |||
|
121 | pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); | |||
|
122 | pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); | |||
|
123 | pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); | |||
109 | pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']); |
|
124 | pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']); | |
110 | pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']); |
|
125 | pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']); | |
111 | pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']); |
|
126 | pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']); | |
112 | pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']); |
|
127 | pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']); | |
113 | pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']); |
|
128 | pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']); | |
114 | pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']); |
|
129 | pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']); | |
115 | pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']); |
|
130 | pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']); | |
116 | pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']); |
|
131 | pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']); | |
117 | pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']); |
|
132 | pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']); | |
118 | pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']); |
|
133 | pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']); | |
119 | pyroutes.register('changeset_children', '/%(repo_name)s/changeset_children/%(revision)s', ['repo_name', 'revision']); |
|
134 | pyroutes.register('changeset_children', '/%(repo_name)s/changeset_children/%(revision)s', ['repo_name', 'revision']); | |
120 | pyroutes.register('changeset_parents', '/%(repo_name)s/changeset_parents/%(revision)s', ['repo_name', 'revision']); |
|
135 | pyroutes.register('changeset_parents', '/%(repo_name)s/changeset_parents/%(revision)s', ['repo_name', 'revision']); | |
121 | pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']); |
|
136 | pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']); | |
122 | pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']); |
|
137 | pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']); | |
123 | pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']); |
|
138 | pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']); | |
124 | pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']); |
|
139 | pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']); | |
125 | pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']); |
|
140 | pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']); | |
126 | pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']); |
|
141 | pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']); | |
127 | pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']); |
|
142 | pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']); | |
128 | pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']); |
|
143 | pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']); | |
129 | pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']); |
|
144 | pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']); | |
130 | pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']); |
|
145 | pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']); | |
131 | pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']); |
|
146 | pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']); | |
132 | pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']); |
|
147 | pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']); | |
133 | pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']); |
|
148 | pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']); | |
134 | pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']); |
|
149 | pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']); | |
135 | pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']); |
|
150 | pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']); | |
136 | pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']); |
|
151 | pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']); | |
137 | pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']); |
|
152 | pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']); | |
138 | pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']); |
|
153 | pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']); | |
139 | pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']); |
|
154 | pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']); | |
140 | pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']); |
|
155 | pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']); | |
141 | pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']); |
|
156 | pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']); | |
142 | pyroutes.register('search', '/_admin/search', []); |
|
157 | pyroutes.register('search', '/_admin/search', []); | |
143 | pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']); |
|
158 | pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']); | |
144 | pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']); |
|
159 | pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']); | |
145 | pyroutes.register('my_account_profile', '/_admin/my_account/profile', []); |
|
160 | pyroutes.register('my_account_profile', '/_admin/my_account/profile', []); | |
146 | pyroutes.register('my_account_edit', '/_admin/my_account/edit', []); |
|
161 | pyroutes.register('my_account_edit', '/_admin/my_account/edit', []); | |
147 | pyroutes.register('my_account_update', '/_admin/my_account/update', []); |
|
162 | pyroutes.register('my_account_update', '/_admin/my_account/update', []); | |
148 | pyroutes.register('my_account_password', '/_admin/my_account/password', []); |
|
163 | pyroutes.register('my_account_password', '/_admin/my_account/password', []); | |
149 | pyroutes.register('my_account_password_update', '/_admin/my_account/password', []); |
|
164 | pyroutes.register('my_account_password_update', '/_admin/my_account/password', []); | |
150 | pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []); |
|
165 | pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []); | |
151 | pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []); |
|
166 | pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []); | |
152 | pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []); |
|
167 | pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []); | |
153 | pyroutes.register('my_account_emails', '/_admin/my_account/emails', []); |
|
168 | pyroutes.register('my_account_emails', '/_admin/my_account/emails', []); | |
154 | pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []); |
|
169 | pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []); | |
155 | pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []); |
|
170 | pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []); | |
156 | pyroutes.register('my_account_repos', '/_admin/my_account/repos', []); |
|
171 | pyroutes.register('my_account_repos', '/_admin/my_account/repos', []); | |
157 | pyroutes.register('my_account_watched', '/_admin/my_account/watched', []); |
|
172 | pyroutes.register('my_account_watched', '/_admin/my_account/watched', []); | |
158 | pyroutes.register('my_account_perms', '/_admin/my_account/perms', []); |
|
173 | pyroutes.register('my_account_perms', '/_admin/my_account/perms', []); | |
159 | pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []); |
|
174 | pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []); | |
160 | pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []); |
|
175 | pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []); | |
161 | pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []); |
|
176 | pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []); | |
162 | pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []); |
|
177 | pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []); | |
163 | pyroutes.register('notifications_show_all', '/_admin/notifications', []); |
|
178 | pyroutes.register('notifications_show_all', '/_admin/notifications', []); | |
164 | pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []); |
|
179 | pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []); | |
165 | pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']); |
|
180 | pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']); | |
166 | pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']); |
|
181 | pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']); | |
167 | pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']); |
|
182 | pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']); | |
168 | pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []); |
|
183 | pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []); | |
169 | pyroutes.register('gists_show', '/_admin/gists', []); |
|
184 | pyroutes.register('gists_show', '/_admin/gists', []); | |
170 | pyroutes.register('gists_new', '/_admin/gists/new', []); |
|
185 | pyroutes.register('gists_new', '/_admin/gists/new', []); | |
171 | pyroutes.register('gists_create', '/_admin/gists/create', []); |
|
186 | pyroutes.register('gists_create', '/_admin/gists/create', []); | |
172 | pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']); |
|
187 | pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']); | |
173 | pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']); |
|
188 | pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']); | |
174 | pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']); |
|
189 | pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']); | |
175 | pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']); |
|
190 | pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']); | |
176 | pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']); |
|
191 | pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']); | |
177 | pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']); |
|
192 | pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']); | |
178 | pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']); |
|
193 | pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']); | |
179 | pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']); |
|
194 | pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']); | |
180 | pyroutes.register('debug_style_home', '/_admin/debug_style', []); |
|
195 | pyroutes.register('debug_style_home', '/_admin/debug_style', []); | |
181 | pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']); |
|
196 | pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']); | |
182 | pyroutes.register('apiv2', '/_admin/api', []); |
|
197 | pyroutes.register('apiv2', '/_admin/api', []); | |
183 | } |
|
198 | } |
@@ -1,84 +1,84 b'' | |||||
1 | /* COMMON */ |
|
1 | /* COMMON */ | |
2 | var select2RefFilterResults = function(queryTerm, data) { |
|
2 | var select2RefFilterResults = function(queryTerm, data) { | |
3 | var filteredData = {results: []}; |
|
3 | var filteredData = {results: []}; | |
4 | //filter results |
|
4 | //filter results | |
5 | $.each(data.results, function() { |
|
5 | $.each(data.results, function() { | |
6 | var section = this.text; |
|
6 | var section = this.text; | |
7 | var children = []; |
|
7 | var children = []; | |
8 | $.each(this.children, function() { |
|
8 | $.each(this.children, function() { | |
9 | if (queryTerm.length === 0 || this.text.toUpperCase().indexOf(queryTerm.toUpperCase()) >= 0) { |
|
9 | if (queryTerm.length === 0 || this.text.toUpperCase().indexOf(queryTerm.toUpperCase()) >= 0) { | |
10 | children.push(this); |
|
10 | children.push(this); | |
11 | } |
|
11 | } | |
12 | }); |
|
12 | }); | |
13 |
|
13 | |||
14 | if (children.length > 0) { |
|
14 | if (children.length > 0) { | |
15 | filteredData.results.push({ |
|
15 | filteredData.results.push({ | |
16 | 'text': section, |
|
16 | 'text': section, | |
17 | 'children': children |
|
17 | 'children': children | |
18 | }); |
|
18 | }); | |
19 | } |
|
19 | } | |
20 | }); |
|
20 | }); | |
21 |
|
21 | |||
22 | return filteredData |
|
22 | return filteredData | |
23 | }; |
|
23 | }; | |
24 |
|
24 | |||
25 |
|
25 | |||
26 | var select2RefBaseSwitcher = function(targetElement, loadUrl, initialData){ |
|
26 | var select2RefBaseSwitcher = function(targetElement, loadUrl, initialData){ | |
27 | var formatResult = function(result, container, query) { |
|
27 | var formatResult = function(result, container, query) { | |
28 | return formatSelect2SelectionRefs(result); |
|
28 | return formatSelect2SelectionRefs(result); | |
29 | }; |
|
29 | }; | |
30 |
|
30 | |||
31 | var formatSelection = function(data, container) { |
|
31 | var formatSelection = function(data, container) { | |
32 | return formatSelect2SelectionRefs(data); |
|
32 | return formatSelect2SelectionRefs(data); | |
33 | }; |
|
33 | }; | |
34 |
|
34 | |||
35 | $(targetElement).select2({ |
|
35 | $(targetElement).select2({ | |
36 | cachedDataSource: {}, |
|
36 | cachedDataSource: {}, | |
37 | dropdownAutoWidth: true, |
|
37 | dropdownAutoWidth: true, | |
38 | formatResult: formatResult, |
|
38 | formatResult: formatResult, | |
39 | width: "resolve", |
|
39 | width: "resolve", | |
40 | containerCssClass: "drop-menu", |
|
40 | containerCssClass: "drop-menu", | |
41 | dropdownCssClass: "drop-menu-dropdown", |
|
41 | dropdownCssClass: "drop-menu-dropdown", | |
42 | query: function(query) { |
|
42 | query: function(query) { | |
43 | var self = this; |
|
43 | var self = this; | |
44 | var cacheKey = '__ALLREFS__'; |
|
44 | var cacheKey = '__ALLREFS__'; | |
45 | var cachedData = self.cachedDataSource[cacheKey]; |
|
45 | var cachedData = self.cachedDataSource[cacheKey]; | |
46 | if (cachedData) { |
|
46 | if (cachedData) { | |
47 | var data = select2RefFilterResults(query.term, cachedData); |
|
47 | var data = select2RefFilterResults(query.term, cachedData); | |
48 | query.callback({results: data.results}); |
|
48 | query.callback({results: data.results}); | |
49 | } else { |
|
49 | } else { | |
50 | $.ajax({ |
|
50 | $.ajax({ | |
51 | url: loadUrl, |
|
51 | url: loadUrl, | |
52 | data: {}, |
|
52 | data: {}, | |
53 | dataType: 'json', |
|
53 | dataType: 'json', | |
54 | type: 'GET', |
|
54 | type: 'GET', | |
55 | success: function(data) { |
|
55 | success: function(data) { | |
56 | self.cachedDataSource[cacheKey] = data; |
|
56 | self.cachedDataSource[cacheKey] = data; | |
57 | query.callback({results: data.results}); |
|
57 | query.callback({results: data.results}); | |
58 | } |
|
58 | } | |
59 | }); |
|
59 | }); | |
60 | } |
|
60 | } | |
61 | }, |
|
61 | }, | |
62 |
|
62 | |||
63 | initSelection: function(element, callback) { |
|
63 | initSelection: function(element, callback) { | |
64 | callback(initialData); |
|
64 | callback(initialData); | |
65 | }, |
|
65 | }, | |
66 |
|
66 | |||
67 | formatSelection: formatSelection |
|
67 | formatSelection: formatSelection | |
68 | }); |
|
68 | }); | |
69 |
|
69 | |||
70 | }; |
|
70 | }; | |
71 |
|
71 | |||
72 | /* WIDGETS */ |
|
72 | /* WIDGETS */ | |
73 | var select2RefSwitcher = function(targetElement, initialData) { |
|
73 | var select2RefSwitcher = function(targetElement, initialData) { | |
74 | var loadUrl = pyroutes.url('repo_refs_data', |
|
74 | var loadUrl = pyroutes.url('repo_refs_data', | |
75 | {'repo_name': templateContext.repo_name}); |
|
75 | {'repo_name': templateContext.repo_name}); | |
76 | select2RefBaseSwitcher(targetElement, loadUrl, initialData); |
|
76 | select2RefBaseSwitcher(targetElement, loadUrl, initialData); | |
77 | }; |
|
77 | }; | |
78 |
|
78 | |||
79 | var select2FileHistorySwitcher = function(targetElement, initialData, state) { |
|
79 | var select2FileHistorySwitcher = function(targetElement, initialData, state) { | |
80 |
var loadUrl = pyroutes.url('file |
|
80 | var loadUrl = pyroutes.url('repo_file_history', | |
81 |
{'repo_name': templateContext.repo_name, ' |
|
81 | {'repo_name': templateContext.repo_name, 'commit_id': state.rev, | |
82 | 'f_path': state.f_path}); |
|
82 | 'f_path': state.f_path}); | |
83 | select2RefBaseSwitcher(targetElement, loadUrl, initialData); |
|
83 | select2RefBaseSwitcher(targetElement, loadUrl, initialData); | |
84 | }; |
|
84 | }; |
@@ -1,600 +1,600 b'' | |||||
1 | ## -*- coding: utf-8 -*- |
|
1 | ## -*- coding: utf-8 -*- | |
2 | <%inherit file="root.mako"/> |
|
2 | <%inherit file="root.mako"/> | |
3 |
|
3 | |||
4 | <div class="outerwrapper"> |
|
4 | <div class="outerwrapper"> | |
5 | <!-- HEADER --> |
|
5 | <!-- HEADER --> | |
6 | <div class="header"> |
|
6 | <div class="header"> | |
7 | <div id="header-inner" class="wrapper"> |
|
7 | <div id="header-inner" class="wrapper"> | |
8 | <div id="logo"> |
|
8 | <div id="logo"> | |
9 | <div class="logo-wrapper"> |
|
9 | <div class="logo-wrapper"> | |
10 | <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a> |
|
10 | <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a> | |
11 | </div> |
|
11 | </div> | |
12 | %if c.rhodecode_name: |
|
12 | %if c.rhodecode_name: | |
13 | <div class="branding">- ${h.branding(c.rhodecode_name)}</div> |
|
13 | <div class="branding">- ${h.branding(c.rhodecode_name)}</div> | |
14 | %endif |
|
14 | %endif | |
15 | </div> |
|
15 | </div> | |
16 | <!-- MENU BAR NAV --> |
|
16 | <!-- MENU BAR NAV --> | |
17 | ${self.menu_bar_nav()} |
|
17 | ${self.menu_bar_nav()} | |
18 | <!-- END MENU BAR NAV --> |
|
18 | <!-- END MENU BAR NAV --> | |
19 | </div> |
|
19 | </div> | |
20 | </div> |
|
20 | </div> | |
21 | ${self.menu_bar_subnav()} |
|
21 | ${self.menu_bar_subnav()} | |
22 | <!-- END HEADER --> |
|
22 | <!-- END HEADER --> | |
23 |
|
23 | |||
24 | <!-- CONTENT --> |
|
24 | <!-- CONTENT --> | |
25 | <div id="content" class="wrapper"> |
|
25 | <div id="content" class="wrapper"> | |
26 |
|
26 | |||
27 | <rhodecode-toast id="notifications"></rhodecode-toast> |
|
27 | <rhodecode-toast id="notifications"></rhodecode-toast> | |
28 |
|
28 | |||
29 | <div class="main"> |
|
29 | <div class="main"> | |
30 | ${next.main()} |
|
30 | ${next.main()} | |
31 | </div> |
|
31 | </div> | |
32 | </div> |
|
32 | </div> | |
33 | <!-- END CONTENT --> |
|
33 | <!-- END CONTENT --> | |
34 |
|
34 | |||
35 | </div> |
|
35 | </div> | |
36 | <!-- FOOTER --> |
|
36 | <!-- FOOTER --> | |
37 | <div id="footer"> |
|
37 | <div id="footer"> | |
38 | <div id="footer-inner" class="title wrapper"> |
|
38 | <div id="footer-inner" class="title wrapper"> | |
39 | <div> |
|
39 | <div> | |
40 | <p class="footer-link-right"> |
|
40 | <p class="footer-link-right"> | |
41 | % if c.visual.show_version: |
|
41 | % if c.visual.show_version: | |
42 | RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition} |
|
42 | RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition} | |
43 | % endif |
|
43 | % endif | |
44 | © 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved. |
|
44 | © 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved. | |
45 | % if c.visual.rhodecode_support_url: |
|
45 | % if c.visual.rhodecode_support_url: | |
46 | <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a> |
|
46 | <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a> | |
47 | % endif |
|
47 | % endif | |
48 | </p> |
|
48 | </p> | |
49 | <% sid = 'block' if request.GET.get('showrcid') else 'none' %> |
|
49 | <% sid = 'block' if request.GET.get('showrcid') else 'none' %> | |
50 | <p class="server-instance" style="display:${sid}"> |
|
50 | <p class="server-instance" style="display:${sid}"> | |
51 | ## display hidden instance ID if specially defined |
|
51 | ## display hidden instance ID if specially defined | |
52 | % if c.rhodecode_instanceid: |
|
52 | % if c.rhodecode_instanceid: | |
53 | ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid} |
|
53 | ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid} | |
54 | % endif |
|
54 | % endif | |
55 | </p> |
|
55 | </p> | |
56 | </div> |
|
56 | </div> | |
57 | </div> |
|
57 | </div> | |
58 | </div> |
|
58 | </div> | |
59 |
|
59 | |||
60 | <!-- END FOOTER --> |
|
60 | <!-- END FOOTER --> | |
61 |
|
61 | |||
62 | ### MAKO DEFS ### |
|
62 | ### MAKO DEFS ### | |
63 |
|
63 | |||
64 | <%def name="menu_bar_subnav()"> |
|
64 | <%def name="menu_bar_subnav()"> | |
65 | </%def> |
|
65 | </%def> | |
66 |
|
66 | |||
67 | <%def name="breadcrumbs(class_='breadcrumbs')"> |
|
67 | <%def name="breadcrumbs(class_='breadcrumbs')"> | |
68 | <div class="${class_}"> |
|
68 | <div class="${class_}"> | |
69 | ${self.breadcrumbs_links()} |
|
69 | ${self.breadcrumbs_links()} | |
70 | </div> |
|
70 | </div> | |
71 | </%def> |
|
71 | </%def> | |
72 |
|
72 | |||
73 | <%def name="admin_menu()"> |
|
73 | <%def name="admin_menu()"> | |
74 | <ul class="admin_menu submenu"> |
|
74 | <ul class="admin_menu submenu"> | |
75 | <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li> |
|
75 | <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li> | |
76 | <li><a href="${h.url('repos')}">${_('Repositories')}</a></li> |
|
76 | <li><a href="${h.url('repos')}">${_('Repositories')}</a></li> | |
77 | <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li> |
|
77 | <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li> | |
78 | <li><a href="${h.route_path('users')}">${_('Users')}</a></li> |
|
78 | <li><a href="${h.route_path('users')}">${_('Users')}</a></li> | |
79 | <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li> |
|
79 | <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li> | |
80 | <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li> |
|
80 | <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li> | |
81 | <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li> |
|
81 | <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li> | |
82 | <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li> |
|
82 | <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li> | |
83 | <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li> |
|
83 | <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li> | |
84 | <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li> |
|
84 | <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li> | |
85 | </ul> |
|
85 | </ul> | |
86 | </%def> |
|
86 | </%def> | |
87 |
|
87 | |||
88 |
|
88 | |||
89 | <%def name="dt_info_panel(elements)"> |
|
89 | <%def name="dt_info_panel(elements)"> | |
90 | <dl class="dl-horizontal"> |
|
90 | <dl class="dl-horizontal"> | |
91 | %for dt, dd, title, show_items in elements: |
|
91 | %for dt, dd, title, show_items in elements: | |
92 | <dt>${dt}:</dt> |
|
92 | <dt>${dt}:</dt> | |
93 | <dd title="${h.tooltip(title)}"> |
|
93 | <dd title="${h.tooltip(title)}"> | |
94 | %if callable(dd): |
|
94 | %if callable(dd): | |
95 | ## allow lazy evaluation of elements |
|
95 | ## allow lazy evaluation of elements | |
96 | ${dd()} |
|
96 | ${dd()} | |
97 | %else: |
|
97 | %else: | |
98 | ${dd} |
|
98 | ${dd} | |
99 | %endif |
|
99 | %endif | |
100 | %if show_items: |
|
100 | %if show_items: | |
101 | <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span> |
|
101 | <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span> | |
102 | %endif |
|
102 | %endif | |
103 | </dd> |
|
103 | </dd> | |
104 |
|
104 | |||
105 | %if show_items: |
|
105 | %if show_items: | |
106 | <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none"> |
|
106 | <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none"> | |
107 | %for item in show_items: |
|
107 | %for item in show_items: | |
108 | <dt></dt> |
|
108 | <dt></dt> | |
109 | <dd>${item}</dd> |
|
109 | <dd>${item}</dd> | |
110 | %endfor |
|
110 | %endfor | |
111 | </div> |
|
111 | </div> | |
112 | %endif |
|
112 | %endif | |
113 |
|
113 | |||
114 | %endfor |
|
114 | %endfor | |
115 | </dl> |
|
115 | </dl> | |
116 | </%def> |
|
116 | </%def> | |
117 |
|
117 | |||
118 |
|
118 | |||
119 | <%def name="gravatar(email, size=16)"> |
|
119 | <%def name="gravatar(email, size=16)"> | |
120 | <% |
|
120 | <% | |
121 | if (size > 16): |
|
121 | if (size > 16): | |
122 | gravatar_class = 'gravatar gravatar-large' |
|
122 | gravatar_class = 'gravatar gravatar-large' | |
123 | else: |
|
123 | else: | |
124 | gravatar_class = 'gravatar' |
|
124 | gravatar_class = 'gravatar' | |
125 | %> |
|
125 | %> | |
126 | <%doc> |
|
126 | <%doc> | |
127 | TODO: johbo: For now we serve double size images to make it smooth |
|
127 | TODO: johbo: For now we serve double size images to make it smooth | |
128 | for retina. This is how it worked until now. Should be replaced |
|
128 | for retina. This is how it worked until now. Should be replaced | |
129 | with a better solution at some point. |
|
129 | with a better solution at some point. | |
130 | </%doc> |
|
130 | </%doc> | |
131 | <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}"> |
|
131 | <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}"> | |
132 | </%def> |
|
132 | </%def> | |
133 |
|
133 | |||
134 |
|
134 | |||
135 | <%def name="gravatar_with_user(contact, size=16, show_disabled=False)"> |
|
135 | <%def name="gravatar_with_user(contact, size=16, show_disabled=False)"> | |
136 | <% email = h.email_or_none(contact) %> |
|
136 | <% email = h.email_or_none(contact) %> | |
137 | <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}"> |
|
137 | <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}"> | |
138 | ${self.gravatar(email, size)} |
|
138 | ${self.gravatar(email, size)} | |
139 | <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span> |
|
139 | <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span> | |
140 | </div> |
|
140 | </div> | |
141 | </%def> |
|
141 | </%def> | |
142 |
|
142 | |||
143 |
|
143 | |||
144 | ## admin menu used for people that have some admin resources |
|
144 | ## admin menu used for people that have some admin resources | |
145 | <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)"> |
|
145 | <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)"> | |
146 | <ul class="submenu"> |
|
146 | <ul class="submenu"> | |
147 | %if repositories: |
|
147 | %if repositories: | |
148 | <li class="local-admin-repos"><a href="${h.url('repos')}">${_('Repositories')}</a></li> |
|
148 | <li class="local-admin-repos"><a href="${h.url('repos')}">${_('Repositories')}</a></li> | |
149 | %endif |
|
149 | %endif | |
150 | %if repository_groups: |
|
150 | %if repository_groups: | |
151 | <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li> |
|
151 | <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li> | |
152 | %endif |
|
152 | %endif | |
153 | %if user_groups: |
|
153 | %if user_groups: | |
154 | <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li> |
|
154 | <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li> | |
155 | %endif |
|
155 | %endif | |
156 | </ul> |
|
156 | </ul> | |
157 | </%def> |
|
157 | </%def> | |
158 |
|
158 | |||
159 | <%def name="repo_page_title(repo_instance)"> |
|
159 | <%def name="repo_page_title(repo_instance)"> | |
160 | <div class="title-content"> |
|
160 | <div class="title-content"> | |
161 | <div class="title-main"> |
|
161 | <div class="title-main"> | |
162 | ## SVN/HG/GIT icons |
|
162 | ## SVN/HG/GIT icons | |
163 | %if h.is_hg(repo_instance): |
|
163 | %if h.is_hg(repo_instance): | |
164 | <i class="icon-hg"></i> |
|
164 | <i class="icon-hg"></i> | |
165 | %endif |
|
165 | %endif | |
166 | %if h.is_git(repo_instance): |
|
166 | %if h.is_git(repo_instance): | |
167 | <i class="icon-git"></i> |
|
167 | <i class="icon-git"></i> | |
168 | %endif |
|
168 | %endif | |
169 | %if h.is_svn(repo_instance): |
|
169 | %if h.is_svn(repo_instance): | |
170 | <i class="icon-svn"></i> |
|
170 | <i class="icon-svn"></i> | |
171 | %endif |
|
171 | %endif | |
172 |
|
172 | |||
173 | ## public/private |
|
173 | ## public/private | |
174 | %if repo_instance.private: |
|
174 | %if repo_instance.private: | |
175 | <i class="icon-repo-private"></i> |
|
175 | <i class="icon-repo-private"></i> | |
176 | %else: |
|
176 | %else: | |
177 | <i class="icon-repo-public"></i> |
|
177 | <i class="icon-repo-public"></i> | |
178 | %endif |
|
178 | %endif | |
179 |
|
179 | |||
180 | ## repo name with group name |
|
180 | ## repo name with group name | |
181 | ${h.breadcrumb_repo_link(c.rhodecode_db_repo)} |
|
181 | ${h.breadcrumb_repo_link(c.rhodecode_db_repo)} | |
182 |
|
182 | |||
183 | </div> |
|
183 | </div> | |
184 |
|
184 | |||
185 | ## FORKED |
|
185 | ## FORKED | |
186 | %if repo_instance.fork: |
|
186 | %if repo_instance.fork: | |
187 | <p> |
|
187 | <p> | |
188 | <i class="icon-code-fork"></i> ${_('Fork of')} |
|
188 | <i class="icon-code-fork"></i> ${_('Fork of')} | |
189 | <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a> |
|
189 | <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a> | |
190 | </p> |
|
190 | </p> | |
191 | %endif |
|
191 | %endif | |
192 |
|
192 | |||
193 | ## IMPORTED FROM REMOTE |
|
193 | ## IMPORTED FROM REMOTE | |
194 | %if repo_instance.clone_uri: |
|
194 | %if repo_instance.clone_uri: | |
195 | <p> |
|
195 | <p> | |
196 | <i class="icon-code-fork"></i> ${_('Clone from')} |
|
196 | <i class="icon-code-fork"></i> ${_('Clone from')} | |
197 | <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a> |
|
197 | <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a> | |
198 | </p> |
|
198 | </p> | |
199 | %endif |
|
199 | %endif | |
200 |
|
200 | |||
201 | ## LOCKING STATUS |
|
201 | ## LOCKING STATUS | |
202 | %if repo_instance.locked[0]: |
|
202 | %if repo_instance.locked[0]: | |
203 | <p class="locking_locked"> |
|
203 | <p class="locking_locked"> | |
204 | <i class="icon-repo-lock"></i> |
|
204 | <i class="icon-repo-lock"></i> | |
205 | ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}} |
|
205 | ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}} | |
206 | </p> |
|
206 | </p> | |
207 | %elif repo_instance.enable_locking: |
|
207 | %elif repo_instance.enable_locking: | |
208 | <p class="locking_unlocked"> |
|
208 | <p class="locking_unlocked"> | |
209 | <i class="icon-repo-unlock"></i> |
|
209 | <i class="icon-repo-unlock"></i> | |
210 | ${_('Repository not locked. Pull repository to lock it.')} |
|
210 | ${_('Repository not locked. Pull repository to lock it.')} | |
211 | </p> |
|
211 | </p> | |
212 | %endif |
|
212 | %endif | |
213 |
|
213 | |||
214 | </div> |
|
214 | </div> | |
215 | </%def> |
|
215 | </%def> | |
216 |
|
216 | |||
217 | <%def name="repo_menu(active=None)"> |
|
217 | <%def name="repo_menu(active=None)"> | |
218 | <% |
|
218 | <% | |
219 | def is_active(selected): |
|
219 | def is_active(selected): | |
220 | if selected == active: |
|
220 | if selected == active: | |
221 | return "active" |
|
221 | return "active" | |
222 | %> |
|
222 | %> | |
223 |
|
223 | |||
224 | <!--- CONTEXT BAR --> |
|
224 | <!--- CONTEXT BAR --> | |
225 | <div id="context-bar"> |
|
225 | <div id="context-bar"> | |
226 | <div class="wrapper"> |
|
226 | <div class="wrapper"> | |
227 | <ul id="context-pages" class="horizontal-list navigation"> |
|
227 | <ul id="context-pages" class="horizontal-list navigation"> | |
228 | <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li> |
|
228 | <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li> | |
229 | <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li> |
|
229 | <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li> | |
230 |
<li class="${is_active('files')}"><a class="menulink" href="${h. |
|
230 | <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li> | |
231 | <li class="${is_active('compare')}"> |
|
231 | <li class="${is_active('compare')}"> | |
232 | <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a> |
|
232 | <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a> | |
233 | </li> |
|
233 | </li> | |
234 | ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()" |
|
234 | ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()" | |
235 | %if c.rhodecode_db_repo.repo_type in ['git','hg']: |
|
235 | %if c.rhodecode_db_repo.repo_type in ['git','hg']: | |
236 | <li class="${is_active('showpullrequest')}"> |
|
236 | <li class="${is_active('showpullrequest')}"> | |
237 | <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}"> |
|
237 | <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}"> | |
238 | %if c.repository_pull_requests: |
|
238 | %if c.repository_pull_requests: | |
239 | <span class="pr_notifications">${c.repository_pull_requests}</span> |
|
239 | <span class="pr_notifications">${c.repository_pull_requests}</span> | |
240 | %endif |
|
240 | %endif | |
241 | <div class="menulabel">${_('Pull Requests')}</div> |
|
241 | <div class="menulabel">${_('Pull Requests')}</div> | |
242 | </a> |
|
242 | </a> | |
243 | </li> |
|
243 | </li> | |
244 | %endif |
|
244 | %endif | |
245 | <li class="${is_active('options')}"> |
|
245 | <li class="${is_active('options')}"> | |
246 | <a class="menulink dropdown"> |
|
246 | <a class="menulink dropdown"> | |
247 | <div class="menulabel">${_('Options')} <div class="show_more"></div></div> |
|
247 | <div class="menulabel">${_('Options')} <div class="show_more"></div></div> | |
248 | </a> |
|
248 | </a> | |
249 | <ul class="submenu"> |
|
249 | <ul class="submenu"> | |
250 | %if h.HasRepoPermissionAll('repository.admin')(c.repo_name): |
|
250 | %if h.HasRepoPermissionAll('repository.admin')(c.repo_name): | |
251 | <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li> |
|
251 | <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li> | |
252 | %endif |
|
252 | %endif | |
253 | %if c.rhodecode_db_repo.fork: |
|
253 | %if c.rhodecode_db_repo.fork: | |
254 | <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}"> |
|
254 | <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}"> | |
255 | ${_('Compare fork')}</a></li> |
|
255 | ${_('Compare fork')}</a></li> | |
256 | %endif |
|
256 | %endif | |
257 |
|
257 | |||
258 | <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li> |
|
258 | <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li> | |
259 |
|
259 | |||
260 | %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking: |
|
260 | %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking: | |
261 | %if c.rhodecode_db_repo.locked[0]: |
|
261 | %if c.rhodecode_db_repo.locked[0]: | |
262 | <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li> |
|
262 | <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li> | |
263 | %else: |
|
263 | %else: | |
264 | <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li> |
|
264 | <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li> | |
265 | %endif |
|
265 | %endif | |
266 | %endif |
|
266 | %endif | |
267 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
267 | %if c.rhodecode_user.username != h.DEFAULT_USER: | |
268 | %if c.rhodecode_db_repo.repo_type in ['git','hg']: |
|
268 | %if c.rhodecode_db_repo.repo_type in ['git','hg']: | |
269 | <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li> |
|
269 | <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li> | |
270 | <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li> |
|
270 | <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li> | |
271 | %endif |
|
271 | %endif | |
272 | %endif |
|
272 | %endif | |
273 | </ul> |
|
273 | </ul> | |
274 | </li> |
|
274 | </li> | |
275 | </ul> |
|
275 | </ul> | |
276 | </div> |
|
276 | </div> | |
277 | <div class="clear"></div> |
|
277 | <div class="clear"></div> | |
278 | </div> |
|
278 | </div> | |
279 | <!--- END CONTEXT BAR --> |
|
279 | <!--- END CONTEXT BAR --> | |
280 |
|
280 | |||
281 | </%def> |
|
281 | </%def> | |
282 |
|
282 | |||
283 | <%def name="usermenu(active=False)"> |
|
283 | <%def name="usermenu(active=False)"> | |
284 | ## USER MENU |
|
284 | ## USER MENU | |
285 | <li id="quick_login_li" class="${'active' if active else ''}"> |
|
285 | <li id="quick_login_li" class="${'active' if active else ''}"> | |
286 | <a id="quick_login_link" class="menulink childs"> |
|
286 | <a id="quick_login_link" class="menulink childs"> | |
287 | ${gravatar(c.rhodecode_user.email, 20)} |
|
287 | ${gravatar(c.rhodecode_user.email, 20)} | |
288 | <span class="user"> |
|
288 | <span class="user"> | |
289 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
289 | %if c.rhodecode_user.username != h.DEFAULT_USER: | |
290 | <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div> |
|
290 | <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div> | |
291 | %else: |
|
291 | %else: | |
292 | <span>${_('Sign in')}</span> |
|
292 | <span>${_('Sign in')}</span> | |
293 | %endif |
|
293 | %endif | |
294 | </span> |
|
294 | </span> | |
295 | </a> |
|
295 | </a> | |
296 |
|
296 | |||
297 | <div class="user-menu submenu"> |
|
297 | <div class="user-menu submenu"> | |
298 | <div id="quick_login"> |
|
298 | <div id="quick_login"> | |
299 | %if c.rhodecode_user.username == h.DEFAULT_USER: |
|
299 | %if c.rhodecode_user.username == h.DEFAULT_USER: | |
300 | <h4>${_('Sign in to your account')}</h4> |
|
300 | <h4>${_('Sign in to your account')}</h4> | |
301 | ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)} |
|
301 | ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)} | |
302 | <div class="form form-vertical"> |
|
302 | <div class="form form-vertical"> | |
303 | <div class="fields"> |
|
303 | <div class="fields"> | |
304 | <div class="field"> |
|
304 | <div class="field"> | |
305 | <div class="label"> |
|
305 | <div class="label"> | |
306 | <label for="username">${_('Username')}:</label> |
|
306 | <label for="username">${_('Username')}:</label> | |
307 | </div> |
|
307 | </div> | |
308 | <div class="input"> |
|
308 | <div class="input"> | |
309 | ${h.text('username',class_='focus',tabindex=1)} |
|
309 | ${h.text('username',class_='focus',tabindex=1)} | |
310 | </div> |
|
310 | </div> | |
311 |
|
311 | |||
312 | </div> |
|
312 | </div> | |
313 | <div class="field"> |
|
313 | <div class="field"> | |
314 | <div class="label"> |
|
314 | <div class="label"> | |
315 | <label for="password">${_('Password')}:</label> |
|
315 | <label for="password">${_('Password')}:</label> | |
316 | %if h.HasPermissionAny('hg.password_reset.enabled')(): |
|
316 | %if h.HasPermissionAny('hg.password_reset.enabled')(): | |
317 | <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span> |
|
317 | <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span> | |
318 | %endif |
|
318 | %endif | |
319 | </div> |
|
319 | </div> | |
320 | <div class="input"> |
|
320 | <div class="input"> | |
321 | ${h.password('password',class_='focus',tabindex=2)} |
|
321 | ${h.password('password',class_='focus',tabindex=2)} | |
322 | </div> |
|
322 | </div> | |
323 | </div> |
|
323 | </div> | |
324 | <div class="buttons"> |
|
324 | <div class="buttons"> | |
325 | <div class="register"> |
|
325 | <div class="register"> | |
326 | %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')(): |
|
326 | %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')(): | |
327 | ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/> |
|
327 | ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/> | |
328 | %endif |
|
328 | %endif | |
329 | ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))} |
|
329 | ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))} | |
330 | </div> |
|
330 | </div> | |
331 | <div class="submit"> |
|
331 | <div class="submit"> | |
332 | ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)} |
|
332 | ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)} | |
333 | </div> |
|
333 | </div> | |
334 | </div> |
|
334 | </div> | |
335 | </div> |
|
335 | </div> | |
336 | </div> |
|
336 | </div> | |
337 | ${h.end_form()} |
|
337 | ${h.end_form()} | |
338 | %else: |
|
338 | %else: | |
339 | <div class=""> |
|
339 | <div class=""> | |
340 | <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div> |
|
340 | <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div> | |
341 | <div class="full_name">${c.rhodecode_user.full_name_or_username}</div> |
|
341 | <div class="full_name">${c.rhodecode_user.full_name_or_username}</div> | |
342 | <div class="email">${c.rhodecode_user.email}</div> |
|
342 | <div class="email">${c.rhodecode_user.email}</div> | |
343 | </div> |
|
343 | </div> | |
344 | <div class=""> |
|
344 | <div class=""> | |
345 | <ol class="links"> |
|
345 | <ol class="links"> | |
346 | <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li> |
|
346 | <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li> | |
347 | % if c.rhodecode_user.personal_repo_group: |
|
347 | % if c.rhodecode_user.personal_repo_group: | |
348 | <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li> |
|
348 | <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li> | |
349 | % endif |
|
349 | % endif | |
350 | <li class="logout"> |
|
350 | <li class="logout"> | |
351 | ${h.secure_form(h.route_path('logout'), request=request)} |
|
351 | ${h.secure_form(h.route_path('logout'), request=request)} | |
352 | ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")} |
|
352 | ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")} | |
353 | ${h.end_form()} |
|
353 | ${h.end_form()} | |
354 | </li> |
|
354 | </li> | |
355 | </ol> |
|
355 | </ol> | |
356 | </div> |
|
356 | </div> | |
357 | %endif |
|
357 | %endif | |
358 | </div> |
|
358 | </div> | |
359 | </div> |
|
359 | </div> | |
360 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
360 | %if c.rhodecode_user.username != h.DEFAULT_USER: | |
361 | <div class="pill_container"> |
|
361 | <div class="pill_container"> | |
362 | <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a> |
|
362 | <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a> | |
363 | </div> |
|
363 | </div> | |
364 | % endif |
|
364 | % endif | |
365 | </li> |
|
365 | </li> | |
366 | </%def> |
|
366 | </%def> | |
367 |
|
367 | |||
368 | <%def name="menu_items(active=None)"> |
|
368 | <%def name="menu_items(active=None)"> | |
369 | <% |
|
369 | <% | |
370 | def is_active(selected): |
|
370 | def is_active(selected): | |
371 | if selected == active: |
|
371 | if selected == active: | |
372 | return "active" |
|
372 | return "active" | |
373 | return "" |
|
373 | return "" | |
374 | %> |
|
374 | %> | |
375 | <ul id="quick" class="main_nav navigation horizontal-list"> |
|
375 | <ul id="quick" class="main_nav navigation horizontal-list"> | |
376 | <!-- repo switcher --> |
|
376 | <!-- repo switcher --> | |
377 | <li class="${is_active('repositories')} repo_switcher_li has_select2"> |
|
377 | <li class="${is_active('repositories')} repo_switcher_li has_select2"> | |
378 | <input id="repo_switcher" name="repo_switcher" type="hidden"> |
|
378 | <input id="repo_switcher" name="repo_switcher" type="hidden"> | |
379 | </li> |
|
379 | </li> | |
380 |
|
380 | |||
381 | ## ROOT MENU |
|
381 | ## ROOT MENU | |
382 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
382 | %if c.rhodecode_user.username != h.DEFAULT_USER: | |
383 | <li class="${is_active('journal')}"> |
|
383 | <li class="${is_active('journal')}"> | |
384 | <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}"> |
|
384 | <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}"> | |
385 | <div class="menulabel">${_('Journal')}</div> |
|
385 | <div class="menulabel">${_('Journal')}</div> | |
386 | </a> |
|
386 | </a> | |
387 | </li> |
|
387 | </li> | |
388 | %else: |
|
388 | %else: | |
389 | <li class="${is_active('journal')}"> |
|
389 | <li class="${is_active('journal')}"> | |
390 | <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}"> |
|
390 | <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}"> | |
391 | <div class="menulabel">${_('Public journal')}</div> |
|
391 | <div class="menulabel">${_('Public journal')}</div> | |
392 | </a> |
|
392 | </a> | |
393 | </li> |
|
393 | </li> | |
394 | %endif |
|
394 | %endif | |
395 | <li class="${is_active('gists')}"> |
|
395 | <li class="${is_active('gists')}"> | |
396 | <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}"> |
|
396 | <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}"> | |
397 | <div class="menulabel">${_('Gists')}</div> |
|
397 | <div class="menulabel">${_('Gists')}</div> | |
398 | </a> |
|
398 | </a> | |
399 | </li> |
|
399 | </li> | |
400 | <li class="${is_active('search')}"> |
|
400 | <li class="${is_active('search')}"> | |
401 | <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}"> |
|
401 | <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}"> | |
402 | <div class="menulabel">${_('Search')}</div> |
|
402 | <div class="menulabel">${_('Search')}</div> | |
403 | </a> |
|
403 | </a> | |
404 | </li> |
|
404 | </li> | |
405 | % if h.HasPermissionAll('hg.admin')('access admin main page'): |
|
405 | % if h.HasPermissionAll('hg.admin')('access admin main page'): | |
406 | <li class="${is_active('admin')}"> |
|
406 | <li class="${is_active('admin')}"> | |
407 | <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;"> |
|
407 | <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;"> | |
408 | <div class="menulabel">${_('Admin')} <div class="show_more"></div></div> |
|
408 | <div class="menulabel">${_('Admin')} <div class="show_more"></div></div> | |
409 | </a> |
|
409 | </a> | |
410 | ${admin_menu()} |
|
410 | ${admin_menu()} | |
411 | </li> |
|
411 | </li> | |
412 | % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin: |
|
412 | % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin: | |
413 | <li class="${is_active('admin')}"> |
|
413 | <li class="${is_active('admin')}"> | |
414 | <a class="menulink childs" title="${_('Delegated Admin settings')}"> |
|
414 | <a class="menulink childs" title="${_('Delegated Admin settings')}"> | |
415 | <div class="menulabel">${_('Admin')} <div class="show_more"></div></div> |
|
415 | <div class="menulabel">${_('Admin')} <div class="show_more"></div></div> | |
416 | </a> |
|
416 | </a> | |
417 | ${admin_menu_simple(c.rhodecode_user.repositories_admin, |
|
417 | ${admin_menu_simple(c.rhodecode_user.repositories_admin, | |
418 | c.rhodecode_user.repository_groups_admin, |
|
418 | c.rhodecode_user.repository_groups_admin, | |
419 | c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())} |
|
419 | c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())} | |
420 | </li> |
|
420 | </li> | |
421 | % endif |
|
421 | % endif | |
422 | % if c.debug_style: |
|
422 | % if c.debug_style: | |
423 | <li class="${is_active('debug_style')}"> |
|
423 | <li class="${is_active('debug_style')}"> | |
424 | <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}"> |
|
424 | <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}"> | |
425 | <div class="menulabel">${_('Style')}</div> |
|
425 | <div class="menulabel">${_('Style')}</div> | |
426 | </a> |
|
426 | </a> | |
427 | </li> |
|
427 | </li> | |
428 | % endif |
|
428 | % endif | |
429 | ## render extra user menu |
|
429 | ## render extra user menu | |
430 | ${usermenu(active=(active=='my_account'))} |
|
430 | ${usermenu(active=(active=='my_account'))} | |
431 | </ul> |
|
431 | </ul> | |
432 |
|
432 | |||
433 | <script type="text/javascript"> |
|
433 | <script type="text/javascript"> | |
434 | var visual_show_public_icon = "${c.visual.show_public_icon}" == "True"; |
|
434 | var visual_show_public_icon = "${c.visual.show_public_icon}" == "True"; | |
435 |
|
435 | |||
436 | /*format the look of items in the list*/ |
|
436 | /*format the look of items in the list*/ | |
437 | var format = function(state, escapeMarkup){ |
|
437 | var format = function(state, escapeMarkup){ | |
438 | if (!state.id){ |
|
438 | if (!state.id){ | |
439 | return state.text; // optgroup |
|
439 | return state.text; // optgroup | |
440 | } |
|
440 | } | |
441 | var obj_dict = state.obj; |
|
441 | var obj_dict = state.obj; | |
442 | var tmpl = ''; |
|
442 | var tmpl = ''; | |
443 |
|
443 | |||
444 | if(obj_dict && state.type == 'repo'){ |
|
444 | if(obj_dict && state.type == 'repo'){ | |
445 | if(obj_dict['repo_type'] === 'hg'){ |
|
445 | if(obj_dict['repo_type'] === 'hg'){ | |
446 | tmpl += '<i class="icon-hg"></i> '; |
|
446 | tmpl += '<i class="icon-hg"></i> '; | |
447 | } |
|
447 | } | |
448 | else if(obj_dict['repo_type'] === 'git'){ |
|
448 | else if(obj_dict['repo_type'] === 'git'){ | |
449 | tmpl += '<i class="icon-git"></i> '; |
|
449 | tmpl += '<i class="icon-git"></i> '; | |
450 | } |
|
450 | } | |
451 | else if(obj_dict['repo_type'] === 'svn'){ |
|
451 | else if(obj_dict['repo_type'] === 'svn'){ | |
452 | tmpl += '<i class="icon-svn"></i> '; |
|
452 | tmpl += '<i class="icon-svn"></i> '; | |
453 | } |
|
453 | } | |
454 | if(obj_dict['private']){ |
|
454 | if(obj_dict['private']){ | |
455 | tmpl += '<i class="icon-lock" ></i> '; |
|
455 | tmpl += '<i class="icon-lock" ></i> '; | |
456 | } |
|
456 | } | |
457 | else if(visual_show_public_icon){ |
|
457 | else if(visual_show_public_icon){ | |
458 | tmpl += '<i class="icon-unlock-alt"></i> '; |
|
458 | tmpl += '<i class="icon-unlock-alt"></i> '; | |
459 | } |
|
459 | } | |
460 | } |
|
460 | } | |
461 | if(obj_dict && state.type == 'commit') { |
|
461 | if(obj_dict && state.type == 'commit') { | |
462 | tmpl += '<i class="icon-tag"></i>'; |
|
462 | tmpl += '<i class="icon-tag"></i>'; | |
463 | } |
|
463 | } | |
464 | if(obj_dict && state.type == 'group'){ |
|
464 | if(obj_dict && state.type == 'group'){ | |
465 | tmpl += '<i class="icon-folder-close"></i> '; |
|
465 | tmpl += '<i class="icon-folder-close"></i> '; | |
466 | } |
|
466 | } | |
467 | tmpl += escapeMarkup(state.text); |
|
467 | tmpl += escapeMarkup(state.text); | |
468 | return tmpl; |
|
468 | return tmpl; | |
469 | }; |
|
469 | }; | |
470 |
|
470 | |||
471 | var formatResult = function(result, container, query, escapeMarkup) { |
|
471 | var formatResult = function(result, container, query, escapeMarkup) { | |
472 | return format(result, escapeMarkup); |
|
472 | return format(result, escapeMarkup); | |
473 | }; |
|
473 | }; | |
474 |
|
474 | |||
475 | var formatSelection = function(data, container, escapeMarkup) { |
|
475 | var formatSelection = function(data, container, escapeMarkup) { | |
476 | return format(data, escapeMarkup); |
|
476 | return format(data, escapeMarkup); | |
477 | }; |
|
477 | }; | |
478 |
|
478 | |||
479 | $("#repo_switcher").select2({ |
|
479 | $("#repo_switcher").select2({ | |
480 | cachedDataSource: {}, |
|
480 | cachedDataSource: {}, | |
481 | minimumInputLength: 2, |
|
481 | minimumInputLength: 2, | |
482 | placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>', |
|
482 | placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>', | |
483 | dropdownAutoWidth: true, |
|
483 | dropdownAutoWidth: true, | |
484 | formatResult: formatResult, |
|
484 | formatResult: formatResult, | |
485 | formatSelection: formatSelection, |
|
485 | formatSelection: formatSelection, | |
486 | containerCssClass: "repo-switcher", |
|
486 | containerCssClass: "repo-switcher", | |
487 | dropdownCssClass: "repo-switcher-dropdown", |
|
487 | dropdownCssClass: "repo-switcher-dropdown", | |
488 | escapeMarkup: function(m){ |
|
488 | escapeMarkup: function(m){ | |
489 | // don't escape our custom placeholder |
|
489 | // don't escape our custom placeholder | |
490 | if(m.substr(0,23) == '<div class="menulabel">'){ |
|
490 | if(m.substr(0,23) == '<div class="menulabel">'){ | |
491 | return m; |
|
491 | return m; | |
492 | } |
|
492 | } | |
493 |
|
493 | |||
494 | return Select2.util.escapeMarkup(m); |
|
494 | return Select2.util.escapeMarkup(m); | |
495 | }, |
|
495 | }, | |
496 | query: $.debounce(250, function(query){ |
|
496 | query: $.debounce(250, function(query){ | |
497 | self = this; |
|
497 | self = this; | |
498 | var cacheKey = query.term; |
|
498 | var cacheKey = query.term; | |
499 | var cachedData = self.cachedDataSource[cacheKey]; |
|
499 | var cachedData = self.cachedDataSource[cacheKey]; | |
500 |
|
500 | |||
501 | if (cachedData) { |
|
501 | if (cachedData) { | |
502 | query.callback({results: cachedData.results}); |
|
502 | query.callback({results: cachedData.results}); | |
503 | } else { |
|
503 | } else { | |
504 | $.ajax({ |
|
504 | $.ajax({ | |
505 | url: pyroutes.url('goto_switcher_data'), |
|
505 | url: pyroutes.url('goto_switcher_data'), | |
506 | data: {'query': query.term}, |
|
506 | data: {'query': query.term}, | |
507 | dataType: 'json', |
|
507 | dataType: 'json', | |
508 | type: 'GET', |
|
508 | type: 'GET', | |
509 | success: function(data) { |
|
509 | success: function(data) { | |
510 | self.cachedDataSource[cacheKey] = data; |
|
510 | self.cachedDataSource[cacheKey] = data; | |
511 | query.callback({results: data.results}); |
|
511 | query.callback({results: data.results}); | |
512 | }, |
|
512 | }, | |
513 | error: function(data, textStatus, errorThrown) { |
|
513 | error: function(data, textStatus, errorThrown) { | |
514 | alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText)); |
|
514 | alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText)); | |
515 | } |
|
515 | } | |
516 | }) |
|
516 | }) | |
517 | } |
|
517 | } | |
518 | }) |
|
518 | }) | |
519 | }); |
|
519 | }); | |
520 |
|
520 | |||
521 | $("#repo_switcher").on('select2-selecting', function(e){ |
|
521 | $("#repo_switcher").on('select2-selecting', function(e){ | |
522 | e.preventDefault(); |
|
522 | e.preventDefault(); | |
523 | window.location = e.choice.url; |
|
523 | window.location = e.choice.url; | |
524 | }); |
|
524 | }); | |
525 |
|
525 | |||
526 | </script> |
|
526 | </script> | |
527 | <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script> |
|
527 | <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script> | |
528 | </%def> |
|
528 | </%def> | |
529 |
|
529 | |||
530 | <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> |
|
530 | <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> | |
531 | <div class="modal-dialog"> |
|
531 | <div class="modal-dialog"> | |
532 | <div class="modal-content"> |
|
532 | <div class="modal-content"> | |
533 | <div class="modal-header"> |
|
533 | <div class="modal-header"> | |
534 | <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> |
|
534 | <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> | |
535 | <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4> |
|
535 | <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4> | |
536 | </div> |
|
536 | </div> | |
537 | <div class="modal-body"> |
|
537 | <div class="modal-body"> | |
538 | <div class="block-left"> |
|
538 | <div class="block-left"> | |
539 | <table class="keyboard-mappings"> |
|
539 | <table class="keyboard-mappings"> | |
540 | <tbody> |
|
540 | <tbody> | |
541 | <tr> |
|
541 | <tr> | |
542 | <th></th> |
|
542 | <th></th> | |
543 | <th>${_('Site-wide shortcuts')}</th> |
|
543 | <th>${_('Site-wide shortcuts')}</th> | |
544 | </tr> |
|
544 | </tr> | |
545 | <% |
|
545 | <% | |
546 | elems = [ |
|
546 | elems = [ | |
547 | ('/', 'Open quick search box'), |
|
547 | ('/', 'Open quick search box'), | |
548 | ('g h', 'Goto home page'), |
|
548 | ('g h', 'Goto home page'), | |
549 | ('g g', 'Goto my private gists page'), |
|
549 | ('g g', 'Goto my private gists page'), | |
550 | ('g G', 'Goto my public gists page'), |
|
550 | ('g G', 'Goto my public gists page'), | |
551 | ('n r', 'New repository page'), |
|
551 | ('n r', 'New repository page'), | |
552 | ('n g', 'New gist page'), |
|
552 | ('n g', 'New gist page'), | |
553 | ] |
|
553 | ] | |
554 | %> |
|
554 | %> | |
555 | %for key, desc in elems: |
|
555 | %for key, desc in elems: | |
556 | <tr> |
|
556 | <tr> | |
557 | <td class="keys"> |
|
557 | <td class="keys"> | |
558 | <span class="key tag">${key}</span> |
|
558 | <span class="key tag">${key}</span> | |
559 | </td> |
|
559 | </td> | |
560 | <td>${desc}</td> |
|
560 | <td>${desc}</td> | |
561 | </tr> |
|
561 | </tr> | |
562 | %endfor |
|
562 | %endfor | |
563 | </tbody> |
|
563 | </tbody> | |
564 | </table> |
|
564 | </table> | |
565 | </div> |
|
565 | </div> | |
566 | <div class="block-left"> |
|
566 | <div class="block-left"> | |
567 | <table class="keyboard-mappings"> |
|
567 | <table class="keyboard-mappings"> | |
568 | <tbody> |
|
568 | <tbody> | |
569 | <tr> |
|
569 | <tr> | |
570 | <th></th> |
|
570 | <th></th> | |
571 | <th>${_('Repositories')}</th> |
|
571 | <th>${_('Repositories')}</th> | |
572 | </tr> |
|
572 | </tr> | |
573 | <% |
|
573 | <% | |
574 | elems = [ |
|
574 | elems = [ | |
575 | ('g s', 'Goto summary page'), |
|
575 | ('g s', 'Goto summary page'), | |
576 | ('g c', 'Goto changelog page'), |
|
576 | ('g c', 'Goto changelog page'), | |
577 | ('g f', 'Goto files page'), |
|
577 | ('g f', 'Goto files page'), | |
578 | ('g F', 'Goto files page with file search activated'), |
|
578 | ('g F', 'Goto files page with file search activated'), | |
579 | ('g p', 'Goto pull requests page'), |
|
579 | ('g p', 'Goto pull requests page'), | |
580 | ('g o', 'Goto repository settings'), |
|
580 | ('g o', 'Goto repository settings'), | |
581 | ('g O', 'Goto repository permissions settings'), |
|
581 | ('g O', 'Goto repository permissions settings'), | |
582 | ] |
|
582 | ] | |
583 | %> |
|
583 | %> | |
584 | %for key, desc in elems: |
|
584 | %for key, desc in elems: | |
585 | <tr> |
|
585 | <tr> | |
586 | <td class="keys"> |
|
586 | <td class="keys"> | |
587 | <span class="key tag">${key}</span> |
|
587 | <span class="key tag">${key}</span> | |
588 | </td> |
|
588 | </td> | |
589 | <td>${desc}</td> |
|
589 | <td>${desc}</td> | |
590 | </tr> |
|
590 | </tr> | |
591 | %endfor |
|
591 | %endfor | |
592 | </tbody> |
|
592 | </tbody> | |
593 | </table> |
|
593 | </table> | |
594 | </div> |
|
594 | </div> | |
595 | </div> |
|
595 | </div> | |
596 | <div class="modal-footer"> |
|
596 | <div class="modal-footer"> | |
597 | </div> |
|
597 | </div> | |
598 | </div><!-- /.modal-content --> |
|
598 | </div><!-- /.modal-content --> | |
599 | </div><!-- /.modal-dialog --> |
|
599 | </div><!-- /.modal-dialog --> | |
600 | </div><!-- /.modal --> |
|
600 | </div><!-- /.modal --> |
@@ -1,33 +1,33 b'' | |||||
1 | ## DATA TABLE RE USABLE ELEMENTS FOR BOOKMARKS |
|
1 | ## DATA TABLE RE USABLE ELEMENTS FOR BOOKMARKS | |
2 | ## usage: |
|
2 | ## usage: | |
3 | ## <%namespace name="bookmarks" file="/bookmarks/bookmarks_data.mako"/> |
|
3 | ## <%namespace name="bookmarks" file="/bookmarks/bookmarks_data.mako"/> | |
4 | ## bookmarks.<func_name>(arg,arg2) |
|
4 | ## bookmarks.<func_name>(arg,arg2) | |
5 |
|
5 | |||
6 | <%def name="compare(commit_id)"> |
|
6 | <%def name="compare(commit_id)"> | |
7 | <input class="compare-radio-button" type="radio" name="compare_source" value="${commit_id}"/> |
|
7 | <input class="compare-radio-button" type="radio" name="compare_source" value="${commit_id}"/> | |
8 | <input class="compare-radio-button" type="radio" name="compare_target" value="${commit_id}"/> |
|
8 | <input class="compare-radio-button" type="radio" name="compare_target" value="${commit_id}"/> | |
9 | </%def> |
|
9 | </%def> | |
10 |
|
10 | |||
11 |
|
11 | |||
12 | <%def name="name(name, files_url, closed)"> |
|
12 | <%def name="name(name, files_url, closed)"> | |
13 | <span class="tag booktag" title="${h.tooltip(_('Bookmark %s') % (name,))}"> |
|
13 | <span class="tag booktag" title="${h.tooltip(_('Bookmark %s') % (name,))}"> | |
14 | <a href="${files_url}"> |
|
14 | <a href="${files_url}"> | |
15 | <i class="icon-bookmark"></i> |
|
15 | <i class="icon-bookmark"></i> | |
16 | ${name} |
|
16 | ${name} | |
17 | </a> |
|
17 | </a> | |
18 | </span> |
|
18 | </span> | |
19 | </%def> |
|
19 | </%def> | |
20 |
|
20 | |||
21 | <%def name="date(date)"> |
|
21 | <%def name="date(date)"> | |
22 | ${h.age_component(date)} |
|
22 | ${h.age_component(date)} | |
23 | </%def> |
|
23 | </%def> | |
24 |
|
24 | |||
25 | <%def name="author(author)"> |
|
25 | <%def name="author(author)"> | |
26 | <span class="tooltip" title="${h.tooltip(author)}">${h.link_to_user(author)}</span> |
|
26 | <span class="tooltip" title="${h.tooltip(author)}">${h.link_to_user(author)}</span> | |
27 | </%def> |
|
27 | </%def> | |
28 |
|
28 | |||
29 | <%def name="commit(message, commit_id, commit_idx)"> |
|
29 | <%def name="commit(message, commit_id, commit_idx)"> | |
30 | <div> |
|
30 | <div> | |
31 |
<pre><a title="${h.tooltip(message)}" href="${h. |
|
31 | <pre><a title="${h.tooltip(message)}" href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit_id)}">r${commit_idx}:${h.short_id(commit_id)}</a></pre> | |
32 | </div> |
|
32 | </div> | |
33 | </%def> |
|
33 | </%def> |
@@ -1,33 +1,33 b'' | |||||
1 | ## DATA TABLE RE USABLE ELEMENTS FOR BRANCHES |
|
1 | ## DATA TABLE RE USABLE ELEMENTS FOR BRANCHES | |
2 | ## usage: |
|
2 | ## usage: | |
3 | ## <%namespace name="branch" file="/branches/branches_data.mako"/> |
|
3 | ## <%namespace name="branch" file="/branches/branches_data.mako"/> | |
4 | ## branch.<func_name>(arg,arg2) |
|
4 | ## branch.<func_name>(arg,arg2) | |
5 |
|
5 | |||
6 | <%def name="compare(commit_id)"> |
|
6 | <%def name="compare(commit_id)"> | |
7 | <input class="compare-radio-button" type="radio" name="compare_source" value="${commit_id}"/> |
|
7 | <input class="compare-radio-button" type="radio" name="compare_source" value="${commit_id}"/> | |
8 | <input class="compare-radio-button" type="radio" name="compare_target" value="${commit_id}"/> |
|
8 | <input class="compare-radio-button" type="radio" name="compare_target" value="${commit_id}"/> | |
9 | </%def> |
|
9 | </%def> | |
10 |
|
10 | |||
11 | <%def name="name(name, files_url, closed)"> |
|
11 | <%def name="name(name, files_url, closed)"> | |
12 | <span class="tag branchtag" title="${h.tooltip(_('Branch %s') % (name,))}"> |
|
12 | <span class="tag branchtag" title="${h.tooltip(_('Branch %s') % (name,))}"> | |
13 | <a href="${files_url}"><i class="icon-code-fork"></i>${name} |
|
13 | <a href="${files_url}"><i class="icon-code-fork"></i>${name} | |
14 | %if closed: |
|
14 | %if closed: | |
15 | [closed] |
|
15 | [closed] | |
16 | %endif |
|
16 | %endif | |
17 | </a> |
|
17 | </a> | |
18 | </span> |
|
18 | </span> | |
19 | </%def> |
|
19 | </%def> | |
20 |
|
20 | |||
21 | <%def name="date(date)"> |
|
21 | <%def name="date(date)"> | |
22 | ${h.age_component(date)} |
|
22 | ${h.age_component(date)} | |
23 | </%def> |
|
23 | </%def> | |
24 |
|
24 | |||
25 | <%def name="author(author)"> |
|
25 | <%def name="author(author)"> | |
26 | <span class="tooltip" title="${h.tooltip(author)}">${h.link_to_user(author)}</span> |
|
26 | <span class="tooltip" title="${h.tooltip(author)}">${h.link_to_user(author)}</span> | |
27 | </%def> |
|
27 | </%def> | |
28 |
|
28 | |||
29 | <%def name="commit(message, commit_id, commit_idx)"> |
|
29 | <%def name="commit(message, commit_id, commit_idx)"> | |
30 | <div> |
|
30 | <div> | |
31 |
<pre><a title="${h.tooltip(message)}" href="${h. |
|
31 | <pre><a title="${h.tooltip(message)}" href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit_id)}">r${commit_idx}:${h.short_id(commit_id)}</a></pre> | |
32 | </div> |
|
32 | </div> | |
33 | </%def> |
|
33 | </%def> |
@@ -1,142 +1,142 b'' | |||||
1 | ## small box that displays changed/added/removed details fetched by AJAX |
|
1 | ## small box that displays changed/added/removed details fetched by AJAX | |
2 | <%namespace name="base" file="/base/base.mako"/> |
|
2 | <%namespace name="base" file="/base/base.mako"/> | |
3 |
|
3 | |||
4 |
|
4 | |||
5 | % if c.prev_page: |
|
5 | % if c.prev_page: | |
6 | <tr> |
|
6 | <tr> | |
7 | <td colspan="9" class="load-more-commits"> |
|
7 | <td colspan="9" class="load-more-commits"> | |
8 | <a class="prev-commits" href="#loadPrevCommits" onclick="commitsController.loadPrev(this, ${c.prev_page}, '${c.branch_name}');return false"> |
|
8 | <a class="prev-commits" href="#loadPrevCommits" onclick="commitsController.loadPrev(this, ${c.prev_page}, '${c.branch_name}');return false"> | |
9 | ${_('load previous')} |
|
9 | ${_('load previous')} | |
10 | </a> |
|
10 | </a> | |
11 | </td> |
|
11 | </td> | |
12 | </tr> |
|
12 | </tr> | |
13 | % endif |
|
13 | % endif | |
14 |
|
14 | |||
15 | % for cnt,commit in enumerate(c.pagination): |
|
15 | % for cnt,commit in enumerate(c.pagination): | |
16 | <tr id="sha_${commit.raw_id}" class="changelogRow container ${'tablerow%s' % (cnt%2)}"> |
|
16 | <tr id="sha_${commit.raw_id}" class="changelogRow container ${'tablerow%s' % (cnt%2)}"> | |
17 |
|
17 | |||
18 | <td class="td-checkbox"> |
|
18 | <td class="td-checkbox"> | |
19 | ${h.checkbox(commit.raw_id,class_="commit-range")} |
|
19 | ${h.checkbox(commit.raw_id,class_="commit-range")} | |
20 | </td> |
|
20 | </td> | |
21 | <td class="td-status"> |
|
21 | <td class="td-status"> | |
22 |
|
22 | |||
23 | %if c.statuses.get(commit.raw_id): |
|
23 | %if c.statuses.get(commit.raw_id): | |
24 | <div class="changeset-status-ico"> |
|
24 | <div class="changeset-status-ico"> | |
25 | %if c.statuses.get(commit.raw_id)[2]: |
|
25 | %if c.statuses.get(commit.raw_id)[2]: | |
26 | <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]), c.statuses.get(commit.raw_id)[2])}" href="${h.route_path('pullrequest_show',repo_name=c.statuses.get(commit.raw_id)[3],pull_request_id=c.statuses.get(commit.raw_id)[2])}"> |
|
26 | <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]), c.statuses.get(commit.raw_id)[2])}" href="${h.route_path('pullrequest_show',repo_name=c.statuses.get(commit.raw_id)[3],pull_request_id=c.statuses.get(commit.raw_id)[2])}"> | |
27 | <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div> |
|
27 | <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div> | |
28 | </a> |
|
28 | </a> | |
29 | %else: |
|
29 | %else: | |
30 | <a class="tooltip" title="${_('Commit status: %s') % h.commit_status_lbl(c.statuses.get(commit.raw_id)[0])}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}"> |
|
30 | <a class="tooltip" title="${_('Commit status: %s') % h.commit_status_lbl(c.statuses.get(commit.raw_id)[0])}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}"> | |
31 | <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div> |
|
31 | <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div> | |
32 | </a> |
|
32 | </a> | |
33 | %endif |
|
33 | %endif | |
34 | </div> |
|
34 | </div> | |
35 | %else: |
|
35 | %else: | |
36 | <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div> |
|
36 | <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div> | |
37 | %endif |
|
37 | %endif | |
38 | </td> |
|
38 | </td> | |
39 | <td class="td-comments comments-col"> |
|
39 | <td class="td-comments comments-col"> | |
40 | %if c.comments.get(commit.raw_id): |
|
40 | %if c.comments.get(commit.raw_id): | |
41 | <a title="${_('Commit has comments')}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}"> |
|
41 | <a title="${_('Commit has comments')}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}"> | |
42 | <i class="icon-comment"></i> ${len(c.comments[commit.raw_id])} |
|
42 | <i class="icon-comment"></i> ${len(c.comments[commit.raw_id])} | |
43 | </a> |
|
43 | </a> | |
44 | %endif |
|
44 | %endif | |
45 | </td> |
|
45 | </td> | |
46 | <td class="td-hash"> |
|
46 | <td class="td-hash"> | |
47 | <code> |
|
47 | <code> | |
48 | <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id)}"> |
|
48 | <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id)}"> | |
49 | <span class="${'commit_hash obsolete' if getattr(commit, 'obsolete', None) else 'commit_hash'}">${h.show_id(commit)}</span> |
|
49 | <span class="${'commit_hash obsolete' if getattr(commit, 'obsolete', None) else 'commit_hash'}">${h.show_id(commit)}</span> | |
50 | </a> |
|
50 | </a> | |
51 | % if hasattr(commit, 'phase'): |
|
51 | % if hasattr(commit, 'phase'): | |
52 | % if commit.phase != 'public': |
|
52 | % if commit.phase != 'public': | |
53 | <span class="tag phase-${commit.phase} tooltip" title="${_('Commit phase')}">${commit.phase}</span> |
|
53 | <span class="tag phase-${commit.phase} tooltip" title="${_('Commit phase')}">${commit.phase}</span> | |
54 | % endif |
|
54 | % endif | |
55 | % endif |
|
55 | % endif | |
56 |
|
56 | |||
57 | ## obsolete commits |
|
57 | ## obsolete commits | |
58 | % if hasattr(commit, 'obsolete'): |
|
58 | % if hasattr(commit, 'obsolete'): | |
59 | % if commit.obsolete: |
|
59 | % if commit.obsolete: | |
60 | <span class="tag obsolete-${commit.obsolete} tooltip" title="${_('Evolve State')}">${_('obsolete')}</span> |
|
60 | <span class="tag obsolete-${commit.obsolete} tooltip" title="${_('Evolve State')}">${_('obsolete')}</span> | |
61 | % endif |
|
61 | % endif | |
62 | % endif |
|
62 | % endif | |
63 |
|
63 | |||
64 | ## hidden commits |
|
64 | ## hidden commits | |
65 | % if hasattr(commit, 'hidden'): |
|
65 | % if hasattr(commit, 'hidden'): | |
66 | % if commit.hidden: |
|
66 | % if commit.hidden: | |
67 | <span class="tag obsolete-${commit.hidden} tooltip" title="${_('Evolve State')}">${_('hidden')}</span> |
|
67 | <span class="tag obsolete-${commit.hidden} tooltip" title="${_('Evolve State')}">${_('hidden')}</span> | |
68 | % endif |
|
68 | % endif | |
69 | % endif |
|
69 | % endif | |
70 |
|
70 | |||
71 | </code> |
|
71 | </code> | |
72 | </td> |
|
72 | </td> | |
73 | <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_('Expand commit message')}" onclick="commitsController.expandCommit(this); return false"> |
|
73 | <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_('Expand commit message')}" onclick="commitsController.expandCommit(this); return false"> | |
74 | <div class="show_more_col"> |
|
74 | <div class="show_more_col"> | |
75 | <i class="show_more"></i> |
|
75 | <i class="show_more"></i> | |
76 | </div> |
|
76 | </div> | |
77 | </td> |
|
77 | </td> | |
78 | <td class="td-description mid"> |
|
78 | <td class="td-description mid"> | |
79 | <div class="log-container truncate-wrap"> |
|
79 | <div class="log-container truncate-wrap"> | |
80 | <div class="message truncate" id="c-${commit.raw_id}">${h.urlify_commit_message(commit.message, c.repo_name)}</div> |
|
80 | <div class="message truncate" id="c-${commit.raw_id}">${h.urlify_commit_message(commit.message, c.repo_name)}</div> | |
81 | </div> |
|
81 | </div> | |
82 | </td> |
|
82 | </td> | |
83 |
|
83 | |||
84 | <td class="td-time"> |
|
84 | <td class="td-time"> | |
85 | ${h.age_component(commit.date)} |
|
85 | ${h.age_component(commit.date)} | |
86 | </td> |
|
86 | </td> | |
87 | <td class="td-user"> |
|
87 | <td class="td-user"> | |
88 | ${base.gravatar_with_user(commit.author)} |
|
88 | ${base.gravatar_with_user(commit.author)} | |
89 | </td> |
|
89 | </td> | |
90 |
|
90 | |||
91 | <td class="td-tags tags-col"> |
|
91 | <td class="td-tags tags-col"> | |
92 | <div id="t-${commit.raw_id}"> |
|
92 | <div id="t-${commit.raw_id}"> | |
93 |
|
93 | |||
94 | ## merge |
|
94 | ## merge | |
95 | %if commit.merge: |
|
95 | %if commit.merge: | |
96 | <span class="tag mergetag"> |
|
96 | <span class="tag mergetag"> | |
97 | <i class="icon-merge"></i>${_('merge')} |
|
97 | <i class="icon-merge"></i>${_('merge')} | |
98 | </span> |
|
98 | </span> | |
99 | %endif |
|
99 | %endif | |
100 |
|
100 | |||
101 | ## branch |
|
101 | ## branch | |
102 | %if commit.branch: |
|
102 | %if commit.branch: | |
103 | <span class="tag branchtag" title="${h.tooltip(_('Branch %s') % commit.branch)}"> |
|
103 | <span class="tag branchtag" title="${h.tooltip(_('Branch %s') % commit.branch)}"> | |
104 | <a href="${h.url('changelog_home',repo_name=c.repo_name,branch=commit.branch)}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a> |
|
104 | <a href="${h.url('changelog_home',repo_name=c.repo_name,branch=commit.branch)}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a> | |
105 | </span> |
|
105 | </span> | |
106 | %endif |
|
106 | %endif | |
107 |
|
107 | |||
108 | ## bookmarks |
|
108 | ## bookmarks | |
109 | %if h.is_hg(c.rhodecode_repo): |
|
109 | %if h.is_hg(c.rhodecode_repo): | |
110 | %for book in commit.bookmarks: |
|
110 | %for book in commit.bookmarks: | |
111 | <span class="tag booktag" title="${h.tooltip(_('Bookmark %s') % book)}"> |
|
111 | <span class="tag booktag" title="${h.tooltip(_('Bookmark %s') % book)}"> | |
112 |
<a href="${h. |
|
112 | <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id, _query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a> | |
113 | </span> |
|
113 | </span> | |
114 | %endfor |
|
114 | %endfor | |
115 | %endif |
|
115 | %endif | |
116 |
|
116 | |||
117 | ## tags |
|
117 | ## tags | |
118 | %for tag in commit.tags: |
|
118 | %for tag in commit.tags: | |
119 | <span class="tag tagtag" title="${h.tooltip(_('Tag %s') % tag)}"> |
|
119 | <span class="tag tagtag" title="${h.tooltip(_('Tag %s') % tag)}"> | |
120 |
<a href="${h. |
|
120 | <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id, _query=dict(at=tag))}"><i class="icon-tag"></i>${h.shorter(tag)}</a> | |
121 | </span> |
|
121 | </span> | |
122 | %endfor |
|
122 | %endfor | |
123 |
|
123 | |||
124 | </div> |
|
124 | </div> | |
125 | </td> |
|
125 | </td> | |
126 | </tr> |
|
126 | </tr> | |
127 | % endfor |
|
127 | % endfor | |
128 |
|
128 | |||
129 | % if c.next_page: |
|
129 | % if c.next_page: | |
130 | <tr> |
|
130 | <tr> | |
131 | <td colspan="9" class="load-more-commits"> |
|
131 | <td colspan="9" class="load-more-commits"> | |
132 | <a class="next-commits" href="#loadNextCommits" onclick="commitsController.loadNext(this, ${c.next_page}, '${c.branch_name}');return false"> |
|
132 | <a class="next-commits" href="#loadNextCommits" onclick="commitsController.loadNext(this, ${c.next_page}, '${c.branch_name}');return false"> | |
133 | ${_('load next')} |
|
133 | ${_('load next')} | |
134 | </a> |
|
134 | </a> | |
135 | </td> |
|
135 | </td> | |
136 | </tr> |
|
136 | </tr> | |
137 | % endif |
|
137 | % endif | |
138 | <tr class="chunk-graph-data" style="display:none" |
|
138 | <tr class="chunk-graph-data" style="display:none" | |
139 | data-graph='${c.graph_data|n}' |
|
139 | data-graph='${c.graph_data|n}' | |
140 | data-node='${c.prev_page}:${c.next_page}' |
|
140 | data-node='${c.prev_page}:${c.next_page}' | |
141 | data-commits='${c.graph_commits|n}'> |
|
141 | data-commits='${c.graph_commits|n}'> | |
142 | </tr> No newline at end of file |
|
142 | </tr> |
@@ -1,39 +1,39 b'' | |||||
1 | <%namespace name="base" file="/base/base.mako"/> |
|
1 | <%namespace name="base" file="/base/base.mako"/> | |
2 | <div class="table"> |
|
2 | <div class="table"> | |
3 |
|
3 | |||
4 | <table class="table rctable file_history"> |
|
4 | <table class="table rctable file_history"> | |
5 | %for cnt,cs in enumerate(c.pagination): |
|
5 | %for cnt,cs in enumerate(c.pagination): | |
6 | <tr id="chg_${cnt+1}" class="${'tablerow%s' % (cnt%2)}"> |
|
6 | <tr id="chg_${cnt+1}" class="${'tablerow%s' % (cnt%2)}"> | |
7 | <td class="td-user"> |
|
7 | <td class="td-user"> | |
8 | ${base.gravatar_with_user(cs.author, 16)} |
|
8 | ${base.gravatar_with_user(cs.author, 16)} | |
9 | </td> |
|
9 | </td> | |
10 | <td class="td-time"> |
|
10 | <td class="td-time"> | |
11 | <div class="date"> |
|
11 | <div class="date"> | |
12 | ${h.age_component(cs.date)} |
|
12 | ${h.age_component(cs.date)} | |
13 | </div> |
|
13 | </div> | |
14 | </td> |
|
14 | </td> | |
15 | <td class="td-message"> |
|
15 | <td class="td-message"> | |
16 | <div class="log-container"> |
|
16 | <div class="log-container"> | |
17 | <div class="message_history" title="${h.tooltip(cs.message)}"> |
|
17 | <div class="message_history" title="${h.tooltip(cs.message)}"> | |
18 | <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"> |
|
18 | <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"> | |
19 | ${h.shorter(cs.message, 75)} |
|
19 | ${h.shorter(cs.message, 75)} | |
20 | </a> |
|
20 | </a> | |
21 | </div> |
|
21 | </div> | |
22 | </div> |
|
22 | </div> | |
23 | </td> |
|
23 | </td> | |
24 | <td class="td-hash"> |
|
24 | <td class="td-hash"> | |
25 | <code> |
|
25 | <code> | |
26 | <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"> |
|
26 | <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"> | |
27 | <span>${h.show_id(cs)}</span> |
|
27 | <span>${h.show_id(cs)}</span> | |
28 | </a> |
|
28 | </a> | |
29 | </code> |
|
29 | </code> | |
30 | </td> |
|
30 | </td> | |
31 | <td class="td-actions"> |
|
31 | <td class="td-actions"> | |
32 |
<a href="${h. |
|
32 | <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=cs.raw_id,f_path=c.changelog_for_path)}"> | |
33 | ${_('Show File')} |
|
33 | ${_('Show File')} | |
34 | </a> |
|
34 | </a> | |
35 | </td> |
|
35 | </td> | |
36 | </tr> |
|
36 | </tr> | |
37 | %endfor |
|
37 | %endfor | |
38 | </table> |
|
38 | </table> | |
39 | </div> |
|
39 | </div> |
@@ -1,351 +1,351 b'' | |||||
1 | ## -*- coding: utf-8 -*- |
|
1 | ## -*- coding: utf-8 -*- | |
2 |
|
2 | |||
3 | <%inherit file="/base/base.mako"/> |
|
3 | <%inherit file="/base/base.mako"/> | |
4 | <%namespace name="diff_block" file="/changeset/diff_block.mako"/> |
|
4 | <%namespace name="diff_block" file="/changeset/diff_block.mako"/> | |
5 |
|
5 | |||
6 | <%def name="title()"> |
|
6 | <%def name="title()"> | |
7 | ${_('%s Commit') % c.repo_name} - ${h.show_id(c.commit)} |
|
7 | ${_('%s Commit') % c.repo_name} - ${h.show_id(c.commit)} | |
8 | %if c.rhodecode_name: |
|
8 | %if c.rhodecode_name: | |
9 | · ${h.branding(c.rhodecode_name)} |
|
9 | · ${h.branding(c.rhodecode_name)} | |
10 | %endif |
|
10 | %endif | |
11 | </%def> |
|
11 | </%def> | |
12 |
|
12 | |||
13 | <%def name="menu_bar_nav()"> |
|
13 | <%def name="menu_bar_nav()"> | |
14 | ${self.menu_items(active='repositories')} |
|
14 | ${self.menu_items(active='repositories')} | |
15 | </%def> |
|
15 | </%def> | |
16 |
|
16 | |||
17 | <%def name="menu_bar_subnav()"> |
|
17 | <%def name="menu_bar_subnav()"> | |
18 | ${self.repo_menu(active='changelog')} |
|
18 | ${self.repo_menu(active='changelog')} | |
19 | </%def> |
|
19 | </%def> | |
20 |
|
20 | |||
21 | <%def name="main()"> |
|
21 | <%def name="main()"> | |
22 | <script> |
|
22 | <script> | |
23 | // TODO: marcink switch this to pyroutes |
|
23 | // TODO: marcink switch this to pyroutes | |
24 | AJAX_COMMENT_DELETE_URL = "${h.url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}"; |
|
24 | AJAX_COMMENT_DELETE_URL = "${h.url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}"; | |
25 | templateContext.commit_data.commit_id = "${c.commit.raw_id}"; |
|
25 | templateContext.commit_data.commit_id = "${c.commit.raw_id}"; | |
26 | </script> |
|
26 | </script> | |
27 | <div class="box"> |
|
27 | <div class="box"> | |
28 | <div class="title"> |
|
28 | <div class="title"> | |
29 | ${self.repo_page_title(c.rhodecode_db_repo)} |
|
29 | ${self.repo_page_title(c.rhodecode_db_repo)} | |
30 | </div> |
|
30 | </div> | |
31 |
|
31 | |||
32 | <div id="changeset_compare_view_content" class="summary changeset"> |
|
32 | <div id="changeset_compare_view_content" class="summary changeset"> | |
33 | <div class="summary-detail"> |
|
33 | <div class="summary-detail"> | |
34 | <div class="summary-detail-header"> |
|
34 | <div class="summary-detail-header"> | |
35 | <span class="breadcrumbs files_location"> |
|
35 | <span class="breadcrumbs files_location"> | |
36 | <h4>${_('Commit')} |
|
36 | <h4>${_('Commit')} | |
37 | <code> |
|
37 | <code> | |
38 | ${h.show_id(c.commit)} |
|
38 | ${h.show_id(c.commit)} | |
39 | % if hasattr(c.commit, 'phase'): |
|
39 | % if hasattr(c.commit, 'phase'): | |
40 | <span class="tag phase-${c.commit.phase} tooltip" title="${_('Commit phase')}">${c.commit.phase}</span> |
|
40 | <span class="tag phase-${c.commit.phase} tooltip" title="${_('Commit phase')}">${c.commit.phase}</span> | |
41 | % endif |
|
41 | % endif | |
42 |
|
42 | |||
43 | ## obsolete commits |
|
43 | ## obsolete commits | |
44 | % if hasattr(c.commit, 'obsolete'): |
|
44 | % if hasattr(c.commit, 'obsolete'): | |
45 | % if c.commit.obsolete: |
|
45 | % if c.commit.obsolete: | |
46 | <span class="tag obsolete-${c.commit.obsolete} tooltip" title="${_('Evolve State')}">${_('obsolete')}</span> |
|
46 | <span class="tag obsolete-${c.commit.obsolete} tooltip" title="${_('Evolve State')}">${_('obsolete')}</span> | |
47 | % endif |
|
47 | % endif | |
48 | % endif |
|
48 | % endif | |
49 |
|
49 | |||
50 | ## hidden commits |
|
50 | ## hidden commits | |
51 | % if hasattr(c.commit, 'hidden'): |
|
51 | % if hasattr(c.commit, 'hidden'): | |
52 | % if c.commit.hidden: |
|
52 | % if c.commit.hidden: | |
53 | <span class="tag hidden-${c.commit.hidden} tooltip" title="${_('Evolve State')}">${_('hidden')}</span> |
|
53 | <span class="tag hidden-${c.commit.hidden} tooltip" title="${_('Evolve State')}">${_('hidden')}</span> | |
54 | % endif |
|
54 | % endif | |
55 | % endif |
|
55 | % endif | |
56 |
|
56 | |||
57 | </code> |
|
57 | </code> | |
58 | </h4> |
|
58 | </h4> | |
59 | </span> |
|
59 | </span> | |
60 | <div class="pull-right"> |
|
60 | <div class="pull-right"> | |
61 | <span id="parent_link"> |
|
61 | <span id="parent_link"> | |
62 | <a href="#" title="${_('Parent Commit')}">${_('Parent')}</a> |
|
62 | <a href="#" title="${_('Parent Commit')}">${_('Parent')}</a> | |
63 | </span> |
|
63 | </span> | |
64 | | |
|
64 | | | |
65 | <span id="child_link"> |
|
65 | <span id="child_link"> | |
66 | <a href="#" title="${_('Child Commit')}">${_('Child')}</a> |
|
66 | <a href="#" title="${_('Child Commit')}">${_('Child')}</a> | |
67 | </span> |
|
67 | </span> | |
68 | </div> |
|
68 | </div> | |
69 | </div> |
|
69 | </div> | |
70 |
|
70 | |||
71 | <div class="fieldset"> |
|
71 | <div class="fieldset"> | |
72 | <div class="left-label"> |
|
72 | <div class="left-label"> | |
73 | ${_('Description')}: |
|
73 | ${_('Description')}: | |
74 | </div> |
|
74 | </div> | |
75 | <div class="right-content"> |
|
75 | <div class="right-content"> | |
76 | <div id="trimmed_message_box" class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div> |
|
76 | <div id="trimmed_message_box" class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div> | |
77 | <div id="message_expand" style="display:none;"> |
|
77 | <div id="message_expand" style="display:none;"> | |
78 | ${_('Expand')} |
|
78 | ${_('Expand')} | |
79 | </div> |
|
79 | </div> | |
80 | </div> |
|
80 | </div> | |
81 | </div> |
|
81 | </div> | |
82 |
|
82 | |||
83 | %if c.statuses: |
|
83 | %if c.statuses: | |
84 | <div class="fieldset"> |
|
84 | <div class="fieldset"> | |
85 | <div class="left-label"> |
|
85 | <div class="left-label"> | |
86 | ${_('Commit status')}: |
|
86 | ${_('Commit status')}: | |
87 | </div> |
|
87 | </div> | |
88 | <div class="right-content"> |
|
88 | <div class="right-content"> | |
89 | <div class="changeset-status-ico"> |
|
89 | <div class="changeset-status-ico"> | |
90 | <div class="${'flag_status %s' % c.statuses[0]} pull-left"></div> |
|
90 | <div class="${'flag_status %s' % c.statuses[0]} pull-left"></div> | |
91 | </div> |
|
91 | </div> | |
92 | <div title="${_('Commit status')}" class="changeset-status-lbl">[${h.commit_status_lbl(c.statuses[0])}]</div> |
|
92 | <div title="${_('Commit status')}" class="changeset-status-lbl">[${h.commit_status_lbl(c.statuses[0])}]</div> | |
93 | </div> |
|
93 | </div> | |
94 | </div> |
|
94 | </div> | |
95 | %endif |
|
95 | %endif | |
96 |
|
96 | |||
97 | <div class="fieldset"> |
|
97 | <div class="fieldset"> | |
98 | <div class="left-label"> |
|
98 | <div class="left-label"> | |
99 | ${_('References')}: |
|
99 | ${_('References')}: | |
100 | </div> |
|
100 | </div> | |
101 | <div class="right-content"> |
|
101 | <div class="right-content"> | |
102 | <div class="tags"> |
|
102 | <div class="tags"> | |
103 |
|
103 | |||
104 | %if c.commit.merge: |
|
104 | %if c.commit.merge: | |
105 | <span class="mergetag tag"> |
|
105 | <span class="mergetag tag"> | |
106 | <i class="icon-merge"></i>${_('merge')} |
|
106 | <i class="icon-merge"></i>${_('merge')} | |
107 | </span> |
|
107 | </span> | |
108 | %endif |
|
108 | %endif | |
109 |
|
109 | |||
110 | %if h.is_hg(c.rhodecode_repo): |
|
110 | %if h.is_hg(c.rhodecode_repo): | |
111 | %for book in c.commit.bookmarks: |
|
111 | %for book in c.commit.bookmarks: | |
112 | <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}"> |
|
112 | <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}"> | |
113 |
<a href="${h. |
|
113 | <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a> | |
114 | </span> |
|
114 | </span> | |
115 | %endfor |
|
115 | %endfor | |
116 | %endif |
|
116 | %endif | |
117 |
|
117 | |||
118 | %for tag in c.commit.tags: |
|
118 | %for tag in c.commit.tags: | |
119 | <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}"> |
|
119 | <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}"> | |
120 |
<a href="${h. |
|
120 | <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(at=tag))}"><i class="icon-tag"></i>${tag}</a> | |
121 | </span> |
|
121 | </span> | |
122 | %endfor |
|
122 | %endfor | |
123 |
|
123 | |||
124 | %if c.commit.branch: |
|
124 | %if c.commit.branch: | |
125 | <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % c.commit.branch)}"> |
|
125 | <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % c.commit.branch)}"> | |
126 |
<a href="${h. |
|
126 | <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(at=c.commit.branch))}"><i class="icon-code-fork"></i>${h.shorter(c.commit.branch)}</a> | |
127 | </span> |
|
127 | </span> | |
128 | %endif |
|
128 | %endif | |
129 | </div> |
|
129 | </div> | |
130 | </div> |
|
130 | </div> | |
131 | </div> |
|
131 | </div> | |
132 |
|
132 | |||
133 | <div class="fieldset"> |
|
133 | <div class="fieldset"> | |
134 | <div class="left-label"> |
|
134 | <div class="left-label"> | |
135 | ${_('Diff options')}: |
|
135 | ${_('Diff options')}: | |
136 | </div> |
|
136 | </div> | |
137 | <div class="right-content"> |
|
137 | <div class="right-content"> | |
138 | <div class="diff-actions"> |
|
138 | <div class="diff-actions"> | |
139 | <a href="${h.url('changeset_raw_home',repo_name=c.repo_name,revision=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Raw diff'))}"> |
|
139 | <a href="${h.url('changeset_raw_home',repo_name=c.repo_name,revision=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Raw diff'))}"> | |
140 | ${_('Raw Diff')} |
|
140 | ${_('Raw Diff')} | |
141 | </a> |
|
141 | </a> | |
142 | | |
|
142 | | | |
143 | <a href="${h.url('changeset_patch_home',repo_name=c.repo_name,revision=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Patch diff'))}"> |
|
143 | <a href="${h.url('changeset_patch_home',repo_name=c.repo_name,revision=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Patch diff'))}"> | |
144 | ${_('Patch Diff')} |
|
144 | ${_('Patch Diff')} | |
145 | </a> |
|
145 | </a> | |
146 | | |
|
146 | | | |
147 | <a href="${h.url('changeset_download_home',repo_name=c.repo_name,revision=c.commit.raw_id,diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}"> |
|
147 | <a href="${h.url('changeset_download_home',repo_name=c.repo_name,revision=c.commit.raw_id,diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}"> | |
148 | ${_('Download Diff')} |
|
148 | ${_('Download Diff')} | |
149 | </a> |
|
149 | </a> | |
150 | | |
|
150 | | | |
151 | ${c.ignorews_url(request.GET)} |
|
151 | ${c.ignorews_url(request.GET)} | |
152 | | |
|
152 | | | |
153 | ${c.context_url(request.GET)} |
|
153 | ${c.context_url(request.GET)} | |
154 | </div> |
|
154 | </div> | |
155 | </div> |
|
155 | </div> | |
156 | </div> |
|
156 | </div> | |
157 |
|
157 | |||
158 | <div class="fieldset"> |
|
158 | <div class="fieldset"> | |
159 | <div class="left-label"> |
|
159 | <div class="left-label"> | |
160 | ${_('Comments')}: |
|
160 | ${_('Comments')}: | |
161 | </div> |
|
161 | </div> | |
162 | <div class="right-content"> |
|
162 | <div class="right-content"> | |
163 | <div class="comments-number"> |
|
163 | <div class="comments-number"> | |
164 | %if c.comments: |
|
164 | %if c.comments: | |
165 | <a href="#comments">${ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}</a>, |
|
165 | <a href="#comments">${ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}</a>, | |
166 | %else: |
|
166 | %else: | |
167 | ${ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)} |
|
167 | ${ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)} | |
168 | %endif |
|
168 | %endif | |
169 | %if c.inline_cnt: |
|
169 | %if c.inline_cnt: | |
170 | <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}</a> |
|
170 | <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}</a> | |
171 | %else: |
|
171 | %else: | |
172 | ${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt} |
|
172 | ${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt} | |
173 | %endif |
|
173 | %endif | |
174 | </div> |
|
174 | </div> | |
175 | </div> |
|
175 | </div> | |
176 | </div> |
|
176 | </div> | |
177 |
|
177 | |||
178 | <div class="fieldset"> |
|
178 | <div class="fieldset"> | |
179 | <div class="left-label"> |
|
179 | <div class="left-label"> | |
180 | ${_('Unresolved TODOs')}: |
|
180 | ${_('Unresolved TODOs')}: | |
181 | </div> |
|
181 | </div> | |
182 | <div class="right-content"> |
|
182 | <div class="right-content"> | |
183 | <div class="comments-number"> |
|
183 | <div class="comments-number"> | |
184 | % if c.unresolved_comments: |
|
184 | % if c.unresolved_comments: | |
185 | % for co in c.unresolved_comments: |
|
185 | % for co in c.unresolved_comments: | |
186 | <a class="permalink" href="#comment-${co.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${co.comment_id}'))"> #${co.comment_id}</a>${'' if loop.last else ','} |
|
186 | <a class="permalink" href="#comment-${co.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${co.comment_id}'))"> #${co.comment_id}</a>${'' if loop.last else ','} | |
187 | % endfor |
|
187 | % endfor | |
188 | % else: |
|
188 | % else: | |
189 | ${_('There are no unresolved TODOs')} |
|
189 | ${_('There are no unresolved TODOs')} | |
190 | % endif |
|
190 | % endif | |
191 | </div> |
|
191 | </div> | |
192 | </div> |
|
192 | </div> | |
193 | </div> |
|
193 | </div> | |
194 |
|
194 | |||
195 | </div> <!-- end summary-detail --> |
|
195 | </div> <!-- end summary-detail --> | |
196 |
|
196 | |||
197 | <div id="commit-stats" class="sidebar-right"> |
|
197 | <div id="commit-stats" class="sidebar-right"> | |
198 | <div class="summary-detail-header"> |
|
198 | <div class="summary-detail-header"> | |
199 | <h4 class="item"> |
|
199 | <h4 class="item"> | |
200 | ${_('Author')} |
|
200 | ${_('Author')} | |
201 | </h4> |
|
201 | </h4> | |
202 | </div> |
|
202 | </div> | |
203 | <div class="sidebar-right-content"> |
|
203 | <div class="sidebar-right-content"> | |
204 | ${self.gravatar_with_user(c.commit.author)} |
|
204 | ${self.gravatar_with_user(c.commit.author)} | |
205 | <div class="user-inline-data">- ${h.age_component(c.commit.date)}</div> |
|
205 | <div class="user-inline-data">- ${h.age_component(c.commit.date)}</div> | |
206 | </div> |
|
206 | </div> | |
207 | </div><!-- end sidebar --> |
|
207 | </div><!-- end sidebar --> | |
208 | </div> <!-- end summary --> |
|
208 | </div> <!-- end summary --> | |
209 | <div class="cs_files"> |
|
209 | <div class="cs_files"> | |
210 | <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/> |
|
210 | <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/> | |
211 | ${cbdiffs.render_diffset_menu()} |
|
211 | ${cbdiffs.render_diffset_menu()} | |
212 | ${cbdiffs.render_diffset( |
|
212 | ${cbdiffs.render_diffset( | |
213 | c.changes[c.commit.raw_id], commit=c.commit, use_comments=True)} |
|
213 | c.changes[c.commit.raw_id], commit=c.commit, use_comments=True)} | |
214 | </div> |
|
214 | </div> | |
215 |
|
215 | |||
216 | ## template for inline comment form |
|
216 | ## template for inline comment form | |
217 | <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/> |
|
217 | <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/> | |
218 |
|
218 | |||
219 | ## render comments |
|
219 | ## render comments | |
220 | ${comment.generate_comments(c.comments)} |
|
220 | ${comment.generate_comments(c.comments)} | |
221 |
|
221 | |||
222 | ## main comment form and it status |
|
222 | ## main comment form and it status | |
223 | ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.commit.raw_id), |
|
223 | ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.commit.raw_id), | |
224 | h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))} |
|
224 | h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))} | |
225 | </div> |
|
225 | </div> | |
226 |
|
226 | |||
227 | ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS |
|
227 | ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS | |
228 | <script type="text/javascript"> |
|
228 | <script type="text/javascript"> | |
229 |
|
229 | |||
230 | $(document).ready(function() { |
|
230 | $(document).ready(function() { | |
231 |
|
231 | |||
232 | var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10); |
|
232 | var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10); | |
233 | if($('#trimmed_message_box').height() === boxmax){ |
|
233 | if($('#trimmed_message_box').height() === boxmax){ | |
234 | $('#message_expand').show(); |
|
234 | $('#message_expand').show(); | |
235 | } |
|
235 | } | |
236 |
|
236 | |||
237 | $('#message_expand').on('click', function(e){ |
|
237 | $('#message_expand').on('click', function(e){ | |
238 | $('#trimmed_message_box').css('max-height', 'none'); |
|
238 | $('#trimmed_message_box').css('max-height', 'none'); | |
239 | $(this).hide(); |
|
239 | $(this).hide(); | |
240 | }); |
|
240 | }); | |
241 |
|
241 | |||
242 | $('.show-inline-comments').on('click', function(e){ |
|
242 | $('.show-inline-comments').on('click', function(e){ | |
243 | var boxid = $(this).attr('data-comment-id'); |
|
243 | var boxid = $(this).attr('data-comment-id'); | |
244 | var button = $(this); |
|
244 | var button = $(this); | |
245 |
|
245 | |||
246 | if(button.hasClass("comments-visible")) { |
|
246 | if(button.hasClass("comments-visible")) { | |
247 | $('#{0} .inline-comments'.format(boxid)).each(function(index){ |
|
247 | $('#{0} .inline-comments'.format(boxid)).each(function(index){ | |
248 | $(this).hide(); |
|
248 | $(this).hide(); | |
249 | }); |
|
249 | }); | |
250 | button.removeClass("comments-visible"); |
|
250 | button.removeClass("comments-visible"); | |
251 | } else { |
|
251 | } else { | |
252 | $('#{0} .inline-comments'.format(boxid)).each(function(index){ |
|
252 | $('#{0} .inline-comments'.format(boxid)).each(function(index){ | |
253 | $(this).show(); |
|
253 | $(this).show(); | |
254 | }); |
|
254 | }); | |
255 | button.addClass("comments-visible"); |
|
255 | button.addClass("comments-visible"); | |
256 | } |
|
256 | } | |
257 | }); |
|
257 | }); | |
258 |
|
258 | |||
259 |
|
259 | |||
260 | // next links |
|
260 | // next links | |
261 | $('#child_link').on('click', function(e){ |
|
261 | $('#child_link').on('click', function(e){ | |
262 | // fetch via ajax what is going to be the next link, if we have |
|
262 | // fetch via ajax what is going to be the next link, if we have | |
263 | // >1 links show them to user to choose |
|
263 | // >1 links show them to user to choose | |
264 | if(!$('#child_link').hasClass('disabled')){ |
|
264 | if(!$('#child_link').hasClass('disabled')){ | |
265 | $.ajax({ |
|
265 | $.ajax({ | |
266 | url: '${h.url('changeset_children',repo_name=c.repo_name, revision=c.commit.raw_id)}', |
|
266 | url: '${h.url('changeset_children',repo_name=c.repo_name, revision=c.commit.raw_id)}', | |
267 | success: function(data) { |
|
267 | success: function(data) { | |
268 | if(data.results.length === 0){ |
|
268 | if(data.results.length === 0){ | |
269 | $('#child_link').html("${_('No Child Commits')}").addClass('disabled'); |
|
269 | $('#child_link').html("${_('No Child Commits')}").addClass('disabled'); | |
270 | } |
|
270 | } | |
271 | if(data.results.length === 1){ |
|
271 | if(data.results.length === 1){ | |
272 | var commit = data.results[0]; |
|
272 | var commit = data.results[0]; | |
273 | window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id}); |
|
273 | window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id}); | |
274 | } |
|
274 | } | |
275 | else if(data.results.length === 2){ |
|
275 | else if(data.results.length === 2){ | |
276 | $('#child_link').addClass('disabled'); |
|
276 | $('#child_link').addClass('disabled'); | |
277 | $('#child_link').addClass('double'); |
|
277 | $('#child_link').addClass('double'); | |
278 | var _html = ''; |
|
278 | var _html = ''; | |
279 | _html +='<a title="__title__" href="__url__">__rev__</a> ' |
|
279 | _html +='<a title="__title__" href="__url__">__rev__</a> ' | |
280 | .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6))) |
|
280 | .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6))) | |
281 | .replace('__title__', data.results[0].message) |
|
281 | .replace('__title__', data.results[0].message) | |
282 | .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id})); |
|
282 | .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id})); | |
283 | _html +=' | '; |
|
283 | _html +=' | '; | |
284 | _html +='<a title="__title__" href="__url__">__rev__</a> ' |
|
284 | _html +='<a title="__title__" href="__url__">__rev__</a> ' | |
285 | .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6))) |
|
285 | .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6))) | |
286 | .replace('__title__', data.results[1].message) |
|
286 | .replace('__title__', data.results[1].message) | |
287 | .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id})); |
|
287 | .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id})); | |
288 | $('#child_link').html(_html); |
|
288 | $('#child_link').html(_html); | |
289 | } |
|
289 | } | |
290 | } |
|
290 | } | |
291 | }); |
|
291 | }); | |
292 | e.preventDefault(); |
|
292 | e.preventDefault(); | |
293 | } |
|
293 | } | |
294 | }); |
|
294 | }); | |
295 |
|
295 | |||
296 | // prev links |
|
296 | // prev links | |
297 | $('#parent_link').on('click', function(e){ |
|
297 | $('#parent_link').on('click', function(e){ | |
298 | // fetch via ajax what is going to be the next link, if we have |
|
298 | // fetch via ajax what is going to be the next link, if we have | |
299 | // >1 links show them to user to choose |
|
299 | // >1 links show them to user to choose | |
300 | if(!$('#parent_link').hasClass('disabled')){ |
|
300 | if(!$('#parent_link').hasClass('disabled')){ | |
301 | $.ajax({ |
|
301 | $.ajax({ | |
302 | url: '${h.url("changeset_parents",repo_name=c.repo_name, revision=c.commit.raw_id)}', |
|
302 | url: '${h.url("changeset_parents",repo_name=c.repo_name, revision=c.commit.raw_id)}', | |
303 | success: function(data) { |
|
303 | success: function(data) { | |
304 | if(data.results.length === 0){ |
|
304 | if(data.results.length === 0){ | |
305 | $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled'); |
|
305 | $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled'); | |
306 | } |
|
306 | } | |
307 | if(data.results.length === 1){ |
|
307 | if(data.results.length === 1){ | |
308 | var commit = data.results[0]; |
|
308 | var commit = data.results[0]; | |
309 | window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id}); |
|
309 | window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id}); | |
310 | } |
|
310 | } | |
311 | else if(data.results.length === 2){ |
|
311 | else if(data.results.length === 2){ | |
312 | $('#parent_link').addClass('disabled'); |
|
312 | $('#parent_link').addClass('disabled'); | |
313 | $('#parent_link').addClass('double'); |
|
313 | $('#parent_link').addClass('double'); | |
314 | var _html = ''; |
|
314 | var _html = ''; | |
315 | _html +='<a title="__title__" href="__url__">Parent __rev__</a>' |
|
315 | _html +='<a title="__title__" href="__url__">Parent __rev__</a>' | |
316 | .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6))) |
|
316 | .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6))) | |
317 | .replace('__title__', data.results[0].message) |
|
317 | .replace('__title__', data.results[0].message) | |
318 | .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id})); |
|
318 | .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id})); | |
319 | _html +=' | '; |
|
319 | _html +=' | '; | |
320 | _html +='<a title="__title__" href="__url__">Parent __rev__</a>' |
|
320 | _html +='<a title="__title__" href="__url__">Parent __rev__</a>' | |
321 | .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6))) |
|
321 | .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6))) | |
322 | .replace('__title__', data.results[1].message) |
|
322 | .replace('__title__', data.results[1].message) | |
323 | .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id})); |
|
323 | .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id})); | |
324 | $('#parent_link').html(_html); |
|
324 | $('#parent_link').html(_html); | |
325 | } |
|
325 | } | |
326 | } |
|
326 | } | |
327 | }); |
|
327 | }); | |
328 | e.preventDefault(); |
|
328 | e.preventDefault(); | |
329 | } |
|
329 | } | |
330 | }); |
|
330 | }); | |
331 |
|
331 | |||
332 | if (location.hash) { |
|
332 | if (location.hash) { | |
333 | var result = splitDelimitedHash(location.hash); |
|
333 | var result = splitDelimitedHash(location.hash); | |
334 | var line = $('html').find(result.loc); |
|
334 | var line = $('html').find(result.loc); | |
335 | if (line.length > 0){ |
|
335 | if (line.length > 0){ | |
336 | offsetScroll(line, 70); |
|
336 | offsetScroll(line, 70); | |
337 | } |
|
337 | } | |
338 | } |
|
338 | } | |
339 |
|
339 | |||
340 | // browse tree @ revision |
|
340 | // browse tree @ revision | |
341 | $('#files_link').on('click', function(e){ |
|
341 | $('#files_link').on('click', function(e){ | |
342 |
window.location = '${h. |
|
342 | window.location = '${h.route_path('repo_files:default_path',repo_name=c.repo_name, commit_id=c.commit.raw_id)}'; | |
343 | e.preventDefault(); |
|
343 | e.preventDefault(); | |
344 | }); |
|
344 | }); | |
345 |
|
345 | |||
346 | // inject comments into their proper positions |
|
346 | // inject comments into their proper positions | |
347 | var file_comments = $('.inline-comment-placeholder'); |
|
347 | var file_comments = $('.inline-comment-placeholder'); | |
348 | }) |
|
348 | }) | |
349 | </script> |
|
349 | </script> | |
350 |
|
350 | |||
351 | </%def> |
|
351 | </%def> |
@@ -1,671 +1,671 b'' | |||||
1 | <%namespace name="commentblock" file="/changeset/changeset_file_comment.mako"/> |
|
1 | <%namespace name="commentblock" file="/changeset/changeset_file_comment.mako"/> | |
2 |
|
2 | |||
3 | <%def name="diff_line_anchor(filename, line, type)"><% |
|
3 | <%def name="diff_line_anchor(filename, line, type)"><% | |
4 | return '%s_%s_%i' % (h.safeid(filename), type, line) |
|
4 | return '%s_%s_%i' % (h.safeid(filename), type, line) | |
5 | %></%def> |
|
5 | %></%def> | |
6 |
|
6 | |||
7 | <%def name="action_class(action)"> |
|
7 | <%def name="action_class(action)"> | |
8 | <% |
|
8 | <% | |
9 | return { |
|
9 | return { | |
10 | '-': 'cb-deletion', |
|
10 | '-': 'cb-deletion', | |
11 | '+': 'cb-addition', |
|
11 | '+': 'cb-addition', | |
12 | ' ': 'cb-context', |
|
12 | ' ': 'cb-context', | |
13 | }.get(action, 'cb-empty') |
|
13 | }.get(action, 'cb-empty') | |
14 | %> |
|
14 | %> | |
15 | </%def> |
|
15 | </%def> | |
16 |
|
16 | |||
17 | <%def name="op_class(op_id)"> |
|
17 | <%def name="op_class(op_id)"> | |
18 | <% |
|
18 | <% | |
19 | return { |
|
19 | return { | |
20 | DEL_FILENODE: 'deletion', # file deleted |
|
20 | DEL_FILENODE: 'deletion', # file deleted | |
21 | BIN_FILENODE: 'warning' # binary diff hidden |
|
21 | BIN_FILENODE: 'warning' # binary diff hidden | |
22 | }.get(op_id, 'addition') |
|
22 | }.get(op_id, 'addition') | |
23 | %> |
|
23 | %> | |
24 | </%def> |
|
24 | </%def> | |
25 |
|
25 | |||
26 | <%def name="link_for(**kw)"> |
|
26 | <%def name="link_for(**kw)"> | |
27 | <% |
|
27 | <% | |
28 | new_args = request.GET.mixed() |
|
28 | new_args = request.GET.mixed() | |
29 | new_args.update(kw) |
|
29 | new_args.update(kw) | |
30 | return h.url('', **new_args) |
|
30 | return h.url('', **new_args) | |
31 | %> |
|
31 | %> | |
32 | </%def> |
|
32 | </%def> | |
33 |
|
33 | |||
34 | <%def name="render_diffset(diffset, commit=None, |
|
34 | <%def name="render_diffset(diffset, commit=None, | |
35 |
|
35 | |||
36 | # collapse all file diff entries when there are more than this amount of files in the diff |
|
36 | # collapse all file diff entries when there are more than this amount of files in the diff | |
37 | collapse_when_files_over=20, |
|
37 | collapse_when_files_over=20, | |
38 |
|
38 | |||
39 | # collapse lines in the diff when more than this amount of lines changed in the file diff |
|
39 | # collapse lines in the diff when more than this amount of lines changed in the file diff | |
40 | lines_changed_limit=500, |
|
40 | lines_changed_limit=500, | |
41 |
|
41 | |||
42 | # add a ruler at to the output |
|
42 | # add a ruler at to the output | |
43 | ruler_at_chars=0, |
|
43 | ruler_at_chars=0, | |
44 |
|
44 | |||
45 | # show inline comments |
|
45 | # show inline comments | |
46 | use_comments=False, |
|
46 | use_comments=False, | |
47 |
|
47 | |||
48 | # disable new comments |
|
48 | # disable new comments | |
49 | disable_new_comments=False, |
|
49 | disable_new_comments=False, | |
50 |
|
50 | |||
51 | # special file-comments that were deleted in previous versions |
|
51 | # special file-comments that were deleted in previous versions | |
52 | # it's used for showing outdated comments for deleted files in a PR |
|
52 | # it's used for showing outdated comments for deleted files in a PR | |
53 | deleted_files_comments=None |
|
53 | deleted_files_comments=None | |
54 |
|
54 | |||
55 | )"> |
|
55 | )"> | |
56 |
|
56 | |||
57 | %if use_comments: |
|
57 | %if use_comments: | |
58 | <div id="cb-comments-inline-container-template" class="js-template"> |
|
58 | <div id="cb-comments-inline-container-template" class="js-template"> | |
59 | ${inline_comments_container([])} |
|
59 | ${inline_comments_container([])} | |
60 | </div> |
|
60 | </div> | |
61 | <div class="js-template" id="cb-comment-inline-form-template"> |
|
61 | <div class="js-template" id="cb-comment-inline-form-template"> | |
62 | <div class="comment-inline-form ac"> |
|
62 | <div class="comment-inline-form ac"> | |
63 |
|
63 | |||
64 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
64 | %if c.rhodecode_user.username != h.DEFAULT_USER: | |
65 | ## render template for inline comments |
|
65 | ## render template for inline comments | |
66 | ${commentblock.comment_form(form_type='inline')} |
|
66 | ${commentblock.comment_form(form_type='inline')} | |
67 | %else: |
|
67 | %else: | |
68 | ${h.form('', class_='inline-form comment-form-login', method='get')} |
|
68 | ${h.form('', class_='inline-form comment-form-login', method='get')} | |
69 | <div class="pull-left"> |
|
69 | <div class="pull-left"> | |
70 | <div class="comment-help pull-right"> |
|
70 | <div class="comment-help pull-right"> | |
71 | ${_('You need to be logged in to leave comments.')} <a href="${h.route_path('login', _query={'came_from': h.url.current()})}">${_('Login now')}</a> |
|
71 | ${_('You need to be logged in to leave comments.')} <a href="${h.route_path('login', _query={'came_from': h.url.current()})}">${_('Login now')}</a> | |
72 | </div> |
|
72 | </div> | |
73 | </div> |
|
73 | </div> | |
74 | <div class="comment-button pull-right"> |
|
74 | <div class="comment-button pull-right"> | |
75 | <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);"> |
|
75 | <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);"> | |
76 | ${_('Cancel')} |
|
76 | ${_('Cancel')} | |
77 | </button> |
|
77 | </button> | |
78 | </div> |
|
78 | </div> | |
79 | <div class="clearfix"></div> |
|
79 | <div class="clearfix"></div> | |
80 | ${h.end_form()} |
|
80 | ${h.end_form()} | |
81 | %endif |
|
81 | %endif | |
82 | </div> |
|
82 | </div> | |
83 | </div> |
|
83 | </div> | |
84 |
|
84 | |||
85 | %endif |
|
85 | %endif | |
86 | <% |
|
86 | <% | |
87 | collapse_all = len(diffset.files) > collapse_when_files_over |
|
87 | collapse_all = len(diffset.files) > collapse_when_files_over | |
88 | %> |
|
88 | %> | |
89 |
|
89 | |||
90 | %if c.diffmode == 'sideside': |
|
90 | %if c.diffmode == 'sideside': | |
91 | <style> |
|
91 | <style> | |
92 | .wrapper { |
|
92 | .wrapper { | |
93 | max-width: 1600px !important; |
|
93 | max-width: 1600px !important; | |
94 | } |
|
94 | } | |
95 | </style> |
|
95 | </style> | |
96 | %endif |
|
96 | %endif | |
97 |
|
97 | |||
98 | %if ruler_at_chars: |
|
98 | %if ruler_at_chars: | |
99 | <style> |
|
99 | <style> | |
100 | .diff table.cb .cb-content:after { |
|
100 | .diff table.cb .cb-content:after { | |
101 | content: ""; |
|
101 | content: ""; | |
102 | border-left: 1px solid blue; |
|
102 | border-left: 1px solid blue; | |
103 | position: absolute; |
|
103 | position: absolute; | |
104 | top: 0; |
|
104 | top: 0; | |
105 | height: 18px; |
|
105 | height: 18px; | |
106 | opacity: .2; |
|
106 | opacity: .2; | |
107 | z-index: 10; |
|
107 | z-index: 10; | |
108 | //## +5 to account for diff action (+/-) |
|
108 | //## +5 to account for diff action (+/-) | |
109 | left: ${ruler_at_chars + 5}ch; |
|
109 | left: ${ruler_at_chars + 5}ch; | |
110 | </style> |
|
110 | </style> | |
111 | %endif |
|
111 | %endif | |
112 |
|
112 | |||
113 | <div class="diffset ${disable_new_comments and 'diffset-comments-disabled'}"> |
|
113 | <div class="diffset ${disable_new_comments and 'diffset-comments-disabled'}"> | |
114 | <div class="diffset-heading ${diffset.limited_diff and 'diffset-heading-warning' or ''}"> |
|
114 | <div class="diffset-heading ${diffset.limited_diff and 'diffset-heading-warning' or ''}"> | |
115 | %if commit: |
|
115 | %if commit: | |
116 | <div class="pull-right"> |
|
116 | <div class="pull-right"> | |
117 |
<a class="btn tooltip" title="${h.tooltip(_('Browse Files at revision {}').format(commit.raw_id))}" href="${h. |
|
117 | <a class="btn tooltip" title="${h.tooltip(_('Browse Files at revision {}').format(commit.raw_id))}" href="${h.route_path('repo_files',repo_name=diffset.repo_name, commit_id=commit.raw_id, f_path='')}"> | |
118 | ${_('Browse Files')} |
|
118 | ${_('Browse Files')} | |
119 | </a> |
|
119 | </a> | |
120 | </div> |
|
120 | </div> | |
121 | %endif |
|
121 | %endif | |
122 | <h2 class="clearinner"> |
|
122 | <h2 class="clearinner"> | |
123 | %if commit: |
|
123 | %if commit: | |
124 | <a class="tooltip revision" title="${h.tooltip(commit.message)}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id)}">${'r%s:%s' % (commit.revision,h.short_id(commit.raw_id))}</a> - |
|
124 | <a class="tooltip revision" title="${h.tooltip(commit.message)}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id)}">${'r%s:%s' % (commit.revision,h.short_id(commit.raw_id))}</a> - | |
125 | ${h.age_component(commit.date)} - |
|
125 | ${h.age_component(commit.date)} - | |
126 | %endif |
|
126 | %endif | |
127 | %if diffset.limited_diff: |
|
127 | %if diffset.limited_diff: | |
128 | ${_('The requested commit is too big and content was truncated.')} |
|
128 | ${_('The requested commit is too big and content was truncated.')} | |
129 |
|
129 | |||
130 | ${ungettext('%(num)s file changed.', '%(num)s files changed.', diffset.changed_files) % {'num': diffset.changed_files}} |
|
130 | ${ungettext('%(num)s file changed.', '%(num)s files changed.', diffset.changed_files) % {'num': diffset.changed_files}} | |
131 | <a href="${link_for(fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a> |
|
131 | <a href="${link_for(fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a> | |
132 | %else: |
|
132 | %else: | |
133 | ${ungettext('%(num)s file changed: %(linesadd)s inserted, ''%(linesdel)s deleted', |
|
133 | ${ungettext('%(num)s file changed: %(linesadd)s inserted, ''%(linesdel)s deleted', | |
134 | '%(num)s files changed: %(linesadd)s inserted, %(linesdel)s deleted', diffset.changed_files) % {'num': diffset.changed_files, 'linesadd': diffset.lines_added, 'linesdel': diffset.lines_deleted}} |
|
134 | '%(num)s files changed: %(linesadd)s inserted, %(linesdel)s deleted', diffset.changed_files) % {'num': diffset.changed_files, 'linesadd': diffset.lines_added, 'linesdel': diffset.lines_deleted}} | |
135 | %endif |
|
135 | %endif | |
136 |
|
136 | |||
137 | </h2> |
|
137 | </h2> | |
138 | </div> |
|
138 | </div> | |
139 |
|
139 | |||
140 | %if not diffset.files: |
|
140 | %if not diffset.files: | |
141 | <p class="empty_data">${_('No files')}</p> |
|
141 | <p class="empty_data">${_('No files')}</p> | |
142 | %endif |
|
142 | %endif | |
143 |
|
143 | |||
144 | <div class="filediffs"> |
|
144 | <div class="filediffs"> | |
145 | ## initial value could be marked as False later on |
|
145 | ## initial value could be marked as False later on | |
146 | <% over_lines_changed_limit = False %> |
|
146 | <% over_lines_changed_limit = False %> | |
147 | %for i, filediff in enumerate(diffset.files): |
|
147 | %for i, filediff in enumerate(diffset.files): | |
148 |
|
148 | |||
149 | <% |
|
149 | <% | |
150 | lines_changed = filediff.patch['stats']['added'] + filediff.patch['stats']['deleted'] |
|
150 | lines_changed = filediff.patch['stats']['added'] + filediff.patch['stats']['deleted'] | |
151 | over_lines_changed_limit = lines_changed > lines_changed_limit |
|
151 | over_lines_changed_limit = lines_changed > lines_changed_limit | |
152 | %> |
|
152 | %> | |
153 | <input ${collapse_all and 'checked' or ''} class="filediff-collapse-state" id="filediff-collapse-${id(filediff)}" type="checkbox"> |
|
153 | <input ${collapse_all and 'checked' or ''} class="filediff-collapse-state" id="filediff-collapse-${id(filediff)}" type="checkbox"> | |
154 | <div |
|
154 | <div | |
155 | class="filediff" |
|
155 | class="filediff" | |
156 | data-f-path="${filediff.patch['filename']}" |
|
156 | data-f-path="${filediff.patch['filename']}" | |
157 | id="a_${h.FID('', filediff.patch['filename'])}"> |
|
157 | id="a_${h.FID('', filediff.patch['filename'])}"> | |
158 | <label for="filediff-collapse-${id(filediff)}" class="filediff-heading"> |
|
158 | <label for="filediff-collapse-${id(filediff)}" class="filediff-heading"> | |
159 | <div class="filediff-collapse-indicator"></div> |
|
159 | <div class="filediff-collapse-indicator"></div> | |
160 | ${diff_ops(filediff)} |
|
160 | ${diff_ops(filediff)} | |
161 | </label> |
|
161 | </label> | |
162 | ${diff_menu(filediff, use_comments=use_comments)} |
|
162 | ${diff_menu(filediff, use_comments=use_comments)} | |
163 | <table class="cb cb-diff-${c.diffmode} code-highlight ${over_lines_changed_limit and 'cb-collapsed' or ''}"> |
|
163 | <table class="cb cb-diff-${c.diffmode} code-highlight ${over_lines_changed_limit and 'cb-collapsed' or ''}"> | |
164 | %if not filediff.hunks: |
|
164 | %if not filediff.hunks: | |
165 | %for op_id, op_text in filediff.patch['stats']['ops'].items(): |
|
165 | %for op_id, op_text in filediff.patch['stats']['ops'].items(): | |
166 | <tr> |
|
166 | <tr> | |
167 | <td class="cb-text cb-${op_class(op_id)}" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}> |
|
167 | <td class="cb-text cb-${op_class(op_id)}" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}> | |
168 | %if op_id == DEL_FILENODE: |
|
168 | %if op_id == DEL_FILENODE: | |
169 | ${_('File was deleted')} |
|
169 | ${_('File was deleted')} | |
170 | %elif op_id == BIN_FILENODE: |
|
170 | %elif op_id == BIN_FILENODE: | |
171 | ${_('Binary file hidden')} |
|
171 | ${_('Binary file hidden')} | |
172 | %else: |
|
172 | %else: | |
173 | ${op_text} |
|
173 | ${op_text} | |
174 | %endif |
|
174 | %endif | |
175 | </td> |
|
175 | </td> | |
176 | </tr> |
|
176 | </tr> | |
177 | %endfor |
|
177 | %endfor | |
178 | %endif |
|
178 | %endif | |
179 | %if filediff.limited_diff: |
|
179 | %if filediff.limited_diff: | |
180 | <tr class="cb-warning cb-collapser"> |
|
180 | <tr class="cb-warning cb-collapser"> | |
181 | <td class="cb-text" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}> |
|
181 | <td class="cb-text" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}> | |
182 | ${_('The requested commit is too big and content was truncated.')} <a href="${link_for(fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a> |
|
182 | ${_('The requested commit is too big and content was truncated.')} <a href="${link_for(fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a> | |
183 | </td> |
|
183 | </td> | |
184 | </tr> |
|
184 | </tr> | |
185 | %else: |
|
185 | %else: | |
186 | %if over_lines_changed_limit: |
|
186 | %if over_lines_changed_limit: | |
187 | <tr class="cb-warning cb-collapser"> |
|
187 | <tr class="cb-warning cb-collapser"> | |
188 | <td class="cb-text" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}> |
|
188 | <td class="cb-text" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}> | |
189 | ${_('This diff has been collapsed as it changes many lines, (%i lines changed)' % lines_changed)} |
|
189 | ${_('This diff has been collapsed as it changes many lines, (%i lines changed)' % lines_changed)} | |
190 | <a href="#" class="cb-expand" |
|
190 | <a href="#" class="cb-expand" | |
191 | onclick="$(this).closest('table').removeClass('cb-collapsed'); return false;">${_('Show them')} |
|
191 | onclick="$(this).closest('table').removeClass('cb-collapsed'); return false;">${_('Show them')} | |
192 | </a> |
|
192 | </a> | |
193 | <a href="#" class="cb-collapse" |
|
193 | <a href="#" class="cb-collapse" | |
194 | onclick="$(this).closest('table').addClass('cb-collapsed'); return false;">${_('Hide them')} |
|
194 | onclick="$(this).closest('table').addClass('cb-collapsed'); return false;">${_('Hide them')} | |
195 | </a> |
|
195 | </a> | |
196 | </td> |
|
196 | </td> | |
197 | </tr> |
|
197 | </tr> | |
198 | %endif |
|
198 | %endif | |
199 | %endif |
|
199 | %endif | |
200 |
|
200 | |||
201 | %for hunk in filediff.hunks: |
|
201 | %for hunk in filediff.hunks: | |
202 | <tr class="cb-hunk"> |
|
202 | <tr class="cb-hunk"> | |
203 | <td ${c.diffmode == 'unified' and 'colspan=3' or ''}> |
|
203 | <td ${c.diffmode == 'unified' and 'colspan=3' or ''}> | |
204 | ## TODO: dan: add ajax loading of more context here |
|
204 | ## TODO: dan: add ajax loading of more context here | |
205 | ## <a href="#"> |
|
205 | ## <a href="#"> | |
206 | <i class="icon-more"></i> |
|
206 | <i class="icon-more"></i> | |
207 | ## </a> |
|
207 | ## </a> | |
208 | </td> |
|
208 | </td> | |
209 | <td ${c.diffmode == 'sideside' and 'colspan=5' or ''}> |
|
209 | <td ${c.diffmode == 'sideside' and 'colspan=5' or ''}> | |
210 | @@ |
|
210 | @@ | |
211 | -${hunk.source_start},${hunk.source_length} |
|
211 | -${hunk.source_start},${hunk.source_length} | |
212 | +${hunk.target_start},${hunk.target_length} |
|
212 | +${hunk.target_start},${hunk.target_length} | |
213 | ${hunk.section_header} |
|
213 | ${hunk.section_header} | |
214 | </td> |
|
214 | </td> | |
215 | </tr> |
|
215 | </tr> | |
216 | %if c.diffmode == 'unified': |
|
216 | %if c.diffmode == 'unified': | |
217 | ${render_hunk_lines_unified(hunk, use_comments=use_comments)} |
|
217 | ${render_hunk_lines_unified(hunk, use_comments=use_comments)} | |
218 | %elif c.diffmode == 'sideside': |
|
218 | %elif c.diffmode == 'sideside': | |
219 | ${render_hunk_lines_sideside(hunk, use_comments=use_comments)} |
|
219 | ${render_hunk_lines_sideside(hunk, use_comments=use_comments)} | |
220 | %else: |
|
220 | %else: | |
221 | <tr class="cb-line"> |
|
221 | <tr class="cb-line"> | |
222 | <td>unknown diff mode</td> |
|
222 | <td>unknown diff mode</td> | |
223 | </tr> |
|
223 | </tr> | |
224 | %endif |
|
224 | %endif | |
225 | %endfor |
|
225 | %endfor | |
226 |
|
226 | |||
227 | ## outdated comments that do not fit into currently displayed lines |
|
227 | ## outdated comments that do not fit into currently displayed lines | |
228 | % for lineno, comments in filediff.left_comments.items(): |
|
228 | % for lineno, comments in filediff.left_comments.items(): | |
229 |
|
229 | |||
230 | %if c.diffmode == 'unified': |
|
230 | %if c.diffmode == 'unified': | |
231 | <tr class="cb-line"> |
|
231 | <tr class="cb-line"> | |
232 | <td class="cb-data cb-context"></td> |
|
232 | <td class="cb-data cb-context"></td> | |
233 | <td class="cb-lineno cb-context"></td> |
|
233 | <td class="cb-lineno cb-context"></td> | |
234 | <td class="cb-lineno cb-context"></td> |
|
234 | <td class="cb-lineno cb-context"></td> | |
235 | <td class="cb-content cb-context"> |
|
235 | <td class="cb-content cb-context"> | |
236 | ${inline_comments_container(comments)} |
|
236 | ${inline_comments_container(comments)} | |
237 | </td> |
|
237 | </td> | |
238 | </tr> |
|
238 | </tr> | |
239 | %elif c.diffmode == 'sideside': |
|
239 | %elif c.diffmode == 'sideside': | |
240 | <tr class="cb-line"> |
|
240 | <tr class="cb-line"> | |
241 | <td class="cb-data cb-context"></td> |
|
241 | <td class="cb-data cb-context"></td> | |
242 | <td class="cb-lineno cb-context"></td> |
|
242 | <td class="cb-lineno cb-context"></td> | |
243 | <td class="cb-content cb-context"></td> |
|
243 | <td class="cb-content cb-context"></td> | |
244 |
|
244 | |||
245 | <td class="cb-data cb-context"></td> |
|
245 | <td class="cb-data cb-context"></td> | |
246 | <td class="cb-lineno cb-context"></td> |
|
246 | <td class="cb-lineno cb-context"></td> | |
247 | <td class="cb-content cb-context"> |
|
247 | <td class="cb-content cb-context"> | |
248 | ${inline_comments_container(comments)} |
|
248 | ${inline_comments_container(comments)} | |
249 | </td> |
|
249 | </td> | |
250 | </tr> |
|
250 | </tr> | |
251 | %endif |
|
251 | %endif | |
252 |
|
252 | |||
253 | % endfor |
|
253 | % endfor | |
254 |
|
254 | |||
255 | </table> |
|
255 | </table> | |
256 | </div> |
|
256 | </div> | |
257 | %endfor |
|
257 | %endfor | |
258 |
|
258 | |||
259 | ## outdated comments that are made for a file that has been deleted |
|
259 | ## outdated comments that are made for a file that has been deleted | |
260 | % for filename, comments_dict in (deleted_files_comments or {}).items(): |
|
260 | % for filename, comments_dict in (deleted_files_comments or {}).items(): | |
261 |
|
261 | |||
262 | <div class="filediffs filediff-outdated" style="display: none"> |
|
262 | <div class="filediffs filediff-outdated" style="display: none"> | |
263 | <input ${collapse_all and 'checked' or ''} class="filediff-collapse-state" id="filediff-collapse-${id(filename)}" type="checkbox"> |
|
263 | <input ${collapse_all and 'checked' or ''} class="filediff-collapse-state" id="filediff-collapse-${id(filename)}" type="checkbox"> | |
264 | <div class="filediff" data-f-path="${filename}" id="a_${h.FID('', filename)}"> |
|
264 | <div class="filediff" data-f-path="${filename}" id="a_${h.FID('', filename)}"> | |
265 | <label for="filediff-collapse-${id(filename)}" class="filediff-heading"> |
|
265 | <label for="filediff-collapse-${id(filename)}" class="filediff-heading"> | |
266 | <div class="filediff-collapse-indicator"></div> |
|
266 | <div class="filediff-collapse-indicator"></div> | |
267 | <span class="pill"> |
|
267 | <span class="pill"> | |
268 | ## file was deleted |
|
268 | ## file was deleted | |
269 | <strong>${filename}</strong> |
|
269 | <strong>${filename}</strong> | |
270 | </span> |
|
270 | </span> | |
271 | <span class="pill-group" style="float: left"> |
|
271 | <span class="pill-group" style="float: left"> | |
272 | ## file op, doesn't need translation |
|
272 | ## file op, doesn't need translation | |
273 | <span class="pill" op="removed">removed in this version</span> |
|
273 | <span class="pill" op="removed">removed in this version</span> | |
274 | </span> |
|
274 | </span> | |
275 | <a class="pill filediff-anchor" href="#a_${h.FID('', filename)}">¶</a> |
|
275 | <a class="pill filediff-anchor" href="#a_${h.FID('', filename)}">¶</a> | |
276 | <span class="pill-group" style="float: right"> |
|
276 | <span class="pill-group" style="float: right"> | |
277 | <span class="pill" op="deleted">-${comments_dict['stats']}</span> |
|
277 | <span class="pill" op="deleted">-${comments_dict['stats']}</span> | |
278 | </span> |
|
278 | </span> | |
279 | </label> |
|
279 | </label> | |
280 |
|
280 | |||
281 | <table class="cb cb-diff-${c.diffmode} code-highlight ${over_lines_changed_limit and 'cb-collapsed' or ''}"> |
|
281 | <table class="cb cb-diff-${c.diffmode} code-highlight ${over_lines_changed_limit and 'cb-collapsed' or ''}"> | |
282 | <tr> |
|
282 | <tr> | |
283 | % if c.diffmode == 'unified': |
|
283 | % if c.diffmode == 'unified': | |
284 | <td></td> |
|
284 | <td></td> | |
285 | %endif |
|
285 | %endif | |
286 |
|
286 | |||
287 | <td></td> |
|
287 | <td></td> | |
288 | <td class="cb-text cb-${op_class(BIN_FILENODE)}" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=5'}> |
|
288 | <td class="cb-text cb-${op_class(BIN_FILENODE)}" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=5'}> | |
289 | ${_('File was deleted in this version, and outdated comments were made on it')} |
|
289 | ${_('File was deleted in this version, and outdated comments were made on it')} | |
290 | </td> |
|
290 | </td> | |
291 | </tr> |
|
291 | </tr> | |
292 | %if c.diffmode == 'unified': |
|
292 | %if c.diffmode == 'unified': | |
293 | <tr class="cb-line"> |
|
293 | <tr class="cb-line"> | |
294 | <td class="cb-data cb-context"></td> |
|
294 | <td class="cb-data cb-context"></td> | |
295 | <td class="cb-lineno cb-context"></td> |
|
295 | <td class="cb-lineno cb-context"></td> | |
296 | <td class="cb-lineno cb-context"></td> |
|
296 | <td class="cb-lineno cb-context"></td> | |
297 | <td class="cb-content cb-context"> |
|
297 | <td class="cb-content cb-context"> | |
298 | ${inline_comments_container(comments_dict['comments'])} |
|
298 | ${inline_comments_container(comments_dict['comments'])} | |
299 | </td> |
|
299 | </td> | |
300 | </tr> |
|
300 | </tr> | |
301 | %elif c.diffmode == 'sideside': |
|
301 | %elif c.diffmode == 'sideside': | |
302 | <tr class="cb-line"> |
|
302 | <tr class="cb-line"> | |
303 | <td class="cb-data cb-context"></td> |
|
303 | <td class="cb-data cb-context"></td> | |
304 | <td class="cb-lineno cb-context"></td> |
|
304 | <td class="cb-lineno cb-context"></td> | |
305 | <td class="cb-content cb-context"></td> |
|
305 | <td class="cb-content cb-context"></td> | |
306 |
|
306 | |||
307 | <td class="cb-data cb-context"></td> |
|
307 | <td class="cb-data cb-context"></td> | |
308 | <td class="cb-lineno cb-context"></td> |
|
308 | <td class="cb-lineno cb-context"></td> | |
309 | <td class="cb-content cb-context"> |
|
309 | <td class="cb-content cb-context"> | |
310 | ${inline_comments_container(comments_dict['comments'])} |
|
310 | ${inline_comments_container(comments_dict['comments'])} | |
311 | </td> |
|
311 | </td> | |
312 | </tr> |
|
312 | </tr> | |
313 | %endif |
|
313 | %endif | |
314 | </table> |
|
314 | </table> | |
315 | </div> |
|
315 | </div> | |
316 | </div> |
|
316 | </div> | |
317 | % endfor |
|
317 | % endfor | |
318 |
|
318 | |||
319 | </div> |
|
319 | </div> | |
320 | </div> |
|
320 | </div> | |
321 | </%def> |
|
321 | </%def> | |
322 |
|
322 | |||
323 | <%def name="diff_ops(filediff)"> |
|
323 | <%def name="diff_ops(filediff)"> | |
324 | <% |
|
324 | <% | |
325 | from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \ |
|
325 | from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \ | |
326 | MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE |
|
326 | MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE | |
327 | %> |
|
327 | %> | |
328 | <span class="pill"> |
|
328 | <span class="pill"> | |
329 | %if filediff.source_file_path and filediff.target_file_path: |
|
329 | %if filediff.source_file_path and filediff.target_file_path: | |
330 | %if filediff.source_file_path != filediff.target_file_path: |
|
330 | %if filediff.source_file_path != filediff.target_file_path: | |
331 | ## file was renamed, or copied |
|
331 | ## file was renamed, or copied | |
332 | %if RENAMED_FILENODE in filediff.patch['stats']['ops']: |
|
332 | %if RENAMED_FILENODE in filediff.patch['stats']['ops']: | |
333 | <strong>${filediff.target_file_path}</strong> ⬅ <del>${filediff.source_file_path}</del> |
|
333 | <strong>${filediff.target_file_path}</strong> ⬅ <del>${filediff.source_file_path}</del> | |
334 | %elif COPIED_FILENODE in filediff.patch['stats']['ops']: |
|
334 | %elif COPIED_FILENODE in filediff.patch['stats']['ops']: | |
335 | <strong>${filediff.target_file_path}</strong> ⬅ ${filediff.source_file_path} |
|
335 | <strong>${filediff.target_file_path}</strong> ⬅ ${filediff.source_file_path} | |
336 | %endif |
|
336 | %endif | |
337 | %else: |
|
337 | %else: | |
338 | ## file was modified |
|
338 | ## file was modified | |
339 | <strong>${filediff.source_file_path}</strong> |
|
339 | <strong>${filediff.source_file_path}</strong> | |
340 | %endif |
|
340 | %endif | |
341 | %else: |
|
341 | %else: | |
342 | %if filediff.source_file_path: |
|
342 | %if filediff.source_file_path: | |
343 | ## file was deleted |
|
343 | ## file was deleted | |
344 | <strong>${filediff.source_file_path}</strong> |
|
344 | <strong>${filediff.source_file_path}</strong> | |
345 | %else: |
|
345 | %else: | |
346 | ## file was added |
|
346 | ## file was added | |
347 | <strong>${filediff.target_file_path}</strong> |
|
347 | <strong>${filediff.target_file_path}</strong> | |
348 | %endif |
|
348 | %endif | |
349 | %endif |
|
349 | %endif | |
350 | </span> |
|
350 | </span> | |
351 | <span class="pill-group" style="float: left"> |
|
351 | <span class="pill-group" style="float: left"> | |
352 | %if filediff.limited_diff: |
|
352 | %if filediff.limited_diff: | |
353 | <span class="pill tooltip" op="limited" title="The stats for this diff are not complete">limited diff</span> |
|
353 | <span class="pill tooltip" op="limited" title="The stats for this diff are not complete">limited diff</span> | |
354 | %endif |
|
354 | %endif | |
355 |
|
355 | |||
356 | %if RENAMED_FILENODE in filediff.patch['stats']['ops']: |
|
356 | %if RENAMED_FILENODE in filediff.patch['stats']['ops']: | |
357 | <span class="pill" op="renamed">renamed</span> |
|
357 | <span class="pill" op="renamed">renamed</span> | |
358 | %endif |
|
358 | %endif | |
359 |
|
359 | |||
360 | %if COPIED_FILENODE in filediff.patch['stats']['ops']: |
|
360 | %if COPIED_FILENODE in filediff.patch['stats']['ops']: | |
361 | <span class="pill" op="copied">copied</span> |
|
361 | <span class="pill" op="copied">copied</span> | |
362 | %endif |
|
362 | %endif | |
363 |
|
363 | |||
364 | %if NEW_FILENODE in filediff.patch['stats']['ops']: |
|
364 | %if NEW_FILENODE in filediff.patch['stats']['ops']: | |
365 | <span class="pill" op="created">created</span> |
|
365 | <span class="pill" op="created">created</span> | |
366 | %if filediff['target_mode'].startswith('120'): |
|
366 | %if filediff['target_mode'].startswith('120'): | |
367 | <span class="pill" op="symlink">symlink</span> |
|
367 | <span class="pill" op="symlink">symlink</span> | |
368 | %else: |
|
368 | %else: | |
369 | <span class="pill" op="mode">${nice_mode(filediff['target_mode'])}</span> |
|
369 | <span class="pill" op="mode">${nice_mode(filediff['target_mode'])}</span> | |
370 | %endif |
|
370 | %endif | |
371 | %endif |
|
371 | %endif | |
372 |
|
372 | |||
373 | %if DEL_FILENODE in filediff.patch['stats']['ops']: |
|
373 | %if DEL_FILENODE in filediff.patch['stats']['ops']: | |
374 | <span class="pill" op="removed">removed</span> |
|
374 | <span class="pill" op="removed">removed</span> | |
375 | %endif |
|
375 | %endif | |
376 |
|
376 | |||
377 | %if CHMOD_FILENODE in filediff.patch['stats']['ops']: |
|
377 | %if CHMOD_FILENODE in filediff.patch['stats']['ops']: | |
378 | <span class="pill" op="mode"> |
|
378 | <span class="pill" op="mode"> | |
379 | ${nice_mode(filediff['source_mode'])} ➡ ${nice_mode(filediff['target_mode'])} |
|
379 | ${nice_mode(filediff['source_mode'])} ➡ ${nice_mode(filediff['target_mode'])} | |
380 | </span> |
|
380 | </span> | |
381 | %endif |
|
381 | %endif | |
382 | </span> |
|
382 | </span> | |
383 |
|
383 | |||
384 | <a class="pill filediff-anchor" href="#a_${h.FID('', filediff.patch['filename'])}">¶</a> |
|
384 | <a class="pill filediff-anchor" href="#a_${h.FID('', filediff.patch['filename'])}">¶</a> | |
385 |
|
385 | |||
386 | <span class="pill-group" style="float: right"> |
|
386 | <span class="pill-group" style="float: right"> | |
387 | %if BIN_FILENODE in filediff.patch['stats']['ops']: |
|
387 | %if BIN_FILENODE in filediff.patch['stats']['ops']: | |
388 | <span class="pill" op="binary">binary</span> |
|
388 | <span class="pill" op="binary">binary</span> | |
389 | %if MOD_FILENODE in filediff.patch['stats']['ops']: |
|
389 | %if MOD_FILENODE in filediff.patch['stats']['ops']: | |
390 | <span class="pill" op="modified">modified</span> |
|
390 | <span class="pill" op="modified">modified</span> | |
391 | %endif |
|
391 | %endif | |
392 | %endif |
|
392 | %endif | |
393 | %if filediff.patch['stats']['added']: |
|
393 | %if filediff.patch['stats']['added']: | |
394 | <span class="pill" op="added">+${filediff.patch['stats']['added']}</span> |
|
394 | <span class="pill" op="added">+${filediff.patch['stats']['added']}</span> | |
395 | %endif |
|
395 | %endif | |
396 | %if filediff.patch['stats']['deleted']: |
|
396 | %if filediff.patch['stats']['deleted']: | |
397 | <span class="pill" op="deleted">-${filediff.patch['stats']['deleted']}</span> |
|
397 | <span class="pill" op="deleted">-${filediff.patch['stats']['deleted']}</span> | |
398 | %endif |
|
398 | %endif | |
399 | </span> |
|
399 | </span> | |
400 |
|
400 | |||
401 | </%def> |
|
401 | </%def> | |
402 |
|
402 | |||
403 | <%def name="nice_mode(filemode)"> |
|
403 | <%def name="nice_mode(filemode)"> | |
404 | ${filemode.startswith('100') and filemode[3:] or filemode} |
|
404 | ${filemode.startswith('100') and filemode[3:] or filemode} | |
405 | </%def> |
|
405 | </%def> | |
406 |
|
406 | |||
407 | <%def name="diff_menu(filediff, use_comments=False)"> |
|
407 | <%def name="diff_menu(filediff, use_comments=False)"> | |
408 | <div class="filediff-menu"> |
|
408 | <div class="filediff-menu"> | |
409 | %if filediff.diffset.source_ref: |
|
409 | %if filediff.diffset.source_ref: | |
410 | %if filediff.operation in ['D', 'M']: |
|
410 | %if filediff.operation in ['D', 'M']: | |
411 | <a |
|
411 | <a | |
412 | class="tooltip" |
|
412 | class="tooltip" | |
413 |
href="${h. |
|
413 | href="${h.route_path('repo_files',repo_name=filediff.diffset.repo_name,commit_id=filediff.diffset.source_ref,f_path=filediff.source_file_path)}" | |
414 | title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}" |
|
414 | title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}" | |
415 | > |
|
415 | > | |
416 | ${_('Show file before')} |
|
416 | ${_('Show file before')} | |
417 | </a> | |
|
417 | </a> | | |
418 | %else: |
|
418 | %else: | |
419 | <span |
|
419 | <span | |
420 | class="tooltip" |
|
420 | class="tooltip" | |
421 | title="${h.tooltip(_('File no longer present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}" |
|
421 | title="${h.tooltip(_('File no longer present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}" | |
422 | > |
|
422 | > | |
423 | ${_('Show file before')} |
|
423 | ${_('Show file before')} | |
424 | </span> | |
|
424 | </span> | | |
425 | %endif |
|
425 | %endif | |
426 | %if filediff.operation in ['A', 'M']: |
|
426 | %if filediff.operation in ['A', 'M']: | |
427 | <a |
|
427 | <a | |
428 | class="tooltip" |
|
428 | class="tooltip" | |
429 |
href="${h. |
|
429 | href="${h.route_path('repo_files',repo_name=filediff.diffset.source_repo_name,commit_id=filediff.diffset.target_ref,f_path=filediff.target_file_path)}" | |
430 | title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}" |
|
430 | title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}" | |
431 | > |
|
431 | > | |
432 | ${_('Show file after')} |
|
432 | ${_('Show file after')} | |
433 | </a> | |
|
433 | </a> | | |
434 | %else: |
|
434 | %else: | |
435 | <span |
|
435 | <span | |
436 | class="tooltip" |
|
436 | class="tooltip" | |
437 | title="${h.tooltip(_('File no longer present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}" |
|
437 | title="${h.tooltip(_('File no longer present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}" | |
438 | > |
|
438 | > | |
439 | ${_('Show file after')} |
|
439 | ${_('Show file after')} | |
440 | </span> | |
|
440 | </span> | | |
441 | %endif |
|
441 | %endif | |
442 | <a |
|
442 | <a | |
443 | class="tooltip" |
|
443 | class="tooltip" | |
444 | title="${h.tooltip(_('Raw diff'))}" |
|
444 | title="${h.tooltip(_('Raw diff'))}" | |
445 |
href="${h. |
|
445 | href="${h.route_path('repo_files_diff',repo_name=filediff.diffset.repo_name,f_path=filediff.target_file_path, _query=dict(diff2=filediff.diffset.target_ref,diff1=filediff.diffset.source_ref,diff='raw'))}" | |
446 | > |
|
446 | > | |
447 | ${_('Raw diff')} |
|
447 | ${_('Raw diff')} | |
448 | </a> | |
|
448 | </a> | | |
449 | <a |
|
449 | <a | |
450 | class="tooltip" |
|
450 | class="tooltip" | |
451 | title="${h.tooltip(_('Download diff'))}" |
|
451 | title="${h.tooltip(_('Download diff'))}" | |
452 |
href="${h. |
|
452 | href="${h.route_path('repo_files_diff',repo_name=filediff.diffset.repo_name,f_path=filediff.target_file_path, _query=dict(diff2=filediff.diffset.target_ref,diff1=filediff.diffset.source_ref,diff='download'))}" | |
453 | > |
|
453 | > | |
454 | ${_('Download diff')} |
|
454 | ${_('Download diff')} | |
455 | </a> |
|
455 | </a> | |
456 | % if use_comments: |
|
456 | % if use_comments: | |
457 | | |
|
457 | | | |
458 | % endif |
|
458 | % endif | |
459 |
|
459 | |||
460 | ## TODO: dan: refactor ignorews_url and context_url into the diff renderer same as diffmode=unified/sideside. Also use ajax to load more context (by clicking hunks) |
|
460 | ## TODO: dan: refactor ignorews_url and context_url into the diff renderer same as diffmode=unified/sideside. Also use ajax to load more context (by clicking hunks) | |
461 | %if hasattr(c, 'ignorews_url'): |
|
461 | %if hasattr(c, 'ignorews_url'): | |
462 | ${c.ignorews_url(request.GET, h.FID('', filediff.patch['filename']))} |
|
462 | ${c.ignorews_url(request.GET, h.FID('', filediff.patch['filename']))} | |
463 | %endif |
|
463 | %endif | |
464 | %if hasattr(c, 'context_url'): |
|
464 | %if hasattr(c, 'context_url'): | |
465 | ${c.context_url(request.GET, h.FID('', filediff.patch['filename']))} |
|
465 | ${c.context_url(request.GET, h.FID('', filediff.patch['filename']))} | |
466 | %endif |
|
466 | %endif | |
467 |
|
467 | |||
468 | %if use_comments: |
|
468 | %if use_comments: | |
469 | <a href="#" onclick="return Rhodecode.comments.toggleComments(this);"> |
|
469 | <a href="#" onclick="return Rhodecode.comments.toggleComments(this);"> | |
470 | <span class="show-comment-button">${_('Show comments')}</span><span class="hide-comment-button">${_('Hide comments')}</span> |
|
470 | <span class="show-comment-button">${_('Show comments')}</span><span class="hide-comment-button">${_('Hide comments')}</span> | |
471 | </a> |
|
471 | </a> | |
472 | %endif |
|
472 | %endif | |
473 | %endif |
|
473 | %endif | |
474 | </div> |
|
474 | </div> | |
475 | </%def> |
|
475 | </%def> | |
476 |
|
476 | |||
477 |
|
477 | |||
478 | <%def name="inline_comments_container(comments)"> |
|
478 | <%def name="inline_comments_container(comments)"> | |
479 | <div class="inline-comments"> |
|
479 | <div class="inline-comments"> | |
480 | %for comment in comments: |
|
480 | %for comment in comments: | |
481 | ${commentblock.comment_block(comment, inline=True)} |
|
481 | ${commentblock.comment_block(comment, inline=True)} | |
482 | %endfor |
|
482 | %endfor | |
483 |
|
483 | |||
484 | % if comments and comments[-1].outdated: |
|
484 | % if comments and comments[-1].outdated: | |
485 | <span class="btn btn-secondary cb-comment-add-button comment-outdated}" |
|
485 | <span class="btn btn-secondary cb-comment-add-button comment-outdated}" | |
486 | style="display: none;}"> |
|
486 | style="display: none;}"> | |
487 | ${_('Add another comment')} |
|
487 | ${_('Add another comment')} | |
488 | </span> |
|
488 | </span> | |
489 | % else: |
|
489 | % else: | |
490 | <span onclick="return Rhodecode.comments.createComment(this)" |
|
490 | <span onclick="return Rhodecode.comments.createComment(this)" | |
491 | class="btn btn-secondary cb-comment-add-button"> |
|
491 | class="btn btn-secondary cb-comment-add-button"> | |
492 | ${_('Add another comment')} |
|
492 | ${_('Add another comment')} | |
493 | </span> |
|
493 | </span> | |
494 | % endif |
|
494 | % endif | |
495 |
|
495 | |||
496 | </div> |
|
496 | </div> | |
497 | </%def> |
|
497 | </%def> | |
498 |
|
498 | |||
499 |
|
499 | |||
500 | <%def name="render_hunk_lines_sideside(hunk, use_comments=False)"> |
|
500 | <%def name="render_hunk_lines_sideside(hunk, use_comments=False)"> | |
501 | %for i, line in enumerate(hunk.sideside): |
|
501 | %for i, line in enumerate(hunk.sideside): | |
502 | <% |
|
502 | <% | |
503 | old_line_anchor, new_line_anchor = None, None |
|
503 | old_line_anchor, new_line_anchor = None, None | |
504 | if line.original.lineno: |
|
504 | if line.original.lineno: | |
505 | old_line_anchor = diff_line_anchor(hunk.source_file_path, line.original.lineno, 'o') |
|
505 | old_line_anchor = diff_line_anchor(hunk.source_file_path, line.original.lineno, 'o') | |
506 | if line.modified.lineno: |
|
506 | if line.modified.lineno: | |
507 | new_line_anchor = diff_line_anchor(hunk.target_file_path, line.modified.lineno, 'n') |
|
507 | new_line_anchor = diff_line_anchor(hunk.target_file_path, line.modified.lineno, 'n') | |
508 | %> |
|
508 | %> | |
509 |
|
509 | |||
510 | <tr class="cb-line"> |
|
510 | <tr class="cb-line"> | |
511 | <td class="cb-data ${action_class(line.original.action)}" |
|
511 | <td class="cb-data ${action_class(line.original.action)}" | |
512 | data-line-number="${line.original.lineno}" |
|
512 | data-line-number="${line.original.lineno}" | |
513 | > |
|
513 | > | |
514 | <div> |
|
514 | <div> | |
515 | %if line.original.comments: |
|
515 | %if line.original.comments: | |
516 | <i class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> |
|
516 | <i class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> | |
517 | %endif |
|
517 | %endif | |
518 | </div> |
|
518 | </div> | |
519 | </td> |
|
519 | </td> | |
520 | <td class="cb-lineno ${action_class(line.original.action)}" |
|
520 | <td class="cb-lineno ${action_class(line.original.action)}" | |
521 | data-line-number="${line.original.lineno}" |
|
521 | data-line-number="${line.original.lineno}" | |
522 | %if old_line_anchor: |
|
522 | %if old_line_anchor: | |
523 | id="${old_line_anchor}" |
|
523 | id="${old_line_anchor}" | |
524 | %endif |
|
524 | %endif | |
525 | > |
|
525 | > | |
526 | %if line.original.lineno: |
|
526 | %if line.original.lineno: | |
527 | <a name="${old_line_anchor}" href="#${old_line_anchor}">${line.original.lineno}</a> |
|
527 | <a name="${old_line_anchor}" href="#${old_line_anchor}">${line.original.lineno}</a> | |
528 | %endif |
|
528 | %endif | |
529 | </td> |
|
529 | </td> | |
530 | <td class="cb-content ${action_class(line.original.action)}" |
|
530 | <td class="cb-content ${action_class(line.original.action)}" | |
531 | data-line-number="o${line.original.lineno}" |
|
531 | data-line-number="o${line.original.lineno}" | |
532 | > |
|
532 | > | |
533 | %if use_comments and line.original.lineno: |
|
533 | %if use_comments and line.original.lineno: | |
534 | ${render_add_comment_button()} |
|
534 | ${render_add_comment_button()} | |
535 | %endif |
|
535 | %endif | |
536 | <span class="cb-code">${line.original.action} ${line.original.content or '' | n}</span> |
|
536 | <span class="cb-code">${line.original.action} ${line.original.content or '' | n}</span> | |
537 | %if use_comments and line.original.lineno and line.original.comments: |
|
537 | %if use_comments and line.original.lineno and line.original.comments: | |
538 | ${inline_comments_container(line.original.comments)} |
|
538 | ${inline_comments_container(line.original.comments)} | |
539 | %endif |
|
539 | %endif | |
540 | </td> |
|
540 | </td> | |
541 | <td class="cb-data ${action_class(line.modified.action)}" |
|
541 | <td class="cb-data ${action_class(line.modified.action)}" | |
542 | data-line-number="${line.modified.lineno}" |
|
542 | data-line-number="${line.modified.lineno}" | |
543 | > |
|
543 | > | |
544 | <div> |
|
544 | <div> | |
545 | %if line.modified.comments: |
|
545 | %if line.modified.comments: | |
546 | <i class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> |
|
546 | <i class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> | |
547 | %endif |
|
547 | %endif | |
548 | </div> |
|
548 | </div> | |
549 | </td> |
|
549 | </td> | |
550 | <td class="cb-lineno ${action_class(line.modified.action)}" |
|
550 | <td class="cb-lineno ${action_class(line.modified.action)}" | |
551 | data-line-number="${line.modified.lineno}" |
|
551 | data-line-number="${line.modified.lineno}" | |
552 | %if new_line_anchor: |
|
552 | %if new_line_anchor: | |
553 | id="${new_line_anchor}" |
|
553 | id="${new_line_anchor}" | |
554 | %endif |
|
554 | %endif | |
555 | > |
|
555 | > | |
556 | %if line.modified.lineno: |
|
556 | %if line.modified.lineno: | |
557 | <a name="${new_line_anchor}" href="#${new_line_anchor}">${line.modified.lineno}</a> |
|
557 | <a name="${new_line_anchor}" href="#${new_line_anchor}">${line.modified.lineno}</a> | |
558 | %endif |
|
558 | %endif | |
559 | </td> |
|
559 | </td> | |
560 | <td class="cb-content ${action_class(line.modified.action)}" |
|
560 | <td class="cb-content ${action_class(line.modified.action)}" | |
561 | data-line-number="n${line.modified.lineno}" |
|
561 | data-line-number="n${line.modified.lineno}" | |
562 | > |
|
562 | > | |
563 | %if use_comments and line.modified.lineno: |
|
563 | %if use_comments and line.modified.lineno: | |
564 | ${render_add_comment_button()} |
|
564 | ${render_add_comment_button()} | |
565 | %endif |
|
565 | %endif | |
566 | <span class="cb-code">${line.modified.action} ${line.modified.content or '' | n}</span> |
|
566 | <span class="cb-code">${line.modified.action} ${line.modified.content or '' | n}</span> | |
567 | %if use_comments and line.modified.lineno and line.modified.comments: |
|
567 | %if use_comments and line.modified.lineno and line.modified.comments: | |
568 | ${inline_comments_container(line.modified.comments)} |
|
568 | ${inline_comments_container(line.modified.comments)} | |
569 | %endif |
|
569 | %endif | |
570 | </td> |
|
570 | </td> | |
571 | </tr> |
|
571 | </tr> | |
572 | %endfor |
|
572 | %endfor | |
573 | </%def> |
|
573 | </%def> | |
574 |
|
574 | |||
575 |
|
575 | |||
576 | <%def name="render_hunk_lines_unified(hunk, use_comments=False)"> |
|
576 | <%def name="render_hunk_lines_unified(hunk, use_comments=False)"> | |
577 | %for old_line_no, new_line_no, action, content, comments in hunk.unified: |
|
577 | %for old_line_no, new_line_no, action, content, comments in hunk.unified: | |
578 | <% |
|
578 | <% | |
579 | old_line_anchor, new_line_anchor = None, None |
|
579 | old_line_anchor, new_line_anchor = None, None | |
580 | if old_line_no: |
|
580 | if old_line_no: | |
581 | old_line_anchor = diff_line_anchor(hunk.source_file_path, old_line_no, 'o') |
|
581 | old_line_anchor = diff_line_anchor(hunk.source_file_path, old_line_no, 'o') | |
582 | if new_line_no: |
|
582 | if new_line_no: | |
583 | new_line_anchor = diff_line_anchor(hunk.target_file_path, new_line_no, 'n') |
|
583 | new_line_anchor = diff_line_anchor(hunk.target_file_path, new_line_no, 'n') | |
584 | %> |
|
584 | %> | |
585 | <tr class="cb-line"> |
|
585 | <tr class="cb-line"> | |
586 | <td class="cb-data ${action_class(action)}"> |
|
586 | <td class="cb-data ${action_class(action)}"> | |
587 | <div> |
|
587 | <div> | |
588 | %if comments: |
|
588 | %if comments: | |
589 | <i class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> |
|
589 | <i class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i> | |
590 | %endif |
|
590 | %endif | |
591 | </div> |
|
591 | </div> | |
592 | </td> |
|
592 | </td> | |
593 | <td class="cb-lineno ${action_class(action)}" |
|
593 | <td class="cb-lineno ${action_class(action)}" | |
594 | data-line-number="${old_line_no}" |
|
594 | data-line-number="${old_line_no}" | |
595 | %if old_line_anchor: |
|
595 | %if old_line_anchor: | |
596 | id="${old_line_anchor}" |
|
596 | id="${old_line_anchor}" | |
597 | %endif |
|
597 | %endif | |
598 | > |
|
598 | > | |
599 | %if old_line_anchor: |
|
599 | %if old_line_anchor: | |
600 | <a name="${old_line_anchor}" href="#${old_line_anchor}">${old_line_no}</a> |
|
600 | <a name="${old_line_anchor}" href="#${old_line_anchor}">${old_line_no}</a> | |
601 | %endif |
|
601 | %endif | |
602 | </td> |
|
602 | </td> | |
603 | <td class="cb-lineno ${action_class(action)}" |
|
603 | <td class="cb-lineno ${action_class(action)}" | |
604 | data-line-number="${new_line_no}" |
|
604 | data-line-number="${new_line_no}" | |
605 | %if new_line_anchor: |
|
605 | %if new_line_anchor: | |
606 | id="${new_line_anchor}" |
|
606 | id="${new_line_anchor}" | |
607 | %endif |
|
607 | %endif | |
608 | > |
|
608 | > | |
609 | %if new_line_anchor: |
|
609 | %if new_line_anchor: | |
610 | <a name="${new_line_anchor}" href="#${new_line_anchor}">${new_line_no}</a> |
|
610 | <a name="${new_line_anchor}" href="#${new_line_anchor}">${new_line_no}</a> | |
611 | %endif |
|
611 | %endif | |
612 | </td> |
|
612 | </td> | |
613 | <td class="cb-content ${action_class(action)}" |
|
613 | <td class="cb-content ${action_class(action)}" | |
614 | data-line-number="${new_line_no and 'n' or 'o'}${new_line_no or old_line_no}" |
|
614 | data-line-number="${new_line_no and 'n' or 'o'}${new_line_no or old_line_no}" | |
615 | > |
|
615 | > | |
616 | %if use_comments: |
|
616 | %if use_comments: | |
617 | ${render_add_comment_button()} |
|
617 | ${render_add_comment_button()} | |
618 | %endif |
|
618 | %endif | |
619 | <span class="cb-code">${action} ${content or '' | n}</span> |
|
619 | <span class="cb-code">${action} ${content or '' | n}</span> | |
620 | %if use_comments and comments: |
|
620 | %if use_comments and comments: | |
621 | ${inline_comments_container(comments)} |
|
621 | ${inline_comments_container(comments)} | |
622 | %endif |
|
622 | %endif | |
623 | </td> |
|
623 | </td> | |
624 | </tr> |
|
624 | </tr> | |
625 | %endfor |
|
625 | %endfor | |
626 | </%def> |
|
626 | </%def> | |
627 |
|
627 | |||
628 | <%def name="render_add_comment_button()"> |
|
628 | <%def name="render_add_comment_button()"> | |
629 | <button class="btn btn-small btn-primary cb-comment-box-opener" onclick="return Rhodecode.comments.createComment(this)"> |
|
629 | <button class="btn btn-small btn-primary cb-comment-box-opener" onclick="return Rhodecode.comments.createComment(this)"> | |
630 | <span><i class="icon-comment"></i></span> |
|
630 | <span><i class="icon-comment"></i></span> | |
631 | </button> |
|
631 | </button> | |
632 | </%def> |
|
632 | </%def> | |
633 |
|
633 | |||
634 | <%def name="render_diffset_menu()"> |
|
634 | <%def name="render_diffset_menu()"> | |
635 |
|
635 | |||
636 | <div class="diffset-menu clearinner"> |
|
636 | <div class="diffset-menu clearinner"> | |
637 | <div class="pull-right"> |
|
637 | <div class="pull-right"> | |
638 | <div class="btn-group"> |
|
638 | <div class="btn-group"> | |
639 |
|
639 | |||
640 | <a |
|
640 | <a | |
641 | class="btn ${c.diffmode == 'sideside' and 'btn-primary'} tooltip" |
|
641 | class="btn ${c.diffmode == 'sideside' and 'btn-primary'} tooltip" | |
642 | title="${h.tooltip(_('View side by side'))}" |
|
642 | title="${h.tooltip(_('View side by side'))}" | |
643 | href="${h.url_replace(diffmode='sideside')}"> |
|
643 | href="${h.url_replace(diffmode='sideside')}"> | |
644 | <span>${_('Side by Side')}</span> |
|
644 | <span>${_('Side by Side')}</span> | |
645 | </a> |
|
645 | </a> | |
646 | <a |
|
646 | <a | |
647 | class="btn ${c.diffmode == 'unified' and 'btn-primary'} tooltip" |
|
647 | class="btn ${c.diffmode == 'unified' and 'btn-primary'} tooltip" | |
648 | title="${h.tooltip(_('View unified'))}" href="${h.url_replace(diffmode='unified')}"> |
|
648 | title="${h.tooltip(_('View unified'))}" href="${h.url_replace(diffmode='unified')}"> | |
649 | <span>${_('Unified')}</span> |
|
649 | <span>${_('Unified')}</span> | |
650 | </a> |
|
650 | </a> | |
651 | </div> |
|
651 | </div> | |
652 | </div> |
|
652 | </div> | |
653 |
|
653 | |||
654 | <div class="pull-left"> |
|
654 | <div class="pull-left"> | |
655 | <div class="btn-group"> |
|
655 | <div class="btn-group"> | |
656 | <a |
|
656 | <a | |
657 | class="btn" |
|
657 | class="btn" | |
658 | href="#" |
|
658 | href="#" | |
659 | onclick="$('input[class=filediff-collapse-state]').prop('checked', false); return false">${_('Expand All Files')}</a> |
|
659 | onclick="$('input[class=filediff-collapse-state]').prop('checked', false); return false">${_('Expand All Files')}</a> | |
660 | <a |
|
660 | <a | |
661 | class="btn" |
|
661 | class="btn" | |
662 | href="#" |
|
662 | href="#" | |
663 | onclick="$('input[class=filediff-collapse-state]').prop('checked', true); return false">${_('Collapse All Files')}</a> |
|
663 | onclick="$('input[class=filediff-collapse-state]').prop('checked', true); return false">${_('Collapse All Files')}</a> | |
664 | <a |
|
664 | <a | |
665 | class="btn" |
|
665 | class="btn" | |
666 | href="#" |
|
666 | href="#" | |
667 | onclick="return Rhodecode.comments.toggleWideMode(this)">${_('Wide Mode Diff')}</a> |
|
667 | onclick="return Rhodecode.comments.toggleWideMode(this)">${_('Wide Mode Diff')}</a> | |
668 | </div> |
|
668 | </div> | |
669 | </div> |
|
669 | </div> | |
670 | </div> |
|
670 | </div> | |
671 | </%def> |
|
671 | </%def> |
@@ -1,91 +1,92 b'' | |||||
1 | <%def name="render_line(line_num, tokens, |
|
1 | <%def name="render_line(line_num, tokens, | |
2 | annotation=None, |
|
2 | annotation=None, | |
3 | bgcolor=None, show_annotation=None)"> |
|
3 | bgcolor=None, show_annotation=None)"> | |
4 | <% |
|
4 | <% | |
5 | from rhodecode.lib.codeblocks import render_tokenstream |
|
5 | from rhodecode.lib.codeblocks import render_tokenstream | |
6 | # avoid module lookup for performance |
|
6 | # avoid module lookup for performance | |
7 | html_escape = h.html_escape |
|
7 | html_escape = h.html_escape | |
8 | tooltip = h.tooltip |
|
8 | tooltip = h.tooltip | |
9 | %> |
|
9 | %> | |
10 | <tr class="cb-line cb-line-fresh ${'cb-annotate' if show_annotation else ''}" |
|
10 | <tr class="cb-line cb-line-fresh ${'cb-annotate' if show_annotation else ''}" | |
11 | %if annotation: |
|
11 | %if annotation: | |
12 | data-revision="${annotation.revision}" |
|
12 | data-revision="${annotation.revision}" | |
13 | %endif |
|
13 | %endif | |
14 | > |
|
14 | > | |
15 |
|
15 | |||
16 | % if annotation: |
|
16 | % if annotation: | |
17 | % if show_annotation: |
|
17 | % if show_annotation: | |
18 | <td class="cb-annotate-info tooltip" |
|
18 | <td class="cb-annotate-info tooltip" | |
19 | title="Author: ${tooltip(annotation.author) | entity}<br>Date: ${annotation.date}<br>Message: ${annotation.message | entity}" |
|
19 | title="Author: ${tooltip(annotation.author) | entity}<br>Date: ${annotation.date}<br>Message: ${annotation.message | entity}" | |
20 | > |
|
20 | > | |
21 | ${h.gravatar_with_user(annotation.author, 16) | n} |
|
21 | ${h.gravatar_with_user(annotation.author, 16) | n} | |
22 | <div class="cb-annotate-message truncate-wrap">${h.chop_at_smart(annotation.message, '\n', suffix_if_chopped='...')}</div> |
|
22 | <div class="cb-annotate-message truncate-wrap">${h.chop_at_smart(annotation.message, '\n', suffix_if_chopped='...')}</div> | |
23 | </td> |
|
23 | </td> | |
24 | <td class="cb-annotate-message-spacer"> |
|
24 | <td class="cb-annotate-message-spacer"> | |
25 | <a class="tooltip" href="#show-previous-annotation" onclick="return annotationController.previousAnnotation('${annotation.raw_id}', '${c.f_path}')" title="${tooltip(_('view annotation from before this change'))}"> |
|
25 | <a class="tooltip" href="#show-previous-annotation" onclick="return annotationController.previousAnnotation('${annotation.raw_id}', '${c.f_path}', ${line_num})" title="${tooltip(_('view annotation from before this change'))}"> | |
26 | <i class="icon-left"></i> |
|
26 | <i class="icon-left"></i> | |
27 | </a> |
|
27 | </a> | |
28 | </td> |
|
28 | </td> | |
29 | <td |
|
29 | <td | |
30 | class="cb-annotate-revision" |
|
30 | class="cb-annotate-revision" | |
31 | data-revision="${annotation.revision}" |
|
31 | data-revision="${annotation.revision}" | |
32 | onclick="$('[data-revision=${annotation.revision}]').toggleClass('cb-line-fresh')" |
|
32 | onclick="$('[data-revision=${annotation.revision}]').toggleClass('cb-line-fresh')" | |
33 | style="background: ${bgcolor}"> |
|
33 | style="background: ${bgcolor}"> | |
34 | <a class="cb-annotate" href="${h.url('changeset_home',repo_name=c.repo_name,revision=annotation.raw_id)}"> |
|
34 | <a class="cb-annotate" href="${h.url('changeset_home',repo_name=c.repo_name,revision=annotation.raw_id)}"> | |
35 | r${annotation.revision} |
|
35 | r${annotation.revision} | |
36 | </a> |
|
36 | </a> | |
37 | </td> |
|
37 | </td> | |
38 | % else: |
|
38 | % else: | |
39 | <td></td> |
|
39 | <td></td> | |
40 | <td class="cb-annotate-message-spacer"></td> |
|
40 | <td class="cb-annotate-message-spacer"></td> | |
41 | <td |
|
41 | <td | |
42 | class="cb-annotate-revision" |
|
42 | class="cb-annotate-revision" | |
43 | data-revision="${annotation.revision}" |
|
43 | data-revision="${annotation.revision}" | |
44 | onclick="$('[data-revision=${annotation.revision}]').toggleClass('cb-line-fresh')" |
|
44 | onclick="$('[data-revision=${annotation.revision}]').toggleClass('cb-line-fresh')" | |
45 | style="background: ${bgcolor}"> |
|
45 | style="background: ${bgcolor}"> | |
46 | </td> |
|
46 | </td> | |
47 | % endif |
|
47 | % endif | |
48 | % else: |
|
48 | % else: | |
49 | <td colspan="3"></td> |
|
49 | <td colspan="3"></td> | |
50 | % endif |
|
50 | % endif | |
51 |
|
51 | |||
52 |
|
52 | |||
53 | <td class="cb-lineno" id="L${line_num}"> |
|
53 | <td class="cb-lineno" id="L${line_num}"> | |
54 | <a data-line-no="${line_num}" href="#L${line_num}"></a> |
|
54 | <a data-line-no="${line_num}" href="#L${line_num}"></a> | |
55 | </td> |
|
55 | </td> | |
56 | <td class="cb-content cb-content-fresh" |
|
56 | <td class="cb-content cb-content-fresh" | |
57 | %if bgcolor: |
|
57 | %if bgcolor: | |
58 | style="background: ${bgcolor}" |
|
58 | style="background: ${bgcolor}" | |
59 | %endif |
|
59 | %endif | |
60 | > |
|
60 | > | |
61 | ## newline at end is necessary for highlight to work when line is empty |
|
61 | ## newline at end is necessary for highlight to work when line is empty | |
62 | ## and for copy pasting code to work as expected |
|
62 | ## and for copy pasting code to work as expected | |
63 | <span class="cb-code">${render_tokenstream(tokens)|n}${'\n'}</span> |
|
63 | <span class="cb-code">${render_tokenstream(tokens)|n}${'\n'}</span> | |
64 | </td> |
|
64 | </td> | |
65 | </tr> |
|
65 | </tr> | |
66 | </%def> |
|
66 | </%def> | |
67 |
|
67 | |||
68 | <%def name="render_annotation_lines(annotation, lines, color_hasher)"> |
|
68 | <%def name="render_annotation_lines(annotation, lines, color_hasher)"> | |
69 | % for line_num, tokens in lines: |
|
69 | % for line_num, tokens in lines: | |
70 | ${render_line(line_num, tokens, |
|
70 | ${render_line(line_num, tokens, | |
71 | bgcolor=color_hasher(annotation and annotation.raw_id or ''), |
|
71 | bgcolor=color_hasher(annotation and annotation.raw_id or ''), | |
72 | annotation=annotation, show_annotation=loop.first |
|
72 | annotation=annotation, show_annotation=loop.first | |
73 | )} |
|
73 | )} | |
74 | % endfor |
|
74 | % endfor | |
75 | <script> |
|
75 | <script> | |
76 | var AnnotationController = function() { |
|
76 | var AnnotationController = function() { | |
77 | var self = this; |
|
77 | var self = this; | |
78 |
|
78 | |||
79 | this.previousAnnotation = function(commitId, fPath) { |
|
79 | this.previousAnnotation = function(commitId, fPath, lineNo) { | |
80 | var params = { |
|
80 | var params = { | |
81 | 'repo_name': templateContext.repo_name, |
|
81 | 'repo_name': templateContext.repo_name, | |
82 |
' |
|
82 | 'commit_id': commitId, | |
83 | 'f_path': fPath |
|
83 | 'f_path': fPath, | |
|
84 | 'line_anchor': lineNo | |||
84 | }; |
|
85 | }; | |
85 |
window.location = pyroutes.url(' |
|
86 | window.location = pyroutes.url('repo_files:annotated_previous', params); | |
86 | return false; |
|
87 | return false; | |
87 | }; |
|
88 | }; | |
88 | }; |
|
89 | }; | |
89 | var annotationController = new AnnotationController(); |
|
90 | var annotationController = new AnnotationController(); | |
90 | </script> |
|
91 | </script> | |
91 | </%def> |
|
92 | </%def> |
@@ -1,317 +1,317 b'' | |||||
1 | ## DATA TABLE RE USABLE ELEMENTS |
|
1 | ## DATA TABLE RE USABLE ELEMENTS | |
2 | ## usage: |
|
2 | ## usage: | |
3 | ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/> |
|
3 | ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/> | |
4 | <%namespace name="base" file="/base/base.mako"/> |
|
4 | <%namespace name="base" file="/base/base.mako"/> | |
5 |
|
5 | |||
6 | ## REPOSITORY RENDERERS |
|
6 | ## REPOSITORY RENDERERS | |
7 | <%def name="quick_menu(repo_name)"> |
|
7 | <%def name="quick_menu(repo_name)"> | |
8 | <i class="pointer icon-more"></i> |
|
8 | <i class="pointer icon-more"></i> | |
9 | <div class="menu_items_container hidden"> |
|
9 | <div class="menu_items_container hidden"> | |
10 | <ul class="menu_items"> |
|
10 | <ul class="menu_items"> | |
11 | <li> |
|
11 | <li> | |
12 | <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}"> |
|
12 | <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}"> | |
13 | <span>${_('Summary')}</span> |
|
13 | <span>${_('Summary')}</span> | |
14 | </a> |
|
14 | </a> | |
15 | </li> |
|
15 | </li> | |
16 | <li> |
|
16 | <li> | |
17 | <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo_name)}"> |
|
17 | <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo_name)}"> | |
18 | <span>${_('Changelog')}</span> |
|
18 | <span>${_('Changelog')}</span> | |
19 | </a> |
|
19 | </a> | |
20 | </li> |
|
20 | </li> | |
21 | <li> |
|
21 | <li> | |
22 |
<a title="${_('Files')}" href="${h. |
|
22 | <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}"> | |
23 | <span>${_('Files')}</span> |
|
23 | <span>${_('Files')}</span> | |
24 | </a> |
|
24 | </a> | |
25 | </li> |
|
25 | </li> | |
26 | <li> |
|
26 | <li> | |
27 | <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}"> |
|
27 | <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}"> | |
28 | <span>${_('Fork')}</span> |
|
28 | <span>${_('Fork')}</span> | |
29 | </a> |
|
29 | </a> | |
30 | </li> |
|
30 | </li> | |
31 | </ul> |
|
31 | </ul> | |
32 | </div> |
|
32 | </div> | |
33 | </%def> |
|
33 | </%def> | |
34 |
|
34 | |||
35 | <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)"> |
|
35 | <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)"> | |
36 | <% |
|
36 | <% | |
37 | def get_name(name,short_name=short_name): |
|
37 | def get_name(name,short_name=short_name): | |
38 | if short_name: |
|
38 | if short_name: | |
39 | return name.split('/')[-1] |
|
39 | return name.split('/')[-1] | |
40 | else: |
|
40 | else: | |
41 | return name |
|
41 | return name | |
42 | %> |
|
42 | %> | |
43 | <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate"> |
|
43 | <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate"> | |
44 | ##NAME |
|
44 | ##NAME | |
45 | <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}"> |
|
45 | <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}"> | |
46 |
|
46 | |||
47 | ##TYPE OF REPO |
|
47 | ##TYPE OF REPO | |
48 | %if h.is_hg(rtype): |
|
48 | %if h.is_hg(rtype): | |
49 | <span title="${_('Mercurial repository')}"><i class="icon-hg"></i></span> |
|
49 | <span title="${_('Mercurial repository')}"><i class="icon-hg"></i></span> | |
50 | %elif h.is_git(rtype): |
|
50 | %elif h.is_git(rtype): | |
51 | <span title="${_('Git repository')}"><i class="icon-git"></i></span> |
|
51 | <span title="${_('Git repository')}"><i class="icon-git"></i></span> | |
52 | %elif h.is_svn(rtype): |
|
52 | %elif h.is_svn(rtype): | |
53 | <span title="${_('Subversion repository')}"><i class="icon-svn"></i></span> |
|
53 | <span title="${_('Subversion repository')}"><i class="icon-svn"></i></span> | |
54 | %endif |
|
54 | %endif | |
55 |
|
55 | |||
56 | ##PRIVATE/PUBLIC |
|
56 | ##PRIVATE/PUBLIC | |
57 | %if private and c.visual.show_private_icon: |
|
57 | %if private and c.visual.show_private_icon: | |
58 | <i class="icon-lock" title="${_('Private repository')}"></i> |
|
58 | <i class="icon-lock" title="${_('Private repository')}"></i> | |
59 | %elif not private and c.visual.show_public_icon: |
|
59 | %elif not private and c.visual.show_public_icon: | |
60 | <i class="icon-unlock-alt" title="${_('Public repository')}"></i> |
|
60 | <i class="icon-unlock-alt" title="${_('Public repository')}"></i> | |
61 | %else: |
|
61 | %else: | |
62 | <span></span> |
|
62 | <span></span> | |
63 | %endif |
|
63 | %endif | |
64 | ${get_name(name)} |
|
64 | ${get_name(name)} | |
65 | </a> |
|
65 | </a> | |
66 | %if fork_of: |
|
66 | %if fork_of: | |
67 | <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a> |
|
67 | <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a> | |
68 | %endif |
|
68 | %endif | |
69 | %if rstate == 'repo_state_pending': |
|
69 | %if rstate == 'repo_state_pending': | |
70 | <i class="icon-cogs" title="${_('Repository creating in progress...')}"></i> |
|
70 | <i class="icon-cogs" title="${_('Repository creating in progress...')}"></i> | |
71 | %endif |
|
71 | %endif | |
72 | </div> |
|
72 | </div> | |
73 | </%def> |
|
73 | </%def> | |
74 |
|
74 | |||
75 | <%def name="repo_desc(description)"> |
|
75 | <%def name="repo_desc(description)"> | |
76 | <div class="truncate-wrap">${description}</div> |
|
76 | <div class="truncate-wrap">${description}</div> | |
77 | </%def> |
|
77 | </%def> | |
78 |
|
78 | |||
79 | <%def name="last_change(last_change)"> |
|
79 | <%def name="last_change(last_change)"> | |
80 | ${h.age_component(last_change)} |
|
80 | ${h.age_component(last_change)} | |
81 | </%def> |
|
81 | </%def> | |
82 |
|
82 | |||
83 | <%def name="revision(name,rev,tip,author,last_msg)"> |
|
83 | <%def name="revision(name,rev,tip,author,last_msg)"> | |
84 | <div> |
|
84 | <div> | |
85 | %if rev >= 0: |
|
85 | %if rev >= 0: | |
86 | <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code> |
|
86 | <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code> | |
87 | %else: |
|
87 | %else: | |
88 | ${_('No commits yet')} |
|
88 | ${_('No commits yet')} | |
89 | %endif |
|
89 | %endif | |
90 | </div> |
|
90 | </div> | |
91 | </%def> |
|
91 | </%def> | |
92 |
|
92 | |||
93 | <%def name="rss(name)"> |
|
93 | <%def name="rss(name)"> | |
94 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
94 | %if c.rhodecode_user.username != h.DEFAULT_USER: | |
95 | <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a> |
|
95 | <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a> | |
96 | %else: |
|
96 | %else: | |
97 | <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a> |
|
97 | <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a> | |
98 | %endif |
|
98 | %endif | |
99 | </%def> |
|
99 | </%def> | |
100 |
|
100 | |||
101 | <%def name="atom(name)"> |
|
101 | <%def name="atom(name)"> | |
102 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
102 | %if c.rhodecode_user.username != h.DEFAULT_USER: | |
103 | <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a> |
|
103 | <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a> | |
104 | %else: |
|
104 | %else: | |
105 | <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a> |
|
105 | <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a> | |
106 | %endif |
|
106 | %endif | |
107 | </%def> |
|
107 | </%def> | |
108 |
|
108 | |||
109 | <%def name="user_gravatar(email, size=16)"> |
|
109 | <%def name="user_gravatar(email, size=16)"> | |
110 | <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}"> |
|
110 | <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}"> | |
111 | ${base.gravatar(email, 16)} |
|
111 | ${base.gravatar(email, 16)} | |
112 | </div> |
|
112 | </div> | |
113 | </%def> |
|
113 | </%def> | |
114 |
|
114 | |||
115 | <%def name="repo_actions(repo_name, super_user=True)"> |
|
115 | <%def name="repo_actions(repo_name, super_user=True)"> | |
116 | <div> |
|
116 | <div> | |
117 | <div class="grid_edit"> |
|
117 | <div class="grid_edit"> | |
118 | <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}"> |
|
118 | <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}"> | |
119 | <i class="icon-pencil"></i>Edit</a> |
|
119 | <i class="icon-pencil"></i>Edit</a> | |
120 | </div> |
|
120 | </div> | |
121 | <div class="grid_delete"> |
|
121 | <div class="grid_delete"> | |
122 | ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), method='POST', request=request)} |
|
122 | ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), method='POST', request=request)} | |
123 | ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger", |
|
123 | ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger", | |
124 | onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")} |
|
124 | onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")} | |
125 | ${h.end_form()} |
|
125 | ${h.end_form()} | |
126 | </div> |
|
126 | </div> | |
127 | </div> |
|
127 | </div> | |
128 | </%def> |
|
128 | </%def> | |
129 |
|
129 | |||
130 | <%def name="repo_state(repo_state)"> |
|
130 | <%def name="repo_state(repo_state)"> | |
131 | <div> |
|
131 | <div> | |
132 | %if repo_state == 'repo_state_pending': |
|
132 | %if repo_state == 'repo_state_pending': | |
133 | <div class="tag tag4">${_('Creating')}</div> |
|
133 | <div class="tag tag4">${_('Creating')}</div> | |
134 | %elif repo_state == 'repo_state_created': |
|
134 | %elif repo_state == 'repo_state_created': | |
135 | <div class="tag tag1">${_('Created')}</div> |
|
135 | <div class="tag tag1">${_('Created')}</div> | |
136 | %else: |
|
136 | %else: | |
137 | <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div> |
|
137 | <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div> | |
138 | %endif |
|
138 | %endif | |
139 | </div> |
|
139 | </div> | |
140 | </%def> |
|
140 | </%def> | |
141 |
|
141 | |||
142 |
|
142 | |||
143 | ## REPO GROUP RENDERERS |
|
143 | ## REPO GROUP RENDERERS | |
144 | <%def name="quick_repo_group_menu(repo_group_name)"> |
|
144 | <%def name="quick_repo_group_menu(repo_group_name)"> | |
145 | <i class="pointer icon-more"></i> |
|
145 | <i class="pointer icon-more"></i> | |
146 | <div class="menu_items_container hidden"> |
|
146 | <div class="menu_items_container hidden"> | |
147 | <ul class="menu_items"> |
|
147 | <ul class="menu_items"> | |
148 | <li> |
|
148 | <li> | |
149 | <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}"> |
|
149 | <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}"> | |
150 | <span class="icon"> |
|
150 | <span class="icon"> | |
151 | <i class="icon-file-text"></i> |
|
151 | <i class="icon-file-text"></i> | |
152 | </span> |
|
152 | </span> | |
153 | <span>${_('Summary')}</span> |
|
153 | <span>${_('Summary')}</span> | |
154 | </a> |
|
154 | </a> | |
155 | </li> |
|
155 | </li> | |
156 |
|
156 | |||
157 | </ul> |
|
157 | </ul> | |
158 | </div> |
|
158 | </div> | |
159 | </%def> |
|
159 | </%def> | |
160 |
|
160 | |||
161 | <%def name="repo_group_name(repo_group_name, children_groups=None)"> |
|
161 | <%def name="repo_group_name(repo_group_name, children_groups=None)"> | |
162 | <div> |
|
162 | <div> | |
163 | <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}"> |
|
163 | <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}"> | |
164 | <i class="icon-folder-close" title="${_('Repository group')}"></i> |
|
164 | <i class="icon-folder-close" title="${_('Repository group')}"></i> | |
165 | %if children_groups: |
|
165 | %if children_groups: | |
166 | ${h.literal(' » '.join(children_groups))} |
|
166 | ${h.literal(' » '.join(children_groups))} | |
167 | %else: |
|
167 | %else: | |
168 | ${repo_group_name} |
|
168 | ${repo_group_name} | |
169 | %endif |
|
169 | %endif | |
170 | </a> |
|
170 | </a> | |
171 | </div> |
|
171 | </div> | |
172 | </%def> |
|
172 | </%def> | |
173 |
|
173 | |||
174 | <%def name="repo_group_desc(description)"> |
|
174 | <%def name="repo_group_desc(description)"> | |
175 | <div class="truncate-wrap">${description}</div> |
|
175 | <div class="truncate-wrap">${description}</div> | |
176 | </%def> |
|
176 | </%def> | |
177 |
|
177 | |||
178 | <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)"> |
|
178 | <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)"> | |
179 | <div class="grid_edit"> |
|
179 | <div class="grid_edit"> | |
180 | <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">Edit</a> |
|
180 | <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">Edit</a> | |
181 | </div> |
|
181 | </div> | |
182 | <div class="grid_delete"> |
|
182 | <div class="grid_delete"> | |
183 | ${h.secure_form(h.url('delete_repo_group', group_name=repo_group_name),method='delete')} |
|
183 | ${h.secure_form(h.url('delete_repo_group', group_name=repo_group_name),method='delete')} | |
184 | ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger", |
|
184 | ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger", | |
185 | onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")} |
|
185 | onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")} | |
186 | ${h.end_form()} |
|
186 | ${h.end_form()} | |
187 | </div> |
|
187 | </div> | |
188 | </%def> |
|
188 | </%def> | |
189 |
|
189 | |||
190 |
|
190 | |||
191 | <%def name="user_actions(user_id, username)"> |
|
191 | <%def name="user_actions(user_id, username)"> | |
192 | <div class="grid_edit"> |
|
192 | <div class="grid_edit"> | |
193 | <a href="${h.url('edit_user',user_id=user_id)}" title="${_('Edit')}"> |
|
193 | <a href="${h.url('edit_user',user_id=user_id)}" title="${_('Edit')}"> | |
194 | <i class="icon-pencil"></i>Edit</a> |
|
194 | <i class="icon-pencil"></i>Edit</a> | |
195 | </div> |
|
195 | </div> | |
196 | <div class="grid_delete"> |
|
196 | <div class="grid_delete"> | |
197 | ${h.secure_form(h.url('delete_user', user_id=user_id),method='delete')} |
|
197 | ${h.secure_form(h.url('delete_user', user_id=user_id),method='delete')} | |
198 | ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger", |
|
198 | ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger", | |
199 | onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")} |
|
199 | onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")} | |
200 | ${h.end_form()} |
|
200 | ${h.end_form()} | |
201 | </div> |
|
201 | </div> | |
202 | </%def> |
|
202 | </%def> | |
203 |
|
203 | |||
204 | <%def name="user_group_actions(user_group_id, user_group_name)"> |
|
204 | <%def name="user_group_actions(user_group_id, user_group_name)"> | |
205 | <div class="grid_edit"> |
|
205 | <div class="grid_edit"> | |
206 | <a href="${h.url('edit_users_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a> |
|
206 | <a href="${h.url('edit_users_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a> | |
207 | </div> |
|
207 | </div> | |
208 | <div class="grid_delete"> |
|
208 | <div class="grid_delete"> | |
209 | ${h.secure_form(h.url('delete_users_group', user_group_id=user_group_id),method='delete')} |
|
209 | ${h.secure_form(h.url('delete_users_group', user_group_id=user_group_id),method='delete')} | |
210 | ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger", |
|
210 | ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger", | |
211 | onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")} |
|
211 | onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")} | |
212 | ${h.end_form()} |
|
212 | ${h.end_form()} | |
213 | </div> |
|
213 | </div> | |
214 | </%def> |
|
214 | </%def> | |
215 |
|
215 | |||
216 |
|
216 | |||
217 | <%def name="user_name(user_id, username)"> |
|
217 | <%def name="user_name(user_id, username)"> | |
218 | ${h.link_to(h.person(username, 'username_or_name_or_email'), h.url('edit_user', user_id=user_id))} |
|
218 | ${h.link_to(h.person(username, 'username_or_name_or_email'), h.url('edit_user', user_id=user_id))} | |
219 | </%def> |
|
219 | </%def> | |
220 |
|
220 | |||
221 | <%def name="user_profile(username)"> |
|
221 | <%def name="user_profile(username)"> | |
222 | ${base.gravatar_with_user(username, 16)} |
|
222 | ${base.gravatar_with_user(username, 16)} | |
223 | </%def> |
|
223 | </%def> | |
224 |
|
224 | |||
225 | <%def name="user_group_name(user_group_id, user_group_name)"> |
|
225 | <%def name="user_group_name(user_group_id, user_group_name)"> | |
226 | <div> |
|
226 | <div> | |
227 | <a href="${h.url('edit_users_group', user_group_id=user_group_id)}"> |
|
227 | <a href="${h.url('edit_users_group', user_group_id=user_group_id)}"> | |
228 | <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a> |
|
228 | <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a> | |
229 | </div> |
|
229 | </div> | |
230 | </%def> |
|
230 | </%def> | |
231 |
|
231 | |||
232 |
|
232 | |||
233 | ## GISTS |
|
233 | ## GISTS | |
234 |
|
234 | |||
235 | <%def name="gist_gravatar(full_contact)"> |
|
235 | <%def name="gist_gravatar(full_contact)"> | |
236 | <div class="gist_gravatar"> |
|
236 | <div class="gist_gravatar"> | |
237 | ${base.gravatar(full_contact, 30)} |
|
237 | ${base.gravatar(full_contact, 30)} | |
238 | </div> |
|
238 | </div> | |
239 | </%def> |
|
239 | </%def> | |
240 |
|
240 | |||
241 | <%def name="gist_access_id(gist_access_id, full_contact)"> |
|
241 | <%def name="gist_access_id(gist_access_id, full_contact)"> | |
242 | <div> |
|
242 | <div> | |
243 | <b> |
|
243 | <b> | |
244 | <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a> |
|
244 | <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a> | |
245 | </b> |
|
245 | </b> | |
246 | </div> |
|
246 | </div> | |
247 | </%def> |
|
247 | </%def> | |
248 |
|
248 | |||
249 | <%def name="gist_author(full_contact, created_on, expires)"> |
|
249 | <%def name="gist_author(full_contact, created_on, expires)"> | |
250 | ${base.gravatar_with_user(full_contact, 16)} |
|
250 | ${base.gravatar_with_user(full_contact, 16)} | |
251 | </%def> |
|
251 | </%def> | |
252 |
|
252 | |||
253 |
|
253 | |||
254 | <%def name="gist_created(created_on)"> |
|
254 | <%def name="gist_created(created_on)"> | |
255 | <div class="created"> |
|
255 | <div class="created"> | |
256 | ${h.age_component(created_on, time_is_local=True)} |
|
256 | ${h.age_component(created_on, time_is_local=True)} | |
257 | </div> |
|
257 | </div> | |
258 | </%def> |
|
258 | </%def> | |
259 |
|
259 | |||
260 | <%def name="gist_expires(expires)"> |
|
260 | <%def name="gist_expires(expires)"> | |
261 | <div class="created"> |
|
261 | <div class="created"> | |
262 | %if expires == -1: |
|
262 | %if expires == -1: | |
263 | ${_('never')} |
|
263 | ${_('never')} | |
264 | %else: |
|
264 | %else: | |
265 | ${h.age_component(h.time_to_utcdatetime(expires))} |
|
265 | ${h.age_component(h.time_to_utcdatetime(expires))} | |
266 | %endif |
|
266 | %endif | |
267 | </div> |
|
267 | </div> | |
268 | </%def> |
|
268 | </%def> | |
269 |
|
269 | |||
270 | <%def name="gist_type(gist_type)"> |
|
270 | <%def name="gist_type(gist_type)"> | |
271 | %if gist_type != 'public': |
|
271 | %if gist_type != 'public': | |
272 | <div class="tag">${_('Private')}</div> |
|
272 | <div class="tag">${_('Private')}</div> | |
273 | %endif |
|
273 | %endif | |
274 | </%def> |
|
274 | </%def> | |
275 |
|
275 | |||
276 | <%def name="gist_description(gist_description)"> |
|
276 | <%def name="gist_description(gist_description)"> | |
277 | ${gist_description} |
|
277 | ${gist_description} | |
278 | </%def> |
|
278 | </%def> | |
279 |
|
279 | |||
280 |
|
280 | |||
281 | ## PULL REQUESTS GRID RENDERERS |
|
281 | ## PULL REQUESTS GRID RENDERERS | |
282 |
|
282 | |||
283 | <%def name="pullrequest_target_repo(repo_name)"> |
|
283 | <%def name="pullrequest_target_repo(repo_name)"> | |
284 | <div class="truncate"> |
|
284 | <div class="truncate"> | |
285 | ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))} |
|
285 | ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))} | |
286 | </div> |
|
286 | </div> | |
287 | </%def> |
|
287 | </%def> | |
288 | <%def name="pullrequest_status(status)"> |
|
288 | <%def name="pullrequest_status(status)"> | |
289 | <div class="${'flag_status %s' % status} pull-left"></div> |
|
289 | <div class="${'flag_status %s' % status} pull-left"></div> | |
290 | </%def> |
|
290 | </%def> | |
291 |
|
291 | |||
292 | <%def name="pullrequest_title(title, description)"> |
|
292 | <%def name="pullrequest_title(title, description)"> | |
293 | ${title} <br/> |
|
293 | ${title} <br/> | |
294 | ${h.shorter(description, 40)} |
|
294 | ${h.shorter(description, 40)} | |
295 | </%def> |
|
295 | </%def> | |
296 |
|
296 | |||
297 | <%def name="pullrequest_comments(comments_nr)"> |
|
297 | <%def name="pullrequest_comments(comments_nr)"> | |
298 | <i class="icon-comment"></i> ${comments_nr} |
|
298 | <i class="icon-comment"></i> ${comments_nr} | |
299 | </%def> |
|
299 | </%def> | |
300 |
|
300 | |||
301 | <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)"> |
|
301 | <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)"> | |
302 | <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}"> |
|
302 | <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}"> | |
303 | % if short: |
|
303 | % if short: | |
304 | #${pull_request_id} |
|
304 | #${pull_request_id} | |
305 | % else: |
|
305 | % else: | |
306 | ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}} |
|
306 | ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}} | |
307 | % endif |
|
307 | % endif | |
308 | </a> |
|
308 | </a> | |
309 | </%def> |
|
309 | </%def> | |
310 |
|
310 | |||
311 | <%def name="pullrequest_updated_on(updated_on)"> |
|
311 | <%def name="pullrequest_updated_on(updated_on)"> | |
312 | ${h.age_component(h.time_to_utcdatetime(updated_on))} |
|
312 | ${h.age_component(h.time_to_utcdatetime(updated_on))} | |
313 | </%def> |
|
313 | </%def> | |
314 |
|
314 | |||
315 | <%def name="pullrequest_author(full_contact)"> |
|
315 | <%def name="pullrequest_author(full_contact)"> | |
316 | ${base.gravatar_with_user(full_contact, 16)} |
|
316 | ${base.gravatar_with_user(full_contact, 16)} | |
317 | </%def> |
|
317 | </%def> |
@@ -1,28 +1,28 b'' | |||||
1 | <%def name="refs(commit)"> |
|
1 | <%def name="refs(commit)"> | |
2 | %if commit.merge: |
|
2 | %if commit.merge: | |
3 | <span class="mergetag tag"> |
|
3 | <span class="mergetag tag"> | |
4 | <i class="icon-merge">${_('merge')}</i> |
|
4 | <i class="icon-merge">${_('merge')}</i> | |
5 | </span> |
|
5 | </span> | |
6 | %endif |
|
6 | %endif | |
7 |
|
7 | |||
8 | %if h.is_hg(c.rhodecode_repo): |
|
8 | %if h.is_hg(c.rhodecode_repo): | |
9 | %for book in commit.bookmarks: |
|
9 | %for book in commit.bookmarks: | |
10 | <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}"> |
|
10 | <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}"> | |
11 |
<a href="${h. |
|
11 | <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> | |
12 | </span> |
|
12 | </span> | |
13 | %endfor |
|
13 | %endfor | |
14 | %endif |
|
14 | %endif | |
15 |
|
15 | |||
16 | %for tag in commit.tags: |
|
16 | %for tag in commit.tags: | |
17 | <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}"> |
|
17 | <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}"> | |
18 |
<a href="${h. |
|
18 | <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>${tag}</a> | |
19 | </span> |
|
19 | </span> | |
20 | %endfor |
|
20 | %endfor | |
21 |
|
21 | |||
22 | %if commit.branch: |
|
22 | %if commit.branch: | |
23 | <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % commit.branch)}"> |
|
23 | <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % commit.branch)}"> | |
24 |
<a href="${h. |
|
24 | <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id,_query=dict(at=commit.branch))}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a> | |
25 | </span> |
|
25 | </span> | |
26 | %endif |
|
26 | %endif | |
27 |
|
27 | |||
28 | </%def> |
|
28 | </%def> |
@@ -1,30 +1,42 b'' | |||||
1 | <%namespace name="base" file="/base/base.mako"/> |
|
1 | <%namespace name="base" file="/base/base.mako"/> | |
2 |
|
2 | |||
3 | <div class="summary-detail-header"> |
|
3 | <div class="summary-detail-header"> | |
4 | <h4 class="item"> |
|
4 | <h4 class="item"> | |
5 | % if c.file_author: |
|
5 | % if c.file_author: | |
6 | ${_('Last Author')} |
|
6 | ${_('Last Author')} | |
7 | % else: |
|
7 | % else: | |
8 | ${h.literal(ungettext(u'File Author (%s)',u'File Authors (%s)',len(c.authors)) % ('<b>%s</b>' % len(c.authors))) } |
|
8 | ${h.literal(_ungettext(u'File Author (%s)',u'File Authors (%s)',len(c.authors)) % ('<b>%s</b>' % len(c.authors))) } | |
9 | % endif |
|
9 | % endif | |
10 | </h4> |
|
10 | </h4> | |
11 | <a href="#" id="show_authors" class="action_link">${_('Show All')}</a> |
|
11 | <a href="#" id="show_authors" class="action_link">${_('Show All')}</a> | |
12 | </div> |
|
12 | </div> | |
13 |
|
13 | |||
14 | % if c.authors: |
|
14 | % if c.authors: | |
15 | <ul class="sidebar-right-content"> |
|
15 | <ul class="sidebar-right-content"> | |
16 | % for email, user in sorted(c.authors, key=lambda e: c.file_last_commit.author_email!=e[0]): |
|
16 | % for email, user, commits in sorted(c.authors, key=lambda e: c.file_last_commit.author_email!=e[0]): | |
17 | <li class="file_author"> |
|
17 | <li class="file_author"> | |
18 |
<div class=" |
|
18 | <div class="tooltip" title="${h.tooltip(h.author_string(email))}"> | |
19 | ${base.gravatar(email, 16)} |
|
19 | ${base.gravatar(email, 16)} | |
20 |
< |
|
20 | <div class="user">${h.link_to_user(user)}</div> | |
|
21 | ||||
|
22 | % if c.file_author: | |||
|
23 | <span>- ${h.age_component(c.file_last_commit.date)}</span> | |||
|
24 | % elif c.file_last_commit.author_email==email: | |||
|
25 | <span> (${_('last author')})</span> | |||
|
26 | % endif | |||
|
27 | ||||
|
28 | % if not c.file_author: | |||
|
29 | <span> | |||
|
30 | % if commits == 1: | |||
|
31 | ${commits} ${_('Commit')} | |||
|
32 | % else: | |||
|
33 | ${commits} ${_('Commits')} | |||
|
34 | % endif | |||
|
35 | </span> | |||
|
36 | % endif | |||
|
37 | ||||
21 | </div> |
|
38 | </div> | |
22 | % if c.file_author: |
|
|||
23 | <div class="user-inline-data">- ${h.age_component(c.file_last_commit.date)}</div> |
|
|||
24 | % elif c.file_last_commit.author_email==email: |
|
|||
25 | <div class="user-inline-data"> (${_('last author')})</div> |
|
|||
26 | % endif |
|
|||
27 | </li> |
|
39 | </li> | |
28 | % endfor |
|
40 | % endfor | |
29 | </ul> |
|
41 | </ul> | |
30 | % endif |
|
42 | % endif |
@@ -1,324 +1,324 b'' | |||||
1 | <%inherit file="/base/base.mako"/> |
|
1 | <%inherit file="/base/base.mako"/> | |
2 |
|
2 | |||
3 | <%def name="title(*args)"> |
|
3 | <%def name="title(*args)"> | |
4 | ${_('%s Files') % c.repo_name} |
|
4 | ${_('%s Files') % c.repo_name} | |
5 | %if hasattr(c,'file'): |
|
5 | %if hasattr(c,'file'): | |
6 | · ${h.safe_unicode(c.file.path) or '\\'} |
|
6 | · ${h.safe_unicode(c.file.path) or '\\'} | |
7 | %endif |
|
7 | %endif | |
8 |
|
8 | |||
9 | %if c.rhodecode_name: |
|
9 | %if c.rhodecode_name: | |
10 | · ${h.branding(c.rhodecode_name)} |
|
10 | · ${h.branding(c.rhodecode_name)} | |
11 | %endif |
|
11 | %endif | |
12 | </%def> |
|
12 | </%def> | |
13 |
|
13 | |||
14 | <%def name="breadcrumbs_links()"> |
|
14 | <%def name="breadcrumbs_links()"> | |
15 | ${_('Files')} |
|
15 | ${_('Files')} | |
16 | %if c.file: |
|
16 | %if c.file: | |
17 | @ ${h.show_id(c.commit)} |
|
17 | @ ${h.show_id(c.commit)} | |
18 | %endif |
|
18 | %endif | |
19 | </%def> |
|
19 | </%def> | |
20 |
|
20 | |||
21 | <%def name="menu_bar_nav()"> |
|
21 | <%def name="menu_bar_nav()"> | |
22 | ${self.menu_items(active='repositories')} |
|
22 | ${self.menu_items(active='repositories')} | |
23 | </%def> |
|
23 | </%def> | |
24 |
|
24 | |||
25 | <%def name="menu_bar_subnav()"> |
|
25 | <%def name="menu_bar_subnav()"> | |
26 | ${self.repo_menu(active='files')} |
|
26 | ${self.repo_menu(active='files')} | |
27 | </%def> |
|
27 | </%def> | |
28 |
|
28 | |||
29 | <%def name="main()"> |
|
29 | <%def name="main()"> | |
30 | <div class="title"> |
|
30 | <div class="title"> | |
31 | ${self.repo_page_title(c.rhodecode_db_repo)} |
|
31 | ${self.repo_page_title(c.rhodecode_db_repo)} | |
32 | </div> |
|
32 | </div> | |
33 |
|
33 | |||
34 | <div id="pjax-container" class="summary"> |
|
34 | <div id="pjax-container" class="summary"> | |
35 | <div id="files_data"> |
|
35 | <div id="files_data"> | |
36 | <%include file='files_pjax.mako'/> |
|
36 | <%include file='files_pjax.mako'/> | |
37 | </div> |
|
37 | </div> | |
38 | </div> |
|
38 | </div> | |
39 | <script> |
|
39 | <script> | |
40 | var curState = { |
|
40 | var curState = { | |
41 | commit_id: "${c.commit.raw_id}" |
|
41 | commit_id: "${c.commit.raw_id}" | |
42 | }; |
|
42 | }; | |
43 |
|
43 | |||
44 | var getState = function(context) { |
|
44 | var getState = function(context) { | |
45 | var url = $(location).attr('href'); |
|
45 | var url = $(location).attr('href'); | |
46 |
var _base_url = '${h. |
|
46 | var _base_url = '${h.route_path("repo_files",repo_name=c.repo_name,commit_id='',f_path='')}'; | |
47 |
var _annotate_url = '${h. |
|
47 | var _annotate_url = '${h.route_path("repo_files:annotated",repo_name=c.repo_name,commit_id='',f_path='')}'; | |
48 | _base_url = _base_url.replace('//', '/'); |
|
48 | _base_url = _base_url.replace('//', '/'); | |
49 | _annotate_url = _annotate_url.replace('//', '/'); |
|
49 | _annotate_url = _annotate_url.replace('//', '/'); | |
50 |
|
50 | |||
51 | //extract f_path from url. |
|
51 | //extract f_path from url. | |
52 | var parts = url.split(_base_url); |
|
52 | var parts = url.split(_base_url); | |
53 | if (parts.length != 2) { |
|
53 | if (parts.length != 2) { | |
54 | parts = url.split(_annotate_url); |
|
54 | parts = url.split(_annotate_url); | |
55 | if (parts.length != 2) { |
|
55 | if (parts.length != 2) { | |
56 | var rev = "tip"; |
|
56 | var rev = "tip"; | |
57 | var f_path = ""; |
|
57 | var f_path = ""; | |
58 | } else { |
|
58 | } else { | |
59 | var parts2 = parts[1].split('/'); |
|
59 | var parts2 = parts[1].split('/'); | |
60 | var rev = parts2.shift(); // pop the first element which is the revision |
|
60 | var rev = parts2.shift(); // pop the first element which is the revision | |
61 | var f_path = parts2.join('/'); |
|
61 | var f_path = parts2.join('/'); | |
62 | } |
|
62 | } | |
63 |
|
63 | |||
64 | } else { |
|
64 | } else { | |
65 | var parts2 = parts[1].split('/'); |
|
65 | var parts2 = parts[1].split('/'); | |
66 | var rev = parts2.shift(); // pop the first element which is the revision |
|
66 | var rev = parts2.shift(); // pop the first element which is the revision | |
67 | var f_path = parts2.join('/'); |
|
67 | var f_path = parts2.join('/'); | |
68 | } |
|
68 | } | |
69 |
|
69 | |||
70 |
var _node_list_url = pyroutes.url('files_nodelist |
|
70 | var _node_list_url = pyroutes.url('repo_files_nodelist', | |
71 | {repo_name: templateContext.repo_name, |
|
71 | {repo_name: templateContext.repo_name, | |
72 |
|
|
72 | commit_id: rev, f_path: f_path}); | |
73 |
var _url_base = pyroutes.url('files |
|
73 | var _url_base = pyroutes.url('repo_files', | |
74 | {repo_name: templateContext.repo_name, |
|
74 | {repo_name: templateContext.repo_name, | |
75 |
|
|
75 | commit_id: rev, f_path:'__FPATH__'}); | |
76 | return { |
|
76 | return { | |
77 | url: url, |
|
77 | url: url, | |
78 | f_path: f_path, |
|
78 | f_path: f_path, | |
79 | rev: rev, |
|
79 | rev: rev, | |
80 | commit_id: curState.commit_id, |
|
80 | commit_id: curState.commit_id, | |
81 | node_list_url: _node_list_url, |
|
81 | node_list_url: _node_list_url, | |
82 | url_base: _url_base |
|
82 | url_base: _url_base | |
83 | }; |
|
83 | }; | |
84 | }; |
|
84 | }; | |
85 |
|
85 | |||
86 | var metadataRequest = null; |
|
86 | var metadataRequest = null; | |
87 | var getFilesMetadata = function() { |
|
87 | var getFilesMetadata = function() { | |
88 | if (metadataRequest && metadataRequest.readyState != 4) { |
|
88 | if (metadataRequest && metadataRequest.readyState != 4) { | |
89 | metadataRequest.abort(); |
|
89 | metadataRequest.abort(); | |
90 | } |
|
90 | } | |
91 | if (fileSourcePage) { |
|
91 | if (fileSourcePage) { | |
92 | return false; |
|
92 | return false; | |
93 | } |
|
93 | } | |
94 |
|
94 | |||
95 | if ($('#file-tree-wrapper').hasClass('full-load')) { |
|
95 | if ($('#file-tree-wrapper').hasClass('full-load')) { | |
96 | // in case our HTML wrapper has full-load class we don't |
|
96 | // in case our HTML wrapper has full-load class we don't | |
97 | // trigger the async load of metadata |
|
97 | // trigger the async load of metadata | |
98 | return false; |
|
98 | return false; | |
99 | } |
|
99 | } | |
100 |
|
100 | |||
101 | var state = getState('metadata'); |
|
101 | var state = getState('metadata'); | |
102 | var url_data = { |
|
102 | var url_data = { | |
103 | 'repo_name': templateContext.repo_name, |
|
103 | 'repo_name': templateContext.repo_name, | |
104 | 'commit_id': state.commit_id, |
|
104 | 'commit_id': state.commit_id, | |
105 | 'f_path': state.f_path |
|
105 | 'f_path': state.f_path | |
106 | }; |
|
106 | }; | |
107 |
|
107 | |||
108 |
var url = pyroutes.url(' |
|
108 | var url = pyroutes.url('repo_nodetree_full', url_data); | |
109 |
|
109 | |||
110 | metadataRequest = $.ajax({url: url}); |
|
110 | metadataRequest = $.ajax({url: url}); | |
111 |
|
111 | |||
112 | metadataRequest.done(function(data) { |
|
112 | metadataRequest.done(function(data) { | |
113 | $('#file-tree').html(data); |
|
113 | $('#file-tree').html(data); | |
114 | timeagoActivate(); |
|
114 | timeagoActivate(); | |
115 | }); |
|
115 | }); | |
116 | metadataRequest.fail(function (data, textStatus, errorThrown) { |
|
116 | metadataRequest.fail(function (data, textStatus, errorThrown) { | |
117 | console.log(data); |
|
117 | console.log(data); | |
118 | if (data.status != 0) { |
|
118 | if (data.status != 0) { | |
119 | alert("Error while fetching metadata.\nError code {0} ({1}).Please consider reloading the page".format(data.status,data.statusText)); |
|
119 | alert("Error while fetching metadata.\nError code {0} ({1}).Please consider reloading the page".format(data.status,data.statusText)); | |
120 | } |
|
120 | } | |
121 | }); |
|
121 | }); | |
122 | }; |
|
122 | }; | |
123 |
|
123 | |||
124 | var callbacks = function() { |
|
124 | var callbacks = function() { | |
125 | var state = getState('callbacks'); |
|
125 | var state = getState('callbacks'); | |
126 | timeagoActivate(); |
|
126 | timeagoActivate(); | |
127 |
|
127 | |||
128 | // used for history, and switch to |
|
128 | // used for history, and switch to | |
129 | var initialCommitData = { |
|
129 | var initialCommitData = { | |
130 | id: null, |
|
130 | id: null, | |
131 | text: '${_("Pick Commit")}', |
|
131 | text: '${_("Pick Commit")}', | |
132 | type: 'sha', |
|
132 | type: 'sha', | |
133 | raw_id: null, |
|
133 | raw_id: null, | |
134 | files_url: null |
|
134 | files_url: null | |
135 | }; |
|
135 | }; | |
136 |
|
136 | |||
137 | if ($('#trimmed_message_box').height() < 50) { |
|
137 | if ($('#trimmed_message_box').height() < 50) { | |
138 | $('#message_expand').hide(); |
|
138 | $('#message_expand').hide(); | |
139 | } |
|
139 | } | |
140 |
|
140 | |||
141 | $('#message_expand').on('click', function(e) { |
|
141 | $('#message_expand').on('click', function(e) { | |
142 | $('#trimmed_message_box').css('max-height', 'none'); |
|
142 | $('#trimmed_message_box').css('max-height', 'none'); | |
143 | $(this).hide(); |
|
143 | $(this).hide(); | |
144 | }); |
|
144 | }); | |
145 |
|
145 | |||
146 | if (fileSourcePage) { |
|
146 | if (fileSourcePage) { | |
147 | // variants for with source code, not tree view |
|
147 | // variants for with source code, not tree view | |
148 |
|
148 | |||
149 | // select code link event |
|
149 | // select code link event | |
150 | $("#hlcode").mouseup(getSelectionLink); |
|
150 | $("#hlcode").mouseup(getSelectionLink); | |
151 |
|
151 | |||
152 | // file history select2 |
|
152 | // file history select2 | |
153 | select2FileHistorySwitcher('#diff1', initialCommitData, state); |
|
153 | select2FileHistorySwitcher('#diff1', initialCommitData, state); | |
154 |
|
154 | |||
155 | // show at, diff to actions handlers |
|
155 | // show at, diff to actions handlers | |
156 | $('#diff1').on('change', function(e) { |
|
156 | $('#diff1').on('change', function(e) { | |
157 | $('#diff_to_commit').removeClass('disabled').removeAttr("disabled"); |
|
157 | $('#diff_to_commit').removeClass('disabled').removeAttr("disabled"); | |
158 | $('#diff_to_commit').val(_gettext('Diff to Commit ') + e.val.truncateAfter(8, '...')); |
|
158 | $('#diff_to_commit').val(_gettext('Diff to Commit ') + e.val.truncateAfter(8, '...')); | |
159 |
|
159 | |||
160 | $('#show_at_commit').removeClass('disabled').removeAttr("disabled"); |
|
160 | $('#show_at_commit').removeClass('disabled').removeAttr("disabled"); | |
161 | $('#show_at_commit').val(_gettext('Show at Commit ') + e.val.truncateAfter(8, '...')); |
|
161 | $('#show_at_commit').val(_gettext('Show at Commit ') + e.val.truncateAfter(8, '...')); | |
162 | }); |
|
162 | }); | |
163 |
|
163 | |||
164 | $('#diff_to_commit').on('click', function(e) { |
|
164 | $('#diff_to_commit').on('click', function(e) { | |
165 | var diff1 = $('#diff1').val(); |
|
165 | var diff1 = $('#diff1').val(); | |
166 | var diff2 = $('#diff2').val(); |
|
166 | var diff2 = $('#diff2').val(); | |
167 |
|
167 | |||
168 | var url_data = { |
|
168 | var url_data = { | |
169 | repo_name: templateContext.repo_name, |
|
169 | repo_name: templateContext.repo_name, | |
170 | source_ref: diff1, |
|
170 | source_ref: diff1, | |
171 | source_ref_type: 'rev', |
|
171 | source_ref_type: 'rev', | |
172 | target_ref: diff2, |
|
172 | target_ref: diff2, | |
173 | target_ref_type: 'rev', |
|
173 | target_ref_type: 'rev', | |
174 | merge: 1, |
|
174 | merge: 1, | |
175 | f_path: state.f_path |
|
175 | f_path: state.f_path | |
176 | }; |
|
176 | }; | |
177 | window.location = pyroutes.url('compare_url', url_data); |
|
177 | window.location = pyroutes.url('compare_url', url_data); | |
178 | }); |
|
178 | }); | |
179 |
|
179 | |||
180 | $('#show_at_commit').on('click', function(e) { |
|
180 | $('#show_at_commit').on('click', function(e) { | |
181 | var diff1 = $('#diff1').val(); |
|
181 | var diff1 = $('#diff1').val(); | |
182 |
|
182 | |||
183 | var annotate = $('#annotate').val(); |
|
183 | var annotate = $('#annotate').val(); | |
184 | if (annotate === "True") { |
|
184 | if (annotate === "True") { | |
185 |
var url = pyroutes.url(' |
|
185 | var url = pyroutes.url('repo_files:annotated', | |
186 | {'repo_name': templateContext.repo_name, |
|
186 | {'repo_name': templateContext.repo_name, | |
187 |
' |
|
187 | 'commit_id': diff1, 'f_path': state.f_path}); | |
188 | } else { |
|
188 | } else { | |
189 |
var url = pyroutes.url('files |
|
189 | var url = pyroutes.url('repo_files', | |
190 | {'repo_name': templateContext.repo_name, |
|
190 | {'repo_name': templateContext.repo_name, | |
191 |
' |
|
191 | 'commit_id': diff1, 'f_path': state.f_path}); | |
192 | } |
|
192 | } | |
193 | window.location = url; |
|
193 | window.location = url; | |
194 |
|
194 | |||
195 | }); |
|
195 | }); | |
196 |
|
196 | |||
197 | // show more authors |
|
197 | // show more authors | |
198 | $('#show_authors').on('click', function(e) { |
|
198 | $('#show_authors').on('click', function(e) { | |
199 | e.preventDefault(); |
|
199 | e.preventDefault(); | |
200 |
var url = pyroutes.url('file |
|
200 | var url = pyroutes.url('repo_file_authors', | |
201 | {'repo_name': templateContext.repo_name, |
|
201 | {'repo_name': templateContext.repo_name, | |
202 |
' |
|
202 | 'commit_id': state.rev, 'f_path': state.f_path}); | |
203 |
|
203 | |||
204 | $.pjax({ |
|
204 | $.pjax({ | |
205 | url: url, |
|
205 | url: url, | |
206 | data: 'annotate=${"1" if c.annotate else "0"}', |
|
206 | data: 'annotate=${"1" if c.annotate else "0"}', | |
207 | container: '#file_authors', |
|
207 | container: '#file_authors', | |
208 | push: false, |
|
208 | push: false, | |
209 | timeout: pjaxTimeout |
|
209 | timeout: pjaxTimeout | |
210 | }).complete(function(){ |
|
210 | }).complete(function(){ | |
211 | $('#show_authors').hide(); |
|
211 | $('#show_authors').hide(); | |
212 | }) |
|
212 | }) | |
213 | }); |
|
213 | }); | |
214 |
|
214 | |||
215 | // load file short history |
|
215 | // load file short history | |
216 | $('#file_history_overview').on('click', function(e) { |
|
216 | $('#file_history_overview').on('click', function(e) { | |
217 | e.preventDefault(); |
|
217 | e.preventDefault(); | |
218 | path = state.f_path; |
|
218 | path = state.f_path; | |
219 | if (path.indexOf("#") >= 0) { |
|
219 | if (path.indexOf("#") >= 0) { | |
220 | path = path.slice(0, path.indexOf("#")); |
|
220 | path = path.slice(0, path.indexOf("#")); | |
221 | } |
|
221 | } | |
222 | var url = pyroutes.url('changelog_file_home', |
|
222 | var url = pyroutes.url('changelog_file_home', | |
223 | {'repo_name': templateContext.repo_name, |
|
223 | {'repo_name': templateContext.repo_name, | |
224 | 'revision': state.rev, 'f_path': path, 'limit': 6}); |
|
224 | 'revision': state.rev, 'f_path': path, 'limit': 6}); | |
225 | $('#file_history_container').show(); |
|
225 | $('#file_history_container').show(); | |
226 | $('#file_history_container').html('<div class="file-history-inner">{0}</div>'.format(_gettext('Loading ...'))); |
|
226 | $('#file_history_container').html('<div class="file-history-inner">{0}</div>'.format(_gettext('Loading ...'))); | |
227 |
|
227 | |||
228 | $.pjax({ |
|
228 | $.pjax({ | |
229 | url: url, |
|
229 | url: url, | |
230 | container: '#file_history_container', |
|
230 | container: '#file_history_container', | |
231 | push: false, |
|
231 | push: false, | |
232 | timeout: pjaxTimeout |
|
232 | timeout: pjaxTimeout | |
233 | }) |
|
233 | }) | |
234 | }); |
|
234 | }); | |
235 |
|
235 | |||
236 | } |
|
236 | } | |
237 | else { |
|
237 | else { | |
238 | getFilesMetadata(); |
|
238 | getFilesMetadata(); | |
239 |
|
239 | |||
240 | // fuzzy file filter |
|
240 | // fuzzy file filter | |
241 | fileBrowserListeners(state.node_list_url, state.url_base); |
|
241 | fileBrowserListeners(state.node_list_url, state.url_base); | |
242 |
|
242 | |||
243 | // switch to widget |
|
243 | // switch to widget | |
244 | select2RefSwitcher('#refs_filter', initialCommitData); |
|
244 | select2RefSwitcher('#refs_filter', initialCommitData); | |
245 | $('#refs_filter').on('change', function(e) { |
|
245 | $('#refs_filter').on('change', function(e) { | |
246 | var data = $('#refs_filter').select2('data'); |
|
246 | var data = $('#refs_filter').select2('data'); | |
247 | curState.commit_id = data.raw_id; |
|
247 | curState.commit_id = data.raw_id; | |
248 | $.pjax({url: data.files_url, container: '#pjax-container', timeout: pjaxTimeout}); |
|
248 | $.pjax({url: data.files_url, container: '#pjax-container', timeout: pjaxTimeout}); | |
249 | }); |
|
249 | }); | |
250 |
|
250 | |||
251 | $("#prev_commit_link").on('click', function(e) { |
|
251 | $("#prev_commit_link").on('click', function(e) { | |
252 | var data = $(this).data(); |
|
252 | var data = $(this).data(); | |
253 | curState.commit_id = data.commitId; |
|
253 | curState.commit_id = data.commitId; | |
254 | }); |
|
254 | }); | |
255 |
|
255 | |||
256 | $("#next_commit_link").on('click', function(e) { |
|
256 | $("#next_commit_link").on('click', function(e) { | |
257 | var data = $(this).data(); |
|
257 | var data = $(this).data(); | |
258 | curState.commit_id = data.commitId; |
|
258 | curState.commit_id = data.commitId; | |
259 | }); |
|
259 | }); | |
260 |
|
260 | |||
261 | $('#at_rev').on("keypress", function(e) { |
|
261 | $('#at_rev').on("keypress", function(e) { | |
262 | /* ENTER PRESSED */ |
|
262 | /* ENTER PRESSED */ | |
263 | if (e.keyCode === 13) { |
|
263 | if (e.keyCode === 13) { | |
264 | var rev = $('#at_rev').val(); |
|
264 | var rev = $('#at_rev').val(); | |
265 | // explicit reload page here. with pjax entering bad input |
|
265 | // explicit reload page here. with pjax entering bad input | |
266 | // produces not so nice results |
|
266 | // produces not so nice results | |
267 |
window.location = pyroutes.url('files |
|
267 | window.location = pyroutes.url('repo_files', | |
268 | {'repo_name': templateContext.repo_name, |
|
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 | var pjaxTimeout = 5000; |
|
275 | var pjaxTimeout = 5000; | |
276 |
|
276 | |||
277 | $(document).pjax(".pjax-link", "#pjax-container", { |
|
277 | $(document).pjax(".pjax-link", "#pjax-container", { | |
278 | "fragment": "#pjax-content", |
|
278 | "fragment": "#pjax-content", | |
279 | "maxCacheLength": 1000, |
|
279 | "maxCacheLength": 1000, | |
280 | "timeout": pjaxTimeout |
|
280 | "timeout": pjaxTimeout | |
281 | }); |
|
281 | }); | |
282 |
|
282 | |||
283 | // define global back/forward states |
|
283 | // define global back/forward states | |
284 | var isPjaxPopState = false; |
|
284 | var isPjaxPopState = false; | |
285 | $(document).on('pjax:popstate', function() { |
|
285 | $(document).on('pjax:popstate', function() { | |
286 | isPjaxPopState = true; |
|
286 | isPjaxPopState = true; | |
287 | }); |
|
287 | }); | |
288 |
|
288 | |||
289 | $(document).on('pjax:end', function(xhr, options) { |
|
289 | $(document).on('pjax:end', function(xhr, options) { | |
290 | if (isPjaxPopState) { |
|
290 | if (isPjaxPopState) { | |
291 | isPjaxPopState = false; |
|
291 | isPjaxPopState = false; | |
292 | callbacks(); |
|
292 | callbacks(); | |
293 | _NODEFILTER.resetFilter(); |
|
293 | _NODEFILTER.resetFilter(); | |
294 | } |
|
294 | } | |
295 |
|
295 | |||
296 | // run callback for tracking if defined for google analytics etc. |
|
296 | // run callback for tracking if defined for google analytics etc. | |
297 | // this is used to trigger tracking on pjax |
|
297 | // this is used to trigger tracking on pjax | |
298 | if (typeof window.rhodecode_statechange_callback !== 'undefined') { |
|
298 | if (typeof window.rhodecode_statechange_callback !== 'undefined') { | |
299 | var state = getState('statechange'); |
|
299 | var state = getState('statechange'); | |
300 | rhodecode_statechange_callback(state.url, null) |
|
300 | rhodecode_statechange_callback(state.url, null) | |
301 | } |
|
301 | } | |
302 | }); |
|
302 | }); | |
303 |
|
303 | |||
304 | $(document).on('pjax:success', function(event, xhr, options) { |
|
304 | $(document).on('pjax:success', function(event, xhr, options) { | |
305 | if (event.target.id == "file_history_container") { |
|
305 | if (event.target.id == "file_history_container") { | |
306 | $('#file_history_overview').hide(); |
|
306 | $('#file_history_overview').hide(); | |
307 | $('#file_history_overview_full').show(); |
|
307 | $('#file_history_overview_full').show(); | |
308 | timeagoActivate(); |
|
308 | timeagoActivate(); | |
309 | } else { |
|
309 | } else { | |
310 | callbacks(); |
|
310 | callbacks(); | |
311 | } |
|
311 | } | |
312 | }); |
|
312 | }); | |
313 |
|
313 | |||
314 | $(document).ready(function() { |
|
314 | $(document).ready(function() { | |
315 | callbacks(); |
|
315 | callbacks(); | |
316 | var search_GET = "${request.GET.get('search','')}"; |
|
316 | var search_GET = "${request.GET.get('search','')}"; | |
317 | if (search_GET == "1") { |
|
317 | if (search_GET == "1") { | |
318 | _NODEFILTER.initFilter(); |
|
318 | _NODEFILTER.initFilter(); | |
319 | } |
|
319 | } | |
320 | }); |
|
320 | }); | |
321 |
|
321 | |||
322 | </script> |
|
322 | </script> | |
323 |
|
323 | |||
324 | </%def> |
|
324 | </%def> |
@@ -1,236 +1,236 b'' | |||||
1 | <%inherit file="/base/base.mako"/> |
|
1 | <%inherit file="/base/base.mako"/> | |
2 |
|
2 | |||
3 | <%def name="title()"> |
|
3 | <%def name="title()"> | |
4 | ${_('%s Files Add') % c.repo_name} |
|
4 | ${_('%s Files Add') % c.repo_name} | |
5 | %if c.rhodecode_name: |
|
5 | %if c.rhodecode_name: | |
6 | · ${h.branding(c.rhodecode_name)} |
|
6 | · ${h.branding(c.rhodecode_name)} | |
7 | %endif |
|
7 | %endif | |
8 | </%def> |
|
8 | </%def> | |
9 |
|
9 | |||
10 | <%def name="menu_bar_nav()"> |
|
10 | <%def name="menu_bar_nav()"> | |
11 | ${self.menu_items(active='repositories')} |
|
11 | ${self.menu_items(active='repositories')} | |
12 | </%def> |
|
12 | </%def> | |
13 |
|
13 | |||
14 | <%def name="breadcrumbs_links()"> |
|
14 | <%def name="breadcrumbs_links()"> | |
15 | ${_('Add new file')} @ ${h.show_id(c.commit)} |
|
15 | ${_('Add new file')} @ ${h.show_id(c.commit)} | |
16 | </%def> |
|
16 | </%def> | |
17 |
|
17 | |||
18 | <%def name="menu_bar_subnav()"> |
|
18 | <%def name="menu_bar_subnav()"> | |
19 | ${self.repo_menu(active='files')} |
|
19 | ${self.repo_menu(active='files')} | |
20 | </%def> |
|
20 | </%def> | |
21 |
|
21 | |||
22 | <%def name="main()"> |
|
22 | <%def name="main()"> | |
23 | <div class="box"> |
|
23 | <div class="box"> | |
24 | <div class="title"> |
|
24 | <div class="title"> | |
25 | ${self.repo_page_title(c.rhodecode_db_repo)} |
|
25 | ${self.repo_page_title(c.rhodecode_db_repo)} | |
26 | </div> |
|
26 | </div> | |
27 | <div class="edit-file-title"> |
|
27 | <div class="edit-file-title"> | |
28 | ${self.breadcrumbs()} |
|
28 | ${self.breadcrumbs()} | |
29 | </div> |
|
29 | </div> | |
30 | ${h.secure_form(h.url.current(),method='post',id='eform',enctype="multipart/form-data", class_="form-horizontal")} |
|
30 | ${h.secure_form(h.route_path('repo_files_create_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', method='POST', enctype="multipart/form-data", class_="form-horizontal")} | |
31 | <div class="edit-file-fieldset"> |
|
31 | <div class="edit-file-fieldset"> | |
32 | <div class="fieldset"> |
|
32 | <div class="fieldset"> | |
33 | <div id="destination-label" class="left-label"> |
|
33 | <div id="destination-label" class="left-label"> | |
34 | ${_('Path')}: |
|
34 | ${_('Path')}: | |
35 | </div> |
|
35 | </div> | |
36 | <div class="right-content"> |
|
36 | <div class="right-content"> | |
37 | <div id="specify-custom-path-container"> |
|
37 | <div id="specify-custom-path-container"> | |
38 | <span id="path-breadcrumbs">${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path)}</span> |
|
38 | <span id="path-breadcrumbs">${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path)}</span> | |
39 | <a class="custom-path-link" id="specify-custom-path" href="#">${_('Specify Custom Path')}</a> |
|
39 | <a class="custom-path-link" id="specify-custom-path" href="#">${_('Specify Custom Path')}</a> | |
40 | </div> |
|
40 | </div> | |
41 | <div id="remove-custom-path-container" style="display: none;"> |
|
41 | <div id="remove-custom-path-container" style="display: none;"> | |
42 | ${c.repo_name}/ |
|
42 | ${c.repo_name}/ | |
43 | <input type="input-small" value="${c.f_path}" size="46" name="location" id="location"> |
|
43 | <input type="input-small" value="${c.f_path}" size="46" name="location" id="location"> | |
44 | <a class="custom-path-link" id="remove-custom-path" href="#">${_('Remove Custom Path')}</a> |
|
44 | <a class="custom-path-link" id="remove-custom-path" href="#">${_('Remove Custom Path')}</a> | |
45 | </div> |
|
45 | </div> | |
46 | </div> |
|
46 | </div> | |
47 | </div> |
|
47 | </div> | |
48 | <div id="filename_container" class="fieldset"> |
|
48 | <div id="filename_container" class="fieldset"> | |
49 | <div class="filename-label left-label"> |
|
49 | <div class="filename-label left-label"> | |
50 | ${_('Filename')}: |
|
50 | ${_('Filename')}: | |
51 | </div> |
|
51 | </div> | |
52 | <div class="right-content"> |
|
52 | <div class="right-content"> | |
53 | <input class="input-small" type="text" value="" size="46" name="filename" id="filename"> |
|
53 | <input class="input-small" type="text" value="" size="46" name="filename" id="filename"> | |
54 | <p>${_('or')} <a id="upload_file_enable" href="#">${_('Upload File')}</a></p> |
|
54 | <p>${_('or')} <a id="upload_file_enable" href="#">${_('Upload File')}</a></p> | |
55 | </div> |
|
55 | </div> | |
56 | </div> |
|
56 | </div> | |
57 | <div id="upload_file_container" class="fieldset" style="display: none;"> |
|
57 | <div id="upload_file_container" class="fieldset" style="display: none;"> | |
58 | <div class="filename-label left-label"> |
|
58 | <div class="filename-label left-label"> | |
59 | ${_('Filename')}: |
|
59 | ${_('Filename')}: | |
60 | </div> |
|
60 | </div> | |
61 | <div class="right-content"> |
|
61 | <div class="right-content"> | |
62 | <input class="input-small" type="text" value="" size="46" name="filename_upload" id="filename_upload" placeholder="${_('No file selected')}"> |
|
62 | <input class="input-small" type="text" value="" size="46" name="filename_upload" id="filename_upload" placeholder="${_('No file selected')}"> | |
63 | </div> |
|
63 | </div> | |
64 | <div class="filename-label left-label file-upload-label"> |
|
64 | <div class="filename-label left-label file-upload-label"> | |
65 | ${_('Upload file')}: |
|
65 | ${_('Upload file')}: | |
66 | </div> |
|
66 | </div> | |
67 | <div class="right-content file-upload-input"> |
|
67 | <div class="right-content file-upload-input"> | |
68 | <label for="upload_file" class="btn btn-default">Browse</label> |
|
68 | <label for="upload_file" class="btn btn-default">Browse</label> | |
69 |
|
69 | |||
70 | <input type="file" name="upload_file" id="upload_file"> |
|
70 | <input type="file" name="upload_file" id="upload_file"> | |
71 | <p>${_('or')} <a id="file_enable" href="#">${_('Create New File')}</a></p> |
|
71 | <p>${_('or')} <a id="file_enable" href="#">${_('Create New File')}</a></p> | |
72 | </div> |
|
72 | </div> | |
73 | </div> |
|
73 | </div> | |
74 | </div> |
|
74 | </div> | |
75 | <div class="table"> |
|
75 | <div class="table"> | |
76 | <div id="files_data"> |
|
76 | <div id="files_data"> | |
77 | <div id="codeblock" class="codeblock"> |
|
77 | <div id="codeblock" class="codeblock"> | |
78 | <div class="code-header form" id="set_mode_header"> |
|
78 | <div class="code-header form" id="set_mode_header"> | |
79 | <div class="fields"> |
|
79 | <div class="fields"> | |
80 | ${h.dropdownmenu('set_mode','plain',[('plain',_('plain'))],enable_filter=True)} |
|
80 | ${h.dropdownmenu('set_mode','plain',[('plain',_('plain'))],enable_filter=True)} | |
81 | <label for="line_wrap">${_('line wraps')}</label> |
|
81 | <label for="line_wrap">${_('line wraps')}</label> | |
82 | ${h.dropdownmenu('line_wrap', 'off', [('on', _('on')), ('off', _('off')),])} |
|
82 | ${h.dropdownmenu('line_wrap', 'off', [('on', _('on')), ('off', _('off')),])} | |
83 |
|
83 | |||
84 | <div id="render_preview" class="btn btn-small preview hidden" >${_('Preview')}</div> |
|
84 | <div id="render_preview" class="btn btn-small preview hidden" >${_('Preview')}</div> | |
85 | </div> |
|
85 | </div> | |
86 | </div> |
|
86 | </div> | |
87 | <div id="editor_container"> |
|
87 | <div id="editor_container"> | |
88 | <pre id="editor_pre"></pre> |
|
88 | <pre id="editor_pre"></pre> | |
89 | <textarea id="editor" name="content" ></textarea> |
|
89 | <textarea id="editor" name="content" ></textarea> | |
90 | <div id="editor_preview"></div> |
|
90 | <div id="editor_preview"></div> | |
91 | </div> |
|
91 | </div> | |
92 | </div> |
|
92 | </div> | |
93 | </div> |
|
93 | </div> | |
94 | </div> |
|
94 | </div> | |
95 |
|
95 | |||
96 | <div class="edit-file-fieldset"> |
|
96 | <div class="edit-file-fieldset"> | |
97 | <div class="fieldset"> |
|
97 | <div class="fieldset"> | |
98 | <div id="commit-message-label" class="commit-message-label left-label"> |
|
98 | <div id="commit-message-label" class="commit-message-label left-label"> | |
99 | ${_('Commit Message')}: |
|
99 | ${_('Commit Message')}: | |
100 | </div> |
|
100 | </div> | |
101 | <div class="right-content"> |
|
101 | <div class="right-content"> | |
102 | <div class="message"> |
|
102 | <div class="message"> | |
103 | <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea> |
|
103 | <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea> | |
104 | </div> |
|
104 | </div> | |
105 | </div> |
|
105 | </div> | |
106 | </div> |
|
106 | </div> | |
107 | <div class="pull-right"> |
|
107 | <div class="pull-right"> | |
108 | ${h.reset('reset',_('Cancel'),class_="btn btn-small")} |
|
108 | ${h.reset('reset',_('Cancel'),class_="btn btn-small")} | |
109 | ${h.submit('commit_btn',_('Commit changes'),class_="btn btn-small btn-success")} |
|
109 | ${h.submit('commit_btn',_('Commit changes'),class_="btn btn-small btn-success")} | |
110 | </div> |
|
110 | </div> | |
111 | </div> |
|
111 | </div> | |
112 | ${h.end_form()} |
|
112 | ${h.end_form()} | |
113 | </div> |
|
113 | </div> | |
114 | <script type="text/javascript"> |
|
114 | <script type="text/javascript"> | |
115 |
|
115 | |||
116 | $('#commit_btn').on('click', function() { |
|
116 | $('#commit_btn').on('click', function() { | |
117 | var button = $(this); |
|
117 | var button = $(this); | |
118 | if (button.hasClass('clicked')) { |
|
118 | if (button.hasClass('clicked')) { | |
119 | button.attr('disabled', true); |
|
119 | button.attr('disabled', true); | |
120 | } else { |
|
120 | } else { | |
121 | button.addClass('clicked'); |
|
121 | button.addClass('clicked'); | |
122 | } |
|
122 | } | |
123 | }); |
|
123 | }); | |
124 |
|
124 | |||
125 | $('#specify-custom-path').on('click', function(e){ |
|
125 | $('#specify-custom-path').on('click', function(e){ | |
126 | e.preventDefault(); |
|
126 | e.preventDefault(); | |
127 | $('#specify-custom-path-container').hide(); |
|
127 | $('#specify-custom-path-container').hide(); | |
128 | $('#remove-custom-path-container').show(); |
|
128 | $('#remove-custom-path-container').show(); | |
129 | $('#destination-label').css('margin-top', '13px'); |
|
129 | $('#destination-label').css('margin-top', '13px'); | |
130 | }); |
|
130 | }); | |
131 |
|
131 | |||
132 | $('#remove-custom-path').on('click', function(e){ |
|
132 | $('#remove-custom-path').on('click', function(e){ | |
133 | e.preventDefault(); |
|
133 | e.preventDefault(); | |
134 | $('#specify-custom-path-container').show(); |
|
134 | $('#specify-custom-path-container').show(); | |
135 | $('#remove-custom-path-container').hide(); |
|
135 | $('#remove-custom-path-container').hide(); | |
136 | $('#location').val('${c.f_path}'); |
|
136 | $('#location').val('${c.f_path}'); | |
137 | $('#destination-label').css('margin-top', '0'); |
|
137 | $('#destination-label').css('margin-top', '0'); | |
138 | }); |
|
138 | }); | |
139 |
|
139 | |||
140 | var hide_upload = function(){ |
|
140 | var hide_upload = function(){ | |
141 | $('#files_data').show(); |
|
141 | $('#files_data').show(); | |
142 | $('#upload_file_container').hide(); |
|
142 | $('#upload_file_container').hide(); | |
143 | $('#filename_container').show(); |
|
143 | $('#filename_container').show(); | |
144 | }; |
|
144 | }; | |
145 |
|
145 | |||
146 | $('#file_enable').on('click', function(e){ |
|
146 | $('#file_enable').on('click', function(e){ | |
147 | e.preventDefault(); |
|
147 | e.preventDefault(); | |
148 | hide_upload(); |
|
148 | hide_upload(); | |
149 | }); |
|
149 | }); | |
150 |
|
150 | |||
151 | $('#upload_file_enable').on('click', function(e){ |
|
151 | $('#upload_file_enable').on('click', function(e){ | |
152 | e.preventDefault(); |
|
152 | e.preventDefault(); | |
153 | $('#files_data').hide(); |
|
153 | $('#files_data').hide(); | |
154 | $('#upload_file_container').show(); |
|
154 | $('#upload_file_container').show(); | |
155 | $('#filename_container').hide(); |
|
155 | $('#filename_container').hide(); | |
156 | if (detectIE() && detectIE() <= 9) { |
|
156 | if (detectIE() && detectIE() <= 9) { | |
157 | $('#upload_file_container .file-upload-input label').hide(); |
|
157 | $('#upload_file_container .file-upload-input label').hide(); | |
158 | $('#upload_file_container .file-upload-input span').hide(); |
|
158 | $('#upload_file_container .file-upload-input span').hide(); | |
159 | $('#upload_file_container .file-upload-input input').show(); |
|
159 | $('#upload_file_container .file-upload-input input').show(); | |
160 | } |
|
160 | } | |
161 | }); |
|
161 | }); | |
162 |
|
162 | |||
163 | $('#upload_file').on('change', function() { |
|
163 | $('#upload_file').on('change', function() { | |
164 | if (this.files && this.files[0]) { |
|
164 | if (this.files && this.files[0]) { | |
165 | $('#filename_upload').val(this.files[0].name); |
|
165 | $('#filename_upload').val(this.files[0].name); | |
166 | } |
|
166 | } | |
167 | }); |
|
167 | }); | |
168 |
|
168 | |||
169 | hide_upload(); |
|
169 | hide_upload(); | |
170 |
|
170 | |||
171 | var renderer = ""; |
|
171 | var renderer = ""; | |
172 |
var reset_url = "${h. |
|
172 | var reset_url = "${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}"; | |
173 | var myCodeMirror = initCodeMirror('editor', reset_url, false); |
|
173 | var myCodeMirror = initCodeMirror('editor', reset_url, false); | |
174 |
|
174 | |||
175 | var modes_select = $('#set_mode'); |
|
175 | var modes_select = $('#set_mode'); | |
176 | fillCodeMirrorOptions(modes_select); |
|
176 | fillCodeMirrorOptions(modes_select); | |
177 |
|
177 | |||
178 | var filename_selector = '#filename'; |
|
178 | var filename_selector = '#filename'; | |
179 | var callback = function(filename, mimetype, mode){ |
|
179 | var callback = function(filename, mimetype, mode){ | |
180 | CodeMirrorPreviewEnable(mode); |
|
180 | CodeMirrorPreviewEnable(mode); | |
181 | }; |
|
181 | }; | |
182 | // on change of select field set mode |
|
182 | // on change of select field set mode | |
183 | setCodeMirrorModeFromSelect( |
|
183 | setCodeMirrorModeFromSelect( | |
184 | modes_select, filename_selector, myCodeMirror, callback); |
|
184 | modes_select, filename_selector, myCodeMirror, callback); | |
185 |
|
185 | |||
186 | // on entering the new filename set mode, from given extension |
|
186 | // on entering the new filename set mode, from given extension | |
187 | setCodeMirrorModeFromInput( |
|
187 | setCodeMirrorModeFromInput( | |
188 | modes_select, filename_selector, myCodeMirror, callback); |
|
188 | modes_select, filename_selector, myCodeMirror, callback); | |
189 |
|
189 | |||
190 | // if the file is renderable set line wraps automatically |
|
190 | // if the file is renderable set line wraps automatically | |
191 | if (renderer !== ""){ |
|
191 | if (renderer !== ""){ | |
192 | var line_wrap = 'on'; |
|
192 | var line_wrap = 'on'; | |
193 | $($('#line_wrap option[value="'+line_wrap+'"]')[0]).attr("selected", "selected"); |
|
193 | $($('#line_wrap option[value="'+line_wrap+'"]')[0]).attr("selected", "selected"); | |
194 | setCodeMirrorLineWrap(myCodeMirror, true); |
|
194 | setCodeMirrorLineWrap(myCodeMirror, true); | |
195 | } |
|
195 | } | |
196 |
|
196 | |||
197 | // on select line wraps change the editor |
|
197 | // on select line wraps change the editor | |
198 | $('#line_wrap').on('change', function(e){ |
|
198 | $('#line_wrap').on('change', function(e){ | |
199 | var selected = e.currentTarget; |
|
199 | var selected = e.currentTarget; | |
200 | var line_wraps = {'on': true, 'off': false}[selected.value]; |
|
200 | var line_wraps = {'on': true, 'off': false}[selected.value]; | |
201 | setCodeMirrorLineWrap(myCodeMirror, line_wraps) |
|
201 | setCodeMirrorLineWrap(myCodeMirror, line_wraps) | |
202 | }); |
|
202 | }); | |
203 |
|
203 | |||
204 | // render preview/edit button |
|
204 | // render preview/edit button | |
205 | $('#render_preview').on('click', function(e){ |
|
205 | $('#render_preview').on('click', function(e){ | |
206 | if($(this).hasClass('preview')){ |
|
206 | if($(this).hasClass('preview')){ | |
207 | $(this).removeClass('preview'); |
|
207 | $(this).removeClass('preview'); | |
208 | $(this).html("${_('Edit')}"); |
|
208 | $(this).html("${_('Edit')}"); | |
209 | $('#editor_preview').show(); |
|
209 | $('#editor_preview').show(); | |
210 | $(myCodeMirror.getWrapperElement()).hide(); |
|
210 | $(myCodeMirror.getWrapperElement()).hide(); | |
211 |
|
211 | |||
212 | var possible_renderer = { |
|
212 | var possible_renderer = { | |
213 | 'rst':'rst', |
|
213 | 'rst':'rst', | |
214 | 'markdown':'markdown', |
|
214 | 'markdown':'markdown', | |
215 | 'gfm': 'markdown'}[myCodeMirror.getMode().name]; |
|
215 | 'gfm': 'markdown'}[myCodeMirror.getMode().name]; | |
216 | var _text = myCodeMirror.getValue(); |
|
216 | var _text = myCodeMirror.getValue(); | |
217 | var _renderer = possible_renderer || DEFAULT_RENDERER; |
|
217 | var _renderer = possible_renderer || DEFAULT_RENDERER; | |
218 | var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN}; |
|
218 | var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN}; | |
219 | $('#editor_preview').html(_gettext('Loading ...')); |
|
219 | $('#editor_preview').html(_gettext('Loading ...')); | |
220 | var url = pyroutes.url('changeset_comment_preview', {'repo_name': '${c.repo_name}'}); |
|
220 | var url = pyroutes.url('changeset_comment_preview', {'repo_name': '${c.repo_name}'}); | |
221 |
|
221 | |||
222 | ajaxPOST(url, post_data, function(o){ |
|
222 | ajaxPOST(url, post_data, function(o){ | |
223 | $('#editor_preview').html(o); |
|
223 | $('#editor_preview').html(o); | |
224 | }) |
|
224 | }) | |
225 | } |
|
225 | } | |
226 | else{ |
|
226 | else{ | |
227 | $(this).addClass('preview'); |
|
227 | $(this).addClass('preview'); | |
228 | $(this).html("${_('Preview')}"); |
|
228 | $(this).html("${_('Preview')}"); | |
229 | $('#editor_preview').hide(); |
|
229 | $('#editor_preview').hide(); | |
230 | $(myCodeMirror.getWrapperElement()).show(); |
|
230 | $(myCodeMirror.getWrapperElement()).show(); | |
231 | } |
|
231 | } | |
232 | }); |
|
232 | }); | |
233 | $('#filename').focus(); |
|
233 | $('#filename').focus(); | |
234 |
|
234 | |||
235 | </script> |
|
235 | </script> | |
236 | </%def> |
|
236 | </%def> |
@@ -1,49 +1,49 b'' | |||||
1 |
|
1 | |||
2 | <div id="codeblock" class="browserblock"> |
|
2 | <div id="codeblock" class="browserblock"> | |
3 | <div class="browser-header"> |
|
3 | <div class="browser-header"> | |
4 | <div class="browser-nav"> |
|
4 | <div class="browser-nav"> | |
5 | ${h.form(h.url.current(), method='GET', id='at_rev_form')} |
|
5 | ${h.form(h.url.current(), method='GET', id='at_rev_form')} | |
6 | <div class="info_box"> |
|
6 | <div class="info_box"> | |
7 | ${h.hidden('refs_filter')} |
|
7 | ${h.hidden('refs_filter')} | |
8 | <div class="info_box_elem previous"> |
|
8 | <div class="info_box_elem previous"> | |
9 | <a id="prev_commit_link" data-commit-id="${c.prev_commit.raw_id}" class="pjax-link ${'disabled' if c.url_prev == '#' else ''}" href="${c.url_prev}" title="${_('Previous commit')}"><i class="icon-chevron-left"></i></a> |
|
9 | <a id="prev_commit_link" data-commit-id="${c.prev_commit.raw_id}" class="pjax-link ${'disabled' if c.url_prev == '#' else ''}" href="${c.url_prev}" title="${_('Previous commit')}"><i class="icon-chevron-left"></i></a> | |
10 | </div> |
|
10 | </div> | |
11 | <div class="info_box_elem">${h.text('at_rev',value=c.commit.revision)}</div> |
|
11 | <div class="info_box_elem">${h.text('at_rev',value=c.commit.revision)}</div> | |
12 | <div class="info_box_elem next"> |
|
12 | <div class="info_box_elem next"> | |
13 | <a id="next_commit_link" data-commit-id="${c.next_commit.raw_id}" class="pjax-link ${'disabled' if c.url_next == '#' else ''}" href="${c.url_next}" title="${_('Next commit')}"><i class="icon-chevron-right"></i></a> |
|
13 | <a id="next_commit_link" data-commit-id="${c.next_commit.raw_id}" class="pjax-link ${'disabled' if c.url_next == '#' else ''}" href="${c.url_next}" title="${_('Next commit')}"><i class="icon-chevron-right"></i></a> | |
14 | </div> |
|
14 | </div> | |
15 | </div> |
|
15 | </div> | |
16 | ${h.end_form()} |
|
16 | ${h.end_form()} | |
17 |
|
17 | |||
18 | <div id="search_activate_id" class="search_activate"> |
|
18 | <div id="search_activate_id" class="search_activate"> | |
19 | <a class="btn btn-default" id="filter_activate" href="javascript:void(0)">${_('Search File List')}</a> |
|
19 | <a class="btn btn-default" id="filter_activate" href="javascript:void(0)">${_('Search File List')}</a> | |
20 | </div> |
|
20 | </div> | |
21 | <div id="search_deactivate_id" class="search_activate hidden"> |
|
21 | <div id="search_deactivate_id" class="search_activate hidden"> | |
22 | <a class="btn btn-default" id="filter_deactivate" href="javascript:void(0)">${_('Close File List')}</a> |
|
22 | <a class="btn btn-default" id="filter_deactivate" href="javascript:void(0)">${_('Close File List')}</a> | |
23 | </div> |
|
23 | </div> | |
24 | % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name): |
|
24 | % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name): | |
25 | <div title="${_('Add New File')}" class="btn btn-primary new-file"> |
|
25 | <div title="${_('Add New File')}" class="btn btn-primary new-file"> | |
26 |
<a href="${h. |
|
26 | <a href="${h.route_path('repo_files_add_file',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path, _anchor='edit')}"> | |
27 | ${_('Add File')}</a> |
|
27 | ${_('Add File')}</a> | |
28 | </div> |
|
28 | </div> | |
29 | % endif |
|
29 | % endif | |
30 | </div> |
|
30 | </div> | |
31 |
|
31 | |||
32 | <div class="browser-search"> |
|
32 | <div class="browser-search"> | |
33 | <div class="node-filter"> |
|
33 | <div class="node-filter"> | |
34 | <div class="node_filter_box hidden" id="node_filter_box_loading" >${_('Loading file list...')}</div> |
|
34 | <div class="node_filter_box hidden" id="node_filter_box_loading" >${_('Loading file list...')}</div> | |
35 | <div class="node_filter_box hidden" id="node_filter_box" > |
|
35 | <div class="node_filter_box hidden" id="node_filter_box" > | |
36 | <div class="node-filter-path">${h.get_last_path_part(c.file)}/</div> |
|
36 | <div class="node-filter-path">${h.get_last_path_part(c.file)}/</div> | |
37 | <div class="node-filter-input"> |
|
37 | <div class="node-filter-input"> | |
38 | <input class="init" type="text" name="filter" size="25" id="node_filter" autocomplete="off"> |
|
38 | <input class="init" type="text" name="filter" size="25" id="node_filter" autocomplete="off"> | |
39 | </div> |
|
39 | </div> | |
40 | </div> |
|
40 | </div> | |
41 | </div> |
|
41 | </div> | |
42 | </div> |
|
42 | </div> | |
43 | </div> |
|
43 | </div> | |
44 | ## file tree is computed from caches, and filled in |
|
44 | ## file tree is computed from caches, and filled in | |
45 | <div id="file-tree"> |
|
45 | <div id="file-tree"> | |
46 | ${c.file_tree} |
|
46 | ${c.file_tree |n} | |
47 | </div> |
|
47 | </div> | |
48 |
|
48 | |||
49 | </div> |
|
49 | </div> |
@@ -1,82 +1,82 b'' | |||||
1 | <div id="file-tree-wrapper" class="browser-body ${'full-load' if c.full_load else ''}"> |
|
1 | <div id="file-tree-wrapper" class="browser-body ${'full-load' if c.full_load else ''}"> | |
2 | <table class="code-browser rctable"> |
|
2 | <table class="code-browser rctable"> | |
3 | <thead> |
|
3 | <thead> | |
4 | <tr> |
|
4 | <tr> | |
5 | <th>${_('Name')}</th> |
|
5 | <th>${_('Name')}</th> | |
6 | <th>${_('Size')}</th> |
|
6 | <th>${_('Size')}</th> | |
7 | <th>${_('Modified')}</th> |
|
7 | <th>${_('Modified')}</th> | |
8 | <th>${_('Last Commit')}</th> |
|
8 | <th>${_('Last Commit')}</th> | |
9 | <th>${_('Author')}</th> |
|
9 | <th>${_('Author')}</th> | |
10 | </tr> |
|
10 | </tr> | |
11 | </thead> |
|
11 | </thead> | |
12 |
|
12 | |||
13 | <tbody id="tbody"> |
|
13 | <tbody id="tbody"> | |
14 | %if c.file.parent: |
|
14 | %if c.file.parent: | |
15 | <tr class="parity0"> |
|
15 | <tr class="parity0"> | |
16 | <td class="td-componentname"> |
|
16 | <td class="td-componentname"> | |
17 |
<a href="${h. |
|
17 | <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.file.parent.path)}" class="pjax-link"> | |
18 | <i class="icon-folder"></i>.. |
|
18 | <i class="icon-folder"></i>.. | |
19 | </a> |
|
19 | </a> | |
20 | </td> |
|
20 | </td> | |
21 | <td></td> |
|
21 | <td></td> | |
22 | <td></td> |
|
22 | <td></td> | |
23 | <td></td> |
|
23 | <td></td> | |
24 | <td></td> |
|
24 | <td></td> | |
25 | </tr> |
|
25 | </tr> | |
26 | %endif |
|
26 | %endif | |
27 | %for cnt,node in enumerate(c.file): |
|
27 | %for cnt,node in enumerate(c.file): | |
28 | <tr class="parity${cnt%2}"> |
|
28 | <tr class="parity${cnt%2}"> | |
29 | <td class="td-componentname"> |
|
29 | <td class="td-componentname"> | |
30 | % if node.is_submodule(): |
|
30 | % if node.is_submodule(): | |
31 | <span class="submodule-dir"> |
|
31 | <span class="submodule-dir"> | |
32 | % if node.url.startswith('http://') or node.url.startswith('https://'): |
|
32 | % if node.url.startswith('http://') or node.url.startswith('https://'): | |
33 | <a href="${node.url}"> |
|
33 | <a href="${node.url}"> | |
34 | <i class="icon-folder browser-dir"></i>${node.name} |
|
34 | <i class="icon-folder browser-dir"></i>${node.name} | |
35 | </a> |
|
35 | </a> | |
36 | % else: |
|
36 | % else: | |
37 | <i class="icon-folder browser-dir"></i>${node.name} |
|
37 | <i class="icon-folder browser-dir"></i>${node.name} | |
38 | % endif |
|
38 | % endif | |
39 | </span> |
|
39 | </span> | |
40 | % else: |
|
40 | % else: | |
41 |
<a href="${h. |
|
41 | <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=h.safe_unicode(node.path))}" class="pjax-link"> | |
42 | <i class="${'icon-file browser-file' if node.is_file() else 'icon-folder browser-dir'}"></i>${node.name} |
|
42 | <i class="${'icon-file browser-file' if node.is_file() else 'icon-folder browser-dir'}"></i>${node.name} | |
43 | </a> |
|
43 | </a> | |
44 | % endif |
|
44 | % endif | |
45 | </td> |
|
45 | </td> | |
46 | %if node.is_file(): |
|
46 | %if node.is_file(): | |
47 | <td class="td-size" data-attr-name="size"> |
|
47 | <td class="td-size" data-attr-name="size"> | |
48 | % if c.full_load: |
|
48 | % if c.full_load: | |
49 | <span data-size="${node.size}">${h.format_byte_size_binary(node.size)}</span> |
|
49 | <span data-size="${node.size}">${h.format_byte_size_binary(node.size)}</span> | |
50 | % else: |
|
50 | % else: | |
51 | ${_('Loading ...')} |
|
51 | ${_('Loading ...')} | |
52 | % endif |
|
52 | % endif | |
53 | </td> |
|
53 | </td> | |
54 | <td class="td-time" data-attr-name="modified_at"> |
|
54 | <td class="td-time" data-attr-name="modified_at"> | |
55 | % if c.full_load: |
|
55 | % if c.full_load: | |
56 | <span data-date="${node.last_commit.date}">${h.age_component(node.last_commit.date)}</span> |
|
56 | <span data-date="${node.last_commit.date}">${h.age_component(node.last_commit.date)}</span> | |
57 | % endif |
|
57 | % endif | |
58 | </td> |
|
58 | </td> | |
59 | <td class="td-hash" data-attr-name="commit_id"> |
|
59 | <td class="td-hash" data-attr-name="commit_id"> | |
60 | % if c.full_load: |
|
60 | % if c.full_load: | |
61 | <div class="tooltip" title="${h.tooltip(node.last_commit.message)}"> |
|
61 | <div class="tooltip" title="${h.tooltip(node.last_commit.message)}"> | |
62 | <pre data-commit-id="${node.last_commit.raw_id}">r${node.last_commit.revision}:${node.last_commit.short_id}</pre> |
|
62 | <pre data-commit-id="${node.last_commit.raw_id}">r${node.last_commit.revision}:${node.last_commit.short_id}</pre> | |
63 | </div> |
|
63 | </div> | |
64 | % endif |
|
64 | % endif | |
65 | </td> |
|
65 | </td> | |
66 | <td class="td-user" data-attr-name="author"> |
|
66 | <td class="td-user" data-attr-name="author"> | |
67 | % if c.full_load: |
|
67 | % if c.full_load: | |
68 | <span data-author="${node.last_commit.author}" title="${h.tooltip(node.last_commit.author)}">${h.gravatar_with_user(node.last_commit.author)|n}</span> |
|
68 | <span data-author="${node.last_commit.author}" title="${h.tooltip(node.last_commit.author)}">${h.gravatar_with_user(node.last_commit.author)|n}</span> | |
69 | % endif |
|
69 | % endif | |
70 | </td> |
|
70 | </td> | |
71 | %else: |
|
71 | %else: | |
72 | <td></td> |
|
72 | <td></td> | |
73 | <td></td> |
|
73 | <td></td> | |
74 | <td></td> |
|
74 | <td></td> | |
75 | <td></td> |
|
75 | <td></td> | |
76 | %endif |
|
76 | %endif | |
77 | </tr> |
|
77 | </tr> | |
78 | %endfor |
|
78 | %endfor | |
79 | </tbody> |
|
79 | </tbody> | |
80 | <tbody id="tbody_filtered"></tbody> |
|
80 | <tbody id="tbody_filtered"></tbody> | |
81 | </table> |
|
81 | </table> | |
82 | </div> |
|
82 | </div> |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
General Comments 0
You need to be logged in to leave comments.
Login now