import logging
import shutil
from urllib import quote
from pylons import c, g
from tg import expose, redirect, url
from tg.decorators import with_trailing_slash, without_trailing_slash
from bson import ObjectId
from ming.utils import LazyProperty
import allura.tasks
from allura import version
from allura.lib import helpers as h
from allura import model as M
from allura.lib import security
from allura.lib.decorators import require_post
from allura.lib.security import has_access
from allura.app import Application, SitemapEntry, DefaultAdminController, ConfigOption
log = logging.getLogger(__name__)
class RepositoryApp(Application):
END_OF_REF_ESCAPE='~'
__version__ = version.__version__
permissions = [
'read', 'write', 'create',
'unmoderated_post', 'post', 'moderate', 'admin',
'configure']
config_options = Application.config_options + [
ConfigOption('cloned_from_project_id', ObjectId, None),
ConfigOption('cloned_from_repo_id', ObjectId, None),
ConfigOption('init_from_url', str, None)
]
tool_label='Repository'
default_mount_label='Code'
default_mount_point='code'
ordinal=2
forkable=False
default_branch_name=None # master or default or some such
repo=None # override with a property in child class
icons={
24:'images/code_24.png',
32:'images/code_32.png',
48:'images/code_48.png'
}
def __init__(self, project, config):
Application.__init__(self, project, config)
self.admin = RepoAdminController(self)
def main_menu(self):
'''Apps should provide their entries to be added to the main nav
:return: a list of :class:`SitemapEntries <allura.app.SitemapEntry>`
'''
return [ SitemapEntry(
self.config.options.mount_label.title(),
'.')]
@property
@h.exceptionless([], log)
def sitemap(self):
menu_id = self.config.options.mount_label.title()
with h.push_config(c, app=self):
return [
SitemapEntry(menu_id, '.')[self.sidebar_menu()] ]
def admin_menu(self):
admin_url = c.project.url()+'admin/'+self.config.options.mount_point+'/'
links = [SitemapEntry('Viewable Files', admin_url + 'extensions', className='admin_modal')]
links.append(SitemapEntry('Refresh Repository',
c.project.url() + self.config.options.mount_point + '/refresh',
className='nav_child'))
links += super(RepositoryApp, self).admin_menu()
[links.remove(l) for l in links[:] if l.label == 'Options']
return links
@h.exceptionless([], log)
def sidebar_menu(self):
if not self.repo or self.repo.status != 'ready':
return [
SitemapEntry(self.repo.status) ]
if self.default_branch_name:
default_branch_url = (
c.app.url
+ url(quote(self.default_branch_name + self.END_OF_REF_ESCAPE))
+ '/')
else:
default_branch_url = c.app.url
links = [SitemapEntry('Browse Commits', c.app.url + 'commit_browser', ui_icon=g.icons['folder'])]
if self.forkable and self.repo.status == 'ready':
links.append(SitemapEntry('Fork', c.app.url + 'fork', ui_icon=g.icons['fork']))
merge_request_count = self.repo.merge_requests_by_statuses('open').count()
if merge_request_count:
links += [
SitemapEntry(
'Merge Requests', c.app.url + 'merge-requests/',
className='nav_child',
small=merge_request_count) ]
if self.repo.upstream_repo.name:
repo_path_parts = self.repo.upstream_repo.name.strip('/').split('/')
links += [
SitemapEntry('Clone of'),
SitemapEntry('%s / <strong>%s</strong>' %
(repo_path_parts[1], repo_path_parts[-1]),
self.repo.upstream_repo.name,
className='nav_child')
]
if len(c.app.repo.branches) and has_access(c.app.repo, 'admin'):
links.append(SitemapEntry('Request Merge', c.app.url + 'request_merge',
ui_icon=g.icons['merge'],
className='nav_child'))
pending_upstream_merges = self.repo.pending_upstream_merges()
if pending_upstream_merges:
links.append(SitemapEntry(
'Pending Merges',
self.repo.upstream_repo.name + 'merge-requests/',
className='nav_child',
small=pending_upstream_merges))
if self.repo.branches:
links.append(SitemapEntry('Branches'))
for b in self.repo.branches:
links.append(SitemapEntry(
b.name, url(c.app.url, dict(branch='ref/' + b.name)),
className='nav_child',
small=b.count))
if self.repo.repo_tags:
links.append(SitemapEntry('Tags'))
max_tags = 10
for i, b in enumerate(self.repo.repo_tags):
if i < max_tags:
links.append(SitemapEntry(
b.name, url(c.app.url, dict(branch='ref/' + b.name)),
className='nav_child',
small=b.count))
elif i == max_tags:
links.append(
SitemapEntry(
'More Tags',
default_branch_url+'tags/',
className='nav_child'))
break
if self.repo.forks:
links.append(SitemapEntry('Forks'))
for f in self.repo.forks:
repo_path_parts = f.url().strip('/').split('/')
links.append(SitemapEntry(
'%s / <strong>%s</strong>' %
(repo_path_parts[1], repo_path_parts[-1]),
f.url(), className='nav_child'))
return links
def install(self, project):
self.config.options['project_name'] = project.name
super(RepositoryApp, self).install(project)
role_admin = M.ProjectRole.by_name('Admin')._id
role_developer = M.ProjectRole.by_name('Developer')._id
role_auth = M.ProjectRole.authenticated()._id
role_anon = M.ProjectRole.anonymous()._id
self.config.acl = [
M.ACE.allow(role_anon, 'read'),
M.ACE.allow(role_auth, 'post'),
M.ACE.allow(role_auth, 'unmoderated_post'),
M.ACE.allow(role_developer, 'create'),
M.ACE.allow(role_developer, 'write'),
M.ACE.allow(role_developer, 'moderate'),
M.ACE.allow(role_admin, 'configure'),
M.ACE.allow(role_admin, 'admin'),
]
def uninstall(self, project):
allura.tasks.repo_tasks.uninstall.post()
class RepoAdminController(DefaultAdminController):
def __init__(self, app):
self.app = app
@LazyProperty
def repo(self):
return self.app.repo
def _check_security(self):
security.require_access(self.app, 'configure')
@with_trailing_slash
@expose()
def index(self, **kw):
redirect('extensions')
@without_trailing_slash
@expose('jinja:allura:templates/repo/admin_extensions.html')
def extensions(self, **kw):
return dict(app=self.app,
allow_config=True,
additional_viewable_extensions=getattr(self.repo, 'additional_viewable_extensions', ''))
@without_trailing_slash
@expose()
@require_post()
def set_extensions(self, **post_data):
self.repo.additional_viewable_extensions = post_data['additional_viewable_extensions']