@@ -233,384 +233,388 @@ def make_map(config):
action="create", conditions=dict(method=["POST"]))
m.connect("users_groups", "/users_groups",
action="index", conditions=dict(method=["GET"]))
m.connect("formatted_users_groups", "/users_groups.{format}",
m.connect("new_users_group", "/users_groups/new",
action="new", conditions=dict(method=["GET"]))
m.connect("formatted_new_users_group", "/users_groups/new.{format}",
m.connect("update_users_group", "/users_groups/{id}",
action="update", conditions=dict(method=["PUT"]))
m.connect("delete_users_group", "/users_groups/{id}",
action="delete", conditions=dict(method=["DELETE"]))
m.connect("edit_users_group", "/users_groups/{id}/edit",
action="edit", conditions=dict(method=["GET"]))
m.connect("formatted_edit_users_group",
"/users_groups/{id}.{format}/edit",
m.connect("users_group", "/users_groups/{id}",
action="show", conditions=dict(method=["GET"]))
m.connect("formatted_users_group", "/users_groups/{id}.{format}",
#EXTRAS USER ROUTES
m.connect("users_group_perm", "/users_groups_perm/{id}",
action="update_perm", conditions=dict(method=["PUT"]))
#ADMIN GROUP REST ROUTES
rmap.resource('group', 'groups',
controller='admin/groups', path_prefix=ADMIN_PREFIX)
#ADMIN PERMISSIONS REST ROUTES
rmap.resource('permission', 'permissions',
controller='admin/permissions', path_prefix=ADMIN_PREFIX)
#ADMIN DEFAULTS REST ROUTES
rmap.resource('default', 'defaults',
controller='admin/defaults', path_prefix=ADMIN_PREFIX)
##ADMIN LDAP SETTINGS
rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
controller='admin/ldap_settings', action='ldap_settings',
conditions=dict(method=["POST"]))
rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
controller='admin/ldap_settings')
#ADMIN SETTINGS REST ROUTES
with rmap.submapper(path_prefix=ADMIN_PREFIX,
controller='admin/settings') as m:
m.connect("admin_settings", "/settings",
m.connect("formatted_admin_settings", "/settings.{format}",
m.connect("admin_new_setting", "/settings/new",
m.connect("formatted_admin_new_setting", "/settings/new.{format}",
m.connect("/settings/{setting_id}",
m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
m.connect("formatted_admin_edit_setting",
"/settings/{setting_id}.{format}/edit",
m.connect("admin_setting", "/settings/{setting_id}",
m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
m.connect("admin_settings_my_account", "/my_account",
action="my_account", conditions=dict(method=["GET"]))
m.connect("admin_settings_my_account_update", "/my_account_update",
action="my_account_update", conditions=dict(method=["PUT"]))
m.connect("admin_settings_create_repository", "/create_repository",
action="create_repository", conditions=dict(method=["GET"]))
m.connect("admin_settings_my_repos", "/my_account/repos",
action="my_account_my_repos", conditions=dict(method=["GET"]))
m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests",
action="my_account_my_pullrequests", conditions=dict(method=["GET"]))
#NOTIFICATION REST ROUTES
controller='admin/notifications') as m:
m.connect("notifications", "/notifications",
m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
action="mark_all_read", conditions=dict(method=["GET"]))
m.connect("formatted_notifications", "/notifications.{format}",
m.connect("new_notification", "/notifications/new",
m.connect("formatted_new_notification", "/notifications/new.{format}",
m.connect("/notification/{notification_id}",
m.connect("edit_notification", "/notification/{notification_id}/edit",
m.connect("formatted_edit_notification",
"/notification/{notification_id}.{format}/edit",
m.connect("notification", "/notification/{notification_id}",
m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
#ADMIN MAIN PAGES
controller='admin/admin') as m:
m.connect('admin_home', '', action='index')
m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
action='add_repo')
#==========================================================================
# API V2
controller='api/api') as m:
m.connect('api', '/api')
#USER JOURNAL
rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
controller='journal', action='index')
rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
controller='journal', action='journal_rss')
rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
controller='journal', action='journal_atom')
rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
controller='journal', action="public_journal")
rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
controller='journal', action="public_journal_rss")
rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
rmap.connect('public_journal_atom',
'%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
action="public_journal_atom")
rmap.connect('public_journal_atom_old',
'%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
controller='journal', action='toggle_following',
#SEARCH
rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
controller='search')
#LOGIN/LOGOUT/REGISTER/SIGN IN
rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
action='logout')
rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
action='register')
rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
controller='login', action='password_reset')
rmap.connect('reset_password_confirmation',
'%s/password_reset_confirmation' % ADMIN_PREFIX,
controller='login', action='password_reset_confirmation')
#FEEDS
rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
controller='feed', action='rss',
conditions=dict(function=check_repo))
rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
controller='feed', action='atom',
# REPOSITORY ROUTES
rmap.connect('summary_home', '/{repo_name:.*?}',
controller='summary',
rmap.connect('repo_size', '/{repo_name:.*?}/repo_size',
controller='summary', action='repo_size',
rmap.connect('repos_group_home', '/{group_name:.*}',
controller='admin/repos_groups', action="show_by_name",
conditions=dict(function=check_group))
rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}',
controller='changeset', revision='tip',
#still working url for backward compat.
rmap.connect('raw_changeset_home_depraced',
'/{repo_name:.*?}/raw-changeset/{revision}',
controller='changeset', action='changeset_raw',
revision='tip', conditions=dict(function=check_repo))
## new URLs
rmap.connect('changeset_raw_home',
'/{repo_name:.*?}/changeset-diff/{revision}',
rmap.connect('changeset_patch_home',
'/{repo_name:.*?}/changeset-patch/{revision}',
controller='changeset', action='changeset_patch',
rmap.connect('changeset_download_home',
'/{repo_name:.*?}/changeset-download/{revision}',
controller='changeset', action='changeset_download',
rmap.connect('changeset_comment',
'/{repo_name:.*?}/changeset/{revision}/comment',
controller='changeset', revision='tip', action='comment',
rmap.connect('changeset_comment_delete',
'/{repo_name:.*?}/changeset/comment/{comment_id}/delete',
controller='changeset', action='delete_comment',
conditions=dict(function=check_repo, method=["DELETE"]))
rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}',
controller='changeset', action='changeset_info')
rmap.connect('compare_url',
'/{repo_name:.*?}/compare/{org_ref_type}@{org_ref:.*?}...{other_ref_type}@{other_ref:.*?}',
controller='compare', action='index',
conditions=dict(function=check_repo),
requirements=dict(
org_ref_type='(branch|book|tag|rev|org_ref_type)',
other_ref_type='(branch|book|tag|rev|other_ref_type)')
)
rmap.connect('pullrequest_home',
'/{repo_name:.*?}/pull-request/new', controller='pullrequests',
action='index', conditions=dict(function=check_repo,
method=["GET"]))
rmap.connect('pullrequest',
action='create', conditions=dict(function=check_repo,
method=["POST"]))
rmap.connect('pullrequest_show',
'/{repo_name:.*?}/pull-request/{pull_request_id}',
controller='pullrequests',
action='show', conditions=dict(function=check_repo,
rmap.connect('pullrequest_update',
action='update', conditions=dict(function=check_repo,
method=["PUT"]))
rmap.connect('pullrequest_delete',
action='delete', conditions=dict(function=check_repo,
method=["DELETE"]))
rmap.connect('pullrequest_show_all',
'/{repo_name:.*?}/pull-request',
action='show_all', conditions=dict(function=check_repo,
rmap.connect('pullrequest_comment',
'/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
action='comment', conditions=dict(function=check_repo,
rmap.connect('pullrequest_comment_delete',
'/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
controller='pullrequests', action='delete_comment',
rmap.connect('summary_home', '/{repo_name:.*?}/summary',
controller='summary', conditions=dict(function=check_repo))
rmap.connect('shortlog_home', '/{repo_name:.*?}/shortlog',
controller='shortlog', conditions=dict(function=check_repo))
rmap.connect('shortlog_file_home', '/{repo_name:.*?}/shortlog/{revision}/{f_path:.*}',
controller='shortlog', f_path=None,
rmap.connect('branches_home', '/{repo_name:.*?}/branches',
controller='branches', conditions=dict(function=check_repo))
rmap.connect('tags_home', '/{repo_name:.*?}/tags',
controller='tags', conditions=dict(function=check_repo))
rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks',
controller='bookmarks', conditions=dict(function=check_repo))
rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
controller='changelog', conditions=dict(function=check_repo))
rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
controller='changelog', action='changelog_details',
rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
controller='files', revision='tip', f_path='',
rmap.connect('files_history_home',
'/{repo_name:.*?}/history/{revision}/{f_path:.*}',
controller='files', action='history', revision='tip', f_path='',
rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
controller='files', action='diff', revision='tip', f_path='',
rmap.connect('files_rawfile_home',
'/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
controller='files', action='rawfile', revision='tip',
f_path='', conditions=dict(function=check_repo))
rmap.connect('files_raw_home',
'/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
controller='files', action='raw', revision='tip', f_path='',
rmap.connect('files_annotate_home',
'/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
controller='files', action='index', revision='tip',
f_path='', annotate=True, 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_add_home',
'/{repo_name:.*?}/add/{revision}/{f_path:.*}',
controller='files', action='add', revision='tip',
rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
controller='files', action='archivefile',
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',
rmap.connect('toggle_locking', "/{repo_name:.*?}/locking_toggle",
controller='settings', action="toggle_locking",
conditions=dict(method=["GET"], function=check_repo))
rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
controller='forks', action='fork_create',
conditions=dict(function=check_repo, method=["POST"]))
rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
controller='forks', action='fork',
# -*- coding: utf-8 -*-
"""
rhodecode.controllers.summary
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Summary controller for Rhodecode
:created_on: Apr 18, 2010
:author: marcink
:copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
:license: GPLv3, see COPYING for more details.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (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 traceback
import calendar
import logging
import urllib
from time import mktime
from datetime import timedelta, date
from urlparse import urlparse
from rhodecode.lib.compat import product
from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
NodeDoesNotExistError
from pylons import tmpl_context as c, request, url, config
from pylons.i18n.translation import _
from webob.exc import HTTPBadRequest
from beaker.cache import cache_region, region_invalidate
from rhodecode.config.conf import ALL_READMES, ALL_EXTS, LANGUAGES_EXTENSIONS_MAP
from rhodecode.model.db import Statistics, CacheInvalidation
from rhodecode.lib.utils import jsonify
from rhodecode.lib.utils2 import safe_unicode
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
NotAnonymous
from rhodecode.lib.base import BaseRepoController, render
from rhodecode.lib.vcs.backends.base import EmptyChangeset
from rhodecode.lib.markup_renderer import MarkupRenderer
from rhodecode.lib.celerylib import run_task
from rhodecode.lib.celerylib.tasks import get_commits_stats
from rhodecode.lib.helpers import RepoPage
from rhodecode.lib.compat import json, OrderedDict
from rhodecode.lib.vcs.nodes import FileNode
log = logging.getLogger(__name__)
README_FILES = [''.join([x[0][0], x[1][0]]) for x in
sorted(list(product(ALL_READMES, ALL_EXTS)),
key=lambda y:y[0][1] + y[1][1])]
class SummaryController(BaseRepoController):
@LoginRequired()
@HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
'repository.admin')
def __before__(self):
super(SummaryController, self).__before__()
def index(self, repo_name):
c.dbrepo = dbrepo = c.rhodecode_db_repo
c.following = self.scm_model.is_following_repo(repo_name,
self.rhodecode_user.user_id)
def url_generator(**kw):
return url('shortlog_home', repo_name=repo_name, size=10, **kw)
c.repo_changesets = RepoPage(c.rhodecode_repo, page=1,
items_per_page=10, url=url_generator)
page_revisions = [x.raw_id for x in list(c.repo_changesets)]
c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
if self.rhodecode_user.username == 'default':
# for default(anonymous) user we don't need to pass credentials
username = ''
password = ''
else:
username = str(self.rhodecode_user.username)
password = '@'
parsed_url = urlparse(url.current(qualified=True))
default_clone_uri = '{scheme}://{user}{pass}{netloc}{path}'
uri_tmpl = config.get('clone_uri', default_clone_uri)
uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s')
decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
uri_dict = {
'user': urllib.quote(username),
'pass': password,
'scheme': parsed_url.scheme,
'netloc': parsed_url.netloc,
'path': decoded_path
}
uri = uri_tmpl % uri_dict
# generate another clone url by id
uri_dict.update(
{'path': decoded_path.replace(repo_name, '_%s' % c.dbrepo.repo_id)}
uri_id = uri_tmpl % uri_dict
c.clone_repo_url = uri
c.clone_repo_url_id = uri_id
c.repo_tags = OrderedDict()
for name, hash_ in c.rhodecode_repo.tags.items()[:10]:
try:
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.rhodecode_repo.branches.items()[:10]:
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)
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.show_stats = True
c.no_data_msg = _('No data loaded yet')
run_task(get_commits_stats, c.dbrepo.repo_name, ts_min_y, ts_max_y)
c.show_stats = False
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)\
.filter(Statistics.repository == dbrepo)\
.scalar()
c.stats_percentage = 0
if stats and stats.languages:
c.no_data = False is dbrepo.enable_statistics
lang_stats_d = json.loads(stats.languages)
c.commit_data = stats.commit_activity
c.overview_data = stats.commit_activity_combined
lang_stats = ((x, {"count": y,
"desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
for x, y in lang_stats_d.items())
c.trending_languages = json.dumps(
sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10]
last_rev = stats.stat_on_revision + 1
c.repo_last_rev = c.rhodecode_repo.count()\
if c.rhodecode_repo.revisions else 0
if last_rev == 0 or c.repo_last_rev == 0:
pass
c.stats_percentage = '%.2f' % ((float((last_rev)) /
c.repo_last_rev) * 100)
c.commit_data = json.dumps({})
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.rhodecode_repo)
c.readme_data, c.readme_file = \
self.__get_readme_data(c.rhodecode_db_repo)
return render('summary/summary.html')
@NotAnonymous()
@jsonify
def repo_size(self, repo_name):
if request.is_xhr:
return _('repository size: %s') % c.rhodecode_db_repo._repo_size()
raise HTTPBadRequest()
def __get_readme_data(self, db_repo):
repo_name = db_repo.repo_name
@cache_region('long_term')
def _get_readme_from_cache(key):
readme_data = None
readme_file = None
log.debug('Looking for README file')
# get's the landing revision! or tip if fails
cs = db_repo.get_landing_changeset()
if isinstance(cs, EmptyChangeset):
raise EmptyRepositoryError()
renderer = MarkupRenderer()
for f in README_FILES:
readme = cs.get_node(f)
if not isinstance(readme, FileNode):
continue
readme_file = f
log.debug('Found README file `%s` rendering...' %
readme_file)
readme_data = renderer.render(readme.content, f)
break
except NodeDoesNotExistError:
log.error(traceback.format_exc())
except EmptyRepositoryError:
except Exception:
return readme_data, readme_file
key = repo_name + '_README'
inv = CacheInvalidation.invalidate(key)
if inv is not None:
region_invalidate(_get_readme_from_cache, None, key)
CacheInvalidation.set_valid(inv.cache_key)
return _get_readme_from_cache(key)
def _get_download_links(self, repo):
download_l = []
branches_group = ([], _("Branches"))
tags_group = ([], _("Tags"))
for name, chs in c.rhodecode_repo.branches.items():
#chs = chs.split(':')[-1]
branches_group[0].append((chs, name),)
download_l.append(branches_group)
for name, chs in c.rhodecode_repo.tags.items():
tags_group[0].append((chs, name),)
download_l.append(tags_group)
return download_l
@@ -887,384 +887,389 @@ class Repository(Base, BaseModel):
return make_ui('db', clear_session=False)
@classmethod
def inject_ui(cls, repo, extras={}):
from rhodecode.lib.vcs.backends.hg import MercurialRepository
from rhodecode.lib.vcs.backends.git import GitRepository
required = (MercurialRepository, GitRepository)
if not isinstance(repo, required):
raise Exception('repo must be instance of %s' % required)
# inject ui extra param to log this action via push logger
for k, v in extras.items():
repo._repo.ui.setconfig('rhodecode_extras', k, v)
def is_valid(cls, repo_name):
returns True if given repo name is a valid filesystem repository
:param cls:
:param repo_name:
from rhodecode.lib.utils import is_valid_repo
return is_valid_repo(repo_name, cls.base_path())
def get_api_data(self):
Common function for generating repo api data
repo = self
data = dict(
repo_id=repo.repo_id,
repo_name=repo.repo_name,
repo_type=repo.repo_type,
clone_uri=repo.clone_uri,
private=repo.private,
created_on=repo.created_on,
description=repo.description,
landing_rev=repo.landing_rev,
owner=repo.user.username,
fork_of=repo.fork.repo_name if repo.fork else None,
enable_statistics=repo.enable_statistics,
enable_locking=repo.enable_locking,
enable_downloads=repo.enable_downloads,
last_changeset=repo.changeset_cache
return data
def lock(cls, repo, user_id):
repo.locked = [user_id, time.time()]
Session().add(repo)
Session().commit()
def unlock(cls, repo):
repo.locked = None
@property
def last_db_change(self):
return self.updated_on
def clone_url(self, **override):
from pylons import url
parsed_url = urlparse(url('home', qualified=True))
default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
args = {
'user': '',
'pass': '',
'prefix': decoded_path,
'path': self.repo_name
args.update(override)
return default_clone_uri % args
# SCM PROPERTIES
def get_changeset(self, rev=None):
return get_changeset_safe(self.scm_instance, rev)
def get_landing_changeset(self):
Returns landing changeset, or if that doesn't exist returns the tip
cs = self.get_changeset(self.landing_rev) or self.get_changeset()
return cs
def update_changeset_cache(self, cs_cache=None):
Update cache of last changeset for repository, keys should be::
short_id
raw_id
revision
message
date
author
:param cs_cache:
from rhodecode.lib.vcs.backends.base import BaseChangeset
if cs_cache is None:
cs_cache = self.get_changeset()
if isinstance(cs_cache, BaseChangeset):
cs_cache = cs_cache.__json__()
if (cs_cache != self.changeset_cache
or not self.last_change
or not self.changeset_cache):
_default = datetime.datetime.fromtimestamp(0)
last_change = cs_cache.get('date') or self.last_change or _default
log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
self.updated_on = last_change
self.changeset_cache = cs_cache
Session().add(self)
log.debug('Skipping repo:%s already with latest changes' % self)
def tip(self):
return self.get_changeset('tip')
def author(self):
return self.tip.author
def last_change(self):
return self.scm_instance.last_change
def get_comments(self, revisions=None):
Returns comments for this repository grouped by revisions
:param revisions: filter query by revisions only
cmts = ChangesetComment.query()\
.filter(ChangesetComment.repo == self)
if revisions:
cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
grouped = defaultdict(list)
for cmt in cmts.all():
grouped[cmt.revision].append(cmt)
return grouped
def statuses(self, revisions=None):
Returns statuses for this repository
:param revisions: list of revisions to get statuses for
:type revisions: list
statuses = ChangesetStatus.query()\
.filter(ChangesetStatus.repo == self)\
.filter(ChangesetStatus.version == 0)
statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
grouped = {}
#maybe we have open new pullrequest without a status ?
stat = ChangesetStatus.STATUS_UNDER_REVIEW
status_lbl = ChangesetStatus.get_status_lbl(stat)
for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
for rev in pr.revisions:
pr_id = pr.pull_request_id
pr_repo = pr.other_repo.repo_name
grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
for stat in statuses.all():
pr_id = pr_repo = None
if stat.pull_request:
pr_id = stat.pull_request.pull_request_id
pr_repo = stat.pull_request.other_repo.repo_name
grouped[stat.revision] = [str(stat.status), stat.status_lbl,
pr_id, pr_repo]
def _repo_size(self):
from rhodecode.lib import helpers as h
log.debug('calculating repository size...')
return h.format_byte_size(self.scm_instance.size)
# SCM CACHE INSTANCE
def invalidate(self):
return CacheInvalidation.invalidate(self.repo_name)
def set_invalidate(self):
set a cache for invalidation for this instance
CacheInvalidation.set_invalidate(repo_name=self.repo_name)
@LazyProperty
def scm_instance(self):
import rhodecode
full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
if full_cache:
return self.scm_instance_cached()
return self.__get_instance()
def scm_instance_cached(self, cache_map=None):
def _c(repo_name):
rn = self.repo_name
log.debug('Getting cached instance of repo')
if cache_map:
# get using prefilled cache_map
invalidate_repo = cache_map[self.repo_name]
if invalidate_repo:
invalidate_repo = (None if invalidate_repo.cache_active
else invalidate_repo)
# get from invalidate
invalidate_repo = self.invalidate
if invalidate_repo is not None:
region_invalidate(_c, None, rn)
# update our cache
CacheInvalidation.set_valid(invalidate_repo.cache_key)
return _c(rn)
def __get_instance(self):
repo_full_path = self.repo_full_path
alias = get_scm(repo_full_path)[0]
log.debug('Creating instance of %s repository' % alias)
backend = get_backend(alias)
except VCSError:
log.error('Perhaps this repository is in db and not in '
'filesystem run rescan repositories with '
'"destroy old data " option from admin panel')
return
if alias == 'hg':
repo = backend(safe_str(repo_full_path), create=False,
baseui=self._ui)
# skip hidden web repository
if repo._get_hidden():
repo = backend(repo_full_path, create=False)
return repo
class RepoGroup(Base, BaseModel):
__tablename__ = 'groups'
__table_args__ = (
UniqueConstraint('group_name', 'group_parent_id'),
CheckConstraint('group_id != group_parent_id'),
{'extend_existing': True, 'mysql_engine': 'InnoDB',
'mysql_charset': 'utf8'},
__mapper_args__ = {'order_by': 'group_name'}
group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
parent_group = relationship('RepoGroup', remote_side=group_id)
def __init__(self, group_name='', parent_group=None):
self.group_name = group_name
self.parent_group = parent_group
def __unicode__(self):
return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
self.group_name)
def groups_choices(cls, groups=None, show_empty_group=True):
from webhelpers.html import literal as _literal
if not groups:
groups = cls.query().all()
repo_groups = []
if show_empty_group:
repo_groups = [('-1', '-- no parent --')]
sep = ' » '
_name = lambda k: _literal(sep.join(k))
repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
for x in groups])
repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
return repo_groups
def url_sep(cls):
return URL_SEP
def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
if case_insensitive:
gr = cls.query()\
.filter(cls.group_name.ilike(group_name))
.filter(cls.group_name == group_name)
if cache:
gr = gr.options(FromCache(
"sql_cache_short",
"get_group_%s" % _hash_key(group_name)
return gr.scalar()
def parents(self):
parents_recursion_limit = 5
groups = []
if self.parent_group is None:
return groups
cur_gr = self.parent_group
groups.insert(0, cur_gr)
cnt = 0
while 1:
cnt += 1
gr = getattr(cur_gr, 'parent_group', None)
cur_gr = cur_gr.parent_group
if gr is None:
if cnt == parents_recursion_limit:
# this will prevent accidental infinit loops
log.error('group nested more than %s' %
parents_recursion_limit)
groups.insert(0, gr)
def children(self):
return RepoGroup.query().filter(RepoGroup.parent_group == self)
def name(self):
return self.group_name.split(RepoGroup.url_sep())[-1]
def full_path(self):
return self.group_name
def full_path_splitted(self):
return self.group_name.split(RepoGroup.url_sep())
def repositories(self):
return Repository.query()\
.filter(Repository.group == self)\
.order_by(Repository.repo_name)
def repositories_recursive_count(self):
cnt = self.repositories.count()
def children_count(group):
for child in group.children:
cnt += child.repositories.count()
@@ -3013,384 +3013,401 @@ table.code-browser .submodule-dir {
.box .search div.search_path div.link a {
color: #003367;
cursor: pointer;
text-decoration: none;
#path_unlock {
color: red;
font-size: 1.2em;
padding-left: 4px;
.info_box span {
margin-left: 3px;
margin-right: 3px;
.info_box .rev {
font-size: 1.6em;
font-weight: bold;
vertical-align: sub;
.info_box input#at_rev,.info_box input#size {
background: #FFF;
border-top: 1px solid #b3b3b3;
border-left: 1px solid #b3b3b3;
border-right: 1px solid #eaeaea;
border-bottom: 1px solid #eaeaea;
color: #000;
font-size: 12px;
margin: 0;
padding: 1px 5px 1px;
.info_box input#view {
text-align: center;
padding: 4px 3px 2px 2px;
.yui-overlay,.yui-panel-container {
visibility: hidden;
position: absolute;
z-index: 2;
#tip-box {
background-color: #FFF;
border: 2px solid #003367;
font: 100% sans-serif;
width: auto;
opacity: 1px;
padding: 8px;
white-space: pre-wrap;
-webkit-border-radius: 8px 8px 8px 8px;
-khtml-border-radius: 8px 8px 8px 8px;
-moz-border-radius: 8px 8px 8px 8px;
border-radius: 8px 8px 8px 8px;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
-moz-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
-webkit-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
.hl-tip-box {
color: #666;
.mentions-container{
width: 90% !important;
.mentions-container .yui-ac-content{
width: 100% !important;
.ac {
vertical-align: top;
.ac .yui-ac {
position: inherit;
font-size: 100%;
.ac .perm_ac {
width: 20em;
.ac .yui-ac-input {
width: 100%;
.ac .yui-ac-container {
top: 1.6em;
.ac .yui-ac-content {
border: 1px solid gray;
background: #fff;
z-index: 9050;
.ac .yui-ac-shadow {
background: #000;
-moz-opacity: 0.1px;
opacity: .10;
filter: alpha(opacity = 10);
z-index: 9049;
margin: .3em;
.ac .yui-ac-content ul {
padding: 0;
.ac .yui-ac-content li {
cursor: default;
white-space: nowrap;
padding: 2px 5px;
height: 18px;
display: block;
width: auto !important;
.ac .yui-ac-content li .ac-container-wrap{
.ac .yui-ac-content li.yui-ac-prehighlight {
background: #B3D4FF;
.ac .yui-ac-content li.yui-ac-highlight {
background: #556CB5;
color: #FFF;
.ac .yui-ac-bd{
.follow {
background: url("../images/icons/heart_add.png") no-repeat scroll 3px;
height: 16px;
width: 20px;
float: right;
margin-top: 2px;
.following {
background: url("../images/icons/heart_delete.png") no-repeat scroll 3px;
.reposize {
background: url("../images/icons/server.png") no-repeat scroll 3px;
#repo_size{
margin-top: 4px;
float:right;
.locking_locked{
background: #FFF url("../images/icons/block_16.png") no-repeat scroll 3px;
.locking_unlocked{
background: #FFF url("../images/icons/accept.png") no-repeat scroll 3px;
.currently_following {
padding-left: 10px;
padding-bottom: 5px;
.add_icon {
background: url("../images/icons/add.png") no-repeat scroll 3px;
padding-left: 20px;
padding-top: 0px;
text-align: left;
.accept_icon {
background: url("../images/icons/accept.png") no-repeat scroll 3px;
.edit_icon {
background: url("../images/icons/application_form_edit.png") no-repeat scroll 3px;
.delete_icon {
background: url("../images/icons/delete.png") no-repeat scroll 3px;
.refresh_icon {
background: url("../images/icons/arrow_refresh.png") no-repeat scroll
3px;
.pull_icon {
background: url("../images/icons/connect.png") no-repeat scroll 3px;
.rss_icon {
background: url("../images/icons/rss_16.png") no-repeat scroll 3px;
padding-top: 4px;
font-size: 8px
.atom_icon {
background: url("../images/icons/atom.png") no-repeat scroll 3px;
.archive_icon {
background: url("../images/icons/compress.png") no-repeat scroll 3px;
padding-top: 1px;
.start_following_icon {
.stop_following_icon {
.action_button {
border: 0;
display: inline;
.action_button:hover {
text-decoration: underline;
#switch_repos {
height: 25px;
z-index: 1;
#switch_repos select {
min-width: 150px;
max-height: 250px;
.breadcrumbs {
border: medium none;
float: left;
font-weight: 700;
font-size: 14px;
padding: 11px 0 11px 10px;
.breadcrumbs .hash {
text-transform: none;
color: #fff;
.breadcrumbs a {
.flash_msg {
.flash_msg ul {
.error_red {
color:red;
.error_msg {
background-color: #c43c35;
background-repeat: repeat-x;
background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35) );
background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35) );
background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
background-image: linear-gradient(top, #ee5f5b, #c43c35);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b',endColorstr='#c43c35', GradientType=0 );
border-color: #c43c35 #c43c35 #882a25;
.warning_msg {
color: #404040 !important;
background-color: #eedc94;
background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94) );
background-image: -moz-linear-gradient(top, #fceec1, #eedc94);
background-image: -ms-linear-gradient(top, #fceec1, #eedc94);
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94) );
background-image: -webkit-linear-gradient(top, #fceec1, #eedc94);
background-image: -o-linear-gradient(top, #fceec1, #eedc94);
background-image: linear-gradient(top, #fceec1, #eedc94);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0 );
border-color: #eedc94 #eedc94 #e4c652;
.success_msg {
background-color: #57a957;
background-repeat: repeat-x !important;
background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957) );
@@ -202,384 +202,397 @@ var _run_callbacks = function(callbacks)
/**
* Partial Ajax Implementation
*
* @param url: defines url to make partial request
* @param container: defines id of container to input partial result
* @param s_call: success callback function that takes o as arg
* o.tId
* o.status
* o.statusText
* o.getResponseHeader[ ]
* o.getAllResponseHeaders
* o.responseText
* o.responseXML
* o.argument
* @param f_call: failure callback
* @param args arguments
*/
function ypjax(url,container,s_call,f_call,args){
var method='GET';
if(args===undefined){
args=null;
// Set special header for partial ajax == HTTP_X_PARTIAL_XHR
YUC.initHeader('X-PARTIAL-XHR',true);
// wrapper of passed callback
var s_wrapper = (function(o){
return function(o){
YUD.get(container).innerHTML=o.responseText;
YUD.setStyle(container,'opacity','1.0');
//execute the given original callback
if (s_call !== undefined){
s_call(o);
})()
YUD.setStyle(container,'opacity','0.3');
YUC.asyncRequest(method,url,{
success:s_wrapper,
failure:function(o){
console.log(o);
YUD.get(container).innerHTML='<span class="error_red">ERROR: {0}</span>'.format(o.status);
},
cache:false
},args);
};
var ajaxGET = function(url,success) {
// Set special header for ajax == HTTP_X_PARTIAL_XHR
var sUrl = url;
var callback = {
success: success,
failure: function (o) {
alert("error");
var request = YAHOO.util.Connect.asyncRequest('GET', sUrl, callback);
return request;
var ajaxPOST = function(url,postData,success) {
var toQueryString = function(o) {
if(typeof o !== 'object') {
return false;
var _p, _qs = [];
for(_p in o) {
_qs.push(encodeURIComponent(_p) + '=' + encodeURIComponent(o[_p]));
return _qs.join('&');
var postData = toQueryString(postData);
var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
* tooltip activate
var tooltip_activate = function(){
yt = YAHOO.yuitip.main;
YUE.onDOMReady(yt.init);
* show more
var show_more_event = function(){
YUE.on(YUD.getElementsByClassName('show_more'),'click',function(e){
var el = e.target;
YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
YUD.setStyle(el.parentNode,'display','none');
});
* show changeset tooltip
var show_changeset_tooltip = function(){
YUE.on(YUD.getElementsByClassName('lazy-cs'), 'mouseover', function(e){
var target = e.currentTarget;
var rid = YUD.getAttribute(target,'raw_id');
var repo_name = YUD.getAttribute(target,'repo_name');
var ttid = 'tt-'+rid;
var success = function(o){
var json = JSON.parse(o.responseText);
YUD.addClass(target,'tooltip')
YUD.setAttribute(target, 'title',json['message']);
YAHOO.yuitip.main.show_yuitip(e, target);
if(rid && !YUD.hasClass(target, 'tooltip')){
YUD.setAttribute(target,'id',ttid);
YUD.setAttribute(target, 'title',_TM['loading...']);
YAHOO.yuitip.main.set_listeners(target);
ajaxGET(LAZY_CS_URL.replace('__NAME__',repo_name).replace('__REV__', rid), success)
var onSuccessFollow = function(target){
var f = YUD.get(target.id);
var f_cnt = YUD.get('current_followers_count');
if(YUD.hasClass(f, 'follow')){
f.setAttribute('class','following');
f.setAttribute('title',_TM['Stop following this repository']);
if(f_cnt){
var cnt = Number(f_cnt.innerHTML)+1;
f_cnt.innerHTML = cnt;
else{
f.setAttribute('class','follow');
f.setAttribute('title',_TM['Start following this repository']);
var cnt = Number(f_cnt.innerHTML)-1;
var toggleFollowingUser = function(target,fallows_user_id,token,user_id){
args = 'follows_user_id='+fallows_user_id;
args+= '&auth_token='+token;
if(user_id != undefined){
args+="&user_id="+user_id;
YUC.asyncRequest('POST',TOGGLE_FOLLOW_URL,{
success:function(o){
onSuccessFollow(target);
var toggleFollowingRepo = function(target,fallows_repo_id,token,user_id){
args = 'follows_repo_id='+fallows_repo_id;
var showRepoSize = function(target, repo_name, token){
var args= 'auth_token='+token;
// start loading
YUD.get(target).innerHTML = _TM['loading...'];
var url = REPO_SIZE_URL.replace('__NAME__', repo_name);
YUC.asyncRequest('POST',url,{
YUD.get(target).innerHTML = JSON.parse(o.responseText);
* TOOLTIP IMPL.
YAHOO.namespace('yuitip');
YAHOO.yuitip.main = {
$: YAHOO.util.Dom.get,
bgColor: '#000',
speed: 0.3,
opacity: 0.9,
offset: [15,15],
useAnim: false,
maxWidth: 600,
add_links: false,
yuitips: [],
set_listeners: function(tt){
YUE.on(tt, 'mouseover', yt.show_yuitip, tt);
YUE.on(tt, 'mousemove', yt.move_yuitip, tt);
YUE.on(tt, 'mouseout', yt.close_yuitip, tt);
init: function(){
yt.tipBox = yt.$('tip-box');
if(!yt.tipBox){
yt.tipBox = document.createElement('div');
document.body.appendChild(yt.tipBox);
yt.tipBox.id = 'tip-box';
YUD.setStyle(yt.tipBox, 'display', 'none');
YUD.setStyle(yt.tipBox, 'position', 'absolute');
if(yt.maxWidth !== null){
YUD.setStyle(yt.tipBox, 'max-width', yt.maxWidth+'px');
var yuitips = YUD.getElementsByClassName('tooltip');
if(yt.add_links === true){
var links = document.getElementsByTagName('a');
var linkLen = links.length;
for(i=0;i<linkLen;i++){
yuitips.push(links[i]);
var yuiLen = yuitips.length;
for(i=0;i<yuiLen;i++){
yt.set_listeners(yuitips[i]);
show_yuitip: function(e, el){
YUE.stopEvent(e);
if(el.tagName.toLowerCase() === 'img'){
yt.tipText = el.alt ? el.alt : '';
} else {
yt.tipText = el.title ? el.title : '';
if(yt.tipText !== ''){
// save org title
YUD.setAttribute(el, 'tt_title', yt.tipText);
// reset title to not show org tooltips
YUD.setAttribute(el, 'title', '');
yt.tipBox.innerHTML = yt.tipText;
YUD.setStyle(yt.tipBox, 'display', 'block');
if(yt.useAnim === true){
YUD.setStyle(yt.tipBox, 'opacity', '0');
var newAnim = new YAHOO.util.Anim(yt.tipBox,
{
opacity: { to: yt.opacity }
}, yt.speed, YAHOO.util.Easing.easeOut
);
newAnim.animate();
move_yuitip: function(e, el){
var movePos = YUE.getXY(e);
YUD.setStyle(yt.tipBox, 'top', (movePos[1] + yt.offset[1]) + 'px');
YUD.setStyle(yt.tipBox, 'left', (movePos[0] + yt.offset[0]) + 'px');
close_yuitip: function(e, el){
opacity: { to: 0 }
YUD.setAttribute(el,'title', YUD.getAttribute(el, 'tt_title'));
* Quick filter widget
* @param target: filter input target
* @param nodes: list of nodes in html we want to filter.
* @param display_element function that takes current node from nodes and
* does hide or show based on the node
var q_filter = function(target,nodes,display_element){
var nodes = nodes;
var q_filter_field = YUD.get(target);
var F = YAHOO.namespace(target);
YUE.on(q_filter_field,'click',function(){
q_filter_field.value = '';
YUE.on(q_filter_field,'keyup',function(e){
clearTimeout(F.filterTimeout);
F.filterTimeout = setTimeout(F.updateFilter,600);
F.filterTimeout = null;
var show_node = function(node){
YUD.setStyle(node,'display','')
var hide_node = function(node){
YUD.setStyle(node,'display','none');
F.updateFilter = function() {
// Reset timeout
var obsolete = [];
var req = q_filter_field.value.toLowerCase();
var l = nodes.length;
var i;
var showing = 0;
for (i=0;i<l;i++ ){
var n = nodes[i];
var target_element = display_element(n)
if(req && n.innerHTML.toLowerCase().indexOf(req) == -1){
hide_node(target_element);
show_node(target_element);
showing+=1;
// if repo_count is set update the number
var cnt = YUD.get('repo_count');
if(cnt){
YUD.get('repo_count').innerHTML = showing;
var tableTr = function(cls, body){
var _el = document.createElement('div');
var cont = new YAHOO.util.Element(body);
var comment_id = fromHTML(body).children[0].id.split('comment-')[1];
var id = 'comment-tr-{0}'.format(comment_id);
var _html = ('<table><tbody><tr id="{0}" class="{1}">'+
'<td class="lineno-inline new-inline"></td>'+
'<td class="lineno-inline old-inline"></td>'+
'<td>{2}</td>'+
'</tr></tbody></table>').format(id, cls, body);
_el.innerHTML = _html;
return _el.children[0].children[0].children[0];
/** comments **/
var removeInlineForm = function(form) {
form.parentNode.removeChild(form);
## -*- coding: utf-8 -*-
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>${self.title()}</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name="robots" content="index, nofollow"/>
<link rel="icon" href="${h.url('/images/icons/database_gear.png')}" type="image/png" />
## CSS ###
<%def name="css()">
<link rel="stylesheet" type="text/css" href="${h.url('/css/style.css', ver=c.rhodecode_version)}" media="screen"/>
<link rel="stylesheet" type="text/css" href="${h.url('/css/pygments.css', ver=c.rhodecode_version)}"/>
## EXTRA FOR CSS
${self.css_extra()}
</%def>
<%def name="css_extra()">
${self.css()}
%if c.ga_code:
<!-- Analytics -->
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', '${c.ga_code}']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
%endif
## JAVASCRIPT ##
<%def name="js()">
//JS translations map
var TRANSLATION_MAP = {
'add another comment':'${_("add another comment")}',
'Stop following this repository':"${_('Stop following this repository')}",
'Start following this repository':"${_('Start following this repository')}",
'Group':"${_('Group')}",
'members':"${_('members')}",
'loading...':"${_('loading...')}",
'search truncated': "${_('search truncated')}",
'no matching files': "${_('no matching files')}",
'Open new pull request': "${_('Open new pull request')}",
'Open new pull request for selected changesets': "${_('Open new pull request for selected changesets')}",
'Show selected changes __S -> __E': "${_('Show selected changes __S -> __E')}",
'Selection link': "${_('Selection link')}",
var _TM = TRANSLATION_MAP;
var TOGGLE_FOLLOW_URL = "${h.url('toggle_following')}";
var LAZY_CS_URL = "${h.url('changeset_info', repo_name='__NAME__', revision='__REV__')}"
var LAZY_CS_URL = "${h.url('changeset_info', repo_name='__NAME__', revision='__REV__')}";
var REPO_SIZE_URL = "${h.url('repo_size', repo_name='__NAME__')}";
<script type="text/javascript" src="${h.url('/js/yui.2.9.js', ver=c.rhodecode_version)}"></script>
<!--[if lt IE 9]>
<script language="javascript" type="text/javascript" src="${h.url('/js/excanvas.min.js')}"></script>
<![endif]-->
<script type="text/javascript" src="${h.url('/js/yui.flot.js', ver=c.rhodecode_version)}"></script>
<script type="text/javascript" src="${h.url('/js/native.history.js', ver=c.rhodecode_version)}"></script>
<script type="text/javascript" src="${h.url('/js/rhodecode.js', ver=c.rhodecode_version)}"></script>
## EXTRA FOR JS
${self.js_extra()}
(function(window,undefined){
// Prepare
var History = window.History; // Note: We are using a capital H instead of a lower h
if ( !History.enabled ) {
// History.js is disabled for this browser.
// This is because we can optionally choose to support HTML4 browsers or not.
})(window);
YUE.onDOMReady(function(){
tooltip_activate();
show_more_event();
show_changeset_tooltip();
})
<%def name="js_extra()"></%def>
${self.js()}
<%def name="head_extra()"></%def>
${self.head_extra()}
</head>
<body id="body">
## IE hacks
<!--[if IE 7]>
<script>YUD.addClass(document.body,'ie7')</script>
<!--[if IE 8]>
<script>YUD.addClass(document.body,'ie8')</script>
<!--[if IE 9]>
<script>YUD.addClass(document.body,'ie9')</script>
${next.body()}
</body>
</html>
<%inherit file="/base/base.html"/>
<%def name="title()">
${_('%s Summary') % c.repo_name} - ${c.rhodecode_name}
<%def name="breadcrumbs_links()">
${h.link_to(_(u'Home'),h.url('/'))}
»
${h.repo_link(c.dbrepo.groups_and_repo)}
${_('summary')}
<%def name="page_nav()">
${self.menu('summary')}
<%def name="head_extra()">
<link href="${h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('repo %s ATOM feed') % c.repo_name}" type="application/atom+xml" />
<link href="${h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('repo %s RSS feed') % c.repo_name}" type="application/rss+xml" />
<%def name="main()">
<%
summary = lambda n:{False:'summary-short'}.get(n)
%>
%if c.show_stats:
<div class="box box-left">
%else:
<div class="box">
<!-- box / title -->
<div class="title">
${self.breadcrumbs()}
</div>
<!-- end box / title -->
<div class="form">
<div id="summary" class="fields">
<div class="field">
<div class="label-summary">
<label>${_('Name')}:</label>
<div class="input ${summary(c.show_stats)}">
<div style="float:right;padding:5px 0px 0px 5px">
%if c.rhodecode_user.username != 'default':
${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.dbrepo.repo_name),class_='rss_icon')}
${h.link_to(_('ATOM'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name),class_='atom_icon')}
%if c.following:
<span id="follow_toggle" class="following tooltip" title="${_('Stop following this repository')}"
onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
</span>
<span id="follow_toggle" class="follow tooltip" title="${_('Start following this repository')}"
<div style="float:right;padding:0px 0px 0px 0px">
<span class="reposize tooltip" title="${_('Click to show size of repository')}"
onclick="javascript:showRepoSize('repo_size','${c.dbrepo.repo_name}','${str(h.get_token())}')">
<span id="repo_size"></span>
%endif:
## locking icon
%if c.rhodecode_db_repo.enable_locking:
%if c.rhodecode_db_repo.locked[0]:
<span class="locking_locked tooltip" title="${_('Repository locked by %s') % h.person_by_id(c.rhodecode_db_repo.locked[0])}"></span>
<span class="locking_unlocked tooltip" title="${_('Repository unlocked')}"></span>
##REPO TYPE
%if h.is_hg(c.dbrepo):
<img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
%if h.is_git(c.dbrepo):
<img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
##PUBLIC/PRIVATE
%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')}"/>
##REPO NAME
<span class="repo_name" title="${_('Non changable ID %s') % c.dbrepo.repo_id}">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
##FORK
%if c.dbrepo.fork:
<div style="margin-top:5px;clear:both"">
<a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}"><img class="icon" alt="${_('public')}" title="${_('Fork of')} ${c.dbrepo.fork.repo_name}" src="${h.url('/images/icons/arrow_divide.png')}"/>
${_('Fork of')} ${c.dbrepo.fork.repo_name}
</a>
##REMOTE
%if c.dbrepo.clone_uri:
<div style="margin-top:5px;clear:both">
<a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}"><img class="icon" alt="${_('remote clone')}" title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}" src="${h.url('/images/icons/connect.png')}"/>
${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
<label>${_('Description')}:</label>
%if c.visual.stylify_metatags:
<div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.desc_stylize(c.dbrepo.description))}</div>
<div class="input ${summary(c.show_stats)} desc">${h.urlify_text(c.dbrepo.description)}</div>
<label>${_('Contact')}:</label>
<div class="gravatar">
<img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
${_('Username')}: ${c.dbrepo.user.username}<br/>
${_('Name')}: ${c.dbrepo.user.name} ${c.dbrepo.user.lastname}<br/>
${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
<label>${_('Clone url')}:</label>
<div style="display:none" id="clone_by_name" class="ui-btn clone">${_('Show by Name')}</div>
<div id="clone_by_id" class="ui-btn clone">${_('Show by ID')}</div>
<input style="width:80%;margin-left:105px" type="text" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
<input style="display:none;width:80%;margin-left:105px" type="text" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}"/>
<label>${_('Trending files')}:</label>
<div id="lang_stats"></div>
${_('Statistics 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),class_="ui-btn")}
<label>${_('Download')}:</label>
%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 downloads on from summary'):
${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
<span id="${'zip_link'}">${h.link_to(_('Download as zip'), h.url('files_archive_home',repo_name=c.dbrepo.repo_name,fname='tip.zip'),class_="archive_icon ui-btn")}</span>
<span style="vertical-align: bottom">
<input id="archive_subrepos" type="checkbox" name="subrepos" />
<label for="archive_subrepos" class="tooltip" title="${h.tooltip(_('Check this to download archive with subrepos'))}" >${_('with subrepos')}</label>
<div class="box box-right" style="min-height:455px">
<h5>${_('Commit activity by day / author')}</h5>
<div class="graph">
<div style="padding:0 10px 10px 17px;">
%if c.no_data:
${c.no_data_msg}
${_('Stats gathered: ')} ${c.stats_percentage}%
<div id="commit_history" style="width:450px;height:300px;float:left"></div>
<div style="clear: both;height: 10px"></div>
<div id="overview" style="width:450px;height:100px;float:left"></div>
<div id="legend_data" style="clear:both;margin-top:10px;">
<div id="legend_container"></div>
<div id="legend_choices">
<table id="legend_choices_tables" class="noborder" style="font-size:smaller;color:#545454"></table>
<div class="breadcrumbs">
%if c.repo_changesets:
${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}
${_('Quick start')}
<div class="table">
<div id="shortlog_data">
<%include file='../shortlog/shortlog_data.html'/>
%if c.readme_data:
<div id="readme" class="anchor">
<div class="box" style="background-color: #FAFAFA">
<div class="title" title="${_("Readme file at revision '%s'" % c.rhodecode_db_repo.landing_rev)}">
<a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a>
<a class="permalink" href="#readme" title="${_('Permalink to this readme')}">¶</a>
<div class="readme">
<div class="readme_box">
${c.readme_data|n}
Status change: