@@ -61,175 +61,173 @@ README_FILES = [''.join([x[0][0], x[1][0
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)
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')
uri_dict = {
'user': username,
'pass': password,
'scheme': parsed_url.scheme,
'netloc': parsed_url.netloc,
'path': parsed_url.path
}
uri = uri_tmpl % uri_dict
# generate another clone url by id
uri_dict.update({'path': '/_%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(OrderedDict(
sorted(lang_stats, reverse=True,
key=lambda k: k[1])[:10]
)
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_repo)
return render('summary/summary.html')
def __get_readme_data(self, repo):
@cache_region('long_term')
def _get_readme_from_cache(key):
readme_data = None
readme_file = None
log.debug('Fetching readme file')
cs = repo.get_changeset('tip')
renderer = MarkupRenderer()
for f in README_FILES:
readme = cs.get_node(f)
readme_file = f
readme_data = renderer.render(readme.content, f)
log.debug('Found readme %s' % readme_file)
break
except NodeDoesNotExistError:
continue
except EmptyRepositoryError:
except Exception:
log.error(traceback.format_exc())
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
# -*- coding: utf-8 -*-
"""
rhodecode.lib.compat
~~~~~~~~~~~~~~~~~~~~
Python backward compatibility functions and common libs
:created_on: Oct 7, 2011
:author: marcink
:copyright: (C) 2010-2010 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 os
from rhodecode import __platform__, PLATFORM_WIN
#==============================================================================
# json
import json
except ImportError:
import simplejson as json
# izip_longest
from itertools import izip_longest
import itertools
def izip_longest(*args, **kwds): # noqa
fillvalue = kwds.get("fillvalue")
def sentinel(counter=([fillvalue] * (len(args) - 1)).pop):
yield counter() # yields the fillvalue, or raises IndexError
fillers = itertools.repeat(fillvalue)
iters = [itertools.chain(it, sentinel(), fillers)
for it in args]
for tup in itertools.izip(*iters):
yield tup
except IndexError:
# OrderedDict
# Python Software Foundation License
# XXX: it feels like using the class with "is" and "is not" instead of "==" and
# "!=" should be faster.
class _Nil(object):
def __repr__(self):
return "nil"
def __eq__(self, other):
if (isinstance(other, _Nil)):
return True
return NotImplemented
def __ne__(self, other):
return False
_nil = _Nil()
class _odict(object):
"""Ordered dict data structure, with O(1) complexity for dict operations
that modify one element.
Overwriting values doesn't change their original sequential order.
def _dict_impl(self):
return None
def __init__(self, data=(), **kwds):
"""This doesn't accept keyword initialization as normal dicts to avoid
a trap - inside a function or method the keyword args are accessible
only as a dict, without a defined order, so their original order is
lost.
if kwds:
raise TypeError("__init__() of ordered dict takes no keyword "
"arguments to avoid an ordering trap.")
self._dict_impl().__init__(self)
# If you give a normal dict, then the order of elements is undefined
if hasattr(data, "iteritems"):
for key, val in data.iteritems():
self[key] = val
for key, val in data:
# Double-linked list header
def _get_lh(self):
dict_impl = self._dict_impl()
if not hasattr(self, '_lh'):
dict_impl.__setattr__(self, '_lh', _nil)
return dict_impl.__getattribute__(self, '_lh')
def _set_lh(self, val):
self._dict_impl().__setattr__(self, '_lh', val)
lh = property(_get_lh, _set_lh)
# Double-linked list tail
def _get_lt(self):
if not hasattr(self, '_lt'):
dict_impl.__setattr__(self, '_lt', _nil)
return dict_impl.__getattribute__(self, '_lt')
def _set_lt(self, val):
self._dict_impl().__setattr__(self, '_lt', val)
lt = property(_get_lt, _set_lt)
def __getitem__(self, key):
return self._dict_impl().__getitem__(self, key)[1]
def __setitem__(self, key, val):
dict_impl.__getitem__(self, key)[1] = val
except KeyError, e:
except KeyError:
new = [dict_impl.__getattribute__(self, 'lt'), val, _nil]
dict_impl.__setitem__(self, key, new)
if dict_impl.__getattribute__(self, 'lt') == _nil:
dict_impl.__setattr__(self, 'lh', key)
dict_impl.__getitem__(
self, dict_impl.__getattribute__(self, 'lt'))[2] = key
dict_impl.__setattr__(self, 'lt', key)
def __delitem__(self, key):
pred, _ , succ = self._dict_impl().__getitem__(self, key)
pred, _, succ = self._dict_impl().__getitem__(self, key)
if pred == _nil:
dict_impl.__setattr__(self, 'lh', succ)
dict_impl.__getitem__(self, pred)[2] = succ
if succ == _nil:
dict_impl.__setattr__(self, 'lt', pred)
dict_impl.__getitem__(self, succ)[0] = pred
dict_impl.__delitem__(self, key)
def __contains__(self, key):
return key in self.keys()
def __len__(self):
return len(self.keys())
def __str__(self):
pairs = ("%r: %r" % (k, v) for k, v in self.iteritems())
return "{%s}" % ", ".join(pairs)
if self:
pairs = ("(%r, %r)" % (k, v) for k, v in self.iteritems())
return "odict([%s])" % ", ".join(pairs)
return "odict()"
def get(self, k, x=None):
if k in self:
return self._dict_impl().__getitem__(self, k)[1]
return x
def __iter__(self):
curr_key = dict_impl.__getattribute__(self, 'lh')
while curr_key != _nil:
yield curr_key
curr_key = dict_impl.__getitem__(self, curr_key)[2]
iterkeys = __iter__
def keys(self):
return list(self.iterkeys())
def itervalues(self):
_, val, curr_key = dict_impl.__getitem__(self, curr_key)
yield val
def values(self):
return list(self.itervalues())
def iteritems(self):
_, val, next_key = dict_impl.__getitem__(self, curr_key)
yield curr_key, val
curr_key = next_key
def items(self):
return list(self.iteritems())
def sort(self, cmp=None, key=None, reverse=False):
items = [(k, v) for k, v in self.items()]
if cmp is not None:
items = sorted(items, cmp=cmp)
elif key is not None:
items = sorted(items, key=key)
items = sorted(items, key=lambda x: x[1])
if reverse:
items.reverse()
self.clear()
self.__init__(items)
def clear(self):
dict_impl.clear(self)
dict_impl.__setattr__(self, 'lh', _nil)
dict_impl.__setattr__(self, 'lt', _nil)
def copy(self):
return self.__class__(self)
def update(self, data=(), **kwds):
raise TypeError("update() of ordered dict takes no keyword "
data = data.iteritems()
def setdefault(self, k, x=None):
return self[k]
self[k] = x
def pop(self, k, x=_nil):
val = self[k]
del self[k]
return val
if x == _nil:
raise
def popitem(self):
key = dict_impl.__getattribute__(self, 'lt')
return key, self.pop(key)
raise KeyError("'popitem(): ordered dictionary is empty'")
def riterkeys(self):
"""To iterate on keys in reversed order.
curr_key = dict_impl.__getattribute__(self, 'lt')
curr_key = dict_impl.__getitem__(self, curr_key)[0]
__reversed__ = riterkeys
def rkeys(self):
"""List of the keys in reversed order.
return list(self.riterkeys())
def ritervalues(self):
"""To iterate on values in reversed order.
curr_key, val, _ = dict_impl.__getitem__(self, curr_key)
def rvalues(self):
"""List of the values in reversed order.
return list(self.ritervalues())
def riteritems(self):
"""To iterate on (key, value) in reversed order.
pred_key, val, _ = dict_impl.__getitem__(self, curr_key)
curr_key = pred_key
def ritems(self):
"""List of the (key, value) in reversed order.
return list(self.riteritems())
def firstkey(self):
return self._dict_impl().__getattribute__(self, 'lh')
raise KeyError("'firstkey(): ordered dictionary is empty'")
def lastkey(self):
return self._dict_impl().__getattribute__(self, 'lt')
raise KeyError("'lastkey(): ordered dictionary is empty'")
def as_dict(self):
return self._dict_impl()(self.items())
def _repr(self):
"""_repr(): low level repr of the whole data contained in the odict.
Useful for debugging.
form = "odict low level repr lh,lt,data: %r, %r, %s"
return form % (dict_impl.__getattribute__(self, 'lh'),
dict_impl.__getattribute__(self, 'lt'),
dict_impl.__repr__(self))
class OrderedDict(_odict, dict):
return dict
# OrderedSet
from sqlalchemy.util import OrderedSet
# kill FUNCTIONS
if __platform__ in PLATFORM_WIN:
import ctypes
def kill(pid, sig):
"""kill function for Win32"""
kernel32 = ctypes.windll.kernel32
handle = kernel32.OpenProcess(1, 0, pid)
return (0 != kernel32.TerminateProcess(handle, 0))
kill = os.kill
@@ -202,219 +202,221 @@
%endif
<div class="box">
<div class="title">
<div class="breadcrumbs">
%if c.repo_changesets:
${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}
%else:
${_('Quick start')}
</div>
<div class="table">
<div id="shortlog_data">
<%include file='../shortlog/shortlog_data.html'/>
%if c.readme_data:
<div class="box" style="background-color: #FAFAFA">
<div class="breadcrumbs"><a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a></div>
<div class="readme">
<div class="readme_box">
${c.readme_data|n}
<script type="text/javascript">
var clone_url = 'clone_url';
YUE.on(clone_url,'click',function(e){
if(YUD.hasClass(clone_url,'selected')){
return
else{
YUD.addClass(clone_url,'selected');
YUD.get(clone_url).select();
})
YUE.on('clone_by_name','click',function(e){
// show url by name and hide name button
YUD.setStyle('clone_url','display','');
YUD.setStyle('clone_by_name','display','none');
// hide url by id and show name button
YUD.setStyle('clone_by_id','display','');
YUD.setStyle('clone_url_id','display','none');
YUE.on('clone_by_id','click',function(e){
// show url by id and hide id button
YUD.setStyle('clone_by_id','display','none');
YUD.setStyle('clone_url_id','display','');
// hide url by name and show id button
YUD.setStyle('clone_by_name','display','');
YUD.setStyle('clone_url','display','none');
var tmpl_links = {};
%for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
tmpl_links["${archive['type']}"] = '${h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.dbrepo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='archive_icon ui-btn')}';
%endfor
YUE.on(['download_options','archive_subrepos'],'change',function(e){
var sm = YUD.get('download_options');
var new_cs = sm.options[sm.selectedIndex];
for(k in tmpl_links){
var s = YUD.get(k+'_link');
if(s){
var title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
title_tmpl= title_tmpl.replace('__CS_NAME__',new_cs.text);
title_tmpl = title_tmpl.replace('__CS_EXT__',k);
var url = tmpl_links[k].replace('__CS__',new_cs.value);
var subrepos = YUD.get('archive_subrepos').checked;
url = url.replace('__SUB__',subrepos);
url = url.replace('__NAME__',title_tmpl);
s.innerHTML = url
});
</script>
%if c.show_stats:
var data = ${c.trending_languages|n};
var total = 0;
var no_data = true;
for (k in data){
total += data[k].count;
no_data = false;
var tbl = document.createElement('table');
tbl.setAttribute('class','trending_language_tbl');
var cnt = 0;
for (var i=0;i<data.length;i++){
total += data[i][1].count;
cnt += 1;
var hide = cnt>2;
var tr = document.createElement('tr');
if (hide){
tr.setAttribute('style','display:none');
tr.setAttribute('class','stats_hidden');
var percentage = Math.round((data[k].count/total*100),2);
var value = data[k].count;
var k = data[i][0];
var obj = data[i][1];
var percentage = Math.round((obj.count/total*100),2);
var td1 = document.createElement('td');
td1.width = 150;
var trending_language_label = document.createElement('div');
trending_language_label.innerHTML = data[k].desc+" ("+k+")";
trending_language_label.innerHTML = obj.desc+" ("+k+")";
td1.appendChild(trending_language_label);
var td2 = document.createElement('td');
td2.setAttribute('style','padding-right:14px !important');
var trending_language = document.createElement('div');
var nr_files = value+" ${_('files')}";
var nr_files = obj.count+" ${_('files')}";
trending_language.title = k+" "+nr_files;
if (percentage>22){
trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
trending_language.style.width=percentage+"%";
td2.appendChild(trending_language);
tr.appendChild(td1);
tr.appendChild(td2);
tbl.appendChild(tr);
if(cnt == 3){
var show_more = document.createElement('tr');
var td = document.createElement('td');
lnk = document.createElement('a');
lnk.href='#';
lnk.innerHTML = "${_('show more')}";
lnk.id='code_stats_show_more';
td.appendChild(lnk);
show_more.appendChild(td);
show_more.appendChild(document.createElement('td'));
tbl.appendChild(show_more);
YUD.get('lang_stats').appendChild(tbl);
YUE.on('code_stats_show_more','click',function(){
l = YUD.getElementsByClassName('stats_hidden')
for (e in l){
YUD.setStyle(l[e],'display','');
};
YUD.setStyle(YUD.get('code_stats_show_more'),
'display','none');
/**
* Plots summary graph
*
* @class SummaryPlot
* @param {from} initial from for detailed graph
* @param {to} initial to for detailed graph
* @param {dataset}
* @param {overview_dataset}
*/
function SummaryPlot(from,to,dataset,overview_dataset) {
var initial_ranges = {
"xaxis":{
"from":from,
"to":to,
},
var dataset = dataset;
var overview_dataset = [overview_dataset];
var choiceContainer = YUD.get("legend_choices");
var choiceContainerTable = YUD.get("legend_choices_tables");
var plotContainer = YUD.get('commit_history');
var overviewContainer = YUD.get('overview');
var plot_options = {
bars: {show:true,align:'center',lineWidth:4},
legend: {show:true, container:"legend_container"},
points: {show:true,radius:0,fill:false},
yaxis: {tickDecimals:0,},
xaxis: {
mode: "time",
timeformat: "%d/%m",
min:from,
max:to,
grid: {
hoverable: true,
clickable: true,
autoHighlight:true,
color: "#999"
//selection: {mode: "x"}
var overview_options = {
legend:{show:false},
bars: {show:true,barWidth: 2,},
shadowSize: 0,
xaxis: {mode: "time", timeformat: "%d/%m/%y",},
yaxis: {ticks: 3, min: 0,tickDecimals:0,},
grid: {color: "#999",},
selection: {mode: "x"}
Status change: