@@ -37,24 +37,25 @@ from vcs.exceptions import RepositoryErr
ChangesetDoesNotExistError
from vcs.nodes import FileNode
import rhodecode.lib.helpers as h
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
from rhodecode.lib.base import BaseRepoController, render
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)
return h.url.current(anchor=fid, **request.GET)
def get_ignore_ws(fid, GET):
ig_ws_global = request.GET.get('ignorews')
ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
@@ -136,33 +137,24 @@ def _context_url(fileid=None):
ig_ws_key = fileid
ig_ws_val = 'WS:%s' % 1
if ig_ws:
params[ig_ws_key] += [ig_ws_val]
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):
super(ChangesetController, self).__before__()
c.affected_files_cut_off = 60
def index(self, revision):
c.anchor_url = anchor_url
@@ -183,145 +175,105 @@ class ChangesetController(BaseRepoContro
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')
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)
for changeset in c.cs_ranges:
c.comments.extend(ChangesetCommentsModel()\
.get_comments(c.rhodecode_db_repo.repo_id,
changeset.raw_id))
inlines = ChangesetCommentsModel()\
.get_inline_comments(c.rhodecode_db_repo.repo_id,
changeset.raw_id)
c.inline_comments.extend(inlines)
c.changes[changeset.raw_id] = []
try:
changeset_parent = changeset.parents[0]
except IndexError:
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:
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)
if len(c.cs_ranges) == 1:
c.changeset = c.cs_ranges[0]
c.changes = c.changes[c.changeset.raw_id]
return render('changeset/changeset.html')
@@ -18,44 +18,46 @@
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
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 rhodecode.lib import convert_line_endings, detect_mode, safe_str
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):
super(FilesController, self).__before__()
c.cut_off_limit = self.cut_off_limit
def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
@@ -96,81 +98,80 @@ class FilesController(BaseRepoController
file_node = cs.get_node(path)
if file_node.is_dir():
raise RepositoryError('given path is a directory')
except RepositoryError, e:
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()
tip = changeset
for topnode, dirs, files in tip.walk(starting_path):
for f in files:
_files.append(f.path)
for d in dirs:
_dirs.append(d.path)
log.debug(traceback.format_exc())
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)
c.file_history = []
revision=revision))
@@ -398,124 +399,109 @@ class FilesController(BaseRepoController
if not data:
os.remove(archive)
yield data
return get_chunked_archive(archive)
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.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)
node1 = FileNode('.', '', changeset=c.changeset_1)
if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
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 = []
changesets_group = ([], _("Changesets"))
branches_group = ([], _("Branches"))
tags_group = ([], _("Tags"))
for chs in changesets:
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
def nodelist(self, repo_name, revision, f_path):
if request.environ.get('HTTP_X_PARTIAL_XHR'):
cs = self.__get_cs_or_redirect(revision, repo_name)
_d, _f = self.__get_paths(cs, f_path)
@@ -18,30 +18,84 @@
# (at your option) any later version.
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``.
:param ignore_whitespace: ignore whitespaces in diff
for filenode in (filenode_old, filenode_new):
if not isinstance(filenode, FileNode):
raise VCSError("Given object should be FileNode object, not %s"
@@ -254,26 +308,26 @@ class DiffProcessor(object):
[int(x or 1) for x in match.groups()[:-1]]
old_line -= 1
new_line -= 1
context = len(match.groups()) == 5
old_end += old_line
new_end += new_line
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:
if line:
command, line = line[0], line[1:]
command = ' '
affects_old = affects_new = False
@@ -362,90 +416,100 @@ class DiffProcessor(object):
def as_html(self, table_class='code-difftable', line_class='line',
new_lineno_class='lineno old', old_lineno_class='lineno new',
code_class='code', enable_comments=False):
Return udiff as html table with customized css classes
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)
def stat(self):
Returns tuple of added, and removed lines for this instance
return self.adds, self.removes
@@ -31,41 +31,42 @@ from webhelpers.text import chop_at, col
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, _make_safe_id_component
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)))
def get_token():
"""Return the current authentication token, creating one if one doesn't
already exist.
token_key = "_authentication_token"
from pylons import session
@@ -107,25 +108,25 @@ 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()
@@ -729,23 +730,23 @@ 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 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
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -46,25 +46,25 @@ class ChangesetCommentsModel(BaseModel):
def _extract_mentions(self, s):
user_objects = []
for username in extract_mentioned_users(s):
user_obj = User.get_by_username(username, case_insensitive=True)
if user_obj:
user_objects.append(user_obj)
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:
if text:
repo = Repository.get(repo_id)
cs = repo.scm_instance.get_changeset(revision)
desc = cs.message
author = cs.author_email
@@ -75,25 +75,25 @@ class ChangesetCommentsModel(BaseModel):
comment.text = text
comment.f_path = f_path
comment.line_no = line_no
self.sa.add(comment)
self.sa.flush()
# 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,
body = text
recipients = ChangesetComment.get_users(revision=revision)
# add changeset author
recipients += [User.get_by_email(author)]
NotificationModel().create(created_by=user_id, subject=subj,
@@ -105,40 +105,39 @@ class ChangesetCommentsModel(BaseModel):
if mention_recipients:
subj = _('[Mention]') + ' ' + subj
body=body,
recipients=mention_recipients,
type_=Notification.TYPE_CHANGESET_COMMENT)
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()
def get_inline_comments(self, repo_id, revision):
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()
@@ -10,41 +10,37 @@
<div id="${h.FID(filenode.changeset.raw_id,filenode.path)}" style="clear:both;height:90px;margin-top:-60px"></div>
<div class="diffblock margined comm">
<div class="code-header">
<div class="changeset_header">
<div class="changeset_file">
${h.link_to_if(change!='removed',h.safe_unicode(filenode.path),h.url('files_home',repo_name=c.repo_name,
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>
<span style="float:right;margin-top:-3px">
<label>
${_('show inline comments')}
${h.checkbox('',checked="checked",class_="show-inline-comments",id_for=h.FID(filenode.changeset.raw_id,filenode.path))}
</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
@@ -12,42 +12,38 @@
${_('File diff')} r${c.changeset_1.revision}:${h.short_id(c.changeset_1.raw_id)} → r${c.changeset_2.revision}:${h.short_id(c.changeset_2.raw_id)}
<%def name="page_nav()">
${self.menu('files')}
<%def name="main()">
<div class="box">
<!-- box / title -->
<div class="title">
${self.breadcrumbs()}
<div class="table">
<div id="body" class="diffblock">
<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>
@@ -50,25 +50,25 @@
${h.link_to(_('show as raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
${h.link_to(_('download as raw'),h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
% if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
% if not c.file.is_binary:
${h.link_to(_('source'),h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
% 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")}
${h.end_form()}
<script type="text/javascript">
var reset_url = "${h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.file.path)}";
initCodeMirror('editor',reset_url);
@@ -2,25 +2,24 @@
<dt style="padding-top:10px;font-size:16px">${_('History')}</dt>
<dd>
${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')}
${h.hidden('diff2',c.file.last_changeset.raw_id)}
${h.select('diff1',c.file.last_changeset.raw_id,c.file_history)}
${h.submit('diff','diff to revision',class_="ui-btn")}
${h.submit('show_rev','show at revision',class_="ui-btn")}
</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>
<div class="left item">${h.format_byte_size(c.file.size,binary=True)}</div>
<div class="left item last">${c.file.mimetype}</div>
<div class="buttons">
${h.link_to(_('show annotation'),h.url('files_annotate_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
${h.link_to(_('show as raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
${h.link_to(_('download as raw'),h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
Status change: