##// END OF EJS Templates
meta-tags: cleanup support for metatags....
marcink -
r2091:90bf6103 default
parent child Browse files
Show More
@@ -1,2047 +1,2062 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Helper functions
22 Helper functions
23
23
24 Consists of functions to typically be used within templates, but also
24 Consists of functions to typically be used within templates, but also
25 available to Controllers. This module is available to both as 'h'.
25 available to Controllers. This module is available to both as 'h'.
26 """
26 """
27
27
28 import random
28 import random
29 import hashlib
29 import hashlib
30 import StringIO
30 import StringIO
31 import urllib
31 import urllib
32 import math
32 import math
33 import logging
33 import logging
34 import re
34 import re
35 import urlparse
35 import urlparse
36 import time
36 import time
37 import string
37 import string
38 import hashlib
38 import hashlib
39 from collections import OrderedDict
39 from collections import OrderedDict
40
40
41 import pygments
41 import pygments
42 import itertools
42 import itertools
43 import fnmatch
43 import fnmatch
44
44
45 from datetime import datetime
45 from datetime import datetime
46 from functools import partial
46 from functools import partial
47 from pygments.formatters.html import HtmlFormatter
47 from pygments.formatters.html import HtmlFormatter
48 from pygments import highlight as code_highlight
48 from pygments import highlight as code_highlight
49 from pygments.lexers import (
49 from pygments.lexers import (
50 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
50 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
51
51
52 from pyramid.threadlocal import get_current_request
52 from pyramid.threadlocal import get_current_request
53
53
54 from webhelpers.html import literal, HTML, escape
54 from webhelpers.html import literal, HTML, escape
55 from webhelpers.html.tools import *
55 from webhelpers.html.tools import *
56 from webhelpers.html.builder import make_tag
56 from webhelpers.html.builder import make_tag
57 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
57 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
58 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
58 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
59 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
59 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
60 submit, text, password, textarea, title, ul, xml_declaration, radio
60 submit, text, password, textarea, title, ul, xml_declaration, radio
61 from webhelpers.html.tools import auto_link, button_to, highlight, \
61 from webhelpers.html.tools import auto_link, button_to, highlight, \
62 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
62 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
63 from webhelpers.pylonslib import Flash as _Flash
63 from webhelpers.pylonslib import Flash as _Flash
64 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
64 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
65 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
65 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
66 replace_whitespace, urlify, truncate, wrap_paragraphs
66 replace_whitespace, urlify, truncate, wrap_paragraphs
67 from webhelpers.date import time_ago_in_words
67 from webhelpers.date import time_ago_in_words
68 from webhelpers.paginate import Page as _Page
68 from webhelpers.paginate import Page as _Page
69 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
69 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
70 convert_boolean_attrs, NotGiven, _make_safe_id_component
70 convert_boolean_attrs, NotGiven, _make_safe_id_component
71 from webhelpers2.number import format_byte_size
71 from webhelpers2.number import format_byte_size
72
72
73 from rhodecode.lib.action_parser import action_parser
73 from rhodecode.lib.action_parser import action_parser
74 from rhodecode.lib.ext_json import json
74 from rhodecode.lib.ext_json import json
75 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
75 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
76 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
76 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
77 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
77 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
78 AttributeDict, safe_int, md5, md5_safe
78 AttributeDict, safe_int, md5, md5_safe
79 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
79 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
80 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
80 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
81 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
81 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
82 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
82 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
83 from rhodecode.model.changeset_status import ChangesetStatusModel
83 from rhodecode.model.changeset_status import ChangesetStatusModel
84 from rhodecode.model.db import Permission, User, Repository
84 from rhodecode.model.db import Permission, User, Repository
85 from rhodecode.model.repo_group import RepoGroupModel
85 from rhodecode.model.repo_group import RepoGroupModel
86 from rhodecode.model.settings import IssueTrackerSettingsModel
86 from rhodecode.model.settings import IssueTrackerSettingsModel
87
87
88 log = logging.getLogger(__name__)
88 log = logging.getLogger(__name__)
89
89
90
90
91 DEFAULT_USER = User.DEFAULT_USER
91 DEFAULT_USER = User.DEFAULT_USER
92 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
92 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
93
93
94
94
95 def url(*args, **kw):
95 def url(*args, **kw):
96 from pylons import url as pylons_url
96 from pylons import url as pylons_url
97 return pylons_url(*args, **kw)
97 return pylons_url(*args, **kw)
98
98
99
99
100 def pylons_url_current(*args, **kw):
100 def pylons_url_current(*args, **kw):
101 """
101 """
102 This function overrides pylons.url.current() which returns the current
102 This function overrides pylons.url.current() which returns the current
103 path so that it will also work from a pyramid only context. This
103 path so that it will also work from a pyramid only context. This
104 should be removed once port to pyramid is complete.
104 should be removed once port to pyramid is complete.
105 """
105 """
106 from pylons import url as pylons_url
106 from pylons import url as pylons_url
107 if not args and not kw:
107 if not args and not kw:
108 request = get_current_request()
108 request = get_current_request()
109 return request.path
109 return request.path
110 return pylons_url.current(*args, **kw)
110 return pylons_url.current(*args, **kw)
111
111
112 url.current = pylons_url_current
112 url.current = pylons_url_current
113
113
114
114
115 def url_replace(**qargs):
115 def url_replace(**qargs):
116 """ Returns the current request url while replacing query string args """
116 """ Returns the current request url while replacing query string args """
117
117
118 request = get_current_request()
118 request = get_current_request()
119 new_args = request.GET.mixed()
119 new_args = request.GET.mixed()
120 new_args.update(qargs)
120 new_args.update(qargs)
121 return url('', **new_args)
121 return url('', **new_args)
122
122
123
123
124 def asset(path, ver=None, **kwargs):
124 def asset(path, ver=None, **kwargs):
125 """
125 """
126 Helper to generate a static asset file path for rhodecode assets
126 Helper to generate a static asset file path for rhodecode assets
127
127
128 eg. h.asset('images/image.png', ver='3923')
128 eg. h.asset('images/image.png', ver='3923')
129
129
130 :param path: path of asset
130 :param path: path of asset
131 :param ver: optional version query param to append as ?ver=
131 :param ver: optional version query param to append as ?ver=
132 """
132 """
133 request = get_current_request()
133 request = get_current_request()
134 query = {}
134 query = {}
135 query.update(kwargs)
135 query.update(kwargs)
136 if ver:
136 if ver:
137 query = {'ver': ver}
137 query = {'ver': ver}
138 return request.static_path(
138 return request.static_path(
139 'rhodecode:public/{}'.format(path), _query=query)
139 'rhodecode:public/{}'.format(path), _query=query)
140
140
141
141
142 default_html_escape_table = {
142 default_html_escape_table = {
143 ord('&'): u'&amp;',
143 ord('&'): u'&amp;',
144 ord('<'): u'&lt;',
144 ord('<'): u'&lt;',
145 ord('>'): u'&gt;',
145 ord('>'): u'&gt;',
146 ord('"'): u'&quot;',
146 ord('"'): u'&quot;',
147 ord("'"): u'&#39;',
147 ord("'"): u'&#39;',
148 }
148 }
149
149
150
150
151 def html_escape(text, html_escape_table=default_html_escape_table):
151 def html_escape(text, html_escape_table=default_html_escape_table):
152 """Produce entities within text."""
152 """Produce entities within text."""
153 return text.translate(html_escape_table)
153 return text.translate(html_escape_table)
154
154
155
155
156 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
156 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
157 """
157 """
158 Truncate string ``s`` at the first occurrence of ``sub``.
158 Truncate string ``s`` at the first occurrence of ``sub``.
159
159
160 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
160 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
161 """
161 """
162 suffix_if_chopped = suffix_if_chopped or ''
162 suffix_if_chopped = suffix_if_chopped or ''
163 pos = s.find(sub)
163 pos = s.find(sub)
164 if pos == -1:
164 if pos == -1:
165 return s
165 return s
166
166
167 if inclusive:
167 if inclusive:
168 pos += len(sub)
168 pos += len(sub)
169
169
170 chopped = s[:pos]
170 chopped = s[:pos]
171 left = s[pos:].strip()
171 left = s[pos:].strip()
172
172
173 if left and suffix_if_chopped:
173 if left and suffix_if_chopped:
174 chopped += suffix_if_chopped
174 chopped += suffix_if_chopped
175
175
176 return chopped
176 return chopped
177
177
178
178
179 def shorter(text, size=20):
179 def shorter(text, size=20):
180 postfix = '...'
180 postfix = '...'
181 if len(text) > size:
181 if len(text) > size:
182 return text[:size - len(postfix)] + postfix
182 return text[:size - len(postfix)] + postfix
183 return text
183 return text
184
184
185
185
186 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
186 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
187 """
187 """
188 Reset button
188 Reset button
189 """
189 """
190 _set_input_attrs(attrs, type, name, value)
190 _set_input_attrs(attrs, type, name, value)
191 _set_id_attr(attrs, id, name)
191 _set_id_attr(attrs, id, name)
192 convert_boolean_attrs(attrs, ["disabled"])
192 convert_boolean_attrs(attrs, ["disabled"])
193 return HTML.input(**attrs)
193 return HTML.input(**attrs)
194
194
195 reset = _reset
195 reset = _reset
196 safeid = _make_safe_id_component
196 safeid = _make_safe_id_component
197
197
198
198
199 def branding(name, length=40):
199 def branding(name, length=40):
200 return truncate(name, length, indicator="")
200 return truncate(name, length, indicator="")
201
201
202
202
203 def FID(raw_id, path):
203 def FID(raw_id, path):
204 """
204 """
205 Creates a unique ID for filenode based on it's hash of path and commit
205 Creates a unique ID for filenode based on it's hash of path and commit
206 it's safe to use in urls
206 it's safe to use in urls
207
207
208 :param raw_id:
208 :param raw_id:
209 :param path:
209 :param path:
210 """
210 """
211
211
212 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
212 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
213
213
214
214
215 class _GetError(object):
215 class _GetError(object):
216 """Get error from form_errors, and represent it as span wrapped error
216 """Get error from form_errors, and represent it as span wrapped error
217 message
217 message
218
218
219 :param field_name: field to fetch errors for
219 :param field_name: field to fetch errors for
220 :param form_errors: form errors dict
220 :param form_errors: form errors dict
221 """
221 """
222
222
223 def __call__(self, field_name, form_errors):
223 def __call__(self, field_name, form_errors):
224 tmpl = """<span class="error_msg">%s</span>"""
224 tmpl = """<span class="error_msg">%s</span>"""
225 if form_errors and field_name in form_errors:
225 if form_errors and field_name in form_errors:
226 return literal(tmpl % form_errors.get(field_name))
226 return literal(tmpl % form_errors.get(field_name))
227
227
228 get_error = _GetError()
228 get_error = _GetError()
229
229
230
230
231 class _ToolTip(object):
231 class _ToolTip(object):
232
232
233 def __call__(self, tooltip_title, trim_at=50):
233 def __call__(self, tooltip_title, trim_at=50):
234 """
234 """
235 Special function just to wrap our text into nice formatted
235 Special function just to wrap our text into nice formatted
236 autowrapped text
236 autowrapped text
237
237
238 :param tooltip_title:
238 :param tooltip_title:
239 """
239 """
240 tooltip_title = escape(tooltip_title)
240 tooltip_title = escape(tooltip_title)
241 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
241 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
242 return tooltip_title
242 return tooltip_title
243 tooltip = _ToolTip()
243 tooltip = _ToolTip()
244
244
245
245
246 def files_breadcrumbs(repo_name, commit_id, file_path):
246 def files_breadcrumbs(repo_name, commit_id, file_path):
247 if isinstance(file_path, str):
247 if isinstance(file_path, str):
248 file_path = safe_unicode(file_path)
248 file_path = safe_unicode(file_path)
249
249
250 # TODO: johbo: Is this always a url like path, or is this operating
250 # TODO: johbo: Is this always a url like path, or is this operating
251 # system dependent?
251 # system dependent?
252 path_segments = file_path.split('/')
252 path_segments = file_path.split('/')
253
253
254 repo_name_html = escape(repo_name)
254 repo_name_html = escape(repo_name)
255 if len(path_segments) == 1 and path_segments[0] == '':
255 if len(path_segments) == 1 and path_segments[0] == '':
256 url_segments = [repo_name_html]
256 url_segments = [repo_name_html]
257 else:
257 else:
258 url_segments = [
258 url_segments = [
259 link_to(
259 link_to(
260 repo_name_html,
260 repo_name_html,
261 route_path(
261 route_path(
262 'repo_files',
262 'repo_files',
263 repo_name=repo_name,
263 repo_name=repo_name,
264 commit_id=commit_id,
264 commit_id=commit_id,
265 f_path=''),
265 f_path=''),
266 class_='pjax-link')]
266 class_='pjax-link')]
267
267
268 last_cnt = len(path_segments) - 1
268 last_cnt = len(path_segments) - 1
269 for cnt, segment in enumerate(path_segments):
269 for cnt, segment in enumerate(path_segments):
270 if not segment:
270 if not segment:
271 continue
271 continue
272 segment_html = escape(segment)
272 segment_html = escape(segment)
273
273
274 if cnt != last_cnt:
274 if cnt != last_cnt:
275 url_segments.append(
275 url_segments.append(
276 link_to(
276 link_to(
277 segment_html,
277 segment_html,
278 route_path(
278 route_path(
279 'repo_files',
279 'repo_files',
280 repo_name=repo_name,
280 repo_name=repo_name,
281 commit_id=commit_id,
281 commit_id=commit_id,
282 f_path='/'.join(path_segments[:cnt + 1])),
282 f_path='/'.join(path_segments[:cnt + 1])),
283 class_='pjax-link'))
283 class_='pjax-link'))
284 else:
284 else:
285 url_segments.append(segment_html)
285 url_segments.append(segment_html)
286
286
287 return literal('/'.join(url_segments))
287 return literal('/'.join(url_segments))
288
288
289
289
290 class CodeHtmlFormatter(HtmlFormatter):
290 class CodeHtmlFormatter(HtmlFormatter):
291 """
291 """
292 My code Html Formatter for source codes
292 My code Html Formatter for source codes
293 """
293 """
294
294
295 def wrap(self, source, outfile):
295 def wrap(self, source, outfile):
296 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
296 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
297
297
298 def _wrap_code(self, source):
298 def _wrap_code(self, source):
299 for cnt, it in enumerate(source):
299 for cnt, it in enumerate(source):
300 i, t = it
300 i, t = it
301 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
301 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
302 yield i, t
302 yield i, t
303
303
304 def _wrap_tablelinenos(self, inner):
304 def _wrap_tablelinenos(self, inner):
305 dummyoutfile = StringIO.StringIO()
305 dummyoutfile = StringIO.StringIO()
306 lncount = 0
306 lncount = 0
307 for t, line in inner:
307 for t, line in inner:
308 if t:
308 if t:
309 lncount += 1
309 lncount += 1
310 dummyoutfile.write(line)
310 dummyoutfile.write(line)
311
311
312 fl = self.linenostart
312 fl = self.linenostart
313 mw = len(str(lncount + fl - 1))
313 mw = len(str(lncount + fl - 1))
314 sp = self.linenospecial
314 sp = self.linenospecial
315 st = self.linenostep
315 st = self.linenostep
316 la = self.lineanchors
316 la = self.lineanchors
317 aln = self.anchorlinenos
317 aln = self.anchorlinenos
318 nocls = self.noclasses
318 nocls = self.noclasses
319 if sp:
319 if sp:
320 lines = []
320 lines = []
321
321
322 for i in range(fl, fl + lncount):
322 for i in range(fl, fl + lncount):
323 if i % st == 0:
323 if i % st == 0:
324 if i % sp == 0:
324 if i % sp == 0:
325 if aln:
325 if aln:
326 lines.append('<a href="#%s%d" class="special">%*d</a>' %
326 lines.append('<a href="#%s%d" class="special">%*d</a>' %
327 (la, i, mw, i))
327 (la, i, mw, i))
328 else:
328 else:
329 lines.append('<span class="special">%*d</span>' % (mw, i))
329 lines.append('<span class="special">%*d</span>' % (mw, i))
330 else:
330 else:
331 if aln:
331 if aln:
332 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
332 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
333 else:
333 else:
334 lines.append('%*d' % (mw, i))
334 lines.append('%*d' % (mw, i))
335 else:
335 else:
336 lines.append('')
336 lines.append('')
337 ls = '\n'.join(lines)
337 ls = '\n'.join(lines)
338 else:
338 else:
339 lines = []
339 lines = []
340 for i in range(fl, fl + lncount):
340 for i in range(fl, fl + lncount):
341 if i % st == 0:
341 if i % st == 0:
342 if aln:
342 if aln:
343 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
343 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
344 else:
344 else:
345 lines.append('%*d' % (mw, i))
345 lines.append('%*d' % (mw, i))
346 else:
346 else:
347 lines.append('')
347 lines.append('')
348 ls = '\n'.join(lines)
348 ls = '\n'.join(lines)
349
349
350 # in case you wonder about the seemingly redundant <div> here: since the
350 # in case you wonder about the seemingly redundant <div> here: since the
351 # content in the other cell also is wrapped in a div, some browsers in
351 # content in the other cell also is wrapped in a div, some browsers in
352 # some configurations seem to mess up the formatting...
352 # some configurations seem to mess up the formatting...
353 if nocls:
353 if nocls:
354 yield 0, ('<table class="%stable">' % self.cssclass +
354 yield 0, ('<table class="%stable">' % self.cssclass +
355 '<tr><td><div class="linenodiv" '
355 '<tr><td><div class="linenodiv" '
356 'style="background-color: #f0f0f0; padding-right: 10px">'
356 'style="background-color: #f0f0f0; padding-right: 10px">'
357 '<pre style="line-height: 125%">' +
357 '<pre style="line-height: 125%">' +
358 ls + '</pre></div></td><td id="hlcode" class="code">')
358 ls + '</pre></div></td><td id="hlcode" class="code">')
359 else:
359 else:
360 yield 0, ('<table class="%stable">' % self.cssclass +
360 yield 0, ('<table class="%stable">' % self.cssclass +
361 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
361 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
362 ls + '</pre></div></td><td id="hlcode" class="code">')
362 ls + '</pre></div></td><td id="hlcode" class="code">')
363 yield 0, dummyoutfile.getvalue()
363 yield 0, dummyoutfile.getvalue()
364 yield 0, '</td></tr></table>'
364 yield 0, '</td></tr></table>'
365
365
366
366
367 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
367 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
368 def __init__(self, **kw):
368 def __init__(self, **kw):
369 # only show these line numbers if set
369 # only show these line numbers if set
370 self.only_lines = kw.pop('only_line_numbers', [])
370 self.only_lines = kw.pop('only_line_numbers', [])
371 self.query_terms = kw.pop('query_terms', [])
371 self.query_terms = kw.pop('query_terms', [])
372 self.max_lines = kw.pop('max_lines', 5)
372 self.max_lines = kw.pop('max_lines', 5)
373 self.line_context = kw.pop('line_context', 3)
373 self.line_context = kw.pop('line_context', 3)
374 self.url = kw.pop('url', None)
374 self.url = kw.pop('url', None)
375
375
376 super(CodeHtmlFormatter, self).__init__(**kw)
376 super(CodeHtmlFormatter, self).__init__(**kw)
377
377
378 def _wrap_code(self, source):
378 def _wrap_code(self, source):
379 for cnt, it in enumerate(source):
379 for cnt, it in enumerate(source):
380 i, t = it
380 i, t = it
381 t = '<pre>%s</pre>' % t
381 t = '<pre>%s</pre>' % t
382 yield i, t
382 yield i, t
383
383
384 def _wrap_tablelinenos(self, inner):
384 def _wrap_tablelinenos(self, inner):
385 yield 0, '<table class="code-highlight %stable">' % self.cssclass
385 yield 0, '<table class="code-highlight %stable">' % self.cssclass
386
386
387 last_shown_line_number = 0
387 last_shown_line_number = 0
388 current_line_number = 1
388 current_line_number = 1
389
389
390 for t, line in inner:
390 for t, line in inner:
391 if not t:
391 if not t:
392 yield t, line
392 yield t, line
393 continue
393 continue
394
394
395 if current_line_number in self.only_lines:
395 if current_line_number in self.only_lines:
396 if last_shown_line_number + 1 != current_line_number:
396 if last_shown_line_number + 1 != current_line_number:
397 yield 0, '<tr>'
397 yield 0, '<tr>'
398 yield 0, '<td class="line">...</td>'
398 yield 0, '<td class="line">...</td>'
399 yield 0, '<td id="hlcode" class="code"></td>'
399 yield 0, '<td id="hlcode" class="code"></td>'
400 yield 0, '</tr>'
400 yield 0, '</tr>'
401
401
402 yield 0, '<tr>'
402 yield 0, '<tr>'
403 if self.url:
403 if self.url:
404 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
404 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
405 self.url, current_line_number, current_line_number)
405 self.url, current_line_number, current_line_number)
406 else:
406 else:
407 yield 0, '<td class="line"><a href="">%i</a></td>' % (
407 yield 0, '<td class="line"><a href="">%i</a></td>' % (
408 current_line_number)
408 current_line_number)
409 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
409 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
410 yield 0, '</tr>'
410 yield 0, '</tr>'
411
411
412 last_shown_line_number = current_line_number
412 last_shown_line_number = current_line_number
413
413
414 current_line_number += 1
414 current_line_number += 1
415
415
416
416
417 yield 0, '</table>'
417 yield 0, '</table>'
418
418
419
419
420 def extract_phrases(text_query):
420 def extract_phrases(text_query):
421 """
421 """
422 Extracts phrases from search term string making sure phrases
422 Extracts phrases from search term string making sure phrases
423 contained in double quotes are kept together - and discarding empty values
423 contained in double quotes are kept together - and discarding empty values
424 or fully whitespace values eg.
424 or fully whitespace values eg.
425
425
426 'some text "a phrase" more' => ['some', 'text', 'a phrase', 'more']
426 'some text "a phrase" more' => ['some', 'text', 'a phrase', 'more']
427
427
428 """
428 """
429
429
430 in_phrase = False
430 in_phrase = False
431 buf = ''
431 buf = ''
432 phrases = []
432 phrases = []
433 for char in text_query:
433 for char in text_query:
434 if in_phrase:
434 if in_phrase:
435 if char == '"': # end phrase
435 if char == '"': # end phrase
436 phrases.append(buf)
436 phrases.append(buf)
437 buf = ''
437 buf = ''
438 in_phrase = False
438 in_phrase = False
439 continue
439 continue
440 else:
440 else:
441 buf += char
441 buf += char
442 continue
442 continue
443 else:
443 else:
444 if char == '"': # start phrase
444 if char == '"': # start phrase
445 in_phrase = True
445 in_phrase = True
446 phrases.append(buf)
446 phrases.append(buf)
447 buf = ''
447 buf = ''
448 continue
448 continue
449 elif char == ' ':
449 elif char == ' ':
450 phrases.append(buf)
450 phrases.append(buf)
451 buf = ''
451 buf = ''
452 continue
452 continue
453 else:
453 else:
454 buf += char
454 buf += char
455
455
456 phrases.append(buf)
456 phrases.append(buf)
457 phrases = [phrase.strip() for phrase in phrases if phrase.strip()]
457 phrases = [phrase.strip() for phrase in phrases if phrase.strip()]
458 return phrases
458 return phrases
459
459
460
460
461 def get_matching_offsets(text, phrases):
461 def get_matching_offsets(text, phrases):
462 """
462 """
463 Returns a list of string offsets in `text` that the list of `terms` match
463 Returns a list of string offsets in `text` that the list of `terms` match
464
464
465 >>> get_matching_offsets('some text here', ['some', 'here'])
465 >>> get_matching_offsets('some text here', ['some', 'here'])
466 [(0, 4), (10, 14)]
466 [(0, 4), (10, 14)]
467
467
468 """
468 """
469 offsets = []
469 offsets = []
470 for phrase in phrases:
470 for phrase in phrases:
471 for match in re.finditer(phrase, text):
471 for match in re.finditer(phrase, text):
472 offsets.append((match.start(), match.end()))
472 offsets.append((match.start(), match.end()))
473
473
474 return offsets
474 return offsets
475
475
476
476
477 def normalize_text_for_matching(x):
477 def normalize_text_for_matching(x):
478 """
478 """
479 Replaces all non alnum characters to spaces and lower cases the string,
479 Replaces all non alnum characters to spaces and lower cases the string,
480 useful for comparing two text strings without punctuation
480 useful for comparing two text strings without punctuation
481 """
481 """
482 return re.sub(r'[^\w]', ' ', x.lower())
482 return re.sub(r'[^\w]', ' ', x.lower())
483
483
484
484
485 def get_matching_line_offsets(lines, terms):
485 def get_matching_line_offsets(lines, terms):
486 """ Return a set of `lines` indices (starting from 1) matching a
486 """ Return a set of `lines` indices (starting from 1) matching a
487 text search query, along with `context` lines above/below matching lines
487 text search query, along with `context` lines above/below matching lines
488
488
489 :param lines: list of strings representing lines
489 :param lines: list of strings representing lines
490 :param terms: search term string to match in lines eg. 'some text'
490 :param terms: search term string to match in lines eg. 'some text'
491 :param context: number of lines above/below a matching line to add to result
491 :param context: number of lines above/below a matching line to add to result
492 :param max_lines: cut off for lines of interest
492 :param max_lines: cut off for lines of interest
493 eg.
493 eg.
494
494
495 text = '''
495 text = '''
496 words words words
496 words words words
497 words words words
497 words words words
498 some text some
498 some text some
499 words words words
499 words words words
500 words words words
500 words words words
501 text here what
501 text here what
502 '''
502 '''
503 get_matching_line_offsets(text, 'text', context=1)
503 get_matching_line_offsets(text, 'text', context=1)
504 {3: [(5, 9)], 6: [(0, 4)]]
504 {3: [(5, 9)], 6: [(0, 4)]]
505
505
506 """
506 """
507 matching_lines = {}
507 matching_lines = {}
508 phrases = [normalize_text_for_matching(phrase)
508 phrases = [normalize_text_for_matching(phrase)
509 for phrase in extract_phrases(terms)]
509 for phrase in extract_phrases(terms)]
510
510
511 for line_index, line in enumerate(lines, start=1):
511 for line_index, line in enumerate(lines, start=1):
512 match_offsets = get_matching_offsets(
512 match_offsets = get_matching_offsets(
513 normalize_text_for_matching(line), phrases)
513 normalize_text_for_matching(line), phrases)
514 if match_offsets:
514 if match_offsets:
515 matching_lines[line_index] = match_offsets
515 matching_lines[line_index] = match_offsets
516
516
517 return matching_lines
517 return matching_lines
518
518
519
519
520 def hsv_to_rgb(h, s, v):
520 def hsv_to_rgb(h, s, v):
521 """ Convert hsv color values to rgb """
521 """ Convert hsv color values to rgb """
522
522
523 if s == 0.0:
523 if s == 0.0:
524 return v, v, v
524 return v, v, v
525 i = int(h * 6.0) # XXX assume int() truncates!
525 i = int(h * 6.0) # XXX assume int() truncates!
526 f = (h * 6.0) - i
526 f = (h * 6.0) - i
527 p = v * (1.0 - s)
527 p = v * (1.0 - s)
528 q = v * (1.0 - s * f)
528 q = v * (1.0 - s * f)
529 t = v * (1.0 - s * (1.0 - f))
529 t = v * (1.0 - s * (1.0 - f))
530 i = i % 6
530 i = i % 6
531 if i == 0:
531 if i == 0:
532 return v, t, p
532 return v, t, p
533 if i == 1:
533 if i == 1:
534 return q, v, p
534 return q, v, p
535 if i == 2:
535 if i == 2:
536 return p, v, t
536 return p, v, t
537 if i == 3:
537 if i == 3:
538 return p, q, v
538 return p, q, v
539 if i == 4:
539 if i == 4:
540 return t, p, v
540 return t, p, v
541 if i == 5:
541 if i == 5:
542 return v, p, q
542 return v, p, q
543
543
544
544
545 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
545 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
546 """
546 """
547 Generator for getting n of evenly distributed colors using
547 Generator for getting n of evenly distributed colors using
548 hsv color and golden ratio. It always return same order of colors
548 hsv color and golden ratio. It always return same order of colors
549
549
550 :param n: number of colors to generate
550 :param n: number of colors to generate
551 :param saturation: saturation of returned colors
551 :param saturation: saturation of returned colors
552 :param lightness: lightness of returned colors
552 :param lightness: lightness of returned colors
553 :returns: RGB tuple
553 :returns: RGB tuple
554 """
554 """
555
555
556 golden_ratio = 0.618033988749895
556 golden_ratio = 0.618033988749895
557 h = 0.22717784590367374
557 h = 0.22717784590367374
558
558
559 for _ in xrange(n):
559 for _ in xrange(n):
560 h += golden_ratio
560 h += golden_ratio
561 h %= 1
561 h %= 1
562 HSV_tuple = [h, saturation, lightness]
562 HSV_tuple = [h, saturation, lightness]
563 RGB_tuple = hsv_to_rgb(*HSV_tuple)
563 RGB_tuple = hsv_to_rgb(*HSV_tuple)
564 yield map(lambda x: str(int(x * 256)), RGB_tuple)
564 yield map(lambda x: str(int(x * 256)), RGB_tuple)
565
565
566
566
567 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
567 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
568 """
568 """
569 Returns a function which when called with an argument returns a unique
569 Returns a function which when called with an argument returns a unique
570 color for that argument, eg.
570 color for that argument, eg.
571
571
572 :param n: number of colors to generate
572 :param n: number of colors to generate
573 :param saturation: saturation of returned colors
573 :param saturation: saturation of returned colors
574 :param lightness: lightness of returned colors
574 :param lightness: lightness of returned colors
575 :returns: css RGB string
575 :returns: css RGB string
576
576
577 >>> color_hash = color_hasher()
577 >>> color_hash = color_hasher()
578 >>> color_hash('hello')
578 >>> color_hash('hello')
579 'rgb(34, 12, 59)'
579 'rgb(34, 12, 59)'
580 >>> color_hash('hello')
580 >>> color_hash('hello')
581 'rgb(34, 12, 59)'
581 'rgb(34, 12, 59)'
582 >>> color_hash('other')
582 >>> color_hash('other')
583 'rgb(90, 224, 159)'
583 'rgb(90, 224, 159)'
584 """
584 """
585
585
586 color_dict = {}
586 color_dict = {}
587 cgenerator = unique_color_generator(
587 cgenerator = unique_color_generator(
588 saturation=saturation, lightness=lightness)
588 saturation=saturation, lightness=lightness)
589
589
590 def get_color_string(thing):
590 def get_color_string(thing):
591 if thing in color_dict:
591 if thing in color_dict:
592 col = color_dict[thing]
592 col = color_dict[thing]
593 else:
593 else:
594 col = color_dict[thing] = cgenerator.next()
594 col = color_dict[thing] = cgenerator.next()
595 return "rgb(%s)" % (', '.join(col))
595 return "rgb(%s)" % (', '.join(col))
596
596
597 return get_color_string
597 return get_color_string
598
598
599
599
600 def get_lexer_safe(mimetype=None, filepath=None):
600 def get_lexer_safe(mimetype=None, filepath=None):
601 """
601 """
602 Tries to return a relevant pygments lexer using mimetype/filepath name,
602 Tries to return a relevant pygments lexer using mimetype/filepath name,
603 defaulting to plain text if none could be found
603 defaulting to plain text if none could be found
604 """
604 """
605 lexer = None
605 lexer = None
606 try:
606 try:
607 if mimetype:
607 if mimetype:
608 lexer = get_lexer_for_mimetype(mimetype)
608 lexer = get_lexer_for_mimetype(mimetype)
609 if not lexer:
609 if not lexer:
610 lexer = get_lexer_for_filename(filepath)
610 lexer = get_lexer_for_filename(filepath)
611 except pygments.util.ClassNotFound:
611 except pygments.util.ClassNotFound:
612 pass
612 pass
613
613
614 if not lexer:
614 if not lexer:
615 lexer = get_lexer_by_name('text')
615 lexer = get_lexer_by_name('text')
616
616
617 return lexer
617 return lexer
618
618
619
619
620 def get_lexer_for_filenode(filenode):
620 def get_lexer_for_filenode(filenode):
621 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
621 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
622 return lexer
622 return lexer
623
623
624
624
625 def pygmentize(filenode, **kwargs):
625 def pygmentize(filenode, **kwargs):
626 """
626 """
627 pygmentize function using pygments
627 pygmentize function using pygments
628
628
629 :param filenode:
629 :param filenode:
630 """
630 """
631 lexer = get_lexer_for_filenode(filenode)
631 lexer = get_lexer_for_filenode(filenode)
632 return literal(code_highlight(filenode.content, lexer,
632 return literal(code_highlight(filenode.content, lexer,
633 CodeHtmlFormatter(**kwargs)))
633 CodeHtmlFormatter(**kwargs)))
634
634
635
635
636 def is_following_repo(repo_name, user_id):
636 def is_following_repo(repo_name, user_id):
637 from rhodecode.model.scm import ScmModel
637 from rhodecode.model.scm import ScmModel
638 return ScmModel().is_following_repo(repo_name, user_id)
638 return ScmModel().is_following_repo(repo_name, user_id)
639
639
640
640
641 class _Message(object):
641 class _Message(object):
642 """A message returned by ``Flash.pop_messages()``.
642 """A message returned by ``Flash.pop_messages()``.
643
643
644 Converting the message to a string returns the message text. Instances
644 Converting the message to a string returns the message text. Instances
645 also have the following attributes:
645 also have the following attributes:
646
646
647 * ``message``: the message text.
647 * ``message``: the message text.
648 * ``category``: the category specified when the message was created.
648 * ``category``: the category specified when the message was created.
649 """
649 """
650
650
651 def __init__(self, category, message):
651 def __init__(self, category, message):
652 self.category = category
652 self.category = category
653 self.message = message
653 self.message = message
654
654
655 def __str__(self):
655 def __str__(self):
656 return self.message
656 return self.message
657
657
658 __unicode__ = __str__
658 __unicode__ = __str__
659
659
660 def __html__(self):
660 def __html__(self):
661 return escape(safe_unicode(self.message))
661 return escape(safe_unicode(self.message))
662
662
663
663
664 class Flash(_Flash):
664 class Flash(_Flash):
665
665
666 def pop_messages(self, request=None):
666 def pop_messages(self, request=None):
667 """Return all accumulated messages and delete them from the session.
667 """Return all accumulated messages and delete them from the session.
668
668
669 The return value is a list of ``Message`` objects.
669 The return value is a list of ``Message`` objects.
670 """
670 """
671 messages = []
671 messages = []
672
672
673 if request:
673 if request:
674 session = request.session
674 session = request.session
675 else:
675 else:
676 from pylons import session
676 from pylons import session
677
677
678 # Pop the 'old' pylons flash messages. They are tuples of the form
678 # Pop the 'old' pylons flash messages. They are tuples of the form
679 # (category, message)
679 # (category, message)
680 for cat, msg in session.pop(self.session_key, []):
680 for cat, msg in session.pop(self.session_key, []):
681 messages.append(_Message(cat, msg))
681 messages.append(_Message(cat, msg))
682
682
683 # Pop the 'new' pyramid flash messages for each category as list
683 # Pop the 'new' pyramid flash messages for each category as list
684 # of strings.
684 # of strings.
685 for cat in self.categories:
685 for cat in self.categories:
686 for msg in session.pop_flash(queue=cat):
686 for msg in session.pop_flash(queue=cat):
687 messages.append(_Message(cat, msg))
687 messages.append(_Message(cat, msg))
688 # Map messages from the default queue to the 'notice' category.
688 # Map messages from the default queue to the 'notice' category.
689 for msg in session.pop_flash():
689 for msg in session.pop_flash():
690 messages.append(_Message('notice', msg))
690 messages.append(_Message('notice', msg))
691
691
692 session.save()
692 session.save()
693 return messages
693 return messages
694
694
695 def json_alerts(self, request=None):
695 def json_alerts(self, request=None):
696 payloads = []
696 payloads = []
697 messages = flash.pop_messages(request=request)
697 messages = flash.pop_messages(request=request)
698 if messages:
698 if messages:
699 for message in messages:
699 for message in messages:
700 subdata = {}
700 subdata = {}
701 if hasattr(message.message, 'rsplit'):
701 if hasattr(message.message, 'rsplit'):
702 flash_data = message.message.rsplit('|DELIM|', 1)
702 flash_data = message.message.rsplit('|DELIM|', 1)
703 org_message = flash_data[0]
703 org_message = flash_data[0]
704 if len(flash_data) > 1:
704 if len(flash_data) > 1:
705 subdata = json.loads(flash_data[1])
705 subdata = json.loads(flash_data[1])
706 else:
706 else:
707 org_message = message.message
707 org_message = message.message
708 payloads.append({
708 payloads.append({
709 'message': {
709 'message': {
710 'message': u'{}'.format(org_message),
710 'message': u'{}'.format(org_message),
711 'level': message.category,
711 'level': message.category,
712 'force': True,
712 'force': True,
713 'subdata': subdata
713 'subdata': subdata
714 }
714 }
715 })
715 })
716 return json.dumps(payloads)
716 return json.dumps(payloads)
717
717
718 flash = Flash()
718 flash = Flash()
719
719
720 #==============================================================================
720 #==============================================================================
721 # SCM FILTERS available via h.
721 # SCM FILTERS available via h.
722 #==============================================================================
722 #==============================================================================
723 from rhodecode.lib.vcs.utils import author_name, author_email
723 from rhodecode.lib.vcs.utils import author_name, author_email
724 from rhodecode.lib.utils2 import credentials_filter, age as _age
724 from rhodecode.lib.utils2 import credentials_filter, age as _age
725 from rhodecode.model.db import User, ChangesetStatus
725 from rhodecode.model.db import User, ChangesetStatus
726
726
727 age = _age
727 age = _age
728 capitalize = lambda x: x.capitalize()
728 capitalize = lambda x: x.capitalize()
729 email = author_email
729 email = author_email
730 short_id = lambda x: x[:12]
730 short_id = lambda x: x[:12]
731 hide_credentials = lambda x: ''.join(credentials_filter(x))
731 hide_credentials = lambda x: ''.join(credentials_filter(x))
732
732
733
733
734 def age_component(datetime_iso, value=None, time_is_local=False):
734 def age_component(datetime_iso, value=None, time_is_local=False):
735 title = value or format_date(datetime_iso)
735 title = value or format_date(datetime_iso)
736 tzinfo = '+00:00'
736 tzinfo = '+00:00'
737
737
738 # detect if we have a timezone info, otherwise, add it
738 # detect if we have a timezone info, otherwise, add it
739 if isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
739 if isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
740 if time_is_local:
740 if time_is_local:
741 tzinfo = time.strftime("+%H:%M",
741 tzinfo = time.strftime("+%H:%M",
742 time.gmtime(
742 time.gmtime(
743 (datetime.now() - datetime.utcnow()).seconds + 1
743 (datetime.now() - datetime.utcnow()).seconds + 1
744 )
744 )
745 )
745 )
746
746
747 return literal(
747 return literal(
748 '<time class="timeago tooltip" '
748 '<time class="timeago tooltip" '
749 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
749 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
750 datetime_iso, title, tzinfo))
750 datetime_iso, title, tzinfo))
751
751
752
752
753 def _shorten_commit_id(commit_id):
753 def _shorten_commit_id(commit_id):
754 from rhodecode import CONFIG
754 from rhodecode import CONFIG
755 def_len = safe_int(CONFIG.get('rhodecode_show_sha_length', 12))
755 def_len = safe_int(CONFIG.get('rhodecode_show_sha_length', 12))
756 return commit_id[:def_len]
756 return commit_id[:def_len]
757
757
758
758
759 def show_id(commit):
759 def show_id(commit):
760 """
760 """
761 Configurable function that shows ID
761 Configurable function that shows ID
762 by default it's r123:fffeeefffeee
762 by default it's r123:fffeeefffeee
763
763
764 :param commit: commit instance
764 :param commit: commit instance
765 """
765 """
766 from rhodecode import CONFIG
766 from rhodecode import CONFIG
767 show_idx = str2bool(CONFIG.get('rhodecode_show_revision_number', True))
767 show_idx = str2bool(CONFIG.get('rhodecode_show_revision_number', True))
768
768
769 raw_id = _shorten_commit_id(commit.raw_id)
769 raw_id = _shorten_commit_id(commit.raw_id)
770 if show_idx:
770 if show_idx:
771 return 'r%s:%s' % (commit.idx, raw_id)
771 return 'r%s:%s' % (commit.idx, raw_id)
772 else:
772 else:
773 return '%s' % (raw_id, )
773 return '%s' % (raw_id, )
774
774
775
775
776 def format_date(date):
776 def format_date(date):
777 """
777 """
778 use a standardized formatting for dates used in RhodeCode
778 use a standardized formatting for dates used in RhodeCode
779
779
780 :param date: date/datetime object
780 :param date: date/datetime object
781 :return: formatted date
781 :return: formatted date
782 """
782 """
783
783
784 if date:
784 if date:
785 _fmt = "%a, %d %b %Y %H:%M:%S"
785 _fmt = "%a, %d %b %Y %H:%M:%S"
786 return safe_unicode(date.strftime(_fmt))
786 return safe_unicode(date.strftime(_fmt))
787
787
788 return u""
788 return u""
789
789
790
790
791 class _RepoChecker(object):
791 class _RepoChecker(object):
792
792
793 def __init__(self, backend_alias):
793 def __init__(self, backend_alias):
794 self._backend_alias = backend_alias
794 self._backend_alias = backend_alias
795
795
796 def __call__(self, repository):
796 def __call__(self, repository):
797 if hasattr(repository, 'alias'):
797 if hasattr(repository, 'alias'):
798 _type = repository.alias
798 _type = repository.alias
799 elif hasattr(repository, 'repo_type'):
799 elif hasattr(repository, 'repo_type'):
800 _type = repository.repo_type
800 _type = repository.repo_type
801 else:
801 else:
802 _type = repository
802 _type = repository
803 return _type == self._backend_alias
803 return _type == self._backend_alias
804
804
805 is_git = _RepoChecker('git')
805 is_git = _RepoChecker('git')
806 is_hg = _RepoChecker('hg')
806 is_hg = _RepoChecker('hg')
807 is_svn = _RepoChecker('svn')
807 is_svn = _RepoChecker('svn')
808
808
809
809
810 def get_repo_type_by_name(repo_name):
810 def get_repo_type_by_name(repo_name):
811 repo = Repository.get_by_repo_name(repo_name)
811 repo = Repository.get_by_repo_name(repo_name)
812 return repo.repo_type
812 return repo.repo_type
813
813
814
814
815 def is_svn_without_proxy(repository):
815 def is_svn_without_proxy(repository):
816 if is_svn(repository):
816 if is_svn(repository):
817 from rhodecode.model.settings import VcsSettingsModel
817 from rhodecode.model.settings import VcsSettingsModel
818 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
818 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
819 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
819 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
820 return False
820 return False
821
821
822
822
823 def discover_user(author):
823 def discover_user(author):
824 """
824 """
825 Tries to discover RhodeCode User based on the autho string. Author string
825 Tries to discover RhodeCode User based on the autho string. Author string
826 is typically `FirstName LastName <email@address.com>`
826 is typically `FirstName LastName <email@address.com>`
827 """
827 """
828
828
829 # if author is already an instance use it for extraction
829 # if author is already an instance use it for extraction
830 if isinstance(author, User):
830 if isinstance(author, User):
831 return author
831 return author
832
832
833 # Valid email in the attribute passed, see if they're in the system
833 # Valid email in the attribute passed, see if they're in the system
834 _email = author_email(author)
834 _email = author_email(author)
835 if _email != '':
835 if _email != '':
836 user = User.get_by_email(_email, case_insensitive=True, cache=True)
836 user = User.get_by_email(_email, case_insensitive=True, cache=True)
837 if user is not None:
837 if user is not None:
838 return user
838 return user
839
839
840 # Maybe it's a username, we try to extract it and fetch by username ?
840 # Maybe it's a username, we try to extract it and fetch by username ?
841 _author = author_name(author)
841 _author = author_name(author)
842 user = User.get_by_username(_author, case_insensitive=True, cache=True)
842 user = User.get_by_username(_author, case_insensitive=True, cache=True)
843 if user is not None:
843 if user is not None:
844 return user
844 return user
845
845
846 return None
846 return None
847
847
848
848
849 def email_or_none(author):
849 def email_or_none(author):
850 # extract email from the commit string
850 # extract email from the commit string
851 _email = author_email(author)
851 _email = author_email(author)
852
852
853 # If we have an email, use it, otherwise
853 # If we have an email, use it, otherwise
854 # see if it contains a username we can get an email from
854 # see if it contains a username we can get an email from
855 if _email != '':
855 if _email != '':
856 return _email
856 return _email
857 else:
857 else:
858 user = User.get_by_username(
858 user = User.get_by_username(
859 author_name(author), case_insensitive=True, cache=True)
859 author_name(author), case_insensitive=True, cache=True)
860
860
861 if user is not None:
861 if user is not None:
862 return user.email
862 return user.email
863
863
864 # No valid email, not a valid user in the system, none!
864 # No valid email, not a valid user in the system, none!
865 return None
865 return None
866
866
867
867
868 def link_to_user(author, length=0, **kwargs):
868 def link_to_user(author, length=0, **kwargs):
869 user = discover_user(author)
869 user = discover_user(author)
870 # user can be None, but if we have it already it means we can re-use it
870 # user can be None, but if we have it already it means we can re-use it
871 # in the person() function, so we save 1 intensive-query
871 # in the person() function, so we save 1 intensive-query
872 if user:
872 if user:
873 author = user
873 author = user
874
874
875 display_person = person(author, 'username_or_name_or_email')
875 display_person = person(author, 'username_or_name_or_email')
876 if length:
876 if length:
877 display_person = shorter(display_person, length)
877 display_person = shorter(display_person, length)
878
878
879 if user:
879 if user:
880 return link_to(
880 return link_to(
881 escape(display_person),
881 escape(display_person),
882 route_path('user_profile', username=user.username),
882 route_path('user_profile', username=user.username),
883 **kwargs)
883 **kwargs)
884 else:
884 else:
885 return escape(display_person)
885 return escape(display_person)
886
886
887
887
888 def person(author, show_attr="username_and_name"):
888 def person(author, show_attr="username_and_name"):
889 user = discover_user(author)
889 user = discover_user(author)
890 if user:
890 if user:
891 return getattr(user, show_attr)
891 return getattr(user, show_attr)
892 else:
892 else:
893 _author = author_name(author)
893 _author = author_name(author)
894 _email = email(author)
894 _email = email(author)
895 return _author or _email
895 return _author or _email
896
896
897
897
898 def author_string(email):
898 def author_string(email):
899 if email:
899 if email:
900 user = User.get_by_email(email, case_insensitive=True, cache=True)
900 user = User.get_by_email(email, case_insensitive=True, cache=True)
901 if user:
901 if user:
902 if user.first_name or user.last_name:
902 if user.first_name or user.last_name:
903 return '%s %s &lt;%s&gt;' % (
903 return '%s %s &lt;%s&gt;' % (
904 user.first_name, user.last_name, email)
904 user.first_name, user.last_name, email)
905 else:
905 else:
906 return email
906 return email
907 else:
907 else:
908 return email
908 return email
909 else:
909 else:
910 return None
910 return None
911
911
912
912
913 def person_by_id(id_, show_attr="username_and_name"):
913 def person_by_id(id_, show_attr="username_and_name"):
914 # attr to return from fetched user
914 # attr to return from fetched user
915 person_getter = lambda usr: getattr(usr, show_attr)
915 person_getter = lambda usr: getattr(usr, show_attr)
916
916
917 #maybe it's an ID ?
917 #maybe it's an ID ?
918 if str(id_).isdigit() or isinstance(id_, int):
918 if str(id_).isdigit() or isinstance(id_, int):
919 id_ = int(id_)
919 id_ = int(id_)
920 user = User.get(id_)
920 user = User.get(id_)
921 if user is not None:
921 if user is not None:
922 return person_getter(user)
922 return person_getter(user)
923 return id_
923 return id_
924
924
925
925
926 def gravatar_with_user(request, author, show_disabled=False):
926 def gravatar_with_user(request, author, show_disabled=False):
927 _render = request.get_partial_renderer('base/base.mako')
927 _render = request.get_partial_renderer('base/base.mako')
928 return _render('gravatar_with_user', author, show_disabled=show_disabled)
928 return _render('gravatar_with_user', author, show_disabled=show_disabled)
929
929
930
930
931 def desc_stylize(value):
931 tags_paterns = OrderedDict((
932 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
933 '<div class="metatag" tag="lang">\\2</div>')),
934
935 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
936 '<div class="metatag" tag="see">see =&gt; \\1 </div>')),
937
938 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((.*?)\)\]'),
939 '<div class="metatag" tag="url"> <a href="\\2">\\1</a> </div>')),
940
941 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
942 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
943
944 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
945 '<div class="metatag" tag="ref \\1">\\1 =&gt; <a href="/\\2">\\2</a></div>')),
946
947 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev)\]'),
948 '<div class="metatag" tag="state \\1">\\1</div>')),
949
950 # label in grey
951 ('label', (re.compile(r'\[([a-z]+)\]'),
952 '<div class="metatag" tag="label">\\1</div>')),
953
954 # generic catch all in grey
955 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
956 '<div class="metatag" tag="generic">\\1</div>')),
957 ))
958
959
960 def extract_metatags(value):
932 """
961 """
933 converts tags from value into html equivalent
962 Extract supported meta-tags from given text value
934
935 :param value:
936 """
963 """
937 if not value:
964 if not value:
938 return ''
965 return ''
939
966
940 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
967 tags = []
941 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
968 for key, val in tags_paterns.items():
942 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
969 pat, replace_html = val
943 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
970 tags.extend([(key, x.group()) for x in pat.finditer(value)])
944 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
971 value = pat.sub('', value)
945 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
946 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
947 '<div class="metatag" tag="lang">\\2</div>', value)
948 value = re.sub(r'\[([a-z]+)\]',
949 '<div class="metatag" tag="\\1">\\1</div>', value)
950
972
951 return value
973 return tags, value
952
974
953
975
954 def escaped_stylize(value):
976 def style_metatag(tag_type, value):
955 """
977 """
956 converts tags from value into html equivalent, but escaping its value first
978 converts tags from value into html equivalent
957 """
979 """
958 if not value:
980 if not value:
959 return ''
981 return ''
960
982
961 # Using default webhelper escape method, but has to force it as a
983 html_value = value
962 # plain unicode instead of a markup tag to be used in regex expressions
984 tag_data = tags_paterns.get(tag_type)
963 value = unicode(escape(safe_unicode(value)))
985 if tag_data:
986 pat, replace_html = tag_data
987 # convert to plain `unicode` instead of a markup tag to be used in
988 # regex expressions. safe_unicode doesn't work here
989 html_value = pat.sub(replace_html, unicode(value))
964
990
965 value = re.sub(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
991 return html_value
966 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
967 value = re.sub(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
968 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
969 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]',
970 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
971 value = re.sub(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+]*)\]',
972 '<div class="metatag" tag="lang">\\2</div>', value)
973 value = re.sub(r'\[([a-z]+)\]',
974 '<div class="metatag" tag="\\1">\\1</div>', value)
975
976 return value
977
992
978
993
979 def bool2icon(value):
994 def bool2icon(value):
980 """
995 """
981 Returns boolean value of a given value, represented as html element with
996 Returns boolean value of a given value, represented as html element with
982 classes that will represent icons
997 classes that will represent icons
983
998
984 :param value: given value to convert to html node
999 :param value: given value to convert to html node
985 """
1000 """
986
1001
987 if value: # does bool conversion
1002 if value: # does bool conversion
988 return HTML.tag('i', class_="icon-true")
1003 return HTML.tag('i', class_="icon-true")
989 else: # not true as bool
1004 else: # not true as bool
990 return HTML.tag('i', class_="icon-false")
1005 return HTML.tag('i', class_="icon-false")
991
1006
992
1007
993 #==============================================================================
1008 #==============================================================================
994 # PERMS
1009 # PERMS
995 #==============================================================================
1010 #==============================================================================
996 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
1011 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
997 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
1012 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
998 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
1013 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
999 csrf_token_key
1014 csrf_token_key
1000
1015
1001
1016
1002 #==============================================================================
1017 #==============================================================================
1003 # GRAVATAR URL
1018 # GRAVATAR URL
1004 #==============================================================================
1019 #==============================================================================
1005 class InitialsGravatar(object):
1020 class InitialsGravatar(object):
1006 def __init__(self, email_address, first_name, last_name, size=30,
1021 def __init__(self, email_address, first_name, last_name, size=30,
1007 background=None, text_color='#fff'):
1022 background=None, text_color='#fff'):
1008 self.size = size
1023 self.size = size
1009 self.first_name = first_name
1024 self.first_name = first_name
1010 self.last_name = last_name
1025 self.last_name = last_name
1011 self.email_address = email_address
1026 self.email_address = email_address
1012 self.background = background or self.str2color(email_address)
1027 self.background = background or self.str2color(email_address)
1013 self.text_color = text_color
1028 self.text_color = text_color
1014
1029
1015 def get_color_bank(self):
1030 def get_color_bank(self):
1016 """
1031 """
1017 returns a predefined list of colors that gravatars can use.
1032 returns a predefined list of colors that gravatars can use.
1018 Those are randomized distinct colors that guarantee readability and
1033 Those are randomized distinct colors that guarantee readability and
1019 uniqueness.
1034 uniqueness.
1020
1035
1021 generated with: http://phrogz.net/css/distinct-colors.html
1036 generated with: http://phrogz.net/css/distinct-colors.html
1022 """
1037 """
1023 return [
1038 return [
1024 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1039 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1025 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1040 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1026 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1041 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1027 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1042 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1028 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1043 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1029 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1044 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1030 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1045 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1031 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1046 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1032 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1047 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1033 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1048 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1034 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1049 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1035 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1050 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1036 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1051 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1037 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1052 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1038 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1053 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1039 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1054 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1040 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1055 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1041 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1056 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1042 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1057 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1043 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1058 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1044 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1059 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1045 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1060 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1046 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1061 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1047 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1062 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1048 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1063 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1049 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1064 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1050 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1065 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1051 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1066 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1052 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1067 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1053 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1068 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1054 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1069 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1055 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1070 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1056 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1071 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1057 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1072 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1058 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1073 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1059 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1074 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1060 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1075 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1061 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1076 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1062 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1077 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1063 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1078 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1064 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1079 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1065 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1080 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1066 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1081 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1067 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1082 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1068 '#4f8c46', '#368dd9', '#5c0073'
1083 '#4f8c46', '#368dd9', '#5c0073'
1069 ]
1084 ]
1070
1085
1071 def rgb_to_hex_color(self, rgb_tuple):
1086 def rgb_to_hex_color(self, rgb_tuple):
1072 """
1087 """
1073 Converts an rgb_tuple passed to an hex color.
1088 Converts an rgb_tuple passed to an hex color.
1074
1089
1075 :param rgb_tuple: tuple with 3 ints represents rgb color space
1090 :param rgb_tuple: tuple with 3 ints represents rgb color space
1076 """
1091 """
1077 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1092 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1078
1093
1079 def email_to_int_list(self, email_str):
1094 def email_to_int_list(self, email_str):
1080 """
1095 """
1081 Get every byte of the hex digest value of email and turn it to integer.
1096 Get every byte of the hex digest value of email and turn it to integer.
1082 It's going to be always between 0-255
1097 It's going to be always between 0-255
1083 """
1098 """
1084 digest = md5_safe(email_str.lower())
1099 digest = md5_safe(email_str.lower())
1085 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1100 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1086
1101
1087 def pick_color_bank_index(self, email_str, color_bank):
1102 def pick_color_bank_index(self, email_str, color_bank):
1088 return self.email_to_int_list(email_str)[0] % len(color_bank)
1103 return self.email_to_int_list(email_str)[0] % len(color_bank)
1089
1104
1090 def str2color(self, email_str):
1105 def str2color(self, email_str):
1091 """
1106 """
1092 Tries to map in a stable algorithm an email to color
1107 Tries to map in a stable algorithm an email to color
1093
1108
1094 :param email_str:
1109 :param email_str:
1095 """
1110 """
1096 color_bank = self.get_color_bank()
1111 color_bank = self.get_color_bank()
1097 # pick position (module it's length so we always find it in the
1112 # pick position (module it's length so we always find it in the
1098 # bank even if it's smaller than 256 values
1113 # bank even if it's smaller than 256 values
1099 pos = self.pick_color_bank_index(email_str, color_bank)
1114 pos = self.pick_color_bank_index(email_str, color_bank)
1100 return color_bank[pos]
1115 return color_bank[pos]
1101
1116
1102 def normalize_email(self, email_address):
1117 def normalize_email(self, email_address):
1103 import unicodedata
1118 import unicodedata
1104 # default host used to fill in the fake/missing email
1119 # default host used to fill in the fake/missing email
1105 default_host = u'localhost'
1120 default_host = u'localhost'
1106
1121
1107 if not email_address:
1122 if not email_address:
1108 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1123 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1109
1124
1110 email_address = safe_unicode(email_address)
1125 email_address = safe_unicode(email_address)
1111
1126
1112 if u'@' not in email_address:
1127 if u'@' not in email_address:
1113 email_address = u'%s@%s' % (email_address, default_host)
1128 email_address = u'%s@%s' % (email_address, default_host)
1114
1129
1115 if email_address.endswith(u'@'):
1130 if email_address.endswith(u'@'):
1116 email_address = u'%s%s' % (email_address, default_host)
1131 email_address = u'%s%s' % (email_address, default_host)
1117
1132
1118 email_address = unicodedata.normalize('NFKD', email_address)\
1133 email_address = unicodedata.normalize('NFKD', email_address)\
1119 .encode('ascii', 'ignore')
1134 .encode('ascii', 'ignore')
1120 return email_address
1135 return email_address
1121
1136
1122 def get_initials(self):
1137 def get_initials(self):
1123 """
1138 """
1124 Returns 2 letter initials calculated based on the input.
1139 Returns 2 letter initials calculated based on the input.
1125 The algorithm picks first given email address, and takes first letter
1140 The algorithm picks first given email address, and takes first letter
1126 of part before @, and then the first letter of server name. In case
1141 of part before @, and then the first letter of server name. In case
1127 the part before @ is in a format of `somestring.somestring2` it replaces
1142 the part before @ is in a format of `somestring.somestring2` it replaces
1128 the server letter with first letter of somestring2
1143 the server letter with first letter of somestring2
1129
1144
1130 In case function was initialized with both first and lastname, this
1145 In case function was initialized with both first and lastname, this
1131 overrides the extraction from email by first letter of the first and
1146 overrides the extraction from email by first letter of the first and
1132 last name. We add special logic to that functionality, In case Full name
1147 last name. We add special logic to that functionality, In case Full name
1133 is compound, like Guido Von Rossum, we use last part of the last name
1148 is compound, like Guido Von Rossum, we use last part of the last name
1134 (Von Rossum) picking `R`.
1149 (Von Rossum) picking `R`.
1135
1150
1136 Function also normalizes the non-ascii characters to they ascii
1151 Function also normalizes the non-ascii characters to they ascii
1137 representation, eg Δ„ => A
1152 representation, eg Δ„ => A
1138 """
1153 """
1139 import unicodedata
1154 import unicodedata
1140 # replace non-ascii to ascii
1155 # replace non-ascii to ascii
1141 first_name = unicodedata.normalize(
1156 first_name = unicodedata.normalize(
1142 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1157 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1143 last_name = unicodedata.normalize(
1158 last_name = unicodedata.normalize(
1144 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1159 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1145
1160
1146 # do NFKD encoding, and also make sure email has proper format
1161 # do NFKD encoding, and also make sure email has proper format
1147 email_address = self.normalize_email(self.email_address)
1162 email_address = self.normalize_email(self.email_address)
1148
1163
1149 # first push the email initials
1164 # first push the email initials
1150 prefix, server = email_address.split('@', 1)
1165 prefix, server = email_address.split('@', 1)
1151
1166
1152 # check if prefix is maybe a 'first_name.last_name' syntax
1167 # check if prefix is maybe a 'first_name.last_name' syntax
1153 _dot_split = prefix.rsplit('.', 1)
1168 _dot_split = prefix.rsplit('.', 1)
1154 if len(_dot_split) == 2 and _dot_split[1]:
1169 if len(_dot_split) == 2 and _dot_split[1]:
1155 initials = [_dot_split[0][0], _dot_split[1][0]]
1170 initials = [_dot_split[0][0], _dot_split[1][0]]
1156 else:
1171 else:
1157 initials = [prefix[0], server[0]]
1172 initials = [prefix[0], server[0]]
1158
1173
1159 # then try to replace either first_name or last_name
1174 # then try to replace either first_name or last_name
1160 fn_letter = (first_name or " ")[0].strip()
1175 fn_letter = (first_name or " ")[0].strip()
1161 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1176 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1162
1177
1163 if fn_letter:
1178 if fn_letter:
1164 initials[0] = fn_letter
1179 initials[0] = fn_letter
1165
1180
1166 if ln_letter:
1181 if ln_letter:
1167 initials[1] = ln_letter
1182 initials[1] = ln_letter
1168
1183
1169 return ''.join(initials).upper()
1184 return ''.join(initials).upper()
1170
1185
1171 def get_img_data_by_type(self, font_family, img_type):
1186 def get_img_data_by_type(self, font_family, img_type):
1172 default_user = """
1187 default_user = """
1173 <svg xmlns="http://www.w3.org/2000/svg"
1188 <svg xmlns="http://www.w3.org/2000/svg"
1174 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1189 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1175 viewBox="-15 -10 439.165 429.164"
1190 viewBox="-15 -10 439.165 429.164"
1176
1191
1177 xml:space="preserve"
1192 xml:space="preserve"
1178 style="background:{background};" >
1193 style="background:{background};" >
1179
1194
1180 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1195 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1181 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1196 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1182 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1197 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1183 168.596,153.916,216.671,
1198 168.596,153.916,216.671,
1184 204.583,216.671z" fill="{text_color}"/>
1199 204.583,216.671z" fill="{text_color}"/>
1185 <path d="M407.164,374.717L360.88,
1200 <path d="M407.164,374.717L360.88,
1186 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1201 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1187 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1202 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1188 15.366-44.203,23.488-69.076,23.488c-24.877,
1203 15.366-44.203,23.488-69.076,23.488c-24.877,
1189 0-48.762-8.122-69.078-23.488
1204 0-48.762-8.122-69.078-23.488
1190 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1205 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1191 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1206 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1192 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1207 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1193 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1208 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1194 19.402-10.527 C409.699,390.129,
1209 19.402-10.527 C409.699,390.129,
1195 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1210 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1196 </svg>""".format(
1211 </svg>""".format(
1197 size=self.size,
1212 size=self.size,
1198 background='#979797', # @grey4
1213 background='#979797', # @grey4
1199 text_color=self.text_color,
1214 text_color=self.text_color,
1200 font_family=font_family)
1215 font_family=font_family)
1201
1216
1202 return {
1217 return {
1203 "default_user": default_user
1218 "default_user": default_user
1204 }[img_type]
1219 }[img_type]
1205
1220
1206 def get_img_data(self, svg_type=None):
1221 def get_img_data(self, svg_type=None):
1207 """
1222 """
1208 generates the svg metadata for image
1223 generates the svg metadata for image
1209 """
1224 """
1210
1225
1211 font_family = ','.join([
1226 font_family = ','.join([
1212 'proximanovaregular',
1227 'proximanovaregular',
1213 'Proxima Nova Regular',
1228 'Proxima Nova Regular',
1214 'Proxima Nova',
1229 'Proxima Nova',
1215 'Arial',
1230 'Arial',
1216 'Lucida Grande',
1231 'Lucida Grande',
1217 'sans-serif'
1232 'sans-serif'
1218 ])
1233 ])
1219 if svg_type:
1234 if svg_type:
1220 return self.get_img_data_by_type(font_family, svg_type)
1235 return self.get_img_data_by_type(font_family, svg_type)
1221
1236
1222 initials = self.get_initials()
1237 initials = self.get_initials()
1223 img_data = """
1238 img_data = """
1224 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1239 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1225 width="{size}" height="{size}"
1240 width="{size}" height="{size}"
1226 style="width: 100%; height: 100%; background-color: {background}"
1241 style="width: 100%; height: 100%; background-color: {background}"
1227 viewBox="0 0 {size} {size}">
1242 viewBox="0 0 {size} {size}">
1228 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1243 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1229 pointer-events="auto" fill="{text_color}"
1244 pointer-events="auto" fill="{text_color}"
1230 font-family="{font_family}"
1245 font-family="{font_family}"
1231 style="font-weight: 400; font-size: {f_size}px;">{text}
1246 style="font-weight: 400; font-size: {f_size}px;">{text}
1232 </text>
1247 </text>
1233 </svg>""".format(
1248 </svg>""".format(
1234 size=self.size,
1249 size=self.size,
1235 f_size=self.size/1.85, # scale the text inside the box nicely
1250 f_size=self.size/1.85, # scale the text inside the box nicely
1236 background=self.background,
1251 background=self.background,
1237 text_color=self.text_color,
1252 text_color=self.text_color,
1238 text=initials.upper(),
1253 text=initials.upper(),
1239 font_family=font_family)
1254 font_family=font_family)
1240
1255
1241 return img_data
1256 return img_data
1242
1257
1243 def generate_svg(self, svg_type=None):
1258 def generate_svg(self, svg_type=None):
1244 img_data = self.get_img_data(svg_type)
1259 img_data = self.get_img_data(svg_type)
1245 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1260 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1246
1261
1247
1262
1248 def initials_gravatar(email_address, first_name, last_name, size=30):
1263 def initials_gravatar(email_address, first_name, last_name, size=30):
1249 svg_type = None
1264 svg_type = None
1250 if email_address == User.DEFAULT_USER_EMAIL:
1265 if email_address == User.DEFAULT_USER_EMAIL:
1251 svg_type = 'default_user'
1266 svg_type = 'default_user'
1252 klass = InitialsGravatar(email_address, first_name, last_name, size)
1267 klass = InitialsGravatar(email_address, first_name, last_name, size)
1253 return klass.generate_svg(svg_type=svg_type)
1268 return klass.generate_svg(svg_type=svg_type)
1254
1269
1255
1270
1256 def gravatar_url(email_address, size=30, request=None):
1271 def gravatar_url(email_address, size=30, request=None):
1257 request = get_current_request()
1272 request = get_current_request()
1258 if request and hasattr(request, 'call_context'):
1273 if request and hasattr(request, 'call_context'):
1259 _use_gravatar = request.call_context.visual.use_gravatar
1274 _use_gravatar = request.call_context.visual.use_gravatar
1260 _gravatar_url = request.call_context.visual.gravatar_url
1275 _gravatar_url = request.call_context.visual.gravatar_url
1261 else:
1276 else:
1262 # doh, we need to re-import those to mock it later
1277 # doh, we need to re-import those to mock it later
1263 from pylons import tmpl_context as c
1278 from pylons import tmpl_context as c
1264
1279
1265 _use_gravatar = c.visual.use_gravatar
1280 _use_gravatar = c.visual.use_gravatar
1266 _gravatar_url = c.visual.gravatar_url
1281 _gravatar_url = c.visual.gravatar_url
1267
1282
1268 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1283 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1269
1284
1270 email_address = email_address or User.DEFAULT_USER_EMAIL
1285 email_address = email_address or User.DEFAULT_USER_EMAIL
1271 if isinstance(email_address, unicode):
1286 if isinstance(email_address, unicode):
1272 # hashlib crashes on unicode items
1287 # hashlib crashes on unicode items
1273 email_address = safe_str(email_address)
1288 email_address = safe_str(email_address)
1274
1289
1275 # empty email or default user
1290 # empty email or default user
1276 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1291 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1277 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1292 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1278
1293
1279 if _use_gravatar:
1294 if _use_gravatar:
1280 # TODO: Disuse pyramid thread locals. Think about another solution to
1295 # TODO: Disuse pyramid thread locals. Think about another solution to
1281 # get the host and schema here.
1296 # get the host and schema here.
1282 request = get_current_request()
1297 request = get_current_request()
1283 tmpl = safe_str(_gravatar_url)
1298 tmpl = safe_str(_gravatar_url)
1284 tmpl = tmpl.replace('{email}', email_address)\
1299 tmpl = tmpl.replace('{email}', email_address)\
1285 .replace('{md5email}', md5_safe(email_address.lower())) \
1300 .replace('{md5email}', md5_safe(email_address.lower())) \
1286 .replace('{netloc}', request.host)\
1301 .replace('{netloc}', request.host)\
1287 .replace('{scheme}', request.scheme)\
1302 .replace('{scheme}', request.scheme)\
1288 .replace('{size}', safe_str(size))
1303 .replace('{size}', safe_str(size))
1289 return tmpl
1304 return tmpl
1290 else:
1305 else:
1291 return initials_gravatar(email_address, '', '', size=size)
1306 return initials_gravatar(email_address, '', '', size=size)
1292
1307
1293
1308
1294 class Page(_Page):
1309 class Page(_Page):
1295 """
1310 """
1296 Custom pager to match rendering style with paginator
1311 Custom pager to match rendering style with paginator
1297 """
1312 """
1298
1313
1299 def _get_pos(self, cur_page, max_page, items):
1314 def _get_pos(self, cur_page, max_page, items):
1300 edge = (items / 2) + 1
1315 edge = (items / 2) + 1
1301 if (cur_page <= edge):
1316 if (cur_page <= edge):
1302 radius = max(items / 2, items - cur_page)
1317 radius = max(items / 2, items - cur_page)
1303 elif (max_page - cur_page) < edge:
1318 elif (max_page - cur_page) < edge:
1304 radius = (items - 1) - (max_page - cur_page)
1319 radius = (items - 1) - (max_page - cur_page)
1305 else:
1320 else:
1306 radius = items / 2
1321 radius = items / 2
1307
1322
1308 left = max(1, (cur_page - (radius)))
1323 left = max(1, (cur_page - (radius)))
1309 right = min(max_page, cur_page + (radius))
1324 right = min(max_page, cur_page + (radius))
1310 return left, cur_page, right
1325 return left, cur_page, right
1311
1326
1312 def _range(self, regexp_match):
1327 def _range(self, regexp_match):
1313 """
1328 """
1314 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1329 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1315
1330
1316 Arguments:
1331 Arguments:
1317
1332
1318 regexp_match
1333 regexp_match
1319 A "re" (regular expressions) match object containing the
1334 A "re" (regular expressions) match object containing the
1320 radius of linked pages around the current page in
1335 radius of linked pages around the current page in
1321 regexp_match.group(1) as a string
1336 regexp_match.group(1) as a string
1322
1337
1323 This function is supposed to be called as a callable in
1338 This function is supposed to be called as a callable in
1324 re.sub.
1339 re.sub.
1325
1340
1326 """
1341 """
1327 radius = int(regexp_match.group(1))
1342 radius = int(regexp_match.group(1))
1328
1343
1329 # Compute the first and last page number within the radius
1344 # Compute the first and last page number within the radius
1330 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1345 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1331 # -> leftmost_page = 5
1346 # -> leftmost_page = 5
1332 # -> rightmost_page = 9
1347 # -> rightmost_page = 9
1333 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1348 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1334 self.last_page,
1349 self.last_page,
1335 (radius * 2) + 1)
1350 (radius * 2) + 1)
1336 nav_items = []
1351 nav_items = []
1337
1352
1338 # Create a link to the first page (unless we are on the first page
1353 # Create a link to the first page (unless we are on the first page
1339 # or there would be no need to insert '..' spacers)
1354 # or there would be no need to insert '..' spacers)
1340 if self.page != self.first_page and self.first_page < leftmost_page:
1355 if self.page != self.first_page and self.first_page < leftmost_page:
1341 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1356 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1342
1357
1343 # Insert dots if there are pages between the first page
1358 # Insert dots if there are pages between the first page
1344 # and the currently displayed page range
1359 # and the currently displayed page range
1345 if leftmost_page - self.first_page > 1:
1360 if leftmost_page - self.first_page > 1:
1346 # Wrap in a SPAN tag if nolink_attr is set
1361 # Wrap in a SPAN tag if nolink_attr is set
1347 text = '..'
1362 text = '..'
1348 if self.dotdot_attr:
1363 if self.dotdot_attr:
1349 text = HTML.span(c=text, **self.dotdot_attr)
1364 text = HTML.span(c=text, **self.dotdot_attr)
1350 nav_items.append(text)
1365 nav_items.append(text)
1351
1366
1352 for thispage in xrange(leftmost_page, rightmost_page + 1):
1367 for thispage in xrange(leftmost_page, rightmost_page + 1):
1353 # Hilight the current page number and do not use a link
1368 # Hilight the current page number and do not use a link
1354 if thispage == self.page:
1369 if thispage == self.page:
1355 text = '%s' % (thispage,)
1370 text = '%s' % (thispage,)
1356 # Wrap in a SPAN tag if nolink_attr is set
1371 # Wrap in a SPAN tag if nolink_attr is set
1357 if self.curpage_attr:
1372 if self.curpage_attr:
1358 text = HTML.span(c=text, **self.curpage_attr)
1373 text = HTML.span(c=text, **self.curpage_attr)
1359 nav_items.append(text)
1374 nav_items.append(text)
1360 # Otherwise create just a link to that page
1375 # Otherwise create just a link to that page
1361 else:
1376 else:
1362 text = '%s' % (thispage,)
1377 text = '%s' % (thispage,)
1363 nav_items.append(self._pagerlink(thispage, text))
1378 nav_items.append(self._pagerlink(thispage, text))
1364
1379
1365 # Insert dots if there are pages between the displayed
1380 # Insert dots if there are pages between the displayed
1366 # page numbers and the end of the page range
1381 # page numbers and the end of the page range
1367 if self.last_page - rightmost_page > 1:
1382 if self.last_page - rightmost_page > 1:
1368 text = '..'
1383 text = '..'
1369 # Wrap in a SPAN tag if nolink_attr is set
1384 # Wrap in a SPAN tag if nolink_attr is set
1370 if self.dotdot_attr:
1385 if self.dotdot_attr:
1371 text = HTML.span(c=text, **self.dotdot_attr)
1386 text = HTML.span(c=text, **self.dotdot_attr)
1372 nav_items.append(text)
1387 nav_items.append(text)
1373
1388
1374 # Create a link to the very last page (unless we are on the last
1389 # Create a link to the very last page (unless we are on the last
1375 # page or there would be no need to insert '..' spacers)
1390 # page or there would be no need to insert '..' spacers)
1376 if self.page != self.last_page and rightmost_page < self.last_page:
1391 if self.page != self.last_page and rightmost_page < self.last_page:
1377 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1392 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1378
1393
1379 ## prerender links
1394 ## prerender links
1380 #_page_link = url.current()
1395 #_page_link = url.current()
1381 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1396 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1382 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1397 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1383 return self.separator.join(nav_items)
1398 return self.separator.join(nav_items)
1384
1399
1385 def pager(self, format='~2~', page_param='page', partial_param='partial',
1400 def pager(self, format='~2~', page_param='page', partial_param='partial',
1386 show_if_single_page=False, separator=' ', onclick=None,
1401 show_if_single_page=False, separator=' ', onclick=None,
1387 symbol_first='<<', symbol_last='>>',
1402 symbol_first='<<', symbol_last='>>',
1388 symbol_previous='<', symbol_next='>',
1403 symbol_previous='<', symbol_next='>',
1389 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1404 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1390 curpage_attr={'class': 'pager_curpage'},
1405 curpage_attr={'class': 'pager_curpage'},
1391 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1406 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1392
1407
1393 self.curpage_attr = curpage_attr
1408 self.curpage_attr = curpage_attr
1394 self.separator = separator
1409 self.separator = separator
1395 self.pager_kwargs = kwargs
1410 self.pager_kwargs = kwargs
1396 self.page_param = page_param
1411 self.page_param = page_param
1397 self.partial_param = partial_param
1412 self.partial_param = partial_param
1398 self.onclick = onclick
1413 self.onclick = onclick
1399 self.link_attr = link_attr
1414 self.link_attr = link_attr
1400 self.dotdot_attr = dotdot_attr
1415 self.dotdot_attr = dotdot_attr
1401
1416
1402 # Don't show navigator if there is no more than one page
1417 # Don't show navigator if there is no more than one page
1403 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1418 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1404 return ''
1419 return ''
1405
1420
1406 from string import Template
1421 from string import Template
1407 # Replace ~...~ in token format by range of pages
1422 # Replace ~...~ in token format by range of pages
1408 result = re.sub(r'~(\d+)~', self._range, format)
1423 result = re.sub(r'~(\d+)~', self._range, format)
1409
1424
1410 # Interpolate '%' variables
1425 # Interpolate '%' variables
1411 result = Template(result).safe_substitute({
1426 result = Template(result).safe_substitute({
1412 'first_page': self.first_page,
1427 'first_page': self.first_page,
1413 'last_page': self.last_page,
1428 'last_page': self.last_page,
1414 'page': self.page,
1429 'page': self.page,
1415 'page_count': self.page_count,
1430 'page_count': self.page_count,
1416 'items_per_page': self.items_per_page,
1431 'items_per_page': self.items_per_page,
1417 'first_item': self.first_item,
1432 'first_item': self.first_item,
1418 'last_item': self.last_item,
1433 'last_item': self.last_item,
1419 'item_count': self.item_count,
1434 'item_count': self.item_count,
1420 'link_first': self.page > self.first_page and \
1435 'link_first': self.page > self.first_page and \
1421 self._pagerlink(self.first_page, symbol_first) or '',
1436 self._pagerlink(self.first_page, symbol_first) or '',
1422 'link_last': self.page < self.last_page and \
1437 'link_last': self.page < self.last_page and \
1423 self._pagerlink(self.last_page, symbol_last) or '',
1438 self._pagerlink(self.last_page, symbol_last) or '',
1424 'link_previous': self.previous_page and \
1439 'link_previous': self.previous_page and \
1425 self._pagerlink(self.previous_page, symbol_previous) \
1440 self._pagerlink(self.previous_page, symbol_previous) \
1426 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1441 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1427 'link_next': self.next_page and \
1442 'link_next': self.next_page and \
1428 self._pagerlink(self.next_page, symbol_next) \
1443 self._pagerlink(self.next_page, symbol_next) \
1429 or HTML.span(symbol_next, class_="pg-next disabled")
1444 or HTML.span(symbol_next, class_="pg-next disabled")
1430 })
1445 })
1431
1446
1432 return literal(result)
1447 return literal(result)
1433
1448
1434
1449
1435 #==============================================================================
1450 #==============================================================================
1436 # REPO PAGER, PAGER FOR REPOSITORY
1451 # REPO PAGER, PAGER FOR REPOSITORY
1437 #==============================================================================
1452 #==============================================================================
1438 class RepoPage(Page):
1453 class RepoPage(Page):
1439
1454
1440 def __init__(self, collection, page=1, items_per_page=20,
1455 def __init__(self, collection, page=1, items_per_page=20,
1441 item_count=None, url=None, **kwargs):
1456 item_count=None, url=None, **kwargs):
1442
1457
1443 """Create a "RepoPage" instance. special pager for paging
1458 """Create a "RepoPage" instance. special pager for paging
1444 repository
1459 repository
1445 """
1460 """
1446 self._url_generator = url
1461 self._url_generator = url
1447
1462
1448 # Safe the kwargs class-wide so they can be used in the pager() method
1463 # Safe the kwargs class-wide so they can be used in the pager() method
1449 self.kwargs = kwargs
1464 self.kwargs = kwargs
1450
1465
1451 # Save a reference to the collection
1466 # Save a reference to the collection
1452 self.original_collection = collection
1467 self.original_collection = collection
1453
1468
1454 self.collection = collection
1469 self.collection = collection
1455
1470
1456 # The self.page is the number of the current page.
1471 # The self.page is the number of the current page.
1457 # The first page has the number 1!
1472 # The first page has the number 1!
1458 try:
1473 try:
1459 self.page = int(page) # make it int() if we get it as a string
1474 self.page = int(page) # make it int() if we get it as a string
1460 except (ValueError, TypeError):
1475 except (ValueError, TypeError):
1461 self.page = 1
1476 self.page = 1
1462
1477
1463 self.items_per_page = items_per_page
1478 self.items_per_page = items_per_page
1464
1479
1465 # Unless the user tells us how many items the collections has
1480 # Unless the user tells us how many items the collections has
1466 # we calculate that ourselves.
1481 # we calculate that ourselves.
1467 if item_count is not None:
1482 if item_count is not None:
1468 self.item_count = item_count
1483 self.item_count = item_count
1469 else:
1484 else:
1470 self.item_count = len(self.collection)
1485 self.item_count = len(self.collection)
1471
1486
1472 # Compute the number of the first and last available page
1487 # Compute the number of the first and last available page
1473 if self.item_count > 0:
1488 if self.item_count > 0:
1474 self.first_page = 1
1489 self.first_page = 1
1475 self.page_count = int(math.ceil(float(self.item_count) /
1490 self.page_count = int(math.ceil(float(self.item_count) /
1476 self.items_per_page))
1491 self.items_per_page))
1477 self.last_page = self.first_page + self.page_count - 1
1492 self.last_page = self.first_page + self.page_count - 1
1478
1493
1479 # Make sure that the requested page number is the range of
1494 # Make sure that the requested page number is the range of
1480 # valid pages
1495 # valid pages
1481 if self.page > self.last_page:
1496 if self.page > self.last_page:
1482 self.page = self.last_page
1497 self.page = self.last_page
1483 elif self.page < self.first_page:
1498 elif self.page < self.first_page:
1484 self.page = self.first_page
1499 self.page = self.first_page
1485
1500
1486 # Note: the number of items on this page can be less than
1501 # Note: the number of items on this page can be less than
1487 # items_per_page if the last page is not full
1502 # items_per_page if the last page is not full
1488 self.first_item = max(0, (self.item_count) - (self.page *
1503 self.first_item = max(0, (self.item_count) - (self.page *
1489 items_per_page))
1504 items_per_page))
1490 self.last_item = ((self.item_count - 1) - items_per_page *
1505 self.last_item = ((self.item_count - 1) - items_per_page *
1491 (self.page - 1))
1506 (self.page - 1))
1492
1507
1493 self.items = list(self.collection[self.first_item:self.last_item + 1])
1508 self.items = list(self.collection[self.first_item:self.last_item + 1])
1494
1509
1495 # Links to previous and next page
1510 # Links to previous and next page
1496 if self.page > self.first_page:
1511 if self.page > self.first_page:
1497 self.previous_page = self.page - 1
1512 self.previous_page = self.page - 1
1498 else:
1513 else:
1499 self.previous_page = None
1514 self.previous_page = None
1500
1515
1501 if self.page < self.last_page:
1516 if self.page < self.last_page:
1502 self.next_page = self.page + 1
1517 self.next_page = self.page + 1
1503 else:
1518 else:
1504 self.next_page = None
1519 self.next_page = None
1505
1520
1506 # No items available
1521 # No items available
1507 else:
1522 else:
1508 self.first_page = None
1523 self.first_page = None
1509 self.page_count = 0
1524 self.page_count = 0
1510 self.last_page = None
1525 self.last_page = None
1511 self.first_item = None
1526 self.first_item = None
1512 self.last_item = None
1527 self.last_item = None
1513 self.previous_page = None
1528 self.previous_page = None
1514 self.next_page = None
1529 self.next_page = None
1515 self.items = []
1530 self.items = []
1516
1531
1517 # This is a subclass of the 'list' type. Initialise the list now.
1532 # This is a subclass of the 'list' type. Initialise the list now.
1518 list.__init__(self, reversed(self.items))
1533 list.__init__(self, reversed(self.items))
1519
1534
1520
1535
1521 def breadcrumb_repo_link(repo):
1536 def breadcrumb_repo_link(repo):
1522 """
1537 """
1523 Makes a breadcrumbs path link to repo
1538 Makes a breadcrumbs path link to repo
1524
1539
1525 ex::
1540 ex::
1526 group >> subgroup >> repo
1541 group >> subgroup >> repo
1527
1542
1528 :param repo: a Repository instance
1543 :param repo: a Repository instance
1529 """
1544 """
1530
1545
1531 path = [
1546 path = [
1532 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name))
1547 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name))
1533 for group in repo.groups_with_parents
1548 for group in repo.groups_with_parents
1534 ] + [
1549 ] + [
1535 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name))
1550 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name))
1536 ]
1551 ]
1537
1552
1538 return literal(' &raquo; '.join(path))
1553 return literal(' &raquo; '.join(path))
1539
1554
1540
1555
1541 def format_byte_size_binary(file_size):
1556 def format_byte_size_binary(file_size):
1542 """
1557 """
1543 Formats file/folder sizes to standard.
1558 Formats file/folder sizes to standard.
1544 """
1559 """
1545 if file_size is None:
1560 if file_size is None:
1546 file_size = 0
1561 file_size = 0
1547
1562
1548 formatted_size = format_byte_size(file_size, binary=True)
1563 formatted_size = format_byte_size(file_size, binary=True)
1549 return formatted_size
1564 return formatted_size
1550
1565
1551
1566
1552 def urlify_text(text_, safe=True):
1567 def urlify_text(text_, safe=True):
1553 """
1568 """
1554 Extrac urls from text and make html links out of them
1569 Extrac urls from text and make html links out of them
1555
1570
1556 :param text_:
1571 :param text_:
1557 """
1572 """
1558
1573
1559 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1574 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1560 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1575 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1561
1576
1562 def url_func(match_obj):
1577 def url_func(match_obj):
1563 url_full = match_obj.groups()[0]
1578 url_full = match_obj.groups()[0]
1564 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1579 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1565 _newtext = url_pat.sub(url_func, text_)
1580 _newtext = url_pat.sub(url_func, text_)
1566 if safe:
1581 if safe:
1567 return literal(_newtext)
1582 return literal(_newtext)
1568 return _newtext
1583 return _newtext
1569
1584
1570
1585
1571 def urlify_commits(text_, repository):
1586 def urlify_commits(text_, repository):
1572 """
1587 """
1573 Extract commit ids from text and make link from them
1588 Extract commit ids from text and make link from them
1574
1589
1575 :param text_:
1590 :param text_:
1576 :param repository: repo name to build the URL with
1591 :param repository: repo name to build the URL with
1577 """
1592 """
1578
1593
1579 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1594 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1580
1595
1581 def url_func(match_obj):
1596 def url_func(match_obj):
1582 commit_id = match_obj.groups()[1]
1597 commit_id = match_obj.groups()[1]
1583 pref = match_obj.groups()[0]
1598 pref = match_obj.groups()[0]
1584 suf = match_obj.groups()[2]
1599 suf = match_obj.groups()[2]
1585
1600
1586 tmpl = (
1601 tmpl = (
1587 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1602 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1588 '%(commit_id)s</a>%(suf)s'
1603 '%(commit_id)s</a>%(suf)s'
1589 )
1604 )
1590 return tmpl % {
1605 return tmpl % {
1591 'pref': pref,
1606 'pref': pref,
1592 'cls': 'revision-link',
1607 'cls': 'revision-link',
1593 'url': route_url('repo_commit', repo_name=repository,
1608 'url': route_url('repo_commit', repo_name=repository,
1594 commit_id=commit_id),
1609 commit_id=commit_id),
1595 'commit_id': commit_id,
1610 'commit_id': commit_id,
1596 'suf': suf
1611 'suf': suf
1597 }
1612 }
1598
1613
1599 newtext = URL_PAT.sub(url_func, text_)
1614 newtext = URL_PAT.sub(url_func, text_)
1600
1615
1601 return newtext
1616 return newtext
1602
1617
1603
1618
1604 def _process_url_func(match_obj, repo_name, uid, entry,
1619 def _process_url_func(match_obj, repo_name, uid, entry,
1605 return_raw_data=False, link_format='html'):
1620 return_raw_data=False, link_format='html'):
1606 pref = ''
1621 pref = ''
1607 if match_obj.group().startswith(' '):
1622 if match_obj.group().startswith(' '):
1608 pref = ' '
1623 pref = ' '
1609
1624
1610 issue_id = ''.join(match_obj.groups())
1625 issue_id = ''.join(match_obj.groups())
1611
1626
1612 if link_format == 'html':
1627 if link_format == 'html':
1613 tmpl = (
1628 tmpl = (
1614 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1629 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1615 '%(issue-prefix)s%(id-repr)s'
1630 '%(issue-prefix)s%(id-repr)s'
1616 '</a>')
1631 '</a>')
1617 elif link_format == 'rst':
1632 elif link_format == 'rst':
1618 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1633 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1619 elif link_format == 'markdown':
1634 elif link_format == 'markdown':
1620 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1635 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1621 else:
1636 else:
1622 raise ValueError('Bad link_format:{}'.format(link_format))
1637 raise ValueError('Bad link_format:{}'.format(link_format))
1623
1638
1624 (repo_name_cleaned,
1639 (repo_name_cleaned,
1625 parent_group_name) = RepoGroupModel().\
1640 parent_group_name) = RepoGroupModel().\
1626 _get_group_name_and_parent(repo_name)
1641 _get_group_name_and_parent(repo_name)
1627
1642
1628 # variables replacement
1643 # variables replacement
1629 named_vars = {
1644 named_vars = {
1630 'id': issue_id,
1645 'id': issue_id,
1631 'repo': repo_name,
1646 'repo': repo_name,
1632 'repo_name': repo_name_cleaned,
1647 'repo_name': repo_name_cleaned,
1633 'group_name': parent_group_name
1648 'group_name': parent_group_name
1634 }
1649 }
1635 # named regex variables
1650 # named regex variables
1636 named_vars.update(match_obj.groupdict())
1651 named_vars.update(match_obj.groupdict())
1637 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1652 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1638
1653
1639 data = {
1654 data = {
1640 'pref': pref,
1655 'pref': pref,
1641 'cls': 'issue-tracker-link',
1656 'cls': 'issue-tracker-link',
1642 'url': _url,
1657 'url': _url,
1643 'id-repr': issue_id,
1658 'id-repr': issue_id,
1644 'issue-prefix': entry['pref'],
1659 'issue-prefix': entry['pref'],
1645 'serv': entry['url'],
1660 'serv': entry['url'],
1646 }
1661 }
1647 if return_raw_data:
1662 if return_raw_data:
1648 return {
1663 return {
1649 'id': issue_id,
1664 'id': issue_id,
1650 'url': _url
1665 'url': _url
1651 }
1666 }
1652 return tmpl % data
1667 return tmpl % data
1653
1668
1654
1669
1655 def process_patterns(text_string, repo_name, link_format='html'):
1670 def process_patterns(text_string, repo_name, link_format='html'):
1656 allowed_formats = ['html', 'rst', 'markdown']
1671 allowed_formats = ['html', 'rst', 'markdown']
1657 if link_format not in allowed_formats:
1672 if link_format not in allowed_formats:
1658 raise ValueError('Link format can be only one of:{} got {}'.format(
1673 raise ValueError('Link format can be only one of:{} got {}'.format(
1659 allowed_formats, link_format))
1674 allowed_formats, link_format))
1660
1675
1661 repo = None
1676 repo = None
1662 if repo_name:
1677 if repo_name:
1663 # Retrieving repo_name to avoid invalid repo_name to explode on
1678 # Retrieving repo_name to avoid invalid repo_name to explode on
1664 # IssueTrackerSettingsModel but still passing invalid name further down
1679 # IssueTrackerSettingsModel but still passing invalid name further down
1665 repo = Repository.get_by_repo_name(repo_name, cache=True)
1680 repo = Repository.get_by_repo_name(repo_name, cache=True)
1666
1681
1667 settings_model = IssueTrackerSettingsModel(repo=repo)
1682 settings_model = IssueTrackerSettingsModel(repo=repo)
1668 active_entries = settings_model.get_settings(cache=True)
1683 active_entries = settings_model.get_settings(cache=True)
1669
1684
1670 issues_data = []
1685 issues_data = []
1671 newtext = text_string
1686 newtext = text_string
1672
1687
1673 for uid, entry in active_entries.items():
1688 for uid, entry in active_entries.items():
1674 log.debug('found issue tracker entry with uid %s' % (uid,))
1689 log.debug('found issue tracker entry with uid %s' % (uid,))
1675
1690
1676 if not (entry['pat'] and entry['url']):
1691 if not (entry['pat'] and entry['url']):
1677 log.debug('skipping due to missing data')
1692 log.debug('skipping due to missing data')
1678 continue
1693 continue
1679
1694
1680 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s'
1695 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s'
1681 % (uid, entry['pat'], entry['url'], entry['pref']))
1696 % (uid, entry['pat'], entry['url'], entry['pref']))
1682
1697
1683 try:
1698 try:
1684 pattern = re.compile(r'%s' % entry['pat'])
1699 pattern = re.compile(r'%s' % entry['pat'])
1685 except re.error:
1700 except re.error:
1686 log.exception(
1701 log.exception(
1687 'issue tracker pattern: `%s` failed to compile',
1702 'issue tracker pattern: `%s` failed to compile',
1688 entry['pat'])
1703 entry['pat'])
1689 continue
1704 continue
1690
1705
1691 data_func = partial(
1706 data_func = partial(
1692 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1707 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1693 return_raw_data=True)
1708 return_raw_data=True)
1694
1709
1695 for match_obj in pattern.finditer(text_string):
1710 for match_obj in pattern.finditer(text_string):
1696 issues_data.append(data_func(match_obj))
1711 issues_data.append(data_func(match_obj))
1697
1712
1698 url_func = partial(
1713 url_func = partial(
1699 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1714 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1700 link_format=link_format)
1715 link_format=link_format)
1701
1716
1702 newtext = pattern.sub(url_func, newtext)
1717 newtext = pattern.sub(url_func, newtext)
1703 log.debug('processed prefix:uid `%s`' % (uid,))
1718 log.debug('processed prefix:uid `%s`' % (uid,))
1704
1719
1705 return newtext, issues_data
1720 return newtext, issues_data
1706
1721
1707
1722
1708 def urlify_commit_message(commit_text, repository=None):
1723 def urlify_commit_message(commit_text, repository=None):
1709 """
1724 """
1710 Parses given text message and makes proper links.
1725 Parses given text message and makes proper links.
1711 issues are linked to given issue-server, and rest is a commit link
1726 issues are linked to given issue-server, and rest is a commit link
1712
1727
1713 :param commit_text:
1728 :param commit_text:
1714 :param repository:
1729 :param repository:
1715 """
1730 """
1716 from pylons import url # doh, we need to re-import url to mock it later
1731 from pylons import url # doh, we need to re-import url to mock it later
1717
1732
1718 def escaper(string):
1733 def escaper(string):
1719 return string.replace('<', '&lt;').replace('>', '&gt;')
1734 return string.replace('<', '&lt;').replace('>', '&gt;')
1720
1735
1721 newtext = escaper(commit_text)
1736 newtext = escaper(commit_text)
1722
1737
1723 # extract http/https links and make them real urls
1738 # extract http/https links and make them real urls
1724 newtext = urlify_text(newtext, safe=False)
1739 newtext = urlify_text(newtext, safe=False)
1725
1740
1726 # urlify commits - extract commit ids and make link out of them, if we have
1741 # urlify commits - extract commit ids and make link out of them, if we have
1727 # the scope of repository present.
1742 # the scope of repository present.
1728 if repository:
1743 if repository:
1729 newtext = urlify_commits(newtext, repository)
1744 newtext = urlify_commits(newtext, repository)
1730
1745
1731 # process issue tracker patterns
1746 # process issue tracker patterns
1732 newtext, issues = process_patterns(newtext, repository or '')
1747 newtext, issues = process_patterns(newtext, repository or '')
1733
1748
1734 return literal(newtext)
1749 return literal(newtext)
1735
1750
1736
1751
1737 def render_binary(repo_name, file_obj):
1752 def render_binary(repo_name, file_obj):
1738 """
1753 """
1739 Choose how to render a binary file
1754 Choose how to render a binary file
1740 """
1755 """
1741 filename = file_obj.name
1756 filename = file_obj.name
1742
1757
1743 # images
1758 # images
1744 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1759 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1745 if fnmatch.fnmatch(filename, pat=ext):
1760 if fnmatch.fnmatch(filename, pat=ext):
1746 alt = filename
1761 alt = filename
1747 src = route_path(
1762 src = route_path(
1748 'repo_file_raw', repo_name=repo_name,
1763 'repo_file_raw', repo_name=repo_name,
1749 commit_id=file_obj.commit.raw_id, f_path=file_obj.path)
1764 commit_id=file_obj.commit.raw_id, f_path=file_obj.path)
1750 return literal('<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1765 return literal('<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1751
1766
1752
1767
1753 def renderer_from_filename(filename, exclude=None):
1768 def renderer_from_filename(filename, exclude=None):
1754 """
1769 """
1755 choose a renderer based on filename, this works only for text based files
1770 choose a renderer based on filename, this works only for text based files
1756 """
1771 """
1757
1772
1758 # ipython
1773 # ipython
1759 for ext in ['*.ipynb']:
1774 for ext in ['*.ipynb']:
1760 if fnmatch.fnmatch(filename, pat=ext):
1775 if fnmatch.fnmatch(filename, pat=ext):
1761 return 'jupyter'
1776 return 'jupyter'
1762
1777
1763 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1778 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1764 if is_markup:
1779 if is_markup:
1765 return is_markup
1780 return is_markup
1766 return None
1781 return None
1767
1782
1768
1783
1769 def render(source, renderer='rst', mentions=False, relative_urls=None,
1784 def render(source, renderer='rst', mentions=False, relative_urls=None,
1770 repo_name=None):
1785 repo_name=None):
1771
1786
1772 def maybe_convert_relative_links(html_source):
1787 def maybe_convert_relative_links(html_source):
1773 if relative_urls:
1788 if relative_urls:
1774 return relative_links(html_source, relative_urls)
1789 return relative_links(html_source, relative_urls)
1775 return html_source
1790 return html_source
1776
1791
1777 if renderer == 'rst':
1792 if renderer == 'rst':
1778 if repo_name:
1793 if repo_name:
1779 # process patterns on comments if we pass in repo name
1794 # process patterns on comments if we pass in repo name
1780 source, issues = process_patterns(
1795 source, issues = process_patterns(
1781 source, repo_name, link_format='rst')
1796 source, repo_name, link_format='rst')
1782
1797
1783 return literal(
1798 return literal(
1784 '<div class="rst-block">%s</div>' %
1799 '<div class="rst-block">%s</div>' %
1785 maybe_convert_relative_links(
1800 maybe_convert_relative_links(
1786 MarkupRenderer.rst(source, mentions=mentions)))
1801 MarkupRenderer.rst(source, mentions=mentions)))
1787 elif renderer == 'markdown':
1802 elif renderer == 'markdown':
1788 if repo_name:
1803 if repo_name:
1789 # process patterns on comments if we pass in repo name
1804 # process patterns on comments if we pass in repo name
1790 source, issues = process_patterns(
1805 source, issues = process_patterns(
1791 source, repo_name, link_format='markdown')
1806 source, repo_name, link_format='markdown')
1792
1807
1793 return literal(
1808 return literal(
1794 '<div class="markdown-block">%s</div>' %
1809 '<div class="markdown-block">%s</div>' %
1795 maybe_convert_relative_links(
1810 maybe_convert_relative_links(
1796 MarkupRenderer.markdown(source, flavored=True,
1811 MarkupRenderer.markdown(source, flavored=True,
1797 mentions=mentions)))
1812 mentions=mentions)))
1798 elif renderer == 'jupyter':
1813 elif renderer == 'jupyter':
1799 return literal(
1814 return literal(
1800 '<div class="ipynb">%s</div>' %
1815 '<div class="ipynb">%s</div>' %
1801 maybe_convert_relative_links(
1816 maybe_convert_relative_links(
1802 MarkupRenderer.jupyter(source)))
1817 MarkupRenderer.jupyter(source)))
1803
1818
1804 # None means just show the file-source
1819 # None means just show the file-source
1805 return None
1820 return None
1806
1821
1807
1822
1808 def commit_status(repo, commit_id):
1823 def commit_status(repo, commit_id):
1809 return ChangesetStatusModel().get_status(repo, commit_id)
1824 return ChangesetStatusModel().get_status(repo, commit_id)
1810
1825
1811
1826
1812 def commit_status_lbl(commit_status):
1827 def commit_status_lbl(commit_status):
1813 return dict(ChangesetStatus.STATUSES).get(commit_status)
1828 return dict(ChangesetStatus.STATUSES).get(commit_status)
1814
1829
1815
1830
1816 def commit_time(repo_name, commit_id):
1831 def commit_time(repo_name, commit_id):
1817 repo = Repository.get_by_repo_name(repo_name)
1832 repo = Repository.get_by_repo_name(repo_name)
1818 commit = repo.get_commit(commit_id=commit_id)
1833 commit = repo.get_commit(commit_id=commit_id)
1819 return commit.date
1834 return commit.date
1820
1835
1821
1836
1822 def get_permission_name(key):
1837 def get_permission_name(key):
1823 return dict(Permission.PERMS).get(key)
1838 return dict(Permission.PERMS).get(key)
1824
1839
1825
1840
1826 def journal_filter_help(request):
1841 def journal_filter_help(request):
1827 _ = request.translate
1842 _ = request.translate
1828
1843
1829 return _(
1844 return _(
1830 'Example filter terms:\n' +
1845 'Example filter terms:\n' +
1831 ' repository:vcs\n' +
1846 ' repository:vcs\n' +
1832 ' username:marcin\n' +
1847 ' username:marcin\n' +
1833 ' username:(NOT marcin)\n' +
1848 ' username:(NOT marcin)\n' +
1834 ' action:*push*\n' +
1849 ' action:*push*\n' +
1835 ' ip:127.0.0.1\n' +
1850 ' ip:127.0.0.1\n' +
1836 ' date:20120101\n' +
1851 ' date:20120101\n' +
1837 ' date:[20120101100000 TO 20120102]\n' +
1852 ' date:[20120101100000 TO 20120102]\n' +
1838 '\n' +
1853 '\n' +
1839 'Generate wildcards using \'*\' character:\n' +
1854 'Generate wildcards using \'*\' character:\n' +
1840 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1855 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1841 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1856 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1842 '\n' +
1857 '\n' +
1843 'Optional AND / OR operators in queries\n' +
1858 'Optional AND / OR operators in queries\n' +
1844 ' "repository:vcs OR repository:test"\n' +
1859 ' "repository:vcs OR repository:test"\n' +
1845 ' "username:test AND repository:test*"\n'
1860 ' "username:test AND repository:test*"\n'
1846 )
1861 )
1847
1862
1848
1863
1849 def search_filter_help(searcher, request):
1864 def search_filter_help(searcher, request):
1850 _ = request.translate
1865 _ = request.translate
1851
1866
1852 terms = ''
1867 terms = ''
1853 return _(
1868 return _(
1854 'Example filter terms for `{searcher}` search:\n' +
1869 'Example filter terms for `{searcher}` search:\n' +
1855 '{terms}\n' +
1870 '{terms}\n' +
1856 'Generate wildcards using \'*\' character:\n' +
1871 'Generate wildcards using \'*\' character:\n' +
1857 ' "repo_name:vcs*" - search everything starting with \'vcs\'\n' +
1872 ' "repo_name:vcs*" - search everything starting with \'vcs\'\n' +
1858 ' "repo_name:*vcs*" - search for repository containing \'vcs\'\n' +
1873 ' "repo_name:*vcs*" - search for repository containing \'vcs\'\n' +
1859 '\n' +
1874 '\n' +
1860 'Optional AND / OR operators in queries\n' +
1875 'Optional AND / OR operators in queries\n' +
1861 ' "repo_name:vcs OR repo_name:test"\n' +
1876 ' "repo_name:vcs OR repo_name:test"\n' +
1862 ' "owner:test AND repo_name:test*"\n' +
1877 ' "owner:test AND repo_name:test*"\n' +
1863 'More: {search_doc}'
1878 'More: {search_doc}'
1864 ).format(searcher=searcher.name,
1879 ).format(searcher=searcher.name,
1865 terms=terms, search_doc=searcher.query_lang_doc)
1880 terms=terms, search_doc=searcher.query_lang_doc)
1866
1881
1867
1882
1868 def not_mapped_error(repo_name):
1883 def not_mapped_error(repo_name):
1869 from rhodecode.translation import _
1884 from rhodecode.translation import _
1870 flash(_('%s repository is not mapped to db perhaps'
1885 flash(_('%s repository is not mapped to db perhaps'
1871 ' it was created or renamed from the filesystem'
1886 ' it was created or renamed from the filesystem'
1872 ' please run the application again'
1887 ' please run the application again'
1873 ' in order to rescan repositories') % repo_name, category='error')
1888 ' in order to rescan repositories') % repo_name, category='error')
1874
1889
1875
1890
1876 def ip_range(ip_addr):
1891 def ip_range(ip_addr):
1877 from rhodecode.model.db import UserIpMap
1892 from rhodecode.model.db import UserIpMap
1878 s, e = UserIpMap._get_ip_range(ip_addr)
1893 s, e = UserIpMap._get_ip_range(ip_addr)
1879 return '%s - %s' % (s, e)
1894 return '%s - %s' % (s, e)
1880
1895
1881
1896
1882 def form(url, method='post', needs_csrf_token=True, **attrs):
1897 def form(url, method='post', needs_csrf_token=True, **attrs):
1883 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1898 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1884 if method.lower() != 'get' and needs_csrf_token:
1899 if method.lower() != 'get' and needs_csrf_token:
1885 raise Exception(
1900 raise Exception(
1886 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1901 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1887 'CSRF token. If the endpoint does not require such token you can ' +
1902 'CSRF token. If the endpoint does not require such token you can ' +
1888 'explicitly set the parameter needs_csrf_token to false.')
1903 'explicitly set the parameter needs_csrf_token to false.')
1889
1904
1890 return wh_form(url, method=method, **attrs)
1905 return wh_form(url, method=method, **attrs)
1891
1906
1892
1907
1893 def secure_form(form_url, method="POST", multipart=False, **attrs):
1908 def secure_form(form_url, method="POST", multipart=False, **attrs):
1894 """Start a form tag that points the action to an url. This
1909 """Start a form tag that points the action to an url. This
1895 form tag will also include the hidden field containing
1910 form tag will also include the hidden field containing
1896 the auth token.
1911 the auth token.
1897
1912
1898 The url options should be given either as a string, or as a
1913 The url options should be given either as a string, or as a
1899 ``url()`` function. The method for the form defaults to POST.
1914 ``url()`` function. The method for the form defaults to POST.
1900
1915
1901 Options:
1916 Options:
1902
1917
1903 ``multipart``
1918 ``multipart``
1904 If set to True, the enctype is set to "multipart/form-data".
1919 If set to True, the enctype is set to "multipart/form-data".
1905 ``method``
1920 ``method``
1906 The method to use when submitting the form, usually either
1921 The method to use when submitting the form, usually either
1907 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1922 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1908 hidden input with name _method is added to simulate the verb
1923 hidden input with name _method is added to simulate the verb
1909 over POST.
1924 over POST.
1910
1925
1911 """
1926 """
1912 from webhelpers.pylonslib.secure_form import insecure_form
1927 from webhelpers.pylonslib.secure_form import insecure_form
1913
1928
1914 session = None
1929 session = None
1915
1930
1916 # TODO(marcink): after pyramid migration require request variable ALWAYS
1931 # TODO(marcink): after pyramid migration require request variable ALWAYS
1917 if 'request' in attrs:
1932 if 'request' in attrs:
1918 session = attrs['request'].session
1933 session = attrs['request'].session
1919 del attrs['request']
1934 del attrs['request']
1920
1935
1921 form = insecure_form(form_url, method, multipart, **attrs)
1936 form = insecure_form(form_url, method, multipart, **attrs)
1922 token = literal(
1937 token = literal(
1923 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1938 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1924 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1939 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1925
1940
1926 return literal("%s\n%s" % (form, token))
1941 return literal("%s\n%s" % (form, token))
1927
1942
1928
1943
1929 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1944 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1930 select_html = select(name, selected, options, **attrs)
1945 select_html = select(name, selected, options, **attrs)
1931 select2 = """
1946 select2 = """
1932 <script>
1947 <script>
1933 $(document).ready(function() {
1948 $(document).ready(function() {
1934 $('#%s').select2({
1949 $('#%s').select2({
1935 containerCssClass: 'drop-menu',
1950 containerCssClass: 'drop-menu',
1936 dropdownCssClass: 'drop-menu-dropdown',
1951 dropdownCssClass: 'drop-menu-dropdown',
1937 dropdownAutoWidth: true%s
1952 dropdownAutoWidth: true%s
1938 });
1953 });
1939 });
1954 });
1940 </script>
1955 </script>
1941 """
1956 """
1942 filter_option = """,
1957 filter_option = """,
1943 minimumResultsForSearch: -1
1958 minimumResultsForSearch: -1
1944 """
1959 """
1945 input_id = attrs.get('id') or name
1960 input_id = attrs.get('id') or name
1946 filter_enabled = "" if enable_filter else filter_option
1961 filter_enabled = "" if enable_filter else filter_option
1947 select_script = literal(select2 % (input_id, filter_enabled))
1962 select_script = literal(select2 % (input_id, filter_enabled))
1948
1963
1949 return literal(select_html+select_script)
1964 return literal(select_html+select_script)
1950
1965
1951
1966
1952 def get_visual_attr(tmpl_context_var, attr_name):
1967 def get_visual_attr(tmpl_context_var, attr_name):
1953 """
1968 """
1954 A safe way to get a variable from visual variable of template context
1969 A safe way to get a variable from visual variable of template context
1955
1970
1956 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1971 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1957 :param attr_name: name of the attribute we fetch from the c.visual
1972 :param attr_name: name of the attribute we fetch from the c.visual
1958 """
1973 """
1959 visual = getattr(tmpl_context_var, 'visual', None)
1974 visual = getattr(tmpl_context_var, 'visual', None)
1960 if not visual:
1975 if not visual:
1961 return
1976 return
1962 else:
1977 else:
1963 return getattr(visual, attr_name, None)
1978 return getattr(visual, attr_name, None)
1964
1979
1965
1980
1966 def get_last_path_part(file_node):
1981 def get_last_path_part(file_node):
1967 if not file_node.path:
1982 if not file_node.path:
1968 return u''
1983 return u''
1969
1984
1970 path = safe_unicode(file_node.path.split('/')[-1])
1985 path = safe_unicode(file_node.path.split('/')[-1])
1971 return u'../' + path
1986 return u'../' + path
1972
1987
1973
1988
1974 def route_url(*args, **kwargs):
1989 def route_url(*args, **kwargs):
1975 """
1990 """
1976 Wrapper around pyramids `route_url` (fully qualified url) function.
1991 Wrapper around pyramids `route_url` (fully qualified url) function.
1977 It is used to generate URLs from within pylons views or templates.
1992 It is used to generate URLs from within pylons views or templates.
1978 This will be removed when pyramid migration if finished.
1993 This will be removed when pyramid migration if finished.
1979 """
1994 """
1980 req = get_current_request()
1995 req = get_current_request()
1981 return req.route_url(*args, **kwargs)
1996 return req.route_url(*args, **kwargs)
1982
1997
1983
1998
1984 def route_path(*args, **kwargs):
1999 def route_path(*args, **kwargs):
1985 """
2000 """
1986 Wrapper around pyramids `route_path` function. It is used to generate
2001 Wrapper around pyramids `route_path` function. It is used to generate
1987 URLs from within pylons views or templates. This will be removed when
2002 URLs from within pylons views or templates. This will be removed when
1988 pyramid migration if finished.
2003 pyramid migration if finished.
1989 """
2004 """
1990 req = get_current_request()
2005 req = get_current_request()
1991 return req.route_path(*args, **kwargs)
2006 return req.route_path(*args, **kwargs)
1992
2007
1993
2008
1994 def route_path_or_none(*args, **kwargs):
2009 def route_path_or_none(*args, **kwargs):
1995 try:
2010 try:
1996 return route_path(*args, **kwargs)
2011 return route_path(*args, **kwargs)
1997 except KeyError:
2012 except KeyError:
1998 return None
2013 return None
1999
2014
2000
2015
2001 def static_url(*args, **kwds):
2016 def static_url(*args, **kwds):
2002 """
2017 """
2003 Wrapper around pyramids `route_path` function. It is used to generate
2018 Wrapper around pyramids `route_path` function. It is used to generate
2004 URLs from within pylons views or templates. This will be removed when
2019 URLs from within pylons views or templates. This will be removed when
2005 pyramid migration if finished.
2020 pyramid migration if finished.
2006 """
2021 """
2007 req = get_current_request()
2022 req = get_current_request()
2008 return req.static_url(*args, **kwds)
2023 return req.static_url(*args, **kwds)
2009
2024
2010
2025
2011 def resource_path(*args, **kwds):
2026 def resource_path(*args, **kwds):
2012 """
2027 """
2013 Wrapper around pyramids `route_path` function. It is used to generate
2028 Wrapper around pyramids `route_path` function. It is used to generate
2014 URLs from within pylons views or templates. This will be removed when
2029 URLs from within pylons views or templates. This will be removed when
2015 pyramid migration if finished.
2030 pyramid migration if finished.
2016 """
2031 """
2017 req = get_current_request()
2032 req = get_current_request()
2018 return req.resource_path(*args, **kwds)
2033 return req.resource_path(*args, **kwds)
2019
2034
2020
2035
2021 def api_call_example(method, args):
2036 def api_call_example(method, args):
2022 """
2037 """
2023 Generates an API call example via CURL
2038 Generates an API call example via CURL
2024 """
2039 """
2025 args_json = json.dumps(OrderedDict([
2040 args_json = json.dumps(OrderedDict([
2026 ('id', 1),
2041 ('id', 1),
2027 ('auth_token', 'SECRET'),
2042 ('auth_token', 'SECRET'),
2028 ('method', method),
2043 ('method', method),
2029 ('args', args)
2044 ('args', args)
2030 ]))
2045 ]))
2031 return literal(
2046 return literal(
2032 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
2047 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
2033 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
2048 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
2034 "and needs to be of `api calls` role."
2049 "and needs to be of `api calls` role."
2035 .format(
2050 .format(
2036 api_url=route_url('apiv2'),
2051 api_url=route_url('apiv2'),
2037 token_url=route_url('my_account_auth_tokens'),
2052 token_url=route_url('my_account_auth_tokens'),
2038 data=args_json))
2053 data=args_json))
2039
2054
2040
2055
2041 def notification_description(notification, request):
2056 def notification_description(notification, request):
2042 """
2057 """
2043 Generate notification human readable description based on notification type
2058 Generate notification human readable description based on notification type
2044 """
2059 """
2045 from rhodecode.model.notification import NotificationModel
2060 from rhodecode.model.notification import NotificationModel
2046 return NotificationModel().make_description(
2061 return NotificationModel().make_description(
2047 notification, translate=request.translate)
2062 notification, translate=request.translate)
@@ -1,1025 +1,1020 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import os
22 import os
23 import re
23 import re
24 import shutil
24 import shutil
25 import time
25 import time
26 import traceback
26 import traceback
27 import datetime
27 import datetime
28
28
29 from pyramid.threadlocal import get_current_request
29 from pyramid.threadlocal import get_current_request
30 from zope.cachedescriptors.property import Lazy as LazyProperty
30 from zope.cachedescriptors.property import Lazy as LazyProperty
31
31
32 from rhodecode import events
32 from rhodecode import events
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib.auth import HasUserGroupPermissionAny
34 from rhodecode.lib.auth import HasUserGroupPermissionAny
35 from rhodecode.lib.caching_query import FromCache
35 from rhodecode.lib.caching_query import FromCache
36 from rhodecode.lib.exceptions import AttachedForksError
36 from rhodecode.lib.exceptions import AttachedForksError
37 from rhodecode.lib.hooks_base import log_delete_repository
37 from rhodecode.lib.hooks_base import log_delete_repository
38 from rhodecode.lib.utils import make_db_config
38 from rhodecode.lib.utils import make_db_config
39 from rhodecode.lib.utils2 import (
39 from rhodecode.lib.utils2 import (
40 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
40 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
41 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
41 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
42 from rhodecode.lib.vcs.backends import get_backend
42 from rhodecode.lib.vcs.backends import get_backend
43 from rhodecode.model import BaseModel
43 from rhodecode.model import BaseModel
44 from rhodecode.model.db import (_hash_key,
44 from rhodecode.model.db import (_hash_key,
45 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
45 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
46 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
46 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
47 RepoGroup, RepositoryField)
47 RepoGroup, RepositoryField)
48
48
49 from rhodecode.model.settings import VcsSettingsModel
49 from rhodecode.model.settings import VcsSettingsModel
50
50
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 class RepoModel(BaseModel):
55 class RepoModel(BaseModel):
56
56
57 cls = Repository
57 cls = Repository
58
58
59 def _get_user_group(self, users_group):
59 def _get_user_group(self, users_group):
60 return self._get_instance(UserGroup, users_group,
60 return self._get_instance(UserGroup, users_group,
61 callback=UserGroup.get_by_group_name)
61 callback=UserGroup.get_by_group_name)
62
62
63 def _get_repo_group(self, repo_group):
63 def _get_repo_group(self, repo_group):
64 return self._get_instance(RepoGroup, repo_group,
64 return self._get_instance(RepoGroup, repo_group,
65 callback=RepoGroup.get_by_group_name)
65 callback=RepoGroup.get_by_group_name)
66
66
67 def _create_default_perms(self, repository, private):
67 def _create_default_perms(self, repository, private):
68 # create default permission
68 # create default permission
69 default = 'repository.read'
69 default = 'repository.read'
70 def_user = User.get_default_user()
70 def_user = User.get_default_user()
71 for p in def_user.user_perms:
71 for p in def_user.user_perms:
72 if p.permission.permission_name.startswith('repository.'):
72 if p.permission.permission_name.startswith('repository.'):
73 default = p.permission.permission_name
73 default = p.permission.permission_name
74 break
74 break
75
75
76 default_perm = 'repository.none' if private else default
76 default_perm = 'repository.none' if private else default
77
77
78 repo_to_perm = UserRepoToPerm()
78 repo_to_perm = UserRepoToPerm()
79 repo_to_perm.permission = Permission.get_by_key(default_perm)
79 repo_to_perm.permission = Permission.get_by_key(default_perm)
80
80
81 repo_to_perm.repository = repository
81 repo_to_perm.repository = repository
82 repo_to_perm.user_id = def_user.user_id
82 repo_to_perm.user_id = def_user.user_id
83
83
84 return repo_to_perm
84 return repo_to_perm
85
85
86 @LazyProperty
86 @LazyProperty
87 def repos_path(self):
87 def repos_path(self):
88 """
88 """
89 Gets the repositories root path from database
89 Gets the repositories root path from database
90 """
90 """
91 settings_model = VcsSettingsModel(sa=self.sa)
91 settings_model = VcsSettingsModel(sa=self.sa)
92 return settings_model.get_repos_location()
92 return settings_model.get_repos_location()
93
93
94 def get(self, repo_id, cache=False):
94 def get(self, repo_id, cache=False):
95 repo = self.sa.query(Repository) \
95 repo = self.sa.query(Repository) \
96 .filter(Repository.repo_id == repo_id)
96 .filter(Repository.repo_id == repo_id)
97
97
98 if cache:
98 if cache:
99 repo = repo.options(
99 repo = repo.options(
100 FromCache("sql_cache_short", "get_repo_%s" % repo_id))
100 FromCache("sql_cache_short", "get_repo_%s" % repo_id))
101 return repo.scalar()
101 return repo.scalar()
102
102
103 def get_repo(self, repository):
103 def get_repo(self, repository):
104 return self._get_repo(repository)
104 return self._get_repo(repository)
105
105
106 def get_by_repo_name(self, repo_name, cache=False):
106 def get_by_repo_name(self, repo_name, cache=False):
107 repo = self.sa.query(Repository) \
107 repo = self.sa.query(Repository) \
108 .filter(Repository.repo_name == repo_name)
108 .filter(Repository.repo_name == repo_name)
109
109
110 if cache:
110 if cache:
111 name_key = _hash_key(repo_name)
111 name_key = _hash_key(repo_name)
112 repo = repo.options(
112 repo = repo.options(
113 FromCache("sql_cache_short", "get_repo_%s" % name_key))
113 FromCache("sql_cache_short", "get_repo_%s" % name_key))
114 return repo.scalar()
114 return repo.scalar()
115
115
116 def _extract_id_from_repo_name(self, repo_name):
116 def _extract_id_from_repo_name(self, repo_name):
117 if repo_name.startswith('/'):
117 if repo_name.startswith('/'):
118 repo_name = repo_name.lstrip('/')
118 repo_name = repo_name.lstrip('/')
119 by_id_match = re.match(r'^_(\d{1,})', repo_name)
119 by_id_match = re.match(r'^_(\d{1,})', repo_name)
120 if by_id_match:
120 if by_id_match:
121 return by_id_match.groups()[0]
121 return by_id_match.groups()[0]
122
122
123 def get_repo_by_id(self, repo_name):
123 def get_repo_by_id(self, repo_name):
124 """
124 """
125 Extracts repo_name by id from special urls.
125 Extracts repo_name by id from special urls.
126 Example url is _11/repo_name
126 Example url is _11/repo_name
127
127
128 :param repo_name:
128 :param repo_name:
129 :return: repo object if matched else None
129 :return: repo object if matched else None
130 """
130 """
131
131
132 try:
132 try:
133 _repo_id = self._extract_id_from_repo_name(repo_name)
133 _repo_id = self._extract_id_from_repo_name(repo_name)
134 if _repo_id:
134 if _repo_id:
135 return self.get(_repo_id)
135 return self.get(_repo_id)
136 except Exception:
136 except Exception:
137 log.exception('Failed to extract repo_name from URL')
137 log.exception('Failed to extract repo_name from URL')
138
138
139 return None
139 return None
140
140
141 def get_repos_for_root(self, root, traverse=False):
141 def get_repos_for_root(self, root, traverse=False):
142 if traverse:
142 if traverse:
143 like_expression = u'{}%'.format(safe_unicode(root))
143 like_expression = u'{}%'.format(safe_unicode(root))
144 repos = Repository.query().filter(
144 repos = Repository.query().filter(
145 Repository.repo_name.like(like_expression)).all()
145 Repository.repo_name.like(like_expression)).all()
146 else:
146 else:
147 if root and not isinstance(root, RepoGroup):
147 if root and not isinstance(root, RepoGroup):
148 raise ValueError(
148 raise ValueError(
149 'Root must be an instance '
149 'Root must be an instance '
150 'of RepoGroup, got:{} instead'.format(type(root)))
150 'of RepoGroup, got:{} instead'.format(type(root)))
151 repos = Repository.query().filter(Repository.group == root).all()
151 repos = Repository.query().filter(Repository.group == root).all()
152 return repos
152 return repos
153
153
154 def get_url(self, repo, request=None, permalink=False):
154 def get_url(self, repo, request=None, permalink=False):
155 if not request:
155 if not request:
156 request = get_current_request()
156 request = get_current_request()
157
157
158 if not request:
158 if not request:
159 return
159 return
160
160
161 if permalink:
161 if permalink:
162 return request.route_url(
162 return request.route_url(
163 'repo_summary', repo_name=safe_str(repo.repo_id))
163 'repo_summary', repo_name=safe_str(repo.repo_id))
164 else:
164 else:
165 return request.route_url(
165 return request.route_url(
166 'repo_summary', repo_name=safe_str(repo.repo_name))
166 'repo_summary', repo_name=safe_str(repo.repo_name))
167
167
168 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
168 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
169 if not request:
169 if not request:
170 request = get_current_request()
170 request = get_current_request()
171
171
172 if not request:
172 if not request:
173 return
173 return
174
174
175 if permalink:
175 if permalink:
176 return request.route_url(
176 return request.route_url(
177 'repo_commit', repo_name=safe_str(repo.repo_id),
177 'repo_commit', repo_name=safe_str(repo.repo_id),
178 commit_id=commit_id)
178 commit_id=commit_id)
179
179
180 else:
180 else:
181 return request.route_url(
181 return request.route_url(
182 'repo_commit', repo_name=safe_str(repo.repo_name),
182 'repo_commit', repo_name=safe_str(repo.repo_name),
183 commit_id=commit_id)
183 commit_id=commit_id)
184
184
185 @classmethod
185 @classmethod
186 def update_repoinfo(cls, repositories=None):
186 def update_repoinfo(cls, repositories=None):
187 if not repositories:
187 if not repositories:
188 repositories = Repository.getAll()
188 repositories = Repository.getAll()
189 for repo in repositories:
189 for repo in repositories:
190 repo.update_commit_cache()
190 repo.update_commit_cache()
191
191
192 def get_repos_as_dict(self, repo_list=None, admin=False,
192 def get_repos_as_dict(self, repo_list=None, admin=False,
193 super_user_actions=False):
193 super_user_actions=False):
194 _render = get_current_request().get_partial_renderer(
194 _render = get_current_request().get_partial_renderer(
195 'data_table/_dt_elements.mako')
195 'data_table/_dt_elements.mako')
196 c = _render.get_call_context()
196 c = _render.get_call_context()
197
197
198 def quick_menu(repo_name):
198 def quick_menu(repo_name):
199 return _render('quick_menu', repo_name)
199 return _render('quick_menu', repo_name)
200
200
201 def repo_lnk(name, rtype, rstate, private, fork_of):
201 def repo_lnk(name, rtype, rstate, private, fork_of):
202 return _render('repo_name', name, rtype, rstate, private, fork_of,
202 return _render('repo_name', name, rtype, rstate, private, fork_of,
203 short_name=not admin, admin=False)
203 short_name=not admin, admin=False)
204
204
205 def last_change(last_change):
205 def last_change(last_change):
206 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
206 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
207 last_change = last_change + datetime.timedelta(seconds=
207 last_change = last_change + datetime.timedelta(seconds=
208 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
208 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
209 return _render("last_change", last_change)
209 return _render("last_change", last_change)
210
210
211 def rss_lnk(repo_name):
211 def rss_lnk(repo_name):
212 return _render("rss", repo_name)
212 return _render("rss", repo_name)
213
213
214 def atom_lnk(repo_name):
214 def atom_lnk(repo_name):
215 return _render("atom", repo_name)
215 return _render("atom", repo_name)
216
216
217 def last_rev(repo_name, cs_cache):
217 def last_rev(repo_name, cs_cache):
218 return _render('revision', repo_name, cs_cache.get('revision'),
218 return _render('revision', repo_name, cs_cache.get('revision'),
219 cs_cache.get('raw_id'), cs_cache.get('author'),
219 cs_cache.get('raw_id'), cs_cache.get('author'),
220 cs_cache.get('message'))
220 cs_cache.get('message'))
221
221
222 def desc(desc):
222 def desc(desc):
223 if c.visual.stylify_metatags:
223 return _render('repo_desc', desc, c.visual.stylify_metatags)
224 desc = h.urlify_text(h.escaped_stylize(desc))
225 else:
226 desc = h.urlify_text(h.html_escape(desc))
227
228 return _render('repo_desc', desc)
229
224
230 def state(repo_state):
225 def state(repo_state):
231 return _render("repo_state", repo_state)
226 return _render("repo_state", repo_state)
232
227
233 def repo_actions(repo_name):
228 def repo_actions(repo_name):
234 return _render('repo_actions', repo_name, super_user_actions)
229 return _render('repo_actions', repo_name, super_user_actions)
235
230
236 def user_profile(username):
231 def user_profile(username):
237 return _render('user_profile', username)
232 return _render('user_profile', username)
238
233
239 repos_data = []
234 repos_data = []
240 for repo in repo_list:
235 for repo in repo_list:
241 cs_cache = repo.changeset_cache
236 cs_cache = repo.changeset_cache
242 row = {
237 row = {
243 "menu": quick_menu(repo.repo_name),
238 "menu": quick_menu(repo.repo_name),
244
239
245 "name": repo_lnk(repo.repo_name, repo.repo_type,
240 "name": repo_lnk(repo.repo_name, repo.repo_type,
246 repo.repo_state, repo.private, repo.fork),
241 repo.repo_state, repo.private, repo.fork),
247 "name_raw": repo.repo_name.lower(),
242 "name_raw": repo.repo_name.lower(),
248
243
249 "last_change": last_change(repo.last_db_change),
244 "last_change": last_change(repo.last_db_change),
250 "last_change_raw": datetime_to_time(repo.last_db_change),
245 "last_change_raw": datetime_to_time(repo.last_db_change),
251
246
252 "last_changeset": last_rev(repo.repo_name, cs_cache),
247 "last_changeset": last_rev(repo.repo_name, cs_cache),
253 "last_changeset_raw": cs_cache.get('revision'),
248 "last_changeset_raw": cs_cache.get('revision'),
254
249
255 "desc": desc(repo.description_safe),
250 "desc": desc(repo.description_safe),
256 "owner": user_profile(repo.user.username),
251 "owner": user_profile(repo.user.username),
257
252
258 "state": state(repo.repo_state),
253 "state": state(repo.repo_state),
259 "rss": rss_lnk(repo.repo_name),
254 "rss": rss_lnk(repo.repo_name),
260
255
261 "atom": atom_lnk(repo.repo_name),
256 "atom": atom_lnk(repo.repo_name),
262 }
257 }
263 if admin:
258 if admin:
264 row.update({
259 row.update({
265 "action": repo_actions(repo.repo_name),
260 "action": repo_actions(repo.repo_name),
266 })
261 })
267 repos_data.append(row)
262 repos_data.append(row)
268
263
269 return repos_data
264 return repos_data
270
265
271 def _get_defaults(self, repo_name):
266 def _get_defaults(self, repo_name):
272 """
267 """
273 Gets information about repository, and returns a dict for
268 Gets information about repository, and returns a dict for
274 usage in forms
269 usage in forms
275
270
276 :param repo_name:
271 :param repo_name:
277 """
272 """
278
273
279 repo_info = Repository.get_by_repo_name(repo_name)
274 repo_info = Repository.get_by_repo_name(repo_name)
280
275
281 if repo_info is None:
276 if repo_info is None:
282 return None
277 return None
283
278
284 defaults = repo_info.get_dict()
279 defaults = repo_info.get_dict()
285 defaults['repo_name'] = repo_info.just_name
280 defaults['repo_name'] = repo_info.just_name
286
281
287 groups = repo_info.groups_with_parents
282 groups = repo_info.groups_with_parents
288 parent_group = groups[-1] if groups else None
283 parent_group = groups[-1] if groups else None
289
284
290 # we use -1 as this is how in HTML, we mark an empty group
285 # we use -1 as this is how in HTML, we mark an empty group
291 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
286 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
292
287
293 keys_to_process = (
288 keys_to_process = (
294 {'k': 'repo_type', 'strip': False},
289 {'k': 'repo_type', 'strip': False},
295 {'k': 'repo_enable_downloads', 'strip': True},
290 {'k': 'repo_enable_downloads', 'strip': True},
296 {'k': 'repo_description', 'strip': True},
291 {'k': 'repo_description', 'strip': True},
297 {'k': 'repo_enable_locking', 'strip': True},
292 {'k': 'repo_enable_locking', 'strip': True},
298 {'k': 'repo_landing_rev', 'strip': True},
293 {'k': 'repo_landing_rev', 'strip': True},
299 {'k': 'clone_uri', 'strip': False},
294 {'k': 'clone_uri', 'strip': False},
300 {'k': 'repo_private', 'strip': True},
295 {'k': 'repo_private', 'strip': True},
301 {'k': 'repo_enable_statistics', 'strip': True}
296 {'k': 'repo_enable_statistics', 'strip': True}
302 )
297 )
303
298
304 for item in keys_to_process:
299 for item in keys_to_process:
305 attr = item['k']
300 attr = item['k']
306 if item['strip']:
301 if item['strip']:
307 attr = remove_prefix(item['k'], 'repo_')
302 attr = remove_prefix(item['k'], 'repo_')
308
303
309 val = defaults[attr]
304 val = defaults[attr]
310 if item['k'] == 'repo_landing_rev':
305 if item['k'] == 'repo_landing_rev':
311 val = ':'.join(defaults[attr])
306 val = ':'.join(defaults[attr])
312 defaults[item['k']] = val
307 defaults[item['k']] = val
313 if item['k'] == 'clone_uri':
308 if item['k'] == 'clone_uri':
314 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
309 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
315
310
316 # fill owner
311 # fill owner
317 if repo_info.user:
312 if repo_info.user:
318 defaults.update({'user': repo_info.user.username})
313 defaults.update({'user': repo_info.user.username})
319 else:
314 else:
320 replacement_user = User.get_first_super_admin().username
315 replacement_user = User.get_first_super_admin().username
321 defaults.update({'user': replacement_user})
316 defaults.update({'user': replacement_user})
322
317
323 return defaults
318 return defaults
324
319
325 def update(self, repo, **kwargs):
320 def update(self, repo, **kwargs):
326 try:
321 try:
327 cur_repo = self._get_repo(repo)
322 cur_repo = self._get_repo(repo)
328 source_repo_name = cur_repo.repo_name
323 source_repo_name = cur_repo.repo_name
329 if 'user' in kwargs:
324 if 'user' in kwargs:
330 cur_repo.user = User.get_by_username(kwargs['user'])
325 cur_repo.user = User.get_by_username(kwargs['user'])
331
326
332 if 'repo_group' in kwargs:
327 if 'repo_group' in kwargs:
333 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
328 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
334 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
329 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
335
330
336 update_keys = [
331 update_keys = [
337 (1, 'repo_description'),
332 (1, 'repo_description'),
338 (1, 'repo_landing_rev'),
333 (1, 'repo_landing_rev'),
339 (1, 'repo_private'),
334 (1, 'repo_private'),
340 (1, 'repo_enable_downloads'),
335 (1, 'repo_enable_downloads'),
341 (1, 'repo_enable_locking'),
336 (1, 'repo_enable_locking'),
342 (1, 'repo_enable_statistics'),
337 (1, 'repo_enable_statistics'),
343 (0, 'clone_uri'),
338 (0, 'clone_uri'),
344 (0, 'fork_id')
339 (0, 'fork_id')
345 ]
340 ]
346 for strip, k in update_keys:
341 for strip, k in update_keys:
347 if k in kwargs:
342 if k in kwargs:
348 val = kwargs[k]
343 val = kwargs[k]
349 if strip:
344 if strip:
350 k = remove_prefix(k, 'repo_')
345 k = remove_prefix(k, 'repo_')
351
346
352 setattr(cur_repo, k, val)
347 setattr(cur_repo, k, val)
353
348
354 new_name = cur_repo.get_new_name(kwargs['repo_name'])
349 new_name = cur_repo.get_new_name(kwargs['repo_name'])
355 cur_repo.repo_name = new_name
350 cur_repo.repo_name = new_name
356
351
357 # if private flag is set, reset default permission to NONE
352 # if private flag is set, reset default permission to NONE
358 if kwargs.get('repo_private'):
353 if kwargs.get('repo_private'):
359 EMPTY_PERM = 'repository.none'
354 EMPTY_PERM = 'repository.none'
360 RepoModel().grant_user_permission(
355 RepoModel().grant_user_permission(
361 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
356 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
362 )
357 )
363
358
364 # handle extra fields
359 # handle extra fields
365 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
360 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
366 kwargs):
361 kwargs):
367 k = RepositoryField.un_prefix_key(field)
362 k = RepositoryField.un_prefix_key(field)
368 ex_field = RepositoryField.get_by_key_name(
363 ex_field = RepositoryField.get_by_key_name(
369 key=k, repo=cur_repo)
364 key=k, repo=cur_repo)
370 if ex_field:
365 if ex_field:
371 ex_field.field_value = kwargs[field]
366 ex_field.field_value = kwargs[field]
372 self.sa.add(ex_field)
367 self.sa.add(ex_field)
373 cur_repo.updated_on = datetime.datetime.now()
368 cur_repo.updated_on = datetime.datetime.now()
374 self.sa.add(cur_repo)
369 self.sa.add(cur_repo)
375
370
376 if source_repo_name != new_name:
371 if source_repo_name != new_name:
377 # rename repository
372 # rename repository
378 self._rename_filesystem_repo(
373 self._rename_filesystem_repo(
379 old=source_repo_name, new=new_name)
374 old=source_repo_name, new=new_name)
380
375
381 return cur_repo
376 return cur_repo
382 except Exception:
377 except Exception:
383 log.error(traceback.format_exc())
378 log.error(traceback.format_exc())
384 raise
379 raise
385
380
386 def _create_repo(self, repo_name, repo_type, description, owner,
381 def _create_repo(self, repo_name, repo_type, description, owner,
387 private=False, clone_uri=None, repo_group=None,
382 private=False, clone_uri=None, repo_group=None,
388 landing_rev='rev:tip', fork_of=None,
383 landing_rev='rev:tip', fork_of=None,
389 copy_fork_permissions=False, enable_statistics=False,
384 copy_fork_permissions=False, enable_statistics=False,
390 enable_locking=False, enable_downloads=False,
385 enable_locking=False, enable_downloads=False,
391 copy_group_permissions=False,
386 copy_group_permissions=False,
392 state=Repository.STATE_PENDING):
387 state=Repository.STATE_PENDING):
393 """
388 """
394 Create repository inside database with PENDING state, this should be
389 Create repository inside database with PENDING state, this should be
395 only executed by create() repo. With exception of importing existing
390 only executed by create() repo. With exception of importing existing
396 repos
391 repos
397 """
392 """
398 from rhodecode.model.scm import ScmModel
393 from rhodecode.model.scm import ScmModel
399
394
400 owner = self._get_user(owner)
395 owner = self._get_user(owner)
401 fork_of = self._get_repo(fork_of)
396 fork_of = self._get_repo(fork_of)
402 repo_group = self._get_repo_group(safe_int(repo_group))
397 repo_group = self._get_repo_group(safe_int(repo_group))
403
398
404 try:
399 try:
405 repo_name = safe_unicode(repo_name)
400 repo_name = safe_unicode(repo_name)
406 description = safe_unicode(description)
401 description = safe_unicode(description)
407 # repo name is just a name of repository
402 # repo name is just a name of repository
408 # while repo_name_full is a full qualified name that is combined
403 # while repo_name_full is a full qualified name that is combined
409 # with name and path of group
404 # with name and path of group
410 repo_name_full = repo_name
405 repo_name_full = repo_name
411 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
406 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
412
407
413 new_repo = Repository()
408 new_repo = Repository()
414 new_repo.repo_state = state
409 new_repo.repo_state = state
415 new_repo.enable_statistics = False
410 new_repo.enable_statistics = False
416 new_repo.repo_name = repo_name_full
411 new_repo.repo_name = repo_name_full
417 new_repo.repo_type = repo_type
412 new_repo.repo_type = repo_type
418 new_repo.user = owner
413 new_repo.user = owner
419 new_repo.group = repo_group
414 new_repo.group = repo_group
420 new_repo.description = description or repo_name
415 new_repo.description = description or repo_name
421 new_repo.private = private
416 new_repo.private = private
422 new_repo.clone_uri = clone_uri
417 new_repo.clone_uri = clone_uri
423 new_repo.landing_rev = landing_rev
418 new_repo.landing_rev = landing_rev
424
419
425 new_repo.enable_statistics = enable_statistics
420 new_repo.enable_statistics = enable_statistics
426 new_repo.enable_locking = enable_locking
421 new_repo.enable_locking = enable_locking
427 new_repo.enable_downloads = enable_downloads
422 new_repo.enable_downloads = enable_downloads
428
423
429 if repo_group:
424 if repo_group:
430 new_repo.enable_locking = repo_group.enable_locking
425 new_repo.enable_locking = repo_group.enable_locking
431
426
432 if fork_of:
427 if fork_of:
433 parent_repo = fork_of
428 parent_repo = fork_of
434 new_repo.fork = parent_repo
429 new_repo.fork = parent_repo
435
430
436 events.trigger(events.RepoPreCreateEvent(new_repo))
431 events.trigger(events.RepoPreCreateEvent(new_repo))
437
432
438 self.sa.add(new_repo)
433 self.sa.add(new_repo)
439
434
440 EMPTY_PERM = 'repository.none'
435 EMPTY_PERM = 'repository.none'
441 if fork_of and copy_fork_permissions:
436 if fork_of and copy_fork_permissions:
442 repo = fork_of
437 repo = fork_of
443 user_perms = UserRepoToPerm.query() \
438 user_perms = UserRepoToPerm.query() \
444 .filter(UserRepoToPerm.repository == repo).all()
439 .filter(UserRepoToPerm.repository == repo).all()
445 group_perms = UserGroupRepoToPerm.query() \
440 group_perms = UserGroupRepoToPerm.query() \
446 .filter(UserGroupRepoToPerm.repository == repo).all()
441 .filter(UserGroupRepoToPerm.repository == repo).all()
447
442
448 for perm in user_perms:
443 for perm in user_perms:
449 UserRepoToPerm.create(
444 UserRepoToPerm.create(
450 perm.user, new_repo, perm.permission)
445 perm.user, new_repo, perm.permission)
451
446
452 for perm in group_perms:
447 for perm in group_perms:
453 UserGroupRepoToPerm.create(
448 UserGroupRepoToPerm.create(
454 perm.users_group, new_repo, perm.permission)
449 perm.users_group, new_repo, perm.permission)
455 # in case we copy permissions and also set this repo to private
450 # in case we copy permissions and also set this repo to private
456 # override the default user permission to make it a private
451 # override the default user permission to make it a private
457 # repo
452 # repo
458 if private:
453 if private:
459 RepoModel(self.sa).grant_user_permission(
454 RepoModel(self.sa).grant_user_permission(
460 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
455 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
461
456
462 elif repo_group and copy_group_permissions:
457 elif repo_group and copy_group_permissions:
463 user_perms = UserRepoGroupToPerm.query() \
458 user_perms = UserRepoGroupToPerm.query() \
464 .filter(UserRepoGroupToPerm.group == repo_group).all()
459 .filter(UserRepoGroupToPerm.group == repo_group).all()
465
460
466 group_perms = UserGroupRepoGroupToPerm.query() \
461 group_perms = UserGroupRepoGroupToPerm.query() \
467 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
462 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
468
463
469 for perm in user_perms:
464 for perm in user_perms:
470 perm_name = perm.permission.permission_name.replace(
465 perm_name = perm.permission.permission_name.replace(
471 'group.', 'repository.')
466 'group.', 'repository.')
472 perm_obj = Permission.get_by_key(perm_name)
467 perm_obj = Permission.get_by_key(perm_name)
473 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
468 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
474
469
475 for perm in group_perms:
470 for perm in group_perms:
476 perm_name = perm.permission.permission_name.replace(
471 perm_name = perm.permission.permission_name.replace(
477 'group.', 'repository.')
472 'group.', 'repository.')
478 perm_obj = Permission.get_by_key(perm_name)
473 perm_obj = Permission.get_by_key(perm_name)
479 UserGroupRepoToPerm.create(
474 UserGroupRepoToPerm.create(
480 perm.users_group, new_repo, perm_obj)
475 perm.users_group, new_repo, perm_obj)
481
476
482 if private:
477 if private:
483 RepoModel(self.sa).grant_user_permission(
478 RepoModel(self.sa).grant_user_permission(
484 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
479 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
485
480
486 else:
481 else:
487 perm_obj = self._create_default_perms(new_repo, private)
482 perm_obj = self._create_default_perms(new_repo, private)
488 self.sa.add(perm_obj)
483 self.sa.add(perm_obj)
489
484
490 # now automatically start following this repository as owner
485 # now automatically start following this repository as owner
491 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
486 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
492 owner.user_id)
487 owner.user_id)
493
488
494 # we need to flush here, in order to check if database won't
489 # we need to flush here, in order to check if database won't
495 # throw any exceptions, create filesystem dirs at the very end
490 # throw any exceptions, create filesystem dirs at the very end
496 self.sa.flush()
491 self.sa.flush()
497 events.trigger(events.RepoCreateEvent(new_repo))
492 events.trigger(events.RepoCreateEvent(new_repo))
498 return new_repo
493 return new_repo
499
494
500 except Exception:
495 except Exception:
501 log.error(traceback.format_exc())
496 log.error(traceback.format_exc())
502 raise
497 raise
503
498
504 def create(self, form_data, cur_user):
499 def create(self, form_data, cur_user):
505 """
500 """
506 Create repository using celery tasks
501 Create repository using celery tasks
507
502
508 :param form_data:
503 :param form_data:
509 :param cur_user:
504 :param cur_user:
510 """
505 """
511 from rhodecode.lib.celerylib import tasks, run_task
506 from rhodecode.lib.celerylib import tasks, run_task
512 return run_task(tasks.create_repo, form_data, cur_user)
507 return run_task(tasks.create_repo, form_data, cur_user)
513
508
514 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
509 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
515 perm_deletions=None, check_perms=True,
510 perm_deletions=None, check_perms=True,
516 cur_user=None):
511 cur_user=None):
517 if not perm_additions:
512 if not perm_additions:
518 perm_additions = []
513 perm_additions = []
519 if not perm_updates:
514 if not perm_updates:
520 perm_updates = []
515 perm_updates = []
521 if not perm_deletions:
516 if not perm_deletions:
522 perm_deletions = []
517 perm_deletions = []
523
518
524 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
519 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
525
520
526 changes = {
521 changes = {
527 'added': [],
522 'added': [],
528 'updated': [],
523 'updated': [],
529 'deleted': []
524 'deleted': []
530 }
525 }
531 # update permissions
526 # update permissions
532 for member_id, perm, member_type in perm_updates:
527 for member_id, perm, member_type in perm_updates:
533 member_id = int(member_id)
528 member_id = int(member_id)
534 if member_type == 'user':
529 if member_type == 'user':
535 member_name = User.get(member_id).username
530 member_name = User.get(member_id).username
536 # this updates also current one if found
531 # this updates also current one if found
537 self.grant_user_permission(
532 self.grant_user_permission(
538 repo=repo, user=member_id, perm=perm)
533 repo=repo, user=member_id, perm=perm)
539 else: # set for user group
534 else: # set for user group
540 # check if we have permissions to alter this usergroup
535 # check if we have permissions to alter this usergroup
541 member_name = UserGroup.get(member_id).users_group_name
536 member_name = UserGroup.get(member_id).users_group_name
542 if not check_perms or HasUserGroupPermissionAny(
537 if not check_perms or HasUserGroupPermissionAny(
543 *req_perms)(member_name, user=cur_user):
538 *req_perms)(member_name, user=cur_user):
544 self.grant_user_group_permission(
539 self.grant_user_group_permission(
545 repo=repo, group_name=member_id, perm=perm)
540 repo=repo, group_name=member_id, perm=perm)
546
541
547 changes['updated'].append({'type': member_type, 'id': member_id,
542 changes['updated'].append({'type': member_type, 'id': member_id,
548 'name': member_name, 'new_perm': perm})
543 'name': member_name, 'new_perm': perm})
549
544
550 # set new permissions
545 # set new permissions
551 for member_id, perm, member_type in perm_additions:
546 for member_id, perm, member_type in perm_additions:
552 member_id = int(member_id)
547 member_id = int(member_id)
553 if member_type == 'user':
548 if member_type == 'user':
554 member_name = User.get(member_id).username
549 member_name = User.get(member_id).username
555 self.grant_user_permission(
550 self.grant_user_permission(
556 repo=repo, user=member_id, perm=perm)
551 repo=repo, user=member_id, perm=perm)
557 else: # set for user group
552 else: # set for user group
558 # check if we have permissions to alter this usergroup
553 # check if we have permissions to alter this usergroup
559 member_name = UserGroup.get(member_id).users_group_name
554 member_name = UserGroup.get(member_id).users_group_name
560 if not check_perms or HasUserGroupPermissionAny(
555 if not check_perms or HasUserGroupPermissionAny(
561 *req_perms)(member_name, user=cur_user):
556 *req_perms)(member_name, user=cur_user):
562 self.grant_user_group_permission(
557 self.grant_user_group_permission(
563 repo=repo, group_name=member_id, perm=perm)
558 repo=repo, group_name=member_id, perm=perm)
564 changes['added'].append({'type': member_type, 'id': member_id,
559 changes['added'].append({'type': member_type, 'id': member_id,
565 'name': member_name, 'new_perm': perm})
560 'name': member_name, 'new_perm': perm})
566 # delete permissions
561 # delete permissions
567 for member_id, perm, member_type in perm_deletions:
562 for member_id, perm, member_type in perm_deletions:
568 member_id = int(member_id)
563 member_id = int(member_id)
569 if member_type == 'user':
564 if member_type == 'user':
570 member_name = User.get(member_id).username
565 member_name = User.get(member_id).username
571 self.revoke_user_permission(repo=repo, user=member_id)
566 self.revoke_user_permission(repo=repo, user=member_id)
572 else: # set for user group
567 else: # set for user group
573 # check if we have permissions to alter this usergroup
568 # check if we have permissions to alter this usergroup
574 member_name = UserGroup.get(member_id).users_group_name
569 member_name = UserGroup.get(member_id).users_group_name
575 if not check_perms or HasUserGroupPermissionAny(
570 if not check_perms or HasUserGroupPermissionAny(
576 *req_perms)(member_name, user=cur_user):
571 *req_perms)(member_name, user=cur_user):
577 self.revoke_user_group_permission(
572 self.revoke_user_group_permission(
578 repo=repo, group_name=member_id)
573 repo=repo, group_name=member_id)
579
574
580 changes['deleted'].append({'type': member_type, 'id': member_id,
575 changes['deleted'].append({'type': member_type, 'id': member_id,
581 'name': member_name, 'new_perm': perm})
576 'name': member_name, 'new_perm': perm})
582 return changes
577 return changes
583
578
584 def create_fork(self, form_data, cur_user):
579 def create_fork(self, form_data, cur_user):
585 """
580 """
586 Simple wrapper into executing celery task for fork creation
581 Simple wrapper into executing celery task for fork creation
587
582
588 :param form_data:
583 :param form_data:
589 :param cur_user:
584 :param cur_user:
590 """
585 """
591 from rhodecode.lib.celerylib import tasks, run_task
586 from rhodecode.lib.celerylib import tasks, run_task
592 return run_task(tasks.create_repo_fork, form_data, cur_user)
587 return run_task(tasks.create_repo_fork, form_data, cur_user)
593
588
594 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
589 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
595 """
590 """
596 Delete given repository, forks parameter defines what do do with
591 Delete given repository, forks parameter defines what do do with
597 attached forks. Throws AttachedForksError if deleted repo has attached
592 attached forks. Throws AttachedForksError if deleted repo has attached
598 forks
593 forks
599
594
600 :param repo:
595 :param repo:
601 :param forks: str 'delete' or 'detach'
596 :param forks: str 'delete' or 'detach'
602 :param fs_remove: remove(archive) repo from filesystem
597 :param fs_remove: remove(archive) repo from filesystem
603 """
598 """
604 if not cur_user:
599 if not cur_user:
605 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
600 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
606 repo = self._get_repo(repo)
601 repo = self._get_repo(repo)
607 if repo:
602 if repo:
608 if forks == 'detach':
603 if forks == 'detach':
609 for r in repo.forks:
604 for r in repo.forks:
610 r.fork = None
605 r.fork = None
611 self.sa.add(r)
606 self.sa.add(r)
612 elif forks == 'delete':
607 elif forks == 'delete':
613 for r in repo.forks:
608 for r in repo.forks:
614 self.delete(r, forks='delete')
609 self.delete(r, forks='delete')
615 elif [f for f in repo.forks]:
610 elif [f for f in repo.forks]:
616 raise AttachedForksError()
611 raise AttachedForksError()
617
612
618 old_repo_dict = repo.get_dict()
613 old_repo_dict = repo.get_dict()
619 events.trigger(events.RepoPreDeleteEvent(repo))
614 events.trigger(events.RepoPreDeleteEvent(repo))
620 try:
615 try:
621 self.sa.delete(repo)
616 self.sa.delete(repo)
622 if fs_remove:
617 if fs_remove:
623 self._delete_filesystem_repo(repo)
618 self._delete_filesystem_repo(repo)
624 else:
619 else:
625 log.debug('skipping removal from filesystem')
620 log.debug('skipping removal from filesystem')
626 old_repo_dict.update({
621 old_repo_dict.update({
627 'deleted_by': cur_user,
622 'deleted_by': cur_user,
628 'deleted_on': time.time(),
623 'deleted_on': time.time(),
629 })
624 })
630 log_delete_repository(**old_repo_dict)
625 log_delete_repository(**old_repo_dict)
631 events.trigger(events.RepoDeleteEvent(repo))
626 events.trigger(events.RepoDeleteEvent(repo))
632 except Exception:
627 except Exception:
633 log.error(traceback.format_exc())
628 log.error(traceback.format_exc())
634 raise
629 raise
635
630
636 def grant_user_permission(self, repo, user, perm):
631 def grant_user_permission(self, repo, user, perm):
637 """
632 """
638 Grant permission for user on given repository, or update existing one
633 Grant permission for user on given repository, or update existing one
639 if found
634 if found
640
635
641 :param repo: Instance of Repository, repository_id, or repository name
636 :param repo: Instance of Repository, repository_id, or repository name
642 :param user: Instance of User, user_id or username
637 :param user: Instance of User, user_id or username
643 :param perm: Instance of Permission, or permission_name
638 :param perm: Instance of Permission, or permission_name
644 """
639 """
645 user = self._get_user(user)
640 user = self._get_user(user)
646 repo = self._get_repo(repo)
641 repo = self._get_repo(repo)
647 permission = self._get_perm(perm)
642 permission = self._get_perm(perm)
648
643
649 # check if we have that permission already
644 # check if we have that permission already
650 obj = self.sa.query(UserRepoToPerm) \
645 obj = self.sa.query(UserRepoToPerm) \
651 .filter(UserRepoToPerm.user == user) \
646 .filter(UserRepoToPerm.user == user) \
652 .filter(UserRepoToPerm.repository == repo) \
647 .filter(UserRepoToPerm.repository == repo) \
653 .scalar()
648 .scalar()
654 if obj is None:
649 if obj is None:
655 # create new !
650 # create new !
656 obj = UserRepoToPerm()
651 obj = UserRepoToPerm()
657 obj.repository = repo
652 obj.repository = repo
658 obj.user = user
653 obj.user = user
659 obj.permission = permission
654 obj.permission = permission
660 self.sa.add(obj)
655 self.sa.add(obj)
661 log.debug('Granted perm %s to %s on %s', perm, user, repo)
656 log.debug('Granted perm %s to %s on %s', perm, user, repo)
662 action_logger_generic(
657 action_logger_generic(
663 'granted permission: {} to user: {} on repo: {}'.format(
658 'granted permission: {} to user: {} on repo: {}'.format(
664 perm, user, repo), namespace='security.repo')
659 perm, user, repo), namespace='security.repo')
665 return obj
660 return obj
666
661
667 def revoke_user_permission(self, repo, user):
662 def revoke_user_permission(self, repo, user):
668 """
663 """
669 Revoke permission for user on given repository
664 Revoke permission for user on given repository
670
665
671 :param repo: Instance of Repository, repository_id, or repository name
666 :param repo: Instance of Repository, repository_id, or repository name
672 :param user: Instance of User, user_id or username
667 :param user: Instance of User, user_id or username
673 """
668 """
674
669
675 user = self._get_user(user)
670 user = self._get_user(user)
676 repo = self._get_repo(repo)
671 repo = self._get_repo(repo)
677
672
678 obj = self.sa.query(UserRepoToPerm) \
673 obj = self.sa.query(UserRepoToPerm) \
679 .filter(UserRepoToPerm.repository == repo) \
674 .filter(UserRepoToPerm.repository == repo) \
680 .filter(UserRepoToPerm.user == user) \
675 .filter(UserRepoToPerm.user == user) \
681 .scalar()
676 .scalar()
682 if obj:
677 if obj:
683 self.sa.delete(obj)
678 self.sa.delete(obj)
684 log.debug('Revoked perm on %s on %s', repo, user)
679 log.debug('Revoked perm on %s on %s', repo, user)
685 action_logger_generic(
680 action_logger_generic(
686 'revoked permission from user: {} on repo: {}'.format(
681 'revoked permission from user: {} on repo: {}'.format(
687 user, repo), namespace='security.repo')
682 user, repo), namespace='security.repo')
688
683
689 def grant_user_group_permission(self, repo, group_name, perm):
684 def grant_user_group_permission(self, repo, group_name, perm):
690 """
685 """
691 Grant permission for user group on given repository, or update
686 Grant permission for user group on given repository, or update
692 existing one if found
687 existing one if found
693
688
694 :param repo: Instance of Repository, repository_id, or repository name
689 :param repo: Instance of Repository, repository_id, or repository name
695 :param group_name: Instance of UserGroup, users_group_id,
690 :param group_name: Instance of UserGroup, users_group_id,
696 or user group name
691 or user group name
697 :param perm: Instance of Permission, or permission_name
692 :param perm: Instance of Permission, or permission_name
698 """
693 """
699 repo = self._get_repo(repo)
694 repo = self._get_repo(repo)
700 group_name = self._get_user_group(group_name)
695 group_name = self._get_user_group(group_name)
701 permission = self._get_perm(perm)
696 permission = self._get_perm(perm)
702
697
703 # check if we have that permission already
698 # check if we have that permission already
704 obj = self.sa.query(UserGroupRepoToPerm) \
699 obj = self.sa.query(UserGroupRepoToPerm) \
705 .filter(UserGroupRepoToPerm.users_group == group_name) \
700 .filter(UserGroupRepoToPerm.users_group == group_name) \
706 .filter(UserGroupRepoToPerm.repository == repo) \
701 .filter(UserGroupRepoToPerm.repository == repo) \
707 .scalar()
702 .scalar()
708
703
709 if obj is None:
704 if obj is None:
710 # create new
705 # create new
711 obj = UserGroupRepoToPerm()
706 obj = UserGroupRepoToPerm()
712
707
713 obj.repository = repo
708 obj.repository = repo
714 obj.users_group = group_name
709 obj.users_group = group_name
715 obj.permission = permission
710 obj.permission = permission
716 self.sa.add(obj)
711 self.sa.add(obj)
717 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
712 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
718 action_logger_generic(
713 action_logger_generic(
719 'granted permission: {} to usergroup: {} on repo: {}'.format(
714 'granted permission: {} to usergroup: {} on repo: {}'.format(
720 perm, group_name, repo), namespace='security.repo')
715 perm, group_name, repo), namespace='security.repo')
721
716
722 return obj
717 return obj
723
718
724 def revoke_user_group_permission(self, repo, group_name):
719 def revoke_user_group_permission(self, repo, group_name):
725 """
720 """
726 Revoke permission for user group on given repository
721 Revoke permission for user group on given repository
727
722
728 :param repo: Instance of Repository, repository_id, or repository name
723 :param repo: Instance of Repository, repository_id, or repository name
729 :param group_name: Instance of UserGroup, users_group_id,
724 :param group_name: Instance of UserGroup, users_group_id,
730 or user group name
725 or user group name
731 """
726 """
732 repo = self._get_repo(repo)
727 repo = self._get_repo(repo)
733 group_name = self._get_user_group(group_name)
728 group_name = self._get_user_group(group_name)
734
729
735 obj = self.sa.query(UserGroupRepoToPerm) \
730 obj = self.sa.query(UserGroupRepoToPerm) \
736 .filter(UserGroupRepoToPerm.repository == repo) \
731 .filter(UserGroupRepoToPerm.repository == repo) \
737 .filter(UserGroupRepoToPerm.users_group == group_name) \
732 .filter(UserGroupRepoToPerm.users_group == group_name) \
738 .scalar()
733 .scalar()
739 if obj:
734 if obj:
740 self.sa.delete(obj)
735 self.sa.delete(obj)
741 log.debug('Revoked perm to %s on %s', repo, group_name)
736 log.debug('Revoked perm to %s on %s', repo, group_name)
742 action_logger_generic(
737 action_logger_generic(
743 'revoked permission from usergroup: {} on repo: {}'.format(
738 'revoked permission from usergroup: {} on repo: {}'.format(
744 group_name, repo), namespace='security.repo')
739 group_name, repo), namespace='security.repo')
745
740
746 def delete_stats(self, repo_name):
741 def delete_stats(self, repo_name):
747 """
742 """
748 removes stats for given repo
743 removes stats for given repo
749
744
750 :param repo_name:
745 :param repo_name:
751 """
746 """
752 repo = self._get_repo(repo_name)
747 repo = self._get_repo(repo_name)
753 try:
748 try:
754 obj = self.sa.query(Statistics) \
749 obj = self.sa.query(Statistics) \
755 .filter(Statistics.repository == repo).scalar()
750 .filter(Statistics.repository == repo).scalar()
756 if obj:
751 if obj:
757 self.sa.delete(obj)
752 self.sa.delete(obj)
758 except Exception:
753 except Exception:
759 log.error(traceback.format_exc())
754 log.error(traceback.format_exc())
760 raise
755 raise
761
756
762 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
757 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
763 field_type='str', field_desc=''):
758 field_type='str', field_desc=''):
764
759
765 repo = self._get_repo(repo_name)
760 repo = self._get_repo(repo_name)
766
761
767 new_field = RepositoryField()
762 new_field = RepositoryField()
768 new_field.repository = repo
763 new_field.repository = repo
769 new_field.field_key = field_key
764 new_field.field_key = field_key
770 new_field.field_type = field_type # python type
765 new_field.field_type = field_type # python type
771 new_field.field_value = field_value
766 new_field.field_value = field_value
772 new_field.field_desc = field_desc
767 new_field.field_desc = field_desc
773 new_field.field_label = field_label
768 new_field.field_label = field_label
774 self.sa.add(new_field)
769 self.sa.add(new_field)
775 return new_field
770 return new_field
776
771
777 def delete_repo_field(self, repo_name, field_key):
772 def delete_repo_field(self, repo_name, field_key):
778 repo = self._get_repo(repo_name)
773 repo = self._get_repo(repo_name)
779 field = RepositoryField.get_by_key_name(field_key, repo)
774 field = RepositoryField.get_by_key_name(field_key, repo)
780 if field:
775 if field:
781 self.sa.delete(field)
776 self.sa.delete(field)
782
777
783 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
778 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
784 clone_uri=None, repo_store_location=None,
779 clone_uri=None, repo_store_location=None,
785 use_global_config=False):
780 use_global_config=False):
786 """
781 """
787 makes repository on filesystem. It's group aware means it'll create
782 makes repository on filesystem. It's group aware means it'll create
788 a repository within a group, and alter the paths accordingly of
783 a repository within a group, and alter the paths accordingly of
789 group location
784 group location
790
785
791 :param repo_name:
786 :param repo_name:
792 :param alias:
787 :param alias:
793 :param parent:
788 :param parent:
794 :param clone_uri:
789 :param clone_uri:
795 :param repo_store_location:
790 :param repo_store_location:
796 """
791 """
797 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
792 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
798 from rhodecode.model.scm import ScmModel
793 from rhodecode.model.scm import ScmModel
799
794
800 if Repository.NAME_SEP in repo_name:
795 if Repository.NAME_SEP in repo_name:
801 raise ValueError(
796 raise ValueError(
802 'repo_name must not contain groups got `%s`' % repo_name)
797 'repo_name must not contain groups got `%s`' % repo_name)
803
798
804 if isinstance(repo_group, RepoGroup):
799 if isinstance(repo_group, RepoGroup):
805 new_parent_path = os.sep.join(repo_group.full_path_splitted)
800 new_parent_path = os.sep.join(repo_group.full_path_splitted)
806 else:
801 else:
807 new_parent_path = repo_group or ''
802 new_parent_path = repo_group or ''
808
803
809 if repo_store_location:
804 if repo_store_location:
810 _paths = [repo_store_location]
805 _paths = [repo_store_location]
811 else:
806 else:
812 _paths = [self.repos_path, new_parent_path, repo_name]
807 _paths = [self.repos_path, new_parent_path, repo_name]
813 # we need to make it str for mercurial
808 # we need to make it str for mercurial
814 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
809 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
815
810
816 # check if this path is not a repository
811 # check if this path is not a repository
817 if is_valid_repo(repo_path, self.repos_path):
812 if is_valid_repo(repo_path, self.repos_path):
818 raise Exception('This path %s is a valid repository' % repo_path)
813 raise Exception('This path %s is a valid repository' % repo_path)
819
814
820 # check if this path is a group
815 # check if this path is a group
821 if is_valid_repo_group(repo_path, self.repos_path):
816 if is_valid_repo_group(repo_path, self.repos_path):
822 raise Exception('This path %s is a valid group' % repo_path)
817 raise Exception('This path %s is a valid group' % repo_path)
823
818
824 log.info('creating repo %s in %s from url: `%s`',
819 log.info('creating repo %s in %s from url: `%s`',
825 repo_name, safe_unicode(repo_path),
820 repo_name, safe_unicode(repo_path),
826 obfuscate_url_pw(clone_uri))
821 obfuscate_url_pw(clone_uri))
827
822
828 backend = get_backend(repo_type)
823 backend = get_backend(repo_type)
829
824
830 config_repo = None if use_global_config else repo_name
825 config_repo = None if use_global_config else repo_name
831 if config_repo and new_parent_path:
826 if config_repo and new_parent_path:
832 config_repo = Repository.NAME_SEP.join(
827 config_repo = Repository.NAME_SEP.join(
833 (new_parent_path, config_repo))
828 (new_parent_path, config_repo))
834 config = make_db_config(clear_session=False, repo=config_repo)
829 config = make_db_config(clear_session=False, repo=config_repo)
835 config.set('extensions', 'largefiles', '')
830 config.set('extensions', 'largefiles', '')
836
831
837 # patch and reset hooks section of UI config to not run any
832 # patch and reset hooks section of UI config to not run any
838 # hooks on creating remote repo
833 # hooks on creating remote repo
839 config.clear_section('hooks')
834 config.clear_section('hooks')
840
835
841 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
836 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
842 if repo_type == 'git':
837 if repo_type == 'git':
843 repo = backend(
838 repo = backend(
844 repo_path, config=config, create=True, src_url=clone_uri,
839 repo_path, config=config, create=True, src_url=clone_uri,
845 bare=True)
840 bare=True)
846 else:
841 else:
847 repo = backend(
842 repo = backend(
848 repo_path, config=config, create=True, src_url=clone_uri)
843 repo_path, config=config, create=True, src_url=clone_uri)
849
844
850 ScmModel().install_hooks(repo, repo_type=repo_type)
845 ScmModel().install_hooks(repo, repo_type=repo_type)
851
846
852 log.debug('Created repo %s with %s backend',
847 log.debug('Created repo %s with %s backend',
853 safe_unicode(repo_name), safe_unicode(repo_type))
848 safe_unicode(repo_name), safe_unicode(repo_type))
854 return repo
849 return repo
855
850
856 def _rename_filesystem_repo(self, old, new):
851 def _rename_filesystem_repo(self, old, new):
857 """
852 """
858 renames repository on filesystem
853 renames repository on filesystem
859
854
860 :param old: old name
855 :param old: old name
861 :param new: new name
856 :param new: new name
862 """
857 """
863 log.info('renaming repo from %s to %s', old, new)
858 log.info('renaming repo from %s to %s', old, new)
864
859
865 old_path = os.path.join(self.repos_path, old)
860 old_path = os.path.join(self.repos_path, old)
866 new_path = os.path.join(self.repos_path, new)
861 new_path = os.path.join(self.repos_path, new)
867 if os.path.isdir(new_path):
862 if os.path.isdir(new_path):
868 raise Exception(
863 raise Exception(
869 'Was trying to rename to already existing dir %s' % new_path
864 'Was trying to rename to already existing dir %s' % new_path
870 )
865 )
871 shutil.move(old_path, new_path)
866 shutil.move(old_path, new_path)
872
867
873 def _delete_filesystem_repo(self, repo):
868 def _delete_filesystem_repo(self, repo):
874 """
869 """
875 removes repo from filesystem, the removal is acctually made by
870 removes repo from filesystem, the removal is acctually made by
876 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
871 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
877 repository is no longer valid for rhodecode, can be undeleted later on
872 repository is no longer valid for rhodecode, can be undeleted later on
878 by reverting the renames on this repository
873 by reverting the renames on this repository
879
874
880 :param repo: repo object
875 :param repo: repo object
881 """
876 """
882 rm_path = os.path.join(self.repos_path, repo.repo_name)
877 rm_path = os.path.join(self.repos_path, repo.repo_name)
883 repo_group = repo.group
878 repo_group = repo.group
884 log.info("Removing repository %s", rm_path)
879 log.info("Removing repository %s", rm_path)
885 # disable hg/git internal that it doesn't get detected as repo
880 # disable hg/git internal that it doesn't get detected as repo
886 alias = repo.repo_type
881 alias = repo.repo_type
887
882
888 config = make_db_config(clear_session=False)
883 config = make_db_config(clear_session=False)
889 config.set('extensions', 'largefiles', '')
884 config.set('extensions', 'largefiles', '')
890 bare = getattr(repo.scm_instance(config=config), 'bare', False)
885 bare = getattr(repo.scm_instance(config=config), 'bare', False)
891
886
892 # skip this for bare git repos
887 # skip this for bare git repos
893 if not bare:
888 if not bare:
894 # disable VCS repo
889 # disable VCS repo
895 vcs_path = os.path.join(rm_path, '.%s' % alias)
890 vcs_path = os.path.join(rm_path, '.%s' % alias)
896 if os.path.exists(vcs_path):
891 if os.path.exists(vcs_path):
897 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
892 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
898
893
899 _now = datetime.datetime.now()
894 _now = datetime.datetime.now()
900 _ms = str(_now.microsecond).rjust(6, '0')
895 _ms = str(_now.microsecond).rjust(6, '0')
901 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
896 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
902 repo.just_name)
897 repo.just_name)
903 if repo_group:
898 if repo_group:
904 # if repository is in group, prefix the removal path with the group
899 # if repository is in group, prefix the removal path with the group
905 args = repo_group.full_path_splitted + [_d]
900 args = repo_group.full_path_splitted + [_d]
906 _d = os.path.join(*args)
901 _d = os.path.join(*args)
907
902
908 if os.path.isdir(rm_path):
903 if os.path.isdir(rm_path):
909 shutil.move(rm_path, os.path.join(self.repos_path, _d))
904 shutil.move(rm_path, os.path.join(self.repos_path, _d))
910
905
911
906
912 class ReadmeFinder:
907 class ReadmeFinder:
913 """
908 """
914 Utility which knows how to find a readme for a specific commit.
909 Utility which knows how to find a readme for a specific commit.
915
910
916 The main idea is that this is a configurable algorithm. When creating an
911 The main idea is that this is a configurable algorithm. When creating an
917 instance you can define parameters, currently only the `default_renderer`.
912 instance you can define parameters, currently only the `default_renderer`.
918 Based on this configuration the method :meth:`search` behaves slightly
913 Based on this configuration the method :meth:`search` behaves slightly
919 different.
914 different.
920 """
915 """
921
916
922 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
917 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
923 path_re = re.compile(r'^docs?', re.IGNORECASE)
918 path_re = re.compile(r'^docs?', re.IGNORECASE)
924
919
925 default_priorities = {
920 default_priorities = {
926 None: 0,
921 None: 0,
927 '.text': 2,
922 '.text': 2,
928 '.txt': 3,
923 '.txt': 3,
929 '.rst': 1,
924 '.rst': 1,
930 '.rest': 2,
925 '.rest': 2,
931 '.md': 1,
926 '.md': 1,
932 '.mkdn': 2,
927 '.mkdn': 2,
933 '.mdown': 3,
928 '.mdown': 3,
934 '.markdown': 4,
929 '.markdown': 4,
935 }
930 }
936
931
937 path_priority = {
932 path_priority = {
938 'doc': 0,
933 'doc': 0,
939 'docs': 1,
934 'docs': 1,
940 }
935 }
941
936
942 FALLBACK_PRIORITY = 99
937 FALLBACK_PRIORITY = 99
943
938
944 RENDERER_TO_EXTENSION = {
939 RENDERER_TO_EXTENSION = {
945 'rst': ['.rst', '.rest'],
940 'rst': ['.rst', '.rest'],
946 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
941 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
947 }
942 }
948
943
949 def __init__(self, default_renderer=None):
944 def __init__(self, default_renderer=None):
950 self._default_renderer = default_renderer
945 self._default_renderer = default_renderer
951 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
946 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
952 default_renderer, [])
947 default_renderer, [])
953
948
954 def search(self, commit, path='/'):
949 def search(self, commit, path='/'):
955 """
950 """
956 Find a readme in the given `commit`.
951 Find a readme in the given `commit`.
957 """
952 """
958 nodes = commit.get_nodes(path)
953 nodes = commit.get_nodes(path)
959 matches = self._match_readmes(nodes)
954 matches = self._match_readmes(nodes)
960 matches = self._sort_according_to_priority(matches)
955 matches = self._sort_according_to_priority(matches)
961 if matches:
956 if matches:
962 return matches[0].node
957 return matches[0].node
963
958
964 paths = self._match_paths(nodes)
959 paths = self._match_paths(nodes)
965 paths = self._sort_paths_according_to_priority(paths)
960 paths = self._sort_paths_according_to_priority(paths)
966 for path in paths:
961 for path in paths:
967 match = self.search(commit, path=path)
962 match = self.search(commit, path=path)
968 if match:
963 if match:
969 return match
964 return match
970
965
971 return None
966 return None
972
967
973 def _match_readmes(self, nodes):
968 def _match_readmes(self, nodes):
974 for node in nodes:
969 for node in nodes:
975 if not node.is_file():
970 if not node.is_file():
976 continue
971 continue
977 path = node.path.rsplit('/', 1)[-1]
972 path = node.path.rsplit('/', 1)[-1]
978 match = self.readme_re.match(path)
973 match = self.readme_re.match(path)
979 if match:
974 if match:
980 extension = match.group(1)
975 extension = match.group(1)
981 yield ReadmeMatch(node, match, self._priority(extension))
976 yield ReadmeMatch(node, match, self._priority(extension))
982
977
983 def _match_paths(self, nodes):
978 def _match_paths(self, nodes):
984 for node in nodes:
979 for node in nodes:
985 if not node.is_dir():
980 if not node.is_dir():
986 continue
981 continue
987 match = self.path_re.match(node.path)
982 match = self.path_re.match(node.path)
988 if match:
983 if match:
989 yield node.path
984 yield node.path
990
985
991 def _priority(self, extension):
986 def _priority(self, extension):
992 renderer_priority = (
987 renderer_priority = (
993 0 if extension in self._renderer_extensions else 1)
988 0 if extension in self._renderer_extensions else 1)
994 extension_priority = self.default_priorities.get(
989 extension_priority = self.default_priorities.get(
995 extension, self.FALLBACK_PRIORITY)
990 extension, self.FALLBACK_PRIORITY)
996 return (renderer_priority, extension_priority)
991 return (renderer_priority, extension_priority)
997
992
998 def _sort_according_to_priority(self, matches):
993 def _sort_according_to_priority(self, matches):
999
994
1000 def priority_and_path(match):
995 def priority_and_path(match):
1001 return (match.priority, match.path)
996 return (match.priority, match.path)
1002
997
1003 return sorted(matches, key=priority_and_path)
998 return sorted(matches, key=priority_and_path)
1004
999
1005 def _sort_paths_according_to_priority(self, paths):
1000 def _sort_paths_according_to_priority(self, paths):
1006
1001
1007 def priority_and_path(path):
1002 def priority_and_path(path):
1008 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1003 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1009
1004
1010 return sorted(paths, key=priority_and_path)
1005 return sorted(paths, key=priority_and_path)
1011
1006
1012
1007
1013 class ReadmeMatch:
1008 class ReadmeMatch:
1014
1009
1015 def __init__(self, node, match, priority):
1010 def __init__(self, node, match, priority):
1016 self.node = node
1011 self.node = node
1017 self._match = match
1012 self._match = match
1018 self.priority = priority
1013 self.priority = priority
1019
1014
1020 @property
1015 @property
1021 def path(self):
1016 def path(self):
1022 return self.node.path
1017 return self.node.path
1023
1018
1024 def __repr__(self):
1019 def __repr__(self):
1025 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
1020 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
@@ -1,744 +1,738 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 repo group model for RhodeCode
23 repo group model for RhodeCode
24 """
24 """
25
25
26 import os
26 import os
27 import datetime
27 import datetime
28 import itertools
28 import itertools
29 import logging
29 import logging
30 import shutil
30 import shutil
31 import traceback
31 import traceback
32 import string
32 import string
33
33
34 from zope.cachedescriptors.property import Lazy as LazyProperty
34 from zope.cachedescriptors.property import Lazy as LazyProperty
35
35
36 from rhodecode import events
36 from rhodecode import events
37 from rhodecode.model import BaseModel
37 from rhodecode.model import BaseModel
38 from rhodecode.model.db import (_hash_key,
38 from rhodecode.model.db import (_hash_key,
39 RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
39 RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
40 UserGroup, Repository)
40 UserGroup, Repository)
41 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
41 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
42 from rhodecode.lib.caching_query import FromCache
42 from rhodecode.lib.caching_query import FromCache
43 from rhodecode.lib.utils2 import action_logger_generic, datetime_to_time
43 from rhodecode.lib.utils2 import action_logger_generic, datetime_to_time
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class RepoGroupModel(BaseModel):
48 class RepoGroupModel(BaseModel):
49
49
50 cls = RepoGroup
50 cls = RepoGroup
51 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
51 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
52 PERSONAL_GROUP_PATTERN = '${username}' # default
52 PERSONAL_GROUP_PATTERN = '${username}' # default
53
53
54 def _get_user_group(self, users_group):
54 def _get_user_group(self, users_group):
55 return self._get_instance(UserGroup, users_group,
55 return self._get_instance(UserGroup, users_group,
56 callback=UserGroup.get_by_group_name)
56 callback=UserGroup.get_by_group_name)
57
57
58 def _get_repo_group(self, repo_group):
58 def _get_repo_group(self, repo_group):
59 return self._get_instance(RepoGroup, repo_group,
59 return self._get_instance(RepoGroup, repo_group,
60 callback=RepoGroup.get_by_group_name)
60 callback=RepoGroup.get_by_group_name)
61
61
62 @LazyProperty
62 @LazyProperty
63 def repos_path(self):
63 def repos_path(self):
64 """
64 """
65 Gets the repositories root path from database
65 Gets the repositories root path from database
66 """
66 """
67
67
68 settings_model = VcsSettingsModel(sa=self.sa)
68 settings_model = VcsSettingsModel(sa=self.sa)
69 return settings_model.get_repos_location()
69 return settings_model.get_repos_location()
70
70
71 def get_by_group_name(self, repo_group_name, cache=None):
71 def get_by_group_name(self, repo_group_name, cache=None):
72 repo = self.sa.query(RepoGroup) \
72 repo = self.sa.query(RepoGroup) \
73 .filter(RepoGroup.group_name == repo_group_name)
73 .filter(RepoGroup.group_name == repo_group_name)
74
74
75 if cache:
75 if cache:
76 name_key = _hash_key(repo_group_name)
76 name_key = _hash_key(repo_group_name)
77 repo = repo.options(
77 repo = repo.options(
78 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
78 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
79 return repo.scalar()
79 return repo.scalar()
80
80
81 def get_default_create_personal_repo_group(self):
81 def get_default_create_personal_repo_group(self):
82 value = SettingsModel().get_setting_by_name(
82 value = SettingsModel().get_setting_by_name(
83 'create_personal_repo_group')
83 'create_personal_repo_group')
84 return value.app_settings_value if value else None or False
84 return value.app_settings_value if value else None or False
85
85
86 def get_personal_group_name_pattern(self):
86 def get_personal_group_name_pattern(self):
87 value = SettingsModel().get_setting_by_name(
87 value = SettingsModel().get_setting_by_name(
88 'personal_repo_group_pattern')
88 'personal_repo_group_pattern')
89 val = value.app_settings_value if value else None
89 val = value.app_settings_value if value else None
90 group_template = val or self.PERSONAL_GROUP_PATTERN
90 group_template = val or self.PERSONAL_GROUP_PATTERN
91
91
92 group_template = group_template.lstrip('/')
92 group_template = group_template.lstrip('/')
93 return group_template
93 return group_template
94
94
95 def get_personal_group_name(self, user):
95 def get_personal_group_name(self, user):
96 template = self.get_personal_group_name_pattern()
96 template = self.get_personal_group_name_pattern()
97 return string.Template(template).safe_substitute(
97 return string.Template(template).safe_substitute(
98 username=user.username,
98 username=user.username,
99 user_id=user.user_id,
99 user_id=user.user_id,
100 )
100 )
101
101
102 def create_personal_repo_group(self, user, commit_early=True):
102 def create_personal_repo_group(self, user, commit_early=True):
103 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
103 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
104 personal_repo_group_name = self.get_personal_group_name(user)
104 personal_repo_group_name = self.get_personal_group_name(user)
105
105
106 # create a new one
106 # create a new one
107 RepoGroupModel().create(
107 RepoGroupModel().create(
108 group_name=personal_repo_group_name,
108 group_name=personal_repo_group_name,
109 group_description=desc,
109 group_description=desc,
110 owner=user.username,
110 owner=user.username,
111 personal=True,
111 personal=True,
112 commit_early=commit_early)
112 commit_early=commit_early)
113
113
114 def _create_default_perms(self, new_group):
114 def _create_default_perms(self, new_group):
115 # create default permission
115 # create default permission
116 default_perm = 'group.read'
116 default_perm = 'group.read'
117 def_user = User.get_default_user()
117 def_user = User.get_default_user()
118 for p in def_user.user_perms:
118 for p in def_user.user_perms:
119 if p.permission.permission_name.startswith('group.'):
119 if p.permission.permission_name.startswith('group.'):
120 default_perm = p.permission.permission_name
120 default_perm = p.permission.permission_name
121 break
121 break
122
122
123 repo_group_to_perm = UserRepoGroupToPerm()
123 repo_group_to_perm = UserRepoGroupToPerm()
124 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
124 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
125
125
126 repo_group_to_perm.group = new_group
126 repo_group_to_perm.group = new_group
127 repo_group_to_perm.user_id = def_user.user_id
127 repo_group_to_perm.user_id = def_user.user_id
128 return repo_group_to_perm
128 return repo_group_to_perm
129
129
130 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
130 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
131 get_object=False):
131 get_object=False):
132 """
132 """
133 Get's the group name and a parent group name from given group name.
133 Get's the group name and a parent group name from given group name.
134 If repo_in_path is set to truth, we asume the full path also includes
134 If repo_in_path is set to truth, we asume the full path also includes
135 repo name, in such case we clean the last element.
135 repo name, in such case we clean the last element.
136
136
137 :param group_name_full:
137 :param group_name_full:
138 """
138 """
139 split_paths = 1
139 split_paths = 1
140 if repo_in_path:
140 if repo_in_path:
141 split_paths = 2
141 split_paths = 2
142 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
142 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
143
143
144 if repo_in_path and len(_parts) > 1:
144 if repo_in_path and len(_parts) > 1:
145 # such case last element is the repo_name
145 # such case last element is the repo_name
146 _parts.pop(-1)
146 _parts.pop(-1)
147 group_name_cleaned = _parts[-1] # just the group name
147 group_name_cleaned = _parts[-1] # just the group name
148 parent_repo_group_name = None
148 parent_repo_group_name = None
149
149
150 if len(_parts) > 1:
150 if len(_parts) > 1:
151 parent_repo_group_name = _parts[0]
151 parent_repo_group_name = _parts[0]
152
152
153 parent_group = None
153 parent_group = None
154 if parent_repo_group_name:
154 if parent_repo_group_name:
155 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
155 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
156
156
157 if get_object:
157 if get_object:
158 return group_name_cleaned, parent_repo_group_name, parent_group
158 return group_name_cleaned, parent_repo_group_name, parent_group
159
159
160 return group_name_cleaned, parent_repo_group_name
160 return group_name_cleaned, parent_repo_group_name
161
161
162 def check_exist_filesystem(self, group_name, exc_on_failure=True):
162 def check_exist_filesystem(self, group_name, exc_on_failure=True):
163 create_path = os.path.join(self.repos_path, group_name)
163 create_path = os.path.join(self.repos_path, group_name)
164 log.debug('creating new group in %s', create_path)
164 log.debug('creating new group in %s', create_path)
165
165
166 if os.path.isdir(create_path):
166 if os.path.isdir(create_path):
167 if exc_on_failure:
167 if exc_on_failure:
168 abs_create_path = os.path.abspath(create_path)
168 abs_create_path = os.path.abspath(create_path)
169 raise Exception('Directory `{}` already exists !'.format(abs_create_path))
169 raise Exception('Directory `{}` already exists !'.format(abs_create_path))
170 return False
170 return False
171 return True
171 return True
172
172
173 def _create_group(self, group_name):
173 def _create_group(self, group_name):
174 """
174 """
175 makes repository group on filesystem
175 makes repository group on filesystem
176
176
177 :param repo_name:
177 :param repo_name:
178 :param parent_id:
178 :param parent_id:
179 """
179 """
180
180
181 self.check_exist_filesystem(group_name)
181 self.check_exist_filesystem(group_name)
182 create_path = os.path.join(self.repos_path, group_name)
182 create_path = os.path.join(self.repos_path, group_name)
183 log.debug('creating new group in %s', create_path)
183 log.debug('creating new group in %s', create_path)
184 os.makedirs(create_path, mode=0755)
184 os.makedirs(create_path, mode=0755)
185 log.debug('created group in %s', create_path)
185 log.debug('created group in %s', create_path)
186
186
187 def _rename_group(self, old, new):
187 def _rename_group(self, old, new):
188 """
188 """
189 Renames a group on filesystem
189 Renames a group on filesystem
190
190
191 :param group_name:
191 :param group_name:
192 """
192 """
193
193
194 if old == new:
194 if old == new:
195 log.debug('skipping group rename')
195 log.debug('skipping group rename')
196 return
196 return
197
197
198 log.debug('renaming repository group from %s to %s', old, new)
198 log.debug('renaming repository group from %s to %s', old, new)
199
199
200 old_path = os.path.join(self.repos_path, old)
200 old_path = os.path.join(self.repos_path, old)
201 new_path = os.path.join(self.repos_path, new)
201 new_path = os.path.join(self.repos_path, new)
202
202
203 log.debug('renaming repos paths from %s to %s', old_path, new_path)
203 log.debug('renaming repos paths from %s to %s', old_path, new_path)
204
204
205 if os.path.isdir(new_path):
205 if os.path.isdir(new_path):
206 raise Exception('Was trying to rename to already '
206 raise Exception('Was trying to rename to already '
207 'existing dir %s' % new_path)
207 'existing dir %s' % new_path)
208 shutil.move(old_path, new_path)
208 shutil.move(old_path, new_path)
209
209
210 def _delete_filesystem_group(self, group, force_delete=False):
210 def _delete_filesystem_group(self, group, force_delete=False):
211 """
211 """
212 Deletes a group from a filesystem
212 Deletes a group from a filesystem
213
213
214 :param group: instance of group from database
214 :param group: instance of group from database
215 :param force_delete: use shutil rmtree to remove all objects
215 :param force_delete: use shutil rmtree to remove all objects
216 """
216 """
217 paths = group.full_path.split(RepoGroup.url_sep())
217 paths = group.full_path.split(RepoGroup.url_sep())
218 paths = os.sep.join(paths)
218 paths = os.sep.join(paths)
219
219
220 rm_path = os.path.join(self.repos_path, paths)
220 rm_path = os.path.join(self.repos_path, paths)
221 log.info("Removing group %s", rm_path)
221 log.info("Removing group %s", rm_path)
222 # delete only if that path really exists
222 # delete only if that path really exists
223 if os.path.isdir(rm_path):
223 if os.path.isdir(rm_path):
224 if force_delete:
224 if force_delete:
225 shutil.rmtree(rm_path)
225 shutil.rmtree(rm_path)
226 else:
226 else:
227 # archive that group`
227 # archive that group`
228 _now = datetime.datetime.now()
228 _now = datetime.datetime.now()
229 _ms = str(_now.microsecond).rjust(6, '0')
229 _ms = str(_now.microsecond).rjust(6, '0')
230 _d = 'rm__%s_GROUP_%s' % (
230 _d = 'rm__%s_GROUP_%s' % (
231 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
231 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
232 shutil.move(rm_path, os.path.join(self.repos_path, _d))
232 shutil.move(rm_path, os.path.join(self.repos_path, _d))
233
233
234 def create(self, group_name, group_description, owner, just_db=False,
234 def create(self, group_name, group_description, owner, just_db=False,
235 copy_permissions=False, personal=None, commit_early=True):
235 copy_permissions=False, personal=None, commit_early=True):
236
236
237 (group_name_cleaned,
237 (group_name_cleaned,
238 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
238 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
239
239
240 parent_group = None
240 parent_group = None
241 if parent_group_name:
241 if parent_group_name:
242 parent_group = self._get_repo_group(parent_group_name)
242 parent_group = self._get_repo_group(parent_group_name)
243 if not parent_group:
243 if not parent_group:
244 # we tried to create a nested group, but the parent is not
244 # we tried to create a nested group, but the parent is not
245 # existing
245 # existing
246 raise ValueError(
246 raise ValueError(
247 'Parent group `%s` given in `%s` group name '
247 'Parent group `%s` given in `%s` group name '
248 'is not yet existing.' % (parent_group_name, group_name))
248 'is not yet existing.' % (parent_group_name, group_name))
249
249
250 # because we are doing a cleanup, we need to check if such directory
250 # because we are doing a cleanup, we need to check if such directory
251 # already exists. If we don't do that we can accidentally delete
251 # already exists. If we don't do that we can accidentally delete
252 # existing directory via cleanup that can cause data issues, since
252 # existing directory via cleanup that can cause data issues, since
253 # delete does a folder rename to special syntax later cleanup
253 # delete does a folder rename to special syntax later cleanup
254 # functions can delete this
254 # functions can delete this
255 cleanup_group = self.check_exist_filesystem(group_name,
255 cleanup_group = self.check_exist_filesystem(group_name,
256 exc_on_failure=False)
256 exc_on_failure=False)
257 try:
257 try:
258 user = self._get_user(owner)
258 user = self._get_user(owner)
259 new_repo_group = RepoGroup()
259 new_repo_group = RepoGroup()
260 new_repo_group.user = user
260 new_repo_group.user = user
261 new_repo_group.group_description = group_description or group_name
261 new_repo_group.group_description = group_description or group_name
262 new_repo_group.parent_group = parent_group
262 new_repo_group.parent_group = parent_group
263 new_repo_group.group_name = group_name
263 new_repo_group.group_name = group_name
264 new_repo_group.personal = personal
264 new_repo_group.personal = personal
265
265
266 self.sa.add(new_repo_group)
266 self.sa.add(new_repo_group)
267
267
268 # create an ADMIN permission for owner except if we're super admin,
268 # create an ADMIN permission for owner except if we're super admin,
269 # later owner should go into the owner field of groups
269 # later owner should go into the owner field of groups
270 if not user.is_admin:
270 if not user.is_admin:
271 self.grant_user_permission(repo_group=new_repo_group,
271 self.grant_user_permission(repo_group=new_repo_group,
272 user=owner, perm='group.admin')
272 user=owner, perm='group.admin')
273
273
274 if parent_group and copy_permissions:
274 if parent_group and copy_permissions:
275 # copy permissions from parent
275 # copy permissions from parent
276 user_perms = UserRepoGroupToPerm.query() \
276 user_perms = UserRepoGroupToPerm.query() \
277 .filter(UserRepoGroupToPerm.group == parent_group).all()
277 .filter(UserRepoGroupToPerm.group == parent_group).all()
278
278
279 group_perms = UserGroupRepoGroupToPerm.query() \
279 group_perms = UserGroupRepoGroupToPerm.query() \
280 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
280 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
281
281
282 for perm in user_perms:
282 for perm in user_perms:
283 # don't copy over the permission for user who is creating
283 # don't copy over the permission for user who is creating
284 # this group, if he is not super admin he get's admin
284 # this group, if he is not super admin he get's admin
285 # permission set above
285 # permission set above
286 if perm.user != user or user.is_admin:
286 if perm.user != user or user.is_admin:
287 UserRepoGroupToPerm.create(
287 UserRepoGroupToPerm.create(
288 perm.user, new_repo_group, perm.permission)
288 perm.user, new_repo_group, perm.permission)
289
289
290 for perm in group_perms:
290 for perm in group_perms:
291 UserGroupRepoGroupToPerm.create(
291 UserGroupRepoGroupToPerm.create(
292 perm.users_group, new_repo_group, perm.permission)
292 perm.users_group, new_repo_group, perm.permission)
293 else:
293 else:
294 perm_obj = self._create_default_perms(new_repo_group)
294 perm_obj = self._create_default_perms(new_repo_group)
295 self.sa.add(perm_obj)
295 self.sa.add(perm_obj)
296
296
297 # now commit the changes, earlier so we are sure everything is in
297 # now commit the changes, earlier so we are sure everything is in
298 # the database.
298 # the database.
299 if commit_early:
299 if commit_early:
300 self.sa.commit()
300 self.sa.commit()
301 if not just_db:
301 if not just_db:
302 self._create_group(new_repo_group.group_name)
302 self._create_group(new_repo_group.group_name)
303
303
304 # trigger the post hook
304 # trigger the post hook
305 from rhodecode.lib.hooks_base import log_create_repository_group
305 from rhodecode.lib.hooks_base import log_create_repository_group
306 repo_group = RepoGroup.get_by_group_name(group_name)
306 repo_group = RepoGroup.get_by_group_name(group_name)
307 log_create_repository_group(
307 log_create_repository_group(
308 created_by=user.username, **repo_group.get_dict())
308 created_by=user.username, **repo_group.get_dict())
309
309
310 # Trigger create event.
310 # Trigger create event.
311 events.trigger(events.RepoGroupCreateEvent(repo_group))
311 events.trigger(events.RepoGroupCreateEvent(repo_group))
312
312
313 return new_repo_group
313 return new_repo_group
314 except Exception:
314 except Exception:
315 self.sa.rollback()
315 self.sa.rollback()
316 log.exception('Exception occurred when creating repository group, '
316 log.exception('Exception occurred when creating repository group, '
317 'doing cleanup...')
317 'doing cleanup...')
318 # rollback things manually !
318 # rollback things manually !
319 repo_group = RepoGroup.get_by_group_name(group_name)
319 repo_group = RepoGroup.get_by_group_name(group_name)
320 if repo_group:
320 if repo_group:
321 RepoGroup.delete(repo_group.group_id)
321 RepoGroup.delete(repo_group.group_id)
322 self.sa.commit()
322 self.sa.commit()
323 if cleanup_group:
323 if cleanup_group:
324 RepoGroupModel()._delete_filesystem_group(repo_group)
324 RepoGroupModel()._delete_filesystem_group(repo_group)
325 raise
325 raise
326
326
327 def update_permissions(
327 def update_permissions(
328 self, repo_group, perm_additions=None, perm_updates=None,
328 self, repo_group, perm_additions=None, perm_updates=None,
329 perm_deletions=None, recursive=None, check_perms=True,
329 perm_deletions=None, recursive=None, check_perms=True,
330 cur_user=None):
330 cur_user=None):
331 from rhodecode.model.repo import RepoModel
331 from rhodecode.model.repo import RepoModel
332 from rhodecode.lib.auth import HasUserGroupPermissionAny
332 from rhodecode.lib.auth import HasUserGroupPermissionAny
333
333
334 if not perm_additions:
334 if not perm_additions:
335 perm_additions = []
335 perm_additions = []
336 if not perm_updates:
336 if not perm_updates:
337 perm_updates = []
337 perm_updates = []
338 if not perm_deletions:
338 if not perm_deletions:
339 perm_deletions = []
339 perm_deletions = []
340
340
341 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
341 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
342
342
343 changes = {
343 changes = {
344 'added': [],
344 'added': [],
345 'updated': [],
345 'updated': [],
346 'deleted': []
346 'deleted': []
347 }
347 }
348
348
349 def _set_perm_user(obj, user, perm):
349 def _set_perm_user(obj, user, perm):
350 if isinstance(obj, RepoGroup):
350 if isinstance(obj, RepoGroup):
351 self.grant_user_permission(
351 self.grant_user_permission(
352 repo_group=obj, user=user, perm=perm)
352 repo_group=obj, user=user, perm=perm)
353 elif isinstance(obj, Repository):
353 elif isinstance(obj, Repository):
354 # private repos will not allow to change the default
354 # private repos will not allow to change the default
355 # permissions using recursive mode
355 # permissions using recursive mode
356 if obj.private and user == User.DEFAULT_USER:
356 if obj.private and user == User.DEFAULT_USER:
357 return
357 return
358
358
359 # we set group permission but we have to switch to repo
359 # we set group permission but we have to switch to repo
360 # permission
360 # permission
361 perm = perm.replace('group.', 'repository.')
361 perm = perm.replace('group.', 'repository.')
362 RepoModel().grant_user_permission(
362 RepoModel().grant_user_permission(
363 repo=obj, user=user, perm=perm)
363 repo=obj, user=user, perm=perm)
364
364
365 def _set_perm_group(obj, users_group, perm):
365 def _set_perm_group(obj, users_group, perm):
366 if isinstance(obj, RepoGroup):
366 if isinstance(obj, RepoGroup):
367 self.grant_user_group_permission(
367 self.grant_user_group_permission(
368 repo_group=obj, group_name=users_group, perm=perm)
368 repo_group=obj, group_name=users_group, perm=perm)
369 elif isinstance(obj, Repository):
369 elif isinstance(obj, Repository):
370 # we set group permission but we have to switch to repo
370 # we set group permission but we have to switch to repo
371 # permission
371 # permission
372 perm = perm.replace('group.', 'repository.')
372 perm = perm.replace('group.', 'repository.')
373 RepoModel().grant_user_group_permission(
373 RepoModel().grant_user_group_permission(
374 repo=obj, group_name=users_group, perm=perm)
374 repo=obj, group_name=users_group, perm=perm)
375
375
376 def _revoke_perm_user(obj, user):
376 def _revoke_perm_user(obj, user):
377 if isinstance(obj, RepoGroup):
377 if isinstance(obj, RepoGroup):
378 self.revoke_user_permission(repo_group=obj, user=user)
378 self.revoke_user_permission(repo_group=obj, user=user)
379 elif isinstance(obj, Repository):
379 elif isinstance(obj, Repository):
380 RepoModel().revoke_user_permission(repo=obj, user=user)
380 RepoModel().revoke_user_permission(repo=obj, user=user)
381
381
382 def _revoke_perm_group(obj, user_group):
382 def _revoke_perm_group(obj, user_group):
383 if isinstance(obj, RepoGroup):
383 if isinstance(obj, RepoGroup):
384 self.revoke_user_group_permission(
384 self.revoke_user_group_permission(
385 repo_group=obj, group_name=user_group)
385 repo_group=obj, group_name=user_group)
386 elif isinstance(obj, Repository):
386 elif isinstance(obj, Repository):
387 RepoModel().revoke_user_group_permission(
387 RepoModel().revoke_user_group_permission(
388 repo=obj, group_name=user_group)
388 repo=obj, group_name=user_group)
389
389
390 # start updates
390 # start updates
391 log.debug('Now updating permissions for %s in recursive mode:%s',
391 log.debug('Now updating permissions for %s in recursive mode:%s',
392 repo_group, recursive)
392 repo_group, recursive)
393
393
394 # initialize check function, we'll call that multiple times
394 # initialize check function, we'll call that multiple times
395 has_group_perm = HasUserGroupPermissionAny(*req_perms)
395 has_group_perm = HasUserGroupPermissionAny(*req_perms)
396
396
397 for obj in repo_group.recursive_groups_and_repos():
397 for obj in repo_group.recursive_groups_and_repos():
398 # iterated obj is an instance of a repos group or repository in
398 # iterated obj is an instance of a repos group or repository in
399 # that group, recursive option can be: none, repos, groups, all
399 # that group, recursive option can be: none, repos, groups, all
400 if recursive == 'all':
400 if recursive == 'all':
401 obj = obj
401 obj = obj
402 elif recursive == 'repos':
402 elif recursive == 'repos':
403 # skip groups, other than this one
403 # skip groups, other than this one
404 if isinstance(obj, RepoGroup) and not obj == repo_group:
404 if isinstance(obj, RepoGroup) and not obj == repo_group:
405 continue
405 continue
406 elif recursive == 'groups':
406 elif recursive == 'groups':
407 # skip repos
407 # skip repos
408 if isinstance(obj, Repository):
408 if isinstance(obj, Repository):
409 continue
409 continue
410 else: # recursive == 'none':
410 else: # recursive == 'none':
411 # DEFAULT option - don't apply to iterated objects
411 # DEFAULT option - don't apply to iterated objects
412 # also we do a break at the end of this loop. if we are not
412 # also we do a break at the end of this loop. if we are not
413 # in recursive mode
413 # in recursive mode
414 obj = repo_group
414 obj = repo_group
415
415
416 change_obj = obj.get_api_data()
416 change_obj = obj.get_api_data()
417
417
418 # update permissions
418 # update permissions
419 for member_id, perm, member_type in perm_updates:
419 for member_id, perm, member_type in perm_updates:
420 member_id = int(member_id)
420 member_id = int(member_id)
421 if member_type == 'user':
421 if member_type == 'user':
422 member_name = User.get(member_id).username
422 member_name = User.get(member_id).username
423 # this updates also current one if found
423 # this updates also current one if found
424 _set_perm_user(obj, user=member_id, perm=perm)
424 _set_perm_user(obj, user=member_id, perm=perm)
425 else: # set for user group
425 else: # set for user group
426 member_name = UserGroup.get(member_id).users_group_name
426 member_name = UserGroup.get(member_id).users_group_name
427 if not check_perms or has_group_perm(member_name,
427 if not check_perms or has_group_perm(member_name,
428 user=cur_user):
428 user=cur_user):
429 _set_perm_group(obj, users_group=member_id, perm=perm)
429 _set_perm_group(obj, users_group=member_id, perm=perm)
430
430
431 changes['updated'].append(
431 changes['updated'].append(
432 {'change_obj': change_obj, 'type': member_type,
432 {'change_obj': change_obj, 'type': member_type,
433 'id': member_id, 'name': member_name, 'new_perm': perm})
433 'id': member_id, 'name': member_name, 'new_perm': perm})
434
434
435 # set new permissions
435 # set new permissions
436 for member_id, perm, member_type in perm_additions:
436 for member_id, perm, member_type in perm_additions:
437 member_id = int(member_id)
437 member_id = int(member_id)
438 if member_type == 'user':
438 if member_type == 'user':
439 member_name = User.get(member_id).username
439 member_name = User.get(member_id).username
440 _set_perm_user(obj, user=member_id, perm=perm)
440 _set_perm_user(obj, user=member_id, perm=perm)
441 else: # set for user group
441 else: # set for user group
442 # check if we have permissions to alter this usergroup
442 # check if we have permissions to alter this usergroup
443 member_name = UserGroup.get(member_id).users_group_name
443 member_name = UserGroup.get(member_id).users_group_name
444 if not check_perms or has_group_perm(member_name,
444 if not check_perms or has_group_perm(member_name,
445 user=cur_user):
445 user=cur_user):
446 _set_perm_group(obj, users_group=member_id, perm=perm)
446 _set_perm_group(obj, users_group=member_id, perm=perm)
447
447
448 changes['added'].append(
448 changes['added'].append(
449 {'change_obj': change_obj, 'type': member_type,
449 {'change_obj': change_obj, 'type': member_type,
450 'id': member_id, 'name': member_name, 'new_perm': perm})
450 'id': member_id, 'name': member_name, 'new_perm': perm})
451
451
452 # delete permissions
452 # delete permissions
453 for member_id, perm, member_type in perm_deletions:
453 for member_id, perm, member_type in perm_deletions:
454 member_id = int(member_id)
454 member_id = int(member_id)
455 if member_type == 'user':
455 if member_type == 'user':
456 member_name = User.get(member_id).username
456 member_name = User.get(member_id).username
457 _revoke_perm_user(obj, user=member_id)
457 _revoke_perm_user(obj, user=member_id)
458 else: # set for user group
458 else: # set for user group
459 # check if we have permissions to alter this usergroup
459 # check if we have permissions to alter this usergroup
460 member_name = UserGroup.get(member_id).users_group_name
460 member_name = UserGroup.get(member_id).users_group_name
461 if not check_perms or has_group_perm(member_name,
461 if not check_perms or has_group_perm(member_name,
462 user=cur_user):
462 user=cur_user):
463 _revoke_perm_group(obj, user_group=member_id)
463 _revoke_perm_group(obj, user_group=member_id)
464
464
465 changes['deleted'].append(
465 changes['deleted'].append(
466 {'change_obj': change_obj, 'type': member_type,
466 {'change_obj': change_obj, 'type': member_type,
467 'id': member_id, 'name': member_name, 'new_perm': perm})
467 'id': member_id, 'name': member_name, 'new_perm': perm})
468
468
469 # if it's not recursive call for all,repos,groups
469 # if it's not recursive call for all,repos,groups
470 # break the loop and don't proceed with other changes
470 # break the loop and don't proceed with other changes
471 if recursive not in ['all', 'repos', 'groups']:
471 if recursive not in ['all', 'repos', 'groups']:
472 break
472 break
473
473
474 return changes
474 return changes
475
475
476 def update(self, repo_group, form_data):
476 def update(self, repo_group, form_data):
477 try:
477 try:
478 repo_group = self._get_repo_group(repo_group)
478 repo_group = self._get_repo_group(repo_group)
479 old_path = repo_group.full_path
479 old_path = repo_group.full_path
480
480
481 # change properties
481 # change properties
482 if 'group_description' in form_data:
482 if 'group_description' in form_data:
483 repo_group.group_description = form_data['group_description']
483 repo_group.group_description = form_data['group_description']
484
484
485 if 'enable_locking' in form_data:
485 if 'enable_locking' in form_data:
486 repo_group.enable_locking = form_data['enable_locking']
486 repo_group.enable_locking = form_data['enable_locking']
487
487
488 if 'group_parent_id' in form_data:
488 if 'group_parent_id' in form_data:
489 parent_group = (
489 parent_group = (
490 self._get_repo_group(form_data['group_parent_id']))
490 self._get_repo_group(form_data['group_parent_id']))
491 repo_group.group_parent_id = (
491 repo_group.group_parent_id = (
492 parent_group.group_id if parent_group else None)
492 parent_group.group_id if parent_group else None)
493 repo_group.parent_group = parent_group
493 repo_group.parent_group = parent_group
494
494
495 # mikhail: to update the full_path, we have to explicitly
495 # mikhail: to update the full_path, we have to explicitly
496 # update group_name
496 # update group_name
497 group_name = form_data.get('group_name', repo_group.name)
497 group_name = form_data.get('group_name', repo_group.name)
498 repo_group.group_name = repo_group.get_new_name(group_name)
498 repo_group.group_name = repo_group.get_new_name(group_name)
499
499
500 new_path = repo_group.full_path
500 new_path = repo_group.full_path
501
501
502 if 'user' in form_data:
502 if 'user' in form_data:
503 repo_group.user = User.get_by_username(form_data['user'])
503 repo_group.user = User.get_by_username(form_data['user'])
504 repo_group.updated_on = datetime.datetime.now()
504 repo_group.updated_on = datetime.datetime.now()
505 self.sa.add(repo_group)
505 self.sa.add(repo_group)
506
506
507 # iterate over all members of this groups and do fixes
507 # iterate over all members of this groups and do fixes
508 # set locking if given
508 # set locking if given
509 # if obj is a repoGroup also fix the name of the group according
509 # if obj is a repoGroup also fix the name of the group according
510 # to the parent
510 # to the parent
511 # if obj is a Repo fix it's name
511 # if obj is a Repo fix it's name
512 # this can be potentially heavy operation
512 # this can be potentially heavy operation
513 for obj in repo_group.recursive_groups_and_repos():
513 for obj in repo_group.recursive_groups_and_repos():
514 # set the value from it's parent
514 # set the value from it's parent
515 obj.enable_locking = repo_group.enable_locking
515 obj.enable_locking = repo_group.enable_locking
516 if isinstance(obj, RepoGroup):
516 if isinstance(obj, RepoGroup):
517 new_name = obj.get_new_name(obj.name)
517 new_name = obj.get_new_name(obj.name)
518 log.debug('Fixing group %s to new name %s',
518 log.debug('Fixing group %s to new name %s',
519 obj.group_name, new_name)
519 obj.group_name, new_name)
520 obj.group_name = new_name
520 obj.group_name = new_name
521 obj.updated_on = datetime.datetime.now()
521 obj.updated_on = datetime.datetime.now()
522 elif isinstance(obj, Repository):
522 elif isinstance(obj, Repository):
523 # we need to get all repositories from this new group and
523 # we need to get all repositories from this new group and
524 # rename them accordingly to new group path
524 # rename them accordingly to new group path
525 new_name = obj.get_new_name(obj.just_name)
525 new_name = obj.get_new_name(obj.just_name)
526 log.debug('Fixing repo %s to new name %s',
526 log.debug('Fixing repo %s to new name %s',
527 obj.repo_name, new_name)
527 obj.repo_name, new_name)
528 obj.repo_name = new_name
528 obj.repo_name = new_name
529 obj.updated_on = datetime.datetime.now()
529 obj.updated_on = datetime.datetime.now()
530 self.sa.add(obj)
530 self.sa.add(obj)
531
531
532 self._rename_group(old_path, new_path)
532 self._rename_group(old_path, new_path)
533
533
534 # Trigger update event.
534 # Trigger update event.
535 events.trigger(events.RepoGroupUpdateEvent(repo_group))
535 events.trigger(events.RepoGroupUpdateEvent(repo_group))
536
536
537 return repo_group
537 return repo_group
538 except Exception:
538 except Exception:
539 log.error(traceback.format_exc())
539 log.error(traceback.format_exc())
540 raise
540 raise
541
541
542 def delete(self, repo_group, force_delete=False, fs_remove=True):
542 def delete(self, repo_group, force_delete=False, fs_remove=True):
543 repo_group = self._get_repo_group(repo_group)
543 repo_group = self._get_repo_group(repo_group)
544 if not repo_group:
544 if not repo_group:
545 return False
545 return False
546 try:
546 try:
547 self.sa.delete(repo_group)
547 self.sa.delete(repo_group)
548 if fs_remove:
548 if fs_remove:
549 self._delete_filesystem_group(repo_group, force_delete)
549 self._delete_filesystem_group(repo_group, force_delete)
550 else:
550 else:
551 log.debug('skipping removal from filesystem')
551 log.debug('skipping removal from filesystem')
552
552
553 # Trigger delete event.
553 # Trigger delete event.
554 events.trigger(events.RepoGroupDeleteEvent(repo_group))
554 events.trigger(events.RepoGroupDeleteEvent(repo_group))
555 return True
555 return True
556
556
557 except Exception:
557 except Exception:
558 log.error('Error removing repo_group %s', repo_group)
558 log.error('Error removing repo_group %s', repo_group)
559 raise
559 raise
560
560
561 def grant_user_permission(self, repo_group, user, perm):
561 def grant_user_permission(self, repo_group, user, perm):
562 """
562 """
563 Grant permission for user on given repository group, or update
563 Grant permission for user on given repository group, or update
564 existing one if found
564 existing one if found
565
565
566 :param repo_group: Instance of RepoGroup, repositories_group_id,
566 :param repo_group: Instance of RepoGroup, repositories_group_id,
567 or repositories_group name
567 or repositories_group name
568 :param user: Instance of User, user_id or username
568 :param user: Instance of User, user_id or username
569 :param perm: Instance of Permission, or permission_name
569 :param perm: Instance of Permission, or permission_name
570 """
570 """
571
571
572 repo_group = self._get_repo_group(repo_group)
572 repo_group = self._get_repo_group(repo_group)
573 user = self._get_user(user)
573 user = self._get_user(user)
574 permission = self._get_perm(perm)
574 permission = self._get_perm(perm)
575
575
576 # check if we have that permission already
576 # check if we have that permission already
577 obj = self.sa.query(UserRepoGroupToPerm)\
577 obj = self.sa.query(UserRepoGroupToPerm)\
578 .filter(UserRepoGroupToPerm.user == user)\
578 .filter(UserRepoGroupToPerm.user == user)\
579 .filter(UserRepoGroupToPerm.group == repo_group)\
579 .filter(UserRepoGroupToPerm.group == repo_group)\
580 .scalar()
580 .scalar()
581 if obj is None:
581 if obj is None:
582 # create new !
582 # create new !
583 obj = UserRepoGroupToPerm()
583 obj = UserRepoGroupToPerm()
584 obj.group = repo_group
584 obj.group = repo_group
585 obj.user = user
585 obj.user = user
586 obj.permission = permission
586 obj.permission = permission
587 self.sa.add(obj)
587 self.sa.add(obj)
588 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
588 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
589 action_logger_generic(
589 action_logger_generic(
590 'granted permission: {} to user: {} on repogroup: {}'.format(
590 'granted permission: {} to user: {} on repogroup: {}'.format(
591 perm, user, repo_group), namespace='security.repogroup')
591 perm, user, repo_group), namespace='security.repogroup')
592 return obj
592 return obj
593
593
594 def revoke_user_permission(self, repo_group, user):
594 def revoke_user_permission(self, repo_group, user):
595 """
595 """
596 Revoke permission for user on given repository group
596 Revoke permission for user on given repository group
597
597
598 :param repo_group: Instance of RepoGroup, repositories_group_id,
598 :param repo_group: Instance of RepoGroup, repositories_group_id,
599 or repositories_group name
599 or repositories_group name
600 :param user: Instance of User, user_id or username
600 :param user: Instance of User, user_id or username
601 """
601 """
602
602
603 repo_group = self._get_repo_group(repo_group)
603 repo_group = self._get_repo_group(repo_group)
604 user = self._get_user(user)
604 user = self._get_user(user)
605
605
606 obj = self.sa.query(UserRepoGroupToPerm)\
606 obj = self.sa.query(UserRepoGroupToPerm)\
607 .filter(UserRepoGroupToPerm.user == user)\
607 .filter(UserRepoGroupToPerm.user == user)\
608 .filter(UserRepoGroupToPerm.group == repo_group)\
608 .filter(UserRepoGroupToPerm.group == repo_group)\
609 .scalar()
609 .scalar()
610 if obj:
610 if obj:
611 self.sa.delete(obj)
611 self.sa.delete(obj)
612 log.debug('Revoked perm on %s on %s', repo_group, user)
612 log.debug('Revoked perm on %s on %s', repo_group, user)
613 action_logger_generic(
613 action_logger_generic(
614 'revoked permission from user: {} on repogroup: {}'.format(
614 'revoked permission from user: {} on repogroup: {}'.format(
615 user, repo_group), namespace='security.repogroup')
615 user, repo_group), namespace='security.repogroup')
616
616
617 def grant_user_group_permission(self, repo_group, group_name, perm):
617 def grant_user_group_permission(self, repo_group, group_name, perm):
618 """
618 """
619 Grant permission for user group on given repository group, or update
619 Grant permission for user group on given repository group, or update
620 existing one if found
620 existing one if found
621
621
622 :param repo_group: Instance of RepoGroup, repositories_group_id,
622 :param repo_group: Instance of RepoGroup, repositories_group_id,
623 or repositories_group name
623 or repositories_group name
624 :param group_name: Instance of UserGroup, users_group_id,
624 :param group_name: Instance of UserGroup, users_group_id,
625 or user group name
625 or user group name
626 :param perm: Instance of Permission, or permission_name
626 :param perm: Instance of Permission, or permission_name
627 """
627 """
628 repo_group = self._get_repo_group(repo_group)
628 repo_group = self._get_repo_group(repo_group)
629 group_name = self._get_user_group(group_name)
629 group_name = self._get_user_group(group_name)
630 permission = self._get_perm(perm)
630 permission = self._get_perm(perm)
631
631
632 # check if we have that permission already
632 # check if we have that permission already
633 obj = self.sa.query(UserGroupRepoGroupToPerm)\
633 obj = self.sa.query(UserGroupRepoGroupToPerm)\
634 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
634 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
635 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
635 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
636 .scalar()
636 .scalar()
637
637
638 if obj is None:
638 if obj is None:
639 # create new
639 # create new
640 obj = UserGroupRepoGroupToPerm()
640 obj = UserGroupRepoGroupToPerm()
641
641
642 obj.group = repo_group
642 obj.group = repo_group
643 obj.users_group = group_name
643 obj.users_group = group_name
644 obj.permission = permission
644 obj.permission = permission
645 self.sa.add(obj)
645 self.sa.add(obj)
646 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
646 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
647 action_logger_generic(
647 action_logger_generic(
648 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
648 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
649 perm, group_name, repo_group), namespace='security.repogroup')
649 perm, group_name, repo_group), namespace='security.repogroup')
650 return obj
650 return obj
651
651
652 def revoke_user_group_permission(self, repo_group, group_name):
652 def revoke_user_group_permission(self, repo_group, group_name):
653 """
653 """
654 Revoke permission for user group on given repository group
654 Revoke permission for user group on given repository group
655
655
656 :param repo_group: Instance of RepoGroup, repositories_group_id,
656 :param repo_group: Instance of RepoGroup, repositories_group_id,
657 or repositories_group name
657 or repositories_group name
658 :param group_name: Instance of UserGroup, users_group_id,
658 :param group_name: Instance of UserGroup, users_group_id,
659 or user group name
659 or user group name
660 """
660 """
661 repo_group = self._get_repo_group(repo_group)
661 repo_group = self._get_repo_group(repo_group)
662 group_name = self._get_user_group(group_name)
662 group_name = self._get_user_group(group_name)
663
663
664 obj = self.sa.query(UserGroupRepoGroupToPerm)\
664 obj = self.sa.query(UserGroupRepoGroupToPerm)\
665 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
665 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
666 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
666 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
667 .scalar()
667 .scalar()
668 if obj:
668 if obj:
669 self.sa.delete(obj)
669 self.sa.delete(obj)
670 log.debug('Revoked perm to %s on %s', repo_group, group_name)
670 log.debug('Revoked perm to %s on %s', repo_group, group_name)
671 action_logger_generic(
671 action_logger_generic(
672 'revoked permission from usergroup: {} on repogroup: {}'.format(
672 'revoked permission from usergroup: {} on repogroup: {}'.format(
673 group_name, repo_group), namespace='security.repogroup')
673 group_name, repo_group), namespace='security.repogroup')
674
674
675 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
675 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
676 super_user_actions=False):
676 super_user_actions=False):
677
677
678 from pyramid.threadlocal import get_current_request
678 from pyramid.threadlocal import get_current_request
679 _render = get_current_request().get_partial_renderer(
679 _render = get_current_request().get_partial_renderer(
680 'data_table/_dt_elements.mako')
680 'data_table/_dt_elements.mako')
681 c = _render.get_call_context()
681 c = _render.get_call_context()
682 h = _render.get_helpers()
682 h = _render.get_helpers()
683
683
684 def quick_menu(repo_group_name):
684 def quick_menu(repo_group_name):
685 return _render('quick_repo_group_menu', repo_group_name)
685 return _render('quick_repo_group_menu', repo_group_name)
686
686
687 def repo_group_lnk(repo_group_name):
687 def repo_group_lnk(repo_group_name):
688 return _render('repo_group_name', repo_group_name)
688 return _render('repo_group_name', repo_group_name)
689
689
690 def last_change(last_change):
690 def last_change(last_change):
691 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
691 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
692 last_change = last_change + datetime.timedelta(seconds=
692 last_change = last_change + datetime.timedelta(seconds=
693 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
693 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
694 return _render("last_change", last_change)
694 return _render("last_change", last_change)
695
695
696 def desc(desc, personal):
696 def desc(desc, personal):
697 prefix = h.escaped_stylize(u'[personal] ') if personal else ''
697 return _render(
698
698 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
699 if c.visual.stylify_metatags:
700 desc = h.urlify_text(prefix + h.escaped_stylize(desc))
701 else:
702 desc = h.urlify_text(prefix + h.html_escape(desc))
703
704 return _render('repo_group_desc', desc)
705
699
706 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
700 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
707 return _render(
701 return _render(
708 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
702 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
709
703
710 def repo_group_name(repo_group_name, children_groups):
704 def repo_group_name(repo_group_name, children_groups):
711 return _render("repo_group_name", repo_group_name, children_groups)
705 return _render("repo_group_name", repo_group_name, children_groups)
712
706
713 def user_profile(username):
707 def user_profile(username):
714 return _render('user_profile', username)
708 return _render('user_profile', username)
715
709
716 repo_group_data = []
710 repo_group_data = []
717 for group in repo_group_list:
711 for group in repo_group_list:
718
712
719 row = {
713 row = {
720 "menu": quick_menu(group.group_name),
714 "menu": quick_menu(group.group_name),
721 "name": repo_group_lnk(group.group_name),
715 "name": repo_group_lnk(group.group_name),
722 "name_raw": group.group_name,
716 "name_raw": group.group_name,
723 "last_change": last_change(group.last_db_change),
717 "last_change": last_change(group.last_db_change),
724 "last_change_raw": datetime_to_time(group.last_db_change),
718 "last_change_raw": datetime_to_time(group.last_db_change),
725 "desc": desc(group.description_safe, group.personal),
719 "desc": desc(group.description_safe, group.personal),
726 "top_level_repos": 0,
720 "top_level_repos": 0,
727 "owner": user_profile(group.user.username)
721 "owner": user_profile(group.user.username)
728 }
722 }
729 if admin:
723 if admin:
730 repo_count = group.repositories.count()
724 repo_count = group.repositories.count()
731 children_groups = map(
725 children_groups = map(
732 h.safe_unicode,
726 h.safe_unicode,
733 itertools.chain((g.name for g in group.parents),
727 itertools.chain((g.name for g in group.parents),
734 (x.name for x in [group])))
728 (x.name for x in [group])))
735 row.update({
729 row.update({
736 "action": repo_group_actions(
730 "action": repo_group_actions(
737 group.group_id, group.group_name, repo_count),
731 group.group_id, group.group_name, repo_count),
738 "top_level_repos": repo_count,
732 "top_level_repos": repo_count,
739 "name": repo_group_name(group.group_name, children_groups),
733 "name": repo_group_name(group.group_name, children_groups),
740
734
741 })
735 })
742 repo_group_data.append(row)
736 repo_group_data.append(row)
743
737
744 return repo_group_data
738 return repo_group_data
@@ -1,109 +1,126 b''
1 // tags.less
1 // tags.less
2 // For use in RhodeCode applications;
2 // For use in RhodeCode applications;
3 // see style guide documentation for guidelines.
3 // see style guide documentation for guidelines.
4
4
5 // TAGS
5 // TAGS
6 .tag,
6 .tag,
7 .tagtag {
7 .tagtag {
8 display: inline-block;
8 display: inline-block;
9 min-height: 0;
9 min-height: 0;
10 margin: 0 auto;
10 margin: 0 auto;
11 padding: .25em;
11 padding: .25em;
12 text-align: center;
12 text-align: center;
13 font-size: (-1 + @basefontsize); //fit in tables
13 font-size: (-1 + @basefontsize); //fit in tables
14 line-height: .9em;
14 line-height: .9em;
15 border: none;
15 border: none;
16 .border-radius(@border-radius);
16 .border-radius(@border-radius);
17 font-family: @text-regular;
17 font-family: @text-regular;
18 background-image: none;
18 background-image: none;
19 color: @grey4;
19 color: @grey4;
20 .border ( @border-thickness-tags, @grey4 );
20 .border ( @border-thickness-tags, @grey4 );
21 white-space: nowrap;
21 white-space: nowrap;
22 a {
22 a {
23 color: inherit;
23 color: inherit;
24 text-decoration: underline;
24 text-decoration: underline;
25
25
26 i,
26 i,
27 [class^="icon-"]:before,
27 [class^="icon-"]:before,
28 [class*=" icon-"]:before {
28 [class*=" icon-"]:before {
29 text-decoration: none;
29 text-decoration: none;
30 }
30 }
31 }
31 }
32 }
32 }
33
33
34 .tag0 { .border ( @border-thickness-tags, @grey4 ); color:@grey4; }
34 .tag0 { .border ( @border-thickness-tags, @grey4 ); color:@grey4; }
35 .tag1 { .border ( @border-thickness-tags, @color1 ); color:@color1; }
35 .tag1 { .border ( @border-thickness-tags, @color1 ); color:@color1; }
36 .tag2 { .border ( @border-thickness-tags, @color2 ); color:@color2; }
36 .tag2 { .border ( @border-thickness-tags, @color2 ); color:@color2; }
37 .tag3 { .border ( @border-thickness-tags, @color3 ); color:@color3; }
37 .tag3 { .border ( @border-thickness-tags, @color3 ); color:@color3; }
38 .tag4 { .border ( @border-thickness-tags, @color4 ); color:@color4; }
38 .tag4 { .border ( @border-thickness-tags, @color4 ); color:@color4; }
39 .tag5 { .border ( @border-thickness-tags, @color5 ); color:@color5; }
39 .tag5 { .border ( @border-thickness-tags, @color5 ); color:@color5; }
40 .tag6 { .border ( @border-thickness-tags, @color6 ); color:@color6; }
40 .tag6 { .border ( @border-thickness-tags, @color6 ); color:@color6; }
41 .tag7 { .border ( @border-thickness-tags, @color7 ); color:@color7; }
41 .tag7 { .border ( @border-thickness-tags, @color7 ); color:@color7; }
42 .tag8 { .border ( @border-thickness-tags, @color8 ); color:@color8; }
42 .tag8 { .border ( @border-thickness-tags, @color8 ); color:@color8; }
43
43
44 .metatag-list {
44 .metatag-list {
45 margin: 0;
45 margin: 0;
46 padding: 0;
46 padding: 0;
47
47
48 li {
48 li {
49 margin: 0 0 @padding;
49 margin: 0 0 @padding;
50 line-height: 1em;
50 line-height: 1em;
51 list-style-type: none;
51 list-style-type: none;
52
52
53 &:before { content: none; }
53 &:before { content: none; }
54 }
54 }
55 }
55 }
56
56
57 .branchtag, .booktag {
57 .branchtag, .booktag {
58 &:extend(.tag);
58 &:extend(.tag);
59
59
60
60
61 a {
61 a {
62 color:inherit;
62 color:inherit;
63 }
63 }
64 }
64 }
65
65
66 .metatag {
66 .metatag {
67 &:extend(.tag);
67 &:extend(.tag);
68 a {
68 a {
69 color:inherit;
69 color:inherit;
70 text-decoration: underline;
70 text-decoration: underline;
71 }
71 }
72 }
72 }
73
73
74 [tag="featured"] { &:extend(.tag1); }
74 [tag="generic"] { &:extend(.tag0); }
75 [tag="stale"] { &:extend(.tag2); }
75 [tag="label"] { &:extend(.tag0); }
76 [tag="dead"] { &:extend(.tag3); }
76
77 [tag="lang"] { &:extend(.tag4); }
77 [tag="state featured"] { &:extend(.tag1); }
78 [tag="license"] { &:extend(.tag5); }
78 [tag="state dev"] { &:extend(.tag1); }
79 [tag="requires"] { &:extend(.tag6); }
79 [tag="ref base"] { &:extend(.tag1); }
80 [tag="recommends"] { &:extend(.tag7); }
80
81 [tag="state stable"] { &:extend(.tag2); }
82 [tag="state stale"] { &:extend(.tag2); }
83
84 [tag="ref requires"] { &:extend(.tag3); }
85
86 [tag="state dead"] { &:extend(.tag4); }
87
88 [tag="ref conflicts"] { &:extend(.tag4); }
89
90 [tag="license"] { &:extend(.tag6); }
91
92 [tag="lang"] { &:extend(.tag7); }
93 [tag="language"] { &:extend(.tag7); }
94 [tag="ref recommends"] { &:extend(.tag7); }
95
81 [tag="see"] { &:extend(.tag8); }
96 [tag="see"] { &:extend(.tag8); }
97 [tag="url"] { &:extend(.tag8); }
98
82
99
83 .perm_overriden {
100 .perm_overriden {
84 text-decoration: line-through;
101 text-decoration: line-through;
85 opacity: 0.6;
102 opacity: 0.6;
86 }
103 }
87
104
88 .perm_tag {
105 .perm_tag {
89 &:extend(.tag);
106 &:extend(.tag);
90
107
91 &.read {
108 &.read {
92 &:extend(.tag1);
109 &:extend(.tag1);
93 }
110 }
94
111
95 &.write {
112 &.write {
96 &:extend(.tag4);
113 &:extend(.tag4);
97 }
114 }
98 &.admin {
115 &.admin {
99 &:extend(.tag5);
116 &:extend(.tag5);
100 }
117 }
101 }
118 }
102
119
103 .phase-draft {
120 .phase-draft {
104 color: @color3
121 color: @color3
105 }
122 }
106
123
107 .phase-secret {
124 .phase-secret {
108 color:@grey3
125 color:@grey3
109 }
126 }
@@ -1,100 +1,106 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Add repository group')}
5 ${_('Add repository group')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 &raquo;
13 &raquo;
14 ${h.link_to(_('Repository groups'),h.url('repo_groups'))}
14 ${h.link_to(_('Repository groups'),h.url('repo_groups'))}
15 &raquo;
15 &raquo;
16 ${_('Add Repository Group')}
16 ${_('Add Repository Group')}
17 </%def>
17 </%def>
18
18
19 <%def name="menu_bar_nav()">
19 <%def name="menu_bar_nav()">
20 ${self.menu_items(active='admin')}
20 ${self.menu_items(active='admin')}
21 </%def>
21 </%def>
22
22
23 <%def name="main()">
23 <%def name="main()">
24 <div class="box">
24 <div class="box">
25 <!-- box / title -->
25 <!-- box / title -->
26 <div class="title">
26 <div class="title">
27 ${self.breadcrumbs()}
27 ${self.breadcrumbs()}
28 </div>
28 </div>
29 <!-- end box / title -->
29 <!-- end box / title -->
30 ${h.secure_form(h.url('repo_groups'), method='post')}
30 ${h.secure_form(h.url('repo_groups'), method='post')}
31 <div class="form">
31 <div class="form">
32 <!-- fields -->
32 <!-- fields -->
33 <div class="fields">
33 <div class="fields">
34 <div class="field">
34 <div class="field">
35 <div class="label">
35 <div class="label">
36 <label for="group_name">${_('Group Name')}:</label>
36 <label for="group_name">${_('Group Name')}:</label>
37 </div>
37 </div>
38 <div class="input">
38 <div class="input">
39 ${h.text('group_name', class_="medium")}
39 ${h.text('group_name', class_="medium")}
40 </div>
40 </div>
41 </div>
41 </div>
42
42
43 <div class="field">
43 <div class="field">
44 <div class="label">
44 <div class="label">
45 <label for="group_description">${_('Description')}:</label>
45 <label for="group_description">${_('Description')}:</label>
46 </div>
46 </div>
47 <div class="textarea editor">
47 <div class="textarea editor">
48 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
48 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
49 <% metatags_url = h.literal('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
50 <span class="help-block">${_('Plain text format with support of {metatags}').format(metatags=metatags_url)|n}</span>
51 <span id="meta-tags-desc" style="display: none">
52 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
53 ${dt.metatags_help()}
54 </span>
49 </div>
55 </div>
50 </div>
56 </div>
51
57
52 <div class="field">
58 <div class="field">
53 <div class="label">
59 <div class="label">
54 <label for="group_parent_id">${_('Group Parent')}:</label>
60 <label for="group_parent_id">${_('Group Parent')}:</label>
55 </div>
61 </div>
56 <div class="select">
62 <div class="select">
57 ${h.select('group_parent_id',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
63 ${h.select('group_parent_id',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
58 </div>
64 </div>
59 </div>
65 </div>
60
66
61 <div id="copy_perms" class="field">
67 <div id="copy_perms" class="field">
62 <div class="label label-checkbox">
68 <div class="label label-checkbox">
63 <label for="group_copy_permissions">${_('Copy Parent Group Permissions')}:</label>
69 <label for="group_copy_permissions">${_('Copy Parent Group Permissions')}:</label>
64 </div>
70 </div>
65 <div class="checkboxes">
71 <div class="checkboxes">
66 ${h.checkbox('group_copy_permissions', value="True", checked="checked")}
72 ${h.checkbox('group_copy_permissions', value="True", checked="checked")}
67 <span class="help-block">${_('Copy permission settings from parent repository group.')}</span>
73 <span class="help-block">${_('Copy permission settings from parent repository group.')}</span>
68 </div>
74 </div>
69 </div>
75 </div>
70
76
71 <div class="buttons">
77 <div class="buttons">
72 ${h.submit('save',_('Save'),class_="btn")}
78 ${h.submit('save',_('Save'),class_="btn")}
73 </div>
79 </div>
74 </div>
80 </div>
75 </div>
81 </div>
76 ${h.end_form()}
82 ${h.end_form()}
77 </div>
83 </div>
78 <script>
84 <script>
79 $(document).ready(function(){
85 $(document).ready(function(){
80 var setCopyPermsOption = function(group_val){
86 var setCopyPermsOption = function(group_val){
81 if(group_val != "-1"){
87 if(group_val != "-1"){
82 $('#copy_perms').show()
88 $('#copy_perms').show()
83 }
89 }
84 else{
90 else{
85 $('#copy_perms').hide();
91 $('#copy_perms').hide();
86 }
92 }
87 }
93 }
88 $("#group_parent_id").select2({
94 $("#group_parent_id").select2({
89 'containerCssClass': "drop-menu",
95 'containerCssClass': "drop-menu",
90 'dropdownCssClass': "drop-menu-dropdown",
96 'dropdownCssClass': "drop-menu-dropdown",
91 'dropdownAutoWidth': true
97 'dropdownAutoWidth': true
92 });
98 });
93 setCopyPermsOption($('#group_parent_id').val())
99 setCopyPermsOption($('#group_parent_id').val())
94 $("#group_parent_id").on("change", function(e) {
100 $("#group_parent_id").on("change", function(e) {
95 setCopyPermsOption(e.val)
101 setCopyPermsOption(e.val)
96 })
102 })
97 $('#group_name').focus();
103 $('#group_name').focus();
98 })
104 })
99 </script>
105 </script>
100 </%def>
106 </%def>
@@ -1,84 +1,90 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3
3
4 <div class="panel panel-default">
4 <div class="panel panel-default">
5 <div class="panel-heading">
5 <div class="panel-heading">
6 <h3 class="panel-title">${_('Settings for Repository Group: %s') % c.repo_group.name}</h3>
6 <h3 class="panel-title">${_('Settings for Repository Group: %s') % c.repo_group.name}</h3>
7 </div>
7 </div>
8 <div class="panel-body">
8 <div class="panel-body">
9 ${h.secure_form(h.url('update_repo_group',group_name=c.repo_group.group_name),method='put')}
9 ${h.secure_form(h.url('update_repo_group',group_name=c.repo_group.group_name),method='put')}
10 <div class="form">
10 <div class="form">
11 <!-- fields -->
11 <!-- fields -->
12 <div class="fields">
12 <div class="fields">
13 <div class="field">
13 <div class="field">
14 <div class="label">
14 <div class="label">
15 <label for="group_name">${_('Group Name')}:</label>
15 <label for="group_name">${_('Group Name')}:</label>
16 </div>
16 </div>
17 <div class="input">
17 <div class="input">
18 ${h.text('group_name',class_='medium')}
18 ${h.text('group_name',class_='medium')}
19 </div>
19 </div>
20 </div>
20 </div>
21
21
22 <div class="field badged-field">
22 <div class="field badged-field">
23 <div class="label">
23 <div class="label">
24 <label for="user">${_('Owner')}:</label>
24 <label for="user">${_('Owner')}:</label>
25 </div>
25 </div>
26 <div class="input">
26 <div class="input">
27 <div class="badge-input-container">
27 <div class="badge-input-container">
28 <div class="user-badge">
28 <div class="user-badge">
29 ${base.gravatar_with_user(c.repo_group.user.email, show_disabled=not c.repo_group.user.active)}
29 ${base.gravatar_with_user(c.repo_group.user.email, show_disabled=not c.repo_group.user.active)}
30 </div>
30 </div>
31 <div class="badge-input-wrap">
31 <div class="badge-input-wrap">
32 ${h.text('user', class_="medium", autocomplete="off")}
32 ${h.text('user', class_="medium", autocomplete="off")}
33 </div>
33 </div>
34 </div>
34 </div>
35 <form:error name="user"/>
35 <form:error name="user"/>
36 <p class="help-block">${_('Change owner of this repository group.')}</p>
36 <p class="help-block">${_('Change owner of this repository group.')}</p>
37 </div>
37 </div>
38 </div>
38 </div>
39
39
40 <div class="field">
40 <div class="field">
41 <div class="label label-textarea">
41 <div class="label label-textarea">
42 <label for="group_description">${_('Description')}:</label>
42 <label for="group_description">${_('Description')}:</label>
43 </div>
43 </div>
44 <div class="textarea text-area editor">
44 <div class="textarea text-area editor">
45 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
45 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
46 <% metatags_url = h.literal('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
47 <span class="help-block">${_('Plain text format with support of {metatags}').format(metatags=metatags_url)|n}</span>
48 <span id="meta-tags-desc" style="display: none">
49 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
50 ${dt.metatags_help()}
51 </span>
46 </div>
52 </div>
47 </div>
53 </div>
48
54
49 <div class="field">
55 <div class="field">
50 <div class="label">
56 <div class="label">
51 <label for="group_parent_id">${_('Group parent')}:</label>
57 <label for="group_parent_id">${_('Group parent')}:</label>
52 </div>
58 </div>
53 <div class="select">
59 <div class="select">
54 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
60 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
55 </div>
61 </div>
56 </div>
62 </div>
57 <div class="field">
63 <div class="field">
58 <div class="label label-checkbox">
64 <div class="label label-checkbox">
59 <label for="enable_locking">${_('Enable Repository Locking')}:</label>
65 <label for="enable_locking">${_('Enable Repository Locking')}:</label>
60 </div>
66 </div>
61 <div class="checkboxes">
67 <div class="checkboxes">
62 ${h.checkbox('enable_locking',value="True")}
68 ${h.checkbox('enable_locking',value="True")}
63 <span class="help-block">${_('Repository locking will be enabled on all subgroups and repositories inside this repository group. Pulling from a repository locks it, and it is unlocked by pushing back by the same user.')}</span>
69 <span class="help-block">${_('Repository locking will be enabled on all subgroups and repositories inside this repository group. Pulling from a repository locks it, and it is unlocked by pushing back by the same user.')}</span>
64 </div>
70 </div>
65 </div>
71 </div>
66 <div class="buttons">
72 <div class="buttons">
67 ${h.submit('save',_('Save'),class_="btn")}
73 ${h.submit('save',_('Save'),class_="btn")}
68 ${h.reset('reset',_('Reset'),class_="btn")}
74 ${h.reset('reset',_('Reset'),class_="btn")}
69 </div>
75 </div>
70 </div>
76 </div>
71 </div>
77 </div>
72 ${h.end_form()}
78 ${h.end_form()}
73 </div>
79 </div>
74 </div>
80 </div>
75 <script>
81 <script>
76 $(document).ready(function(){
82 $(document).ready(function(){
77 $("#group_parent_id").select2({
83 $("#group_parent_id").select2({
78 'containerCssClass': "drop-menu",
84 'containerCssClass': "drop-menu",
79 'dropdownCssClass': "drop-menu-dropdown",
85 'dropdownCssClass': "drop-menu-dropdown",
80 'dropdownAutoWidth': true
86 'dropdownAutoWidth': true
81 });
87 });
82 UsersAutoComplete('user', '${c.rhodecode_user.user_id}');
88 UsersAutoComplete('user', '${c.rhodecode_user.user_id}');
83 })
89 })
84 </script>
90 </script>
@@ -1,159 +1,164 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 ${h.secure_form(h.route_path('repo_create'), method='POST', request=request)}
3 ${h.secure_form(h.route_path('repo_create'), method='POST', request=request)}
4 <div class="form">
4 <div class="form">
5 <!-- fields -->
5 <!-- fields -->
6 <div class="fields">
6 <div class="fields">
7 <div class="field">
7 <div class="field">
8 <div class="label">
8 <div class="label">
9 <label for="repo_name">${_('Name')}:</label>
9 <label for="repo_name">${_('Name')}:</label>
10 </div>
10 </div>
11 <div class="input">
11 <div class="input">
12 ${h.text('repo_name', class_="medium")}
12 ${h.text('repo_name', class_="medium")}
13 <div class="info-block">
13 <div class="info-block">
14 <a id="remote_clone_toggle" href="#"><i class="icon-download-alt"></i> ${_('Import Existing Repository ?')}</a>
14 <a id="remote_clone_toggle" href="#"><i class="icon-download-alt"></i> ${_('Import Existing Repository ?')}</a>
15 </div>
15 </div>
16 %if not c.rhodecode_user.is_admin:
16 %if not c.rhodecode_user.is_admin:
17 ${h.hidden('user_created',True)}
17 ${h.hidden('user_created',True)}
18 %endif
18 %endif
19 </div>
19 </div>
20 </div>
20 </div>
21 <div id="remote_clone" class="field" style="display: none;">
21 <div id="remote_clone" class="field" style="display: none;">
22 <div class="label">
22 <div class="label">
23 <label for="clone_uri">${_('Clone from')}:</label>
23 <label for="clone_uri">${_('Clone from')}:</label>
24 </div>
24 </div>
25 <div class="input">
25 <div class="input">
26 ${h.text('clone_uri', class_="medium")}
26 ${h.text('clone_uri', class_="medium")}
27 <span class="help-block">
27 <span class="help-block">
28 <pre>
28 <pre>
29 - The repository must be accessible over http:// or https://
29 - The repository must be accessible over http:// or https://
30 - For Git projects it's recommended appending .git to the end of clone url.
30 - For Git projects it's recommended appending .git to the end of clone url.
31 - Make sure to select proper repository type from the below selector before importing it.
31 - Make sure to select proper repository type from the below selector before importing it.
32 - If your HTTP[S] repository is not publicly accessible,
32 - If your HTTP[S] repository is not publicly accessible,
33 add authentication information to the URL: https://username:password@server.company.com/repo-name.
33 add authentication information to the URL: https://username:password@server.company.com/repo-name.
34 - The Git LFS/Mercurial Largefiles objects will not be imported.
34 - The Git LFS/Mercurial Largefiles objects will not be imported.
35 - For very large repositories, it's recommended to manually copy them into the
35 - For very large repositories, it's recommended to manually copy them into the
36 RhodeCode <a href="${h.url('admin_settings_vcs', anchor='vcs-storage-options')}">storage location</a> and run <a href="${h.url('admin_settings_mapping')}">Remap and Rescan</a>.
36 RhodeCode <a href="${h.url('admin_settings_vcs', anchor='vcs-storage-options')}">storage location</a> and run <a href="${h.url('admin_settings_mapping')}">Remap and Rescan</a>.
37 </pre>
37 </pre>
38 </span>
38 </span>
39 </div>
39 </div>
40 </div>
40 </div>
41 <div class="field">
41 <div class="field">
42 <div class="label">
42 <div class="label">
43 <label for="repo_description">${_('Description')}:</label>
43 <label for="repo_description">${_('Description')}:</label>
44 </div>
44 </div>
45 <div class="textarea editor">
45 <div class="textarea editor">
46 ${h.textarea('repo_description')}
46 ${h.textarea('repo_description')}
47 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
47 <% metatags_url = h.literal('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
48 <span class="help-block">${_('Plain text format with support of {metatags}. Add a README file for longer descriptions').format(metatags=metatags_url)|n}</span>
49 <span id="meta-tags-desc" style="display: none">
50 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
51 ${dt.metatags_help()}
52 </span>
48 </div>
53 </div>
49 </div>
54 </div>
50 <div class="field">
55 <div class="field">
51 <div class="label">
56 <div class="label">
52 <label for="repo_group">${_('Repository Group')}:</label>
57 <label for="repo_group">${_('Repository Group')}:</label>
53 </div>
58 </div>
54 <div class="select">
59 <div class="select">
55 ${h.select('repo_group',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
60 ${h.select('repo_group',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
56 % if c.personal_repo_group:
61 % if c.personal_repo_group:
57 <a class="btn" href="#" id="select_my_group" data-personal-group-id="${c.personal_repo_group.group_id}">
62 <a class="btn" href="#" id="select_my_group" data-personal-group-id="${c.personal_repo_group.group_id}">
58 ${_('Select my personal group (%(repo_group_name)s)') % {'repo_group_name': c.personal_repo_group.group_name}}
63 ${_('Select my personal group (%(repo_group_name)s)') % {'repo_group_name': c.personal_repo_group.group_name}}
59 </a>
64 </a>
60 % endif
65 % endif
61 <span class="help-block">${_('Optionally select a group to put this repository into.')}</span>
66 <span class="help-block">${_('Optionally select a group to put this repository into.')}</span>
62 </div>
67 </div>
63 </div>
68 </div>
64 <div id="copy_perms" class="field">
69 <div id="copy_perms" class="field">
65 <div class="label label-checkbox">
70 <div class="label label-checkbox">
66 <label for="repo_copy_permissions">${_('Copy Parent Group Permissions')}:</label>
71 <label for="repo_copy_permissions">${_('Copy Parent Group Permissions')}:</label>
67 </div>
72 </div>
68 <div class="checkboxes">
73 <div class="checkboxes">
69 ${h.checkbox('repo_copy_permissions', value="True", checked="checked")}
74 ${h.checkbox('repo_copy_permissions', value="True", checked="checked")}
70 <span class="help-block">${_('Copy permission set from the parent repository group.')}</span>
75 <span class="help-block">${_('Copy permission set from the parent repository group.')}</span>
71 </div>
76 </div>
72 </div>
77 </div>
73 <div class="field">
78 <div class="field">
74 <div class="label">
79 <div class="label">
75 <label for="repo_type">${_('Type')}:</label>
80 <label for="repo_type">${_('Type')}:</label>
76 </div>
81 </div>
77 <div class="select">
82 <div class="select">
78 ${h.select('repo_type','hg',c.backends)}
83 ${h.select('repo_type','hg',c.backends)}
79 <span class="help-block">${_('Set the type of repository to create.')}</span>
84 <span class="help-block">${_('Set the type of repository to create.')}</span>
80 </div>
85 </div>
81 </div>
86 </div>
82 <div class="field">
87 <div class="field">
83 <div class="label">
88 <div class="label">
84 <label for="repo_landing_rev">${_('Landing commit')}:</label>
89 <label for="repo_landing_rev">${_('Landing commit')}:</label>
85 </div>
90 </div>
86 <div class="select">
91 <div class="select">
87 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
92 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
88 <span class="help-block">${_('The default commit for file pages, downloads, full text search index, and README generation.')}</span>
93 <span class="help-block">${_('The default commit for file pages, downloads, full text search index, and README generation.')}</span>
89 </div>
94 </div>
90 </div>
95 </div>
91 <div class="field">
96 <div class="field">
92 <div class="label label-checkbox">
97 <div class="label label-checkbox">
93 <label for="repo_private">${_('Private Repository')}:</label>
98 <label for="repo_private">${_('Private Repository')}:</label>
94 </div>
99 </div>
95 <div class="checkboxes">
100 <div class="checkboxes">
96 ${h.checkbox('repo_private',value="True")}
101 ${h.checkbox('repo_private',value="True")}
97 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
102 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
98 </div>
103 </div>
99 </div>
104 </div>
100 <div class="buttons">
105 <div class="buttons">
101 ${h.submit('save',_('Save'),class_="btn")}
106 ${h.submit('save',_('Save'),class_="btn")}
102 </div>
107 </div>
103 </div>
108 </div>
104 </div>
109 </div>
105 <script>
110 <script>
106 $(document).ready(function(){
111 $(document).ready(function(){
107 var setCopyPermsOption = function(group_val){
112 var setCopyPermsOption = function(group_val){
108 if(group_val != "-1"){
113 if(group_val != "-1"){
109 $('#copy_perms').show()
114 $('#copy_perms').show()
110 }
115 }
111 else{
116 else{
112 $('#copy_perms').hide();
117 $('#copy_perms').hide();
113 }
118 }
114 };
119 };
115
120
116 $('#remote_clone_toggle').on('click', function(e){
121 $('#remote_clone_toggle').on('click', function(e){
117 $('#remote_clone').show();
122 $('#remote_clone').show();
118 e.preventDefault();
123 e.preventDefault();
119 });
124 });
120
125
121 if($('#remote_clone input').hasClass('error')){
126 if($('#remote_clone input').hasClass('error')){
122 $('#remote_clone').show();
127 $('#remote_clone').show();
123 }
128 }
124 if($('#remote_clone input').val()){
129 if($('#remote_clone input').val()){
125 $('#remote_clone').show();
130 $('#remote_clone').show();
126 }
131 }
127
132
128 $("#repo_group").select2({
133 $("#repo_group").select2({
129 'containerCssClass': "drop-menu",
134 'containerCssClass': "drop-menu",
130 'dropdownCssClass': "drop-menu-dropdown",
135 'dropdownCssClass': "drop-menu-dropdown",
131 'dropdownAutoWidth': true,
136 'dropdownAutoWidth': true,
132 'width': "resolve"
137 'width': "resolve"
133 });
138 });
134
139
135 setCopyPermsOption($('#repo_group').val());
140 setCopyPermsOption($('#repo_group').val());
136 $("#repo_group").on("change", function(e) {
141 $("#repo_group").on("change", function(e) {
137 setCopyPermsOption(e.val)
142 setCopyPermsOption(e.val)
138 });
143 });
139
144
140 $("#repo_type").select2({
145 $("#repo_type").select2({
141 'containerCssClass': "drop-menu",
146 'containerCssClass': "drop-menu",
142 'dropdownCssClass': "drop-menu-dropdown",
147 'dropdownCssClass': "drop-menu-dropdown",
143 'minimumResultsForSearch': -1,
148 'minimumResultsForSearch': -1,
144 });
149 });
145 $("#repo_landing_rev").select2({
150 $("#repo_landing_rev").select2({
146 'containerCssClass': "drop-menu",
151 'containerCssClass': "drop-menu",
147 'dropdownCssClass': "drop-menu-dropdown",
152 'dropdownCssClass': "drop-menu-dropdown",
148 'minimumResultsForSearch': -1,
153 'minimumResultsForSearch': -1,
149 });
154 });
150 $('#repo_name').focus();
155 $('#repo_name').focus();
151
156
152 $('#select_my_group').on('click', function(e){
157 $('#select_my_group').on('click', function(e){
153 e.preventDefault();
158 e.preventDefault();
154 $("#repo_group").val($(this).data('personalGroupId')).trigger("change");
159 $("#repo_group").val($(this).data('personalGroupId')).trigger("change");
155 })
160 })
156
161
157 })
162 })
158 </script>
163 </script>
159 ${h.end_form()}
164 ${h.end_form()}
@@ -1,260 +1,266 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3
3
4 <div class="panel panel-default">
4 <div class="panel panel-default">
5 <div class="panel-heading">
5 <div class="panel-heading">
6 <h3 class="panel-title">${_('Settings for Repository: %s') % c.rhodecode_db_repo.repo_name}</h3>
6 <h3 class="panel-title">${_('Settings for Repository: %s') % c.rhodecode_db_repo.repo_name}</h3>
7 </div>
7 </div>
8 <div class="panel-body">
8 <div class="panel-body">
9 ${h.secure_form(h.route_path('edit_repo', repo_name=c.rhodecode_db_repo.repo_name), method='POST', request=request)}
9 ${h.secure_form(h.route_path('edit_repo', repo_name=c.rhodecode_db_repo.repo_name), method='POST', request=request)}
10 <div class="form">
10 <div class="form">
11 <!-- fields -->
11 <!-- fields -->
12 <div class="fields">
12 <div class="fields">
13
13
14 <div class="field">
14 <div class="field">
15 <div class="label">
15 <div class="label">
16 <label for="repo_name">${_('Name')}:</label>
16 <label for="repo_name">${_('Name')}:</label>
17 </div>
17 </div>
18 <div class="input">
18 <div class="input">
19 ${c.form['repo_name'].render(css_class='medium', oid='repo_name')|n}
19 ${c.form['repo_name'].render(css_class='medium', oid='repo_name')|n}
20 ${c.form.render_error(request, c.form['repo_name'])|n}
20 ${c.form.render_error(request, c.form['repo_name'])|n}
21
21
22 <p class="help-block">${_('Non-changeable id')}: `_${c.rhodecode_db_repo.repo_id}` <span><a href="#" onclick="$('#clone_id').toggle();return false">${_('what is that ?')}</a></span></p>
22 <p class="help-block">${_('Non-changeable id')}: `_${c.rhodecode_db_repo.repo_id}` <span><a href="#" onclick="$('#clone_id').toggle();return false">${_('what is that ?')}</a></span></p>
23 <p id="clone_id" style="display:none;">
23 <p id="clone_id" style="display:none;">
24 ${_('URL by id')}: `${c.rhodecode_db_repo.clone_url(with_id=True)}` <br/>
24 ${_('URL by id')}: `${c.rhodecode_db_repo.clone_url(with_id=True)}` <br/>
25 ${_('''In case this repository is renamed or moved into another group the repository url changes.
25 ${_('''In case this repository is renamed or moved into another group the repository url changes.
26 Using above url guarantees that this repository will always be accessible under such url.
26 Using above url guarantees that this repository will always be accessible under such url.
27 Useful for CI systems, or any other cases that you need to hardcode the url into 3rd party service.''')}</p>
27 Useful for CI systems, or any other cases that you need to hardcode the url into 3rd party service.''')}</p>
28 </div>
28 </div>
29 </div>
29 </div>
30
30
31 <div class="field">
31 <div class="field">
32 <div class="label">
32 <div class="label">
33 <label for="repo_group">${_('Repository group')}:</label>
33 <label for="repo_group">${_('Repository group')}:</label>
34 </div>
34 </div>
35 <div class="select">
35 <div class="select">
36 ${c.form['repo_group'].render(css_class='medium', oid='repo_group')|n}
36 ${c.form['repo_group'].render(css_class='medium', oid='repo_group')|n}
37 ${c.form.render_error(request, c.form['repo_group'])|n}
37 ${c.form.render_error(request, c.form['repo_group'])|n}
38
38
39 % if c.personal_repo_group:
39 % if c.personal_repo_group:
40 <a class="btn" href="#" data-personal-group-name="${c.personal_repo_group.group_name}" data-personal-group-id="${c.personal_repo_group.group_id}" onclick="selectMyGroup(this); return false">
40 <a class="btn" href="#" data-personal-group-name="${c.personal_repo_group.group_name}" data-personal-group-id="${c.personal_repo_group.group_id}" onclick="selectMyGroup(this); return false">
41 ${_('Select my personal group (`%(repo_group_name)s`)') % {'repo_group_name': c.personal_repo_group.group_name}}
41 ${_('Select my personal group (`%(repo_group_name)s`)') % {'repo_group_name': c.personal_repo_group.group_name}}
42 </a>
42 </a>
43 % endif
43 % endif
44 <p class="help-block">${_('Optional select a group to put this repository into.')}</p>
44 <p class="help-block">${_('Optional select a group to put this repository into.')}</p>
45 </div>
45 </div>
46 </div>
46 </div>
47
47
48 % if c.rhodecode_db_repo.repo_type != 'svn':
48 % if c.rhodecode_db_repo.repo_type != 'svn':
49 <div class="field">
49 <div class="field">
50 <div class="label">
50 <div class="label">
51 <label for="clone_uri">${_('Remote uri')}:</label>
51 <label for="clone_uri">${_('Remote uri')}:</label>
52 </div>
52 </div>
53 <div class="input">
53 <div class="input">
54 %if c.rhodecode_db_repo.clone_uri:
54 %if c.rhodecode_db_repo.clone_uri:
55 ## display, if we don't have any errors
55 ## display, if we don't have any errors
56 % if not c.form['repo_clone_uri'].error:
56 % if not c.form['repo_clone_uri'].error:
57 <div id="clone_uri_hidden" class='text-as-placeholder'>
57 <div id="clone_uri_hidden" class='text-as-placeholder'>
58 <span id="clone_uri_hidden_value">${c.rhodecode_db_repo.clone_uri_hidden}</span>
58 <span id="clone_uri_hidden_value">${c.rhodecode_db_repo.clone_uri_hidden}</span>
59 <span class="link" id="edit_clone_uri"><i class="icon-edit"></i>${_('edit')}</span>
59 <span class="link" id="edit_clone_uri"><i class="icon-edit"></i>${_('edit')}</span>
60 </div>
60 </div>
61 % endif
61 % endif
62
62
63 ## alter field
63 ## alter field
64 <div id="alter_clone_uri" style="${'' if c.form['repo_clone_uri'].error else 'display: none'}">
64 <div id="alter_clone_uri" style="${'' if c.form['repo_clone_uri'].error else 'display: none'}">
65 ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri', placeholder=_('enter new value, or leave empty to remove'))|n}
65 ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri', placeholder=_('enter new value, or leave empty to remove'))|n}
66 ${c.form.render_error(request, c.form['repo_clone_uri'])|n}
66 ${c.form.render_error(request, c.form['repo_clone_uri'])|n}
67 % if c.form['repo_clone_uri'].error:
67 % if c.form['repo_clone_uri'].error:
68 ## we got error from form subit, means we modify the url
68 ## we got error from form subit, means we modify the url
69 ${h.hidden('repo_clone_uri_change', 'MOD')}
69 ${h.hidden('repo_clone_uri_change', 'MOD')}
70 % else:
70 % else:
71 ${h.hidden('repo_clone_uri_change', 'OLD')}
71 ${h.hidden('repo_clone_uri_change', 'OLD')}
72 % endif
72 % endif
73
73
74 % if not c.form['repo_clone_uri'].error:
74 % if not c.form['repo_clone_uri'].error:
75 <span class="link" id="cancel_edit_clone_uri">${_('cancel')}</span>
75 <span class="link" id="cancel_edit_clone_uri">${_('cancel')}</span>
76 % endif
76 % endif
77
77
78 </div>
78 </div>
79 %else:
79 %else:
80 ## not set yet, display form to set it
80 ## not set yet, display form to set it
81 ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri')|n}
81 ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri')|n}
82 ${c.form.render_error(request, c.form['repo_clone_uri'])|n}
82 ${c.form.render_error(request, c.form['repo_clone_uri'])|n}
83 ${h.hidden('repo_clone_uri_change', 'NEW')}
83 ${h.hidden('repo_clone_uri_change', 'NEW')}
84 %endif
84 %endif
85 <p id="alter_clone_uri_help_block" class="help-block">
85 <p id="alter_clone_uri_help_block" class="help-block">
86 <% pull_link = h.literal(h.link_to('remote sync', h.route_path('edit_repo_remote', repo_name=c.repo_name))) %>
86 <% pull_link = h.literal(h.link_to('remote sync', h.route_path('edit_repo_remote', repo_name=c.repo_name))) %>
87 ${_('http[s] url where from repository was imported, this field can used for doing {pull_link}.').format(pull_link=pull_link)|n} <br/>
87 ${_('http[s] url where from repository was imported, this field can used for doing {pull_link}.').format(pull_link=pull_link)|n} <br/>
88 ${_('This field is stored encrypted inside Database, a format of http://user:password@server.com/repo_name can be used and will be hidden from display.')}
88 ${_('This field is stored encrypted inside Database, a format of http://user:password@server.com/repo_name can be used and will be hidden from display.')}
89 </p>
89 </p>
90 </div>
90 </div>
91 </div>
91 </div>
92 % else:
92 % else:
93 ${h.hidden('repo_clone_uri', '')}
93 ${h.hidden('repo_clone_uri', '')}
94 % endif
94 % endif
95
95
96 <div class="field">
96 <div class="field">
97 <div class="label">
97 <div class="label">
98 <label for="repo_landing_commit_ref">${_('Landing commit')}:</label>
98 <label for="repo_landing_commit_ref">${_('Landing commit')}:</label>
99 </div>
99 </div>
100 <div class="select">
100 <div class="select">
101 ${c.form['repo_landing_commit_ref'].render(css_class='medium', oid='repo_landing_commit_ref')|n}
101 ${c.form['repo_landing_commit_ref'].render(css_class='medium', oid='repo_landing_commit_ref')|n}
102 ${c.form.render_error(request, c.form['repo_landing_commit_ref'])|n}
102 ${c.form.render_error(request, c.form['repo_landing_commit_ref'])|n}
103 <p class="help-block">${_('Default commit for files page, downloads, full text search index and readme')}</p>
103 <p class="help-block">${_('Default commit for files page, downloads, full text search index and readme')}</p>
104 </div>
104 </div>
105 </div>
105 </div>
106
106
107 <div class="field badged-field">
107 <div class="field badged-field">
108 <div class="label">
108 <div class="label">
109 <label for="repo_owner">${_('Owner')}:</label>
109 <label for="repo_owner">${_('Owner')}:</label>
110 </div>
110 </div>
111 <div class="input">
111 <div class="input">
112 <div class="badge-input-container">
112 <div class="badge-input-container">
113 <div class="user-badge">
113 <div class="user-badge">
114 ${base.gravatar_with_user(c.rhodecode_db_repo.user.email or c.rhodecode_db_repo.user.username, show_disabled=not c.rhodecode_db_repo.user.active)}
114 ${base.gravatar_with_user(c.rhodecode_db_repo.user.email or c.rhodecode_db_repo.user.username, show_disabled=not c.rhodecode_db_repo.user.active)}
115 </div>
115 </div>
116 <div class="badge-input-wrap">
116 <div class="badge-input-wrap">
117 ${c.form['repo_owner'].render(css_class='medium', oid='repo_owner')|n}
117 ${c.form['repo_owner'].render(css_class='medium', oid='repo_owner')|n}
118 </div>
118 </div>
119 </div>
119 </div>
120 ${c.form.render_error(request, c.form['repo_owner'])|n}
120 ${c.form.render_error(request, c.form['repo_owner'])|n}
121 <p class="help-block">${_('Change owner of this repository.')}</p>
121 <p class="help-block">${_('Change owner of this repository.')}</p>
122 </div>
122 </div>
123 </div>
123 </div>
124
124
125 <div class="field">
125 <div class="field">
126 <div class="label label-textarea">
126 <div class="label label-textarea">
127 <label for="repo_description">${_('Description')}:</label>
127 <label for="repo_description">${_('Description')}:</label>
128 </div>
128 </div>
129 <div class="textarea text-area editor">
129 <div class="textarea text-area editor">
130 ${c.form['repo_description'].render(css_class='medium', oid='repo_description')|n}
130 ${c.form['repo_description'].render(css_class='medium', oid='repo_description')|n}
131 ${c.form.render_error(request, c.form['repo_description'])|n}
131 ${c.form.render_error(request, c.form['repo_description'])|n}
132 <p class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</p>
132
133 <% metatags_url = h.literal('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
134 <span class="help-block">${_('Plain text format with support of {metatags}. Add a README file for longer descriptions').format(metatags=metatags_url)|n}</span>
135 <span id="meta-tags-desc" style="display: none">
136 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
137 ${dt.metatags_help()}
138 </span>
133 </div>
139 </div>
134 </div>
140 </div>
135
141
136 <div class="field">
142 <div class="field">
137 <div class="label label-checkbox">
143 <div class="label label-checkbox">
138 <label for="${c.form['repo_private'].oid}">${_('Private repository')}:</label>
144 <label for="${c.form['repo_private'].oid}">${_('Private repository')}:</label>
139 </div>
145 </div>
140 <div class="checkboxes">
146 <div class="checkboxes">
141 ${c.form['repo_private'].render(css_class='medium')|n}
147 ${c.form['repo_private'].render(css_class='medium')|n}
142 ${c.form.render_error(request, c.form['repo_private'])|n}
148 ${c.form.render_error(request, c.form['repo_private'])|n}
143 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
149 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
144 </div>
150 </div>
145 </div>
151 </div>
146 <div class="field">
152 <div class="field">
147 <div class="label label-checkbox">
153 <div class="label label-checkbox">
148 <label for="${c.form['repo_enable_statistics'].oid}">${_('Enable statistics')}:</label>
154 <label for="${c.form['repo_enable_statistics'].oid}">${_('Enable statistics')}:</label>
149 </div>
155 </div>
150 <div class="checkboxes">
156 <div class="checkboxes">
151 ${c.form['repo_enable_statistics'].render(css_class='medium')|n}
157 ${c.form['repo_enable_statistics'].render(css_class='medium')|n}
152 ${c.form.render_error(request, c.form['repo_enable_statistics'])|n}
158 ${c.form.render_error(request, c.form['repo_enable_statistics'])|n}
153 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
159 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
154 </div>
160 </div>
155 </div>
161 </div>
156 <div class="field">
162 <div class="field">
157 <div class="label label-checkbox">
163 <div class="label label-checkbox">
158 <label for="${c.form['repo_enable_downloads'].oid}">${_('Enable downloads')}:</label>
164 <label for="${c.form['repo_enable_downloads'].oid}">${_('Enable downloads')}:</label>
159 </div>
165 </div>
160 <div class="checkboxes">
166 <div class="checkboxes">
161 ${c.form['repo_enable_downloads'].render(css_class='medium')|n}
167 ${c.form['repo_enable_downloads'].render(css_class='medium')|n}
162 ${c.form.render_error(request, c.form['repo_enable_downloads'])|n}
168 ${c.form.render_error(request, c.form['repo_enable_downloads'])|n}
163 <span class="help-block">${_('Enable download menu on summary page.')}</span>
169 <span class="help-block">${_('Enable download menu on summary page.')}</span>
164 </div>
170 </div>
165 </div>
171 </div>
166 <div class="field">
172 <div class="field">
167 <div class="label label-checkbox">
173 <div class="label label-checkbox">
168 <label for="${c.form['repo_enable_locking'].oid}">${_('Enable automatic locking')}:</label>
174 <label for="${c.form['repo_enable_locking'].oid}">${_('Enable automatic locking')}:</label>
169 </div>
175 </div>
170 <div class="checkboxes">
176 <div class="checkboxes">
171 ${c.form['repo_enable_locking'].render(css_class='medium')|n}
177 ${c.form['repo_enable_locking'].render(css_class='medium')|n}
172 ${c.form.render_error(request, c.form['repo_enable_locking'])|n}
178 ${c.form.render_error(request, c.form['repo_enable_locking'])|n}
173 <span class="help-block">${_('Enable automatic locking on repository. Pulling from this repository creates a lock that can be released by pushing back by the same user')}</span>
179 <span class="help-block">${_('Enable automatic locking on repository. Pulling from this repository creates a lock that can be released by pushing back by the same user')}</span>
174 </div>
180 </div>
175 </div>
181 </div>
176
182
177 %if c.visual.repository_fields:
183 %if c.visual.repository_fields:
178 ## EXTRA FIELDS
184 ## EXTRA FIELDS
179 %for field in c.repo_fields:
185 %for field in c.repo_fields:
180 <div class="field">
186 <div class="field">
181 <div class="label">
187 <div class="label">
182 <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
188 <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
183 </div>
189 </div>
184 <div class="input input-medium">
190 <div class="input input-medium">
185 ${h.text(field.field_key_prefixed, field.field_value, class_='medium')}
191 ${h.text(field.field_key_prefixed, field.field_value, class_='medium')}
186 %if field.field_desc:
192 %if field.field_desc:
187 <span class="help-block">${field.field_desc}</span>
193 <span class="help-block">${field.field_desc}</span>
188 %endif
194 %endif
189 </div>
195 </div>
190 </div>
196 </div>
191 %endfor
197 %endfor
192 %endif
198 %endif
193 <div class="buttons">
199 <div class="buttons">
194 ${h.submit('save',_('Save'),class_="btn")}
200 ${h.submit('save',_('Save'),class_="btn")}
195 ${h.reset('reset',_('Reset'),class_="btn")}
201 ${h.reset('reset',_('Reset'),class_="btn")}
196 </div>
202 </div>
197 </div>
203 </div>
198 </div>
204 </div>
199 ${h.end_form()}
205 ${h.end_form()}
200 </div>
206 </div>
201 </div>
207 </div>
202
208
203 <script>
209 <script>
204 $(document).ready(function(){
210 $(document).ready(function(){
205 var cloneUrl = function() {
211 var cloneUrl = function() {
206 var alterButton = $('#alter_clone_uri');
212 var alterButton = $('#alter_clone_uri');
207 var editButton = $('#edit_clone_uri');
213 var editButton = $('#edit_clone_uri');
208 var cancelEditButton = $('#cancel_edit_clone_uri');
214 var cancelEditButton = $('#cancel_edit_clone_uri');
209 var hiddenUrl = $('#clone_uri_hidden');
215 var hiddenUrl = $('#clone_uri_hidden');
210 var hiddenUrlValue = $('#clone_uri_hidden_value');
216 var hiddenUrlValue = $('#clone_uri_hidden_value');
211 var input = $('#clone_uri');
217 var input = $('#clone_uri');
212 var helpBlock = $('#alter_clone_uri_help_block');
218 var helpBlock = $('#alter_clone_uri_help_block');
213 var changedFlag = $('#repo_clone_uri_change');
219 var changedFlag = $('#repo_clone_uri_change');
214 var originalText = helpBlock.html();
220 var originalText = helpBlock.html();
215 var obfuscatedUrl = hiddenUrlValue.html();
221 var obfuscatedUrl = hiddenUrlValue.html();
216
222
217 var edit = function(e) {
223 var edit = function(e) {
218 alterButton.show();
224 alterButton.show();
219 editButton.hide();
225 editButton.hide();
220 hiddenUrl.hide();
226 hiddenUrl.hide();
221
227
222 //add the old value next to input for verification
228 //add the old value next to input for verification
223 helpBlock.html("(" + obfuscatedUrl + ")" + "<br\>" + originalText);
229 helpBlock.html("(" + obfuscatedUrl + ")" + "<br\>" + originalText);
224 changedFlag.val('MOD');
230 changedFlag.val('MOD');
225 };
231 };
226
232
227 var cancelEdit = function(e) {
233 var cancelEdit = function(e) {
228 alterButton.hide();
234 alterButton.hide();
229 editButton.show();
235 editButton.show();
230 hiddenUrl.show();
236 hiddenUrl.show();
231
237
232 helpBlock.html(originalText);
238 helpBlock.html(originalText);
233 changedFlag.val('OLD');
239 changedFlag.val('OLD');
234 input.val('');
240 input.val('');
235 };
241 };
236
242
237 var initEvents = function() {
243 var initEvents = function() {
238 editButton.on('click', edit);
244 editButton.on('click', edit);
239 cancelEditButton.on('click', cancelEdit);
245 cancelEditButton.on('click', cancelEdit);
240 };
246 };
241
247
242 var setInitialState = function() {
248 var setInitialState = function() {
243 if (input.hasClass('error')) {
249 if (input.hasClass('error')) {
244 alterButton.show();
250 alterButton.show();
245 editButton.hide();
251 editButton.hide();
246 hiddenUrl.hide();
252 hiddenUrl.hide();
247 }
253 }
248 };
254 };
249
255
250 setInitialState();
256 setInitialState();
251 initEvents();
257 initEvents();
252 }();
258 }();
253
259
254 selectMyGroup = function(element) {
260 selectMyGroup = function(element) {
255 $("#repo_group").val($(element).data('personalGroupId')).trigger("change");
261 $("#repo_group").val($(element).data('personalGroupId')).trigger("change");
256 };
262 };
257
263
258 UsersAutoComplete('repo_owner', '${c.rhodecode_user.user_id}');
264 UsersAutoComplete('repo_owner', '${c.rhodecode_user.user_id}');
259 });
265 });
260 </script>
266 </script>
@@ -1,237 +1,226 b''
1 ${h.secure_form(h.url('admin_settings_visual'), method='post')}
1 ${h.secure_form(h.url('admin_settings_visual'), method='post')}
2
2
3 <div class="panel panel-default">
3 <div class="panel panel-default">
4 <div class="panel-heading" id="general">
4 <div class="panel-heading" id="general">
5 <h3 class="panel-title">${_('General')}</h3>
5 <h3 class="panel-title">${_('General')}</h3>
6 </div>
6 </div>
7 <div class="panel-body">
7 <div class="panel-body">
8 <div class="checkbox">
8 <div class="checkbox">
9 ${h.checkbox('rhodecode_repository_fields','True')}
9 ${h.checkbox('rhodecode_repository_fields','True')}
10 <label for="rhodecode_repository_fields">${_('Use repository extra fields')}</label>
10 <label for="rhodecode_repository_fields">${_('Use repository extra fields')}</label>
11 </div>
11 </div>
12 <span class="help-block">${_('Allows storing additional customized fields per repository.')}</span>
12 <span class="help-block">${_('Allows storing additional customized fields per repository.')}</span>
13
13
14 <div></div>
14 <div></div>
15 <div class="checkbox">
15 <div class="checkbox">
16 ${h.checkbox('rhodecode_show_version','True')}
16 ${h.checkbox('rhodecode_show_version','True')}
17 <label for="rhodecode_show_version">${_('Show RhodeCode version')}</label>
17 <label for="rhodecode_show_version">${_('Show RhodeCode version')}</label>
18 </div>
18 </div>
19 <span class="help-block">${_('Shows or hides a version number of RhodeCode displayed in the footer.')}</span>
19 <span class="help-block">${_('Shows or hides a version number of RhodeCode displayed in the footer.')}</span>
20 </div>
20 </div>
21 </div>
21 </div>
22
22
23
23
24 <div class="panel panel-default">
24 <div class="panel panel-default">
25 <div class="panel-heading" id="gravatars">
25 <div class="panel-heading" id="gravatars">
26 <h3 class="panel-title">${_('Gravatars')}</h3>
26 <h3 class="panel-title">${_('Gravatars')}</h3>
27 </div>
27 </div>
28 <div class="panel-body">
28 <div class="panel-body">
29 <div class="checkbox">
29 <div class="checkbox">
30 ${h.checkbox('rhodecode_use_gravatar','True')}
30 ${h.checkbox('rhodecode_use_gravatar','True')}
31 <label for="rhodecode_use_gravatar">${_('Use Gravatars based avatars')}</label>
31 <label for="rhodecode_use_gravatar">${_('Use Gravatars based avatars')}</label>
32 </div>
32 </div>
33 <span class="help-block">${_('Use gravatar.com as avatar system for RhodeCode accounts. If this is disabled avatars are generated based on initials and email.')}</span>
33 <span class="help-block">${_('Use gravatar.com as avatar system for RhodeCode accounts. If this is disabled avatars are generated based on initials and email.')}</span>
34
34
35 <div class="label">
35 <div class="label">
36 <label for="rhodecode_gravatar_url">${_('Gravatar URL')}</label>
36 <label for="rhodecode_gravatar_url">${_('Gravatar URL')}</label>
37 </div>
37 </div>
38 <div class="input">
38 <div class="input">
39 <div class="field">
39 <div class="field">
40 ${h.text('rhodecode_gravatar_url', size='100%')}
40 ${h.text('rhodecode_gravatar_url', size='100%')}
41 </div>
41 </div>
42
42
43 <div class="field">
43 <div class="field">
44 <span class="help-block">${_('''Gravatar url allows you to use other avatar server application.
44 <span class="help-block">${_('''Gravatar url allows you to use other avatar server application.
45 Following variables of the URL will be replaced accordingly.
45 Following variables of the URL will be replaced accordingly.
46 {scheme} 'http' or 'https' sent from running RhodeCode server,
46 {scheme} 'http' or 'https' sent from running RhodeCode server,
47 {email} user email,
47 {email} user email,
48 {md5email} md5 hash of the user email (like at gravatar.com),
48 {md5email} md5 hash of the user email (like at gravatar.com),
49 {size} size of the image that is expected from the server application,
49 {size} size of the image that is expected from the server application,
50 {netloc} network location/server host of running RhodeCode server''')}</span>
50 {netloc} network location/server host of running RhodeCode server''')}</span>
51 </div>
51 </div>
52 </div>
52 </div>
53 </div>
53 </div>
54 </div>
54 </div>
55
55
56
56
57 <div class="panel panel-default">
57 <div class="panel panel-default">
58 <div class="panel-heading" id="meta-tagging">
58 <div class="panel-heading" id="meta-tagging">
59 <h3 class="panel-title">${_('Meta-Tagging')}</h3>
59 <h3 class="panel-title">${_('Meta-Tagging')}</h3>
60 </div>
60 </div>
61 <div class="panel-body">
61 <div class="panel-body">
62 <div class="checkbox">
62 <div class="checkbox">
63 ${h.checkbox('rhodecode_stylify_metatags','True')}
63 ${h.checkbox('rhodecode_stylify_metatags','True')}
64 <label for="rhodecode_stylify_metatags">${_('Stylify recognised meta tags')}</label>
64 <label for="rhodecode_stylify_metatags">${_('Stylify recognised meta tags')}</label>
65 </div>
65 </div>
66 <span class="help-block">${_('Parses meta tags from repository description field and turns them into colored tags.')}</span>
66 <span class="help-block">${_('Parses meta tags from repository or repository group description fields and turns them into colored tags.')}</span>
67 <div>
67 <div>
68 <table>
68 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
69 <tr><td>[featured] </td><td><span class="metatag" tag="featured">featured</span></td></tr>
69 ${dt.metatags_help()}
70 <tr><td>[stale] </td><td><span class="metatag" tag="stale">stale</span></td></tr>
71 <tr><td>[dead] </td><td><span class="metatag" tag="dead">dead</span></td></tr>
72 <tr><td>[personal] </td><td><span class="metatag" tag="personal">personal</span></td></tr>
73
74 <tr><td>[lang =&gt; lang] </td><td><span class="metatag" tag="lang" >lang</span></td></tr>
75
76 <tr><td>[license =&gt; License] </td><td><span class="metatag" tag="license"><a href="http://www.opensource.org/licenses/License" >License</a></span></td></tr>
77 <tr><td>[requires =&gt; Repo] </td><td><span class="metatag" tag="requires" >requires =&gt; <a href="#" >Repo</a></span></td></tr>
78 <tr><td>[recommends =&gt; Repo] </td><td><span class="metatag" tag="recommends" >recommends =&gt; <a href="#" >Repo</a></span></td></tr>
79 <tr><td>[see =&gt; URI] </td><td><span class="metatag" tag="see">see =&gt; <a href="#">URI</a> </span></td></tr>
80 </table>
81 </div>
70 </div>
82 </div>
71 </div>
83 </div>
72 </div>
84
73
85
74
86 <div class="panel panel-default">
75 <div class="panel panel-default">
87 <div class="panel-heading">
76 <div class="panel-heading">
88 <h3 class="panel-title">${_('Dashboard Items')}</h3>
77 <h3 class="panel-title">${_('Dashboard Items')}</h3>
89 </div>
78 </div>
90 <div class="panel-body">
79 <div class="panel-body">
91 <div class="label">
80 <div class="label">
92 <label for="rhodecode_dashboard_items">${_('Main page dashboard items')}</label>
81 <label for="rhodecode_dashboard_items">${_('Main page dashboard items')}</label>
93 </div>
82 </div>
94 <div class="field input">
83 <div class="field input">
95 ${h.text('rhodecode_dashboard_items',size=5)}
84 ${h.text('rhodecode_dashboard_items',size=5)}
96 </div>
85 </div>
97 <div class="field">
86 <div class="field">
98 <span class="help-block">${_('Number of items displayed in the main page dashboard before pagination is shown.')}</span>
87 <span class="help-block">${_('Number of items displayed in the main page dashboard before pagination is shown.')}</span>
99 </div>
88 </div>
100
89
101 <div class="label">
90 <div class="label">
102 <label for="rhodecode_admin_grid_items">${_('Admin pages items')}</label>
91 <label for="rhodecode_admin_grid_items">${_('Admin pages items')}</label>
103 </div>
92 </div>
104 <div class="field input">
93 <div class="field input">
105 ${h.text('rhodecode_admin_grid_items',size=5)}
94 ${h.text('rhodecode_admin_grid_items',size=5)}
106 </div>
95 </div>
107 <div class="field">
96 <div class="field">
108 <span class="help-block">${_('Number of items displayed in the admin pages grids before pagination is shown.')}</span>
97 <span class="help-block">${_('Number of items displayed in the admin pages grids before pagination is shown.')}</span>
109 </div>
98 </div>
110 </div>
99 </div>
111 </div>
100 </div>
112
101
113
102
114
103
115 <div class="panel panel-default">
104 <div class="panel panel-default">
116 <div class="panel-heading" id="commit-id">
105 <div class="panel-heading" id="commit-id">
117 <h3 class="panel-title">${_('Commit ID Style')}</h3>
106 <h3 class="panel-title">${_('Commit ID Style')}</h3>
118 </div>
107 </div>
119 <div class="panel-body">
108 <div class="panel-body">
120 <div class="label">
109 <div class="label">
121 <label for="rhodecode_show_sha_length">${_('Commit sha length')}</label>
110 <label for="rhodecode_show_sha_length">${_('Commit sha length')}</label>
122 </div>
111 </div>
123 <div class="input">
112 <div class="input">
124 <div class="field">
113 <div class="field">
125 ${h.text('rhodecode_show_sha_length',size=5)}
114 ${h.text('rhodecode_show_sha_length',size=5)}
126 </div>
115 </div>
127 <div class="field">
116 <div class="field">
128 <span class="help-block">${_('''Number of chars to show in commit sha displayed in web interface.
117 <span class="help-block">${_('''Number of chars to show in commit sha displayed in web interface.
129 By default it's shown as r123:9043a6a4c226 this value defines the
118 By default it's shown as r123:9043a6a4c226 this value defines the
130 length of the sha after the `r123:` part.''')}</span>
119 length of the sha after the `r123:` part.''')}</span>
131 </div>
120 </div>
132 </div>
121 </div>
133
122
134 <div class="checkbox">
123 <div class="checkbox">
135 ${h.checkbox('rhodecode_show_revision_number','True')}
124 ${h.checkbox('rhodecode_show_revision_number','True')}
136 <label for="rhodecode_show_revision_number">${_('Show commit ID numeric reference')} / ${_('Commit show revision number')}</label>
125 <label for="rhodecode_show_revision_number">${_('Show commit ID numeric reference')} / ${_('Commit show revision number')}</label>
137 </div>
126 </div>
138 <span class="help-block">${_('''Show revision number in commit sha displayed in web interface.
127 <span class="help-block">${_('''Show revision number in commit sha displayed in web interface.
139 By default it's shown as r123:9043a6a4c226 this value defines the
128 By default it's shown as r123:9043a6a4c226 this value defines the
140 if the `r123:` part is shown.''')}</span>
129 if the `r123:` part is shown.''')}</span>
141 </div>
130 </div>
142 </div>
131 </div>
143
132
144
133
145 <div class="panel panel-default">
134 <div class="panel panel-default">
146 <div class="panel-heading" id="icons">
135 <div class="panel-heading" id="icons">
147 <h3 class="panel-title">${_('Icons')}</h3>
136 <h3 class="panel-title">${_('Icons')}</h3>
148 </div>
137 </div>
149 <div class="panel-body">
138 <div class="panel-body">
150 <div class="checkbox">
139 <div class="checkbox">
151 ${h.checkbox('rhodecode_show_public_icon','True')}
140 ${h.checkbox('rhodecode_show_public_icon','True')}
152 <label for="rhodecode_show_public_icon">${_('Show public repo icon on repositories')}</label>
141 <label for="rhodecode_show_public_icon">${_('Show public repo icon on repositories')}</label>
153 </div>
142 </div>
154 <div></div>
143 <div></div>
155
144
156 <div class="checkbox">
145 <div class="checkbox">
157 ${h.checkbox('rhodecode_show_private_icon','True')}
146 ${h.checkbox('rhodecode_show_private_icon','True')}
158 <label for="rhodecode_show_private_icon">${_('Show private repo icon on repositories')}</label>
147 <label for="rhodecode_show_private_icon">${_('Show private repo icon on repositories')}</label>
159 </div>
148 </div>
160 <span class="help-block">${_('Show public/private icons next to repositories names.')}</span>
149 <span class="help-block">${_('Show public/private icons next to repositories names.')}</span>
161 </div>
150 </div>
162 </div>
151 </div>
163
152
164
153
165 <div class="panel panel-default">
154 <div class="panel panel-default">
166 <div class="panel-heading">
155 <div class="panel-heading">
167 <h3 class="panel-title">${_('Markup Renderer')}</h3>
156 <h3 class="panel-title">${_('Markup Renderer')}</h3>
168 </div>
157 </div>
169 <div class="panel-body">
158 <div class="panel-body">
170 <div class="field select">
159 <div class="field select">
171 ${h.select('rhodecode_markup_renderer', '', ['rst', 'markdown'])}
160 ${h.select('rhodecode_markup_renderer', '', ['rst', 'markdown'])}
172 </div>
161 </div>
173 <div class="field">
162 <div class="field">
174 <span class="help-block">${_('Default renderer used to render comments, pull request descriptions and other description elements. After change old entries will still work correctly.')}</span>
163 <span class="help-block">${_('Default renderer used to render comments, pull request descriptions and other description elements. After change old entries will still work correctly.')}</span>
175 </div>
164 </div>
176 </div>
165 </div>
177 </div>
166 </div>
178
167
179 <div class="panel panel-default">
168 <div class="panel panel-default">
180 <div class="panel-heading">
169 <div class="panel-heading">
181 <h3 class="panel-title">${_('Clone URL')}</h3>
170 <h3 class="panel-title">${_('Clone URL')}</h3>
182 </div>
171 </div>
183 <div class="panel-body">
172 <div class="panel-body">
184 <div class="field">
173 <div class="field">
185 ${h.text('rhodecode_clone_uri_tmpl', size=60)}
174 ${h.text('rhodecode_clone_uri_tmpl', size=60)}
186 </div>
175 </div>
187
176
188 <div class="field">
177 <div class="field">
189 <span class="help-block">
178 <span class="help-block">
190 ${_('''Schema of clone url construction eg. '{scheme}://{user}@{netloc}/{repo}', available vars:
179 ${_('''Schema of clone url construction eg. '{scheme}://{user}@{netloc}/{repo}', available vars:
191 {scheme} 'http' or 'https' sent from running RhodeCode server,
180 {scheme} 'http' or 'https' sent from running RhodeCode server,
192 {user} current user username,
181 {user} current user username,
193 {netloc} network location/server host of running RhodeCode server,
182 {netloc} network location/server host of running RhodeCode server,
194 {repo} full repository name,
183 {repo} full repository name,
195 {repoid} ID of repository, can be used to contruct clone-by-id''')}
184 {repoid} ID of repository, can be used to contruct clone-by-id''')}
196 </span>
185 </span>
197 </div>
186 </div>
198 </div>
187 </div>
199 </div>
188 </div>
200
189
201 <div class="panel panel-default">
190 <div class="panel panel-default">
202 <div class="panel-heading">
191 <div class="panel-heading">
203 <h3 class="panel-title">${_('Custom Support Link')}</h3>
192 <h3 class="panel-title">${_('Custom Support Link')}</h3>
204 </div>
193 </div>
205 <div class="panel-body">
194 <div class="panel-body">
206 <div class="field">
195 <div class="field">
207 ${h.text('rhodecode_support_url', size=60)}
196 ${h.text('rhodecode_support_url', size=60)}
208 </div>
197 </div>
209 <div class="field">
198 <div class="field">
210 <span class="help-block">
199 <span class="help-block">
211 ${_('''Custom url for the support link located at the bottom.
200 ${_('''Custom url for the support link located at the bottom.
212 The default is set to %(default_url)s. In case there's a need
201 The default is set to %(default_url)s. In case there's a need
213 to change the support link to internal issue tracker, it should be done here.
202 to change the support link to internal issue tracker, it should be done here.
214 ''') % {'default_url': h.url('rhodecode_support')}}
203 ''') % {'default_url': h.url('rhodecode_support')}}
215 </span>
204 </span>
216 </div>
205 </div>
217 </div>
206 </div>
218 </div>
207 </div>
219
208
220 <div class="buttons">
209 <div class="buttons">
221 ${h.submit('save',_('Save settings'),class_="btn")}
210 ${h.submit('save',_('Save settings'),class_="btn")}
222 ${h.reset('reset',_('Reset'),class_="btn")}
211 ${h.reset('reset',_('Reset'),class_="btn")}
223 </div>
212 </div>
224
213
225
214
226 ${h.end_form()}
215 ${h.end_form()}
227
216
228 <script>
217 <script>
229 $(document).ready(function() {
218 $(document).ready(function() {
230 $('#rhodecode_markup_renderer').select2({
219 $('#rhodecode_markup_renderer').select2({
231 containerCssClass: 'drop-menu',
220 containerCssClass: 'drop-menu',
232 dropdownCssClass: 'drop-menu-dropdown',
221 dropdownCssClass: 'drop-menu-dropdown',
233 dropdownAutoWidth: true,
222 dropdownAutoWidth: true,
234 minimumResultsForSearch: -1
223 minimumResultsForSearch: -1
235 });
224 });
236 });
225 });
237 </script>
226 </script>
@@ -1,314 +1,376 b''
1 ## DATA TABLE RE USABLE ELEMENTS
1 ## DATA TABLE RE USABLE ELEMENTS
2 ## usage:
2 ## usage:
3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
5
5
6 <%def name="metatags_help()">
7 <table>
8 <%
9 example_tags = [
10 ('state','[stable]'),
11 ('state','[stale]'),
12 ('state','[featured]'),
13 ('state','[dev]'),
14 ('state','[dead]'),
15
16 ('label','[personal]'),
17 ('generic','[v2.0.0]'),
18
19 ('lang','[lang =&gt; JavaScript]'),
20 ('license','[license =&gt; LicenseName]'),
21
22 ('ref','[requires =&gt; RepoName]'),
23 ('ref','[recommends =&gt; GroupName]'),
24 ('ref','[conflicts =&gt; SomeName]'),
25 ('ref','[base =&gt; SomeName]'),
26 ('url','[url =&gt; [linkName](https://rhodecode.com)]'),
27 ('see','[see =&gt; http://rhodecode.com]'),
28 ]
29 %>
30 % for tag_type, tag in example_tags:
31 <tr>
32 <td>${tag|n}</td>
33 <td>${h.style_metatag(tag_type, tag)|n}</td>
34 </tr>
35 % endfor
36 </table>
37 </%def>
38
6 ## REPOSITORY RENDERERS
39 ## REPOSITORY RENDERERS
7 <%def name="quick_menu(repo_name)">
40 <%def name="quick_menu(repo_name)">
8 <i class="icon-more"></i>
41 <i class="icon-more"></i>
9 <div class="menu_items_container hidden">
42 <div class="menu_items_container hidden">
10 <ul class="menu_items">
43 <ul class="menu_items">
11 <li>
44 <li>
12 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
45 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
13 <span>${_('Summary')}</span>
46 <span>${_('Summary')}</span>
14 </a>
47 </a>
15 </li>
48 </li>
16 <li>
49 <li>
17 <a title="${_('Changelog')}" href="${h.route_path('repo_changelog',repo_name=repo_name)}">
50 <a title="${_('Changelog')}" href="${h.route_path('repo_changelog',repo_name=repo_name)}">
18 <span>${_('Changelog')}</span>
51 <span>${_('Changelog')}</span>
19 </a>
52 </a>
20 </li>
53 </li>
21 <li>
54 <li>
22 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
55 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
23 <span>${_('Files')}</span>
56 <span>${_('Files')}</span>
24 </a>
57 </a>
25 </li>
58 </li>
26 <li>
59 <li>
27 <a title="${_('Fork')}" href="${h.route_path('repo_fork_new',repo_name=repo_name)}">
60 <a title="${_('Fork')}" href="${h.route_path('repo_fork_new',repo_name=repo_name)}">
28 <span>${_('Fork')}</span>
61 <span>${_('Fork')}</span>
29 </a>
62 </a>
30 </li>
63 </li>
31 </ul>
64 </ul>
32 </div>
65 </div>
33 </%def>
66 </%def>
34
67
35 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
68 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
36 <%
69 <%
37 def get_name(name,short_name=short_name):
70 def get_name(name,short_name=short_name):
38 if short_name:
71 if short_name:
39 return name.split('/')[-1]
72 return name.split('/')[-1]
40 else:
73 else:
41 return name
74 return name
42 %>
75 %>
43 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
76 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
44 ##NAME
77 ##NAME
45 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
78 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
46
79
47 ##TYPE OF REPO
80 ##TYPE OF REPO
48 %if h.is_hg(rtype):
81 %if h.is_hg(rtype):
49 <span title="${_('Mercurial repository')}"><i class="icon-hg"></i></span>
82 <span title="${_('Mercurial repository')}"><i class="icon-hg"></i></span>
50 %elif h.is_git(rtype):
83 %elif h.is_git(rtype):
51 <span title="${_('Git repository')}"><i class="icon-git"></i></span>
84 <span title="${_('Git repository')}"><i class="icon-git"></i></span>
52 %elif h.is_svn(rtype):
85 %elif h.is_svn(rtype):
53 <span title="${_('Subversion repository')}"><i class="icon-svn"></i></span>
86 <span title="${_('Subversion repository')}"><i class="icon-svn"></i></span>
54 %endif
87 %endif
55
88
56 ##PRIVATE/PUBLIC
89 ##PRIVATE/PUBLIC
57 %if private and c.visual.show_private_icon:
90 %if private and c.visual.show_private_icon:
58 <i class="icon-lock" title="${_('Private repository')}"></i>
91 <i class="icon-lock" title="${_('Private repository')}"></i>
59 %elif not private and c.visual.show_public_icon:
92 %elif not private and c.visual.show_public_icon:
60 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
93 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
61 %else:
94 %else:
62 <span></span>
95 <span></span>
63 %endif
96 %endif
64 ${get_name(name)}
97 ${get_name(name)}
65 </a>
98 </a>
66 %if fork_of:
99 %if fork_of:
67 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
100 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
68 %endif
101 %endif
69 %if rstate == 'repo_state_pending':
102 %if rstate == 'repo_state_pending':
70 <span class="creation_in_progress tooltip" title="${_('This repository is being created in a background task')}">
103 <span class="creation_in_progress tooltip" title="${_('This repository is being created in a background task')}">
71 (${_('creating...')})
104 (${_('creating...')})
72 </span>
105 </span>
73 %endif
106 %endif
74 </div>
107 </div>
75 </%def>
108 </%def>
76
109
77 <%def name="repo_desc(description)">
110 <%def name="repo_desc(description, stylify_metatags)">
78 <div class="truncate-wrap">${description}</div>
111 <%
112 tags, description = h.extract_metatags(description)
113 %>
114
115 <div class="truncate-wrap">
116 % if stylify_metatags:
117 % for tag_type, tag in tags:
118 ${h.style_metatag(tag_type, tag)|n}
119 % endfor
120 % endif
121 ${description}
122 </div>
123
79 </%def>
124 </%def>
80
125
81 <%def name="last_change(last_change)">
126 <%def name="last_change(last_change)">
82 ${h.age_component(last_change)}
127 ${h.age_component(last_change)}
83 </%def>
128 </%def>
84
129
85 <%def name="revision(name,rev,tip,author,last_msg)">
130 <%def name="revision(name,rev,tip,author,last_msg)">
86 <div>
131 <div>
87 %if rev >= 0:
132 %if rev >= 0:
88 <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.route_path('repo_commit',repo_name=name,commit_id=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
133 <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.route_path('repo_commit',repo_name=name,commit_id=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
89 %else:
134 %else:
90 ${_('No commits yet')}
135 ${_('No commits yet')}
91 %endif
136 %endif
92 </div>
137 </div>
93 </%def>
138 </%def>
94
139
95 <%def name="rss(name)">
140 <%def name="rss(name)">
96 %if c.rhodecode_user.username != h.DEFAULT_USER:
141 %if c.rhodecode_user.username != h.DEFAULT_USER:
97 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
142 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
98 %else:
143 %else:
99 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
144 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
100 %endif
145 %endif
101 </%def>
146 </%def>
102
147
103 <%def name="atom(name)">
148 <%def name="atom(name)">
104 %if c.rhodecode_user.username != h.DEFAULT_USER:
149 %if c.rhodecode_user.username != h.DEFAULT_USER:
105 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
150 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
106 %else:
151 %else:
107 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
152 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
108 %endif
153 %endif
109 </%def>
154 </%def>
110
155
111 <%def name="user_gravatar(email, size=16)">
156 <%def name="user_gravatar(email, size=16)">
112 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
157 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
113 ${base.gravatar(email, 16)}
158 ${base.gravatar(email, 16)}
114 </div>
159 </div>
115 </%def>
160 </%def>
116
161
117 <%def name="repo_actions(repo_name, super_user=True)">
162 <%def name="repo_actions(repo_name, super_user=True)">
118 <div>
163 <div>
119 <div class="grid_edit">
164 <div class="grid_edit">
120 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
165 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
121 <i class="icon-pencil"></i>Edit</a>
166 <i class="icon-pencil"></i>Edit</a>
122 </div>
167 </div>
123 <div class="grid_delete">
168 <div class="grid_delete">
124 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), method='POST', request=request)}
169 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), method='POST', request=request)}
125 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
170 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
126 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
171 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
127 ${h.end_form()}
172 ${h.end_form()}
128 </div>
173 </div>
129 </div>
174 </div>
130 </%def>
175 </%def>
131
176
132 <%def name="repo_state(repo_state)">
177 <%def name="repo_state(repo_state)">
133 <div>
178 <div>
134 %if repo_state == 'repo_state_pending':
179 %if repo_state == 'repo_state_pending':
135 <div class="tag tag4">${_('Creating')}</div>
180 <div class="tag tag4">${_('Creating')}</div>
136 %elif repo_state == 'repo_state_created':
181 %elif repo_state == 'repo_state_created':
137 <div class="tag tag1">${_('Created')}</div>
182 <div class="tag tag1">${_('Created')}</div>
138 %else:
183 %else:
139 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
184 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
140 %endif
185 %endif
141 </div>
186 </div>
142 </%def>
187 </%def>
143
188
144
189
145 ## REPO GROUP RENDERERS
190 ## REPO GROUP RENDERERS
146 <%def name="quick_repo_group_menu(repo_group_name)">
191 <%def name="quick_repo_group_menu(repo_group_name)">
147 <i class="icon-more"></i>
192 <i class="icon-more"></i>
148 <div class="menu_items_container hidden">
193 <div class="menu_items_container hidden">
149 <ul class="menu_items">
194 <ul class="menu_items">
150 <li>
195 <li>
151 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">${_('Summary')}</a>
196 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">${_('Summary')}</a>
152 </li>
197 </li>
153
198
154 </ul>
199 </ul>
155 </div>
200 </div>
156 </%def>
201 </%def>
157
202
158 <%def name="repo_group_name(repo_group_name, children_groups=None)">
203 <%def name="repo_group_name(repo_group_name, children_groups=None)">
159 <div>
204 <div>
160 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
205 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
161 <i class="icon-folder-close" title="${_('Repository group')}"></i>
206 <i class="icon-folder-close" title="${_('Repository group')}"></i>
162 %if children_groups:
207 %if children_groups:
163 ${h.literal(' &raquo; '.join(children_groups))}
208 ${h.literal(' &raquo; '.join(children_groups))}
164 %else:
209 %else:
165 ${repo_group_name}
210 ${repo_group_name}
166 %endif
211 %endif
167 </a>
212 </a>
168 </div>
213 </div>
169 </%def>
214 </%def>
170
215
171 <%def name="repo_group_desc(description)">
216 <%def name="repo_group_desc(description, personal, stylify_metatags)">
172 <div class="truncate-wrap">${description}</div>
217
218 <%
219 tags, description = h.extract_metatags(description)
220 %>
221
222 <div class="truncate-wrap">
223 % if personal:
224 <div class="metatag" tag="personal">${_('personal')}</div>
225 % endif
226
227 % if stylify_metatags:
228 % for tag_type, tag in tags:
229 ${h.style_metatag(tag_type, tag)|n}
230 % endfor
231 % endif
232 ${description}
233 </div>
234
173 </%def>
235 </%def>
174
236
175 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
237 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
176 <div class="grid_edit">
238 <div class="grid_edit">
177 <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
239 <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
178 </div>
240 </div>
179 <div class="grid_delete">
241 <div class="grid_delete">
180 ${h.secure_form(h.url('delete_repo_group', group_name=repo_group_name),method='delete')}
242 ${h.secure_form(h.url('delete_repo_group', group_name=repo_group_name),method='delete')}
181 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
243 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
182 onclick="return confirm('"+_ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
244 onclick="return confirm('"+_ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
183 ${h.end_form()}
245 ${h.end_form()}
184 </div>
246 </div>
185 </%def>
247 </%def>
186
248
187
249
188 <%def name="user_actions(user_id, username)">
250 <%def name="user_actions(user_id, username)">
189 <div class="grid_edit">
251 <div class="grid_edit">
190 <a href="${h.url('edit_user',user_id=user_id)}" title="${_('Edit')}">
252 <a href="${h.url('edit_user',user_id=user_id)}" title="${_('Edit')}">
191 <i class="icon-pencil"></i>Edit</a>
253 <i class="icon-pencil"></i>Edit</a>
192 </div>
254 </div>
193 <div class="grid_delete">
255 <div class="grid_delete">
194 ${h.secure_form(h.url('delete_user', user_id=user_id),method='delete')}
256 ${h.secure_form(h.url('delete_user', user_id=user_id),method='delete')}
195 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
257 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
196 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
258 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
197 ${h.end_form()}
259 ${h.end_form()}
198 </div>
260 </div>
199 </%def>
261 </%def>
200
262
201 <%def name="user_group_actions(user_group_id, user_group_name)">
263 <%def name="user_group_actions(user_group_id, user_group_name)">
202 <div class="grid_edit">
264 <div class="grid_edit">
203 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
265 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
204 </div>
266 </div>
205 <div class="grid_delete">
267 <div class="grid_delete">
206 ${h.secure_form(h.route_path('user_groups_delete', user_group_id=user_group_id), method='POST', request=request)}
268 ${h.secure_form(h.route_path('user_groups_delete', user_group_id=user_group_id), method='POST', request=request)}
207 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
269 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
208 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
270 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
209 ${h.end_form()}
271 ${h.end_form()}
210 </div>
272 </div>
211 </%def>
273 </%def>
212
274
213
275
214 <%def name="user_name(user_id, username)">
276 <%def name="user_name(user_id, username)">
215 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.url('edit_user', user_id=user_id))}
277 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.url('edit_user', user_id=user_id))}
216 </%def>
278 </%def>
217
279
218 <%def name="user_profile(username)">
280 <%def name="user_profile(username)">
219 ${base.gravatar_with_user(username, 16)}
281 ${base.gravatar_with_user(username, 16)}
220 </%def>
282 </%def>
221
283
222 <%def name="user_group_name(user_group_id, user_group_name)">
284 <%def name="user_group_name(user_group_id, user_group_name)">
223 <div>
285 <div>
224 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}">
286 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}">
225 <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a>
287 <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a>
226 </div>
288 </div>
227 </%def>
289 </%def>
228
290
229
291
230 ## GISTS
292 ## GISTS
231
293
232 <%def name="gist_gravatar(full_contact)">
294 <%def name="gist_gravatar(full_contact)">
233 <div class="gist_gravatar">
295 <div class="gist_gravatar">
234 ${base.gravatar(full_contact, 30)}
296 ${base.gravatar(full_contact, 30)}
235 </div>
297 </div>
236 </%def>
298 </%def>
237
299
238 <%def name="gist_access_id(gist_access_id, full_contact)">
300 <%def name="gist_access_id(gist_access_id, full_contact)">
239 <div>
301 <div>
240 <b>
302 <b>
241 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
303 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
242 </b>
304 </b>
243 </div>
305 </div>
244 </%def>
306 </%def>
245
307
246 <%def name="gist_author(full_contact, created_on, expires)">
308 <%def name="gist_author(full_contact, created_on, expires)">
247 ${base.gravatar_with_user(full_contact, 16)}
309 ${base.gravatar_with_user(full_contact, 16)}
248 </%def>
310 </%def>
249
311
250
312
251 <%def name="gist_created(created_on)">
313 <%def name="gist_created(created_on)">
252 <div class="created">
314 <div class="created">
253 ${h.age_component(created_on, time_is_local=True)}
315 ${h.age_component(created_on, time_is_local=True)}
254 </div>
316 </div>
255 </%def>
317 </%def>
256
318
257 <%def name="gist_expires(expires)">
319 <%def name="gist_expires(expires)">
258 <div class="created">
320 <div class="created">
259 %if expires == -1:
321 %if expires == -1:
260 ${_('never')}
322 ${_('never')}
261 %else:
323 %else:
262 ${h.age_component(h.time_to_utcdatetime(expires))}
324 ${h.age_component(h.time_to_utcdatetime(expires))}
263 %endif
325 %endif
264 </div>
326 </div>
265 </%def>
327 </%def>
266
328
267 <%def name="gist_type(gist_type)">
329 <%def name="gist_type(gist_type)">
268 %if gist_type != 'public':
330 %if gist_type != 'public':
269 <div class="tag">${_('Private')}</div>
331 <div class="tag">${_('Private')}</div>
270 %endif
332 %endif
271 </%def>
333 </%def>
272
334
273 <%def name="gist_description(gist_description)">
335 <%def name="gist_description(gist_description)">
274 ${gist_description}
336 ${gist_description}
275 </%def>
337 </%def>
276
338
277
339
278 ## PULL REQUESTS GRID RENDERERS
340 ## PULL REQUESTS GRID RENDERERS
279
341
280 <%def name="pullrequest_target_repo(repo_name)">
342 <%def name="pullrequest_target_repo(repo_name)">
281 <div class="truncate">
343 <div class="truncate">
282 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
344 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
283 </div>
345 </div>
284 </%def>
346 </%def>
285 <%def name="pullrequest_status(status)">
347 <%def name="pullrequest_status(status)">
286 <div class="${'flag_status %s' % status} pull-left"></div>
348 <div class="${'flag_status %s' % status} pull-left"></div>
287 </%def>
349 </%def>
288
350
289 <%def name="pullrequest_title(title, description)">
351 <%def name="pullrequest_title(title, description)">
290 ${title} <br/>
352 ${title} <br/>
291 ${h.shorter(description, 40)}
353 ${h.shorter(description, 40)}
292 </%def>
354 </%def>
293
355
294 <%def name="pullrequest_comments(comments_nr)">
356 <%def name="pullrequest_comments(comments_nr)">
295 <i class="icon-comment"></i> ${comments_nr}
357 <i class="icon-comment"></i> ${comments_nr}
296 </%def>
358 </%def>
297
359
298 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
360 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
299 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
361 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
300 % if short:
362 % if short:
301 #${pull_request_id}
363 #${pull_request_id}
302 % else:
364 % else:
303 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
365 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
304 % endif
366 % endif
305 </a>
367 </a>
306 </%def>
368 </%def>
307
369
308 <%def name="pullrequest_updated_on(updated_on)">
370 <%def name="pullrequest_updated_on(updated_on)">
309 ${h.age_component(h.time_to_utcdatetime(updated_on))}
371 ${h.age_component(h.time_to_utcdatetime(updated_on))}
310 </%def>
372 </%def>
311
373
312 <%def name="pullrequest_author(full_contact)">
374 <%def name="pullrequest_author(full_contact)">
313 ${base.gravatar_with_user(full_contact, 16)}
375 ${base.gravatar_with_user(full_contact, 16)}
314 </%def>
376 </%def>
@@ -1,129 +1,134 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Fork repository %s') % c.repo_name}
5 ${_('Fork repository %s') % c.repo_name}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${_('New Fork')}
12 ${_('New Fork')}
13 </%def>
13 </%def>
14
14
15 <%def name="menu_bar_nav()">
15 <%def name="menu_bar_nav()">
16 ${self.menu_items(active='repositories')}
16 ${self.menu_items(active='repositories')}
17 </%def>
17 </%def>
18
18
19 <%def name="menu_bar_subnav()">
19 <%def name="menu_bar_subnav()">
20 ${self.repo_menu(active='options')}
20 ${self.repo_menu(active='options')}
21 </%def>
21 </%def>
22
22
23 <%def name="main()">
23 <%def name="main()">
24 <div class="box">
24 <div class="box">
25 <div class="title">
25 <div class="title">
26 ${self.repo_page_title(c.rhodecode_db_repo)}
26 ${self.repo_page_title(c.rhodecode_db_repo)}
27 ${self.breadcrumbs()}
27 ${self.breadcrumbs()}
28 </div>
28 </div>
29
29
30 ${h.secure_form(h.route_path('repo_fork_create',repo_name=c.rhodecode_db_repo.repo_name), method='POST', request=request)}
30 ${h.secure_form(h.route_path('repo_fork_create',repo_name=c.rhodecode_db_repo.repo_name), method='POST', request=request)}
31 <div class="form">
31 <div class="form">
32 <!-- fields -->
32 <!-- fields -->
33 <div class="fields">
33 <div class="fields">
34
34
35 <div class="field">
35 <div class="field">
36 <div class="label">
36 <div class="label">
37 <label for="repo_name">${_('Fork name')}:</label>
37 <label for="repo_name">${_('Fork name')}:</label>
38 </div>
38 </div>
39 <div class="input">
39 <div class="input">
40 ${h.text('repo_name', class_="medium")}
40 ${h.text('repo_name', class_="medium")}
41 ${h.hidden('repo_type',c.rhodecode_db_repo.repo_type)}
41 ${h.hidden('repo_type',c.rhodecode_db_repo.repo_type)}
42 ${h.hidden('fork_parent_id',c.rhodecode_db_repo.repo_id)}
42 ${h.hidden('fork_parent_id',c.rhodecode_db_repo.repo_id)}
43 </div>
43 </div>
44 </div>
44 </div>
45
45
46 <div class="field">
46 <div class="field">
47 <div class="label label-textarea">
47 <div class="label label-textarea">
48 <label for="description">${_('Description')}:</label>
48 <label for="description">${_('Description')}:</label>
49 </div>
49 </div>
50 <div class="textarea-repo textarea text-area editor">
50 <div class="textarea-repo textarea text-area editor">
51 ${h.textarea('description')}
51 ${h.textarea('description')}
52 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
52 <% metatags_url = h.literal('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
53 <span class="help-block">${_('Plain text format with support of {metatags}. Add a README file for longer descriptions').format(metatags=metatags_url)|n}</span>
54 <span id="meta-tags-desc" style="display: none">
55 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
56 ${dt.metatags_help()}
57 </span>
53 </div>
58 </div>
54 </div>
59 </div>
55
60
56 <div class="field">
61 <div class="field">
57 <div class="label">
62 <div class="label">
58 <label for="repo_group">${_('Repository group')}:</label>
63 <label for="repo_group">${_('Repository group')}:</label>
59 </div>
64 </div>
60 <div class="select">
65 <div class="select">
61 ${h.select('repo_group','',c.repo_groups,class_="medium")}
66 ${h.select('repo_group','',c.repo_groups,class_="medium")}
62 % if c.personal_repo_group:
67 % if c.personal_repo_group:
63 <a class="btn" href="#" id="select_my_group" data-personal-group-id="${c.personal_repo_group.group_id}">
68 <a class="btn" href="#" id="select_my_group" data-personal-group-id="${c.personal_repo_group.group_id}">
64 ${_('Select my personal group (%(repo_group_name)s)') % {'repo_group_name': c.personal_repo_group.group_name}}
69 ${_('Select my personal group (%(repo_group_name)s)') % {'repo_group_name': c.personal_repo_group.group_name}}
65 </a>
70 </a>
66 % endif
71 % endif
67 <span class="help-block">${_('Optionally select a group to put this repository into.')}</span>
72 <span class="help-block">${_('Optionally select a group to put this repository into.')}</span>
68 </div>
73 </div>
69 </div>
74 </div>
70
75
71 <div class="field">
76 <div class="field">
72 <div class="label">
77 <div class="label">
73 <label for="landing_rev">${_('Landing commit')}:</label>
78 <label for="landing_rev">${_('Landing commit')}:</label>
74 </div>
79 </div>
75 <div class="select">
80 <div class="select">
76 ${h.select('landing_rev','',c.landing_revs,class_="medium")}
81 ${h.select('landing_rev','',c.landing_revs,class_="medium")}
77 <span class="help-block">${_('Default commit for files page, downloads, whoosh and readme')}</span>
82 <span class="help-block">${_('Default commit for files page, downloads, whoosh and readme')}</span>
78 </div>
83 </div>
79 </div>
84 </div>
80
85
81 <div class="field">
86 <div class="field">
82 <div class="label label-checkbox">
87 <div class="label label-checkbox">
83 <label for="private">${_('Private')}:</label>
88 <label for="private">${_('Private')}:</label>
84 </div>
89 </div>
85 <div class="checkboxes">
90 <div class="checkboxes">
86 ${h.checkbox('private',value="True")}
91 ${h.checkbox('private',value="True")}
87 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
92 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
88 </div>
93 </div>
89 </div>
94 </div>
90
95
91 <div class="field">
96 <div class="field">
92 <div class="label label-checkbox">
97 <div class="label label-checkbox">
93 <label for="private">${_('Copy permissions')}:</label>
98 <label for="private">${_('Copy permissions')}:</label>
94 </div>
99 </div>
95 <div class="checkboxes">
100 <div class="checkboxes">
96 ${h.checkbox('copy_permissions',value="True", checked="checked")}
101 ${h.checkbox('copy_permissions',value="True", checked="checked")}
97 <span class="help-block">${_('Copy permissions from forked repository')}</span>
102 <span class="help-block">${_('Copy permissions from forked repository')}</span>
98 </div>
103 </div>
99 </div>
104 </div>
100
105
101 <div class="buttons">
106 <div class="buttons">
102 ${h.submit('',_('Fork this Repository'),class_="btn")}
107 ${h.submit('',_('Fork this Repository'),class_="btn")}
103 </div>
108 </div>
104 </div>
109 </div>
105 </div>
110 </div>
106 ${h.end_form()}
111 ${h.end_form()}
107 </div>
112 </div>
108 <script>
113 <script>
109 $(document).ready(function(){
114 $(document).ready(function(){
110 $("#repo_group").select2({
115 $("#repo_group").select2({
111 'dropdownAutoWidth': true,
116 'dropdownAutoWidth': true,
112 'containerCssClass': "drop-menu",
117 'containerCssClass': "drop-menu",
113 'dropdownCssClass': "drop-menu-dropdown",
118 'dropdownCssClass': "drop-menu-dropdown",
114 'width': "resolve"
119 'width': "resolve"
115 });
120 });
116 $("#landing_rev").select2({
121 $("#landing_rev").select2({
117 'containerCssClass': "drop-menu",
122 'containerCssClass': "drop-menu",
118 'dropdownCssClass': "drop-menu-dropdown",
123 'dropdownCssClass': "drop-menu-dropdown",
119 'minimumResultsForSearch': -1
124 'minimumResultsForSearch': -1
120 });
125 });
121 $('#repo_name').focus();
126 $('#repo_name').focus();
122
127
123 $('#select_my_group').on('click', function(e){
128 $('#select_my_group').on('click', function(e){
124 e.preventDefault();
129 e.preventDefault();
125 $("#repo_group").val($(this).data('personalGroupId')).trigger("change");
130 $("#repo_group").val($(this).data('personalGroupId')).trigger("change");
126 })
131 })
127 })
132 })
128 </script>
133 </script>
129 </%def>
134 </%def>
@@ -1,211 +1,210 b''
1 <%def name="refs_counters(branches, closed_branches, tags, bookmarks)">
1 <%def name="refs_counters(branches, closed_branches, tags, bookmarks)">
2 <span class="branchtag tag">
2 <span class="branchtag tag">
3 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
3 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
4 <i class="icon-branch"></i>${_ungettext(
4 <i class="icon-branch"></i>${_ungettext(
5 '%(num)s Branch','%(num)s Branches', len(branches)) % {'num': len(branches)}}</a>
5 '%(num)s Branch','%(num)s Branches', len(branches)) % {'num': len(branches)}}</a>
6 </span>
6 </span>
7
7
8 %if closed_branches:
8 %if closed_branches:
9 <span class="branchtag tag">
9 <span class="branchtag tag">
10 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
10 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
11 <i class="icon-branch"></i>${_ungettext(
11 <i class="icon-branch"></i>${_ungettext(
12 '%(num)s Closed Branch', '%(num)s Closed Branches', len(closed_branches)) % {'num': len(closed_branches)}}</a>
12 '%(num)s Closed Branch', '%(num)s Closed Branches', len(closed_branches)) % {'num': len(closed_branches)}}</a>
13 </span>
13 </span>
14 %endif
14 %endif
15
15
16 <span class="tagtag tag">
16 <span class="tagtag tag">
17 <a href="${h.route_path('tags_home',repo_name=c.repo_name)}" class="childs">
17 <a href="${h.route_path('tags_home',repo_name=c.repo_name)}" class="childs">
18 <i class="icon-tag"></i>${_ungettext(
18 <i class="icon-tag"></i>${_ungettext(
19 '%(num)s Tag', '%(num)s Tags', len(tags)) % {'num': len(tags)}}</a>
19 '%(num)s Tag', '%(num)s Tags', len(tags)) % {'num': len(tags)}}</a>
20 </span>
20 </span>
21
21
22 %if bookmarks:
22 %if bookmarks:
23 <span class="booktag tag">
23 <span class="booktag tag">
24 <a href="${h.route_path('bookmarks_home',repo_name=c.repo_name)}" class="childs">
24 <a href="${h.route_path('bookmarks_home',repo_name=c.repo_name)}" class="childs">
25 <i class="icon-bookmark"></i>${_ungettext(
25 <i class="icon-bookmark"></i>${_ungettext(
26 '%(num)s Bookmark', '%(num)s Bookmarks', len(bookmarks)) % {'num': len(bookmarks)}}</a>
26 '%(num)s Bookmark', '%(num)s Bookmarks', len(bookmarks)) % {'num': len(bookmarks)}}</a>
27 </span>
27 </span>
28 %endif
28 %endif
29 </%def>
29 </%def>
30
30
31 <%def name="summary_detail(breadcrumbs_links, show_downloads=True)">
31 <%def name="summary_detail(breadcrumbs_links, show_downloads=True)">
32 <% summary = lambda n:{False:'summary-short'}.get(n) %>
32 <% summary = lambda n:{False:'summary-short'}.get(n) %>
33
33
34 <div id="summary-menu-stats" class="summary-detail">
34 <div id="summary-menu-stats" class="summary-detail">
35 <div class="summary-detail-header">
35 <div class="summary-detail-header">
36 <div class="breadcrumbs files_location">
36 <div class="breadcrumbs files_location">
37 <h4>
37 <h4>
38 ${breadcrumbs_links}
38 ${breadcrumbs_links}
39 </h4>
39 </h4>
40 </div>
40 </div>
41 <div id="summary_details_expand" class="btn-collapse" data-toggle="summary-details">
41 <div id="summary_details_expand" class="btn-collapse" data-toggle="summary-details">
42 ${_('Show More')}
42 ${_('Show More')}
43 </div>
43 </div>
44 </div>
44 </div>
45
45
46 <div class="fieldset">
46 <div class="fieldset">
47 %if h.is_svn_without_proxy(c.rhodecode_db_repo):
47 %if h.is_svn_without_proxy(c.rhodecode_db_repo):
48 <div class="left-label disabled">
48 <div class="left-label disabled">
49 ${_('Read-only url')}:
49 ${_('Read-only url')}:
50 </div>
50 </div>
51 <div class="right-content disabled">
51 <div class="right-content disabled">
52 <input type="text" class="input-monospace" id="clone_url" disabled value="${c.clone_repo_url}"/>
52 <input type="text" class="input-monospace" id="clone_url" disabled value="${c.clone_repo_url}"/>
53 <i id="clone_by_name_copy" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url}" title="${_('Copy the clone url')}"></i>
53 <i id="clone_by_name_copy" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url}" title="${_('Copy the clone url')}"></i>
54
54
55 <input type="text" class="input-monospace" id="clone_url_id" disabled value="${c.clone_repo_url_id}" style="display: none;"/>
55 <input type="text" class="input-monospace" id="clone_url_id" disabled value="${c.clone_repo_url_id}" style="display: none;"/>
56 <i id="clone_by_id_copy" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_id}" title="${_('Copy the clone by id url')}" style="display: none"></i>
56 <i id="clone_by_id_copy" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_id}" title="${_('Copy the clone by id url')}" style="display: none"></i>
57
57
58 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
58 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
59 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
59 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
60
60
61 <p class="help-block">${_('SVN Protocol is disabled. To enable it, see the')} <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('documentation here')}</a>.</p>
61 <p class="help-block">${_('SVN Protocol is disabled. To enable it, see the')} <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('documentation here')}</a>.</p>
62 </div>
62 </div>
63 %else:
63 %else:
64 <div class="left-label">
64 <div class="left-label">
65 ${_('Clone url')}:
65 ${_('Clone url')}:
66 </div>
66 </div>
67 <div class="right-content">
67 <div class="right-content">
68 <input type="text" class="input-monospace" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
68 <input type="text" class="input-monospace" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
69 <i id="clone_by_name_copy" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url}" title="${_('Copy the clone url')}"></i>
69 <i id="clone_by_name_copy" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url}" title="${_('Copy the clone url')}"></i>
70
70
71 <input type="text" class="input-monospace" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}" style="display: none;"/>
71 <input type="text" class="input-monospace" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}" style="display: none;"/>
72 <i id="clone_by_id_copy" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_id}" title="${_('Copy the clone by id url')}" style="display: none"></i>
72 <i id="clone_by_id_copy" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_id}" title="${_('Copy the clone by id url')}" style="display: none"></i>
73
73
74 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
74 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
75 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
75 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
76 </div>
76 </div>
77 %endif
77 %endif
78 </div>
78 </div>
79
79
80 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
80 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
81 <div class="left-label">
81 <div class="left-label">
82 ${_('Description')}:
82 ${_('Description')}:
83 </div>
83 </div>
84 <div class="right-content">
84 <div class="right-content">
85 %if c.visual.stylify_metatags:
85 <div class="input ${summary(c.show_stats)}">
86 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.escaped_stylize(c.rhodecode_db_repo.description))}</div>
86 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
87 %else:
87 ${dt.repo_desc(c.rhodecode_db_repo.description_safe, c.visual.stylify_metatags)}
88 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.html_escape(c.rhodecode_db_repo.description))}</div>
88 </div>
89 %endif
90 </div>
89 </div>
91 </div>
90 </div>
92
91
93 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
92 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
94 <div class="left-label">
93 <div class="left-label">
95 ${_('Information')}:
94 ${_('Information')}:
96 </div>
95 </div>
97 <div class="right-content">
96 <div class="right-content">
98
97
99 <div class="repo-size">
98 <div class="repo-size">
100 <% commit_rev = c.rhodecode_db_repo.changeset_cache.get('revision') %>
99 <% commit_rev = c.rhodecode_db_repo.changeset_cache.get('revision') %>
101
100
102 ## commits
101 ## commits
103 % if commit_rev == -1:
102 % if commit_rev == -1:
104 ${_ungettext('%(num)s Commit', '%(num)s Commits', 0) % {'num': 0}},
103 ${_ungettext('%(num)s Commit', '%(num)s Commits', 0) % {'num': 0}},
105 % else:
104 % else:
106 <a href="${h.route_path('repo_changelog', repo_name=c.repo_name)}">
105 <a href="${h.route_path('repo_changelog', repo_name=c.repo_name)}">
107 ${_ungettext('%(num)s Commit', '%(num)s Commits', commit_rev) % {'num': commit_rev}}</a>,
106 ${_ungettext('%(num)s Commit', '%(num)s Commits', commit_rev) % {'num': commit_rev}}</a>,
108 % endif
107 % endif
109
108
110 ## forks
109 ## forks
111 <a title="${_('Number of Repository Forks')}" href="${h.route_path('repo_forks_show_all', repo_name=c.repo_name)}">
110 <a title="${_('Number of Repository Forks')}" href="${h.route_path('repo_forks_show_all', repo_name=c.repo_name)}">
112 ${c.repository_forks} ${_ungettext('Fork', 'Forks', c.repository_forks)}</a>,
111 ${c.repository_forks} ${_ungettext('Fork', 'Forks', c.repository_forks)}</a>,
113
112
114 ## repo size
113 ## repo size
115 % if commit_rev == -1:
114 % if commit_rev == -1:
116 <span class="stats-bullet">0 B</span>
115 <span class="stats-bullet">0 B</span>
117 % else:
116 % else:
118 <span class="stats-bullet" id="repo_size_container">
117 <span class="stats-bullet" id="repo_size_container">
119 ${_('Calculating Repository Size...')}
118 ${_('Calculating Repository Size...')}
120 </span>
119 </span>
121 % endif
120 % endif
122 </div>
121 </div>
123
122
124 <div class="commit-info">
123 <div class="commit-info">
125 <div class="tags">
124 <div class="tags">
126 % if c.rhodecode_repo:
125 % if c.rhodecode_repo:
127 ${refs_counters(
126 ${refs_counters(
128 c.rhodecode_repo.branches,
127 c.rhodecode_repo.branches,
129 c.rhodecode_repo.branches_closed,
128 c.rhodecode_repo.branches_closed,
130 c.rhodecode_repo.tags,
129 c.rhodecode_repo.tags,
131 c.rhodecode_repo.bookmarks)}
130 c.rhodecode_repo.bookmarks)}
132 % else:
131 % else:
133 ## missing requirements can make c.rhodecode_repo None
132 ## missing requirements can make c.rhodecode_repo None
134 ${refs_counters([], [], [], [])}
133 ${refs_counters([], [], [], [])}
135 % endif
134 % endif
136 </div>
135 </div>
137 </div>
136 </div>
138
137
139 </div>
138 </div>
140 </div>
139 </div>
141
140
142 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
141 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
143 <div class="left-label">
142 <div class="left-label">
144 ${_('Statistics')}:
143 ${_('Statistics')}:
145 </div>
144 </div>
146 <div class="right-content">
145 <div class="right-content">
147 <div class="input ${summary(c.show_stats)} statistics">
146 <div class="input ${summary(c.show_stats)} statistics">
148 % if c.show_stats:
147 % if c.show_stats:
149 <div id="lang_stats" class="enabled">
148 <div id="lang_stats" class="enabled">
150 ${_('Calculating Code Statistics...')}
149 ${_('Calculating Code Statistics...')}
151 </div>
150 </div>
152 % else:
151 % else:
153 <span class="disabled">
152 <span class="disabled">
154 ${_('Statistics are disabled for this repository')}
153 ${_('Statistics are disabled for this repository')}
155 </span>
154 </span>
156 % if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
155 % if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
157 , ${h.link_to(_('enable statistics'),h.route_path('edit_repo',repo_name=c.repo_name, _anchor='repo_enable_statistics'))}
156 , ${h.link_to(_('enable statistics'),h.route_path('edit_repo',repo_name=c.repo_name, _anchor='repo_enable_statistics'))}
158 % endif
157 % endif
159 % endif
158 % endif
160 </div>
159 </div>
161
160
162 </div>
161 </div>
163 </div>
162 </div>
164
163
165 % if show_downloads:
164 % if show_downloads:
166 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
165 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
167 <div class="left-label">
166 <div class="left-label">
168 ${_('Downloads')}:
167 ${_('Downloads')}:
169 </div>
168 </div>
170 <div class="right-content">
169 <div class="right-content">
171 <div class="input ${summary(c.show_stats)} downloads">
170 <div class="input ${summary(c.show_stats)} downloads">
172 % if c.rhodecode_repo and len(c.rhodecode_repo.revisions) == 0:
171 % if c.rhodecode_repo and len(c.rhodecode_repo.revisions) == 0:
173 <span class="disabled">
172 <span class="disabled">
174 ${_('There are no downloads yet')}
173 ${_('There are no downloads yet')}
175 </span>
174 </span>
176 % elif not c.enable_downloads:
175 % elif not c.enable_downloads:
177 <span class="disabled">
176 <span class="disabled">
178 ${_('Downloads are disabled for this repository')}
177 ${_('Downloads are disabled for this repository')}
179 </span>
178 </span>
180 % if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
179 % if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
181 , ${h.link_to(_('enable downloads'),h.route_path('edit_repo',repo_name=c.repo_name, _anchor='repo_enable_downloads'))}
180 , ${h.link_to(_('enable downloads'),h.route_path('edit_repo',repo_name=c.repo_name, _anchor='repo_enable_downloads'))}
182 % endif
181 % endif
183 % else:
182 % else:
184 <span class="enabled">
183 <span class="enabled">
185 <a id="archive_link" class="btn btn-small" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name,fname='tip.zip')}">
184 <a id="archive_link" class="btn btn-small" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name,fname='tip.zip')}">
186 <i class="icon-archive"></i> tip.zip
185 <i class="icon-archive"></i> tip.zip
187 ## replaced by some JS on select
186 ## replaced by some JS on select
188 </a>
187 </a>
189 </span>
188 </span>
190 ${h.hidden('download_options')}
189 ${h.hidden('download_options')}
191 % endif
190 % endif
192 </div>
191 </div>
193 </div>
192 </div>
194 </div>
193 </div>
195 % endif
194 % endif
196
195
197 </div><!--end summary-detail-->
196 </div><!--end summary-detail-->
198 </%def>
197 </%def>
199
198
200 <%def name="summary_stats(gravatar_function)">
199 <%def name="summary_stats(gravatar_function)">
201 <div class="sidebar-right">
200 <div class="sidebar-right">
202 <div class="summary-detail-header">
201 <div class="summary-detail-header">
203 <h4 class="item">
202 <h4 class="item">
204 ${_('Owner')}
203 ${_('Owner')}
205 </h4>
204 </h4>
206 </div>
205 </div>
207 <div class="sidebar-right-content">
206 <div class="sidebar-right-content">
208 ${gravatar_function(c.rhodecode_db_repo.user.email, 16)}
207 ${gravatar_function(c.rhodecode_db_repo.user.email, 16)}
209 </div>
208 </div>
210 </div><!--end sidebar-right-->
209 </div><!--end sidebar-right-->
211 </%def>
210 </%def>
@@ -1,540 +1,624 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 Package for testing various lib/helper functions in rhodecode
23 Package for testing various lib/helper functions in rhodecode
24 """
24 """
25
25
26 import datetime
26 import datetime
27 import string
27 import string
28 import mock
28 import mock
29 import pytest
29 import pytest
30
30
31 from rhodecode.tests import no_newline_id_generator
31 from rhodecode.tests import no_newline_id_generator
32 from rhodecode.tests.utils import run_test_concurrently
32 from rhodecode.tests.utils import run_test_concurrently
33 from rhodecode.lib.helpers import InitialsGravatar
33 from rhodecode.lib.helpers import InitialsGravatar
34
34
35 from rhodecode.lib.utils2 import AttributeDict
35 from rhodecode.lib.utils2 import AttributeDict
36 from rhodecode.model.db import Repository
36 from rhodecode.model.db import Repository
37
37
38
38
39 def _urls_for_proto(proto):
39 def _urls_for_proto(proto):
40 return [
40 return [
41 ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
41 ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
42 '%s://127.0.0.1' % proto),
42 '%s://127.0.0.1' % proto),
43 ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
43 ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
44 '%s://127.0.0.1' % proto),
44 '%s://127.0.0.1' % proto),
45 ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
45 ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
46 '%s://127.0.0.1' % proto),
46 '%s://127.0.0.1' % proto),
47 ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
47 ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
48 '%s://127.0.0.1:8080' % proto),
48 '%s://127.0.0.1:8080' % proto),
49 ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'],
49 ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'],
50 '%s://domain.org' % proto),
50 '%s://domain.org' % proto),
51 ('%s://user:pass@domain.org:8080' % proto,
51 ('%s://user:pass@domain.org:8080' % proto,
52 ['%s://' % proto, 'domain.org', '8080'],
52 ['%s://' % proto, 'domain.org', '8080'],
53 '%s://domain.org:8080' % proto),
53 '%s://domain.org:8080' % proto),
54 ]
54 ]
55
55
56 TEST_URLS = _urls_for_proto('http') + _urls_for_proto('https')
56 TEST_URLS = _urls_for_proto('http') + _urls_for_proto('https')
57
57
58
58
59 @pytest.mark.parametrize("test_url, expected, expected_creds", TEST_URLS)
59 @pytest.mark.parametrize("test_url, expected, expected_creds", TEST_URLS)
60 def test_uri_filter(test_url, expected, expected_creds):
60 def test_uri_filter(test_url, expected, expected_creds):
61 from rhodecode.lib.utils2 import uri_filter
61 from rhodecode.lib.utils2 import uri_filter
62 assert uri_filter(test_url) == expected
62 assert uri_filter(test_url) == expected
63
63
64
64
65 @pytest.mark.parametrize("test_url, expected, expected_creds", TEST_URLS)
65 @pytest.mark.parametrize("test_url, expected, expected_creds", TEST_URLS)
66 def test_credentials_filter(test_url, expected, expected_creds):
66 def test_credentials_filter(test_url, expected, expected_creds):
67 from rhodecode.lib.utils2 import credentials_filter
67 from rhodecode.lib.utils2 import credentials_filter
68 assert credentials_filter(test_url) == expected_creds
68 assert credentials_filter(test_url) == expected_creds
69
69
70
70
71 @pytest.mark.parametrize("str_bool, expected", [
71 @pytest.mark.parametrize("str_bool, expected", [
72 ('t', True),
72 ('t', True),
73 ('true', True),
73 ('true', True),
74 ('y', True),
74 ('y', True),
75 ('yes', True),
75 ('yes', True),
76 ('on', True),
76 ('on', True),
77 ('1', True),
77 ('1', True),
78 ('Y', True),
78 ('Y', True),
79 ('yeS', True),
79 ('yeS', True),
80 ('Y', True),
80 ('Y', True),
81 ('TRUE', True),
81 ('TRUE', True),
82 ('T', True),
82 ('T', True),
83 ('False', False),
83 ('False', False),
84 ('F', False),
84 ('F', False),
85 ('FALSE', False),
85 ('FALSE', False),
86 ('0', False),
86 ('0', False),
87 ('-1', False),
87 ('-1', False),
88 ('', False)
88 ('', False)
89 ])
89 ])
90 def test_str2bool(str_bool, expected):
90 def test_str2bool(str_bool, expected):
91 from rhodecode.lib.utils2 import str2bool
91 from rhodecode.lib.utils2 import str2bool
92 assert str2bool(str_bool) == expected
92 assert str2bool(str_bool) == expected
93
93
94
94
95 @pytest.mark.parametrize("text, expected", reduce(lambda a1,a2:a1+a2, [
95 @pytest.mark.parametrize("text, expected", reduce(lambda a1,a2:a1+a2, [
96 [
96 [
97 (pref+"", []),
97 (pref+"", []),
98 (pref+"Hi there @marcink", ['marcink']),
98 (pref+"Hi there @marcink", ['marcink']),
99 (pref+"Hi there @marcink and @bob", ['bob', 'marcink']),
99 (pref+"Hi there @marcink and @bob", ['bob', 'marcink']),
100 (pref+"Hi there @marcink\n", ['marcink']),
100 (pref+"Hi there @marcink\n", ['marcink']),
101 (pref+"Hi there @marcink and @bob\n", ['bob', 'marcink']),
101 (pref+"Hi there @marcink and @bob\n", ['bob', 'marcink']),
102 (pref+"Hi there marcin@rhodecode.com", []),
102 (pref+"Hi there marcin@rhodecode.com", []),
103 (pref+"Hi there @john.malcovic and @bob\n", ['bob', 'john.malcovic']),
103 (pref+"Hi there @john.malcovic and @bob\n", ['bob', 'john.malcovic']),
104 (pref+"This needs to be reviewed: (@marcink,@john)", ["john", "marcink"]),
104 (pref+"This needs to be reviewed: (@marcink,@john)", ["john", "marcink"]),
105 (pref+"This needs to be reviewed: (@marcink, @john)", ["john", "marcink"]),
105 (pref+"This needs to be reviewed: (@marcink, @john)", ["john", "marcink"]),
106 (pref+"This needs to be reviewed: [@marcink,@john]", ["john", "marcink"]),
106 (pref+"This needs to be reviewed: [@marcink,@john]", ["john", "marcink"]),
107 (pref+"This needs to be reviewed: (@marcink @john)", ["john", "marcink"]),
107 (pref+"This needs to be reviewed: (@marcink @john)", ["john", "marcink"]),
108 (pref+"@john @mary, please review", ["john", "mary"]),
108 (pref+"@john @mary, please review", ["john", "mary"]),
109 (pref+"@john,@mary, please review", ["john", "mary"]),
109 (pref+"@john,@mary, please review", ["john", "mary"]),
110 (pref+"Hej @123, @22john,@mary, please review", ['123', '22john', 'mary']),
110 (pref+"Hej @123, @22john,@mary, please review", ['123', '22john', 'mary']),
111 (pref+"@first hi there @marcink here's my email marcin@email.com "
111 (pref+"@first hi there @marcink here's my email marcin@email.com "
112 "@lukaszb check @one_more22 it pls @ ttwelve @D[] @one@two@three ", ['first', 'lukaszb', 'marcink', 'one', 'one_more22']),
112 "@lukaszb check @one_more22 it pls @ ttwelve @D[] @one@two@three ", ['first', 'lukaszb', 'marcink', 'one', 'one_more22']),
113 (pref+"@MARCIN @maRCiN @2one_more22 @john please see this http://org.pl", ['2one_more22', 'john', 'MARCIN', 'maRCiN']),
113 (pref+"@MARCIN @maRCiN @2one_more22 @john please see this http://org.pl", ['2one_more22', 'john', 'MARCIN', 'maRCiN']),
114 (pref+"@marian.user just do it @marco-polo and next extract @marco_polo", ['marco-polo', 'marco_polo', 'marian.user']),
114 (pref+"@marian.user just do it @marco-polo and next extract @marco_polo", ['marco-polo', 'marco_polo', 'marian.user']),
115 (pref+"user.dot hej ! not-needed maril@domain.org", []),
115 (pref+"user.dot hej ! not-needed maril@domain.org", []),
116 (pref+"\n@marcin", ['marcin']),
116 (pref+"\n@marcin", ['marcin']),
117 ]
117 ]
118 for pref in ['', '\n', 'hi !', '\t', '\n\n']]), ids=no_newline_id_generator)
118 for pref in ['', '\n', 'hi !', '\t', '\n\n']]), ids=no_newline_id_generator)
119 def test_mention_extractor(text, expected):
119 def test_mention_extractor(text, expected):
120 from rhodecode.lib.utils2 import extract_mentioned_users
120 from rhodecode.lib.utils2 import extract_mentioned_users
121 got = extract_mentioned_users(text)
121 got = extract_mentioned_users(text)
122 assert sorted(got, key=lambda x: x.lower()) == got
122 assert sorted(got, key=lambda x: x.lower()) == got
123 assert set(expected) == set(got)
123 assert set(expected) == set(got)
124
124
125 @pytest.mark.parametrize("age_args, expected, kw", [
125 @pytest.mark.parametrize("age_args, expected, kw", [
126 ({}, u'just now', {}),
126 ({}, u'just now', {}),
127 ({'seconds': -1}, u'1 second ago', {}),
127 ({'seconds': -1}, u'1 second ago', {}),
128 ({'seconds': -60 * 2}, u'2 minutes ago', {}),
128 ({'seconds': -60 * 2}, u'2 minutes ago', {}),
129 ({'hours': -1}, u'1 hour ago', {}),
129 ({'hours': -1}, u'1 hour ago', {}),
130 ({'hours': -24}, u'1 day ago', {}),
130 ({'hours': -24}, u'1 day ago', {}),
131 ({'hours': -24 * 5}, u'5 days ago', {}),
131 ({'hours': -24 * 5}, u'5 days ago', {}),
132 ({'months': -1}, u'1 month ago', {}),
132 ({'months': -1}, u'1 month ago', {}),
133 ({'months': -1, 'days': -2}, u'1 month and 2 days ago', {}),
133 ({'months': -1, 'days': -2}, u'1 month and 2 days ago', {}),
134 ({'years': -1, 'months': -1}, u'1 year and 1 month ago', {}),
134 ({'years': -1, 'months': -1}, u'1 year and 1 month ago', {}),
135 ({}, u'just now', {'short_format': True}),
135 ({}, u'just now', {'short_format': True}),
136 ({'seconds': -1}, u'1sec ago', {'short_format': True}),
136 ({'seconds': -1}, u'1sec ago', {'short_format': True}),
137 ({'seconds': -60 * 2}, u'2min ago', {'short_format': True}),
137 ({'seconds': -60 * 2}, u'2min ago', {'short_format': True}),
138 ({'hours': -1}, u'1h ago', {'short_format': True}),
138 ({'hours': -1}, u'1h ago', {'short_format': True}),
139 ({'hours': -24}, u'1d ago', {'short_format': True}),
139 ({'hours': -24}, u'1d ago', {'short_format': True}),
140 ({'hours': -24 * 5}, u'5d ago', {'short_format': True}),
140 ({'hours': -24 * 5}, u'5d ago', {'short_format': True}),
141 ({'months': -1}, u'1m ago', {'short_format': True}),
141 ({'months': -1}, u'1m ago', {'short_format': True}),
142 ({'months': -1, 'days': -2}, u'1m, 2d ago', {'short_format': True}),
142 ({'months': -1, 'days': -2}, u'1m, 2d ago', {'short_format': True}),
143 ({'years': -1, 'months': -1}, u'1y, 1m ago', {'short_format': True}),
143 ({'years': -1, 'months': -1}, u'1y, 1m ago', {'short_format': True}),
144 ])
144 ])
145 def test_age(age_args, expected, kw, pylonsapp):
145 def test_age(age_args, expected, kw, pylonsapp):
146 from rhodecode.lib.utils2 import age
146 from rhodecode.lib.utils2 import age
147 from dateutil import relativedelta
147 from dateutil import relativedelta
148 n = datetime.datetime(year=2012, month=5, day=17)
148 n = datetime.datetime(year=2012, month=5, day=17)
149 delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
149 delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
150
150
151 def translate(elem):
151 def translate(elem):
152 return elem.interpolate()
152 return elem.interpolate()
153
153
154 assert translate(age(n + delt(**age_args), now=n, **kw)) == expected
154 assert translate(age(n + delt(**age_args), now=n, **kw)) == expected
155
155
156
156
157 @pytest.mark.parametrize("age_args, expected, kw", [
157 @pytest.mark.parametrize("age_args, expected, kw", [
158 ({}, u'just now', {}),
158 ({}, u'just now', {}),
159 ({'seconds': 1}, u'in 1 second', {}),
159 ({'seconds': 1}, u'in 1 second', {}),
160 ({'seconds': 60 * 2}, u'in 2 minutes', {}),
160 ({'seconds': 60 * 2}, u'in 2 minutes', {}),
161 ({'hours': 1}, u'in 1 hour', {}),
161 ({'hours': 1}, u'in 1 hour', {}),
162 ({'hours': 24}, u'in 1 day', {}),
162 ({'hours': 24}, u'in 1 day', {}),
163 ({'hours': 24 * 5}, u'in 5 days', {}),
163 ({'hours': 24 * 5}, u'in 5 days', {}),
164 ({'months': 1}, u'in 1 month', {}),
164 ({'months': 1}, u'in 1 month', {}),
165 ({'months': 1, 'days': 1}, u'in 1 month and 1 day', {}),
165 ({'months': 1, 'days': 1}, u'in 1 month and 1 day', {}),
166 ({'years': 1, 'months': 1}, u'in 1 year and 1 month', {}),
166 ({'years': 1, 'months': 1}, u'in 1 year and 1 month', {}),
167 ({}, u'just now', {'short_format': True}),
167 ({}, u'just now', {'short_format': True}),
168 ({'seconds': 1}, u'in 1sec', {'short_format': True}),
168 ({'seconds': 1}, u'in 1sec', {'short_format': True}),
169 ({'seconds': 60 * 2}, u'in 2min', {'short_format': True}),
169 ({'seconds': 60 * 2}, u'in 2min', {'short_format': True}),
170 ({'hours': 1}, u'in 1h', {'short_format': True}),
170 ({'hours': 1}, u'in 1h', {'short_format': True}),
171 ({'hours': 24}, u'in 1d', {'short_format': True}),
171 ({'hours': 24}, u'in 1d', {'short_format': True}),
172 ({'hours': 24 * 5}, u'in 5d', {'short_format': True}),
172 ({'hours': 24 * 5}, u'in 5d', {'short_format': True}),
173 ({'months': 1}, u'in 1m', {'short_format': True}),
173 ({'months': 1}, u'in 1m', {'short_format': True}),
174 ({'months': 1, 'days': 1}, u'in 1m, 1d', {'short_format': True}),
174 ({'months': 1, 'days': 1}, u'in 1m, 1d', {'short_format': True}),
175 ({'years': 1, 'months': 1}, u'in 1y, 1m', {'short_format': True}),
175 ({'years': 1, 'months': 1}, u'in 1y, 1m', {'short_format': True}),
176 ])
176 ])
177 def test_age_in_future(age_args, expected, kw, pylonsapp):
177 def test_age_in_future(age_args, expected, kw, pylonsapp):
178 from rhodecode.lib.utils2 import age
178 from rhodecode.lib.utils2 import age
179 from dateutil import relativedelta
179 from dateutil import relativedelta
180 n = datetime.datetime(year=2012, month=5, day=17)
180 n = datetime.datetime(year=2012, month=5, day=17)
181 delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
181 delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
182
182
183 def translate(elem):
183 def translate(elem):
184 return elem.interpolate()
184 return elem.interpolate()
185
185
186 assert translate(age(n + delt(**age_args), now=n, **kw)) == expected
186 assert translate(age(n + delt(**age_args), now=n, **kw)) == expected
187
187
188
188
189 def test_tag_exctrator():
189 @pytest.mark.parametrize("sample, expected_tags", [
190 sample = (
190 ((
191 "hello pta[tag] gog [[]] [[] sda ero[or]d [me =>>< sa]"
191 "hello world [stale]"
192 "[requires] [stale] [see<>=>] [see => http://url.com]"
192 ),
193 "[requires => url] [lang => python] [just a tag] <html_tag first='abc' attr=\"my.url?attr=&another=\"></html_tag>"
193 [
194 "[,d] [ => ULR ] [obsolete] [desc]]"
194 ('state', '[stale]'),
195 )
195 ]),
196 from rhodecode.lib.helpers import desc_stylize, escaped_stylize
196 # entry
197 res = desc_stylize(sample)
197 ((
198 assert '<div class="metatag" tag="tag">tag</div>' in res
198 "hello world [v2.0.0] [v1.0.0]"
199 assert '<div class="metatag" tag="obsolete">obsolete</div>' in res
199 ),
200 assert '<div class="metatag" tag="stale">stale</div>' in res
200 [
201 assert '<div class="metatag" tag="lang">python</div>' in res
201 ('generic', '[v2.0.0]'),
202 assert '<div class="metatag" tag="requires">requires =&gt; <a href="/url">url</a></div>' in res
202 ('generic', '[v1.0.0]'),
203 assert '<div class="metatag" tag="tag">tag</div>' in res
203 ]),
204 assert '<html_tag first=\'abc\' attr=\"my.url?attr=&another=\"></html_tag>' in res
204 # entry
205 ((
206 "he[ll]o wo[rl]d"
207 ),
208 [
209 ('label', '[ll]'),
210 ('label', '[rl]'),
211 ]),
212 # entry
213 ((
214 "hello world [stale]\n[featured]\n[stale] [dead] [dev]"
215 ),
216 [
217 ('state', '[stale]'),
218 ('state', '[featured]'),
219 ('state', '[stale]'),
220 ('state', '[dead]'),
221 ('state', '[dev]'),
222 ]),
223 # entry
224 ((
225 "hello world \n\n [stale] \n [url =&gt; [name](http://rc.com)]"
226 ),
227 [
228 ('state', '[stale]'),
229 ('url', '[url =&gt; [name](http://rc.com)]'),
230 ]),
231 # entry
232 ((
233 "hello pta[tag] gog [[]] [[] sda ero[or]d [me =&gt;>< sa]"
234 "[requires] [stale] [see<>=&gt;] [see =&gt; http://url.com]"
235 "[requires =&gt; url] [lang =&gt; python] [just a tag] "
236 "<html_tag first='abc' attr=\"my.url?attr=&another=\"></html_tag>"
237 "[,d] [ =&gt; ULR ] [obsolete] [desc]]"
238 ),
239 [
240 ('label', '[desc]'),
241 ('label', '[obsolete]'),
242 ('label', '[or]'),
243 ('label', '[requires]'),
244 ('label', '[tag]'),
245 ('state', '[stale]'),
246 ('lang', '[lang =&gt; python]'),
247 ('ref', '[requires =&gt; url]'),
248 ('see', '[see =&gt; http://url.com]'),
205
249
206 res_encoded = escaped_stylize(sample)
250 ]),
207 assert '<div class="metatag" tag="tag">tag</div>' in res_encoded
251
208 assert '<div class="metatag" tag="obsolete">obsolete</div>' in res_encoded
252 ], ids=no_newline_id_generator)
209 assert '<div class="metatag" tag="stale">stale</div>' in res_encoded
253 def test_metatag_extraction(sample, expected_tags):
210 assert '<div class="metatag" tag="lang">python</div>' in res_encoded
254 from rhodecode.lib.helpers import extract_metatags
211 assert '<div class="metatag" tag="requires">requires =&gt; <a href="/url">url</a></div>' in res_encoded
255 tags, value = extract_metatags(sample)
212 assert '<div class="metatag" tag="tag">tag</div>' in res_encoded
256 assert sorted(tags) == sorted(expected_tags)
213 assert '&lt;html_tag first=&#39;abc&#39; attr=&#34;my.url?attr=&amp;another=&#34;&gt;&lt;/html_tag&gt;' in res_encoded
257
258
259 @pytest.mark.parametrize("tag_data, expected_html", [
260
261 (('state', '[stable]'), '<div class="metatag" tag="state stable">stable</div>'),
262 (('state', '[stale]'), '<div class="metatag" tag="state stale">stale</div>'),
263 (('state', '[featured]'), '<div class="metatag" tag="state featured">featured</div>'),
264 (('state', '[dev]'), '<div class="metatag" tag="state dev">dev</div>'),
265 (('state', '[dead]'), '<div class="metatag" tag="state dead">dead</div>'),
266
267 (('label', '[personal]'), '<div class="metatag" tag="label">personal</div>'),
268 (('generic', '[v2.0.0]'), '<div class="metatag" tag="generic">v2.0.0</div>'),
269
270 (('lang', '[lang =&gt; JavaScript]'), '<div class="metatag" tag="lang">JavaScript</div>'),
271 (('lang', '[lang =&gt; C++]'), '<div class="metatag" tag="lang">C++</div>'),
272 (('lang', '[lang =&gt; C#]'), '<div class="metatag" tag="lang">C#</div>'),
273 (('lang', '[lang =&gt; Delphi/Object]'), '<div class="metatag" tag="lang">Delphi/Object</div>'),
274 (('lang', '[lang =&gt; Objective-C]'), '<div class="metatag" tag="lang">Objective-C</div>'),
275 (('lang', '[lang =&gt; .NET]'), '<div class="metatag" tag="lang">.NET</div>'),
276
277 (('license', '[license =&gt; BSD 3-clause]'), '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/BSD 3-clause">BSD 3-clause</a></div>'),
278 (('license', '[license =&gt; GPLv3]'), '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/GPLv3">GPLv3</a></div>'),
279 (('license', '[license =&gt; MIT]'), '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/MIT">MIT</a></div>'),
280 (('license', '[license =&gt; AGPLv3]'), '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/AGPLv3">AGPLv3</a></div>'),
281
282 (('ref', '[requires =&gt; RepoName]'), '<div class="metatag" tag="ref requires">requires =&gt; <a href="/RepoName">RepoName</a></div>'),
283 (('ref', '[recommends =&gt; GroupName]'), '<div class="metatag" tag="ref recommends">recommends =&gt; <a href="/GroupName">GroupName</a></div>'),
284 (('ref', '[conflicts =&gt; SomeName]'), '<div class="metatag" tag="ref conflicts">conflicts =&gt; <a href="/SomeName">SomeName</a></div>'),
285 (('ref', '[base =&gt; SomeName]'), '<div class="metatag" tag="ref base">base =&gt; <a href="/SomeName">SomeName</a></div>'),
286
287 (('see', '[see =&gt; http://rhodecode.com]'), '<div class="metatag" tag="see">see =&gt; http://rhodecode.com </div>'),
288
289 (('url', '[url =&gt; [linkName](https://rhodecode.com)]'), '<div class="metatag" tag="url"> <a href="https://rhodecode.com">linkName</a> </div>'),
290 (('url', '[url =&gt; [example link](https://rhodecode.com)]'), '<div class="metatag" tag="url"> <a href="https://rhodecode.com">example link</a> </div>'),
291 (('url', '[url =&gt; [v1.0.0](https://rhodecode.com)]'), '<div class="metatag" tag="url"> <a href="https://rhodecode.com">v1.0.0</a> </div>'),
292
293 ])
294 def test_metatags_stylize(tag_data, expected_html):
295 from rhodecode.lib.helpers import style_metatag
296 tag_type,value = tag_data
297 assert style_metatag(tag_type, value) == expected_html
214
298
215
299
216 @pytest.mark.parametrize("tmpl_url, email, expected", [
300 @pytest.mark.parametrize("tmpl_url, email, expected", [
217 ('http://test.com/{email}', 'test@foo.com', 'http://test.com/test@foo.com'),
301 ('http://test.com/{email}', 'test@foo.com', 'http://test.com/test@foo.com'),
218
302
219 ('http://test.com/{md5email}', 'test@foo.com', 'http://test.com/3cb7232fcc48743000cb86d0d5022bd9'),
303 ('http://test.com/{md5email}', 'test@foo.com', 'http://test.com/3cb7232fcc48743000cb86d0d5022bd9'),
220 ('http://test.com/{md5email}', 'testΔ…Δ‡@foo.com', 'http://test.com/978debb907a3c55cd741872ab293ef30'),
304 ('http://test.com/{md5email}', 'testΔ…Δ‡@foo.com', 'http://test.com/978debb907a3c55cd741872ab293ef30'),
221
305
222 ('http://testX.com/{md5email}?s={size}', 'test@foo.com', 'http://testX.com/3cb7232fcc48743000cb86d0d5022bd9?s=24'),
306 ('http://testX.com/{md5email}?s={size}', 'test@foo.com', 'http://testX.com/3cb7232fcc48743000cb86d0d5022bd9?s=24'),
223 ('http://testX.com/{md5email}?s={size}', 'testΔ…Δ‡@foo.com', 'http://testX.com/978debb907a3c55cd741872ab293ef30?s=24'),
307 ('http://testX.com/{md5email}?s={size}', 'testΔ…Δ‡@foo.com', 'http://testX.com/978debb907a3c55cd741872ab293ef30?s=24'),
224
308
225 ('{scheme}://{netloc}/{md5email}/{size}', 'test@foo.com', 'https://server.com/3cb7232fcc48743000cb86d0d5022bd9/24'),
309 ('{scheme}://{netloc}/{md5email}/{size}', 'test@foo.com', 'https://server.com/3cb7232fcc48743000cb86d0d5022bd9/24'),
226 ('{scheme}://{netloc}/{md5email}/{size}', 'testΔ…Δ‡@foo.com', 'https://server.com/978debb907a3c55cd741872ab293ef30/24'),
310 ('{scheme}://{netloc}/{md5email}/{size}', 'testΔ…Δ‡@foo.com', 'https://server.com/978debb907a3c55cd741872ab293ef30/24'),
227
311
228 ('http://test.com/{email}', 'testΔ…Δ‡@foo.com', 'http://test.com/testΔ…Δ‡@foo.com'),
312 ('http://test.com/{email}', 'testΔ…Δ‡@foo.com', 'http://test.com/testΔ…Δ‡@foo.com'),
229 ('http://test.com/{email}?size={size}', 'test@foo.com', 'http://test.com/test@foo.com?size=24'),
313 ('http://test.com/{email}?size={size}', 'test@foo.com', 'http://test.com/test@foo.com?size=24'),
230 ('http://test.com/{email}?size={size}', 'testΔ…Δ‡@foo.com', 'http://test.com/testΔ…Δ‡@foo.com?size=24'),
314 ('http://test.com/{email}?size={size}', 'testΔ…Δ‡@foo.com', 'http://test.com/testΔ…Δ‡@foo.com?size=24'),
231 ])
315 ])
232 def test_gravatar_url_builder(tmpl_url, email, expected, request_stub):
316 def test_gravatar_url_builder(tmpl_url, email, expected, request_stub):
233 from rhodecode.lib.helpers import gravatar_url
317 from rhodecode.lib.helpers import gravatar_url
234
318
235 # mock pyramid.threadlocals
319 # mock pyramid.threadlocals
236 def fake_get_current_request():
320 def fake_get_current_request():
237 request_stub.scheme = 'https'
321 request_stub.scheme = 'https'
238 request_stub.host = 'server.com'
322 request_stub.host = 'server.com'
239 return request_stub
323 return request_stub
240
324
241 # mock pylons.tmpl_context
325 # mock pylons.tmpl_context
242 def fake_tmpl_context(_url):
326 def fake_tmpl_context(_url):
243 _c = AttributeDict()
327 _c = AttributeDict()
244 _c.visual = AttributeDict()
328 _c.visual = AttributeDict()
245 _c.visual.use_gravatar = True
329 _c.visual.use_gravatar = True
246 _c.visual.gravatar_url = _url
330 _c.visual.gravatar_url = _url
247
331
248 return _c
332 return _c
249
333
250 with mock.patch('rhodecode.lib.helpers.get_current_request',
334 with mock.patch('rhodecode.lib.helpers.get_current_request',
251 fake_get_current_request):
335 fake_get_current_request):
252 fake = fake_tmpl_context(_url=tmpl_url)
336 fake = fake_tmpl_context(_url=tmpl_url)
253 with mock.patch('pylons.tmpl_context', fake):
337 with mock.patch('pylons.tmpl_context', fake):
254 grav = gravatar_url(email_address=email, size=24)
338 grav = gravatar_url(email_address=email, size=24)
255 assert grav == expected
339 assert grav == expected
256
340
257
341
258 @pytest.mark.parametrize(
342 @pytest.mark.parametrize(
259 "email, first_name, last_name, expected_initials, expected_color", [
343 "email, first_name, last_name, expected_initials, expected_color", [
260
344
261 ('test@rhodecode.com', '', '', 'TR', '#8a994d'),
345 ('test@rhodecode.com', '', '', 'TR', '#8a994d'),
262 ('marcin.kuzminski@rhodecode.com', '', '', 'MK', '#6559b3'),
346 ('marcin.kuzminski@rhodecode.com', '', '', 'MK', '#6559b3'),
263 # special cases of email
347 # special cases of email
264 ('john.van.dam@rhodecode.com', '', '', 'JD', '#526600'),
348 ('john.van.dam@rhodecode.com', '', '', 'JD', '#526600'),
265 ('Guido.van.Rossum@rhodecode.com', '', '', 'GR', '#990052'),
349 ('Guido.van.Rossum@rhodecode.com', '', '', 'GR', '#990052'),
266 ('Guido.van.Rossum@rhodecode.com', 'Guido', 'Van Rossum', 'GR', '#990052'),
350 ('Guido.van.Rossum@rhodecode.com', 'Guido', 'Van Rossum', 'GR', '#990052'),
267
351
268 ('rhodecode+Guido.van.Rossum@rhodecode.com', '', '', 'RR', '#46598c'),
352 ('rhodecode+Guido.van.Rossum@rhodecode.com', '', '', 'RR', '#46598c'),
269 ('pclouds@rhodecode.com', 'Nguyα»…n ThΓ‘i', 'Tgọc Duy', 'ND', '#665200'),
353 ('pclouds@rhodecode.com', 'Nguyα»…n ThΓ‘i', 'Tgọc Duy', 'ND', '#665200'),
270
354
271 ('john-brown@foo.com', '', '', 'JF', '#73006b'),
355 ('john-brown@foo.com', '', '', 'JF', '#73006b'),
272 ('admin@rhodecode.com', 'Marcin', 'Kuzminski', 'MK', '#104036'),
356 ('admin@rhodecode.com', 'Marcin', 'Kuzminski', 'MK', '#104036'),
273 # partials
357 # partials
274 ('admin@rhodecode.com', 'Marcin', '', 'MR', '#104036'), # fn+email
358 ('admin@rhodecode.com', 'Marcin', '', 'MR', '#104036'), # fn+email
275 ('admin@rhodecode.com', '', 'Kuzminski', 'AK', '#104036'), # em+ln
359 ('admin@rhodecode.com', '', 'Kuzminski', 'AK', '#104036'), # em+ln
276 # non-ascii
360 # non-ascii
277 ('admin@rhodecode.com', 'Marcin', 'Śuzminski', 'MS', '#104036'),
361 ('admin@rhodecode.com', 'Marcin', 'Śuzminski', 'MS', '#104036'),
278 ('marcin.Ε›uzminski@rhodecode.com', '', '', 'MS', '#73000f'),
362 ('marcin.Ε›uzminski@rhodecode.com', '', '', 'MS', '#73000f'),
279
363
280 # special cases, LDAP can provide those...
364 # special cases, LDAP can provide those...
281 ('admin@', 'Marcin', 'Śuzminski', 'MS', '#aa00ff'),
365 ('admin@', 'Marcin', 'Śuzminski', 'MS', '#aa00ff'),
282 ('marcin.Ε›uzminski', '', '', 'MS', '#402020'),
366 ('marcin.Ε›uzminski', '', '', 'MS', '#402020'),
283 ('null', '', '', 'NL', '#8c4646'),
367 ('null', '', '', 'NL', '#8c4646'),
284 ('some.@abc.com', 'some', '', 'SA', '#664e33')
368 ('some.@abc.com', 'some', '', 'SA', '#664e33')
285 ])
369 ])
286 def test_initials_gravatar_pick_of_initials_and_color_algo(
370 def test_initials_gravatar_pick_of_initials_and_color_algo(
287 email, first_name, last_name, expected_initials, expected_color):
371 email, first_name, last_name, expected_initials, expected_color):
288 instance = InitialsGravatar(email, first_name, last_name)
372 instance = InitialsGravatar(email, first_name, last_name)
289 assert instance.get_initials() == expected_initials
373 assert instance.get_initials() == expected_initials
290 assert instance.str2color(email) == expected_color
374 assert instance.str2color(email) == expected_color
291
375
292
376
293 def test_initials_gravatar_mapping_algo():
377 def test_initials_gravatar_mapping_algo():
294 pos = set()
378 pos = set()
295 instance = InitialsGravatar('', '', '')
379 instance = InitialsGravatar('', '', '')
296 iterations = 0
380 iterations = 0
297
381
298 variations = []
382 variations = []
299 for letter1 in string.ascii_letters:
383 for letter1 in string.ascii_letters:
300 for letter2 in string.ascii_letters[::-1][:10]:
384 for letter2 in string.ascii_letters[::-1][:10]:
301 for letter3 in string.ascii_letters[:10]:
385 for letter3 in string.ascii_letters[:10]:
302 variations.append(
386 variations.append(
303 '%s@rhodecode.com' % (letter1+letter2+letter3))
387 '%s@rhodecode.com' % (letter1+letter2+letter3))
304
388
305 max_variations = 4096
389 max_variations = 4096
306 for email in variations[:max_variations]:
390 for email in variations[:max_variations]:
307 iterations += 1
391 iterations += 1
308 pos.add(
392 pos.add(
309 instance.pick_color_bank_index(email,
393 instance.pick_color_bank_index(email,
310 instance.get_color_bank()))
394 instance.get_color_bank()))
311
395
312 # we assume that we have match all 256 possible positions,
396 # we assume that we have match all 256 possible positions,
313 # in reasonable amount of different email addresses
397 # in reasonable amount of different email addresses
314 assert len(pos) == 256
398 assert len(pos) == 256
315 assert iterations == max_variations
399 assert iterations == max_variations
316
400
317
401
318 @pytest.mark.parametrize("tmpl, repo_name, overrides, prefix, expected", [
402 @pytest.mark.parametrize("tmpl, repo_name, overrides, prefix, expected", [
319 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {}, '', 'http://vps1:8000/group/repo1'),
403 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {}, '', 'http://vps1:8000/group/repo1'),
320 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/group/repo1'),
404 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/group/repo1'),
321 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {}, '/rc', 'http://vps1:8000/rc/group/repo1'),
405 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {}, '/rc', 'http://vps1:8000/rc/group/repo1'),
322 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/rc', 'http://user@vps1:8000/rc/group/repo1'),
406 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/rc', 'http://user@vps1:8000/rc/group/repo1'),
323 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '/rc', 'http://marcink@vps1:8000/rc/group/repo1'),
407 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '/rc', 'http://marcink@vps1:8000/rc/group/repo1'),
324 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/rc/', 'http://user@vps1:8000/rc/group/repo1'),
408 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/rc/', 'http://user@vps1:8000/rc/group/repo1'),
325 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '/rc/', 'http://marcink@vps1:8000/rc/group/repo1'),
409 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '/rc/', 'http://marcink@vps1:8000/rc/group/repo1'),
326 ('{scheme}://{user}@{netloc}/_{repoid}', 'group/repo1', {}, '', 'http://vps1:8000/_23'),
410 ('{scheme}://{user}@{netloc}/_{repoid}', 'group/repo1', {}, '', 'http://vps1:8000/_23'),
327 ('{scheme}://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/_23'),
411 ('{scheme}://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/_23'),
328 ('http://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/_23'),
412 ('http://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/_23'),
329 ('http://{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://vps1:8000/_23'),
413 ('http://{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://vps1:8000/_23'),
330 ('https://{user}@proxy1.server.com/{repo}', 'group/repo1', {'user': 'marcink'}, '', 'https://marcink@proxy1.server.com/group/repo1'),
414 ('https://{user}@proxy1.server.com/{repo}', 'group/repo1', {'user': 'marcink'}, '', 'https://marcink@proxy1.server.com/group/repo1'),
331 ('https://{user}@proxy1.server.com/{repo}', 'group/repo1', {}, '', 'https://proxy1.server.com/group/repo1'),
415 ('https://{user}@proxy1.server.com/{repo}', 'group/repo1', {}, '', 'https://proxy1.server.com/group/repo1'),
332 ('https://proxy1.server.com/{user}/{repo}', 'group/repo1', {'user': 'marcink'}, '', 'https://proxy1.server.com/marcink/group/repo1'),
416 ('https://proxy1.server.com/{user}/{repo}', 'group/repo1', {'user': 'marcink'}, '', 'https://proxy1.server.com/marcink/group/repo1'),
333 ])
417 ])
334 def test_clone_url_generator(tmpl, repo_name, overrides, prefix, expected):
418 def test_clone_url_generator(tmpl, repo_name, overrides, prefix, expected):
335 from rhodecode.lib.utils2 import get_clone_url
419 from rhodecode.lib.utils2 import get_clone_url
336
420
337 class RequestStub(object):
421 class RequestStub(object):
338 def request_url(self, name):
422 def request_url(self, name):
339 return 'http://vps1:8000' + prefix
423 return 'http://vps1:8000' + prefix
340
424
341 def route_url(self, name):
425 def route_url(self, name):
342 return self.request_url(name)
426 return self.request_url(name)
343
427
344 clone_url = get_clone_url(
428 clone_url = get_clone_url(
345 request=RequestStub(),
429 request=RequestStub(),
346 uri_tmpl=tmpl,
430 uri_tmpl=tmpl,
347 repo_name=repo_name, repo_id=23, **overrides)
431 repo_name=repo_name, repo_id=23, **overrides)
348 assert clone_url == expected
432 assert clone_url == expected
349
433
350
434
351 def _quick_url(text, tmpl="""<a class="revision-link" href="%s">%s</a>""", url_=None):
435 def _quick_url(text, tmpl="""<a class="revision-link" href="%s">%s</a>""", url_=None):
352 """
436 """
353 Changes `some text url[foo]` => `some text <a href="/">foo</a>
437 Changes `some text url[foo]` => `some text <a href="/">foo</a>
354
438
355 :param text:
439 :param text:
356 """
440 """
357 import re
441 import re
358 # quickly change expected url[] into a link
442 # quickly change expected url[] into a link
359 URL_PAT = re.compile(r'(?:url\[)(.+?)(?:\])')
443 URL_PAT = re.compile(r'(?:url\[)(.+?)(?:\])')
360
444
361 def url_func(match_obj):
445 def url_func(match_obj):
362 _url = match_obj.groups()[0]
446 _url = match_obj.groups()[0]
363 return tmpl % (url_ or '/some-url', _url)
447 return tmpl % (url_ or '/some-url', _url)
364 return URL_PAT.sub(url_func, text)
448 return URL_PAT.sub(url_func, text)
365
449
366
450
367 @pytest.mark.parametrize("sample, expected", [
451 @pytest.mark.parametrize("sample, expected", [
368 ("",
452 ("",
369 ""),
453 ""),
370 ("git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1441655 13f79535-47bb-0310-9956-ffa450edef68",
454 ("git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1441655 13f79535-47bb-0310-9956-ffa450edef68",
371 "git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1441655 13f79535-47bb-0310-9956-ffa450edef68"),
455 "git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1441655 13f79535-47bb-0310-9956-ffa450edef68"),
372 ("from rev 000000000000",
456 ("from rev 000000000000",
373 "from rev url[000000000000]"),
457 "from rev url[000000000000]"),
374 ("from rev 000000000000123123 also rev 000000000000",
458 ("from rev 000000000000123123 also rev 000000000000",
375 "from rev url[000000000000123123] also rev url[000000000000]"),
459 "from rev url[000000000000123123] also rev url[000000000000]"),
376 ("this should-000 00",
460 ("this should-000 00",
377 "this should-000 00"),
461 "this should-000 00"),
378 ("longtextffffffffff rev 123123123123",
462 ("longtextffffffffff rev 123123123123",
379 "longtextffffffffff rev url[123123123123]"),
463 "longtextffffffffff rev url[123123123123]"),
380 ("rev ffffffffffffffffffffffffffffffffffffffffffffffffff",
464 ("rev ffffffffffffffffffffffffffffffffffffffffffffffffff",
381 "rev ffffffffffffffffffffffffffffffffffffffffffffffffff"),
465 "rev ffffffffffffffffffffffffffffffffffffffffffffffffff"),
382 ("ffffffffffff some text traalaa",
466 ("ffffffffffff some text traalaa",
383 "url[ffffffffffff] some text traalaa"),
467 "url[ffffffffffff] some text traalaa"),
384 ("""Multi line
468 ("""Multi line
385 123123123123
469 123123123123
386 some text 123123123123
470 some text 123123123123
387 sometimes !
471 sometimes !
388 """,
472 """,
389 """Multi line
473 """Multi line
390 url[123123123123]
474 url[123123123123]
391 some text url[123123123123]
475 some text url[123123123123]
392 sometimes !
476 sometimes !
393 """)
477 """)
394 ], ids=no_newline_id_generator)
478 ], ids=no_newline_id_generator)
395 def test_urlify_commits(sample, expected):
479 def test_urlify_commits(sample, expected):
396 def fake_url(self, *args, **kwargs):
480 def fake_url(self, *args, **kwargs):
397 return '/some-url'
481 return '/some-url'
398
482
399 expected = _quick_url(expected)
483 expected = _quick_url(expected)
400
484
401 with mock.patch('rhodecode.lib.helpers.route_url', fake_url):
485 with mock.patch('rhodecode.lib.helpers.route_url', fake_url):
402 from rhodecode.lib.helpers import urlify_commits
486 from rhodecode.lib.helpers import urlify_commits
403 assert urlify_commits(sample, 'repo_name') == expected
487 assert urlify_commits(sample, 'repo_name') == expected
404
488
405
489
406 @pytest.mark.parametrize("sample, expected, url_", [
490 @pytest.mark.parametrize("sample, expected, url_", [
407 ("",
491 ("",
408 "",
492 "",
409 ""),
493 ""),
410 ("https://svn.apache.org/repos",
494 ("https://svn.apache.org/repos",
411 "url[https://svn.apache.org/repos]",
495 "url[https://svn.apache.org/repos]",
412 "https://svn.apache.org/repos"),
496 "https://svn.apache.org/repos"),
413 ("http://svn.apache.org/repos",
497 ("http://svn.apache.org/repos",
414 "url[http://svn.apache.org/repos]",
498 "url[http://svn.apache.org/repos]",
415 "http://svn.apache.org/repos"),
499 "http://svn.apache.org/repos"),
416 ("from rev a also rev http://google.com",
500 ("from rev a also rev http://google.com",
417 "from rev a also rev url[http://google.com]",
501 "from rev a also rev url[http://google.com]",
418 "http://google.com"),
502 "http://google.com"),
419 ("""Multi line
503 ("""Multi line
420 https://foo.bar.com
504 https://foo.bar.com
421 some text lalala""",
505 some text lalala""",
422 """Multi line
506 """Multi line
423 url[https://foo.bar.com]
507 url[https://foo.bar.com]
424 some text lalala""",
508 some text lalala""",
425 "https://foo.bar.com")
509 "https://foo.bar.com")
426 ], ids=no_newline_id_generator)
510 ], ids=no_newline_id_generator)
427 def test_urlify_test(sample, expected, url_):
511 def test_urlify_test(sample, expected, url_):
428 from rhodecode.lib.helpers import urlify_text
512 from rhodecode.lib.helpers import urlify_text
429 expected = _quick_url(expected, tmpl="""<a href="%s">%s</a>""", url_=url_)
513 expected = _quick_url(expected, tmpl="""<a href="%s">%s</a>""", url_=url_)
430 assert urlify_text(sample) == expected
514 assert urlify_text(sample) == expected
431
515
432
516
433 @pytest.mark.parametrize("test, expected", [
517 @pytest.mark.parametrize("test, expected", [
434 ("", None),
518 ("", None),
435 ("/_2", '2'),
519 ("/_2", '2'),
436 ("_2", '2'),
520 ("_2", '2'),
437 ("/_2/", '2'),
521 ("/_2/", '2'),
438 ("_2/", '2'),
522 ("_2/", '2'),
439
523
440 ("/_21", '21'),
524 ("/_21", '21'),
441 ("_21", '21'),
525 ("_21", '21'),
442 ("/_21/", '21'),
526 ("/_21/", '21'),
443 ("_21/", '21'),
527 ("_21/", '21'),
444
528
445 ("/_21/foobar", '21'),
529 ("/_21/foobar", '21'),
446 ("_21/121", '21'),
530 ("_21/121", '21'),
447 ("/_21/_12", '21'),
531 ("/_21/_12", '21'),
448 ("_21/rc/foo", '21'),
532 ("_21/rc/foo", '21'),
449
533
450 ])
534 ])
451 def test_get_repo_by_id(test, expected):
535 def test_get_repo_by_id(test, expected):
452 from rhodecode.model.repo import RepoModel
536 from rhodecode.model.repo import RepoModel
453 _test = RepoModel()._extract_id_from_repo_name(test)
537 _test = RepoModel()._extract_id_from_repo_name(test)
454 assert _test == expected
538 assert _test == expected
455
539
456
540
457 @pytest.mark.parametrize("test_repo_name, repo_type", [
541 @pytest.mark.parametrize("test_repo_name, repo_type", [
458 ("test_repo_1", None),
542 ("test_repo_1", None),
459 ("repo_group/foobar", None),
543 ("repo_group/foobar", None),
460 ("test_non_asci_Δ…Δ‡Δ™", None),
544 ("test_non_asci_Δ…Δ‡Δ™", None),
461 (u"test_non_asci_unicode_Δ…Δ‡Δ™", None),
545 (u"test_non_asci_unicode_Δ…Δ‡Δ™", None),
462 ])
546 ])
463 def test_invalidation_context(pylonsapp, test_repo_name, repo_type):
547 def test_invalidation_context(pylonsapp, test_repo_name, repo_type):
464 from beaker.cache import cache_region
548 from beaker.cache import cache_region
465 from rhodecode.lib import caches
549 from rhodecode.lib import caches
466 from rhodecode.model.db import CacheKey
550 from rhodecode.model.db import CacheKey
467
551
468 @cache_region('long_term')
552 @cache_region('long_term')
469 def _dummy_func(cache_key):
553 def _dummy_func(cache_key):
470 return 'result'
554 return 'result'
471
555
472 invalidator_context = CacheKey.repo_context_cache(
556 invalidator_context = CacheKey.repo_context_cache(
473 _dummy_func, test_repo_name, 'repo')
557 _dummy_func, test_repo_name, 'repo')
474
558
475 with invalidator_context as context:
559 with invalidator_context as context:
476 invalidated = context.invalidate()
560 invalidated = context.invalidate()
477 result = context.compute()
561 result = context.compute()
478
562
479 assert invalidated == True
563 assert invalidated == True
480 assert 'result' == result
564 assert 'result' == result
481 assert isinstance(context, caches.FreshRegionCache)
565 assert isinstance(context, caches.FreshRegionCache)
482
566
483 assert 'InvalidationContext' in repr(invalidator_context)
567 assert 'InvalidationContext' in repr(invalidator_context)
484
568
485 with invalidator_context as context:
569 with invalidator_context as context:
486 context.invalidate()
570 context.invalidate()
487 result = context.compute()
571 result = context.compute()
488
572
489 assert 'result' == result
573 assert 'result' == result
490 assert isinstance(context, caches.ActiveRegionCache)
574 assert isinstance(context, caches.ActiveRegionCache)
491
575
492
576
493 def test_invalidation_context_exception_in_compute(pylonsapp):
577 def test_invalidation_context_exception_in_compute(pylonsapp):
494 from rhodecode.model.db import CacheKey
578 from rhodecode.model.db import CacheKey
495 from beaker.cache import cache_region
579 from beaker.cache import cache_region
496
580
497 @cache_region('long_term')
581 @cache_region('long_term')
498 def _dummy_func(cache_key):
582 def _dummy_func(cache_key):
499 # this causes error since it doesn't get any params
583 # this causes error since it doesn't get any params
500 raise Exception('ups')
584 raise Exception('ups')
501
585
502 invalidator_context = CacheKey.repo_context_cache(
586 invalidator_context = CacheKey.repo_context_cache(
503 _dummy_func, 'test_repo_2', 'repo')
587 _dummy_func, 'test_repo_2', 'repo')
504
588
505 with pytest.raises(Exception):
589 with pytest.raises(Exception):
506 with invalidator_context as context:
590 with invalidator_context as context:
507 context.invalidate()
591 context.invalidate()
508 context.compute()
592 context.compute()
509
593
510
594
511 @pytest.mark.parametrize('execution_number', range(5))
595 @pytest.mark.parametrize('execution_number', range(5))
512 def test_cache_invalidation_race_condition(execution_number, pylonsapp):
596 def test_cache_invalidation_race_condition(execution_number, pylonsapp):
513 import time
597 import time
514 from beaker.cache import cache_region
598 from beaker.cache import cache_region
515 from rhodecode.model.db import CacheKey
599 from rhodecode.model.db import CacheKey
516
600
517 if CacheKey.metadata.bind.url.get_backend_name() == "mysql":
601 if CacheKey.metadata.bind.url.get_backend_name() == "mysql":
518 reason = (
602 reason = (
519 'Fails on MariaDB due to some locking issues. Investigation'
603 'Fails on MariaDB due to some locking issues. Investigation'
520 ' needed')
604 ' needed')
521 pytest.xfail(reason=reason)
605 pytest.xfail(reason=reason)
522
606
523 @run_test_concurrently(25)
607 @run_test_concurrently(25)
524 def test_create_and_delete_cache_keys():
608 def test_create_and_delete_cache_keys():
525 time.sleep(0.2)
609 time.sleep(0.2)
526
610
527 @cache_region('long_term')
611 @cache_region('long_term')
528 def _dummy_func(cache_key):
612 def _dummy_func(cache_key):
529 return 'result'
613 return 'result'
530
614
531 invalidator_context = CacheKey.repo_context_cache(
615 invalidator_context = CacheKey.repo_context_cache(
532 _dummy_func, 'test_repo_1', 'repo')
616 _dummy_func, 'test_repo_1', 'repo')
533
617
534 with invalidator_context as context:
618 with invalidator_context as context:
535 context.invalidate()
619 context.invalidate()
536 context.compute()
620 context.compute()
537
621
538 CacheKey.set_invalidate('test_repo_1', delete=True)
622 CacheKey.set_invalidate('test_repo_1', delete=True)
539
623
540 test_create_and_delete_cache_keys()
624 test_create_and_delete_cache_keys()
General Comments 0
You need to be logged in to leave comments. Login now