@@ -373,24 +373,29 @@ def make_map(config):
controller='files', action='annotate', revision='tip',
f_path='', conditions=dict(function=check_repo))
rmap.connect('files_edit_home',
'/{repo_name:.*}/edit/{revision}/{f_path:.*}',
controller='files', action='edit', revision='tip',
rmap.connect('files_archive_home', '/{repo_name:.*}/archive/{fname}',
controller='files', action='archivefile',
conditions=dict(function=check_repo))
rmap.connect('files_nodelist_home',
'/{repo_name:.*}/nodelist/{revision}/{f_path:.*}',
controller='files', action='nodelist',
rmap.connect('repo_settings_delete', '/{repo_name:.*}/settings',
controller='settings', action="delete",
conditions=dict(method=["DELETE"], function=check_repo))
rmap.connect('repo_settings_update', '/{repo_name:.*}/settings',
controller='settings', action="update",
conditions=dict(method=["PUT"], function=check_repo))
rmap.connect('repo_settings_home', '/{repo_name:.*}/settings',
controller='settings', action='index',
@@ -16,30 +16,32 @@
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import logging
import mimetypes
import traceback
from os.path import join as jn
from pylons import request, response, session, tmpl_context as c, url
from pylons.i18n.translation import _
from pylons.controllers.util import redirect
from pylons.decorators import jsonify
from vcs.backends import ARCHIVE_SPECS
from vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
EmptyRepositoryError, ImproperArchiveTypeError, VCSError
from vcs.nodes import FileNode, NodeKind
from vcs.utils import diffs as differ
from rhodecode.lib import convert_line_endings, detect_mode, safe_str
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
from rhodecode.lib.base import BaseRepoController, render
from rhodecode.lib.utils import EmptyChangeset
import rhodecode.lib.helpers as h
@@ -86,24 +88,44 @@ class FilesController(BaseRepoController
try:
file_node = cs.get_node(path)
if file_node.is_dir():
raise RepositoryError('given path is a directory')
except RepositoryError, e:
h.flash(str(e), category='warning')
redirect(h.url('files_home', repo_name=repo_name,
revision=cs.raw_id))
return file_node
def __get_paths(self, changeset, starting_path):
"""recursive walk in root dir and return a set of all path in that dir
based on repository walk function
"""
_files = list()
_dirs = list()
tip = changeset
for topnode, dirs, files in tip.walk(starting_path):
for f in files:
_files.append(f.path)
for d in dirs:
_dirs.append(d.path)
log.debug(traceback.format_exc())
pass
return _dirs, _files
@HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
'repository.admin')
def index(self, repo_name, revision, f_path):
#reditect to given revision from form if given
post_revision = request.POST.get('at_rev', None)
if post_revision:
cs = self.__get_cs_or_redirect(post_revision, repo_name)
redirect(url('files_home', repo_name=c.repo_name,
revision=cs.raw_id, f_path=f_path))
c.changeset = self.__get_cs_or_redirect(revision, repo_name)
c.branch = request.GET.get('branch', None)
@@ -404,12 +426,22 @@ class FilesController(BaseRepoController
for name, chs in c.rhodecode_repo.branches.items():
#chs = chs.split(':')[-1]
branches_group[0].append((chs, name),)
hist_l.append(branches_group)
for name, chs in c.rhodecode_repo.tags.items():
tags_group[0].append((chs, name),)
hist_l.append(tags_group)
return hist_l
@jsonify
def nodelist(self, repo_name, revision, f_path):
if request.environ.get('HTTP_X_PARTIAL_XHR'):
cs = self.__get_cs_or_redirect(revision, repo_name)
_d, _f = self.__get_paths(cs, f_path)
return _d + _f
@@ -43,25 +43,24 @@ table {
border-collapse:collapse;
border-spacing:0;
}
html {
height:100%;
a {
color:#003367;
text-decoration:none;
cursor:pointer;
font-weight:700;
a:hover {
color:#316293;
text-decoration:underline;
h1,h2,h3,h4,h5,h6 {
color:#292929;
@@ -1854,45 +1853,64 @@ padding:11px 16px 0 0;
div.browserblock {
overflow:hidden;
border:1px solid #ccc;
background:#f8f8f8;
font-size:100%;
line-height:125%;
padding:0;
div.browserblock .browser-header {
background:#FFF;
padding:10px 0px 25px 0px;
padding:10px 0px 15px 0px;
width: 100%;
div.browserblock .browser-nav {
float:left
div.browserblock .browser-branch {
float:left;
div.browserblock .browser-branch label {
color:#4A4A4A;
vertical-align:text-top;
div.browserblock .browser-header span {
margin-left:5px;
div.browserblock .browser-search{
clear:both;
padding:8px 8px 0px 5px;
div.browserblock .search_activate #filter_activate{
vertical-align: sub;
border: 1px solid;
padding:2px;
border-radius: 4px 4px 4px 4px;
background: url("../images/button.png") repeat-x scroll 0 0 #E5E3E3;
border-color: #DDDDDD #DDDDDD #C6C6C6 #C6C6C6;
color: #515151;
div.browserblock .search_activate a:hover{
text-decoration: none !important;
div.browserblock .browser-body {
background:#EEE;
border-top:1px solid #CCC;
table.code-browser {
width:100%;
table.code-browser tr {
margin:3px;
@@ -2710,12 +2728,17 @@ border:none !important;
height:20px !important;
padding:0 !important;
#q_filter{
border:0 none;
color:#AAAAAA;
margin-bottom:-4px;
margin-top:-4px;
padding-left:3px;
#node_filter{
border:0px solid #545454;
@@ -3,62 +3,176 @@
<%return "browser-file" %>
%else:
<%return "browser-dir"%>
%endif
</%def>
<div id="body" class="browserblock">
<div class="browser-header">
<div class="browser-nav">
${h.form(h.url.current())}
<div class="info_box">
<span class="rev">${_('view')}@rev</span>
<a class="rev" href="${c.url_prev}" title="${_('previous revision')}">«</a>
${h.text('at_rev',value=c.changeset.revision,size=3)}
${h.text('at_rev',value=c.changeset.revision,size=5)}
<a class="rev" href="${c.url_next}" title="${_('next revision')}">»</a>
## ${h.submit('view',_('view'),class_="ui-button-small")}
</div>
${h.end_form()}
<div class="browser-branch">
${h.checkbox('stay_at_branch',c.changeset.branch,c.changeset.branch==c.branch)}
<label>${_('follow current branch')}</label>
<script type="text/javascript">
YUE.on('stay_at_branch','click',function(e){
if(e.target.checked){
var uri = "${h.url.current(branch='__BRANCH__')}"
uri = uri.replace('__BRANCH__',e.target.value);
window.location = uri;
else{
window.location = "${h.url.current()}";
})
</script>
<div class="browser-search">
<div class="search_activate">
<a id="filter_activate" href="#">${_('search file list')}</a>
<div>
<div id="node_filter_box_loading" style="display:none">${_('Loading file list...')}</div>
<div id="node_filter_box" style="display:none">
${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.files_list.path)}/<input type="text" value="type to search..." name="filter" size="25" id="node_filter" autocomplete="off">
var n_filter = YUD.get('node_filter');
var F = YAHOO.namespace('node_filter');
var url = '${h.url("files_nodelist_home",repo_name="__REPO__",revision="__REVISION__",f_path="__FPATH__")}';
var node_url = '${h.url("files_home",repo_name="__REPO__",revision="__REVISION__",f_path="__FPATH__")}';
url = url.replace('__REPO__','${c.repo_name}');
url = url.replace('__REVISION__','${c.changeset.raw_id}');
url = url.replace('__FPATH__','${c.files_list.path}');
node_url = node_url.replace('__REPO__','${c.repo_name}');
node_url = node_url.replace('__REVISION__','${c.changeset.raw_id}');
F.filterTimeout = null;
var nodes = null;
F.initFilter = function(){
YUD.setStyle('node_filter_box_loading','display','');
YUD.setStyle('filter_activate','display','none');
YUC.initHeader('X-PARTIAL-XHR',true);
YUC.asyncRequest('GET',url,{
success:function(o){
nodes = JSON.parse(o.responseText);
YUD.setStyle('node_filter_box_loading','display','none');
YUD.setStyle('node_filter_box','display','');
},
failure:function(o){
console.log('failed to load');
},null);
F.updateFilter = function(e) {
return function(){
// Reset timeout
var query = e.target.value;
var match = [];
var matches = 0;
var matches_max = 20;
if (query != ""){
for(var i=0;i<nodes.length;i++){
var pos = nodes[i].toLowerCase().indexOf(query)
if(query && pos != -1){
matches++
//show only certain amount to not kill browser
if (matches > matches_max){
break;
var n = nodes[i];
var n_hl = n.substring(0,pos)
+"<b>{0}</b>".format(n.substring(pos,pos+query.length))
+n.substring(pos+query.length)
match.push('<tr><td><a class="browser-file" href="{0}">{1}</a></td><td colspan="5"></td></tr>'.format(node_url.replace('__FPATH__',n),n_hl));
if(match.length >= matches_max){
match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format("${_('search truncated')}"));
if(query != ""){
YUD.setStyle('tbody','display','none');
YUD.setStyle('tbody_filtered','display','');
if (match.length==0){
match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format("${_('no matching files')}"));
YUD.get('tbody_filtered').innerHTML = match.join("");
YUD.setStyle('tbody','display','');
YUD.setStyle('tbody_filtered','display','none');
YUE.on(YUD.get('filter_activate'),'click',function(){
F.initFilter();
YUE.on(n_filter,'click',function(){
n_filter.value = '';
});
YUE.on(n_filter,'keyup',function(e){
clearTimeout(F.filterTimeout);
F.filterTimeout = setTimeout(F.updateFilter(e),600);
<div class="browser-body">
<table class="code-browser">
<thead>
<tr>
<th>${_('Name')}</th>
<th>${_('Size')}</th>
<th>${_('Mimetype')}</th>
<th>${_('Revision')}</th>
<th>${_('Last modified')}</th>
<th>${_('Last commiter')}</th>
</tr>
</thead>
<tbody id="tbody">
%if c.files_list.parent:
<tr class="parity0">
<td>
${h.link_to('..',h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.files_list.parent.path),class_="browser-dir")}
</td>
<td></td>
@@ -88,15 +202,18 @@
%if node.is_file():
<span class="tooltip" title="${node.last_changeset.date}">
${h.age(node.last_changeset.date)}</span>
${node.last_changeset.author}
%endfor
</tbody>
<tbody id="tbody_filtered" style="display:none">
</table>
\ No newline at end of file
Status change: