@@ -370,776 +370,776 @@ hide_credentials = lambda x: ''.join(cre
def fmt_date(date):
if date:
_fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
return date.strftime(_fmt).decode('utf8')
return ""
def is_git(repository):
if hasattr(repository, 'alias'):
_type = repository.alias
elif hasattr(repository, 'repo_type'):
_type = repository.repo_type
else:
_type = repository
return _type == 'git'
def is_hg(repository):
return _type == 'hg'
def email_or_none(author):
# extract email from the commit string
_email = email(author)
if _email != '':
# check it against RhodeCode database, and use the MAIN email for this
# user
user = User.get_by_email(_email, case_insensitive=True, cache=True)
if user is not None:
return user.email
return _email
# See if it contains a username we can get an email from
user = User.get_by_username(author_name(author), case_insensitive=True,
cache=True)
# No valid email, not a valid user in the system, none!
return None
def person(author, show_attr="username_and_name"):
# attr to return from fetched user
person_getter = lambda usr: getattr(usr, show_attr)
# Valid email in the attribute passed, see if they're in the system
return person_getter(user)
# Maybe it's a username?
_author = author_name(author)
user = User.get_by_username(_author, case_insensitive=True,
# Still nothing? Just pass back the author name then
return _author
def person_by_id(id_, show_attr="username_and_name"):
#maybe it's an ID ?
if str(id_).isdigit() or isinstance(id_, int):
id_ = int(id_)
user = User.get(id_)
return id_
def desc_stylize(value):
"""
converts tags from value into html equivalent
:param value:
value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
'<div class="metatag" tag="see">see => \\1 </div>', value)
value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
'<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
'<div class="metatag" tag="\\1">\\1 => <a href="/\\2">\\2</a></div>', value)
value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
'<div class="metatag" tag="lang">\\2</div>', value)
value = re.sub(r'\[([a-z]+)\]',
'<div class="metatag" tag="\\1">\\1</div>', value)
return value
def bool2icon(value):
"""Returns True/False values represented as small html image of true/false
icons
:param value: bool value
if value is True:
return HTML.tag('img', src=url("/images/icons/accept.png"),
alt=_('True'))
if value is False:
return HTML.tag('img', src=url("/images/icons/cancel.png"),
alt=_('False'))
def action_parser(user_log, feed=False, parse_cs=False):
This helper will action_map the specified string action into translated
fancy names with icons and links
:param user_log: user log instance
:param feed: use output for feeds (no html and fancy icons)
:param parse_cs: parse Changesets into VCS instances
action = user_log.action
action_params = ' '
x = action.split(':')
if len(x) > 1:
action, action_params = x
def get_cs_links():
revs_limit = 3 # display this amount always
revs_top_limit = 50 # show upto this amount of changesets hidden
revs_ids = action_params.split(',')
deleted = user_log.repository is None
if deleted:
return ','.join(revs_ids)
repo_name = user_log.repository.repo_name
def lnk(rev, repo_name):
if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
lazy_cs = True
if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
lazy_cs = False
lbl = '?'
if rev.op == 'delete_branch':
lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
title = ''
elif rev.op == 'tag':
lbl = '%s' % _('Created tag: %s') % rev.ref_name
_url = '#'
lbl = '%s' % (rev.short_id[:8])
_url = url('changeset_home', repo_name=repo_name,
revision=rev.raw_id)
title = tooltip(rev.message)
## changeset cannot be found/striped/removed etc.
lbl = ('%s' % rev)[:12]
title = _('Changeset not found')
if parse_cs:
return link_to(lbl, _url, title=title, class_='tooltip')
return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
class_='lazy-cs' if lazy_cs else '')
revs = []
if len(filter(lambda v: v != '', revs_ids)) > 0:
repo = None
for rev in revs_ids[:revs_top_limit]:
_op = _name = None
if len(rev.split('=>')) == 2:
_op, _name = rev.split('=>')
# we want parsed changesets, or new log store format is bad
try:
if repo is None:
repo = user_log.repository.scm_instance
_rev = repo.get_changeset(rev)
revs.append(_rev)
except ChangesetDoesNotExistError:
log.error('cannot find revision %s in this repo' % rev)
revs.append(rev)
continue
_rev = AttributeDict({
'short_id': rev[:12],
'raw_id': rev,
'message': '',
'op': _op,
'ref_name': _name
})
cs_links = []
cs_links.append(" " + ', '.join(
[lnk(rev, repo_name) for rev in revs[:revs_limit]]
)
compare_view = (
' <div class="compare_view tooltip" title="%s">'
'<a href="%s">%s</a> </div>' % (
_('Show all combined changesets %s->%s') % (
revs_ids[0][:12], revs_ids[-1][:12]
),
url('changeset_home', repo_name=repo_name,
revision='%s...%s' % (revs_ids[0], revs_ids[-1])
_('compare view')
# if we have exactly one more than normally displayed
# just display it, takes less space than displaying
# "and 1 more revisions"
if len(revs_ids) == revs_limit + 1:
rev = revs[revs_limit]
cs_links.append(", " + lnk(rev, repo_name))
# hidden-by-default ones
if len(revs_ids) > revs_limit + 1:
uniq_id = revs_ids[0]
html_tmpl = (
'<span> %s <a class="show_more" id="_%s" '
'href="#more">%s</a> %s</span>'
if not feed:
cs_links.append(html_tmpl % (
_('and'),
uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
_('revisions')
html_tmpl = '<span id="%s" style="display:none">, %s </span>'
html_tmpl = '<span id="%s"> %s </span>'
morelinks = ', '.join(
[lnk(rev, repo_name) for rev in revs[revs_limit:]]
if len(revs_ids) > revs_top_limit:
morelinks += ', ...'
cs_links.append(html_tmpl % (uniq_id, morelinks))
if len(revs) > 1:
cs_links.append(compare_view)
return ''.join(cs_links)
def get_fork_name():
repo_name = action_params
_url = url('summary_home', repo_name=repo_name)
return _('fork name %s') % link_to(action_params, _url)
def get_user_name():
user_name = action_params
return user_name
def get_users_group():
group_name = action_params
return group_name
def get_pull_request():
pull_request_id = action_params
repo_name = user_log.repository_name
return link_to(_('Pull request #%s') % pull_request_id,
url('pullrequest_show', repo_name=repo_name,
pull_request_id=pull_request_id))
# action : translated str, callback(extractor), icon
action_map = {
'user_deleted_repo': (_('[deleted] repository'),
None, 'database_delete.png'),
'user_created_repo': (_('[created] repository'),
None, 'database_add.png'),
'user_created_fork': (_('[created] repository as fork'),
None, 'arrow_divide.png'),
'user_forked_repo': (_('[forked] repository'),
get_fork_name, 'arrow_divide.png'),
'user_updated_repo': (_('[updated] repository'),
None, 'database_edit.png'),
'admin_deleted_repo': (_('[delete] repository'),
'admin_created_repo': (_('[created] repository'),
'admin_forked_repo': (_('[forked] repository'),
'admin_updated_repo': (_('[updated] repository'),
'admin_created_user': (_('[created] user'),
get_user_name, 'user_add.png'),
'admin_updated_user': (_('[updated] user'),
get_user_name, 'user_edit.png'),
'admin_created_users_group': (_('[created] users group'),
get_users_group, 'group_add.png'),
'admin_updated_users_group': (_('[updated] users group'),
get_users_group, 'group_edit.png'),
'user_commented_revision': (_('[commented] on revision in repository'),
get_cs_links, 'comment_add.png'),
'user_commented_pull_request': (_('[commented] on pull request for'),
get_pull_request, 'comment_add.png'),
'user_closed_pull_request': (_('[closed] pull request for'),
get_pull_request, 'tick.png'),
'push': (_('[pushed] into'),
get_cs_links, 'script_add.png'),
'push_local': (_('[committed via RhodeCode] into repository'),
get_cs_links, 'script_edit.png'),
'push_remote': (_('[pulled from remote] into repository'),
get_cs_links, 'connect.png'),
'pull': (_('[pulled] from'),
None, 'down_16.png'),
'started_following_repo': (_('[started following] repository'),
None, 'heart_add.png'),
'stopped_following_repo': (_('[stopped following] repository'),
None, 'heart_delete.png'),
}
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]
def action_parser_icon():
action_params = None
tmpl = """<img src="%s%s" alt="%s"/>"""
ico = action_map.get(action, ['', '', ''])[2]
return literal(tmpl % ((url('/images/icons/')), ico, action))
# returned callbacks we need to call to get
return [lambda: literal(action), action_params_func, action_parser_icon]
#==============================================================================
# PERMS
from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \
HasReposGroupPermissionAny
# GRAVATAR URL
def gravatar_url(email_address, size=30):
from pylons import url # doh, we need to re-import url to mock it later
if (not str2bool(config['app_conf'].get('use_gravatar')) or
not email_address or email_address == 'anonymous@rhodecode.org'):
_def = 'anonymous@rhodecode.org'
use_gravatar = str2bool(config['app_conf'].get('use_gravatar'))
email_address = email_address or _def
if (not use_gravatar or not email_address or email_address == _def):
f = lambda a, l: min(l, key=lambda x: abs(x - a))
return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
if(str2bool(config['app_conf'].get('use_gravatar')) and
config['app_conf'].get('alternative_gravatar_url')):
if use_gravatar and config['app_conf'].get('alternative_gravatar_url'):
tmpl = config['app_conf'].get('alternative_gravatar_url', '')
parsed_url = urlparse.urlparse(url.current(qualified=True))
tmpl = tmpl.replace('{email}', email_address)\
.replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
.replace('{netloc}', parsed_url.netloc)\
.replace('{scheme}', parsed_url.scheme)\
.replace('{size}', str(size))
return tmpl
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, **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))
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, 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, last_url=None):
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:
:param last_url:
groups, repo_name = groups_and_repos
last_link = link_to(repo_name, last_url) if last_url else repo_name
if not groups:
if last_url:
return last_link
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) + [last_link]))
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
def cgen(l_type, a_v, d_v):
mapping = {'tr': 'top-right-rounded-corner-mid',
'tl': 'top-left-rounded-corner-mid',
'br': 'bottom-right-rounded-corner-mid',
'bl': 'bottom-left-rounded-corner-mid'}
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:
a, d = stats[0], stats[1]
width = 100
if a == 'b':
#binary mode
b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
t = stats[0] + stats[1]
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 ''
d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
cgen('a', a_v, d_v), a_p, a_v
d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
cgen('d', a_v, d_v), d_p, d_v
return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
def urlify_text(text_):
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_))
def urlify_changesets(text_, repository):
Extract revision ids from changeset and make link from them
:param text_:
:param repository:
URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
rev = match_obj.groups()[0]
pref = ''
if match_obj.group().startswith(' '):
pref = ' '
tmpl = (
'%(pref)s<a class="%(cls)s" href="%(url)s">'
'%(rev)s'
'</a>'
return tmpl % {
'pref': pref,
'cls': 'revision-link',
'url': url('changeset_home', repo_name=repository, revision=rev),
'rev': rev,
newtext = URL_PAT.sub(url_func, text_)
return newtext
def urlify_commit(text_, repository=None, link_=None):
Parses given text message and makes proper links.
issues are linked to given issue-server, and rest is a changeset link
if link_ is given, in other case it's a plain text
:param link_: changeset link
import traceback
def escaper(string):
return string.replace('<', '<').replace('>', '>')
def linkify_others(t, l):
urls = re.compile(r'(\<a.*?\<\/a\>)',)
links = []
for e in urls.split(t):
if not urls.match(e):
links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
links.append(e)
return ''.join(links)
# urlify changesets - extrac revisions and make link out of them
newtext = urlify_changesets(escaper(text_), repository)
conf = config['app_conf']
# allow multiple issue servers to be used
valid_indices = [
x.group(1)
for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
if x and 'issue_server_link%s' % x.group(1) in conf
and 'issue_prefix%s' % x.group(1) in conf
]
log.debug('found issue server suffixes `%s` during valuation of: %s'
% (','.join(valid_indices), newtext))
for pattern_index in valid_indices:
ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
% (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
ISSUE_PREFIX))
URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
issue_id = ''.join(match_obj.groups())
'%(issue-prefix)s%(id-repr)s'
url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
if repository:
url = url.replace('{repo}', repository)
repo_name = repository.split(URL_SEP)[-1]
url = url.replace('{repo_name}', repo_name)
'cls': 'issue-tracker-link',
'url': url,
'id-repr': issue_id,
'issue-prefix': ISSUE_PREFIX,
'serv': ISSUE_SERVER_LNK,
newtext = URL_PAT.sub(url_func, newtext)
log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
# if we actually did something above
if link_:
# wrap not links into final link => link_
newtext = linkify_others(newtext, link_)
except:
log.error(traceback.format_exc())
pass
return literal(newtext)
def rst(source):
return literal('<div class="rst-block">%s</div>' %
MarkupRenderer.rst(source))
def rst_w_mentions(source):
Wrapped rst renderer with @mention highlighting
:param source:
MarkupRenderer.rst_with_mentions(source))
def changeset_status(repo, revision):
return ChangesetStatusModel().get_status(repo, revision)
def changeset_status_lbl(changeset_status):
return dict(ChangesetStatus.STATUSES).get(changeset_status)
def get_permission_name(key):
return dict(Permission.PERMS).get(key)
def journal_filter_help():
return _(textwrap.dedent('''
Example filter terms:
Status change: