here:
- # since the content in the other cell also is wrapped in a div,
- # some browsers in some configurations seem to mess up the formatting.
- '''
- yield 0, ('
'
-
- '''
- headers_row = []
- if self.headers:
- headers_row = ['')
-
- body_row_start = ['
']
- for key in self.order:
- if key == 'ls':
- body_row_start.append(
- ' | ')
- elif key == 'annotate':
- body_row_start.append(
- ' | ')
- elif key == 'code':
- body_row_start.append('')
- yield 0, ('' % self.cssclass +
- ''.join(headers_row) +
- ''.join(body_row_start)
- )
- yield 0, dummyoutfile.getvalue()
- yield 0, ' '
diff --git a/rhodecode/lib/codeblocks.py b/rhodecode/lib/codeblocks.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/lib/codeblocks.py
@@ -0,0 +1,147 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2011-2016 RhodeCode GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3
+# (only), as published by the Free Software Foundation.
+#
+# 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 Affero General Public License
+# along with this program. If not, see .
+#
+# This program is dual-licensed. If you wish to learn more about the
+# RhodeCode Enterprise Edition, including its added features, Support services,
+# and proprietary license terms, please see https://rhodecode.com/licenses/
+
+
+from itertools import groupby
+
+from pygments import lex
+# PYGMENTS_TOKEN_TYPES is used in a hot loop keep attribute lookups to a minimum
+from pygments.token import STANDARD_TYPES as PYGMENTS_TOKEN_TYPES
+
+from rhodecode.lib.helpers import get_lexer_for_filenode
+
+def tokenize_file(content, lexer):
+ """
+ Use pygments to tokenize some content based on a lexer
+ ensuring all original new lines and whitespace is preserved
+ """
+
+ lexer.stripall = False
+ lexer.stripnl = False
+ lexer.ensurenl = False
+ return lex(content, lexer)
+
+
+def pygment_token_class(token_type):
+ """ Convert a pygments token type to html class name """
+
+ fname = PYGMENTS_TOKEN_TYPES.get(token_type)
+ if fname:
+ return fname
+
+ aname = ''
+ while fname is None:
+ aname = '-' + token_type[-1] + aname
+ token_type = token_type.parent
+ fname = PYGMENTS_TOKEN_TYPES.get(token_type)
+
+ return fname + aname
+
+
+def tokens_as_lines(tokens, split_string=u'\n'):
+ """
+ Take a list of (TokenType, text) tuples and split them by a string
+
+ eg. [(TEXT, 'some\ntext')] => [(TEXT, 'some'), (TEXT, 'text')]
+ """
+
+ buffer = []
+ for token_type, token_text in tokens:
+ parts = token_text.split(split_string)
+ for part in parts[:-1]:
+ buffer.append((token_type, part))
+ yield buffer
+ buffer = []
+
+ buffer.append((token_type, parts[-1]))
+
+ if buffer:
+ yield buffer
+
+
+def filenode_as_lines_tokens(filenode):
+ """
+ Return a generator of lines with pygment tokens for a filenode eg:
+
+ [
+ (1, line1_tokens_list),
+ (2, line1_tokens_list]),
+ ]
+ """
+
+ return enumerate(
+ tokens_as_lines(
+ tokenize_file(
+ filenode.content, get_lexer_for_filenode(filenode)
+ )
+ ),
+ 1)
+
+
+def filenode_as_annotated_lines_tokens(filenode):
+ """
+ Take a file node and return a list of annotations => lines, if no annotation
+ is found, it will be None.
+
+ eg:
+
+ [
+ (annotation1, [
+ (1, line1_tokens_list),
+ (2, line2_tokens_list),
+ ]),
+ (annotation2, [
+ (3, line1_tokens_list),
+ ]),
+ (None, [
+ (4, line1_tokens_list),
+ ]),
+ (annotation1, [
+ (5, line1_tokens_list),
+ (6, line2_tokens_list),
+ ])
+ ]
+ """
+
+
+ # cache commit_getter lookups
+ commit_cache = {}
+ def _get_annotation(commit_id, commit_getter):
+ if commit_id not in commit_cache:
+ commit_cache[commit_id] = commit_getter()
+ return commit_cache[commit_id]
+
+ annotation_lookup = {
+ line_no: _get_annotation(commit_id, commit_getter)
+ for line_no, commit_id, commit_getter, line_content
+ in filenode.annotate
+ }
+
+ annotations_lines = ((annotation_lookup.get(line_no), line_no, tokens)
+ for line_no, tokens
+ in filenode_as_lines_tokens(filenode))
+
+ grouped_annotations_lines = groupby(annotations_lines, lambda x: x[0])
+
+ for annotation, group in grouped_annotations_lines:
+ yield (
+ annotation, [(line_no, tokens)
+ for (_, line_no, tokens) in group]
+ )
diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py
--- a/rhodecode/lib/helpers.py
+++ b/rhodecode/lib/helpers.py
@@ -67,7 +67,6 @@ from webhelpers.html.tags import _set_in
convert_boolean_attrs, NotGiven, _make_safe_id_component
from webhelpers2.number import format_byte_size
-from rhodecode.lib.annotate import annotate_highlight
from rhodecode.lib.action_parser import action_parser
from rhodecode.lib.ext_json import json
from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
@@ -125,17 +124,18 @@ def asset(path, ver=None):
'rhodecode:public/{}'.format(path), _query=query)
-def html_escape(text, html_escape_table=None):
+default_html_escape_table = {
+ ord('&'): u'&',
+ ord('<'): u'<',
+ ord('>'): u'>',
+ ord('"'): u'"',
+ ord("'"): u''',
+}
+
+
+def html_escape(text, html_escape_table=default_html_escape_table):
"""Produce entities within text."""
- if not html_escape_table:
- html_escape_table = {
- "&": "&",
- '"': """,
- "'": "'",
- ">": ">",
- "<": "<",
- }
- return "".join(html_escape_table.get(c, c) for c in text)
+ return text.translate(html_escape_table)
def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
@@ -500,6 +500,86 @@ def get_matching_line_offsets(lines, ter
return matching_lines
+def hsv_to_rgb(h, s, v):
+ """ Convert hsv color values to rgb """
+
+ if s == 0.0:
+ return v, v, v
+ i = int(h * 6.0) # XXX assume int() truncates!
+ f = (h * 6.0) - i
+ p = v * (1.0 - s)
+ q = v * (1.0 - s * f)
+ t = v * (1.0 - s * (1.0 - f))
+ i = i % 6
+ if i == 0:
+ return v, t, p
+ if i == 1:
+ return q, v, p
+ if i == 2:
+ return p, v, t
+ if i == 3:
+ return p, q, v
+ if i == 4:
+ return t, p, v
+ if i == 5:
+ return v, p, q
+
+
+def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
+ """
+ Generator for getting n of evenly distributed colors using
+ hsv color and golden ratio. It always return same order of colors
+
+ :param n: number of colors to generate
+ :param saturation: saturation of returned colors
+ :param lightness: lightness of returned colors
+ :returns: RGB tuple
+ """
+
+ golden_ratio = 0.618033988749895
+ h = 0.22717784590367374
+
+ for _ in xrange(n):
+ h += golden_ratio
+ h %= 1
+ HSV_tuple = [h, saturation, lightness]
+ RGB_tuple = hsv_to_rgb(*HSV_tuple)
+ yield map(lambda x: str(int(x * 256)), RGB_tuple)
+
+
+def color_hasher(n=10000, saturation=0.10, lightness=0.95):
+ """
+ Returns a function which when called with an argument returns a unique
+ color for that argument, eg.
+
+ :param n: number of colors to generate
+ :param saturation: saturation of returned colors
+ :param lightness: lightness of returned colors
+ :returns: css RGB string
+
+ >>> color_hash = color_hasher()
+ >>> color_hash('hello')
+ 'rgb(34, 12, 59)'
+ >>> color_hash('hello')
+ 'rgb(34, 12, 59)'
+ >>> color_hash('other')
+ 'rgb(90, 224, 159)'
+ """
+
+ color_dict = {}
+ cgenerator = unique_color_generator(
+ saturation=saturation, lightness=lightness)
+
+ def get_color_string(thing):
+ if thing in color_dict:
+ col = color_dict[thing]
+ else:
+ col = color_dict[thing] = cgenerator.next()
+ return "rgb(%s)" % (', '.join(col))
+
+ return get_color_string
+
+
def get_lexer_safe(mimetype=None, filepath=None):
"""
Tries to return a relevant pygments lexer using mimetype/filepath name,
@@ -536,92 +616,6 @@ def pygmentize(filenode, **kwargs):
CodeHtmlFormatter(**kwargs)))
-def pygmentize_annotation(repo_name, filenode, **kwargs):
- """
- pygmentize function for annotation
-
- :param filenode:
- """
-
- color_dict = {}
-
- def gen_color(n=10000):
- """generator for getting n of evenly distributed colors using
- hsv color and golden ratio. It always return same order of colors
-
- :returns: RGB tuple
- """
-
- def hsv_to_rgb(h, s, v):
- if s == 0.0:
- return v, v, v
- i = int(h * 6.0) # XXX assume int() truncates!
- f = (h * 6.0) - i
- p = v * (1.0 - s)
- q = v * (1.0 - s * f)
- t = v * (1.0 - s * (1.0 - f))
- i = i % 6
- if i == 0:
- return v, t, p
- if i == 1:
- return q, v, p
- if i == 2:
- return p, v, t
- if i == 3:
- return p, q, v
- if i == 4:
- return t, p, v
- if i == 5:
- return v, p, q
-
- golden_ratio = 0.618033988749895
- h = 0.22717784590367374
-
- for _ in xrange(n):
- h += golden_ratio
- h %= 1
- HSV_tuple = [h, 0.95, 0.95]
- RGB_tuple = hsv_to_rgb(*HSV_tuple)
- yield map(lambda x: str(int(x * 256)), RGB_tuple)
-
- cgenerator = gen_color()
-
- def get_color_string(commit_id):
- if commit_id in color_dict:
- col = color_dict[commit_id]
- else:
- col = color_dict[commit_id] = cgenerator.next()
- return "color: rgb(%s)! important;" % (', '.join(col))
-
- def url_func(repo_name):
-
- def _url_func(commit):
- author = commit.author
- date = commit.date
- message = tooltip(commit.message)
-
- tooltip_html = ("Author:"
- " %s Date: %s Message:"
- " %s
")
-
- tooltip_html = tooltip_html % (author, date, message)
- lnk_format = '%5s:%s' % ('r%s' % commit.idx, commit.short_id)
- uri = link_to(
- lnk_format,
- url('changeset_home', repo_name=repo_name,
- revision=commit.raw_id),
- style=get_color_string(commit.raw_id),
- class_='tooltip',
- title=tooltip_html
- )
-
- uri += '\n'
- return uri
- return _url_func
-
- return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
-
-
def is_following_repo(repo_name, user_id):
from rhodecode.model.scm import ScmModel
return ScmModel().is_following_repo(repo_name, user_id)
diff --git a/rhodecode/public/css/bootstrap-variables.less b/rhodecode/public/css/bootstrap-variables.less
--- a/rhodecode/public/css/bootstrap-variables.less
+++ b/rhodecode/public/css/bootstrap-variables.less
@@ -45,7 +45,8 @@
@font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif;
@font-family-serif: Georgia, "Times New Roman", Times, serif;
//** Default monospace fonts for ``, ``, and ``.
-@font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace;
+@font-family-monospace: Consolas, "Liberation Mono", Menlo, Monaco, Courier, monospace;
+
@font-family-base: @font-family-sans-serif;
@font-size-base: 14px;
diff --git a/rhodecode/public/css/code-block.less b/rhodecode/public/css/code-block.less
--- a/rhodecode/public/css/code-block.less
+++ b/rhodecode/public/css/code-block.less
@@ -506,20 +506,19 @@ div.codeblock {
th,
td {
- padding: .5em !important;
- border: @border-thickness solid @border-default-color !important;
+ padding: .5em;
+ border: @border-thickness solid @border-default-color;
}
}
table {
- width: 0 !important;
- border: 0px !important;
+ border: 0px;
margin: 0;
letter-spacing: normal;
-
-
+
+
td {
- border: 0px !important;
+ border: 0px;
vertical-align: top;
}
}
@@ -569,7 +568,7 @@ div.annotatediv { margin-left: 2px; marg
.CodeMirror ::-moz-selection { background: @rchighlightblue; }
.code { display: block; border:0px !important; }
-.code-highlight,
+.code-highlight, /* TODO: dan: merge codehilite into code-highlight */
.codehilite {
.hll { background-color: #ffffcc }
.c { color: #408080; font-style: italic } /* Comment */
@@ -641,3 +640,110 @@ pre.literal-block, .codehilite pre{
.border-radius(@border-radius);
background-color: @grey7;
}
+
+
+/* START NEW CODE BLOCK CSS */
+
+table.cb {
+ width: 100%;
+ border-collapse: collapse;
+ margin-bottom: 10px;
+
+ * {
+ box-sizing: border-box;
+ }
+
+ /* intentionally general selector since .cb-line-selected must override it
+ and they both use !important since the td itself may have a random color
+ generated by annotation blocks. TLDR: if you change it, make sure
+ annotated block selection and line selection in file view still work */
+ .cb-line-fresh .cb-content {
+ background: white !important;
+ }
+
+ tr.cb-annotate {
+ border-top: 1px solid #eee;
+
+ &+ .cb-line {
+ border-top: 1px solid #eee;
+ }
+
+ &:first-child {
+ border-top: none;
+ &+ .cb-line {
+ border-top: none;
+ }
+ }
+ }
+
+ td {
+ vertical-align: top;
+ padding: 2px 10px;
+
+ &.cb-content {
+ white-space: pre-wrap;
+ font-family: @font-family-monospace;
+ font-size: 12.35px;
+
+ span {
+ word-break: break-word;
+ }
+ }
+
+ &.cb-lineno {
+ padding: 0;
+ height: 1px; /* this allows the link to fill to 100% height of the td */
+ width: 50px;
+ color: rgba(0, 0, 0, 0.3);
+ text-align: right;
+ border-right: 1px solid #eee;
+ font-family: @font-family-monospace;
+
+ a::before {
+ content: attr(data-line-no);
+ }
+ &.cb-line-selected {
+ background: @comment-highlight-color !important;
+ }
+
+ a {
+ display: block;
+ height: 100%;
+ color: rgba(0, 0, 0, 0.3);
+ padding: 0 10px; /* vertical padding is 0 so that height: 100% works */
+ line-height: 18px; /* use this instead of vertical padding */
+ }
+ }
+
+ &.cb-content {
+ &.cb-line-selected {
+ background: @comment-highlight-color !important;
+ }
+ }
+
+ &.cb-annotate-info {
+ width: 320px;
+ min-width: 320px;
+ max-width: 320px;
+ padding: 5px 2px;
+ font-size: 13px;
+
+ strong.cb-annotate-message {
+ padding: 5px 0;
+ white-space: pre-line;
+ display: inline-block;
+ }
+ .rc-user {
+ float: none;
+ padding: 0 6px 0 17px;
+ min-width: auto;
+ min-height: auto;
+ }
+ }
+
+ &.cb-annotate-revision {
+ cursor: pointer;
+ text-align: right;
+ }
+ }
+}
diff --git a/rhodecode/public/js/src/rhodecode.js b/rhodecode/public/js/src/rhodecode.js
--- a/rhodecode/public/js/src/rhodecode.js
+++ b/rhodecode/public/js/src/rhodecode.js
@@ -266,7 +266,35 @@ function offsetScroll(element, offset){
}
});
- $('.compare_view_files').on(
+ $('body').on( /* TODO: replace the $('.compare_view_files').on('click') below
+ when new diffs are integrated */
+ 'click', '.cb-lineno a', function(event) {
+
+ if ($(this).attr('data-line-no') !== ""){
+ $('.cb-line-selected').removeClass('cb-line-selected');
+ var td = $(this).parent();
+ td.addClass('cb-line-selected'); // line number td
+ td.next().addClass('cb-line-selected'); // line content td
+
+ // Replace URL without jumping to it if browser supports.
+ // Default otherwise
+ if (history.pushState) {
+ var new_location = location.href.rstrip('#');
+ if (location.hash) {
+ new_location = new_location.replace(location.hash, "");
+ }
+
+ // Make new anchor url
+ new_location = new_location + $(this).attr('href');
+ history.pushState(true, document.title, new_location);
+
+ return false;
+ }
+ }
+ });
+
+ $('.compare_view_files').on( /* TODO: replace this with .cb function above
+ when new diffs are integrated */
'click', 'tr.line .lineno a',function(event) {
if ($(this).text() != ""){
$('tr.line').removeClass('selected');
@@ -365,10 +393,11 @@ function offsetScroll(element, offset){
// Select the line that comes from the url anchor
// At the time of development, Chrome didn't seem to support jquery's :target
// element, so I had to scroll manually
- if (location.hash) {
+
+ if (location.hash) { /* TODO: dan: remove this and replace with code block
+ below when new diffs are ready */
var result = splitDelimitedHash(location.hash);
var loc = result.loc;
- var remainder = result.remainder;
if (loc.length > 1){
var lineno = $(loc+'.lineno');
if (lineno.length > 0){
@@ -378,11 +407,70 @@ function offsetScroll(element, offset){
tr[0].scrollIntoView();
$.Topic('/ui/plugins/code/anchor_focus').prepareOrPublish({
- tr:tr,
- remainder:remainder});
+ tr: tr,
+ remainder: result.remainder});
}
}
}
+ if (location.hash) { /* TODO: dan: use this to replace the code block above
+ when new diffs are ready */
+ var result = splitDelimitedHash(location.hash);
+ var loc = result.loc;
+ if (loc.length > 1) {
+ var page_highlights = loc.substring(
+ loc.indexOf('#') + 1).split('L');
+
+ if (page_highlights.length > 1) {
+ var highlight_ranges = page_highlights[1].split(",");
+ var h_lines = [];
+ for (var pos in highlight_ranges) {
+ var _range = highlight_ranges[pos].split('-');
+ if (_range.length === 2) {
+ var start = parseInt(_range[0]);
+ var end = parseInt(_range[1]);
+ if (start < end) {
+ for (var i = start; i <= end; i++) {
+ h_lines.push(i);
+ }
+ }
+ }
+ else {
+ h_lines.push(parseInt(highlight_ranges[pos]));
+ }
+ }
+ for (pos in h_lines) {
+ var line_td = $('td.cb-lineno#L' + h_lines[pos]);
+ if (line_td.length) {
+ line_td.addClass('cb-line-selected'); // line number td
+ line_td.next().addClass('cb-line-selected'); // line content
+ }
+ }
+ var first_line_td = $('td.cb-lineno#L' + h_lines[0]);
+ if (first_line_td.length) {
+ var elOffset = first_line_td.offset().top;
+ var elHeight = first_line_td.height();
+ var windowHeight = $(window).height();
+ var offset;
+
+ if (elHeight < windowHeight) {
+ offset = elOffset - ((windowHeight / 4) - (elHeight / 2));
+ }
+ else {
+ offset = elOffset;
+ }
+ $(function() { // let browser scroll to hash first, then
+ // scroll the line to the middle of page
+ setTimeout(function() {
+ $('html, body').animate({ scrollTop: offset });
+ }, 100);
+ });
+ $.Topic('/ui/plugins/code/anchor_focus').prepareOrPublish({
+ lineno: first_line_td,
+ remainder: result.remainder});
+ }
+ }
+ }
+ }
collapsableContent();
});
diff --git a/rhodecode/templates/codeblocks/source.html b/rhodecode/templates/codeblocks/source.html
new file mode 100644
--- /dev/null
+++ b/rhodecode/templates/codeblocks/source.html
@@ -0,0 +1,70 @@
+<%def name="render_line(line_num, tokens,
+ annotation=None,
+ bgcolor=None)">
+ <%
+ # avoid module lookups for performance
+ from rhodecode.lib.codeblocks import pygment_token_class
+ from rhodecode.lib.helpers import html_escape
+ %>
+
+
+
+ |
+ ${
+ ''.join(
+ '%s' %
+ (pygment_token_class(token_type), html_escape(token_text))
+ for token_type, token_text in tokens) + '\n' | n
+ } |
+ ## this ugly list comp is necessary for performance
+
+%def>
+
+<%def name="render_annotation_lines(annotation, lines, color_hasher)">
+ <%
+ rowspan = len(lines) + 1 # span the line's and annotation
+ %>
+ %if not annotation:
+
+ |
+ |
+
+ %else:
+
+
+ ${h.gravatar_with_user(annotation.author, 16) | n}
+ ${
+ h.truncate(annotation.message, len(lines) * 30)
+ }
+ |
+
+
+ r${annotation.revision}
+
+ |
+
+ %endif
+
+ %for line_num, tokens in lines:
+ ${render_line(line_num, tokens,
+ bgcolor=color_hasher(annotation and annotation.raw_id or ''),
+ annotation=annotation,
+ )}
+ %endfor
+%def>
diff --git a/rhodecode/templates/files/files.html b/rhodecode/templates/files/files.html
--- a/rhodecode/templates/files/files.html
+++ b/rhodecode/templates/files/files.html
@@ -147,43 +147,6 @@
if (source_page) {
// variants for with source code, not tree view
- if (location.href.indexOf('#') != -1) {
- page_highlights = location.href.substring(location.href.indexOf('#') + 1).split('L');
- if (page_highlights.length == 2) {
- highlight_ranges = page_highlights[1].split(",");
-
- var h_lines = [];
- for (pos in highlight_ranges) {
- var _range = highlight_ranges[pos].split('-');
- if (_range.length == 2) {
- var start = parseInt(_range[0]);
- var end = parseInt(_range[1]);
- if (start < end) {
- for (var i = start; i <= end; i++) {
- h_lines.push(i);
- }
- }
- }
- else {
- h_lines.push(parseInt(highlight_ranges[pos]));
- }
- }
-
- for (pos in h_lines) {
- // @comment-highlight-color
- $('#L' + h_lines[pos]).css('background-color', '#ffd887');
- }
-
- var _first_line = $('#L' + h_lines[0]).get(0);
- if (_first_line) {
- var line = $('#L' + h_lines[0]);
- if (line.length > 0){
- offsetScroll(line, 70);
- }
- }
- }
- }
-
// select code link event
$("#hlcode").mouseup(getSelectionLink);
diff --git a/rhodecode/templates/files/files_source.html b/rhodecode/templates/files/files_source.html
--- a/rhodecode/templates/files/files_source.html
+++ b/rhodecode/templates/files/files_source.html
@@ -1,3 +1,4 @@
+<%namespace name="sourceblock" file="/codeblocks/source.html"/>
%else:
% if c.file.size < c.cut_off_limit:
- %if c.annotate:
- ${h.pygmentize_annotation(c.repo_name,c.file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
- %elif c.renderer:
+ %if c.renderer and not c.annotate:
${h.render(c.file.content, renderer=c.renderer)}
%else:
- ${h.pygmentize(c.file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
+
+ %if c.annotate:
+ <% color_hasher = h.color_hasher() %>
+ %for annotation, lines in c.annotated_lines:
+ ${sourceblock.render_annotation_lines(annotation, lines, color_hasher)}
+ %endfor
+ %else:
+ %for line_num, tokens in c.lines:
+ ${sourceblock.render_line(line_num, tokens)}
+ %endfor
+ %endif
+
+
%endif
%else:
${_('File is too big to display')} ${h.link_to(_('Show as raw'),
diff --git a/rhodecode/tests/functional/test_files.py b/rhodecode/tests/functional/test_files.py
--- a/rhodecode/tests/functional/test_files.py
+++ b/rhodecode/tests/functional/test_files.py
@@ -81,7 +81,7 @@ def _commit_change(
return commit
-
+
@pytest.mark.usefixtures("app")
class TestFilesController:
@@ -270,9 +270,9 @@ class TestFilesController:
annotate=True))
expected_revisions = {
- 'hg': 'r356:25213a5fbb04',
- 'git': 'r345:c994f0de03b2',
- 'svn': 'r208:209',
+ 'hg': 'r356',
+ 'git': 'r345',
+ 'svn': 'r208',
}
response.mustcontain(expected_revisions[backend.alias])
|