##// END OF EJS Templates
white space cleanup
marcink -
r2367:86aa4f1f beta
parent child Browse files
Show More
@@ -1,952 +1,952 b''
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10 import math
10 import math
11 import logging
11 import logging
12
12
13 from datetime import datetime
13 from datetime import datetime
14 from pygments.formatters.html import HtmlFormatter
14 from pygments.formatters.html import HtmlFormatter
15 from pygments import highlight as code_highlight
15 from pygments import highlight as code_highlight
16 from pylons import url, request, config
16 from pylons import url, request, config
17 from pylons.i18n.translation import _, ungettext
17 from pylons.i18n.translation import _, ungettext
18 from hashlib import md5
18 from hashlib import md5
19
19
20 from webhelpers.html import literal, HTML, escape
20 from webhelpers.html import literal, HTML, escape
21 from webhelpers.html.tools import *
21 from webhelpers.html.tools import *
22 from webhelpers.html.builder import make_tag
22 from webhelpers.html.builder import make_tag
23 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
23 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
24 end_form, file, form, hidden, image, javascript_link, link_to, \
24 end_form, file, form, hidden, image, javascript_link, link_to, \
25 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
25 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
26 submit, text, password, textarea, title, ul, xml_declaration, radio
26 submit, text, password, textarea, title, ul, xml_declaration, radio
27 from webhelpers.html.tools import auto_link, button_to, highlight, \
27 from webhelpers.html.tools import auto_link, button_to, highlight, \
28 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
28 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
29 from webhelpers.number import format_byte_size, format_bit_size
29 from webhelpers.number import format_byte_size, format_bit_size
30 from webhelpers.pylonslib import Flash as _Flash
30 from webhelpers.pylonslib import Flash as _Flash
31 from webhelpers.pylonslib.secure_form import secure_form
31 from webhelpers.pylonslib.secure_form import secure_form
32 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
32 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
33 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
33 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
34 replace_whitespace, urlify, truncate, wrap_paragraphs
34 replace_whitespace, urlify, truncate, wrap_paragraphs
35 from webhelpers.date import time_ago_in_words
35 from webhelpers.date import time_ago_in_words
36 from webhelpers.paginate import Page
36 from webhelpers.paginate import Page
37 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
37 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
38 convert_boolean_attrs, NotGiven, _make_safe_id_component
38 convert_boolean_attrs, NotGiven, _make_safe_id_component
39
39
40 from rhodecode.lib.annotate import annotate_highlight
40 from rhodecode.lib.annotate import annotate_highlight
41 from rhodecode.lib.utils import repo_name_slug
41 from rhodecode.lib.utils import repo_name_slug
42 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
42 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
43 get_changeset_safe
43 get_changeset_safe
44 from rhodecode.lib.markup_renderer import MarkupRenderer
44 from rhodecode.lib.markup_renderer import MarkupRenderer
45 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
45 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
46 from rhodecode.lib.vcs.backends.base import BaseChangeset
46 from rhodecode.lib.vcs.backends.base import BaseChangeset
47 from rhodecode.model.db import URL_SEP
47 from rhodecode.model.db import URL_SEP
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 def shorter(text, size=20):
52 def shorter(text, size=20):
53 postfix = '...'
53 postfix = '...'
54 if len(text) > size:
54 if len(text) > size:
55 return text[:size - len(postfix)] + postfix
55 return text[:size - len(postfix)] + postfix
56 return text
56 return text
57
57
58
58
59 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
59 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
60 """
60 """
61 Reset button
61 Reset button
62 """
62 """
63 _set_input_attrs(attrs, type, name, value)
63 _set_input_attrs(attrs, type, name, value)
64 _set_id_attr(attrs, id, name)
64 _set_id_attr(attrs, id, name)
65 convert_boolean_attrs(attrs, ["disabled"])
65 convert_boolean_attrs(attrs, ["disabled"])
66 return HTML.input(**attrs)
66 return HTML.input(**attrs)
67
67
68 reset = _reset
68 reset = _reset
69 safeid = _make_safe_id_component
69 safeid = _make_safe_id_component
70
70
71
71
72 def FID(raw_id, path):
72 def FID(raw_id, path):
73 """
73 """
74 Creates a uniqe ID for filenode based on it's hash of path and revision
74 Creates a uniqe ID for filenode based on it's hash of path and revision
75 it's safe to use in urls
75 it's safe to use in urls
76
76
77 :param raw_id:
77 :param raw_id:
78 :param path:
78 :param path:
79 """
79 """
80
80
81 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
81 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
82
82
83
83
84 def get_token():
84 def get_token():
85 """Return the current authentication token, creating one if one doesn't
85 """Return the current authentication token, creating one if one doesn't
86 already exist.
86 already exist.
87 """
87 """
88 token_key = "_authentication_token"
88 token_key = "_authentication_token"
89 from pylons import session
89 from pylons import session
90 if not token_key in session:
90 if not token_key in session:
91 try:
91 try:
92 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
92 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
93 except AttributeError: # Python < 2.4
93 except AttributeError: # Python < 2.4
94 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
94 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
95 session[token_key] = token
95 session[token_key] = token
96 if hasattr(session, 'save'):
96 if hasattr(session, 'save'):
97 session.save()
97 session.save()
98 return session[token_key]
98 return session[token_key]
99
99
100
100
101 class _GetError(object):
101 class _GetError(object):
102 """Get error from form_errors, and represent it as span wrapped error
102 """Get error from form_errors, and represent it as span wrapped error
103 message
103 message
104
104
105 :param field_name: field to fetch errors for
105 :param field_name: field to fetch errors for
106 :param form_errors: form errors dict
106 :param form_errors: form errors dict
107 """
107 """
108
108
109 def __call__(self, field_name, form_errors):
109 def __call__(self, field_name, form_errors):
110 tmpl = """<span class="error_msg">%s</span>"""
110 tmpl = """<span class="error_msg">%s</span>"""
111 if form_errors and form_errors.has_key(field_name):
111 if form_errors and form_errors.has_key(field_name):
112 return literal(tmpl % form_errors.get(field_name))
112 return literal(tmpl % form_errors.get(field_name))
113
113
114 get_error = _GetError()
114 get_error = _GetError()
115
115
116
116
117 class _ToolTip(object):
117 class _ToolTip(object):
118
118
119 def __call__(self, tooltip_title, trim_at=50):
119 def __call__(self, tooltip_title, trim_at=50):
120 """Special function just to wrap our text into nice formatted
120 """Special function just to wrap our text into nice formatted
121 autowrapped text
121 autowrapped text
122
122
123 :param tooltip_title:
123 :param tooltip_title:
124 """
124 """
125 return escape(tooltip_title)
125 return escape(tooltip_title)
126 tooltip = _ToolTip()
126 tooltip = _ToolTip()
127
127
128
128
129 class _FilesBreadCrumbs(object):
129 class _FilesBreadCrumbs(object):
130
130
131 def __call__(self, repo_name, rev, paths):
131 def __call__(self, repo_name, rev, paths):
132 if isinstance(paths, str):
132 if isinstance(paths, str):
133 paths = safe_unicode(paths)
133 paths = safe_unicode(paths)
134 url_l = [link_to(repo_name, url('files_home',
134 url_l = [link_to(repo_name, url('files_home',
135 repo_name=repo_name,
135 repo_name=repo_name,
136 revision=rev, f_path=''))]
136 revision=rev, f_path=''))]
137 paths_l = paths.split('/')
137 paths_l = paths.split('/')
138 for cnt, p in enumerate(paths_l):
138 for cnt, p in enumerate(paths_l):
139 if p != '':
139 if p != '':
140 url_l.append(link_to(p,
140 url_l.append(link_to(p,
141 url('files_home',
141 url('files_home',
142 repo_name=repo_name,
142 repo_name=repo_name,
143 revision=rev,
143 revision=rev,
144 f_path='/'.join(paths_l[:cnt + 1])
144 f_path='/'.join(paths_l[:cnt + 1])
145 )
145 )
146 )
146 )
147 )
147 )
148
148
149 return literal('/'.join(url_l))
149 return literal('/'.join(url_l))
150
150
151 files_breadcrumbs = _FilesBreadCrumbs()
151 files_breadcrumbs = _FilesBreadCrumbs()
152
152
153
153
154 class CodeHtmlFormatter(HtmlFormatter):
154 class CodeHtmlFormatter(HtmlFormatter):
155 """
155 """
156 My code Html Formatter for source codes
156 My code Html Formatter for source codes
157 """
157 """
158
158
159 def wrap(self, source, outfile):
159 def wrap(self, source, outfile):
160 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
160 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
161
161
162 def _wrap_code(self, source):
162 def _wrap_code(self, source):
163 for cnt, it in enumerate(source):
163 for cnt, it in enumerate(source):
164 i, t = it
164 i, t = it
165 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
165 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
166 yield i, t
166 yield i, t
167
167
168 def _wrap_tablelinenos(self, inner):
168 def _wrap_tablelinenos(self, inner):
169 dummyoutfile = StringIO.StringIO()
169 dummyoutfile = StringIO.StringIO()
170 lncount = 0
170 lncount = 0
171 for t, line in inner:
171 for t, line in inner:
172 if t:
172 if t:
173 lncount += 1
173 lncount += 1
174 dummyoutfile.write(line)
174 dummyoutfile.write(line)
175
175
176 fl = self.linenostart
176 fl = self.linenostart
177 mw = len(str(lncount + fl - 1))
177 mw = len(str(lncount + fl - 1))
178 sp = self.linenospecial
178 sp = self.linenospecial
179 st = self.linenostep
179 st = self.linenostep
180 la = self.lineanchors
180 la = self.lineanchors
181 aln = self.anchorlinenos
181 aln = self.anchorlinenos
182 nocls = self.noclasses
182 nocls = self.noclasses
183 if sp:
183 if sp:
184 lines = []
184 lines = []
185
185
186 for i in range(fl, fl + lncount):
186 for i in range(fl, fl + lncount):
187 if i % st == 0:
187 if i % st == 0:
188 if i % sp == 0:
188 if i % sp == 0:
189 if aln:
189 if aln:
190 lines.append('<a href="#%s%d" class="special">%*d</a>' %
190 lines.append('<a href="#%s%d" class="special">%*d</a>' %
191 (la, i, mw, i))
191 (la, i, mw, i))
192 else:
192 else:
193 lines.append('<span class="special">%*d</span>' % (mw, i))
193 lines.append('<span class="special">%*d</span>' % (mw, i))
194 else:
194 else:
195 if aln:
195 if aln:
196 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
196 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
197 else:
197 else:
198 lines.append('%*d' % (mw, i))
198 lines.append('%*d' % (mw, i))
199 else:
199 else:
200 lines.append('')
200 lines.append('')
201 ls = '\n'.join(lines)
201 ls = '\n'.join(lines)
202 else:
202 else:
203 lines = []
203 lines = []
204 for i in range(fl, fl + lncount):
204 for i in range(fl, fl + lncount):
205 if i % st == 0:
205 if i % st == 0:
206 if aln:
206 if aln:
207 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
207 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
208 else:
208 else:
209 lines.append('%*d' % (mw, i))
209 lines.append('%*d' % (mw, i))
210 else:
210 else:
211 lines.append('')
211 lines.append('')
212 ls = '\n'.join(lines)
212 ls = '\n'.join(lines)
213
213
214 # in case you wonder about the seemingly redundant <div> here: since the
214 # in case you wonder about the seemingly redundant <div> here: since the
215 # content in the other cell also is wrapped in a div, some browsers in
215 # content in the other cell also is wrapped in a div, some browsers in
216 # some configurations seem to mess up the formatting...
216 # some configurations seem to mess up the formatting...
217 if nocls:
217 if nocls:
218 yield 0, ('<table class="%stable">' % self.cssclass +
218 yield 0, ('<table class="%stable">' % self.cssclass +
219 '<tr><td><div class="linenodiv" '
219 '<tr><td><div class="linenodiv" '
220 'style="background-color: #f0f0f0; padding-right: 10px">'
220 'style="background-color: #f0f0f0; padding-right: 10px">'
221 '<pre style="line-height: 125%">' +
221 '<pre style="line-height: 125%">' +
222 ls + '</pre></div></td><td id="hlcode" class="code">')
222 ls + '</pre></div></td><td id="hlcode" class="code">')
223 else:
223 else:
224 yield 0, ('<table class="%stable">' % self.cssclass +
224 yield 0, ('<table class="%stable">' % self.cssclass +
225 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
225 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
226 ls + '</pre></div></td><td id="hlcode" class="code">')
226 ls + '</pre></div></td><td id="hlcode" class="code">')
227 yield 0, dummyoutfile.getvalue()
227 yield 0, dummyoutfile.getvalue()
228 yield 0, '</td></tr></table>'
228 yield 0, '</td></tr></table>'
229
229
230
230
231 def pygmentize(filenode, **kwargs):
231 def pygmentize(filenode, **kwargs):
232 """pygmentize function using pygments
232 """pygmentize function using pygments
233
233
234 :param filenode:
234 :param filenode:
235 """
235 """
236
236
237 return literal(code_highlight(filenode.content,
237 return literal(code_highlight(filenode.content,
238 filenode.lexer, CodeHtmlFormatter(**kwargs)))
238 filenode.lexer, CodeHtmlFormatter(**kwargs)))
239
239
240
240
241 def pygmentize_annotation(repo_name, filenode, **kwargs):
241 def pygmentize_annotation(repo_name, filenode, **kwargs):
242 """
242 """
243 pygmentize function for annotation
243 pygmentize function for annotation
244
244
245 :param filenode:
245 :param filenode:
246 """
246 """
247
247
248 color_dict = {}
248 color_dict = {}
249
249
250 def gen_color(n=10000):
250 def gen_color(n=10000):
251 """generator for getting n of evenly distributed colors using
251 """generator for getting n of evenly distributed colors using
252 hsv color and golden ratio. It always return same order of colors
252 hsv color and golden ratio. It always return same order of colors
253
253
254 :returns: RGB tuple
254 :returns: RGB tuple
255 """
255 """
256
256
257 def hsv_to_rgb(h, s, v):
257 def hsv_to_rgb(h, s, v):
258 if s == 0.0:
258 if s == 0.0:
259 return v, v, v
259 return v, v, v
260 i = int(h * 6.0) # XXX assume int() truncates!
260 i = int(h * 6.0) # XXX assume int() truncates!
261 f = (h * 6.0) - i
261 f = (h * 6.0) - i
262 p = v * (1.0 - s)
262 p = v * (1.0 - s)
263 q = v * (1.0 - s * f)
263 q = v * (1.0 - s * f)
264 t = v * (1.0 - s * (1.0 - f))
264 t = v * (1.0 - s * (1.0 - f))
265 i = i % 6
265 i = i % 6
266 if i == 0:
266 if i == 0:
267 return v, t, p
267 return v, t, p
268 if i == 1:
268 if i == 1:
269 return q, v, p
269 return q, v, p
270 if i == 2:
270 if i == 2:
271 return p, v, t
271 return p, v, t
272 if i == 3:
272 if i == 3:
273 return p, q, v
273 return p, q, v
274 if i == 4:
274 if i == 4:
275 return t, p, v
275 return t, p, v
276 if i == 5:
276 if i == 5:
277 return v, p, q
277 return v, p, q
278
278
279 golden_ratio = 0.618033988749895
279 golden_ratio = 0.618033988749895
280 h = 0.22717784590367374
280 h = 0.22717784590367374
281
281
282 for _ in xrange(n):
282 for _ in xrange(n):
283 h += golden_ratio
283 h += golden_ratio
284 h %= 1
284 h %= 1
285 HSV_tuple = [h, 0.95, 0.95]
285 HSV_tuple = [h, 0.95, 0.95]
286 RGB_tuple = hsv_to_rgb(*HSV_tuple)
286 RGB_tuple = hsv_to_rgb(*HSV_tuple)
287 yield map(lambda x: str(int(x * 256)), RGB_tuple)
287 yield map(lambda x: str(int(x * 256)), RGB_tuple)
288
288
289 cgenerator = gen_color()
289 cgenerator = gen_color()
290
290
291 def get_color_string(cs):
291 def get_color_string(cs):
292 if cs in color_dict:
292 if cs in color_dict:
293 col = color_dict[cs]
293 col = color_dict[cs]
294 else:
294 else:
295 col = color_dict[cs] = cgenerator.next()
295 col = color_dict[cs] = cgenerator.next()
296 return "color: rgb(%s)! important;" % (', '.join(col))
296 return "color: rgb(%s)! important;" % (', '.join(col))
297
297
298 def url_func(repo_name):
298 def url_func(repo_name):
299
299
300 def _url_func(changeset):
300 def _url_func(changeset):
301 author = changeset.author
301 author = changeset.author
302 date = changeset.date
302 date = changeset.date
303 message = tooltip(changeset.message)
303 message = tooltip(changeset.message)
304
304
305 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
305 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
306 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
306 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
307 "</b> %s<br/></div>")
307 "</b> %s<br/></div>")
308
308
309 tooltip_html = tooltip_html % (author, date, message)
309 tooltip_html = tooltip_html % (author, date, message)
310 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
310 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
311 short_id(changeset.raw_id))
311 short_id(changeset.raw_id))
312 uri = link_to(
312 uri = link_to(
313 lnk_format,
313 lnk_format,
314 url('changeset_home', repo_name=repo_name,
314 url('changeset_home', repo_name=repo_name,
315 revision=changeset.raw_id),
315 revision=changeset.raw_id),
316 style=get_color_string(changeset.raw_id),
316 style=get_color_string(changeset.raw_id),
317 class_='tooltip',
317 class_='tooltip',
318 title=tooltip_html
318 title=tooltip_html
319 )
319 )
320
320
321 uri += '\n'
321 uri += '\n'
322 return uri
322 return uri
323 return _url_func
323 return _url_func
324
324
325 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
325 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
326
326
327
327
328 def is_following_repo(repo_name, user_id):
328 def is_following_repo(repo_name, user_id):
329 from rhodecode.model.scm import ScmModel
329 from rhodecode.model.scm import ScmModel
330 return ScmModel().is_following_repo(repo_name, user_id)
330 return ScmModel().is_following_repo(repo_name, user_id)
331
331
332 flash = _Flash()
332 flash = _Flash()
333
333
334 #==============================================================================
334 #==============================================================================
335 # SCM FILTERS available via h.
335 # SCM FILTERS available via h.
336 #==============================================================================
336 #==============================================================================
337 from rhodecode.lib.vcs.utils import author_name, author_email
337 from rhodecode.lib.vcs.utils import author_name, author_email
338 from rhodecode.lib.utils2 import credentials_filter, age as _age
338 from rhodecode.lib.utils2 import credentials_filter, age as _age
339 from rhodecode.model.db import User
339 from rhodecode.model.db import User
340
340
341 age = lambda x: _age(x)
341 age = lambda x: _age(x)
342 capitalize = lambda x: x.capitalize()
342 capitalize = lambda x: x.capitalize()
343 email = author_email
343 email = author_email
344 short_id = lambda x: x[:12]
344 short_id = lambda x: x[:12]
345 hide_credentials = lambda x: ''.join(credentials_filter(x))
345 hide_credentials = lambda x: ''.join(credentials_filter(x))
346
346
347
347
348 def is_git(repository):
348 def is_git(repository):
349 if hasattr(repository, 'alias'):
349 if hasattr(repository, 'alias'):
350 _type = repository.alias
350 _type = repository.alias
351 elif hasattr(repository, 'repo_type'):
351 elif hasattr(repository, 'repo_type'):
352 _type = repository.repo_type
352 _type = repository.repo_type
353 else:
353 else:
354 _type = repository
354 _type = repository
355 return _type == 'git'
355 return _type == 'git'
356
356
357
357
358 def is_hg(repository):
358 def is_hg(repository):
359 if hasattr(repository, 'alias'):
359 if hasattr(repository, 'alias'):
360 _type = repository.alias
360 _type = repository.alias
361 elif hasattr(repository, 'repo_type'):
361 elif hasattr(repository, 'repo_type'):
362 _type = repository.repo_type
362 _type = repository.repo_type
363 else:
363 else:
364 _type = repository
364 _type = repository
365 return _type == 'hg'
365 return _type == 'hg'
366
366
367
367
368 def email_or_none(author):
368 def email_or_none(author):
369 _email = email(author)
369 _email = email(author)
370 if _email != '':
370 if _email != '':
371 return _email
371 return _email
372
372
373 # See if it contains a username we can get an email from
373 # See if it contains a username we can get an email from
374 user = User.get_by_username(author_name(author), case_insensitive=True,
374 user = User.get_by_username(author_name(author), case_insensitive=True,
375 cache=True)
375 cache=True)
376 if user is not None:
376 if user is not None:
377 return user.email
377 return user.email
378
378
379 # No valid email, not a valid user in the system, none!
379 # No valid email, not a valid user in the system, none!
380 return None
380 return None
381
381
382
382
383 def person(author):
383 def person(author):
384 # attr to return from fetched user
384 # attr to return from fetched user
385 person_getter = lambda usr: usr.username
385 person_getter = lambda usr: usr.username
386
386
387 # Valid email in the attribute passed, see if they're in the system
387 # Valid email in the attribute passed, see if they're in the system
388 _email = email(author)
388 _email = email(author)
389 if _email != '':
389 if _email != '':
390 user = User.get_by_email(_email, case_insensitive=True, cache=True)
390 user = User.get_by_email(_email, case_insensitive=True, cache=True)
391 if user is not None:
391 if user is not None:
392 return person_getter(user)
392 return person_getter(user)
393 return _email
393 return _email
394
394
395 # Maybe it's a username?
395 # Maybe it's a username?
396 _author = author_name(author)
396 _author = author_name(author)
397 user = User.get_by_username(_author, case_insensitive=True,
397 user = User.get_by_username(_author, case_insensitive=True,
398 cache=True)
398 cache=True)
399 if user is not None:
399 if user is not None:
400 return person_getter(user)
400 return person_getter(user)
401
401
402 # Still nothing? Just pass back the author name then
402 # Still nothing? Just pass back the author name then
403 return _author
403 return _author
404
404
405
405
406 def bool2icon(value):
406 def bool2icon(value):
407 """Returns True/False values represented as small html image of true/false
407 """Returns True/False values represented as small html image of true/false
408 icons
408 icons
409
409
410 :param value: bool value
410 :param value: bool value
411 """
411 """
412
412
413 if value is True:
413 if value is True:
414 return HTML.tag('img', src=url("/images/icons/accept.png"),
414 return HTML.tag('img', src=url("/images/icons/accept.png"),
415 alt=_('True'))
415 alt=_('True'))
416
416
417 if value is False:
417 if value is False:
418 return HTML.tag('img', src=url("/images/icons/cancel.png"),
418 return HTML.tag('img', src=url("/images/icons/cancel.png"),
419 alt=_('False'))
419 alt=_('False'))
420
420
421 return value
421 return value
422
422
423
423
424 def action_parser(user_log, feed=False):
424 def action_parser(user_log, feed=False):
425 """
425 """
426 This helper will action_map the specified string action into translated
426 This helper will action_map the specified string action into translated
427 fancy names with icons and links
427 fancy names with icons and links
428
428
429 :param user_log: user log instance
429 :param user_log: user log instance
430 :param feed: use output for feeds (no html and fancy icons)
430 :param feed: use output for feeds (no html and fancy icons)
431 """
431 """
432
432
433 action = user_log.action
433 action = user_log.action
434 action_params = ' '
434 action_params = ' '
435
435
436 x = action.split(':')
436 x = action.split(':')
437
437
438 if len(x) > 1:
438 if len(x) > 1:
439 action, action_params = x
439 action, action_params = x
440
440
441 def get_cs_links():
441 def get_cs_links():
442 revs_limit = 3 # display this amount always
442 revs_limit = 3 # display this amount always
443 revs_top_limit = 50 # show upto this amount of changesets hidden
443 revs_top_limit = 50 # show upto this amount of changesets hidden
444 revs_ids = action_params.split(',')
444 revs_ids = action_params.split(',')
445 deleted = user_log.repository is None
445 deleted = user_log.repository is None
446 if deleted:
446 if deleted:
447 return ','.join(revs_ids)
447 return ','.join(revs_ids)
448
448
449 repo_name = user_log.repository.repo_name
449 repo_name = user_log.repository.repo_name
450
450
451 repo = user_log.repository.scm_instance
451 repo = user_log.repository.scm_instance
452
452
453 def lnk(rev, repo_name):
453 def lnk(rev, repo_name):
454
454
455 if isinstance(rev, BaseChangeset):
455 if isinstance(rev, BaseChangeset):
456 lbl = 'r%s:%s' % (rev.revision, rev.short_id)
456 lbl = 'r%s:%s' % (rev.revision, rev.short_id)
457 _url = url('changeset_home', repo_name=repo_name,
457 _url = url('changeset_home', repo_name=repo_name,
458 revision=rev.raw_id)
458 revision=rev.raw_id)
459 title = tooltip(rev.message)
459 title = tooltip(rev.message)
460 else:
460 else:
461 lbl = '%s' % rev
461 lbl = '%s' % rev
462 _url = '#'
462 _url = '#'
463 title = _('Changeset not found')
463 title = _('Changeset not found')
464
464
465 return link_to(lbl, _url, title=title, class_='tooltip',)
465 return link_to(lbl, _url, title=title, class_='tooltip',)
466
466
467 revs = []
467 revs = []
468 if len(filter(lambda v: v != '', revs_ids)) > 0:
468 if len(filter(lambda v: v != '', revs_ids)) > 0:
469 for rev in revs_ids[:revs_top_limit]:
469 for rev in revs_ids[:revs_top_limit]:
470 try:
470 try:
471 rev = repo.get_changeset(rev)
471 rev = repo.get_changeset(rev)
472 revs.append(rev)
472 revs.append(rev)
473 except ChangesetDoesNotExistError:
473 except ChangesetDoesNotExistError:
474 log.error('cannot find revision %s in this repo' % rev)
474 log.error('cannot find revision %s in this repo' % rev)
475 revs.append(rev)
475 revs.append(rev)
476 continue
476 continue
477 cs_links = []
477 cs_links = []
478 cs_links.append(" " + ', '.join(
478 cs_links.append(" " + ', '.join(
479 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
479 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
480 )
480 )
481 )
481 )
482
482
483 compare_view = (
483 compare_view = (
484 ' <div class="compare_view tooltip" title="%s">'
484 ' <div class="compare_view tooltip" title="%s">'
485 '<a href="%s">%s</a> </div>' % (
485 '<a href="%s">%s</a> </div>' % (
486 _('Show all combined changesets %s->%s') % (
486 _('Show all combined changesets %s->%s') % (
487 revs_ids[0], revs_ids[-1]
487 revs_ids[0], revs_ids[-1]
488 ),
488 ),
489 url('changeset_home', repo_name=repo_name,
489 url('changeset_home', repo_name=repo_name,
490 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
490 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
491 ),
491 ),
492 _('compare view')
492 _('compare view')
493 )
493 )
494 )
494 )
495
495
496 # if we have exactly one more than normally displayed
496 # if we have exactly one more than normally displayed
497 # just display it, takes less space than displaying
497 # just display it, takes less space than displaying
498 # "and 1 more revisions"
498 # "and 1 more revisions"
499 if len(revs_ids) == revs_limit + 1:
499 if len(revs_ids) == revs_limit + 1:
500 rev = revs[revs_limit]
500 rev = revs[revs_limit]
501 cs_links.append(", " + lnk(rev, repo_name))
501 cs_links.append(", " + lnk(rev, repo_name))
502
502
503 # hidden-by-default ones
503 # hidden-by-default ones
504 if len(revs_ids) > revs_limit + 1:
504 if len(revs_ids) > revs_limit + 1:
505 uniq_id = revs_ids[0]
505 uniq_id = revs_ids[0]
506 html_tmpl = (
506 html_tmpl = (
507 '<span> %s <a class="show_more" id="_%s" '
507 '<span> %s <a class="show_more" id="_%s" '
508 'href="#more">%s</a> %s</span>'
508 'href="#more">%s</a> %s</span>'
509 )
509 )
510 if not feed:
510 if not feed:
511 cs_links.append(html_tmpl % (
511 cs_links.append(html_tmpl % (
512 _('and'),
512 _('and'),
513 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
513 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
514 _('revisions')
514 _('revisions')
515 )
515 )
516 )
516 )
517
517
518 if not feed:
518 if not feed:
519 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
519 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
520 else:
520 else:
521 html_tmpl = '<span id="%s"> %s </span>'
521 html_tmpl = '<span id="%s"> %s </span>'
522
522
523 morelinks = ', '.join(
523 morelinks = ', '.join(
524 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
524 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
525 )
525 )
526
526
527 if len(revs_ids) > revs_top_limit:
527 if len(revs_ids) > revs_top_limit:
528 morelinks += ', ...'
528 morelinks += ', ...'
529
529
530 cs_links.append(html_tmpl % (uniq_id, morelinks))
530 cs_links.append(html_tmpl % (uniq_id, morelinks))
531 if len(revs) > 1:
531 if len(revs) > 1:
532 cs_links.append(compare_view)
532 cs_links.append(compare_view)
533 return ''.join(cs_links)
533 return ''.join(cs_links)
534
534
535 def get_fork_name():
535 def get_fork_name():
536 repo_name = action_params
536 repo_name = action_params
537 return _('fork name ') + str(link_to(action_params, url('summary_home',
537 return _('fork name ') + str(link_to(action_params, url('summary_home',
538 repo_name=repo_name,)))
538 repo_name=repo_name,)))
539
539
540 action_map = {'user_deleted_repo': (_('[deleted] repository'), None),
540 action_map = {'user_deleted_repo': (_('[deleted] repository'), None),
541 'user_created_repo': (_('[created] repository'), None),
541 'user_created_repo': (_('[created] repository'), None),
542 'user_created_fork': (_('[created] repository as fork'), None),
542 'user_created_fork': (_('[created] repository as fork'), None),
543 'user_forked_repo': (_('[forked] repository'), get_fork_name),
543 'user_forked_repo': (_('[forked] repository'), get_fork_name),
544 'user_updated_repo': (_('[updated] repository'), None),
544 'user_updated_repo': (_('[updated] repository'), None),
545 'admin_deleted_repo': (_('[delete] repository'), None),
545 'admin_deleted_repo': (_('[delete] repository'), None),
546 'admin_created_repo': (_('[created] repository'), None),
546 'admin_created_repo': (_('[created] repository'), None),
547 'admin_forked_repo': (_('[forked] repository'), None),
547 'admin_forked_repo': (_('[forked] repository'), None),
548 'admin_updated_repo': (_('[updated] repository'), None),
548 'admin_updated_repo': (_('[updated] repository'), None),
549 'push': (_('[pushed] into'), get_cs_links),
549 'push': (_('[pushed] into'), get_cs_links),
550 'push_local': (_('[committed via RhodeCode] into'), get_cs_links),
550 'push_local': (_('[committed via RhodeCode] into'), get_cs_links),
551 'push_remote': (_('[pulled from remote] into'), get_cs_links),
551 'push_remote': (_('[pulled from remote] into'), get_cs_links),
552 'pull': (_('[pulled] from'), None),
552 'pull': (_('[pulled] from'), None),
553 'started_following_repo': (_('[started following] repository'), None),
553 'started_following_repo': (_('[started following] repository'), None),
554 'stopped_following_repo': (_('[stopped following] repository'), None),
554 'stopped_following_repo': (_('[stopped following] repository'), None),
555 }
555 }
556
556
557 action_str = action_map.get(action, action)
557 action_str = action_map.get(action, action)
558 if feed:
558 if feed:
559 action = action_str[0].replace('[', '').replace(']', '')
559 action = action_str[0].replace('[', '').replace(']', '')
560 else:
560 else:
561 action = action_str[0]\
561 action = action_str[0]\
562 .replace('[', '<span class="journal_highlight">')\
562 .replace('[', '<span class="journal_highlight">')\
563 .replace(']', '</span>')
563 .replace(']', '</span>')
564
564
565 action_params_func = lambda: ""
565 action_params_func = lambda: ""
566
566
567 if callable(action_str[1]):
567 if callable(action_str[1]):
568 action_params_func = action_str[1]
568 action_params_func = action_str[1]
569
569
570 return [literal(action), action_params_func]
570 return [literal(action), action_params_func]
571
571
572
572
573 def action_parser_icon(user_log):
573 def action_parser_icon(user_log):
574 action = user_log.action
574 action = user_log.action
575 action_params = None
575 action_params = None
576 x = action.split(':')
576 x = action.split(':')
577
577
578 if len(x) > 1:
578 if len(x) > 1:
579 action, action_params = x
579 action, action_params = x
580
580
581 tmpl = """<img src="%s%s" alt="%s"/>"""
581 tmpl = """<img src="%s%s" alt="%s"/>"""
582 map = {'user_deleted_repo':'database_delete.png',
582 map = {'user_deleted_repo':'database_delete.png',
583 'user_created_repo':'database_add.png',
583 'user_created_repo':'database_add.png',
584 'user_created_fork':'arrow_divide.png',
584 'user_created_fork':'arrow_divide.png',
585 'user_forked_repo':'arrow_divide.png',
585 'user_forked_repo':'arrow_divide.png',
586 'user_updated_repo':'database_edit.png',
586 'user_updated_repo':'database_edit.png',
587 'admin_deleted_repo':'database_delete.png',
587 'admin_deleted_repo':'database_delete.png',
588 'admin_created_repo':'database_add.png',
588 'admin_created_repo':'database_add.png',
589 'admin_forked_repo':'arrow_divide.png',
589 'admin_forked_repo':'arrow_divide.png',
590 'admin_updated_repo':'database_edit.png',
590 'admin_updated_repo':'database_edit.png',
591 'push':'script_add.png',
591 'push':'script_add.png',
592 'push_local':'script_edit.png',
592 'push_local':'script_edit.png',
593 'push_remote':'connect.png',
593 'push_remote':'connect.png',
594 'pull':'down_16.png',
594 'pull':'down_16.png',
595 'started_following_repo':'heart_add.png',
595 'started_following_repo':'heart_add.png',
596 'stopped_following_repo':'heart_delete.png',
596 'stopped_following_repo':'heart_delete.png',
597 }
597 }
598 return literal(tmpl % ((url('/images/icons/')),
598 return literal(tmpl % ((url('/images/icons/')),
599 map.get(action, action), action))
599 map.get(action, action), action))
600
600
601
601
602 #==============================================================================
602 #==============================================================================
603 # PERMS
603 # PERMS
604 #==============================================================================
604 #==============================================================================
605 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
605 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
606 HasRepoPermissionAny, HasRepoPermissionAll
606 HasRepoPermissionAny, HasRepoPermissionAll
607
607
608
608
609 #==============================================================================
609 #==============================================================================
610 # GRAVATAR URL
610 # GRAVATAR URL
611 #==============================================================================
611 #==============================================================================
612
612
613 def gravatar_url(email_address, size=30):
613 def gravatar_url(email_address, size=30):
614 if (not str2bool(config['app_conf'].get('use_gravatar')) or
614 if (not str2bool(config['app_conf'].get('use_gravatar')) or
615 not email_address or email_address == 'anonymous@rhodecode.org'):
615 not email_address or email_address == 'anonymous@rhodecode.org'):
616 f = lambda a, l: min(l, key=lambda x: abs(x - a))
616 f = lambda a, l: min(l, key=lambda x: abs(x - a))
617 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
617 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
618
618
619 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
619 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
620 default = 'identicon'
620 default = 'identicon'
621 baseurl_nossl = "http://www.gravatar.com/avatar/"
621 baseurl_nossl = "http://www.gravatar.com/avatar/"
622 baseurl_ssl = "https://secure.gravatar.com/avatar/"
622 baseurl_ssl = "https://secure.gravatar.com/avatar/"
623 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
623 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
624
624
625 if isinstance(email_address, unicode):
625 if isinstance(email_address, unicode):
626 #hashlib crashes on unicode items
626 #hashlib crashes on unicode items
627 email_address = safe_str(email_address)
627 email_address = safe_str(email_address)
628 # construct the url
628 # construct the url
629 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
629 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
630 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
630 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
631
631
632 return gravatar_url
632 return gravatar_url
633
633
634
634
635 #==============================================================================
635 #==============================================================================
636 # REPO PAGER, PAGER FOR REPOSITORY
636 # REPO PAGER, PAGER FOR REPOSITORY
637 #==============================================================================
637 #==============================================================================
638 class RepoPage(Page):
638 class RepoPage(Page):
639
639
640 def __init__(self, collection, page=1, items_per_page=20,
640 def __init__(self, collection, page=1, items_per_page=20,
641 item_count=None, url=None, **kwargs):
641 item_count=None, url=None, **kwargs):
642
642
643 """Create a "RepoPage" instance. special pager for paging
643 """Create a "RepoPage" instance. special pager for paging
644 repository
644 repository
645 """
645 """
646 self._url_generator = url
646 self._url_generator = url
647
647
648 # Safe the kwargs class-wide so they can be used in the pager() method
648 # Safe the kwargs class-wide so they can be used in the pager() method
649 self.kwargs = kwargs
649 self.kwargs = kwargs
650
650
651 # Save a reference to the collection
651 # Save a reference to the collection
652 self.original_collection = collection
652 self.original_collection = collection
653
653
654 self.collection = collection
654 self.collection = collection
655
655
656 # The self.page is the number of the current page.
656 # The self.page is the number of the current page.
657 # The first page has the number 1!
657 # The first page has the number 1!
658 try:
658 try:
659 self.page = int(page) # make it int() if we get it as a string
659 self.page = int(page) # make it int() if we get it as a string
660 except (ValueError, TypeError):
660 except (ValueError, TypeError):
661 self.page = 1
661 self.page = 1
662
662
663 self.items_per_page = items_per_page
663 self.items_per_page = items_per_page
664
664
665 # Unless the user tells us how many items the collections has
665 # Unless the user tells us how many items the collections has
666 # we calculate that ourselves.
666 # we calculate that ourselves.
667 if item_count is not None:
667 if item_count is not None:
668 self.item_count = item_count
668 self.item_count = item_count
669 else:
669 else:
670 self.item_count = len(self.collection)
670 self.item_count = len(self.collection)
671
671
672 # Compute the number of the first and last available page
672 # Compute the number of the first and last available page
673 if self.item_count > 0:
673 if self.item_count > 0:
674 self.first_page = 1
674 self.first_page = 1
675 self.page_count = int(math.ceil(float(self.item_count) /
675 self.page_count = int(math.ceil(float(self.item_count) /
676 self.items_per_page))
676 self.items_per_page))
677 self.last_page = self.first_page + self.page_count - 1
677 self.last_page = self.first_page + self.page_count - 1
678
678
679 # Make sure that the requested page number is the range of
679 # Make sure that the requested page number is the range of
680 # valid pages
680 # valid pages
681 if self.page > self.last_page:
681 if self.page > self.last_page:
682 self.page = self.last_page
682 self.page = self.last_page
683 elif self.page < self.first_page:
683 elif self.page < self.first_page:
684 self.page = self.first_page
684 self.page = self.first_page
685
685
686 # Note: the number of items on this page can be less than
686 # Note: the number of items on this page can be less than
687 # items_per_page if the last page is not full
687 # items_per_page if the last page is not full
688 self.first_item = max(0, (self.item_count) - (self.page *
688 self.first_item = max(0, (self.item_count) - (self.page *
689 items_per_page))
689 items_per_page))
690 self.last_item = ((self.item_count - 1) - items_per_page *
690 self.last_item = ((self.item_count - 1) - items_per_page *
691 (self.page - 1))
691 (self.page - 1))
692
692
693 self.items = list(self.collection[self.first_item:self.last_item + 1])
693 self.items = list(self.collection[self.first_item:self.last_item + 1])
694
694
695 # Links to previous and next page
695 # Links to previous and next page
696 if self.page > self.first_page:
696 if self.page > self.first_page:
697 self.previous_page = self.page - 1
697 self.previous_page = self.page - 1
698 else:
698 else:
699 self.previous_page = None
699 self.previous_page = None
700
700
701 if self.page < self.last_page:
701 if self.page < self.last_page:
702 self.next_page = self.page + 1
702 self.next_page = self.page + 1
703 else:
703 else:
704 self.next_page = None
704 self.next_page = None
705
705
706 # No items available
706 # No items available
707 else:
707 else:
708 self.first_page = None
708 self.first_page = None
709 self.page_count = 0
709 self.page_count = 0
710 self.last_page = None
710 self.last_page = None
711 self.first_item = None
711 self.first_item = None
712 self.last_item = None
712 self.last_item = None
713 self.previous_page = None
713 self.previous_page = None
714 self.next_page = None
714 self.next_page = None
715 self.items = []
715 self.items = []
716
716
717 # This is a subclass of the 'list' type. Initialise the list now.
717 # This is a subclass of the 'list' type. Initialise the list now.
718 list.__init__(self, reversed(self.items))
718 list.__init__(self, reversed(self.items))
719
719
720
720
721 def changed_tooltip(nodes):
721 def changed_tooltip(nodes):
722 """
722 """
723 Generates a html string for changed nodes in changeset page.
723 Generates a html string for changed nodes in changeset page.
724 It limits the output to 30 entries
724 It limits the output to 30 entries
725
725
726 :param nodes: LazyNodesGenerator
726 :param nodes: LazyNodesGenerator
727 """
727 """
728 if nodes:
728 if nodes:
729 pref = ': <br/> '
729 pref = ': <br/> '
730 suf = ''
730 suf = ''
731 if len(nodes) > 30:
731 if len(nodes) > 30:
732 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
732 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
733 return literal(pref + '<br/> '.join([safe_unicode(x.path)
733 return literal(pref + '<br/> '.join([safe_unicode(x.path)
734 for x in nodes[:30]]) + suf)
734 for x in nodes[:30]]) + suf)
735 else:
735 else:
736 return ': ' + _('No Files')
736 return ': ' + _('No Files')
737
737
738
738
739 def repo_link(groups_and_repos):
739 def repo_link(groups_and_repos):
740 """
740 """
741 Makes a breadcrumbs link to repo within a group
741 Makes a breadcrumbs link to repo within a group
742 joins &raquo; on each group to create a fancy link
742 joins &raquo; on each group to create a fancy link
743
743
744 ex::
744 ex::
745 group >> subgroup >> repo
745 group >> subgroup >> repo
746
746
747 :param groups_and_repos:
747 :param groups_and_repos:
748 """
748 """
749 groups, repo_name = groups_and_repos
749 groups, repo_name = groups_and_repos
750
750
751 if not groups:
751 if not groups:
752 return repo_name
752 return repo_name
753 else:
753 else:
754 def make_link(group):
754 def make_link(group):
755 return link_to(group.name, url('repos_group_home',
755 return link_to(group.name, url('repos_group_home',
756 group_name=group.group_name))
756 group_name=group.group_name))
757 return literal(' &raquo; '.join(map(make_link, groups)) + \
757 return literal(' &raquo; '.join(map(make_link, groups)) + \
758 " &raquo; " + repo_name)
758 " &raquo; " + repo_name)
759
759
760
760
761 def fancy_file_stats(stats):
761 def fancy_file_stats(stats):
762 """
762 """
763 Displays a fancy two colored bar for number of added/deleted
763 Displays a fancy two colored bar for number of added/deleted
764 lines of code on file
764 lines of code on file
765
765
766 :param stats: two element list of added/deleted lines of code
766 :param stats: two element list of added/deleted lines of code
767 """
767 """
768
768
769 a, d, t = stats[0], stats[1], stats[0] + stats[1]
769 a, d, t = stats[0], stats[1], stats[0] + stats[1]
770 width = 100
770 width = 100
771 unit = float(width) / (t or 1)
771 unit = float(width) / (t or 1)
772
772
773 # needs > 9% of width to be visible or 0 to be hidden
773 # needs > 9% of width to be visible or 0 to be hidden
774 a_p = max(9, unit * a) if a > 0 else 0
774 a_p = max(9, unit * a) if a > 0 else 0
775 d_p = max(9, unit * d) if d > 0 else 0
775 d_p = max(9, unit * d) if d > 0 else 0
776 p_sum = a_p + d_p
776 p_sum = a_p + d_p
777
777
778 if p_sum > width:
778 if p_sum > width:
779 #adjust the percentage to be == 100% since we adjusted to 9
779 #adjust the percentage to be == 100% since we adjusted to 9
780 if a_p > d_p:
780 if a_p > d_p:
781 a_p = a_p - (p_sum - width)
781 a_p = a_p - (p_sum - width)
782 else:
782 else:
783 d_p = d_p - (p_sum - width)
783 d_p = d_p - (p_sum - width)
784
784
785 a_v = a if a > 0 else ''
785 a_v = a if a > 0 else ''
786 d_v = d if d > 0 else ''
786 d_v = d if d > 0 else ''
787
787
788 def cgen(l_type):
788 def cgen(l_type):
789 mapping = {'tr': 'top-right-rounded-corner-mid',
789 mapping = {'tr': 'top-right-rounded-corner-mid',
790 'tl': 'top-left-rounded-corner-mid',
790 'tl': 'top-left-rounded-corner-mid',
791 'br': 'bottom-right-rounded-corner-mid',
791 'br': 'bottom-right-rounded-corner-mid',
792 'bl': 'bottom-left-rounded-corner-mid'}
792 'bl': 'bottom-left-rounded-corner-mid'}
793 map_getter = lambda x: mapping[x]
793 map_getter = lambda x: mapping[x]
794
794
795 if l_type == 'a' and d_v:
795 if l_type == 'a' and d_v:
796 #case when added and deleted are present
796 #case when added and deleted are present
797 return ' '.join(map(map_getter, ['tl', 'bl']))
797 return ' '.join(map(map_getter, ['tl', 'bl']))
798
798
799 if l_type == 'a' and not d_v:
799 if l_type == 'a' and not d_v:
800 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
800 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
801
801
802 if l_type == 'd' and a_v:
802 if l_type == 'd' and a_v:
803 return ' '.join(map(map_getter, ['tr', 'br']))
803 return ' '.join(map(map_getter, ['tr', 'br']))
804
804
805 if l_type == 'd' and not a_v:
805 if l_type == 'd' and not a_v:
806 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
806 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
807
807
808 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
808 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
809 cgen('a'), a_p, a_v
809 cgen('a'), a_p, a_v
810 )
810 )
811 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
811 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
812 cgen('d'), d_p, d_v
812 cgen('d'), d_p, d_v
813 )
813 )
814 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
814 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
815
815
816
816
817 def urlify_text(text_):
817 def urlify_text(text_):
818 import re
818 import re
819
819
820 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
820 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
821 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
821 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
822
822
823 def url_func(match_obj):
823 def url_func(match_obj):
824 url_full = match_obj.groups()[0]
824 url_full = match_obj.groups()[0]
825 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
825 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
826
826
827 return literal(url_pat.sub(url_func, text_))
827 return literal(url_pat.sub(url_func, text_))
828
828
829
829
830 def urlify_changesets(text_, repository):
830 def urlify_changesets(text_, repository):
831 """
831 """
832 Extract revision ids from changeset and make link from them
832 Extract revision ids from changeset and make link from them
833
833
834 :param text_:
834 :param text_:
835 :param repository:
835 :param repository:
836 """
836 """
837 import re
837 import re
838 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
838 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
839
839
840 def url_func(match_obj):
840 def url_func(match_obj):
841 rev = match_obj.groups()[0]
841 rev = match_obj.groups()[0]
842 pref = ''
842 pref = ''
843 if match_obj.group().startswith(' '):
843 if match_obj.group().startswith(' '):
844 pref = ' '
844 pref = ' '
845 tmpl = (
845 tmpl = (
846 '%(pref)s<a class="%(cls)s" href="%(url)s">'
846 '%(pref)s<a class="%(cls)s" href="%(url)s">'
847 '%(rev)s'
847 '%(rev)s'
848 '</a>'
848 '</a>'
849 )
849 )
850 return tmpl % {
850 return tmpl % {
851 'pref': pref,
851 'pref': pref,
852 'cls': 'revision-link',
852 'cls': 'revision-link',
853 'url': url('changeset_home', repo_name=repository, revision=rev),
853 'url': url('changeset_home', repo_name=repository, revision=rev),
854 'rev': rev,
854 'rev': rev,
855 }
855 }
856
856
857 newtext = URL_PAT.sub(url_func, text_)
857 newtext = URL_PAT.sub(url_func, text_)
858
858
859 return newtext
859 return newtext
860
860
861
861
862 def urlify_commit(text_, repository=None, link_=None):
862 def urlify_commit(text_, repository=None, link_=None):
863 """
863 """
864 Parses given text message and makes proper links.
864 Parses given text message and makes proper links.
865 issues are linked to given issue-server, and rest is a changeset link
865 issues are linked to given issue-server, and rest is a changeset link
866 if link_ is given, in other case it's a plain text
866 if link_ is given, in other case it's a plain text
867
867
868 :param text_:
868 :param text_:
869 :param repository:
869 :param repository:
870 :param link_: changeset link
870 :param link_: changeset link
871 """
871 """
872 import re
872 import re
873 import traceback
873 import traceback
874
874
875 def escaper(string):
875 def escaper(string):
876 return string.replace('<', '&lt;').replace('>', '&gt;')
876 return string.replace('<', '&lt;').replace('>', '&gt;')
877
877
878 def linkify_others(t, l):
878 def linkify_others(t, l):
879 urls = re.compile(r'(\<a.*?\<\/a\>)',)
879 urls = re.compile(r'(\<a.*?\<\/a\>)',)
880 links = []
880 links = []
881 for e in urls.split(t):
881 for e in urls.split(t):
882 if not urls.match(e):
882 if not urls.match(e):
883 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
883 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
884 else:
884 else:
885 links.append(e)
885 links.append(e)
886
886
887 return ''.join(links)
887 return ''.join(links)
888
888
889 # urlify changesets - extrac revisions and make link out of them
889 # urlify changesets - extrac revisions and make link out of them
890 text_ = urlify_changesets(escaper(text_), repository)
890 text_ = urlify_changesets(escaper(text_), repository)
891
891
892 try:
892 try:
893 conf = config['app_conf']
893 conf = config['app_conf']
894
894
895 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
895 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
896
896
897 if URL_PAT:
897 if URL_PAT:
898 ISSUE_SERVER_LNK = conf.get('issue_server_link')
898 ISSUE_SERVER_LNK = conf.get('issue_server_link')
899 ISSUE_PREFIX = conf.get('issue_prefix')
899 ISSUE_PREFIX = conf.get('issue_prefix')
900
900
901 def url_func(match_obj):
901 def url_func(match_obj):
902 pref = ''
902 pref = ''
903 if match_obj.group().startswith(' '):
903 if match_obj.group().startswith(' '):
904 pref = ' '
904 pref = ' '
905
905
906 issue_id = ''.join(match_obj.groups())
906 issue_id = ''.join(match_obj.groups())
907 tmpl = (
907 tmpl = (
908 '%(pref)s<a class="%(cls)s" href="%(url)s">'
908 '%(pref)s<a class="%(cls)s" href="%(url)s">'
909 '%(issue-prefix)s%(id-repr)s'
909 '%(issue-prefix)s%(id-repr)s'
910 '</a>'
910 '</a>'
911 )
911 )
912 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
912 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
913 if repository:
913 if repository:
914 url = url.replace('{repo}', repository)
914 url = url.replace('{repo}', repository)
915 repo_name = repository.split(URL_SEP)[-1]
915 repo_name = repository.split(URL_SEP)[-1]
916 url = url.replace('{repo_name}', repo_name)
916 url = url.replace('{repo_name}', repo_name)
917 return tmpl % {
917 return tmpl % {
918 'pref': pref,
918 'pref': pref,
919 'cls': 'issue-tracker-link',
919 'cls': 'issue-tracker-link',
920 'url': url,
920 'url': url,
921 'id-repr': issue_id,
921 'id-repr': issue_id,
922 'issue-prefix': ISSUE_PREFIX,
922 'issue-prefix': ISSUE_PREFIX,
923 'serv': ISSUE_SERVER_LNK,
923 'serv': ISSUE_SERVER_LNK,
924 }
924 }
925
925
926 newtext = URL_PAT.sub(url_func, text_)
926 newtext = URL_PAT.sub(url_func, text_)
927
927
928 if link_:
928 if link_:
929 # wrap not links into final link => link_
929 # wrap not links into final link => link_
930 newtext = linkify_others(newtext, link_)
930 newtext = linkify_others(newtext, link_)
931
931
932 return literal(newtext)
932 return literal(newtext)
933 except:
933 except:
934 log.error(traceback.format_exc())
934 log.error(traceback.format_exc())
935 pass
935 pass
936
936
937 return text_
937 return text_
938
938
939
939
940 def rst(source):
940 def rst(source):
941 return literal('<div class="rst-block">%s</div>' %
941 return literal('<div class="rst-block">%s</div>' %
942 MarkupRenderer.rst(source))
942 MarkupRenderer.rst(source))
943
943
944
944
945 def rst_w_mentions(source):
945 def rst_w_mentions(source):
946 """
946 """
947 Wrapped rst renderer with @mention highlighting
947 Wrapped rst renderer with @mention highlighting
948
948
949 :param source:
949 :param source:
950 """
950 """
951 return literal('<div class="rst-block">%s</div>' %
951 return literal('<div class="rst-block">%s</div>' %
952 MarkupRenderer.rst_with_mentions(source))
952 MarkupRenderer.rst_with_mentions(source))
@@ -1,445 +1,445 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Some simple helper functions
6 Some simple helper functions
7
7
8 :created_on: Jan 5, 2011
8 :created_on: Jan 5, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import re
26 import re
27 from datetime import datetime
27 from datetime import datetime
28 from pylons.i18n.translation import _, ungettext
28 from pylons.i18n.translation import _, ungettext
29 from rhodecode.lib.vcs.utils.lazy import LazyProperty
29 from rhodecode.lib.vcs.utils.lazy import LazyProperty
30
30
31
31
32 def __get_lem():
32 def __get_lem():
33 """
33 """
34 Get language extension map based on what's inside pygments lexers
34 Get language extension map based on what's inside pygments lexers
35 """
35 """
36 from pygments import lexers
36 from pygments import lexers
37 from string import lower
37 from string import lower
38 from collections import defaultdict
38 from collections import defaultdict
39
39
40 d = defaultdict(lambda: [])
40 d = defaultdict(lambda: [])
41
41
42 def __clean(s):
42 def __clean(s):
43 s = s.lstrip('*')
43 s = s.lstrip('*')
44 s = s.lstrip('.')
44 s = s.lstrip('.')
45
45
46 if s.find('[') != -1:
46 if s.find('[') != -1:
47 exts = []
47 exts = []
48 start, stop = s.find('['), s.find(']')
48 start, stop = s.find('['), s.find(']')
49
49
50 for suffix in s[start + 1:stop]:
50 for suffix in s[start + 1:stop]:
51 exts.append(s[:s.find('[')] + suffix)
51 exts.append(s[:s.find('[')] + suffix)
52 return map(lower, exts)
52 return map(lower, exts)
53 else:
53 else:
54 return map(lower, [s])
54 return map(lower, [s])
55
55
56 for lx, t in sorted(lexers.LEXERS.items()):
56 for lx, t in sorted(lexers.LEXERS.items()):
57 m = map(__clean, t[-2])
57 m = map(__clean, t[-2])
58 if m:
58 if m:
59 m = reduce(lambda x, y: x + y, m)
59 m = reduce(lambda x, y: x + y, m)
60 for ext in m:
60 for ext in m:
61 desc = lx.replace('Lexer', '')
61 desc = lx.replace('Lexer', '')
62 d[ext].append(desc)
62 d[ext].append(desc)
63
63
64 return dict(d)
64 return dict(d)
65
65
66 def str2bool(_str):
66 def str2bool(_str):
67 """
67 """
68 returs True/False value from given string, it tries to translate the
68 returs True/False value from given string, it tries to translate the
69 string into boolean
69 string into boolean
70
70
71 :param _str: string value to translate into boolean
71 :param _str: string value to translate into boolean
72 :rtype: boolean
72 :rtype: boolean
73 :returns: boolean from given string
73 :returns: boolean from given string
74 """
74 """
75 if _str is None:
75 if _str is None:
76 return False
76 return False
77 if _str in (True, False):
77 if _str in (True, False):
78 return _str
78 return _str
79 _str = str(_str).strip().lower()
79 _str = str(_str).strip().lower()
80 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
80 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
81
81
82
82
83 def convert_line_endings(line, mode):
83 def convert_line_endings(line, mode):
84 """
84 """
85 Converts a given line "line end" accordingly to given mode
85 Converts a given line "line end" accordingly to given mode
86
86
87 Available modes are::
87 Available modes are::
88 0 - Unix
88 0 - Unix
89 1 - Mac
89 1 - Mac
90 2 - DOS
90 2 - DOS
91
91
92 :param line: given line to convert
92 :param line: given line to convert
93 :param mode: mode to convert to
93 :param mode: mode to convert to
94 :rtype: str
94 :rtype: str
95 :return: converted line according to mode
95 :return: converted line according to mode
96 """
96 """
97 from string import replace
97 from string import replace
98
98
99 if mode == 0:
99 if mode == 0:
100 line = replace(line, '\r\n', '\n')
100 line = replace(line, '\r\n', '\n')
101 line = replace(line, '\r', '\n')
101 line = replace(line, '\r', '\n')
102 elif mode == 1:
102 elif mode == 1:
103 line = replace(line, '\r\n', '\r')
103 line = replace(line, '\r\n', '\r')
104 line = replace(line, '\n', '\r')
104 line = replace(line, '\n', '\r')
105 elif mode == 2:
105 elif mode == 2:
106 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
106 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
107 return line
107 return line
108
108
109
109
110 def detect_mode(line, default):
110 def detect_mode(line, default):
111 """
111 """
112 Detects line break for given line, if line break couldn't be found
112 Detects line break for given line, if line break couldn't be found
113 given default value is returned
113 given default value is returned
114
114
115 :param line: str line
115 :param line: str line
116 :param default: default
116 :param default: default
117 :rtype: int
117 :rtype: int
118 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
118 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
119 """
119 """
120 if line.endswith('\r\n'):
120 if line.endswith('\r\n'):
121 return 2
121 return 2
122 elif line.endswith('\n'):
122 elif line.endswith('\n'):
123 return 0
123 return 0
124 elif line.endswith('\r'):
124 elif line.endswith('\r'):
125 return 1
125 return 1
126 else:
126 else:
127 return default
127 return default
128
128
129
129
130 def generate_api_key(username, salt=None):
130 def generate_api_key(username, salt=None):
131 """
131 """
132 Generates unique API key for given username, if salt is not given
132 Generates unique API key for given username, if salt is not given
133 it'll be generated from some random string
133 it'll be generated from some random string
134
134
135 :param username: username as string
135 :param username: username as string
136 :param salt: salt to hash generate KEY
136 :param salt: salt to hash generate KEY
137 :rtype: str
137 :rtype: str
138 :returns: sha1 hash from username+salt
138 :returns: sha1 hash from username+salt
139 """
139 """
140 from tempfile import _RandomNameSequence
140 from tempfile import _RandomNameSequence
141 import hashlib
141 import hashlib
142
142
143 if salt is None:
143 if salt is None:
144 salt = _RandomNameSequence().next()
144 salt = _RandomNameSequence().next()
145
145
146 return hashlib.sha1(username + salt).hexdigest()
146 return hashlib.sha1(username + salt).hexdigest()
147
147
148
148
149 def safe_unicode(str_, from_encoding=None):
149 def safe_unicode(str_, from_encoding=None):
150 """
150 """
151 safe unicode function. Does few trick to turn str_ into unicode
151 safe unicode function. Does few trick to turn str_ into unicode
152
152
153 In case of UnicodeDecode error we try to return it with encoding detected
153 In case of UnicodeDecode error we try to return it with encoding detected
154 by chardet library if it fails fallback to unicode with errors replaced
154 by chardet library if it fails fallback to unicode with errors replaced
155
155
156 :param str_: string to decode
156 :param str_: string to decode
157 :rtype: unicode
157 :rtype: unicode
158 :returns: unicode object
158 :returns: unicode object
159 """
159 """
160 if isinstance(str_, unicode):
160 if isinstance(str_, unicode):
161 return str_
161 return str_
162
162
163 if not from_encoding:
163 if not from_encoding:
164 import rhodecode
164 import rhodecode
165 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
165 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
166 from_encoding = DEFAULT_ENCODING
166 from_encoding = DEFAULT_ENCODING
167
167
168 try:
168 try:
169 return unicode(str_)
169 return unicode(str_)
170 except UnicodeDecodeError:
170 except UnicodeDecodeError:
171 pass
171 pass
172
172
173 try:
173 try:
174 return unicode(str_, from_encoding)
174 return unicode(str_, from_encoding)
175 except UnicodeDecodeError:
175 except UnicodeDecodeError:
176 pass
176 pass
177
177
178 try:
178 try:
179 import chardet
179 import chardet
180 encoding = chardet.detect(str_)['encoding']
180 encoding = chardet.detect(str_)['encoding']
181 if encoding is None:
181 if encoding is None:
182 raise Exception()
182 raise Exception()
183 return str_.decode(encoding)
183 return str_.decode(encoding)
184 except (ImportError, UnicodeDecodeError, Exception):
184 except (ImportError, UnicodeDecodeError, Exception):
185 return unicode(str_, from_encoding, 'replace')
185 return unicode(str_, from_encoding, 'replace')
186
186
187
187
188 def safe_str(unicode_, to_encoding=None):
188 def safe_str(unicode_, to_encoding=None):
189 """
189 """
190 safe str function. Does few trick to turn unicode_ into string
190 safe str function. Does few trick to turn unicode_ into string
191
191
192 In case of UnicodeEncodeError we try to return it with encoding detected
192 In case of UnicodeEncodeError we try to return it with encoding detected
193 by chardet library if it fails fallback to string with errors replaced
193 by chardet library if it fails fallback to string with errors replaced
194
194
195 :param unicode_: unicode to encode
195 :param unicode_: unicode to encode
196 :rtype: str
196 :rtype: str
197 :returns: str object
197 :returns: str object
198 """
198 """
199
199
200 # if it's not basestr cast to str
200 # if it's not basestr cast to str
201 if not isinstance(unicode_, basestring):
201 if not isinstance(unicode_, basestring):
202 return str(unicode_)
202 return str(unicode_)
203
203
204 if isinstance(unicode_, str):
204 if isinstance(unicode_, str):
205 return unicode_
205 return unicode_
206
206
207 if not to_encoding:
207 if not to_encoding:
208 import rhodecode
208 import rhodecode
209 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
209 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
210 to_encoding = DEFAULT_ENCODING
210 to_encoding = DEFAULT_ENCODING
211
211
212 try:
212 try:
213 return unicode_.encode(to_encoding)
213 return unicode_.encode(to_encoding)
214 except UnicodeEncodeError:
214 except UnicodeEncodeError:
215 pass
215 pass
216
216
217 try:
217 try:
218 import chardet
218 import chardet
219 encoding = chardet.detect(unicode_)['encoding']
219 encoding = chardet.detect(unicode_)['encoding']
220 if encoding is None:
220 if encoding is None:
221 raise UnicodeEncodeError()
221 raise UnicodeEncodeError()
222
222
223 return unicode_.encode(encoding)
223 return unicode_.encode(encoding)
224 except (ImportError, UnicodeEncodeError):
224 except (ImportError, UnicodeEncodeError):
225 return unicode_.encode(to_encoding, 'replace')
225 return unicode_.encode(to_encoding, 'replace')
226
226
227 return safe_str
227 return safe_str
228
228
229
229
230 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
230 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
231 """
231 """
232 Custom engine_from_config functions that makes sure we use NullPool for
232 Custom engine_from_config functions that makes sure we use NullPool for
233 file based sqlite databases. This prevents errors on sqlite. This only
233 file based sqlite databases. This prevents errors on sqlite. This only
234 applies to sqlalchemy versions < 0.7.0
234 applies to sqlalchemy versions < 0.7.0
235
235
236 """
236 """
237 import sqlalchemy
237 import sqlalchemy
238 from sqlalchemy import engine_from_config as efc
238 from sqlalchemy import engine_from_config as efc
239 import logging
239 import logging
240
240
241 if int(sqlalchemy.__version__.split('.')[1]) < 7:
241 if int(sqlalchemy.__version__.split('.')[1]) < 7:
242
242
243 # This solution should work for sqlalchemy < 0.7.0, and should use
243 # This solution should work for sqlalchemy < 0.7.0, and should use
244 # proxy=TimerProxy() for execution time profiling
244 # proxy=TimerProxy() for execution time profiling
245
245
246 from sqlalchemy.pool import NullPool
246 from sqlalchemy.pool import NullPool
247 url = configuration[prefix + 'url']
247 url = configuration[prefix + 'url']
248
248
249 if url.startswith('sqlite'):
249 if url.startswith('sqlite'):
250 kwargs.update({'poolclass': NullPool})
250 kwargs.update({'poolclass': NullPool})
251 return efc(configuration, prefix, **kwargs)
251 return efc(configuration, prefix, **kwargs)
252 else:
252 else:
253 import time
253 import time
254 from sqlalchemy import event
254 from sqlalchemy import event
255 from sqlalchemy.engine import Engine
255 from sqlalchemy.engine import Engine
256
256
257 log = logging.getLogger('sqlalchemy.engine')
257 log = logging.getLogger('sqlalchemy.engine')
258 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
258 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
259 engine = efc(configuration, prefix, **kwargs)
259 engine = efc(configuration, prefix, **kwargs)
260
260
261 def color_sql(sql):
261 def color_sql(sql):
262 COLOR_SEQ = "\033[1;%dm"
262 COLOR_SEQ = "\033[1;%dm"
263 COLOR_SQL = YELLOW
263 COLOR_SQL = YELLOW
264 normal = '\x1b[0m'
264 normal = '\x1b[0m'
265 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
265 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
266
266
267 if configuration['debug']:
267 if configuration['debug']:
268 #attach events only for debug configuration
268 #attach events only for debug configuration
269
269
270 def before_cursor_execute(conn, cursor, statement,
270 def before_cursor_execute(conn, cursor, statement,
271 parameters, context, executemany):
271 parameters, context, executemany):
272 context._query_start_time = time.time()
272 context._query_start_time = time.time()
273 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
273 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
274
274
275
275
276 def after_cursor_execute(conn, cursor, statement,
276 def after_cursor_execute(conn, cursor, statement,
277 parameters, context, executemany):
277 parameters, context, executemany):
278 total = time.time() - context._query_start_time
278 total = time.time() - context._query_start_time
279 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
279 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
280
280
281 event.listen(engine, "before_cursor_execute",
281 event.listen(engine, "before_cursor_execute",
282 before_cursor_execute)
282 before_cursor_execute)
283 event.listen(engine, "after_cursor_execute",
283 event.listen(engine, "after_cursor_execute",
284 after_cursor_execute)
284 after_cursor_execute)
285
285
286 return engine
286 return engine
287
287
288
288
289 def age(prevdate):
289 def age(prevdate):
290 """
290 """
291 turns a datetime into an age string.
291 turns a datetime into an age string.
292
292
293 :param prevdate: datetime object
293 :param prevdate: datetime object
294 :rtype: unicode
294 :rtype: unicode
295 :returns: unicode words describing age
295 :returns: unicode words describing age
296 """
296 """
297
297
298 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
298 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
299 deltas = {}
299 deltas = {}
300
300
301 # Get date parts deltas
301 # Get date parts deltas
302 now = datetime.now()
302 now = datetime.now()
303 for part in order:
303 for part in order:
304 deltas[part] = getattr(now, part) - getattr(prevdate, part)
304 deltas[part] = getattr(now, part) - getattr(prevdate, part)
305
305
306 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
306 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
307 # not 1 hour, -59 minutes and -59 seconds)
307 # not 1 hour, -59 minutes and -59 seconds)
308
308
309 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
309 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
310 part = order[num]
310 part = order[num]
311 carry_part = order[num - 1]
311 carry_part = order[num - 1]
312
312
313 if deltas[part] < 0:
313 if deltas[part] < 0:
314 deltas[part] += length
314 deltas[part] += length
315 deltas[carry_part] -= 1
315 deltas[carry_part] -= 1
316
316
317 # Same thing for days except that the increment depends on the (variable)
317 # Same thing for days except that the increment depends on the (variable)
318 # number of days in the month
318 # number of days in the month
319 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
319 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
320 if deltas['day'] < 0:
320 if deltas['day'] < 0:
321 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
321 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
322 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
322 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
323 deltas['day'] += 29
323 deltas['day'] += 29
324 else:
324 else:
325 deltas['day'] += month_lengths[prevdate.month - 1]
325 deltas['day'] += month_lengths[prevdate.month - 1]
326
326
327 deltas['month'] -= 1
327 deltas['month'] -= 1
328
328
329 if deltas['month'] < 0:
329 if deltas['month'] < 0:
330 deltas['month'] += 12
330 deltas['month'] += 12
331 deltas['year'] -= 1
331 deltas['year'] -= 1
332
332
333 # Format the result
333 # Format the result
334 fmt_funcs = {
334 fmt_funcs = {
335 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
335 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
336 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
336 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
337 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
337 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
338 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
338 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
339 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
339 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
340 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
340 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
341 }
341 }
342
342
343 for i, part in enumerate(order):
343 for i, part in enumerate(order):
344 value = deltas[part]
344 value = deltas[part]
345 if value == 0:
345 if value == 0:
346 continue
346 continue
347
347
348 if i < 5:
348 if i < 5:
349 sub_part = order[i + 1]
349 sub_part = order[i + 1]
350 sub_value = deltas[sub_part]
350 sub_value = deltas[sub_part]
351 else:
351 else:
352 sub_value = 0
352 sub_value = 0
353
353
354 if sub_value == 0:
354 if sub_value == 0:
355 return _(u'%s ago') % fmt_funcs[part](value)
355 return _(u'%s ago') % fmt_funcs[part](value)
356
356
357 return _(u'%s and %s ago') % (fmt_funcs[part](value),
357 return _(u'%s and %s ago') % (fmt_funcs[part](value),
358 fmt_funcs[sub_part](sub_value))
358 fmt_funcs[sub_part](sub_value))
359
359
360 return _(u'just now')
360 return _(u'just now')
361
361
362
362
363 def uri_filter(uri):
363 def uri_filter(uri):
364 """
364 """
365 Removes user:password from given url string
365 Removes user:password from given url string
366
366
367 :param uri:
367 :param uri:
368 :rtype: unicode
368 :rtype: unicode
369 :returns: filtered list of strings
369 :returns: filtered list of strings
370 """
370 """
371 if not uri:
371 if not uri:
372 return ''
372 return ''
373
373
374 proto = ''
374 proto = ''
375
375
376 for pat in ('https://', 'http://'):
376 for pat in ('https://', 'http://'):
377 if uri.startswith(pat):
377 if uri.startswith(pat):
378 uri = uri[len(pat):]
378 uri = uri[len(pat):]
379 proto = pat
379 proto = pat
380 break
380 break
381
381
382 # remove passwords and username
382 # remove passwords and username
383 uri = uri[uri.find('@') + 1:]
383 uri = uri[uri.find('@') + 1:]
384
384
385 # get the port
385 # get the port
386 cred_pos = uri.find(':')
386 cred_pos = uri.find(':')
387 if cred_pos == -1:
387 if cred_pos == -1:
388 host, port = uri, None
388 host, port = uri, None
389 else:
389 else:
390 host, port = uri[:cred_pos], uri[cred_pos + 1:]
390 host, port = uri[:cred_pos], uri[cred_pos + 1:]
391
391
392 return filter(None, [proto, host, port])
392 return filter(None, [proto, host, port])
393
393
394
394
395 def credentials_filter(uri):
395 def credentials_filter(uri):
396 """
396 """
397 Returns a url with removed credentials
397 Returns a url with removed credentials
398
398
399 :param uri:
399 :param uri:
400 """
400 """
401
401
402 uri = uri_filter(uri)
402 uri = uri_filter(uri)
403 #check if we have port
403 #check if we have port
404 if len(uri) > 2 and uri[2]:
404 if len(uri) > 2 and uri[2]:
405 uri[2] = ':' + uri[2]
405 uri[2] = ':' + uri[2]
406
406
407 return ''.join(uri)
407 return ''.join(uri)
408
408
409
409
410 def get_changeset_safe(repo, rev):
410 def get_changeset_safe(repo, rev):
411 """
411 """
412 Safe version of get_changeset if this changeset doesn't exists for a
412 Safe version of get_changeset if this changeset doesn't exists for a
413 repo it returns a Dummy one instead
413 repo it returns a Dummy one instead
414
414
415 :param repo:
415 :param repo:
416 :param rev:
416 :param rev:
417 """
417 """
418 from rhodecode.lib.vcs.backends.base import BaseRepository
418 from rhodecode.lib.vcs.backends.base import BaseRepository
419 from rhodecode.lib.vcs.exceptions import RepositoryError
419 from rhodecode.lib.vcs.exceptions import RepositoryError
420 if not isinstance(repo, BaseRepository):
420 if not isinstance(repo, BaseRepository):
421 raise Exception('You must pass an Repository '
421 raise Exception('You must pass an Repository '
422 'object as first argument got %s', type(repo))
422 'object as first argument got %s', type(repo))
423
423
424 try:
424 try:
425 cs = repo.get_changeset(rev)
425 cs = repo.get_changeset(rev)
426 except RepositoryError:
426 except RepositoryError:
427 from rhodecode.lib.utils import EmptyChangeset
427 from rhodecode.lib.utils import EmptyChangeset
428 cs = EmptyChangeset(requested_revision=rev)
428 cs = EmptyChangeset(requested_revision=rev)
429 return cs
429 return cs
430
430
431
431
432 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
432 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
433
433
434
434
435 def extract_mentioned_users(s):
435 def extract_mentioned_users(s):
436 """
436 """
437 Returns unique usernames from given string s that have @mention
437 Returns unique usernames from given string s that have @mention
438
438
439 :param s: string to get mentions
439 :param s: string to get mentions
440 """
440 """
441 usrs = set()
441 usrs = set()
442 for username in re.findall(MENTIONS_REGEX, s):
442 for username in re.findall(MENTIONS_REGEX, s):
443 usrs.add(username)
443 usrs.add(username)
444
444
445 return sorted(list(usrs), key=lambda k: k.lower())
445 return sorted(list(usrs), key=lambda k: k.lower())
@@ -1,554 +1,554 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 vcs.backends.git
3 vcs.backends.git
4 ~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~
5
5
6 Git backend implementation.
6 Git backend implementation.
7
7
8 :created_on: Apr 8, 2010
8 :created_on: Apr 8, 2010
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 """
10 """
11
11
12 import os
12 import os
13 import re
13 import re
14 import time
14 import time
15 import posixpath
15 import posixpath
16 from dulwich.repo import Repo, NotGitRepository
16 from dulwich.repo import Repo, NotGitRepository
17 #from dulwich.config import ConfigFile
17 #from dulwich.config import ConfigFile
18 from string import Template
18 from string import Template
19 from subprocess import Popen, PIPE
19 from subprocess import Popen, PIPE
20 from rhodecode.lib.vcs.backends.base import BaseRepository
20 from rhodecode.lib.vcs.backends.base import BaseRepository
21 from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError
21 from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError
22 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
22 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
23 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
23 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
24 from rhodecode.lib.vcs.exceptions import RepositoryError
24 from rhodecode.lib.vcs.exceptions import RepositoryError
25 from rhodecode.lib.vcs.exceptions import TagAlreadyExistError
25 from rhodecode.lib.vcs.exceptions import TagAlreadyExistError
26 from rhodecode.lib.vcs.exceptions import TagDoesNotExistError
26 from rhodecode.lib.vcs.exceptions import TagDoesNotExistError
27 from rhodecode.lib.vcs.utils import safe_unicode, makedate, date_fromtimestamp
27 from rhodecode.lib.vcs.utils import safe_unicode, makedate, date_fromtimestamp
28 from rhodecode.lib.vcs.utils.lazy import LazyProperty
28 from rhodecode.lib.vcs.utils.lazy import LazyProperty
29 from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict
29 from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict
30 from rhodecode.lib.vcs.utils.paths import abspath
30 from rhodecode.lib.vcs.utils.paths import abspath
31 from rhodecode.lib.vcs.utils.paths import get_user_home
31 from rhodecode.lib.vcs.utils.paths import get_user_home
32 from .workdir import GitWorkdir
32 from .workdir import GitWorkdir
33 from .changeset import GitChangeset
33 from .changeset import GitChangeset
34 from .inmemory import GitInMemoryChangeset
34 from .inmemory import GitInMemoryChangeset
35 from .config import ConfigFile
35 from .config import ConfigFile
36
36
37
37
38 class GitRepository(BaseRepository):
38 class GitRepository(BaseRepository):
39 """
39 """
40 Git repository backend.
40 Git repository backend.
41 """
41 """
42 DEFAULT_BRANCH_NAME = 'master'
42 DEFAULT_BRANCH_NAME = 'master'
43 scm = 'git'
43 scm = 'git'
44
44
45 def __init__(self, repo_path, create=False, src_url=None,
45 def __init__(self, repo_path, create=False, src_url=None,
46 update_after_clone=False, bare=False):
46 update_after_clone=False, bare=False):
47
47
48 self.path = abspath(repo_path)
48 self.path = abspath(repo_path)
49 self._repo = self._get_repo(create, src_url, update_after_clone, bare)
49 self._repo = self._get_repo(create, src_url, update_after_clone, bare)
50 #temporary set that to now at later we will move it to constructor
50 #temporary set that to now at later we will move it to constructor
51 baseui = None
51 baseui = None
52 if baseui is None:
52 if baseui is None:
53 from mercurial.ui import ui
53 from mercurial.ui import ui
54 baseui = ui()
54 baseui = ui()
55 # patch the instance of GitRepo with an "FAKE" ui object to add
55 # patch the instance of GitRepo with an "FAKE" ui object to add
56 # compatibility layer with Mercurial
56 # compatibility layer with Mercurial
57 setattr(self._repo, 'ui', baseui)
57 setattr(self._repo, 'ui', baseui)
58
58
59 try:
59 try:
60 self.head = self._repo.head()
60 self.head = self._repo.head()
61 except KeyError:
61 except KeyError:
62 self.head = None
62 self.head = None
63
63
64 self._config_files = [
64 self._config_files = [
65 bare and abspath(self.path, 'config') or abspath(self.path, '.git',
65 bare and abspath(self.path, 'config') or abspath(self.path, '.git',
66 'config'),
66 'config'),
67 abspath(get_user_home(), '.gitconfig'),
67 abspath(get_user_home(), '.gitconfig'),
68 ]
68 ]
69 self.bare = self._repo.bare
69 self.bare = self._repo.bare
70
70
71 @LazyProperty
71 @LazyProperty
72 def revisions(self):
72 def revisions(self):
73 """
73 """
74 Returns list of revisions' ids, in ascending order. Being lazy
74 Returns list of revisions' ids, in ascending order. Being lazy
75 attribute allows external tools to inject shas from cache.
75 attribute allows external tools to inject shas from cache.
76 """
76 """
77 return self._get_all_revisions()
77 return self._get_all_revisions()
78
78
79 def run_git_command(self, cmd):
79 def run_git_command(self, cmd):
80 """
80 """
81 Runs given ``cmd`` as git command and returns tuple
81 Runs given ``cmd`` as git command and returns tuple
82 (returncode, stdout, stderr).
82 (returncode, stdout, stderr).
83
83
84 .. note::
84 .. note::
85 This method exists only until log/blame functionality is implemented
85 This method exists only until log/blame functionality is implemented
86 at Dulwich (see https://bugs.launchpad.net/bugs/645142). Parsing
86 at Dulwich (see https://bugs.launchpad.net/bugs/645142). Parsing
87 os command's output is road to hell...
87 os command's output is road to hell...
88
88
89 :param cmd: git command to be executed
89 :param cmd: git command to be executed
90 """
90 """
91
91
92 _copts = ['-c', 'core.quotepath=false', ]
92 _copts = ['-c', 'core.quotepath=false', ]
93 _str_cmd = False
93 _str_cmd = False
94 if isinstance(cmd, basestring):
94 if isinstance(cmd, basestring):
95 cmd = [cmd]
95 cmd = [cmd]
96 _str_cmd = True
96 _str_cmd = True
97
97
98 gitenv = os.environ
98 gitenv = os.environ
99 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
99 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
100
100
101 cmd = ['git'] + _copts + cmd
101 cmd = ['git'] + _copts + cmd
102 if _str_cmd:
102 if _str_cmd:
103 cmd = ' '.join(cmd)
103 cmd = ' '.join(cmd)
104 try:
104 try:
105 opts = dict(
105 opts = dict(
106 shell=isinstance(cmd, basestring),
106 shell=isinstance(cmd, basestring),
107 stdout=PIPE,
107 stdout=PIPE,
108 stderr=PIPE,
108 stderr=PIPE,
109 env=gitenv,
109 env=gitenv,
110 )
110 )
111 if os.path.isdir(self.path):
111 if os.path.isdir(self.path):
112 opts['cwd'] = self.path
112 opts['cwd'] = self.path
113 p = Popen(cmd, **opts)
113 p = Popen(cmd, **opts)
114 except OSError, err:
114 except OSError, err:
115 raise RepositoryError("Couldn't run git command (%s).\n"
115 raise RepositoryError("Couldn't run git command (%s).\n"
116 "Original error was:%s" % (cmd, err))
116 "Original error was:%s" % (cmd, err))
117 so, se = p.communicate()
117 so, se = p.communicate()
118 if not se.startswith("fatal: bad default revision 'HEAD'") and \
118 if not se.startswith("fatal: bad default revision 'HEAD'") and \
119 p.returncode != 0:
119 p.returncode != 0:
120 raise RepositoryError("Couldn't run git command (%s).\n"
120 raise RepositoryError("Couldn't run git command (%s).\n"
121 "stderr:\n%s" % (cmd, se))
121 "stderr:\n%s" % (cmd, se))
122 return so, se
122 return so, se
123
123
124 def _check_url(self, url):
124 def _check_url(self, url):
125 """
125 """
126 Functon will check given url and try to verify if it's a valid
126 Functon will check given url and try to verify if it's a valid
127 link. Sometimes it may happened that mercurial will issue basic
127 link. Sometimes it may happened that mercurial will issue basic
128 auth request that can cause whole API to hang when used from python
128 auth request that can cause whole API to hang when used from python
129 or other external calls.
129 or other external calls.
130
130
131 On failures it'll raise urllib2.HTTPError
131 On failures it'll raise urllib2.HTTPError
132 """
132 """
133
133
134 #TODO: implement this
134 #TODO: implement this
135 pass
135 pass
136
136
137 def _get_repo(self, create, src_url=None, update_after_clone=False,
137 def _get_repo(self, create, src_url=None, update_after_clone=False,
138 bare=False):
138 bare=False):
139 if create and os.path.exists(self.path):
139 if create and os.path.exists(self.path):
140 raise RepositoryError("Location already exist")
140 raise RepositoryError("Location already exist")
141 if src_url and not create:
141 if src_url and not create:
142 raise RepositoryError("Create should be set to True if src_url is "
142 raise RepositoryError("Create should be set to True if src_url is "
143 "given (clone operation creates repository)")
143 "given (clone operation creates repository)")
144 try:
144 try:
145 if create and src_url:
145 if create and src_url:
146 self._check_url(src_url)
146 self._check_url(src_url)
147 self.clone(src_url, update_after_clone, bare)
147 self.clone(src_url, update_after_clone, bare)
148 return Repo(self.path)
148 return Repo(self.path)
149 elif create:
149 elif create:
150 os.mkdir(self.path)
150 os.mkdir(self.path)
151 if bare:
151 if bare:
152 return Repo.init_bare(self.path)
152 return Repo.init_bare(self.path)
153 else:
153 else:
154 return Repo.init(self.path)
154 return Repo.init(self.path)
155 else:
155 else:
156 return Repo(self.path)
156 return Repo(self.path)
157 except (NotGitRepository, OSError), err:
157 except (NotGitRepository, OSError), err:
158 raise RepositoryError(err)
158 raise RepositoryError(err)
159
159
160 def _get_all_revisions(self):
160 def _get_all_revisions(self):
161 cmd = 'rev-list --all --date-order'
161 cmd = 'rev-list --all --date-order'
162 try:
162 try:
163 so, se = self.run_git_command(cmd)
163 so, se = self.run_git_command(cmd)
164 except RepositoryError:
164 except RepositoryError:
165 # Can be raised for empty repositories
165 # Can be raised for empty repositories
166 return []
166 return []
167 revisions = so.splitlines()
167 revisions = so.splitlines()
168 revisions.reverse()
168 revisions.reverse()
169 return revisions
169 return revisions
170
170
171 def _get_revision(self, revision):
171 def _get_revision(self, revision):
172 """
172 """
173 For git backend we always return integer here. This way we ensure
173 For git backend we always return integer here. This way we ensure
174 that changset's revision attribute would become integer.
174 that changset's revision attribute would become integer.
175 """
175 """
176 pattern = re.compile(r'^[[0-9a-fA-F]{12}|[0-9a-fA-F]{40}]$')
176 pattern = re.compile(r'^[[0-9a-fA-F]{12}|[0-9a-fA-F]{40}]$')
177 is_bstr = lambda o: isinstance(o, (str, unicode))
177 is_bstr = lambda o: isinstance(o, (str, unicode))
178 is_null = lambda o: len(o) == revision.count('0')
178 is_null = lambda o: len(o) == revision.count('0')
179
179
180 if len(self.revisions) == 0:
180 if len(self.revisions) == 0:
181 raise EmptyRepositoryError("There are no changesets yet")
181 raise EmptyRepositoryError("There are no changesets yet")
182
182
183 if revision in (None, '', 'tip', 'HEAD', 'head', -1):
183 if revision in (None, '', 'tip', 'HEAD', 'head', -1):
184 revision = self.revisions[-1]
184 revision = self.revisions[-1]
185
185
186 if ((is_bstr(revision) and revision.isdigit() and len(revision) < 12)
186 if ((is_bstr(revision) and revision.isdigit() and len(revision) < 12)
187 or isinstance(revision, int) or is_null(revision)):
187 or isinstance(revision, int) or is_null(revision)):
188 try:
188 try:
189 revision = self.revisions[int(revision)]
189 revision = self.revisions[int(revision)]
190 except:
190 except:
191 raise ChangesetDoesNotExistError("Revision %r does not exist "
191 raise ChangesetDoesNotExistError("Revision %r does not exist "
192 "for this repository %s" % (revision, self))
192 "for this repository %s" % (revision, self))
193
193
194 elif is_bstr(revision):
194 elif is_bstr(revision):
195 if not pattern.match(revision) or revision not in self.revisions:
195 if not pattern.match(revision) or revision not in self.revisions:
196 raise ChangesetDoesNotExistError("Revision %r does not exist "
196 raise ChangesetDoesNotExistError("Revision %r does not exist "
197 "for this repository %s" % (revision, self))
197 "for this repository %s" % (revision, self))
198
198
199 # Ensure we return full id
199 # Ensure we return full id
200 if not pattern.match(str(revision)):
200 if not pattern.match(str(revision)):
201 raise ChangesetDoesNotExistError("Given revision %r not recognized"
201 raise ChangesetDoesNotExistError("Given revision %r not recognized"
202 % revision)
202 % revision)
203 return revision
203 return revision
204
204
205 def _get_archives(self, archive_name='tip'):
205 def _get_archives(self, archive_name='tip'):
206
206
207 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
207 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
208 yield {"type": i[0], "extension": i[1], "node": archive_name}
208 yield {"type": i[0], "extension": i[1], "node": archive_name}
209
209
210 def _get_url(self, url):
210 def _get_url(self, url):
211 """
211 """
212 Returns normalized url. If schema is not given, would fall to
212 Returns normalized url. If schema is not given, would fall to
213 filesystem (``file:///``) schema.
213 filesystem (``file:///``) schema.
214 """
214 """
215 url = str(url)
215 url = str(url)
216 if url != 'default' and not '://' in url:
216 if url != 'default' and not '://' in url:
217 url = ':///'.join(('file', url))
217 url = ':///'.join(('file', url))
218 return url
218 return url
219
219
220 @LazyProperty
220 @LazyProperty
221 def name(self):
221 def name(self):
222 return os.path.basename(self.path)
222 return os.path.basename(self.path)
223
223
224 @LazyProperty
224 @LazyProperty
225 def last_change(self):
225 def last_change(self):
226 """
226 """
227 Returns last change made on this repository as datetime object
227 Returns last change made on this repository as datetime object
228 """
228 """
229 return date_fromtimestamp(self._get_mtime(), makedate()[1])
229 return date_fromtimestamp(self._get_mtime(), makedate()[1])
230
230
231 def _get_mtime(self):
231 def _get_mtime(self):
232 try:
232 try:
233 return time.mktime(self.get_changeset().date.timetuple())
233 return time.mktime(self.get_changeset().date.timetuple())
234 except RepositoryError:
234 except RepositoryError:
235 idx_loc = '' if self.bare else '.git'
235 idx_loc = '' if self.bare else '.git'
236 # fallback to filesystem
236 # fallback to filesystem
237 in_path = os.path.join(self.path, idx_loc, "index")
237 in_path = os.path.join(self.path, idx_loc, "index")
238 he_path = os.path.join(self.path, idx_loc, "HEAD")
238 he_path = os.path.join(self.path, idx_loc, "HEAD")
239 if os.path.exists(in_path):
239 if os.path.exists(in_path):
240 return os.stat(in_path).st_mtime
240 return os.stat(in_path).st_mtime
241 else:
241 else:
242 return os.stat(he_path).st_mtime
242 return os.stat(he_path).st_mtime
243
243
244 @LazyProperty
244 @LazyProperty
245 def description(self):
245 def description(self):
246 idx_loc = '' if self.bare else '.git'
246 idx_loc = '' if self.bare else '.git'
247 undefined_description = u'unknown'
247 undefined_description = u'unknown'
248 description_path = os.path.join(self.path, idx_loc, 'description')
248 description_path = os.path.join(self.path, idx_loc, 'description')
249 if os.path.isfile(description_path):
249 if os.path.isfile(description_path):
250 return safe_unicode(open(description_path).read())
250 return safe_unicode(open(description_path).read())
251 else:
251 else:
252 return undefined_description
252 return undefined_description
253
253
254 @LazyProperty
254 @LazyProperty
255 def contact(self):
255 def contact(self):
256 undefined_contact = u'Unknown'
256 undefined_contact = u'Unknown'
257 return undefined_contact
257 return undefined_contact
258
258
259 @property
259 @property
260 def branches(self):
260 def branches(self):
261 if not self.revisions:
261 if not self.revisions:
262 return {}
262 return {}
263 refs = self._repo.refs.as_dict()
263 refs = self._repo.refs.as_dict()
264 sortkey = lambda ctx: ctx[0]
264 sortkey = lambda ctx: ctx[0]
265 _branches = [('/'.join(ref.split('/')[2:]), head)
265 _branches = [('/'.join(ref.split('/')[2:]), head)
266 for ref, head in refs.items()
266 for ref, head in refs.items()
267 if ref.startswith('refs/heads/') and not ref.endswith('/HEAD')]
267 if ref.startswith('refs/heads/') and not ref.endswith('/HEAD')]
268 return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
268 return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
269
269
270 def _heads(self, reverse=False):
270 def _heads(self, reverse=False):
271 refs = self._repo.get_refs()
271 refs = self._repo.get_refs()
272 heads = {}
272 heads = {}
273
273
274 for key, val in refs.items():
274 for key, val in refs.items():
275 for ref_key in ['refs/heads/', 'refs/remotes/origin/']:
275 for ref_key in ['refs/heads/', 'refs/remotes/origin/']:
276 if key.startswith(ref_key):
276 if key.startswith(ref_key):
277 n = key[len(ref_key):]
277 n = key[len(ref_key):]
278 if n not in ['HEAD']:
278 if n not in ['HEAD']:
279 heads[n] = val
279 heads[n] = val
280
280
281 return heads if reverse else dict((y,x) for x,y in heads.iteritems())
281 return heads if reverse else dict((y,x) for x,y in heads.iteritems())
282
282
283 def _get_tags(self):
283 def _get_tags(self):
284 if not self.revisions:
284 if not self.revisions:
285 return {}
285 return {}
286 sortkey = lambda ctx: ctx[0]
286 sortkey = lambda ctx: ctx[0]
287 _tags = [('/'.join(ref.split('/')[2:]), head) for ref, head in
287 _tags = [('/'.join(ref.split('/')[2:]), head) for ref, head in
288 self._repo.get_refs().items() if ref.startswith('refs/tags/')]
288 self._repo.get_refs().items() if ref.startswith('refs/tags/')]
289 return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
289 return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
290
290
291 @LazyProperty
291 @LazyProperty
292 def tags(self):
292 def tags(self):
293 return self._get_tags()
293 return self._get_tags()
294
294
295 def tag(self, name, user, revision=None, message=None, date=None,
295 def tag(self, name, user, revision=None, message=None, date=None,
296 **kwargs):
296 **kwargs):
297 """
297 """
298 Creates and returns a tag for the given ``revision``.
298 Creates and returns a tag for the given ``revision``.
299
299
300 :param name: name for new tag
300 :param name: name for new tag
301 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
301 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
302 :param revision: changeset id for which new tag would be created
302 :param revision: changeset id for which new tag would be created
303 :param message: message of the tag's commit
303 :param message: message of the tag's commit
304 :param date: date of tag's commit
304 :param date: date of tag's commit
305
305
306 :raises TagAlreadyExistError: if tag with same name already exists
306 :raises TagAlreadyExistError: if tag with same name already exists
307 """
307 """
308 if name in self.tags:
308 if name in self.tags:
309 raise TagAlreadyExistError("Tag %s already exists" % name)
309 raise TagAlreadyExistError("Tag %s already exists" % name)
310 changeset = self.get_changeset(revision)
310 changeset = self.get_changeset(revision)
311 message = message or "Added tag %s for commit %s" % (name,
311 message = message or "Added tag %s for commit %s" % (name,
312 changeset.raw_id)
312 changeset.raw_id)
313 self._repo.refs["refs/tags/%s" % name] = changeset._commit.id
313 self._repo.refs["refs/tags/%s" % name] = changeset._commit.id
314
314
315 self.tags = self._get_tags()
315 self.tags = self._get_tags()
316 return changeset
316 return changeset
317
317
318 def remove_tag(self, name, user, message=None, date=None):
318 def remove_tag(self, name, user, message=None, date=None):
319 """
319 """
320 Removes tag with the given ``name``.
320 Removes tag with the given ``name``.
321
321
322 :param name: name of the tag to be removed
322 :param name: name of the tag to be removed
323 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
323 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
324 :param message: message of the tag's removal commit
324 :param message: message of the tag's removal commit
325 :param date: date of tag's removal commit
325 :param date: date of tag's removal commit
326
326
327 :raises TagDoesNotExistError: if tag with given name does not exists
327 :raises TagDoesNotExistError: if tag with given name does not exists
328 """
328 """
329 if name not in self.tags:
329 if name not in self.tags:
330 raise TagDoesNotExistError("Tag %s does not exist" % name)
330 raise TagDoesNotExistError("Tag %s does not exist" % name)
331 tagpath = posixpath.join(self._repo.refs.path, 'refs', 'tags', name)
331 tagpath = posixpath.join(self._repo.refs.path, 'refs', 'tags', name)
332 try:
332 try:
333 os.remove(tagpath)
333 os.remove(tagpath)
334 self.tags = self._get_tags()
334 self.tags = self._get_tags()
335 except OSError, e:
335 except OSError, e:
336 raise RepositoryError(e.strerror)
336 raise RepositoryError(e.strerror)
337
337
338 def get_changeset(self, revision=None):
338 def get_changeset(self, revision=None):
339 """
339 """
340 Returns ``GitChangeset`` object representing commit from git repository
340 Returns ``GitChangeset`` object representing commit from git repository
341 at the given revision or head (most recent commit) if None given.
341 at the given revision or head (most recent commit) if None given.
342 """
342 """
343 if isinstance(revision, GitChangeset):
343 if isinstance(revision, GitChangeset):
344 return revision
344 return revision
345 revision = self._get_revision(revision)
345 revision = self._get_revision(revision)
346 changeset = GitChangeset(repository=self, revision=revision)
346 changeset = GitChangeset(repository=self, revision=revision)
347 return changeset
347 return changeset
348
348
349 def get_changesets(self, start=None, end=None, start_date=None,
349 def get_changesets(self, start=None, end=None, start_date=None,
350 end_date=None, branch_name=None, reverse=False):
350 end_date=None, branch_name=None, reverse=False):
351 """
351 """
352 Returns iterator of ``GitChangeset`` objects from start to end (both
352 Returns iterator of ``GitChangeset`` objects from start to end (both
353 are inclusive), in ascending date order (unless ``reverse`` is set).
353 are inclusive), in ascending date order (unless ``reverse`` is set).
354
354
355 :param start: changeset ID, as str; first returned changeset
355 :param start: changeset ID, as str; first returned changeset
356 :param end: changeset ID, as str; last returned changeset
356 :param end: changeset ID, as str; last returned changeset
357 :param start_date: if specified, changesets with commit date less than
357 :param start_date: if specified, changesets with commit date less than
358 ``start_date`` would be filtered out from returned set
358 ``start_date`` would be filtered out from returned set
359 :param end_date: if specified, changesets with commit date greater than
359 :param end_date: if specified, changesets with commit date greater than
360 ``end_date`` would be filtered out from returned set
360 ``end_date`` would be filtered out from returned set
361 :param branch_name: if specified, changesets not reachable from given
361 :param branch_name: if specified, changesets not reachable from given
362 branch would be filtered out from returned set
362 branch would be filtered out from returned set
363 :param reverse: if ``True``, returned generator would be reversed
363 :param reverse: if ``True``, returned generator would be reversed
364 (meaning that returned changesets would have descending date order)
364 (meaning that returned changesets would have descending date order)
365
365
366 :raise BranchDoesNotExistError: If given ``branch_name`` does not
366 :raise BranchDoesNotExistError: If given ``branch_name`` does not
367 exist.
367 exist.
368 :raise ChangesetDoesNotExistError: If changeset for given ``start`` or
368 :raise ChangesetDoesNotExistError: If changeset for given ``start`` or
369 ``end`` could not be found.
369 ``end`` could not be found.
370
370
371 """
371 """
372 if branch_name and branch_name not in self.branches:
372 if branch_name and branch_name not in self.branches:
373 raise BranchDoesNotExistError("Branch '%s' not found" \
373 raise BranchDoesNotExistError("Branch '%s' not found" \
374 % branch_name)
374 % branch_name)
375 # %H at format means (full) commit hash, initial hashes are retrieved
375 # %H at format means (full) commit hash, initial hashes are retrieved
376 # in ascending date order
376 # in ascending date order
377 cmd_template = 'log --date-order --reverse --pretty=format:"%H"'
377 cmd_template = 'log --date-order --reverse --pretty=format:"%H"'
378 cmd_params = {}
378 cmd_params = {}
379 if start_date:
379 if start_date:
380 cmd_template += ' --since "$since"'
380 cmd_template += ' --since "$since"'
381 cmd_params['since'] = start_date.strftime('%m/%d/%y %H:%M:%S')
381 cmd_params['since'] = start_date.strftime('%m/%d/%y %H:%M:%S')
382 if end_date:
382 if end_date:
383 cmd_template += ' --until "$until"'
383 cmd_template += ' --until "$until"'
384 cmd_params['until'] = end_date.strftime('%m/%d/%y %H:%M:%S')
384 cmd_params['until'] = end_date.strftime('%m/%d/%y %H:%M:%S')
385 if branch_name:
385 if branch_name:
386 cmd_template += ' $branch_name'
386 cmd_template += ' $branch_name'
387 cmd_params['branch_name'] = branch_name
387 cmd_params['branch_name'] = branch_name
388 else:
388 else:
389 cmd_template += ' --all'
389 cmd_template += ' --all'
390
390
391 cmd = Template(cmd_template).safe_substitute(**cmd_params)
391 cmd = Template(cmd_template).safe_substitute(**cmd_params)
392 revs = self.run_git_command(cmd)[0].splitlines()
392 revs = self.run_git_command(cmd)[0].splitlines()
393 start_pos = 0
393 start_pos = 0
394 end_pos = len(revs)
394 end_pos = len(revs)
395 if start:
395 if start:
396 _start = self._get_revision(start)
396 _start = self._get_revision(start)
397 try:
397 try:
398 start_pos = revs.index(_start)
398 start_pos = revs.index(_start)
399 except ValueError:
399 except ValueError:
400 pass
400 pass
401
401
402 if end is not None:
402 if end is not None:
403 _end = self._get_revision(end)
403 _end = self._get_revision(end)
404 try:
404 try:
405 end_pos = revs.index(_end)
405 end_pos = revs.index(_end)
406 except ValueError:
406 except ValueError:
407 pass
407 pass
408
408
409 if None not in [start, end] and start_pos > end_pos:
409 if None not in [start, end] and start_pos > end_pos:
410 raise RepositoryError('start cannot be after end')
410 raise RepositoryError('start cannot be after end')
411
411
412 if end_pos is not None:
412 if end_pos is not None:
413 end_pos += 1
413 end_pos += 1
414
414
415 revs = revs[start_pos:end_pos]
415 revs = revs[start_pos:end_pos]
416 if reverse:
416 if reverse:
417 revs = reversed(revs)
417 revs = reversed(revs)
418 for rev in revs:
418 for rev in revs:
419 yield self.get_changeset(rev)
419 yield self.get_changeset(rev)
420
420
421 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
421 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
422 context=3):
422 context=3):
423 """
423 """
424 Returns (git like) *diff*, as plain text. Shows changes introduced by
424 Returns (git like) *diff*, as plain text. Shows changes introduced by
425 ``rev2`` since ``rev1``.
425 ``rev2`` since ``rev1``.
426
426
427 :param rev1: Entry point from which diff is shown. Can be
427 :param rev1: Entry point from which diff is shown. Can be
428 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
428 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
429 the changes since empty state of the repository until ``rev2``
429 the changes since empty state of the repository until ``rev2``
430 :param rev2: Until which revision changes should be shown.
430 :param rev2: Until which revision changes should be shown.
431 :param ignore_whitespace: If set to ``True``, would not show whitespace
431 :param ignore_whitespace: If set to ``True``, would not show whitespace
432 changes. Defaults to ``False``.
432 changes. Defaults to ``False``.
433 :param context: How many lines before/after changed lines should be
433 :param context: How many lines before/after changed lines should be
434 shown. Defaults to ``3``.
434 shown. Defaults to ``3``.
435 """
435 """
436 flags = ['-U%s' % context]
436 flags = ['-U%s' % context]
437 if ignore_whitespace:
437 if ignore_whitespace:
438 flags.append('-w')
438 flags.append('-w')
439
439
440 if rev1 == self.EMPTY_CHANGESET:
440 if rev1 == self.EMPTY_CHANGESET:
441 rev2 = self.get_changeset(rev2).raw_id
441 rev2 = self.get_changeset(rev2).raw_id
442 cmd = ' '.join(['show'] + flags + [rev2])
442 cmd = ' '.join(['show'] + flags + [rev2])
443 else:
443 else:
444 rev1 = self.get_changeset(rev1).raw_id
444 rev1 = self.get_changeset(rev1).raw_id
445 rev2 = self.get_changeset(rev2).raw_id
445 rev2 = self.get_changeset(rev2).raw_id
446 cmd = ' '.join(['diff'] + flags + [rev1, rev2])
446 cmd = ' '.join(['diff'] + flags + [rev1, rev2])
447
447
448 if path:
448 if path:
449 cmd += ' -- "%s"' % path
449 cmd += ' -- "%s"' % path
450 stdout, stderr = self.run_git_command(cmd)
450 stdout, stderr = self.run_git_command(cmd)
451 # If we used 'show' command, strip first few lines (until actual diff
451 # If we used 'show' command, strip first few lines (until actual diff
452 # starts)
452 # starts)
453 if rev1 == self.EMPTY_CHANGESET:
453 if rev1 == self.EMPTY_CHANGESET:
454 lines = stdout.splitlines()
454 lines = stdout.splitlines()
455 x = 0
455 x = 0
456 for line in lines:
456 for line in lines:
457 if line.startswith('diff'):
457 if line.startswith('diff'):
458 break
458 break
459 x += 1
459 x += 1
460 # Append new line just like 'diff' command do
460 # Append new line just like 'diff' command do
461 stdout = '\n'.join(lines[x:]) + '\n'
461 stdout = '\n'.join(lines[x:]) + '\n'
462 return stdout
462 return stdout
463
463
464 @LazyProperty
464 @LazyProperty
465 def in_memory_changeset(self):
465 def in_memory_changeset(self):
466 """
466 """
467 Returns ``GitInMemoryChangeset`` object for this repository.
467 Returns ``GitInMemoryChangeset`` object for this repository.
468 """
468 """
469 return GitInMemoryChangeset(self)
469 return GitInMemoryChangeset(self)
470
470
471 def clone(self, url, update_after_clone=True, bare=False):
471 def clone(self, url, update_after_clone=True, bare=False):
472 """
472 """
473 Tries to clone changes from external location.
473 Tries to clone changes from external location.
474
474
475 :param update_after_clone: If set to ``False``, git won't checkout
475 :param update_after_clone: If set to ``False``, git won't checkout
476 working directory
476 working directory
477 :param bare: If set to ``True``, repository would be cloned into
477 :param bare: If set to ``True``, repository would be cloned into
478 *bare* git repository (no working directory at all).
478 *bare* git repository (no working directory at all).
479 """
479 """
480 url = self._get_url(url)
480 url = self._get_url(url)
481 cmd = ['clone']
481 cmd = ['clone']
482 if bare:
482 if bare:
483 cmd.append('--bare')
483 cmd.append('--bare')
484 elif not update_after_clone:
484 elif not update_after_clone:
485 cmd.append('--no-checkout')
485 cmd.append('--no-checkout')
486 cmd += ['--', '"%s"' % url, '"%s"' % self.path]
486 cmd += ['--', '"%s"' % url, '"%s"' % self.path]
487 cmd = ' '.join(cmd)
487 cmd = ' '.join(cmd)
488 # If error occurs run_git_command raises RepositoryError already
488 # If error occurs run_git_command raises RepositoryError already
489 self.run_git_command(cmd)
489 self.run_git_command(cmd)
490
490
491 def pull(self, url):
491 def pull(self, url):
492 """
492 """
493 Tries to pull changes from external location.
493 Tries to pull changes from external location.
494 """
494 """
495 url = self._get_url(url)
495 url = self._get_url(url)
496 cmd = ['pull']
496 cmd = ['pull']
497 cmd.append("--ff-only")
497 cmd.append("--ff-only")
498 cmd.append(url)
498 cmd.append(url)
499 cmd = ' '.join(cmd)
499 cmd = ' '.join(cmd)
500 # If error occurs run_git_command raises RepositoryError already
500 # If error occurs run_git_command raises RepositoryError already
501 self.run_git_command(cmd)
501 self.run_git_command(cmd)
502
502
503 @LazyProperty
503 @LazyProperty
504 def workdir(self):
504 def workdir(self):
505 """
505 """
506 Returns ``Workdir`` instance for this repository.
506 Returns ``Workdir`` instance for this repository.
507 """
507 """
508 return GitWorkdir(self)
508 return GitWorkdir(self)
509
509
510 def get_config_value(self, section, name, config_file=None):
510 def get_config_value(self, section, name, config_file=None):
511 """
511 """
512 Returns configuration value for a given [``section``] and ``name``.
512 Returns configuration value for a given [``section``] and ``name``.
513
513
514 :param section: Section we want to retrieve value from
514 :param section: Section we want to retrieve value from
515 :param name: Name of configuration we want to retrieve
515 :param name: Name of configuration we want to retrieve
516 :param config_file: A path to file which should be used to retrieve
516 :param config_file: A path to file which should be used to retrieve
517 configuration from (might also be a list of file paths)
517 configuration from (might also be a list of file paths)
518 """
518 """
519 if config_file is None:
519 if config_file is None:
520 config_file = []
520 config_file = []
521 elif isinstance(config_file, basestring):
521 elif isinstance(config_file, basestring):
522 config_file = [config_file]
522 config_file = [config_file]
523
523
524 def gen_configs():
524 def gen_configs():
525 for path in config_file + self._config_files:
525 for path in config_file + self._config_files:
526 try:
526 try:
527 yield ConfigFile.from_path(path)
527 yield ConfigFile.from_path(path)
528 except (IOError, OSError, ValueError):
528 except (IOError, OSError, ValueError):
529 continue
529 continue
530
530
531 for config in gen_configs():
531 for config in gen_configs():
532 try:
532 try:
533 return config.get(section, name)
533 return config.get(section, name)
534 except KeyError:
534 except KeyError:
535 continue
535 continue
536 return None
536 return None
537
537
538 def get_user_name(self, config_file=None):
538 def get_user_name(self, config_file=None):
539 """
539 """
540 Returns user's name from global configuration file.
540 Returns user's name from global configuration file.
541
541
542 :param config_file: A path to file which should be used to retrieve
542 :param config_file: A path to file which should be used to retrieve
543 configuration from (might also be a list of file paths)
543 configuration from (might also be a list of file paths)
544 """
544 """
545 return self.get_config_value('user', 'name', config_file)
545 return self.get_config_value('user', 'name', config_file)
546
546
547 def get_user_email(self, config_file=None):
547 def get_user_email(self, config_file=None):
548 """
548 """
549 Returns user's email from global configuration file.
549 Returns user's email from global configuration file.
550
550
551 :param config_file: A path to file which should be used to retrieve
551 :param config_file: A path to file which should be used to retrieve
552 configuration from (might also be a list of file paths)
552 configuration from (might also be a list of file paths)
553 """
553 """
554 return self.get_config_value('user', 'email', config_file)
554 return self.get_config_value('user', 'email', config_file)
@@ -1,85 +1,85 b''
1 <div>
1 <div>
2 ${h.form(url('admin_settings_my_account_update'),method='put')}
2 ${h.form(url('admin_settings_my_account_update'),method='put')}
3 <div class="form">
3 <div class="form">
4
4
5 <div class="field">
5 <div class="field">
6 <div class="gravatar_box">
6 <div class="gravatar_box">
7 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
7 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
8 <p>
8 <p>
9 %if c.use_gravatar:
9 %if c.use_gravatar:
10 <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
10 <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
11 <br/>${_('Using')} ${c.user.email}
11 <br/>${_('Using')} ${c.user.email}
12 %else:
12 %else:
13 <br/>${c.user.email}
13 <br/>${c.user.email}
14 %endif
14 %endif
15 </p>
15 </p>
16 </div>
16 </div>
17 </div>
17 </div>
18 <div class="field">
18 <div class="field">
19 <div class="label">
19 <div class="label">
20 <label>${_('API key')}</label> ${c.user.api_key}
20 <label>${_('API key')}</label> ${c.user.api_key}
21 </div>
21 </div>
22 </div>
22 </div>
23 <div class="fields">
23 <div class="fields">
24 <div class="field">
24 <div class="field">
25 <div class="label">
25 <div class="label">
26 <label for="username">${_('Username')}:</label>
26 <label for="username">${_('Username')}:</label>
27 </div>
27 </div>
28 <div class="input">
28 <div class="input">
29 ${h.text('username',class_="medium")}
29 ${h.text('username',class_="medium")}
30 </div>
30 </div>
31 </div>
31 </div>
32
32
33 <div class="field">
33 <div class="field">
34 <div class="label">
34 <div class="label">
35 <label for="new_password">${_('New password')}:</label>
35 <label for="new_password">${_('New password')}:</label>
36 </div>
36 </div>
37 <div class="input">
37 <div class="input">
38 ${h.password('new_password',class_="medium",autocomplete="off")}
38 ${h.password('new_password',class_="medium",autocomplete="off")}
39 </div>
39 </div>
40 </div>
40 </div>
41
41
42 <div class="field">
42 <div class="field">
43 <div class="label">
43 <div class="label">
44 <label for="password_confirmation">${_('New password confirmation')}:</label>
44 <label for="password_confirmation">${_('New password confirmation')}:</label>
45 </div>
45 </div>
46 <div class="input">
46 <div class="input">
47 ${h.password('password_confirmation',class_="medium",autocomplete="off")}
47 ${h.password('password_confirmation',class_="medium",autocomplete="off")}
48 </div>
48 </div>
49 </div>
49 </div>
50
50
51 <div class="field">
51 <div class="field">
52 <div class="label">
52 <div class="label">
53 <label for="name">${_('First Name')}:</label>
53 <label for="name">${_('First Name')}:</label>
54 </div>
54 </div>
55 <div class="input">
55 <div class="input">
56 ${h.text('name',class_="medium")}
56 ${h.text('name',class_="medium")}
57 </div>
57 </div>
58 </div>
58 </div>
59
59
60 <div class="field">
60 <div class="field">
61 <div class="label">
61 <div class="label">
62 <label for="lastname">${_('Last Name')}:</label>
62 <label for="lastname">${_('Last Name')}:</label>
63 </div>
63 </div>
64 <div class="input">
64 <div class="input">
65 ${h.text('lastname',class_="medium")}
65 ${h.text('lastname',class_="medium")}
66 </div>
66 </div>
67 </div>
67 </div>
68
68
69 <div class="field">
69 <div class="field">
70 <div class="label">
70 <div class="label">
71 <label for="email">${_('Email')}:</label>
71 <label for="email">${_('Email')}:</label>
72 </div>
72 </div>
73 <div class="input">
73 <div class="input">
74 ${h.text('email',class_="medium")}
74 ${h.text('email',class_="medium")}
75 </div>
75 </div>
76 </div>
76 </div>
77
77
78 <div class="buttons">
78 <div class="buttons">
79 ${h.submit('save',_('Save'),class_="ui-button")}
79 ${h.submit('save',_('Save'),class_="ui-button")}
80 ${h.reset('reset',_('Reset'),class_="ui-button")}
80 ${h.reset('reset',_('Reset'),class_="ui-button")}
81 </div>
81 </div>
82 </div>
82 </div>
83 </div>
83 </div>
84 ${h.end_form()}
84 ${h.end_form()}
85 </div> No newline at end of file
85 </div>
@@ -1,320 +1,320 b''
1 from rhodecode.tests import *
1 from rhodecode.tests import *
2
2
3 ARCHIVE_SPECS = {
3 ARCHIVE_SPECS = {
4 '.tar.bz2': ('application/x-bzip2', 'tbz2', ''),
4 '.tar.bz2': ('application/x-bzip2', 'tbz2', ''),
5 '.tar.gz': ('application/x-gzip', 'tgz', ''),
5 '.tar.gz': ('application/x-gzip', 'tgz', ''),
6 '.zip': ('application/zip', 'zip', ''),
6 '.zip': ('application/zip', 'zip', ''),
7 }
7 }
8
8
9
9
10 class TestFilesController(TestController):
10 class TestFilesController(TestController):
11
11
12 def test_index(self):
12 def test_index(self):
13 self.log_user()
13 self.log_user()
14 response = self.app.get(url(controller='files', action='index',
14 response = self.app.get(url(controller='files', action='index',
15 repo_name=HG_REPO,
15 repo_name=HG_REPO,
16 revision='tip',
16 revision='tip',
17 f_path='/'))
17 f_path='/'))
18 # Test response...
18 # Test response...
19 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/docs">docs</a>')
19 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/docs">docs</a>')
20 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/tests">tests</a>')
20 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/tests">tests</a>')
21 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/vcs">vcs</a>')
21 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/vcs">vcs</a>')
22 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/.hgignore">.hgignore</a>')
22 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/.hgignore">.hgignore</a>')
23 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/MANIFEST.in">MANIFEST.in</a>')
23 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/MANIFEST.in">MANIFEST.in</a>')
24
24
25 def test_index_revision(self):
25 def test_index_revision(self):
26 self.log_user()
26 self.log_user()
27
27
28 response = self.app.get(
28 response = self.app.get(
29 url(controller='files', action='index',
29 url(controller='files', action='index',
30 repo_name=HG_REPO,
30 repo_name=HG_REPO,
31 revision='7ba66bec8d6dbba14a2155be32408c435c5f4492',
31 revision='7ba66bec8d6dbba14a2155be32408c435c5f4492',
32 f_path='/')
32 f_path='/')
33 )
33 )
34
34
35 #Test response...
35 #Test response...
36
36
37 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/docs">docs</a>')
37 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/docs">docs</a>')
38 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/tests">tests</a>')
38 response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/tests">tests</a>')
39 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/README.rst">README.rst</a>')
39 response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/README.rst">README.rst</a>')
40 response.mustcontain('1.1 KiB')
40 response.mustcontain('1.1 KiB')
41 response.mustcontain('text/x-python')
41 response.mustcontain('text/x-python')
42
42
43 def test_index_different_branch(self):
43 def test_index_different_branch(self):
44 self.log_user()
44 self.log_user()
45
45
46 response = self.app.get(url(controller='files', action='index',
46 response = self.app.get(url(controller='files', action='index',
47 repo_name=HG_REPO,
47 repo_name=HG_REPO,
48 revision='97e8b885c04894463c51898e14387d80c30ed1ee',
48 revision='97e8b885c04894463c51898e14387d80c30ed1ee',
49 f_path='/'))
49 f_path='/'))
50
50
51 response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">branch: git</a></span>""")
51 response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">branch: git</a></span>""")
52
52
53 def test_index_paging(self):
53 def test_index_paging(self):
54 self.log_user()
54 self.log_user()
55
55
56 for r in [(73, 'a066b25d5df7016b45a41b7e2a78c33b57adc235'),
56 for r in [(73, 'a066b25d5df7016b45a41b7e2a78c33b57adc235'),
57 (92, 'cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e'),
57 (92, 'cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e'),
58 (109, '75feb4c33e81186c87eac740cee2447330288412'),
58 (109, '75feb4c33e81186c87eac740cee2447330288412'),
59 (1, '3d8f361e72ab303da48d799ff1ac40d5ac37c67e'),
59 (1, '3d8f361e72ab303da48d799ff1ac40d5ac37c67e'),
60 (0, 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]:
60 (0, 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]:
61
61
62 response = self.app.get(url(controller='files', action='index',
62 response = self.app.get(url(controller='files', action='index',
63 repo_name=HG_REPO,
63 repo_name=HG_REPO,
64 revision=r[1],
64 revision=r[1],
65 f_path='/'))
65 f_path='/'))
66
66
67 response.mustcontain("""@ r%s:%s""" % (r[0], r[1][:12]))
67 response.mustcontain("""@ r%s:%s""" % (r[0], r[1][:12]))
68
68
69 def test_file_source(self):
69 def test_file_source(self):
70 self.log_user()
70 self.log_user()
71 response = self.app.get(url(controller='files', action='index',
71 response = self.app.get(url(controller='files', action='index',
72 repo_name=HG_REPO,
72 repo_name=HG_REPO,
73 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
73 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
74 f_path='vcs/nodes.py'))
74 f_path='vcs/nodes.py'))
75
75
76 #test or history
76 #test or history
77 response.mustcontain("""<optgroup label="Changesets">
77 response.mustcontain("""<optgroup label="Changesets">
78 <option value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776 (default)</option>
78 <option value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776 (default)</option>
79 <option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35 (default)</option>
79 <option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35 (default)</option>
80 <option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c (default)</option>
80 <option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c (default)</option>
81 <option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329 (default)</option>
81 <option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329 (default)</option>
82 <option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf (git)</option>
82 <option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf (git)</option>
83 <option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81 (default)</option>
83 <option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81 (default)</option>
84 <option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc (default)</option>
84 <option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc (default)</option>
85 <option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21 (default)</option>
85 <option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21 (default)</option>
86 <option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2 (default)</option>
86 <option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2 (default)</option>
87 <option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85 (default)</option>
87 <option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85 (default)</option>
88 <option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2 (git)</option>
88 <option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2 (git)</option>
89 <option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93 (default)</option>
89 <option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93 (default)</option>
90 <option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b (default)</option>
90 <option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b (default)</option>
91 <option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455 (default)</option>
91 <option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455 (default)</option>
92 <option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32 (default)</option>
92 <option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32 (default)</option>
93 <option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0 (default)</option>
93 <option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0 (default)</option>
94 <option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081 (default)</option>
94 <option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081 (default)</option>
95 <option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad (default)</option>
95 <option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad (default)</option>
96 <option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7 (default)</option>
96 <option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7 (default)</option>
97 <option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059 (web)</option>
97 <option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059 (web)</option>
98 <option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb (web)</option>
98 <option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb (web)</option>
99 <option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1 (web)</option>
99 <option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1 (web)</option>
100 <option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71 (web)</option>
100 <option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71 (web)</option>
101 <option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771 (web)</option>
101 <option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771 (web)</option>
102 <option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526 (web)</option>
102 <option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526 (web)</option>
103 <option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4 (web)</option>
103 <option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4 (web)</option>
104 <option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b (web)</option>
104 <option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b (web)</option>
105 <option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c (web)</option>
105 <option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c (web)</option>
106 <option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1 (default)</option>
106 <option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1 (default)</option>
107 <option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5 (default)</option>
107 <option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5 (default)</option>
108 <option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283 (default)</option>
108 <option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283 (default)</option>
109 <option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6 (default)</option>
109 <option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6 (default)</option>
110 <option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3 (default)</option>
110 <option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3 (default)</option>
111 </optgroup>
111 </optgroup>
112 <optgroup label="Branches">
112 <optgroup label="Branches">
113 <option selected="selected" value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</option>
113 <option selected="selected" value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</option>
114 <option value="97e8b885c04894463c51898e14387d80c30ed1ee">git</option>
114 <option value="97e8b885c04894463c51898e14387d80c30ed1ee">git</option>
115 <option value="2e6a2bf9356ca56df08807f4ad86d480da72a8f4">web</option>
115 <option value="2e6a2bf9356ca56df08807f4ad86d480da72a8f4">web</option>
116 </optgroup>
116 </optgroup>
117 <optgroup label="Tags">
117 <optgroup label="Tags">
118 <option selected="selected" value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">tip</option>
118 <option selected="selected" value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">tip</option>
119 <option value="fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200">0.1.4</option>
119 <option value="fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200">0.1.4</option>
120 <option value="17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">0.1.3</option>
120 <option value="17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">0.1.3</option>
121 <option value="a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">0.1.2</option>
121 <option value="a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">0.1.2</option>
122 <option value="eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">0.1.1</option>
122 <option value="eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">0.1.1</option>
123 </optgroup>
123 </optgroup>
124 """)
124 """)
125
125
126 response.mustcontain("""<div class="commit">merge</div>""")
126 response.mustcontain("""<div class="commit">merge</div>""")
127
127
128 response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">branch: default</a></span>""")
128 response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">branch: default</a></span>""")
129
129
130 def test_file_annotation(self):
130 def test_file_annotation(self):
131 self.log_user()
131 self.log_user()
132 response = self.app.get(url(controller='files', action='index',
132 response = self.app.get(url(controller='files', action='index',
133 repo_name=HG_REPO,
133 repo_name=HG_REPO,
134 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
134 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
135 f_path='vcs/nodes.py',
135 f_path='vcs/nodes.py',
136 annotate=True))
136 annotate=True))
137
137
138
138
139 response.mustcontain("""<optgroup label="Changesets">
139 response.mustcontain("""<optgroup label="Changesets">
140 <option value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776 (default)</option>
140 <option value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776 (default)</option>
141 <option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35 (default)</option>
141 <option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35 (default)</option>
142 <option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c (default)</option>
142 <option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c (default)</option>
143 <option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329 (default)</option>
143 <option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329 (default)</option>
144 <option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf (git)</option>
144 <option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf (git)</option>
145 <option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81 (default)</option>
145 <option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81 (default)</option>
146 <option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc (default)</option>
146 <option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc (default)</option>
147 <option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21 (default)</option>
147 <option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21 (default)</option>
148 <option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2 (default)</option>
148 <option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2 (default)</option>
149 <option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85 (default)</option>
149 <option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85 (default)</option>
150 <option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2 (git)</option>
150 <option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2 (git)</option>
151 <option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93 (default)</option>
151 <option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93 (default)</option>
152 <option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b (default)</option>
152 <option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b (default)</option>
153 <option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455 (default)</option>
153 <option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455 (default)</option>
154 <option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32 (default)</option>
154 <option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32 (default)</option>
155 <option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0 (default)</option>
155 <option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0 (default)</option>
156 <option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081 (default)</option>
156 <option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081 (default)</option>
157 <option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad (default)</option>
157 <option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad (default)</option>
158 <option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7 (default)</option>
158 <option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7 (default)</option>
159 <option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059 (web)</option>
159 <option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059 (web)</option>
160 <option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb (web)</option>
160 <option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb (web)</option>
161 <option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1 (web)</option>
161 <option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1 (web)</option>
162 <option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71 (web)</option>
162 <option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71 (web)</option>
163 <option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771 (web)</option>
163 <option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771 (web)</option>
164 <option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526 (web)</option>
164 <option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526 (web)</option>
165 <option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4 (web)</option>
165 <option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4 (web)</option>
166 <option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b (web)</option>
166 <option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b (web)</option>
167 <option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c (web)</option>
167 <option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c (web)</option>
168 <option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1 (default)</option>
168 <option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1 (default)</option>
169 <option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5 (default)</option>
169 <option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5 (default)</option>
170 <option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283 (default)</option>
170 <option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283 (default)</option>
171 <option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6 (default)</option>
171 <option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6 (default)</option>
172 <option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3 (default)</option>
172 <option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3 (default)</option>
173 </optgroup>
173 </optgroup>
174 <optgroup label="Branches">
174 <optgroup label="Branches">
175 <option selected="selected" value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</option>
175 <option selected="selected" value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</option>
176 <option value="97e8b885c04894463c51898e14387d80c30ed1ee">git</option>
176 <option value="97e8b885c04894463c51898e14387d80c30ed1ee">git</option>
177 <option value="2e6a2bf9356ca56df08807f4ad86d480da72a8f4">web</option>
177 <option value="2e6a2bf9356ca56df08807f4ad86d480da72a8f4">web</option>
178 </optgroup>
178 </optgroup>
179 <optgroup label="Tags">
179 <optgroup label="Tags">
180 <option selected="selected" value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">tip</option>
180 <option selected="selected" value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">tip</option>
181 <option value="fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200">0.1.4</option>
181 <option value="fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200">0.1.4</option>
182 <option value="17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">0.1.3</option>
182 <option value="17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">0.1.3</option>
183 <option value="a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">0.1.2</option>
183 <option value="a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">0.1.2</option>
184 <option value="eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">0.1.1</option>
184 <option value="eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">0.1.1</option>
185 </optgroup>""")
185 </optgroup>""")
186
186
187 response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">branch: default</a></span>""")
187 response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">branch: default</a></span>""")
188
188
189 def test_archival(self):
189 def test_archival(self):
190 self.log_user()
190 self.log_user()
191
191
192 for arch_ext, info in ARCHIVE_SPECS.items():
192 for arch_ext, info in ARCHIVE_SPECS.items():
193 short = '27cd5cce30c9%s' % arch_ext
193 short = '27cd5cce30c9%s' % arch_ext
194 fname = '27cd5cce30c96924232dffcd24178a07ffeb5dfc%s' % arch_ext
194 fname = '27cd5cce30c96924232dffcd24178a07ffeb5dfc%s' % arch_ext
195 filename = '%s-%s' % (HG_REPO, short)
195 filename = '%s-%s' % (HG_REPO, short)
196 response = self.app.get(url(controller='files',
196 response = self.app.get(url(controller='files',
197 action='archivefile',
197 action='archivefile',
198 repo_name=HG_REPO,
198 repo_name=HG_REPO,
199 fname=fname))
199 fname=fname))
200
200
201 self.assertEqual(response.status, '200 OK')
201 self.assertEqual(response.status, '200 OK')
202 heads = [
202 heads = [
203 ('Pragma', 'no-cache'),
203 ('Pragma', 'no-cache'),
204 ('Cache-Control', 'no-cache'),
204 ('Cache-Control', 'no-cache'),
205 ('Content-Disposition', 'attachment; filename=%s' % filename),
205 ('Content-Disposition', 'attachment; filename=%s' % filename),
206 ('Content-Type', '%s; charset=utf-8' % info[0]),
206 ('Content-Type', '%s; charset=utf-8' % info[0]),
207 ]
207 ]
208 self.assertEqual(response.response._headers.items(), heads)
208 self.assertEqual(response.response._headers.items(), heads)
209
209
210 def test_archival_wrong_ext(self):
210 def test_archival_wrong_ext(self):
211 self.log_user()
211 self.log_user()
212
212
213 for arch_ext in ['tar', 'rar', 'x', '..ax', '.zipz']:
213 for arch_ext in ['tar', 'rar', 'x', '..ax', '.zipz']:
214 fname = '27cd5cce30c96924232dffcd24178a07ffeb5dfc%s' % arch_ext
214 fname = '27cd5cce30c96924232dffcd24178a07ffeb5dfc%s' % arch_ext
215
215
216 response = self.app.get(url(controller='files',
216 response = self.app.get(url(controller='files',
217 action='archivefile',
217 action='archivefile',
218 repo_name=HG_REPO,
218 repo_name=HG_REPO,
219 fname=fname))
219 fname=fname))
220 response.mustcontain('Unknown archive type')
220 response.mustcontain('Unknown archive type')
221
221
222 def test_archival_wrong_revision(self):
222 def test_archival_wrong_revision(self):
223 self.log_user()
223 self.log_user()
224
224
225 for rev in ['00x000000', 'tar', 'wrong', '@##$@$42413232', '232dffcd']:
225 for rev in ['00x000000', 'tar', 'wrong', '@##$@$42413232', '232dffcd']:
226 fname = '%s.zip' % rev
226 fname = '%s.zip' % rev
227
227
228 response = self.app.get(url(controller='files',
228 response = self.app.get(url(controller='files',
229 action='archivefile',
229 action='archivefile',
230 repo_name=HG_REPO,
230 repo_name=HG_REPO,
231 fname=fname))
231 fname=fname))
232 response.mustcontain('Unknown revision')
232 response.mustcontain('Unknown revision')
233
233
234 #==========================================================================
234 #==========================================================================
235 # RAW FILE
235 # RAW FILE
236 #==========================================================================
236 #==========================================================================
237 def test_raw_file_ok(self):
237 def test_raw_file_ok(self):
238 self.log_user()
238 self.log_user()
239 response = self.app.get(url(controller='files', action='rawfile',
239 response = self.app.get(url(controller='files', action='rawfile',
240 repo_name=HG_REPO,
240 repo_name=HG_REPO,
241 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
241 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
242 f_path='vcs/nodes.py'))
242 f_path='vcs/nodes.py'))
243
243
244 self.assertEqual(response.content_disposition, "attachment; filename=nodes.py")
244 self.assertEqual(response.content_disposition, "attachment; filename=nodes.py")
245 self.assertEqual(response.content_type, "text/x-python")
245 self.assertEqual(response.content_type, "text/x-python")
246
246
247 def test_raw_file_wrong_cs(self):
247 def test_raw_file_wrong_cs(self):
248 self.log_user()
248 self.log_user()
249 rev = u'ERRORce30c96924232dffcd24178a07ffeb5dfc'
249 rev = u'ERRORce30c96924232dffcd24178a07ffeb5dfc'
250 f_path = 'vcs/nodes.py'
250 f_path = 'vcs/nodes.py'
251
251
252 response = self.app.get(url(controller='files', action='rawfile',
252 response = self.app.get(url(controller='files', action='rawfile',
253 repo_name=HG_REPO,
253 repo_name=HG_REPO,
254 revision=rev,
254 revision=rev,
255 f_path=f_path))
255 f_path=f_path))
256
256
257 msg = """Revision %r does not exist for this repository""" % (rev)
257 msg = """Revision %r does not exist for this repository""" % (rev)
258 self.checkSessionFlash(response, msg)
258 self.checkSessionFlash(response, msg)
259
259
260 msg = """%s""" % (HG_REPO)
260 msg = """%s""" % (HG_REPO)
261 self.checkSessionFlash(response, msg)
261 self.checkSessionFlash(response, msg)
262
262
263 def test_raw_file_wrong_f_path(self):
263 def test_raw_file_wrong_f_path(self):
264 self.log_user()
264 self.log_user()
265 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
265 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
266 f_path = 'vcs/ERRORnodes.py'
266 f_path = 'vcs/ERRORnodes.py'
267 response = self.app.get(url(controller='files', action='rawfile',
267 response = self.app.get(url(controller='files', action='rawfile',
268 repo_name=HG_REPO,
268 repo_name=HG_REPO,
269 revision=rev,
269 revision=rev,
270 f_path=f_path))
270 f_path=f_path))
271
271
272 msg = "There is no file nor directory at the given path: %r at revision %r" % (f_path, rev[:12])
272 msg = "There is no file nor directory at the given path: %r at revision %r" % (f_path, rev[:12])
273 self.checkSessionFlash(response, msg)
273 self.checkSessionFlash(response, msg)
274
274
275 #==========================================================================
275 #==========================================================================
276 # RAW RESPONSE - PLAIN
276 # RAW RESPONSE - PLAIN
277 #==========================================================================
277 #==========================================================================
278 def test_raw_ok(self):
278 def test_raw_ok(self):
279 self.log_user()
279 self.log_user()
280 response = self.app.get(url(controller='files', action='raw',
280 response = self.app.get(url(controller='files', action='raw',
281 repo_name=HG_REPO,
281 repo_name=HG_REPO,
282 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
282 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
283 f_path='vcs/nodes.py'))
283 f_path='vcs/nodes.py'))
284
284
285 self.assertEqual(response.content_type, "text/plain")
285 self.assertEqual(response.content_type, "text/plain")
286
286
287 def test_raw_wrong_cs(self):
287 def test_raw_wrong_cs(self):
288 self.log_user()
288 self.log_user()
289 rev = u'ERRORcce30c96924232dffcd24178a07ffeb5dfc'
289 rev = u'ERRORcce30c96924232dffcd24178a07ffeb5dfc'
290 f_path = 'vcs/nodes.py'
290 f_path = 'vcs/nodes.py'
291
291
292 response = self.app.get(url(controller='files', action='raw',
292 response = self.app.get(url(controller='files', action='raw',
293 repo_name=HG_REPO,
293 repo_name=HG_REPO,
294 revision=rev,
294 revision=rev,
295 f_path=f_path))
295 f_path=f_path))
296 msg = """Revision %r does not exist for this repository""" % (rev)
296 msg = """Revision %r does not exist for this repository""" % (rev)
297 self.checkSessionFlash(response, msg)
297 self.checkSessionFlash(response, msg)
298
298
299 msg = """%s""" % (HG_REPO)
299 msg = """%s""" % (HG_REPO)
300 self.checkSessionFlash(response, msg)
300 self.checkSessionFlash(response, msg)
301
301
302 def test_raw_wrong_f_path(self):
302 def test_raw_wrong_f_path(self):
303 self.log_user()
303 self.log_user()
304 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
304 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
305 f_path = 'vcs/ERRORnodes.py'
305 f_path = 'vcs/ERRORnodes.py'
306 response = self.app.get(url(controller='files', action='raw',
306 response = self.app.get(url(controller='files', action='raw',
307 repo_name=HG_REPO,
307 repo_name=HG_REPO,
308 revision=rev,
308 revision=rev,
309 f_path=f_path))
309 f_path=f_path))
310 msg = "There is no file nor directory at the given path: %r at revision %r" % (f_path, rev[:12])
310 msg = "There is no file nor directory at the given path: %r at revision %r" % (f_path, rev[:12])
311 self.checkSessionFlash(response, msg)
311 self.checkSessionFlash(response, msg)
312
312
313 def test_ajaxed_files_list(self):
313 def test_ajaxed_files_list(self):
314 self.log_user()
314 self.log_user()
315 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
315 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
316 response = self.app.get(
316 response = self.app.get(
317 url('files_nodelist_home', repo_name=HG_REPO,f_path='/',revision=rev),
317 url('files_nodelist_home', repo_name=HG_REPO,f_path='/',revision=rev),
318 extra_environ={'HTTP_X_PARTIAL_XHR': '1'},
318 extra_environ={'HTTP_X_PARTIAL_XHR': '1'},
319 )
319 )
320 response.mustcontain("vcs/web/simplevcs/views/repository.py")
320 response.mustcontain("vcs/web/simplevcs/views/repository.py")
General Comments 0
You need to be logged in to leave comments. Login now