# -*- coding: utf-8 -*-
"""
rhodecode.controllers.changelog
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
changelog controller for rhodecode
:created_on: Apr 21, 2010
:author: marcink
:copyright: (C) 2009-2011 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 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
def index(self):
limit = 100
default = 20
if request.params.get('size'):
try:
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)
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)
rhodecode.controllers.changeset
changeset controller for pylons showoing changes beetween
revisions
:created_on: Apr 25, 2010
from pylons import tmpl_context as c, url, request, response
from rhodecode.lib.utils import EmptyChangeset
from rhodecode.lib.compat import OrderedDict
ChangesetDoesNotExistError
from vcs.nodes import FileNode
from vcs.utils import diffs as differ
class ChangesetController(BaseRepoController):
super(ChangesetController, self).__before__()
def index(self, revision):
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
# Iterate over ranges (default changeset view is always one changeset)
for changeset in c.cs_ranges:
c.changes[changeset.raw_id] = []
changeset_parent = changeset.parents[0]
changeset_parent = None
#==================================================================
# ADDED FILES
for node in changeset.added:
filenode_old = FileNode(node.path, '', EmptyChangeset())
if filenode_old.is_binary or node.is_binary:
diff = wrap_to_table(_('binary file'))
st = (0, 0)
# in this case node.size is good parameter since those are
# added nodes and their size defines how many changes were
# made
c.sum_added += node.size
if c.sum_added < self.cut_off_limit:
f_gitdiff = differ.get_gitdiff(filenode_old, node)
d = differ.DiffProcessor(f_gitdiff, format='gitdiff')
st = d.stat()
diff = d.as_html()
diff = wrap_to_table(_('Changeset is to big and '
'was cut off, see raw '
'changeset instead'))
c.cut_off = True
break
cs1 = None
cs2 = node.last_changeset.raw_id
c.lines_added += st[0]
c.lines_deleted += st[1]
c.changes[changeset.raw_id].append(('added', node, diff,
cs1, cs2, st))
# CHANGED FILES
if not c.cut_off:
for node in changeset.changed:
filenode_old = changeset_parent.get_node(node.path)
except ChangesetError:
log.warning('Unable to fetch parent node for diff')
filenode_old = FileNode(node.path, '',
EmptyChangeset())
if c.sum_removed < self.cut_off_limit:
d = differ.DiffProcessor(f_gitdiff,
format='gitdiff')
if (st[0] + st[1]) * 256 > self.cut_off_limit:
diff = wrap_to_table(_('Diff is to big '
'and was cut off, see '
'raw diff instead'))
if diff:
c.sum_removed += len(diff)
cs1 = filenode_old.last_changeset.raw_id
rhodecode.controllers.shortlog
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Shortlog controller for rhodecode
:created_on: Apr 18, 2010
from pylons import tmpl_context as c, request, url
class ShortlogController(BaseRepoController):
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
"""Helper functions
Consists of functions to typically be used within templates, but also
available to Controllers. This module is available to both as 'h'.
import random
import hashlib
import StringIO
import urllib
import math
from datetime import datetime
from pygments.formatters import HtmlFormatter
from pygments import highlight as code_highlight
from pylons import url, request, config
from pylons.i18n.translation import _, ungettext
from webhelpers.html import literal, HTML, escape
from webhelpers.html.tools import *
from webhelpers.html.builder import make_tag
from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
password, textarea, title, ul, xml_declaration, radio
from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
mail_to, strip_links, strip_tags, tag_re
from webhelpers.number import format_byte_size, format_bit_size
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
def get_token():
"""Return the current authentication token, creating one if one doesn't
already exist.
token_key = "_authentication_token"
from pylons import session
if not token_key in session:
token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
except AttributeError: # Python < 2.4
token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
session[token_key] = token
if hasattr(session, 'save'):
return session[token_key]
class _GetError(object):
"""Get error from form_errors, and represent it as span wrapped error
message
:param field_name: field to fetch errors for
:param form_errors: form errors dict
def __call__(self, field_name, form_errors):
tmpl = """<span class="error_msg">%s</span>"""
if form_errors and form_errors.has_key(field_name):
return literal(tmpl % form_errors.get(field_name))
get_error = _GetError()
class _ToolTip(object):
def __call__(self, tooltip_title, trim_at=50):
"""Special function just to wrap our text into nice formatted
autowrapped text
:param tooltip_title:
return escape(tooltip_title)
tooltip = _ToolTip()
class _FilesBreadCrumbs(object):
def __call__(self, repo_name, rev, paths):
if isinstance(paths, str):
paths = safe_unicode(paths)
url_l = [link_to(repo_name, url('files_home',
repo_name=repo_name,
revision=rev, f_path=''))]
paths_l = paths.split('/')
for cnt, p in enumerate(paths_l):
if p != '':
url_l.append(link_to(p, url('files_home',
revision=rev,
f_path='/'.join(paths_l[:cnt + 1]))))
return literal('/'.join(url_l))
files_breadcrumbs = _FilesBreadCrumbs()
class CodeHtmlFormatter(HtmlFormatter):
"""My code Html Formatter for source codes
def wrap(self, source, outfile):
return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
def _wrap_code(self, source):
for cnt, it in enumerate(source):
i, t = it
t = '<div id="L%s">%s</div>' % (cnt + 1, t)
yield i, t
def _wrap_tablelinenos(self, inner):
dummyoutfile = StringIO.StringIO()
lncount = 0
for t, line in inner:
if t:
lncount += 1
dummyoutfile.write(line)
@@ -387,286 +387,284 @@ def action_parser(user_log, feed=False):
action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
'user_created_repo':(_('[created] repository'), None),
'user_forked_repo':(_('[forked] repository'), get_fork_name),
'user_updated_repo':(_('[updated] repository'), None),
'admin_deleted_repo':(_('[delete] repository'), None),
'admin_created_repo':(_('[created] repository'), None),
'admin_forked_repo':(_('[forked] repository'), None),
'admin_updated_repo':(_('[updated] repository'), None),
'push':(_('[pushed] into'), get_cs_links),
'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
'push_remote':(_('[pulled from remote] into'), get_cs_links),
'pull':(_('[pulled] from'), None),
'started_following_repo':(_('[started following] repository'), None),
'stopped_following_repo':(_('[stopped following] repository'), None),
}
action_str = action_map.get(action, action)
if feed:
action = action_str[0].replace('[', '').replace(']', '')
action = action_str[0].replace('[', '<span class="journal_highlight">')\
.replace(']', '</span>')
action_params_func = lambda :""
if callable(action_str[1]):
action_params_func = action_str[1]
return [literal(action), action_params_func]
def action_parser_icon(user_log):
action = user_log.action
action_params = None
x = action.split(':')
if len(x) > 1:
action, action_params = x
tmpl = """<img src="%s%s" alt="%s"/>"""
map = {'user_deleted_repo':'database_delete.png',
'user_created_repo':'database_add.png',
'user_forked_repo':'arrow_divide.png',
'user_updated_repo':'database_edit.png',
'admin_deleted_repo':'database_delete.png',
'admin_created_repo':'database_add.png',
'admin_forked_repo':'arrow_divide.png',
'admin_updated_repo':'database_edit.png',
'push':'script_add.png',
'push_local':'script_edit.png',
'push_remote':'connect.png',
'pull':'down_16.png',
'started_following_repo':'heart_add.png',
'stopped_following_repo':'heart_delete.png',
return literal(tmpl % ((url('/images/icons/')),
map.get(action, action), action))
#==============================================================================
# PERMS
from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
HasRepoPermissionAny, HasRepoPermissionAll
# GRAVATAR URL
def gravatar_url(email_address, size=30):
if (not str2bool(config['app_conf'].get('use_gravatar')) or
not email_address or email_address == 'anonymous@rhodecode.org'):
return url("/images/user%s.png" % size)
ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
default = 'identicon'
baseurl_nossl = "http://www.gravatar.com/avatar/"
baseurl_ssl = "https://secure.gravatar.com/avatar/"
baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
if isinstance(email_address, unicode):
#hashlib crashes on unicode items
email_address = safe_str(email_address)
# construct the url
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
self.collection = collection
# The self.page is the number of the current page.
# The first page has the number 1!
self.page = int(page) # make it int() if we get it as a string
except (ValueError, TypeError):
self.page = 1
self.items_per_page = items_per_page
# Unless the user tells us how many items the collections has
# we calculate that ourselves.
if item_count is not None:
self.item_count = item_count
self.item_count = len(self.collection)
# Compute the number of the first and last available page
if self.item_count > 0:
self.first_page = 1
self.page_count = int(math.ceil(float(self.item_count) /
self.items_per_page))
self.last_page = self.first_page + self.page_count - 1
# Make sure that the requested page number is the range of valid pages
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 = ''
if len(nodes) > 30:
suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
return literal(pref + '<br/> '.join([safe_unicode(x.path)
for x in nodes[:30]]) + suf)
return ': ' + _('No Files')
def repo_link(groups_and_repos):
Makes a breadcrumbs link to repo within a group
joins » on each group to create a fancy link
ex::
group >> subgroup >> repo
:param groups_and_repos:
groups, repo_name = groups_and_repos
if not groups:
return repo_name
def make_link(group):
return link_to(group.name, url('repos_group_home',
group_name=group.group_name))
return literal(' » '.join(map(make_link, groups)) + \
" » " + repo_name)
def fancy_file_stats(stats):
Displays a fancy two colored bar for number of added/deleted
lines of code on file
:param stats: two element list of added/deleted lines of code
a, d, t = stats[0], stats[1], stats[0] + stats[1]
width = 100
unit = float(width) / (t or 1)
# needs > 9% of width to be visible or 0 to be hidden
a_p = max(9, unit * a) if a > 0 else 0
d_p = max(9, unit * d) if d > 0 else 0
p_sum = a_p + d_p
if p_sum > width:
#adjust the percentage to be == 100% since we adjusted to 9
if a_p > d_p:
a_p = a_p - (p_sum - width)
d_p = d_p - (p_sum - width)
a_v = a if a > 0 else ''
d_v = d if d > 0 else ''
def cgen(l_type):
mapping = {'tr':'top-right-rounded-corner',
'tl':'top-left-rounded-corner',
'br':'bottom-right-rounded-corner',
'bl':'bottom-left-rounded-corner'}
map_getter = lambda x:mapping[x]
if l_type == 'a' and d_v:
#case when added and deleted are present
return ' '.join(map(map_getter, ['tl', 'bl']))
if l_type == 'a' and not d_v:
return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
if l_type == 'd' and a_v:
return ' '.join(map(map_getter, ['tr', 'br']))
if l_type == 'd' and not a_v:
d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
a_p, a_v)
d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
d_p, d_v)
return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
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))
@@ -1821,317 +1821,338 @@ h3.files_location {
font-size: 1.2em;
overflow: hidden;
#changeset_compare_view_content .compare_view_commits {
width: auto !important;
#changeset_compare_view_content .compare_view_commits td {
padding: 0px 0px 0px 12px !important;
#changeset_content .container .right {
float: right;
width: 25%;
text-align: right;
#changeset_content .container .left .message {
font-style: italic;
color: #556CB5;
white-space: pre-wrap;
.cs_files .cur_cs {
margin: 10px 2px;
font-weight: bold;
.cs_files .node {
float: left;
.cs_files .changes {
.cs_files .changes .added {
background-color: #BBFFBB;
text-align: center;
font-size: 90%;
.cs_files .changes .deleted {
background-color: #FF8888;
.cs_files .cs_added {
background: url("../images/icons/page_white_add.png") no-repeat scroll
3px;
height: 16px;
padding-left: 20px;
margin-top: 7px;
text-align: left;
.cs_files .cs_changed {
background: url("../images/icons/page_white_edit.png") no-repeat scroll
.cs_files .cs_removed {
background: url("../images/icons/page_white_delete.png") no-repeat
scroll 3px;
#graph {
#graph_nodes {
margin-right: -6px;
margin-top: 0px;
#graph_content {
width: 800px;
#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;
#graph_content .container .right {
width: 28%;
padding-bottom: 5px;
#graph_content .container .left .date {
font-weight: 700;
#graph_content .container .left .date span {
vertical-align: text-top;
#graph_content .container .left .author {
height: 22px;
#graph_content .container .left .author .user {
color: #444444;
font-size: 12px;
margin-left: -4px;
margin-top: 4px;
#graph_content .container .left .message {
font-size: 100%;
padding-top: 3px;
#graph_content .container .left .message a:hover{
text-decoration: none;
.right div {
.right .changes .changed_total {
border: 1px solid #DDD;
display: block;
min-width: 45px;
cursor: pointer;
background: #FD8;
.right .changes .added,.changed,.removed {
min-width: 15px;
cursor: help;
.right .changes .large {
background: #54A9F7;
.right .changes .added {
background: #BFB;
.right .changes .changed {
.right .changes .removed {
background: #F88;
.right .merge {
vertical-align: top;
font-size: 0.75em;
.right .parent {
font-family: monospace;
.right .logtags .branchtag {
background: #FFF url("../images/icons/arrow_branch.png") no-repeat right
6px;
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;
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{
.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;
line-height: 125%;
padding: 0;
div.browserblock .browser-header {
background: #FFF;
padding: 10px 0px 15px 0px;
width: 100%;
div.browserblock .browser-nav {
float: left
div.browserblock .browser-branch {
div.browserblock .browser-branch label {
color: #4A4A4A;
div.browserblock .browser-header span {
margin-left: 5px;
div.browserblock .browser-search {
padding: 8px 8px 0px 5px;
height: 20px;
div.browserblock #node_filter_box {
div.browserblock .search_activate {
div.browserblock .add_node {
padding-left: 5px;
div.browserblock .search_activate a:hover,div.browserblock .add_node a:hover
{
text-decoration: none !important;
div.browserblock .browser-body {
background: #EEE;
border-top: 1px solid #CCC;
table.code-browser {
border-collapse: collapse;
table.code-browser tr {
margin: 3px;
table.code-browser thead th {
background-color: #EEE;
font-size: 1.1em;
padding-left: 10px;
table.code-browser tbody td {
table.code-browser .browser-file {
background: url("../images/icons/document_16.png") no-repeat scroll 3px;
.diffblock .changeset_file {
background: url("../images/icons/file.png") no-repeat scroll 3px;
padding-left: 22px;
@@ -3002,115 +3023,115 @@ div.form div.fields div.field div.highli
div#legend_container table td,div#legend_choices table td {
border: none !important;
height: 20px !important;
padding: 0 !important;
.q_filter_box {
-webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
border: 0 none;
color: #AAAAAA;
margin-bottom: -4px;
margin-top: -4px;
padding-left: 3px;
#node_filter {
border: 0px solid #545454;
/*README STYLE*/
div.readme {
padding:0px;
div.readme h2 {
font-weight: normal;
div.readme .readme_box {
background-color: #fafafa;
clear:both;
overflow:hidden;
margin:0;
padding:0 20px 10px;
div.readme .readme_box h1, div.readme .readme_box h2, div.readme .readme_box h3, div.readme .readme_box h4, div.readme .readme_box h5, div.readme .readme_box h6 {
border-bottom: 0 !important;
margin: 0 !important;
line-height: 1.5em !important;
div.readme .readme_box h1:first-child {
padding-top: .25em !important;
div.readme .readme_box h2, div.readme .readme_box h3 {
margin: 1em 0 !important;
div.readme .readme_box h2 {
margin-top: 1.5em !important;
border-top: 4px solid #e0e0e0 !important;
padding-top: .5em !important;
div.readme .readme_box p {
color: black !important;
div.readme .readme_box ul {
list-style: disc !important;
margin: 1em 0 1em 2em !important;
div.readme .readme_box ol {
list-style: decimal;
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 {
background-color: #eee !important;
div.readme .readme_box pre {
margin: 1em 0;
background-color: #eee;
border: 1px solid #ddd;
padding: 5px;
color: #444;
overflow: auto;
% if c.repo_branches:
<table class="table_disp">
<tr>
<th class="left">${_('date')}</th>
<th class="left">${_('name')}</th>
<th class="left">${_('author')}</th>
<th class="left">${_('revision')}</th>
<th class="left">${_('links')}</th>
%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
## -*- coding: utf-8 -*-
<%inherit file="/base/base.html"/>
<%def name="title()">
${c.repo_name} ${_('Changelog')} - ${c.rhodecode_name}
</%def>
<%def name="breadcrumbs_links()">
${h.link_to(u'Home',h.url('/'))}
»
${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
${_('Changelog')} - ${_('showing ')} ${c.size if c.size <= c.total_cs else c.total_cs} ${_('out of')} ${c.total_cs} ${_('revisions')}
<%def name="page_nav()">
${self.menu('changelog')}
<%def name="main()">
<div class="box">
<!-- box / title -->
<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>
%if cs.branch:
<span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
${h.link_to(cs.branch,h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
%for tag in cs.tags:
<span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
<div class="pagination-wh pagination-left">
${c.pagination.pager('$link_previous ~2~ $link_next')}
<script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
<script type="text/javascript">
YAHOO.util.Event.onDOMReady(function(){
//Monitor range checkboxes and build a link to changesets
//ranges
var checkboxes = YUD.getElementsByClassName('changeset_range');
var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
YUE.on(checkboxes,'click',function(e){
var checked_checkboxes = [];
for (pos in checkboxes){
if(checkboxes[pos].checked){
checked_checkboxes.push(checkboxes[pos]);
if(checked_checkboxes.length>1){
var rev_end = checked_checkboxes[0].name;
var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
var url = url_tmpl.replace('__REVRANGE__',
rev_start+'...'+rev_end);
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;
var max_heads = 0;
var jsdata = ${c.jsdata|n};
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')}
% if c.repo_changesets:
<table>
<th class="left">${_('commit message')}</th>
<th class="left">${_('age')}</th>
<th class="left">${_('branch')}</th>
<th class="left">${_('tags')}</th>
%for cnt,cs in enumerate(c.repo_changesets):
${h.link_to(h.truncate(cs.message,50),
h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id),
title=cs.message)}
<td><span class="tooltip" title="${cs.date}">
${h.age(cs.date)}</span>
<td title="${cs.author}">${h.person(cs.author)}</td>
<td>r${cs.revision}:${h.short_id(cs.raw_id)}</td>
<span class="branchtag">${cs.branch}</span>
<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")}
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');
${c.repo_changesets.pager('$link_previous ~2~ $link_next')}
%if c.repo_tags:
%for cnt,tag in enumerate(c.repo_tags.items()):
<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: