|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
# Copyright (C) 2010-2017 RhodeCode GmbH
|
|
|
#
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
# it under the terms of the GNU Affero General Public License, version 3
|
|
|
# (only), as published by the Free Software Foundation.
|
|
|
#
|
|
|
# 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 Affero General Public License
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
#
|
|
|
# This program is dual-licensed. If you wish to learn more about the
|
|
|
# RhodeCode Enterprise Edition, including its added features, Support services,
|
|
|
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
|
|
|
|
|
"""
|
|
|
Summary controller for RhodeCode Enterprise
|
|
|
"""
|
|
|
|
|
|
import logging
|
|
|
from string import lower
|
|
|
|
|
|
from pylons import tmpl_context as c, request
|
|
|
from pylons.i18n.translation import _
|
|
|
from beaker.cache import cache_region
|
|
|
|
|
|
from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
|
|
|
from rhodecode.controllers import utils
|
|
|
from rhodecode.controllers.changelog import _load_changelog_summary
|
|
|
from rhodecode.lib import caches, helpers as h
|
|
|
from rhodecode.lib.utils import jsonify
|
|
|
from rhodecode.lib.utils2 import safe_str
|
|
|
from rhodecode.lib.auth import (
|
|
|
LoginRequired, HasRepoPermissionAnyDecorator, XHRRequired)
|
|
|
from rhodecode.lib.base import BaseRepoController, render
|
|
|
from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
|
|
|
from rhodecode.lib.ext_json import json
|
|
|
from rhodecode.lib.vcs.backends.base import EmptyCommit
|
|
|
from rhodecode.lib.vcs.exceptions import CommitError, EmptyRepositoryError
|
|
|
from rhodecode.model.db import Statistics, CacheKey, User
|
|
|
from rhodecode.model.repo import ReadmeFinder
|
|
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
class SummaryController(BaseRepoController):
|
|
|
|
|
|
def __before__(self):
|
|
|
super(SummaryController, self).__before__()
|
|
|
|
|
|
def __get_readme_data(self, db_repo):
|
|
|
repo_name = db_repo.repo_name
|
|
|
log.debug('Looking for README file')
|
|
|
default_renderer = c.visual.default_renderer
|
|
|
|
|
|
@cache_region('long_term')
|
|
|
def _generate_readme(cache_key):
|
|
|
readme_data = None
|
|
|
readme_node = None
|
|
|
readme_filename = None
|
|
|
commit = self._get_landing_commit_or_none(db_repo)
|
|
|
if commit:
|
|
|
log.debug("Searching for a README file.")
|
|
|
readme_node = ReadmeFinder(default_renderer).search(commit)
|
|
|
if readme_node:
|
|
|
relative_url = h.url('files_raw_home',
|
|
|
repo_name=repo_name,
|
|
|
revision=commit.raw_id,
|
|
|
f_path=readme_node.path)
|
|
|
readme_data = self._render_readme_or_none(
|
|
|
commit, readme_node, relative_url)
|
|
|
readme_filename = readme_node.path
|
|
|
return readme_data, readme_filename
|
|
|
|
|
|
invalidator_context = CacheKey.repo_context_cache(
|
|
|
_generate_readme, repo_name, CacheKey.CACHE_TYPE_README)
|
|
|
|
|
|
with invalidator_context as context:
|
|
|
context.invalidate()
|
|
|
computed = context.compute()
|
|
|
|
|
|
return computed
|
|
|
|
|
|
def _get_landing_commit_or_none(self, db_repo):
|
|
|
log.debug("Getting the landing commit.")
|
|
|
try:
|
|
|
commit = db_repo.get_landing_commit()
|
|
|
if not isinstance(commit, EmptyCommit):
|
|
|
return commit
|
|
|
else:
|
|
|
log.debug("Repository is empty, no README to render.")
|
|
|
except CommitError:
|
|
|
log.exception(
|
|
|
"Problem getting commit when trying to render the README.")
|
|
|
|
|
|
def _render_readme_or_none(self, commit, readme_node, relative_url):
|
|
|
log.debug(
|
|
|
'Found README file `%s` rendering...', readme_node.path)
|
|
|
renderer = MarkupRenderer()
|
|
|
try:
|
|
|
html_source = renderer.render(
|
|
|
readme_node.content, filename=readme_node.path)
|
|
|
if relative_url:
|
|
|
return relative_links(html_source, relative_url)
|
|
|
return html_source
|
|
|
except Exception:
|
|
|
log.exception(
|
|
|
"Exception while trying to render the README")
|
|
|
|
|
|
@LoginRequired()
|
|
|
@HasRepoPermissionAnyDecorator(
|
|
|
'repository.read', 'repository.write', 'repository.admin')
|
|
|
def index(self, repo_name):
|
|
|
|
|
|
# Prepare the clone URL
|
|
|
|
|
|
username = ''
|
|
|
if c.rhodecode_user.username != User.DEFAULT_USER:
|
|
|
username = safe_str(c.rhodecode_user.username)
|
|
|
|
|
|
_def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl
|
|
|
if '{repo}' in _def_clone_uri:
|
|
|
_def_clone_uri_by_id = _def_clone_uri.replace(
|
|
|
'{repo}', '_{repoid}')
|
|
|
elif '{repoid}' in _def_clone_uri:
|
|
|
_def_clone_uri_by_id = _def_clone_uri.replace(
|
|
|
'_{repoid}', '{repo}')
|
|
|
|
|
|
c.clone_repo_url = c.rhodecode_db_repo.clone_url(
|
|
|
user=username, uri_tmpl=_def_clone_uri)
|
|
|
c.clone_repo_url_id = c.rhodecode_db_repo.clone_url(
|
|
|
user=username, uri_tmpl=_def_clone_uri_by_id)
|
|
|
|
|
|
# If enabled, get statistics data
|
|
|
|
|
|
c.show_stats = bool(c.rhodecode_db_repo.enable_statistics)
|
|
|
|
|
|
stats = self.sa.query(Statistics)\
|
|
|
.filter(Statistics.repository == c.rhodecode_db_repo)\
|
|
|
.scalar()
|
|
|
|
|
|
c.stats_percentage = 0
|
|
|
|
|
|
if stats and stats.languages:
|
|
|
c.no_data = False is c.rhodecode_db_repo.enable_statistics
|
|
|
lang_stats_d = json.loads(stats.languages)
|
|
|
|
|
|
# Sort first by decreasing count and second by the file extension,
|
|
|
# so we have a consistent output.
|
|
|
lang_stats_items = sorted(lang_stats_d.iteritems(),
|
|
|
key=lambda k: (-k[1], k[0]))[:10]
|
|
|
lang_stats = [(x, {"count": y,
|
|
|
"desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
|
|
|
for x, y in lang_stats_items]
|
|
|
|
|
|
c.trending_languages = json.dumps(lang_stats)
|
|
|
else:
|
|
|
c.no_data = True
|
|
|
c.trending_languages = json.dumps({})
|
|
|
|
|
|
c.enable_downloads = c.rhodecode_db_repo.enable_downloads
|
|
|
c.repository_followers = self.scm_model.get_followers(
|
|
|
c.rhodecode_db_repo)
|
|
|
c.repository_forks = self.scm_model.get_forks(c.rhodecode_db_repo)
|
|
|
c.repository_is_user_following = self.scm_model.is_following_repo(
|
|
|
c.repo_name, c.rhodecode_user.user_id)
|
|
|
|
|
|
if c.repository_requirements_missing:
|
|
|
return render('summary/missing_requirements.mako')
|
|
|
|
|
|
c.readme_data, c.readme_file = \
|
|
|
self.__get_readme_data(c.rhodecode_db_repo)
|
|
|
|
|
|
_load_changelog_summary()
|
|
|
|
|
|
if request.is_xhr:
|
|
|
return render('changelog/changelog_summary_data.mako')
|
|
|
|
|
|
return render('summary/summary.mako')
|
|
|
|
|
|
@LoginRequired()
|
|
|
@XHRRequired()
|
|
|
@HasRepoPermissionAnyDecorator(
|
|
|
'repository.read', 'repository.write', 'repository.admin')
|
|
|
@jsonify
|
|
|
def repo_stats(self, repo_name, commit_id):
|
|
|
_namespace = caches.get_repo_namespace_key(
|
|
|
caches.SUMMARY_STATS, repo_name)
|
|
|
show_stats = bool(c.rhodecode_db_repo.enable_statistics)
|
|
|
cache_manager = caches.get_cache_manager('repo_cache_long', _namespace)
|
|
|
_cache_key = caches.compute_key_from_params(
|
|
|
repo_name, commit_id, show_stats)
|
|
|
|
|
|
def compute_stats():
|
|
|
code_stats = {}
|
|
|
size = 0
|
|
|
try:
|
|
|
scm_instance = c.rhodecode_db_repo.scm_instance()
|
|
|
commit = scm_instance.get_commit(commit_id)
|
|
|
|
|
|
for node in commit.get_filenodes_generator():
|
|
|
size += node.size
|
|
|
if not show_stats:
|
|
|
continue
|
|
|
ext = lower(node.extension)
|
|
|
ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
|
|
|
if ext_info:
|
|
|
if ext in code_stats:
|
|
|
code_stats[ext]['count'] += 1
|
|
|
else:
|
|
|
code_stats[ext] = {"count": 1, "desc": ext_info}
|
|
|
except EmptyRepositoryError:
|
|
|
pass
|
|
|
return {'size': h.format_byte_size_binary(size),
|
|
|
'code_stats': code_stats}
|
|
|
|
|
|
stats = cache_manager.get(_cache_key, createfunc=compute_stats)
|
|
|
return stats
|
|
|
|
|
|
def _switcher_reference_data(self, repo_name, references, is_svn):
|
|
|
"""Prepare reference data for given `references`"""
|
|
|
items = []
|
|
|
for name, commit_id in references.items():
|
|
|
use_commit_id = '/' in name or is_svn
|
|
|
items.append({
|
|
|
'name': name,
|
|
|
'commit_id': commit_id,
|
|
|
'files_url': h.url(
|
|
|
'files_home',
|
|
|
repo_name=repo_name,
|
|
|
f_path=name if is_svn else '',
|
|
|
revision=commit_id if use_commit_id else name,
|
|
|
at=name)
|
|
|
})
|
|
|
return items
|
|
|
|
|
|
@LoginRequired()
|
|
|
@HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
|
|
|
'repository.admin')
|
|
|
@jsonify
|
|
|
def repo_refs_data(self, repo_name):
|
|
|
repo = c.rhodecode_repo
|
|
|
refs_to_create = [
|
|
|
(_("Branch"), repo.branches, 'branch'),
|
|
|
(_("Tag"), repo.tags, 'tag'),
|
|
|
(_("Bookmark"), repo.bookmarks, 'book'),
|
|
|
]
|
|
|
res = self._create_reference_data(repo, repo_name, refs_to_create)
|
|
|
data = {
|
|
|
'more': False,
|
|
|
'results': res
|
|
|
}
|
|
|
return data
|
|
|
|
|
|
@jsonify
|
|
|
def repo_refs_changelog_data(self, repo_name):
|
|
|
repo = c.rhodecode_repo
|
|
|
|
|
|
refs_to_create = [
|
|
|
(_("Branches"), repo.branches, 'branch'),
|
|
|
(_("Closed branches"), repo.branches_closed, 'branch_closed'),
|
|
|
# TODO: enable when vcs can handle bookmarks filters
|
|
|
# (_("Bookmarks"), repo.bookmarks, "book"),
|
|
|
]
|
|
|
res = self._create_reference_data(repo, repo_name, refs_to_create)
|
|
|
data = {
|
|
|
'more': False,
|
|
|
'results': res
|
|
|
}
|
|
|
return data
|
|
|
|
|
|
def _create_reference_data(self, repo, full_repo_name, refs_to_create):
|
|
|
format_ref_id = utils.get_format_ref_id(repo)
|
|
|
|
|
|
result = []
|
|
|
for title, refs, ref_type in refs_to_create:
|
|
|
if refs:
|
|
|
result.append({
|
|
|
'text': title,
|
|
|
'children': self._create_reference_items(
|
|
|
repo, full_repo_name, refs, ref_type, format_ref_id),
|
|
|
})
|
|
|
return result
|
|
|
|
|
|
def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
|
|
|
format_ref_id):
|
|
|
result = []
|
|
|
is_svn = h.is_svn(repo)
|
|
|
for ref_name, raw_id in refs.iteritems():
|
|
|
files_url = self._create_files_url(
|
|
|
repo, full_repo_name, ref_name, raw_id, is_svn)
|
|
|
result.append({
|
|
|
'text': ref_name,
|
|
|
'id': format_ref_id(ref_name, raw_id),
|
|
|
'raw_id': raw_id,
|
|
|
'type': ref_type,
|
|
|
'files_url': files_url,
|
|
|
})
|
|
|
return result
|
|
|
|
|
|
def _create_files_url(self, repo, full_repo_name, ref_name, raw_id,
|
|
|
is_svn):
|
|
|
use_commit_id = '/' in ref_name or is_svn
|
|
|
return h.url(
|
|
|
'files_home',
|
|
|
repo_name=full_repo_name,
|
|
|
f_path=ref_name if is_svn else '',
|
|
|
revision=raw_id if use_commit_id else ref_name,
|
|
|
at=ref_name)
|
|
|
|