##// END OF EJS Templates
file-browser: fixes #4083
marcink -
r422:a5bc4f35 default
parent child Browse files
Show More
@@ -0,0 +1,60 b''
1 <div class="browser-body">
2 <table class="code-browser rctable">
3 <thead>
4 <tr>
5 <th>${_('Name')}</th>
6 <th>${_('Size')}</th>
7 <th>${_('Modified')}</th>
8 <th>${_('Last Commit')}</th>
9 <th>${_('Author')}</th>
10 </tr>
11 </thead>
12
13 <tbody id="tbody">
14 %if c.file.parent:
15 <tr class="parity0">
16 <td class="td-componentname">
17 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.file.parent.path)}" class="pjax-link">
18 <i class="icon-folder"></i>..
19 </a>
20 </td>
21 <td></td>
22 <td></td>
23 <td></td>
24 <td></td>
25 </tr>
26 %endif
27 %for cnt,node in enumerate(c.file):
28 <tr class="parity${cnt%2}">
29 <td class="td-componentname">
30 %if node.is_submodule():
31 <span class="submodule-dir">
32 ${h.link_to_if(
33 node.url.startswith('http://') or node.url.startswith('https://'),
34 node.name,node.url)}
35 </span>
36 %else:
37 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=h.safe_unicode(node.path))}" class="pjax-link">
38 <i class="${'icon-file browser-file' if node.is_file() else 'icon-folder browser-dir'}"></i>${node.name}
39 </a>
40 %endif
41 </td>
42 %if node.is_file():
43 <td class="td-size" title="${'size-%s' % node.name}"></td>
44 <td class="td-time" title="${'modified_at-%s' % node.name}">
45 <span class="browser-loading">${_('Loading...')}</span>
46 </td>
47 <td class="td-hash" title="${'revision-%s' % node.name}"></td>
48 <td class="td-user" title="${'author-%s' % node.name}"></td>
49 %else:
50 <td></td>
51 <td></td>
52 <td></td>
53 <td></td>
54 %endif
55 </tr>
56 %endfor
57 </tbody>
58 <tbody id="tbody_filtered"></tbody>
59 </table>
60 </div> No newline at end of file
@@ -1,1114 +1,1114 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Files controller for RhodeCode Enterprise
23 23 """
24 24
25 25 import itertools
26 26 import logging
27 27 import os
28 28 import shutil
29 29 import tempfile
30 30
31 31 from pylons import request, response, tmpl_context as c, url
32 32 from pylons.i18n.translation import _
33 33 from pylons.controllers.util import redirect
34 34 from webob.exc import HTTPNotFound, HTTPBadRequest
35 35
36 36 from rhodecode.controllers.utils import parse_path_ref
37 37 from rhodecode.lib import diffs, helpers as h, caches
38 38 from rhodecode.lib.compat import OrderedDict
39 39 from rhodecode.lib.utils import jsonify, action_logger
40 40 from rhodecode.lib.utils2 import (
41 41 convert_line_endings, detect_mode, safe_str, str2bool)
42 42 from rhodecode.lib.auth import (
43 43 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired, XHRRequired)
44 44 from rhodecode.lib.base import BaseRepoController, render
45 45 from rhodecode.lib.vcs import path as vcspath
46 46 from rhodecode.lib.vcs.backends.base import EmptyCommit
47 47 from rhodecode.lib.vcs.conf import settings
48 48 from rhodecode.lib.vcs.exceptions import (
49 49 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
50 50 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
51 51 NodeDoesNotExistError, CommitError, NodeError)
52 52 from rhodecode.lib.vcs.nodes import FileNode
53 53
54 54 from rhodecode.model.repo import RepoModel
55 55 from rhodecode.model.scm import ScmModel
56 56 from rhodecode.model.db import Repository
57 57
58 58 from rhodecode.controllers.changeset import (
59 59 _ignorews_url, _context_url, get_line_ctx, get_ignore_ws)
60 60 from rhodecode.lib.exceptions import NonRelativePathError
61 61
62 62 log = logging.getLogger(__name__)
63 63
64 64
65 65 class FilesController(BaseRepoController):
66 66
67 67 def __before__(self):
68 68 super(FilesController, self).__before__()
69 69 c.cut_off_limit = self.cut_off_limit_file
70 70
71 71 def _get_default_encoding(self):
72 72 enc_list = getattr(c, 'default_encodings', [])
73 73 return enc_list[0] if enc_list else 'UTF-8'
74 74
75 75 def __get_commit_or_redirect(self, commit_id, repo_name,
76 76 redirect_after=True):
77 77 """
78 78 This is a safe way to get commit. If an error occurs it redirects to
79 79 tip with proper message
80 80
81 81 :param commit_id: id of commit to fetch
82 82 :param repo_name: repo name to redirect after
83 83 :param redirect_after: toggle redirection
84 84 """
85 85 try:
86 86 return c.rhodecode_repo.get_commit(commit_id)
87 87 except EmptyRepositoryError:
88 88 if not redirect_after:
89 89 return None
90 90 url_ = url('files_add_home',
91 91 repo_name=c.repo_name,
92 92 revision=0, f_path='', anchor='edit')
93 93 if h.HasRepoPermissionAny(
94 94 'repository.write', 'repository.admin')(c.repo_name):
95 95 add_new = h.link_to(
96 96 _('Click here to add a new file.'),
97 97 url_, class_="alert-link")
98 98 else:
99 99 add_new = ""
100 100 h.flash(h.literal(
101 101 _('There are no files yet. %s') % add_new), category='warning')
102 102 redirect(h.url('summary_home', repo_name=repo_name))
103 103 except (CommitDoesNotExistError, LookupError):
104 104 msg = _('No such commit exists for this repository')
105 105 h.flash(msg, category='error')
106 106 raise HTTPNotFound()
107 107 except RepositoryError as e:
108 108 h.flash(safe_str(e), category='error')
109 109 raise HTTPNotFound()
110 110
111 111 def __get_filenode_or_redirect(self, repo_name, commit, path):
112 112 """
113 113 Returns file_node, if error occurs or given path is directory,
114 114 it'll redirect to top level path
115 115
116 116 :param repo_name: repo_name
117 117 :param commit: given commit
118 118 :param path: path to lookup
119 119 """
120 120 try:
121 121 file_node = commit.get_node(path)
122 122 if file_node.is_dir():
123 123 raise RepositoryError('The given path is a directory')
124 124 except CommitDoesNotExistError:
125 125 msg = _('No such commit exists for this repository')
126 126 log.exception(msg)
127 127 h.flash(msg, category='error')
128 128 raise HTTPNotFound()
129 129 except RepositoryError as e:
130 130 h.flash(safe_str(e), category='error')
131 131 raise HTTPNotFound()
132 132
133 133 return file_node
134 134
135 135 def __get_tree_cache_manager(self, repo_name, namespace_type):
136 136 _namespace = caches.get_repo_namespace_key(namespace_type, repo_name)
137 137 return caches.get_cache_manager('repo_cache_long', _namespace)
138 138
139 139 def _get_tree_at_commit(self, repo_name, commit_id, f_path):
140 140 def _cached_tree():
141 141 log.debug('Generating cached file tree for %s, %s, %s',
142 142 repo_name, commit_id, f_path)
143 return render('files/files_browser.html')
143 return render('files/files_browser_tree.html')
144 144
145 145 cache_manager = self.__get_tree_cache_manager(
146 146 repo_name, caches.FILE_TREE)
147 147
148 148 cache_key = caches.compute_key_from_params(
149 149 repo_name, commit_id, f_path)
150 150
151 151 return cache_manager.get(cache_key, createfunc=_cached_tree)
152 152
153 153 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
154 154 def _cached_nodes():
155 155 log.debug('Generating cached nodelist for %s, %s, %s',
156 156 repo_name, commit_id, f_path)
157 157 _d, _f = ScmModel().get_nodes(
158 158 repo_name, commit_id, f_path, flat=False)
159 159 return _d + _f
160 160
161 161 cache_manager = self.__get_tree_cache_manager(
162 162 repo_name, caches.FILE_SEARCH_TREE_META)
163 163
164 164 cache_key = caches.compute_key_from_params(
165 165 repo_name, commit_id, f_path)
166 166 return cache_manager.get(cache_key, createfunc=_cached_nodes)
167 167
168 168 def _get_metadata_at_commit(self, repo_name, commit, dir_node):
169 169 def _cached_metadata():
170 170 log.debug('Generating cached metadata for %s, %s, %s',
171 171 repo_name, commit.raw_id, safe_str(dir_node.path))
172 172
173 173 data = ScmModel().get_dirnode_metadata(commit, dir_node)
174 174 return data
175 175
176 176 cache_manager = self.__get_tree_cache_manager(
177 177 repo_name, caches.FILE_TREE_META)
178 178
179 179 cache_key = caches.compute_key_from_params(
180 180 repo_name, commit.raw_id, safe_str(dir_node.path))
181 181
182 182 return cache_manager.get(cache_key, createfunc=_cached_metadata)
183 183
184 184 @LoginRequired()
185 185 @HasRepoPermissionAnyDecorator(
186 186 'repository.read', 'repository.write', 'repository.admin')
187 187 def index(
188 188 self, repo_name, revision, f_path, annotate=False, rendered=False):
189 189 commit_id = revision
190 190
191 191 # redirect to given commit_id from form if given
192 192 get_commit_id = request.GET.get('at_rev', None)
193 193 if get_commit_id:
194 194 self.__get_commit_or_redirect(get_commit_id, repo_name)
195 195
196 196 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
197 197 c.branch = request.GET.get('branch', None)
198 198 c.f_path = f_path
199 199 c.annotate = annotate
200 200 # default is false, but .rst/.md files later are autorendered, we can
201 201 # overwrite autorendering by setting this GET flag
202 202 c.renderer = rendered or not request.GET.get('no-render', False)
203 203
204 204 # prev link
205 205 try:
206 206 prev_commit = c.commit.prev(c.branch)
207 207 c.prev_commit = prev_commit
208 208 c.url_prev = url('files_home', repo_name=c.repo_name,
209 209 revision=prev_commit.raw_id, f_path=f_path)
210 210 if c.branch:
211 211 c.url_prev += '?branch=%s' % c.branch
212 212 except (CommitDoesNotExistError, VCSError):
213 213 c.url_prev = '#'
214 214 c.prev_commit = EmptyCommit()
215 215
216 216 # next link
217 217 try:
218 218 next_commit = c.commit.next(c.branch)
219 219 c.next_commit = next_commit
220 220 c.url_next = url('files_home', repo_name=c.repo_name,
221 221 revision=next_commit.raw_id, f_path=f_path)
222 222 if c.branch:
223 223 c.url_next += '?branch=%s' % c.branch
224 224 except (CommitDoesNotExistError, VCSError):
225 225 c.url_next = '#'
226 226 c.next_commit = EmptyCommit()
227 227
228 228 # files or dirs
229 229 try:
230 230 c.file = c.commit.get_node(f_path)
231 231 c.file_author = True
232 232 c.file_tree = ''
233 233 if c.file.is_file():
234 234 c.renderer = (
235 235 c.renderer and h.renderer_from_filename(c.file.path))
236 236 c.file_last_commit = c.file.last_commit
237 237
238 238 c.on_branch_head = self._is_valid_head(
239 239 commit_id, c.rhodecode_repo)
240 240 c.branch_or_raw_id = c.commit.branch or c.commit.raw_id
241 241
242 242 author = c.file_last_commit.author
243 243 c.authors = [(h.email(author),
244 244 h.person(author, 'username_or_name_or_email'))]
245 245 else:
246 246 c.authors = []
247 247 c.file_tree = self._get_tree_at_commit(
248 248 repo_name, c.commit.raw_id, f_path)
249 249 except RepositoryError as e:
250 250 h.flash(safe_str(e), category='error')
251 251 raise HTTPNotFound()
252 252
253 253 if request.environ.get('HTTP_X_PJAX'):
254 254 return render('files/files_pjax.html')
255 255
256 256 return render('files/files.html')
257 257
258 258 @LoginRequired()
259 259 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
260 260 'repository.admin')
261 261 @jsonify
262 262 def history(self, repo_name, revision, f_path):
263 263 commit = self.__get_commit_or_redirect(revision, repo_name)
264 264 f_path = f_path
265 265 _file = commit.get_node(f_path)
266 266 if _file.is_file():
267 267 file_history, _hist = self._get_node_history(commit, f_path)
268 268
269 269 res = []
270 270 for obj in file_history:
271 271 res.append({
272 272 'text': obj[1],
273 273 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
274 274 })
275 275
276 276 data = {
277 277 'more': False,
278 278 'results': res
279 279 }
280 280 return data
281 281
282 282 @LoginRequired()
283 283 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
284 284 'repository.admin')
285 285 def authors(self, repo_name, revision, f_path):
286 286 commit = self.__get_commit_or_redirect(revision, repo_name)
287 287 file_node = commit.get_node(f_path)
288 288 if file_node.is_file():
289 289 c.file_last_commit = file_node.last_commit
290 290 if request.GET.get('annotate') == '1':
291 291 # use _hist from annotation if annotation mode is on
292 292 commit_ids = set(x[1] for x in file_node.annotate)
293 293 _hist = (
294 294 c.rhodecode_repo.get_commit(commit_id)
295 295 for commit_id in commit_ids)
296 296 else:
297 297 _f_history, _hist = self._get_node_history(commit, f_path)
298 298 c.file_author = False
299 299 c.authors = []
300 300 for author in set(commit.author for commit in _hist):
301 301 c.authors.append((
302 302 h.email(author),
303 303 h.person(author, 'username_or_name_or_email')))
304 304 return render('files/file_authors_box.html')
305 305
306 306 @LoginRequired()
307 307 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
308 308 'repository.admin')
309 309 def rawfile(self, repo_name, revision, f_path):
310 310 """
311 311 Action for download as raw
312 312 """
313 313 commit = self.__get_commit_or_redirect(revision, repo_name)
314 314 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
315 315
316 316 response.content_disposition = 'attachment; filename=%s' % \
317 317 safe_str(f_path.split(Repository.NAME_SEP)[-1])
318 318
319 319 response.content_type = file_node.mimetype
320 320 charset = self._get_default_encoding()
321 321 if charset:
322 322 response.charset = charset
323 323
324 324 return file_node.content
325 325
326 326 @LoginRequired()
327 327 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
328 328 'repository.admin')
329 329 def raw(self, repo_name, revision, f_path):
330 330 """
331 331 Action for show as raw, some mimetypes are "rendered",
332 332 those include images, icons.
333 333 """
334 334 commit = self.__get_commit_or_redirect(revision, repo_name)
335 335 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
336 336
337 337 raw_mimetype_mapping = {
338 338 # map original mimetype to a mimetype used for "show as raw"
339 339 # you can also provide a content-disposition to override the
340 340 # default "attachment" disposition.
341 341 # orig_type: (new_type, new_dispo)
342 342
343 343 # show images inline:
344 344 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
345 345 # for example render an SVG with javascript inside or even render
346 346 # HTML.
347 347 'image/x-icon': ('image/x-icon', 'inline'),
348 348 'image/png': ('image/png', 'inline'),
349 349 'image/gif': ('image/gif', 'inline'),
350 350 'image/jpeg': ('image/jpeg', 'inline'),
351 351 }
352 352
353 353 mimetype = file_node.mimetype
354 354 try:
355 355 mimetype, dispo = raw_mimetype_mapping[mimetype]
356 356 except KeyError:
357 357 # we don't know anything special about this, handle it safely
358 358 if file_node.is_binary:
359 359 # do same as download raw for binary files
360 360 mimetype, dispo = 'application/octet-stream', 'attachment'
361 361 else:
362 362 # do not just use the original mimetype, but force text/plain,
363 363 # otherwise it would serve text/html and that might be unsafe.
364 364 # Note: underlying vcs library fakes text/plain mimetype if the
365 365 # mimetype can not be determined and it thinks it is not
366 366 # binary.This might lead to erroneous text display in some
367 367 # cases, but helps in other cases, like with text files
368 368 # without extension.
369 369 mimetype, dispo = 'text/plain', 'inline'
370 370
371 371 if dispo == 'attachment':
372 372 dispo = 'attachment; filename=%s' % safe_str(
373 373 f_path.split(os.sep)[-1])
374 374
375 375 response.content_disposition = dispo
376 376 response.content_type = mimetype
377 377 charset = self._get_default_encoding()
378 378 if charset:
379 379 response.charset = charset
380 380 return file_node.content
381 381
382 382 @CSRFRequired()
383 383 @LoginRequired()
384 384 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
385 385 def delete(self, repo_name, revision, f_path):
386 386 commit_id = revision
387 387
388 388 repo = c.rhodecode_db_repo
389 389 if repo.enable_locking and repo.locked[0]:
390 390 h.flash(_('This repository has been locked by %s on %s')
391 391 % (h.person_by_id(repo.locked[0]),
392 392 h.format_date(h.time_to_datetime(repo.locked[1]))),
393 393 'warning')
394 394 return redirect(h.url('files_home',
395 395 repo_name=repo_name, revision='tip'))
396 396
397 397 if not self._is_valid_head(commit_id, repo.scm_instance()):
398 398 h.flash(_('You can only delete files with revision '
399 399 'being a valid branch '), category='warning')
400 400 return redirect(h.url('files_home',
401 401 repo_name=repo_name, revision='tip',
402 402 f_path=f_path))
403 403
404 404 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
405 405 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
406 406
407 407 c.default_message = _(
408 408 'Deleted file %s via RhodeCode Enterprise') % (f_path)
409 409 c.f_path = f_path
410 410 node_path = f_path
411 411 author = c.rhodecode_user.full_contact
412 412 message = request.POST.get('message') or c.default_message
413 413 try:
414 414 nodes = {
415 415 node_path: {
416 416 'content': ''
417 417 }
418 418 }
419 419 self.scm_model.delete_nodes(
420 420 user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
421 421 message=message,
422 422 nodes=nodes,
423 423 parent_commit=c.commit,
424 424 author=author,
425 425 )
426 426
427 427 h.flash(_('Successfully deleted file %s') % f_path,
428 428 category='success')
429 429 except Exception:
430 430 msg = _('Error occurred during commit')
431 431 log.exception(msg)
432 432 h.flash(msg, category='error')
433 433 return redirect(url('changeset_home',
434 434 repo_name=c.repo_name, revision='tip'))
435 435
436 436 @LoginRequired()
437 437 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
438 438 def delete_home(self, repo_name, revision, f_path):
439 439 commit_id = revision
440 440
441 441 repo = c.rhodecode_db_repo
442 442 if repo.enable_locking and repo.locked[0]:
443 443 h.flash(_('This repository has been locked by %s on %s')
444 444 % (h.person_by_id(repo.locked[0]),
445 445 h.format_date(h.time_to_datetime(repo.locked[1]))),
446 446 'warning')
447 447 return redirect(h.url('files_home',
448 448 repo_name=repo_name, revision='tip'))
449 449
450 450 if not self._is_valid_head(commit_id, repo.scm_instance()):
451 451 h.flash(_('You can only delete files with revision '
452 452 'being a valid branch '), category='warning')
453 453 return redirect(h.url('files_home',
454 454 repo_name=repo_name, revision='tip',
455 455 f_path=f_path))
456 456
457 457 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
458 458 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
459 459
460 460 c.default_message = _(
461 461 'Deleted file %s via RhodeCode Enterprise') % (f_path)
462 462 c.f_path = f_path
463 463
464 464 return render('files/files_delete.html')
465 465
466 466 @CSRFRequired()
467 467 @LoginRequired()
468 468 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
469 469 def edit(self, repo_name, revision, f_path):
470 470 commit_id = revision
471 471
472 472 repo = c.rhodecode_db_repo
473 473 if repo.enable_locking and repo.locked[0]:
474 474 h.flash(_('This repository has been locked by %s on %s')
475 475 % (h.person_by_id(repo.locked[0]),
476 476 h.format_date(h.time_to_datetime(repo.locked[1]))),
477 477 'warning')
478 478 return redirect(h.url('files_home',
479 479 repo_name=repo_name, revision='tip'))
480 480
481 481 if not self._is_valid_head(commit_id, repo.scm_instance()):
482 482 h.flash(_('You can only edit files with revision '
483 483 'being a valid branch '), category='warning')
484 484 return redirect(h.url('files_home',
485 485 repo_name=repo_name, revision='tip',
486 486 f_path=f_path))
487 487
488 488 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
489 489 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
490 490
491 491 if c.file.is_binary:
492 492 return redirect(url('files_home', repo_name=c.repo_name,
493 493 revision=c.commit.raw_id, f_path=f_path))
494 494 c.default_message = _(
495 495 'Edited file %s via RhodeCode Enterprise') % (f_path)
496 496 c.f_path = f_path
497 497 old_content = c.file.content
498 498 sl = old_content.splitlines(1)
499 499 first_line = sl[0] if sl else ''
500 500
501 501 # modes: 0 - Unix, 1 - Mac, 2 - DOS
502 502 mode = detect_mode(first_line, 0)
503 503 content = convert_line_endings(request.POST.get('content', ''), mode)
504 504
505 505 message = request.POST.get('message') or c.default_message
506 506 org_f_path = c.file.unicode_path
507 507 filename = request.POST['filename']
508 508 org_filename = c.file.name
509 509
510 510 if content == old_content and filename == org_filename:
511 511 h.flash(_('No changes'), category='warning')
512 512 return redirect(url('changeset_home', repo_name=c.repo_name,
513 513 revision='tip'))
514 514 try:
515 515 mapping = {
516 516 org_f_path: {
517 517 'org_filename': org_f_path,
518 518 'filename': os.path.join(c.file.dir_path, filename),
519 519 'content': content,
520 520 'lexer': '',
521 521 'op': 'mod',
522 522 }
523 523 }
524 524
525 525 ScmModel().update_nodes(
526 526 user=c.rhodecode_user.user_id,
527 527 repo=c.rhodecode_db_repo,
528 528 message=message,
529 529 nodes=mapping,
530 530 parent_commit=c.commit,
531 531 )
532 532
533 533 h.flash(_('Successfully committed to %s') % f_path,
534 534 category='success')
535 535 except Exception:
536 536 msg = _('Error occurred during commit')
537 537 log.exception(msg)
538 538 h.flash(msg, category='error')
539 539 return redirect(url('changeset_home',
540 540 repo_name=c.repo_name, revision='tip'))
541 541
542 542 @LoginRequired()
543 543 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
544 544 def edit_home(self, repo_name, revision, f_path):
545 545 commit_id = revision
546 546
547 547 repo = c.rhodecode_db_repo
548 548 if repo.enable_locking and repo.locked[0]:
549 549 h.flash(_('This repository has been locked by %s on %s')
550 550 % (h.person_by_id(repo.locked[0]),
551 551 h.format_date(h.time_to_datetime(repo.locked[1]))),
552 552 'warning')
553 553 return redirect(h.url('files_home',
554 554 repo_name=repo_name, revision='tip'))
555 555
556 556 if not self._is_valid_head(commit_id, repo.scm_instance()):
557 557 h.flash(_('You can only edit files with revision '
558 558 'being a valid branch '), category='warning')
559 559 return redirect(h.url('files_home',
560 560 repo_name=repo_name, revision='tip',
561 561 f_path=f_path))
562 562
563 563 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
564 564 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
565 565
566 566 if c.file.is_binary:
567 567 return redirect(url('files_home', repo_name=c.repo_name,
568 568 revision=c.commit.raw_id, f_path=f_path))
569 569 c.default_message = _(
570 570 'Edited file %s via RhodeCode Enterprise') % (f_path)
571 571 c.f_path = f_path
572 572
573 573 return render('files/files_edit.html')
574 574
575 575 def _is_valid_head(self, commit_id, repo):
576 576 # check if commit is a branch identifier- basically we cannot
577 577 # create multiple heads via file editing
578 578 valid_heads = repo.branches.keys() + repo.branches.values()
579 579
580 580 if h.is_svn(repo) and not repo.is_empty():
581 581 # Note: Subversion only has one head, we add it here in case there
582 582 # is no branch matched.
583 583 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
584 584
585 585 # check if commit is a branch name or branch hash
586 586 return commit_id in valid_heads
587 587
588 588 @CSRFRequired()
589 589 @LoginRequired()
590 590 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
591 591 def add(self, repo_name, revision, f_path):
592 592 repo = Repository.get_by_repo_name(repo_name)
593 593 if repo.enable_locking and repo.locked[0]:
594 594 h.flash(_('This repository has been locked by %s on %s')
595 595 % (h.person_by_id(repo.locked[0]),
596 596 h.format_date(h.time_to_datetime(repo.locked[1]))),
597 597 'warning')
598 598 return redirect(h.url('files_home',
599 599 repo_name=repo_name, revision='tip'))
600 600
601 601 r_post = request.POST
602 602
603 603 c.commit = self.__get_commit_or_redirect(
604 604 revision, repo_name, redirect_after=False)
605 605 if c.commit is None:
606 606 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
607 607 c.default_message = (_('Added file via RhodeCode Enterprise'))
608 608 c.f_path = f_path
609 609 unix_mode = 0
610 610 content = convert_line_endings(r_post.get('content', ''), unix_mode)
611 611
612 612 message = r_post.get('message') or c.default_message
613 613 filename = r_post.get('filename')
614 614 location = r_post.get('location', '') # dir location
615 615 file_obj = r_post.get('upload_file', None)
616 616
617 617 if file_obj is not None and hasattr(file_obj, 'filename'):
618 618 filename = file_obj.filename
619 619 content = file_obj.file
620 620
621 621 if hasattr(content, 'file'):
622 622 # non posix systems store real file under file attr
623 623 content = content.file
624 624
625 625 # If there's no commit, redirect to repo summary
626 626 if type(c.commit) is EmptyCommit:
627 627 redirect_url = "summary_home"
628 628 else:
629 629 redirect_url = "changeset_home"
630 630
631 631 if not filename:
632 632 h.flash(_('No filename'), category='warning')
633 633 return redirect(url(redirect_url, repo_name=c.repo_name,
634 634 revision='tip'))
635 635
636 636 # extract the location from filename,
637 637 # allows using foo/bar.txt syntax to create subdirectories
638 638 subdir_loc = filename.rsplit('/', 1)
639 639 if len(subdir_loc) == 2:
640 640 location = os.path.join(location, subdir_loc[0])
641 641
642 642 # strip all crap out of file, just leave the basename
643 643 filename = os.path.basename(filename)
644 644 node_path = os.path.join(location, filename)
645 645 author = c.rhodecode_user.full_contact
646 646
647 647 try:
648 648 nodes = {
649 649 node_path: {
650 650 'content': content
651 651 }
652 652 }
653 653 self.scm_model.create_nodes(
654 654 user=c.rhodecode_user.user_id,
655 655 repo=c.rhodecode_db_repo,
656 656 message=message,
657 657 nodes=nodes,
658 658 parent_commit=c.commit,
659 659 author=author,
660 660 )
661 661
662 662 h.flash(_('Successfully committed to %s') % node_path,
663 663 category='success')
664 664 except NonRelativePathError as e:
665 665 h.flash(_(
666 666 'The location specified must be a relative path and must not '
667 667 'contain .. in the path'), category='warning')
668 668 return redirect(url('changeset_home', repo_name=c.repo_name,
669 669 revision='tip'))
670 670 except (NodeError, NodeAlreadyExistsError) as e:
671 671 h.flash(_(e), category='error')
672 672 except Exception:
673 673 msg = _('Error occurred during commit')
674 674 log.exception(msg)
675 675 h.flash(msg, category='error')
676 676 return redirect(url('changeset_home',
677 677 repo_name=c.repo_name, revision='tip'))
678 678
679 679 @LoginRequired()
680 680 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
681 681 def add_home(self, repo_name, revision, f_path):
682 682
683 683 repo = Repository.get_by_repo_name(repo_name)
684 684 if repo.enable_locking and repo.locked[0]:
685 685 h.flash(_('This repository has been locked by %s on %s')
686 686 % (h.person_by_id(repo.locked[0]),
687 687 h.format_date(h.time_to_datetime(repo.locked[1]))),
688 688 'warning')
689 689 return redirect(h.url('files_home',
690 690 repo_name=repo_name, revision='tip'))
691 691
692 692 c.commit = self.__get_commit_or_redirect(
693 693 revision, repo_name, redirect_after=False)
694 694 if c.commit is None:
695 695 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
696 696 c.default_message = (_('Added file via RhodeCode Enterprise'))
697 697 c.f_path = f_path
698 698
699 699 return render('files/files_add.html')
700 700
701 701 @LoginRequired()
702 702 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
703 703 'repository.admin')
704 704 def archivefile(self, repo_name, fname):
705 705 fileformat = None
706 706 commit_id = None
707 707 ext = None
708 708 subrepos = request.GET.get('subrepos') == 'true'
709 709
710 710 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
711 711 archive_spec = fname.split(ext_data[1])
712 712 if len(archive_spec) == 2 and archive_spec[1] == '':
713 713 fileformat = a_type or ext_data[1]
714 714 commit_id = archive_spec[0]
715 715 ext = ext_data[1]
716 716
717 717 dbrepo = RepoModel().get_by_repo_name(repo_name)
718 718 if not dbrepo.enable_downloads:
719 719 return _('Downloads disabled')
720 720
721 721 try:
722 722 commit = c.rhodecode_repo.get_commit(commit_id)
723 723 content_type = settings.ARCHIVE_SPECS[fileformat][0]
724 724 except CommitDoesNotExistError:
725 725 return _('Unknown revision %s') % commit_id
726 726 except EmptyRepositoryError:
727 727 return _('Empty repository')
728 728 except KeyError:
729 729 return _('Unknown archive type')
730 730
731 731 # archive cache
732 732 from rhodecode import CONFIG
733 733
734 734 archive_name = '%s-%s%s%s' % (
735 735 safe_str(repo_name.replace('/', '_')),
736 736 '-sub' if subrepos else '',
737 737 safe_str(commit.short_id), ext)
738 738
739 739 use_cached_archive = False
740 740 archive_cache_enabled = CONFIG.get(
741 741 'archive_cache_dir') and not request.GET.get('no_cache')
742 742
743 743 if archive_cache_enabled:
744 744 # check if we it's ok to write
745 745 if not os.path.isdir(CONFIG['archive_cache_dir']):
746 746 os.makedirs(CONFIG['archive_cache_dir'])
747 747 cached_archive_path = os.path.join(
748 748 CONFIG['archive_cache_dir'], archive_name)
749 749 if os.path.isfile(cached_archive_path):
750 750 log.debug('Found cached archive in %s', cached_archive_path)
751 751 fd, archive = None, cached_archive_path
752 752 use_cached_archive = True
753 753 else:
754 754 log.debug('Archive %s is not yet cached', archive_name)
755 755
756 756 if not use_cached_archive:
757 757 # generate new archive
758 758 fd, archive = tempfile.mkstemp()
759 759 log.debug('Creating new temp archive in %s' % (archive,))
760 760 try:
761 761 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
762 762 except ImproperArchiveTypeError:
763 763 return _('Unknown archive type')
764 764 if archive_cache_enabled:
765 765 # if we generated the archive and we have cache enabled
766 766 # let's use this for future
767 767 log.debug('Storing new archive in %s' % (cached_archive_path,))
768 768 shutil.move(archive, cached_archive_path)
769 769 archive = cached_archive_path
770 770
771 771 def get_chunked_archive(archive):
772 772 with open(archive, 'rb') as stream:
773 773 while True:
774 774 data = stream.read(16 * 1024)
775 775 if not data:
776 776 if fd: # fd means we used temporary file
777 777 os.close(fd)
778 778 if not archive_cache_enabled:
779 779 log.debug('Destroying temp archive %s', archive)
780 780 os.remove(archive)
781 781 break
782 782 yield data
783 783
784 784 # store download action
785 785 action_logger(user=c.rhodecode_user,
786 786 action='user_downloaded_archive:%s' % archive_name,
787 787 repo=repo_name, ipaddr=self.ip_addr, commit=True)
788 788 response.content_disposition = str(
789 789 'attachment; filename=%s' % archive_name)
790 790 response.content_type = str(content_type)
791 791
792 792 return get_chunked_archive(archive)
793 793
794 794 @LoginRequired()
795 795 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
796 796 'repository.admin')
797 797 def diff(self, repo_name, f_path):
798 798 ignore_whitespace = request.GET.get('ignorews') == '1'
799 799 line_context = request.GET.get('context', 3)
800 800 diff1 = request.GET.get('diff1', '')
801 801
802 802 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
803 803
804 804 diff2 = request.GET.get('diff2', '')
805 805 c.action = request.GET.get('diff')
806 806 c.no_changes = diff1 == diff2
807 807 c.f_path = f_path
808 808 c.big_diff = False
809 809 c.ignorews_url = _ignorews_url
810 810 c.context_url = _context_url
811 811 c.changes = OrderedDict()
812 812 c.changes[diff2] = []
813 813
814 814 if not any((diff1, diff2)):
815 815 h.flash(
816 816 'Need query parameter "diff1" or "diff2" to generate a diff.',
817 817 category='error')
818 818 raise HTTPBadRequest()
819 819
820 820 # special case if we want a show commit_id only, it's impl here
821 821 # to reduce JS and callbacks
822 822
823 823 if request.GET.get('show_rev') and diff1:
824 824 if str2bool(request.GET.get('annotate', 'False')):
825 825 _url = url('files_annotate_home', repo_name=c.repo_name,
826 826 revision=diff1, f_path=path1)
827 827 else:
828 828 _url = url('files_home', repo_name=c.repo_name,
829 829 revision=diff1, f_path=path1)
830 830
831 831 return redirect(_url)
832 832
833 833 try:
834 834 node1 = self._get_file_node(diff1, path1)
835 835 node2 = self._get_file_node(diff2, f_path)
836 836 except (RepositoryError, NodeError):
837 837 log.exception("Exception while trying to get node from repository")
838 838 return redirect(url(
839 839 'files_home', repo_name=c.repo_name, f_path=f_path))
840 840
841 841 if all(isinstance(node.commit, EmptyCommit)
842 842 for node in (node1, node2)):
843 843 raise HTTPNotFound
844 844
845 845 c.commit_1 = node1.commit
846 846 c.commit_2 = node2.commit
847 847
848 848 if c.action == 'download':
849 849 _diff = diffs.get_gitdiff(node1, node2,
850 850 ignore_whitespace=ignore_whitespace,
851 851 context=line_context)
852 852 diff = diffs.DiffProcessor(_diff, format='gitdiff')
853 853
854 854 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
855 855 response.content_type = 'text/plain'
856 856 response.content_disposition = (
857 857 'attachment; filename=%s' % (diff_name,)
858 858 )
859 859 charset = self._get_default_encoding()
860 860 if charset:
861 861 response.charset = charset
862 862 return diff.as_raw()
863 863
864 864 elif c.action == 'raw':
865 865 _diff = diffs.get_gitdiff(node1, node2,
866 866 ignore_whitespace=ignore_whitespace,
867 867 context=line_context)
868 868 diff = diffs.DiffProcessor(_diff, format='gitdiff')
869 869 response.content_type = 'text/plain'
870 870 charset = self._get_default_encoding()
871 871 if charset:
872 872 response.charset = charset
873 873 return diff.as_raw()
874 874
875 875 else:
876 876 fid = h.FID(diff2, node2.path)
877 877 line_context_lcl = get_line_ctx(fid, request.GET)
878 878 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
879 879
880 880 __, commit1, commit2, diff, st, data = diffs.wrapped_diff(
881 881 filenode_old=node1,
882 882 filenode_new=node2,
883 883 diff_limit=self.cut_off_limit_diff,
884 884 file_limit=self.cut_off_limit_file,
885 885 show_full_diff=request.GET.get('fulldiff'),
886 886 ignore_whitespace=ign_whitespace_lcl,
887 887 line_context=line_context_lcl,)
888 888
889 889 c.lines_added = data['stats']['added'] if data else 0
890 890 c.lines_deleted = data['stats']['deleted'] if data else 0
891 891 c.files = [data]
892 892 c.commit_ranges = [c.commit_1, c.commit_2]
893 893 c.ancestor = None
894 894 c.statuses = []
895 895 c.target_repo = c.rhodecode_db_repo
896 896 c.filename1 = node1.path
897 897 c.filename = node2.path
898 898 c.binary_file = node1.is_binary or node2.is_binary
899 899 operation = data['operation'] if data else ''
900 900
901 901 commit_changes = {
902 902 # TODO: it's passing the old file to the diff to keep the
903 903 # standard but this is not being used for this template,
904 904 # but might need both files in the future or a more standard
905 905 # way to work with that
906 906 'fid': [commit1, commit2, operation,
907 907 c.filename, diff, st, data]
908 908 }
909 909
910 910 c.changes = commit_changes
911 911
912 912 return render('files/file_diff.html')
913 913
914 914 @LoginRequired()
915 915 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
916 916 'repository.admin')
917 917 def diff_2way(self, repo_name, f_path):
918 918 diff1 = request.GET.get('diff1', '')
919 919 diff2 = request.GET.get('diff2', '')
920 920
921 921 nodes = []
922 922 unknown_commits = []
923 923 for commit in [diff1, diff2]:
924 924 try:
925 925 nodes.append(self._get_file_node(commit, f_path))
926 926 except (RepositoryError, NodeError):
927 927 log.exception('%(commit)s does not exist' % {'commit': commit})
928 928 unknown_commits.append(commit)
929 929 h.flash(h.literal(
930 930 _('Commit %(commit)s does not exist.') % {'commit': commit}
931 931 ), category='error')
932 932
933 933 if unknown_commits:
934 934 return redirect(url('files_home', repo_name=c.repo_name,
935 935 f_path=f_path))
936 936
937 937 if all(isinstance(node.commit, EmptyCommit) for node in nodes):
938 938 raise HTTPNotFound
939 939
940 940 node1, node2 = nodes
941 941
942 942 f_gitdiff = diffs.get_gitdiff(node1, node2, ignore_whitespace=False)
943 943 diff_processor = diffs.DiffProcessor(f_gitdiff, format='gitdiff')
944 944 diff_data = diff_processor.prepare()
945 945
946 946 if not diff_data or diff_data[0]['raw_diff'] == '':
947 947 h.flash(h.literal(_('%(file_path)s has not changed '
948 948 'between %(commit_1)s and %(commit_2)s.') % {
949 949 'file_path': f_path,
950 950 'commit_1': node1.commit.id,
951 951 'commit_2': node2.commit.id
952 952 }), category='error')
953 953 return redirect(url('files_home', repo_name=c.repo_name,
954 954 f_path=f_path))
955 955
956 956 c.diff_data = diff_data[0]
957 957 c.FID = h.FID(diff2, node2.path)
958 958 # cleanup some unneeded data
959 959 del c.diff_data['raw_diff']
960 960 del c.diff_data['chunks']
961 961
962 962 c.node1 = node1
963 963 c.commit_1 = node1.commit
964 964 c.node2 = node2
965 965 c.commit_2 = node2.commit
966 966
967 967 return render('files/diff_2way.html')
968 968
969 969 def _get_file_node(self, commit_id, f_path):
970 970 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
971 971 commit = c.rhodecode_repo.get_commit(commit_id=commit_id)
972 972 try:
973 973 node = commit.get_node(f_path)
974 974 if node.is_dir():
975 975 raise NodeError('%s path is a %s not a file'
976 976 % (node, type(node)))
977 977 except NodeDoesNotExistError:
978 978 commit = EmptyCommit(
979 979 commit_id=commit_id,
980 980 idx=commit.idx,
981 981 repo=commit.repository,
982 982 alias=commit.repository.alias,
983 983 message=commit.message,
984 984 author=commit.author,
985 985 date=commit.date)
986 986 node = FileNode(f_path, '', commit=commit)
987 987 else:
988 988 commit = EmptyCommit(
989 989 repo=c.rhodecode_repo,
990 990 alias=c.rhodecode_repo.alias)
991 991 node = FileNode(f_path, '', commit=commit)
992 992 return node
993 993
994 994 def _get_node_history(self, commit, f_path, commits=None):
995 995 """
996 996 get commit history for given node
997 997
998 998 :param commit: commit to calculate history
999 999 :param f_path: path for node to calculate history for
1000 1000 :param commits: if passed don't calculate history and take
1001 1001 commits defined in this list
1002 1002 """
1003 1003 # calculate history based on tip
1004 1004 tip = c.rhodecode_repo.get_commit()
1005 1005 if commits is None:
1006 1006 pre_load = ["author", "branch"]
1007 1007 try:
1008 1008 commits = tip.get_file_history(f_path, pre_load=pre_load)
1009 1009 except (NodeDoesNotExistError, CommitError):
1010 1010 # this node is not present at tip!
1011 1011 commits = commit.get_file_history(f_path, pre_load=pre_load)
1012 1012
1013 1013 history = []
1014 1014 commits_group = ([], _("Changesets"))
1015 1015 for commit in commits:
1016 1016 branch = ' (%s)' % commit.branch if commit.branch else ''
1017 1017 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
1018 1018 commits_group[0].append((commit.raw_id, n_desc,))
1019 1019 history.append(commits_group)
1020 1020
1021 1021 symbolic_reference = self._symbolic_reference
1022 1022
1023 1023 if c.rhodecode_repo.alias == 'svn':
1024 1024 adjusted_f_path = self._adjust_file_path_for_svn(
1025 1025 f_path, c.rhodecode_repo)
1026 1026 if adjusted_f_path != f_path:
1027 1027 log.debug(
1028 1028 'Recognized svn tag or branch in file "%s", using svn '
1029 1029 'specific symbolic references', f_path)
1030 1030 f_path = adjusted_f_path
1031 1031 symbolic_reference = self._symbolic_reference_svn
1032 1032
1033 1033 branches = self._create_references(
1034 1034 c.rhodecode_repo.branches, symbolic_reference, f_path)
1035 1035 branches_group = (branches, _("Branches"))
1036 1036
1037 1037 tags = self._create_references(
1038 1038 c.rhodecode_repo.tags, symbolic_reference, f_path)
1039 1039 tags_group = (tags, _("Tags"))
1040 1040
1041 1041 history.append(branches_group)
1042 1042 history.append(tags_group)
1043 1043
1044 1044 return history, commits
1045 1045
1046 1046 def _adjust_file_path_for_svn(self, f_path, repo):
1047 1047 """
1048 1048 Computes the relative path of `f_path`.
1049 1049
1050 1050 This is mainly based on prefix matching of the recognized tags and
1051 1051 branches in the underlying repository.
1052 1052 """
1053 1053 tags_and_branches = itertools.chain(
1054 1054 repo.branches.iterkeys(),
1055 1055 repo.tags.iterkeys())
1056 1056 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
1057 1057
1058 1058 for name in tags_and_branches:
1059 1059 if f_path.startswith(name + '/'):
1060 1060 f_path = vcspath.relpath(f_path, name)
1061 1061 break
1062 1062 return f_path
1063 1063
1064 1064 def _create_references(
1065 1065 self, branches_or_tags, symbolic_reference, f_path):
1066 1066 items = []
1067 1067 for name, commit_id in branches_or_tags.items():
1068 1068 sym_ref = symbolic_reference(commit_id, name, f_path)
1069 1069 items.append((sym_ref, name))
1070 1070 return items
1071 1071
1072 1072 def _symbolic_reference(self, commit_id, name, f_path):
1073 1073 return commit_id
1074 1074
1075 1075 def _symbolic_reference_svn(self, commit_id, name, f_path):
1076 1076 new_f_path = vcspath.join(name, f_path)
1077 1077 return u'%s@%s' % (new_f_path, commit_id)
1078 1078
1079 1079 @LoginRequired()
1080 1080 @XHRRequired()
1081 1081 @HasRepoPermissionAnyDecorator(
1082 1082 'repository.read', 'repository.write', 'repository.admin')
1083 1083 @jsonify
1084 1084 def nodelist(self, repo_name, revision, f_path):
1085 1085 commit = self.__get_commit_or_redirect(revision, repo_name)
1086 1086
1087 1087 metadata = self._get_nodelist_at_commit(
1088 1088 repo_name, commit.raw_id, f_path)
1089 1089 return {'nodes': metadata}
1090 1090
1091 1091 @LoginRequired()
1092 1092 @XHRRequired()
1093 1093 @HasRepoPermissionAnyDecorator(
1094 1094 'repository.read', 'repository.write', 'repository.admin')
1095 1095 @jsonify
1096 1096 def metadata_list(self, repo_name, revision, f_path):
1097 1097 """
1098 1098 Returns a json dict that contains commit date, author, revision
1099 1099 and id for the specified repo, revision and file path
1100 1100
1101 1101 :param repo_name: name of the repository
1102 1102 :param revision: revision of files
1103 1103 :param f_path: file path of the requested directory
1104 1104 """
1105 1105
1106 1106 commit = self.__get_commit_or_redirect(revision, repo_name)
1107 1107 try:
1108 1108 file_node = commit.get_node(f_path)
1109 1109 except RepositoryError as e:
1110 1110 return {'error': safe_str(e)}
1111 1111
1112 1112 metadata = self._get_metadata_at_commit(
1113 1113 repo_name, commit, file_node)
1114 1114 return {'metadata': metadata}
@@ -1,109 +1,51 b''
1 1
2 2 <div id="codeblock" class="browserblock">
3 3 <div class="browser-header">
4 4 <div class="browser-nav">
5 5 ${h.form(h.url.current(), method='GET', id='at_rev_form')}
6 6 <div class="info_box">
7 7 ${h.hidden('refs_filter')}
8 8 <div class="info_box_elem previous">
9 9 <a id="prev_commit_link" data-commit-id="${c.prev_commit.raw_id}" class="pjax-link ${'disabled' if c.url_prev == '#' else ''}" href="${c.url_prev}" title="${_('Previous commit')}"><i class="icon-chevron-left"></i></a>
10 10 </div>
11 11 <div class="info_box_elem">${h.text('at_rev',value=c.commit.revision)}</div>
12 12 <div class="info_box_elem next">
13 13 <a id="next_commit_link" data-commit-id="${c.next_commit.raw_id}" class="pjax-link ${'disabled' if c.url_next == '#' else ''}" href="${c.url_next}" title="${_('Next commit')}"><i class="icon-chevron-right"></i></a>
14 14 </div>
15 15 </div>
16 16 ${h.end_form()}
17 17
18 18 <div id="search_activate_id" class="search_activate">
19 19 <a class="btn btn-default" id="filter_activate" href="javascript:void(0)">${_('Search File List')}</a>
20 20 </div>
21 21 <div id="search_deactivate_id" class="search_activate hidden">
22 22 <a class="btn btn-default" id="filter_deactivate" href="javascript:void(0)">${_('Close File List')}</a>
23 23 </div>
24 24 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
25 25 <div title="${_('Add New File')}" class="btn btn-primary new-file">
26 26 <a href="${h.url('files_add_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path, anchor='edit')}">
27 27 ${_('Add File')}</a>
28 28 </div>
29 29 % endif
30 30 </div>
31 31
32 32 <div class="browser-search">
33 33 <div class="node-filter">
34 34 <div class="node_filter_box hidden" id="node_filter_box_loading" >${_('Loading file list...')}</div>
35 35 <div class="node_filter_box hidden" id="node_filter_box" >
36 36 <div class="node-filter-path">${h.get_last_path_part(c.file)}/</div>
37 37 <div class="node-filter-input">
38 38 <input class="init" type="text" name="filter" size="25" id="node_filter" autocomplete="off">
39 39 </div>
40 40 </div>
41 41 </div>
42 42 </div>
43 43 </div>
44
45 <div class="browser-body">
46 <table class="code-browser rctable">
47 <thead>
48 <tr>
49 <th>${_('Name')}</th>
50 <th>${_('Size')}</th>
51 <th>${_('Modified')}</th>
52 <th>${_('Last Commit')}</th>
53 <th>${_('Author')}</th>
54 </tr>
55 </thead>
44 ## file tree is computed from caches, and filled in
45 ${c.file_tree}
56 46
57 <tbody id="tbody">
58 %if c.file.parent:
59 <tr class="parity0">
60 <td class="td-componentname">
61 <a href='${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.file.parent.path)}' class="pjax-link">
62 <i class="icon-folder"></i>..
63 </a>
64 </td>
65 <td></td>
66 <td></td>
67 <td></td>
68 <td></td>
69 </tr>
70 %endif
71 %for cnt,node in enumerate(c.file):
72 <tr class="parity${cnt%2}">
73 <td class="td-componentname">
74 %if node.is_submodule():
75 <span class="submodule-dir">
76 ${h.link_to_if(
77 node.url.startswith('http://') or node.url.startswith('https://'),
78 node.name,node.url)}
79 </span>
80 %else:
81 <a href='${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=h.safe_unicode(node.path))}' class="pjax-link">
82 <i class="${"icon-file browser-file" if node.is_file() else "icon-folder browser-dir"}"></i>${node.name}
83 </a>
84 %endif
85 </td>
86 %if node.is_file():
87 <td class="td-size" title="${'size-%s' % node.name}"></td>
88 <td class="td-time" title="${'modified_at-%s' % node.name}">
89 <span class="browser-loading">${_('Loading...')}</span>
90 </td>
91 <td class="td-hash" title="${'revision-%s' % node.name}"></td>
92 <td class="td-user" title="${'author-%s' % node.name}"></td>
93 %else:
94 <td></td>
95 <td></td>
96 <td></td>
97 <td></td>
98 %endif
99 </tr>
100 %endfor
101 </tbody>
102 <tbody id="tbody_filtered"></tbody>
103 </table>
104 </div>
105 47 </div>
106 48
107 49 <script>
108 50 var source_page = false;
109 51 </script>
@@ -1,52 +1,52 b''
1 1 <%def name="title(*args)">
2 2 ${_('%s Files') % c.repo_name}
3 3 %if hasattr(c,'file'):
4 4 &middot; ${h.safe_unicode(c.file.path) or '\\'}
5 5 %endif
6 6
7 7 %if c.rhodecode_name:
8 8 &middot; ${h.branding(c.rhodecode_name)}
9 9 %endif
10 10 </%def>
11 11
12 12 <div id="pjax-content" data-title="${self.title()}">
13 13 <div class="summary-detail">
14 14 <div class="summary-detail-header">
15 15 <div class="breadcrumbs files_location">
16 16 <h4>
17 17 ${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.file.path)}
18 18 %if c.annotate:
19 19 - ${_('annotation')}
20 20 %endif
21 21 </h4>
22 22 </div>
23 23 <div class="btn-collapse" data-toggle="summary-details">
24 24 ${_('Show More')}
25 25 </div>
26 26 </div><!--end summary-detail-header-->
27 27
28 28 % if c.file.is_submodule():
29 29 <span class="submodule-dir">Submodule ${h.escape(c.file.name)}</span>
30 30 % elif c.file.is_dir():
31 31 <%include file='file_tree_detail.html'/>
32 32 % else:
33 33 <%include file='files_detail.html'/>
34 34 % endif
35 35
36 36 </div> <!--end summary-detail-->
37 37
38 38 % if c.file.is_dir():
39 39 <div id="commit-stats" class="sidebar-right">
40 40 <%include file='file_tree_author_box.html'/>
41 41 </div>
42 42
43 ${c.file_tree}
43 <%include file='files_browser.html'/>
44 44 % else:
45 45 <div id="file_authors" class="sidebar-right">
46 46 <%include file='file_authors_box.html'/>
47 47 </div>
48 48
49 49 <%include file='files_source.html'/>
50 50 % endif
51 51
52 52 </div> No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now