##// END OF EJS Templates
files: fixed the repo switcher at flag beeing nor persistens...
dan -
r4294:d4e644bf default
parent child Browse files
Show More
@@ -1,1555 +1,1568 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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 itertools
21 import itertools
22 import logging
22 import logging
23 import os
23 import os
24 import shutil
24 import shutil
25 import tempfile
25 import tempfile
26 import collections
26 import collections
27 import urllib
27 import urllib
28 import pathlib2
28 import pathlib2
29
29
30 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
30 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
31 from pyramid.view import view_config
31 from pyramid.view import view_config
32 from pyramid.renderers import render
32 from pyramid.renderers import render
33 from pyramid.response import Response
33 from pyramid.response import Response
34
34
35 import rhodecode
35 import rhodecode
36 from rhodecode.apps._base import RepoAppView
36 from rhodecode.apps._base import RepoAppView
37
37
38
38
39 from rhodecode.lib import diffs, helpers as h, rc_cache
39 from rhodecode.lib import diffs, helpers as h, rc_cache
40 from rhodecode.lib import audit_logger
40 from rhodecode.lib import audit_logger
41 from rhodecode.lib.view_utils import parse_path_ref
41 from rhodecode.lib.view_utils import parse_path_ref
42 from rhodecode.lib.exceptions import NonRelativePathError
42 from rhodecode.lib.exceptions import NonRelativePathError
43 from rhodecode.lib.codeblocks import (
43 from rhodecode.lib.codeblocks import (
44 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
44 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
45 from rhodecode.lib.utils2 import (
45 from rhodecode.lib.utils2 import (
46 convert_line_endings, detect_mode, safe_str, str2bool, safe_int, sha1, safe_unicode)
46 convert_line_endings, detect_mode, safe_str, str2bool, safe_int, sha1, safe_unicode)
47 from rhodecode.lib.auth import (
47 from rhodecode.lib.auth import (
48 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
48 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
49 from rhodecode.lib.vcs import path as vcspath
49 from rhodecode.lib.vcs import path as vcspath
50 from rhodecode.lib.vcs.backends.base import EmptyCommit
50 from rhodecode.lib.vcs.backends.base import EmptyCommit
51 from rhodecode.lib.vcs.conf import settings
51 from rhodecode.lib.vcs.conf import settings
52 from rhodecode.lib.vcs.nodes import FileNode
52 from rhodecode.lib.vcs.nodes import FileNode
53 from rhodecode.lib.vcs.exceptions import (
53 from rhodecode.lib.vcs.exceptions import (
54 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
54 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
55 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
55 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
56 NodeDoesNotExistError, CommitError, NodeError)
56 NodeDoesNotExistError, CommitError, NodeError)
57
57
58 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.scm import ScmModel
59 from rhodecode.model.db import Repository
59 from rhodecode.model.db import Repository
60
60
61 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
62
62
63
63
64 class RepoFilesView(RepoAppView):
64 class RepoFilesView(RepoAppView):
65
65
66 @staticmethod
66 @staticmethod
67 def adjust_file_path_for_svn(f_path, repo):
67 def adjust_file_path_for_svn(f_path, repo):
68 """
68 """
69 Computes the relative path of `f_path`.
69 Computes the relative path of `f_path`.
70
70
71 This is mainly based on prefix matching of the recognized tags and
71 This is mainly based on prefix matching of the recognized tags and
72 branches in the underlying repository.
72 branches in the underlying repository.
73 """
73 """
74 tags_and_branches = itertools.chain(
74 tags_and_branches = itertools.chain(
75 repo.branches.iterkeys(),
75 repo.branches.iterkeys(),
76 repo.tags.iterkeys())
76 repo.tags.iterkeys())
77 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
77 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
78
78
79 for name in tags_and_branches:
79 for name in tags_and_branches:
80 if f_path.startswith('{}/'.format(name)):
80 if f_path.startswith('{}/'.format(name)):
81 f_path = vcspath.relpath(f_path, name)
81 f_path = vcspath.relpath(f_path, name)
82 break
82 break
83 return f_path
83 return f_path
84
84
85 def load_default_context(self):
85 def load_default_context(self):
86 c = self._get_local_tmpl_context(include_app_defaults=True)
86 c = self._get_local_tmpl_context(include_app_defaults=True)
87 c.rhodecode_repo = self.rhodecode_vcs_repo
87 c.rhodecode_repo = self.rhodecode_vcs_repo
88 c.enable_downloads = self.db_repo.enable_downloads
88 c.enable_downloads = self.db_repo.enable_downloads
89 return c
89 return c
90
90
91 def _ensure_not_locked(self, commit_id='tip'):
91 def _ensure_not_locked(self, commit_id='tip'):
92 _ = self.request.translate
92 _ = self.request.translate
93
93
94 repo = self.db_repo
94 repo = self.db_repo
95 if repo.enable_locking and repo.locked[0]:
95 if repo.enable_locking and repo.locked[0]:
96 h.flash(_('This repository has been locked by %s on %s')
96 h.flash(_('This repository has been locked by %s on %s')
97 % (h.person_by_id(repo.locked[0]),
97 % (h.person_by_id(repo.locked[0]),
98 h.format_date(h.time_to_datetime(repo.locked[1]))),
98 h.format_date(h.time_to_datetime(repo.locked[1]))),
99 'warning')
99 'warning')
100 files_url = h.route_path(
100 files_url = h.route_path(
101 'repo_files:default_path',
101 'repo_files:default_path',
102 repo_name=self.db_repo_name, commit_id=commit_id)
102 repo_name=self.db_repo_name, commit_id=commit_id)
103 raise HTTPFound(files_url)
103 raise HTTPFound(files_url)
104
104
105 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
105 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
106 _ = self.request.translate
106 _ = self.request.translate
107
107
108 if not is_head:
108 if not is_head:
109 message = _('Cannot modify file. '
109 message = _('Cannot modify file. '
110 'Given commit `{}` is not head of a branch.').format(commit_id)
110 'Given commit `{}` is not head of a branch.').format(commit_id)
111 h.flash(message, category='warning')
111 h.flash(message, category='warning')
112
112
113 if json_mode:
113 if json_mode:
114 return message
114 return message
115
115
116 files_url = h.route_path(
116 files_url = h.route_path(
117 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
117 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
118 f_path=f_path)
118 f_path=f_path)
119 raise HTTPFound(files_url)
119 raise HTTPFound(files_url)
120
120
121 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
121 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
122 _ = self.request.translate
122 _ = self.request.translate
123
123
124 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
124 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
125 self.db_repo_name, branch_name)
125 self.db_repo_name, branch_name)
126 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
126 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
127 message = _('Branch `{}` changes forbidden by rule {}.').format(
127 message = _('Branch `{}` changes forbidden by rule {}.').format(
128 branch_name, rule)
128 branch_name, rule)
129 h.flash(message, 'warning')
129 h.flash(message, 'warning')
130
130
131 if json_mode:
131 if json_mode:
132 return message
132 return message
133
133
134 files_url = h.route_path(
134 files_url = h.route_path(
135 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
135 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
136
136
137 raise HTTPFound(files_url)
137 raise HTTPFound(files_url)
138
138
139 def _get_commit_and_path(self):
139 def _get_commit_and_path(self):
140 default_commit_id = self.db_repo.landing_rev[1]
140 default_commit_id = self.db_repo.landing_rev[1]
141 default_f_path = '/'
141 default_f_path = '/'
142
142
143 commit_id = self.request.matchdict.get(
143 commit_id = self.request.matchdict.get(
144 'commit_id', default_commit_id)
144 'commit_id', default_commit_id)
145 f_path = self._get_f_path(self.request.matchdict, default_f_path)
145 f_path = self._get_f_path(self.request.matchdict, default_f_path)
146 return commit_id, f_path
146 return commit_id, f_path
147
147
148 def _get_default_encoding(self, c):
148 def _get_default_encoding(self, c):
149 enc_list = getattr(c, 'default_encodings', [])
149 enc_list = getattr(c, 'default_encodings', [])
150 return enc_list[0] if enc_list else 'UTF-8'
150 return enc_list[0] if enc_list else 'UTF-8'
151
151
152 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
152 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
153 """
153 """
154 This is a safe way to get commit. If an error occurs it redirects to
154 This is a safe way to get commit. If an error occurs it redirects to
155 tip with proper message
155 tip with proper message
156
156
157 :param commit_id: id of commit to fetch
157 :param commit_id: id of commit to fetch
158 :param redirect_after: toggle redirection
158 :param redirect_after: toggle redirection
159 """
159 """
160 _ = self.request.translate
160 _ = self.request.translate
161
161
162 try:
162 try:
163 return self.rhodecode_vcs_repo.get_commit(commit_id)
163 return self.rhodecode_vcs_repo.get_commit(commit_id)
164 except EmptyRepositoryError:
164 except EmptyRepositoryError:
165 if not redirect_after:
165 if not redirect_after:
166 return None
166 return None
167
167
168 _url = h.route_path(
168 _url = h.route_path(
169 'repo_files_add_file',
169 'repo_files_add_file',
170 repo_name=self.db_repo_name, commit_id=0, f_path='')
170 repo_name=self.db_repo_name, commit_id=0, f_path='')
171
171
172 if h.HasRepoPermissionAny(
172 if h.HasRepoPermissionAny(
173 'repository.write', 'repository.admin')(self.db_repo_name):
173 'repository.write', 'repository.admin')(self.db_repo_name):
174 add_new = h.link_to(
174 add_new = h.link_to(
175 _('Click here to add a new file.'), _url, class_="alert-link")
175 _('Click here to add a new file.'), _url, class_="alert-link")
176 else:
176 else:
177 add_new = ""
177 add_new = ""
178
178
179 h.flash(h.literal(
179 h.flash(h.literal(
180 _('There are no files yet. %s') % add_new), category='warning')
180 _('There are no files yet. %s') % add_new), category='warning')
181 raise HTTPFound(
181 raise HTTPFound(
182 h.route_path('repo_summary', repo_name=self.db_repo_name))
182 h.route_path('repo_summary', repo_name=self.db_repo_name))
183
183
184 except (CommitDoesNotExistError, LookupError):
184 except (CommitDoesNotExistError, LookupError):
185 msg = _('No such commit exists for this repository')
185 msg = _('No such commit exists for this repository')
186 h.flash(msg, category='error')
186 h.flash(msg, category='error')
187 raise HTTPNotFound()
187 raise HTTPNotFound()
188 except RepositoryError as e:
188 except RepositoryError as e:
189 h.flash(safe_str(h.escape(e)), category='error')
189 h.flash(safe_str(h.escape(e)), category='error')
190 raise HTTPNotFound()
190 raise HTTPNotFound()
191
191
192 def _get_filenode_or_redirect(self, commit_obj, path):
192 def _get_filenode_or_redirect(self, commit_obj, path):
193 """
193 """
194 Returns file_node, if error occurs or given path is directory,
194 Returns file_node, if error occurs or given path is directory,
195 it'll redirect to top level path
195 it'll redirect to top level path
196 """
196 """
197 _ = self.request.translate
197 _ = self.request.translate
198
198
199 try:
199 try:
200 file_node = commit_obj.get_node(path)
200 file_node = commit_obj.get_node(path)
201 if file_node.is_dir():
201 if file_node.is_dir():
202 raise RepositoryError('The given path is a directory')
202 raise RepositoryError('The given path is a directory')
203 except CommitDoesNotExistError:
203 except CommitDoesNotExistError:
204 log.exception('No such commit exists for this repository')
204 log.exception('No such commit exists for this repository')
205 h.flash(_('No such commit exists for this repository'), category='error')
205 h.flash(_('No such commit exists for this repository'), category='error')
206 raise HTTPNotFound()
206 raise HTTPNotFound()
207 except RepositoryError as e:
207 except RepositoryError as e:
208 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
208 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
209 h.flash(safe_str(h.escape(e)), category='error')
209 h.flash(safe_str(h.escape(e)), category='error')
210 raise HTTPNotFound()
210 raise HTTPNotFound()
211
211
212 return file_node
212 return file_node
213
213
214 def _is_valid_head(self, commit_id, repo):
214 def _is_valid_head(self, commit_id, repo):
215 branch_name = sha_commit_id = ''
215 branch_name = sha_commit_id = ''
216 is_head = False
216 is_head = False
217 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
217 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
218
218
219 for _branch_name, branch_commit_id in repo.branches.items():
219 for _branch_name, branch_commit_id in repo.branches.items():
220 # simple case we pass in branch name, it's a HEAD
220 # simple case we pass in branch name, it's a HEAD
221 if commit_id == _branch_name:
221 if commit_id == _branch_name:
222 is_head = True
222 is_head = True
223 branch_name = _branch_name
223 branch_name = _branch_name
224 sha_commit_id = branch_commit_id
224 sha_commit_id = branch_commit_id
225 break
225 break
226 # case when we pass in full sha commit_id, which is a head
226 # case when we pass in full sha commit_id, which is a head
227 elif commit_id == branch_commit_id:
227 elif commit_id == branch_commit_id:
228 is_head = True
228 is_head = True
229 branch_name = _branch_name
229 branch_name = _branch_name
230 sha_commit_id = branch_commit_id
230 sha_commit_id = branch_commit_id
231 break
231 break
232
232
233 if h.is_svn(repo) and not repo.is_empty():
233 if h.is_svn(repo) and not repo.is_empty():
234 # Note: Subversion only has one head.
234 # Note: Subversion only has one head.
235 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
235 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
236 is_head = True
236 is_head = True
237 return branch_name, sha_commit_id, is_head
237 return branch_name, sha_commit_id, is_head
238
238
239 # checked branches, means we only need to try to get the branch/commit_sha
239 # checked branches, means we only need to try to get the branch/commit_sha
240 if not repo.is_empty():
240 if not repo.is_empty():
241 commit = repo.get_commit(commit_id=commit_id)
241 commit = repo.get_commit(commit_id=commit_id)
242 if commit:
242 if commit:
243 branch_name = commit.branch
243 branch_name = commit.branch
244 sha_commit_id = commit.raw_id
244 sha_commit_id = commit.raw_id
245
245
246 return branch_name, sha_commit_id, is_head
246 return branch_name, sha_commit_id, is_head
247
247
248 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False):
248 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False, at_rev=None):
249
249
250 repo_id = self.db_repo.repo_id
250 repo_id = self.db_repo.repo_id
251 force_recache = self.get_recache_flag()
251 force_recache = self.get_recache_flag()
252
252
253 cache_seconds = safe_int(
253 cache_seconds = safe_int(
254 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
254 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
255 cache_on = not force_recache and cache_seconds > 0
255 cache_on = not force_recache and cache_seconds > 0
256 log.debug(
256 log.debug(
257 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
257 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
258 'with caching: %s[TTL: %ss]' % (
258 'with caching: %s[TTL: %ss]' % (
259 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
259 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
260
260
261 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
261 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
262 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
262 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
263
263
264 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
264 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
265 condition=cache_on)
265 condition=cache_on)
266 def compute_file_tree(ver, repo_id, commit_id, f_path, full_load):
266 def compute_file_tree(ver, repo_id, commit_id, f_path, full_load, at_rev):
267 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
267 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
268 ver, repo_id, commit_id, f_path)
268 ver, repo_id, commit_id, f_path)
269
269
270 c.full_load = full_load
270 c.full_load = full_load
271 return render(
271 return render(
272 'rhodecode:templates/files/files_browser_tree.mako',
272 'rhodecode:templates/files/files_browser_tree.mako',
273 self._get_template_context(c), self.request)
273 self._get_template_context(c), self.request, at_rev)
274
274
275 return compute_file_tree(
275 return compute_file_tree(
276 rc_cache.FILE_TREE_CACHE_VER, self.db_repo.repo_id, commit_id, f_path, full_load)
276 rc_cache.FILE_TREE_CACHE_VER, self.db_repo.repo_id, commit_id,
277 f_path, full_load, at_rev)
277
278
278 def _get_archive_spec(self, fname):
279 def _get_archive_spec(self, fname):
279 log.debug('Detecting archive spec for: `%s`', fname)
280 log.debug('Detecting archive spec for: `%s`', fname)
280
281
281 fileformat = None
282 fileformat = None
282 ext = None
283 ext = None
283 content_type = None
284 content_type = None
284 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
285 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
285
286
286 if fname.endswith(extension):
287 if fname.endswith(extension):
287 fileformat = a_type
288 fileformat = a_type
288 log.debug('archive is of type: %s', fileformat)
289 log.debug('archive is of type: %s', fileformat)
289 ext = extension
290 ext = extension
290 break
291 break
291
292
292 if not fileformat:
293 if not fileformat:
293 raise ValueError()
294 raise ValueError()
294
295
295 # left over part of whole fname is the commit
296 # left over part of whole fname is the commit
296 commit_id = fname[:-len(ext)]
297 commit_id = fname[:-len(ext)]
297
298
298 return commit_id, ext, fileformat, content_type
299 return commit_id, ext, fileformat, content_type
299
300
300 def create_pure_path(self, *parts):
301 def create_pure_path(self, *parts):
301 # Split paths and sanitize them, removing any ../ etc
302 # Split paths and sanitize them, removing any ../ etc
302 sanitized_path = [
303 sanitized_path = [
303 x for x in pathlib2.PurePath(*parts).parts
304 x for x in pathlib2.PurePath(*parts).parts
304 if x not in ['.', '..']]
305 if x not in ['.', '..']]
305
306
306 pure_path = pathlib2.PurePath(*sanitized_path)
307 pure_path = pathlib2.PurePath(*sanitized_path)
307 return pure_path
308 return pure_path
308
309
309 def _is_lf_enabled(self, target_repo):
310 def _is_lf_enabled(self, target_repo):
310 lf_enabled = False
311 lf_enabled = False
311
312
312 lf_key_for_vcs_map = {
313 lf_key_for_vcs_map = {
313 'hg': 'extensions_largefiles',
314 'hg': 'extensions_largefiles',
314 'git': 'vcs_git_lfs_enabled'
315 'git': 'vcs_git_lfs_enabled'
315 }
316 }
316
317
317 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
318 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
318
319
319 if lf_key_for_vcs:
320 if lf_key_for_vcs:
320 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
321 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
321
322
322 return lf_enabled
323 return lf_enabled
323
324
324 @LoginRequired()
325 @LoginRequired()
325 @HasRepoPermissionAnyDecorator(
326 @HasRepoPermissionAnyDecorator(
326 'repository.read', 'repository.write', 'repository.admin')
327 'repository.read', 'repository.write', 'repository.admin')
327 @view_config(
328 @view_config(
328 route_name='repo_archivefile', request_method='GET',
329 route_name='repo_archivefile', request_method='GET',
329 renderer=None)
330 renderer=None)
330 def repo_archivefile(self):
331 def repo_archivefile(self):
331 # archive cache config
332 # archive cache config
332 from rhodecode import CONFIG
333 from rhodecode import CONFIG
333 _ = self.request.translate
334 _ = self.request.translate
334 self.load_default_context()
335 self.load_default_context()
335 default_at_path = '/'
336 default_at_path = '/'
336 fname = self.request.matchdict['fname']
337 fname = self.request.matchdict['fname']
337 subrepos = self.request.GET.get('subrepos') == 'true'
338 subrepos = self.request.GET.get('subrepos') == 'true'
338 at_path = self.request.GET.get('at_path') or default_at_path
339 at_path = self.request.GET.get('at_path') or default_at_path
339
340
340 if not self.db_repo.enable_downloads:
341 if not self.db_repo.enable_downloads:
341 return Response(_('Downloads disabled'))
342 return Response(_('Downloads disabled'))
342
343
343 try:
344 try:
344 commit_id, ext, fileformat, content_type = \
345 commit_id, ext, fileformat, content_type = \
345 self._get_archive_spec(fname)
346 self._get_archive_spec(fname)
346 except ValueError:
347 except ValueError:
347 return Response(_('Unknown archive type for: `{}`').format(
348 return Response(_('Unknown archive type for: `{}`').format(
348 h.escape(fname)))
349 h.escape(fname)))
349
350
350 try:
351 try:
351 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
352 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
352 except CommitDoesNotExistError:
353 except CommitDoesNotExistError:
353 return Response(_('Unknown commit_id {}').format(
354 return Response(_('Unknown commit_id {}').format(
354 h.escape(commit_id)))
355 h.escape(commit_id)))
355 except EmptyRepositoryError:
356 except EmptyRepositoryError:
356 return Response(_('Empty repository'))
357 return Response(_('Empty repository'))
357
358
358 try:
359 try:
359 at_path = commit.get_node(at_path).path or default_at_path
360 at_path = commit.get_node(at_path).path or default_at_path
360 except Exception:
361 except Exception:
361 return Response(_('No node at path {} for this repository').format(at_path))
362 return Response(_('No node at path {} for this repository').format(at_path))
362
363
363 path_sha = sha1(at_path)[:8]
364 path_sha = sha1(at_path)[:8]
364
365
365 # original backward compat name of archive
366 # original backward compat name of archive
366 clean_name = safe_str(self.db_repo_name.replace('/', '_'))
367 clean_name = safe_str(self.db_repo_name.replace('/', '_'))
367 short_sha = safe_str(commit.short_id)
368 short_sha = safe_str(commit.short_id)
368
369
369 if at_path == default_at_path:
370 if at_path == default_at_path:
370 archive_name = '{}-{}{}{}'.format(
371 archive_name = '{}-{}{}{}'.format(
371 clean_name,
372 clean_name,
372 '-sub' if subrepos else '',
373 '-sub' if subrepos else '',
373 short_sha,
374 short_sha,
374 ext)
375 ext)
375 # custom path and new name
376 # custom path and new name
376 else:
377 else:
377 archive_name = '{}-{}{}-{}{}'.format(
378 archive_name = '{}-{}{}-{}{}'.format(
378 clean_name,
379 clean_name,
379 '-sub' if subrepos else '',
380 '-sub' if subrepos else '',
380 short_sha,
381 short_sha,
381 path_sha,
382 path_sha,
382 ext)
383 ext)
383
384
384 use_cached_archive = False
385 use_cached_archive = False
385 archive_cache_enabled = CONFIG.get(
386 archive_cache_enabled = CONFIG.get(
386 'archive_cache_dir') and not self.request.GET.get('no_cache')
387 'archive_cache_dir') and not self.request.GET.get('no_cache')
387 cached_archive_path = None
388 cached_archive_path = None
388
389
389 if archive_cache_enabled:
390 if archive_cache_enabled:
390 # check if we it's ok to write
391 # check if we it's ok to write
391 if not os.path.isdir(CONFIG['archive_cache_dir']):
392 if not os.path.isdir(CONFIG['archive_cache_dir']):
392 os.makedirs(CONFIG['archive_cache_dir'])
393 os.makedirs(CONFIG['archive_cache_dir'])
393 cached_archive_path = os.path.join(
394 cached_archive_path = os.path.join(
394 CONFIG['archive_cache_dir'], archive_name)
395 CONFIG['archive_cache_dir'], archive_name)
395 if os.path.isfile(cached_archive_path):
396 if os.path.isfile(cached_archive_path):
396 log.debug('Found cached archive in %s', cached_archive_path)
397 log.debug('Found cached archive in %s', cached_archive_path)
397 fd, archive = None, cached_archive_path
398 fd, archive = None, cached_archive_path
398 use_cached_archive = True
399 use_cached_archive = True
399 else:
400 else:
400 log.debug('Archive %s is not yet cached', archive_name)
401 log.debug('Archive %s is not yet cached', archive_name)
401
402
402 if not use_cached_archive:
403 if not use_cached_archive:
403 # generate new archive
404 # generate new archive
404 fd, archive = tempfile.mkstemp()
405 fd, archive = tempfile.mkstemp()
405 log.debug('Creating new temp archive in %s', archive)
406 log.debug('Creating new temp archive in %s', archive)
406 try:
407 try:
407 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos,
408 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos,
408 archive_at_path=at_path)
409 archive_at_path=at_path)
409 except ImproperArchiveTypeError:
410 except ImproperArchiveTypeError:
410 return _('Unknown archive type')
411 return _('Unknown archive type')
411 if archive_cache_enabled:
412 if archive_cache_enabled:
412 # if we generated the archive and we have cache enabled
413 # if we generated the archive and we have cache enabled
413 # let's use this for future
414 # let's use this for future
414 log.debug('Storing new archive in %s', cached_archive_path)
415 log.debug('Storing new archive in %s', cached_archive_path)
415 shutil.move(archive, cached_archive_path)
416 shutil.move(archive, cached_archive_path)
416 archive = cached_archive_path
417 archive = cached_archive_path
417
418
418 # store download action
419 # store download action
419 audit_logger.store_web(
420 audit_logger.store_web(
420 'repo.archive.download', action_data={
421 'repo.archive.download', action_data={
421 'user_agent': self.request.user_agent,
422 'user_agent': self.request.user_agent,
422 'archive_name': archive_name,
423 'archive_name': archive_name,
423 'archive_spec': fname,
424 'archive_spec': fname,
424 'archive_cached': use_cached_archive},
425 'archive_cached': use_cached_archive},
425 user=self._rhodecode_user,
426 user=self._rhodecode_user,
426 repo=self.db_repo,
427 repo=self.db_repo,
427 commit=True
428 commit=True
428 )
429 )
429
430
430 def get_chunked_archive(archive_path):
431 def get_chunked_archive(archive_path):
431 with open(archive_path, 'rb') as stream:
432 with open(archive_path, 'rb') as stream:
432 while True:
433 while True:
433 data = stream.read(16 * 1024)
434 data = stream.read(16 * 1024)
434 if not data:
435 if not data:
435 if fd: # fd means we used temporary file
436 if fd: # fd means we used temporary file
436 os.close(fd)
437 os.close(fd)
437 if not archive_cache_enabled:
438 if not archive_cache_enabled:
438 log.debug('Destroying temp archive %s', archive_path)
439 log.debug('Destroying temp archive %s', archive_path)
439 os.remove(archive_path)
440 os.remove(archive_path)
440 break
441 break
441 yield data
442 yield data
442
443
443 response = Response(app_iter=get_chunked_archive(archive))
444 response = Response(app_iter=get_chunked_archive(archive))
444 response.content_disposition = str(
445 response.content_disposition = str(
445 'attachment; filename=%s' % archive_name)
446 'attachment; filename=%s' % archive_name)
446 response.content_type = str(content_type)
447 response.content_type = str(content_type)
447
448
448 return response
449 return response
449
450
450 def _get_file_node(self, commit_id, f_path):
451 def _get_file_node(self, commit_id, f_path):
451 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
452 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
452 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
453 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
453 try:
454 try:
454 node = commit.get_node(f_path)
455 node = commit.get_node(f_path)
455 if node.is_dir():
456 if node.is_dir():
456 raise NodeError('%s path is a %s not a file'
457 raise NodeError('%s path is a %s not a file'
457 % (node, type(node)))
458 % (node, type(node)))
458 except NodeDoesNotExistError:
459 except NodeDoesNotExistError:
459 commit = EmptyCommit(
460 commit = EmptyCommit(
460 commit_id=commit_id,
461 commit_id=commit_id,
461 idx=commit.idx,
462 idx=commit.idx,
462 repo=commit.repository,
463 repo=commit.repository,
463 alias=commit.repository.alias,
464 alias=commit.repository.alias,
464 message=commit.message,
465 message=commit.message,
465 author=commit.author,
466 author=commit.author,
466 date=commit.date)
467 date=commit.date)
467 node = FileNode(f_path, '', commit=commit)
468 node = FileNode(f_path, '', commit=commit)
468 else:
469 else:
469 commit = EmptyCommit(
470 commit = EmptyCommit(
470 repo=self.rhodecode_vcs_repo,
471 repo=self.rhodecode_vcs_repo,
471 alias=self.rhodecode_vcs_repo.alias)
472 alias=self.rhodecode_vcs_repo.alias)
472 node = FileNode(f_path, '', commit=commit)
473 node = FileNode(f_path, '', commit=commit)
473 return node
474 return node
474
475
475 @LoginRequired()
476 @LoginRequired()
476 @HasRepoPermissionAnyDecorator(
477 @HasRepoPermissionAnyDecorator(
477 'repository.read', 'repository.write', 'repository.admin')
478 'repository.read', 'repository.write', 'repository.admin')
478 @view_config(
479 @view_config(
479 route_name='repo_files_diff', request_method='GET',
480 route_name='repo_files_diff', request_method='GET',
480 renderer=None)
481 renderer=None)
481 def repo_files_diff(self):
482 def repo_files_diff(self):
482 c = self.load_default_context()
483 c = self.load_default_context()
483 f_path = self._get_f_path(self.request.matchdict)
484 f_path = self._get_f_path(self.request.matchdict)
484 diff1 = self.request.GET.get('diff1', '')
485 diff1 = self.request.GET.get('diff1', '')
485 diff2 = self.request.GET.get('diff2', '')
486 diff2 = self.request.GET.get('diff2', '')
486
487
487 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
488 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
488
489
489 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
490 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
490 line_context = self.request.GET.get('context', 3)
491 line_context = self.request.GET.get('context', 3)
491
492
492 if not any((diff1, diff2)):
493 if not any((diff1, diff2)):
493 h.flash(
494 h.flash(
494 'Need query parameter "diff1" or "diff2" to generate a diff.',
495 'Need query parameter "diff1" or "diff2" to generate a diff.',
495 category='error')
496 category='error')
496 raise HTTPBadRequest()
497 raise HTTPBadRequest()
497
498
498 c.action = self.request.GET.get('diff')
499 c.action = self.request.GET.get('diff')
499 if c.action not in ['download', 'raw']:
500 if c.action not in ['download', 'raw']:
500 compare_url = h.route_path(
501 compare_url = h.route_path(
501 'repo_compare',
502 'repo_compare',
502 repo_name=self.db_repo_name,
503 repo_name=self.db_repo_name,
503 source_ref_type='rev',
504 source_ref_type='rev',
504 source_ref=diff1,
505 source_ref=diff1,
505 target_repo=self.db_repo_name,
506 target_repo=self.db_repo_name,
506 target_ref_type='rev',
507 target_ref_type='rev',
507 target_ref=diff2,
508 target_ref=diff2,
508 _query=dict(f_path=f_path))
509 _query=dict(f_path=f_path))
509 # redirect to new view if we render diff
510 # redirect to new view if we render diff
510 raise HTTPFound(compare_url)
511 raise HTTPFound(compare_url)
511
512
512 try:
513 try:
513 node1 = self._get_file_node(diff1, path1)
514 node1 = self._get_file_node(diff1, path1)
514 node2 = self._get_file_node(diff2, f_path)
515 node2 = self._get_file_node(diff2, f_path)
515 except (RepositoryError, NodeError):
516 except (RepositoryError, NodeError):
516 log.exception("Exception while trying to get node from repository")
517 log.exception("Exception while trying to get node from repository")
517 raise HTTPFound(
518 raise HTTPFound(
518 h.route_path('repo_files', repo_name=self.db_repo_name,
519 h.route_path('repo_files', repo_name=self.db_repo_name,
519 commit_id='tip', f_path=f_path))
520 commit_id='tip', f_path=f_path))
520
521
521 if all(isinstance(node.commit, EmptyCommit)
522 if all(isinstance(node.commit, EmptyCommit)
522 for node in (node1, node2)):
523 for node in (node1, node2)):
523 raise HTTPNotFound()
524 raise HTTPNotFound()
524
525
525 c.commit_1 = node1.commit
526 c.commit_1 = node1.commit
526 c.commit_2 = node2.commit
527 c.commit_2 = node2.commit
527
528
528 if c.action == 'download':
529 if c.action == 'download':
529 _diff = diffs.get_gitdiff(node1, node2,
530 _diff = diffs.get_gitdiff(node1, node2,
530 ignore_whitespace=ignore_whitespace,
531 ignore_whitespace=ignore_whitespace,
531 context=line_context)
532 context=line_context)
532 diff = diffs.DiffProcessor(_diff, format='gitdiff')
533 diff = diffs.DiffProcessor(_diff, format='gitdiff')
533
534
534 response = Response(self.path_filter.get_raw_patch(diff))
535 response = Response(self.path_filter.get_raw_patch(diff))
535 response.content_type = 'text/plain'
536 response.content_type = 'text/plain'
536 response.content_disposition = (
537 response.content_disposition = (
537 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
538 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
538 )
539 )
539 charset = self._get_default_encoding(c)
540 charset = self._get_default_encoding(c)
540 if charset:
541 if charset:
541 response.charset = charset
542 response.charset = charset
542 return response
543 return response
543
544
544 elif c.action == 'raw':
545 elif c.action == 'raw':
545 _diff = diffs.get_gitdiff(node1, node2,
546 _diff = diffs.get_gitdiff(node1, node2,
546 ignore_whitespace=ignore_whitespace,
547 ignore_whitespace=ignore_whitespace,
547 context=line_context)
548 context=line_context)
548 diff = diffs.DiffProcessor(_diff, format='gitdiff')
549 diff = diffs.DiffProcessor(_diff, format='gitdiff')
549
550
550 response = Response(self.path_filter.get_raw_patch(diff))
551 response = Response(self.path_filter.get_raw_patch(diff))
551 response.content_type = 'text/plain'
552 response.content_type = 'text/plain'
552 charset = self._get_default_encoding(c)
553 charset = self._get_default_encoding(c)
553 if charset:
554 if charset:
554 response.charset = charset
555 response.charset = charset
555 return response
556 return response
556
557
557 # in case we ever end up here
558 # in case we ever end up here
558 raise HTTPNotFound()
559 raise HTTPNotFound()
559
560
560 @LoginRequired()
561 @LoginRequired()
561 @HasRepoPermissionAnyDecorator(
562 @HasRepoPermissionAnyDecorator(
562 'repository.read', 'repository.write', 'repository.admin')
563 'repository.read', 'repository.write', 'repository.admin')
563 @view_config(
564 @view_config(
564 route_name='repo_files_diff_2way_redirect', request_method='GET',
565 route_name='repo_files_diff_2way_redirect', request_method='GET',
565 renderer=None)
566 renderer=None)
566 def repo_files_diff_2way_redirect(self):
567 def repo_files_diff_2way_redirect(self):
567 """
568 """
568 Kept only to make OLD links work
569 Kept only to make OLD links work
569 """
570 """
570 f_path = self._get_f_path_unchecked(self.request.matchdict)
571 f_path = self._get_f_path_unchecked(self.request.matchdict)
571 diff1 = self.request.GET.get('diff1', '')
572 diff1 = self.request.GET.get('diff1', '')
572 diff2 = self.request.GET.get('diff2', '')
573 diff2 = self.request.GET.get('diff2', '')
573
574
574 if not any((diff1, diff2)):
575 if not any((diff1, diff2)):
575 h.flash(
576 h.flash(
576 'Need query parameter "diff1" or "diff2" to generate a diff.',
577 'Need query parameter "diff1" or "diff2" to generate a diff.',
577 category='error')
578 category='error')
578 raise HTTPBadRequest()
579 raise HTTPBadRequest()
579
580
580 compare_url = h.route_path(
581 compare_url = h.route_path(
581 'repo_compare',
582 'repo_compare',
582 repo_name=self.db_repo_name,
583 repo_name=self.db_repo_name,
583 source_ref_type='rev',
584 source_ref_type='rev',
584 source_ref=diff1,
585 source_ref=diff1,
585 target_ref_type='rev',
586 target_ref_type='rev',
586 target_ref=diff2,
587 target_ref=diff2,
587 _query=dict(f_path=f_path, diffmode='sideside',
588 _query=dict(f_path=f_path, diffmode='sideside',
588 target_repo=self.db_repo_name,))
589 target_repo=self.db_repo_name,))
589 raise HTTPFound(compare_url)
590 raise HTTPFound(compare_url)
590
591
591 @LoginRequired()
592 @LoginRequired()
592 @HasRepoPermissionAnyDecorator(
593 @HasRepoPermissionAnyDecorator(
593 'repository.read', 'repository.write', 'repository.admin')
594 'repository.read', 'repository.write', 'repository.admin')
594 @view_config(
595 @view_config(
595 route_name='repo_files', request_method='GET',
596 route_name='repo_files', request_method='GET',
596 renderer=None)
597 renderer=None)
597 @view_config(
598 @view_config(
598 route_name='repo_files:default_path', request_method='GET',
599 route_name='repo_files:default_path', request_method='GET',
599 renderer=None)
600 renderer=None)
600 @view_config(
601 @view_config(
601 route_name='repo_files:default_commit', request_method='GET',
602 route_name='repo_files:default_commit', request_method='GET',
602 renderer=None)
603 renderer=None)
603 @view_config(
604 @view_config(
604 route_name='repo_files:rendered', request_method='GET',
605 route_name='repo_files:rendered', request_method='GET',
605 renderer=None)
606 renderer=None)
606 @view_config(
607 @view_config(
607 route_name='repo_files:annotated', request_method='GET',
608 route_name='repo_files:annotated', request_method='GET',
608 renderer=None)
609 renderer=None)
609 def repo_files(self):
610 def repo_files(self):
610 c = self.load_default_context()
611 c = self.load_default_context()
611
612
612 view_name = getattr(self.request.matched_route, 'name', None)
613 view_name = getattr(self.request.matched_route, 'name', None)
613
614
614 c.annotate = view_name == 'repo_files:annotated'
615 c.annotate = view_name == 'repo_files:annotated'
615 # default is false, but .rst/.md files later are auto rendered, we can
616 # default is false, but .rst/.md files later are auto rendered, we can
616 # overwrite auto rendering by setting this GET flag
617 # overwrite auto rendering by setting this GET flag
617 c.renderer = view_name == 'repo_files:rendered' or \
618 c.renderer = view_name == 'repo_files:rendered' or \
618 not self.request.GET.get('no-render', False)
619 not self.request.GET.get('no-render', False)
619
620
620 # redirect to given commit_id from form if given
621 commit_id, f_path = self._get_commit_and_path()
621 get_commit_id = self.request.GET.get('at_rev', None)
622 if get_commit_id:
623 self._get_commit_or_redirect(get_commit_id)
624
622
625 commit_id, f_path = self._get_commit_and_path()
626 c.commit = self._get_commit_or_redirect(commit_id)
623 c.commit = self._get_commit_or_redirect(commit_id)
627 c.branch = self.request.GET.get('branch', None)
624 c.branch = self.request.GET.get('branch', None)
628 c.f_path = f_path
625 c.f_path = f_path
626 at_rev = self.request.GET.get('at')
629
627
630 # prev link
628 # prev link
631 try:
629 try:
632 prev_commit = c.commit.prev(c.branch)
630 prev_commit = c.commit.prev(c.branch)
633 c.prev_commit = prev_commit
631 c.prev_commit = prev_commit
634 c.url_prev = h.route_path(
632 c.url_prev = h.route_path(
635 'repo_files', repo_name=self.db_repo_name,
633 'repo_files', repo_name=self.db_repo_name,
636 commit_id=prev_commit.raw_id, f_path=f_path)
634 commit_id=prev_commit.raw_id, f_path=f_path)
637 if c.branch:
635 if c.branch:
638 c.url_prev += '?branch=%s' % c.branch
636 c.url_prev += '?branch=%s' % c.branch
639 except (CommitDoesNotExistError, VCSError):
637 except (CommitDoesNotExistError, VCSError):
640 c.url_prev = '#'
638 c.url_prev = '#'
641 c.prev_commit = EmptyCommit()
639 c.prev_commit = EmptyCommit()
642
640
643 # next link
641 # next link
644 try:
642 try:
645 next_commit = c.commit.next(c.branch)
643 next_commit = c.commit.next(c.branch)
646 c.next_commit = next_commit
644 c.next_commit = next_commit
647 c.url_next = h.route_path(
645 c.url_next = h.route_path(
648 'repo_files', repo_name=self.db_repo_name,
646 'repo_files', repo_name=self.db_repo_name,
649 commit_id=next_commit.raw_id, f_path=f_path)
647 commit_id=next_commit.raw_id, f_path=f_path)
650 if c.branch:
648 if c.branch:
651 c.url_next += '?branch=%s' % c.branch
649 c.url_next += '?branch=%s' % c.branch
652 except (CommitDoesNotExistError, VCSError):
650 except (CommitDoesNotExistError, VCSError):
653 c.url_next = '#'
651 c.url_next = '#'
654 c.next_commit = EmptyCommit()
652 c.next_commit = EmptyCommit()
655
653
656 # files or dirs
654 # files or dirs
657 try:
655 try:
658 c.file = c.commit.get_node(f_path)
656 c.file = c.commit.get_node(f_path)
659 c.file_author = True
657 c.file_author = True
660 c.file_tree = ''
658 c.file_tree = ''
661
659
662 # load file content
660 # load file content
663 if c.file.is_file():
661 if c.file.is_file():
664 c.lf_node = {}
662 c.lf_node = {}
665
663
666 has_lf_enabled = self._is_lf_enabled(self.db_repo)
664 has_lf_enabled = self._is_lf_enabled(self.db_repo)
667 if has_lf_enabled:
665 if has_lf_enabled:
668 c.lf_node = c.file.get_largefile_node()
666 c.lf_node = c.file.get_largefile_node()
669
667
670 c.file_source_page = 'true'
668 c.file_source_page = 'true'
671 c.file_last_commit = c.file.last_commit
669 c.file_last_commit = c.file.last_commit
672
670
673 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
671 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
674
672
675 if not (c.file_size_too_big or c.file.is_binary):
673 if not (c.file_size_too_big or c.file.is_binary):
676 if c.annotate: # annotation has precedence over renderer
674 if c.annotate: # annotation has precedence over renderer
677 c.annotated_lines = filenode_as_annotated_lines_tokens(
675 c.annotated_lines = filenode_as_annotated_lines_tokens(
678 c.file
676 c.file
679 )
677 )
680 else:
678 else:
681 c.renderer = (
679 c.renderer = (
682 c.renderer and h.renderer_from_filename(c.file.path)
680 c.renderer and h.renderer_from_filename(c.file.path)
683 )
681 )
684 if not c.renderer:
682 if not c.renderer:
685 c.lines = filenode_as_lines_tokens(c.file)
683 c.lines = filenode_as_lines_tokens(c.file)
686
684
687 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
685 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
688 commit_id, self.rhodecode_vcs_repo)
686 commit_id, self.rhodecode_vcs_repo)
689 c.on_branch_head = is_head
687 c.on_branch_head = is_head
690
688
691 branch = c.commit.branch if (
689 branch = c.commit.branch if (
692 c.commit.branch and '/' not in c.commit.branch) else None
690 c.commit.branch and '/' not in c.commit.branch) else None
693 c.branch_or_raw_id = branch or c.commit.raw_id
691 c.branch_or_raw_id = branch or c.commit.raw_id
694 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
692 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
695
693
696 author = c.file_last_commit.author
694 author = c.file_last_commit.author
697 c.authors = [[
695 c.authors = [[
698 h.email(author),
696 h.email(author),
699 h.person(author, 'username_or_name_or_email'),
697 h.person(author, 'username_or_name_or_email'),
700 1
698 1
701 ]]
699 ]]
702
700
703 else: # load tree content at path
701 else: # load tree content at path
704 c.file_source_page = 'false'
702 c.file_source_page = 'false'
705 c.authors = []
703 c.authors = []
706 # this loads a simple tree without metadata to speed things up
704 # this loads a simple tree without metadata to speed things up
707 # later via ajax we call repo_nodetree_full and fetch whole
705 # later via ajax we call repo_nodetree_full and fetch whole
708 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path)
706 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
709
707
710 c.readme_data, c.readme_file = \
708 c.readme_data, c.readme_file = \
711 self._get_readme_data(self.db_repo, c.visual.default_renderer,
709 self._get_readme_data(self.db_repo, c.visual.default_renderer,
712 c.commit.raw_id, f_path)
710 c.commit.raw_id, f_path)
713
711
714 except RepositoryError as e:
712 except RepositoryError as e:
715 h.flash(safe_str(h.escape(e)), category='error')
713 h.flash(safe_str(h.escape(e)), category='error')
716 raise HTTPNotFound()
714 raise HTTPNotFound()
717
715
718 if self.request.environ.get('HTTP_X_PJAX'):
716 if self.request.environ.get('HTTP_X_PJAX'):
719 html = render('rhodecode:templates/files/files_pjax.mako',
717 html = render('rhodecode:templates/files/files_pjax.mako',
720 self._get_template_context(c), self.request)
718 self._get_template_context(c), self.request)
721 else:
719 else:
722 html = render('rhodecode:templates/files/files.mako',
720 html = render('rhodecode:templates/files/files.mako',
723 self._get_template_context(c), self.request)
721 self._get_template_context(c), self.request)
724 return Response(html)
722 return Response(html)
725
723
726 @HasRepoPermissionAnyDecorator(
724 @HasRepoPermissionAnyDecorator(
727 'repository.read', 'repository.write', 'repository.admin')
725 'repository.read', 'repository.write', 'repository.admin')
728 @view_config(
726 @view_config(
729 route_name='repo_files:annotated_previous', request_method='GET',
727 route_name='repo_files:annotated_previous', request_method='GET',
730 renderer=None)
728 renderer=None)
731 def repo_files_annotated_previous(self):
729 def repo_files_annotated_previous(self):
732 self.load_default_context()
730 self.load_default_context()
733
731
734 commit_id, f_path = self._get_commit_and_path()
732 commit_id, f_path = self._get_commit_and_path()
735 commit = self._get_commit_or_redirect(commit_id)
733 commit = self._get_commit_or_redirect(commit_id)
736 prev_commit_id = commit.raw_id
734 prev_commit_id = commit.raw_id
737 line_anchor = self.request.GET.get('line_anchor')
735 line_anchor = self.request.GET.get('line_anchor')
738 is_file = False
736 is_file = False
739 try:
737 try:
740 _file = commit.get_node(f_path)
738 _file = commit.get_node(f_path)
741 is_file = _file.is_file()
739 is_file = _file.is_file()
742 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
740 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
743 pass
741 pass
744
742
745 if is_file:
743 if is_file:
746 history = commit.get_path_history(f_path)
744 history = commit.get_path_history(f_path)
747 prev_commit_id = history[1].raw_id \
745 prev_commit_id = history[1].raw_id \
748 if len(history) > 1 else prev_commit_id
746 if len(history) > 1 else prev_commit_id
749 prev_url = h.route_path(
747 prev_url = h.route_path(
750 'repo_files:annotated', repo_name=self.db_repo_name,
748 'repo_files:annotated', repo_name=self.db_repo_name,
751 commit_id=prev_commit_id, f_path=f_path,
749 commit_id=prev_commit_id, f_path=f_path,
752 _anchor='L{}'.format(line_anchor))
750 _anchor='L{}'.format(line_anchor))
753
751
754 raise HTTPFound(prev_url)
752 raise HTTPFound(prev_url)
755
753
756 @LoginRequired()
754 @LoginRequired()
757 @HasRepoPermissionAnyDecorator(
755 @HasRepoPermissionAnyDecorator(
758 'repository.read', 'repository.write', 'repository.admin')
756 'repository.read', 'repository.write', 'repository.admin')
759 @view_config(
757 @view_config(
760 route_name='repo_nodetree_full', request_method='GET',
758 route_name='repo_nodetree_full', request_method='GET',
761 renderer=None, xhr=True)
759 renderer=None, xhr=True)
762 @view_config(
760 @view_config(
763 route_name='repo_nodetree_full:default_path', request_method='GET',
761 route_name='repo_nodetree_full:default_path', request_method='GET',
764 renderer=None, xhr=True)
762 renderer=None, xhr=True)
765 def repo_nodetree_full(self):
763 def repo_nodetree_full(self):
766 """
764 """
767 Returns rendered html of file tree that contains commit date,
765 Returns rendered html of file tree that contains commit date,
768 author, commit_id for the specified combination of
766 author, commit_id for the specified combination of
769 repo, commit_id and file path
767 repo, commit_id and file path
770 """
768 """
771 c = self.load_default_context()
769 c = self.load_default_context()
772
770
773 commit_id, f_path = self._get_commit_and_path()
771 commit_id, f_path = self._get_commit_and_path()
774 commit = self._get_commit_or_redirect(commit_id)
772 commit = self._get_commit_or_redirect(commit_id)
775 try:
773 try:
776 dir_node = commit.get_node(f_path)
774 dir_node = commit.get_node(f_path)
777 except RepositoryError as e:
775 except RepositoryError as e:
778 return Response('error: {}'.format(h.escape(safe_str(e))))
776 return Response('error: {}'.format(h.escape(safe_str(e))))
779
777
780 if dir_node.is_file():
778 if dir_node.is_file():
781 return Response('')
779 return Response('')
782
780
783 c.file = dir_node
781 c.file = dir_node
784 c.commit = commit
782 c.commit = commit
783 at_rev = self.request.GET.get('at')
785
784
786 html = self._get_tree_at_commit(
785 html = self._get_tree_at_commit(
787 c, commit.raw_id, dir_node.path, full_load=True)
786 c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
788
787
789 return Response(html)
788 return Response(html)
790
789
791 def _get_attachement_headers(self, f_path):
790 def _get_attachement_headers(self, f_path):
792 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
791 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
793 safe_path = f_name.replace('"', '\\"')
792 safe_path = f_name.replace('"', '\\"')
794 encoded_path = urllib.quote(f_name)
793 encoded_path = urllib.quote(f_name)
795
794
796 return "attachment; " \
795 return "attachment; " \
797 "filename=\"{}\"; " \
796 "filename=\"{}\"; " \
798 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
797 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
799
798
800 @LoginRequired()
799 @LoginRequired()
801 @HasRepoPermissionAnyDecorator(
800 @HasRepoPermissionAnyDecorator(
802 'repository.read', 'repository.write', 'repository.admin')
801 'repository.read', 'repository.write', 'repository.admin')
803 @view_config(
802 @view_config(
804 route_name='repo_file_raw', request_method='GET',
803 route_name='repo_file_raw', request_method='GET',
805 renderer=None)
804 renderer=None)
806 def repo_file_raw(self):
805 def repo_file_raw(self):
807 """
806 """
808 Action for show as raw, some mimetypes are "rendered",
807 Action for show as raw, some mimetypes are "rendered",
809 those include images, icons.
808 those include images, icons.
810 """
809 """
811 c = self.load_default_context()
810 c = self.load_default_context()
812
811
813 commit_id, f_path = self._get_commit_and_path()
812 commit_id, f_path = self._get_commit_and_path()
814 commit = self._get_commit_or_redirect(commit_id)
813 commit = self._get_commit_or_redirect(commit_id)
815 file_node = self._get_filenode_or_redirect(commit, f_path)
814 file_node = self._get_filenode_or_redirect(commit, f_path)
816
815
817 raw_mimetype_mapping = {
816 raw_mimetype_mapping = {
818 # map original mimetype to a mimetype used for "show as raw"
817 # map original mimetype to a mimetype used for "show as raw"
819 # you can also provide a content-disposition to override the
818 # you can also provide a content-disposition to override the
820 # default "attachment" disposition.
819 # default "attachment" disposition.
821 # orig_type: (new_type, new_dispo)
820 # orig_type: (new_type, new_dispo)
822
821
823 # show images inline:
822 # show images inline:
824 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
823 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
825 # for example render an SVG with javascript inside or even render
824 # for example render an SVG with javascript inside or even render
826 # HTML.
825 # HTML.
827 'image/x-icon': ('image/x-icon', 'inline'),
826 'image/x-icon': ('image/x-icon', 'inline'),
828 'image/png': ('image/png', 'inline'),
827 'image/png': ('image/png', 'inline'),
829 'image/gif': ('image/gif', 'inline'),
828 'image/gif': ('image/gif', 'inline'),
830 'image/jpeg': ('image/jpeg', 'inline'),
829 'image/jpeg': ('image/jpeg', 'inline'),
831 'application/pdf': ('application/pdf', 'inline'),
830 'application/pdf': ('application/pdf', 'inline'),
832 }
831 }
833
832
834 mimetype = file_node.mimetype
833 mimetype = file_node.mimetype
835 try:
834 try:
836 mimetype, disposition = raw_mimetype_mapping[mimetype]
835 mimetype, disposition = raw_mimetype_mapping[mimetype]
837 except KeyError:
836 except KeyError:
838 # we don't know anything special about this, handle it safely
837 # we don't know anything special about this, handle it safely
839 if file_node.is_binary:
838 if file_node.is_binary:
840 # do same as download raw for binary files
839 # do same as download raw for binary files
841 mimetype, disposition = 'application/octet-stream', 'attachment'
840 mimetype, disposition = 'application/octet-stream', 'attachment'
842 else:
841 else:
843 # do not just use the original mimetype, but force text/plain,
842 # do not just use the original mimetype, but force text/plain,
844 # otherwise it would serve text/html and that might be unsafe.
843 # otherwise it would serve text/html and that might be unsafe.
845 # Note: underlying vcs library fakes text/plain mimetype if the
844 # Note: underlying vcs library fakes text/plain mimetype if the
846 # mimetype can not be determined and it thinks it is not
845 # mimetype can not be determined and it thinks it is not
847 # binary.This might lead to erroneous text display in some
846 # binary.This might lead to erroneous text display in some
848 # cases, but helps in other cases, like with text files
847 # cases, but helps in other cases, like with text files
849 # without extension.
848 # without extension.
850 mimetype, disposition = 'text/plain', 'inline'
849 mimetype, disposition = 'text/plain', 'inline'
851
850
852 if disposition == 'attachment':
851 if disposition == 'attachment':
853 disposition = self._get_attachement_headers(f_path)
852 disposition = self._get_attachement_headers(f_path)
854
853
855 stream_content = file_node.stream_bytes()
854 stream_content = file_node.stream_bytes()
856
855
857 response = Response(app_iter=stream_content)
856 response = Response(app_iter=stream_content)
858 response.content_disposition = disposition
857 response.content_disposition = disposition
859 response.content_type = mimetype
858 response.content_type = mimetype
860
859
861 charset = self._get_default_encoding(c)
860 charset = self._get_default_encoding(c)
862 if charset:
861 if charset:
863 response.charset = charset
862 response.charset = charset
864
863
865 return response
864 return response
866
865
867 @LoginRequired()
866 @LoginRequired()
868 @HasRepoPermissionAnyDecorator(
867 @HasRepoPermissionAnyDecorator(
869 'repository.read', 'repository.write', 'repository.admin')
868 'repository.read', 'repository.write', 'repository.admin')
870 @view_config(
869 @view_config(
871 route_name='repo_file_download', request_method='GET',
870 route_name='repo_file_download', request_method='GET',
872 renderer=None)
871 renderer=None)
873 @view_config(
872 @view_config(
874 route_name='repo_file_download:legacy', request_method='GET',
873 route_name='repo_file_download:legacy', request_method='GET',
875 renderer=None)
874 renderer=None)
876 def repo_file_download(self):
875 def repo_file_download(self):
877 c = self.load_default_context()
876 c = self.load_default_context()
878
877
879 commit_id, f_path = self._get_commit_and_path()
878 commit_id, f_path = self._get_commit_and_path()
880 commit = self._get_commit_or_redirect(commit_id)
879 commit = self._get_commit_or_redirect(commit_id)
881 file_node = self._get_filenode_or_redirect(commit, f_path)
880 file_node = self._get_filenode_or_redirect(commit, f_path)
882
881
883 if self.request.GET.get('lf'):
882 if self.request.GET.get('lf'):
884 # only if lf get flag is passed, we download this file
883 # only if lf get flag is passed, we download this file
885 # as LFS/Largefile
884 # as LFS/Largefile
886 lf_node = file_node.get_largefile_node()
885 lf_node = file_node.get_largefile_node()
887 if lf_node:
886 if lf_node:
888 # overwrite our pointer with the REAL large-file
887 # overwrite our pointer with the REAL large-file
889 file_node = lf_node
888 file_node = lf_node
890
889
891 disposition = self._get_attachement_headers(f_path)
890 disposition = self._get_attachement_headers(f_path)
892
891
893 stream_content = file_node.stream_bytes()
892 stream_content = file_node.stream_bytes()
894
893
895 response = Response(app_iter=stream_content)
894 response = Response(app_iter=stream_content)
896 response.content_disposition = disposition
895 response.content_disposition = disposition
897 response.content_type = file_node.mimetype
896 response.content_type = file_node.mimetype
898
897
899 charset = self._get_default_encoding(c)
898 charset = self._get_default_encoding(c)
900 if charset:
899 if charset:
901 response.charset = charset
900 response.charset = charset
902
901
903 return response
902 return response
904
903
905 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
904 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
906
905
907 cache_seconds = safe_int(
906 cache_seconds = safe_int(
908 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
907 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
909 cache_on = cache_seconds > 0
908 cache_on = cache_seconds > 0
910 log.debug(
909 log.debug(
911 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
910 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
912 'with caching: %s[TTL: %ss]' % (
911 'with caching: %s[TTL: %ss]' % (
913 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
912 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
914
913
915 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
914 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
916 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
915 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
917
916
918 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
917 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
919 condition=cache_on)
918 condition=cache_on)
920 def compute_file_search(repo_id, commit_id, f_path):
919 def compute_file_search(repo_id, commit_id, f_path):
921 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
920 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
922 repo_id, commit_id, f_path)
921 repo_id, commit_id, f_path)
923 try:
922 try:
924 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, commit_id, f_path)
923 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, commit_id, f_path)
925 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
924 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
926 log.exception(safe_str(e))
925 log.exception(safe_str(e))
927 h.flash(safe_str(h.escape(e)), category='error')
926 h.flash(safe_str(h.escape(e)), category='error')
928 raise HTTPFound(h.route_path(
927 raise HTTPFound(h.route_path(
929 'repo_files', repo_name=self.db_repo_name,
928 'repo_files', repo_name=self.db_repo_name,
930 commit_id='tip', f_path='/'))
929 commit_id='tip', f_path='/'))
931
930
932 return _d + _f
931 return _d + _f
933
932
934 result = compute_file_search(self.db_repo.repo_id, commit_id, f_path)
933 result = compute_file_search(self.db_repo.repo_id, commit_id, f_path)
935 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
934 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
936
935
937 @LoginRequired()
936 @LoginRequired()
938 @HasRepoPermissionAnyDecorator(
937 @HasRepoPermissionAnyDecorator(
939 'repository.read', 'repository.write', 'repository.admin')
938 'repository.read', 'repository.write', 'repository.admin')
940 @view_config(
939 @view_config(
941 route_name='repo_files_nodelist', request_method='GET',
940 route_name='repo_files_nodelist', request_method='GET',
942 renderer='json_ext', xhr=True)
941 renderer='json_ext', xhr=True)
943 def repo_nodelist(self):
942 def repo_nodelist(self):
944 self.load_default_context()
943 self.load_default_context()
945
944
946 commit_id, f_path = self._get_commit_and_path()
945 commit_id, f_path = self._get_commit_and_path()
947 commit = self._get_commit_or_redirect(commit_id)
946 commit = self._get_commit_or_redirect(commit_id)
948
947
949 metadata = self._get_nodelist_at_commit(
948 metadata = self._get_nodelist_at_commit(
950 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
949 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
951 return {'nodes': metadata}
950 return {'nodes': metadata}
952
951
953 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
952 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
954 items = []
953 items = []
955 for name, commit_id in branches_or_tags.items():
954 for name, commit_id in branches_or_tags.items():
956 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
955 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
957 items.append((sym_ref, name, ref_type))
956 items.append((sym_ref, name, ref_type))
958 return items
957 return items
959
958
960 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
959 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
961 return commit_id
960 return commit_id
962
961
963 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
962 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
964 return commit_id
963 return commit_id
965
964
966 # NOTE(dan): old code we used in "diff" mode compare
965 # NOTE(dan): old code we used in "diff" mode compare
967 new_f_path = vcspath.join(name, f_path)
966 new_f_path = vcspath.join(name, f_path)
968 return u'%s@%s' % (new_f_path, commit_id)
967 return u'%s@%s' % (new_f_path, commit_id)
969
968
970 def _get_node_history(self, commit_obj, f_path, commits=None):
969 def _get_node_history(self, commit_obj, f_path, commits=None):
971 """
970 """
972 get commit history for given node
971 get commit history for given node
973
972
974 :param commit_obj: commit to calculate history
973 :param commit_obj: commit to calculate history
975 :param f_path: path for node to calculate history for
974 :param f_path: path for node to calculate history for
976 :param commits: if passed don't calculate history and take
975 :param commits: if passed don't calculate history and take
977 commits defined in this list
976 commits defined in this list
978 """
977 """
979 _ = self.request.translate
978 _ = self.request.translate
980
979
981 # calculate history based on tip
980 # calculate history based on tip
982 tip = self.rhodecode_vcs_repo.get_commit()
981 tip = self.rhodecode_vcs_repo.get_commit()
983 if commits is None:
982 if commits is None:
984 pre_load = ["author", "branch"]
983 pre_load = ["author", "branch"]
985 try:
984 try:
986 commits = tip.get_path_history(f_path, pre_load=pre_load)
985 commits = tip.get_path_history(f_path, pre_load=pre_load)
987 except (NodeDoesNotExistError, CommitError):
986 except (NodeDoesNotExistError, CommitError):
988 # this node is not present at tip!
987 # this node is not present at tip!
989 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
988 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
990
989
991 history = []
990 history = []
992 commits_group = ([], _("Changesets"))
991 commits_group = ([], _("Changesets"))
993 for commit in commits:
992 for commit in commits:
994 branch = ' (%s)' % commit.branch if commit.branch else ''
993 branch = ' (%s)' % commit.branch if commit.branch else ''
995 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
994 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
996 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
995 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
997 history.append(commits_group)
996 history.append(commits_group)
998
997
999 symbolic_reference = self._symbolic_reference
998 symbolic_reference = self._symbolic_reference
1000
999
1001 if self.rhodecode_vcs_repo.alias == 'svn':
1000 if self.rhodecode_vcs_repo.alias == 'svn':
1002 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1001 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1003 f_path, self.rhodecode_vcs_repo)
1002 f_path, self.rhodecode_vcs_repo)
1004 if adjusted_f_path != f_path:
1003 if adjusted_f_path != f_path:
1005 log.debug(
1004 log.debug(
1006 'Recognized svn tag or branch in file "%s", using svn '
1005 'Recognized svn tag or branch in file "%s", using svn '
1007 'specific symbolic references', f_path)
1006 'specific symbolic references', f_path)
1008 f_path = adjusted_f_path
1007 f_path = adjusted_f_path
1009 symbolic_reference = self._symbolic_reference_svn
1008 symbolic_reference = self._symbolic_reference_svn
1010
1009
1011 branches = self._create_references(
1010 branches = self._create_references(
1012 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1011 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1013 branches_group = (branches, _("Branches"))
1012 branches_group = (branches, _("Branches"))
1014
1013
1015 tags = self._create_references(
1014 tags = self._create_references(
1016 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1015 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1017 tags_group = (tags, _("Tags"))
1016 tags_group = (tags, _("Tags"))
1018
1017
1019 history.append(branches_group)
1018 history.append(branches_group)
1020 history.append(tags_group)
1019 history.append(tags_group)
1021
1020
1022 return history, commits
1021 return history, commits
1023
1022
1024 @LoginRequired()
1023 @LoginRequired()
1025 @HasRepoPermissionAnyDecorator(
1024 @HasRepoPermissionAnyDecorator(
1026 'repository.read', 'repository.write', 'repository.admin')
1025 'repository.read', 'repository.write', 'repository.admin')
1027 @view_config(
1026 @view_config(
1028 route_name='repo_file_history', request_method='GET',
1027 route_name='repo_file_history', request_method='GET',
1029 renderer='json_ext')
1028 renderer='json_ext')
1030 def repo_file_history(self):
1029 def repo_file_history(self):
1031 self.load_default_context()
1030 self.load_default_context()
1032
1031
1033 commit_id, f_path = self._get_commit_and_path()
1032 commit_id, f_path = self._get_commit_and_path()
1034 commit = self._get_commit_or_redirect(commit_id)
1033 commit = self._get_commit_or_redirect(commit_id)
1035 file_node = self._get_filenode_or_redirect(commit, f_path)
1034 file_node = self._get_filenode_or_redirect(commit, f_path)
1036
1035
1037 if file_node.is_file():
1036 if file_node.is_file():
1038 file_history, _hist = self._get_node_history(commit, f_path)
1037 file_history, _hist = self._get_node_history(commit, f_path)
1039
1038
1040 res = []
1039 res = []
1041 for obj in file_history:
1040 for section_items, section in file_history:
1041 items = []
1042 for obj_id, obj_text, obj_type in section_items:
1043 at_rev = ''
1044 if obj_type in ['branch', 'bookmark', 'tag']:
1045 at_rev = obj_text
1046 entry = {
1047 'id': obj_id,
1048 'text': obj_text,
1049 'type': obj_type,
1050 'at_rev': at_rev
1051 }
1052
1053 items.append(entry)
1054
1042 res.append({
1055 res.append({
1043 'text': obj[1],
1056 'text': section,
1044 'children': [{'id': o[0], 'text': o[1], 'type': o[2]} for o in obj[0]]
1057 'children': items
1045 })
1058 })
1046
1059
1047 data = {
1060 data = {
1048 'more': False,
1061 'more': False,
1049 'results': res
1062 'results': res
1050 }
1063 }
1051 return data
1064 return data
1052
1065
1053 log.warning('Cannot fetch history for directory')
1066 log.warning('Cannot fetch history for directory')
1054 raise HTTPBadRequest()
1067 raise HTTPBadRequest()
1055
1068
1056 @LoginRequired()
1069 @LoginRequired()
1057 @HasRepoPermissionAnyDecorator(
1070 @HasRepoPermissionAnyDecorator(
1058 'repository.read', 'repository.write', 'repository.admin')
1071 'repository.read', 'repository.write', 'repository.admin')
1059 @view_config(
1072 @view_config(
1060 route_name='repo_file_authors', request_method='GET',
1073 route_name='repo_file_authors', request_method='GET',
1061 renderer='rhodecode:templates/files/file_authors_box.mako')
1074 renderer='rhodecode:templates/files/file_authors_box.mako')
1062 def repo_file_authors(self):
1075 def repo_file_authors(self):
1063 c = self.load_default_context()
1076 c = self.load_default_context()
1064
1077
1065 commit_id, f_path = self._get_commit_and_path()
1078 commit_id, f_path = self._get_commit_and_path()
1066 commit = self._get_commit_or_redirect(commit_id)
1079 commit = self._get_commit_or_redirect(commit_id)
1067 file_node = self._get_filenode_or_redirect(commit, f_path)
1080 file_node = self._get_filenode_or_redirect(commit, f_path)
1068
1081
1069 if not file_node.is_file():
1082 if not file_node.is_file():
1070 raise HTTPBadRequest()
1083 raise HTTPBadRequest()
1071
1084
1072 c.file_last_commit = file_node.last_commit
1085 c.file_last_commit = file_node.last_commit
1073 if self.request.GET.get('annotate') == '1':
1086 if self.request.GET.get('annotate') == '1':
1074 # use _hist from annotation if annotation mode is on
1087 # use _hist from annotation if annotation mode is on
1075 commit_ids = set(x[1] for x in file_node.annotate)
1088 commit_ids = set(x[1] for x in file_node.annotate)
1076 _hist = (
1089 _hist = (
1077 self.rhodecode_vcs_repo.get_commit(commit_id)
1090 self.rhodecode_vcs_repo.get_commit(commit_id)
1078 for commit_id in commit_ids)
1091 for commit_id in commit_ids)
1079 else:
1092 else:
1080 _f_history, _hist = self._get_node_history(commit, f_path)
1093 _f_history, _hist = self._get_node_history(commit, f_path)
1081 c.file_author = False
1094 c.file_author = False
1082
1095
1083 unique = collections.OrderedDict()
1096 unique = collections.OrderedDict()
1084 for commit in _hist:
1097 for commit in _hist:
1085 author = commit.author
1098 author = commit.author
1086 if author not in unique:
1099 if author not in unique:
1087 unique[commit.author] = [
1100 unique[commit.author] = [
1088 h.email(author),
1101 h.email(author),
1089 h.person(author, 'username_or_name_or_email'),
1102 h.person(author, 'username_or_name_or_email'),
1090 1 # counter
1103 1 # counter
1091 ]
1104 ]
1092
1105
1093 else:
1106 else:
1094 # increase counter
1107 # increase counter
1095 unique[commit.author][2] += 1
1108 unique[commit.author][2] += 1
1096
1109
1097 c.authors = [val for val in unique.values()]
1110 c.authors = [val for val in unique.values()]
1098
1111
1099 return self._get_template_context(c)
1112 return self._get_template_context(c)
1100
1113
1101 @LoginRequired()
1114 @LoginRequired()
1102 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1115 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1103 @view_config(
1116 @view_config(
1104 route_name='repo_files_remove_file', request_method='GET',
1117 route_name='repo_files_remove_file', request_method='GET',
1105 renderer='rhodecode:templates/files/files_delete.mako')
1118 renderer='rhodecode:templates/files/files_delete.mako')
1106 def repo_files_remove_file(self):
1119 def repo_files_remove_file(self):
1107 _ = self.request.translate
1120 _ = self.request.translate
1108 c = self.load_default_context()
1121 c = self.load_default_context()
1109 commit_id, f_path = self._get_commit_and_path()
1122 commit_id, f_path = self._get_commit_and_path()
1110
1123
1111 self._ensure_not_locked()
1124 self._ensure_not_locked()
1112 _branch_name, _sha_commit_id, is_head = \
1125 _branch_name, _sha_commit_id, is_head = \
1113 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1126 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1114
1127
1115 self.forbid_non_head(is_head, f_path)
1128 self.forbid_non_head(is_head, f_path)
1116 self.check_branch_permission(_branch_name)
1129 self.check_branch_permission(_branch_name)
1117
1130
1118 c.commit = self._get_commit_or_redirect(commit_id)
1131 c.commit = self._get_commit_or_redirect(commit_id)
1119 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1132 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1120
1133
1121 c.default_message = _(
1134 c.default_message = _(
1122 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1135 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1123 c.f_path = f_path
1136 c.f_path = f_path
1124
1137
1125 return self._get_template_context(c)
1138 return self._get_template_context(c)
1126
1139
1127 @LoginRequired()
1140 @LoginRequired()
1128 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1141 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1129 @CSRFRequired()
1142 @CSRFRequired()
1130 @view_config(
1143 @view_config(
1131 route_name='repo_files_delete_file', request_method='POST',
1144 route_name='repo_files_delete_file', request_method='POST',
1132 renderer=None)
1145 renderer=None)
1133 def repo_files_delete_file(self):
1146 def repo_files_delete_file(self):
1134 _ = self.request.translate
1147 _ = self.request.translate
1135
1148
1136 c = self.load_default_context()
1149 c = self.load_default_context()
1137 commit_id, f_path = self._get_commit_and_path()
1150 commit_id, f_path = self._get_commit_and_path()
1138
1151
1139 self._ensure_not_locked()
1152 self._ensure_not_locked()
1140 _branch_name, _sha_commit_id, is_head = \
1153 _branch_name, _sha_commit_id, is_head = \
1141 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1154 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1142
1155
1143 self.forbid_non_head(is_head, f_path)
1156 self.forbid_non_head(is_head, f_path)
1144 self.check_branch_permission(_branch_name)
1157 self.check_branch_permission(_branch_name)
1145
1158
1146 c.commit = self._get_commit_or_redirect(commit_id)
1159 c.commit = self._get_commit_or_redirect(commit_id)
1147 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1160 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1148
1161
1149 c.default_message = _(
1162 c.default_message = _(
1150 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1163 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1151 c.f_path = f_path
1164 c.f_path = f_path
1152 node_path = f_path
1165 node_path = f_path
1153 author = self._rhodecode_db_user.full_contact
1166 author = self._rhodecode_db_user.full_contact
1154 message = self.request.POST.get('message') or c.default_message
1167 message = self.request.POST.get('message') or c.default_message
1155 try:
1168 try:
1156 nodes = {
1169 nodes = {
1157 node_path: {
1170 node_path: {
1158 'content': ''
1171 'content': ''
1159 }
1172 }
1160 }
1173 }
1161 ScmModel().delete_nodes(
1174 ScmModel().delete_nodes(
1162 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1175 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1163 message=message,
1176 message=message,
1164 nodes=nodes,
1177 nodes=nodes,
1165 parent_commit=c.commit,
1178 parent_commit=c.commit,
1166 author=author,
1179 author=author,
1167 )
1180 )
1168
1181
1169 h.flash(
1182 h.flash(
1170 _('Successfully deleted file `{}`').format(
1183 _('Successfully deleted file `{}`').format(
1171 h.escape(f_path)), category='success')
1184 h.escape(f_path)), category='success')
1172 except Exception:
1185 except Exception:
1173 log.exception('Error during commit operation')
1186 log.exception('Error during commit operation')
1174 h.flash(_('Error occurred during commit'), category='error')
1187 h.flash(_('Error occurred during commit'), category='error')
1175 raise HTTPFound(
1188 raise HTTPFound(
1176 h.route_path('repo_commit', repo_name=self.db_repo_name,
1189 h.route_path('repo_commit', repo_name=self.db_repo_name,
1177 commit_id='tip'))
1190 commit_id='tip'))
1178
1191
1179 @LoginRequired()
1192 @LoginRequired()
1180 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1193 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1181 @view_config(
1194 @view_config(
1182 route_name='repo_files_edit_file', request_method='GET',
1195 route_name='repo_files_edit_file', request_method='GET',
1183 renderer='rhodecode:templates/files/files_edit.mako')
1196 renderer='rhodecode:templates/files/files_edit.mako')
1184 def repo_files_edit_file(self):
1197 def repo_files_edit_file(self):
1185 _ = self.request.translate
1198 _ = self.request.translate
1186 c = self.load_default_context()
1199 c = self.load_default_context()
1187 commit_id, f_path = self._get_commit_and_path()
1200 commit_id, f_path = self._get_commit_and_path()
1188
1201
1189 self._ensure_not_locked()
1202 self._ensure_not_locked()
1190 _branch_name, _sha_commit_id, is_head = \
1203 _branch_name, _sha_commit_id, is_head = \
1191 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1204 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1192
1205
1193 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1206 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1194 self.check_branch_permission(_branch_name, commit_id=commit_id)
1207 self.check_branch_permission(_branch_name, commit_id=commit_id)
1195
1208
1196 c.commit = self._get_commit_or_redirect(commit_id)
1209 c.commit = self._get_commit_or_redirect(commit_id)
1197 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1210 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1198
1211
1199 if c.file.is_binary:
1212 if c.file.is_binary:
1200 files_url = h.route_path(
1213 files_url = h.route_path(
1201 'repo_files',
1214 'repo_files',
1202 repo_name=self.db_repo_name,
1215 repo_name=self.db_repo_name,
1203 commit_id=c.commit.raw_id, f_path=f_path)
1216 commit_id=c.commit.raw_id, f_path=f_path)
1204 raise HTTPFound(files_url)
1217 raise HTTPFound(files_url)
1205
1218
1206 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1219 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1207 c.f_path = f_path
1220 c.f_path = f_path
1208
1221
1209 return self._get_template_context(c)
1222 return self._get_template_context(c)
1210
1223
1211 @LoginRequired()
1224 @LoginRequired()
1212 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1225 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1213 @CSRFRequired()
1226 @CSRFRequired()
1214 @view_config(
1227 @view_config(
1215 route_name='repo_files_update_file', request_method='POST',
1228 route_name='repo_files_update_file', request_method='POST',
1216 renderer=None)
1229 renderer=None)
1217 def repo_files_update_file(self):
1230 def repo_files_update_file(self):
1218 _ = self.request.translate
1231 _ = self.request.translate
1219 c = self.load_default_context()
1232 c = self.load_default_context()
1220 commit_id, f_path = self._get_commit_and_path()
1233 commit_id, f_path = self._get_commit_and_path()
1221
1234
1222 self._ensure_not_locked()
1235 self._ensure_not_locked()
1223
1236
1224 c.commit = self._get_commit_or_redirect(commit_id)
1237 c.commit = self._get_commit_or_redirect(commit_id)
1225 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1238 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1226
1239
1227 if c.file.is_binary:
1240 if c.file.is_binary:
1228 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1241 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1229 commit_id=c.commit.raw_id, f_path=f_path))
1242 commit_id=c.commit.raw_id, f_path=f_path))
1230
1243
1231 _branch_name, _sha_commit_id, is_head = \
1244 _branch_name, _sha_commit_id, is_head = \
1232 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1245 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1233
1246
1234 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1247 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1235 self.check_branch_permission(_branch_name, commit_id=commit_id)
1248 self.check_branch_permission(_branch_name, commit_id=commit_id)
1236
1249
1237 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1250 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1238 c.f_path = f_path
1251 c.f_path = f_path
1239
1252
1240 old_content = c.file.content
1253 old_content = c.file.content
1241 sl = old_content.splitlines(1)
1254 sl = old_content.splitlines(1)
1242 first_line = sl[0] if sl else ''
1255 first_line = sl[0] if sl else ''
1243
1256
1244 r_post = self.request.POST
1257 r_post = self.request.POST
1245 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1258 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1246 line_ending_mode = detect_mode(first_line, 0)
1259 line_ending_mode = detect_mode(first_line, 0)
1247 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1260 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1248
1261
1249 message = r_post.get('message') or c.default_message
1262 message = r_post.get('message') or c.default_message
1250 org_node_path = c.file.unicode_path
1263 org_node_path = c.file.unicode_path
1251 filename = r_post['filename']
1264 filename = r_post['filename']
1252
1265
1253 root_path = c.file.dir_path
1266 root_path = c.file.dir_path
1254 pure_path = self.create_pure_path(root_path, filename)
1267 pure_path = self.create_pure_path(root_path, filename)
1255 node_path = safe_unicode(bytes(pure_path))
1268 node_path = safe_unicode(bytes(pure_path))
1256
1269
1257 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1270 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1258 commit_id=commit_id)
1271 commit_id=commit_id)
1259 if content == old_content and node_path == org_node_path:
1272 if content == old_content and node_path == org_node_path:
1260 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1273 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1261 category='warning')
1274 category='warning')
1262 raise HTTPFound(default_redirect_url)
1275 raise HTTPFound(default_redirect_url)
1263
1276
1264 try:
1277 try:
1265 mapping = {
1278 mapping = {
1266 org_node_path: {
1279 org_node_path: {
1267 'org_filename': org_node_path,
1280 'org_filename': org_node_path,
1268 'filename': node_path,
1281 'filename': node_path,
1269 'content': content,
1282 'content': content,
1270 'lexer': '',
1283 'lexer': '',
1271 'op': 'mod',
1284 'op': 'mod',
1272 'mode': c.file.mode
1285 'mode': c.file.mode
1273 }
1286 }
1274 }
1287 }
1275
1288
1276 commit = ScmModel().update_nodes(
1289 commit = ScmModel().update_nodes(
1277 user=self._rhodecode_db_user.user_id,
1290 user=self._rhodecode_db_user.user_id,
1278 repo=self.db_repo,
1291 repo=self.db_repo,
1279 message=message,
1292 message=message,
1280 nodes=mapping,
1293 nodes=mapping,
1281 parent_commit=c.commit,
1294 parent_commit=c.commit,
1282 )
1295 )
1283
1296
1284 h.flash(_('Successfully committed changes to file `{}`').format(
1297 h.flash(_('Successfully committed changes to file `{}`').format(
1285 h.escape(f_path)), category='success')
1298 h.escape(f_path)), category='success')
1286 default_redirect_url = h.route_path(
1299 default_redirect_url = h.route_path(
1287 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1300 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1288
1301
1289 except Exception:
1302 except Exception:
1290 log.exception('Error occurred during commit')
1303 log.exception('Error occurred during commit')
1291 h.flash(_('Error occurred during commit'), category='error')
1304 h.flash(_('Error occurred during commit'), category='error')
1292
1305
1293 raise HTTPFound(default_redirect_url)
1306 raise HTTPFound(default_redirect_url)
1294
1307
1295 @LoginRequired()
1308 @LoginRequired()
1296 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1309 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1297 @view_config(
1310 @view_config(
1298 route_name='repo_files_add_file', request_method='GET',
1311 route_name='repo_files_add_file', request_method='GET',
1299 renderer='rhodecode:templates/files/files_add.mako')
1312 renderer='rhodecode:templates/files/files_add.mako')
1300 @view_config(
1313 @view_config(
1301 route_name='repo_files_upload_file', request_method='GET',
1314 route_name='repo_files_upload_file', request_method='GET',
1302 renderer='rhodecode:templates/files/files_upload.mako')
1315 renderer='rhodecode:templates/files/files_upload.mako')
1303 def repo_files_add_file(self):
1316 def repo_files_add_file(self):
1304 _ = self.request.translate
1317 _ = self.request.translate
1305 c = self.load_default_context()
1318 c = self.load_default_context()
1306 commit_id, f_path = self._get_commit_and_path()
1319 commit_id, f_path = self._get_commit_and_path()
1307
1320
1308 self._ensure_not_locked()
1321 self._ensure_not_locked()
1309
1322
1310 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1323 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1311 if c.commit is None:
1324 if c.commit is None:
1312 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1325 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1313
1326
1314 if self.rhodecode_vcs_repo.is_empty():
1327 if self.rhodecode_vcs_repo.is_empty():
1315 # for empty repository we cannot check for current branch, we rely on
1328 # for empty repository we cannot check for current branch, we rely on
1316 # c.commit.branch instead
1329 # c.commit.branch instead
1317 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1330 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1318 else:
1331 else:
1319 _branch_name, _sha_commit_id, is_head = \
1332 _branch_name, _sha_commit_id, is_head = \
1320 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1333 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1321
1334
1322 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1335 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1323 self.check_branch_permission(_branch_name, commit_id=commit_id)
1336 self.check_branch_permission(_branch_name, commit_id=commit_id)
1324
1337
1325 c.default_message = (_('Added file via RhodeCode Enterprise'))
1338 c.default_message = (_('Added file via RhodeCode Enterprise'))
1326 c.f_path = f_path.lstrip('/') # ensure not relative path
1339 c.f_path = f_path.lstrip('/') # ensure not relative path
1327
1340
1328 return self._get_template_context(c)
1341 return self._get_template_context(c)
1329
1342
1330 @LoginRequired()
1343 @LoginRequired()
1331 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1344 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1332 @CSRFRequired()
1345 @CSRFRequired()
1333 @view_config(
1346 @view_config(
1334 route_name='repo_files_create_file', request_method='POST',
1347 route_name='repo_files_create_file', request_method='POST',
1335 renderer=None)
1348 renderer=None)
1336 def repo_files_create_file(self):
1349 def repo_files_create_file(self):
1337 _ = self.request.translate
1350 _ = self.request.translate
1338 c = self.load_default_context()
1351 c = self.load_default_context()
1339 commit_id, f_path = self._get_commit_and_path()
1352 commit_id, f_path = self._get_commit_and_path()
1340
1353
1341 self._ensure_not_locked()
1354 self._ensure_not_locked()
1342
1355
1343 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1356 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1344 if c.commit is None:
1357 if c.commit is None:
1345 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1358 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1346
1359
1347 # calculate redirect URL
1360 # calculate redirect URL
1348 if self.rhodecode_vcs_repo.is_empty():
1361 if self.rhodecode_vcs_repo.is_empty():
1349 default_redirect_url = h.route_path(
1362 default_redirect_url = h.route_path(
1350 'repo_summary', repo_name=self.db_repo_name)
1363 'repo_summary', repo_name=self.db_repo_name)
1351 else:
1364 else:
1352 default_redirect_url = h.route_path(
1365 default_redirect_url = h.route_path(
1353 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1366 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1354
1367
1355 if self.rhodecode_vcs_repo.is_empty():
1368 if self.rhodecode_vcs_repo.is_empty():
1356 # for empty repository we cannot check for current branch, we rely on
1369 # for empty repository we cannot check for current branch, we rely on
1357 # c.commit.branch instead
1370 # c.commit.branch instead
1358 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1371 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1359 else:
1372 else:
1360 _branch_name, _sha_commit_id, is_head = \
1373 _branch_name, _sha_commit_id, is_head = \
1361 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1374 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1362
1375
1363 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1376 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1364 self.check_branch_permission(_branch_name, commit_id=commit_id)
1377 self.check_branch_permission(_branch_name, commit_id=commit_id)
1365
1378
1366 c.default_message = (_('Added file via RhodeCode Enterprise'))
1379 c.default_message = (_('Added file via RhodeCode Enterprise'))
1367 c.f_path = f_path
1380 c.f_path = f_path
1368
1381
1369 r_post = self.request.POST
1382 r_post = self.request.POST
1370 message = r_post.get('message') or c.default_message
1383 message = r_post.get('message') or c.default_message
1371 filename = r_post.get('filename')
1384 filename = r_post.get('filename')
1372 unix_mode = 0
1385 unix_mode = 0
1373 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1386 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1374
1387
1375 if not filename:
1388 if not filename:
1376 # If there's no commit, redirect to repo summary
1389 # If there's no commit, redirect to repo summary
1377 if type(c.commit) is EmptyCommit:
1390 if type(c.commit) is EmptyCommit:
1378 redirect_url = h.route_path(
1391 redirect_url = h.route_path(
1379 'repo_summary', repo_name=self.db_repo_name)
1392 'repo_summary', repo_name=self.db_repo_name)
1380 else:
1393 else:
1381 redirect_url = default_redirect_url
1394 redirect_url = default_redirect_url
1382 h.flash(_('No filename specified'), category='warning')
1395 h.flash(_('No filename specified'), category='warning')
1383 raise HTTPFound(redirect_url)
1396 raise HTTPFound(redirect_url)
1384
1397
1385 root_path = f_path
1398 root_path = f_path
1386 pure_path = self.create_pure_path(root_path, filename)
1399 pure_path = self.create_pure_path(root_path, filename)
1387 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1400 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1388
1401
1389 author = self._rhodecode_db_user.full_contact
1402 author = self._rhodecode_db_user.full_contact
1390 nodes = {
1403 nodes = {
1391 node_path: {
1404 node_path: {
1392 'content': content
1405 'content': content
1393 }
1406 }
1394 }
1407 }
1395
1408
1396 try:
1409 try:
1397
1410
1398 commit = ScmModel().create_nodes(
1411 commit = ScmModel().create_nodes(
1399 user=self._rhodecode_db_user.user_id,
1412 user=self._rhodecode_db_user.user_id,
1400 repo=self.db_repo,
1413 repo=self.db_repo,
1401 message=message,
1414 message=message,
1402 nodes=nodes,
1415 nodes=nodes,
1403 parent_commit=c.commit,
1416 parent_commit=c.commit,
1404 author=author,
1417 author=author,
1405 )
1418 )
1406
1419
1407 h.flash(_('Successfully committed new file `{}`').format(
1420 h.flash(_('Successfully committed new file `{}`').format(
1408 h.escape(node_path)), category='success')
1421 h.escape(node_path)), category='success')
1409
1422
1410 default_redirect_url = h.route_path(
1423 default_redirect_url = h.route_path(
1411 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1424 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1412
1425
1413 except NonRelativePathError:
1426 except NonRelativePathError:
1414 log.exception('Non Relative path found')
1427 log.exception('Non Relative path found')
1415 h.flash(_('The location specified must be a relative path and must not '
1428 h.flash(_('The location specified must be a relative path and must not '
1416 'contain .. in the path'), category='warning')
1429 'contain .. in the path'), category='warning')
1417 raise HTTPFound(default_redirect_url)
1430 raise HTTPFound(default_redirect_url)
1418 except (NodeError, NodeAlreadyExistsError) as e:
1431 except (NodeError, NodeAlreadyExistsError) as e:
1419 h.flash(_(h.escape(e)), category='error')
1432 h.flash(_(h.escape(e)), category='error')
1420 except Exception:
1433 except Exception:
1421 log.exception('Error occurred during commit')
1434 log.exception('Error occurred during commit')
1422 h.flash(_('Error occurred during commit'), category='error')
1435 h.flash(_('Error occurred during commit'), category='error')
1423
1436
1424 raise HTTPFound(default_redirect_url)
1437 raise HTTPFound(default_redirect_url)
1425
1438
1426 @LoginRequired()
1439 @LoginRequired()
1427 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1440 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1428 @CSRFRequired()
1441 @CSRFRequired()
1429 @view_config(
1442 @view_config(
1430 route_name='repo_files_upload_file', request_method='POST',
1443 route_name='repo_files_upload_file', request_method='POST',
1431 renderer='json_ext')
1444 renderer='json_ext')
1432 def repo_files_upload_file(self):
1445 def repo_files_upload_file(self):
1433 _ = self.request.translate
1446 _ = self.request.translate
1434 c = self.load_default_context()
1447 c = self.load_default_context()
1435 commit_id, f_path = self._get_commit_and_path()
1448 commit_id, f_path = self._get_commit_and_path()
1436
1449
1437 self._ensure_not_locked()
1450 self._ensure_not_locked()
1438
1451
1439 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1452 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1440 if c.commit is None:
1453 if c.commit is None:
1441 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1454 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1442
1455
1443 # calculate redirect URL
1456 # calculate redirect URL
1444 if self.rhodecode_vcs_repo.is_empty():
1457 if self.rhodecode_vcs_repo.is_empty():
1445 default_redirect_url = h.route_path(
1458 default_redirect_url = h.route_path(
1446 'repo_summary', repo_name=self.db_repo_name)
1459 'repo_summary', repo_name=self.db_repo_name)
1447 else:
1460 else:
1448 default_redirect_url = h.route_path(
1461 default_redirect_url = h.route_path(
1449 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1462 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1450
1463
1451 if self.rhodecode_vcs_repo.is_empty():
1464 if self.rhodecode_vcs_repo.is_empty():
1452 # for empty repository we cannot check for current branch, we rely on
1465 # for empty repository we cannot check for current branch, we rely on
1453 # c.commit.branch instead
1466 # c.commit.branch instead
1454 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1467 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1455 else:
1468 else:
1456 _branch_name, _sha_commit_id, is_head = \
1469 _branch_name, _sha_commit_id, is_head = \
1457 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1470 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1458
1471
1459 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1472 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1460 if error:
1473 if error:
1461 return {
1474 return {
1462 'error': error,
1475 'error': error,
1463 'redirect_url': default_redirect_url
1476 'redirect_url': default_redirect_url
1464 }
1477 }
1465 error = self.check_branch_permission(_branch_name, json_mode=True)
1478 error = self.check_branch_permission(_branch_name, json_mode=True)
1466 if error:
1479 if error:
1467 return {
1480 return {
1468 'error': error,
1481 'error': error,
1469 'redirect_url': default_redirect_url
1482 'redirect_url': default_redirect_url
1470 }
1483 }
1471
1484
1472 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1485 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1473 c.f_path = f_path
1486 c.f_path = f_path
1474
1487
1475 r_post = self.request.POST
1488 r_post = self.request.POST
1476
1489
1477 message = c.default_message
1490 message = c.default_message
1478 user_message = r_post.getall('message')
1491 user_message = r_post.getall('message')
1479 if isinstance(user_message, list) and user_message:
1492 if isinstance(user_message, list) and user_message:
1480 # we take the first from duplicated results if it's not empty
1493 # we take the first from duplicated results if it's not empty
1481 message = user_message[0] if user_message[0] else message
1494 message = user_message[0] if user_message[0] else message
1482
1495
1483 nodes = {}
1496 nodes = {}
1484
1497
1485 for file_obj in r_post.getall('files_upload') or []:
1498 for file_obj in r_post.getall('files_upload') or []:
1486 content = file_obj.file
1499 content = file_obj.file
1487 filename = file_obj.filename
1500 filename = file_obj.filename
1488
1501
1489 root_path = f_path
1502 root_path = f_path
1490 pure_path = self.create_pure_path(root_path, filename)
1503 pure_path = self.create_pure_path(root_path, filename)
1491 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1504 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1492
1505
1493 nodes[node_path] = {
1506 nodes[node_path] = {
1494 'content': content
1507 'content': content
1495 }
1508 }
1496
1509
1497 if not nodes:
1510 if not nodes:
1498 error = 'missing files'
1511 error = 'missing files'
1499 return {
1512 return {
1500 'error': error,
1513 'error': error,
1501 'redirect_url': default_redirect_url
1514 'redirect_url': default_redirect_url
1502 }
1515 }
1503
1516
1504 author = self._rhodecode_db_user.full_contact
1517 author = self._rhodecode_db_user.full_contact
1505
1518
1506 try:
1519 try:
1507 commit = ScmModel().create_nodes(
1520 commit = ScmModel().create_nodes(
1508 user=self._rhodecode_db_user.user_id,
1521 user=self._rhodecode_db_user.user_id,
1509 repo=self.db_repo,
1522 repo=self.db_repo,
1510 message=message,
1523 message=message,
1511 nodes=nodes,
1524 nodes=nodes,
1512 parent_commit=c.commit,
1525 parent_commit=c.commit,
1513 author=author,
1526 author=author,
1514 )
1527 )
1515 if len(nodes) == 1:
1528 if len(nodes) == 1:
1516 flash_message = _('Successfully committed {} new files').format(len(nodes))
1529 flash_message = _('Successfully committed {} new files').format(len(nodes))
1517 else:
1530 else:
1518 flash_message = _('Successfully committed 1 new file')
1531 flash_message = _('Successfully committed 1 new file')
1519
1532
1520 h.flash(flash_message, category='success')
1533 h.flash(flash_message, category='success')
1521
1534
1522 default_redirect_url = h.route_path(
1535 default_redirect_url = h.route_path(
1523 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1536 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1524
1537
1525 except NonRelativePathError:
1538 except NonRelativePathError:
1526 log.exception('Non Relative path found')
1539 log.exception('Non Relative path found')
1527 error = _('The location specified must be a relative path and must not '
1540 error = _('The location specified must be a relative path and must not '
1528 'contain .. in the path')
1541 'contain .. in the path')
1529 h.flash(error, category='warning')
1542 h.flash(error, category='warning')
1530
1543
1531 return {
1544 return {
1532 'error': error,
1545 'error': error,
1533 'redirect_url': default_redirect_url
1546 'redirect_url': default_redirect_url
1534 }
1547 }
1535 except (NodeError, NodeAlreadyExistsError) as e:
1548 except (NodeError, NodeAlreadyExistsError) as e:
1536 error = h.escape(e)
1549 error = h.escape(e)
1537 h.flash(error, category='error')
1550 h.flash(error, category='error')
1538
1551
1539 return {
1552 return {
1540 'error': error,
1553 'error': error,
1541 'redirect_url': default_redirect_url
1554 'redirect_url': default_redirect_url
1542 }
1555 }
1543 except Exception:
1556 except Exception:
1544 log.exception('Error occurred during commit')
1557 log.exception('Error occurred during commit')
1545 error = _('Error occurred during commit')
1558 error = _('Error occurred during commit')
1546 h.flash(error, category='error')
1559 h.flash(error, category='error')
1547 return {
1560 return {
1548 'error': error,
1561 'error': error,
1549 'redirect_url': default_redirect_url
1562 'redirect_url': default_redirect_url
1550 }
1563 }
1551
1564
1552 return {
1565 return {
1553 'error': None,
1566 'error': None,
1554 'redirect_url': default_redirect_url
1567 'redirect_url': default_redirect_url
1555 }
1568 }
@@ -1,81 +1,81 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2015-2019 RhodeCode GmbH
3 # Copyright (C) 2015-2019 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 from dogpile.cache import register_backend
22 from dogpile.cache import register_backend
23
23
24 register_backend(
24 register_backend(
25 "dogpile.cache.rc.memory_lru", "rhodecode.lib.rc_cache.backends",
25 "dogpile.cache.rc.memory_lru", "rhodecode.lib.rc_cache.backends",
26 "LRUMemoryBackend")
26 "LRUMemoryBackend")
27
27
28 register_backend(
28 register_backend(
29 "dogpile.cache.rc.file_namespace", "rhodecode.lib.rc_cache.backends",
29 "dogpile.cache.rc.file_namespace", "rhodecode.lib.rc_cache.backends",
30 "FileNamespaceBackend")
30 "FileNamespaceBackend")
31
31
32 register_backend(
32 register_backend(
33 "dogpile.cache.rc.redis", "rhodecode.lib.rc_cache.backends",
33 "dogpile.cache.rc.redis", "rhodecode.lib.rc_cache.backends",
34 "RedisPickleBackend")
34 "RedisPickleBackend")
35
35
36 register_backend(
36 register_backend(
37 "dogpile.cache.rc.redis_msgpack", "rhodecode.lib.rc_cache.backends",
37 "dogpile.cache.rc.redis_msgpack", "rhodecode.lib.rc_cache.backends",
38 "RedisMsgPackBackend")
38 "RedisMsgPackBackend")
39
39
40
40
41 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
42
42
43 from . import region_meta
43 from . import region_meta
44 from .utils import (
44 from .utils import (
45 get_default_cache_settings, backend_key_generator, get_or_create_region,
45 get_default_cache_settings, backend_key_generator, get_or_create_region,
46 clear_cache_namespace, make_region, InvalidationContext,
46 clear_cache_namespace, make_region, InvalidationContext,
47 FreshRegionCache, ActiveRegionCache)
47 FreshRegionCache, ActiveRegionCache)
48
48
49
49
50 FILE_TREE_CACHE_VER = 'v2'
50 FILE_TREE_CACHE_VER = 'v3'
51
51
52
52
53 def configure_dogpile_cache(settings):
53 def configure_dogpile_cache(settings):
54 cache_dir = settings.get('cache_dir')
54 cache_dir = settings.get('cache_dir')
55 if cache_dir:
55 if cache_dir:
56 region_meta.dogpile_config_defaults['cache_dir'] = cache_dir
56 region_meta.dogpile_config_defaults['cache_dir'] = cache_dir
57
57
58 rc_cache_data = get_default_cache_settings(settings, prefixes=['rc_cache.'])
58 rc_cache_data = get_default_cache_settings(settings, prefixes=['rc_cache.'])
59
59
60 # inspect available namespaces
60 # inspect available namespaces
61 avail_regions = set()
61 avail_regions = set()
62 for key in rc_cache_data.keys():
62 for key in rc_cache_data.keys():
63 namespace_name = key.split('.', 1)[0]
63 namespace_name = key.split('.', 1)[0]
64 avail_regions.add(namespace_name)
64 avail_regions.add(namespace_name)
65 log.debug('dogpile: found following cache regions: %s', avail_regions)
65 log.debug('dogpile: found following cache regions: %s', avail_regions)
66
66
67 # register them into namespace
67 # register them into namespace
68 for region_name in avail_regions:
68 for region_name in avail_regions:
69 new_region = make_region(
69 new_region = make_region(
70 name=region_name,
70 name=region_name,
71 function_key_generator=None
71 function_key_generator=None
72 )
72 )
73
73
74 new_region.configure_from_config(settings, 'rc_cache.{}.'.format(region_name))
74 new_region.configure_from_config(settings, 'rc_cache.{}.'.format(region_name))
75 new_region.function_key_generator = backend_key_generator(new_region.actual_backend)
75 new_region.function_key_generator = backend_key_generator(new_region.actual_backend)
76 log.debug('dogpile: registering a new region %s[%s]', region_name, new_region.__dict__)
76 log.debug('dogpile: registering a new region %s[%s]', region_name, new_region.__dict__)
77 region_meta.dogpile_cache_regions[region_name] = new_region
77 region_meta.dogpile_cache_regions[region_name] = new_region
78
78
79
79
80 def includeme(config):
80 def includeme(config):
81 configure_dogpile_cache(config.registry.settings)
81 configure_dogpile_cache(config.registry.settings)
@@ -1,519 +1,523 b''
1 // # Copyright (C) 2010-2019 RhodeCode GmbH
1 // # Copyright (C) 2010-2019 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 /**
19 /**
20 * Search file list
20 * Search file list
21 */
21 */
22
22
23 var NodeFilter = {};
23 var NodeFilter = {};
24
24
25 var fileBrowserListeners = function (node_list_url, url_base) {
25 var fileBrowserListeners = function (node_list_url, url_base) {
26 var $filterInput = $('#node_filter');
26 var $filterInput = $('#node_filter');
27 var n_filter = $filterInput.get(0);
27 var n_filter = $filterInput.get(0);
28
28
29 NodeFilter.filterTimeout = null;
29 NodeFilter.filterTimeout = null;
30 var nodes = null;
30 var nodes = null;
31
31
32 NodeFilter.focus = function () {
32 NodeFilter.focus = function () {
33 $filterInput.focus()
33 $filterInput.focus()
34 };
34 };
35
35
36 NodeFilter.fetchNodes = function (callback) {
36 NodeFilter.fetchNodes = function (callback) {
37 $.ajax(
37 $.ajax(
38 {url: node_list_url, headers: {'X-PARTIAL-XHR': true}})
38 {url: node_list_url, headers: {'X-PARTIAL-XHR': true}})
39 .done(function (data) {
39 .done(function (data) {
40 nodes = data.nodes;
40 nodes = data.nodes;
41 if (callback) {
41 if (callback) {
42 callback();
42 callback();
43 }
43 }
44 })
44 })
45 .fail(function (data) {
45 .fail(function (data) {
46 console.log('failed to load');
46 console.log('failed to load');
47 });
47 });
48 };
48 };
49
49
50 NodeFilter.initFilter = function (e) {
50 NodeFilter.initFilter = function (e) {
51 if ($filterInput.hasClass('loading')) {
51 if ($filterInput.hasClass('loading')) {
52 return
52 return
53 }
53 }
54
54
55 // in case we are already loaded, do nothing
55 // in case we are already loaded, do nothing
56 if (!$filterInput.hasClass('init')) {
56 if (!$filterInput.hasClass('init')) {
57 return NodeFilter.handleKey(e);
57 return NodeFilter.handleKey(e);
58 }
58 }
59 var iconLoading = 'icon-spin animate-spin';
59 var iconLoading = 'icon-spin animate-spin';
60 var iconSearch = 'icon-search';
60 var iconSearch = 'icon-search';
61 $('.files-filter-box-path i').removeClass(iconSearch).addClass(iconLoading);
61 $('.files-filter-box-path i').removeClass(iconSearch).addClass(iconLoading);
62 $filterInput.addClass('loading');
62 $filterInput.addClass('loading');
63
63
64 var callback = function (org) {
64 var callback = function (org) {
65 return function () {
65 return function () {
66 if ($filterInput.hasClass('init')) {
66 if ($filterInput.hasClass('init')) {
67 $filterInput.removeClass('init');
67 $filterInput.removeClass('init');
68 $filterInput.removeClass('loading');
68 $filterInput.removeClass('loading');
69 }
69 }
70 $('.files-filter-box-path i').removeClass(iconLoading).addClass(iconSearch);
70 $('.files-filter-box-path i').removeClass(iconLoading).addClass(iconSearch);
71
71
72 // auto re-filter if we filled in the input
72 // auto re-filter if we filled in the input
73 if (n_filter.value !== "") {
73 if (n_filter.value !== "") {
74 NodeFilter.updateFilter(n_filter, e)()
74 NodeFilter.updateFilter(n_filter, e)()
75 }
75 }
76
76
77 }
77 }
78 };
78 };
79 // load node data
79 // load node data
80 NodeFilter.fetchNodes(callback());
80 NodeFilter.fetchNodes(callback());
81
81
82 };
82 };
83
83
84 NodeFilter.resetFilter = function () {
84 NodeFilter.resetFilter = function () {
85 $('#tbody').show();
85 $('#tbody').show();
86 $('#tbody_filtered').hide();
86 $('#tbody_filtered').hide();
87 $filterInput.val('');
87 $filterInput.val('');
88 };
88 };
89
89
90 NodeFilter.handleKey = function (e) {
90 NodeFilter.handleKey = function (e) {
91 var scrollDown = function (element) {
91 var scrollDown = function (element) {
92 var elementBottom = element.offset().top + $(element).outerHeight();
92 var elementBottom = element.offset().top + $(element).outerHeight();
93 var windowBottom = window.innerHeight + $(window).scrollTop();
93 var windowBottom = window.innerHeight + $(window).scrollTop();
94 if (elementBottom > windowBottom) {
94 if (elementBottom > windowBottom) {
95 var offset = elementBottom - window.innerHeight;
95 var offset = elementBottom - window.innerHeight;
96 $('html,body').scrollTop(offset);
96 $('html,body').scrollTop(offset);
97 return false;
97 return false;
98 }
98 }
99 return true;
99 return true;
100 };
100 };
101
101
102 var scrollUp = function (element) {
102 var scrollUp = function (element) {
103 if (element.offset().top < $(window).scrollTop()) {
103 if (element.offset().top < $(window).scrollTop()) {
104 $('html,body').scrollTop(element.offset().top);
104 $('html,body').scrollTop(element.offset().top);
105 return false;
105 return false;
106 }
106 }
107 return true;
107 return true;
108 };
108 };
109 var $hlElem = $('.browser-highlight');
109 var $hlElem = $('.browser-highlight');
110
110
111 if (e.keyCode === 40) { // Down
111 if (e.keyCode === 40) { // Down
112 if ($hlElem.length === 0) {
112 if ($hlElem.length === 0) {
113 $('.browser-result').first().addClass('browser-highlight');
113 $('.browser-result').first().addClass('browser-highlight');
114 } else {
114 } else {
115 var next = $hlElem.next();
115 var next = $hlElem.next();
116 if (next.length !== 0) {
116 if (next.length !== 0) {
117 $hlElem.removeClass('browser-highlight');
117 $hlElem.removeClass('browser-highlight');
118 next.addClass('browser-highlight');
118 next.addClass('browser-highlight');
119 }
119 }
120 }
120 }
121
121
122 if ($hlElem.get(0) !== undefined){
122 if ($hlElem.get(0) !== undefined){
123 scrollDown($hlElem);
123 scrollDown($hlElem);
124 }
124 }
125 }
125 }
126 if (e.keyCode === 38) { // Up
126 if (e.keyCode === 38) { // Up
127 e.preventDefault();
127 e.preventDefault();
128 if ($hlElem.length !== 0) {
128 if ($hlElem.length !== 0) {
129 var next = $hlElem.prev();
129 var next = $hlElem.prev();
130 if (next.length !== 0) {
130 if (next.length !== 0) {
131 $('.browser-highlight').removeClass('browser-highlight');
131 $('.browser-highlight').removeClass('browser-highlight');
132 next.addClass('browser-highlight');
132 next.addClass('browser-highlight');
133 }
133 }
134 }
134 }
135
135
136 if ($hlElem.get(0) !== undefined){
136 if ($hlElem.get(0) !== undefined){
137 scrollUp($hlElem);
137 scrollUp($hlElem);
138 }
138 }
139
139
140 }
140 }
141 if (e.keyCode === 13) { // Enter
141 if (e.keyCode === 13) { // Enter
142 if ($('.browser-highlight').length !== 0) {
142 if ($('.browser-highlight').length !== 0) {
143 var url = $('.browser-highlight').find('.match-link').attr('href');
143 var url = $('.browser-highlight').find('.match-link').attr('href');
144 window.location = url;
144 window.location = url;
145 }
145 }
146 }
146 }
147 if (e.keyCode === 27) { // Esc
147 if (e.keyCode === 27) { // Esc
148 NodeFilter.resetFilter();
148 NodeFilter.resetFilter();
149 $('html,body').scrollTop(0);
149 $('html,body').scrollTop(0);
150 }
150 }
151
151
152 var capture_keys = [
152 var capture_keys = [
153 40, // ArrowDown
153 40, // ArrowDown
154 38, // ArrowUp
154 38, // ArrowUp
155 39, // ArrowRight
155 39, // ArrowRight
156 37, // ArrowLeft
156 37, // ArrowLeft
157 13, // Enter
157 13, // Enter
158 27 // Esc
158 27 // Esc
159 ];
159 ];
160
160
161 if ($.inArray(e.keyCode, capture_keys) === -1) {
161 if ($.inArray(e.keyCode, capture_keys) === -1) {
162 clearTimeout(NodeFilter.filterTimeout);
162 clearTimeout(NodeFilter.filterTimeout);
163 NodeFilter.filterTimeout = setTimeout(NodeFilter.updateFilter(n_filter, e), 200);
163 NodeFilter.filterTimeout = setTimeout(NodeFilter.updateFilter(n_filter, e), 200);
164 }
164 }
165
165
166 };
166 };
167
167
168 NodeFilter.fuzzy_match = function (filepath, query) {
168 NodeFilter.fuzzy_match = function (filepath, query) {
169 var highlight = [];
169 var highlight = [];
170 var order = 0;
170 var order = 0;
171 for (var i = 0; i < query.length; i++) {
171 for (var i = 0; i < query.length; i++) {
172 var match_position = filepath.indexOf(query[i]);
172 var match_position = filepath.indexOf(query[i]);
173 if (match_position !== -1) {
173 if (match_position !== -1) {
174 var prev_match_position = highlight[highlight.length - 1];
174 var prev_match_position = highlight[highlight.length - 1];
175 if (prev_match_position === undefined) {
175 if (prev_match_position === undefined) {
176 highlight.push(match_position);
176 highlight.push(match_position);
177 } else {
177 } else {
178 var current_match_position = prev_match_position + match_position + 1;
178 var current_match_position = prev_match_position + match_position + 1;
179 highlight.push(current_match_position);
179 highlight.push(current_match_position);
180 order = order + current_match_position - prev_match_position;
180 order = order + current_match_position - prev_match_position;
181 }
181 }
182 filepath = filepath.substring(match_position + 1);
182 filepath = filepath.substring(match_position + 1);
183 } else {
183 } else {
184 return false;
184 return false;
185 }
185 }
186 }
186 }
187 return {
187 return {
188 'order': order,
188 'order': order,
189 'highlight': highlight
189 'highlight': highlight
190 };
190 };
191 };
191 };
192
192
193 NodeFilter.sortPredicate = function (a, b) {
193 NodeFilter.sortPredicate = function (a, b) {
194 if (a.order < b.order) return -1;
194 if (a.order < b.order) return -1;
195 if (a.order > b.order) return 1;
195 if (a.order > b.order) return 1;
196 if (a.filepath < b.filepath) return -1;
196 if (a.filepath < b.filepath) return -1;
197 if (a.filepath > b.filepath) return 1;
197 if (a.filepath > b.filepath) return 1;
198 return 0;
198 return 0;
199 };
199 };
200
200
201 NodeFilter.updateFilter = function (elem, e) {
201 NodeFilter.updateFilter = function (elem, e) {
202 return function () {
202 return function () {
203 // Reset timeout
203 // Reset timeout
204 NodeFilter.filterTimeout = null;
204 NodeFilter.filterTimeout = null;
205 var query = elem.value.toLowerCase();
205 var query = elem.value.toLowerCase();
206 var match = [];
206 var match = [];
207 var matches_max = 20;
207 var matches_max = 20;
208 if (query !== "") {
208 if (query !== "") {
209 var results = [];
209 var results = [];
210 for (var k = 0; k < nodes.length; k++) {
210 for (var k = 0; k < nodes.length; k++) {
211 var result = NodeFilter.fuzzy_match(
211 var result = NodeFilter.fuzzy_match(
212 nodes[k].name.toLowerCase(), query);
212 nodes[k].name.toLowerCase(), query);
213 if (result) {
213 if (result) {
214 result.type = nodes[k].type;
214 result.type = nodes[k].type;
215 result.filepath = nodes[k].name;
215 result.filepath = nodes[k].name;
216 results.push(result);
216 results.push(result);
217 }
217 }
218 }
218 }
219 results = results.sort(NodeFilter.sortPredicate);
219 results = results.sort(NodeFilter.sortPredicate);
220 var limit = matches_max;
220 var limit = matches_max;
221 if (results.length < matches_max) {
221 if (results.length < matches_max) {
222 limit = results.length;
222 limit = results.length;
223 }
223 }
224 for (var i = 0; i < limit; i++) {
224 for (var i = 0; i < limit; i++) {
225 if (query && results.length > 0) {
225 if (query && results.length > 0) {
226 var n = results[i].filepath;
226 var n = results[i].filepath;
227 var t = results[i].type;
227 var t = results[i].type;
228 var n_hl = n.split("");
228 var n_hl = n.split("");
229 var pos = results[i].highlight;
229 var pos = results[i].highlight;
230 for (var j = 0; j < pos.length; j++) {
230 for (var j = 0; j < pos.length; j++) {
231 n_hl[pos[j]] = "<em>" + n_hl[pos[j]] + "</em>";
231 n_hl[pos[j]] = "<em>" + n_hl[pos[j]] + "</em>";
232 }
232 }
233 n_hl = n_hl.join("");
233 n_hl = n_hl.join("");
234 var new_url = url_base.replace('__FPATH__', n);
234 var new_url = url_base.replace('__FPATH__', n);
235
235
236 var typeObj = {
236 var typeObj = {
237 dir: 'icon-directory browser-dir',
237 dir: 'icon-directory browser-dir',
238 file: 'icon-file-text browser-file'
238 file: 'icon-file-text browser-file'
239 };
239 };
240
240
241 var typeIcon = '<i class="{0}"></i>'.format(typeObj[t]);
241 var typeIcon = '<i class="{0}"></i>'.format(typeObj[t]);
242 match.push('<tr class="browser-result"><td><a class="match-link" href="{0}">{1}{2}</a></td><td colspan="5"></td></tr>'.format(new_url, typeIcon, n_hl));
242 match.push('<tr class="browser-result"><td><a class="match-link" href="{0}">{1}{2}</a></td><td colspan="5"></td></tr>'.format(new_url, typeIcon, n_hl));
243 }
243 }
244 }
244 }
245 if (results.length > limit) {
245 if (results.length > limit) {
246 var truncated_count = results.length - matches_max;
246 var truncated_count = results.length - matches_max;
247 if (truncated_count === 1) {
247 if (truncated_count === 1) {
248 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _gettext('truncated result')));
248 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _gettext('truncated result')));
249 } else {
249 } else {
250 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _gettext('truncated results')));
250 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _gettext('truncated results')));
251 }
251 }
252 }
252 }
253 }
253 }
254 if (query !== "") {
254 if (query !== "") {
255 $('#tbody').hide();
255 $('#tbody').hide();
256 $('#tbody_filtered').show();
256 $('#tbody_filtered').show();
257
257
258 if (match.length === 0) {
258 if (match.length === 0) {
259 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_gettext('No matching files')));
259 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_gettext('No matching files')));
260 }
260 }
261 $('#tbody_filtered').html(match.join(""));
261 $('#tbody_filtered').html(match.join(""));
262 } else {
262 } else {
263 $('#tbody').show();
263 $('#tbody').show();
264 $('#tbody_filtered').hide();
264 $('#tbody_filtered').hide();
265 }
265 }
266
266
267 };
267 };
268 };
268 };
269
269
270 };
270 };
271
271
272 var getIdentNode = function(n){
272 var getIdentNode = function(n){
273 // iterate through nodes until matched interesting node
273 // iterate through nodes until matched interesting node
274 if (typeof n === 'undefined'){
274 if (typeof n === 'undefined'){
275 return -1;
275 return -1;
276 }
276 }
277 if(typeof n.id !== "undefined" && n.id.match('L[0-9]+')){
277 if(typeof n.id !== "undefined" && n.id.match('L[0-9]+')){
278 return n;
278 return n;
279 }
279 }
280 else{
280 else{
281 return getIdentNode(n.parentNode);
281 return getIdentNode(n.parentNode);
282 }
282 }
283 };
283 };
284
284
285 var getSelectionLink = function(e) {
285 var getSelectionLink = function(e) {
286 // get selection from start/to nodes
286 // get selection from start/to nodes
287 if (typeof window.getSelection !== "undefined") {
287 if (typeof window.getSelection !== "undefined") {
288 s = window.getSelection();
288 s = window.getSelection();
289
289
290 from = getIdentNode(s.anchorNode);
290 from = getIdentNode(s.anchorNode);
291 till = getIdentNode(s.focusNode);
291 till = getIdentNode(s.focusNode);
292
292
293 f_int = parseInt(from.id.replace('L',''));
293 f_int = parseInt(from.id.replace('L',''));
294 t_int = parseInt(till.id.replace('L',''));
294 t_int = parseInt(till.id.replace('L',''));
295
295
296 if (f_int > t_int){
296 if (f_int > t_int){
297 // highlight from bottom
297 // highlight from bottom
298 offset = -35;
298 offset = -35;
299 ranges = [t_int,f_int];
299 ranges = [t_int,f_int];
300 }
300 }
301 else{
301 else{
302 // highligth from top
302 // highligth from top
303 offset = 35;
303 offset = 35;
304 ranges = [f_int,t_int];
304 ranges = [f_int,t_int];
305 }
305 }
306 // if we select more than 2 lines
306 // if we select more than 2 lines
307 if (ranges[0] !== ranges[1]){
307 if (ranges[0] !== ranges[1]){
308 if($('#linktt').length === 0){
308 if($('#linktt').length === 0){
309 hl_div = document.createElement('div');
309 hl_div = document.createElement('div');
310 hl_div.id = 'linktt';
310 hl_div.id = 'linktt';
311 }
311 }
312 hl_div.innerHTML = '';
312 hl_div.innerHTML = '';
313
313
314 anchor = '#L'+ranges[0]+'-'+ranges[1];
314 anchor = '#L'+ranges[0]+'-'+ranges[1];
315 var link = document.createElement('a');
315 var link = document.createElement('a');
316 link.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
316 link.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
317 link.innerHTML = _gettext('Selection link');
317 link.innerHTML = _gettext('Selection link');
318 hl_div.appendChild(link);
318 hl_div.appendChild(link);
319 $('#codeblock').append(hl_div);
319 $('#codeblock').append(hl_div);
320
320
321 var xy = $(till).offset();
321 var xy = $(till).offset();
322 $('#linktt').addClass('hl-tip-box tip-box');
322 $('#linktt').addClass('hl-tip-box tip-box');
323 $('#linktt').offset({top: xy.top + offset, left: xy.left});
323 $('#linktt').offset({top: xy.top + offset, left: xy.left});
324 $('#linktt').css('visibility','visible');
324 $('#linktt').css('visibility','visible');
325 }
325 }
326 else{
326 else{
327 $('#linktt').css('visibility','hidden');
327 $('#linktt').css('visibility','hidden');
328 }
328 }
329 }
329 }
330 };
330 };
331
331
332 var getFileState = function() {
332 var getFileState = function() {
333 // relies on a global set filesUrlData
333 // relies on a global set filesUrlData
334 var f_path = filesUrlData['f_path'];
334 var f_path = filesUrlData['f_path'];
335 var commit_id = filesUrlData['commit_id'];
335 var commit_id = filesUrlData['commit_id'];
336
336
337 var url_params = {
337 var url_params = {
338 repo_name: templateContext.repo_name,
338 repo_name: templateContext.repo_name,
339 commit_id: commit_id,
339 commit_id: commit_id,
340 f_path:'__FPATH__'
340 f_path:'__FPATH__'
341 };
341 };
342 if (atRef !== '') {
342 if (atRef !== '') {
343 url_params['at'] = atRef
343 url_params['at'] = atRef
344 }
344 }
345
345
346 var _url_base = pyroutes.url('repo_files', url_params);
346 var _url_base = pyroutes.url('repo_files', url_params);
347 var _node_list_url = pyroutes.url('repo_files_nodelist',
347 var _node_list_url = pyroutes.url('repo_files_nodelist',
348 {repo_name: templateContext.repo_name,
348 {repo_name: templateContext.repo_name,
349 commit_id: commit_id, f_path: f_path});
349 commit_id: commit_id, f_path: f_path});
350
350
351 return {
351 return {
352 f_path: f_path,
352 f_path: f_path,
353 commit_id: commit_id,
353 commit_id: commit_id,
354 node_list_url: _node_list_url,
354 node_list_url: _node_list_url,
355 url_base: _url_base
355 url_base: _url_base
356 };
356 };
357 };
357 };
358
358
359 var getFilesMetadata = function() {
359 var getFilesMetadata = function() {
360 // relies on metadataRequest global state
360 // relies on metadataRequest global state
361 if (metadataRequest && metadataRequest.readyState != 4) {
361 if (metadataRequest && metadataRequest.readyState != 4) {
362 metadataRequest.abort();
362 metadataRequest.abort();
363 }
363 }
364
364
365 if ($('#file-tree-wrapper').hasClass('full-load')) {
365 if ($('#file-tree-wrapper').hasClass('full-load')) {
366 // in case our HTML wrapper has full-load class we don't
366 // in case our HTML wrapper has full-load class we don't
367 // trigger the async load of metadata
367 // trigger the async load of metadata
368 return false;
368 return false;
369 }
369 }
370
370
371 var state = getFileState();
371 var state = getFileState();
372 var url_data = {
372 var url_data = {
373 'repo_name': templateContext.repo_name,
373 'repo_name': templateContext.repo_name,
374 'commit_id': state.commit_id,
374 'commit_id': state.commit_id,
375 'f_path': state.f_path
375 'f_path': state.f_path,
376 };
376 };
377
377
378 if (atRef !== '') {
379 url_data['at'] = atRef
380 }
381
378 var url = pyroutes.url('repo_nodetree_full', url_data);
382 var url = pyroutes.url('repo_nodetree_full', url_data);
379
383
380 metadataRequest = $.ajax({url: url});
384 metadataRequest = $.ajax({url: url});
381
385
382 metadataRequest.done(function(data) {
386 metadataRequest.done(function(data) {
383 $('#file-tree').html(data);
387 $('#file-tree').html(data);
384 timeagoActivate();
388 timeagoActivate();
385 tooltipActivate();
389 tooltipActivate();
386 });
390 });
387 metadataRequest.fail(function (data, textStatus, errorThrown) {
391 metadataRequest.fail(function (data, textStatus, errorThrown) {
388 if (data.status != 0) {
392 if (data.status != 0) {
389 alert("Error while fetching metadata.\nError code {0} ({1}).Please consider reloading the page".format(data.status,data.statusText));
393 alert("Error while fetching metadata.\nError code {0} ({1}).Please consider reloading the page".format(data.status,data.statusText));
390 }
394 }
391 });
395 });
392 };
396 };
393
397
394 // show more authors
398 // show more authors
395 var showAuthors = function(elem, annotate) {
399 var showAuthors = function(elem, annotate) {
396 var state = getFileState('callbacks');
400 var state = getFileState('callbacks');
397
401
398 var url = pyroutes.url('repo_file_authors',
402 var url = pyroutes.url('repo_file_authors',
399 {'repo_name': templateContext.repo_name,
403 {'repo_name': templateContext.repo_name,
400 'commit_id': state.commit_id, 'f_path': state.f_path});
404 'commit_id': state.commit_id, 'f_path': state.f_path});
401
405
402 $.pjax({
406 $.pjax({
403 url: url,
407 url: url,
404 data: 'annotate={0}'.format(annotate),
408 data: 'annotate={0}'.format(annotate),
405 container: '#file_authors',
409 container: '#file_authors',
406 push: false,
410 push: false,
407 timeout: 5000
411 timeout: 5000
408 }).complete(function(){
412 }).complete(function(){
409 $(elem).hide();
413 $(elem).hide();
410 $('#file_authors_title').html(_gettext('All Authors'));
414 $('#file_authors_title').html(_gettext('All Authors'));
411 tooltipActivate();
415 tooltipActivate();
412 })
416 })
413 };
417 };
414
418
415
419
416 (function (mod) {
420 (function (mod) {
417
421
418 if (typeof exports == "object" && typeof module == "object") {
422 if (typeof exports == "object" && typeof module == "object") {
419 // CommonJS
423 // CommonJS
420 module.exports = mod();
424 module.exports = mod();
421 } else {
425 } else {
422 // Plain browser env
426 // Plain browser env
423 (this || window).FileEditor = mod();
427 (this || window).FileEditor = mod();
424 }
428 }
425
429
426 })(function () {
430 })(function () {
427 "use strict";
431 "use strict";
428
432
429 function FileEditor(textAreaElement, options) {
433 function FileEditor(textAreaElement, options) {
430 if (!(this instanceof FileEditor)) {
434 if (!(this instanceof FileEditor)) {
431 return new FileEditor(textAreaElement, options);
435 return new FileEditor(textAreaElement, options);
432 }
436 }
433 // bind the element instance to our Form
437 // bind the element instance to our Form
434 var te = $(textAreaElement).get(0);
438 var te = $(textAreaElement).get(0);
435 if (te !== undefined) {
439 if (te !== undefined) {
436 te.FileEditor = this;
440 te.FileEditor = this;
437 }
441 }
438
442
439 this.modes_select = '#set_mode';
443 this.modes_select = '#set_mode';
440 this.filename_selector = '#filename';
444 this.filename_selector = '#filename';
441 this.commit_btn_selector = '#commit_btn';
445 this.commit_btn_selector = '#commit_btn';
442 this.line_wrap_selector = '#line_wrap';
446 this.line_wrap_selector = '#line_wrap';
443 this.editor_preview_selector = '#editor_preview';
447 this.editor_preview_selector = '#editor_preview';
444
448
445 if (te !== undefined) {
449 if (te !== undefined) {
446 this.cm = initCodeMirror(textAreaElement, null, false);
450 this.cm = initCodeMirror(textAreaElement, null, false);
447 }
451 }
448
452
449 // FUNCTIONS and helpers
453 // FUNCTIONS and helpers
450 var self = this;
454 var self = this;
451
455
452 this.submitHandler = function() {
456 this.submitHandler = function() {
453 $(self.commit_btn_selector).on('click', function(e) {
457 $(self.commit_btn_selector).on('click', function(e) {
454
458
455 var filename = $(self.filename_selector).val();
459 var filename = $(self.filename_selector).val();
456 if (filename === "") {
460 if (filename === "") {
457 alert("Missing filename");
461 alert("Missing filename");
458 e.preventDefault();
462 e.preventDefault();
459 }
463 }
460
464
461 var button = $(this);
465 var button = $(this);
462 if (button.hasClass('clicked')) {
466 if (button.hasClass('clicked')) {
463 button.attr('disabled', true);
467 button.attr('disabled', true);
464 } else {
468 } else {
465 button.addClass('clicked');
469 button.addClass('clicked');
466 }
470 }
467 });
471 });
468 };
472 };
469 this.submitHandler();
473 this.submitHandler();
470
474
471 // on select line wraps change the editor
475 // on select line wraps change the editor
472 this.lineWrapHandler = function () {
476 this.lineWrapHandler = function () {
473 $(self.line_wrap_selector).on('change', function (e) {
477 $(self.line_wrap_selector).on('change', function (e) {
474 var selected = e.currentTarget;
478 var selected = e.currentTarget;
475 var line_wraps = {'on': true, 'off': false}[selected.value];
479 var line_wraps = {'on': true, 'off': false}[selected.value];
476 setCodeMirrorLineWrap(self.cm, line_wraps)
480 setCodeMirrorLineWrap(self.cm, line_wraps)
477 });
481 });
478 };
482 };
479 this.lineWrapHandler();
483 this.lineWrapHandler();
480
484
481
485
482 this.showPreview = function () {
486 this.showPreview = function () {
483
487
484 var _text = self.cm.getValue();
488 var _text = self.cm.getValue();
485 var _file_path = $(self.filename_selector).val();
489 var _file_path = $(self.filename_selector).val();
486 if (_text && _file_path) {
490 if (_text && _file_path) {
487 $('.show-preview').addClass('active');
491 $('.show-preview').addClass('active');
488 $('.show-editor').removeClass('active');
492 $('.show-editor').removeClass('active');
489
493
490 $(self.editor_preview_selector).show();
494 $(self.editor_preview_selector).show();
491 $(self.cm.getWrapperElement()).hide();
495 $(self.cm.getWrapperElement()).hide();
492
496
493
497
494 var post_data = {'text': _text, 'file_path': _file_path, 'csrf_token': CSRF_TOKEN};
498 var post_data = {'text': _text, 'file_path': _file_path, 'csrf_token': CSRF_TOKEN};
495 $(self.editor_preview_selector).html(_gettext('Loading ...'));
499 $(self.editor_preview_selector).html(_gettext('Loading ...'));
496
500
497 var url = pyroutes.url('file_preview');
501 var url = pyroutes.url('file_preview');
498
502
499 ajaxPOST(url, post_data, function (o) {
503 ajaxPOST(url, post_data, function (o) {
500 $(self.editor_preview_selector).html(o);
504 $(self.editor_preview_selector).html(o);
501 })
505 })
502 }
506 }
503
507
504 };
508 };
505
509
506 this.showEditor = function () {
510 this.showEditor = function () {
507 $(self.editor_preview_selector).hide();
511 $(self.editor_preview_selector).hide();
508 $('.show-editor').addClass('active');
512 $('.show-editor').addClass('active');
509 $('.show-preview').removeClass('active');
513 $('.show-preview').removeClass('active');
510
514
511 $(self.cm.getWrapperElement()).show();
515 $(self.cm.getWrapperElement()).show();
512 };
516 };
513
517
514
518
515 }
519 }
516
520
517 return FileEditor;
521 return FileEditor;
518 });
522 });
519
523
@@ -1,44 +1,59 b''
1 <%def name="refs(commit)">
1 <%def name="refs(commit, at_rev=None)">
2 ## Build a cache of refs for selector
2
3 ## Build a cache of refs for selector, based on this the files ref selector gets pre-selected values
3 <script>
4 <script>
4 fileTreeRefs = {
5 fileTreeRefs = {}
6 </script>
5
7
6 }
8 % if h.is_svn(c.rhodecode_repo):
7 </script>
9 ## since SVN doesn't have an commit<->refs association, we simply inject it
10 ## based on our at_rev marker
11 % if at_rev and at_rev.startswith('branches/'):
12 <%
13 commit.branch = at_rev
14 %>
15 % endif
16 % if at_rev and at_rev.startswith('tags/'):
17 <%
18 commit.tags.append(at_rev)
19 %>
20 % endif
21
22 % endif
8
23
9 %if commit.merge:
24 %if commit.merge:
10 <span class="mergetag tag">
25 <span class="mergetag tag">
11 <i class="icon-merge">${_('merge')}</i>
26 <i class="icon-merge">${_('merge')}</i>
12 </span>
27 </span>
13 %endif
28 %endif
14
29
15 %if h.is_hg(c.rhodecode_repo):
30 %if h.is_hg(c.rhodecode_repo):
16 %for book in commit.bookmarks:
31 %for book in commit.bookmarks:
17 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
32 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
18 <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>
33 <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>
19 </span>
34 </span>
20 <script>
35 <script>
21 fileTreeRefs["${book}"] = {raw_id: "${commit.raw_id}", type:"book", text: "${book}"};
36 fileTreeRefs["${book}"] = {raw_id: "${commit.raw_id}", type:"book", text: "${book}"};
22 </script>
37 </script>
23 %endfor
38 %endfor
24 %endif
39 %endif
25
40
26 %for tag in commit.tags:
41 %for tag in commit.tags:
27 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
42 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
28 <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>
43 <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>
29 </span>
44 </span>
30 <script>
45 <script>
31 fileTreeRefs["${tag}"] = {raw_id: "${commit.raw_id}", type:"tag", text: "${tag}"};
46 fileTreeRefs["${tag}"] = {raw_id: "${commit.raw_id}", type:"tag", text: "${tag}"};
32 </script>
47 </script>
33 %endfor
48 %endfor
34
49
35 %if commit.branch:
50 %if commit.branch:
36 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % commit.branch)}">
51 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % commit.branch)}">
37 <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>
52 <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>
38 </span>
53 </span>
39 <script>
54 <script>
40 fileTreeRefs["${commit.branch}"] = {raw_id: "${commit.raw_id}", type:"branch", text: "${commit.branch}"};
55 fileTreeRefs["${commit.branch}"] = {raw_id: "${commit.raw_id}", type:"branch", text: "${commit.branch}"};
41 </script>
56 </script>
42 %endif
57 %endif
43
58
44 </%def>
59 </%def>
@@ -1,372 +1,379 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 ${_('{} Files').format(c.repo_name)}
4 ${_('{} Files').format(c.repo_name)}
5 %if hasattr(c,'file'):
5 %if hasattr(c,'file'):
6 &middot; ${(h.safe_unicode(c.file.path) or '\\')}
6 &middot; ${(h.safe_unicode(c.file.path) or '\\')}
7 %endif
7 %endif
8
8
9 %if c.rhodecode_name:
9 %if c.rhodecode_name:
10 &middot; ${h.branding(c.rhodecode_name)}
10 &middot; ${h.branding(c.rhodecode_name)}
11 %endif
11 %endif
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()">
15 ${_('Files')}
15 ${_('Files')}
16 %if c.file:
16 %if c.file:
17 @ ${h.show_id(c.commit)}
17 @ ${h.show_id(c.commit)}
18 %endif
18 %endif
19 </%def>
19 </%def>
20
20
21 <%def name="menu_bar_nav()">
21 <%def name="menu_bar_nav()">
22 ${self.menu_items(active='repositories')}
22 ${self.menu_items(active='repositories')}
23 </%def>
23 </%def>
24
24
25 <%def name="menu_bar_subnav()">
25 <%def name="menu_bar_subnav()">
26 ${self.repo_menu(active='files')}
26 ${self.repo_menu(active='files')}
27 </%def>
27 </%def>
28
28
29 <%def name="main()">
29 <%def name="main()">
30 <script type="text/javascript">
30 <script type="text/javascript">
31 var fileSourcePage = ${c.file_source_page};
31 var fileSourcePage = ${c.file_source_page};
32 var atRef = '${request.GET.get('at', '')}';
32 var atRef = '${request.GET.get('at', '')}';
33
33
34 // global state for fetching metadata
34 // global state for fetching metadata
35 metadataRequest = null;
35 metadataRequest = null;
36
36
37 // global metadata about URL
37 // global metadata about URL
38 filesUrlData = ${h.files_url_data(request)|n};
38 filesUrlData = ${h.files_url_data(request)|n};
39 </script>
39 </script>
40
40
41 <div>
41 <div>
42 <div>
42 <div>
43 <%include file='files_pjax.mako'/>
43 <%include file='files_pjax.mako'/>
44 </div>
44 </div>
45 </div>
45 </div>
46
46
47 <script type="text/javascript">
47 <script type="text/javascript">
48
48
49 var initFileJS = function () {
49 var initFileJS = function () {
50 var state = getFileState();
50 var state = getFileState();
51
51
52 // select code link event
52 // select code link event
53 $("#hlcode").mouseup(getSelectionLink);
53 $("#hlcode").mouseup(getSelectionLink);
54
54
55 // file history select2 used for history of file, and switch to
55 // file history select2 used for history of file, and switch to
56 var initialCommitData = {
56 var initialCommitData = {
57 at_ref: atRef,
57 at_ref: atRef,
58 id: null,
58 id: null,
59 text: '${c.commit.raw_id}',
59 text: '${c.commit.raw_id}',
60 type: 'sha',
60 type: 'sha',
61 raw_id: '${c.commit.raw_id}',
61 raw_id: '${c.commit.raw_id}',
62 idx: ${c.commit.idx},
62 idx: ${c.commit.idx},
63 files_url: null,
63 files_url: null,
64 };
64 };
65
65
66 // check if we have ref info.
66 // check if we have ref info.
67 var selectedRef = fileTreeRefs[atRef];
67 var selectedRef = fileTreeRefs[atRef];
68 if (selectedRef !== undefined) {
68 if (selectedRef !== undefined) {
69 $.extend(initialCommitData, selectedRef)
69 $.extend(initialCommitData, selectedRef)
70 }
70 }
71
71
72 var loadUrl = pyroutes.url('repo_file_history', {'repo_name': templateContext.repo_name, 'commit_id': state.commit_id,'f_path': state.f_path});
72 var loadUrl = pyroutes.url('repo_file_history', {'repo_name': templateContext.repo_name, 'commit_id': state.commit_id,'f_path': state.f_path});
73 var cacheKey = '__SINGLE_FILE_REFS__';
73 var cacheKey = '__SINGLE_FILE_REFS__';
74 var cachedDataSource = {};
74 var cachedDataSource = {};
75
75
76 var loadRefsData = function (query) {
76 var loadRefsData = function (query) {
77 $.ajax({
77 $.ajax({
78 url: loadUrl,
78 url: loadUrl,
79 data: {},
79 data: {},
80 dataType: 'json',
80 dataType: 'json',
81 type: 'GET',
81 type: 'GET',
82 success: function (data) {
82 success: function (data) {
83 cachedDataSource[cacheKey] = data;
83 cachedDataSource[cacheKey] = data;
84 query.callback({results: data.results});
84 query.callback({results: data.results});
85 }
85 }
86 });
86 });
87 };
87 };
88
88
89 var feedRefsData = function (query, cachedData) {
89 var feedRefsData = function (query, cachedData) {
90 var data = {results: []};
90 var data = {results: []};
91 //filter results
91 //filter results
92 $.each(cachedData.results, function () {
92 $.each(cachedData.results, function () {
93 var section = this.text;
93 var section = this.text;
94 var children = [];
94 var children = [];
95 $.each(this.children, function () {
95 $.each(this.children, function () {
96 if (query.term.length === 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
96 if (query.term.length === 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
97 children.push(this)
97 children.push(this)
98 }
98 }
99 });
99 });
100 data.results.push({
100 data.results.push({
101 'text': section,
101 'text': section,
102 'children': children
102 'children': children
103 })
103 })
104 });
104 });
105
105
106 query.callback(data);
106 query.callback(data);
107 };
107 };
108
108
109 var select2FileHistorySwitcher = function (targetElement, loadUrl, initialData) {
109 var select2FileHistorySwitcher = function (targetElement, loadUrl, initialData) {
110 var formatResult = function (result, container, query) {
110 var formatResult = function (result, container, query) {
111 return formatSelect2SelectionRefs(result);
111 return formatSelect2SelectionRefs(result);
112 };
112 };
113
113
114 var formatSelection = function (data, container) {
114 var formatSelection = function (data, container) {
115 var commit_ref = data;
115 var commit_ref = data;
116
116
117 var tmpl = '';
117 var tmpl = '';
118 if (commit_ref.type === 'sha') {
118 if (commit_ref.type === 'sha') {
119 tmpl = (commit_ref.raw_id || "").substr(0,8);
119 tmpl = (commit_ref.raw_id || "").substr(0,8);
120 } else if (commit_ref.type === 'branch') {
120 } else if (commit_ref.type === 'branch') {
121 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
121 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
122 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
122 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
123 } else if (commit_ref.type === 'tag') {
123 } else if (commit_ref.type === 'tag') {
124 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
124 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
125 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
125 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
126 } else if (commit_ref.type === 'book') {
126 } else if (commit_ref.type === 'book') {
127 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
127 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
128 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
128 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
129 }
129 }
130 var idx = commit_ref.idx || 0;
130 var idx = commit_ref.idx || 0;
131 if (idx !== 0) {
131 if (idx !== 0) {
132 tmpl = tmpl.concat('<span class="select-index-number">r{0}</span>'.format(idx));
132 tmpl = tmpl.concat('<span class="select-index-number">r{0}</span>'.format(idx));
133 }
133 }
134 return tmpl
134 return tmpl
135 };
135 };
136
136
137 $(targetElement).select2({
137 $(targetElement).select2({
138 dropdownAutoWidth: true,
138 dropdownAutoWidth: true,
139 width: "resolve",
139 width: "resolve",
140 containerCssClass: "drop-menu",
140 containerCssClass: "drop-menu",
141 dropdownCssClass: "drop-menu-dropdown",
141 dropdownCssClass: "drop-menu-dropdown",
142 query: function(query) {
142 query: function(query) {
143 var cachedData = cachedDataSource[cacheKey];
143 var cachedData = cachedDataSource[cacheKey];
144 if (cachedData) {
144 if (cachedData) {
145 feedRefsData(query, cachedData)
145 feedRefsData(query, cachedData)
146 } else {
146 } else {
147 loadRefsData(query)
147 loadRefsData(query)
148 }
148 }
149 },
149 },
150 initSelection: function(element, callback) {
150 initSelection: function(element, callback) {
151 callback(initialData);
151 callback(initialData);
152 },
152 },
153 formatResult: formatResult,
153 formatResult: formatResult,
154 formatSelection: formatSelection
154 formatSelection: formatSelection
155 });
155 });
156
156
157 };
157 };
158
158
159 select2FileHistorySwitcher('#file_refs_filter', loadUrl, initialCommitData);
159 select2FileHistorySwitcher('#file_refs_filter', loadUrl, initialCommitData);
160
160
161 // switcher for files
161 $('#file_refs_filter').on('change', function(e) {
162 $('#file_refs_filter').on('change', function(e) {
162 var data = $('#file_refs_filter').select2('data');
163 var data = $('#file_refs_filter').select2('data');
163 var commit_id = data.id;
164 var commit_id = data.id;
165 var params = {
166 'repo_name': templateContext.repo_name,
167 'commit_id': commit_id,
168 'f_path': state.f_path
169 };
170
171 if(data.at_rev !== undefined && data.at_rev !== "") {
172 params['at'] = data.at_rev;
173 }
164
174
165 if ("${c.annotate}" === "True") {
175 if ("${c.annotate}" === "True") {
166 var url = pyroutes.url('repo_files:annotated',
176 var url = pyroutes.url('repo_files:annotated', params);
167 {'repo_name': templateContext.repo_name,
168 'commit_id': commit_id, 'f_path': state.f_path});
169 } else {
177 } else {
170 var url = pyroutes.url('repo_files',
178 var url = pyroutes.url('repo_files', params);
171 {'repo_name': templateContext.repo_name,
172 'commit_id': commit_id, 'f_path': state.f_path});
173 }
179 }
174 window.location = url;
180 window.location = url;
175
181
176 });
182 });
177
183
178 // load file short history
184 // load file short history
179 $('#file_history_overview').on('click', function(e) {
185 $('#file_history_overview').on('click', function(e) {
180 e.preventDefault();
186 e.preventDefault();
181 path = state.f_path;
187 path = state.f_path;
182 if (path.indexOf("#") >= 0) {
188 if (path.indexOf("#") >= 0) {
183 path = path.slice(0, path.indexOf("#"));
189 path = path.slice(0, path.indexOf("#"));
184 }
190 }
185 var url = pyroutes.url('repo_commits_file',
191 var url = pyroutes.url('repo_commits_file',
186 {'repo_name': templateContext.repo_name,
192 {'repo_name': templateContext.repo_name,
187 'commit_id': state.commit_id, 'f_path': path, 'limit': 6});
193 'commit_id': state.commit_id, 'f_path': path, 'limit': 6});
188 $('#file_history_container').show();
194 $('#file_history_container').show();
189 $('#file_history_container').html('<div class="file-history-inner">{0}</div>'.format(_gettext('Loading ...')));
195 $('#file_history_container').html('<div class="file-history-inner">{0}</div>'.format(_gettext('Loading ...')));
190
196
191 $.pjax({
197 $.pjax({
192 url: url,
198 url: url,
193 container: '#file_history_container',
199 container: '#file_history_container',
194 push: false,
200 push: false,
195 timeout: 5000
201 timeout: 5000
196 }).complete(function () {
202 }).complete(function () {
197 tooltipActivate();
203 tooltipActivate();
198 });
204 });
199 });
205 });
200
206
201 };
207 };
202
208
203 var initTreeJS = function () {
209 var initTreeJS = function () {
204 var state = getFileState();
210 var state = getFileState();
205 getFilesMetadata();
211 getFilesMetadata();
206
212
207 // fuzzy file filter
213 // fuzzy file filter
208 fileBrowserListeners(state.node_list_url, state.url_base);
214 fileBrowserListeners(state.node_list_url, state.url_base);
209
215
210 // switch to widget
216 // switch to widget
211 var initialCommitData = {
217 var initialCommitData = {
212 at_ref: atRef,
218 at_ref: atRef,
213 id: null,
219 id: null,
214 text: '${c.commit.raw_id}',
220 text: '${c.commit.raw_id}',
215 type: 'sha',
221 type: 'sha',
216 raw_id: '${c.commit.raw_id}',
222 raw_id: '${c.commit.raw_id}',
217 idx: ${c.commit.idx},
223 idx: ${c.commit.idx},
218 files_url: null,
224 files_url: null,
219 };
225 };
220
226
221 // check if we have ref info.
227 // check if we have ref info.
222 var selectedRef = fileTreeRefs[atRef];
228 var selectedRef = fileTreeRefs[atRef];
223 if (selectedRef !== undefined) {
229 if (selectedRef !== undefined) {
224 $.extend(initialCommitData, selectedRef)
230 $.extend(initialCommitData, selectedRef)
225 }
231 }
226
232
227 var loadUrl = pyroutes.url('repo_refs_data', {'repo_name': templateContext.repo_name});
233 var loadUrl = pyroutes.url('repo_refs_data', {'repo_name': templateContext.repo_name});
228 var cacheKey = '__ALL_FILE_REFS__';
234 var cacheKey = '__ALL_FILE_REFS__';
229 var cachedDataSource = {};
235 var cachedDataSource = {};
230
236
231 var loadRefsData = function (query) {
237 var loadRefsData = function (query) {
232 $.ajax({
238 $.ajax({
233 url: loadUrl,
239 url: loadUrl,
234 data: {},
240 data: {},
235 dataType: 'json',
241 dataType: 'json',
236 type: 'GET',
242 type: 'GET',
237 success: function (data) {
243 success: function (data) {
238 cachedDataSource[cacheKey] = data;
244 cachedDataSource[cacheKey] = data;
239 query.callback({results: data.results});
245 query.callback({results: data.results});
240 }
246 }
241 });
247 });
242 };
248 };
243
249
244 var feedRefsData = function (query, cachedData) {
250 var feedRefsData = function (query, cachedData) {
245 var data = {results: []};
251 var data = {results: []};
246 //filter results
252 //filter results
247 $.each(cachedData.results, function () {
253 $.each(cachedData.results, function () {
248 var section = this.text;
254 var section = this.text;
249 var children = [];
255 var children = [];
250 $.each(this.children, function () {
256 $.each(this.children, function () {
251 if (query.term.length === 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
257 if (query.term.length === 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
252 children.push(this)
258 children.push(this)
253 }
259 }
254 });
260 });
255 data.results.push({
261 data.results.push({
256 'text': section,
262 'text': section,
257 'children': children
263 'children': children
258 })
264 })
259 });
265 });
260
266
261 //push the typed in commit idx
267 //push the typed in commit idx
262 if (!isNaN(query.term)) {
268 if (!isNaN(query.term)) {
263 var files_url = pyroutes.url('repo_files',
269 var files_url = pyroutes.url('repo_files',
264 {'repo_name': templateContext.repo_name,
270 {'repo_name': templateContext.repo_name,
265 'commit_id': query.term, 'f_path': state.f_path});
271 'commit_id': query.term, 'f_path': state.f_path});
266
272
267 data.results.push({
273 data.results.push({
268 'text': _gettext('go to numeric commit'),
274 'text': _gettext('go to numeric commit'),
269 'children': [{
275 'children': [{
270 at_ref: null,
276 at_ref: null,
271 id: null,
277 id: null,
272 text: 'r{0}'.format(query.term),
278 text: 'r{0}'.format(query.term),
273 type: 'sha',
279 type: 'sha',
274 raw_id: query.term,
280 raw_id: query.term,
275 idx: query.term,
281 idx: query.term,
276 files_url: files_url,
282 files_url: files_url,
277 }]
283 }]
278 });
284 });
279 }
285 }
280 query.callback(data);
286 query.callback(data);
281 };
287 };
282
288
283 var select2RefFileSwitcher = function (targetElement, loadUrl, initialData) {
289 var select2RefFileSwitcher = function (targetElement, loadUrl, initialData) {
284 var formatResult = function (result, container, query) {
290 var formatResult = function (result, container, query) {
285 return formatSelect2SelectionRefs(result);
291 return formatSelect2SelectionRefs(result);
286 };
292 };
287
293
288 var formatSelection = function (data, container) {
294 var formatSelection = function (data, container) {
289 var commit_ref = data;
295 var commit_ref = data;
290
296
291 var tmpl = '';
297 var tmpl = '';
292 if (commit_ref.type === 'sha') {
298 if (commit_ref.type === 'sha') {
293 tmpl = (commit_ref.raw_id || "").substr(0,8);
299 tmpl = (commit_ref.raw_id || "").substr(0,8);
294 } else if (commit_ref.type === 'branch') {
300 } else if (commit_ref.type === 'branch') {
295 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
301 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
296 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
302 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
297 } else if (commit_ref.type === 'tag') {
303 } else if (commit_ref.type === 'tag') {
298 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
304 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
299 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
305 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
300 } else if (commit_ref.type === 'book') {
306 } else if (commit_ref.type === 'book') {
301 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
307 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
302 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
308 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
303 }
309 }
304
310
305 var idx = commit_ref.idx || 0;
311 var idx = commit_ref.idx || 0;
306 if (idx !== 0) {
312 if (idx !== 0) {
307 tmpl = tmpl.concat('<span class="select-index-number">r{0}</span>'.format(idx));
313 tmpl = tmpl.concat('<span class="select-index-number">r{0}</span>'.format(idx));
308 }
314 }
309 return tmpl
315 return tmpl
310 };
316 };
311
317
312 $(targetElement).select2({
318 $(targetElement).select2({
313 dropdownAutoWidth: true,
319 dropdownAutoWidth: true,
314 width: "resolve",
320 width: "resolve",
315 containerCssClass: "drop-menu",
321 containerCssClass: "drop-menu",
316 dropdownCssClass: "drop-menu-dropdown",
322 dropdownCssClass: "drop-menu-dropdown",
317 query: function(query) {
323 query: function(query) {
318
324
319 var cachedData = cachedDataSource[cacheKey];
325 var cachedData = cachedDataSource[cacheKey];
320 if (cachedData) {
326 if (cachedData) {
321 feedRefsData(query, cachedData)
327 feedRefsData(query, cachedData)
322 } else {
328 } else {
323 loadRefsData(query)
329 loadRefsData(query)
324 }
330 }
325 },
331 },
326 initSelection: function(element, callback) {
332 initSelection: function(element, callback) {
327 callback(initialData);
333 callback(initialData);
328 },
334 },
329 formatResult: formatResult,
335 formatResult: formatResult,
330 formatSelection: formatSelection
336 formatSelection: formatSelection
331 });
337 });
332
338
333 };
339 };
334
340
335 select2RefFileSwitcher('#refs_filter', loadUrl, initialCommitData);
341 select2RefFileSwitcher('#refs_filter', loadUrl, initialCommitData);
336
342
343 // switcher for file tree
337 $('#refs_filter').on('change', function(e) {
344 $('#refs_filter').on('change', function(e) {
338 var data = $('#refs_filter').select2('data');
345 var data = $('#refs_filter').select2('data');
339 window.location = data.files_url
346 window.location = data.files_url
340 });
347 });
341
348
342 };
349 };
343
350
344 $(document).ready(function() {
351 $(document).ready(function() {
345 timeagoActivate();
352 timeagoActivate();
346 tooltipActivate();
353 tooltipActivate();
347
354
348 if ($('#trimmed_message_box').height() < 50) {
355 if ($('#trimmed_message_box').height() < 50) {
349 $('#message_expand').hide();
356 $('#message_expand').hide();
350 }
357 }
351
358
352 $('#message_expand').on('click', function(e) {
359 $('#message_expand').on('click', function(e) {
353 $('#trimmed_message_box').css('max-height', 'none');
360 $('#trimmed_message_box').css('max-height', 'none');
354 $(this).hide();
361 $(this).hide();
355 });
362 });
356
363
357 if (fileSourcePage) {
364 if (fileSourcePage) {
358 initFileJS()
365 initFileJS()
359 } else {
366 } else {
360 initTreeJS()
367 initTreeJS()
361 }
368 }
362
369
363 var search_GET = "${request.GET.get('search','')}";
370 var search_GET = "${request.GET.get('search','')}";
364 if (search_GET === "1") {
371 if (search_GET === "1") {
365 NodeFilter.initFilter();
372 NodeFilter.initFilter();
366 NodeFilter.focus();
373 NodeFilter.focus();
367 }
374 }
368 });
375 });
369
376
370 </script>
377 </script>
371
378
372 </%def> No newline at end of file
379 </%def>
@@ -1,96 +1,95 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <%
3 <%
4 if request.GET.get('at'):
4 if request.GET.get('at'):
5 query={'at': request.GET.get('at')}
5 query={'at': request.GET.get('at')}
6 else:
6 else:
7 query=None
7 query=None
8 %>
8 %>
9 <div id="file-tree-wrapper" class="browser-body ${('full-load' if c.full_load else '')}">
9 <div id="file-tree-wrapper" class="browser-body ${('full-load' if c.full_load else '')}">
10 <table class="code-browser rctable repo_summary">
10 <table class="code-browser rctable repo_summary">
11 <thead>
11 <thead>
12 <tr>
12 <tr>
13 <th>${_('Name')}</th>
13 <th>${_('Name')}</th>
14 <th>${_('Size')}</th>
14 <th>${_('Size')}</th>
15 <th>${_('Modified')}</th>
15 <th>${_('Modified')}</th>
16 <th>${_('Last Commit')}</th>
16 <th>${_('Last Commit')}</th>
17 <th>${_('Author')}</th>
17 <th>${_('Author')}</th>
18 </tr>
18 </tr>
19 </thead>
19 </thead>
20
20
21 <tbody id="tbody">
21 <tbody id="tbody">
22 <tr>
22 <tr>
23 <td colspan="5">
23 <td colspan="5">
24 ${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.file.path, request.GET.get('at'), limit_items=True)}
24 ${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.file.path, request.GET.get('at'), limit_items=True)}
25 </td>
25 </td>
26 </tr>
26 </tr>
27
27
28 <% has_files = False %>
28 <% has_files = False %>
29 % for cnt,node in enumerate(c.file):
29 % for cnt,node in enumerate(c.file):
30 <% has_files = True %>
30 <% has_files = True %>
31 <tr class="parity${(cnt % 2)}">
31 <tr class="parity${(cnt % 2)}">
32 <td class="td-componentname">
32 <td class="td-componentname">
33 % if node.is_submodule():
33 % if node.is_submodule():
34 <span class="submodule-dir">
34 <span class="submodule-dir">
35 % if node.url.startswith('http://') or node.url.startswith('https://'):
35 % if node.url.startswith('http://') or node.url.startswith('https://'):
36 <a href="${node.url}">
36 <a href="${node.url}">
37 <i class="icon-directory browser-dir"></i>${node.name}
37 <i class="icon-directory browser-dir"></i>${node.name}
38 </a>
38 </a>
39 % else:
39 % else:
40 <i class="icon-directory browser-dir"></i>${node.name}
40 <i class="icon-directory browser-dir"></i>${node.name}
41 % endif
41 % endif
42 </span>
42 </span>
43 % else:
43 % else:
44
45 <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), _query=query)}">
44 <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), _query=query)}">
46 <i class="${('icon-file-text browser-file' if node.is_file() else 'icon-directory browser-dir')}"></i>${node.name}
45 <i class="${('icon-file-text browser-file' if node.is_file() else 'icon-directory browser-dir')}"></i>${node.name}
47 </a>
46 </a>
48 % endif
47 % endif
49 </td>
48 </td>
50 %if node.is_file():
49 %if node.is_file():
51 <td class="td-size" data-attr-name="size">
50 <td class="td-size" data-attr-name="size">
52 % if c.full_load:
51 % if c.full_load:
53 <span data-size="${node.size}">${h.format_byte_size_binary(node.size)}</span>
52 <span data-size="${node.size}">${h.format_byte_size_binary(node.size)}</span>
54 % else:
53 % else:
55 ${_('Loading ...')}
54 ${_('Loading ...')}
56 % endif
55 % endif
57 </td>
56 </td>
58 <td class="td-time" data-attr-name="modified_at">
57 <td class="td-time" data-attr-name="modified_at">
59 % if c.full_load:
58 % if c.full_load:
60 <span data-date="${node.last_commit.date}">${h.age_component(node.last_commit.date)}</span>
59 <span data-date="${node.last_commit.date}">${h.age_component(node.last_commit.date)}</span>
61 % endif
60 % endif
62 </td>
61 </td>
63 <td class="td-hash" data-attr-name="commit_id">
62 <td class="td-hash" data-attr-name="commit_id">
64 % if c.full_load:
63 % if c.full_load:
65 <div class="tooltip-hovercard" data-hovercard-alt="${node.last_commit.message}" data-hovercard-url="${h.route_path('hovercard_repo_commit', repo_name=c.repo_name, commit_id=node.last_commit.raw_id)}">
64 <div class="tooltip-hovercard" data-hovercard-alt="${node.last_commit.message}" data-hovercard-url="${h.route_path('hovercard_repo_commit', repo_name=c.repo_name, commit_id=node.last_commit.raw_id)}">
66 <pre data-commit-id="${node.last_commit.raw_id}">r${node.last_commit.idx}:${node.last_commit.short_id}</pre>
65 <pre data-commit-id="${node.last_commit.raw_id}">r${node.last_commit.idx}:${node.last_commit.short_id}</pre>
67 </div>
66 </div>
68 % endif
67 % endif
69 </td>
68 </td>
70 <td class="td-user" data-attr-name="author">
69 <td class="td-user" data-attr-name="author">
71 % if c.full_load:
70 % if c.full_load:
72 <span data-author="${node.last_commit.author}">${h.gravatar_with_user(request, node.last_commit.author, tooltip=True)|n}</span>
71 <span data-author="${node.last_commit.author}">${h.gravatar_with_user(request, node.last_commit.author, tooltip=True)|n}</span>
73 % endif
72 % endif
74 </td>
73 </td>
75 %else:
74 %else:
76 <td></td>
75 <td></td>
77 <td></td>
76 <td></td>
78 <td></td>
77 <td></td>
79 <td></td>
78 <td></td>
80 %endif
79 %endif
81 </tr>
80 </tr>
82 % endfor
81 % endfor
83
82
84 % if not has_files:
83 % if not has_files:
85 <tr>
84 <tr>
86 <td colspan="5">
85 <td colspan="5">
87 ##empty-dir mostly SVN
86 ##empty-dir mostly SVN
88 &nbsp;
87 &nbsp;
89 </td>
88 </td>
90 </tr>
89 </tr>
91 % endif
90 % endif
92
91
93 </tbody>
92 </tbody>
94 <tbody id="tbody_filtered"></tbody>
93 <tbody id="tbody_filtered"></tbody>
95 </table>
94 </table>
96 </div>
95 </div>
@@ -1,65 +1,65 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="file_base" file="/files/base.mako"/>
2 <%namespace name="file_base" file="/files/base.mako"/>
3
3
4 <div class="summary">
4 <div class="summary">
5 <div class="fieldset">
5 <div class="fieldset">
6 <div class="left-content">
6 <div class="left-content">
7 <%
7 <%
8 rc_user = h.discover_user(c.commit.author_email)
8 rc_user = h.discover_user(c.commit.author_email)
9 %>
9 %>
10 <div class="left-content-avatar">
10 <div class="left-content-avatar">
11 ${base.gravatar(c.file_last_commit.author_email, 30, tooltip=(True if rc_user else False), user=rc_user)}
11 ${base.gravatar(c.file_last_commit.author_email, 30, tooltip=(True if rc_user else False), user=rc_user)}
12 </div>
12 </div>
13
13
14 <div class="left-content-message">
14 <div class="left-content-message">
15 <div class="fieldset collapsable-content no-hide" data-toggle="summary-details">
15 <div class="fieldset collapsable-content no-hide" data-toggle="summary-details">
16 <div class="commit truncate-wrap">${h.urlify_commit_message(h.chop_at_smart(c.commit.message, '\n', suffix_if_chopped='...'), c.repo_name)}</div>
16 <div class="commit truncate-wrap">${h.urlify_commit_message(h.chop_at_smart(c.commit.message, '\n', suffix_if_chopped='...'), c.repo_name)}</div>
17 </div>
17 </div>
18
18
19 <div class="fieldset collapsable-content" data-toggle="summary-details">
19 <div class="fieldset collapsable-content" data-toggle="summary-details">
20 <div class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
20 <div class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
21 </div>
21 </div>
22
22
23 <div class="fieldset" data-toggle="summary-details">
23 <div class="fieldset" data-toggle="summary-details">
24 <div class="" id="file_authors">
24 <div class="" id="file_authors">
25 ## loads single author, or ALL
25 ## loads single author, or ALL
26 <%include file='file_authors_box.mako'/>
26 <%include file='file_authors_box.mako'/>
27 </div>
27 </div>
28 </div>
28 </div>
29 </div>
29 </div>
30
30
31 <div class="fieldset collapsable-content" data-toggle="summary-details">
31 <div class="fieldset collapsable-content" data-toggle="summary-details">
32 <div class="left-label-summary-files">
32 <div class="left-label-summary-files">
33 <p>${_('File last commit')}:</p>
33 <p>${_('File last commit')}:</p>
34 <div class="right-label-summary">
34 <div class="right-label-summary">
35 <code><a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.file_last_commit.raw_id)}">${h.show_id(c.file_last_commit)}</a></code>
35 <code><a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.file_last_commit.raw_id)}">${h.show_id(c.file_last_commit)}</a></code>
36 ${file_base.refs(c.file_last_commit)}
36 ${file_base.refs(c.file_last_commit)}
37 </div>
37 </div>
38 </div>
38 </div>
39 </div>
39 </div>
40 </div>
40 </div>
41
41
42 <div class="right-content">
42 <div class="right-content">
43 <div data-toggle="summary-details">
43 <div data-toggle="summary-details">
44 <div class="tags tags-main">
44 <div class="tags tags-main">
45 <code>
45 <code>
46 <a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">${h.show_id(c.commit)}</a>
46 <a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">${h.show_id(c.commit)}</a>
47 </code>
47 </code>
48 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.commit.raw_id}" title="${_('Copy the full commit id')}"></i>
48 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.commit.raw_id}" title="${_('Copy the full commit id')}"></i>
49 ${file_base.refs(c.commit)}
49 ${file_base.refs(c.commit, request.GET.get('at'))}
50 </div>
50 </div>
51 </div>
51 </div>
52 </div>
52 </div>
53
53
54 <div class="clear-fix"></div>
54 <div class="clear-fix"></div>
55
55
56 <div class="btn-collapse" data-toggle="summary-details">
56 <div class="btn-collapse" data-toggle="summary-details">
57 ${_('Show More')}
57 ${_('Show More')}
58 </div>
58 </div>
59
59
60 </div>
60 </div>
61 </div>
61 </div>
62
62
63 <script>
63 <script>
64 collapsableContent();
64 collapsableContent();
65 </script>
65 </script>
@@ -1,49 +1,50 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="file_base" file="/files/base.mako"/>
2 <%namespace name="file_base" file="/files/base.mako"/>
3
3
4 <div class="summary">
4 <div class="summary">
5 <div class="fieldset">
5 <div class="fieldset">
6 <div class="left-content">
6 <div class="left-content">
7 <%
7 <%
8 rc_user = h.discover_user(c.commit.author_email)
8 rc_user = h.discover_user(c.commit.author_email)
9 %>
9 %>
10 <div class="left-content-avatar">
10 <div class="left-content-avatar">
11 ${base.gravatar(c.commit.author_email, 30, tooltip=(True if rc_user else False), user=rc_user)}
11 ${base.gravatar(c.commit.author_email, 30, tooltip=(True if rc_user else False), user=rc_user)}
12 </div>
12 </div>
13
13
14 <div class="left-content-message">
14 <div class="left-content-message">
15 <div class="fieldset collapsable-content no-hide" data-toggle="summary-details">
15 <div class="fieldset collapsable-content no-hide" data-toggle="summary-details">
16 <div class="commit truncate-wrap">${h.urlify_commit_message(h.chop_at_smart(c.commit.message, '\n', suffix_if_chopped='...'), c.repo_name)}</div>
16 <div class="commit truncate-wrap">${h.urlify_commit_message(h.chop_at_smart(c.commit.message, '\n', suffix_if_chopped='...'), c.repo_name)}</div>
17 </div>
17 </div>
18
18
19 <div class="fieldset collapsable-content" data-toggle="summary-details">
19 <div class="fieldset collapsable-content" data-toggle="summary-details">
20 <div class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
20 <div class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
21 </div>
21 </div>
22
22
23 <div class="fieldset clear-fix">
23 <div class="fieldset clear-fix">
24 <span class="commit-author">${h.link_to_user(c.commit.author)}</span><span class="commit-date"> - ${h.age_component(c.commit.date)}</span>
24 <span class="commit-author">${h.link_to_user(c.commit.author)}</span><span class="commit-date"> - ${h.age_component(c.commit.date)}</span>
25 </div>
25 </div>
26 </div>
26 </div>
27 </div>
27 </div>
28
28
29 <div class="right-content">
29 <div class="right-content">
30 <div class="tags">
30 <div class="tags">
31 <code>
31 <code>
32 <a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">${h.show_id(c.commit)}</a>
32 <a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">${h.show_id(c.commit)}</a>
33 </code>
33 </code>
34
34
35 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.commit.raw_id}" title="${_('Copy the full commit id')}"></i>
35 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.commit.raw_id}" title="${_('Copy the full commit id')}"></i>
36 ${file_base.refs(c.commit)}
36 ${file_base.refs(c.commit, request.GET.get('at'))}
37
37 </div>
38 </div>
38 </div>
39 </div>
39
40
40 <div class="clear-fix"></div>
41 <div class="clear-fix"></div>
41
42
42 <div class="btn-collapse" data-toggle="summary-details">
43 <div class="btn-collapse" data-toggle="summary-details">
43 ${_('Show More')}
44 ${_('Show More')}
44 </div>
45 </div>
45 </div>
46 </div>
46 </div>
47 </div>
47 <script>
48 <script>
48 collapsableContent();
49 collapsableContent();
49 </script>
50 </script>
General Comments 0
You need to be logged in to leave comments. Login now