##// END OF EJS Templates
added emulation of pull hook for git-backend, and dummy git-push hook
marcink -
r2203:d9972f76 beta
parent child Browse files
Show More
@@ -1,938 +1,941 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
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 def shorter(text, size=20):
49 def shorter(text, size=20):
50 postfix = '...'
50 postfix = '...'
51 if len(text) > size:
51 if len(text) > size:
52 return text[:size - len(postfix)] + postfix
52 return text[:size - len(postfix)] + postfix
53 return text
53 return text
54
54
55
55
56 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
56 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
57 """
57 """
58 Reset button
58 Reset button
59 """
59 """
60 _set_input_attrs(attrs, type, name, value)
60 _set_input_attrs(attrs, type, name, value)
61 _set_id_attr(attrs, id, name)
61 _set_id_attr(attrs, id, name)
62 convert_boolean_attrs(attrs, ["disabled"])
62 convert_boolean_attrs(attrs, ["disabled"])
63 return HTML.input(**attrs)
63 return HTML.input(**attrs)
64
64
65 reset = _reset
65 reset = _reset
66 safeid = _make_safe_id_component
66 safeid = _make_safe_id_component
67
67
68
68
69 def FID(raw_id, path):
69 def FID(raw_id, path):
70 """
70 """
71 Creates a uniqe ID for filenode based on it's hash of path and revision
71 Creates a uniqe ID for filenode based on it's hash of path and revision
72 it's safe to use in urls
72 it's safe to use in urls
73
73
74 :param raw_id:
74 :param raw_id:
75 :param path:
75 :param path:
76 """
76 """
77
77
78 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
78 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
79
79
80
80
81 def get_token():
81 def get_token():
82 """Return the current authentication token, creating one if one doesn't
82 """Return the current authentication token, creating one if one doesn't
83 already exist.
83 already exist.
84 """
84 """
85 token_key = "_authentication_token"
85 token_key = "_authentication_token"
86 from pylons import session
86 from pylons import session
87 if not token_key in session:
87 if not token_key in session:
88 try:
88 try:
89 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
89 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
90 except AttributeError: # Python < 2.4
90 except AttributeError: # Python < 2.4
91 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
91 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
92 session[token_key] = token
92 session[token_key] = token
93 if hasattr(session, 'save'):
93 if hasattr(session, 'save'):
94 session.save()
94 session.save()
95 return session[token_key]
95 return session[token_key]
96
96
97
97
98 class _GetError(object):
98 class _GetError(object):
99 """Get error from form_errors, and represent it as span wrapped error
99 """Get error from form_errors, and represent it as span wrapped error
100 message
100 message
101
101
102 :param field_name: field to fetch errors for
102 :param field_name: field to fetch errors for
103 :param form_errors: form errors dict
103 :param form_errors: form errors dict
104 """
104 """
105
105
106 def __call__(self, field_name, form_errors):
106 def __call__(self, field_name, form_errors):
107 tmpl = """<span class="error_msg">%s</span>"""
107 tmpl = """<span class="error_msg">%s</span>"""
108 if form_errors and form_errors.has_key(field_name):
108 if form_errors and form_errors.has_key(field_name):
109 return literal(tmpl % form_errors.get(field_name))
109 return literal(tmpl % form_errors.get(field_name))
110
110
111 get_error = _GetError()
111 get_error = _GetError()
112
112
113
113
114 class _ToolTip(object):
114 class _ToolTip(object):
115
115
116 def __call__(self, tooltip_title, trim_at=50):
116 def __call__(self, tooltip_title, trim_at=50):
117 """Special function just to wrap our text into nice formatted
117 """Special function just to wrap our text into nice formatted
118 autowrapped text
118 autowrapped text
119
119
120 :param tooltip_title:
120 :param tooltip_title:
121 """
121 """
122 return escape(tooltip_title)
122 return escape(tooltip_title)
123 tooltip = _ToolTip()
123 tooltip = _ToolTip()
124
124
125
125
126 class _FilesBreadCrumbs(object):
126 class _FilesBreadCrumbs(object):
127
127
128 def __call__(self, repo_name, rev, paths):
128 def __call__(self, repo_name, rev, paths):
129 if isinstance(paths, str):
129 if isinstance(paths, str):
130 paths = safe_unicode(paths)
130 paths = safe_unicode(paths)
131 url_l = [link_to(repo_name, url('files_home',
131 url_l = [link_to(repo_name, url('files_home',
132 repo_name=repo_name,
132 repo_name=repo_name,
133 revision=rev, f_path=''))]
133 revision=rev, f_path=''))]
134 paths_l = paths.split('/')
134 paths_l = paths.split('/')
135 for cnt, p in enumerate(paths_l):
135 for cnt, p in enumerate(paths_l):
136 if p != '':
136 if p != '':
137 url_l.append(link_to(p,
137 url_l.append(link_to(p,
138 url('files_home',
138 url('files_home',
139 repo_name=repo_name,
139 repo_name=repo_name,
140 revision=rev,
140 revision=rev,
141 f_path='/'.join(paths_l[:cnt + 1])
141 f_path='/'.join(paths_l[:cnt + 1])
142 )
142 )
143 )
143 )
144 )
144 )
145
145
146 return literal('/'.join(url_l))
146 return literal('/'.join(url_l))
147
147
148 files_breadcrumbs = _FilesBreadCrumbs()
148 files_breadcrumbs = _FilesBreadCrumbs()
149
149
150
150
151 class CodeHtmlFormatter(HtmlFormatter):
151 class CodeHtmlFormatter(HtmlFormatter):
152 """
152 """
153 My code Html Formatter for source codes
153 My code Html Formatter for source codes
154 """
154 """
155
155
156 def wrap(self, source, outfile):
156 def wrap(self, source, outfile):
157 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
157 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
158
158
159 def _wrap_code(self, source):
159 def _wrap_code(self, source):
160 for cnt, it in enumerate(source):
160 for cnt, it in enumerate(source):
161 i, t = it
161 i, t = it
162 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
162 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
163 yield i, t
163 yield i, t
164
164
165 def _wrap_tablelinenos(self, inner):
165 def _wrap_tablelinenos(self, inner):
166 dummyoutfile = StringIO.StringIO()
166 dummyoutfile = StringIO.StringIO()
167 lncount = 0
167 lncount = 0
168 for t, line in inner:
168 for t, line in inner:
169 if t:
169 if t:
170 lncount += 1
170 lncount += 1
171 dummyoutfile.write(line)
171 dummyoutfile.write(line)
172
172
173 fl = self.linenostart
173 fl = self.linenostart
174 mw = len(str(lncount + fl - 1))
174 mw = len(str(lncount + fl - 1))
175 sp = self.linenospecial
175 sp = self.linenospecial
176 st = self.linenostep
176 st = self.linenostep
177 la = self.lineanchors
177 la = self.lineanchors
178 aln = self.anchorlinenos
178 aln = self.anchorlinenos
179 nocls = self.noclasses
179 nocls = self.noclasses
180 if sp:
180 if sp:
181 lines = []
181 lines = []
182
182
183 for i in range(fl, fl + lncount):
183 for i in range(fl, fl + lncount):
184 if i % st == 0:
184 if i % st == 0:
185 if i % sp == 0:
185 if i % sp == 0:
186 if aln:
186 if aln:
187 lines.append('<a href="#%s%d" class="special">%*d</a>' %
187 lines.append('<a href="#%s%d" class="special">%*d</a>' %
188 (la, i, mw, i))
188 (la, i, mw, i))
189 else:
189 else:
190 lines.append('<span class="special">%*d</span>' % (mw, i))
190 lines.append('<span class="special">%*d</span>' % (mw, i))
191 else:
191 else:
192 if aln:
192 if aln:
193 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
193 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
194 else:
194 else:
195 lines.append('%*d' % (mw, i))
195 lines.append('%*d' % (mw, i))
196 else:
196 else:
197 lines.append('')
197 lines.append('')
198 ls = '\n'.join(lines)
198 ls = '\n'.join(lines)
199 else:
199 else:
200 lines = []
200 lines = []
201 for i in range(fl, fl + lncount):
201 for i in range(fl, fl + lncount):
202 if i % st == 0:
202 if i % st == 0:
203 if aln:
203 if aln:
204 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
204 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
205 else:
205 else:
206 lines.append('%*d' % (mw, i))
206 lines.append('%*d' % (mw, i))
207 else:
207 else:
208 lines.append('')
208 lines.append('')
209 ls = '\n'.join(lines)
209 ls = '\n'.join(lines)
210
210
211 # in case you wonder about the seemingly redundant <div> here: since the
211 # in case you wonder about the seemingly redundant <div> here: since the
212 # content in the other cell also is wrapped in a div, some browsers in
212 # content in the other cell also is wrapped in a div, some browsers in
213 # some configurations seem to mess up the formatting...
213 # some configurations seem to mess up the formatting...
214 if nocls:
214 if nocls:
215 yield 0, ('<table class="%stable">' % self.cssclass +
215 yield 0, ('<table class="%stable">' % self.cssclass +
216 '<tr><td><div class="linenodiv" '
216 '<tr><td><div class="linenodiv" '
217 'style="background-color: #f0f0f0; padding-right: 10px">'
217 'style="background-color: #f0f0f0; padding-right: 10px">'
218 '<pre style="line-height: 125%">' +
218 '<pre style="line-height: 125%">' +
219 ls + '</pre></div></td><td id="hlcode" class="code">')
219 ls + '</pre></div></td><td id="hlcode" class="code">')
220 else:
220 else:
221 yield 0, ('<table class="%stable">' % self.cssclass +
221 yield 0, ('<table class="%stable">' % self.cssclass +
222 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
222 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
223 ls + '</pre></div></td><td id="hlcode" class="code">')
223 ls + '</pre></div></td><td id="hlcode" class="code">')
224 yield 0, dummyoutfile.getvalue()
224 yield 0, dummyoutfile.getvalue()
225 yield 0, '</td></tr></table>'
225 yield 0, '</td></tr></table>'
226
226
227
227
228 def pygmentize(filenode, **kwargs):
228 def pygmentize(filenode, **kwargs):
229 """pygmentize function using pygments
229 """pygmentize function using pygments
230
230
231 :param filenode:
231 :param filenode:
232 """
232 """
233
233
234 return literal(code_highlight(filenode.content,
234 return literal(code_highlight(filenode.content,
235 filenode.lexer, CodeHtmlFormatter(**kwargs)))
235 filenode.lexer, CodeHtmlFormatter(**kwargs)))
236
236
237
237
238 def pygmentize_annotation(repo_name, filenode, **kwargs):
238 def pygmentize_annotation(repo_name, filenode, **kwargs):
239 """
239 """
240 pygmentize function for annotation
240 pygmentize function for annotation
241
241
242 :param filenode:
242 :param filenode:
243 """
243 """
244
244
245 color_dict = {}
245 color_dict = {}
246
246
247 def gen_color(n=10000):
247 def gen_color(n=10000):
248 """generator for getting n of evenly distributed colors using
248 """generator for getting n of evenly distributed colors using
249 hsv color and golden ratio. It always return same order of colors
249 hsv color and golden ratio. It always return same order of colors
250
250
251 :returns: RGB tuple
251 :returns: RGB tuple
252 """
252 """
253
253
254 def hsv_to_rgb(h, s, v):
254 def hsv_to_rgb(h, s, v):
255 if s == 0.0:
255 if s == 0.0:
256 return v, v, v
256 return v, v, v
257 i = int(h * 6.0) # XXX assume int() truncates!
257 i = int(h * 6.0) # XXX assume int() truncates!
258 f = (h * 6.0) - i
258 f = (h * 6.0) - i
259 p = v * (1.0 - s)
259 p = v * (1.0 - s)
260 q = v * (1.0 - s * f)
260 q = v * (1.0 - s * f)
261 t = v * (1.0 - s * (1.0 - f))
261 t = v * (1.0 - s * (1.0 - f))
262 i = i % 6
262 i = i % 6
263 if i == 0:
263 if i == 0:
264 return v, t, p
264 return v, t, p
265 if i == 1:
265 if i == 1:
266 return q, v, p
266 return q, v, p
267 if i == 2:
267 if i == 2:
268 return p, v, t
268 return p, v, t
269 if i == 3:
269 if i == 3:
270 return p, q, v
270 return p, q, v
271 if i == 4:
271 if i == 4:
272 return t, p, v
272 return t, p, v
273 if i == 5:
273 if i == 5:
274 return v, p, q
274 return v, p, q
275
275
276 golden_ratio = 0.618033988749895
276 golden_ratio = 0.618033988749895
277 h = 0.22717784590367374
277 h = 0.22717784590367374
278
278
279 for _ in xrange(n):
279 for _ in xrange(n):
280 h += golden_ratio
280 h += golden_ratio
281 h %= 1
281 h %= 1
282 HSV_tuple = [h, 0.95, 0.95]
282 HSV_tuple = [h, 0.95, 0.95]
283 RGB_tuple = hsv_to_rgb(*HSV_tuple)
283 RGB_tuple = hsv_to_rgb(*HSV_tuple)
284 yield map(lambda x: str(int(x * 256)), RGB_tuple)
284 yield map(lambda x: str(int(x * 256)), RGB_tuple)
285
285
286 cgenerator = gen_color()
286 cgenerator = gen_color()
287
287
288 def get_color_string(cs):
288 def get_color_string(cs):
289 if cs in color_dict:
289 if cs in color_dict:
290 col = color_dict[cs]
290 col = color_dict[cs]
291 else:
291 else:
292 col = color_dict[cs] = cgenerator.next()
292 col = color_dict[cs] = cgenerator.next()
293 return "color: rgb(%s)! important;" % (', '.join(col))
293 return "color: rgb(%s)! important;" % (', '.join(col))
294
294
295 def url_func(repo_name):
295 def url_func(repo_name):
296
296
297 def _url_func(changeset):
297 def _url_func(changeset):
298 author = changeset.author
298 author = changeset.author
299 date = changeset.date
299 date = changeset.date
300 message = tooltip(changeset.message)
300 message = tooltip(changeset.message)
301
301
302 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
302 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
303 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
303 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
304 "</b> %s<br/></div>")
304 "</b> %s<br/></div>")
305
305
306 tooltip_html = tooltip_html % (author, date, message)
306 tooltip_html = tooltip_html % (author, date, message)
307 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
307 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
308 short_id(changeset.raw_id))
308 short_id(changeset.raw_id))
309 uri = link_to(
309 uri = link_to(
310 lnk_format,
310 lnk_format,
311 url('changeset_home', repo_name=repo_name,
311 url('changeset_home', repo_name=repo_name,
312 revision=changeset.raw_id),
312 revision=changeset.raw_id),
313 style=get_color_string(changeset.raw_id),
313 style=get_color_string(changeset.raw_id),
314 class_='tooltip',
314 class_='tooltip',
315 title=tooltip_html
315 title=tooltip_html
316 )
316 )
317
317
318 uri += '\n'
318 uri += '\n'
319 return uri
319 return uri
320 return _url_func
320 return _url_func
321
321
322 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
322 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
323
323
324
324
325 def is_following_repo(repo_name, user_id):
325 def is_following_repo(repo_name, user_id):
326 from rhodecode.model.scm import ScmModel
326 from rhodecode.model.scm import ScmModel
327 return ScmModel().is_following_repo(repo_name, user_id)
327 return ScmModel().is_following_repo(repo_name, user_id)
328
328
329 flash = _Flash()
329 flash = _Flash()
330
330
331 #==============================================================================
331 #==============================================================================
332 # SCM FILTERS available via h.
332 # SCM FILTERS available via h.
333 #==============================================================================
333 #==============================================================================
334 from rhodecode.lib.vcs.utils import author_name, author_email
334 from rhodecode.lib.vcs.utils import author_name, author_email
335 from rhodecode.lib.utils2 import credentials_filter, age as _age
335 from rhodecode.lib.utils2 import credentials_filter, age as _age
336 from rhodecode.model.db import User
336 from rhodecode.model.db import User
337
337
338 age = lambda x: _age(x)
338 age = lambda x: _age(x)
339 capitalize = lambda x: x.capitalize()
339 capitalize = lambda x: x.capitalize()
340 email = author_email
340 email = author_email
341 short_id = lambda x: x[:12]
341 short_id = lambda x: x[:12]
342 hide_credentials = lambda x: ''.join(credentials_filter(x))
342 hide_credentials = lambda x: ''.join(credentials_filter(x))
343
343
344
344
345 def is_git(repository):
345 def is_git(repository):
346 if hasattr(repository, 'alias'):
346 if hasattr(repository, 'alias'):
347 _type = repository.alias
347 _type = repository.alias
348 elif hasattr(repository, 'repo_type'):
348 elif hasattr(repository, 'repo_type'):
349 _type = repository.repo_type
349 _type = repository.repo_type
350 else:
350 else:
351 _type = repository
351 _type = repository
352 return _type == 'git'
352 return _type == 'git'
353
353
354
354
355 def is_hg(repository):
355 def is_hg(repository):
356 if hasattr(repository, 'alias'):
356 if hasattr(repository, 'alias'):
357 _type = repository.alias
357 _type = repository.alias
358 elif hasattr(repository, 'repo_type'):
358 elif hasattr(repository, 'repo_type'):
359 _type = repository.repo_type
359 _type = repository.repo_type
360 else:
360 else:
361 _type = repository
361 _type = repository
362 return _type == 'hg'
362 return _type == 'hg'
363
363
364
364
365 def email_or_none(author):
365 def email_or_none(author):
366 _email = email(author)
366 _email = email(author)
367 if _email != '':
367 if _email != '':
368 return _email
368 return _email
369
369
370 # See if it contains a username we can get an email from
370 # See if it contains a username we can get an email from
371 user = User.get_by_username(author_name(author), case_insensitive=True,
371 user = User.get_by_username(author_name(author), case_insensitive=True,
372 cache=True)
372 cache=True)
373 if user is not None:
373 if user is not None:
374 return user.email
374 return user.email
375
375
376 # No valid email, not a valid user in the system, none!
376 # No valid email, not a valid user in the system, none!
377 return None
377 return None
378
378
379
379
380 def person(author):
380 def person(author):
381 # attr to return from fetched user
381 # attr to return from fetched user
382 person_getter = lambda usr: usr.username
382 person_getter = lambda usr: usr.username
383
383
384 # Valid email in the attribute passed, see if they're in the system
384 # Valid email in the attribute passed, see if they're in the system
385 _email = email(author)
385 _email = email(author)
386 if _email != '':
386 if _email != '':
387 user = User.get_by_email(_email, case_insensitive=True, cache=True)
387 user = User.get_by_email(_email, case_insensitive=True, cache=True)
388 if user is not None:
388 if user is not None:
389 return person_getter(user)
389 return person_getter(user)
390 return _email
390 return _email
391
391
392 # Maybe it's a username?
392 # Maybe it's a username?
393 _author = author_name(author)
393 _author = author_name(author)
394 user = User.get_by_username(_author, case_insensitive=True,
394 user = User.get_by_username(_author, case_insensitive=True,
395 cache=True)
395 cache=True)
396 if user is not None:
396 if user is not None:
397 return person_getter(user)
397 return person_getter(user)
398
398
399 # Still nothing? Just pass back the author name then
399 # Still nothing? Just pass back the author name then
400 return _author
400 return _author
401
401
402
402
403 def bool2icon(value):
403 def bool2icon(value):
404 """Returns True/False values represented as small html image of true/false
404 """Returns True/False values represented as small html image of true/false
405 icons
405 icons
406
406
407 :param value: bool value
407 :param value: bool value
408 """
408 """
409
409
410 if value is True:
410 if value is True:
411 return HTML.tag('img', src=url("/images/icons/accept.png"),
411 return HTML.tag('img', src=url("/images/icons/accept.png"),
412 alt=_('True'))
412 alt=_('True'))
413
413
414 if value is False:
414 if value is False:
415 return HTML.tag('img', src=url("/images/icons/cancel.png"),
415 return HTML.tag('img', src=url("/images/icons/cancel.png"),
416 alt=_('False'))
416 alt=_('False'))
417
417
418 return value
418 return value
419
419
420
420
421 def action_parser(user_log, feed=False):
421 def action_parser(user_log, feed=False):
422 """
422 """
423 This helper will action_map the specified string action into translated
423 This helper will action_map the specified string action into translated
424 fancy names with icons and links
424 fancy names with icons and links
425
425
426 :param user_log: user log instance
426 :param user_log: user log instance
427 :param feed: use output for feeds (no html and fancy icons)
427 :param feed: use output for feeds (no html and fancy icons)
428 """
428 """
429
429
430 action = user_log.action
430 action = user_log.action
431 action_params = ' '
431 action_params = ' '
432
432
433 x = action.split(':')
433 x = action.split(':')
434
434
435 if len(x) > 1:
435 if len(x) > 1:
436 action, action_params = x
436 action, action_params = x
437
437
438 def get_cs_links():
438 def get_cs_links():
439 revs_limit = 3 # display this amount always
439 revs_limit = 3 # display this amount always
440 revs_top_limit = 50 # show upto this amount of changesets hidden
440 revs_top_limit = 50 # show upto this amount of changesets hidden
441 revs_ids = action_params.split(',')
441 revs_ids = action_params.split(',')
442 deleted = user_log.repository is None
442 deleted = user_log.repository is None
443 if deleted:
443 if deleted:
444 return ','.join(revs_ids)
444 return ','.join(revs_ids)
445
445
446 repo_name = user_log.repository.repo_name
446 repo_name = user_log.repository.repo_name
447
447
448 repo = user_log.repository.scm_instance
448 repo = user_log.repository.scm_instance
449
449
450 message = lambda rev: rev.message
450 message = lambda rev: rev.message
451 lnk = lambda rev, repo_name: (
451 lnk = lambda rev, repo_name: (
452 link_to('r%s:%s' % (rev.revision, rev.short_id),
452 link_to('r%s:%s' % (rev.revision, rev.short_id),
453 url('changeset_home', repo_name=repo_name,
453 url('changeset_home', repo_name=repo_name,
454 revision=rev.raw_id),
454 revision=rev.raw_id),
455 title=tooltip(message(rev)), class_='tooltip')
455 title=tooltip(message(rev)), class_='tooltip')
456 )
456 )
457 # get only max revs_top_limit of changeset for performance/ui reasons
457
458 revs = [
458 revs = []
459 x for x in repo.get_changesets(revs_ids[0],
459 if len(filter(lambda v: v != '', revs_ids)) > 0:
460 revs_ids[:revs_top_limit][-1])
460 # get only max revs_top_limit of changeset for performance/ui reasons
461 ]
461 revs = [
462 x for x in repo.get_changesets(revs_ids[0],
463 revs_ids[:revs_top_limit][-1])
464 ]
462
465
463 cs_links = []
466 cs_links = []
464 cs_links.append(" " + ', '.join(
467 cs_links.append(" " + ', '.join(
465 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
468 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
466 )
469 )
467 )
470 )
468
471
469 compare_view = (
472 compare_view = (
470 ' <div class="compare_view tooltip" title="%s">'
473 ' <div class="compare_view tooltip" title="%s">'
471 '<a href="%s">%s</a> </div>' % (
474 '<a href="%s">%s</a> </div>' % (
472 _('Show all combined changesets %s->%s') % (
475 _('Show all combined changesets %s->%s') % (
473 revs_ids[0], revs_ids[-1]
476 revs_ids[0], revs_ids[-1]
474 ),
477 ),
475 url('changeset_home', repo_name=repo_name,
478 url('changeset_home', repo_name=repo_name,
476 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
479 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
477 ),
480 ),
478 _('compare view')
481 _('compare view')
479 )
482 )
480 )
483 )
481
484
482 # if we have exactly one more than normally displayed
485 # if we have exactly one more than normally displayed
483 # just display it, takes less space than displaying
486 # just display it, takes less space than displaying
484 # "and 1 more revisions"
487 # "and 1 more revisions"
485 if len(revs_ids) == revs_limit + 1:
488 if len(revs_ids) == revs_limit + 1:
486 rev = revs[revs_limit]
489 rev = revs[revs_limit]
487 cs_links.append(", " + lnk(rev, repo_name))
490 cs_links.append(", " + lnk(rev, repo_name))
488
491
489 # hidden-by-default ones
492 # hidden-by-default ones
490 if len(revs_ids) > revs_limit + 1:
493 if len(revs_ids) > revs_limit + 1:
491 uniq_id = revs_ids[0]
494 uniq_id = revs_ids[0]
492 html_tmpl = (
495 html_tmpl = (
493 '<span> %s <a class="show_more" id="_%s" '
496 '<span> %s <a class="show_more" id="_%s" '
494 'href="#more">%s</a> %s</span>'
497 'href="#more">%s</a> %s</span>'
495 )
498 )
496 if not feed:
499 if not feed:
497 cs_links.append(html_tmpl % (
500 cs_links.append(html_tmpl % (
498 _('and'),
501 _('and'),
499 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
502 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
500 _('revisions')
503 _('revisions')
501 )
504 )
502 )
505 )
503
506
504 if not feed:
507 if not feed:
505 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
508 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
506 else:
509 else:
507 html_tmpl = '<span id="%s"> %s </span>'
510 html_tmpl = '<span id="%s"> %s </span>'
508
511
509 morelinks = ', '.join(
512 morelinks = ', '.join(
510 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
513 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
511 )
514 )
512
515
513 if len(revs_ids) > revs_top_limit:
516 if len(revs_ids) > revs_top_limit:
514 morelinks += ', ...'
517 morelinks += ', ...'
515
518
516 cs_links.append(html_tmpl % (uniq_id, morelinks))
519 cs_links.append(html_tmpl % (uniq_id, morelinks))
517 if len(revs) > 1:
520 if len(revs) > 1:
518 cs_links.append(compare_view)
521 cs_links.append(compare_view)
519 return ''.join(cs_links)
522 return ''.join(cs_links)
520
523
521 def get_fork_name():
524 def get_fork_name():
522 repo_name = action_params
525 repo_name = action_params
523 return _('fork name ') + str(link_to(action_params, url('summary_home',
526 return _('fork name ') + str(link_to(action_params, url('summary_home',
524 repo_name=repo_name,)))
527 repo_name=repo_name,)))
525
528
526 action_map = {'user_deleted_repo': (_('[deleted] repository'), None),
529 action_map = {'user_deleted_repo': (_('[deleted] repository'), None),
527 'user_created_repo': (_('[created] repository'), None),
530 'user_created_repo': (_('[created] repository'), None),
528 'user_created_fork': (_('[created] repository as fork'), None),
531 'user_created_fork': (_('[created] repository as fork'), None),
529 'user_forked_repo': (_('[forked] repository'), get_fork_name),
532 'user_forked_repo': (_('[forked] repository'), get_fork_name),
530 'user_updated_repo': (_('[updated] repository'), None),
533 'user_updated_repo': (_('[updated] repository'), None),
531 'admin_deleted_repo': (_('[delete] repository'), None),
534 'admin_deleted_repo': (_('[delete] repository'), None),
532 'admin_created_repo': (_('[created] repository'), None),
535 'admin_created_repo': (_('[created] repository'), None),
533 'admin_forked_repo': (_('[forked] repository'), None),
536 'admin_forked_repo': (_('[forked] repository'), None),
534 'admin_updated_repo': (_('[updated] repository'), None),
537 'admin_updated_repo': (_('[updated] repository'), None),
535 'push': (_('[pushed] into'), get_cs_links),
538 'push': (_('[pushed] into'), get_cs_links),
536 'push_local': (_('[committed via RhodeCode] into'), get_cs_links),
539 'push_local': (_('[committed via RhodeCode] into'), get_cs_links),
537 'push_remote': (_('[pulled from remote] into'), get_cs_links),
540 'push_remote': (_('[pulled from remote] into'), get_cs_links),
538 'pull': (_('[pulled] from'), None),
541 'pull': (_('[pulled] from'), None),
539 'started_following_repo': (_('[started following] repository'), None),
542 'started_following_repo': (_('[started following] repository'), None),
540 'stopped_following_repo': (_('[stopped following] repository'), None),
543 'stopped_following_repo': (_('[stopped following] repository'), None),
541 }
544 }
542
545
543 action_str = action_map.get(action, action)
546 action_str = action_map.get(action, action)
544 if feed:
547 if feed:
545 action = action_str[0].replace('[', '').replace(']', '')
548 action = action_str[0].replace('[', '').replace(']', '')
546 else:
549 else:
547 action = action_str[0]\
550 action = action_str[0]\
548 .replace('[', '<span class="journal_highlight">')\
551 .replace('[', '<span class="journal_highlight">')\
549 .replace(']', '</span>')
552 .replace(']', '</span>')
550
553
551 action_params_func = lambda: ""
554 action_params_func = lambda: ""
552
555
553 if callable(action_str[1]):
556 if callable(action_str[1]):
554 action_params_func = action_str[1]
557 action_params_func = action_str[1]
555
558
556 return [literal(action), action_params_func]
559 return [literal(action), action_params_func]
557
560
558
561
559 def action_parser_icon(user_log):
562 def action_parser_icon(user_log):
560 action = user_log.action
563 action = user_log.action
561 action_params = None
564 action_params = None
562 x = action.split(':')
565 x = action.split(':')
563
566
564 if len(x) > 1:
567 if len(x) > 1:
565 action, action_params = x
568 action, action_params = x
566
569
567 tmpl = """<img src="%s%s" alt="%s"/>"""
570 tmpl = """<img src="%s%s" alt="%s"/>"""
568 map = {'user_deleted_repo':'database_delete.png',
571 map = {'user_deleted_repo':'database_delete.png',
569 'user_created_repo':'database_add.png',
572 'user_created_repo':'database_add.png',
570 'user_created_fork':'arrow_divide.png',
573 'user_created_fork':'arrow_divide.png',
571 'user_forked_repo':'arrow_divide.png',
574 'user_forked_repo':'arrow_divide.png',
572 'user_updated_repo':'database_edit.png',
575 'user_updated_repo':'database_edit.png',
573 'admin_deleted_repo':'database_delete.png',
576 'admin_deleted_repo':'database_delete.png',
574 'admin_created_repo':'database_add.png',
577 'admin_created_repo':'database_add.png',
575 'admin_forked_repo':'arrow_divide.png',
578 'admin_forked_repo':'arrow_divide.png',
576 'admin_updated_repo':'database_edit.png',
579 'admin_updated_repo':'database_edit.png',
577 'push':'script_add.png',
580 'push':'script_add.png',
578 'push_local':'script_edit.png',
581 'push_local':'script_edit.png',
579 'push_remote':'connect.png',
582 'push_remote':'connect.png',
580 'pull':'down_16.png',
583 'pull':'down_16.png',
581 'started_following_repo':'heart_add.png',
584 'started_following_repo':'heart_add.png',
582 'stopped_following_repo':'heart_delete.png',
585 'stopped_following_repo':'heart_delete.png',
583 }
586 }
584 return literal(tmpl % ((url('/images/icons/')),
587 return literal(tmpl % ((url('/images/icons/')),
585 map.get(action, action), action))
588 map.get(action, action), action))
586
589
587
590
588 #==============================================================================
591 #==============================================================================
589 # PERMS
592 # PERMS
590 #==============================================================================
593 #==============================================================================
591 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
594 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
592 HasRepoPermissionAny, HasRepoPermissionAll
595 HasRepoPermissionAny, HasRepoPermissionAll
593
596
594
597
595 #==============================================================================
598 #==============================================================================
596 # GRAVATAR URL
599 # GRAVATAR URL
597 #==============================================================================
600 #==============================================================================
598
601
599 def gravatar_url(email_address, size=30):
602 def gravatar_url(email_address, size=30):
600 if (not str2bool(config['app_conf'].get('use_gravatar')) or
603 if (not str2bool(config['app_conf'].get('use_gravatar')) or
601 not email_address or email_address == 'anonymous@rhodecode.org'):
604 not email_address or email_address == 'anonymous@rhodecode.org'):
602 f = lambda a, l: min(l, key=lambda x: abs(x - a))
605 f = lambda a, l: min(l, key=lambda x: abs(x - a))
603 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
606 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
604
607
605 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
608 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
606 default = 'identicon'
609 default = 'identicon'
607 baseurl_nossl = "http://www.gravatar.com/avatar/"
610 baseurl_nossl = "http://www.gravatar.com/avatar/"
608 baseurl_ssl = "https://secure.gravatar.com/avatar/"
611 baseurl_ssl = "https://secure.gravatar.com/avatar/"
609 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
612 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
610
613
611 if isinstance(email_address, unicode):
614 if isinstance(email_address, unicode):
612 #hashlib crashes on unicode items
615 #hashlib crashes on unicode items
613 email_address = safe_str(email_address)
616 email_address = safe_str(email_address)
614 # construct the url
617 # construct the url
615 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
618 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
616 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
619 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
617
620
618 return gravatar_url
621 return gravatar_url
619
622
620
623
621 #==============================================================================
624 #==============================================================================
622 # REPO PAGER, PAGER FOR REPOSITORY
625 # REPO PAGER, PAGER FOR REPOSITORY
623 #==============================================================================
626 #==============================================================================
624 class RepoPage(Page):
627 class RepoPage(Page):
625
628
626 def __init__(self, collection, page=1, items_per_page=20,
629 def __init__(self, collection, page=1, items_per_page=20,
627 item_count=None, url=None, **kwargs):
630 item_count=None, url=None, **kwargs):
628
631
629 """Create a "RepoPage" instance. special pager for paging
632 """Create a "RepoPage" instance. special pager for paging
630 repository
633 repository
631 """
634 """
632 self._url_generator = url
635 self._url_generator = url
633
636
634 # Safe the kwargs class-wide so they can be used in the pager() method
637 # Safe the kwargs class-wide so they can be used in the pager() method
635 self.kwargs = kwargs
638 self.kwargs = kwargs
636
639
637 # Save a reference to the collection
640 # Save a reference to the collection
638 self.original_collection = collection
641 self.original_collection = collection
639
642
640 self.collection = collection
643 self.collection = collection
641
644
642 # The self.page is the number of the current page.
645 # The self.page is the number of the current page.
643 # The first page has the number 1!
646 # The first page has the number 1!
644 try:
647 try:
645 self.page = int(page) # make it int() if we get it as a string
648 self.page = int(page) # make it int() if we get it as a string
646 except (ValueError, TypeError):
649 except (ValueError, TypeError):
647 self.page = 1
650 self.page = 1
648
651
649 self.items_per_page = items_per_page
652 self.items_per_page = items_per_page
650
653
651 # Unless the user tells us how many items the collections has
654 # Unless the user tells us how many items the collections has
652 # we calculate that ourselves.
655 # we calculate that ourselves.
653 if item_count is not None:
656 if item_count is not None:
654 self.item_count = item_count
657 self.item_count = item_count
655 else:
658 else:
656 self.item_count = len(self.collection)
659 self.item_count = len(self.collection)
657
660
658 # Compute the number of the first and last available page
661 # Compute the number of the first and last available page
659 if self.item_count > 0:
662 if self.item_count > 0:
660 self.first_page = 1
663 self.first_page = 1
661 self.page_count = int(math.ceil(float(self.item_count) /
664 self.page_count = int(math.ceil(float(self.item_count) /
662 self.items_per_page))
665 self.items_per_page))
663 self.last_page = self.first_page + self.page_count - 1
666 self.last_page = self.first_page + self.page_count - 1
664
667
665 # Make sure that the requested page number is the range of
668 # Make sure that the requested page number is the range of
666 # valid pages
669 # valid pages
667 if self.page > self.last_page:
670 if self.page > self.last_page:
668 self.page = self.last_page
671 self.page = self.last_page
669 elif self.page < self.first_page:
672 elif self.page < self.first_page:
670 self.page = self.first_page
673 self.page = self.first_page
671
674
672 # Note: the number of items on this page can be less than
675 # Note: the number of items on this page can be less than
673 # items_per_page if the last page is not full
676 # items_per_page if the last page is not full
674 self.first_item = max(0, (self.item_count) - (self.page *
677 self.first_item = max(0, (self.item_count) - (self.page *
675 items_per_page))
678 items_per_page))
676 self.last_item = ((self.item_count - 1) - items_per_page *
679 self.last_item = ((self.item_count - 1) - items_per_page *
677 (self.page - 1))
680 (self.page - 1))
678
681
679 self.items = list(self.collection[self.first_item:self.last_item + 1])
682 self.items = list(self.collection[self.first_item:self.last_item + 1])
680
683
681 # Links to previous and next page
684 # Links to previous and next page
682 if self.page > self.first_page:
685 if self.page > self.first_page:
683 self.previous_page = self.page - 1
686 self.previous_page = self.page - 1
684 else:
687 else:
685 self.previous_page = None
688 self.previous_page = None
686
689
687 if self.page < self.last_page:
690 if self.page < self.last_page:
688 self.next_page = self.page + 1
691 self.next_page = self.page + 1
689 else:
692 else:
690 self.next_page = None
693 self.next_page = None
691
694
692 # No items available
695 # No items available
693 else:
696 else:
694 self.first_page = None
697 self.first_page = None
695 self.page_count = 0
698 self.page_count = 0
696 self.last_page = None
699 self.last_page = None
697 self.first_item = None
700 self.first_item = None
698 self.last_item = None
701 self.last_item = None
699 self.previous_page = None
702 self.previous_page = None
700 self.next_page = None
703 self.next_page = None
701 self.items = []
704 self.items = []
702
705
703 # This is a subclass of the 'list' type. Initialise the list now.
706 # This is a subclass of the 'list' type. Initialise the list now.
704 list.__init__(self, reversed(self.items))
707 list.__init__(self, reversed(self.items))
705
708
706
709
707 def changed_tooltip(nodes):
710 def changed_tooltip(nodes):
708 """
711 """
709 Generates a html string for changed nodes in changeset page.
712 Generates a html string for changed nodes in changeset page.
710 It limits the output to 30 entries
713 It limits the output to 30 entries
711
714
712 :param nodes: LazyNodesGenerator
715 :param nodes: LazyNodesGenerator
713 """
716 """
714 if nodes:
717 if nodes:
715 pref = ': <br/> '
718 pref = ': <br/> '
716 suf = ''
719 suf = ''
717 if len(nodes) > 30:
720 if len(nodes) > 30:
718 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
721 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
719 return literal(pref + '<br/> '.join([safe_unicode(x.path)
722 return literal(pref + '<br/> '.join([safe_unicode(x.path)
720 for x in nodes[:30]]) + suf)
723 for x in nodes[:30]]) + suf)
721 else:
724 else:
722 return ': ' + _('No Files')
725 return ': ' + _('No Files')
723
726
724
727
725 def repo_link(groups_and_repos):
728 def repo_link(groups_and_repos):
726 """
729 """
727 Makes a breadcrumbs link to repo within a group
730 Makes a breadcrumbs link to repo within a group
728 joins &raquo; on each group to create a fancy link
731 joins &raquo; on each group to create a fancy link
729
732
730 ex::
733 ex::
731 group >> subgroup >> repo
734 group >> subgroup >> repo
732
735
733 :param groups_and_repos:
736 :param groups_and_repos:
734 """
737 """
735 groups, repo_name = groups_and_repos
738 groups, repo_name = groups_and_repos
736
739
737 if not groups:
740 if not groups:
738 return repo_name
741 return repo_name
739 else:
742 else:
740 def make_link(group):
743 def make_link(group):
741 return link_to(group.name, url('repos_group_home',
744 return link_to(group.name, url('repos_group_home',
742 group_name=group.group_name))
745 group_name=group.group_name))
743 return literal(' &raquo; '.join(map(make_link, groups)) + \
746 return literal(' &raquo; '.join(map(make_link, groups)) + \
744 " &raquo; " + repo_name)
747 " &raquo; " + repo_name)
745
748
746
749
747 def fancy_file_stats(stats):
750 def fancy_file_stats(stats):
748 """
751 """
749 Displays a fancy two colored bar for number of added/deleted
752 Displays a fancy two colored bar for number of added/deleted
750 lines of code on file
753 lines of code on file
751
754
752 :param stats: two element list of added/deleted lines of code
755 :param stats: two element list of added/deleted lines of code
753 """
756 """
754
757
755 a, d, t = stats[0], stats[1], stats[0] + stats[1]
758 a, d, t = stats[0], stats[1], stats[0] + stats[1]
756 width = 100
759 width = 100
757 unit = float(width) / (t or 1)
760 unit = float(width) / (t or 1)
758
761
759 # needs > 9% of width to be visible or 0 to be hidden
762 # needs > 9% of width to be visible or 0 to be hidden
760 a_p = max(9, unit * a) if a > 0 else 0
763 a_p = max(9, unit * a) if a > 0 else 0
761 d_p = max(9, unit * d) if d > 0 else 0
764 d_p = max(9, unit * d) if d > 0 else 0
762 p_sum = a_p + d_p
765 p_sum = a_p + d_p
763
766
764 if p_sum > width:
767 if p_sum > width:
765 #adjust the percentage to be == 100% since we adjusted to 9
768 #adjust the percentage to be == 100% since we adjusted to 9
766 if a_p > d_p:
769 if a_p > d_p:
767 a_p = a_p - (p_sum - width)
770 a_p = a_p - (p_sum - width)
768 else:
771 else:
769 d_p = d_p - (p_sum - width)
772 d_p = d_p - (p_sum - width)
770
773
771 a_v = a if a > 0 else ''
774 a_v = a if a > 0 else ''
772 d_v = d if d > 0 else ''
775 d_v = d if d > 0 else ''
773
776
774 def cgen(l_type):
777 def cgen(l_type):
775 mapping = {'tr': 'top-right-rounded-corner-mid',
778 mapping = {'tr': 'top-right-rounded-corner-mid',
776 'tl': 'top-left-rounded-corner-mid',
779 'tl': 'top-left-rounded-corner-mid',
777 'br': 'bottom-right-rounded-corner-mid',
780 'br': 'bottom-right-rounded-corner-mid',
778 'bl': 'bottom-left-rounded-corner-mid'}
781 'bl': 'bottom-left-rounded-corner-mid'}
779 map_getter = lambda x: mapping[x]
782 map_getter = lambda x: mapping[x]
780
783
781 if l_type == 'a' and d_v:
784 if l_type == 'a' and d_v:
782 #case when added and deleted are present
785 #case when added and deleted are present
783 return ' '.join(map(map_getter, ['tl', 'bl']))
786 return ' '.join(map(map_getter, ['tl', 'bl']))
784
787
785 if l_type == 'a' and not d_v:
788 if l_type == 'a' and not d_v:
786 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
789 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
787
790
788 if l_type == 'd' and a_v:
791 if l_type == 'd' and a_v:
789 return ' '.join(map(map_getter, ['tr', 'br']))
792 return ' '.join(map(map_getter, ['tr', 'br']))
790
793
791 if l_type == 'd' and not a_v:
794 if l_type == 'd' and not a_v:
792 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
795 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
793
796
794 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
797 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
795 cgen('a'), a_p, a_v
798 cgen('a'), a_p, a_v
796 )
799 )
797 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
800 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
798 cgen('d'), d_p, d_v
801 cgen('d'), d_p, d_v
799 )
802 )
800 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
803 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
801
804
802
805
803 def urlify_text(text_):
806 def urlify_text(text_):
804 import re
807 import re
805
808
806 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
809 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
807 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
810 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
808
811
809 def url_func(match_obj):
812 def url_func(match_obj):
810 url_full = match_obj.groups()[0]
813 url_full = match_obj.groups()[0]
811 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
814 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
812
815
813 return literal(url_pat.sub(url_func, text_))
816 return literal(url_pat.sub(url_func, text_))
814
817
815
818
816 def urlify_changesets(text_, repository):
819 def urlify_changesets(text_, repository):
817 """
820 """
818 Extract revision ids from changeset and make link from them
821 Extract revision ids from changeset and make link from them
819
822
820 :param text_:
823 :param text_:
821 :param repository:
824 :param repository:
822 """
825 """
823 import re
826 import re
824 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
827 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
825
828
826 def url_func(match_obj):
829 def url_func(match_obj):
827 rev = match_obj.groups()[0]
830 rev = match_obj.groups()[0]
828 pref = ''
831 pref = ''
829 if match_obj.group().startswith(' '):
832 if match_obj.group().startswith(' '):
830 pref = ' '
833 pref = ' '
831 tmpl = (
834 tmpl = (
832 '%(pref)s<a class="%(cls)s" href="%(url)s">'
835 '%(pref)s<a class="%(cls)s" href="%(url)s">'
833 '%(rev)s'
836 '%(rev)s'
834 '</a>'
837 '</a>'
835 )
838 )
836 return tmpl % {
839 return tmpl % {
837 'pref': pref,
840 'pref': pref,
838 'cls': 'revision-link',
841 'cls': 'revision-link',
839 'url': url('changeset_home', repo_name=repository, revision=rev),
842 'url': url('changeset_home', repo_name=repository, revision=rev),
840 'rev': rev,
843 'rev': rev,
841 }
844 }
842
845
843 newtext = URL_PAT.sub(url_func, text_)
846 newtext = URL_PAT.sub(url_func, text_)
844
847
845 return newtext
848 return newtext
846
849
847
850
848 def urlify_commit(text_, repository=None, link_=None):
851 def urlify_commit(text_, repository=None, link_=None):
849 """
852 """
850 Parses given text message and makes proper links.
853 Parses given text message and makes proper links.
851 issues are linked to given issue-server, and rest is a changeset link
854 issues are linked to given issue-server, and rest is a changeset link
852 if link_ is given, in other case it's a plain text
855 if link_ is given, in other case it's a plain text
853
856
854 :param text_:
857 :param text_:
855 :param repository:
858 :param repository:
856 :param link_: changeset link
859 :param link_: changeset link
857 """
860 """
858 import re
861 import re
859 import traceback
862 import traceback
860
863
861 def escaper(string):
864 def escaper(string):
862 return string.replace('<', '&lt;').replace('>', '&gt;')
865 return string.replace('<', '&lt;').replace('>', '&gt;')
863
866
864 def linkify_others(t, l):
867 def linkify_others(t, l):
865 urls = re.compile(r'(\<a.*?\<\/a\>)',)
868 urls = re.compile(r'(\<a.*?\<\/a\>)',)
866 links = []
869 links = []
867 for e in urls.split(t):
870 for e in urls.split(t):
868 if not urls.match(e):
871 if not urls.match(e):
869 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
872 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
870 else:
873 else:
871 links.append(e)
874 links.append(e)
872
875
873 return ''.join(links)
876 return ''.join(links)
874
877
875
878
876 # urlify changesets - extrac revisions and make link out of them
879 # urlify changesets - extrac revisions and make link out of them
877 text_ = urlify_changesets(escaper(text_), repository)
880 text_ = urlify_changesets(escaper(text_), repository)
878
881
879 try:
882 try:
880 conf = config['app_conf']
883 conf = config['app_conf']
881
884
882 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
885 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
883
886
884 if URL_PAT:
887 if URL_PAT:
885 ISSUE_SERVER_LNK = conf.get('issue_server_link')
888 ISSUE_SERVER_LNK = conf.get('issue_server_link')
886 ISSUE_PREFIX = conf.get('issue_prefix')
889 ISSUE_PREFIX = conf.get('issue_prefix')
887
890
888 def url_func(match_obj):
891 def url_func(match_obj):
889 pref = ''
892 pref = ''
890 if match_obj.group().startswith(' '):
893 if match_obj.group().startswith(' '):
891 pref = ' '
894 pref = ' '
892
895
893 issue_id = ''.join(match_obj.groups())
896 issue_id = ''.join(match_obj.groups())
894 tmpl = (
897 tmpl = (
895 '%(pref)s<a class="%(cls)s" href="%(url)s">'
898 '%(pref)s<a class="%(cls)s" href="%(url)s">'
896 '%(issue-prefix)s%(id-repr)s'
899 '%(issue-prefix)s%(id-repr)s'
897 '</a>'
900 '</a>'
898 )
901 )
899 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
902 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
900 if repository:
903 if repository:
901 url = url.replace('{repo}', repository)
904 url = url.replace('{repo}', repository)
902
905
903 return tmpl % {
906 return tmpl % {
904 'pref': pref,
907 'pref': pref,
905 'cls': 'issue-tracker-link',
908 'cls': 'issue-tracker-link',
906 'url': url,
909 'url': url,
907 'id-repr': issue_id,
910 'id-repr': issue_id,
908 'issue-prefix': ISSUE_PREFIX,
911 'issue-prefix': ISSUE_PREFIX,
909 'serv': ISSUE_SERVER_LNK,
912 'serv': ISSUE_SERVER_LNK,
910 }
913 }
911
914
912 newtext = URL_PAT.sub(url_func, text_)
915 newtext = URL_PAT.sub(url_func, text_)
913
916
914 if link_:
917 if link_:
915 # wrap not links into final link => link_
918 # wrap not links into final link => link_
916 newtext = linkify_others(newtext, link_)
919 newtext = linkify_others(newtext, link_)
917
920
918 return literal(newtext)
921 return literal(newtext)
919 except:
922 except:
920 log.error(traceback.format_exc())
923 log.error(traceback.format_exc())
921 pass
924 pass
922
925
923 return text_
926 return text_
924
927
925
928
926 def rst(source):
929 def rst(source):
927 return literal('<div class="rst-block">%s</div>' %
930 return literal('<div class="rst-block">%s</div>' %
928 MarkupRenderer.rst(source))
931 MarkupRenderer.rst(source))
929
932
930
933
931 def rst_w_mentions(source):
934 def rst_w_mentions(source):
932 """
935 """
933 Wrapped rst renderer with @mention highlighting
936 Wrapped rst renderer with @mention highlighting
934
937
935 :param source:
938 :param source:
936 """
939 """
937 return literal('<div class="rst-block">%s</div>' %
940 return literal('<div class="rst-block">%s</div>' %
938 MarkupRenderer.rst_with_mentions(source))
941 MarkupRenderer.rst_with_mentions(source))
@@ -1,184 +1,190 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.hooks
3 rhodecode.lib.hooks
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Hooks runned by rhodecode
6 Hooks runned by rhodecode
7
7
8 :created_on: Aug 6, 2010
8 :created_on: Aug 6, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import os
26 import sys
26 import sys
27
27
28 from mercurial.scmutil import revrange
28 from mercurial.scmutil import revrange
29 from mercurial.node import nullrev
29 from mercurial.node import nullrev
30 from rhodecode import EXTENSIONS
30 from rhodecode import EXTENSIONS
31 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
32 from rhodecode.lib.utils import action_logger
32 from rhodecode.lib.utils import action_logger
33 from inspect import isfunction
33 from inspect import isfunction
34
34
35
35
36 def _get_scm_size(alias, root_path):
36 def _get_scm_size(alias, root_path):
37
37
38 if not alias.startswith('.'):
38 if not alias.startswith('.'):
39 alias += '.'
39 alias += '.'
40
40
41 size_scm, size_root = 0, 0
41 size_scm, size_root = 0, 0
42 for path, dirs, files in os.walk(root_path):
42 for path, dirs, files in os.walk(root_path):
43 if path.find(alias) != -1:
43 if path.find(alias) != -1:
44 for f in files:
44 for f in files:
45 try:
45 try:
46 size_scm += os.path.getsize(os.path.join(path, f))
46 size_scm += os.path.getsize(os.path.join(path, f))
47 except OSError:
47 except OSError:
48 pass
48 pass
49 else:
49 else:
50 for f in files:
50 for f in files:
51 try:
51 try:
52 size_root += os.path.getsize(os.path.join(path, f))
52 size_root += os.path.getsize(os.path.join(path, f))
53 except OSError:
53 except OSError:
54 pass
54 pass
55
55
56 size_scm_f = h.format_byte_size(size_scm)
56 size_scm_f = h.format_byte_size(size_scm)
57 size_root_f = h.format_byte_size(size_root)
57 size_root_f = h.format_byte_size(size_root)
58 size_total_f = h.format_byte_size(size_root + size_scm)
58 size_total_f = h.format_byte_size(size_root + size_scm)
59
59
60 return size_scm_f, size_root_f, size_total_f
60 return size_scm_f, size_root_f, size_total_f
61
61
62
62
63 def repo_size(ui, repo, hooktype=None, **kwargs):
63 def repo_size(ui, repo, hooktype=None, **kwargs):
64 """
64 """
65 Presents size of repository after push
65 Presents size of repository after push
66
66
67 :param ui:
67 :param ui:
68 :param repo:
68 :param repo:
69 :param hooktype:
69 :param hooktype:
70 """
70 """
71
71
72 size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
72 size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
73
73
74 last_cs = repo[len(repo) - 1]
74 last_cs = repo[len(repo) - 1]
75
75
76 msg = ('Repository size .hg:%s repo:%s total:%s\n'
76 msg = ('Repository size .hg:%s repo:%s total:%s\n'
77 'Last revision is now r%s:%s\n') % (
77 'Last revision is now r%s:%s\n') % (
78 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
78 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
79 )
79 )
80
80
81 sys.stdout.write(msg)
81 sys.stdout.write(msg)
82
82
83
83
84 def log_pull_action(ui, repo, **kwargs):
84 def log_pull_action(ui, repo, **kwargs):
85 """
85 """
86 Logs user last pull action
86 Logs user last pull action
87
87
88 :param ui:
88 :param ui:
89 :param repo:
89 :param repo:
90 """
90 """
91
91
92 extras = dict(repo.ui.configitems('rhodecode_extras'))
92 extras = dict(repo.ui.configitems('rhodecode_extras'))
93 username = extras['username']
93 username = extras['username']
94 repository = extras['repository']
94 repository = extras['repository']
95 scm = extras['scm']
95 action = 'pull'
96 action = 'pull'
96
97
97 action_logger(username, action, repository, extras['ip'], commit=True)
98 action_logger(username, action, repository, extras['ip'], commit=True)
98 # extension hook call
99 # extension hook call
99 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
100 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
100
101
101 if isfunction(callback):
102 if isfunction(callback):
102 kw = {}
103 kw = {}
103 kw.update(extras)
104 kw.update(extras)
104 callback(**kw)
105 callback(**kw)
105 return 0
106 return 0
106
107
107
108
108 def log_push_action(ui, repo, **kwargs):
109 def log_push_action(ui, repo, **kwargs):
109 """
110 """
110 Maps user last push action to new changeset id, from mercurial
111 Maps user last push action to new changeset id, from mercurial
111
112
112 :param ui:
113 :param ui:
113 :param repo:
114 :param repo:
114 """
115 """
115
116
116 extras = dict(repo.ui.configitems('rhodecode_extras'))
117 extras = dict(repo.ui.configitems('rhodecode_extras'))
117 username = extras['username']
118 username = extras['username']
118 repository = extras['repository']
119 repository = extras['repository']
119 action = extras['action'] + ':%s'
120 action = extras['action'] + ':%s'
120 node = kwargs['node']
121 scm = extras['scm']
121
122
122 def get_revs(repo, rev_opt):
123 if scm == 'hg':
123 if rev_opt:
124 node = kwargs['node']
124 revs = revrange(repo, rev_opt)
125
126 def get_revs(repo, rev_opt):
127 if rev_opt:
128 revs = revrange(repo, rev_opt)
125
129
126 if len(revs) == 0:
130 if len(revs) == 0:
127 return (nullrev, nullrev)
131 return (nullrev, nullrev)
128 return (max(revs), min(revs))
132 return (max(revs), min(revs))
129 else:
133 else:
130 return (len(repo) - 1, 0)
134 return (len(repo) - 1, 0)
131
135
132 stop, start = get_revs(repo, [node + ':'])
136 stop, start = get_revs(repo, [node + ':'])
133
137
134 revs = (str(repo[r]) for r in xrange(start, stop + 1))
138 revs = (str(repo[r]) for r in xrange(start, stop + 1))
139 elif scm == 'git':
140 revs = []
135
141
136 action = action % ','.join(revs)
142 action = action % ','.join(revs)
137
143
138 action_logger(username, action, repository, extras['ip'], commit=True)
144 action_logger(username, action, repository, extras['ip'], commit=True)
139
145
140 # extension hook call
146 # extension hook call
141 callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
147 callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
142 if isfunction(callback):
148 if isfunction(callback):
143 kw = {'pushed_revs': revs}
149 kw = {'pushed_revs': revs}
144 kw.update(extras)
150 kw.update(extras)
145 callback(**kw)
151 callback(**kw)
146 return 0
152 return 0
147
153
148
154
149 def log_create_repository(repository_dict, created_by, **kwargs):
155 def log_create_repository(repository_dict, created_by, **kwargs):
150 """
156 """
151 Post create repository Hook. This is a dummy function for admins to re-use
157 Post create repository Hook. This is a dummy function for admins to re-use
152 if needed. It's taken from rhodecode-extensions module and executed
158 if needed. It's taken from rhodecode-extensions module and executed
153 if present
159 if present
154
160
155 :param repository: dict dump of repository object
161 :param repository: dict dump of repository object
156 :param created_by: username who created repository
162 :param created_by: username who created repository
157 :param created_date: date of creation
163 :param created_date: date of creation
158
164
159 available keys of repository_dict:
165 available keys of repository_dict:
160
166
161 'repo_type',
167 'repo_type',
162 'description',
168 'description',
163 'private',
169 'private',
164 'created_on',
170 'created_on',
165 'enable_downloads',
171 'enable_downloads',
166 'repo_id',
172 'repo_id',
167 'user_id',
173 'user_id',
168 'enable_statistics',
174 'enable_statistics',
169 'clone_uri',
175 'clone_uri',
170 'fork_id',
176 'fork_id',
171 'group_id',
177 'group_id',
172 'repo_name'
178 'repo_name'
173
179
174 """
180 """
175
181
176 callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
182 callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
177 if isfunction(callback):
183 if isfunction(callback):
178 kw = {}
184 kw = {}
179 kw.update(repository_dict)
185 kw.update(repository_dict)
180 kw.update({'created_by': created_by})
186 kw.update({'created_by': created_by})
181 kw.update(kwargs)
187 kw.update(kwargs)
182 return callback(**kw)
188 return callback(**kw)
183
189
184 return 0
190 return 0
@@ -1,251 +1,287 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplegit
3 rhodecode.lib.middleware.simplegit
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleGit middleware for handling git protocol request (push/clone etc.)
6 SimpleGit middleware for handling git protocol request (push/clone etc.)
7 It's implemented with basic auth function
7 It's implemented with basic auth function
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import re
28 import re
29 import logging
29 import logging
30 import traceback
30 import traceback
31
31
32 from dulwich import server as dulserver
32 from dulwich import server as dulserver
33
33
34
34
35 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
35 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
36
36
37 def handle(self):
37 def handle(self):
38 write = lambda x: self.proto.write_sideband(1, x)
38 write = lambda x: self.proto.write_sideband(1, x)
39
39
40 graph_walker = dulserver.ProtocolGraphWalker(self,
40 graph_walker = dulserver.ProtocolGraphWalker(self,
41 self.repo.object_store,
41 self.repo.object_store,
42 self.repo.get_peeled)
42 self.repo.get_peeled)
43 objects_iter = self.repo.fetch_objects(
43 objects_iter = self.repo.fetch_objects(
44 graph_walker.determine_wants, graph_walker, self.progress,
44 graph_walker.determine_wants, graph_walker, self.progress,
45 get_tagged=self.get_tagged)
45 get_tagged=self.get_tagged)
46
46
47 # Did the process short-circuit (e.g. in a stateless RPC call)? Note
47 # Did the process short-circuit (e.g. in a stateless RPC call)? Note
48 # that the client still expects a 0-object pack in most cases.
48 # that the client still expects a 0-object pack in most cases.
49 if objects_iter is None:
49 if objects_iter is None:
50 return
50 return
51
51
52 self.progress("counting objects: %d, done.\n" % len(objects_iter))
52 self.progress("counting objects: %d, done.\n" % len(objects_iter))
53 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
53 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
54 objects_iter)
54 objects_iter)
55 messages = []
55 messages = []
56 messages.append('thank you for using rhodecode')
56 messages.append('thank you for using rhodecode')
57
57
58 for msg in messages:
58 for msg in messages:
59 self.progress(msg + "\n")
59 self.progress(msg + "\n")
60 # we are done
60 # we are done
61 self.proto.write("0000")
61 self.proto.write("0000")
62
62
63
63
64 dulserver.DEFAULT_HANDLERS = {
64 dulserver.DEFAULT_HANDLERS = {
65 'git-upload-pack': SimpleGitUploadPackHandler,
65 'git-upload-pack': SimpleGitUploadPackHandler,
66 'git-receive-pack': dulserver.ReceivePackHandler,
66 'git-receive-pack': dulserver.ReceivePackHandler,
67 }
67 }
68
68
69 from dulwich.repo import Repo
69 from dulwich.repo import Repo
70 from dulwich.web import make_wsgi_chain
70 from dulwich.web import make_wsgi_chain
71
71
72 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
72 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
73
73
74 from rhodecode.lib.utils2 import safe_str
74 from rhodecode.lib.utils2 import safe_str
75 from rhodecode.lib.base import BaseVCSController
75 from rhodecode.lib.base import BaseVCSController
76 from rhodecode.lib.auth import get_container_username
76 from rhodecode.lib.auth import get_container_username
77 from rhodecode.lib.utils import is_valid_repo
77 from rhodecode.lib.utils import is_valid_repo, make_ui
78 from rhodecode.model.db import User
78 from rhodecode.model.db import User
79
79
80 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
80 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
81
81
82 log = logging.getLogger(__name__)
82 log = logging.getLogger(__name__)
83
83
84
84
85 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
85 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
86
86
87
87
88 def is_git(environ):
88 def is_git(environ):
89 path_info = environ['PATH_INFO']
89 path_info = environ['PATH_INFO']
90 isgit_path = GIT_PROTO_PAT.match(path_info)
90 isgit_path = GIT_PROTO_PAT.match(path_info)
91 log.debug('pathinfo: %s detected as GIT %s' % (
91 log.debug('pathinfo: %s detected as GIT %s' % (
92 path_info, isgit_path != None)
92 path_info, isgit_path != None)
93 )
93 )
94 return isgit_path
94 return isgit_path
95
95
96
96
97 class SimpleGit(BaseVCSController):
97 class SimpleGit(BaseVCSController):
98
98
99 def _handle_request(self, environ, start_response):
99 def _handle_request(self, environ, start_response):
100
100
101 if not is_git(environ):
101 if not is_git(environ):
102 return self.application(environ, start_response)
102 return self.application(environ, start_response)
103
103
104 ipaddr = self._get_ip_addr(environ)
104 ipaddr = self._get_ip_addr(environ)
105 username = None
105 username = None
106 self._git_first_op = False
106 # skip passing error to error controller
107 # skip passing error to error controller
107 environ['pylons.status_code_redirect'] = True
108 environ['pylons.status_code_redirect'] = True
108
109
109 #======================================================================
110 #======================================================================
110 # EXTRACT REPOSITORY NAME FROM ENV
111 # EXTRACT REPOSITORY NAME FROM ENV
111 #======================================================================
112 #======================================================================
112 try:
113 try:
113 repo_name = self.__get_repository(environ)
114 repo_name = self.__get_repository(environ)
114 log.debug('Extracted repo name is %s' % repo_name)
115 log.debug('Extracted repo name is %s' % repo_name)
115 except:
116 except:
116 return HTTPInternalServerError()(environ, start_response)
117 return HTTPInternalServerError()(environ, start_response)
117
118
118 # quick check if that dir exists...
119 # quick check if that dir exists...
119 if is_valid_repo(repo_name, self.basepath) is False:
120 if is_valid_repo(repo_name, self.basepath) is False:
120 return HTTPNotFound()(environ, start_response)
121 return HTTPNotFound()(environ, start_response)
121
122
122 #======================================================================
123 #======================================================================
123 # GET ACTION PULL or PUSH
124 # GET ACTION PULL or PUSH
124 #======================================================================
125 #======================================================================
125 action = self.__get_action(environ)
126 action = self.__get_action(environ)
126
127
127 #======================================================================
128 #======================================================================
128 # CHECK ANONYMOUS PERMISSION
129 # CHECK ANONYMOUS PERMISSION
129 #======================================================================
130 #======================================================================
130 if action in ['pull', 'push']:
131 if action in ['pull', 'push']:
131 anonymous_user = self.__get_user('default')
132 anonymous_user = self.__get_user('default')
132 username = anonymous_user.username
133 username = anonymous_user.username
133 anonymous_perm = self._check_permission(action, anonymous_user,
134 anonymous_perm = self._check_permission(action, anonymous_user,
134 repo_name)
135 repo_name)
135
136
136 if anonymous_perm is not True or anonymous_user.active is False:
137 if anonymous_perm is not True or anonymous_user.active is False:
137 if anonymous_perm is not True:
138 if anonymous_perm is not True:
138 log.debug('Not enough credentials to access this '
139 log.debug('Not enough credentials to access this '
139 'repository as anonymous user')
140 'repository as anonymous user')
140 if anonymous_user.active is False:
141 if anonymous_user.active is False:
141 log.debug('Anonymous access is disabled, running '
142 log.debug('Anonymous access is disabled, running '
142 'authentication')
143 'authentication')
143 #==============================================================
144 #==============================================================
144 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
145 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
145 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
146 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
146 #==============================================================
147 #==============================================================
147
148
148 # Attempting to retrieve username from the container
149 # Attempting to retrieve username from the container
149 username = get_container_username(environ, self.config)
150 username = get_container_username(environ, self.config)
150
151
151 # If not authenticated by the container, running basic auth
152 # If not authenticated by the container, running basic auth
152 if not username:
153 if not username:
153 self.authenticate.realm = \
154 self.authenticate.realm = \
154 safe_str(self.config['rhodecode_realm'])
155 safe_str(self.config['rhodecode_realm'])
155 result = self.authenticate(environ)
156 result = self.authenticate(environ)
156 if isinstance(result, str):
157 if isinstance(result, str):
157 AUTH_TYPE.update(environ, 'basic')
158 AUTH_TYPE.update(environ, 'basic')
158 REMOTE_USER.update(environ, result)
159 REMOTE_USER.update(environ, result)
159 username = result
160 username = result
160 else:
161 else:
161 return result.wsgi_application(environ, start_response)
162 return result.wsgi_application(environ, start_response)
162
163
163 #==============================================================
164 #==============================================================
164 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
165 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
165 #==============================================================
166 #==============================================================
166 if action in ['pull', 'push']:
167 if action in ['pull', 'push']:
167 try:
168 try:
168 user = self.__get_user(username)
169 user = self.__get_user(username)
169 if user is None or not user.active:
170 if user is None or not user.active:
170 return HTTPForbidden()(environ, start_response)
171 return HTTPForbidden()(environ, start_response)
171 username = user.username
172 username = user.username
172 except:
173 except:
173 log.error(traceback.format_exc())
174 log.error(traceback.format_exc())
174 return HTTPInternalServerError()(environ,
175 return HTTPInternalServerError()(environ,
175 start_response)
176 start_response)
176
177
177 #check permissions for this repository
178 #check permissions for this repository
178 perm = self._check_permission(action, user, repo_name)
179 perm = self._check_permission(action, user, repo_name)
179 if perm is not True:
180 if perm is not True:
180 return HTTPForbidden()(environ, start_response)
181 return HTTPForbidden()(environ, start_response)
182 extras = {
183 'ip': ipaddr,
184 'username': username,
185 'action': action,
186 'repository': repo_name,
187 'scm': 'git',
188 }
181
189
182 #===================================================================
190 #===================================================================
183 # GIT REQUEST HANDLING
191 # GIT REQUEST HANDLING
184 #===================================================================
192 #===================================================================
185 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
193 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
186 log.debug('Repository path is %s' % repo_path)
194 log.debug('Repository path is %s' % repo_path)
187
195
196 baseui = make_ui('db')
197 for k, v in extras.items():
198 baseui.setconfig('rhodecode_extras', k, v)
199
188 try:
200 try:
189 #invalidate cache on push
201 # invalidate cache on push
190 if action == 'push':
202 if action == 'push':
191 self._invalidate_cache(repo_name)
203 self._invalidate_cache(repo_name)
204 self._handle_githooks(action, baseui, environ)
205
192 log.info('%s action on GIT repo "%s"' % (action, repo_name))
206 log.info('%s action on GIT repo "%s"' % (action, repo_name))
193 app = self.__make_app(repo_name, repo_path)
207 app = self.__make_app(repo_name, repo_path)
194 return app(environ, start_response)
208 return app(environ, start_response)
195 except Exception:
209 except Exception:
196 log.error(traceback.format_exc())
210 log.error(traceback.format_exc())
197 return HTTPInternalServerError()(environ, start_response)
211 return HTTPInternalServerError()(environ, start_response)
198
212
199 def __make_app(self, repo_name, repo_path):
213 def __make_app(self, repo_name, repo_path):
200 """
214 """
201 Make an wsgi application using dulserver
215 Make an wsgi application using dulserver
202
216
203 :param repo_name: name of the repository
217 :param repo_name: name of the repository
204 :param repo_path: full path to the repository
218 :param repo_path: full path to the repository
205 """
219 """
206 _d = {'/' + repo_name: Repo(repo_path)}
220 _d = {'/' + repo_name: Repo(repo_path)}
207 backend = dulserver.DictBackend(_d)
221 backend = dulserver.DictBackend(_d)
208 gitserve = make_wsgi_chain(backend)
222 gitserve = make_wsgi_chain(backend)
209
223
210 return gitserve
224 return gitserve
211
225
212 def __get_repository(self, environ):
226 def __get_repository(self, environ):
213 """
227 """
214 Get's repository name out of PATH_INFO header
228 Get's repository name out of PATH_INFO header
215
229
216 :param environ: environ where PATH_INFO is stored
230 :param environ: environ where PATH_INFO is stored
217 """
231 """
218 try:
232 try:
219 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
233 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
220 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
234 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
221 except:
235 except:
222 log.error(traceback.format_exc())
236 log.error(traceback.format_exc())
223 raise
237 raise
224
238
225 return repo_name
239 return repo_name
226
240
227 def __get_user(self, username):
241 def __get_user(self, username):
228 return User.get_by_username(username)
242 return User.get_by_username(username)
229
243
230 def __get_action(self, environ):
244 def __get_action(self, environ):
231 """
245 """
232 Maps git request commands into a pull or push command.
246 Maps git request commands into a pull or push command.
233
247
234 :param environ:
248 :param environ:
235 """
249 """
236 service = environ['QUERY_STRING'].split('=')
250 service = environ['QUERY_STRING'].split('=')
237
251
238 if len(service) > 1:
252 if len(service) > 1:
239 service_cmd = service[1]
253 service_cmd = service[1]
240 mapping = {
254 mapping = {
241 'git-receive-pack': 'push',
255 'git-receive-pack': 'push',
242 'git-upload-pack': 'pull',
256 'git-upload-pack': 'pull',
243 }
257 }
244 op = mapping[service_cmd]
258 op = mapping[service_cmd]
245 self._git_stored_op = op
259 self._git_stored_op = op
246 return op
260 return op
247 else:
261 else:
248 # try to fallback to stored variable as we don't know if the last
262 # try to fallback to stored variable as we don't know if the last
249 # operation is pull/push
263 # operation is pull/push
250 op = getattr(self, '_git_stored_op', 'pull')
264 op = getattr(self, '_git_stored_op', 'pull')
251 return op
265 return op
266
267 def _handle_githooks(self, action, baseui, environ):
268
269 from rhodecode.lib.hooks import log_pull_action, log_push_action
270 service = environ['QUERY_STRING'].split('=')
271 if len(service) < 2:
272 return
273
274 class cont(object):
275 pass
276
277 repo = cont()
278 setattr(repo, 'ui', baseui)
279
280 push_hook = 'pretxnchangegroup.push_logger'
281 pull_hook = 'preoutgoing.pull_logger'
282 _hooks = dict(baseui.configitems('hooks')) or {}
283 if action == 'push' and _hooks.get(push_hook):
284 log_push_action(ui=baseui, repo=repo)
285 elif action == 'pull' and _hooks.get(pull_hook):
286 log_pull_action(ui=baseui, repo=repo)
287
@@ -1,256 +1,257 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplehg
3 rhodecode.lib.middleware.simplehg
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleHG middleware for handling mercurial protocol request
6 SimpleHG middleware for handling mercurial protocol request
7 (push/clone etc.). It's implemented with basic auth function
7 (push/clone etc.). It's implemented with basic auth function
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30 import urllib
30 import urllib
31
31
32 from mercurial.error import RepoError
32 from mercurial.error import RepoError
33 from mercurial.hgweb import hgweb_mod
33 from mercurial.hgweb import hgweb_mod
34
34
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
36
36
37 from rhodecode.lib.utils2 import safe_str
37 from rhodecode.lib.utils2 import safe_str
38 from rhodecode.lib.base import BaseVCSController
38 from rhodecode.lib.base import BaseVCSController
39 from rhodecode.lib.auth import get_container_username
39 from rhodecode.lib.auth import get_container_username
40 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
40 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
41 from rhodecode.model.db import User
41 from rhodecode.model.db import User
42
42
43 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
43 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 def is_mercurial(environ):
48 def is_mercurial(environ):
49 """
49 """
50 Returns True if request's target is mercurial server - header
50 Returns True if request's target is mercurial server - header
51 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
51 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
52 """
52 """
53 http_accept = environ.get('HTTP_ACCEPT')
53 http_accept = environ.get('HTTP_ACCEPT')
54 path_info = environ['PATH_INFO']
54 path_info = environ['PATH_INFO']
55 if http_accept and http_accept.startswith('application/mercurial'):
55 if http_accept and http_accept.startswith('application/mercurial'):
56 ishg_path = True
56 ishg_path = True
57 else:
57 else:
58 ishg_path = False
58 ishg_path = False
59
59
60 log.debug('pathinfo: %s detected as HG %s' % (
60 log.debug('pathinfo: %s detected as HG %s' % (
61 path_info, ishg_path)
61 path_info, ishg_path)
62 )
62 )
63 return ishg_path
63 return ishg_path
64
64
65
65
66 class SimpleHg(BaseVCSController):
66 class SimpleHg(BaseVCSController):
67
67
68 def _handle_request(self, environ, start_response):
68 def _handle_request(self, environ, start_response):
69 if not is_mercurial(environ):
69 if not is_mercurial(environ):
70 return self.application(environ, start_response)
70 return self.application(environ, start_response)
71
71
72 ipaddr = self._get_ip_addr(environ)
72 ipaddr = self._get_ip_addr(environ)
73
73
74 # skip passing error to error controller
74 # skip passing error to error controller
75 environ['pylons.status_code_redirect'] = True
75 environ['pylons.status_code_redirect'] = True
76
76
77 #======================================================================
77 #======================================================================
78 # EXTRACT REPOSITORY NAME FROM ENV
78 # EXTRACT REPOSITORY NAME FROM ENV
79 #======================================================================
79 #======================================================================
80 try:
80 try:
81 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
81 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
82 log.debug('Extracted repo name is %s' % repo_name)
82 log.debug('Extracted repo name is %s' % repo_name)
83 except:
83 except:
84 return HTTPInternalServerError()(environ, start_response)
84 return HTTPInternalServerError()(environ, start_response)
85
85
86 # quick check if that dir exists...
86 # quick check if that dir exists...
87 if is_valid_repo(repo_name, self.basepath) is False:
87 if is_valid_repo(repo_name, self.basepath) is False:
88 return HTTPNotFound()(environ, start_response)
88 return HTTPNotFound()(environ, start_response)
89
89
90 #======================================================================
90 #======================================================================
91 # GET ACTION PULL or PUSH
91 # GET ACTION PULL or PUSH
92 #======================================================================
92 #======================================================================
93 action = self.__get_action(environ)
93 action = self.__get_action(environ)
94
94
95 #======================================================================
95 #======================================================================
96 # CHECK ANONYMOUS PERMISSION
96 # CHECK ANONYMOUS PERMISSION
97 #======================================================================
97 #======================================================================
98 if action in ['pull', 'push']:
98 if action in ['pull', 'push']:
99 anonymous_user = self.__get_user('default')
99 anonymous_user = self.__get_user('default')
100 username = anonymous_user.username
100 username = anonymous_user.username
101 anonymous_perm = self._check_permission(action, anonymous_user,
101 anonymous_perm = self._check_permission(action, anonymous_user,
102 repo_name)
102 repo_name)
103
103
104 if anonymous_perm is not True or anonymous_user.active is False:
104 if anonymous_perm is not True or anonymous_user.active is False:
105 if anonymous_perm is not True:
105 if anonymous_perm is not True:
106 log.debug('Not enough credentials to access this '
106 log.debug('Not enough credentials to access this '
107 'repository as anonymous user')
107 'repository as anonymous user')
108 if anonymous_user.active is False:
108 if anonymous_user.active is False:
109 log.debug('Anonymous access is disabled, running '
109 log.debug('Anonymous access is disabled, running '
110 'authentication')
110 'authentication')
111 #==============================================================
111 #==============================================================
112 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
112 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
113 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
113 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
114 #==============================================================
114 #==============================================================
115
115
116 # Attempting to retrieve username from the container
116 # Attempting to retrieve username from the container
117 username = get_container_username(environ, self.config)
117 username = get_container_username(environ, self.config)
118
118
119 # If not authenticated by the container, running basic auth
119 # If not authenticated by the container, running basic auth
120 if not username:
120 if not username:
121 self.authenticate.realm = \
121 self.authenticate.realm = \
122 safe_str(self.config['rhodecode_realm'])
122 safe_str(self.config['rhodecode_realm'])
123 result = self.authenticate(environ)
123 result = self.authenticate(environ)
124 if isinstance(result, str):
124 if isinstance(result, str):
125 AUTH_TYPE.update(environ, 'basic')
125 AUTH_TYPE.update(environ, 'basic')
126 REMOTE_USER.update(environ, result)
126 REMOTE_USER.update(environ, result)
127 username = result
127 username = result
128 else:
128 else:
129 return result.wsgi_application(environ, start_response)
129 return result.wsgi_application(environ, start_response)
130
130
131 #==============================================================
131 #==============================================================
132 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
132 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
133 #==============================================================
133 #==============================================================
134 if action in ['pull', 'push']:
134 if action in ['pull', 'push']:
135 try:
135 try:
136 user = self.__get_user(username)
136 user = self.__get_user(username)
137 if user is None or not user.active:
137 if user is None or not user.active:
138 return HTTPForbidden()(environ, start_response)
138 return HTTPForbidden()(environ, start_response)
139 username = user.username
139 username = user.username
140 except:
140 except:
141 log.error(traceback.format_exc())
141 log.error(traceback.format_exc())
142 return HTTPInternalServerError()(environ,
142 return HTTPInternalServerError()(environ,
143 start_response)
143 start_response)
144
144
145 #check permissions for this repository
145 #check permissions for this repository
146 perm = self._check_permission(action, user, repo_name)
146 perm = self._check_permission(action, user, repo_name)
147 if perm is not True:
147 if perm is not True:
148 return HTTPForbidden()(environ, start_response)
148 return HTTPForbidden()(environ, start_response)
149
149
150 # extras are injected into mercurial UI object and later available
150 # extras are injected into mercurial UI object and later available
151 # in hg hooks executed by rhodecode
151 # in hg hooks executed by rhodecode
152 extras = {
152 extras = {
153 'ip': ipaddr,
153 'ip': ipaddr,
154 'username': username,
154 'username': username,
155 'action': action,
155 'action': action,
156 'repository': repo_name
156 'repository': repo_name,
157 'scm': 'hg',
157 }
158 }
158
159
159 #======================================================================
160 #======================================================================
160 # MERCURIAL REQUEST HANDLING
161 # MERCURIAL REQUEST HANDLING
161 #======================================================================
162 #======================================================================
162 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
163 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
163 log.debug('Repository path is %s' % repo_path)
164 log.debug('Repository path is %s' % repo_path)
164
165
165 baseui = make_ui('db')
166 baseui = make_ui('db')
166 self.__inject_extras(repo_path, baseui, extras)
167 self.__inject_extras(repo_path, baseui, extras)
167
168
168 try:
169 try:
169 # invalidate cache on push
170 # invalidate cache on push
170 if action == 'push':
171 if action == 'push':
171 self._invalidate_cache(repo_name)
172 self._invalidate_cache(repo_name)
172 log.info('%s action on HG repo "%s"' % (action, repo_name))
173 log.info('%s action on HG repo "%s"' % (action, repo_name))
173 app = self.__make_app(repo_path, baseui, extras)
174 app = self.__make_app(repo_path, baseui, extras)
174 return app(environ, start_response)
175 return app(environ, start_response)
175 except RepoError, e:
176 except RepoError, e:
176 if str(e).find('not found') != -1:
177 if str(e).find('not found') != -1:
177 return HTTPNotFound()(environ, start_response)
178 return HTTPNotFound()(environ, start_response)
178 except Exception:
179 except Exception:
179 log.error(traceback.format_exc())
180 log.error(traceback.format_exc())
180 return HTTPInternalServerError()(environ, start_response)
181 return HTTPInternalServerError()(environ, start_response)
181
182
182 def __make_app(self, repo_name, baseui, extras):
183 def __make_app(self, repo_name, baseui, extras):
183 """
184 """
184 Make an wsgi application using hgweb, and inject generated baseui
185 Make an wsgi application using hgweb, and inject generated baseui
185 instance, additionally inject some extras into ui object
186 instance, additionally inject some extras into ui object
186 """
187 """
187 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
188 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
188
189
189 def __get_repository(self, environ):
190 def __get_repository(self, environ):
190 """
191 """
191 Get's repository name out of PATH_INFO header
192 Get's repository name out of PATH_INFO header
192
193
193 :param environ: environ where PATH_INFO is stored
194 :param environ: environ where PATH_INFO is stored
194 """
195 """
195 try:
196 try:
196 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
197 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
197 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
198 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
198 if repo_name.endswith('/'):
199 if repo_name.endswith('/'):
199 repo_name = repo_name.rstrip('/')
200 repo_name = repo_name.rstrip('/')
200 except:
201 except:
201 log.error(traceback.format_exc())
202 log.error(traceback.format_exc())
202 raise
203 raise
203
204
204 return repo_name
205 return repo_name
205
206
206 def __get_user(self, username):
207 def __get_user(self, username):
207 return User.get_by_username(username)
208 return User.get_by_username(username)
208
209
209 def __get_action(self, environ):
210 def __get_action(self, environ):
210 """
211 """
211 Maps mercurial request commands into a clone,pull or push command.
212 Maps mercurial request commands into a clone,pull or push command.
212 This should always return a valid command string
213 This should always return a valid command string
213
214
214 :param environ:
215 :param environ:
215 """
216 """
216 mapping = {'changegroup': 'pull',
217 mapping = {'changegroup': 'pull',
217 'changegroupsubset': 'pull',
218 'changegroupsubset': 'pull',
218 'stream_out': 'pull',
219 'stream_out': 'pull',
219 'listkeys': 'pull',
220 'listkeys': 'pull',
220 'unbundle': 'push',
221 'unbundle': 'push',
221 'pushkey': 'push', }
222 'pushkey': 'push', }
222 for qry in environ['QUERY_STRING'].split('&'):
223 for qry in environ['QUERY_STRING'].split('&'):
223 if qry.startswith('cmd'):
224 if qry.startswith('cmd'):
224 cmd = qry.split('=')[-1]
225 cmd = qry.split('=')[-1]
225 if cmd in mapping:
226 if cmd in mapping:
226 return mapping[cmd]
227 return mapping[cmd]
227 else:
228 else:
228 return 'pull'
229 return 'pull'
229
230
230 def __inject_extras(self, repo_path, baseui, extras={}):
231 def __inject_extras(self, repo_path, baseui, extras={}):
231 """
232 """
232 Injects some extra params into baseui instance
233 Injects some extra params into baseui instance
233
234
234 also overwrites global settings with those takes from local hgrc file
235 also overwrites global settings with those takes from local hgrc file
235
236
236 :param baseui: baseui instance
237 :param baseui: baseui instance
237 :param extras: dict with extra params to put into baseui
238 :param extras: dict with extra params to put into baseui
238 """
239 """
239
240
240 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
241 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
241
242
242 # make our hgweb quiet so it doesn't print output
243 # make our hgweb quiet so it doesn't print output
243 baseui.setconfig('ui', 'quiet', 'true')
244 baseui.setconfig('ui', 'quiet', 'true')
244
245
245 #inject some additional parameters that will be available in ui
246 #inject some additional parameters that will be available in ui
246 #for hooks
247 #for hooks
247 for k, v in extras.items():
248 for k, v in extras.items():
248 baseui.setconfig('rhodecode_extras', k, v)
249 baseui.setconfig('rhodecode_extras', k, v)
249
250
250 repoui = make_ui('file', hgrc, False)
251 repoui = make_ui('file', hgrc, False)
251
252
252 if repoui:
253 if repoui:
253 #overwrite our ui instance with the section from hgrc file
254 #overwrite our ui instance with the section from hgrc file
254 for section in ui_sections:
255 for section in ui_sections:
255 for k, v in repoui.configitems(section):
256 for k, v in repoui.configitems(section):
256 baseui.setconfig(section, k, v)
257 baseui.setconfig(section, k, v)
General Comments 0
You need to be logged in to leave comments. Login now