# HG changeset patch # User Marcin Kuzminski # Date 2011-12-04 21:39:32 # Node ID 1d1ccb873d00d0d3c02d8d16eca988a4feea17df # Parent f28dc032adf0117124473fe2ab10bc16333eae32 moved soon-to-be-deleted code from vcs to rhodecode - diff lib - annotate highlighter diff --git a/rhodecode/controllers/changeset.py b/rhodecode/controllers/changeset.py --- a/rhodecode/controllers/changeset.py +++ b/rhodecode/controllers/changeset.py @@ -36,13 +36,13 @@ from rhodecode.lib.auth import LoginRequ 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 vcs.exceptions import RepositoryError, ChangesetError, \ ChangesetDoesNotExistError from vcs.nodes import FileNode -from vcs.utils import diffs as differ from webob.exc import HTTPForbidden from rhodecode.model.meta import Session @@ -130,9 +130,9 @@ class ChangesetController(BaseRepoContro # made c.sum_added += node.size if c.sum_added < self.cut_off_limit: - f_gitdiff = differ.get_gitdiff(filenode_old, node, + f_gitdiff = diffs.get_gitdiff(filenode_old, node, ignore_whitespace=ignore_whitespace) - d = differ.DiffProcessor(f_gitdiff, format='gitdiff') + d = diffs.DiffProcessor(f_gitdiff, format='gitdiff') st = d.stat() diff = d.as_html() @@ -169,9 +169,9 @@ class ChangesetController(BaseRepoContro else: if c.sum_removed < self.cut_off_limit: - f_gitdiff = differ.get_gitdiff(filenode_old, node, + f_gitdiff = diffs.get_gitdiff(filenode_old, node, ignore_whitespace=ignore_whitespace) - d = differ.DiffProcessor(f_gitdiff, + d = diffs.DiffProcessor(f_gitdiff, format='gitdiff') st = d.stat() if (st[0] + st[1]) * 256 > self.cut_off_limit: @@ -240,9 +240,9 @@ class ChangesetController(BaseRepoContro if filenode_old.is_binary or node.is_binary: diff = _('binary file') + '\n' else: - f_gitdiff = differ.get_gitdiff(filenode_old, node, + f_gitdiff = diffs.get_gitdiff(filenode_old, node, ignore_whitespace=ignore_whitespace) - diff = differ.DiffProcessor(f_gitdiff, + diff = diffs.DiffProcessor(f_gitdiff, format='gitdiff').raw_diff() cs1 = None @@ -254,9 +254,9 @@ class ChangesetController(BaseRepoContro if filenode_old.is_binary or node.is_binary: diff = _('binary file') else: - f_gitdiff = differ.get_gitdiff(filenode_old, node, + f_gitdiff = diffs.get_gitdiff(filenode_old, node, ignore_whitespace=ignore_whitespace) - diff = differ.DiffProcessor(f_gitdiff, + diff = diffs.DiffProcessor(f_gitdiff, format='gitdiff').raw_diff() cs1 = filenode_old.last_changeset.raw_id diff --git a/rhodecode/controllers/files.py b/rhodecode/controllers/files.py --- a/rhodecode/controllers/files.py +++ b/rhodecode/controllers/files.py @@ -38,12 +38,13 @@ from vcs.conf import settings from vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \ EmptyRepositoryError, ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError from vcs.nodes import FileNode, NodeKind -from vcs.utils import diffs as differ + 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 from rhodecode.lib.utils import EmptyChangeset +from rhodecode.lib import diffs import rhodecode.lib.helpers as h from rhodecode.model.repo import RepoModel @@ -431,9 +432,9 @@ class FilesController(BaseRepoController repo_name=c.repo_name, f_path=f_path)) if c.action == 'download': - _diff = differ.get_gitdiff(node1, node2, + _diff = diffs.get_gitdiff(node1, node2, ignore_whitespace=ignore_whitespace) - diff = differ.DiffProcessor(_diff,format='gitdiff') + diff = diffs.DiffProcessor(_diff,format='gitdiff') diff_name = '%s_vs_%s.diff' % (diff1, diff2) response.content_type = 'text/plain' @@ -442,9 +443,9 @@ class FilesController(BaseRepoController return diff.raw_diff() elif c.action == 'raw': - _diff = differ.get_gitdiff(node1, node2, + _diff = diffs.get_gitdiff(node1, node2, ignore_whitespace=ignore_whitespace) - diff = differ.DiffProcessor(_diff,format='gitdiff') + diff = diffs.DiffProcessor(_diff,format='gitdiff') response.content_type = 'text/plain' return diff.raw_diff() @@ -456,9 +457,9 @@ class FilesController(BaseRepoController c.cur_diff = '' c.big_diff = True else: - _diff = differ.get_gitdiff(node1, node2, + _diff = diffs.get_gitdiff(node1, node2, ignore_whitespace=ignore_whitespace) - diff = differ.DiffProcessor(_diff,format='gitdiff') + diff = diffs.DiffProcessor(_diff,format='gitdiff') c.cur_diff = diff.as_html() else: @@ -471,9 +472,9 @@ class FilesController(BaseRepoController c.big_diff = True else: - _diff = differ.get_gitdiff(node1, node2, + _diff = diffs.get_gitdiff(node1, node2, ignore_whitespace=ignore_whitespace) - diff = differ.DiffProcessor(_diff,format='gitdiff') + diff = diffs.DiffProcessor(_diff,format='gitdiff') c.cur_diff = diff.as_html() if not c.cur_diff and not c.big_diff: diff --git a/rhodecode/lib/annotate.py b/rhodecode/lib/annotate.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/annotate.py @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- +""" + rhodecode.lib.annotate + ~~~~~~~~~~~~~~~~~~~~~~ + + Anontation library for usage in rhodecode, previously part of vcs + + :created_on: Dec 4, 2011 + :author: marcink + :copyright: (C) 2009-2011 Marcin Kuzminski + :license: GPLv3, see COPYING for more details. +""" + +from vcs.exceptions import VCSError +from vcs.nodes import FileNode +from pygments.formatters import HtmlFormatter +from pygments import highlight + +import StringIO + + +def annotate_highlight(filenode, annotate_from_changeset_func=None, + order=None, headers=None, **options): + """ + Returns html portion containing annotated table with 3 columns: line + numbers, changeset information and pygmentized line of code. + + :param filenode: FileNode object + :param annotate_from_changeset_func: function taking changeset and + returning single annotate cell; needs break line at the end + :param order: ordered sequence of ``ls`` (line numbers column), + ``annotate`` (annotate column), ``code`` (code column); Default is + ``['ls', 'annotate', 'code']`` + :param headers: dictionary with headers (keys are whats in ``order`` + parameter) + """ + options['linenos'] = True + formatter = AnnotateHtmlFormatter(filenode=filenode, order=order, + headers=headers, + annotate_from_changeset_func=annotate_from_changeset_func, **options) + lexer = filenode.lexer + highlighted = highlight(filenode.content, lexer, formatter) + return highlighted + + +class AnnotateHtmlFormatter(HtmlFormatter): + + def __init__(self, filenode, annotate_from_changeset_func=None, + order=None, **options): + """ + If ``annotate_from_changeset_func`` is passed it should be a function + which returns string from the given changeset. For example, we may pass + following function as ``annotate_from_changeset_func``:: + + def changeset_to_anchor(changeset): + return '%s\n' %\ + (changeset.id, changeset.id) + + :param annotate_from_changeset_func: see above + :param order: (default: ``['ls', 'annotate', 'code']``); order of + columns; + :param options: standard pygment's HtmlFormatter options, there is + extra option tough, ``headers``. For instance we can pass:: + + formatter = AnnotateHtmlFormatter(filenode, headers={ + 'ls': '#', + 'annotate': 'Annotate', + 'code': 'Code', + }) + + """ + super(AnnotateHtmlFormatter, self).__init__(**options) + self.annotate_from_changeset_func = annotate_from_changeset_func + self.order = order or ('ls', 'annotate', 'code') + headers = options.pop('headers', None) + if headers and not ('ls' in headers and 'annotate' in headers and + 'code' in headers): + raise ValueError("If headers option dict is specified it must " + "all 'ls', 'annotate' and 'code' keys") + self.headers = headers + if isinstance(filenode, FileNode): + self.filenode = filenode + else: + raise VCSError("This formatter expect FileNode parameter, not %r" + % type(filenode)) + + def annotate_from_changeset(self, changeset): + """ + Returns full html line for single changeset per annotated line. + """ + if self.annotate_from_changeset_func: + return self.annotate_from_changeset_func(changeset) + else: + return ''.join((changeset.id, '\n')) + + def _wrap_tablelinenos(self, inner): + dummyoutfile = StringIO.StringIO() + lncount = 0 + for t, line in inner: + if t: + lncount += 1 + dummyoutfile.write(line) + + fl = self.linenostart + mw = len(str(lncount + fl - 1)) + sp = self.linenospecial + st = self.linenostep + la = self.lineanchors + aln = self.anchorlinenos + if sp: + lines = [] + + for i in range(fl, fl + lncount): + if i % st == 0: + if i % sp == 0: + if aln: + lines.append('' + '%*d' % + (la, i, mw, i)) + else: + lines.append('' + '%*d' % (mw, i)) + else: + if aln: + lines.append('' + '%*d' % (la, i, mw, i)) + else: + lines.append('%*d' % (mw, i)) + else: + lines.append('') + ls = '\n'.join(lines) + else: + lines = [] + for i in range(fl, fl + lncount): + if i % st == 0: + if aln: + lines.append('%*d' \ + % (la, i, mw, i)) + else: + lines.append('%*d' % (mw, i)) + else: + lines.append('') + ls = '\n'.join(lines) + + annotate_changesets = [tup[1] for tup in self.filenode.annotate] + # If pygments cropped last lines break we need do that too + ln_cs = len(annotate_changesets) + ln_ = len(ls.splitlines()) + if ln_cs > ln_: + annotate_changesets = annotate_changesets[:ln_ - ln_cs] + annotate = ''.join((self.annotate_from_changeset(changeset) + for changeset in annotate_changesets)) + # in case you wonder about the seemingly redundant
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, ('' % self.cssclass + + '' + + '
' +
+                  ls + '
') + yield 0, dummyoutfile.getvalue() + yield 0, '
' + + ''' + headers_row = [] + if self.headers: + headers_row = [''] + for key in self.order: + td = ''.join(('', self.headers[key], '')) + headers_row.append(td) + headers_row.append('') + + body_row_start = [''] + for key in self.order: + if key == 'ls': + body_row_start.append( + '
' +
+                    ls + '
') + elif key == 'annotate': + body_row_start.append( + '
' +
+                    annotate + '
') + 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/diffs.py b/rhodecode/lib/diffs.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/diffs.py @@ -0,0 +1,447 @@ +# -*- coding: utf-8 -*- +""" + rhodecode.lib.diffs + ~~~~~~~~~~~~~~~~~~~ + + Set of diffing helpers, previously part of vcs + + + :created_on: Dec 4, 2011 + :author: marcink + :copyright: (C) 2009-2011 Marcin Kuzminski + :original copyright: 2007-2008 by Armin Ronacher + :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 . + +import re +import difflib + +from itertools import tee, imap + +from mercurial.match import match + +from vcs.exceptions import VCSError +from vcs.nodes import FileNode + +def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True): + """ + 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" + % filenode.__class__) + + old_raw_id = getattr(filenode_old.changeset, 'raw_id', '0' * 40) + new_raw_id = getattr(filenode_new.changeset, 'raw_id', '0' * 40) + + repo = filenode_new.changeset.repository + vcs_gitdiff = repo._get_diff(old_raw_id, new_raw_id, filenode_new.path, + ignore_whitespace) + + return vcs_gitdiff + + +class DiffProcessor(object): + """ + Give it a unified diff and it returns a list of the files that were + mentioned in the diff together with a dict of meta information that + can be used to render it in a HTML template. + """ + _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)') + + def __init__(self, diff, differ='diff', format='udiff'): + """ + :param diff: a text in diff format or generator + :param format: format of diff passed, `udiff` or `gitdiff` + """ + if isinstance(diff, basestring): + diff = [diff] + + self.__udiff = diff + self.__format = format + self.adds = 0 + self.removes = 0 + + if isinstance(self.__udiff, basestring): + self.lines = iter(self.__udiff.splitlines(1)) + + elif self.__format == 'gitdiff': + udiff_copy = self.copy_iterator() + self.lines = imap(self.escaper, self._parse_gitdiff(udiff_copy)) + else: + udiff_copy = self.copy_iterator() + self.lines = imap(self.escaper, udiff_copy) + + # Select a differ. + if differ == 'difflib': + self.differ = self._highlight_line_difflib + else: + self.differ = self._highlight_line_udiff + + def escaper(self, string): + return string.replace('<', '<').replace('>', '>') + + def copy_iterator(self): + """ + make a fresh copy of generator, we should not iterate thru + an original as it's needed for repeating operations on + this instance of DiffProcessor + """ + self.__udiff, iterator_copy = tee(self.__udiff) + return iterator_copy + + def _extract_rev(self, line1, line2): + """ + Extract the filename and revision hint from a line. + """ + + try: + if line1.startswith('--- ') and line2.startswith('+++ '): + l1 = line1[4:].split(None, 1) + old_filename = l1[0].lstrip('a/') if len(l1) >= 1 else None + old_rev = l1[1] if len(l1) == 2 else 'old' + + l2 = line2[4:].split(None, 1) + new_filename = l2[0].lstrip('b/') if len(l1) >= 1 else None + new_rev = l2[1] if len(l2) == 2 else 'new' + + filename = old_filename if (old_filename != + 'dev/null') else new_filename + + return filename, new_rev, old_rev + except (ValueError, IndexError): + pass + + return None, None, None + + def _parse_gitdiff(self, diffiterator): + def line_decoder(l): + if l.startswith('+') and not l.startswith('+++'): + self.adds += 1 + elif l.startswith('-') and not l.startswith('---'): + self.removes += 1 + return l.decode('utf8', 'replace') + + output = list(diffiterator) + size = len(output) + + if size == 2: + l = [] + l.extend([output[0]]) + l.extend(output[1].splitlines(1)) + return map(line_decoder, l) + elif size == 1: + return map(line_decoder, output[0].splitlines(1)) + elif size == 0: + return [] + + raise Exception('wrong size of diff %s' % size) + + def _highlight_line_difflib(self, line, next): + """ + Highlight inline changes in both lines. + """ + + if line['action'] == 'del': + old, new = line, next + else: + old, new = next, line + + oldwords = re.split(r'(\W)', old['line']) + newwords = re.split(r'(\W)', new['line']) + + sequence = difflib.SequenceMatcher(None, oldwords, newwords) + + oldfragments, newfragments = [], [] + for tag, i1, i2, j1, j2 in sequence.get_opcodes(): + oldfrag = ''.join(oldwords[i1:i2]) + newfrag = ''.join(newwords[j1:j2]) + if tag != 'equal': + if oldfrag: + oldfrag = '%s' % oldfrag + if newfrag: + newfrag = '%s' % newfrag + oldfragments.append(oldfrag) + newfragments.append(newfrag) + + old['line'] = "".join(oldfragments) + new['line'] = "".join(newfragments) + + def _highlight_line_udiff(self, line, next): + """ + Highlight inline changes in both lines. + """ + start = 0 + limit = min(len(line['line']), len(next['line'])) + while start < limit and line['line'][start] == next['line'][start]: + start += 1 + end = -1 + limit -= start + while -end <= limit and line['line'][end] == next['line'][end]: + end -= 1 + end += 1 + if start or end: + def do(l): + last = end + len(l['line']) + if l['action'] == 'add': + tag = 'ins' + else: + tag = 'del' + l['line'] = '%s<%s>%s%s' % ( + l['line'][:start], + tag, + l['line'][start:last], + tag, + l['line'][last:] + ) + do(line) + do(next) + + def _parse_udiff(self): + """ + Parse the diff an return data for the template. + """ + lineiter = self.lines + files = [] + try: + line = lineiter.next() + # skip first context + skipfirst = True + while 1: + # continue until we found the old file + if not line.startswith('--- '): + line = lineiter.next() + continue + + chunks = [] + filename, old_rev, new_rev = \ + self._extract_rev(line, lineiter.next()) + files.append({ + 'filename': filename, + 'old_revision': old_rev, + 'new_revision': new_rev, + 'chunks': chunks + }) + + line = lineiter.next() + while line: + match = self._chunk_re.match(line) + if not match: + break + + lines = [] + chunks.append(lines) + + old_line, old_end, new_line, new_end = \ + [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, + }) + else: + skipfirst = False + + line = lineiter.next() + while old_line < old_end or new_line < new_end: + if line: + command, line = line[0], line[1:] + else: + command = ' ' + affects_old = affects_new = False + + # ignore those if we don't expect them + if command in '#@': + continue + elif command == '+': + affects_new = True + action = 'add' + elif command == '-': + affects_old = True + action = 'del' + else: + affects_old = affects_new = True + action = 'unmod' + + old_line += affects_old + new_line += affects_new + lines.append({ + 'old_lineno': affects_old and old_line or '', + 'new_lineno': affects_new and new_line or '', + 'action': action, + 'line': line + }) + line = lineiter.next() + + except StopIteration: + pass + + # highlight inline changes + for file in files: + for chunk in chunks: + lineiter = iter(chunk) + #first = True + try: + while 1: + line = lineiter.next() + if line['action'] != 'unmod': + nextline = lineiter.next() + if nextline['action'] == 'unmod' or \ + nextline['action'] == line['action']: + continue + self.differ(line, nextline) + except StopIteration: + pass + + return files + + def prepare(self): + """ + Prepare the passed udiff for HTML rendering. It'l return a list + of dicts + """ + return self._parse_udiff() + + def _safe_id(self, idstring): + """Make a string safe for including in an id attribute. + + The HTML spec says that id attributes 'must begin with + a letter ([A-Za-z]) and may be followed by any number + of letters, digits ([0-9]), hyphens ("-"), underscores + ("_"), colons (":"), and periods (".")'. These regexps + are slightly over-zealous, in that they remove colons + and periods unnecessarily. + + Whitespace is transformed into underscores, and then + anything which is not a hyphen or a character that + matches \w (alphanumerics and underscore) is removed. + + """ + # Transform all whitespace to underscore + idstring = re.sub(r'\s', "_", '%s' % idstring) + # Remove everything that is not a hyphen or a member of \w + idstring = re.sub(r'(?!-)\W', "", idstring).lower() + return idstring + + def raw_diff(self): + """ + Returns raw string as udiff + """ + udiff_copy = self.copy_iterator() + if self.__format == 'gitdiff': + udiff_copy = self._parse_gitdiff(udiff_copy) + return u''.join(udiff_copy) + + def as_html(self, table_class='code-difftable', line_class='line', + new_lineno_class='lineno old', old_lineno_class='lineno new', + code_class='code'): + """ + 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 '''%(label)s''' % {'url': url, + 'label': label} + else: + return label + diff_lines = self.prepare() + _html_empty = True + _html = [] + _html.append('''\n''' \ + % {'table_class': table_class}) + for diff in diff_lines: + for line in diff['chunks']: + _html_empty = False + for change in line: + _html.append('''\n''' \ + % {'line_class': 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" % \ + {'filename': self._safe_id(diff['filename']), + '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\n''') + ########################################################### + # NEW LINE NUMBER + ########################################################### + + _html.append('''\t\n''') + ########################################################### + # CODE + ########################################################### + _html.append('''\t''') + _html.append('''\n\n''') + _html.append('''
''' \ + % {'a_id': anchor_old_id, + 'old_lineno_cls': old_lineno_class}) + + _html.append('''
%(link)s
''' \ + % {'link': + _link_to_if(cond_old, change['old_lineno'], '#%s' \ + % anchor_old)}) + _html.append('''
''' \ + % {'a_id': anchor_new_id, + 'new_lineno_cls': new_lineno_class}) + + _html.append('''
%(link)s
''' \ + % {'link': + _link_to_if(cond_new, change['new_lineno'], '#%s' \ + % anchor_new)}) + _html.append('''
''' \ + % {'code_class': code_class}) + _html.append('''\n\t\t
%(code)s
\n''' \ + % {'code': change['line']}) + _html.append('''\t
''') + 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 diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py --- a/rhodecode/lib/helpers.py +++ b/rhodecode/lib/helpers.py @@ -35,7 +35,7 @@ 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 vcs.utils.annotate import annotate_highlight +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 diff --git a/rhodecode/lib/utils.py b/rhodecode/lib/utils.py --- a/rhodecode/lib/utils.py +++ b/rhodecode/lib/utils.py @@ -597,5 +597,4 @@ class BasePasterCommand(Command): path_to_ini_file = os.path.realpath(conf) conf = paste.deploy.appconfig('config:' + path_to_ini_file) - pylonsconfig.init_app(conf.global_conf, conf.local_conf) - + pylonsconfig.init_app(conf.global_conf, conf.local_conf) \ No newline at end of file diff --git a/rhodecode/model/scm.py b/rhodecode/model/scm.py --- a/rhodecode/model/scm.py +++ b/rhodecode/model/scm.py @@ -158,10 +158,7 @@ class ScmModel(BaseModel): klass = get_backend(path[0]) if path[0] == 'hg' and path[0] in BACKENDS.keys(): - - # for mercurial we need to have an str path - repos[name] = klass(safe_str(path[1]), - baseui=baseui) + repos[name] = klass(safe_str(path[1]), baseui=baseui) if path[0] == 'git' and path[0] in BACKENDS.keys(): repos[name] = klass(path[1])