@@ -93,17 +93,19 @@ def make_map(config):
action="repo_public_journal", conditions=dict(method=["PUT"],
function=check_repo))
m.connect('repo_pull', "/repo_pull/{repo_name:.*}",
action="repo_pull", conditions=dict(method=["PUT"],
#ADMIN REPOS GROUP REST ROUTES
routes_map.resource('repos_group', 'repos_groups', controller='admin/repos_groups', path_prefix='/_admin')
#ADMIN USER REST ROUTES
routes_map.resource('user', 'users', controller='admin/users', path_prefix='/_admin')
#ADMIN USERS REST ROUTES
routes_map.resource('users_group', 'users_groups', controller='admin/users_groups', path_prefix='/_admin')
#ADMIN GROUP REST ROUTES
routes_map.resource('group', 'groups', controller='admin/groups', path_prefix='/_admin')
#ADMIN PERMISSIONS REST ROUTES
new file 100644
import logging
from pylons import request, response, session, tmpl_context as c, url
from pylons.controllers.util import abort, redirect
from rhodecode.lib.base import BaseController, render
log = logging.getLogger(__name__)
class ReposGroupsController(BaseController):
"""REST Controller styled on the Atom Publishing Protocol"""
# To properly map this controller, ensure your config/routing.py
# file has a resource setup:
# map.resource('repos_group', 'repos_groups')
def index(self, format='html'):
"""GET /repos_groups: All items in the collection"""
# url('repos_groups')
def create(self):
"""POST /repos_groups: Create a new item"""
def new(self, format='html'):
"""GET /repos_groups/new: Form to create a new item"""
# url('new_repos_group')
def update(self, id):
"""PUT /repos_groups/id: Update an existing item"""
# Forms posted to this method should contain a hidden field:
# <input type="hidden" name="_method" value="PUT" />
# Or using helpers:
# h.form(url('repos_group', id=ID),
# method='put')
# url('repos_group', id=ID)
def delete(self, id):
"""DELETE /repos_groups/id: Delete an existing item"""
# <input type="hidden" name="_method" value="DELETE" />
# method='delete')
def show(self, id, format='html'):
"""GET /repos_groups/id: Show a specific item"""
def edit(self, id, format='html'):
"""GET /repos_groups/id/edit: Form to edit an existing item"""
# url('edit_repos_group', id=ID)
@@ -32,13 +32,14 @@ from datetime import datetime, timedelta
from vcs.exceptions import ChangesetError
from pylons import tmpl_context as c, request, url
from pylons.i18n.translation import _
from rhodecode.model.db import Statistics
from rhodecode.model.db import Statistics, Repository
from rhodecode.model.repo import RepoModel
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
from rhodecode.lib.base import BaseRepoController, render
from rhodecode.lib.utils import OrderedDict, EmptyChangeset
from rhodecode.lib.celerylib import run_task
@@ -57,25 +58,26 @@ class SummaryController(BaseRepoControll
@LoginRequired()
@HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
'repository.admin')
def __before__(self):
super(SummaryController, self).__before__()
def index(self):
c.repo, dbrepo = self.scm_model.get(c.repo_name)
c.dbrepo = dbrepo
def index(self, repo_name):
e = request.environ
c.dbrepo = dbrepo = Repository.by_repo_name(repo_name)
c.following = self.scm_model.is_following_repo(c.repo_name,
self.rhodecode_user.user_id)
c.following = self.scm_model.is_following_repo(repo_name,
def url_generator(**kw):
return url('shortlog_home', repo_name=c.repo_name, **kw)
return url('shortlog_home', repo_name=repo_name, **kw)
c.repo_changesets = RepoPage(c.repo, page=1, items_per_page=10,
c.repo_changesets = RepoPage(c.rhodecode_repo, page=1, items_per_page=10,
url=url_generator)
if self.rhodecode_user.username == 'default':
#for default(anonymous) user we don't need to pass credentials
username = ''
password = ''
else:
@@ -85,25 +87,25 @@ class SummaryController(BaseRepoControll
uri = u'%(protocol)s://%(user)s%(password)s%(host)s%(prefix)s/%(repo_name)s' % {
'protocol': e.get('wsgi.url_scheme'),
'user':username,
'password':password,
'host':e.get('HTTP_HOST'),
'prefix':e.get('SCRIPT_NAME'),
'repo_name':c.repo_name, }
'repo_name':repo_name, }
c.clone_repo_url = uri
c.repo_tags = OrderedDict()
for name, hash in c.repo.tags.items()[:10]:
for name, hash in c.rhodecode_repo.tags.items()[:10]:
try:
c.repo_tags[name] = c.repo.get_changeset(hash)
c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash)
except ChangesetError:
c.repo_tags[name] = EmptyChangeset(hash)
c.repo_branches = OrderedDict()
for name, hash in c.repo.branches.items()[:10]:
for name, hash in c.rhodecode_repo.branches.items()[:10]:
c.repo_branches[name] = c.repo.get_changeset(hash)
c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash)
c.repo_branches[name] = EmptyChangeset(hash)
td = date.today() + timedelta(days=1)
td_1m = td - timedelta(days=calendar.mdays[td.month])
td_1y = td - timedelta(days=365)
@@ -111,13 +113,13 @@ class SummaryController(BaseRepoControll
ts_min_m = mktime(td_1m.timetuple())
ts_min_y = mktime(td_1y.timetuple())
ts_max_y = mktime(td.timetuple())
if dbrepo.enable_statistics:
c.no_data_msg = _('No data loaded yet')
run_task(get_commits_stats, c.repo.name, ts_min_y, ts_max_y)
run_task(get_commits_stats, c.dbrepo.repo_name, ts_min_y, ts_max_y)
c.no_data_msg = _('Statistics are disabled for this repository')
c.ts_min = ts_min_m
c.ts_max = ts_max_y
stats = self.sa.query(Statistics)\
@@ -140,13 +142,13 @@ class SummaryController(BaseRepoControll
c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10] ])
c.trending_languages = json.dumps({})
c.no_data = True
c.enable_downloads = dbrepo.enable_downloads
if c.enable_downloads:
c.download_options = self._get_download_links(c.repo)
c.download_options = self._get_download_links(c.rhodecode_repo)
return render('summary/summary.html')
def _get_download_links(self, repo):
@@ -290,13 +290,13 @@ def pygmentize(filenode, **kwargs):
:param filenode:
"""
return literal(code_highlight(filenode.content,
filenode.lexer, CodeHtmlFormatter(**kwargs)))
def pygmentize_annotation(filenode, **kwargs):
def pygmentize_annotation(repo_name, filenode, **kwargs):
"""pygmentize function for annotation
color_dict = {}
@@ -323,33 +323,36 @@ def pygmentize_annotation(filenode, **kw
if color_dict.has_key(cs):
col = color_dict[cs]
col = color_dict[cs] = cgenerator.next()
return "color: rgb(%s)! important;" % (', '.join(col))
def url_func(changeset):
tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
" %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
def url_func(repo_name):
def _url_func(changeset):
tooltip_html = tooltip_html % (changeset.author,
changeset.date,
tooltip(changeset.message))
lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
short_id(changeset.raw_id))
uri = link_to(
lnk_format,
url('changeset_home', repo_name=changeset.repository.name,
revision=changeset.raw_id),
style=get_color_string(changeset.raw_id),
class_='tooltip',
title=tooltip_html
)
url('changeset_home', repo_name=repo_name,
uri += '\n'
return uri
return literal(annotate_highlight(filenode, url_func, **kwargs))
return _url_func
return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
def get_changeset_safe(repo, rev):
from vcs.backends.base import BaseRepository
from vcs.exceptions import RepositoryError
if not isinstance(repo, BaseRepository):
raise Exception('You must pass an Repository '
@@ -687,11 +690,11 @@ def repo_link(groups_and_repos):
groups, repo_name = groups_and_repos
if not groups:
return repo_name
def make_link(group):
return link_to(group.group_name, url('/', group.group_id))
return link_to(group.group_name, url('repos_group', id=group.group_id))
return literal(' » '.join(map(make_link, groups)) + \
" » " + repo_name)
@@ -88,13 +88,13 @@ class WhooshIndexingDaemon(object):
self.repo_paths = ScmModel(sa).repo_scan(self.repo_location)
if repo_list:
filtered_repo_paths = {}
for repo_name, repo in self.repo_paths.items():
if repo_name in repo_list:
filtered_repo_paths[repo.name] = repo
filtered_repo_paths[repo_name] = repo
self.repo_paths = filtered_repo_paths
self.initial = False
if not os.path.isdir(self.index_location):
@@ -127,13 +127,13 @@ class WhooshIndexingDaemon(object):
node = repo.get_changeset().get_node(n_path)
return node
def get_node_mtime(self, node):
return mktime(node.last_changeset.date.timetuple())
def add_doc(self, writer, path, repo):
def add_doc(self, writer, path, repo, repo_name):
"""Adding doc to writer this function itself fetches data from
the instance of vcs backend"""
node = self.get_node(repo, path)
#we just index the content of chosen files, and skip binary files
if node.extension in INDEX_EXTENSIONS and not node.is_binary:
@@ -149,13 +149,13 @@ class WhooshIndexingDaemon(object):
log.debug(' >> %s' % path)
#just index file name without it's content
u_content = u''
writer.add_document(owner=unicode(repo.contact),
repository=safe_unicode(repo.name),
repository=safe_unicode(repo_name),
path=safe_unicode(path),
content=u_content,
modtime=self.get_node_mtime(node),
extension=node.extension)
@@ -167,17 +167,17 @@ class WhooshIndexingDaemon(object):
if not os.path.exists(self.index_location):
os.mkdir(self.index_location)
idx = create_in(self.index_location, SCHEMA, indexname=IDX_NAME)
writer = idx.writer()
for repo in self.repo_paths.values():
log.debug('building index @ %s' % repo.path)
for idx_path in self.get_paths(repo):
self.add_doc(writer, idx_path, repo)
self.add_doc(writer, idx_path, repo, repo_name)
log.debug('>> COMMITING CHANGES <<')
writer.commit(merge=True)
log.debug('>>> FINISHED BUILDING INDEX <<<')
@@ -218,18 +218,18 @@ class WhooshIndexingDaemon(object):
writer.delete_by_term('path', indexed_path)
to_index.add(indexed_path)
# Loop over the files in the filesystem
# Assume we have a function that gathers the filenames of the
# documents to be indexed
for path in self.get_paths(repo):
if path in to_index or path not in indexed_paths:
# This is either a file that's changed, or a new file
# that wasn't indexed before. So index it!
self.add_doc(writer, path, repo)
self.add_doc(writer, path, repo, repo_name)
log.debug('re indexing %s' % path)
log.debug('>>> FINISHED REBUILDING INDEX <<<')
@@ -133,25 +133,25 @@
%if repo['dbrepo']['private']:
<img class="icon" alt="${_('private')}" src="${h.url("/images/icons/lock.png")}"/>
%else:
<img class="icon" alt="${_('public')}" src="${h.url("/images/icons/lock_open.png")}"/>
%endif
${h.link_to(repo['repo'].name, h.url('summary_home',repo_name=repo['repo'].name),class_="repo_name")}
${h.link_to(repo['name'], h.url('summary_home',repo_name=repo['name']),class_="repo_name")}
%if repo['dbrepo_fork']:
<a href="${h.url('summary_home',repo_name=repo['dbrepo_fork']['repo_name'])}">
<img class="icon" alt="${_('public')}"
title="${_('Fork of')} ${repo['dbrepo_fork']['repo_name']}"
src="${h.url("/images/icons/arrow_divide.png")}"/></a>
</td>
<td><span class="tooltip" title="${repo['repo'].last_change}">${("r%s:%s") % (h.get_changeset_safe(repo['repo'],'tip').revision,h.short_id(h.get_changeset_safe(repo['repo'],'tip').raw_id))}</span></td>
<td><a href="${h.url('repo_settings_home',repo_name=repo['repo'].name)}" title="${_('edit')}"><img class="icon" alt="${_('private')}" src="${h.url("/images/icons/application_form_edit.png")}"/></a></td>
<td><a href="${h.url('repo_settings_home',repo_name=repo['name'])}" title="${_('edit')}"><img class="icon" alt="${_('private')}" src="${h.url("/images/icons/application_form_edit.png")}"/></a></td>
<td>
${h.form(url('repo_settings_delete', repo_name=repo['repo'].name),method='delete')}
${h.submit('remove_%s' % repo['repo'].name,'',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
${h.form(url('repo_settings_delete', repo_name=repo['name']),method='delete')}
${h.submit('remove_%s' % repo['name'],'',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
${h.end_form()}
</tr>
%endfor
${_('No repositories yet')}
@@ -64,13 +64,13 @@
</div>
<div class="code-body">
%if c.file.is_binary:
${_('Binary file')}
% if c.file.size < c.cut_off_limit:
${h.pygmentize_annotation(c.file,linenos=True,anchorlinenos=True,lineanchors='S',cssclass="code-highlight")}
${h.pygmentize_annotation(c.repo_name,c.file,linenos=True,anchorlinenos=True,lineanchors='S',cssclass="code-highlight")}
${_('File is to big to display')} ${h.link_to(_('show as raw'),
h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.revision,f_path=c.f_path))}
<script type="text/javascript">
YAHOO.util.Event.onDOMReady(function(){
## -*- coding: utf-8 -*-
%for repo in c.repos_list:
<li>
<img src="${h.url("/images/icons/lock.png")}" alt="${_('Private repository')}" class="repo_switcher_type"/>
${h.link_to(repo['name'].name,h.url('summary_home',repo_name=repo['name']),class_="%s" % repo['dbrepo']['repo_type'])}
${h.link_to(repo['name'],h.url('summary_home',repo_name=repo['name']),class_="%s" % repo['dbrepo']['repo_type'])}
</li>
<img src="${h.url("/images/icons/lock_open.png")}" alt="${_('Public repository')}" class="repo_switcher_type" />
@@ -4,13 +4,13 @@
${c.repo_name} ${_('Summary')} - ${c.rhodecode_name}
</%def>
<%def name="breadcrumbs_links()">
${h.link_to(u'Home',h.url('/'))}
»
${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
${h.link_to(c.dbrepo.just_name,h.url('summary_home',repo_name=c.repo_name))}
${_('summary')}
<%def name="page_nav()">
${self.menu('summary')}
@@ -28,37 +28,37 @@
<div class="field">
<div class="label">
<label>${_('Name')}:</label>
<div class="input-short">
%if c.rhodecode_user.username != 'default':
%if c.following:
<span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
</span>
<span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
%endif:
%if c.dbrepo.repo_type =='hg':
<img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url("/images/icons/hgicon.png")}"/>
%if c.dbrepo.repo_type =='git':
<img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url("/images/icons/giticon.png")}"/>
%if c.dbrepo.private:
<img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url("/images/icons/lock.png")}"/>
<img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url("/images/icons/lock_open.png")}"/>
<span style="font-size: 1.6em;font-weight: bold;vertical-align: baseline;">${c.repo.name}</span>
<br/>
<span style="font-size: 1.6em;font-weight: bold;vertical-align: baseline;">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
%if c.dbrepo.fork:
<span style="margin-top:5px">
<a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}">
title="${_('Fork of')} ${c.dbrepo.fork.repo_name}"
src="${h.url("/images/icons/arrow_divide.png")}"/>
@@ -106,14 +106,17 @@
<label>${_('Last change')}:</label>
${h.age(c.repo.last_change)} - ${c.repo.last_change}
${_('by')} ${h.get_changeset_safe(c.repo,'tip').author}
<b>${'r%s:%s' % (h.get_changeset_safe(c.rhodecode_repo,'tip').revision,
h.get_changeset_safe(c.rhodecode_repo,'tip').short_id)}</b> -
<span class="tooltip" title="${c.rhodecode_repo.last_change}">
${h.age(c.rhodecode_repo.last_change)}</span><br/>
${_('by')} ${h.get_changeset_safe(c.rhodecode_repo,'tip').author}
@@ -135,45 +138,45 @@
<label>${_('Download')}:</label>
%if len(c.repo.revisions) == 0:
%if len(c.rhodecode_repo.revisions) == 0:
${_('There are no downloads yet')}
%elif c.enable_downloads is False:
${_('Downloads are disabled for this repository')}
%if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
[${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
${h.select('download_options',c.repo.get_changeset().raw_id,c.download_options)}
%for cnt,archive in enumerate(c.repo._get_archives()):
${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
%for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
%if cnt >=1:
|
<span class="tooltip" title="${_('Download %s as %s') %('tip',archive['type'])}"
id="${archive['type']+'_link'}">${h.link_to(archive['type'],
h.url('files_archive_home',repo_name=c.repo.name,
h.url('files_archive_home',repo_name=c.dbrepo.repo_name,
fname='tip'+archive['extension']),class_="archive_icon")}</span>
<label>${_('Feeds')}:</label>
${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.repo.name,api_key=c.rhodecode_user.api_key),class_='rss_icon')}
${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.repo.name,api_key=c.rhodecode_user.api_key),class_='atom_icon')}
${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='rss_icon')}
${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='atom_icon')}
${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.repo.name),class_='rss_icon')}
${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.repo.name),class_='atom_icon')}
${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name),class_='rss_icon')}
${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name),class_='atom_icon')}
@@ -262,15 +265,15 @@
})
YUE.on('download_options','change',function(e){
var new_cs = e.target.options[e.target.selectedIndex];
var tmpl_links = {}
tmpl_links['${archive['type']}'] = '${h.link_to(archive['type'],
fname='__CS__'+archive['extension']),class_="archive_icon")}';
for(k in tmpl_links){
var s = YUD.get(k+'_link')
from rhodecode.tests import *
class TestReposGroupsController(TestController):
def test_index(self):
response = self.app.get(url('repos_groups'))
# Test response...
def test_index_as_xml(self):
response = self.app.get(url('formatted_repos_groups', format='xml'))
def test_create(self):
response = self.app.post(url('repos_groups'))
def test_new(self):
response = self.app.get(url('new_repos_group'))
def test_new_as_xml(self):
response = self.app.get(url('formatted_new_repos_group', format='xml'))
def test_update(self):
response = self.app.put(url('repos_group', id=1))
def test_update_browser_fakeout(self):
response = self.app.post(url('repos_group', id=1), params=dict(_method='put'))
def test_delete(self):
response = self.app.delete(url('repos_group', id=1))
def test_delete_browser_fakeout(self):
response = self.app.post(url('repos_group', id=1), params=dict(_method='delete'))
def test_show(self):
response = self.app.get(url('repos_group', id=1))
def test_show_as_xml(self):
response = self.app.get(url('formatted_repos_group', id=1, format='xml'))
def test_edit(self):
response = self.app.get(url('edit_repos_group', id=1))
def test_edit_as_xml(self):
response = self.app.get(url('formatted_edit_repos_group', id=1, format='xml'))
Status change: