##// END OF EJS Templates
Add option to define custom lexers for custom extensions for code highlight in rcextension module
marcink -
r3375:7000fc4a beta
parent child Browse files
Show More
@@ -1,118 +1,126 b''
1 # Additional mappings that are not present in the pygments lexers
1 # Additional mappings that are not present in the pygments lexers
2 # used for building stats
2 # used for building stats
3 # format is {'ext':['Names']} eg. {'py':['Python']} note: there can be
3 # format is {'ext':['Names']} eg. {'py':['Python']} note: there can be
4 # more than one name for extension
4 # more than one name for extension
5 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
5 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
6 # build by pygments
6 # build by pygments
7 EXTRA_MAPPINGS = {}
7 EXTRA_MAPPINGS = {}
8
8
9 # additional lexer definitions for custom files
10 # it's overrides pygments lexers, and uses defined name of lexer to colorize the
11 # files. Format is {'ext': 'lexer_name'}
12 # List of lexers can be printed running:
13 # python -c "import pprint;from pygments import lexers;pprint.pprint([(x[0], x[1]) for x in lexers.get_all_lexers()]);"
14
15 EXTRA_LEXERS = {}
16
9 #==============================================================================
17 #==============================================================================
10 # WHOOSH INDEX EXTENSIONS
18 # WHOOSH INDEX EXTENSIONS
11 #==============================================================================
19 #==============================================================================
12 # if INDEX_EXTENSIONS is [] it'll use pygments lexers extensions by default.
20 # if INDEX_EXTENSIONS is [] it'll use pygments lexers extensions by default.
13 # To set your own just add to this list extensions to index with content
21 # To set your own just add to this list extensions to index with content
14 INDEX_EXTENSIONS = []
22 INDEX_EXTENSIONS = []
15
23
16 # additional extensions for indexing besides the default from pygments
24 # additional extensions for indexing besides the default from pygments
17 # those get's added to INDEX_EXTENSIONS
25 # those get's added to INDEX_EXTENSIONS
18 EXTRA_INDEX_EXTENSIONS = []
26 EXTRA_INDEX_EXTENSIONS = []
19
27
20
28
21 #==============================================================================
29 #==============================================================================
22 # POST CREATE REPOSITORY HOOK
30 # POST CREATE REPOSITORY HOOK
23 #==============================================================================
31 #==============================================================================
24 # this function will be executed after each repository is created
32 # this function will be executed after each repository is created
25 def _crhook(*args, **kwargs):
33 def _crhook(*args, **kwargs):
26 """
34 """
27 Post create repository HOOK
35 Post create repository HOOK
28 kwargs available:
36 kwargs available:
29 :param repo_name:
37 :param repo_name:
30 :param repo_type:
38 :param repo_type:
31 :param description:
39 :param description:
32 :param private:
40 :param private:
33 :param created_on:
41 :param created_on:
34 :param enable_downloads:
42 :param enable_downloads:
35 :param repo_id:
43 :param repo_id:
36 :param user_id:
44 :param user_id:
37 :param enable_statistics:
45 :param enable_statistics:
38 :param clone_uri:
46 :param clone_uri:
39 :param fork_id:
47 :param fork_id:
40 :param group_id:
48 :param group_id:
41 :param created_by:
49 :param created_by:
42 """
50 """
43 return 0
51 return 0
44 CREATE_REPO_HOOK = _crhook
52 CREATE_REPO_HOOK = _crhook
45
53
46
54
47 #==============================================================================
55 #==============================================================================
48 # POST DELETE REPOSITORY HOOK
56 # POST DELETE REPOSITORY HOOK
49 #==============================================================================
57 #==============================================================================
50 # this function will be executed after each repository deletion
58 # this function will be executed after each repository deletion
51 def _dlhook(*args, **kwargs):
59 def _dlhook(*args, **kwargs):
52 """
60 """
53 Post create repository HOOK
61 Post create repository HOOK
54 kwargs available:
62 kwargs available:
55 :param repo_name:
63 :param repo_name:
56 :param repo_type:
64 :param repo_type:
57 :param description:
65 :param description:
58 :param private:
66 :param private:
59 :param created_on:
67 :param created_on:
60 :param enable_downloads:
68 :param enable_downloads:
61 :param repo_id:
69 :param repo_id:
62 :param user_id:
70 :param user_id:
63 :param enable_statistics:
71 :param enable_statistics:
64 :param clone_uri:
72 :param clone_uri:
65 :param fork_id:
73 :param fork_id:
66 :param group_id:
74 :param group_id:
67 :param deleted_by:
75 :param deleted_by:
68 :param deleted_on:
76 :param deleted_on:
69 """
77 """
70 return 0
78 return 0
71 DELETE_REPO_HOOK = _dlhook
79 DELETE_REPO_HOOK = _dlhook
72
80
73
81
74 #==============================================================================
82 #==============================================================================
75 # POST PUSH HOOK
83 # POST PUSH HOOK
76 #==============================================================================
84 #==============================================================================
77
85
78 # this function will be executed after each push it's executed after the
86 # this function will be executed after each push it's executed after the
79 # build-in hook that RhodeCode uses for logging pushes
87 # build-in hook that RhodeCode uses for logging pushes
80 def _pushhook(*args, **kwargs):
88 def _pushhook(*args, **kwargs):
81 """
89 """
82 Post push hook
90 Post push hook
83 kwargs available:
91 kwargs available:
84
92
85 :param server_url: url of instance that triggered this hook
93 :param server_url: url of instance that triggered this hook
86 :param config: path to .ini config used
94 :param config: path to .ini config used
87 :param scm: type of VS 'git' or 'hg'
95 :param scm: type of VS 'git' or 'hg'
88 :param username: name of user who pushed
96 :param username: name of user who pushed
89 :param ip: ip of who pushed
97 :param ip: ip of who pushed
90 :param action: push
98 :param action: push
91 :param repository: repository name
99 :param repository: repository name
92 :param pushed_revs: list of pushed revisions
100 :param pushed_revs: list of pushed revisions
93 """
101 """
94 return 0
102 return 0
95 PUSH_HOOK = _pushhook
103 PUSH_HOOK = _pushhook
96
104
97
105
98 #==============================================================================
106 #==============================================================================
99 # POST PULL HOOK
107 # POST PULL HOOK
100 #==============================================================================
108 #==============================================================================
101
109
102 # this function will be executed after each push it's executed after the
110 # this function will be executed after each push it's executed after the
103 # build-in hook that RhodeCode uses for logging pulls
111 # build-in hook that RhodeCode uses for logging pulls
104 def _pullhook(*args, **kwargs):
112 def _pullhook(*args, **kwargs):
105 """
113 """
106 Post pull hook
114 Post pull hook
107 kwargs available::
115 kwargs available::
108
116
109 :param server_url: url of instance that triggered this hook
117 :param server_url: url of instance that triggered this hook
110 :param config: path to .ini config used
118 :param config: path to .ini config used
111 :param scm: type of VS 'git' or 'hg'
119 :param scm: type of VS 'git' or 'hg'
112 :param username: name of user who pulled
120 :param username: name of user who pulled
113 :param ip: ip of who pulled
121 :param ip: ip of who pulled
114 :param action: pull
122 :param action: pull
115 :param repository: repository name
123 :param repository: repository name
116 """
124 """
117 return 0
125 return 0
118 PULL_HOOK = _pullhook
126 PULL_HOOK = _pullhook
@@ -1,191 +1,191 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.annotate
3 rhodecode.lib.annotate
4 ~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Anontation library for usage in rhodecode, previously part of vcs
6 Anontation library for usage in rhodecode, previously part of vcs
7
7
8 :created_on: Dec 4, 2011
8 :created_on: Dec 4, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 import StringIO
13
14
14 from rhodecode.lib.vcs.exceptions import VCSError
15 from rhodecode.lib.vcs.exceptions import VCSError
15 from rhodecode.lib.vcs.nodes import FileNode
16 from rhodecode.lib.vcs.nodes import FileNode
16 from pygments.formatters import HtmlFormatter
17 from pygments.formatters import HtmlFormatter
17 from pygments import highlight
18 from pygments import highlight
18
19
19 import StringIO
20
21
20
22 def annotate_highlight(filenode, annotate_from_changeset_func=None,
21 def annotate_highlight(filenode, annotate_from_changeset_func=None,
23 order=None, headers=None, **options):
22 order=None, headers=None, **options):
24 """
23 """
25 Returns html portion containing annotated table with 3 columns: line
24 Returns html portion containing annotated table with 3 columns: line
26 numbers, changeset information and pygmentized line of code.
25 numbers, changeset information and pygmentized line of code.
27
26
28 :param filenode: FileNode object
27 :param filenode: FileNode object
29 :param annotate_from_changeset_func: function taking changeset and
28 :param annotate_from_changeset_func: function taking changeset and
30 returning single annotate cell; needs break line at the end
29 returning single annotate cell; needs break line at the end
31 :param order: ordered sequence of ``ls`` (line numbers column),
30 :param order: ordered sequence of ``ls`` (line numbers column),
32 ``annotate`` (annotate column), ``code`` (code column); Default is
31 ``annotate`` (annotate column), ``code`` (code column); Default is
33 ``['ls', 'annotate', 'code']``
32 ``['ls', 'annotate', 'code']``
34 :param headers: dictionary with headers (keys are whats in ``order``
33 :param headers: dictionary with headers (keys are whats in ``order``
35 parameter)
34 parameter)
36 """
35 """
36 from rhodecode.lib.utils import get_custom_lexer
37 options['linenos'] = True
37 options['linenos'] = True
38 formatter = AnnotateHtmlFormatter(filenode=filenode, order=order,
38 formatter = AnnotateHtmlFormatter(filenode=filenode, order=order,
39 headers=headers,
39 headers=headers,
40 annotate_from_changeset_func=annotate_from_changeset_func, **options)
40 annotate_from_changeset_func=annotate_from_changeset_func, **options)
41 lexer = filenode.lexer
41 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
42 highlighted = highlight(filenode.content, lexer, formatter)
42 highlighted = highlight(filenode.content, lexer, formatter)
43 return highlighted
43 return highlighted
44
44
45
45
46 class AnnotateHtmlFormatter(HtmlFormatter):
46 class AnnotateHtmlFormatter(HtmlFormatter):
47
47
48 def __init__(self, filenode, annotate_from_changeset_func=None,
48 def __init__(self, filenode, annotate_from_changeset_func=None,
49 order=None, **options):
49 order=None, **options):
50 """
50 """
51 If ``annotate_from_changeset_func`` is passed it should be a function
51 If ``annotate_from_changeset_func`` is passed it should be a function
52 which returns string from the given changeset. For example, we may pass
52 which returns string from the given changeset. For example, we may pass
53 following function as ``annotate_from_changeset_func``::
53 following function as ``annotate_from_changeset_func``::
54
54
55 def changeset_to_anchor(changeset):
55 def changeset_to_anchor(changeset):
56 return '<a href="/changesets/%s/">%s</a>\n' %\
56 return '<a href="/changesets/%s/">%s</a>\n' %\
57 (changeset.id, changeset.id)
57 (changeset.id, changeset.id)
58
58
59 :param annotate_from_changeset_func: see above
59 :param annotate_from_changeset_func: see above
60 :param order: (default: ``['ls', 'annotate', 'code']``); order of
60 :param order: (default: ``['ls', 'annotate', 'code']``); order of
61 columns;
61 columns;
62 :param options: standard pygment's HtmlFormatter options, there is
62 :param options: standard pygment's HtmlFormatter options, there is
63 extra option tough, ``headers``. For instance we can pass::
63 extra option tough, ``headers``. For instance we can pass::
64
64
65 formatter = AnnotateHtmlFormatter(filenode, headers={
65 formatter = AnnotateHtmlFormatter(filenode, headers={
66 'ls': '#',
66 'ls': '#',
67 'annotate': 'Annotate',
67 'annotate': 'Annotate',
68 'code': 'Code',
68 'code': 'Code',
69 })
69 })
70
70
71 """
71 """
72 super(AnnotateHtmlFormatter, self).__init__(**options)
72 super(AnnotateHtmlFormatter, self).__init__(**options)
73 self.annotate_from_changeset_func = annotate_from_changeset_func
73 self.annotate_from_changeset_func = annotate_from_changeset_func
74 self.order = order or ('ls', 'annotate', 'code')
74 self.order = order or ('ls', 'annotate', 'code')
75 headers = options.pop('headers', None)
75 headers = options.pop('headers', None)
76 if headers and not ('ls' in headers and 'annotate' in headers and
76 if headers and not ('ls' in headers and 'annotate' in headers and
77 'code' in headers):
77 'code' in headers):
78 raise ValueError("If headers option dict is specified it must "
78 raise ValueError("If headers option dict is specified it must "
79 "all 'ls', 'annotate' and 'code' keys")
79 "all 'ls', 'annotate' and 'code' keys")
80 self.headers = headers
80 self.headers = headers
81 if isinstance(filenode, FileNode):
81 if isinstance(filenode, FileNode):
82 self.filenode = filenode
82 self.filenode = filenode
83 else:
83 else:
84 raise VCSError("This formatter expect FileNode parameter, not %r"
84 raise VCSError("This formatter expect FileNode parameter, not %r"
85 % type(filenode))
85 % type(filenode))
86
86
87 def annotate_from_changeset(self, changeset):
87 def annotate_from_changeset(self, changeset):
88 """
88 """
89 Returns full html line for single changeset per annotated line.
89 Returns full html line for single changeset per annotated line.
90 """
90 """
91 if self.annotate_from_changeset_func:
91 if self.annotate_from_changeset_func:
92 return self.annotate_from_changeset_func(changeset)
92 return self.annotate_from_changeset_func(changeset)
93 else:
93 else:
94 return ''.join((changeset.id, '\n'))
94 return ''.join((changeset.id, '\n'))
95
95
96 def _wrap_tablelinenos(self, inner):
96 def _wrap_tablelinenos(self, inner):
97 dummyoutfile = StringIO.StringIO()
97 dummyoutfile = StringIO.StringIO()
98 lncount = 0
98 lncount = 0
99 for t, line in inner:
99 for t, line in inner:
100 if t:
100 if t:
101 lncount += 1
101 lncount += 1
102 dummyoutfile.write(line)
102 dummyoutfile.write(line)
103
103
104 fl = self.linenostart
104 fl = self.linenostart
105 mw = len(str(lncount + fl - 1))
105 mw = len(str(lncount + fl - 1))
106 sp = self.linenospecial
106 sp = self.linenospecial
107 st = self.linenostep
107 st = self.linenostep
108 la = self.lineanchors
108 la = self.lineanchors
109 aln = self.anchorlinenos
109 aln = self.anchorlinenos
110 if sp:
110 if sp:
111 lines = []
111 lines = []
112
112
113 for i in range(fl, fl + lncount):
113 for i in range(fl, fl + lncount):
114 if i % st == 0:
114 if i % st == 0:
115 if i % sp == 0:
115 if i % sp == 0:
116 if aln:
116 if aln:
117 lines.append('<a href="#%s-%d" class="special">'
117 lines.append('<a href="#%s-%d" class="special">'
118 '%*d</a>' %
118 '%*d</a>' %
119 (la, i, mw, i))
119 (la, i, mw, i))
120 else:
120 else:
121 lines.append('<span class="special">'
121 lines.append('<span class="special">'
122 '%*d</span>' % (mw, i))
122 '%*d</span>' % (mw, i))
123 else:
123 else:
124 if aln:
124 if aln:
125 lines.append('<a href="#%s-%d">'
125 lines.append('<a href="#%s-%d">'
126 '%*d</a>' % (la, i, mw, i))
126 '%*d</a>' % (la, i, mw, i))
127 else:
127 else:
128 lines.append('%*d' % (mw, i))
128 lines.append('%*d' % (mw, i))
129 else:
129 else:
130 lines.append('')
130 lines.append('')
131 ls = '\n'.join(lines)
131 ls = '\n'.join(lines)
132 else:
132 else:
133 lines = []
133 lines = []
134 for i in range(fl, fl + lncount):
134 for i in range(fl, fl + lncount):
135 if i % st == 0:
135 if i % st == 0:
136 if aln:
136 if aln:
137 lines.append('<a href="#%s-%d">%*d</a>' \
137 lines.append('<a href="#%s-%d">%*d</a>' \
138 % (la, i, mw, i))
138 % (la, i, mw, i))
139 else:
139 else:
140 lines.append('%*d' % (mw, i))
140 lines.append('%*d' % (mw, i))
141 else:
141 else:
142 lines.append('')
142 lines.append('')
143 ls = '\n'.join(lines)
143 ls = '\n'.join(lines)
144
144
145 # annotate_changesets = [tup[1] for tup in self.filenode.annotate]
145 # annotate_changesets = [tup[1] for tup in self.filenode.annotate]
146 ## TODO: not sure what that fixes
146 ## TODO: not sure what that fixes
147 # # If pygments cropped last lines break we need do that too
147 # # If pygments cropped last lines break we need do that too
148 # ln_cs = len(annotate_changesets)
148 # ln_cs = len(annotate_changesets)
149 # ln_ = len(ls.splitlines())
149 # ln_ = len(ls.splitlines())
150 # if ln_cs > ln_:
150 # if ln_cs > ln_:
151 # annotate_changesets = annotate_changesets[:ln_ - ln_cs]
151 # annotate_changesets = annotate_changesets[:ln_ - ln_cs]
152 annotate = ''.join((self.annotate_from_changeset(el[2]())
152 annotate = ''.join((self.annotate_from_changeset(el[2]())
153 for el in self.filenode.annotate))
153 for el in self.filenode.annotate))
154 # in case you wonder about the seemingly redundant <div> here:
154 # in case you wonder about the seemingly redundant <div> here:
155 # since the content in the other cell also is wrapped in a div,
155 # since the content in the other cell also is wrapped in a div,
156 # some browsers in some configurations seem to mess up the formatting.
156 # some browsers in some configurations seem to mess up the formatting.
157 '''
157 '''
158 yield 0, ('<table class="%stable">' % self.cssclass +
158 yield 0, ('<table class="%stable">' % self.cssclass +
159 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
159 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
160 ls + '</pre></div></td>' +
160 ls + '</pre></div></td>' +
161 '<td class="code">')
161 '<td class="code">')
162 yield 0, dummyoutfile.getvalue()
162 yield 0, dummyoutfile.getvalue()
163 yield 0, '</td></tr></table>'
163 yield 0, '</td></tr></table>'
164
164
165 '''
165 '''
166 headers_row = []
166 headers_row = []
167 if self.headers:
167 if self.headers:
168 headers_row = ['<tr class="annotate-header">']
168 headers_row = ['<tr class="annotate-header">']
169 for key in self.order:
169 for key in self.order:
170 td = ''.join(('<td>', self.headers[key], '</td>'))
170 td = ''.join(('<td>', self.headers[key], '</td>'))
171 headers_row.append(td)
171 headers_row.append(td)
172 headers_row.append('</tr>')
172 headers_row.append('</tr>')
173
173
174 body_row_start = ['<tr>']
174 body_row_start = ['<tr>']
175 for key in self.order:
175 for key in self.order:
176 if key == 'ls':
176 if key == 'ls':
177 body_row_start.append(
177 body_row_start.append(
178 '<td class="linenos"><div class="linenodiv"><pre>' +
178 '<td class="linenos"><div class="linenodiv"><pre>' +
179 ls + '</pre></div></td>')
179 ls + '</pre></div></td>')
180 elif key == 'annotate':
180 elif key == 'annotate':
181 body_row_start.append(
181 body_row_start.append(
182 '<td class="annotate"><div class="annotatediv"><pre>' +
182 '<td class="annotate"><div class="annotatediv"><pre>' +
183 annotate + '</pre></div></td>')
183 annotate + '</pre></div></td>')
184 elif key == 'code':
184 elif key == 'code':
185 body_row_start.append('<td class="code">')
185 body_row_start.append('<td class="code">')
186 yield 0, ('<table class="%stable">' % self.cssclass +
186 yield 0, ('<table class="%stable">' % self.cssclass +
187 ''.join(headers_row) +
187 ''.join(headers_row) +
188 ''.join(body_row_start)
188 ''.join(body_row_start)
189 )
189 )
190 yield 0, dummyoutfile.getvalue()
190 yield 0, dummyoutfile.getvalue()
191 yield 0, '</td></tr></table>'
191 yield 0, '</td></tr></table>'
@@ -1,1173 +1,1174 b''
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10 import math
10 import math
11 import logging
11 import logging
12 import re
12 import re
13 import urlparse
13 import urlparse
14 import textwrap
14 import textwrap
15
15
16 from datetime import datetime
16 from datetime import datetime
17 from pygments.formatters.html import HtmlFormatter
17 from pygments.formatters.html import HtmlFormatter
18 from pygments import highlight as code_highlight
18 from pygments import highlight as code_highlight
19 from pylons import url, request, config
19 from pylons import url, request, config
20 from pylons.i18n.translation import _, ungettext
20 from pylons.i18n.translation import _, ungettext
21 from hashlib import md5
21 from hashlib import md5
22
22
23 from webhelpers.html import literal, HTML, escape
23 from webhelpers.html import literal, HTML, escape
24 from webhelpers.html.tools import *
24 from webhelpers.html.tools import *
25 from webhelpers.html.builder import make_tag
25 from webhelpers.html.builder import make_tag
26 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
26 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
27 end_form, file, form, hidden, image, javascript_link, link_to, \
27 end_form, file, form, hidden, image, javascript_link, link_to, \
28 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
28 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
29 submit, text, password, textarea, title, ul, xml_declaration, radio
29 submit, text, password, textarea, title, ul, xml_declaration, radio
30 from webhelpers.html.tools import auto_link, button_to, highlight, \
30 from webhelpers.html.tools import auto_link, button_to, highlight, \
31 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
31 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
32 from webhelpers.number import format_byte_size, format_bit_size
32 from webhelpers.number import format_byte_size, format_bit_size
33 from webhelpers.pylonslib import Flash as _Flash
33 from webhelpers.pylonslib import Flash as _Flash
34 from webhelpers.pylonslib.secure_form import secure_form
34 from webhelpers.pylonslib.secure_form import secure_form
35 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
35 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
36 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
36 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
37 replace_whitespace, urlify, truncate, wrap_paragraphs
37 replace_whitespace, urlify, truncate, wrap_paragraphs
38 from webhelpers.date import time_ago_in_words
38 from webhelpers.date import time_ago_in_words
39 from webhelpers.paginate import Page
39 from webhelpers.paginate import Page
40 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
40 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
41 convert_boolean_attrs, NotGiven, _make_safe_id_component
41 convert_boolean_attrs, NotGiven, _make_safe_id_component
42
42
43 from rhodecode.lib.annotate import annotate_highlight
43 from rhodecode.lib.annotate import annotate_highlight
44 from rhodecode.lib.utils import repo_name_slug
44 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
45 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
45 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
46 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict
46 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict
47 from rhodecode.lib.markup_renderer import MarkupRenderer
47 from rhodecode.lib.markup_renderer import MarkupRenderer
48 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
48 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
49 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
49 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
50 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
50 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
51 from rhodecode.model.changeset_status import ChangesetStatusModel
51 from rhodecode.model.changeset_status import ChangesetStatusModel
52 from rhodecode.model.db import URL_SEP, Permission
52 from rhodecode.model.db import URL_SEP, Permission
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 html_escape_table = {
57 html_escape_table = {
58 "&": "&amp;",
58 "&": "&amp;",
59 '"': "&quot;",
59 '"': "&quot;",
60 "'": "&apos;",
60 "'": "&apos;",
61 ">": "&gt;",
61 ">": "&gt;",
62 "<": "&lt;",
62 "<": "&lt;",
63 }
63 }
64
64
65
65
66 def html_escape(text):
66 def html_escape(text):
67 """Produce entities within text."""
67 """Produce entities within text."""
68 return "".join(html_escape_table.get(c, c) for c in text)
68 return "".join(html_escape_table.get(c, c) for c in text)
69
69
70
70
71 def shorter(text, size=20):
71 def shorter(text, size=20):
72 postfix = '...'
72 postfix = '...'
73 if len(text) > size:
73 if len(text) > size:
74 return text[:size - len(postfix)] + postfix
74 return text[:size - len(postfix)] + postfix
75 return text
75 return text
76
76
77
77
78 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
78 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
79 """
79 """
80 Reset button
80 Reset button
81 """
81 """
82 _set_input_attrs(attrs, type, name, value)
82 _set_input_attrs(attrs, type, name, value)
83 _set_id_attr(attrs, id, name)
83 _set_id_attr(attrs, id, name)
84 convert_boolean_attrs(attrs, ["disabled"])
84 convert_boolean_attrs(attrs, ["disabled"])
85 return HTML.input(**attrs)
85 return HTML.input(**attrs)
86
86
87 reset = _reset
87 reset = _reset
88 safeid = _make_safe_id_component
88 safeid = _make_safe_id_component
89
89
90
90
91 def FID(raw_id, path):
91 def FID(raw_id, path):
92 """
92 """
93 Creates a uniqe ID for filenode based on it's hash of path and revision
93 Creates a uniqe ID for filenode based on it's hash of path and revision
94 it's safe to use in urls
94 it's safe to use in urls
95
95
96 :param raw_id:
96 :param raw_id:
97 :param path:
97 :param path:
98 """
98 """
99
99
100 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
100 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
101
101
102
102
103 def get_token():
103 def get_token():
104 """Return the current authentication token, creating one if one doesn't
104 """Return the current authentication token, creating one if one doesn't
105 already exist.
105 already exist.
106 """
106 """
107 token_key = "_authentication_token"
107 token_key = "_authentication_token"
108 from pylons import session
108 from pylons import session
109 if not token_key in session:
109 if not token_key in session:
110 try:
110 try:
111 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
111 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
112 except AttributeError: # Python < 2.4
112 except AttributeError: # Python < 2.4
113 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
113 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
114 session[token_key] = token
114 session[token_key] = token
115 if hasattr(session, 'save'):
115 if hasattr(session, 'save'):
116 session.save()
116 session.save()
117 return session[token_key]
117 return session[token_key]
118
118
119
119
120 class _GetError(object):
120 class _GetError(object):
121 """Get error from form_errors, and represent it as span wrapped error
121 """Get error from form_errors, and represent it as span wrapped error
122 message
122 message
123
123
124 :param field_name: field to fetch errors for
124 :param field_name: field to fetch errors for
125 :param form_errors: form errors dict
125 :param form_errors: form errors dict
126 """
126 """
127
127
128 def __call__(self, field_name, form_errors):
128 def __call__(self, field_name, form_errors):
129 tmpl = """<span class="error_msg">%s</span>"""
129 tmpl = """<span class="error_msg">%s</span>"""
130 if form_errors and field_name in form_errors:
130 if form_errors and field_name in form_errors:
131 return literal(tmpl % form_errors.get(field_name))
131 return literal(tmpl % form_errors.get(field_name))
132
132
133 get_error = _GetError()
133 get_error = _GetError()
134
134
135
135
136 class _ToolTip(object):
136 class _ToolTip(object):
137
137
138 def __call__(self, tooltip_title, trim_at=50):
138 def __call__(self, tooltip_title, trim_at=50):
139 """
139 """
140 Special function just to wrap our text into nice formatted
140 Special function just to wrap our text into nice formatted
141 autowrapped text
141 autowrapped text
142
142
143 :param tooltip_title:
143 :param tooltip_title:
144 """
144 """
145 tooltip_title = escape(tooltip_title)
145 tooltip_title = escape(tooltip_title)
146 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
146 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
147 return tooltip_title
147 return tooltip_title
148 tooltip = _ToolTip()
148 tooltip = _ToolTip()
149
149
150
150
151 class _FilesBreadCrumbs(object):
151 class _FilesBreadCrumbs(object):
152
152
153 def __call__(self, repo_name, rev, paths):
153 def __call__(self, repo_name, rev, paths):
154 if isinstance(paths, str):
154 if isinstance(paths, str):
155 paths = safe_unicode(paths)
155 paths = safe_unicode(paths)
156 url_l = [link_to(repo_name, url('files_home',
156 url_l = [link_to(repo_name, url('files_home',
157 repo_name=repo_name,
157 repo_name=repo_name,
158 revision=rev, f_path=''),
158 revision=rev, f_path=''),
159 class_='ypjax-link')]
159 class_='ypjax-link')]
160 paths_l = paths.split('/')
160 paths_l = paths.split('/')
161 for cnt, p in enumerate(paths_l):
161 for cnt, p in enumerate(paths_l):
162 if p != '':
162 if p != '':
163 url_l.append(link_to(p,
163 url_l.append(link_to(p,
164 url('files_home',
164 url('files_home',
165 repo_name=repo_name,
165 repo_name=repo_name,
166 revision=rev,
166 revision=rev,
167 f_path='/'.join(paths_l[:cnt + 1])
167 f_path='/'.join(paths_l[:cnt + 1])
168 ),
168 ),
169 class_='ypjax-link'
169 class_='ypjax-link'
170 )
170 )
171 )
171 )
172
172
173 return literal('/'.join(url_l))
173 return literal('/'.join(url_l))
174
174
175 files_breadcrumbs = _FilesBreadCrumbs()
175 files_breadcrumbs = _FilesBreadCrumbs()
176
176
177
177
178 class CodeHtmlFormatter(HtmlFormatter):
178 class CodeHtmlFormatter(HtmlFormatter):
179 """
179 """
180 My code Html Formatter for source codes
180 My code Html Formatter for source codes
181 """
181 """
182
182
183 def wrap(self, source, outfile):
183 def wrap(self, source, outfile):
184 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
184 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
185
185
186 def _wrap_code(self, source):
186 def _wrap_code(self, source):
187 for cnt, it in enumerate(source):
187 for cnt, it in enumerate(source):
188 i, t = it
188 i, t = it
189 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
189 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
190 yield i, t
190 yield i, t
191
191
192 def _wrap_tablelinenos(self, inner):
192 def _wrap_tablelinenos(self, inner):
193 dummyoutfile = StringIO.StringIO()
193 dummyoutfile = StringIO.StringIO()
194 lncount = 0
194 lncount = 0
195 for t, line in inner:
195 for t, line in inner:
196 if t:
196 if t:
197 lncount += 1
197 lncount += 1
198 dummyoutfile.write(line)
198 dummyoutfile.write(line)
199
199
200 fl = self.linenostart
200 fl = self.linenostart
201 mw = len(str(lncount + fl - 1))
201 mw = len(str(lncount + fl - 1))
202 sp = self.linenospecial
202 sp = self.linenospecial
203 st = self.linenostep
203 st = self.linenostep
204 la = self.lineanchors
204 la = self.lineanchors
205 aln = self.anchorlinenos
205 aln = self.anchorlinenos
206 nocls = self.noclasses
206 nocls = self.noclasses
207 if sp:
207 if sp:
208 lines = []
208 lines = []
209
209
210 for i in range(fl, fl + lncount):
210 for i in range(fl, fl + lncount):
211 if i % st == 0:
211 if i % st == 0:
212 if i % sp == 0:
212 if i % sp == 0:
213 if aln:
213 if aln:
214 lines.append('<a href="#%s%d" class="special">%*d</a>' %
214 lines.append('<a href="#%s%d" class="special">%*d</a>' %
215 (la, i, mw, i))
215 (la, i, mw, i))
216 else:
216 else:
217 lines.append('<span class="special">%*d</span>' % (mw, i))
217 lines.append('<span class="special">%*d</span>' % (mw, i))
218 else:
218 else:
219 if aln:
219 if aln:
220 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
220 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
221 else:
221 else:
222 lines.append('%*d' % (mw, i))
222 lines.append('%*d' % (mw, i))
223 else:
223 else:
224 lines.append('')
224 lines.append('')
225 ls = '\n'.join(lines)
225 ls = '\n'.join(lines)
226 else:
226 else:
227 lines = []
227 lines = []
228 for i in range(fl, fl + lncount):
228 for i in range(fl, fl + lncount):
229 if i % st == 0:
229 if i % st == 0:
230 if aln:
230 if aln:
231 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
231 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
232 else:
232 else:
233 lines.append('%*d' % (mw, i))
233 lines.append('%*d' % (mw, i))
234 else:
234 else:
235 lines.append('')
235 lines.append('')
236 ls = '\n'.join(lines)
236 ls = '\n'.join(lines)
237
237
238 # in case you wonder about the seemingly redundant <div> here: since the
238 # in case you wonder about the seemingly redundant <div> here: since the
239 # content in the other cell also is wrapped in a div, some browsers in
239 # content in the other cell also is wrapped in a div, some browsers in
240 # some configurations seem to mess up the formatting...
240 # some configurations seem to mess up the formatting...
241 if nocls:
241 if nocls:
242 yield 0, ('<table class="%stable">' % self.cssclass +
242 yield 0, ('<table class="%stable">' % self.cssclass +
243 '<tr><td><div class="linenodiv" '
243 '<tr><td><div class="linenodiv" '
244 'style="background-color: #f0f0f0; padding-right: 10px">'
244 'style="background-color: #f0f0f0; padding-right: 10px">'
245 '<pre style="line-height: 125%">' +
245 '<pre style="line-height: 125%">' +
246 ls + '</pre></div></td><td id="hlcode" class="code">')
246 ls + '</pre></div></td><td id="hlcode" class="code">')
247 else:
247 else:
248 yield 0, ('<table class="%stable">' % self.cssclass +
248 yield 0, ('<table class="%stable">' % self.cssclass +
249 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
249 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
250 ls + '</pre></div></td><td id="hlcode" class="code">')
250 ls + '</pre></div></td><td id="hlcode" class="code">')
251 yield 0, dummyoutfile.getvalue()
251 yield 0, dummyoutfile.getvalue()
252 yield 0, '</td></tr></table>'
252 yield 0, '</td></tr></table>'
253
253
254
254
255 def pygmentize(filenode, **kwargs):
255 def pygmentize(filenode, **kwargs):
256 """pygmentize function using pygments
256 """
257 pygmentize function using pygments
257
258
258 :param filenode:
259 :param filenode:
259 """
260 """
260
261 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
261 return literal(code_highlight(filenode.content,
262 return literal(code_highlight(filenode.content, lexer,
262 filenode.lexer, CodeHtmlFormatter(**kwargs)))
263 CodeHtmlFormatter(**kwargs)))
263
264
264
265
265 def pygmentize_annotation(repo_name, filenode, **kwargs):
266 def pygmentize_annotation(repo_name, filenode, **kwargs):
266 """
267 """
267 pygmentize function for annotation
268 pygmentize function for annotation
268
269
269 :param filenode:
270 :param filenode:
270 """
271 """
271
272
272 color_dict = {}
273 color_dict = {}
273
274
274 def gen_color(n=10000):
275 def gen_color(n=10000):
275 """generator for getting n of evenly distributed colors using
276 """generator for getting n of evenly distributed colors using
276 hsv color and golden ratio. It always return same order of colors
277 hsv color and golden ratio. It always return same order of colors
277
278
278 :returns: RGB tuple
279 :returns: RGB tuple
279 """
280 """
280
281
281 def hsv_to_rgb(h, s, v):
282 def hsv_to_rgb(h, s, v):
282 if s == 0.0:
283 if s == 0.0:
283 return v, v, v
284 return v, v, v
284 i = int(h * 6.0) # XXX assume int() truncates!
285 i = int(h * 6.0) # XXX assume int() truncates!
285 f = (h * 6.0) - i
286 f = (h * 6.0) - i
286 p = v * (1.0 - s)
287 p = v * (1.0 - s)
287 q = v * (1.0 - s * f)
288 q = v * (1.0 - s * f)
288 t = v * (1.0 - s * (1.0 - f))
289 t = v * (1.0 - s * (1.0 - f))
289 i = i % 6
290 i = i % 6
290 if i == 0:
291 if i == 0:
291 return v, t, p
292 return v, t, p
292 if i == 1:
293 if i == 1:
293 return q, v, p
294 return q, v, p
294 if i == 2:
295 if i == 2:
295 return p, v, t
296 return p, v, t
296 if i == 3:
297 if i == 3:
297 return p, q, v
298 return p, q, v
298 if i == 4:
299 if i == 4:
299 return t, p, v
300 return t, p, v
300 if i == 5:
301 if i == 5:
301 return v, p, q
302 return v, p, q
302
303
303 golden_ratio = 0.618033988749895
304 golden_ratio = 0.618033988749895
304 h = 0.22717784590367374
305 h = 0.22717784590367374
305
306
306 for _ in xrange(n):
307 for _ in xrange(n):
307 h += golden_ratio
308 h += golden_ratio
308 h %= 1
309 h %= 1
309 HSV_tuple = [h, 0.95, 0.95]
310 HSV_tuple = [h, 0.95, 0.95]
310 RGB_tuple = hsv_to_rgb(*HSV_tuple)
311 RGB_tuple = hsv_to_rgb(*HSV_tuple)
311 yield map(lambda x: str(int(x * 256)), RGB_tuple)
312 yield map(lambda x: str(int(x * 256)), RGB_tuple)
312
313
313 cgenerator = gen_color()
314 cgenerator = gen_color()
314
315
315 def get_color_string(cs):
316 def get_color_string(cs):
316 if cs in color_dict:
317 if cs in color_dict:
317 col = color_dict[cs]
318 col = color_dict[cs]
318 else:
319 else:
319 col = color_dict[cs] = cgenerator.next()
320 col = color_dict[cs] = cgenerator.next()
320 return "color: rgb(%s)! important;" % (', '.join(col))
321 return "color: rgb(%s)! important;" % (', '.join(col))
321
322
322 def url_func(repo_name):
323 def url_func(repo_name):
323
324
324 def _url_func(changeset):
325 def _url_func(changeset):
325 author = changeset.author
326 author = changeset.author
326 date = changeset.date
327 date = changeset.date
327 message = tooltip(changeset.message)
328 message = tooltip(changeset.message)
328
329
329 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
330 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
330 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
331 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
331 "</b> %s<br/></div>")
332 "</b> %s<br/></div>")
332
333
333 tooltip_html = tooltip_html % (author, date, message)
334 tooltip_html = tooltip_html % (author, date, message)
334 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
335 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
335 short_id(changeset.raw_id))
336 short_id(changeset.raw_id))
336 uri = link_to(
337 uri = link_to(
337 lnk_format,
338 lnk_format,
338 url('changeset_home', repo_name=repo_name,
339 url('changeset_home', repo_name=repo_name,
339 revision=changeset.raw_id),
340 revision=changeset.raw_id),
340 style=get_color_string(changeset.raw_id),
341 style=get_color_string(changeset.raw_id),
341 class_='tooltip',
342 class_='tooltip',
342 title=tooltip_html
343 title=tooltip_html
343 )
344 )
344
345
345 uri += '\n'
346 uri += '\n'
346 return uri
347 return uri
347 return _url_func
348 return _url_func
348
349
349 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
350 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
350
351
351
352
352 def is_following_repo(repo_name, user_id):
353 def is_following_repo(repo_name, user_id):
353 from rhodecode.model.scm import ScmModel
354 from rhodecode.model.scm import ScmModel
354 return ScmModel().is_following_repo(repo_name, user_id)
355 return ScmModel().is_following_repo(repo_name, user_id)
355
356
356 flash = _Flash()
357 flash = _Flash()
357
358
358 #==============================================================================
359 #==============================================================================
359 # SCM FILTERS available via h.
360 # SCM FILTERS available via h.
360 #==============================================================================
361 #==============================================================================
361 from rhodecode.lib.vcs.utils import author_name, author_email
362 from rhodecode.lib.vcs.utils import author_name, author_email
362 from rhodecode.lib.utils2 import credentials_filter, age as _age
363 from rhodecode.lib.utils2 import credentials_filter, age as _age
363 from rhodecode.model.db import User, ChangesetStatus
364 from rhodecode.model.db import User, ChangesetStatus
364
365
365 age = lambda x: _age(x)
366 age = lambda x: _age(x)
366 capitalize = lambda x: x.capitalize()
367 capitalize = lambda x: x.capitalize()
367 email = author_email
368 email = author_email
368 short_id = lambda x: x[:12]
369 short_id = lambda x: x[:12]
369 hide_credentials = lambda x: ''.join(credentials_filter(x))
370 hide_credentials = lambda x: ''.join(credentials_filter(x))
370
371
371
372
372 def fmt_date(date):
373 def fmt_date(date):
373 if date:
374 if date:
374 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
375 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
375 return date.strftime(_fmt).decode('utf8')
376 return date.strftime(_fmt).decode('utf8')
376
377
377 return ""
378 return ""
378
379
379
380
380 def is_git(repository):
381 def is_git(repository):
381 if hasattr(repository, 'alias'):
382 if hasattr(repository, 'alias'):
382 _type = repository.alias
383 _type = repository.alias
383 elif hasattr(repository, 'repo_type'):
384 elif hasattr(repository, 'repo_type'):
384 _type = repository.repo_type
385 _type = repository.repo_type
385 else:
386 else:
386 _type = repository
387 _type = repository
387 return _type == 'git'
388 return _type == 'git'
388
389
389
390
390 def is_hg(repository):
391 def is_hg(repository):
391 if hasattr(repository, 'alias'):
392 if hasattr(repository, 'alias'):
392 _type = repository.alias
393 _type = repository.alias
393 elif hasattr(repository, 'repo_type'):
394 elif hasattr(repository, 'repo_type'):
394 _type = repository.repo_type
395 _type = repository.repo_type
395 else:
396 else:
396 _type = repository
397 _type = repository
397 return _type == 'hg'
398 return _type == 'hg'
398
399
399
400
400 def email_or_none(author):
401 def email_or_none(author):
401 # extract email from the commit string
402 # extract email from the commit string
402 _email = email(author)
403 _email = email(author)
403 if _email != '':
404 if _email != '':
404 # check it against RhodeCode database, and use the MAIN email for this
405 # check it against RhodeCode database, and use the MAIN email for this
405 # user
406 # user
406 user = User.get_by_email(_email, case_insensitive=True, cache=True)
407 user = User.get_by_email(_email, case_insensitive=True, cache=True)
407 if user is not None:
408 if user is not None:
408 return user.email
409 return user.email
409 return _email
410 return _email
410
411
411 # See if it contains a username we can get an email from
412 # See if it contains a username we can get an email from
412 user = User.get_by_username(author_name(author), case_insensitive=True,
413 user = User.get_by_username(author_name(author), case_insensitive=True,
413 cache=True)
414 cache=True)
414 if user is not None:
415 if user is not None:
415 return user.email
416 return user.email
416
417
417 # No valid email, not a valid user in the system, none!
418 # No valid email, not a valid user in the system, none!
418 return None
419 return None
419
420
420
421
421 def person(author, show_attr="username_and_name"):
422 def person(author, show_attr="username_and_name"):
422 # attr to return from fetched user
423 # attr to return from fetched user
423 person_getter = lambda usr: getattr(usr, show_attr)
424 person_getter = lambda usr: getattr(usr, show_attr)
424
425
425 # Valid email in the attribute passed, see if they're in the system
426 # Valid email in the attribute passed, see if they're in the system
426 _email = email(author)
427 _email = email(author)
427 if _email != '':
428 if _email != '':
428 user = User.get_by_email(_email, case_insensitive=True, cache=True)
429 user = User.get_by_email(_email, case_insensitive=True, cache=True)
429 if user is not None:
430 if user is not None:
430 return person_getter(user)
431 return person_getter(user)
431 return _email
432 return _email
432
433
433 # Maybe it's a username?
434 # Maybe it's a username?
434 _author = author_name(author)
435 _author = author_name(author)
435 user = User.get_by_username(_author, case_insensitive=True,
436 user = User.get_by_username(_author, case_insensitive=True,
436 cache=True)
437 cache=True)
437 if user is not None:
438 if user is not None:
438 return person_getter(user)
439 return person_getter(user)
439
440
440 # Still nothing? Just pass back the author name then
441 # Still nothing? Just pass back the author name then
441 return _author
442 return _author
442
443
443
444
444 def person_by_id(id_, show_attr="username_and_name"):
445 def person_by_id(id_, show_attr="username_and_name"):
445 # attr to return from fetched user
446 # attr to return from fetched user
446 person_getter = lambda usr: getattr(usr, show_attr)
447 person_getter = lambda usr: getattr(usr, show_attr)
447
448
448 #maybe it's an ID ?
449 #maybe it's an ID ?
449 if str(id_).isdigit() or isinstance(id_, int):
450 if str(id_).isdigit() or isinstance(id_, int):
450 id_ = int(id_)
451 id_ = int(id_)
451 user = User.get(id_)
452 user = User.get(id_)
452 if user is not None:
453 if user is not None:
453 return person_getter(user)
454 return person_getter(user)
454 return id_
455 return id_
455
456
456
457
457 def desc_stylize(value):
458 def desc_stylize(value):
458 """
459 """
459 converts tags from value into html equivalent
460 converts tags from value into html equivalent
460
461
461 :param value:
462 :param value:
462 """
463 """
463 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
464 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
464 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
465 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
465 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
466 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
466 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
467 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
467 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
468 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
468 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
469 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
469 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
470 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
470 '<div class="metatag" tag="lang">\\2</div>', value)
471 '<div class="metatag" tag="lang">\\2</div>', value)
471 value = re.sub(r'\[([a-z]+)\]',
472 value = re.sub(r'\[([a-z]+)\]',
472 '<div class="metatag" tag="\\1">\\1</div>', value)
473 '<div class="metatag" tag="\\1">\\1</div>', value)
473
474
474 return value
475 return value
475
476
476
477
477 def bool2icon(value):
478 def bool2icon(value):
478 """Returns True/False values represented as small html image of true/false
479 """Returns True/False values represented as small html image of true/false
479 icons
480 icons
480
481
481 :param value: bool value
482 :param value: bool value
482 """
483 """
483
484
484 if value is True:
485 if value is True:
485 return HTML.tag('img', src=url("/images/icons/accept.png"),
486 return HTML.tag('img', src=url("/images/icons/accept.png"),
486 alt=_('True'))
487 alt=_('True'))
487
488
488 if value is False:
489 if value is False:
489 return HTML.tag('img', src=url("/images/icons/cancel.png"),
490 return HTML.tag('img', src=url("/images/icons/cancel.png"),
490 alt=_('False'))
491 alt=_('False'))
491
492
492 return value
493 return value
493
494
494
495
495 def action_parser(user_log, feed=False, parse_cs=False):
496 def action_parser(user_log, feed=False, parse_cs=False):
496 """
497 """
497 This helper will action_map the specified string action into translated
498 This helper will action_map the specified string action into translated
498 fancy names with icons and links
499 fancy names with icons and links
499
500
500 :param user_log: user log instance
501 :param user_log: user log instance
501 :param feed: use output for feeds (no html and fancy icons)
502 :param feed: use output for feeds (no html and fancy icons)
502 :param parse_cs: parse Changesets into VCS instances
503 :param parse_cs: parse Changesets into VCS instances
503 """
504 """
504
505
505 action = user_log.action
506 action = user_log.action
506 action_params = ' '
507 action_params = ' '
507
508
508 x = action.split(':')
509 x = action.split(':')
509
510
510 if len(x) > 1:
511 if len(x) > 1:
511 action, action_params = x
512 action, action_params = x
512
513
513 def get_cs_links():
514 def get_cs_links():
514 revs_limit = 3 # display this amount always
515 revs_limit = 3 # display this amount always
515 revs_top_limit = 50 # show upto this amount of changesets hidden
516 revs_top_limit = 50 # show upto this amount of changesets hidden
516 revs_ids = action_params.split(',')
517 revs_ids = action_params.split(',')
517 deleted = user_log.repository is None
518 deleted = user_log.repository is None
518 if deleted:
519 if deleted:
519 return ','.join(revs_ids)
520 return ','.join(revs_ids)
520
521
521 repo_name = user_log.repository.repo_name
522 repo_name = user_log.repository.repo_name
522
523
523 def lnk(rev, repo_name):
524 def lnk(rev, repo_name):
524 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
525 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
525 lazy_cs = True
526 lazy_cs = True
526 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
527 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
527 lazy_cs = False
528 lazy_cs = False
528 lbl = '?'
529 lbl = '?'
529 if rev.op == 'delete_branch':
530 if rev.op == 'delete_branch':
530 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
531 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
531 title = ''
532 title = ''
532 elif rev.op == 'tag':
533 elif rev.op == 'tag':
533 lbl = '%s' % _('Created tag: %s') % rev.ref_name
534 lbl = '%s' % _('Created tag: %s') % rev.ref_name
534 title = ''
535 title = ''
535 _url = '#'
536 _url = '#'
536
537
537 else:
538 else:
538 lbl = '%s' % (rev.short_id[:8])
539 lbl = '%s' % (rev.short_id[:8])
539 _url = url('changeset_home', repo_name=repo_name,
540 _url = url('changeset_home', repo_name=repo_name,
540 revision=rev.raw_id)
541 revision=rev.raw_id)
541 title = tooltip(rev.message)
542 title = tooltip(rev.message)
542 else:
543 else:
543 ## changeset cannot be found/striped/removed etc.
544 ## changeset cannot be found/striped/removed etc.
544 lbl = ('%s' % rev)[:12]
545 lbl = ('%s' % rev)[:12]
545 _url = '#'
546 _url = '#'
546 title = _('Changeset not found')
547 title = _('Changeset not found')
547 if parse_cs:
548 if parse_cs:
548 return link_to(lbl, _url, title=title, class_='tooltip')
549 return link_to(lbl, _url, title=title, class_='tooltip')
549 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
550 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
550 class_='lazy-cs' if lazy_cs else '')
551 class_='lazy-cs' if lazy_cs else '')
551
552
552 revs = []
553 revs = []
553 if len(filter(lambda v: v != '', revs_ids)) > 0:
554 if len(filter(lambda v: v != '', revs_ids)) > 0:
554 repo = None
555 repo = None
555 for rev in revs_ids[:revs_top_limit]:
556 for rev in revs_ids[:revs_top_limit]:
556 _op = _name = None
557 _op = _name = None
557 if len(rev.split('=>')) == 2:
558 if len(rev.split('=>')) == 2:
558 _op, _name = rev.split('=>')
559 _op, _name = rev.split('=>')
559
560
560 # we want parsed changesets, or new log store format is bad
561 # we want parsed changesets, or new log store format is bad
561 if parse_cs:
562 if parse_cs:
562 try:
563 try:
563 if repo is None:
564 if repo is None:
564 repo = user_log.repository.scm_instance
565 repo = user_log.repository.scm_instance
565 _rev = repo.get_changeset(rev)
566 _rev = repo.get_changeset(rev)
566 revs.append(_rev)
567 revs.append(_rev)
567 except ChangesetDoesNotExistError:
568 except ChangesetDoesNotExistError:
568 log.error('cannot find revision %s in this repo' % rev)
569 log.error('cannot find revision %s in this repo' % rev)
569 revs.append(rev)
570 revs.append(rev)
570 continue
571 continue
571 else:
572 else:
572 _rev = AttributeDict({
573 _rev = AttributeDict({
573 'short_id': rev[:12],
574 'short_id': rev[:12],
574 'raw_id': rev,
575 'raw_id': rev,
575 'message': '',
576 'message': '',
576 'op': _op,
577 'op': _op,
577 'ref_name': _name
578 'ref_name': _name
578 })
579 })
579 revs.append(_rev)
580 revs.append(_rev)
580 cs_links = []
581 cs_links = []
581 cs_links.append(" " + ', '.join(
582 cs_links.append(" " + ', '.join(
582 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
583 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
583 )
584 )
584 )
585 )
585
586
586 compare_view = (
587 compare_view = (
587 ' <div class="compare_view tooltip" title="%s">'
588 ' <div class="compare_view tooltip" title="%s">'
588 '<a href="%s">%s</a> </div>' % (
589 '<a href="%s">%s</a> </div>' % (
589 _('Show all combined changesets %s->%s') % (
590 _('Show all combined changesets %s->%s') % (
590 revs_ids[0][:12], revs_ids[-1][:12]
591 revs_ids[0][:12], revs_ids[-1][:12]
591 ),
592 ),
592 url('changeset_home', repo_name=repo_name,
593 url('changeset_home', repo_name=repo_name,
593 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
594 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
594 ),
595 ),
595 _('compare view')
596 _('compare view')
596 )
597 )
597 )
598 )
598
599
599 # if we have exactly one more than normally displayed
600 # if we have exactly one more than normally displayed
600 # just display it, takes less space than displaying
601 # just display it, takes less space than displaying
601 # "and 1 more revisions"
602 # "and 1 more revisions"
602 if len(revs_ids) == revs_limit + 1:
603 if len(revs_ids) == revs_limit + 1:
603 rev = revs[revs_limit]
604 rev = revs[revs_limit]
604 cs_links.append(", " + lnk(rev, repo_name))
605 cs_links.append(", " + lnk(rev, repo_name))
605
606
606 # hidden-by-default ones
607 # hidden-by-default ones
607 if len(revs_ids) > revs_limit + 1:
608 if len(revs_ids) > revs_limit + 1:
608 uniq_id = revs_ids[0]
609 uniq_id = revs_ids[0]
609 html_tmpl = (
610 html_tmpl = (
610 '<span> %s <a class="show_more" id="_%s" '
611 '<span> %s <a class="show_more" id="_%s" '
611 'href="#more">%s</a> %s</span>'
612 'href="#more">%s</a> %s</span>'
612 )
613 )
613 if not feed:
614 if not feed:
614 cs_links.append(html_tmpl % (
615 cs_links.append(html_tmpl % (
615 _('and'),
616 _('and'),
616 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
617 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
617 _('revisions')
618 _('revisions')
618 )
619 )
619 )
620 )
620
621
621 if not feed:
622 if not feed:
622 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
623 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
623 else:
624 else:
624 html_tmpl = '<span id="%s"> %s </span>'
625 html_tmpl = '<span id="%s"> %s </span>'
625
626
626 morelinks = ', '.join(
627 morelinks = ', '.join(
627 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
628 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
628 )
629 )
629
630
630 if len(revs_ids) > revs_top_limit:
631 if len(revs_ids) > revs_top_limit:
631 morelinks += ', ...'
632 morelinks += ', ...'
632
633
633 cs_links.append(html_tmpl % (uniq_id, morelinks))
634 cs_links.append(html_tmpl % (uniq_id, morelinks))
634 if len(revs) > 1:
635 if len(revs) > 1:
635 cs_links.append(compare_view)
636 cs_links.append(compare_view)
636 return ''.join(cs_links)
637 return ''.join(cs_links)
637
638
638 def get_fork_name():
639 def get_fork_name():
639 repo_name = action_params
640 repo_name = action_params
640 _url = url('summary_home', repo_name=repo_name)
641 _url = url('summary_home', repo_name=repo_name)
641 return _('fork name %s') % link_to(action_params, _url)
642 return _('fork name %s') % link_to(action_params, _url)
642
643
643 def get_user_name():
644 def get_user_name():
644 user_name = action_params
645 user_name = action_params
645 return user_name
646 return user_name
646
647
647 def get_users_group():
648 def get_users_group():
648 group_name = action_params
649 group_name = action_params
649 return group_name
650 return group_name
650
651
651 def get_pull_request():
652 def get_pull_request():
652 pull_request_id = action_params
653 pull_request_id = action_params
653 deleted = user_log.repository is None
654 deleted = user_log.repository is None
654 if deleted:
655 if deleted:
655 repo_name = user_log.repository_name
656 repo_name = user_log.repository_name
656 else:
657 else:
657 repo_name = user_log.repository.repo_name
658 repo_name = user_log.repository.repo_name
658 return link_to(_('Pull request #%s') % pull_request_id,
659 return link_to(_('Pull request #%s') % pull_request_id,
659 url('pullrequest_show', repo_name=repo_name,
660 url('pullrequest_show', repo_name=repo_name,
660 pull_request_id=pull_request_id))
661 pull_request_id=pull_request_id))
661
662
662 # action : translated str, callback(extractor), icon
663 # action : translated str, callback(extractor), icon
663 action_map = {
664 action_map = {
664 'user_deleted_repo': (_('[deleted] repository'),
665 'user_deleted_repo': (_('[deleted] repository'),
665 None, 'database_delete.png'),
666 None, 'database_delete.png'),
666 'user_created_repo': (_('[created] repository'),
667 'user_created_repo': (_('[created] repository'),
667 None, 'database_add.png'),
668 None, 'database_add.png'),
668 'user_created_fork': (_('[created] repository as fork'),
669 'user_created_fork': (_('[created] repository as fork'),
669 None, 'arrow_divide.png'),
670 None, 'arrow_divide.png'),
670 'user_forked_repo': (_('[forked] repository'),
671 'user_forked_repo': (_('[forked] repository'),
671 get_fork_name, 'arrow_divide.png'),
672 get_fork_name, 'arrow_divide.png'),
672 'user_updated_repo': (_('[updated] repository'),
673 'user_updated_repo': (_('[updated] repository'),
673 None, 'database_edit.png'),
674 None, 'database_edit.png'),
674 'admin_deleted_repo': (_('[delete] repository'),
675 'admin_deleted_repo': (_('[delete] repository'),
675 None, 'database_delete.png'),
676 None, 'database_delete.png'),
676 'admin_created_repo': (_('[created] repository'),
677 'admin_created_repo': (_('[created] repository'),
677 None, 'database_add.png'),
678 None, 'database_add.png'),
678 'admin_forked_repo': (_('[forked] repository'),
679 'admin_forked_repo': (_('[forked] repository'),
679 None, 'arrow_divide.png'),
680 None, 'arrow_divide.png'),
680 'admin_updated_repo': (_('[updated] repository'),
681 'admin_updated_repo': (_('[updated] repository'),
681 None, 'database_edit.png'),
682 None, 'database_edit.png'),
682 'admin_created_user': (_('[created] user'),
683 'admin_created_user': (_('[created] user'),
683 get_user_name, 'user_add.png'),
684 get_user_name, 'user_add.png'),
684 'admin_updated_user': (_('[updated] user'),
685 'admin_updated_user': (_('[updated] user'),
685 get_user_name, 'user_edit.png'),
686 get_user_name, 'user_edit.png'),
686 'admin_created_users_group': (_('[created] users group'),
687 'admin_created_users_group': (_('[created] users group'),
687 get_users_group, 'group_add.png'),
688 get_users_group, 'group_add.png'),
688 'admin_updated_users_group': (_('[updated] users group'),
689 'admin_updated_users_group': (_('[updated] users group'),
689 get_users_group, 'group_edit.png'),
690 get_users_group, 'group_edit.png'),
690 'user_commented_revision': (_('[commented] on revision in repository'),
691 'user_commented_revision': (_('[commented] on revision in repository'),
691 get_cs_links, 'comment_add.png'),
692 get_cs_links, 'comment_add.png'),
692 'user_commented_pull_request': (_('[commented] on pull request for'),
693 'user_commented_pull_request': (_('[commented] on pull request for'),
693 get_pull_request, 'comment_add.png'),
694 get_pull_request, 'comment_add.png'),
694 'user_closed_pull_request': (_('[closed] pull request for'),
695 'user_closed_pull_request': (_('[closed] pull request for'),
695 get_pull_request, 'tick.png'),
696 get_pull_request, 'tick.png'),
696 'push': (_('[pushed] into'),
697 'push': (_('[pushed] into'),
697 get_cs_links, 'script_add.png'),
698 get_cs_links, 'script_add.png'),
698 'push_local': (_('[committed via RhodeCode] into repository'),
699 'push_local': (_('[committed via RhodeCode] into repository'),
699 get_cs_links, 'script_edit.png'),
700 get_cs_links, 'script_edit.png'),
700 'push_remote': (_('[pulled from remote] into repository'),
701 'push_remote': (_('[pulled from remote] into repository'),
701 get_cs_links, 'connect.png'),
702 get_cs_links, 'connect.png'),
702 'pull': (_('[pulled] from'),
703 'pull': (_('[pulled] from'),
703 None, 'down_16.png'),
704 None, 'down_16.png'),
704 'started_following_repo': (_('[started following] repository'),
705 'started_following_repo': (_('[started following] repository'),
705 None, 'heart_add.png'),
706 None, 'heart_add.png'),
706 'stopped_following_repo': (_('[stopped following] repository'),
707 'stopped_following_repo': (_('[stopped following] repository'),
707 None, 'heart_delete.png'),
708 None, 'heart_delete.png'),
708 }
709 }
709
710
710 action_str = action_map.get(action, action)
711 action_str = action_map.get(action, action)
711 if feed:
712 if feed:
712 action = action_str[0].replace('[', '').replace(']', '')
713 action = action_str[0].replace('[', '').replace(']', '')
713 else:
714 else:
714 action = action_str[0]\
715 action = action_str[0]\
715 .replace('[', '<span class="journal_highlight">')\
716 .replace('[', '<span class="journal_highlight">')\
716 .replace(']', '</span>')
717 .replace(']', '</span>')
717
718
718 action_params_func = lambda: ""
719 action_params_func = lambda: ""
719
720
720 if callable(action_str[1]):
721 if callable(action_str[1]):
721 action_params_func = action_str[1]
722 action_params_func = action_str[1]
722
723
723 def action_parser_icon():
724 def action_parser_icon():
724 action = user_log.action
725 action = user_log.action
725 action_params = None
726 action_params = None
726 x = action.split(':')
727 x = action.split(':')
727
728
728 if len(x) > 1:
729 if len(x) > 1:
729 action, action_params = x
730 action, action_params = x
730
731
731 tmpl = """<img src="%s%s" alt="%s"/>"""
732 tmpl = """<img src="%s%s" alt="%s"/>"""
732 ico = action_map.get(action, ['', '', ''])[2]
733 ico = action_map.get(action, ['', '', ''])[2]
733 return literal(tmpl % ((url('/images/icons/')), ico, action))
734 return literal(tmpl % ((url('/images/icons/')), ico, action))
734
735
735 # returned callbacks we need to call to get
736 # returned callbacks we need to call to get
736 return [lambda: literal(action), action_params_func, action_parser_icon]
737 return [lambda: literal(action), action_params_func, action_parser_icon]
737
738
738
739
739
740
740 #==============================================================================
741 #==============================================================================
741 # PERMS
742 # PERMS
742 #==============================================================================
743 #==============================================================================
743 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
744 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
744 HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \
745 HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \
745 HasReposGroupPermissionAny
746 HasReposGroupPermissionAny
746
747
747
748
748 #==============================================================================
749 #==============================================================================
749 # GRAVATAR URL
750 # GRAVATAR URL
750 #==============================================================================
751 #==============================================================================
751
752
752 def gravatar_url(email_address, size=30):
753 def gravatar_url(email_address, size=30):
753 from pylons import url # doh, we need to re-import url to mock it later
754 from pylons import url # doh, we need to re-import url to mock it later
754 _def = 'anonymous@rhodecode.org'
755 _def = 'anonymous@rhodecode.org'
755 use_gravatar = str2bool(config['app_conf'].get('use_gravatar'))
756 use_gravatar = str2bool(config['app_conf'].get('use_gravatar'))
756 email_address = email_address or _def
757 email_address = email_address or _def
757 if (not use_gravatar or not email_address or email_address == _def):
758 if (not use_gravatar or not email_address or email_address == _def):
758 f = lambda a, l: min(l, key=lambda x: abs(x - a))
759 f = lambda a, l: min(l, key=lambda x: abs(x - a))
759 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
760 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
760
761
761 if use_gravatar and config['app_conf'].get('alternative_gravatar_url'):
762 if use_gravatar and config['app_conf'].get('alternative_gravatar_url'):
762 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
763 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
763 parsed_url = urlparse.urlparse(url.current(qualified=True))
764 parsed_url = urlparse.urlparse(url.current(qualified=True))
764 tmpl = tmpl.replace('{email}', email_address)\
765 tmpl = tmpl.replace('{email}', email_address)\
765 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
766 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
766 .replace('{netloc}', parsed_url.netloc)\
767 .replace('{netloc}', parsed_url.netloc)\
767 .replace('{scheme}', parsed_url.scheme)\
768 .replace('{scheme}', parsed_url.scheme)\
768 .replace('{size}', str(size))
769 .replace('{size}', str(size))
769 return tmpl
770 return tmpl
770
771
771 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
772 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
772 default = 'identicon'
773 default = 'identicon'
773 baseurl_nossl = "http://www.gravatar.com/avatar/"
774 baseurl_nossl = "http://www.gravatar.com/avatar/"
774 baseurl_ssl = "https://secure.gravatar.com/avatar/"
775 baseurl_ssl = "https://secure.gravatar.com/avatar/"
775 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
776 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
776
777
777 if isinstance(email_address, unicode):
778 if isinstance(email_address, unicode):
778 #hashlib crashes on unicode items
779 #hashlib crashes on unicode items
779 email_address = safe_str(email_address)
780 email_address = safe_str(email_address)
780 # construct the url
781 # construct the url
781 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
782 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
782 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
783 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
783
784
784 return gravatar_url
785 return gravatar_url
785
786
786
787
787 #==============================================================================
788 #==============================================================================
788 # REPO PAGER, PAGER FOR REPOSITORY
789 # REPO PAGER, PAGER FOR REPOSITORY
789 #==============================================================================
790 #==============================================================================
790 class RepoPage(Page):
791 class RepoPage(Page):
791
792
792 def __init__(self, collection, page=1, items_per_page=20,
793 def __init__(self, collection, page=1, items_per_page=20,
793 item_count=None, url=None, **kwargs):
794 item_count=None, url=None, **kwargs):
794
795
795 """Create a "RepoPage" instance. special pager for paging
796 """Create a "RepoPage" instance. special pager for paging
796 repository
797 repository
797 """
798 """
798 self._url_generator = url
799 self._url_generator = url
799
800
800 # Safe the kwargs class-wide so they can be used in the pager() method
801 # Safe the kwargs class-wide so they can be used in the pager() method
801 self.kwargs = kwargs
802 self.kwargs = kwargs
802
803
803 # Save a reference to the collection
804 # Save a reference to the collection
804 self.original_collection = collection
805 self.original_collection = collection
805
806
806 self.collection = collection
807 self.collection = collection
807
808
808 # The self.page is the number of the current page.
809 # The self.page is the number of the current page.
809 # The first page has the number 1!
810 # The first page has the number 1!
810 try:
811 try:
811 self.page = int(page) # make it int() if we get it as a string
812 self.page = int(page) # make it int() if we get it as a string
812 except (ValueError, TypeError):
813 except (ValueError, TypeError):
813 self.page = 1
814 self.page = 1
814
815
815 self.items_per_page = items_per_page
816 self.items_per_page = items_per_page
816
817
817 # Unless the user tells us how many items the collections has
818 # Unless the user tells us how many items the collections has
818 # we calculate that ourselves.
819 # we calculate that ourselves.
819 if item_count is not None:
820 if item_count is not None:
820 self.item_count = item_count
821 self.item_count = item_count
821 else:
822 else:
822 self.item_count = len(self.collection)
823 self.item_count = len(self.collection)
823
824
824 # Compute the number of the first and last available page
825 # Compute the number of the first and last available page
825 if self.item_count > 0:
826 if self.item_count > 0:
826 self.first_page = 1
827 self.first_page = 1
827 self.page_count = int(math.ceil(float(self.item_count) /
828 self.page_count = int(math.ceil(float(self.item_count) /
828 self.items_per_page))
829 self.items_per_page))
829 self.last_page = self.first_page + self.page_count - 1
830 self.last_page = self.first_page + self.page_count - 1
830
831
831 # Make sure that the requested page number is the range of
832 # Make sure that the requested page number is the range of
832 # valid pages
833 # valid pages
833 if self.page > self.last_page:
834 if self.page > self.last_page:
834 self.page = self.last_page
835 self.page = self.last_page
835 elif self.page < self.first_page:
836 elif self.page < self.first_page:
836 self.page = self.first_page
837 self.page = self.first_page
837
838
838 # Note: the number of items on this page can be less than
839 # Note: the number of items on this page can be less than
839 # items_per_page if the last page is not full
840 # items_per_page if the last page is not full
840 self.first_item = max(0, (self.item_count) - (self.page *
841 self.first_item = max(0, (self.item_count) - (self.page *
841 items_per_page))
842 items_per_page))
842 self.last_item = ((self.item_count - 1) - items_per_page *
843 self.last_item = ((self.item_count - 1) - items_per_page *
843 (self.page - 1))
844 (self.page - 1))
844
845
845 self.items = list(self.collection[self.first_item:self.last_item + 1])
846 self.items = list(self.collection[self.first_item:self.last_item + 1])
846
847
847 # Links to previous and next page
848 # Links to previous and next page
848 if self.page > self.first_page:
849 if self.page > self.first_page:
849 self.previous_page = self.page - 1
850 self.previous_page = self.page - 1
850 else:
851 else:
851 self.previous_page = None
852 self.previous_page = None
852
853
853 if self.page < self.last_page:
854 if self.page < self.last_page:
854 self.next_page = self.page + 1
855 self.next_page = self.page + 1
855 else:
856 else:
856 self.next_page = None
857 self.next_page = None
857
858
858 # No items available
859 # No items available
859 else:
860 else:
860 self.first_page = None
861 self.first_page = None
861 self.page_count = 0
862 self.page_count = 0
862 self.last_page = None
863 self.last_page = None
863 self.first_item = None
864 self.first_item = None
864 self.last_item = None
865 self.last_item = None
865 self.previous_page = None
866 self.previous_page = None
866 self.next_page = None
867 self.next_page = None
867 self.items = []
868 self.items = []
868
869
869 # This is a subclass of the 'list' type. Initialise the list now.
870 # This is a subclass of the 'list' type. Initialise the list now.
870 list.__init__(self, reversed(self.items))
871 list.__init__(self, reversed(self.items))
871
872
872
873
873 def changed_tooltip(nodes):
874 def changed_tooltip(nodes):
874 """
875 """
875 Generates a html string for changed nodes in changeset page.
876 Generates a html string for changed nodes in changeset page.
876 It limits the output to 30 entries
877 It limits the output to 30 entries
877
878
878 :param nodes: LazyNodesGenerator
879 :param nodes: LazyNodesGenerator
879 """
880 """
880 if nodes:
881 if nodes:
881 pref = ': <br/> '
882 pref = ': <br/> '
882 suf = ''
883 suf = ''
883 if len(nodes) > 30:
884 if len(nodes) > 30:
884 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
885 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
885 return literal(pref + '<br/> '.join([safe_unicode(x.path)
886 return literal(pref + '<br/> '.join([safe_unicode(x.path)
886 for x in nodes[:30]]) + suf)
887 for x in nodes[:30]]) + suf)
887 else:
888 else:
888 return ': ' + _('No Files')
889 return ': ' + _('No Files')
889
890
890
891
891 def repo_link(groups_and_repos, last_url=None):
892 def repo_link(groups_and_repos, last_url=None):
892 """
893 """
893 Makes a breadcrumbs link to repo within a group
894 Makes a breadcrumbs link to repo within a group
894 joins &raquo; on each group to create a fancy link
895 joins &raquo; on each group to create a fancy link
895
896
896 ex::
897 ex::
897 group >> subgroup >> repo
898 group >> subgroup >> repo
898
899
899 :param groups_and_repos:
900 :param groups_and_repos:
900 :param last_url:
901 :param last_url:
901 """
902 """
902 groups, repo_name = groups_and_repos
903 groups, repo_name = groups_and_repos
903 last_link = link_to(repo_name, last_url) if last_url else repo_name
904 last_link = link_to(repo_name, last_url) if last_url else repo_name
904
905
905 if not groups:
906 if not groups:
906 if last_url:
907 if last_url:
907 return last_link
908 return last_link
908 return repo_name
909 return repo_name
909 else:
910 else:
910 def make_link(group):
911 def make_link(group):
911 return link_to(group.name,
912 return link_to(group.name,
912 url('repos_group_home', group_name=group.group_name))
913 url('repos_group_home', group_name=group.group_name))
913 return literal(' &raquo; '.join(map(make_link, groups) + [last_link]))
914 return literal(' &raquo; '.join(map(make_link, groups) + [last_link]))
914
915
915
916
916 def fancy_file_stats(stats):
917 def fancy_file_stats(stats):
917 """
918 """
918 Displays a fancy two colored bar for number of added/deleted
919 Displays a fancy two colored bar for number of added/deleted
919 lines of code on file
920 lines of code on file
920
921
921 :param stats: two element list of added/deleted lines of code
922 :param stats: two element list of added/deleted lines of code
922 """
923 """
923 def cgen(l_type, a_v, d_v):
924 def cgen(l_type, a_v, d_v):
924 mapping = {'tr': 'top-right-rounded-corner-mid',
925 mapping = {'tr': 'top-right-rounded-corner-mid',
925 'tl': 'top-left-rounded-corner-mid',
926 'tl': 'top-left-rounded-corner-mid',
926 'br': 'bottom-right-rounded-corner-mid',
927 'br': 'bottom-right-rounded-corner-mid',
927 'bl': 'bottom-left-rounded-corner-mid'}
928 'bl': 'bottom-left-rounded-corner-mid'}
928 map_getter = lambda x: mapping[x]
929 map_getter = lambda x: mapping[x]
929
930
930 if l_type == 'a' and d_v:
931 if l_type == 'a' and d_v:
931 #case when added and deleted are present
932 #case when added and deleted are present
932 return ' '.join(map(map_getter, ['tl', 'bl']))
933 return ' '.join(map(map_getter, ['tl', 'bl']))
933
934
934 if l_type == 'a' and not d_v:
935 if l_type == 'a' and not d_v:
935 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
936 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
936
937
937 if l_type == 'd' and a_v:
938 if l_type == 'd' and a_v:
938 return ' '.join(map(map_getter, ['tr', 'br']))
939 return ' '.join(map(map_getter, ['tr', 'br']))
939
940
940 if l_type == 'd' and not a_v:
941 if l_type == 'd' and not a_v:
941 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
942 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
942
943
943 a, d = stats[0], stats[1]
944 a, d = stats[0], stats[1]
944 width = 100
945 width = 100
945
946
946 if a == 'b':
947 if a == 'b':
947 #binary mode
948 #binary mode
948 b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
949 b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
949 b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
950 b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
950 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
951 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
951
952
952 t = stats[0] + stats[1]
953 t = stats[0] + stats[1]
953 unit = float(width) / (t or 1)
954 unit = float(width) / (t or 1)
954
955
955 # needs > 9% of width to be visible or 0 to be hidden
956 # needs > 9% of width to be visible or 0 to be hidden
956 a_p = max(9, unit * a) if a > 0 else 0
957 a_p = max(9, unit * a) if a > 0 else 0
957 d_p = max(9, unit * d) if d > 0 else 0
958 d_p = max(9, unit * d) if d > 0 else 0
958 p_sum = a_p + d_p
959 p_sum = a_p + d_p
959
960
960 if p_sum > width:
961 if p_sum > width:
961 #adjust the percentage to be == 100% since we adjusted to 9
962 #adjust the percentage to be == 100% since we adjusted to 9
962 if a_p > d_p:
963 if a_p > d_p:
963 a_p = a_p - (p_sum - width)
964 a_p = a_p - (p_sum - width)
964 else:
965 else:
965 d_p = d_p - (p_sum - width)
966 d_p = d_p - (p_sum - width)
966
967
967 a_v = a if a > 0 else ''
968 a_v = a if a > 0 else ''
968 d_v = d if d > 0 else ''
969 d_v = d if d > 0 else ''
969
970
970 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
971 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
971 cgen('a', a_v, d_v), a_p, a_v
972 cgen('a', a_v, d_v), a_p, a_v
972 )
973 )
973 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
974 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
974 cgen('d', a_v, d_v), d_p, d_v
975 cgen('d', a_v, d_v), d_p, d_v
975 )
976 )
976 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
977 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
977
978
978
979
979 def urlify_text(text_):
980 def urlify_text(text_):
980
981
981 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
982 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
982 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
983 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
983
984
984 def url_func(match_obj):
985 def url_func(match_obj):
985 url_full = match_obj.groups()[0]
986 url_full = match_obj.groups()[0]
986 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
987 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
987
988
988 return literal(url_pat.sub(url_func, text_))
989 return literal(url_pat.sub(url_func, text_))
989
990
990
991
991 def urlify_changesets(text_, repository):
992 def urlify_changesets(text_, repository):
992 """
993 """
993 Extract revision ids from changeset and make link from them
994 Extract revision ids from changeset and make link from them
994
995
995 :param text_:
996 :param text_:
996 :param repository:
997 :param repository:
997 """
998 """
998
999
999 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
1000 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
1000
1001
1001 def url_func(match_obj):
1002 def url_func(match_obj):
1002 rev = match_obj.groups()[0]
1003 rev = match_obj.groups()[0]
1003 pref = ''
1004 pref = ''
1004 if match_obj.group().startswith(' '):
1005 if match_obj.group().startswith(' '):
1005 pref = ' '
1006 pref = ' '
1006 tmpl = (
1007 tmpl = (
1007 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1008 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1008 '%(rev)s'
1009 '%(rev)s'
1009 '</a>'
1010 '</a>'
1010 )
1011 )
1011 return tmpl % {
1012 return tmpl % {
1012 'pref': pref,
1013 'pref': pref,
1013 'cls': 'revision-link',
1014 'cls': 'revision-link',
1014 'url': url('changeset_home', repo_name=repository, revision=rev),
1015 'url': url('changeset_home', repo_name=repository, revision=rev),
1015 'rev': rev,
1016 'rev': rev,
1016 }
1017 }
1017
1018
1018 newtext = URL_PAT.sub(url_func, text_)
1019 newtext = URL_PAT.sub(url_func, text_)
1019
1020
1020 return newtext
1021 return newtext
1021
1022
1022
1023
1023 def urlify_commit(text_, repository=None, link_=None):
1024 def urlify_commit(text_, repository=None, link_=None):
1024 """
1025 """
1025 Parses given text message and makes proper links.
1026 Parses given text message and makes proper links.
1026 issues are linked to given issue-server, and rest is a changeset link
1027 issues are linked to given issue-server, and rest is a changeset link
1027 if link_ is given, in other case it's a plain text
1028 if link_ is given, in other case it's a plain text
1028
1029
1029 :param text_:
1030 :param text_:
1030 :param repository:
1031 :param repository:
1031 :param link_: changeset link
1032 :param link_: changeset link
1032 """
1033 """
1033 import traceback
1034 import traceback
1034
1035
1035 def escaper(string):
1036 def escaper(string):
1036 return string.replace('<', '&lt;').replace('>', '&gt;')
1037 return string.replace('<', '&lt;').replace('>', '&gt;')
1037
1038
1038 def linkify_others(t, l):
1039 def linkify_others(t, l):
1039 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1040 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1040 links = []
1041 links = []
1041 for e in urls.split(t):
1042 for e in urls.split(t):
1042 if not urls.match(e):
1043 if not urls.match(e):
1043 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1044 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1044 else:
1045 else:
1045 links.append(e)
1046 links.append(e)
1046
1047
1047 return ''.join(links)
1048 return ''.join(links)
1048
1049
1049 # urlify changesets - extrac revisions and make link out of them
1050 # urlify changesets - extrac revisions and make link out of them
1050 newtext = urlify_changesets(escaper(text_), repository)
1051 newtext = urlify_changesets(escaper(text_), repository)
1051
1052
1052 try:
1053 try:
1053 conf = config['app_conf']
1054 conf = config['app_conf']
1054
1055
1055 # allow multiple issue servers to be used
1056 # allow multiple issue servers to be used
1056 valid_indices = [
1057 valid_indices = [
1057 x.group(1)
1058 x.group(1)
1058 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1059 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1059 if x and 'issue_server_link%s' % x.group(1) in conf
1060 if x and 'issue_server_link%s' % x.group(1) in conf
1060 and 'issue_prefix%s' % x.group(1) in conf
1061 and 'issue_prefix%s' % x.group(1) in conf
1061 ]
1062 ]
1062
1063
1063 log.debug('found issue server suffixes `%s` during valuation of: %s'
1064 log.debug('found issue server suffixes `%s` during valuation of: %s'
1064 % (','.join(valid_indices), newtext))
1065 % (','.join(valid_indices), newtext))
1065
1066
1066 for pattern_index in valid_indices:
1067 for pattern_index in valid_indices:
1067 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1068 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1068 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1069 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1069 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1070 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1070
1071
1071 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1072 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1072 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1073 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1073 ISSUE_PREFIX))
1074 ISSUE_PREFIX))
1074
1075
1075 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1076 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1076
1077
1077 def url_func(match_obj):
1078 def url_func(match_obj):
1078 pref = ''
1079 pref = ''
1079 if match_obj.group().startswith(' '):
1080 if match_obj.group().startswith(' '):
1080 pref = ' '
1081 pref = ' '
1081
1082
1082 issue_id = ''.join(match_obj.groups())
1083 issue_id = ''.join(match_obj.groups())
1083 tmpl = (
1084 tmpl = (
1084 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1085 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1085 '%(issue-prefix)s%(id-repr)s'
1086 '%(issue-prefix)s%(id-repr)s'
1086 '</a>'
1087 '</a>'
1087 )
1088 )
1088 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1089 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1089 if repository:
1090 if repository:
1090 url = url.replace('{repo}', repository)
1091 url = url.replace('{repo}', repository)
1091 repo_name = repository.split(URL_SEP)[-1]
1092 repo_name = repository.split(URL_SEP)[-1]
1092 url = url.replace('{repo_name}', repo_name)
1093 url = url.replace('{repo_name}', repo_name)
1093
1094
1094 return tmpl % {
1095 return tmpl % {
1095 'pref': pref,
1096 'pref': pref,
1096 'cls': 'issue-tracker-link',
1097 'cls': 'issue-tracker-link',
1097 'url': url,
1098 'url': url,
1098 'id-repr': issue_id,
1099 'id-repr': issue_id,
1099 'issue-prefix': ISSUE_PREFIX,
1100 'issue-prefix': ISSUE_PREFIX,
1100 'serv': ISSUE_SERVER_LNK,
1101 'serv': ISSUE_SERVER_LNK,
1101 }
1102 }
1102 newtext = URL_PAT.sub(url_func, newtext)
1103 newtext = URL_PAT.sub(url_func, newtext)
1103 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1104 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1104
1105
1105 # if we actually did something above
1106 # if we actually did something above
1106 if link_:
1107 if link_:
1107 # wrap not links into final link => link_
1108 # wrap not links into final link => link_
1108 newtext = linkify_others(newtext, link_)
1109 newtext = linkify_others(newtext, link_)
1109 except:
1110 except:
1110 log.error(traceback.format_exc())
1111 log.error(traceback.format_exc())
1111 pass
1112 pass
1112
1113
1113 return literal(newtext)
1114 return literal(newtext)
1114
1115
1115
1116
1116 def rst(source):
1117 def rst(source):
1117 return literal('<div class="rst-block">%s</div>' %
1118 return literal('<div class="rst-block">%s</div>' %
1118 MarkupRenderer.rst(source))
1119 MarkupRenderer.rst(source))
1119
1120
1120
1121
1121 def rst_w_mentions(source):
1122 def rst_w_mentions(source):
1122 """
1123 """
1123 Wrapped rst renderer with @mention highlighting
1124 Wrapped rst renderer with @mention highlighting
1124
1125
1125 :param source:
1126 :param source:
1126 """
1127 """
1127 return literal('<div class="rst-block">%s</div>' %
1128 return literal('<div class="rst-block">%s</div>' %
1128 MarkupRenderer.rst_with_mentions(source))
1129 MarkupRenderer.rst_with_mentions(source))
1129
1130
1130
1131
1131 def changeset_status(repo, revision):
1132 def changeset_status(repo, revision):
1132 return ChangesetStatusModel().get_status(repo, revision)
1133 return ChangesetStatusModel().get_status(repo, revision)
1133
1134
1134
1135
1135 def changeset_status_lbl(changeset_status):
1136 def changeset_status_lbl(changeset_status):
1136 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1137 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1137
1138
1138
1139
1139 def get_permission_name(key):
1140 def get_permission_name(key):
1140 return dict(Permission.PERMS).get(key)
1141 return dict(Permission.PERMS).get(key)
1141
1142
1142
1143
1143 def journal_filter_help():
1144 def journal_filter_help():
1144 return _(textwrap.dedent('''
1145 return _(textwrap.dedent('''
1145 Example filter terms:
1146 Example filter terms:
1146 repository:vcs
1147 repository:vcs
1147 username:marcin
1148 username:marcin
1148 action:*push*
1149 action:*push*
1149 ip:127.0.0.1
1150 ip:127.0.0.1
1150 date:20120101
1151 date:20120101
1151 date:[20120101100000 TO 20120102]
1152 date:[20120101100000 TO 20120102]
1152
1153
1153 Generate wildcards using '*' character:
1154 Generate wildcards using '*' character:
1154 "repositroy:vcs*" - search everything starting with 'vcs'
1155 "repositroy:vcs*" - search everything starting with 'vcs'
1155 "repository:*vcs*" - search for repository containing 'vcs'
1156 "repository:*vcs*" - search for repository containing 'vcs'
1156
1157
1157 Optional AND / OR operators in queries
1158 Optional AND / OR operators in queries
1158 "repository:vcs OR repository:test"
1159 "repository:vcs OR repository:test"
1159 "username:test AND repository:test*"
1160 "username:test AND repository:test*"
1160 '''))
1161 '''))
1161
1162
1162
1163
1163 def not_mapped_error(repo_name):
1164 def not_mapped_error(repo_name):
1164 flash(_('%s repository is not mapped to db perhaps'
1165 flash(_('%s repository is not mapped to db perhaps'
1165 ' it was created or renamed from the filesystem'
1166 ' it was created or renamed from the filesystem'
1166 ' please run the application again'
1167 ' please run the application again'
1167 ' in order to rescan repositories') % repo_name, category='error')
1168 ' in order to rescan repositories') % repo_name, category='error')
1168
1169
1169
1170
1170 def ip_range(ip_addr):
1171 def ip_range(ip_addr):
1171 from rhodecode.model.db import UserIpMap
1172 from rhodecode.model.db import UserIpMap
1172 s, e = UserIpMap._get_ip_range(ip_addr)
1173 s, e = UserIpMap._get_ip_range(ip_addr)
1173 return '%s - %s' % (s, e)
1174 return '%s - %s' % (s, e)
@@ -1,781 +1,801 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Utilities library for RhodeCode
6 Utilities library for RhodeCode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import re
27 import re
28 import logging
28 import logging
29 import datetime
29 import datetime
30 import traceback
30 import traceback
31 import paste
31 import paste
32 import beaker
32 import beaker
33 import tarfile
33 import tarfile
34 import shutil
34 import shutil
35 import decorator
35 import decorator
36 import warnings
36 import warnings
37 from os.path import abspath
37 from os.path import abspath
38 from os.path import dirname as dn, join as jn
38 from os.path import dirname as dn, join as jn
39
39
40 from paste.script.command import Command, BadCommand
40 from paste.script.command import Command, BadCommand
41
41
42 from mercurial import ui, config
42 from mercurial import ui, config
43
43
44 from webhelpers.text import collapse, remove_formatting, strip_tags
44 from webhelpers.text import collapse, remove_formatting, strip_tags
45
45
46 from rhodecode.lib.vcs import get_backend
46 from rhodecode.lib.vcs import get_backend
47 from rhodecode.lib.vcs.backends.base import BaseChangeset
47 from rhodecode.lib.vcs.backends.base import BaseChangeset
48 from rhodecode.lib.vcs.utils.lazy import LazyProperty
48 from rhodecode.lib.vcs.utils.lazy import LazyProperty
49 from rhodecode.lib.vcs.utils.helpers import get_scm
49 from rhodecode.lib.vcs.utils.helpers import get_scm
50 from rhodecode.lib.vcs.exceptions import VCSError
50 from rhodecode.lib.vcs.exceptions import VCSError
51
51
52 from rhodecode.lib.caching_query import FromCache
52 from rhodecode.lib.caching_query import FromCache
53
53
54 from rhodecode.model import meta
54 from rhodecode.model import meta
55 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
55 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
56 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
56 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
57 from rhodecode.model.meta import Session
57 from rhodecode.model.meta import Session
58 from rhodecode.model.repos_group import ReposGroupModel
58 from rhodecode.model.repos_group import ReposGroupModel
59 from rhodecode.lib.utils2 import safe_str, safe_unicode
59 from rhodecode.lib.utils2 import safe_str, safe_unicode
60 from rhodecode.lib.vcs.utils.fakemod import create_module
60 from rhodecode.lib.vcs.utils.fakemod import create_module
61
61
62 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
63
63
64 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
64 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
65
65
66
66
67 def recursive_replace(str_, replace=' '):
67 def recursive_replace(str_, replace=' '):
68 """
68 """
69 Recursive replace of given sign to just one instance
69 Recursive replace of given sign to just one instance
70
70
71 :param str_: given string
71 :param str_: given string
72 :param replace: char to find and replace multiple instances
72 :param replace: char to find and replace multiple instances
73
73
74 Examples::
74 Examples::
75 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
75 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
76 'Mighty-Mighty-Bo-sstones'
76 'Mighty-Mighty-Bo-sstones'
77 """
77 """
78
78
79 if str_.find(replace * 2) == -1:
79 if str_.find(replace * 2) == -1:
80 return str_
80 return str_
81 else:
81 else:
82 str_ = str_.replace(replace * 2, replace)
82 str_ = str_.replace(replace * 2, replace)
83 return recursive_replace(str_, replace)
83 return recursive_replace(str_, replace)
84
84
85
85
86 def repo_name_slug(value):
86 def repo_name_slug(value):
87 """
87 """
88 Return slug of name of repository
88 Return slug of name of repository
89 This function is called on each creation/modification
89 This function is called on each creation/modification
90 of repository to prevent bad names in repo
90 of repository to prevent bad names in repo
91 """
91 """
92
92
93 slug = remove_formatting(value)
93 slug = remove_formatting(value)
94 slug = strip_tags(slug)
94 slug = strip_tags(slug)
95
95
96 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
96 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
97 slug = slug.replace(c, '-')
97 slug = slug.replace(c, '-')
98 slug = recursive_replace(slug, '-')
98 slug = recursive_replace(slug, '-')
99 slug = collapse(slug, '-')
99 slug = collapse(slug, '-')
100 return slug
100 return slug
101
101
102
102
103 def get_repo_slug(request):
103 def get_repo_slug(request):
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
105 if _repo:
105 if _repo:
106 _repo = _repo.rstrip('/')
106 _repo = _repo.rstrip('/')
107 return _repo
107 return _repo
108
108
109
109
110 def get_repos_group_slug(request):
110 def get_repos_group_slug(request):
111 _group = request.environ['pylons.routes_dict'].get('group_name')
111 _group = request.environ['pylons.routes_dict'].get('group_name')
112 if _group:
112 if _group:
113 _group = _group.rstrip('/')
113 _group = _group.rstrip('/')
114 return _group
114 return _group
115
115
116
116
117 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
117 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
118 """
118 """
119 Action logger for various actions made by users
119 Action logger for various actions made by users
120
120
121 :param user: user that made this action, can be a unique username string or
121 :param user: user that made this action, can be a unique username string or
122 object containing user_id attribute
122 object containing user_id attribute
123 :param action: action to log, should be on of predefined unique actions for
123 :param action: action to log, should be on of predefined unique actions for
124 easy translations
124 easy translations
125 :param repo: string name of repository or object containing repo_id,
125 :param repo: string name of repository or object containing repo_id,
126 that action was made on
126 that action was made on
127 :param ipaddr: optional ip address from what the action was made
127 :param ipaddr: optional ip address from what the action was made
128 :param sa: optional sqlalchemy session
128 :param sa: optional sqlalchemy session
129
129
130 """
130 """
131
131
132 if not sa:
132 if not sa:
133 sa = meta.Session()
133 sa = meta.Session()
134
134
135 try:
135 try:
136 if hasattr(user, 'user_id'):
136 if hasattr(user, 'user_id'):
137 user_obj = User.get(user.user_id)
137 user_obj = User.get(user.user_id)
138 elif isinstance(user, basestring):
138 elif isinstance(user, basestring):
139 user_obj = User.get_by_username(user)
139 user_obj = User.get_by_username(user)
140 else:
140 else:
141 raise Exception('You have to provide a user object or a username')
141 raise Exception('You have to provide a user object or a username')
142
142
143 if hasattr(repo, 'repo_id'):
143 if hasattr(repo, 'repo_id'):
144 repo_obj = Repository.get(repo.repo_id)
144 repo_obj = Repository.get(repo.repo_id)
145 repo_name = repo_obj.repo_name
145 repo_name = repo_obj.repo_name
146 elif isinstance(repo, basestring):
146 elif isinstance(repo, basestring):
147 repo_name = repo.lstrip('/')
147 repo_name = repo.lstrip('/')
148 repo_obj = Repository.get_by_repo_name(repo_name)
148 repo_obj = Repository.get_by_repo_name(repo_name)
149 else:
149 else:
150 repo_obj = None
150 repo_obj = None
151 repo_name = ''
151 repo_name = ''
152
152
153 user_log = UserLog()
153 user_log = UserLog()
154 user_log.user_id = user_obj.user_id
154 user_log.user_id = user_obj.user_id
155 user_log.username = user_obj.username
155 user_log.username = user_obj.username
156 user_log.action = safe_unicode(action)
156 user_log.action = safe_unicode(action)
157
157
158 user_log.repository = repo_obj
158 user_log.repository = repo_obj
159 user_log.repository_name = repo_name
159 user_log.repository_name = repo_name
160
160
161 user_log.action_date = datetime.datetime.now()
161 user_log.action_date = datetime.datetime.now()
162 user_log.user_ip = ipaddr
162 user_log.user_ip = ipaddr
163 sa.add(user_log)
163 sa.add(user_log)
164
164
165 log.info('Logging action %s on %s by %s' %
165 log.info('Logging action %s on %s by %s' %
166 (action, safe_unicode(repo), user_obj))
166 (action, safe_unicode(repo), user_obj))
167 if commit:
167 if commit:
168 sa.commit()
168 sa.commit()
169 except:
169 except:
170 log.error(traceback.format_exc())
170 log.error(traceback.format_exc())
171 raise
171 raise
172
172
173
173
174 def get_repos(path, recursive=False, skip_removed_repos=True):
174 def get_repos(path, recursive=False, skip_removed_repos=True):
175 """
175 """
176 Scans given path for repos and return (name,(type,path)) tuple
176 Scans given path for repos and return (name,(type,path)) tuple
177
177
178 :param path: path to scan for repositories
178 :param path: path to scan for repositories
179 :param recursive: recursive search and return names with subdirs in front
179 :param recursive: recursive search and return names with subdirs in front
180 """
180 """
181
181
182 # remove ending slash for better results
182 # remove ending slash for better results
183 path = path.rstrip(os.sep)
183 path = path.rstrip(os.sep)
184 log.debug('now scanning in %s location recursive:%s...' % (path, recursive))
184 log.debug('now scanning in %s location recursive:%s...' % (path, recursive))
185
185
186 def _get_repos(p):
186 def _get_repos(p):
187 if not os.access(p, os.W_OK):
187 if not os.access(p, os.W_OK):
188 return
188 return
189 for dirpath in os.listdir(p):
189 for dirpath in os.listdir(p):
190 if os.path.isfile(os.path.join(p, dirpath)):
190 if os.path.isfile(os.path.join(p, dirpath)):
191 continue
191 continue
192 cur_path = os.path.join(p, dirpath)
192 cur_path = os.path.join(p, dirpath)
193
193
194 # skip removed repos
194 # skip removed repos
195 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
195 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
196 continue
196 continue
197
197
198 #skip .<somethin> dirs
198 #skip .<somethin> dirs
199 if dirpath.startswith('.'):
199 if dirpath.startswith('.'):
200 continue
200 continue
201
201
202 try:
202 try:
203 scm_info = get_scm(cur_path)
203 scm_info = get_scm(cur_path)
204 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
204 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
205 except VCSError:
205 except VCSError:
206 if not recursive:
206 if not recursive:
207 continue
207 continue
208 #check if this dir containts other repos for recursive scan
208 #check if this dir containts other repos for recursive scan
209 rec_path = os.path.join(p, dirpath)
209 rec_path = os.path.join(p, dirpath)
210 if os.path.isdir(rec_path):
210 if os.path.isdir(rec_path):
211 for inner_scm in _get_repos(rec_path):
211 for inner_scm in _get_repos(rec_path):
212 yield inner_scm
212 yield inner_scm
213
213
214 return _get_repos(path)
214 return _get_repos(path)
215
215
216 #alias for backward compat
216 #alias for backward compat
217 get_filesystem_repos = get_repos
217 get_filesystem_repos = get_repos
218
218
219
219
220 def is_valid_repo(repo_name, base_path, scm=None):
220 def is_valid_repo(repo_name, base_path, scm=None):
221 """
221 """
222 Returns True if given path is a valid repository False otherwise.
222 Returns True if given path is a valid repository False otherwise.
223 If scm param is given also compare if given scm is the same as expected
223 If scm param is given also compare if given scm is the same as expected
224 from scm parameter
224 from scm parameter
225
225
226 :param repo_name:
226 :param repo_name:
227 :param base_path:
227 :param base_path:
228 :param scm:
228 :param scm:
229
229
230 :return True: if given path is a valid repository
230 :return True: if given path is a valid repository
231 """
231 """
232 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
232 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
233
233
234 try:
234 try:
235 scm_ = get_scm(full_path)
235 scm_ = get_scm(full_path)
236 if scm:
236 if scm:
237 return scm_[0] == scm
237 return scm_[0] == scm
238 return True
238 return True
239 except VCSError:
239 except VCSError:
240 return False
240 return False
241
241
242
242
243 def is_valid_repos_group(repos_group_name, base_path):
243 def is_valid_repos_group(repos_group_name, base_path):
244 """
244 """
245 Returns True if given path is a repos group False otherwise
245 Returns True if given path is a repos group False otherwise
246
246
247 :param repo_name:
247 :param repo_name:
248 :param base_path:
248 :param base_path:
249 """
249 """
250 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
250 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
251
251
252 # check if it's not a repo
252 # check if it's not a repo
253 if is_valid_repo(repos_group_name, base_path):
253 if is_valid_repo(repos_group_name, base_path):
254 return False
254 return False
255
255
256 try:
256 try:
257 # we need to check bare git repos at higher level
257 # we need to check bare git repos at higher level
258 # since we might match branches/hooks/info/objects or possible
258 # since we might match branches/hooks/info/objects or possible
259 # other things inside bare git repo
259 # other things inside bare git repo
260 get_scm(os.path.dirname(full_path))
260 get_scm(os.path.dirname(full_path))
261 return False
261 return False
262 except VCSError:
262 except VCSError:
263 pass
263 pass
264
264
265 # check if it's a valid path
265 # check if it's a valid path
266 if os.path.isdir(full_path):
266 if os.path.isdir(full_path):
267 return True
267 return True
268
268
269 return False
269 return False
270
270
271
271
272 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
272 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
273 while True:
273 while True:
274 ok = raw_input(prompt)
274 ok = raw_input(prompt)
275 if ok in ('y', 'ye', 'yes'):
275 if ok in ('y', 'ye', 'yes'):
276 return True
276 return True
277 if ok in ('n', 'no', 'nop', 'nope'):
277 if ok in ('n', 'no', 'nop', 'nope'):
278 return False
278 return False
279 retries = retries - 1
279 retries = retries - 1
280 if retries < 0:
280 if retries < 0:
281 raise IOError
281 raise IOError
282 print complaint
282 print complaint
283
283
284 #propagated from mercurial documentation
284 #propagated from mercurial documentation
285 ui_sections = ['alias', 'auth',
285 ui_sections = ['alias', 'auth',
286 'decode/encode', 'defaults',
286 'decode/encode', 'defaults',
287 'diff', 'email',
287 'diff', 'email',
288 'extensions', 'format',
288 'extensions', 'format',
289 'merge-patterns', 'merge-tools',
289 'merge-patterns', 'merge-tools',
290 'hooks', 'http_proxy',
290 'hooks', 'http_proxy',
291 'smtp', 'patch',
291 'smtp', 'patch',
292 'paths', 'profiling',
292 'paths', 'profiling',
293 'server', 'trusted',
293 'server', 'trusted',
294 'ui', 'web', ]
294 'ui', 'web', ]
295
295
296
296
297 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
297 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
298 """
298 """
299 A function that will read python rc files or database
299 A function that will read python rc files or database
300 and make an mercurial ui object from read options
300 and make an mercurial ui object from read options
301
301
302 :param path: path to mercurial config file
302 :param path: path to mercurial config file
303 :param checkpaths: check the path
303 :param checkpaths: check the path
304 :param read_from: read from 'file' or 'db'
304 :param read_from: read from 'file' or 'db'
305 """
305 """
306
306
307 baseui = ui.ui()
307 baseui = ui.ui()
308
308
309 # clean the baseui object
309 # clean the baseui object
310 baseui._ocfg = config.config()
310 baseui._ocfg = config.config()
311 baseui._ucfg = config.config()
311 baseui._ucfg = config.config()
312 baseui._tcfg = config.config()
312 baseui._tcfg = config.config()
313
313
314 if read_from == 'file':
314 if read_from == 'file':
315 if not os.path.isfile(path):
315 if not os.path.isfile(path):
316 log.debug('hgrc file is not present at %s, skipping...' % path)
316 log.debug('hgrc file is not present at %s, skipping...' % path)
317 return False
317 return False
318 log.debug('reading hgrc from %s' % path)
318 log.debug('reading hgrc from %s' % path)
319 cfg = config.config()
319 cfg = config.config()
320 cfg.read(path)
320 cfg.read(path)
321 for section in ui_sections:
321 for section in ui_sections:
322 for k, v in cfg.items(section):
322 for k, v in cfg.items(section):
323 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
323 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
324 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
324 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
325
325
326 elif read_from == 'db':
326 elif read_from == 'db':
327 sa = meta.Session()
327 sa = meta.Session()
328 ret = sa.query(RhodeCodeUi)\
328 ret = sa.query(RhodeCodeUi)\
329 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
329 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
330 .all()
330 .all()
331
331
332 hg_ui = ret
332 hg_ui = ret
333 for ui_ in hg_ui:
333 for ui_ in hg_ui:
334 if ui_.ui_active:
334 if ui_.ui_active:
335 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
335 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
336 ui_.ui_key, ui_.ui_value)
336 ui_.ui_key, ui_.ui_value)
337 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
337 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
338 safe_str(ui_.ui_value))
338 safe_str(ui_.ui_value))
339 if ui_.ui_key == 'push_ssl':
339 if ui_.ui_key == 'push_ssl':
340 # force set push_ssl requirement to False, rhodecode
340 # force set push_ssl requirement to False, rhodecode
341 # handles that
341 # handles that
342 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
342 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
343 False)
343 False)
344 if clear_session:
344 if clear_session:
345 meta.Session.remove()
345 meta.Session.remove()
346 return baseui
346 return baseui
347
347
348
348
349 def set_rhodecode_config(config):
349 def set_rhodecode_config(config):
350 """
350 """
351 Updates pylons config with new settings from database
351 Updates pylons config with new settings from database
352
352
353 :param config:
353 :param config:
354 """
354 """
355 hgsettings = RhodeCodeSetting.get_app_settings()
355 hgsettings = RhodeCodeSetting.get_app_settings()
356
356
357 for k, v in hgsettings.items():
357 for k, v in hgsettings.items():
358 config[k] = v
358 config[k] = v
359
359
360
360
361 def invalidate_cache(cache_key, *args):
361 def invalidate_cache(cache_key, *args):
362 """
362 """
363 Puts cache invalidation task into db for
363 Puts cache invalidation task into db for
364 further global cache invalidation
364 further global cache invalidation
365 """
365 """
366
366
367 from rhodecode.model.scm import ScmModel
367 from rhodecode.model.scm import ScmModel
368
368
369 if cache_key.startswith('get_repo_cached_'):
369 if cache_key.startswith('get_repo_cached_'):
370 name = cache_key.split('get_repo_cached_')[-1]
370 name = cache_key.split('get_repo_cached_')[-1]
371 ScmModel().mark_for_invalidation(name)
371 ScmModel().mark_for_invalidation(name)
372
372
373
373
374 def map_groups(path):
374 def map_groups(path):
375 """
375 """
376 Given a full path to a repository, create all nested groups that this
376 Given a full path to a repository, create all nested groups that this
377 repo is inside. This function creates parent-child relationships between
377 repo is inside. This function creates parent-child relationships between
378 groups and creates default perms for all new groups.
378 groups and creates default perms for all new groups.
379
379
380 :param paths: full path to repository
380 :param paths: full path to repository
381 """
381 """
382 sa = meta.Session()
382 sa = meta.Session()
383 groups = path.split(Repository.url_sep())
383 groups = path.split(Repository.url_sep())
384 parent = None
384 parent = None
385 group = None
385 group = None
386
386
387 # last element is repo in nested groups structure
387 # last element is repo in nested groups structure
388 groups = groups[:-1]
388 groups = groups[:-1]
389 rgm = ReposGroupModel(sa)
389 rgm = ReposGroupModel(sa)
390 for lvl, group_name in enumerate(groups):
390 for lvl, group_name in enumerate(groups):
391 group_name = '/'.join(groups[:lvl] + [group_name])
391 group_name = '/'.join(groups[:lvl] + [group_name])
392 group = RepoGroup.get_by_group_name(group_name)
392 group = RepoGroup.get_by_group_name(group_name)
393 desc = '%s group' % group_name
393 desc = '%s group' % group_name
394
394
395 # skip folders that are now removed repos
395 # skip folders that are now removed repos
396 if REMOVED_REPO_PAT.match(group_name):
396 if REMOVED_REPO_PAT.match(group_name):
397 break
397 break
398
398
399 if group is None:
399 if group is None:
400 log.debug('creating group level: %s group_name: %s' % (lvl,
400 log.debug('creating group level: %s group_name: %s' % (lvl,
401 group_name))
401 group_name))
402 group = RepoGroup(group_name, parent)
402 group = RepoGroup(group_name, parent)
403 group.group_description = desc
403 group.group_description = desc
404 sa.add(group)
404 sa.add(group)
405 rgm._create_default_perms(group)
405 rgm._create_default_perms(group)
406 sa.flush()
406 sa.flush()
407 parent = group
407 parent = group
408 return group
408 return group
409
409
410
410
411 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
411 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
412 install_git_hook=False):
412 install_git_hook=False):
413 """
413 """
414 maps all repos given in initial_repo_list, non existing repositories
414 maps all repos given in initial_repo_list, non existing repositories
415 are created, if remove_obsolete is True it also check for db entries
415 are created, if remove_obsolete is True it also check for db entries
416 that are not in initial_repo_list and removes them.
416 that are not in initial_repo_list and removes them.
417
417
418 :param initial_repo_list: list of repositories found by scanning methods
418 :param initial_repo_list: list of repositories found by scanning methods
419 :param remove_obsolete: check for obsolete entries in database
419 :param remove_obsolete: check for obsolete entries in database
420 :param install_git_hook: if this is True, also check and install githook
420 :param install_git_hook: if this is True, also check and install githook
421 for a repo if missing
421 for a repo if missing
422 """
422 """
423 from rhodecode.model.repo import RepoModel
423 from rhodecode.model.repo import RepoModel
424 from rhodecode.model.scm import ScmModel
424 from rhodecode.model.scm import ScmModel
425 sa = meta.Session()
425 sa = meta.Session()
426 rm = RepoModel()
426 rm = RepoModel()
427 user = sa.query(User).filter(User.admin == True).first()
427 user = sa.query(User).filter(User.admin == True).first()
428 if user is None:
428 if user is None:
429 raise Exception('Missing administrative account!')
429 raise Exception('Missing administrative account!')
430 added = []
430 added = []
431
431
432 # # clear cache keys
432 # # clear cache keys
433 # log.debug("Clearing cache keys now...")
433 # log.debug("Clearing cache keys now...")
434 # CacheInvalidation.clear_cache()
434 # CacheInvalidation.clear_cache()
435 # sa.commit()
435 # sa.commit()
436
436
437 ##creation defaults
437 ##creation defaults
438 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
438 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
439 enable_statistics = defs.get('repo_enable_statistics')
439 enable_statistics = defs.get('repo_enable_statistics')
440 enable_locking = defs.get('repo_enable_locking')
440 enable_locking = defs.get('repo_enable_locking')
441 enable_downloads = defs.get('repo_enable_downloads')
441 enable_downloads = defs.get('repo_enable_downloads')
442 private = defs.get('repo_private')
442 private = defs.get('repo_private')
443
443
444 for name, repo in initial_repo_list.items():
444 for name, repo in initial_repo_list.items():
445 group = map_groups(name)
445 group = map_groups(name)
446 db_repo = rm.get_by_repo_name(name)
446 db_repo = rm.get_by_repo_name(name)
447 # found repo that is on filesystem not in RhodeCode database
447 # found repo that is on filesystem not in RhodeCode database
448 if not db_repo:
448 if not db_repo:
449 log.info('repository %s not found, creating now' % name)
449 log.info('repository %s not found, creating now' % name)
450 added.append(name)
450 added.append(name)
451 desc = (repo.description
451 desc = (repo.description
452 if repo.description != 'unknown'
452 if repo.description != 'unknown'
453 else '%s repository' % name)
453 else '%s repository' % name)
454
454
455 new_repo = rm.create_repo(
455 new_repo = rm.create_repo(
456 repo_name=name,
456 repo_name=name,
457 repo_type=repo.alias,
457 repo_type=repo.alias,
458 description=desc,
458 description=desc,
459 repos_group=getattr(group, 'group_id', None),
459 repos_group=getattr(group, 'group_id', None),
460 owner=user,
460 owner=user,
461 just_db=True,
461 just_db=True,
462 enable_locking=enable_locking,
462 enable_locking=enable_locking,
463 enable_downloads=enable_downloads,
463 enable_downloads=enable_downloads,
464 enable_statistics=enable_statistics,
464 enable_statistics=enable_statistics,
465 private=private
465 private=private
466 )
466 )
467 # we added that repo just now, and make sure it has githook
467 # we added that repo just now, and make sure it has githook
468 # installed
468 # installed
469 if new_repo.repo_type == 'git':
469 if new_repo.repo_type == 'git':
470 ScmModel().install_git_hook(new_repo.scm_instance)
470 ScmModel().install_git_hook(new_repo.scm_instance)
471 new_repo.update_changeset_cache()
471 new_repo.update_changeset_cache()
472 elif install_git_hook:
472 elif install_git_hook:
473 if db_repo.repo_type == 'git':
473 if db_repo.repo_type == 'git':
474 ScmModel().install_git_hook(db_repo.scm_instance)
474 ScmModel().install_git_hook(db_repo.scm_instance)
475 # during starting install all cache keys for all repositories in the
475 # during starting install all cache keys for all repositories in the
476 # system, this will register all repos and multiple instances
476 # system, this will register all repos and multiple instances
477 key, _prefix, _org_key = CacheInvalidation._get_key(name)
477 key, _prefix, _org_key = CacheInvalidation._get_key(name)
478 CacheInvalidation.invalidate(name)
478 CacheInvalidation.invalidate(name)
479 log.debug("Creating a cache key for %s, instance_id %s"
479 log.debug("Creating a cache key for %s, instance_id %s"
480 % (name, _prefix or 'unknown'))
480 % (name, _prefix or 'unknown'))
481
481
482 sa.commit()
482 sa.commit()
483 removed = []
483 removed = []
484 if remove_obsolete:
484 if remove_obsolete:
485 # remove from database those repositories that are not in the filesystem
485 # remove from database those repositories that are not in the filesystem
486 for repo in sa.query(Repository).all():
486 for repo in sa.query(Repository).all():
487 if repo.repo_name not in initial_repo_list.keys():
487 if repo.repo_name not in initial_repo_list.keys():
488 log.debug("Removing non-existing repository found in db `%s`" %
488 log.debug("Removing non-existing repository found in db `%s`" %
489 repo.repo_name)
489 repo.repo_name)
490 try:
490 try:
491 sa.delete(repo)
491 sa.delete(repo)
492 sa.commit()
492 sa.commit()
493 removed.append(repo.repo_name)
493 removed.append(repo.repo_name)
494 except:
494 except:
495 #don't hold further removals on error
495 #don't hold further removals on error
496 log.error(traceback.format_exc())
496 log.error(traceback.format_exc())
497 sa.rollback()
497 sa.rollback()
498
498
499 return added, removed
499 return added, removed
500
500
501
501
502 # set cache regions for beaker so celery can utilise it
502 # set cache regions for beaker so celery can utilise it
503 def add_cache(settings):
503 def add_cache(settings):
504 cache_settings = {'regions': None}
504 cache_settings = {'regions': None}
505 for key in settings.keys():
505 for key in settings.keys():
506 for prefix in ['beaker.cache.', 'cache.']:
506 for prefix in ['beaker.cache.', 'cache.']:
507 if key.startswith(prefix):
507 if key.startswith(prefix):
508 name = key.split(prefix)[1].strip()
508 name = key.split(prefix)[1].strip()
509 cache_settings[name] = settings[key].strip()
509 cache_settings[name] = settings[key].strip()
510 if cache_settings['regions']:
510 if cache_settings['regions']:
511 for region in cache_settings['regions'].split(','):
511 for region in cache_settings['regions'].split(','):
512 region = region.strip()
512 region = region.strip()
513 region_settings = {}
513 region_settings = {}
514 for key, value in cache_settings.items():
514 for key, value in cache_settings.items():
515 if key.startswith(region):
515 if key.startswith(region):
516 region_settings[key.split('.')[1]] = value
516 region_settings[key.split('.')[1]] = value
517 region_settings['expire'] = int(region_settings.get('expire',
517 region_settings['expire'] = int(region_settings.get('expire',
518 60))
518 60))
519 region_settings.setdefault('lock_dir',
519 region_settings.setdefault('lock_dir',
520 cache_settings.get('lock_dir'))
520 cache_settings.get('lock_dir'))
521 region_settings.setdefault('data_dir',
521 region_settings.setdefault('data_dir',
522 cache_settings.get('data_dir'))
522 cache_settings.get('data_dir'))
523
523
524 if 'type' not in region_settings:
524 if 'type' not in region_settings:
525 region_settings['type'] = cache_settings.get('type',
525 region_settings['type'] = cache_settings.get('type',
526 'memory')
526 'memory')
527 beaker.cache.cache_regions[region] = region_settings
527 beaker.cache.cache_regions[region] = region_settings
528
528
529
529
530 def load_rcextensions(root_path):
530 def load_rcextensions(root_path):
531 import rhodecode
531 import rhodecode
532 from rhodecode.config import conf
532 from rhodecode.config import conf
533
533
534 path = os.path.join(root_path, 'rcextensions', '__init__.py')
534 path = os.path.join(root_path, 'rcextensions', '__init__.py')
535 if os.path.isfile(path):
535 if os.path.isfile(path):
536 rcext = create_module('rc', path)
536 rcext = create_module('rc', path)
537 EXT = rhodecode.EXTENSIONS = rcext
537 EXT = rhodecode.EXTENSIONS = rcext
538 log.debug('Found rcextensions now loading %s...' % rcext)
538 log.debug('Found rcextensions now loading %s...' % rcext)
539
539
540 # Additional mappings that are not present in the pygments lexers
540 # Additional mappings that are not present in the pygments lexers
541 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
541 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
542
542
543 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
543 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
544
544
545 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
545 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
546 log.debug('settings custom INDEX_EXTENSIONS')
546 log.debug('settings custom INDEX_EXTENSIONS')
547 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
547 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
548
548
549 #ADDITIONAL MAPPINGS
549 #ADDITIONAL MAPPINGS
550 log.debug('adding extra into INDEX_EXTENSIONS')
550 log.debug('adding extra into INDEX_EXTENSIONS')
551 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
551 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
552
552
553 # auto check if the module is not missing any data, set to default if is
554 # this will help autoupdate new feature of rcext module
555 from rhodecode.config import rcextensions
556 for k in dir(rcextensions):
557 if not k.startswith('_') and not hasattr(EXT, k):
558 setattr(EXT, k, getattr(rcextensions, k))
559
560
561 def get_custom_lexer(extension):
562 """
563 returns a custom lexer if it's defined in rcextensions module, or None
564 if there's no custom lexer defined
565 """
566 import rhodecode
567 from pygments import lexers
568 #check if we didn't define this extension as other lexer
569 if rhodecode.EXTENSIONS and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
570 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
571 return lexers.get_lexer_by_name(_lexer_name)
572
553
573
554 #==============================================================================
574 #==============================================================================
555 # TEST FUNCTIONS AND CREATORS
575 # TEST FUNCTIONS AND CREATORS
556 #==============================================================================
576 #==============================================================================
557 def create_test_index(repo_location, config, full_index):
577 def create_test_index(repo_location, config, full_index):
558 """
578 """
559 Makes default test index
579 Makes default test index
560
580
561 :param config: test config
581 :param config: test config
562 :param full_index:
582 :param full_index:
563 """
583 """
564
584
565 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
585 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
566 from rhodecode.lib.pidlock import DaemonLock, LockHeld
586 from rhodecode.lib.pidlock import DaemonLock, LockHeld
567
587
568 repo_location = repo_location
588 repo_location = repo_location
569
589
570 index_location = os.path.join(config['app_conf']['index_dir'])
590 index_location = os.path.join(config['app_conf']['index_dir'])
571 if not os.path.exists(index_location):
591 if not os.path.exists(index_location):
572 os.makedirs(index_location)
592 os.makedirs(index_location)
573
593
574 try:
594 try:
575 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
595 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
576 WhooshIndexingDaemon(index_location=index_location,
596 WhooshIndexingDaemon(index_location=index_location,
577 repo_location=repo_location)\
597 repo_location=repo_location)\
578 .run(full_index=full_index)
598 .run(full_index=full_index)
579 l.release()
599 l.release()
580 except LockHeld:
600 except LockHeld:
581 pass
601 pass
582
602
583
603
584 def create_test_env(repos_test_path, config):
604 def create_test_env(repos_test_path, config):
585 """
605 """
586 Makes a fresh database and
606 Makes a fresh database and
587 install test repository into tmp dir
607 install test repository into tmp dir
588 """
608 """
589 from rhodecode.lib.db_manage import DbManage
609 from rhodecode.lib.db_manage import DbManage
590 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
610 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
591
611
592 # PART ONE create db
612 # PART ONE create db
593 dbconf = config['sqlalchemy.db1.url']
613 dbconf = config['sqlalchemy.db1.url']
594 log.debug('making test db %s' % dbconf)
614 log.debug('making test db %s' % dbconf)
595
615
596 # create test dir if it doesn't exist
616 # create test dir if it doesn't exist
597 if not os.path.isdir(repos_test_path):
617 if not os.path.isdir(repos_test_path):
598 log.debug('Creating testdir %s' % repos_test_path)
618 log.debug('Creating testdir %s' % repos_test_path)
599 os.makedirs(repos_test_path)
619 os.makedirs(repos_test_path)
600
620
601 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
621 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
602 tests=True)
622 tests=True)
603 dbmanage.create_tables(override=True)
623 dbmanage.create_tables(override=True)
604 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
624 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
605 dbmanage.create_default_user()
625 dbmanage.create_default_user()
606 dbmanage.admin_prompt()
626 dbmanage.admin_prompt()
607 dbmanage.create_permissions()
627 dbmanage.create_permissions()
608 dbmanage.populate_default_permissions()
628 dbmanage.populate_default_permissions()
609 Session().commit()
629 Session().commit()
610 # PART TWO make test repo
630 # PART TWO make test repo
611 log.debug('making test vcs repositories')
631 log.debug('making test vcs repositories')
612
632
613 idx_path = config['app_conf']['index_dir']
633 idx_path = config['app_conf']['index_dir']
614 data_path = config['app_conf']['cache_dir']
634 data_path = config['app_conf']['cache_dir']
615
635
616 #clean index and data
636 #clean index and data
617 if idx_path and os.path.exists(idx_path):
637 if idx_path and os.path.exists(idx_path):
618 log.debug('remove %s' % idx_path)
638 log.debug('remove %s' % idx_path)
619 shutil.rmtree(idx_path)
639 shutil.rmtree(idx_path)
620
640
621 if data_path and os.path.exists(data_path):
641 if data_path and os.path.exists(data_path):
622 log.debug('remove %s' % data_path)
642 log.debug('remove %s' % data_path)
623 shutil.rmtree(data_path)
643 shutil.rmtree(data_path)
624
644
625 #CREATE DEFAULT TEST REPOS
645 #CREATE DEFAULT TEST REPOS
626 cur_dir = dn(dn(abspath(__file__)))
646 cur_dir = dn(dn(abspath(__file__)))
627 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
647 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
628 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
648 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
629 tar.close()
649 tar.close()
630
650
631 cur_dir = dn(dn(abspath(__file__)))
651 cur_dir = dn(dn(abspath(__file__)))
632 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
652 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
633 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
653 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
634 tar.close()
654 tar.close()
635
655
636 #LOAD VCS test stuff
656 #LOAD VCS test stuff
637 from rhodecode.tests.vcs import setup_package
657 from rhodecode.tests.vcs import setup_package
638 setup_package()
658 setup_package()
639
659
640
660
641 #==============================================================================
661 #==============================================================================
642 # PASTER COMMANDS
662 # PASTER COMMANDS
643 #==============================================================================
663 #==============================================================================
644 class BasePasterCommand(Command):
664 class BasePasterCommand(Command):
645 """
665 """
646 Abstract Base Class for paster commands.
666 Abstract Base Class for paster commands.
647
667
648 The celery commands are somewhat aggressive about loading
668 The celery commands are somewhat aggressive about loading
649 celery.conf, and since our module sets the `CELERY_LOADER`
669 celery.conf, and since our module sets the `CELERY_LOADER`
650 environment variable to our loader, we have to bootstrap a bit and
670 environment variable to our loader, we have to bootstrap a bit and
651 make sure we've had a chance to load the pylons config off of the
671 make sure we've had a chance to load the pylons config off of the
652 command line, otherwise everything fails.
672 command line, otherwise everything fails.
653 """
673 """
654 min_args = 1
674 min_args = 1
655 min_args_error = "Please provide a paster config file as an argument."
675 min_args_error = "Please provide a paster config file as an argument."
656 takes_config_file = 1
676 takes_config_file = 1
657 requires_config_file = True
677 requires_config_file = True
658
678
659 def notify_msg(self, msg, log=False):
679 def notify_msg(self, msg, log=False):
660 """Make a notification to user, additionally if logger is passed
680 """Make a notification to user, additionally if logger is passed
661 it logs this action using given logger
681 it logs this action using given logger
662
682
663 :param msg: message that will be printed to user
683 :param msg: message that will be printed to user
664 :param log: logging instance, to use to additionally log this message
684 :param log: logging instance, to use to additionally log this message
665
685
666 """
686 """
667 if log and isinstance(log, logging):
687 if log and isinstance(log, logging):
668 log(msg)
688 log(msg)
669
689
670 def run(self, args):
690 def run(self, args):
671 """
691 """
672 Overrides Command.run
692 Overrides Command.run
673
693
674 Checks for a config file argument and loads it.
694 Checks for a config file argument and loads it.
675 """
695 """
676 if len(args) < self.min_args:
696 if len(args) < self.min_args:
677 raise BadCommand(
697 raise BadCommand(
678 self.min_args_error % {'min_args': self.min_args,
698 self.min_args_error % {'min_args': self.min_args,
679 'actual_args': len(args)})
699 'actual_args': len(args)})
680
700
681 # Decrement because we're going to lob off the first argument.
701 # Decrement because we're going to lob off the first argument.
682 # @@ This is hacky
702 # @@ This is hacky
683 self.min_args -= 1
703 self.min_args -= 1
684 self.bootstrap_config(args[0])
704 self.bootstrap_config(args[0])
685 self.update_parser()
705 self.update_parser()
686 return super(BasePasterCommand, self).run(args[1:])
706 return super(BasePasterCommand, self).run(args[1:])
687
707
688 def update_parser(self):
708 def update_parser(self):
689 """
709 """
690 Abstract method. Allows for the class's parser to be updated
710 Abstract method. Allows for the class's parser to be updated
691 before the superclass's `run` method is called. Necessary to
711 before the superclass's `run` method is called. Necessary to
692 allow options/arguments to be passed through to the underlying
712 allow options/arguments to be passed through to the underlying
693 celery command.
713 celery command.
694 """
714 """
695 raise NotImplementedError("Abstract Method.")
715 raise NotImplementedError("Abstract Method.")
696
716
697 def bootstrap_config(self, conf):
717 def bootstrap_config(self, conf):
698 """
718 """
699 Loads the pylons configuration.
719 Loads the pylons configuration.
700 """
720 """
701 from pylons import config as pylonsconfig
721 from pylons import config as pylonsconfig
702
722
703 self.path_to_ini_file = os.path.realpath(conf)
723 self.path_to_ini_file = os.path.realpath(conf)
704 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
724 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
705 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
725 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
706
726
707 def _init_session(self):
727 def _init_session(self):
708 """
728 """
709 Inits SqlAlchemy Session
729 Inits SqlAlchemy Session
710 """
730 """
711 logging.config.fileConfig(self.path_to_ini_file)
731 logging.config.fileConfig(self.path_to_ini_file)
712 from pylons import config
732 from pylons import config
713 from rhodecode.model import init_model
733 from rhodecode.model import init_model
714 from rhodecode.lib.utils2 import engine_from_config
734 from rhodecode.lib.utils2 import engine_from_config
715
735
716 #get to remove repos !!
736 #get to remove repos !!
717 add_cache(config)
737 add_cache(config)
718 engine = engine_from_config(config, 'sqlalchemy.db1.')
738 engine = engine_from_config(config, 'sqlalchemy.db1.')
719 init_model(engine)
739 init_model(engine)
720
740
721
741
722 def check_git_version():
742 def check_git_version():
723 """
743 """
724 Checks what version of git is installed in system, and issues a warning
744 Checks what version of git is installed in system, and issues a warning
725 if it's too old for RhodeCode to properly work.
745 if it's too old for RhodeCode to properly work.
726 """
746 """
727 import subprocess
747 import subprocess
728 from distutils.version import StrictVersion
748 from distutils.version import StrictVersion
729 from rhodecode import BACKENDS
749 from rhodecode import BACKENDS
730
750
731 p = subprocess.Popen('git --version', shell=True,
751 p = subprocess.Popen('git --version', shell=True,
732 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
752 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
733 stdout, stderr = p.communicate()
753 stdout, stderr = p.communicate()
734 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
754 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
735 if len(ver.split('.')) > 3:
755 if len(ver.split('.')) > 3:
736 #StrictVersion needs to be only 3 element type
756 #StrictVersion needs to be only 3 element type
737 ver = '.'.join(ver.split('.')[:3])
757 ver = '.'.join(ver.split('.')[:3])
738 try:
758 try:
739 _ver = StrictVersion(ver)
759 _ver = StrictVersion(ver)
740 except:
760 except:
741 _ver = StrictVersion('0.0.0')
761 _ver = StrictVersion('0.0.0')
742 stderr = traceback.format_exc()
762 stderr = traceback.format_exc()
743
763
744 req_ver = '1.7.4'
764 req_ver = '1.7.4'
745 to_old_git = False
765 to_old_git = False
746 if _ver < StrictVersion(req_ver):
766 if _ver < StrictVersion(req_ver):
747 to_old_git = True
767 to_old_git = True
748
768
749 if 'git' in BACKENDS:
769 if 'git' in BACKENDS:
750 log.debug('GIT version detected: %s' % stdout)
770 log.debug('GIT version detected: %s' % stdout)
751 if stderr:
771 if stderr:
752 log.warning('Unable to detect git version org error was:%r' % stderr)
772 log.warning('Unable to detect git version org error was:%r' % stderr)
753 elif to_old_git:
773 elif to_old_git:
754 log.warning('RhodeCode detected git version %s, which is too old '
774 log.warning('RhodeCode detected git version %s, which is too old '
755 'for the system to function properly. Make sure '
775 'for the system to function properly. Make sure '
756 'its version is at least %s' % (ver, req_ver))
776 'its version is at least %s' % (ver, req_ver))
757 return _ver
777 return _ver
758
778
759
779
760 @decorator.decorator
780 @decorator.decorator
761 def jsonify(func, *args, **kwargs):
781 def jsonify(func, *args, **kwargs):
762 """Action decorator that formats output for JSON
782 """Action decorator that formats output for JSON
763
783
764 Given a function that will return content, this decorator will turn
784 Given a function that will return content, this decorator will turn
765 the result into JSON, with a content-type of 'application/json' and
785 the result into JSON, with a content-type of 'application/json' and
766 output it.
786 output it.
767
787
768 """
788 """
769 from pylons.decorators.util import get_pylons
789 from pylons.decorators.util import get_pylons
770 from rhodecode.lib.ext_json import json
790 from rhodecode.lib.ext_json import json
771 pylons = get_pylons(args)
791 pylons = get_pylons(args)
772 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
792 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
773 data = func(*args, **kwargs)
793 data = func(*args, **kwargs)
774 if isinstance(data, (list, tuple)):
794 if isinstance(data, (list, tuple)):
775 msg = "JSON responses with Array envelopes are susceptible to " \
795 msg = "JSON responses with Array envelopes are susceptible to " \
776 "cross-site data leak attacks, see " \
796 "cross-site data leak attacks, see " \
777 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
797 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
778 warnings.warn(msg, Warning, 2)
798 warnings.warn(msg, Warning, 2)
779 log.warning(msg)
799 log.warning(msg)
780 log.debug("Returning JSON wrapped action output")
800 log.debug("Returning JSON wrapped action output")
781 return json.dumps(data, encoding='utf-8')
801 return json.dumps(data, encoding='utf-8')
General Comments 0
You need to be logged in to leave comments. Login now