@@ -43,12 +43,13 @@ from rhodecode.lib.base import BaseRepoC
from rhodecode.lib.utils import EmptyChangeset
from rhodecode.lib.compat import OrderedDict
from rhodecode.lib import diffs
from rhodecode.model.db import ChangesetComment
from rhodecode.model.comment import ChangesetCommentsModel
from rhodecode.model.meta import Session
from rhodecode.lib.diffs import wrapped_diff
log = logging.getLogger(__name__)
def anchor_url(revision, path):
fid = h.FID(revision, path)
@@ -142,21 +143,12 @@ def _context_url(fileid=None):
lbl = _('%s line context') % ln_ctx
params['anchor'] = fileid
return h.link_to(lbl, h.url.current(**params))
def wrap_to_table(str_):
return '''<table class="code-difftable">
<tr class="line no-comment">
<td class="lineno new"></td>
<td class="code no-comment"><pre>%s</pre></td>
</tr>
</table>''' % str_
class ChangesetController(BaseRepoController):
@LoginRequired()
@HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
'repository.admin')
def __before__(self):
@@ -189,16 +181,17 @@ class ChangesetController(BaseRepoContro
except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
log.error(traceback.format_exc())
h.flash(str(e), category='warning')
return redirect(url('home'))
c.changes = OrderedDict()
c.sum_added = 0
c.sum_removed = 0
c.lines_added = 0
c.lines_deleted = 0
c.lines_added = 0 # count of lines added
c.lines_deleted = 0 # count of lines removes
cumulative_diff = 0
c.cut_off = False # defines if cut off limit is reached
c.comments = []
c.inline_comments = []
c.inline_cnt = 0
# Iterate over ranges (default changeset view is always one changeset)
@@ -217,105 +210,64 @@ class ChangesetController(BaseRepoContro
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)
else:
# 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
fid = h.FID(revision, node.path)
line_context_lcl = get_line_ctx(fid, request.GET)
ignore_whitespace_lcl = get_ignore_ws(fid, request.GET)
if c.sum_added < self.cut_off_limit:
f_gitdiff = diffs.get_gitdiff(filenode_old, node,
ignore_whitespace=ignore_whitespace_lcl,
context=line_context_lcl)
d = diffs.DiffProcessor(f_gitdiff, format='gitdiff')
st = d.stat()
diff = d.as_html(enable_comments=enable_comments)
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
ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
lim = self.cut_off_limit
if cumulative_diff > self.cut_off_limit:
lim = -1
size, cs1, cs2, diff, st = wrapped_diff(filenode_old=None,
filenode_new=node,
cut_off_limit=lim,
ignore_whitespace=ign_whitespace_lcl,
line_context=line_context_lcl,
enable_comments=enable_comments)
cumulative_diff += size
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:
try:
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:
ignore_whitespace_lcl = get_ignore_ws(fid, request.GET,)
d = diffs.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
c.changes[changeset.raw_id].append(('changed', node, diff,
size, cs1, cs2, diff, st = wrapped_diff(filenode_old=filenode_old,
# REMOVED FILES
for node in changeset.removed:
c.changes[changeset.raw_id].append(('removed', node, None,
None, None, (0, 0)))
# count inline comments
for path, lines in c.inline_comments:
for comments in lines.values():
c.inline_cnt += len(comments)
@@ -24,32 +24,34 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import logging
import traceback
from os.path import join as jn
from pylons import request, response, session, tmpl_context as c, url
from pylons import request, response, tmpl_context as c, url
from pylons.i18n.translation import _
from pylons.controllers.util import redirect
from pylons.decorators import jsonify
from vcs.conf import settings
from vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
EmptyRepositoryError, ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError
from vcs.nodes import FileNode, NodeKind
EmptyRepositoryError, ImproperArchiveTypeError, VCSError, \
NodeAlreadyExistsError
from vcs.nodes import FileNode
from rhodecode.lib import convert_line_endings, detect_mode, safe_str
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
from rhodecode.lib.base import BaseRepoController, render
import rhodecode.lib.helpers as h
from rhodecode.model.repo import RepoModel
from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
_context_url, get_line_ctx, get_ignore_ws
class FilesController(BaseRepoController):
@@ -102,13 +104,12 @@ class FilesController(BaseRepoController
redirect(h.url('files_home', repo_name=repo_name,
revision=cs.raw_id))
return file_node
def __get_paths(self, changeset, starting_path):
"""recursive walk in root dir and return a set of all path in that dir
based on repository walk function
"""
_files = list()
_dirs = list()
@@ -125,46 +126,46 @@ class FilesController(BaseRepoController
pass
return _dirs, _files
def index(self, repo_name, revision, f_path):
#reditect to given revision from form if given
# redirect to given revision from form if given
post_revision = request.POST.get('at_rev', None)
if post_revision:
cs = self.__get_cs_or_redirect(post_revision, repo_name)
redirect(url('files_home', repo_name=c.repo_name,
revision=cs.raw_id, f_path=f_path))
c.changeset = self.__get_cs_or_redirect(revision, repo_name)
c.branch = request.GET.get('branch', None)
c.f_path = f_path
cur_rev = c.changeset.revision
#prev link
# prev link
prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
c.url_prev = url('files_home', repo_name=c.repo_name,
revision=prev_rev.raw_id, f_path=f_path)
if c.branch:
c.url_prev += '?branch=%s' % c.branch
except (ChangesetDoesNotExistError, VCSError):
c.url_prev = '#'
#next link
# next link
next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
c.url_next = url('files_home', repo_name=c.repo_name,
revision=next_rev.raw_id, f_path=f_path)
c.url_next += '?branch=%s' % c.branch
c.url_next = '#'
#files or dirs
# files or dirs
c.file = c.changeset.get_node(f_path)
if c.file.is_file():
c.file_history = self._get_node_history(c.changeset, f_path)
@@ -404,19 +405,23 @@ class FilesController(BaseRepoController
def diff(self, repo_name, f_path):
ignore_whitespace = request.GET.get('ignorews') == '1'
line_context = request.GET.get('context', 3)
diff1 = request.GET.get('diff1')
diff2 = request.GET.get('diff2')
diff1 = request.GET.get('diff1', '')
diff2 = request.GET.get('diff2', '')
c.action = request.GET.get('diff')
c.no_changes = diff1 == diff2
c.big_diff = False
c.anchor_url = anchor_url
c.ignorews_url = _ignorews_url
c.context_url = _context_url
c.changes[diff2] = []
if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
node1 = c.changeset_1.get_node(f_path)
c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
@@ -426,67 +431,50 @@ class FilesController(BaseRepoController
c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
node2 = c.changeset_2.get_node(f_path)
c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
node2 = FileNode('.', '', changeset=c.changeset_2)
except RepositoryError:
return redirect(url('files_home',
repo_name=c.repo_name, f_path=f_path))
return redirect(url('files_home', repo_name=c.repo_name,
f_path=f_path))
if c.action == 'download':
_diff = diffs.get_gitdiff(node1, node2,
ignore_whitespace=ignore_whitespace,
context=line_context)
diff = diffs.DiffProcessor(_diff,format='gitdiff')
diff = diffs.DiffProcessor(_diff, format='gitdiff')
diff_name = '%s_vs_%s.diff' % (diff1, diff2)
response.content_type = 'text/plain'
response.content_disposition = 'attachment; filename=%s' \
% diff_name
return diff.raw_diff()
elif c.action == 'raw':
elif c.action == 'diff':
if node1.is_binary or node2.is_binary:
c.cur_diff = _('Binary file')
elif node1.size > self.cut_off_limit or \
node2.size > self.cut_off_limit:
c.cur_diff = ''
c.big_diff = True
c.cur_diff = diff.as_html()
fid = h.FID(diff2, node2.path)
#default option
lim = request.GET.get('fulldiff') or self.cut_off_limit
_, cs1, cs2, diff, st = wrapped_diff(filenode_old=node1,
filenode_new=node2,
enable_comments=False)
c.changes = [('', node2, diff, cs1, cs2, st,)]
if not c.cur_diff and not c.big_diff:
c.no_changes = True
return render('files/file_diff.html')
def _get_node_history(self, cs, f_path):
changesets = cs.get_file_history(f_path)
hist_l = []
@@ -498,18 +486,16 @@ class FilesController(BaseRepoController
n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
changesets_group[0].append((chs.raw_id, n_desc,))
hist_l.append(changesets_group)
for name, chs in c.rhodecode_repo.branches.items():
#chs = chs.split(':')[-1]
branches_group[0].append((chs, name),)
hist_l.append(branches_group)
for name, chs in c.rhodecode_repo.tags.items():
tags_group[0].append((chs, name),)
hist_l.append(tags_group)
return hist_l
@jsonify
@@ -24,18 +24,72 @@
#
# You should have received a copy of the GNU General Public License
import re
import difflib
import markupsafe
from itertools import tee, imap
from vcs.exceptions import VCSError
def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
ignore_whitespace=True, line_context=3,
enable_comments=False):
returns a wrapped diff into a table, checks for cut_off_limit and presents
proper message
if filenode_old is None:
filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
if filenode_old.is_binary or filenode_new.is_binary:
stats = (0, 0)
size = 0
elif cut_off_limit != -1 and (cut_off_limit is None or
(filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
f_gitdiff = get_gitdiff(filenode_old, filenode_new,
diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
diff = diff_processor.as_html(enable_comments=enable_comments)
stats = diff_processor.stat()
size = len(diff or '')
diff = wrap_to_table(_('Changeset was to big and was cut off, use '
'diff menu to display this diff'))
if not diff:
diff = wrap_to_table(_('No changes detected'))
cs2 = filenode_new.last_changeset.raw_id
return size, cs1, cs2, diff, stats
def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
Returns git style diff between given ``filenode_old`` and ``filenode_new``.
@@ -260,14 +314,14 @@ class DiffProcessor(object):
if context:
if not skipfirst:
lines.append({
'old_lineno': '...',
'new_lineno': '...',
'action': 'context',
'line': line,
})
skipfirst = False
line = lineiter.next()
while old_line < old_end or new_line < new_end:
@@ -368,78 +422,88 @@ class DiffProcessor(object):
def _link_to_if(condition, label, url):
Generates a link if condition is meet or just the label if not.
if condition:
return '''<a href="%(url)s">%(label)s</a>''' % {'url': url,
'label': label}
return '''<a href="%(url)s">%(label)s</a>''' % {
'url': url,
'label': label
}
return label
diff_lines = self.prepare()
_html_empty = True
_html = []
_html.append('''<table class="%(table_class)s">\n''' \
% {'table_class': table_class})
_html.append('''<table class="%(table_class)s">\n''' % {
'table_class': table_class
for diff in diff_lines:
for line in diff['chunks']:
_html_empty = False
for change in line:
_html.append('''<tr class="%(line_class)s %(action)s">\n''' \
% {'line_class': line_class,
'action': change['action']})
_html.append('''<tr class="%(lc)s %(action)s">\n''' % {
'lc': line_class,
'action': change['action']
anchor_old_id = ''
anchor_new_id = ''
anchor_old = "%(filename)s_o%(oldline_no)s" % \
{'filename': self._safe_id(diff['filename']),
'oldline_no': change['old_lineno']}
anchor_new = "%(filename)s_n%(oldline_no)s" % \
'oldline_no': change['new_lineno']}
cond_old = change['old_lineno'] != '...' and \
change['old_lineno']
cond_new = change['new_lineno'] != '...' and \
change['new_lineno']
anchor_old = "%(filename)s_o%(oldline_no)s" % {
'filename': self._safe_id(diff['filename']),
'oldline_no': change['old_lineno']
anchor_new = "%(filename)s_n%(oldline_no)s" % {
'oldline_no': change['new_lineno']
cond_old = (change['old_lineno'] != '...' and
change['old_lineno'])
cond_new = (change['new_lineno'] != '...' and
change['new_lineno'])
if cond_old:
anchor_old_id = 'id="%s"' % anchor_old
if cond_new:
anchor_new_id = 'id="%s"' % anchor_new
###########################################################
# OLD LINE NUMBER
_html.append('''\t<td %(a_id)s class="%(old_lineno_cls)s">''' \
% {'a_id': anchor_old_id,
'old_lineno_cls': old_lineno_class})
_html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
'a_id': anchor_old_id,
'olc': old_lineno_class
_html.append('''%(link)s''' \
% {'link':
_link_to_if(cond_old, change['old_lineno'], '#%s' \
% anchor_old)})
_html.append('''%(link)s''' % {
'link': _link_to_if(True, change['old_lineno'],
'#%s' % anchor_old)
_html.append('''</td>\n''')
# NEW LINE NUMBER
_html.append('''\t<td %(a_id)s class="%(new_lineno_cls)s">''' \
% {'a_id': anchor_new_id,
'new_lineno_cls': new_lineno_class})
_html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
'a_id': anchor_new_id,
'nlc': new_lineno_class
_link_to_if(cond_new, change['new_lineno'], '#%s' \
% anchor_new)})
'link': _link_to_if(True, change['new_lineno'],
'#%s' % anchor_new)
# CODE
comments = '' if enable_comments else 'no-comment'
_html.append('''\t<td class="%(code_class)s %(in-comments)s">''' \
% {'code_class': code_class,
'in-comments': comments})
_html.append('''\n\t\t<pre>%(code)s</pre>\n''' \
% {'code': change['line']})
_html.append('''\t<td class="%(cc)s %(inc)s">''' % {
'cc': code_class,
'inc': comments
_html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
'code': change['line']
_html.append('''\t</td>''')
_html.append('''\n</tr>\n''')
_html.append('''</table>''')
if _html_empty:
return None
return ''.join(_html)
@@ -37,29 +37,30 @@ from webhelpers.html.tags import _set_in
from rhodecode.lib.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.markup_renderer import MarkupRenderer
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
safeid = _make_safe_id_component
def FID(raw_id,path):
def FID(raw_id, path):
Creates a uniqe ID for filenode based on it's path and revision
:param raw_id:
:param path:
return 'C-%s-%s' % (short_id(raw_id), safeid(safe_unicode(path)))
@@ -113,13 +114,13 @@ class _FilesBreadCrumbs(object):
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])
)
@@ -735,17 +736,17 @@ def urlify_text(text):
return '<a href="%(url)s">%(url)s</a>' % ({'url':url_full})
return literal(url_pat.sub(url_func, text))
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))
# -*- coding: utf-8 -*-
rhodecode.model.comment
~~~~~~~~~~~~~~~~~~~~~~~
comments model for RhodeCode
:created_on: Nov 11, 2011
: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
@@ -52,13 +52,13 @@ class ChangesetCommentsModel(BaseModel):
return user_objects
def create(self, text, repo_id, user_id, revision, f_path=None,
line_no=None):
Creates new comment for changeset
:param text:
:param repo_id:
:param user_id:
:param revision:
:param f_path:
:param line_no:
@@ -81,13 +81,13 @@ class ChangesetCommentsModel(BaseModel):
# make notification
line = ''
if line_no:
line = _('on line %s') % line_no
subj = h.link_to('Re commit: %(commit_desc)s %(line)s' % \
{'commit_desc':desc, 'line':line},
{'commit_desc': desc, 'line': line},
h.url('changeset_home', repo_name=repo.repo_name,
revision=revision,
anchor='comment-%s' % comment.comment_id,
qualified=True,
@@ -111,21 +111,20 @@ class ChangesetCommentsModel(BaseModel):
return comment
def delete(self, comment):
Deletes given comment
:param comment_id:
comment = self.__get_changeset_comment(comment)
self.sa.delete(comment)
def get_comments(self, repo_id, revision):
return ChangesetComment.query()\
.filter(ChangesetComment.repo_id == repo_id)\
.filter(ChangesetComment.revision == revision)\
.filter(ChangesetComment.line_no == None)\
.filter(ChangesetComment.f_path == None).all()
@@ -134,11 +133,11 @@ class ChangesetCommentsModel(BaseModel):
comments = self.sa.query(ChangesetComment)\
.filter(ChangesetComment.line_no != None)\
.filter(ChangesetComment.f_path != None).all()
paths = defaultdict(lambda:defaultdict(list))
paths = defaultdict(lambda: defaultdict(list))
for co in comments:
paths[co.f_path][co.line_no].append(co)
return paths.items()
@@ -16,13 +16,13 @@
revision=filenode.changeset.raw_id,f_path=h.safe_unicode(filenode.path)))}
</div>
<div class="diff-menu-wrapper">
<img class="diff-menu-activate" style="margin-bottom:-6px;cursor: pointer" alt="diff-menu" src="${h.url('/images/icons/script_gear.png')}" />
<div class="diff-menu" style="display:none">
<ul>
<li>${h.link_to(_('diff'),h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='diff'))}</li>
<li>${h.link_to(_('diff'),h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1))}</li>
<li>${h.link_to(_('raw diff'),h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='raw'))}</li>
<li>${h.link_to(_('download diff'),h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='download'))}</li>
<li>${c.ignorews_url(h.FID(filenode.changeset.raw_id,filenode.path))}</li>
<li>${c.context_url(h.FID(filenode.changeset.raw_id,filenode.path))}</li>
</ul>
@@ -34,17 +34,13 @@
</label>
</span>
<div class="code-body">
<div class="full_f_path" path="${h.safe_unicode(filenode.path)}"></div>
%if diff:
${diff|n}
%else:
${_('No changes in this file')}
%endif
%endfor
</%def>
\ No newline at end of file
@@ -18,36 +18,32 @@
<%def name="main()">
<div class="box">
<!-- box / title -->
<div class="title">
${self.breadcrumbs()}
<div class="table">
<div id="body" class="diffblock">
<div class="code-header">
<div class="changeset_header">
<span class="changeset_file">${h.link_to(c.f_path,h.url('files_home',repo_name=c.repo_name,
revision=c.changeset_2.raw_id,f_path=c.f_path))}</span>
» <span>${h.link_to(_('diff'),
h.url.current(diff2=c.changeset_2.raw_id,diff1=c.changeset_1.raw_id,diff='diff'))}</span>
» <span>${h.link_to(_('raw diff'),
h.url.current(diff2=c.changeset_2.raw_id,diff1=c.changeset_1.raw_id,diff='raw'))}</span>
» <span>${h.link_to(_('download diff'),
h.url.current(diff2=c.changeset_2.raw_id,diff1=c.changeset_1.raw_id,diff='download'))}</span>
%if c.no_changes:
${_('No changes')}
%elif c.big_diff:
${_('Diff is to big to display')} ${h.link_to(_('raw diff'),
h.url.current(diff2=c.changeset_2.raw_id,diff1=c.changeset_1.raw_id,diff='raw'))}
${c.cur_diff|n}
<div>
## diff block
<%namespace name="diff_block" file="/changeset/diff_block.html"/>
${diff_block.diff_block(c.changes)}
<script>
YUE.onDOMReady(function(){
YUE.on(YUQ('.diff-menu-activate'),'click',function(e){
var act = e.currentTarget.nextElementSibling;
if(YUD.hasClass(act,'active')){
YUD.removeClass(act,'active');
YUD.setStyle(act,'display','none');
}else{
YUD.addClass(act,'active');
YUD.setStyle(act,'display','');
});
</script>
@@ -56,13 +56,13 @@
% endif
<div class="commit">${_('Editing file')}: ${c.file.path}</div>
<pre id="editor_pre"></pre>
<textarea id="editor" name="content" style="display:none">${c.file.content|n}</textarea>
<textarea id="editor" name="content" style="display:none">${h.escape(c.file.content)|n}</textarea>
<div style="padding: 10px;color:#666666">${_('commit message')}</div>
<textarea id="commit" name="message" style="height: 60px;width: 99%;margin-left:4px"></textarea>
<div style="text-align: left;padding-top: 5px">
${h.submit('commit',_('Commit changes'),class_="ui-btn")}
${h.reset('reset',_('Reset'),class_="ui-btn")}
@@ -8,13 +8,12 @@
${h.submit('diff','diff to revision',class_="ui-btn")}
${h.submit('show_rev','show at revision',class_="ui-btn")}
${h.end_form()}
</dd>
</dl>
<div id="body" class="codeblock">
<div class="stats">
<div class="left"><img src="${h.url('/images/icons/file.png')}"/></div>
<div class="left item"><pre>${h.link_to("r%s:%s" % (c.file.last_changeset.revision,h.short_id(c.file.last_changeset.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id))}</pre></div>
Status change: