##// END OF EJS Templates
fixed vcs import....
marcink -
r953:def3578d beta
parent child Browse files
Show More
@@ -1,267 +1,267 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.files
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Files controller for RhodeCode
7 7
8 8 :created_on: Apr 21, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27 import tempfile
28 28 import logging
29 29 import rhodecode.lib.helpers as h
30 30
31 31 from mercurial import archival
32 32
33 33 from pylons import request, response, session, tmpl_context as c, url
34 34 from pylons.i18n.translation import _
35 35 from pylons.controllers.util import redirect
36 36
37 37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
38 38 from rhodecode.lib.base import BaseController, render
39 39 from rhodecode.lib.utils import EmptyChangeset
40 40 from rhodecode.model.scm import ScmModel
41 41
42 42 from vcs.backends import ARCHIVE_SPECS
43 43 from vcs.exceptions import RepositoryError, ChangesetError, \
44 ChangesetDoesNotExistError, EmptyRepositoryError, InproperArchiveTypeError
44 ChangesetDoesNotExistError, EmptyRepositoryError, ImproperArchiveTypeError
45 45 from vcs.nodes import FileNode
46 46 from vcs.utils import diffs as differ
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50 class FilesController(BaseController):
51 51
52 52 @LoginRequired()
53 53 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
54 54 'repository.admin')
55 55 def __before__(self):
56 56 super(FilesController, self).__before__()
57 57 c.cut_off_limit = self.cut_off_limit
58 58
59 59 def index(self, repo_name, revision, f_path):
60 60 hg_model = ScmModel()
61 61 c.repo = hg_model.get_repo(c.repo_name)
62 62
63 63 try:
64 64 #reditect to given revision from form
65 65 post_revision = request.POST.get('at_rev', None)
66 66 if post_revision:
67 67 post_revision = c.repo.get_changeset(post_revision).raw_id
68 68 redirect(url('files_home', repo_name=c.repo_name,
69 69 revision=post_revision, f_path=f_path))
70 70
71 71 c.branch = request.GET.get('branch', None)
72 72
73 73 c.f_path = f_path
74 74
75 75 c.changeset = c.repo.get_changeset(revision)
76 76 cur_rev = c.changeset.revision
77 77
78 78 #prev link
79 79 try:
80 80 prev_rev = c.repo.get_changeset(cur_rev).prev(c.branch).raw_id
81 81 c.url_prev = url('files_home', repo_name=c.repo_name,
82 82 revision=prev_rev, f_path=f_path)
83 83 if c.branch:
84 84 c.url_prev += '?branch=%s' % c.branch
85 85 except ChangesetDoesNotExistError:
86 86 c.url_prev = '#'
87 87
88 88 #next link
89 89 try:
90 90 next_rev = c.repo.get_changeset(cur_rev).next(c.branch).raw_id
91 91 c.url_next = url('files_home', repo_name=c.repo_name,
92 92 revision=next_rev, f_path=f_path)
93 93 if c.branch:
94 94 c.url_next += '?branch=%s' % c.branch
95 95 except ChangesetDoesNotExistError:
96 96 c.url_next = '#'
97 97
98 98 #files
99 99 try:
100 100 c.files_list = c.changeset.get_node(f_path)
101 101 c.file_history = self._get_history(c.repo, c.files_list, f_path)
102 102 except RepositoryError, e:
103 103 h.flash(str(e), category='warning')
104 104 redirect(h.url('files_home', repo_name=repo_name, revision=revision))
105 105
106 106 except EmptyRepositoryError, e:
107 107 h.flash(_('There are no files yet'), category='warning')
108 108 redirect(h.url('summary_home', repo_name=repo_name))
109 109
110 110 except RepositoryError, e:
111 111 h.flash(str(e), category='warning')
112 112 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
113 113
114 114
115 115
116 116 return render('files/files.html')
117 117
118 118 def rawfile(self, repo_name, revision, f_path):
119 119 hg_model = ScmModel()
120 120 c.repo = hg_model.get_repo(c.repo_name)
121 121 file_node = c.repo.get_changeset(revision).get_node(f_path)
122 122 response.content_type = file_node.mimetype
123 123 response.content_disposition = 'attachment; filename=%s' \
124 124 % f_path.split('/')[-1]
125 125 return file_node.content
126 126
127 127 def raw(self, repo_name, revision, f_path):
128 128 hg_model = ScmModel()
129 129 c.repo = hg_model.get_repo(c.repo_name)
130 130 file_node = c.repo.get_changeset(revision).get_node(f_path)
131 131 response.content_type = 'text/plain'
132 132
133 133 return file_node.content
134 134
135 135 def annotate(self, repo_name, revision, f_path):
136 136 hg_model = ScmModel()
137 137 c.repo = hg_model.get_repo(c.repo_name)
138 138
139 139 try:
140 140 c.cs = c.repo.get_changeset(revision)
141 141 c.file = c.cs.get_node(f_path)
142 142 except RepositoryError, e:
143 143 h.flash(str(e), category='warning')
144 144 redirect(h.url('files_home', repo_name=repo_name, revision=revision))
145 145
146 146 c.file_history = self._get_history(c.repo, c.file, f_path)
147 147
148 148 c.f_path = f_path
149 149
150 150 return render('files/files_annotate.html')
151 151
152 152 def archivefile(self, repo_name, fname):
153 153
154 154 fileformat = None
155 155 revision = None
156 156 ext = None
157 157
158 158 for a_type, ext_data in ARCHIVE_SPECS.items():
159 159 archive_spec = fname.split(ext_data[1])
160 160 if len(archive_spec) == 2 and archive_spec[1] == '':
161 161 fileformat = a_type or ext_data[1]
162 162 revision = archive_spec[0]
163 163 ext = ext_data[1]
164 164
165 165 try:
166 166 repo = ScmModel().get_repo(repo_name)
167 167 cs = repo.get_changeset(revision)
168 168 content_type = ARCHIVE_SPECS[fileformat][0]
169 169 except ChangesetDoesNotExistError:
170 170 return _('Unknown revision %s') % revision
171 171 except EmptyRepositoryError:
172 172 return _('Empty repository')
173 173 except (InproperArchiveTypeError, KeyError):
174 174 return _('Unknown archive type')
175 175
176 176 response.content_type = content_type
177 177 response.content_disposition = 'attachment; filename=%s-%s%s' \
178 178 % (repo_name, revision, ext)
179 179
180 180 return cs.get_chunked_archive(kind=fileformat)
181 181
182 182
183 183 def diff(self, repo_name, f_path):
184 184 hg_model = ScmModel()
185 185 diff1 = request.GET.get('diff1')
186 186 diff2 = request.GET.get('diff2')
187 187 c.action = request.GET.get('diff')
188 188 c.no_changes = diff1 == diff2
189 189 c.f_path = f_path
190 190 c.repo = hg_model.get_repo(c.repo_name)
191 191
192 192 try:
193 193 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
194 194 c.changeset_1 = c.repo.get_changeset(diff1)
195 195 node1 = c.changeset_1.get_node(f_path)
196 196 else:
197 197 c.changeset_1 = EmptyChangeset()
198 198 node1 = FileNode('.', '', changeset=c.changeset_1)
199 199
200 200 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
201 201 c.changeset_2 = c.repo.get_changeset(diff2)
202 202 node2 = c.changeset_2.get_node(f_path)
203 203 else:
204 204 c.changeset_2 = EmptyChangeset()
205 205 node2 = FileNode('.', '', changeset=c.changeset_2)
206 206 except RepositoryError:
207 207 return redirect(url('files_home',
208 208 repo_name=c.repo_name, f_path=f_path))
209 209
210 210 f_udiff = differ.get_udiff(node1, node2)
211 211 diff = differ.DiffProcessor(f_udiff)
212 212
213 213 if c.action == 'download':
214 214 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
215 215 response.content_type = 'text/plain'
216 216 response.content_disposition = 'attachment; filename=%s' \
217 217 % diff_name
218 218 return diff.raw_diff()
219 219
220 220 elif c.action == 'raw':
221 221 response.content_type = 'text/plain'
222 222 return diff.raw_diff()
223 223
224 224 elif c.action == 'diff':
225 225 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
226 226 c.cur_diff = _('Diff is to big to display')
227 227 else:
228 228 c.cur_diff = diff.as_html()
229 229 else:
230 230 #default option
231 231 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
232 232 c.cur_diff = _('Diff is to big to display')
233 233 else:
234 234 c.cur_diff = diff.as_html()
235 235
236 236 if not c.cur_diff: c.no_changes = True
237 237 return render('files/file_diff.html')
238 238
239 239 def _get_history(self, repo, node, f_path):
240 240 from vcs.nodes import NodeKind
241 241 if not node.kind is NodeKind.FILE:
242 242 return []
243 243 changesets = node.history
244 244 hist_l = []
245 245
246 246 changesets_group = ([], _("Changesets"))
247 247 branches_group = ([], _("Branches"))
248 248 tags_group = ([], _("Tags"))
249 249
250 250 for chs in changesets:
251 251 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
252 252 changesets_group[0].append((chs.raw_id, n_desc,))
253 253
254 254 hist_l.append(changesets_group)
255 255
256 256 for name, chs in c.repository_branches.items():
257 257 #chs = chs.split(':')[-1]
258 258 branches_group[0].append((chs, name),)
259 259 hist_l.append(branches_group)
260 260
261 261 for name, chs in c.repository_tags.items():
262 262 #chs = chs.split(':')[-1]
263 263 tags_group[0].append((chs, name),)
264 264 hist_l.append(tags_group)
265 265
266 266 return hist_l
267 267
@@ -1,526 +1,527 b''
1 1 """Helper functions
2 2
3 3 Consists of functions to typically be used within templates, but also
4 4 available to Controllers. This module is available to both as 'h'.
5 5 """
6 6 import random
7 7 import hashlib
8 8 from pygments.formatters import HtmlFormatter
9 9 from pygments import highlight as code_highlight
10 10 from pylons import url, app_globals as g
11 11 from pylons.i18n.translation import _, ungettext
12 12 from vcs.utils.annotate import annotate_highlight
13 13 from webhelpers.html import literal, HTML, escape
14 14 from webhelpers.html.tools import *
15 15 from webhelpers.html.builder import make_tag
16 16 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
17 17 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
18 18 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
19 19 password, textarea, title, ul, xml_declaration, radio
20 20 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
21 21 mail_to, strip_links, strip_tags, tag_re
22 22 from webhelpers.number import format_byte_size, format_bit_size
23 23 from webhelpers.pylonslib import Flash as _Flash
24 24 from webhelpers.pylonslib.secure_form import secure_form
25 25 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
26 26 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
27 27 replace_whitespace, urlify, truncate, wrap_paragraphs
28 28 from webhelpers.date import time_ago_in_words
29 29
30 30 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
31 31 convert_boolean_attrs, NotGiven
32 32
33 33 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
34 34 """Reset button
35 35 """
36 36 _set_input_attrs(attrs, type, name, value)
37 37 _set_id_attr(attrs, id, name)
38 38 convert_boolean_attrs(attrs, ["disabled"])
39 39 return HTML.input(**attrs)
40 40
41 41 reset = _reset
42 42
43 43
44 44 def get_token():
45 45 """Return the current authentication token, creating one if one doesn't
46 46 already exist.
47 47 """
48 48 token_key = "_authentication_token"
49 49 from pylons import session
50 50 if not token_key in session:
51 51 try:
52 52 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
53 53 except AttributeError: # Python < 2.4
54 54 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
55 55 session[token_key] = token
56 56 if hasattr(session, 'save'):
57 57 session.save()
58 58 return session[token_key]
59 59
60 60 class _GetError(object):
61 61 """Get error from form_errors, and represent it as span wrapped error
62 62 message
63 63
64 64 :param field_name: field to fetch errors for
65 65 :param form_errors: form errors dict
66 66 """
67 67
68 68 def __call__(self, field_name, form_errors):
69 69 tmpl = """<span class="error_msg">%s</span>"""
70 70 if form_errors and form_errors.has_key(field_name):
71 71 return literal(tmpl % form_errors.get(field_name))
72 72
73 73 get_error = _GetError()
74 74
75 75 def recursive_replace(str, replace=' '):
76 76 """Recursive replace of given sign to just one instance
77 77
78 78 :param str: given string
79 79 :param replace: char to find and replace multiple instances
80 80
81 81 Examples::
82 82 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
83 83 'Mighty-Mighty-Bo-sstones'
84 84 """
85 85
86 86 if str.find(replace * 2) == -1:
87 87 return str
88 88 else:
89 89 str = str.replace(replace * 2, replace)
90 90 return recursive_replace(str, replace)
91 91
92 92 class _ToolTip(object):
93 93
94 94 def __call__(self, tooltip_title, trim_at=50):
95 95 """Special function just to wrap our text into nice formatted
96 96 autowrapped text
97 97
98 98 :param tooltip_title:
99 99 """
100 100
101 101 return wrap_paragraphs(escape(tooltip_title), trim_at)\
102 102 .replace('\n', '<br/>')
103 103
104 104 def activate(self):
105 105 """Adds tooltip mechanism to the given Html all tooltips have to have
106 106 set class `tooltip` and set attribute `tooltip_title`.
107 107 Then a tooltip will be generated based on that. All with yui js tooltip
108 108 """
109 109
110 110 js = '''
111 111 YAHOO.util.Event.onDOMReady(function(){
112 112 function toolTipsId(){
113 113 var ids = [];
114 114 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
115 115
116 116 for (var i = 0; i < tts.length; i++) {
117 117 //if element doesn't not have and id autogenerate one for tooltip
118 118
119 119 if (!tts[i].id){
120 120 tts[i].id='tt'+i*100;
121 121 }
122 122 ids.push(tts[i].id);
123 123 }
124 124 return ids
125 125 };
126 126 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
127 127 context: toolTipsId(),
128 128 monitorresize:false,
129 129 xyoffset :[0,0],
130 130 autodismissdelay:300000,
131 131 hidedelay:5,
132 132 showdelay:20,
133 133 });
134 134
135 135 // Set the text for the tooltip just before we display it. Lazy method
136 136 myToolTips.contextTriggerEvent.subscribe(
137 137 function(type, args) {
138 138
139 139 var context = args[0];
140 140
141 141 //positioning of tooltip
142 142 var tt_w = this.element.clientWidth;//tooltip width
143 143 var tt_h = this.element.clientHeight;//tooltip height
144 144
145 145 var context_w = context.offsetWidth;
146 146 var context_h = context.offsetHeight;
147 147
148 148 var pos_x = YAHOO.util.Dom.getX(context);
149 149 var pos_y = YAHOO.util.Dom.getY(context);
150 150
151 151 var display_strategy = 'right';
152 152 var xy_pos = [0,0];
153 153 switch (display_strategy){
154 154
155 155 case 'top':
156 156 var cur_x = (pos_x+context_w/2)-(tt_w/2);
157 157 var cur_y = (pos_y-tt_h-4);
158 158 xy_pos = [cur_x,cur_y];
159 159 break;
160 160 case 'bottom':
161 161 var cur_x = (pos_x+context_w/2)-(tt_w/2);
162 162 var cur_y = pos_y+context_h+4;
163 163 xy_pos = [cur_x,cur_y];
164 164 break;
165 165 case 'left':
166 166 var cur_x = (pos_x-tt_w-4);
167 167 var cur_y = pos_y-((tt_h/2)-context_h/2);
168 168 xy_pos = [cur_x,cur_y];
169 169 break;
170 170 case 'right':
171 171 var cur_x = (pos_x+context_w+4);
172 172 var cur_y = pos_y-((tt_h/2)-context_h/2);
173 173 xy_pos = [cur_x,cur_y];
174 174 break;
175 175 default:
176 176 var cur_x = (pos_x+context_w/2)-(tt_w/2);
177 177 var cur_y = pos_y-tt_h-4;
178 178 xy_pos = [cur_x,cur_y];
179 179 break;
180 180
181 181 }
182 182
183 183 this.cfg.setProperty("xy",xy_pos);
184 184
185 185 });
186 186
187 187 //Mouse out
188 188 myToolTips.contextMouseOutEvent.subscribe(
189 189 function(type, args) {
190 190 var context = args[0];
191 191
192 192 });
193 193 });
194 194 '''
195 195 return literal(js)
196 196
197 197 tooltip = _ToolTip()
198 198
199 199 class _FilesBreadCrumbs(object):
200 200
201 201 def __call__(self, repo_name, rev, paths):
202 202 url_l = [link_to(repo_name, url('files_home',
203 203 repo_name=repo_name,
204 204 revision=rev, f_path=''))]
205 205 paths_l = paths.split('/')
206 206
207 207 for cnt, p in enumerate(paths_l):
208 208 if p != '':
209 209 url_l.append(link_to(p, url('files_home',
210 210 repo_name=repo_name,
211 211 revision=rev,
212 212 f_path='/'.join(paths_l[:cnt + 1]))))
213 213
214 214 return literal('/'.join(url_l))
215 215
216 216 files_breadcrumbs = _FilesBreadCrumbs()
217 217
218 218 class CodeHtmlFormatter(HtmlFormatter):
219 219
220 220 def wrap(self, source, outfile):
221 221 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
222 222
223 223 def _wrap_code(self, source):
224 224 for cnt, it in enumerate(source):
225 225 i, t = it
226 226 t = '<div id="#S-%s">%s</div>' % (cnt + 1, t)
227 227 yield i, t
228 228 def pygmentize(filenode, **kwargs):
229 229 """pygmentize function using pygments
230 230
231 231 :param filenode:
232 232 """
233 233
234 234 return literal(code_highlight(filenode.content,
235 235 filenode.lexer, CodeHtmlFormatter(**kwargs)))
236 236
237 237 def pygmentize_annotation(filenode, **kwargs):
238 238 """pygmentize function for annotation
239 239
240 240 :param filenode:
241 241 """
242 242
243 243 color_dict = {}
244 244 def gen_color(n=10000):
245 245 """generator for getting n of evenly distributed colors using
246 246 hsv color and golden ratio. It always return same order of colors
247 247
248 248 :returns: RGB tuple
249 249 """
250 250 import colorsys
251 251 golden_ratio = 0.618033988749895
252 252 h = 0.22717784590367374
253 253
254 254 for c in xrange(n):
255 255 h += golden_ratio
256 256 h %= 1
257 257 HSV_tuple = [h, 0.95, 0.95]
258 258 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
259 259 yield map(lambda x:str(int(x * 256)), RGB_tuple)
260 260
261 261 cgenerator = gen_color()
262 262
263 263 def get_color_string(cs):
264 264 if color_dict.has_key(cs):
265 265 col = color_dict[cs]
266 266 else:
267 267 col = color_dict[cs] = cgenerator.next()
268 268 return "color: rgb(%s)! important;" % (', '.join(col))
269 269
270 270 def url_func(changeset):
271 271 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
272 272 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
273 273
274 274 tooltip_html = tooltip_html % (changeset.author,
275 275 changeset.date,
276 276 tooltip(changeset.message))
277 277 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
278 278 short_id(changeset.raw_id))
279 279 uri = link_to(
280 280 lnk_format,
281 281 url('changeset_home', repo_name=changeset.repository.name,
282 282 revision=changeset.raw_id),
283 283 style=get_color_string(changeset.raw_id),
284 284 class_='tooltip',
285 285 title=tooltip_html
286 286 )
287 287
288 288 uri += '\n'
289 289 return uri
290 290 return literal(annotate_highlight(filenode, url_func, **kwargs))
291 291
292 292 def repo_name_slug(value):
293 293 """Return slug of name of repository
294 294 This function is called on each creation/modification
295 295 of repository to prevent bad names in repo
296 296 """
297 297
298 298 slug = remove_formatting(value)
299 299 slug = strip_tags(slug)
300 300
301 301 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
302 302 slug = slug.replace(c, '-')
303 303 slug = recursive_replace(slug, '-')
304 304 slug = collapse(slug, '-')
305 305 return slug
306 306
307 307 def get_changeset_safe(repo, rev):
308 308 from vcs.backends.base import BaseRepository
309 309 from vcs.exceptions import RepositoryError
310 310 if not isinstance(repo, BaseRepository):
311 311 raise Exception('You must pass an Repository '
312 312 'object as first argument got %s', type(repo))
313 313
314 314 try:
315 315 cs = repo.get_changeset(rev)
316 316 except RepositoryError:
317 317 from rhodecode.lib.utils import EmptyChangeset
318 318 cs = EmptyChangeset()
319 319 return cs
320 320
321 321
322 322 flash = _Flash()
323 323
324 324
325 325 #==============================================================================
326 326 # MERCURIAL FILTERS available via h.
327 327 #==============================================================================
328 328 from mercurial import util
329 329 from mercurial.templatefilters import person as _person
330 330
331 331 def _age(curdate):
332 332 """turns a datetime into an age string."""
333 333
334 334 if not curdate:
335 335 return ''
336 336
337 337 from datetime import timedelta, datetime
338 338
339 339 agescales = [("year", 3600 * 24 * 365),
340 340 ("month", 3600 * 24 * 30),
341 341 ("day", 3600 * 24),
342 342 ("hour", 3600),
343 343 ("minute", 60),
344 344 ("second", 1), ]
345 345
346 346 age = datetime.now() - curdate
347 347 age_seconds = (age.days * agescales[2][1]) + age.seconds
348 348 pos = 1
349 349 for scale in agescales:
350 350 if scale[1] <= age_seconds:
351 351 if pos == 6:pos = 5
352 352 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
353 353 pos += 1
354 354
355 355 return _('just now')
356 356
357 357 age = lambda x:_age(x)
358 358 capitalize = lambda x: x.capitalize()
359 359 email = util.email
360 360 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
361 361 person = lambda x: _person(x)
362 362 short_id = lambda x: x[:12]
363 363
364 364
365 365 def bool2icon(value):
366 366 """Returns True/False values represented as small html image of true/false
367 367 icons
368 368
369 369 :param value: bool value
370 370 """
371 371
372 372 if value is True:
373 373 return HTML.tag('img', src="/images/icons/accept.png", alt=_('True'))
374 374
375 375 if value is False:
376 376 return HTML.tag('img', src="/images/icons/cancel.png", alt=_('False'))
377 377
378 378 return value
379 379
380 380
381 381 def action_parser(user_log):
382 382 """This helper will map the specified string action into translated
383 383 fancy names with icons and links
384 384
385 385 :param user_log: user log instance
386 386 """
387 387
388 388 action = user_log.action
389 389 action_params = ' '
390 390
391 391 x = action.split(':')
392 392
393 393 if len(x) > 1:
394 394 action, action_params = x
395 395
396 396 def get_cs_links():
397 if action == 'push':
398 revs_limit = 5 #display this amount always
399 revs_top_limit = 50 #show upto this amount of changesets hidden
400 revs = action_params.split(',')
401 repo_name = user_log.repository.repo_name
402 from rhodecode.model.scm import ScmModel
397 revs_limit = 5 #display this amount always
398 revs_top_limit = 50 #show upto this amount of changesets hidden
399 revs = action_params.split(',')
400 repo_name = user_log.repository.repo_name
401 from rhodecode.model.scm import ScmModel
403 402
404 message = lambda rev: get_changeset_safe(ScmModel().get(repo_name),
405 rev).message
403 message = lambda rev: get_changeset_safe(ScmModel().get(repo_name),
404 rev).message
406 405
407 cs_links = " " + ', '.join ([link_to(rev,
408 url('changeset_home',
409 repo_name=repo_name,
410 revision=rev), title=tooltip(message(rev)),
411 class_='tooltip') for rev in revs[:revs_limit] ])
412 if len(revs) > revs_limit:
413 uniq_id = revs[0]
414 html_tmpl = ('<span> %s '
415 '<a class="show_more" id="_%s" href="#">%s</a> '
416 '%s</span>')
417 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
418 % (len(revs) - revs_limit),
419 _('revisions'))
406 cs_links = " " + ', '.join ([link_to(rev,
407 url('changeset_home',
408 repo_name=repo_name,
409 revision=rev), title=tooltip(message(rev)),
410 class_='tooltip') for rev in revs[:revs_limit] ])
411 if len(revs) > revs_limit:
412 uniq_id = revs[0]
413 html_tmpl = ('<span> %s '
414 '<a class="show_more" id="_%s" href="#">%s</a> '
415 '%s</span>')
416 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
417 % (len(revs) - revs_limit),
418 _('revisions'))
420 419
421 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
422 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
423 url('changeset_home',
424 repo_name=repo_name, revision=rev),
425 title=message(rev), class_='tooltip')
426 for rev in revs[revs_limit:revs_top_limit]]))
420 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
421 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
422 url('changeset_home',
423 repo_name=repo_name, revision=rev),
424 title=message(rev), class_='tooltip')
425 for rev in revs[revs_limit:revs_top_limit]]))
427 426
428 return cs_links
429 return ''
427 return cs_links
430 428
431 429 def get_fork_name():
432 if action == 'user_forked_repo':
433 from rhodecode.model.scm import ScmModel
434 repo_name = action_params
435 repo = ScmModel().get(repo_name)
436 if repo is None:
437 return repo_name
438 return link_to(action_params, url('summary_home',
439 repo_name=repo.name,),
440 title=repo.dbrepo.description)
441 return ''
442 map = {'user_deleted_repo':_('User [deleted] repository'),
443 'user_created_repo':_('User [created] repository'),
444 'user_forked_repo':_('User [forked] repository as: %s') % get_fork_name(),
445 'user_updated_repo':_('User [updated] repository'),
446 'admin_deleted_repo':_('Admin [delete] repository'),
447 'admin_created_repo':_('Admin [created] repository'),
448 'admin_forked_repo':_('Admin [forked] repository'),
449 'admin_updated_repo':_('Admin [updated] repository'),
450 'push':_('[Pushed] %s') % get_cs_links(),
451 'pull':_('[Pulled]'),
452 'started_following_repo':_('User [started following] repository'),
453 'stopped_following_repo':_('User [stopped following] repository'),
430 from rhodecode.model.scm import ScmModel
431 repo_name = action_params
432 repo = ScmModel().get(repo_name)
433 if repo is None:
434 return repo_name
435 return link_to(action_params, url('summary_home',
436 repo_name=repo.name,),
437 title=repo.dbrepo.description)
438
439 map = {'user_deleted_repo':(_('User [deleted] repository'), None),
440 'user_created_repo':(_('User [created] repository'), None),
441 'user_forked_repo':(_('User [forked] repository as:'), get_fork_name),
442 'user_updated_repo':(_('User [updated] repository'), None),
443 'admin_deleted_repo':(_('Admin [delete] repository'), None),
444 'admin_created_repo':(_('Admin [created] repository'), None),
445 'admin_forked_repo':(_('Admin [forked] repository'), None),
446 'admin_updated_repo':(_('Admin [updated] repository'), None),
447 'push':(_('[Pushed]'), get_cs_links),
448 'pull':(_('[Pulled]'), None),
449 'started_following_repo':(_('User [started following] repository'), None),
450 'stopped_following_repo':(_('User [stopped following] repository'), None),
454 451 }
455 452
456 453 action_str = map.get(action, action)
457 return literal(action_str.replace('[', '<span class="journal_highlight">')\
458 .replace(']', '</span>'))
454 action = action_str[0].replace('[', '<span class="journal_highlight">')\
455 .replace(']', '</span>')
456 if action_str[1] is not None:
457 action = action + " " + action_str[1]()
458
459 return literal(action)
459 460
460 461 def action_parser_icon(user_log):
461 462 action = user_log.action
462 463 action_params = None
463 464 x = action.split(':')
464 465
465 466 if len(x) > 1:
466 467 action, action_params = x
467 468
468 469 tmpl = """<img src="/images/icons/%s" alt="%s"/>"""
469 470 map = {'user_deleted_repo':'database_delete.png',
470 471 'user_created_repo':'database_add.png',
471 472 'user_forked_repo':'arrow_divide.png',
472 473 'user_updated_repo':'database_edit.png',
473 474 'admin_deleted_repo':'database_delete.png',
474 475 'admin_created_repo':'database_add.png',
475 476 'admin_forked_repo':'arrow_divide.png',
476 477 'admin_updated_repo':'database_edit.png',
477 478 'push':'script_add.png',
478 479 'pull':'down_16.png',
479 480 'started_following_repo':'heart_add.png',
480 481 'stopped_following_repo':'heart_delete.png',
481 482 }
482 483 return literal(tmpl % (map.get(action, action), action))
483 484
484 485
485 486 #==============================================================================
486 487 # PERMS
487 488 #==============================================================================
488 489 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
489 490 HasRepoPermissionAny, HasRepoPermissionAll
490 491
491 492 #==============================================================================
492 493 # GRAVATAR URL
493 494 #==============================================================================
494 495 import hashlib
495 496 import urllib
496 497 from pylons import request
497 498
498 499 def gravatar_url(email_address, size=30):
499 500 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
500 501 default = 'identicon'
501 502 baseurl_nossl = "http://www.gravatar.com/avatar/"
502 503 baseurl_ssl = "https://secure.gravatar.com/avatar/"
503 504 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
504 505
505 506
506 507 # construct the url
507 508 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
508 509 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
509 510
510 511 return gravatar_url
511 512
512 513 def safe_unicode(str):
513 514 """safe unicode function. In case of UnicodeDecode error we try to return
514 515 unicode with errors replace, if this failes we return unicode with
515 516 string_escape decoding """
516 517
517 518 try:
518 519 u_str = unicode(str)
519 520 except UnicodeDecodeError:
520 521 try:
521 522 u_str = unicode(str, 'utf-8', 'replace')
522 523 except UnicodeDecodeError:
523 524 #incase we have a decode error just represent as byte string
524 525 u_str = unicode(str(str).encode('string_escape'))
525 526
526 527 return u_str
General Comments 0
You need to be logged in to leave comments. Login now