@@ -15,33 +15,40 @@
# 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 logging
import traceback
from mercurial import graphmod
from pylons import request, session, tmpl_context as c
from pylons import request, url, session, tmpl_context as c
from pylons.controllers.util import redirect
from pylons.i18n.translation import _
import rhodecode.lib.helpers as h
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
from rhodecode.lib.base import BaseRepoController, render
from rhodecode.lib.helpers import RepoPage
from rhodecode.lib.compat import json
from vcs.exceptions import RepositoryError, ChangesetError, \
ChangesetDoesNotExistError,BranchDoesNotExistError
log = logging.getLogger(__name__)
class ChangelogController(BaseRepoController):
@LoginRequired()
@HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
'repository.admin')
def __before__(self):
super(ChangelogController, self).__before__()
c.affected_files_cut_off = 60
@@ -53,64 +60,83 @@ class ChangelogController(BaseRepoContro
int_size = int(request.params.get('size'))
except ValueError:
int_size = default
int_size = int_size if int_size <= limit else limit
c.size = int_size
session['changelog_size'] = c.size
session.save()
else:
c.size = int(session.get('changelog_size', default))
p = int(request.params.get('page', 1))
branch_name = request.params.get('branch', None)
c.total_cs = len(c.rhodecode_repo)
c.pagination = RepoPage(c.rhodecode_repo, page=p,
item_count=c.total_cs, items_per_page=c.size,
branch_name=branch_name)
try:
if branch_name:
collection = [z for z in
c.rhodecode_repo.get_changesets(start=0,
branch_name=branch_name)]
c.total_cs = len(collection)
collection = list(c.rhodecode_repo)
self._graph(c.rhodecode_repo, c.total_cs, c.size, p)
c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
items_per_page=c.size, branch=branch_name)
except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
log.error(traceback.format_exc())
h.flash(str(e), category='warning')
return redirect(url('home'))
self._graph(c.rhodecode_repo, collection, c.total_cs, c.size, p)
c.branch_name = branch_name
c.branch_filters = [('',_('All Branches'))] + \
[(k,k) for k in c.rhodecode_repo.branches.keys()]
return render('changelog/changelog.html')
def changelog_details(self, cs):
if request.environ.get('HTTP_X_PARTIAL_XHR'):
c.cs = c.rhodecode_repo.get_changeset(cs)
return render('changelog/changelog_details.html')
def _graph(self, repo, repo_size, size, p):
def _graph(self, repo, collection, repo_size, size, p):
"""
Generates a DAG graph for mercurial
:param repo: repo instance
:param size: number of commits to show
:param p: page number
if not repo.revisions:
if not collection:
c.jsdata = json.dumps([])
return
revcount = min(repo_size, size)
offset = 1 if p == 1 else ((p - 1) * revcount + 1)
rev_end = repo.revisions.index(repo.revisions[(-1 * offset)])
rev_end = collection.index(collection[(-1 * offset)])
except IndexError:
rev_end = repo.revisions.index(repo.revisions[-1])
rev_end = collection.index(collection[-1])
rev_start = max(0, rev_end - revcount)
data = []
rev_end += 1
if repo.alias == 'git':
for _ in xrange(rev_start, rev_end):
vtx = [0, 1]
edges = [[0, 0, 1]]
data.append(['', vtx, edges])
elif repo.alias == 'hg':
revs = list(reversed(xrange(rev_start, rev_end)))
c.dag = graphmod.colored(graphmod.dagwalker(repo._repo, revs))
for (id, type, ctx, vtx, edges) in c.dag:
if type != graphmod.CHANGESET:
continue
c.jsdata = json.dumps(data)
@@ -57,35 +57,37 @@ class ChangesetController(BaseRepoContro
def wrap_to_table(str):
return '''<table class="code-difftable">
<tr class="line">
<td class="lineno new"></td>
<td class="code"><pre>%s</pre></td>
</tr>
</table>''' % str
#get ranges of revisions if preset
rev_range = revision.split('...')[:2]
if len(rev_range) == 2:
rev_start = rev_range[0]
rev_end = rev_range[1]
rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
end=rev_end)
rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
c.cs_ranges = list(rev_ranges)
if not c.cs_ranges:
raise RepositoryError('Changeset range returned empty result')
c.changes = OrderedDict()
c.sum_added = 0
c.sum_removed = 0
c.lines_added = 0
c.lines_deleted = 0
c.cut_off = False # defines if cut off limit is reached
@@ -41,19 +41,19 @@ class ShortlogController(BaseRepoControl
super(ShortlogController, self).__before__()
def index(self, repo_name):
size = int(request.params.get('size', 20))
def url_generator(**kw):
return url('shortlog_home', repo_name=repo_name, size=size, **kw)
c.repo_changesets = RepoPage(c.rhodecode_repo, page=p,
items_per_page=size,
url=url_generator)
items_per_page=size, url=url_generator)
c.shortlog_data = render('shortlog/shortlog_data.html')
return c.shortlog_data
r = render('shortlog/shortlog.html')
return r
@@ -28,25 +28,25 @@ from webhelpers.number import format_byt
from webhelpers.pylonslib import Flash as _Flash
from webhelpers.pylonslib.secure_form import secure_form
from webhelpers.text import chop_at, collapse, convert_accented_entities, \
convert_misc_entities, lchop, plural, rchop, remove_formatting, \
replace_whitespace, urlify, truncate, wrap_paragraphs
from webhelpers.date import time_ago_in_words
from webhelpers.paginate import Page
from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
convert_boolean_attrs, NotGiven
from vcs.utils.annotate import annotate_highlight
from rhodecode.lib.utils import repo_name_slug
from rhodecode.lib import str2bool, safe_unicode, safe_str,get_changeset_safe
from rhodecode.lib import str2bool, safe_unicode, safe_str, get_changeset_safe
def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
Reset button
_set_input_attrs(attrs, type, name, value)
_set_id_attr(attrs, id, name)
convert_boolean_attrs(attrs, ["disabled"])
return HTML.input(**attrs)
reset = _reset
@@ -471,25 +471,25 @@ def gravatar_url(email_address, size=30)
gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
return gravatar_url
#==============================================================================
# REPO PAGER, PAGER FOR REPOSITORY
class RepoPage(Page):
def __init__(self, collection, page=1, items_per_page=20,
item_count=None, url=None, branch_name=None, **kwargs):
item_count=None, url=None, **kwargs):
"""Create a "RepoPage" instance. special pager for paging
repository
self._url_generator = url
# Safe the kwargs class-wide so they can be used in the pager() method
self.kwargs = kwargs
# Save a reference to the collection
self.original_collection = collection
@@ -522,54 +522,51 @@ class RepoPage(Page):
if self.page > self.last_page:
self.page = self.last_page
elif self.page < self.first_page:
self.page = self.first_page
# Note: the number of items on this page can be less than
# items_per_page if the last page is not full
self.first_item = max(0, (self.item_count) - (self.page *
items_per_page))
self.last_item = ((self.item_count - 1) - items_per_page *
(self.page - 1))
iterator = self.collection.get_changesets(start=self.first_item,
end=self.last_item,
reverse=True,
self.items = list(iterator)
self.items = list(self.collection[self.first_item:self.last_item+1])
# Links to previous and next page
if self.page > self.first_page:
self.previous_page = self.page - 1
self.previous_page = None
if self.page < self.last_page:
self.next_page = self.page + 1
self.next_page = None
# No items available
self.first_page = None
self.page_count = 0
self.last_page = None
self.first_item = None
self.last_item = None
self.items = []
# This is a subclass of the 'list' type. Initialise the list now.
list.__init__(self, self.items)
list.__init__(self, reversed(self.items))
def changed_tooltip(nodes):
Generates a html string for changed nodes in changeset page.
It limits the output to 30 entries
:param nodes: LazyNodesGenerator
if nodes:
pref = ': <br/> '
suf = ''
@@ -661,12 +658,13 @@ def fancy_file_stats(stats):
def urlify_text(text):
import re
url_pat = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
def url_func(match_obj):
url_full = match_obj.groups()[0]
return '<a href="%(url)s">%(url)s</a>' % ({'url':url_full})
return literal(url_pat.sub(url_func, text))
@@ -1905,28 +1905,30 @@ h3.files_location {
margin-right: -6px;
margin-top: 0px;
}
#graph_content {
width: 800px;
float: left;
#graph_content .container_header {
border: 1px solid #CCC;
padding: 10px;
height: 45px;
#graph_content #rev_range_container {
padding: 10px 0px;
clear: both;
#graph_content .container {
border-bottom: 1px solid #CCC;
border-left: 1px solid #CCC;
border-right: 1px solid #CCC;
min-height: 70px;
overflow: hidden;
font-size: 1.2em;
#graph_content .container .right {
@@ -2013,41 +2015,60 @@ h3.files_location {
background: #F88;
.right .merge {
vertical-align: top;
font-size: 0.75em;
font-weight: 700;
.right .parent {
font-size: 90%;
font-family: monospace;
.right .logtags .branchtag {
background: #FFF url("../images/icons/arrow_branch.png") no-repeat right
6px;
display: block;
font-size: 0.8em;
padding: 11px 16px 0 0;
.right .logtags .tagtag {
background: #FFF url("../images/icons/tag_blue.png") no-repeat right 6px;
padding: 2px 2px 2px 2px;
.right .logtags{
.right .logtags .branchtag,.logtags .branchtag {
padding: 1px 3px 2px;
background-color: #bfbfbf;
font-size: 9.75px;
font-weight: bold;
color: #ffffff;
text-transform: uppercase;
white-space: nowrap;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
padding-left:4px;
.right .logtags .branchtag a:hover,.logtags .branchtag a:hover{
text-decoration: none;
.right .logtags .tagtag,.logtags .tagtag {
background-color: #62cffc;
.right .logtags .tagtag a:hover,.logtags .tagtag a:hover{
div.browserblock {
border: 1px solid #ccc;
background: #f8f8f8;
font-size: 100%;
line-height: 125%;
padding: 0;
div.browserblock .browser-header {
background: #FFF;
padding: 10px 0px 15px 0px;
@@ -3086,31 +3107,31 @@ div.readme .readme_box pre, code {
font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
div.readme .readme_box code {
font-size: 12px !important;
background-color: ghostWhite !important;
color: #444 !important;
padding: 0 .2em !important;
border: 1px solid #dedede !important;
div.readme .readme_box pre code {
padding: 0 !important;
background-color: #eee !important;
border: none !important;
div.readme .readme_box pre {
margin: 1em 0;
font-size: 12px;
background-color: #eee;
border: 1px solid #ddd;
padding: 5px;
color: #444;
overflow: auto;
-webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
@@ -10,44 +10,44 @@
%for cnt,branch in enumerate(c.repo_branches.items()):
<tr class="parity${cnt%2}">
<td><span class="tooltip" title="${h.age(branch[1].date)}">${branch[1].date}</span>
</td>
<td>
<span class="logtags">
<span class="branchtag">${h.link_to(branch[0],
h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].raw_id))}</span>
</span>
<td title="${branch[1].author}">${h.person(branch[1].author)}</td>
<td>r${branch[1].revision}:${h.short_id(branch[1].raw_id)}</td>
<td class="nowrap">
${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].raw_id))}
|
${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id))}
${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].raw_id),class_="ui-button-small")}
<span style="color:#515151">|</span>
${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=branch[1].raw_id),class_="ui-button-small")}
%endfor
% if hasattr(c,'repo_closed_branches') and c.repo_closed_branches:
%for cnt,branch in enumerate(c.repo_closed_branches.items()):
<span class="branchtag">${h.link_to(branch[0]+' [closed]',
%endif
</table>
%else:
${_('There are no branches yet')}
\ No newline at end of file
@@ -24,57 +24,56 @@ ${c.repo_name} ${_('Changelog')} - ${c.r
<div class="title">
${self.breadcrumbs()}
</div>
<div class="table">
% if c.pagination:
<div id="graph">
<div id="graph_nodes">
<canvas id="graph_canvas"></canvas>
<div id="graph_content">
<div class="container_header">
${h.form(h.url.current(),method='get')}
<div class="info_box">
<div class="info_box" style="float:left">
${h.submit('set',_('Show'),class_="ui-button-small")}
${h.text('size',size=1,value=c.size)}
<span class="rev">${_('revisions')}</span>
${h.end_form()}
<div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
<div id="rev_range_container" style="display:none"></div>
%for cnt,cs in enumerate(c.pagination):
<div id="chg_${cnt+1}" class="container">
<div class="left">
<div class="date">
${h.checkbox(cs.short_id,class_="changeset_range")}
<span>${_('commit')} ${cs.revision}: ${h.short_id(cs.raw_id)}@${cs.date}</span>
<div class="author">
<div class="gravatar">
<img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),16)}"/>
<div title="${h.email_or_none(cs.author)}" class="user">${h.person(cs.author)}</div>
##<span><a href="mailto:${h.email_or_none(cs.author)}">${h.email_or_none(cs.author)}</a></span><br/>
<div class="message">${h.link_to(h.wrap_paragraphs(cs.message),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
<div class="right">
<div id="${cs.raw_id}_changes_info" class="changes">
<span id="${cs.raw_id}" class="changed_total tooltip" title="${_('Affected number of files, click to show more details')}">${len(cs.affected_files)}</span>
%if len(cs.parents)>1:
<div class="merge">
${_('merge')}<img alt="merge" src="${h.url('/images/icons/arrow_join.png')}"/>
<div class="merge">${_('merge')}</div>
%if cs.parents:
%for p_cs in reversed(cs.parents):
<div class="parent">${_('Parent')} ${p_cs.revision}: ${h.link_to(h.short_id(p_cs.raw_id),
h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}
<div class="parent">${_('No parents')}</div>
@@ -122,32 +121,46 @@ ${c.repo_name} ${_('Changelog')} - ${c.r
var link = "<a href="+url+">${_('Show selected changes __S -> __E')}</a>"
link = link.replace('__S',rev_start);
link = link.replace('__E',rev_end);
YUD.get('rev_range_container').innerHTML = link;
YUD.setStyle('rev_range_container','display','');
else{
YUD.setStyle('rev_range_container','display','none');
});
//Fetch changeset details
// Fetch changeset details
YUE.on(YUD.getElementsByClassName('changed_total'),'click',function(e){
var id = e.currentTarget.id
var url = "${h.url('changelog_details',repo_name=c.repo_name,cs='__CS__')}"
var url = url.replace('__CS__',id);
ypjax(url,id+'_changes_info',function(){tooltip_activate()});
// change branch filter
YUE.on(YUD.get('branch_filter'),'change',function(e){
var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
console.log(selected_branch);
var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
var url = url.replace('__BRANCH__',selected_branch);
if(selected_branch != ''){
window.location = url;
}else{
window.location = url_main;
function set_canvas(heads) {
var c = document.getElementById('graph_nodes');
var t = document.getElementById('graph_content');
canvas = document.getElementById('graph_canvas');
var div_h = t.clientHeight;
c.style.height=div_h+'px';
canvas.setAttribute('height',div_h);
c.style.height=max_w+'px';
canvas.setAttribute('width',max_w);
};
var heads = 1;
@@ -156,21 +169,21 @@ ${c.repo_name} ${_('Changelog')} - ${c.r
for( var i=0;i<jsdata.length;i++){
var m = Math.max.apply(Math, jsdata[i][1]);
if (m>max_heads){
max_heads = m;
var max_w = Math.max(100,max_heads*25);
set_canvas(max_w);
var r = new BranchRenderer();
r.render(jsdata,max_w);
</script>
${_('There are no changes yet')}
</%def>
@@ -27,27 +27,27 @@
<span class="branchtag">${cs.branch}</span>
%for tag in cs.tags:
<span class="tagtag">${tag}</span>
${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id),class_="ui-button-small")}
${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id),class_="ui-button-small")}
<script type="text/javascript">
YUE.onDOMReady(function(){
YUE.delegate("shortlog_data","click",function(e, matchedEl, container){
ypjax(e.target.href,"shortlog_data",function(){tooltip_activate();});
YUE.preventDefault(e);
},'.pager_link');
@@ -12,22 +12,22 @@
<td><span class="tooltip" title="${h.age(tag[1].date)}">
${tag[1].date}</span>
<span class="tagtag">${h.link_to(tag[0],
h.url('changeset_home',repo_name=c.repo_name,revision=tag[1].raw_id))}</span>
<td title="${tag[1].author}">${h.person(tag[1].author)}</td>
<td>r${tag[1].revision}:${h.short_id(tag[1].raw_id)}</td>
${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=tag[1].raw_id))}
${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=tag[1].raw_id))}
${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=tag[1].raw_id),class_="ui-button-small")}
${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=tag[1].raw_id),class_="ui-button-small")}
${_('There are no tags yet')}
Status change: