##// END OF EJS Templates
White space cleanup
marcink -
r2815:acc05c33 beta
parent child Browse files
Show More
@@ -1,447 +1,447 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.changeset
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 changeset controller for pylons showoing changes beetween
7 7 revisions
8 8
9 9 :created_on: Apr 25, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26 import logging
27 27 import traceback
28 28 from collections import defaultdict
29 29 from webob.exc import HTTPForbidden
30 30
31 31 from pylons import tmpl_context as c, url, request, response
32 32 from pylons.i18n.translation import _
33 33 from pylons.controllers.util import redirect
34 34 from pylons.decorators import jsonify
35 35
36 36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
37 37 ChangesetDoesNotExistError
38 38 from rhodecode.lib.vcs.nodes import FileNode
39 39
40 40 import rhodecode.lib.helpers as h
41 41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 42 from rhodecode.lib.base import BaseRepoController, render
43 43 from rhodecode.lib.utils import action_logger
44 44 from rhodecode.lib.compat import OrderedDict
45 45 from rhodecode.lib import diffs
46 46 from rhodecode.model.db import ChangesetComment, ChangesetStatus
47 47 from rhodecode.model.comment import ChangesetCommentsModel
48 48 from rhodecode.model.changeset_status import ChangesetStatusModel
49 49 from rhodecode.model.meta import Session
50 50 from rhodecode.lib.diffs import wrapped_diff
51 51 from rhodecode.model.repo import RepoModel
52 52 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
53 53 from rhodecode.lib.vcs.backends.base import EmptyChangeset
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 def _update_with_GET(params, GET):
59 59 for k in ['diff1', 'diff2', 'diff']:
60 60 params[k] += GET.getall(k)
61 61
62 62
63 63 def anchor_url(revision, path, GET):
64 64 fid = h.FID(revision, path)
65 65 return h.url.current(anchor=fid, **dict(GET))
66 66
67 67
68 68 def get_ignore_ws(fid, GET):
69 69 ig_ws_global = GET.get('ignorews')
70 70 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
71 71 if ig_ws:
72 72 try:
73 73 return int(ig_ws[0].split(':')[-1])
74 74 except:
75 75 pass
76 76 return ig_ws_global
77 77
78 78
79 79 def _ignorews_url(GET, fileid=None):
80 80 fileid = str(fileid) if fileid else None
81 81 params = defaultdict(list)
82 82 _update_with_GET(params, GET)
83 83 lbl = _('show white space')
84 84 ig_ws = get_ignore_ws(fileid, GET)
85 85 ln_ctx = get_line_ctx(fileid, GET)
86 86 # global option
87 87 if fileid is None:
88 88 if ig_ws is None:
89 89 params['ignorews'] += [1]
90 90 lbl = _('ignore white space')
91 91 ctx_key = 'context'
92 92 ctx_val = ln_ctx
93 93 # per file options
94 94 else:
95 95 if ig_ws is None:
96 96 params[fileid] += ['WS:1']
97 97 lbl = _('ignore white space')
98 98
99 99 ctx_key = fileid
100 100 ctx_val = 'C:%s' % ln_ctx
101 101 # if we have passed in ln_ctx pass it along to our params
102 102 if ln_ctx:
103 103 params[ctx_key] += [ctx_val]
104 104
105 105 params['anchor'] = fileid
106 106 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
107 107 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
108 108
109 109
110 110 def get_line_ctx(fid, GET):
111 111 ln_ctx_global = GET.get('context')
112 112 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
113 113
114 114 if ln_ctx:
115 115 retval = ln_ctx[0].split(':')[-1]
116 116 else:
117 117 retval = ln_ctx_global
118 118
119 119 try:
120 120 return int(retval)
121 121 except:
122 122 return
123 123
124 124
125 125 def _context_url(GET, fileid=None):
126 126 """
127 127 Generates url for context lines
128 128
129 129 :param fileid:
130 130 """
131 131
132 132 fileid = str(fileid) if fileid else None
133 133 ig_ws = get_ignore_ws(fileid, GET)
134 134 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
135 135
136 136 params = defaultdict(list)
137 137 _update_with_GET(params, GET)
138 138
139 139 # global option
140 140 if fileid is None:
141 141 if ln_ctx > 0:
142 142 params['context'] += [ln_ctx]
143 143
144 144 if ig_ws:
145 145 ig_ws_key = 'ignorews'
146 146 ig_ws_val = 1
147 147
148 148 # per file option
149 149 else:
150 150 params[fileid] += ['C:%s' % ln_ctx]
151 151 ig_ws_key = fileid
152 152 ig_ws_val = 'WS:%s' % 1
153 153
154 154 if ig_ws:
155 155 params[ig_ws_key] += [ig_ws_val]
156 156
157 157 lbl = _('%s line context') % ln_ctx
158 158
159 159 params['anchor'] = fileid
160 160 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
161 161 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
162 162
163 163
164 164 class ChangesetController(BaseRepoController):
165 165
166 166 @LoginRequired()
167 167 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
168 168 'repository.admin')
169 169 def __before__(self):
170 170 super(ChangesetController, self).__before__()
171 171 c.affected_files_cut_off = 60
172 172 repo_model = RepoModel()
173 173 c.users_array = repo_model.get_users_js()
174 174 c.users_groups_array = repo_model.get_users_groups_js()
175 175
176 176 def index(self, revision):
177 177
178 178 c.anchor_url = anchor_url
179 179 c.ignorews_url = _ignorews_url
180 180 c.context_url = _context_url
181 181 limit_off = request.GET.get('fulldiff')
182 182 #get ranges of revisions if preset
183 183 rev_range = revision.split('...')[:2]
184 184 enable_comments = True
185 185 try:
186 186 if len(rev_range) == 2:
187 187 enable_comments = False
188 188 rev_start = rev_range[0]
189 189 rev_end = rev_range[1]
190 190 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
191 191 end=rev_end)
192 192 else:
193 193 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
194 194
195 195 c.cs_ranges = list(rev_ranges)
196 196 if not c.cs_ranges:
197 197 raise RepositoryError('Changeset range returned empty result')
198 198
199 199 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
200 200 log.error(traceback.format_exc())
201 201 h.flash(str(e), category='warning')
202 202 return redirect(url('home'))
203 203
204 204 c.changes = OrderedDict()
205 205
206 206 c.lines_added = 0 # count of lines added
207 207 c.lines_deleted = 0 # count of lines removes
208 208
209 209 cumulative_diff = 0
210 210 c.cut_off = False # defines if cut off limit is reached
211 211 c.changeset_statuses = ChangesetStatus.STATUSES
212 212 c.comments = []
213 213 c.statuses = []
214 214 c.inline_comments = []
215 215 c.inline_cnt = 0
216 216 # Iterate over ranges (default changeset view is always one changeset)
217 217 for changeset in c.cs_ranges:
218 218
219 219 c.statuses.extend([ChangesetStatusModel()\
220 220 .get_status(c.rhodecode_db_repo.repo_id,
221 221 changeset.raw_id)])
222 222
223 223 c.comments.extend(ChangesetCommentsModel()\
224 224 .get_comments(c.rhodecode_db_repo.repo_id,
225 225 revision=changeset.raw_id))
226 226 inlines = ChangesetCommentsModel()\
227 227 .get_inline_comments(c.rhodecode_db_repo.repo_id,
228 228 revision=changeset.raw_id)
229 229 c.inline_comments.extend(inlines)
230 230 c.changes[changeset.raw_id] = []
231 231 try:
232 232 changeset_parent = changeset.parents[0]
233 233 except IndexError:
234 234 changeset_parent = None
235 235
236 236 #==================================================================
237 237 # ADDED FILES
238 238 #==================================================================
239 239 for node in changeset.added:
240 240 fid = h.FID(revision, node.path)
241 241 line_context_lcl = get_line_ctx(fid, request.GET)
242 242 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
243 243 lim = self.cut_off_limit
244 244 if cumulative_diff > self.cut_off_limit:
245 245 lim = -1 if limit_off is None else None
246 246 size, cs1, cs2, diff, st = wrapped_diff(
247 247 filenode_old=None,
248 248 filenode_new=node,
249 249 cut_off_limit=lim,
250 250 ignore_whitespace=ign_whitespace_lcl,
251 251 line_context=line_context_lcl,
252 252 enable_comments=enable_comments
253 253 )
254 254 cumulative_diff += size
255 255 c.lines_added += st[0]
256 256 c.lines_deleted += st[1]
257 257 c.changes[changeset.raw_id].append(
258 258 ('added', node, diff, cs1, cs2, st)
259 259 )
260 260
261 261 #==================================================================
262 262 # CHANGED FILES
263 263 #==================================================================
264 264 for node in changeset.changed:
265 265 try:
266 266 filenode_old = changeset_parent.get_node(node.path)
267 267 except ChangesetError:
268 268 log.warning('Unable to fetch parent node for diff')
269 269 filenode_old = FileNode(node.path, '', EmptyChangeset())
270 270
271 271 fid = h.FID(revision, node.path)
272 272 line_context_lcl = get_line_ctx(fid, request.GET)
273 273 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
274 274 lim = self.cut_off_limit
275 275 if cumulative_diff > self.cut_off_limit:
276 276 lim = -1 if limit_off is None else None
277 277 size, cs1, cs2, diff, st = wrapped_diff(
278 278 filenode_old=filenode_old,
279 279 filenode_new=node,
280 280 cut_off_limit=lim,
281 281 ignore_whitespace=ign_whitespace_lcl,
282 282 line_context=line_context_lcl,
283 283 enable_comments=enable_comments
284 284 )
285 285 cumulative_diff += size
286 286 c.lines_added += st[0]
287 287 c.lines_deleted += st[1]
288 288 c.changes[changeset.raw_id].append(
289 289 ('changed', node, diff, cs1, cs2, st)
290 290 )
291 291 #==================================================================
292 292 # REMOVED FILES
293 293 #==================================================================
294 294 for node in changeset.removed:
295 295 c.changes[changeset.raw_id].append(
296 296 ('removed', node, None, None, None, (0, 0))
297 297 )
298 298
299 299 # count inline comments
300 300 for __, lines in c.inline_comments:
301 301 for comments in lines.values():
302 302 c.inline_cnt += len(comments)
303 303
304 304 if len(c.cs_ranges) == 1:
305 305 c.changeset = c.cs_ranges[0]
306 306 c.changes = c.changes[c.changeset.raw_id]
307 307
308 308 return render('changeset/changeset.html')
309 309 else:
310 310 return render('changeset/changeset_range.html')
311 311
312 312 def raw_changeset(self, revision):
313 313
314 314 method = request.GET.get('diff', 'show')
315 315 ignore_whitespace = request.GET.get('ignorews') == '1'
316 316 line_context = request.GET.get('context', 3)
317 317 try:
318 318 c.scm_type = c.rhodecode_repo.alias
319 319 c.changeset = c.rhodecode_repo.get_changeset(revision)
320 320 except RepositoryError:
321 321 log.error(traceback.format_exc())
322 322 return redirect(url('home'))
323 323 else:
324 324 try:
325 325 c.changeset_parent = c.changeset.parents[0]
326 326 except IndexError:
327 327 c.changeset_parent = None
328 328 c.changes = []
329 329
330 330 for node in c.changeset.added:
331 331 filenode_old = FileNode(node.path, '')
332 332 if filenode_old.is_binary or node.is_binary:
333 333 diff = _('binary file') + '\n'
334 334 else:
335 335 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
336 336 ignore_whitespace=ignore_whitespace,
337 337 context=line_context)
338 338 diff = diffs.DiffProcessor(f_gitdiff,
339 339 format='gitdiff').raw_diff()
340 340
341 341 cs1 = None
342 342 cs2 = node.changeset.raw_id
343 343 c.changes.append(('added', node, diff, cs1, cs2))
344 344
345 345 for node in c.changeset.changed:
346 346 filenode_old = c.changeset_parent.get_node(node.path)
347 347 if filenode_old.is_binary or node.is_binary:
348 348 diff = _('binary file')
349 349 else:
350 350 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
351 351 ignore_whitespace=ignore_whitespace,
352 352 context=line_context)
353 353 diff = diffs.DiffProcessor(f_gitdiff,
354 354 format='gitdiff').raw_diff()
355 355
356 356 cs1 = filenode_old.changeset.raw_id
357 357 cs2 = node.changeset.raw_id
358 358 c.changes.append(('changed', node, diff, cs1, cs2))
359 359
360 360 response.content_type = 'text/plain'
361 361
362 362 if method == 'download':
363 363 response.content_disposition = 'attachment; filename=%s.patch' \
364 364 % revision
365 365
366 366 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
367 367 for x in c.changeset.parents])
368 368
369 369 c.diffs = ''
370 370 for x in c.changes:
371 371 c.diffs += x[2]
372 372
373 373 return render('changeset/raw_changeset.html')
374 374
375 375 @jsonify
376 376 def comment(self, repo_name, revision):
377 377 status = request.POST.get('changeset_status')
378 378 change_status = request.POST.get('change_changeset_status')
379 379 text = request.POST.get('text')
380 380 if status and change_status:
381 381 text = text or (_('Status change -> %s')
382 382 % ChangesetStatus.get_status_lbl(status))
383 383
384 384 comm = ChangesetCommentsModel().create(
385 385 text=text,
386 386 repo=c.rhodecode_db_repo.repo_id,
387 387 user=c.rhodecode_user.user_id,
388 388 revision=revision,
389 389 f_path=request.POST.get('f_path'),
390 390 line_no=request.POST.get('line'),
391 391 status_change=(ChangesetStatus.get_status_lbl(status)
392 392 if status and change_status else None)
393 393 )
394 394
395 395 # get status if set !
396 396 if status and change_status:
397 397 # if latest status was from pull request and it's closed
398 # disallow changing status !
398 # disallow changing status !
399 399 # dont_allow_on_closed_pull_request = True !
400 400
401 401 try:
402 402 ChangesetStatusModel().set_status(
403 403 c.rhodecode_db_repo.repo_id,
404 404 status,
405 405 c.rhodecode_user.user_id,
406 406 comm,
407 407 revision=revision,
408 408 dont_allow_on_closed_pull_request=True
409 409 )
410 410 except StatusChangeOnClosedPullRequestError:
411 411 log.error(traceback.format_exc())
412 412 msg = _('Changing status on a changeset associated with'
413 413 'a closed pull request is not allowed')
414 414 h.flash(msg, category='warning')
415 415 return redirect(h.url('changeset_home', repo_name=repo_name,
416 416 revision=revision))
417 417 action_logger(self.rhodecode_user,
418 418 'user_commented_revision:%s' % revision,
419 419 c.rhodecode_db_repo, self.ip_addr, self.sa)
420 420
421 421 Session().commit()
422 422
423 423 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
424 424 return redirect(h.url('changeset_home', repo_name=repo_name,
425 425 revision=revision))
426 426
427 427 data = {
428 428 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
429 429 }
430 430 if comm:
431 431 c.co = comm
432 432 data.update(comm.get_dict())
433 433 data.update({'rendered_text':
434 434 render('changeset/changeset_comment_block.html')})
435 435
436 436 return data
437 437
438 438 @jsonify
439 439 def delete_comment(self, repo_name, comment_id):
440 440 co = ChangesetComment.get(comment_id)
441 441 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
442 442 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
443 443 ChangesetCommentsModel().delete(comment=co)
444 444 Session().commit()
445 445 return True
446 446 else:
447 447 raise HTTPForbidden()
@@ -1,594 +1,591 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.compat
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Python backward compatibility functions and common libs
7 7
8 8
9 9 :created_on: Oct 7, 2011
10 10 :author: marcink
11 11 :copyright: (C) 2010-2010 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 from rhodecode import __platform__, PLATFORM_WIN, __py_version__
29 29
30 30 #==============================================================================
31 31 # json
32 32 #==============================================================================
33 33 from rhodecode.lib.ext_json import json
34 34
35 35
36 36 #==============================================================================
37 37 # izip_longest
38 38 #==============================================================================
39 39 try:
40 40 from itertools import izip_longest
41 41 except ImportError:
42 42 import itertools
43 43
44 44 def izip_longest(*args, **kwds):
45 45 fillvalue = kwds.get("fillvalue")
46 46
47 47 def sentinel(counter=([fillvalue] * (len(args) - 1)).pop):
48 48 yield counter() # yields the fillvalue, or raises IndexError
49 49
50 50 fillers = itertools.repeat(fillvalue)
51 51 iters = [itertools.chain(it, sentinel(), fillers)
52 52 for it in args]
53 53 try:
54 54 for tup in itertools.izip(*iters):
55 55 yield tup
56 56 except IndexError:
57 57 pass
58 58
59 59
60 60 #==============================================================================
61 61 # OrderedDict
62 62 #==============================================================================
63 63
64 64 # Python Software Foundation License
65 65
66 66 # XXX: it feels like using the class with "is" and "is not" instead of "==" and
67 67 # "!=" should be faster.
68 68 class _Nil(object):
69 69
70 70 def __repr__(self):
71 71 return "nil"
72 72
73 73 def __eq__(self, other):
74 74 if (isinstance(other, _Nil)):
75 75 return True
76 76 else:
77 77 return NotImplemented
78 78
79 79 def __ne__(self, other):
80 80 if (isinstance(other, _Nil)):
81 81 return False
82 82 else:
83 83 return NotImplemented
84 84
85 85 _nil = _Nil()
86 86
87 87
88 88 class _odict(object):
89 89 """Ordered dict data structure, with O(1) complexity for dict operations
90 90 that modify one element.
91 91
92 92 Overwriting values doesn't change their original sequential order.
93 93 """
94 94
95 95 def _dict_impl(self):
96 96 return None
97 97
98 98 def __init__(self, data=(), **kwds):
99 99 """This doesn't accept keyword initialization as normal dicts to avoid
100 100 a trap - inside a function or method the keyword args are accessible
101 101 only as a dict, without a defined order, so their original order is
102 102 lost.
103 103 """
104 104 if kwds:
105 105 raise TypeError("__init__() of ordered dict takes no keyword "
106 106 "arguments to avoid an ordering trap.")
107 107 self._dict_impl().__init__(self)
108 108 # If you give a normal dict, then the order of elements is undefined
109 109 if hasattr(data, "iteritems"):
110 110 for key, val in data.iteritems():
111 111 self[key] = val
112 112 else:
113 113 for key, val in data:
114 114 self[key] = val
115 115
116 116 # Double-linked list header
117 117 def _get_lh(self):
118 118 dict_impl = self._dict_impl()
119 119 if not hasattr(self, '_lh'):
120 120 dict_impl.__setattr__(self, '_lh', _nil)
121 121 return dict_impl.__getattribute__(self, '_lh')
122 122
123 123 def _set_lh(self, val):
124 124 self._dict_impl().__setattr__(self, '_lh', val)
125 125
126 126 lh = property(_get_lh, _set_lh)
127 127
128 128 # Double-linked list tail
129 129 def _get_lt(self):
130 130 dict_impl = self._dict_impl()
131 131 if not hasattr(self, '_lt'):
132 132 dict_impl.__setattr__(self, '_lt', _nil)
133 133 return dict_impl.__getattribute__(self, '_lt')
134 134
135 135 def _set_lt(self, val):
136 136 self._dict_impl().__setattr__(self, '_lt', val)
137 137
138 138 lt = property(_get_lt, _set_lt)
139 139
140 140 def __getitem__(self, key):
141 141 return self._dict_impl().__getitem__(self, key)[1]
142 142
143 143 def __setitem__(self, key, val):
144 144 dict_impl = self._dict_impl()
145 145 try:
146 146 dict_impl.__getitem__(self, key)[1] = val
147 147 except KeyError:
148 148 new = [dict_impl.__getattribute__(self, 'lt'), val, _nil]
149 149 dict_impl.__setitem__(self, key, new)
150 150 if dict_impl.__getattribute__(self, 'lt') == _nil:
151 151 dict_impl.__setattr__(self, 'lh', key)
152 152 else:
153 153 dict_impl.__getitem__(
154 154 self, dict_impl.__getattribute__(self, 'lt'))[2] = key
155 155 dict_impl.__setattr__(self, 'lt', key)
156 156
157 157 def __delitem__(self, key):
158 158 dict_impl = self._dict_impl()
159 159 pred, _, succ = self._dict_impl().__getitem__(self, key)
160 160 if pred == _nil:
161 161 dict_impl.__setattr__(self, 'lh', succ)
162 162 else:
163 163 dict_impl.__getitem__(self, pred)[2] = succ
164 164 if succ == _nil:
165 165 dict_impl.__setattr__(self, 'lt', pred)
166 166 else:
167 167 dict_impl.__getitem__(self, succ)[0] = pred
168 168 dict_impl.__delitem__(self, key)
169 169
170 170 def __contains__(self, key):
171 171 return key in self.keys()
172 172
173 173 def __len__(self):
174 174 return len(self.keys())
175 175
176 176 def __str__(self):
177 177 pairs = ("%r: %r" % (k, v) for k, v in self.iteritems())
178 178 return "{%s}" % ", ".join(pairs)
179 179
180 180 def __repr__(self):
181 181 if self:
182 182 pairs = ("(%r, %r)" % (k, v) for k, v in self.iteritems())
183 183 return "odict([%s])" % ", ".join(pairs)
184 184 else:
185 185 return "odict()"
186 186
187 187 def get(self, k, x=None):
188 188 if k in self:
189 189 return self._dict_impl().__getitem__(self, k)[1]
190 190 else:
191 191 return x
192 192
193 193 def __iter__(self):
194 194 dict_impl = self._dict_impl()
195 195 curr_key = dict_impl.__getattribute__(self, 'lh')
196 196 while curr_key != _nil:
197 197 yield curr_key
198 198 curr_key = dict_impl.__getitem__(self, curr_key)[2]
199 199
200 200 iterkeys = __iter__
201 201
202 202 def keys(self):
203 203 return list(self.iterkeys())
204 204
205 205 def itervalues(self):
206 206 dict_impl = self._dict_impl()
207 207 curr_key = dict_impl.__getattribute__(self, 'lh')
208 208 while curr_key != _nil:
209 209 _, val, curr_key = dict_impl.__getitem__(self, curr_key)
210 210 yield val
211 211
212 212 def values(self):
213 213 return list(self.itervalues())
214 214
215 215 def iteritems(self):
216 216 dict_impl = self._dict_impl()
217 217 curr_key = dict_impl.__getattribute__(self, 'lh')
218 218 while curr_key != _nil:
219 219 _, val, next_key = dict_impl.__getitem__(self, curr_key)
220 220 yield curr_key, val
221 221 curr_key = next_key
222 222
223 223 def items(self):
224 224 return list(self.iteritems())
225 225
226 226 def sort(self, cmp=None, key=None, reverse=False):
227 227 items = [(k, v) for k, v in self.items()]
228 228 if cmp is not None:
229 229 items = sorted(items, cmp=cmp)
230 230 elif key is not None:
231 231 items = sorted(items, key=key)
232 232 else:
233 233 items = sorted(items, key=lambda x: x[1])
234 234 if reverse:
235 235 items.reverse()
236 236 self.clear()
237 237 self.__init__(items)
238 238
239 239 def clear(self):
240 240 dict_impl = self._dict_impl()
241 241 dict_impl.clear(self)
242 242 dict_impl.__setattr__(self, 'lh', _nil)
243 243 dict_impl.__setattr__(self, 'lt', _nil)
244 244
245 245 def copy(self):
246 246 return self.__class__(self)
247 247
248 248 def update(self, data=(), **kwds):
249 249 if kwds:
250 250 raise TypeError("update() of ordered dict takes no keyword "
251 251 "arguments to avoid an ordering trap.")
252 252 if hasattr(data, "iteritems"):
253 253 data = data.iteritems()
254 254 for key, val in data:
255 255 self[key] = val
256 256
257 257 def setdefault(self, k, x=None):
258 258 try:
259 259 return self[k]
260 260 except KeyError:
261 261 self[k] = x
262 262 return x
263 263
264 264 def pop(self, k, x=_nil):
265 265 try:
266 266 val = self[k]
267 267 del self[k]
268 268 return val
269 269 except KeyError:
270 270 if x == _nil:
271 271 raise
272 272 return x
273 273
274 274 def popitem(self):
275 275 try:
276 276 dict_impl = self._dict_impl()
277 277 key = dict_impl.__getattribute__(self, 'lt')
278 278 return key, self.pop(key)
279 279 except KeyError:
280 280 raise KeyError("'popitem(): ordered dictionary is empty'")
281 281
282 282 def riterkeys(self):
283 283 """To iterate on keys in reversed order.
284 284 """
285 285 dict_impl = self._dict_impl()
286 286 curr_key = dict_impl.__getattribute__(self, 'lt')
287 287 while curr_key != _nil:
288 288 yield curr_key
289 289 curr_key = dict_impl.__getitem__(self, curr_key)[0]
290 290
291 291 __reversed__ = riterkeys
292 292
293 293 def rkeys(self):
294 294 """List of the keys in reversed order.
295 295 """
296 296 return list(self.riterkeys())
297 297
298 298 def ritervalues(self):
299 299 """To iterate on values in reversed order.
300 300 """
301 301 dict_impl = self._dict_impl()
302 302 curr_key = dict_impl.__getattribute__(self, 'lt')
303 303 while curr_key != _nil:
304 304 curr_key, val, _ = dict_impl.__getitem__(self, curr_key)
305 305 yield val
306 306
307 307 def rvalues(self):
308 308 """List of the values in reversed order.
309 309 """
310 310 return list(self.ritervalues())
311 311
312 312 def riteritems(self):
313 313 """To iterate on (key, value) in reversed order.
314 314 """
315 315 dict_impl = self._dict_impl()
316 316 curr_key = dict_impl.__getattribute__(self, 'lt')
317 317 while curr_key != _nil:
318 318 pred_key, val, _ = dict_impl.__getitem__(self, curr_key)
319 319 yield curr_key, val
320 320 curr_key = pred_key
321 321
322 322 def ritems(self):
323 323 """List of the (key, value) in reversed order.
324 324 """
325 325 return list(self.riteritems())
326 326
327 327 def firstkey(self):
328 328 if self:
329 329 return self._dict_impl().__getattribute__(self, 'lh')
330 330 else:
331 331 raise KeyError("'firstkey(): ordered dictionary is empty'")
332 332
333 333 def lastkey(self):
334 334 if self:
335 335 return self._dict_impl().__getattribute__(self, 'lt')
336 336 else:
337 337 raise KeyError("'lastkey(): ordered dictionary is empty'")
338 338
339 339 def as_dict(self):
340 340 return self._dict_impl()(self.items())
341 341
342 342 def _repr(self):
343 343 """_repr(): low level repr of the whole data contained in the odict.
344 344 Useful for debugging.
345 345 """
346 346 dict_impl = self._dict_impl()
347 347 form = "odict low level repr lh,lt,data: %r, %r, %s"
348 348 return form % (dict_impl.__getattribute__(self, 'lh'),
349 349 dict_impl.__getattribute__(self, 'lt'),
350 350 dict_impl.__repr__(self))
351 351
352 352
353 353 class OrderedDict(_odict, dict):
354 354
355 355 def _dict_impl(self):
356 356 return dict
357 357
358 358
359 359 #==============================================================================
360 360 # OrderedSet
361 361 #==============================================================================
362 362 from sqlalchemy.util import OrderedSet
363 363
364 364
365 365 #==============================================================================
366 366 # kill FUNCTIONS
367 367 #==============================================================================
368 368 if __platform__ in PLATFORM_WIN:
369 369 import ctypes
370 370
371 371 def kill(pid, sig):
372 372 """kill function for Win32"""
373 373 kernel32 = ctypes.windll.kernel32
374 374 handle = kernel32.OpenProcess(1, 0, pid)
375 375 return (0 != kernel32.TerminateProcess(handle, 0))
376 376
377 377 else:
378 378 kill = os.kill
379 379
380 380
381 381 #==============================================================================
382 382 # itertools.product
383 383 #==============================================================================
384 384
385 385 try:
386 386 from itertools import product
387 387 except ImportError:
388 388 def product(*args, **kwds):
389 389 # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
390 390 # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111
391 391 pools = map(tuple, args) * kwds.get('repeat', 1)
392 392 result = [[]]
393 393 for pool in pools:
394 394 result = [x + [y] for x in result for y in pool]
395 395 for prod in result:
396 396 yield tuple(prod)
397 397
398 398
399 399 #==============================================================================
400 400 # BytesIO
401 401 #==============================================================================
402 402
403 403 try:
404 404 from io import BytesIO
405 405 except ImportError:
406 406 from cStringIO import StringIO as BytesIO
407 407
408 408
409 409 #==============================================================================
410 410 # bytes
411 411 #==============================================================================
412 412 if __py_version__ >= (2, 6):
413 413 _bytes = bytes
414 414 else:
415 415 # in py2.6 bytes is a synonim for str
416 416 _bytes = str
417 417
418 418 #==============================================================================
419 419 # deque
420 420 #==============================================================================
421 421
422 422 if __py_version__ >= (2, 6):
423 423 from collections import deque
424 424 else:
425 425 #need to implement our own deque with maxlen
426 426 class deque(object):
427 427
428 428 def __init__(self, iterable=(), maxlen= -1):
429 429 if not hasattr(self, 'data'):
430 430 self.left = self.right = 0
431 431 self.data = {}
432 432 self.maxlen = maxlen or -1
433 433 self.extend(iterable)
434 434
435 435 def append(self, x):
436 436 self.data[self.right] = x
437 437 self.right += 1
438 438 if self.maxlen != -1 and len(self) > self.maxlen:
439 439 self.popleft()
440 440
441 441 def appendleft(self, x):
442 442 self.left -= 1
443 443 self.data[self.left] = x
444 444 if self.maxlen != -1 and len(self) > self.maxlen:
445 445 self.pop()
446 446
447 447 def pop(self):
448 448 if self.left == self.right:
449 449 raise IndexError('cannot pop from empty deque')
450 450 self.right -= 1
451 451 elem = self.data[self.right]
452 452 del self.data[self.right]
453 453 return elem
454 454
455 455 def popleft(self):
456 456 if self.left == self.right:
457 457 raise IndexError('cannot pop from empty deque')
458 458 elem = self.data[self.left]
459 459 del self.data[self.left]
460 460 self.left += 1
461 461 return elem
462 462
463 463 def clear(self):
464 464 self.data.clear()
465 465 self.left = self.right = 0
466 466
467 467 def extend(self, iterable):
468 468 for elem in iterable:
469 469 self.append(elem)
470 470
471 471 def extendleft(self, iterable):
472 472 for elem in iterable:
473 473 self.appendleft(elem)
474 474
475 475 def rotate(self, n=1):
476 476 if self:
477 477 n %= len(self)
478 478 for i in xrange(n):
479 479 self.appendleft(self.pop())
480 480
481 481 def __getitem__(self, i):
482 482 if i < 0:
483 483 i += len(self)
484 484 try:
485 485 return self.data[i + self.left]
486 486 except KeyError:
487 487 raise IndexError
488 488
489 489 def __setitem__(self, i, value):
490 490 if i < 0:
491 491 i += len(self)
492 492 try:
493 493 self.data[i + self.left] = value
494 494 except KeyError:
495 495 raise IndexError
496 496
497 497 def __delitem__(self, i):
498 498 size = len(self)
499 499 if not (-size <= i < size):
500 500 raise IndexError
501 501 data = self.data
502 502 if i < 0:
503 503 i += size
504 504 for j in xrange(self.left + i, self.right - 1):
505 505 data[j] = data[j + 1]
506 506 self.pop()
507 507
508 508 def __len__(self):
509 509 return self.right - self.left
510 510
511 511 def __cmp__(self, other):
512 512 if type(self) != type(other):
513 513 return cmp(type(self), type(other))
514 514 return cmp(list(self), list(other))
515 515
516 516 def __repr__(self, _track=[]):
517 517 if id(self) in _track:
518 518 return '...'
519 519 _track.append(id(self))
520 520 r = 'deque(%r, maxlen=%s)' % (list(self), self.maxlen)
521 521 _track.remove(id(self))
522 522 return r
523 523
524 524 def __getstate__(self):
525 525 return (tuple(self),)
526 526
527 527 def __setstate__(self, s):
528 528 self.__init__(s[0])
529 529
530 530 def __hash__(self):
531 531 raise TypeError
532 532
533 533 def __copy__(self):
534 534 return self.__class__(self)
535 535
536 536 def __deepcopy__(self, memo={}):
537 537 from copy import deepcopy
538 538 result = self.__class__()
539 539 memo[id(self)] = result
540 540 result.__init__(deepcopy(tuple(self), memo))
541 541 return result
542 542
543 543
544 544 #==============================================================================
545 545 # threading.Event
546 546 #==============================================================================
547 547
548 548 if __py_version__ >= (2, 6):
549 549 from threading import Event, Thread
550 550 else:
551 551 from threading import _Verbose, Condition, Lock, Thread
552 552
553 553 def Event(*args, **kwargs):
554 554 return _Event(*args, **kwargs)
555 555
556 556 class _Event(_Verbose):
557 557
558 558 # After Tim Peters' event class (without is_posted())
559 559
560 560 def __init__(self, verbose=None):
561 561 _Verbose.__init__(self, verbose)
562 562 self.__cond = Condition(Lock())
563 563 self.__flag = False
564 564
565 565 def isSet(self):
566 566 return self.__flag
567 567
568 568 is_set = isSet
569 569
570 570 def set(self):
571 571 self.__cond.acquire()
572 572 try:
573 573 self.__flag = True
574 574 self.__cond.notify_all()
575 575 finally:
576 576 self.__cond.release()
577 577
578 578 def clear(self):
579 579 self.__cond.acquire()
580 580 try:
581 581 self.__flag = False
582 582 finally:
583 583 self.__cond.release()
584 584
585 585 def wait(self, timeout=None):
586 586 self.__cond.acquire()
587 587 try:
588 588 if not self.__flag:
589 589 self.__cond.wait(timeout)
590 590 finally:
591 591 self.__cond.release()
592
593
594
@@ -1,1320 +1,1320 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db_1_3_0
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode <=1.3.X
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 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 modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 from collections import defaultdict
31 31
32 32 from sqlalchemy import *
33 33 from sqlalchemy.ext.hybrid import hybrid_property
34 34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 35 from beaker.cache import cache_region, region_invalidate
36 36
37 37 from rhodecode.lib.vcs import get_backend
38 38 from rhodecode.lib.vcs.utils.helpers import get_scm
39 39 from rhodecode.lib.vcs.exceptions import VCSError
40 40 from rhodecode.lib.vcs.utils.lazy import LazyProperty
41 41
42 42 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
43 43 safe_unicode
44 44 from rhodecode.lib.compat import json
45 45 from rhodecode.lib.caching_query import FromCache
46 46
47 47 from rhodecode.model.meta import Base, Session
48 48 import hashlib
49 49
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53 #==============================================================================
54 54 # BASE CLASSES
55 55 #==============================================================================
56 56
57 57 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
58 58
59 59
60 60 class ModelSerializer(json.JSONEncoder):
61 61 """
62 62 Simple Serializer for JSON,
63 63
64 64 usage::
65 65
66 66 to make object customized for serialization implement a __json__
67 67 method that will return a dict for serialization into json
68 68
69 69 example::
70 70
71 71 class Task(object):
72 72
73 73 def __init__(self, name, value):
74 74 self.name = name
75 75 self.value = value
76 76
77 77 def __json__(self):
78 78 return dict(name=self.name,
79 79 value=self.value)
80 80
81 81 """
82 82
83 83 def default(self, obj):
84 84
85 85 if hasattr(obj, '__json__'):
86 86 return obj.__json__()
87 87 else:
88 88 return json.JSONEncoder.default(self, obj)
89 89
90 90
91 91 class BaseModel(object):
92 92 """
93 93 Base Model for all classess
94 94 """
95 95
96 96 @classmethod
97 97 def _get_keys(cls):
98 98 """return column names for this model """
99 99 return class_mapper(cls).c.keys()
100 100
101 101 def get_dict(self):
102 102 """
103 103 return dict with keys and values corresponding
104 104 to this model data """
105 105
106 106 d = {}
107 107 for k in self._get_keys():
108 108 d[k] = getattr(self, k)
109 109
110 110 # also use __json__() if present to get additional fields
111 111 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
112 112 d[k] = val
113 113 return d
114 114
115 115 def get_appstruct(self):
116 116 """return list with keys and values tupples corresponding
117 117 to this model data """
118 118
119 119 l = []
120 120 for k in self._get_keys():
121 121 l.append((k, getattr(self, k),))
122 122 return l
123 123
124 124 def populate_obj(self, populate_dict):
125 125 """populate model with data from given populate_dict"""
126 126
127 127 for k in self._get_keys():
128 128 if k in populate_dict:
129 129 setattr(self, k, populate_dict[k])
130 130
131 131 @classmethod
132 132 def query(cls):
133 133 return Session.query(cls)
134 134
135 135 @classmethod
136 136 def get(cls, id_):
137 137 if id_:
138 138 return cls.query().get(id_)
139 139
140 140 @classmethod
141 141 def getAll(cls):
142 142 return cls.query().all()
143 143
144 144 @classmethod
145 145 def delete(cls, id_):
146 146 obj = cls.query().get(id_)
147 147 Session.delete(obj)
148 148
149 149 def __repr__(self):
150 150 if hasattr(self, '__unicode__'):
151 151 # python repr needs to return str
152 152 return safe_str(self.__unicode__())
153 153 return '<DB:%s>' % (self.__class__.__name__)
154 154
155 155 class RhodeCodeSetting(Base, BaseModel):
156 156 __tablename__ = 'rhodecode_settings'
157 157 __table_args__ = (
158 158 UniqueConstraint('app_settings_name'),
159 159 {'extend_existing': True, 'mysql_engine':'InnoDB',
160 160 'mysql_charset': 'utf8'}
161 161 )
162 162 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
163 163 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
164 164 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
165 165
166 166 def __init__(self, k='', v=''):
167 167 self.app_settings_name = k
168 168 self.app_settings_value = v
169 169
170 170 @validates('_app_settings_value')
171 171 def validate_settings_value(self, key, val):
172 172 assert type(val) == unicode
173 173 return val
174 174
175 175 @hybrid_property
176 176 def app_settings_value(self):
177 177 v = self._app_settings_value
178 178 if self.app_settings_name == 'ldap_active':
179 179 v = str2bool(v)
180 180 return v
181 181
182 182 @app_settings_value.setter
183 183 def app_settings_value(self, val):
184 184 """
185 185 Setter that will always make sure we use unicode in app_settings_value
186 186
187 187 :param val:
188 188 """
189 189 self._app_settings_value = safe_unicode(val)
190 190
191 191 def __unicode__(self):
192 192 return u"<%s('%s:%s')>" % (
193 193 self.__class__.__name__,
194 194 self.app_settings_name, self.app_settings_value
195 195 )
196 196
197 197 @classmethod
198 198 def get_by_name(cls, ldap_key):
199 199 return cls.query()\
200 200 .filter(cls.app_settings_name == ldap_key).scalar()
201 201
202 202 @classmethod
203 203 def get_app_settings(cls, cache=False):
204 204
205 205 ret = cls.query()
206 206
207 207 if cache:
208 208 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
209 209
210 210 if not ret:
211 211 raise Exception('Could not get application settings !')
212 212 settings = {}
213 213 for each in ret:
214 214 settings['rhodecode_' + each.app_settings_name] = \
215 215 each.app_settings_value
216 216
217 217 return settings
218 218
219 219 @classmethod
220 220 def get_ldap_settings(cls, cache=False):
221 221 ret = cls.query()\
222 222 .filter(cls.app_settings_name.startswith('ldap_')).all()
223 223 fd = {}
224 224 for row in ret:
225 225 fd.update({row.app_settings_name:row.app_settings_value})
226 226
227 227 return fd
228 228
229 229
230 230 class RhodeCodeUi(Base, BaseModel):
231 231 __tablename__ = 'rhodecode_ui'
232 232 __table_args__ = (
233 233 UniqueConstraint('ui_key'),
234 234 {'extend_existing': True, 'mysql_engine':'InnoDB',
235 235 'mysql_charset': 'utf8'}
236 236 )
237 237
238 238 HOOK_UPDATE = 'changegroup.update'
239 239 HOOK_REPO_SIZE = 'changegroup.repo_size'
240 240 HOOK_PUSH = 'pretxnchangegroup.push_logger'
241 241 HOOK_PULL = 'preoutgoing.pull_logger'
242 242
243 243 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
244 244 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
245 245 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
246 246 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
247 247 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
248 248
249 249 @classmethod
250 250 def get_by_key(cls, key):
251 251 return cls.query().filter(cls.ui_key == key)
252 252
253 253 @classmethod
254 254 def get_builtin_hooks(cls):
255 255 q = cls.query()
256 256 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
257 257 cls.HOOK_REPO_SIZE,
258 258 cls.HOOK_PUSH, cls.HOOK_PULL]))
259 259 return q.all()
260 260
261 261 @classmethod
262 262 def get_custom_hooks(cls):
263 263 q = cls.query()
264 264 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
265 265 cls.HOOK_REPO_SIZE,
266 266 cls.HOOK_PUSH, cls.HOOK_PULL]))
267 267 q = q.filter(cls.ui_section == 'hooks')
268 268 return q.all()
269 269
270 270 @classmethod
271 271 def create_or_update_hook(cls, key, val):
272 272 new_ui = cls.get_by_key(key).scalar() or cls()
273 273 new_ui.ui_section = 'hooks'
274 274 new_ui.ui_active = True
275 275 new_ui.ui_key = key
276 276 new_ui.ui_value = val
277 277
278 278 Session.add(new_ui)
279 279
280 280
281 281 class User(Base, BaseModel):
282 282 __tablename__ = 'users'
283 283 __table_args__ = (
284 284 UniqueConstraint('username'), UniqueConstraint('email'),
285 285 {'extend_existing': True, 'mysql_engine':'InnoDB',
286 286 'mysql_charset': 'utf8'}
287 287 )
288 288 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
289 289 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
290 290 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
291 291 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
292 292 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
293 293 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
294 294 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
295 295 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
296 296 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
297 297 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
298 298 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
299 299
300 300 user_log = relationship('UserLog', cascade='all')
301 301 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
302 302
303 303 repositories = relationship('Repository')
304 304 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
305 305 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
306 306 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
307 307
308 308 group_member = relationship('UsersGroupMember', cascade='all')
309 309
310 310 notifications = relationship('UserNotification', cascade='all')
311 311 # notifications assigned to this user
312 312 user_created_notifications = relationship('Notification', cascade='all')
313 313 # comments created by this user
314 314 user_comments = relationship('ChangesetComment', cascade='all')
315 315
316 316 @hybrid_property
317 317 def email(self):
318 318 return self._email
319 319
320 320 @email.setter
321 321 def email(self, val):
322 322 self._email = val.lower() if val else None
323 323
324 324 @property
325 325 def full_name(self):
326 326 return '%s %s' % (self.name, self.lastname)
327 327
328 328 @property
329 329 def full_name_or_username(self):
330 330 return ('%s %s' % (self.name, self.lastname)
331 331 if (self.name and self.lastname) else self.username)
332 332
333 333 @property
334 334 def full_contact(self):
335 335 return '%s %s <%s>' % (self.name, self.lastname, self.email)
336 336
337 337 @property
338 338 def short_contact(self):
339 339 return '%s %s' % (self.name, self.lastname)
340 340
341 341 @property
342 342 def is_admin(self):
343 343 return self.admin
344 344
345 345 def __unicode__(self):
346 346 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
347 347 self.user_id, self.username)
348 348
349 349 @classmethod
350 350 def get_by_username(cls, username, case_insensitive=False, cache=False):
351 351 if case_insensitive:
352 352 q = cls.query().filter(cls.username.ilike(username))
353 353 else:
354 354 q = cls.query().filter(cls.username == username)
355 355
356 356 if cache:
357 357 q = q.options(FromCache(
358 358 "sql_cache_short",
359 359 "get_user_%s" % _hash_key(username)
360 360 )
361 361 )
362 362 return q.scalar()
363 363
364 364 @classmethod
365 365 def get_by_api_key(cls, api_key, cache=False):
366 366 q = cls.query().filter(cls.api_key == api_key)
367 367
368 368 if cache:
369 369 q = q.options(FromCache("sql_cache_short",
370 370 "get_api_key_%s" % api_key))
371 371 return q.scalar()
372 372
373 373 @classmethod
374 374 def get_by_email(cls, email, case_insensitive=False, cache=False):
375 375 if case_insensitive:
376 376 q = cls.query().filter(cls.email.ilike(email))
377 377 else:
378 378 q = cls.query().filter(cls.email == email)
379 379
380 380 if cache:
381 381 q = q.options(FromCache("sql_cache_short",
382 382 "get_api_key_%s" % email))
383 383 return q.scalar()
384 384
385 385 def update_lastlogin(self):
386 386 """Update user lastlogin"""
387 387 self.last_login = datetime.datetime.now()
388 388 Session.add(self)
389 389 log.debug('updated user %s lastlogin' % self.username)
390 390
391 391 def __json__(self):
392 392 return dict(
393 393 user_id=self.user_id,
394 394 first_name=self.name,
395 395 last_name=self.lastname,
396 396 email=self.email,
397 397 full_name=self.full_name,
398 398 full_name_or_username=self.full_name_or_username,
399 399 short_contact=self.short_contact,
400 400 full_contact=self.full_contact
401 401 )
402 402
403 403
404 404 class UserLog(Base, BaseModel):
405 405 __tablename__ = 'user_logs'
406 406 __table_args__ = (
407 407 {'extend_existing': True, 'mysql_engine':'InnoDB',
408 408 'mysql_charset': 'utf8'},
409 409 )
410 410 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
411 411 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
412 412 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
413 413 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
414 414 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
415 415 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
416 416 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
417 417
418 418 @property
419 419 def action_as_day(self):
420 420 return datetime.date(*self.action_date.timetuple()[:3])
421 421
422 422 user = relationship('User')
423 423 repository = relationship('Repository', cascade='')
424 424
425 425
426 426 class UsersGroup(Base, BaseModel):
427 427 __tablename__ = 'users_groups'
428 428 __table_args__ = (
429 429 {'extend_existing': True, 'mysql_engine':'InnoDB',
430 430 'mysql_charset': 'utf8'},
431 431 )
432 432
433 433 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
434 434 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
435 435 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
436 436
437 437 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
438 438 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
439 439 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
440 440
441 441 def __unicode__(self):
442 442 return u'<userGroup(%s)>' % (self.users_group_name)
443 443
444 444 @classmethod
445 445 def get_by_group_name(cls, group_name, cache=False,
446 446 case_insensitive=False):
447 447 if case_insensitive:
448 448 q = cls.query().filter(cls.users_group_name.ilike(group_name))
449 449 else:
450 450 q = cls.query().filter(cls.users_group_name == group_name)
451 451 if cache:
452 452 q = q.options(FromCache(
453 453 "sql_cache_short",
454 454 "get_user_%s" % _hash_key(group_name)
455 455 )
456 456 )
457 457 return q.scalar()
458 458
459 459 @classmethod
460 460 def get(cls, users_group_id, cache=False):
461 461 users_group = cls.query()
462 462 if cache:
463 463 users_group = users_group.options(FromCache("sql_cache_short",
464 464 "get_users_group_%s" % users_group_id))
465 465 return users_group.get(users_group_id)
466 466
467 467
468 468 class UsersGroupMember(Base, BaseModel):
469 469 __tablename__ = 'users_groups_members'
470 470 __table_args__ = (
471 471 {'extend_existing': True, 'mysql_engine':'InnoDB',
472 472 'mysql_charset': 'utf8'},
473 473 )
474 474
475 475 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
476 476 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
477 477 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
478 478
479 479 user = relationship('User', lazy='joined')
480 480 users_group = relationship('UsersGroup')
481 481
482 482 def __init__(self, gr_id='', u_id=''):
483 483 self.users_group_id = gr_id
484 484 self.user_id = u_id
485 485
486 486
487 487 class Repository(Base, BaseModel):
488 488 __tablename__ = 'repositories'
489 489 __table_args__ = (
490 490 UniqueConstraint('repo_name'),
491 491 {'extend_existing': True, 'mysql_engine':'InnoDB',
492 492 'mysql_charset': 'utf8'},
493 493 )
494 494
495 495 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
496 496 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
497 497 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
498 498 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
499 499 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
500 500 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
501 501 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
502 502 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
503 503 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
504 504 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
505 505
506 506 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
507 507 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
508 508
509 509 user = relationship('User')
510 510 fork = relationship('Repository', remote_side=repo_id)
511 511 group = relationship('RepoGroup')
512 512 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
513 513 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
514 514 stats = relationship('Statistics', cascade='all', uselist=False)
515 515
516 516 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
517 517
518 518 logs = relationship('UserLog')
519 519
520 520 def __unicode__(self):
521 521 return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id,
522 522 self.repo_name)
523 523
524 524 @classmethod
525 525 def url_sep(cls):
526 526 return '/'
527 527
528 528 @classmethod
529 529 def get_by_repo_name(cls, repo_name):
530 530 q = Session.query(cls).filter(cls.repo_name == repo_name)
531 531 q = q.options(joinedload(Repository.fork))\
532 532 .options(joinedload(Repository.user))\
533 533 .options(joinedload(Repository.group))
534 534 return q.scalar()
535 535
536 536 @classmethod
537 537 def get_repo_forks(cls, repo_id):
538 538 return cls.query().filter(Repository.fork_id == repo_id)
539 539
540 540 @classmethod
541 541 def base_path(cls):
542 542 """
543 543 Returns base path when all repos are stored
544 544
545 545 :param cls:
546 546 """
547 547 q = Session.query(RhodeCodeUi)\
548 548 .filter(RhodeCodeUi.ui_key == cls.url_sep())
549 549 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
550 550 return q.one().ui_value
551 551
552 552 @property
553 553 def just_name(self):
554 554 return self.repo_name.split(Repository.url_sep())[-1]
555 555
556 556 @property
557 557 def groups_with_parents(self):
558 558 groups = []
559 559 if self.group is None:
560 560 return groups
561 561
562 562 cur_gr = self.group
563 563 groups.insert(0, cur_gr)
564 564 while 1:
565 565 gr = getattr(cur_gr, 'parent_group', None)
566 566 cur_gr = cur_gr.parent_group
567 567 if gr is None:
568 568 break
569 569 groups.insert(0, gr)
570 570
571 571 return groups
572 572
573 573 @property
574 574 def groups_and_repo(self):
575 575 return self.groups_with_parents, self.just_name
576 576
577 577 @LazyProperty
578 578 def repo_path(self):
579 579 """
580 580 Returns base full path for that repository means where it actually
581 581 exists on a filesystem
582 582 """
583 583 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
584 584 Repository.url_sep())
585 585 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
586 586 return q.one().ui_value
587 587
588 588 @property
589 589 def repo_full_path(self):
590 590 p = [self.repo_path]
591 591 # we need to split the name by / since this is how we store the
592 592 # names in the database, but that eventually needs to be converted
593 593 # into a valid system path
594 594 p += self.repo_name.split(Repository.url_sep())
595 595 return os.path.join(*p)
596 596
597 597 def get_new_name(self, repo_name):
598 598 """
599 599 returns new full repository name based on assigned group and new new
600 600
601 601 :param group_name:
602 602 """
603 603 path_prefix = self.group.full_path_splitted if self.group else []
604 604 return Repository.url_sep().join(path_prefix + [repo_name])
605 605
606 606 @property
607 607 def _ui(self):
608 608 """
609 609 Creates an db based ui object for this repository
610 610 """
611 611 from mercurial import ui
612 612 from mercurial import config
613 613 baseui = ui.ui()
614 614
615 615 #clean the baseui object
616 616 baseui._ocfg = config.config()
617 617 baseui._ucfg = config.config()
618 618 baseui._tcfg = config.config()
619 619
620 620 ret = RhodeCodeUi.query()\
621 621 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
622 622
623 623 hg_ui = ret
624 624 for ui_ in hg_ui:
625 625 if ui_.ui_active:
626 626 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
627 627 ui_.ui_key, ui_.ui_value)
628 628 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
629 629
630 630 return baseui
631 631
632 632 @classmethod
633 633 def is_valid(cls, repo_name):
634 634 """
635 635 returns True if given repo name is a valid filesystem repository
636 636
637 637 :param cls:
638 638 :param repo_name:
639 639 """
640 640 from rhodecode.lib.utils import is_valid_repo
641 641
642 642 return is_valid_repo(repo_name, cls.base_path())
643 643
644 644 #==========================================================================
645 645 # SCM PROPERTIES
646 646 #==========================================================================
647 647
648 648 def get_changeset(self, rev):
649 649 return get_changeset_safe(self.scm_instance, rev)
650 650
651 651 @property
652 652 def tip(self):
653 653 return self.get_changeset('tip')
654 654
655 655 @property
656 656 def author(self):
657 657 return self.tip.author
658 658
659 659 @property
660 660 def last_change(self):
661 661 return self.scm_instance.last_change
662 662
663 663 def comments(self, revisions=None):
664 664 """
665 665 Returns comments for this repository grouped by revisions
666 666
667 667 :param revisions: filter query by revisions only
668 668 """
669 669 cmts = ChangesetComment.query()\
670 670 .filter(ChangesetComment.repo == self)
671 671 if revisions:
672 672 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
673 673 grouped = defaultdict(list)
674 674 for cmt in cmts.all():
675 675 grouped[cmt.revision].append(cmt)
676 676 return grouped
677 677
678 678 #==========================================================================
679 679 # SCM CACHE INSTANCE
680 680 #==========================================================================
681 681
682 682 @property
683 683 def invalidate(self):
684 684 return CacheInvalidation.invalidate(self.repo_name)
685 685
686 686 def set_invalidate(self):
687 687 """
688 688 set a cache for invalidation for this instance
689 689 """
690 690 CacheInvalidation.set_invalidate(self.repo_name)
691 691
692 692 @LazyProperty
693 693 def scm_instance(self):
694 694 return self.__get_instance()
695 695
696 696 @property
697 697 def scm_instance_cached(self):
698 698 @cache_region('long_term')
699 699 def _c(repo_name):
700 700 return self.__get_instance()
701 701 rn = self.repo_name
702 702 log.debug('Getting cached instance of repo')
703 703 inv = self.invalidate
704 704 if inv is not None:
705 705 region_invalidate(_c, None, rn)
706 706 # update our cache
707 707 CacheInvalidation.set_valid(inv.cache_key)
708 708 return _c(rn)
709 709
710 710 def __get_instance(self):
711 711 repo_full_path = self.repo_full_path
712 712 try:
713 713 alias = get_scm(repo_full_path)[0]
714 714 log.debug('Creating instance of %s repository' % alias)
715 715 backend = get_backend(alias)
716 716 except VCSError:
717 717 log.error(traceback.format_exc())
718 718 log.error('Perhaps this repository is in db and not in '
719 719 'filesystem run rescan repositories with '
720 720 '"destroy old data " option from admin panel')
721 721 return
722 722
723 723 if alias == 'hg':
724 724
725 725 repo = backend(safe_str(repo_full_path), create=False,
726 726 baseui=self._ui)
727 727 # skip hidden web repository
728 728 if repo._get_hidden():
729 729 return
730 730 else:
731 731 repo = backend(repo_full_path, create=False)
732 732
733 733 return repo
734 734
735 735
736 736 class RepoGroup(Base, BaseModel):
737 737 __tablename__ = 'groups'
738 738 __table_args__ = (
739 739 UniqueConstraint('group_name', 'group_parent_id'),
740 740 CheckConstraint('group_id != group_parent_id'),
741 741 {'extend_existing': True, 'mysql_engine':'InnoDB',
742 742 'mysql_charset': 'utf8'},
743 743 )
744 744 __mapper_args__ = {'order_by': 'group_name'}
745 745
746 746 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
747 747 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
748 748 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
749 749 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
750 750
751 751 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
752 752 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
753 753
754 754 parent_group = relationship('RepoGroup', remote_side=group_id)
755 755
756 756 def __init__(self, group_name='', parent_group=None):
757 757 self.group_name = group_name
758 758 self.parent_group = parent_group
759 759
760 760 def __unicode__(self):
761 761 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
762 762 self.group_name)
763 763
764 764 @classmethod
765 765 def groups_choices(cls):
766 766 from webhelpers.html import literal as _literal
767 767 repo_groups = [('', '')]
768 768 sep = ' &raquo; '
769 769 _name = lambda k: _literal(sep.join(k))
770 770
771 771 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
772 772 for x in cls.query().all()])
773 773
774 774 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
775 775 return repo_groups
776 776
777 777 @classmethod
778 778 def url_sep(cls):
779 779 return '/'
780 780
781 781 @classmethod
782 782 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
783 783 if case_insensitive:
784 784 gr = cls.query()\
785 785 .filter(cls.group_name.ilike(group_name))
786 786 else:
787 787 gr = cls.query()\
788 788 .filter(cls.group_name == group_name)
789 789 if cache:
790 790 gr = gr.options(FromCache(
791 791 "sql_cache_short",
792 792 "get_group_%s" % _hash_key(group_name)
793 793 )
794 794 )
795 795 return gr.scalar()
796 796
797 797 @property
798 798 def parents(self):
799 799 parents_recursion_limit = 5
800 800 groups = []
801 801 if self.parent_group is None:
802 802 return groups
803 803 cur_gr = self.parent_group
804 804 groups.insert(0, cur_gr)
805 805 cnt = 0
806 806 while 1:
807 807 cnt += 1
808 808 gr = getattr(cur_gr, 'parent_group', None)
809 809 cur_gr = cur_gr.parent_group
810 810 if gr is None:
811 811 break
812 812 if cnt == parents_recursion_limit:
813 813 # this will prevent accidental infinit loops
814 814 log.error('group nested more than %s' %
815 815 parents_recursion_limit)
816 816 break
817 817
818 818 groups.insert(0, gr)
819 819 return groups
820 820
821 821 @property
822 822 def children(self):
823 823 return RepoGroup.query().filter(RepoGroup.parent_group == self)
824 824
825 825 @property
826 826 def name(self):
827 827 return self.group_name.split(RepoGroup.url_sep())[-1]
828 828
829 829 @property
830 830 def full_path(self):
831 831 return self.group_name
832 832
833 833 @property
834 834 def full_path_splitted(self):
835 835 return self.group_name.split(RepoGroup.url_sep())
836 836
837 837 @property
838 838 def repositories(self):
839 839 return Repository.query()\
840 840 .filter(Repository.group == self)\
841 841 .order_by(Repository.repo_name)
842 842
843 843 @property
844 844 def repositories_recursive_count(self):
845 845 cnt = self.repositories.count()
846 846
847 847 def children_count(group):
848 848 cnt = 0
849 849 for child in group.children:
850 850 cnt += child.repositories.count()
851 851 cnt += children_count(child)
852 852 return cnt
853 853
854 854 return cnt + children_count(self)
855 855
856 856 def get_new_name(self, group_name):
857 857 """
858 858 returns new full group name based on parent and new name
859 859
860 860 :param group_name:
861 861 """
862 862 path_prefix = (self.parent_group.full_path_splitted if
863 863 self.parent_group else [])
864 864 return RepoGroup.url_sep().join(path_prefix + [group_name])
865 865
866 866
867 867 class Permission(Base, BaseModel):
868 868 __tablename__ = 'permissions'
869 869 __table_args__ = (
870 870 {'extend_existing': True, 'mysql_engine':'InnoDB',
871 871 'mysql_charset': 'utf8'},
872 872 )
873 873 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
874 874 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
875 875 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
876 876
877 877 def __unicode__(self):
878 878 return u"<%s('%s:%s')>" % (
879 879 self.__class__.__name__, self.permission_id, self.permission_name
880 880 )
881 881
882 882 @classmethod
883 883 def get_by_key(cls, key):
884 884 return cls.query().filter(cls.permission_name == key).scalar()
885 885
886 886 @classmethod
887 887 def get_default_perms(cls, default_user_id):
888 888 q = Session.query(UserRepoToPerm, Repository, cls)\
889 889 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
890 890 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
891 891 .filter(UserRepoToPerm.user_id == default_user_id)
892 892
893 893 return q.all()
894 894
895 895 @classmethod
896 896 def get_default_group_perms(cls, default_user_id):
897 897 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
898 898 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
899 899 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
900 900 .filter(UserRepoGroupToPerm.user_id == default_user_id)
901 901
902 902 return q.all()
903 903
904 904
905 905 class UserRepoToPerm(Base, BaseModel):
906 906 __tablename__ = 'repo_to_perm'
907 907 __table_args__ = (
908 908 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
909 909 {'extend_existing': True, 'mysql_engine':'InnoDB',
910 910 'mysql_charset': 'utf8'}
911 911 )
912 912 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
913 913 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
914 914 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
915 915 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
916 916
917 917 user = relationship('User')
918 918 repository = relationship('Repository')
919 919 permission = relationship('Permission')
920 920
921 921 @classmethod
922 922 def create(cls, user, repository, permission):
923 923 n = cls()
924 924 n.user = user
925 925 n.repository = repository
926 926 n.permission = permission
927 927 Session.add(n)
928 928 return n
929 929
930 930 def __unicode__(self):
931 931 return u'<user:%s => %s >' % (self.user, self.repository)
932 932
933 933
934 934 class UserToPerm(Base, BaseModel):
935 935 __tablename__ = 'user_to_perm'
936 936 __table_args__ = (
937 937 UniqueConstraint('user_id', 'permission_id'),
938 938 {'extend_existing': True, 'mysql_engine':'InnoDB',
939 939 'mysql_charset': 'utf8'}
940 940 )
941 941 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
942 942 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
943 943 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
944 944
945 945 user = relationship('User')
946 946 permission = relationship('Permission', lazy='joined')
947 947
948 948
949 949 class UsersGroupRepoToPerm(Base, BaseModel):
950 950 __tablename__ = 'users_group_repo_to_perm'
951 951 __table_args__ = (
952 952 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
953 953 {'extend_existing': True, 'mysql_engine':'InnoDB',
954 954 'mysql_charset': 'utf8'}
955 955 )
956 956 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
957 957 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
958 958 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
959 959 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
960 960
961 961 users_group = relationship('UsersGroup')
962 962 permission = relationship('Permission')
963 963 repository = relationship('Repository')
964 964
965 965 @classmethod
966 966 def create(cls, users_group, repository, permission):
967 967 n = cls()
968 968 n.users_group = users_group
969 969 n.repository = repository
970 970 n.permission = permission
971 971 Session.add(n)
972 972 return n
973 973
974 974 def __unicode__(self):
975 975 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
976 976
977 977
978 978 class UsersGroupToPerm(Base, BaseModel):
979 979 __tablename__ = 'users_group_to_perm'
980 980 __table_args__ = (
981 981 UniqueConstraint('users_group_id', 'permission_id',),
982 982 {'extend_existing': True, 'mysql_engine':'InnoDB',
983 983 'mysql_charset': 'utf8'}
984 984 )
985 985 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
986 986 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
987 987 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
988 988
989 989 users_group = relationship('UsersGroup')
990 990 permission = relationship('Permission')
991 991
992 992
993 993 class UserRepoGroupToPerm(Base, BaseModel):
994 994 __tablename__ = 'user_repo_group_to_perm'
995 995 __table_args__ = (
996 996 UniqueConstraint('user_id', 'group_id', 'permission_id'),
997 997 {'extend_existing': True, 'mysql_engine':'InnoDB',
998 998 'mysql_charset': 'utf8'}
999 999 )
1000 1000
1001 1001 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1002 1002 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1003 1003 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1004 1004 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1005 1005
1006 1006 user = relationship('User')
1007 1007 group = relationship('RepoGroup')
1008 1008 permission = relationship('Permission')
1009 1009
1010 1010
1011 1011 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1012 1012 __tablename__ = 'users_group_repo_group_to_perm'
1013 1013 __table_args__ = (
1014 1014 UniqueConstraint('users_group_id', 'group_id'),
1015 1015 {'extend_existing': True, 'mysql_engine':'InnoDB',
1016 1016 'mysql_charset': 'utf8'}
1017 1017 )
1018 1018
1019 1019 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1020 1020 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1021 1021 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1022 1022 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1023 1023
1024 1024 users_group = relationship('UsersGroup')
1025 1025 permission = relationship('Permission')
1026 1026 group = relationship('RepoGroup')
1027 1027
1028 1028
1029 1029 class Statistics(Base, BaseModel):
1030 1030 __tablename__ = 'statistics'
1031 1031 __table_args__ = (
1032 1032 UniqueConstraint('repository_id'),
1033 1033 {'extend_existing': True, 'mysql_engine':'InnoDB',
1034 1034 'mysql_charset': 'utf8'}
1035 1035 )
1036 1036 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1037 1037 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1038 1038 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1039 1039 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1040 1040 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1041 1041 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1042 1042
1043 1043 repository = relationship('Repository', single_parent=True)
1044 1044
1045 1045
1046 1046 class UserFollowing(Base, BaseModel):
1047 1047 __tablename__ = 'user_followings'
1048 1048 __table_args__ = (
1049 1049 UniqueConstraint('user_id', 'follows_repository_id'),
1050 1050 UniqueConstraint('user_id', 'follows_user_id'),
1051 1051 {'extend_existing': True, 'mysql_engine':'InnoDB',
1052 1052 'mysql_charset': 'utf8'}
1053 1053 )
1054 1054
1055 1055 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1056 1056 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1057 1057 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1058 1058 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1059 1059 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1060 1060
1061 1061 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1062 1062
1063 1063 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1064 1064 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1065 1065
1066 1066 @classmethod
1067 1067 def get_repo_followers(cls, repo_id):
1068 1068 return cls.query().filter(cls.follows_repo_id == repo_id)
1069 1069
1070 1070
1071 1071 class CacheInvalidation(Base, BaseModel):
1072 1072 __tablename__ = 'cache_invalidation'
1073 1073 __table_args__ = (
1074 1074 UniqueConstraint('cache_key'),
1075 1075 {'extend_existing': True, 'mysql_engine':'InnoDB',
1076 1076 'mysql_charset': 'utf8'},
1077 1077 )
1078 1078 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1079 1079 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1080 1080 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1081 1081 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1082 1082
1083 1083 def __init__(self, cache_key, cache_args=''):
1084 1084 self.cache_key = cache_key
1085 1085 self.cache_args = cache_args
1086 1086 self.cache_active = False
1087 1087
1088 1088 def __unicode__(self):
1089 1089 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1090 1090 self.cache_id, self.cache_key)
1091 1091 @classmethod
1092 1092 def clear_cache(cls):
1093 1093 cls.query().delete()
1094 1094
1095 1095 @classmethod
1096 1096 def _get_key(cls, key):
1097 1097 """
1098 1098 Wrapper for generating a key, together with a prefix
1099 1099
1100 1100 :param key:
1101 1101 """
1102 1102 import rhodecode
1103 1103 prefix = ''
1104 1104 iid = rhodecode.CONFIG.get('instance_id')
1105 1105 if iid:
1106 1106 prefix = iid
1107 1107 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1108 1108
1109 1109 @classmethod
1110 1110 def get_by_key(cls, key):
1111 1111 return cls.query().filter(cls.cache_key == key).scalar()
1112 1112
1113 1113 @classmethod
1114 1114 def _get_or_create_key(cls, key, prefix, org_key):
1115 1115 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1116 1116 if not inv_obj:
1117 1117 try:
1118 1118 inv_obj = CacheInvalidation(key, org_key)
1119 1119 Session.add(inv_obj)
1120 1120 Session.commit()
1121 1121 except Exception:
1122 1122 log.error(traceback.format_exc())
1123 1123 Session.rollback()
1124 1124 return inv_obj
1125 1125
1126 1126 @classmethod
1127 1127 def invalidate(cls, key):
1128 1128 """
1129 1129 Returns Invalidation object if this given key should be invalidated
1130 1130 None otherwise. `cache_active = False` means that this cache
1131 1131 state is not valid and needs to be invalidated
1132 1132
1133 1133 :param key:
1134 1134 """
1135 1135
1136 1136 key, _prefix, _org_key = cls._get_key(key)
1137 1137 inv = cls._get_or_create_key(key, _prefix, _org_key)
1138 1138
1139 1139 if inv and inv.cache_active is False:
1140 1140 return inv
1141 1141
1142 1142 @classmethod
1143 1143 def set_invalidate(cls, key):
1144 1144 """
1145 1145 Mark this Cache key for invalidation
1146 1146
1147 1147 :param key:
1148 1148 """
1149 1149
1150 1150 key, _prefix, _org_key = cls._get_key(key)
1151 1151 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1152 1152 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1153 1153 _org_key))
1154 1154 try:
1155 1155 for inv_obj in inv_objs:
1156 1156 if inv_obj:
1157 1157 inv_obj.cache_active = False
1158 1158
1159 1159 Session.add(inv_obj)
1160 1160 Session.commit()
1161 1161 except Exception:
1162 1162 log.error(traceback.format_exc())
1163 1163 Session.rollback()
1164 1164
1165 1165 @classmethod
1166 1166 def set_valid(cls, key):
1167 1167 """
1168 1168 Mark this cache key as active and currently cached
1169 1169
1170 1170 :param key:
1171 1171 """
1172 1172 inv_obj = cls.get_by_key(key)
1173 1173 inv_obj.cache_active = True
1174 1174 Session.add(inv_obj)
1175 1175 Session.commit()
1176 1176
1177 1177
1178 1178 class ChangesetComment(Base, BaseModel):
1179 1179 __tablename__ = 'changeset_comments'
1180 1180 __table_args__ = (
1181 1181 {'extend_existing': True, 'mysql_engine':'InnoDB',
1182 1182 'mysql_charset': 'utf8'},
1183 1183 )
1184 1184 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1185 1185 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1186 1186 revision = Column('revision', String(40), nullable=False)
1187 1187 line_no = Column('line_no', Unicode(10), nullable=True)
1188 1188 f_path = Column('f_path', Unicode(1000), nullable=True)
1189 1189 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1190 1190 text = Column('text', Unicode(25000), nullable=False)
1191 1191 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1192 1192
1193 1193 author = relationship('User', lazy='joined')
1194 1194 repo = relationship('Repository')
1195 1195
1196 1196 @classmethod
1197 1197 def get_users(cls, revision):
1198 1198 """
1199 1199 Returns user associated with this changesetComment. ie those
1200 1200 who actually commented
1201 1201
1202 1202 :param cls:
1203 1203 :param revision:
1204 1204 """
1205 1205 return Session.query(User)\
1206 1206 .filter(cls.revision == revision)\
1207 1207 .join(ChangesetComment.author).all()
1208 1208
1209 1209
1210 1210 class Notification(Base, BaseModel):
1211 1211 __tablename__ = 'notifications'
1212 1212 __table_args__ = (
1213 1213 {'extend_existing': True, 'mysql_engine':'InnoDB',
1214 1214 'mysql_charset': 'utf8'},
1215 1215 )
1216 1216
1217 1217 TYPE_CHANGESET_COMMENT = u'cs_comment'
1218 1218 TYPE_MESSAGE = u'message'
1219 1219 TYPE_MENTION = u'mention'
1220 1220 TYPE_REGISTRATION = u'registration'
1221 1221
1222 1222 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1223 1223 subject = Column('subject', Unicode(512), nullable=True)
1224 1224 body = Column('body', Unicode(50000), nullable=True)
1225 1225 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1226 1226 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1227 1227 type_ = Column('type', Unicode(256))
1228 1228
1229 1229 created_by_user = relationship('User')
1230 1230 notifications_to_users = relationship('UserNotification', lazy='joined',
1231 1231 cascade="all, delete, delete-orphan")
1232 1232
1233 1233 @property
1234 1234 def recipients(self):
1235 1235 return [x.user for x in UserNotification.query()\
1236 1236 .filter(UserNotification.notification == self).all()]
1237 1237
1238 1238 @classmethod
1239 1239 def create(cls, created_by, subject, body, recipients, type_=None):
1240 1240 if type_ is None:
1241 1241 type_ = Notification.TYPE_MESSAGE
1242 1242
1243 1243 notification = cls()
1244 1244 notification.created_by_user = created_by
1245 1245 notification.subject = subject
1246 1246 notification.body = body
1247 1247 notification.type_ = type_
1248 1248 notification.created_on = datetime.datetime.now()
1249 1249
1250 1250 for u in recipients:
1251 1251 assoc = UserNotification()
1252 1252 assoc.notification = notification
1253 1253 u.notifications.append(assoc)
1254 1254 Session.add(notification)
1255 1255 return notification
1256 1256
1257 1257 @property
1258 1258 def description(self):
1259 1259 from rhodecode.model.notification import NotificationModel
1260 1260 return NotificationModel().make_description(self)
1261 1261
1262 1262
1263 1263 class UserNotification(Base, BaseModel):
1264 1264 __tablename__ = 'user_to_notification'
1265 1265 __table_args__ = (
1266 1266 UniqueConstraint('user_id', 'notification_id'),
1267 1267 {'extend_existing': True, 'mysql_engine':'InnoDB',
1268 1268 'mysql_charset': 'utf8'}
1269 1269 )
1270 1270 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1271 1271 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1272 1272 read = Column('read', Boolean, default=False)
1273 1273 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1274 1274
1275 1275 user = relationship('User', lazy="joined")
1276 1276 notification = relationship('Notification', lazy="joined",
1277 1277 order_by=lambda: Notification.created_on.desc(),)
1278 1278
1279 1279 def mark_as_read(self):
1280 1280 self.read = True
1281 1281 Session.add(self)
1282 1282
1283 1283
1284 1284 class DbMigrateVersion(Base, BaseModel):
1285 1285 __tablename__ = 'db_migrate_version'
1286 1286 __table_args__ = (
1287 1287 {'extend_existing': True, 'mysql_engine':'InnoDB',
1288 1288 'mysql_charset': 'utf8'},
1289 1289 )
1290 1290 repository_id = Column('repository_id', String(250), primary_key=True)
1291 1291 repository_path = Column('repository_path', Text)
1292 1292 version = Column('version', Integer)
1293 1293
1294 1294 ## this is migration from 1_4_0, but now it's here to overcome a problem of
1295 1295 ## attaching a FK to this from 1_3_0 !
1296 1296
1297 1297
1298 1298 class PullRequest(Base, BaseModel):
1299 1299 __tablename__ = 'pull_requests'
1300 1300 __table_args__ = (
1301 1301 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1302 1302 'mysql_charset': 'utf8'},
1303 1303 )
1304 1304
1305 1305 STATUS_NEW = u'new'
1306 1306 STATUS_OPEN = u'open'
1307 1307 STATUS_CLOSED = u'closed'
1308 1308
1309 1309 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1310 1310 title = Column('title', Unicode(256), nullable=True)
1311 1311 description = Column('description', UnicodeText(10240), nullable=True)
1312 1312 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1313 1313 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1314 1314 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1315 1315 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1316 1316 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1317 1317 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1318 1318 org_ref = Column('org_ref', Unicode(256), nullable=False)
1319 1319 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1320 other_ref = Column('other_ref', Unicode(256), nullable=False) No newline at end of file
1320 other_ref = Column('other_ref', Unicode(256), nullable=False)
@@ -1,186 +1,186 b''
1 1 import logging
2 2 import datetime
3 3
4 4 from sqlalchemy import *
5 5 from sqlalchemy.exc import DatabaseError
6 6 from sqlalchemy.orm import relation, backref, class_mapper
7 7 from sqlalchemy.orm.session import Session
8 8 from sqlalchemy.ext.declarative import declarative_base
9 9
10 10 from rhodecode.lib.dbmigrate.migrate import *
11 11 from rhodecode.lib.dbmigrate.migrate.changeset import *
12 12
13 13 from rhodecode.model.meta import Base
14 14 from rhodecode.model import meta
15 15
16 16 log = logging.getLogger(__name__)
17 17
18 18
19 19 def upgrade(migrate_engine):
20 20 """
21 21 Upgrade operations go here.
22 22 Don't create your own engine; bind migrate_engine to your metadata
23 23 """
24 24
25 25 #==========================================================================
26 26 # USEREMAILMAP
27 27 #==========================================================================
28 28 from rhodecode.lib.dbmigrate.schema.db_1_4_0 import UserEmailMap
29 29 tbl = UserEmailMap.__table__
30 30 tbl.create()
31 31 #==========================================================================
32 32 # PULL REQUEST
33 33 #==========================================================================
34 34 from rhodecode.lib.dbmigrate.schema.db_1_4_0 import PullRequest
35 35 tbl = PullRequest.__table__
36 36 tbl.create()
37 37
38 38 #==========================================================================
39 39 # PULL REQUEST REVIEWERS
40 40 #==========================================================================
41 41 from rhodecode.lib.dbmigrate.schema.db_1_4_0 import PullRequestReviewers
42 42 tbl = PullRequestReviewers.__table__
43 43 tbl.create()
44 44
45 45 #==========================================================================
46 46 # CHANGESET STATUS
47 47 #==========================================================================
48 48 from rhodecode.lib.dbmigrate.schema.db_1_4_0 import ChangesetStatus
49 49 tbl = ChangesetStatus.__table__
50 50 tbl.create()
51 51
52 ## RESET COMPLETLY THE metadata for sqlalchemy to use the 1_3_0 Base
52 ## RESET COMPLETLY THE metadata for sqlalchemy to use the 1_3_0 Base
53 53 Base = declarative_base()
54 54 Base.metadata.clear()
55 55 Base.metadata = MetaData()
56 56 Base.metadata.bind = migrate_engine
57 57 meta.Base = Base
58 58
59 59 #==========================================================================
60 60 # USERS TABLE
61 61 #==========================================================================
62 62 from rhodecode.lib.dbmigrate.schema.db_1_3_0 import User
63 63 tbl = User.__table__
64 64
65 65 # change column name -> firstname
66 66 col = User.__table__.columns.name
67 67 col.alter(index=Index('u_username_idx', 'username'))
68 68 col.alter(index=Index('u_email_idx', 'email'))
69 69 col.alter(name="firstname", table=tbl)
70 70
71 71 # add inherit_default_permission column
72 72 inherit_default_permissions = Column("inherit_default_permissions",
73 73 Boolean(), nullable=True, unique=None,
74 74 default=True)
75 75 inherit_default_permissions.create(table=tbl)
76 76 inherit_default_permissions.alter(nullable=False, default=True, table=tbl)
77 77
78 78 #==========================================================================
79 79 # USERS GROUP TABLE
80 80 #==========================================================================
81 81 from rhodecode.lib.dbmigrate.schema.db_1_3_0 import UsersGroup
82 82 tbl = UsersGroup.__table__
83 83 # add inherit_default_permission column
84 84 gr_inherit_default_permissions = Column(
85 85 "users_group_inherit_default_permissions",
86 86 Boolean(), nullable=True, unique=None,
87 87 default=True)
88 88 gr_inherit_default_permissions.create(table=tbl)
89 89 gr_inherit_default_permissions.alter(nullable=False, default=True, table=tbl)
90 90
91 91 #==========================================================================
92 92 # REPOSITORIES
93 93 #==========================================================================
94 94 from rhodecode.lib.dbmigrate.schema.db_1_3_0 import Repository
95 95 tbl = Repository.__table__
96 96
97 97 # add enable locking column
98 98 enable_locking = Column("enable_locking", Boolean(), nullable=True,
99 99 unique=None, default=False)
100 100 enable_locking.create(table=tbl)
101 101 enable_locking.alter(nullable=False, default=False, table=tbl)
102 102
103 103 # add locked column
104 104 _locked = Column("locked", String(255), nullable=True, unique=False,
105 105 default=None)
106 106 _locked.create(table=tbl)
107 107
108 108 #add langing revision column
109 109 landing_rev = Column("landing_revision", String(255), nullable=True,
110 110 unique=False, default='tip')
111 111 landing_rev.create(table=tbl)
112 112 landing_rev.alter(nullable=False, default='tip', table=tbl)
113 113
114 114 #==========================================================================
115 115 # GROUPS
116 116 #==========================================================================
117 117 from rhodecode.lib.dbmigrate.schema.db_1_3_0 import RepoGroup
118 118 tbl = RepoGroup.__table__
119 119
120 120 # add enable locking column
121 121 enable_locking = Column("enable_locking", Boolean(), nullable=True,
122 122 unique=None, default=False)
123 123 enable_locking.create(table=tbl)
124 124 enable_locking.alter(nullable=False, default=False)
125 125
126 126 #==========================================================================
127 127 # CACHE INVALIDATION
128 128 #==========================================================================
129 129 from rhodecode.lib.dbmigrate.schema.db_1_3_0 import CacheInvalidation
130 130 tbl = CacheInvalidation.__table__
131 131
132 132 # add INDEX for cache keys
133 133 col = CacheInvalidation.__table__.columns.cache_key
134 134 col.alter(index=Index('key_idx', 'cache_key'))
135 135
136 136 #==========================================================================
137 137 # NOTIFICATION
138 138 #==========================================================================
139 139 from rhodecode.lib.dbmigrate.schema.db_1_3_0 import Notification
140 140 tbl = Notification.__table__
141 141
142 142 # add index for notification type
143 143 col = Notification.__table__.columns.type
144 144 col.alter(index=Index('notification_type_idx', 'type'),)
145 145
146 146 #==========================================================================
147 147 # CHANGESET_COMMENTS
148 148 #==========================================================================
149 149 from rhodecode.lib.dbmigrate.schema.db_1_3_0 import ChangesetComment
150 150
151 151 tbl = ChangesetComment.__table__
152 152 col = ChangesetComment.__table__.columns.revision
153 153
154 154 # add index for revisions
155 155 col.alter(index=Index('cc_revision_idx', 'revision'),)
156 156
157 157 # add hl_lines column
158 158 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
159 159 hl_lines.create(table=tbl)
160 160
161 161 # add created_on column
162 162 created_on = Column('created_on', DateTime(timezone=False), nullable=True,
163 163 default=datetime.datetime.now)
164 164 created_on.create(table=tbl)
165 165 created_on.alter(nullable=False, default=datetime.datetime.now)
166 166
167 167 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False,
168 168 default=datetime.datetime.now)
169 169 modified_at.alter(type=DateTime(timezone=False), table=tbl)
170 170
171 171 # add FK to pull_request
172 172 pull_request_id = Column("pull_request_id", Integer(),
173 173 ForeignKey('pull_requests.pull_request_id'),
174 174 nullable=True)
175 175 pull_request_id.create(table=tbl)
176 176 ## RESET COMPLETLY THE metadata for sqlalchemy back after using 1_3_0
177 177 Base = declarative_base()
178 178 Base.metadata.clear()
179 179 Base.metadata = MetaData()
180 180 Base.metadata.bind = migrate_engine
181 181 meta.Base = Base
182 182
183 183
184 184 def downgrade(migrate_engine):
185 185 meta = MetaData()
186 186 meta.bind = migrate_engine
@@ -1,674 +1,674 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Utilities library for RhodeCode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 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 modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import re
28 28 import logging
29 29 import datetime
30 30 import traceback
31 31 import paste
32 32 import beaker
33 33 import tarfile
34 34 import shutil
35 35 from os.path import abspath
36 36 from os.path import dirname as dn, join as jn
37 37
38 38 from paste.script.command import Command, BadCommand
39 39
40 40 from mercurial import ui, config
41 41
42 42 from webhelpers.text import collapse, remove_formatting, strip_tags
43 43
44 44 from rhodecode.lib.vcs import get_backend
45 45 from rhodecode.lib.vcs.backends.base import BaseChangeset
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47 from rhodecode.lib.vcs.utils.helpers import get_scm
48 48 from rhodecode.lib.vcs.exceptions import VCSError
49 49
50 50 from rhodecode.lib.caching_query import FromCache
51 51
52 52 from rhodecode.model import meta
53 53 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
54 54 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
55 55 from rhodecode.model.meta import Session
56 56 from rhodecode.model.repos_group import ReposGroupModel
57 57 from rhodecode.lib.utils2 import safe_str, safe_unicode
58 58 from rhodecode.lib.vcs.utils.fakemod import create_module
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
63 63
64 64
65 65 def recursive_replace(str_, replace=' '):
66 66 """
67 67 Recursive replace of given sign to just one instance
68 68
69 69 :param str_: given string
70 70 :param replace: char to find and replace multiple instances
71 71
72 72 Examples::
73 73 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
74 74 'Mighty-Mighty-Bo-sstones'
75 75 """
76 76
77 77 if str_.find(replace * 2) == -1:
78 78 return str_
79 79 else:
80 80 str_ = str_.replace(replace * 2, replace)
81 81 return recursive_replace(str_, replace)
82 82
83 83
84 84 def repo_name_slug(value):
85 85 """
86 86 Return slug of name of repository
87 87 This function is called on each creation/modification
88 88 of repository to prevent bad names in repo
89 89 """
90 90
91 91 slug = remove_formatting(value)
92 92 slug = strip_tags(slug)
93 93
94 94 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
95 95 slug = slug.replace(c, '-')
96 96 slug = recursive_replace(slug, '-')
97 97 slug = collapse(slug, '-')
98 98 return slug
99 99
100 100
101 101 def get_repo_slug(request):
102 102 _repo = request.environ['pylons.routes_dict'].get('repo_name')
103 103 if _repo:
104 104 _repo = _repo.rstrip('/')
105 105 return _repo
106 106
107 107
108 108 def get_repos_group_slug(request):
109 109 _group = request.environ['pylons.routes_dict'].get('group_name')
110 110 if _group:
111 111 _group = _group.rstrip('/')
112 112 return _group
113 113
114 114
115 115 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
116 116 """
117 117 Action logger for various actions made by users
118 118
119 119 :param user: user that made this action, can be a unique username string or
120 120 object containing user_id attribute
121 121 :param action: action to log, should be on of predefined unique actions for
122 122 easy translations
123 123 :param repo: string name of repository or object containing repo_id,
124 124 that action was made on
125 125 :param ipaddr: optional ip address from what the action was made
126 126 :param sa: optional sqlalchemy session
127 127
128 128 """
129 129
130 130 if not sa:
131 131 sa = meta.Session()
132 132
133 133 try:
134 134 if hasattr(user, 'user_id'):
135 135 user_obj = user
136 136 elif isinstance(user, basestring):
137 137 user_obj = User.get_by_username(user)
138 138 else:
139 139 raise Exception('You have to provide user object or username')
140 140
141 141 if hasattr(repo, 'repo_id'):
142 142 repo_obj = Repository.get(repo.repo_id)
143 143 repo_name = repo_obj.repo_name
144 144 elif isinstance(repo, basestring):
145 145 repo_name = repo.lstrip('/')
146 146 repo_obj = Repository.get_by_repo_name(repo_name)
147 147 else:
148 148 repo_obj = None
149 149 repo_name = ''
150 150
151 151 user_log = UserLog()
152 152 user_log.user_id = user_obj.user_id
153 153 user_log.action = safe_unicode(action)
154 154
155 155 user_log.repository = repo_obj
156 156 user_log.repository_name = repo_name
157 157
158 158 user_log.action_date = datetime.datetime.now()
159 159 user_log.user_ip = ipaddr
160 160 sa.add(user_log)
161 161
162 162 log.info(
163 163 'Adding user %s, action %s on %s' % (user_obj, action,
164 164 safe_unicode(repo))
165 165 )
166 166 if commit:
167 167 sa.commit()
168 168 except:
169 169 log.error(traceback.format_exc())
170 170 raise
171 171
172 172
173 173 def get_repos(path, recursive=False):
174 174 """
175 175 Scans given path for repos and return (name,(type,path)) tuple
176 176
177 177 :param path: path to scan for repositories
178 178 :param recursive: recursive search and return names with subdirs in front
179 179 """
180 180
181 181 # remove ending slash for better results
182 182 path = path.rstrip(os.sep)
183 183
184 184 def _get_repos(p):
185 185 if not os.access(p, os.W_OK):
186 186 return
187 187 for dirpath in os.listdir(p):
188 188 if os.path.isfile(os.path.join(p, dirpath)):
189 189 continue
190 190 cur_path = os.path.join(p, dirpath)
191 191 try:
192 192 scm_info = get_scm(cur_path)
193 193 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
194 194 except VCSError:
195 195 if not recursive:
196 196 continue
197 197 #check if this dir containts other repos for recursive scan
198 198 rec_path = os.path.join(p, dirpath)
199 199 if os.path.isdir(rec_path):
200 200 for inner_scm in _get_repos(rec_path):
201 201 yield inner_scm
202 202
203 203 return _get_repos(path)
204 204
205 205
206 206 def is_valid_repo(repo_name, base_path, scm=None):
207 207 """
208 208 Returns True if given path is a valid repository False otherwise.
209 If scm param is given also compare if given scm is the same as expected
209 If scm param is given also compare if given scm is the same as expected
210 210 from scm parameter
211 211
212 212 :param repo_name:
213 213 :param base_path:
214 214 :param scm:
215 215
216 216 :return True: if given path is a valid repository
217 217 """
218 218 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
219 219
220 220 try:
221 221 scm_ = get_scm(full_path)
222 222 if scm:
223 223 return scm_[0] == scm
224 224 return True
225 225 except VCSError:
226 226 return False
227 227
228 228
229 229 def is_valid_repos_group(repos_group_name, base_path):
230 230 """
231 231 Returns True if given path is a repos group False otherwise
232 232
233 233 :param repo_name:
234 234 :param base_path:
235 235 """
236 236 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
237 237
238 238 # check if it's not a repo
239 239 if is_valid_repo(repos_group_name, base_path):
240 240 return False
241 241
242 242 try:
243 243 # we need to check bare git repos at higher level
244 244 # since we might match branches/hooks/info/objects or possible
245 245 # other things inside bare git repo
246 246 get_scm(os.path.dirname(full_path))
247 247 return False
248 248 except VCSError:
249 249 pass
250 250
251 251 # check if it's a valid path
252 252 if os.path.isdir(full_path):
253 253 return True
254 254
255 255 return False
256 256
257 257
258 258 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
259 259 while True:
260 260 ok = raw_input(prompt)
261 261 if ok in ('y', 'ye', 'yes'):
262 262 return True
263 263 if ok in ('n', 'no', 'nop', 'nope'):
264 264 return False
265 265 retries = retries - 1
266 266 if retries < 0:
267 267 raise IOError
268 268 print complaint
269 269
270 270 #propagated from mercurial documentation
271 271 ui_sections = ['alias', 'auth',
272 272 'decode/encode', 'defaults',
273 273 'diff', 'email',
274 274 'extensions', 'format',
275 275 'merge-patterns', 'merge-tools',
276 276 'hooks', 'http_proxy',
277 277 'smtp', 'patch',
278 278 'paths', 'profiling',
279 279 'server', 'trusted',
280 280 'ui', 'web', ]
281 281
282 282
283 283 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
284 284 """
285 285 A function that will read python rc files or database
286 286 and make an mercurial ui object from read options
287 287
288 288 :param path: path to mercurial config file
289 289 :param checkpaths: check the path
290 290 :param read_from: read from 'file' or 'db'
291 291 """
292 292
293 293 baseui = ui.ui()
294 294
295 295 # clean the baseui object
296 296 baseui._ocfg = config.config()
297 297 baseui._ucfg = config.config()
298 298 baseui._tcfg = config.config()
299 299
300 300 if read_from == 'file':
301 301 if not os.path.isfile(path):
302 302 log.debug('hgrc file is not present at %s skipping...' % path)
303 303 return False
304 304 log.debug('reading hgrc from %s' % path)
305 305 cfg = config.config()
306 306 cfg.read(path)
307 307 for section in ui_sections:
308 308 for k, v in cfg.items(section):
309 309 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
310 310 baseui.setconfig(section, k, v)
311 311
312 312 elif read_from == 'db':
313 313 sa = meta.Session()
314 314 ret = sa.query(RhodeCodeUi)\
315 315 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
316 316 .all()
317 317
318 318 hg_ui = ret
319 319 for ui_ in hg_ui:
320 320 if ui_.ui_active:
321 321 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
322 322 ui_.ui_key, ui_.ui_value)
323 323 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
324 324 if ui_.ui_key == 'push_ssl':
325 325 # force set push_ssl requirement to False, rhodecode
326 326 # handles that
327 327 baseui.setconfig(ui_.ui_section, ui_.ui_key, False)
328 328 if clear_session:
329 329 meta.Session.remove()
330 330 return baseui
331 331
332 332
333 333 def set_rhodecode_config(config):
334 334 """
335 335 Updates pylons config with new settings from database
336 336
337 337 :param config:
338 338 """
339 339 hgsettings = RhodeCodeSetting.get_app_settings()
340 340
341 341 for k, v in hgsettings.items():
342 342 config[k] = v
343 343
344 344
345 345 def invalidate_cache(cache_key, *args):
346 346 """
347 347 Puts cache invalidation task into db for
348 348 further global cache invalidation
349 349 """
350 350
351 351 from rhodecode.model.scm import ScmModel
352 352
353 353 if cache_key.startswith('get_repo_cached_'):
354 354 name = cache_key.split('get_repo_cached_')[-1]
355 355 ScmModel().mark_for_invalidation(name)
356 356
357 357
358 358 def map_groups(path):
359 359 """
360 360 Given a full path to a repository, create all nested groups that this
361 361 repo is inside. This function creates parent-child relationships between
362 362 groups and creates default perms for all new groups.
363 363
364 364 :param paths: full path to repository
365 365 """
366 366 sa = meta.Session()
367 367 groups = path.split(Repository.url_sep())
368 368 parent = None
369 369 group = None
370 370
371 371 # last element is repo in nested groups structure
372 372 groups = groups[:-1]
373 373 rgm = ReposGroupModel(sa)
374 374 for lvl, group_name in enumerate(groups):
375 375 group_name = '/'.join(groups[:lvl] + [group_name])
376 376 group = RepoGroup.get_by_group_name(group_name)
377 377 desc = '%s group' % group_name
378 378
379 379 # skip folders that are now removed repos
380 380 if REMOVED_REPO_PAT.match(group_name):
381 381 break
382 382
383 383 if group is None:
384 384 log.debug('creating group level: %s group_name: %s' % (lvl,
385 385 group_name))
386 386 group = RepoGroup(group_name, parent)
387 387 group.group_description = desc
388 388 sa.add(group)
389 389 rgm._create_default_perms(group)
390 390 sa.flush()
391 391 parent = group
392 392 return group
393 393
394 394
395 395 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
396 396 install_git_hook=False):
397 397 """
398 398 maps all repos given in initial_repo_list, non existing repositories
399 399 are created, if remove_obsolete is True it also check for db entries
400 400 that are not in initial_repo_list and removes them.
401 401
402 402 :param initial_repo_list: list of repositories found by scanning methods
403 403 :param remove_obsolete: check for obsolete entries in database
404 404 :param install_git_hook: if this is True, also check and install githook
405 405 for a repo if missing
406 406 """
407 407 from rhodecode.model.repo import RepoModel
408 408 from rhodecode.model.scm import ScmModel
409 409 sa = meta.Session()
410 410 rm = RepoModel()
411 411 user = sa.query(User).filter(User.admin == True).first()
412 412 if user is None:
413 413 raise Exception('Missing administrative account !')
414 414 added = []
415 415
416 416 # # clear cache keys
417 417 # log.debug("Clearing cache keys now...")
418 418 # CacheInvalidation.clear_cache()
419 419 # sa.commit()
420 420
421 421 for name, repo in initial_repo_list.items():
422 422 group = map_groups(name)
423 423 db_repo = rm.get_by_repo_name(name)
424 424 # found repo that is on filesystem not in RhodeCode database
425 425 if not db_repo:
426 426 log.info('repository %s not found creating now' % name)
427 427 added.append(name)
428 428 desc = (repo.description
429 429 if repo.description != 'unknown'
430 430 else '%s repository' % name)
431 431 new_repo = rm.create_repo(
432 432 repo_name=name,
433 433 repo_type=repo.alias,
434 434 description=desc,
435 435 repos_group=getattr(group, 'group_id', None),
436 436 owner=user,
437 437 just_db=True
438 438 )
439 439 # we added that repo just now, and make sure it has githook
440 440 # installed
441 441 if new_repo.repo_type == 'git':
442 442 ScmModel().install_git_hook(new_repo.scm_instance)
443 443 elif install_git_hook:
444 444 if db_repo.repo_type == 'git':
445 445 ScmModel().install_git_hook(db_repo.scm_instance)
446 446 # during starting install all cache keys for all repositories in the
447 447 # system, this will register all repos and multiple instances
448 448 key, _prefix, _org_key = CacheInvalidation._get_key(name)
449 449 log.debug("Creating cache key for %s instance_id:`%s`" % (name, _prefix))
450 450 CacheInvalidation._get_or_create_key(key, _prefix, _org_key, commit=False)
451 451 sa.commit()
452 452 removed = []
453 453 if remove_obsolete:
454 454 # remove from database those repositories that are not in the filesystem
455 455 for repo in sa.query(Repository).all():
456 456 if repo.repo_name not in initial_repo_list.keys():
457 457 log.debug("Removing non existing repository found in db `%s`" %
458 458 repo.repo_name)
459 459 try:
460 460 sa.delete(repo)
461 461 sa.commit()
462 462 removed.append(repo.repo_name)
463 463 except:
464 464 #don't hold further removals on error
465 465 log.error(traceback.format_exc())
466 466 sa.rollback()
467 467
468 468 return added, removed
469 469
470 470
471 471 # set cache regions for beaker so celery can utilise it
472 472 def add_cache(settings):
473 473 cache_settings = {'regions': None}
474 474 for key in settings.keys():
475 475 for prefix in ['beaker.cache.', 'cache.']:
476 476 if key.startswith(prefix):
477 477 name = key.split(prefix)[1].strip()
478 478 cache_settings[name] = settings[key].strip()
479 479 if cache_settings['regions']:
480 480 for region in cache_settings['regions'].split(','):
481 481 region = region.strip()
482 482 region_settings = {}
483 483 for key, value in cache_settings.items():
484 484 if key.startswith(region):
485 485 region_settings[key.split('.')[1]] = value
486 486 region_settings['expire'] = int(region_settings.get('expire',
487 487 60))
488 488 region_settings.setdefault('lock_dir',
489 489 cache_settings.get('lock_dir'))
490 490 region_settings.setdefault('data_dir',
491 491 cache_settings.get('data_dir'))
492 492
493 493 if 'type' not in region_settings:
494 494 region_settings['type'] = cache_settings.get('type',
495 495 'memory')
496 496 beaker.cache.cache_regions[region] = region_settings
497 497
498 498
499 499 def load_rcextensions(root_path):
500 500 import rhodecode
501 501 from rhodecode.config import conf
502 502
503 503 path = os.path.join(root_path, 'rcextensions', '__init__.py')
504 504 if os.path.isfile(path):
505 505 rcext = create_module('rc', path)
506 506 EXT = rhodecode.EXTENSIONS = rcext
507 507 log.debug('Found rcextensions now loading %s...' % rcext)
508 508
509 509 # Additional mappings that are not present in the pygments lexers
510 510 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
511 511
512 512 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
513 513
514 514 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
515 515 log.debug('settings custom INDEX_EXTENSIONS')
516 516 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
517 517
518 518 #ADDITIONAL MAPPINGS
519 519 log.debug('adding extra into INDEX_EXTENSIONS')
520 520 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
521 521
522 522
523 523 #==============================================================================
524 524 # TEST FUNCTIONS AND CREATORS
525 525 #==============================================================================
526 526 def create_test_index(repo_location, config, full_index):
527 527 """
528 528 Makes default test index
529 529
530 530 :param config: test config
531 531 :param full_index:
532 532 """
533 533
534 534 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
535 535 from rhodecode.lib.pidlock import DaemonLock, LockHeld
536 536
537 537 repo_location = repo_location
538 538
539 539 index_location = os.path.join(config['app_conf']['index_dir'])
540 540 if not os.path.exists(index_location):
541 541 os.makedirs(index_location)
542 542
543 543 try:
544 544 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
545 545 WhooshIndexingDaemon(index_location=index_location,
546 546 repo_location=repo_location)\
547 547 .run(full_index=full_index)
548 548 l.release()
549 549 except LockHeld:
550 550 pass
551 551
552 552
553 553 def create_test_env(repos_test_path, config):
554 554 """
555 555 Makes a fresh database and
556 556 install test repository into tmp dir
557 557 """
558 558 from rhodecode.lib.db_manage import DbManage
559 559 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
560 560
561 561 # PART ONE create db
562 562 dbconf = config['sqlalchemy.db1.url']
563 563 log.debug('making test db %s' % dbconf)
564 564
565 565 # create test dir if it doesn't exist
566 566 if not os.path.isdir(repos_test_path):
567 567 log.debug('Creating testdir %s' % repos_test_path)
568 568 os.makedirs(repos_test_path)
569 569
570 570 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
571 571 tests=True)
572 572 dbmanage.create_tables(override=True)
573 573 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
574 574 dbmanage.create_default_user()
575 575 dbmanage.admin_prompt()
576 576 dbmanage.create_permissions()
577 577 dbmanage.populate_default_permissions()
578 578 Session().commit()
579 579 # PART TWO make test repo
580 580 log.debug('making test vcs repositories')
581 581
582 582 idx_path = config['app_conf']['index_dir']
583 583 data_path = config['app_conf']['cache_dir']
584 584
585 585 #clean index and data
586 586 if idx_path and os.path.exists(idx_path):
587 587 log.debug('remove %s' % idx_path)
588 588 shutil.rmtree(idx_path)
589 589
590 590 if data_path and os.path.exists(data_path):
591 591 log.debug('remove %s' % data_path)
592 592 shutil.rmtree(data_path)
593 593
594 594 #CREATE DEFAULT TEST REPOS
595 595 cur_dir = dn(dn(abspath(__file__)))
596 596 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
597 597 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
598 598 tar.close()
599 599
600 600 cur_dir = dn(dn(abspath(__file__)))
601 601 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
602 602 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
603 603 tar.close()
604 604
605 605 #LOAD VCS test stuff
606 606 from rhodecode.tests.vcs import setup_package
607 607 setup_package()
608 608
609 609
610 610 #==============================================================================
611 611 # PASTER COMMANDS
612 612 #==============================================================================
613 613 class BasePasterCommand(Command):
614 614 """
615 615 Abstract Base Class for paster commands.
616 616
617 617 The celery commands are somewhat aggressive about loading
618 618 celery.conf, and since our module sets the `CELERY_LOADER`
619 619 environment variable to our loader, we have to bootstrap a bit and
620 620 make sure we've had a chance to load the pylons config off of the
621 621 command line, otherwise everything fails.
622 622 """
623 623 min_args = 1
624 624 min_args_error = "Please provide a paster config file as an argument."
625 625 takes_config_file = 1
626 626 requires_config_file = True
627 627
628 628 def notify_msg(self, msg, log=False):
629 629 """Make a notification to user, additionally if logger is passed
630 630 it logs this action using given logger
631 631
632 632 :param msg: message that will be printed to user
633 633 :param log: logging instance, to use to additionally log this message
634 634
635 635 """
636 636 if log and isinstance(log, logging):
637 637 log(msg)
638 638
639 639 def run(self, args):
640 640 """
641 641 Overrides Command.run
642 642
643 643 Checks for a config file argument and loads it.
644 644 """
645 645 if len(args) < self.min_args:
646 646 raise BadCommand(
647 647 self.min_args_error % {'min_args': self.min_args,
648 648 'actual_args': len(args)})
649 649
650 650 # Decrement because we're going to lob off the first argument.
651 651 # @@ This is hacky
652 652 self.min_args -= 1
653 653 self.bootstrap_config(args[0])
654 654 self.update_parser()
655 655 return super(BasePasterCommand, self).run(args[1:])
656 656
657 657 def update_parser(self):
658 658 """
659 659 Abstract method. Allows for the class's parser to be updated
660 660 before the superclass's `run` method is called. Necessary to
661 661 allow options/arguments to be passed through to the underlying
662 662 celery command.
663 663 """
664 664 raise NotImplementedError("Abstract Method.")
665 665
666 666 def bootstrap_config(self, conf):
667 667 """
668 668 Loads the pylons configuration.
669 669 """
670 670 from pylons import config as pylonsconfig
671 671
672 672 self.path_to_ini_file = os.path.realpath(conf)
673 673 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
674 674 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
@@ -1,18 +1,18 b''
1 1 """
2 2 Mercurial libs compatibility
3 3 """
4 4
5 5 from mercurial import archival, merge as hg_merge, patch, ui
6 6 from mercurial.commands import clone, nullid, pull
7 7 from mercurial.context import memctx, memfilectx
8 8 from mercurial.error import RepoError, RepoLookupError, Abort
9 9 from mercurial.hgweb.common import get_contact
10 10 from mercurial.localrepo import localrepository
11 11 from mercurial.match import match
12 12 from mercurial.mdiff import diffopts
13 13 from mercurial.node import hex
14 14 from mercurial.encoding import tolocal
15 15 from mercurial import discovery
16 16 from mercurial import localrepo
17 17 from mercurial import scmutil
18 from mercurial.discovery import findcommonoutgoing No newline at end of file
18 from mercurial.discovery import findcommonoutgoing
@@ -1,343 +1,343 b''
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14 14
15 15
16 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 import logging
23 23
24 24 import formencode
25 25 from formencode import All
26 26
27 27 from pylons.i18n.translation import _
28 28
29 29 from rhodecode.model import validators as v
30 30 from rhodecode import BACKENDS
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34
35 35 class LoginForm(formencode.Schema):
36 36 allow_extra_fields = True
37 37 filter_extra_fields = True
38 38 username = v.UnicodeString(
39 39 strip=True,
40 40 min=1,
41 41 not_empty=True,
42 42 messages={
43 43 'empty': _(u'Please enter a login'),
44 44 'tooShort': _(u'Enter a value %(min)i characters long or more')}
45 45 )
46 46
47 47 password = v.UnicodeString(
48 48 strip=False,
49 49 min=3,
50 50 not_empty=True,
51 51 messages={
52 52 'empty': _(u'Please enter a password'),
53 53 'tooShort': _(u'Enter %(min)i characters or more')}
54 54 )
55 55
56 56 remember = v.StringBoolean(if_missing=False)
57 57
58 58 chained_validators = [v.ValidAuth()]
59 59
60 60
61 61 def UserForm(edit=False, old_data={}):
62 62 class _UserForm(formencode.Schema):
63 63 allow_extra_fields = True
64 64 filter_extra_fields = True
65 65 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
66 66 v.ValidUsername(edit, old_data))
67 67 if edit:
68 68 new_password = All(
69 69 v.ValidPassword(),
70 70 v.UnicodeString(strip=False, min=6, not_empty=False)
71 71 )
72 72 password_confirmation = All(
73 73 v.ValidPassword(),
74 74 v.UnicodeString(strip=False, min=6, not_empty=False),
75 75 )
76 76 admin = v.StringBoolean(if_missing=False)
77 77 else:
78 78 password = All(
79 79 v.ValidPassword(),
80 80 v.UnicodeString(strip=False, min=6, not_empty=True)
81 81 )
82 82 password_confirmation = All(
83 83 v.ValidPassword(),
84 84 v.UnicodeString(strip=False, min=6, not_empty=False)
85 85 )
86 86
87 87 active = v.StringBoolean(if_missing=False)
88 88 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
89 89 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
90 90 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
91 91
92 92 chained_validators = [v.ValidPasswordsMatch()]
93 93
94 94 return _UserForm
95 95
96 96
97 97 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
98 98 class _UsersGroupForm(formencode.Schema):
99 99 allow_extra_fields = True
100 100 filter_extra_fields = True
101 101
102 102 users_group_name = All(
103 103 v.UnicodeString(strip=True, min=1, not_empty=True),
104 104 v.ValidUsersGroup(edit, old_data)
105 105 )
106 106
107 107 users_group_active = v.StringBoolean(if_missing=False)
108 108
109 109 if edit:
110 110 users_group_members = v.OneOf(
111 111 available_members, hideList=False, testValueList=True,
112 112 if_missing=None, not_empty=False
113 113 )
114 114
115 115 return _UsersGroupForm
116 116
117 117
118 118 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
119 119 class _ReposGroupForm(formencode.Schema):
120 120 allow_extra_fields = True
121 121 filter_extra_fields = False
122 122
123 123 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
124 124 v.SlugifyName())
125 125 group_description = v.UnicodeString(strip=True, min=1,
126 126 not_empty=True)
127 127 group_parent_id = v.OneOf(available_groups, hideList=False,
128 128 testValueList=True,
129 129 if_missing=None, not_empty=False)
130 130 enable_locking = v.StringBoolean(if_missing=False)
131 131 chained_validators = [v.ValidReposGroup(edit, old_data),
132 132 v.ValidPerms('group')]
133 133
134 134 return _ReposGroupForm
135 135
136 136
137 137 def RegisterForm(edit=False, old_data={}):
138 138 class _RegisterForm(formencode.Schema):
139 139 allow_extra_fields = True
140 140 filter_extra_fields = True
141 141 username = All(
142 142 v.ValidUsername(edit, old_data),
143 143 v.UnicodeString(strip=True, min=1, not_empty=True)
144 144 )
145 145 password = All(
146 146 v.ValidPassword(),
147 147 v.UnicodeString(strip=False, min=6, not_empty=True)
148 148 )
149 149 password_confirmation = All(
150 150 v.ValidPassword(),
151 151 v.UnicodeString(strip=False, min=6, not_empty=True)
152 152 )
153 153 active = v.StringBoolean(if_missing=False)
154 154 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
155 155 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
156 156 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
157 157
158 158 chained_validators = [v.ValidPasswordsMatch()]
159 159
160 160 return _RegisterForm
161 161
162 162
163 163 def PasswordResetForm():
164 164 class _PasswordResetForm(formencode.Schema):
165 165 allow_extra_fields = True
166 166 filter_extra_fields = True
167 167 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
168 168 return _PasswordResetForm
169 169
170 170
171 171 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
172 172 repo_groups=[], landing_revs=[]):
173 173 class _RepoForm(formencode.Schema):
174 174 allow_extra_fields = True
175 175 filter_extra_fields = False
176 176 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
177 177 v.SlugifyName())
178 178 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
179 179 repo_group = v.OneOf(repo_groups, hideList=True)
180 180 repo_type = v.OneOf(supported_backends)
181 181 description = v.UnicodeString(strip=True, min=1, not_empty=False)
182 182 private = v.StringBoolean(if_missing=False)
183 183 enable_statistics = v.StringBoolean(if_missing=False)
184 184 enable_downloads = v.StringBoolean(if_missing=False)
185 185 enable_locking = v.StringBoolean(if_missing=False)
186 186 landing_rev = v.OneOf(landing_revs, hideList=True)
187 187
188 188 if edit:
189 189 #this is repo owner
190 190 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
191 191
192 192 chained_validators = [v.ValidCloneUri(),
193 193 v.ValidRepoName(edit, old_data),
194 194 v.ValidPerms()]
195 195 return _RepoForm
196 196
197 197
198 198 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
199 199 repo_groups=[], landing_revs=[]):
200 200 class _RepoForkForm(formencode.Schema):
201 201 allow_extra_fields = True
202 202 filter_extra_fields = False
203 203 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
204 204 v.SlugifyName())
205 205 repo_group = v.OneOf(repo_groups, hideList=True)
206 206 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
207 207 description = v.UnicodeString(strip=True, min=1, not_empty=True)
208 208 private = v.StringBoolean(if_missing=False)
209 209 copy_permissions = v.StringBoolean(if_missing=False)
210 210 update_after_clone = v.StringBoolean(if_missing=False)
211 211 fork_parent_id = v.UnicodeString()
212 212 chained_validators = [v.ValidForkName(edit, old_data)]
213 213 landing_rev = v.OneOf(landing_revs, hideList=True)
214 214
215 215 return _RepoForkForm
216 216
217 217
218 218 def RepoSettingsForm(edit=False, old_data={},
219 219 supported_backends=BACKENDS.keys(), repo_groups=[],
220 220 landing_revs=[]):
221 221 class _RepoForm(formencode.Schema):
222 222 allow_extra_fields = True
223 223 filter_extra_fields = False
224 224 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
225 225 v.SlugifyName())
226 226 description = v.UnicodeString(strip=True, min=1, not_empty=True)
227 227 repo_group = v.OneOf(repo_groups, hideList=True)
228 228 private = v.StringBoolean(if_missing=False)
229 229 landing_rev = v.OneOf(landing_revs, hideList=True)
230 230 chained_validators = [v.ValidRepoName(edit, old_data), v.ValidPerms(),
231 231 v.ValidSettings()]
232 232 return _RepoForm
233 233
234 234
235 235 def ApplicationSettingsForm():
236 236 class _ApplicationSettingsForm(formencode.Schema):
237 237 allow_extra_fields = True
238 238 filter_extra_fields = False
239 239 rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True)
240 240 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
241 241 rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
242 242
243 243 return _ApplicationSettingsForm
244 244
245 245
246 246 def ApplicationVisualisationForm():
247 247 class _ApplicationVisualisationForm(formencode.Schema):
248 248 allow_extra_fields = True
249 249 filter_extra_fields = False
250 250 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
251 251 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
252 252 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
253 253
254 254 return _ApplicationVisualisationForm
255 255
256 256
257 257 def ApplicationUiSettingsForm():
258 258 class _ApplicationUiSettingsForm(formencode.Schema):
259 259 allow_extra_fields = True
260 260 filter_extra_fields = False
261 261 web_push_ssl = v.StringBoolean(if_missing=False)
262 262 paths_root_path = All(
263 263 v.ValidPath(),
264 264 v.UnicodeString(strip=True, min=1, not_empty=True)
265 265 )
266 266 hooks_changegroup_update = v.StringBoolean(if_missing=False)
267 267 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
268 268 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
269 269 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
270 270
271 271 extensions_largefiles = v.StringBoolean(if_missing=False)
272 272 extensions_hgsubversion = v.StringBoolean(if_missing=False)
273 273 extensions_hggit = v.StringBoolean(if_missing=False)
274 274
275 275 return _ApplicationUiSettingsForm
276 276
277 277
278 278 def DefaultPermissionsForm(perms_choices, register_choices, create_choices,
279 279 fork_choices):
280 280 class _DefaultPermissionsForm(formencode.Schema):
281 281 allow_extra_fields = True
282 282 filter_extra_fields = True
283 283 overwrite_default = v.StringBoolean(if_missing=False)
284 284 anonymous = v.StringBoolean(if_missing=False)
285 285 default_perm = v.OneOf(perms_choices)
286 286 default_register = v.OneOf(register_choices)
287 287 default_create = v.OneOf(create_choices)
288 288 default_fork = v.OneOf(fork_choices)
289 289
290 290 return _DefaultPermissionsForm
291 291
292 292
293 293 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
294 294 tls_kind_choices):
295 295 class _LdapSettingsForm(formencode.Schema):
296 296 allow_extra_fields = True
297 297 filter_extra_fields = True
298 298 #pre_validators = [LdapLibValidator]
299 299 ldap_active = v.StringBoolean(if_missing=False)
300 300 ldap_host = v.UnicodeString(strip=True,)
301 301 ldap_port = v.Number(strip=True,)
302 302 ldap_tls_kind = v.OneOf(tls_kind_choices)
303 303 ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
304 304 ldap_dn_user = v.UnicodeString(strip=True,)
305 305 ldap_dn_pass = v.UnicodeString(strip=True,)
306 306 ldap_base_dn = v.UnicodeString(strip=True,)
307 307 ldap_filter = v.UnicodeString(strip=True,)
308 308 ldap_search_scope = v.OneOf(search_scope_choices)
309 309 ldap_attr_login = All(
310 310 v.AttrLoginValidator(),
311 311 v.UnicodeString(strip=True,)
312 312 )
313 313 ldap_attr_firstname = v.UnicodeString(strip=True,)
314 314 ldap_attr_lastname = v.UnicodeString(strip=True,)
315 315 ldap_attr_email = v.UnicodeString(strip=True,)
316 316
317 317 return _LdapSettingsForm
318 318
319 319
320 320 def UserExtraEmailForm():
321 321 class _UserExtraEmailForm(formencode.Schema):
322 322 email = All(v.UniqSystemEmail(), v.Email)
323 323
324 324 return _UserExtraEmailForm
325 325
326 326
327 327 def PullRequestForm():
328 328 class _PullRequestForm(formencode.Schema):
329 329 allow_extra_fields = True
330 330 filter_extra_fields = True
331 331
332 332 user = v.UnicodeString(strip=True, required=True)
333 333 org_repo = v.UnicodeString(strip=True, required=True)
334 334 org_ref = v.UnicodeString(strip=True, required=True)
335 335 other_repo = v.UnicodeString(strip=True, required=True)
336 336 other_ref = v.UnicodeString(strip=True, required=True)
337 337 revisions = All(v.NotReviewedRevisions()(), v.UniqueList(not_empty=True))
338 338 review_members = v.UniqueList(not_empty=True)
339 339
340 340 pullrequest_title = v.UnicodeString(strip=True, required=True, min=3)
341 341 pullrequest_desc = v.UnicodeString(strip=True, required=False)
342 342
343 return _PullRequestForm No newline at end of file
343 return _PullRequestForm
@@ -1,282 +1,282 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Edit repository')} ${c.repo_info.repo_name} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 11 ${h.link_to(_('Repositories'),h.url('repos'))}
12 12 &raquo;
13 13 ${_('edit')} &raquo; ${h.link_to(c.repo_info.just_name,h.url('summary_home',repo_name=c.repo_name))}
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
17 17 ${self.menu('admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box box-left">
22 22 <!-- box / title -->
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 </div>
26 26 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
27 27 <div class="form">
28 28 <!-- fields -->
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label">
32 32 <label for="repo_name">${_('Name')}:</label>
33 33 </div>
34 34 <div class="input">
35 35 ${h.text('repo_name',class_="medium")}
36 36 </div>
37 37 </div>
38 38 <div class="field">
39 39 <div class="label">
40 40 <label for="clone_uri">${_('Clone uri')}:</label>
41 41 </div>
42 42 <div class="input">
43 43 ${h.text('clone_uri',class_="medium")}
44 44 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
45 45 </div>
46 46 </div>
47 47 <div class="field">
48 48 <div class="label">
49 49 <label for="repo_group">${_('Repository group')}:</label>
50 50 </div>
51 51 <div class="input">
52 52 ${h.select('repo_group','',c.repo_groups,class_="medium")}
53 53 <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
54 54 </div>
55 55 </div>
56 56 <div class="field">
57 57 <div class="label">
58 58 <label for="repo_type">${_('Type')}:</label>
59 59 </div>
60 60 <div class="input">
61 61 ${h.select('repo_type','hg',c.backends,class_="medium")}
62 62 </div>
63 63 </div>
64 64 <div class="field">
65 65 <div class="label">
66 66 <label for="landing_rev">${_('Landing revision')}:</label>
67 67 </div>
68 68 <div class="input">
69 69 ${h.select('landing_rev','',c.landing_revs,class_="medium")}
70 70 <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
71 71 </div>
72 72 </div>
73 73 <div class="field">
74 74 <div class="label label-textarea">
75 75 <label for="description">${_('Description')}:</label>
76 76 </div>
77 77 <div class="textarea text-area editor">
78 78 ${h.textarea('description')}
79 79 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
80 80 </div>
81 81 </div>
82 82
83 83 <div class="field">
84 84 <div class="label label-checkbox">
85 85 <label for="private">${_('Private repository')}:</label>
86 86 </div>
87 87 <div class="checkboxes">
88 88 ${h.checkbox('private',value="True")}
89 89 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
90 90 </div>
91 91 </div>
92 92 <div class="field">
93 93 <div class="label label-checkbox">
94 94 <label for="enable_statistics">${_('Enable statistics')}:</label>
95 95 </div>
96 96 <div class="checkboxes">
97 97 ${h.checkbox('enable_statistics',value="True")}
98 98 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
99 99 </div>
100 100 </div>
101 101 <div class="field">
102 102 <div class="label label-checkbox">
103 103 <label for="enable_downloads">${_('Enable downloads')}:</label>
104 104 </div>
105 105 <div class="checkboxes">
106 106 ${h.checkbox('enable_downloads',value="True")}
107 107 <span class="help-block">${_('Enable download menu on summary page.')}</span>
108 108 </div>
109 109 </div>
110 110 <div class="field">
111 111 <div class="label label-checkbox">
112 112 <label for="enable_locking">${_('Enable locking')}:</label>
113 113 </div>
114 114 <div class="checkboxes">
115 115 ${h.checkbox('enable_locking',value="True")}
116 116 <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span>
117 117 </div>
118 </div>
118 </div>
119 119 <div class="field">
120 120 <div class="label">
121 121 <label for="user">${_('Owner')}:</label>
122 122 </div>
123 123 <div class="input input-medium ac">
124 124 <div class="perm_ac">
125 125 ${h.text('user',class_='yui-ac-input')}
126 126 <span class="help-block">${_('Change owner of this repository.')}</span>
127 127 <div id="owner_container"></div>
128 128 </div>
129 129 </div>
130 130 </div>
131 131
132 132 <div class="field">
133 133 <div class="label">
134 134 <label for="input">${_('Permissions')}:</label>
135 135 </div>
136 136 <div class="input">
137 137 <%include file="repo_edit_perms.html"/>
138 138 </div>
139 139
140 140 <div class="buttons">
141 141 ${h.submit('save',_('Save'),class_="ui-btn large")}
142 142 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
143 143 </div>
144 144 </div>
145 145 </div>
146 146 </div>
147 147 ${h.end_form()}
148 148 </div>
149 149
150 150 <div class="box box-right">
151 151 <div class="title">
152 152 <h5>${_('Administration')}</h5>
153 153 </div>
154 154
155 155 <h3>${_('Statistics')}</h3>
156 156 ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')}
157 157 <div class="form">
158 158 <div class="fields">
159 159 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="ui-btn",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
160 160 <div class="field" style="border:none;color:#888">
161 161 <ul>
162 162 <li>${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}</li>
163 163 <li>${_('Stats gathered')}: ${c.stats_percentage}%</li>
164 164 </ul>
165 165 </div>
166 166 </div>
167 167 </div>
168 168 ${h.end_form()}
169 169
170 170 %if c.repo_info.clone_uri:
171 171 <h3>${_('Remote')}</h3>
172 172 ${h.form(url('repo_pull', repo_name=c.repo_info.repo_name),method='put')}
173 173 <div class="form">
174 174 <div class="fields">
175 175 ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="ui-btn",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
176 176 <div class="field" style="border:none">
177 177 <ul>
178 178 <li><a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri}</a></li>
179 179 </ul>
180 180 </div>
181 181 </div>
182 182 </div>
183 183 ${h.end_form()}
184 184 %endif
185 185
186 186 <h3>${_('Cache')}</h3>
187 187 ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')}
188 188 <div class="form">
189 189 <div class="fields">
190 190 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="ui-btn",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
191 191 <div class="field" style="border:none;color:#888">
192 192 <ul>
193 193 <li>${_('Manually invalidate cache for this repository. On first access repository will be cached again')}
194 194 </li>
195 195 </ul>
196 </div>
196 </div>
197 197 <div class="field" style="border:none;">
198 198 ${_('List of cached values')}
199 199 <ul>
200 200 %for cache in c.repo_info.cache_keys:
201 201 <li>INSTANCE ID:${cache.prefix or '-'} ${cache.cache_args} CACHED: ${h.bool2icon(cache.cache_active)}</li>
202 202 %endfor
203 203 </ul>
204 </div>
204 </div>
205 205 </div>
206 206 </div>
207 207 ${h.end_form()}
208 208
209 209 <h3>${_('Public journal')}</h3>
210 210 ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')}
211 211 <div class="form">
212 212 ${h.hidden('auth_token',str(h.get_token()))}
213 213 <div class="field">
214 214 %if c.in_public_journal:
215 215 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="ui-btn")}
216 216 %else:
217 217 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="ui-btn")}
218 218 %endif
219 219 </div>
220 220 <div class="field" style="border:none;color:#888">
221 221 <ul>
222 222 <li>${_('All actions made on this repository will be accessible to everyone in public journal')}
223 223 </li>
224 224 </ul>
225 225 </div>
226 226 </div>
227 227 ${h.end_form()}
228 228
229 229 <h3>${_('Locking')}</h3>
230 230 ${h.form(url('repo_locking', repo_name=c.repo_info.repo_name),method='put')}
231 231 <div class="form">
232 232 <div class="fields">
233 233 %if c.repo_info.locked[0]:
234 234 ${h.submit('set_unlock' ,_('Unlock locked repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to unlock repository')+"');")}
235 235 ${'Locked by %s on %s' % (h.person_by_id(c.repo_info.locked[0]),h.fmt_date(h.time_to_datetime(c.repo_info.locked[1])))}
236 236 %else:
237 237 ${h.submit('set_lock',_('lock repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to lock repository')+"');")}
238 238 ${_('Repository is not locked')}
239 239 %endif
240 240 </div>
241 241 <div class="field" style="border:none;color:#888">
242 242 <ul>
243 243 <li>${_('Force locking on repository. Works only when anonymous access is disabled')}
244 244 </li>
245 245 </ul>
246 </div>
246 </div>
247 247 </div>
248 248 ${h.end_form()}
249 249
250 250 <h3>${_('Set as fork of')}</h3>
251 251 ${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')}
252 252 <div class="form">
253 253 <div class="fields">
254 254 ${h.select('id_fork_of','',c.repos_list,class_="medium")}
255 255 ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('set'),class_="ui-btn",)}
256 256 </div>
257 257 <div class="field" style="border:none;color:#888">
258 258 <ul>
259 259 <li>${_('''Manually set this repository as a fork of another from the list''')}</li>
260 260 </ul>
261 261 </div>
262 </div>
262 </div>
263 263 ${h.end_form()}
264
264
265 265 <h3>${_('Delete')}</h3>
266 266 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
267 267 <div class="form">
268 268 <div class="fields">
269 269 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
270 270 </div>
271 271 <div class="field" style="border:none;color:#888">
272 272 <ul>
273 273 <li>${_('''This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems.
274 274 If you need fully delete it from filesystem please do it manually''')}
275 275 </li>
276 276 </ul>
277 277 </div>
278 278 </div>
279 ${h.end_form()}
279 ${h.end_form()}
280 280 </div>
281 281
282 282 </%def>
@@ -1,128 +1,128 b''
1 1 <table id="permissions_manage" class="noborder">
2 2 <tr>
3 3 <td>${_('none')}</td>
4 4 <td>${_('read')}</td>
5 5 <td>${_('write')}</td>
6 6 <td>${_('admin')}</td>
7 7 <td>${_('member')}</td>
8 8 <td></td>
9 9 </tr>
10 10 ## USERS
11 11 %for r2p in c.repo_info.repo_to_perm:
12 12 %if r2p.user.username =='default' and c.repo_info.private:
13 13 <tr>
14 14 <td colspan="4">
15 15 <span class="private_repo_msg">
16 16 ${_('private repository')}
17 17 </span>
18 18 </td>
19 19 <td class="private_repo_msg"><img style="vertical-align:bottom" src="${h.url('/images/icons/user.png')}"/>${_('default')}</td>
20 20 </tr>
21 21 %else:
22 22 <tr id="id${id(r2p.user.username)}">
23 23 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.none')}</td>
24 24 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.read')}</td>
25 25 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.write')}</td>
26 26 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.admin')}</td>
27 27 <td style="white-space: nowrap;">
28 28 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
29 29 </td>
30 30 <td>
31 31 %if r2p.user.username !='default':
32 32 <span class="delete_icon action_button" onclick="ajaxActionUser(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')">
33 33 ${_('revoke')}
34 34 </span>
35 35 %endif
36 36 </td>
37 37 </tr>
38 38 %endif
39 39 %endfor
40 40
41 41 ## USERS GROUPS
42 42 %for g2p in c.repo_info.users_group_to_perm:
43 43 <tr id="id${id(g2p.users_group.users_group_name)}">
44 44 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.none')}</td>
45 45 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.read')}</td>
46 46 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.write')}</td>
47 47 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.admin')}</td>
48 48 <td style="white-space: nowrap;">
49 49 <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>
50 50 %if h.HasPermissionAny('hg.admin')():
51 51 <a href="${h.url('edit_users_group',id=g2p.users_group.users_group_id)}">${g2p.users_group.users_group_name}</a>
52 52 %else:
53 53 ${g2p.users_group.users_group_name}
54 54 %endif
55 55 </td>
56 56 <td>
57 57 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
58 58 ${_('revoke')}
59 59 </span>
60 60 </td>
61 61 </tr>
62 62 %endfor
63 63 <%
64 64 _tmpl = h.literal("""' \
65 65 <td><input type="radio" value="repository.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
66 66 <td><input type="radio" value="repository.read" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
67 67 <td><input type="radio" value="repository.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
68 68 <td><input type="radio" value="repository.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
69 69 <td class="ac"> \
70 70 <div class="perm_ac" id="perm_ac_{0}"> \
71 71 <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
72 72 <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden"> \
73 73 <div id="perm_container_{0}"></div> \
74 74 </div> \
75 75 </td> \
76 76 <td></td>'""")
77 %>
78 ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
77 %>
78 ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
79 79 <tr class="new_members last_new_member" id="add_perm_input"></tr>
80 80 <tr>
81 81 <td colspan="6">
82 82 <span id="add_perm" class="add_icon" style="cursor: pointer;">
83 83 ${_('Add another member')}
84 84 </span>
85 85 </td>
86 86 </tr>
87 87 </table>
88 88 <script type="text/javascript">
89 89 function ajaxActionUser(user_id, field_id) {
90 90 var sUrl = "${h.url('delete_repo_user',repo_name=c.repo_name)}";
91 91 var callback = {
92 92 success: function (o) {
93 93 var tr = YUD.get(String(field_id));
94 94 tr.parentNode.removeChild(tr);
95 95 },
96 96 failure: function (o) {
97 97 alert("${_('Failed to remove user')}");
98 98 },
99 99 };
100 100 var postData = '_method=delete&user_id=' + user_id;
101 101 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
102 102 };
103 103
104 104 function ajaxActionUsersGroup(users_group_id,field_id){
105 105 var sUrl = "${h.url('delete_repo_users_group',repo_name=c.repo_name)}";
106 106 var callback = {
107 107 success:function(o){
108 108 var tr = YUD.get(String(field_id));
109 109 tr.parentNode.removeChild(tr);
110 110 },
111 111 failure:function(o){
112 112 alert("${_('Failed to remove users group')}");
113 113 },
114 114 };
115 115 var postData = '_method=delete&users_group_id='+users_group_id;
116 116 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
117 117 };
118 118
119 119 YUE.onDOMReady(function () {
120 120 if (!YUD.hasClass('perm_new_member_name', 'error')) {
121 121 YUD.setStyle('add_perm_input', 'display', 'none');
122 122 }
123 123 YAHOO.util.Event.addListener('add_perm', 'click', function () {
124 124 addPermAction(${_tmpl}, ${c.users_array|n}, ${c.users_groups_array|n});
125 125 });
126 126 });
127 127
128 128 </script>
@@ -1,112 +1,112 b''
1 1 <table id="permissions_manage" class="noborder">
2 2 <tr>
3 3 <td>${_('none')}</td>
4 4 <td>${_('read')}</td>
5 5 <td>${_('write')}</td>
6 6 <td>${_('admin')}</td>
7 7 <td>${_('member')}</td>
8 8 <td></td>
9 9 </tr>
10 10 ## USERS
11 11 %for r2p in c.repos_group.repo_group_to_perm:
12 12 <tr id="id${id(r2p.user.username)}">
13 13 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none')}</td>
14 14 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read')}</td>
15 15 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write')}</td>
16 16 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin')}</td>
17 17 <td style="white-space: nowrap;">
18 18 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
19 19 </td>
20 20 <td>
21 21 %if r2p.user.username !='default':
22 22 <span class="delete_icon action_button" onclick="ajaxActionUser(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')">
23 23 ${_('revoke')}
24 24 </span>
25 25 %endif
26 26 </td>
27 27 </tr>
28 28 %endfor
29 29
30 30 ## USERS GROUPS
31 31 %for g2p in c.repos_group.users_group_to_perm:
32 32 <tr id="id${id(g2p.users_group.users_group_name)}">
33 33 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.none')}</td>
34 34 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.read')}</td>
35 35 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td>
36 36 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td>
37 37 <td style="white-space: nowrap;">
38 38 <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
39 39 </td>
40 40 <td>
41 41 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
42 42 ${_('revoke')}
43 43 </span>
44 44 </td>
45 45 </tr>
46 46 %endfor
47 47 <%
48 48 _tmpl = h.literal("""' \
49 49 <td><input type="radio" value="group.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
50 50 <td><input type="radio" value="group.read" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
51 51 <td><input type="radio" value="group.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
52 52 <td><input type="radio" value="group.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
53 53 <td class="ac"> \
54 54 <div class="perm_ac" id="perm_ac_{0}"> \
55 55 <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
56 56 <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden"> \
57 57 <div id="perm_container_{0}"></div> \
58 58 </div> \
59 59 </td> \
60 60 <td></td>'""")
61 %>
62 ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
61 %>
62 ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
63 63 <tr class="new_members last_new_member" id="add_perm_input"></tr>
64 64 <tr>
65 65 <td colspan="6">
66 66 <span id="add_perm" class="add_icon" style="cursor: pointer;">
67 67 ${_('Add another member')}
68 68 </span>
69 69 </td>
70 70 </tr>
71 71 </table>
72 72 <script type="text/javascript">
73 73 function ajaxActionUser(user_id, field_id) {
74 74 var sUrl = "${h.url('delete_repos_group_user_perm',group_name=c.repos_group.group_name)}";
75 75 var callback = {
76 76 success: function (o) {
77 77 var tr = YUD.get(String(field_id));
78 78 tr.parentNode.removeChild(tr);
79 79 },
80 80 failure: function (o) {
81 81 alert("${_('Failed to remove user')}");
82 82 },
83 83 };
84 84 var postData = '_method=delete&user_id=' + user_id;
85 85 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
86 86 };
87 87
88 88 function ajaxActionUsersGroup(users_group_id,field_id){
89 89 var sUrl = "${h.url('delete_repos_group_users_group_perm',group_name=c.repos_group.group_name)}";
90 90 var callback = {
91 91 success:function(o){
92 92 var tr = YUD.get(String(field_id));
93 93 tr.parentNode.removeChild(tr);
94 94 },
95 95 failure:function(o){
96 96 alert("${_('Failed to remove users group')}");
97 97 },
98 98 };
99 99 var postData = '_method=delete&users_group_id='+users_group_id;
100 100 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
101 101 };
102 102
103 103 YUE.onDOMReady(function () {
104 104 if (!YUD.hasClass('perm_new_member_name', 'error')) {
105 105 YUD.setStyle('add_perm_input', 'display', 'none');
106 106 }
107 107 YAHOO.util.Event.addListener('add_perm', 'click', function () {
108 108 addPermAction(${_tmpl}, ${c.users_array|n}, ${c.users_groups_array|n});
109 109 });
110 110 });
111 111
112 112 </script>
@@ -1,22 +1,22 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3 <%def name="title()">
4 4 ${_('Repository group')} - ${c.rhodecode_name}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs()">
8 <span class="groups_breadcrumbs">
8 <span class="groups_breadcrumbs">
9 9 ${h.link_to(_(u'Home'),h.url('/'))}
10 10 %if c.group.parent_group:
11 11 &raquo; ${h.link_to(c.group.parent_group.name,h.url('repos_group_home',group_name=c.group.parent_group.group_name))}
12 12 %endif
13 13 &raquo; "${c.group.name}" ${_('with')}
14 14 </span>
15 15 </%def>
16 16
17 17 <%def name="page_nav()">
18 18 ${self.menu('admin')}
19 19 </%def>
20 20 <%def name="main()">
21 21 <%include file="/index_base.html" args="parent=self,short_repo_names=True"/>
22 22 </%def>
@@ -1,81 +1,81 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Edit repos group')} ${c.repos_group.name} - ${c.rhodecode_name}
6 6 </%def>
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 9 &raquo;
10 10 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
11 11 &raquo;
12 12 ${_('edit repos group')} "${c.repos_group.name}"
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 16 ${self.menu('admin')}
17 17 </%def>
18 18
19 19 <%def name="main()">
20 20 <div class="box">
21 21 <!-- box / title -->
22 22 <div class="title">
23 23 ${self.breadcrumbs()}
24 24 </div>
25 25 <!-- end box / title -->
26 26 ${h.form(url('repos_group',id=c.repos_group.group_id),method='put')}
27 27 <div class="form">
28 28 <!-- fields -->
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label">
32 32 <label for="group_name">${_('Group name')}:</label>
33 33 </div>
34 34 <div class="input">
35 35 ${h.text('group_name',class_='medium')}
36 36 </div>
37 37 </div>
38 38
39 39 <div class="field">
40 40 <div class="label label-textarea">
41 41 <label for="group_description">${_('Description')}:</label>
42 42 </div>
43 43 <div class="textarea text-area editor">
44 44 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
45 45 </div>
46 46 </div>
47 47
48 48 <div class="field">
49 49 <div class="label">
50 50 <label for="group_parent_id">${_('Group parent')}:</label>
51 51 </div>
52 52 <div class="input">
53 53 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
54 54 </div>
55 55 </div>
56 56 <div class="field">
57 57 <div class="label">
58 58 <label for="input">${_('Permissions')}:</label>
59 59 </div>
60 60 <div class="input">
61 61 <%include file="repos_group_edit_perms.html"/>
62 62 </div>
63 63 </div>
64 64 <div class="field">
65 65 <div class="label label-checkbox">
66 66 <label for="enable_locking">${_('Enable locking')}:</label>
67 67 </div>
68 68 <div class="checkboxes">
69 69 ${h.checkbox('enable_locking',value="True")}
70 70 <span class="help-block">${_('Enable lock-by-pulling on group. This option will be applied to all other groups and repositories inside')}</span>
71 71 </div>
72 </div>
72 </div>
73 73 <div class="buttons">
74 74 ${h.submit('save',_('Save'),class_="ui-btn large")}
75 75 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
76 76 </div>
77 77 </div>
78 78 </div>
79 79 ${h.end_form()}
80 80 </div>
81 81 </%def>
@@ -1,327 +1,327 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Settings administration')} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Settings')}
10 10 </%def>
11 11
12 12 <%def name="page_nav()">
13 13 ${self.menu('admin')}
14 14 </%def>
15 15
16 16 <%def name="main()">
17 17 <div class="box">
18 18 <!-- box / title -->
19 19 <div class="title">
20 20 ${self.breadcrumbs()}
21 21 </div>
22 22 <!-- end box / title -->
23 23
24 24 <h3>${_('Remap and rescan repositories')}</h3>
25 25 ${h.form(url('admin_setting', setting_id='mapping'),method='put')}
26 26 <div class="form">
27 27 <!-- fields -->
28 28
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label label-checkbox">
32 32 <label for="destroy">${_('rescan option')}:</label>
33 33 </div>
34 34 <div class="checkboxes">
35 35 <div class="checkbox">
36 36 ${h.checkbox('destroy',True)}
37 37 <label for="destroy">
38 38 <span class="tooltip" title="${h.tooltip(_('In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it.'))}">
39 39 ${_('destroy old data')}</span> </label>
40 40 </div>
41 41 <span class="help-block">${_('Rescan repositories location for new repositories. Also deletes obsolete if `destroy` flag is checked ')}</span>
42 42 </div>
43 43 </div>
44 44
45 45 <div class="buttons">
46 46 ${h.submit('rescan',_('Rescan repositories'),class_="ui-btn large")}
47 47 </div>
48 48 </div>
49 49 </div>
50 50 ${h.end_form()}
51 51
52 52 <h3>${_('Whoosh indexing')}</h3>
53 53 ${h.form(url('admin_setting', setting_id='whoosh'),method='put')}
54 54 <div class="form">
55 55 <!-- fields -->
56 56
57 57 <div class="fields">
58 58 <div class="field">
59 59 <div class="label label-checkbox">
60 60 <label>${_('index build option')}:</label>
61 61 </div>
62 62 <div class="checkboxes">
63 63 <div class="checkbox">
64 64 ${h.checkbox('full_index',True)}
65 65 <label for="full_index">${_('build from scratch')}</label>
66 66 </div>
67 67 </div>
68 68 </div>
69 69
70 70 <div class="buttons">
71 71 ${h.submit('reindex',_('Reindex'),class_="ui-btn large")}
72 72 </div>
73 73 </div>
74 74 </div>
75 75 ${h.end_form()}
76 76
77 77 <h3>${_('Global application settings')}</h3>
78 78 ${h.form(url('admin_setting', setting_id='global'),method='put')}
79 79 <div class="form">
80 80 <!-- fields -->
81 81
82 82 <div class="fields">
83 83
84 84 <div class="field">
85 85 <div class="label">
86 86 <label for="rhodecode_title">${_('Application name')}:</label>
87 87 </div>
88 88 <div class="input">
89 89 ${h.text('rhodecode_title',size=30)}
90 90 </div>
91 91 </div>
92 92
93 93 <div class="field">
94 94 <div class="label">
95 95 <label for="rhodecode_realm">${_('Realm text')}:</label>
96 96 </div>
97 97 <div class="input">
98 98 ${h.text('rhodecode_realm',size=30)}
99 99 </div>
100 100 </div>
101 101
102 102 <div class="field">
103 103 <div class="label">
104 104 <label for="rhodecode_ga_code">${_('GA code')}:</label>
105 105 </div>
106 106 <div class="input">
107 107 ${h.text('rhodecode_ga_code',size=30)}
108 108 </div>
109 109 </div>
110 110
111 111 <div class="buttons">
112 112 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
113 113 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
114 114 </div>
115 115 </div>
116 116 </div>
117 117 ${h.end_form()}
118 118
119 119 <h3>${_('Visualisation settings')}</h3>
120 120 ${h.form(url('admin_setting', setting_id='visual'),method='put')}
121 121 <div class="form">
122 122 <!-- fields -->
123 123
124 124 <div class="fields">
125 125
126 126 <div class="field">
127 127 <div class="label label-checkbox">
128 128 <label>${_('Icons')}:</label>
129 129 </div>
130 130 <div class="checkboxes">
131 131 <div class="checkbox">
132 132 ${h.checkbox('rhodecode_show_public_icon','True')}
133 133 <label for="rhodecode_show_public_icon">${_('Show public repo icon on repositories')}</label>
134 134 </div>
135 135 <div class="checkbox">
136 136 ${h.checkbox('rhodecode_show_private_icon','True')}
137 137 <label for="rhodecode_show_private_icon">${_('Show private repo icon on repositories')}</label>
138 138 </div>
139 139 </div>
140 140 </div>
141 141
142 142 <div class="field">
143 143 <div class="label label-checkbox">
144 144 <label>${_('Meta-Tagging')}:</label>
145 145 </div>
146 146 <div class="checkboxes">
147 147 <div class="checkbox">
148 148 ${h.checkbox('rhodecode_stylify_metatags','True')}
149 149 <label for="rhodecode_stylify_metatags">${_('Stylify recognised metatags:')}</label>
150 150 </div>
151 151 <div style="padding-left: 20px;">
152 152 <ul> <!-- Fix style here -->
153 153 <li>[featured] <span class="metatag" tag="featured">featured</span></li>
154 154 <li>[stale] <span class="metatag" tag="stale">stale</span></li>
155 155 <li>[dead] <span class="metatag" tag="dead">dead</span></li>
156 156 <li>[lang =&gt; lang] <span class="metatag" tag="lang" >lang</span></li>
157 <li>[license =&gt; License] <span class="metatag" tag="license"><a href="http://www.opensource.org/licenses/License" >License</a></span></li>
157 <li>[license =&gt; License] <span class="metatag" tag="license"><a href="http://www.opensource.org/licenses/License" >License</a></span></li>
158 158 <li>[requires =&gt; Repo] <span class="metatag" tag="requires" >requires =&gt; <a href="#" >Repo</a></span></li>
159 159 <li>[recommends =&gt; Repo] <span class="metatag" tag="recommends" >recommends =&gt; <a href="#" >Repo</a></span></li>
160 160 <li>[see =&gt; URI] <span class="metatag" tag="see">see =&gt; <a href="#">URI</a> </span></li>
161 161 </ul>
162 162 </div>
163 163 </div>
164 164 </div>
165 165
166 166 <div class="buttons">
167 167 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
168 168 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
169 169 </div>
170 170
171 171 </div>
172 172 </div>
173 173 ${h.end_form()}
174 174
175 175
176 176 <h3>${_('VCS settings')}</h3>
177 177 ${h.form(url('admin_setting', setting_id='vcs'),method='put')}
178 178 <div class="form">
179 179 <!-- fields -->
180 180
181 181 <div class="fields">
182 182
183 183 <div class="field">
184 184 <div class="label label-checkbox">
185 185 <label>${_('Web')}:</label>
186 186 </div>
187 187 <div class="checkboxes">
188 188 <div class="checkbox">
189 189 ${h.checkbox('web_push_ssl','true')}
190 190 <label for="web_push_ssl">${_('require ssl for vcs operations')}</label>
191 191 </div>
192 192 <span class="help-block">${_('RhodeCode will require SSL for pushing or pulling. If SSL is missing it will return HTTP Error 406: Not Acceptable')}</span>
193 193 </div>
194 194 </div>
195 195
196 196 <div class="field">
197 197 <div class="label label-checkbox">
198 198 <label>${_('Hooks')}:</label>
199 199 </div>
200 200 <div class="checkboxes">
201 201 <div class="checkbox">
202 202 ${h.checkbox('hooks_changegroup_update','True')}
203 203 <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
204 204 </div>
205 205 <div class="checkbox">
206 206 ${h.checkbox('hooks_changegroup_repo_size','True')}
207 207 <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
208 208 </div>
209 209 <div class="checkbox">
210 210 ${h.checkbox('hooks_changegroup_push_logger','True')}
211 211 <label for="hooks_changegroup_push_logger">${_('Log user push commands')}</label>
212 212 </div>
213 213 <div class="checkbox">
214 214 ${h.checkbox('hooks_outgoing_pull_logger','True')}
215 215 <label for="hooks_outgoing_pull_logger">${_('Log user pull commands')}</label>
216 216 </div>
217 217 </div>
218 218 <div class="input" style="margin-top:10px">
219 219 ${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'),class_="ui-btn")}
220 220 </div>
221 221 </div>
222 222 <div class="field">
223 223 <div class="label label-checkbox">
224 224 <label>${_('Mercurial Extensions')}:</label>
225 225 </div>
226 226 <div class="checkboxes">
227 227 <div class="checkbox">
228 228 ${h.checkbox('extensions_largefiles','True')}
229 229 <label for="extensions_hgsubversion">${_('largefiles extensions')}</label>
230 230 </div>
231 231 <div class="checkbox">
232 232 ${h.checkbox('extensions_hgsubversion','True')}
233 233 <label for="extensions_hgsubversion">${_('hgsubversion extensions')}</label>
234 234 </div>
235 235 <span class="help-block">${_('Requires hgsubversion library installed. Allows clonning from svn remote locations')}</span>
236 236 ##<div class="checkbox">
237 237 ## ${h.checkbox('extensions_hggit','True')}
238 238 ## <label for="extensions_hggit">${_('hg-git extensions')}</label>
239 239 ##</div>
240 ##<span class="help-block">${_('Requires hg-git library installed. Allows clonning from git remote locations')}</span>
240 ##<span class="help-block">${_('Requires hg-git library installed. Allows clonning from git remote locations')}</span>
241 241 </div>
242 </div>
242 </div>
243 243 <div class="field">
244 244 <div class="label">
245 245 <label for="paths_root_path">${_('Repositories location')}:</label>
246 246 </div>
247 247 <div class="input">
248 248 ${h.text('paths_root_path',size=30,readonly="readonly")}
249 249 <span id="path_unlock" class="tooltip"
250 250 title="${h.tooltip(_('This a crucial application setting. If you are really sure you need to change this, you must restart application in order to make this setting take effect. Click this label to unlock.'))}">
251 251 ${_('unlock')}</span>
252 252 <span class="help-block">${_('Location where repositories are stored. After changing this value a restart, and rescan is required')}</span>
253 253 </div>
254 254 </div>
255 255
256 256 <div class="buttons">
257 257 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
258 258 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
259 259 </div>
260 260 </div>
261 261 </div>
262 262 ${h.end_form()}
263 263
264 264 <script type="text/javascript">
265 265 YAHOO.util.Event.onDOMReady(function(){
266 266 YAHOO.util.Event.addListener('path_unlock','click',function(){
267 267 YAHOO.util.Dom.get('paths_root_path').removeAttribute('readonly');
268 268 });
269 269 });
270 270 </script>
271 271
272 272 <h3>${_('Test Email')}</h3>
273 273 ${h.form(url('admin_setting', setting_id='email'),method='put')}
274 274 <div class="form">
275 275 <!-- fields -->
276 276
277 277 <div class="fields">
278 278 <div class="field">
279 279 <div class="label">
280 280 <label for="test_email">${_('Email to')}:</label>
281 281 </div>
282 282 <div class="input">
283 283 ${h.text('test_email',size=30)}
284 284 </div>
285 285 </div>
286 286
287 287 <div class="buttons">
288 288 ${h.submit('send',_('Send'),class_="ui-btn large")}
289 289 </div>
290 290 </div>
291 291 </div>
292 292 ${h.end_form()}
293 293
294 294 <h3>${_('System Info and Packages')}</h3>
295 295 <div class="form">
296 296 <div>
297 297 <h5 id="expand_modules" style="cursor: pointer">&darr; ${_('show')} &darr;</h5>
298 298 </div>
299 299 <div id="expand_modules_table" style="display:none">
300 300 <h5>Python - ${c.py_version}</h5>
301 301 <h5>System - ${c.platform}</h5>
302 302
303 303 <table class="table" style="margin:0px 0px 0px 20px">
304 304 <colgroup>
305 305 <col style="width:220px">
306 306 </colgroup>
307 307 <tbody>
308 308 %for key, value in c.modules:
309 309 <tr>
310 310 <th style="text-align: right;padding-right:5px;">${key}</th>
311 311 <td>${value}</td>
312 312 </tr>
313 313 %endfor
314 314 </tbody>
315 315 </table>
316 316 </div>
317 317 </div>
318 318
319 319 <script type="text/javascript">
320 320 YUE.on('expand_modules','click',function(e){
321 321 YUD.setStyle('expand_modules_table','display','');
322 322 YUD.setStyle('expand_modules','display','none');
323 323 })
324 324 </script>
325 325
326 326 </div>
327 327 </%def>
@@ -1,284 +1,284 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Edit user')} ${c.user.username} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 11 ${h.link_to(_('Users'),h.url('users'))}
12 12 &raquo;
13 13 ${_('edit')} "${c.user.username}"
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
17 17 ${self.menu('admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box box-left">
22 22 <!-- box / title -->
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 </div>
26 26 <!-- end box / title -->
27 27 ${h.form(url('update_user', id=c.user.user_id),method='put')}
28 28 <div class="form">
29 29 <div class="field">
30 30 <div class="gravatar_box">
31 31 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
32 32 <p>
33 33 %if c.use_gravatar:
34 34 <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
35 35 <br/>${_('Using')} ${c.user.email}
36 36 %else:
37 37 <br/>${c.user.email}
38 38 %endif
39 39 </div>
40 40 </div>
41 41 <div class="field">
42 42 <div class="label">
43 43 <label>${_('API key')}</label> ${c.user.api_key}
44 44 </div>
45 45 </div>
46 46
47 47 <div class="fields">
48 48 <div class="field">
49 49 <div class="label">
50 50 <label for="username">${_('Username')}:</label>
51 51 </div>
52 52 <div class="input">
53 53 ${h.text('username',class_='medium')}
54 54 </div>
55 55 </div>
56 56
57 57 <div class="field">
58 58 <div class="label">
59 59 <label for="ldap_dn">${_('LDAP DN')}:</label>
60 60 </div>
61 61 <div class="input">
62 62 ${h.text('ldap_dn',class_='medium disabled',readonly="readonly")}
63 63 </div>
64 64 </div>
65 65
66 66 <div class="field">
67 67 <div class="label">
68 68 <label for="new_password">${_('New password')}:</label>
69 69 </div>
70 70 <div class="input">
71 71 ${h.password('new_password',class_='medium',autocomplete="off")}
72 72 </div>
73 73 </div>
74 74
75 75 <div class="field">
76 76 <div class="label">
77 77 <label for="password_confirmation">${_('New password confirmation')}:</label>
78 78 </div>
79 79 <div class="input">
80 80 ${h.password('password_confirmation',class_="medium",autocomplete="off")}
81 81 </div>
82 82 </div>
83 83
84 84 <div class="field">
85 85 <div class="label">
86 86 <label for="firstname">${_('First Name')}:</label>
87 87 </div>
88 88 <div class="input">
89 89 ${h.text('firstname',class_='medium')}
90 90 </div>
91 91 </div>
92 92
93 93 <div class="field">
94 94 <div class="label">
95 95 <label for="lastname">${_('Last Name')}:</label>
96 96 </div>
97 97 <div class="input">
98 98 ${h.text('lastname',class_='medium')}
99 99 </div>
100 100 </div>
101 101
102 102 <div class="field">
103 103 <div class="label">
104 104 <label for="email">${_('Email')}:</label>
105 105 </div>
106 106 <div class="input">
107 107 ${h.text('email',class_='medium')}
108 108 </div>
109 109 </div>
110 110
111 111 <div class="field">
112 112 <div class="label label-checkbox">
113 113 <label for="active">${_('Active')}:</label>
114 114 </div>
115 115 <div class="checkboxes">
116 116 ${h.checkbox('active',value=True)}
117 117 </div>
118 118 </div>
119 119
120 120 <div class="field">
121 121 <div class="label label-checkbox">
122 122 <label for="admin">${_('Admin')}:</label>
123 123 </div>
124 124 <div class="checkboxes">
125 125 ${h.checkbox('admin',value=True)}
126 126 </div>
127 127 </div>
128 128 <div class="buttons">
129 129 ${h.submit('save',_('Save'),class_="ui-btn large")}
130 130 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
131 131 </div>
132 132 </div>
133 133 </div>
134 134 ${h.end_form()}
135 135 </div>
136 136 <div style="min-height:780px" class="box box-right">
137 137 <!-- box / title -->
138 138 <div class="title">
139 139 <h5>${_('Permissions')}</h5>
140 140 </div>
141 141 ${h.form(url('user_perm', id=c.user.user_id),method='put')}
142 142 <div class="form">
143 143 <!-- fields -->
144 144 <div class="fields">
145 145 <div class="field">
146 146 <div class="label label-checkbox">
147 147 <label for="inherit_permissions">${_('Inherit default permissions')}:</label>
148 148 </div>
149 149 <div class="checkboxes">
150 150 ${h.checkbox('inherit_default_permissions',value=True)}
151 151 </div>
152 152 <span class="help-block">${h.literal(_('Select to inherit permissions from %s settings. '
153 153 'With this selected below options does not have any action') % h.link_to('default', url('edit_permission', id='default')))}</span>
154 154 </div>
155 <div id="inherit_overlay" style="${'opacity:0.3' if c.user.inherit_default_permissions else ''}" >
155 <div id="inherit_overlay" style="${'opacity:0.3' if c.user.inherit_default_permissions else ''}" >
156 156 <div class="field">
157 157 <div class="label label-checkbox">
158 158 <label for="create_repo_perm">${_('Create repositories')}:</label>
159 159 </div>
160 160 <div class="checkboxes">
161 161 ${h.checkbox('create_repo_perm',value=True)}
162 162 </div>
163 163 </div>
164 164 <div class="field">
165 165 <div class="label label-checkbox">
166 166 <label for="fork_repo_perm">${_('Fork repositories')}:</label>
167 167 </div>
168 168 <div class="checkboxes">
169 169 ${h.checkbox('fork_repo_perm',value=True)}
170 170 </div>
171 171 </div>
172 </div>
172 </div>
173 173 <div class="buttons">
174 174 ${h.submit('save',_('Save'),class_="ui-btn large")}
175 175 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
176 176 </div>
177 177 </div>
178 178 </div>
179 179 ${h.end_form()}
180 180
181 181 ## permissions overview
182 182 <div id="perms" class="table">
183 183 %for section in sorted(c.perm_user.permissions.keys()):
184 184 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
185 185 %if not c.perm_user.permissions[section]:
186 186 <span class="empty_data">${_('Nothing here yet')}</span>
187 187 %else:
188 188 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
189 189 <table id="tbl_list_${section}">
190 190 <thead>
191 191 <tr>
192 192 <th class="left">${_('Name')}</th>
193 193 <th class="left">${_('Permission')}</th>
194 194 <th class="left">${_('Edit Permission')}</th>
195 195 </thead>
196 196 <tbody>
197 197 %for k in c.perm_user.permissions[section]:
198 198 <%
199 199 if section != 'global':
200 200 section_perm = c.perm_user.permissions[section].get(k)
201 201 _perm = section_perm.split('.')[-1]
202 202 else:
203 203 _perm = section_perm = None
204 204 %>
205 205 <tr>
206 206 <td>
207 207 %if section == 'repositories':
208 208 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
209 209 %elif section == 'repositories_groups':
210 210 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
211 211 %else:
212 212 ${h.get_permission_name(k)}
213 213 %endif
214 214 </td>
215 215 <td>
216 216 %if section == 'global':
217 217 ${h.bool2icon(k.split('.')[-1] != 'none')}
218 218 %else:
219 219 <span class="perm_tag ${_perm}">${section_perm}</span>
220 220 %endif
221 221 </td>
222 222 <td>
223 223 %if section == 'repositories':
224 224 <a href="${h.url('edit_repo',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
225 225 %elif section == 'repositories_groups':
226 226 <a href="${h.url('edit_repos_group',id=k,anchor='permissions_manage')}">${_('edit')}</a>
227 227 %else:
228 228 --
229 229 %endif
230 230 </td>
231 231 </tr>
232 232 %endfor
233 233 </tbody>
234 234 </table>
235 235 </div>
236 236 %endif
237 237 %endfor
238 238 </div>
239 239 </div>
240 240 <div class="box box-left">
241 241 <!-- box / title -->
242 242 <div class="title">
243 243 <h5>${_('Email addresses')}</h5>
244 244 </div>
245 245
246 246 <div class="emails_wrap">
247 247 <table class="noborder">
248 248 %for em in c.user_email_map:
249 249 <tr>
250 250 <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(em.user.email,16)}"/> </div></td>
251 251 <td><div class="email">${em.email}</div></td>
252 252 <td>
253 253 ${h.form(url('user_emails_delete', id=c.user.user_id),method='delete')}
254 254 ${h.hidden('del_email',em.email_id)}
255 255 ${h.submit('remove_',_('delete'),id="remove_email_%s" % em.email_id,
256 256 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this email: %s') % em.email+"');")}
257 257 ${h.end_form()}
258 258 </td>
259 259 </tr>
260 260 %endfor
261 261 </table>
262 262 </div>
263 263
264 264 ${h.form(url('user_emails', id=c.user.user_id),method='put')}
265 265 <div class="form">
266 266 <!-- fields -->
267 267 <div class="fields">
268 268 <div class="field">
269 269 <div class="label">
270 270 <label for="email">${_('New email address')}:</label>
271 271 </div>
272 272 <div class="input">
273 273 ${h.text('new_email', class_='medium')}
274 274 </div>
275 275 </div>
276 276 <div class="buttons">
277 277 ${h.submit('save',_('Add'),class_="ui-btn large")}
278 278 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
279 279 </div>
280 280 </div>
281 281 </div>
282 282 ${h.end_form()}
283 283 </div>
284 284 </%def>
@@ -1,165 +1,165 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Edit users group')} ${c.users_group.users_group_name} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 11 ${h.link_to(_('UsersGroups'),h.url('users_groups'))}
12 12 &raquo;
13 13 ${_('edit')} "${c.users_group.users_group_name}"
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
17 17 ${self.menu('admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box box-left">
22 22 <!-- box / title -->
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 </div>
26 26 <!-- end box / title -->
27 27 ${h.form(url('users_group', id=c.users_group.users_group_id),method='put', id='edit_users_group')}
28 28 <div class="form">
29 29 <!-- fields -->
30 30 <div class="fields">
31 31 <div class="field">
32 32 <div class="label">
33 33 <label for="users_group_name">${_('Group name')}:</label>
34 34 </div>
35 35 <div class="input">
36 36 ${h.text('users_group_name',class_='small')}
37 37 </div>
38 38 </div>
39 39
40 40 <div class="field">
41 41 <div class="label label-checkbox">
42 42 <label for="users_group_active">${_('Active')}:</label>
43 43 </div>
44 44 <div class="checkboxes">
45 45 ${h.checkbox('users_group_active',value=True)}
46 46 </div>
47 47 </div>
48 48 <div class="field">
49 49 <div class="label">
50 50 <label for="users_group_active">${_('Members')}:</label>
51 51 </div>
52 52 <div class="select">
53 53 <table>
54 54 <tr>
55 55 <td>
56 56 <div>
57 57 <div style="float:left">
58 58 <div class="text" style="padding: 0px 0px 6px;">${_('Choosen group members')}</div>
59 59 ${h.select('users_group_members',[x[0] for x in c.group_members],c.group_members,multiple=True,size=8,style="min-width:210px")}
60 60 <div id="remove_all_elements" style="cursor:pointer;text-align:center">
61 61 ${_('Remove all elements')}
62 62 <img alt="remove" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_right.png')}"/>
63 63 </div>
64 64 </div>
65 65 <div style="float:left;width:20px;padding-top:50px">
66 66 <img alt="add" id="add_element"
67 67 style="padding:2px;cursor:pointer"
68 68 src="${h.url('/images/icons/arrow_left.png')}"/>
69 69 <br />
70 70 <img alt="remove" id="remove_element"
71 71 style="padding:2px;cursor:pointer"
72 72 src="${h.url('/images/icons/arrow_right.png')}"/>
73 73 </div>
74 74 <div style="float:left">
75 75 <div class="text" style="padding: 0px 0px 6px;">${_('Available members')}</div>
76 76 ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")}
77 77 <div id="add_all_elements" style="cursor:pointer;text-align:center">
78 78 <img alt="add" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_left.png')}"/>
79 79 ${_('Add all elements')}
80 80 </div>
81 81 </div>
82 82 </div>
83 83 </td>
84 84 </tr>
85 85 </table>
86 86 </div>
87 87
88 88 </div>
89 89 <div class="buttons">
90 90 ${h.submit('save',_('save'),class_="ui-btn large")}
91 91 </div>
92 92 </div>
93 93 </div>
94 94 ${h.end_form()}
95 95 </div>
96 96
97 97 <div class="box box-right">
98 98 <!-- box / title -->
99 99 <div class="title">
100 100 <h5>${_('Permissions')}</h5>
101 101 </div>
102 102 ${h.form(url('users_group_perm', id=c.users_group.users_group_id), method='put')}
103 103 <div class="form">
104 104 <!-- fields -->
105 105 <div class="fields">
106 106 <div class="field">
107 107 <div class="label label-checkbox">
108 108 <label for="inherit_permissions">${_('Inherit default permissions')}:</label>
109 109 </div>
110 110 <div class="checkboxes">
111 111 ${h.checkbox('inherit_default_permissions',value=True)}
112 112 </div>
113 113 <span class="help-block">${h.literal(_('Select to inherit permissions from %s settings. '
114 114 'With this selected below options does not have any action') % h.link_to('default', url('edit_permission', id='default')))}</span>
115 </div>
115 </div>
116 116 <div id="inherit_overlay" style="${'opacity:0.3' if c.users_group.inherit_default_permissions else ''}" >
117 117 <div class="field">
118 118 <div class="label label-checkbox">
119 119 <label for="create_repo_perm">${_('Create repositories')}:</label>
120 120 </div>
121 121 <div class="checkboxes">
122 122 ${h.checkbox('create_repo_perm',value=True)}
123 123 </div>
124 124 </div>
125 125 <div class="field">
126 126 <div class="label label-checkbox">
127 127 <label for="fork_repo_perm">${_('Fork repositories')}:</label>
128 128 </div>
129 129 <div class="checkboxes">
130 130 ${h.checkbox('fork_repo_perm',value=True)}
131 131 </div>
132 132 </div>
133 </div>
133 </div>
134 134 <div class="buttons">
135 135 ${h.submit('save',_('Save'),class_="ui-btn large")}
136 136 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
137 137 </div>
138 138 </div>
139 139 </div>
140 140 ${h.end_form()}
141 141 </div>
142 142
143 143 <div class="box box-right">
144 144 <!-- box / title -->
145 145 <div class="title">
146 146 <h5>${_('Group members')}</h5>
147 147 </div>
148 148 <div class="group_members_wrap">
149 149 <ul class="group_members">
150 150 %for user in c.group_members_obj:
151 151 <li>
152 152 <div class="group_member">
153 153 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(user.email,24)}"/> </div>
154 154 <div>${user.username}</div>
155 155 <div>${user.full_name}</div>
156 156 </div>
157 157 </li>
158 158 %endfor
159 159 </ul>
160 160 </div>
161 161 </div>
162 162 <script type="text/javascript">
163 163 MultiSelectWidget('users_group_members','available_members','edit_users_group');
164 164 </script>
165 165 </%def>
@@ -1,20 +1,20 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="main.html"/>
3
4 User <b>${pr_user_created}</b> opened pull request for repository
3
4 User <b>${pr_user_created}</b> opened pull request for repository
5 5 ${pr_repo_url} and wants you to review changes.
6 6
7 7 <div>title: ${pr_title}</div>
8 8 <div>description:</div>
9 9 <p>
10 10 ${body}
11 11 </p>
12 12
13 13 <div>revisions for reviewing</div>
14 14 <ul>
15 15 %for r in pr_revisions:
16 16 <li>${r}</li>
17 17 %endfor
18 18 </ul>
19 19
20 20 View this pull request here: ${pr_url}
@@ -1,15 +1,15 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="main.html"/>
3
3
4 4 User <b>${pr_comment_user}</b> commented on pull request #${pr_id} for
5 5 repository ${pr_target_repo}
6 6
7 7 <p>
8 8 ${body}
9 9
10 10 %if status_change:
11 11 <span>New status -> ${status_change}</span>
12 12 %endif
13 13 </p>
14 14
15 15 View this comment here: ${pr_comment_url}
@@ -1,142 +1,142 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${_('%s files') % c.repo_name} - ${c.rhodecode_name}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(_(u'Home'),h.url('/'))}
9 9 &raquo;
10 10 ${h.link_to(c.repo_name,h.url('files_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('files')}
13 13 %if c.file:
14 14 @ r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
15 15 %endif
16 16 </%def>
17 17
18 18 <%def name="page_nav()">
19 19 ${self.menu('files')}
20 20 </%def>
21 21
22 22 <%def name="main()">
23 23 <div class="box">
24 24 <!-- box / title -->
25 25 <div class="title">
26 26 ${self.breadcrumbs()}
27 27 <ul class="links">
28 28 <li>
29 29 <span style="text-transform: uppercase;"><a href="#">${_('branch')}: ${c.changeset.branch}</a></span>
30 30 </li>
31 31 </ul>
32 32 </div>
33 33 <div class="table">
34 34 <div id="files_data">
35 35 <%include file='files_ypjax.html'/>
36 36 </div>
37 37 </div>
38 38 </div>
39 39
40 40 <script type="text/javascript">
41 41 var CACHE = {};
42 42 var CACHE_EXPIRE = 60*1000; //cache for 60s
43 43 //used to construct links from the search list
44 44 var node_list_url = '${h.url("files_home",repo_name=c.repo_name,revision='__REV__',f_path='__FPATH__')}';
45 45 //send the nodelist request to this url
46 46 var url_base = '${h.url("files_nodelist_home",repo_name=c.repo_name,revision='__REV__',f_path='__FPATH__')}';
47 47
48 48 var ypjax_links = function(){
49 49 YUE.on(YUQ('.ypjax-link'), 'click',function(e){
50
50
51 51 //don't do ypjax on middle click
52 if(e.which == 2 || !History.enabled){
52 if(e.which == 2 || !History.enabled){
53 53 return true;
54 54 }
55
55
56 56 var el = e.currentTarget;
57 57 var url = el.href;
58 58
59 59 var _base_url = '${h.url("files_home",repo_name=c.repo_name,revision='',f_path='')}';
60 60 _base_url = _base_url.replace('//','/')
61
61
62 62 //extract rev and the f_path from url.
63 63 parts = url.split(_base_url)
64 64 if(parts.length != 2){
65 65 return false;
66 66 }
67
67
68 68 var parts2 = parts[1].split('/');
69 69 var rev = parts2.shift(); // pop the first element which is the revision
70 70 var f_path = parts2.join('/');
71
71
72 72 var title = "${_('%s files') % c.repo_name}" + " - " + f_path;
73
73
74 74 var _node_list_url = node_list_url.replace('__REV__',rev);
75 75 var _url_base = url_base.replace('__REV__',rev).replace('__FPATH__', f_path);
76 76
77 77 // Change our States and save some data for handling events
78 78 var data = {url:url,title:title, url_base:_url_base,
79 79 node_list_url:_node_list_url};
80 History.pushState(data, title, url);
81
80 History.pushState(data, title, url);
81
82 82 //now we're sure that we can do ypjax things
83 83 YUE.preventDefault(e)
84 84 return false;
85 85 });
86 86 }
87 87
88 88 var callbacks = function(State){
89 89 ypjax_links();
90 90 tooltip_activate();
91 91 fileBrowserListeners(State.url, State.data.node_list_url, State.data.url_base);
92 92 // Inform Google Analytics of the change
93 93 if ( typeof window.pageTracker !== 'undefined' ) {
94 94 window.pageTracker._trackPageview(State.url);
95 }
95 }
96 96 }
97 97
98 YUE.onDOMReady(function(){
98 YUE.onDOMReady(function(){
99 99 ypjax_links();
100 100 var container = 'files_data';
101 101 //Bind to StateChange Event
102 102 History.Adapter.bind(window,'statechange',function(){
103 103 var State = History.getState();
104 104 cache_key = State.url;
105 105 //check if we have this request in cache maybe ?
106 106 var _cache_obj = CACHE[cache_key];
107 107 var _cur_time = new Date().getTime();
108 108 // get from cache if it's there and not yet expired !
109 109 if(_cache_obj !== undefined && _cache_obj[0] > _cur_time){
110 110 YUD.get(container).innerHTML=_cache_obj[1];
111 111 YUD.setStyle(container,'opacity','1.0');
112 112
113 113 //callbacks after ypjax call
114 114 callbacks(State);
115 115 }
116 116 else{
117 117 ypjax(State.url,container,function(o){
118 118 //callbacks after ypjax call
119 119 callbacks(State);
120 120 if (o !== undefined){
121 121 //store our request in cache
122 122 var _expire_on = new Date().getTime()+CACHE_EXPIRE;
123 123 CACHE[cache_key] = [_expire_on, o.responseText];
124 124 }
125 125 });
126 126 }
127 });
128
127 });
128
129 129 // init the search filter
130 130 var _State = {
131 131 url: "${h.url.current()}",
132 132 data: {
133 133 node_list_url: node_list_url.replace('__REV__',"${c.changeset.raw_id}"),
134 134 url_base: url_base.replace('__REV__',"${c.changeset.raw_id}").replace('__FPATH__', "${h.safe_unicode(c.file.path)}")
135 135 }
136 136 }
137 137 fileBrowserListeners(_State.url, _State.data.node_list_url, _State.data.url_base);
138 138 });
139 139
140 140 </script>
141 141
142 142 </%def>
@@ -1,18 +1,18 b''
1 1 %if c.file:
2 2 <h3 class="files_location">
3 3 ${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.file.path)}
4 4 %if c.annotate:
5 5 - ${_('annotation')}
6 6 %endif
7 7 </h3>
8 8 %if c.file.is_dir():
9 9 <%include file='files_browser.html'/>
10 10 %else:
11 11 <%include file='files_source.html'/>
12 %endif
12 %endif
13 13 %else:
14 14 <h2>
15 15 <a href="#" onClick="javascript:parent.history.back();" target="main">${_('Go back')}</a>
16 16 ${_('No files at given path')}: "${c.f_path or "/"}"
17 17 </h2>
18 18 %endif
@@ -1,195 +1,195 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(_(u'Home'),h.url('/'))}
9 9 &raquo;
10 10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('Pull request #%s') % c.pull_request.pull_request_id}
13 13 </%def>
14 14
15 15 <%def name="main()">
16 16
17 17 <div class="box">
18 18 <!-- box / title -->
19 19 <div class="title">
20 20 ${self.breadcrumbs()}
21 21 </div>
22 22 %if c.pull_request.is_closed():
23 23 <div style="padding:10px; font-size:22px;width:100%;text-align: center; color:#88D882">${_('Closed %s') % (h.age(c.pull_request.updated_on))} ${_('with status %s') % h.changeset_status_lbl(c.current_changeset_status)}</div>
24 24 %endif
25 25 <h3>${_('Title')}: ${c.pull_request.title}</h3>
26
26
27 27 <div class="form">
28 28 <div id="summary" class="fields">
29 29 <div class="field">
30 30 <div class="label-summary">
31 31 <label>${_('Status')}:</label>
32 32 </div>
33 33 <div class="input">
34 34 <div class="changeset-status-container" style="float:none;clear:both">
35 35 %if c.current_changeset_status:
36 36 <div title="${_('Pull request status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.current_changeset_status)}]</div>
37 37 <div class="changeset-status-ico" style="padding:1px 4px"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
38 38 %endif
39 39 </div>
40 40 </div>
41 41 </div>
42 42 <div class="field">
43 43 <div class="label-summary">
44 44 <label>${_('Still not reviewed by')}:</label>
45 45 </div>
46 46 <div class="input">
47 47 <div class="tooltip" title="${h.tooltip(','.join([x.username for x in c.pull_request_pending_reviewers]))}">${ungettext('%d reviewer', '%d reviewers',len(c.pull_request_pending_reviewers)) % len(c.pull_request_pending_reviewers)}</div>
48 48 </div>
49 </div>
49 </div>
50 50 </div>
51 </div>
51 </div>
52 52 <div style="white-space:pre-wrap;padding:3px 3px 5px 20px">${h.literal(c.pull_request.description)}</div>
53 53 <div style="padding:4px 4px 10px 20px">
54 54 <div>${_('Created on')}: ${h.fmt_date(c.pull_request.created_on)}</div>
55 55 </div>
56 56
57 57 <div style="min-height:160px">
58 58 ##DIFF
59 59 <div class="table" style="float:left;clear:none">
60 60 <div id="body" class="diffblock">
61 61 <div style="white-space:pre-wrap;padding:5px">${_('Compare view')}</div>
62 62 </div>
63 63 <div id="changeset_compare_view_content">
64 64 ##CS
65 65 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Incoming changesets')}</div>
66 66 <%include file="/compare/compare_cs.html" />
67 67
68 68 ## FILES
69 69 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
70 70 <div class="cs_files">
71 71 %for fid, change, f, stat in c.files:
72 72 <div class="cs_${change}">
73 73 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
74 74 <div class="changes">${h.fancy_file_stats(stat)}</div>
75 75 </div>
76 76 %endfor
77 77 </div>
78 78 </div>
79 79 </div>
80 80 ## REVIEWERS
81 81 <div style="float:left; border-left:1px dashed #eee">
82 82 <h4>${_('Pull request reviewers')}</h4>
83 83 <div id="reviewers" style="padding:0px 0px 0px 15px">
84 84 ## members goes here !
85 85 <div class="group_members_wrap">
86 86 <ul id="review_members" class="group_members">
87 87 %for member,status in c.pull_request_reviewers:
88 88 <li id="reviewer_${member.user_id}">
89 89 <div class="reviewers_member">
90 90 <div style="float:left;padding:0px 3px 0px 0px" class="tooltip" title="${h.tooltip(h.changeset_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
91 91 <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
92 92 </div>
93 93 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
94 94 <div style="float:left">${member.full_name} (${_('owner')})</div>
95 95 <input type="hidden" value="${member.user_id}" name="review_members" />
96 96 %if not c.pull_request.is_closed() and (h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.author.user_id == c.rhodecode_user.user_id):
97 97 <span class="delete_icon action_button" onclick="removeReviewer(${member.user_id})"></span>
98 98 %endif
99 99 </div>
100 100 </li>
101 101 %endfor
102 102 </ul>
103 103 </div>
104 104 %if not c.pull_request.is_closed():
105 105 <div class='ac'>
106 106 %if h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.author.user_id == c.rhodecode_user.user_id:
107 107 <div class="reviewer_ac">
108 108 ${h.text('user', class_='yui-ac-input')}
109 109 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
110 110 <div id="reviewers_container"></div>
111 111 </div>
112 112 <div style="padding:0px 10px">
113 113 <span id="update_pull_request" class="ui-btn xsmall">${_('save')}</span>
114 114 </div>
115 115 %endif
116 116 </div>
117 117 %endif
118 118 </div>
119 119 </div>
120 120 </div>
121 121 <script>
122 122 var _USERS_AC_DATA = ${c.users_array|n};
123 123 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
124 124 AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
125 125 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
126 126 AJAX_UPDATE_PULLREQUEST = "${url('pullrequest_update',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}"
127 127 </script>
128 128
129 129 ## diff block
130 130 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
131 131 %for fid, change, f, stat in c.files:
132 132 ${diff_block.diff_block_simple([c.changes[fid]])}
133 133 %endfor
134 134
135 135 ## template for inline comment form
136 136 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
137 137 ${comment.comment_inline_form()}
138 138
139 139 ## render comments and inlines
140 140 ${comment.generate_comments()}
141 141
142 142 % if not c.pull_request.is_closed():
143 143 ## main comment form and it status
144 144 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
145 145 pull_request_id=c.pull_request.pull_request_id),
146 146 c.current_changeset_status,
147 147 close_btn=True)}
148 148 %endif
149 149
150 150 <script type="text/javascript">
151 151 YUE.onDOMReady(function(){
152 152 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
153 153
154 154 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
155 155 var show = 'none';
156 156 var target = e.currentTarget;
157 157 if(target.checked){
158 158 var show = ''
159 159 }
160 160 var boxid = YUD.getAttribute(target,'id_for');
161 161 var comments = YUQ('#{0} .inline-comments'.format(boxid));
162 162 for(c in comments){
163 163 YUD.setStyle(comments[c],'display',show);
164 164 }
165 165 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
166 166 for(c in btns){
167 167 YUD.setStyle(btns[c],'display',show);
168 168 }
169 169 })
170 170
171 171 YUE.on(YUQ('.line'),'click',function(e){
172 172 var tr = e.currentTarget;
173 173 injectInlineForm(tr);
174 174 });
175 175
176 176 // inject comments into they proper positions
177 177 var file_comments = YUQ('.inline-comment-placeholder');
178 178 renderInlineComments(file_comments);
179 179
180 180 YUE.on(YUD.get('update_pull_request'),'click',function(e){
181 181
182 182 var reviewers_ids = [];
183 183 var ids = YUQ('#review_members input');
184 184 for(var i=0; i<ids.length;i++){
185 185 var id = ids[i].value
186 186 reviewers_ids.push(id);
187 187 }
188 188 updateReviewers(reviewers_ids);
189 189 })
190 190 })
191 191 </script>
192 192
193 193 </div>
194 194
195 195 </%def>
@@ -1,710 +1,710 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${_('%s Summary') % c.repo_name} - ${c.rhodecode_name}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(_(u'Home'),h.url('/'))}
9 9 &raquo;
10 10 ${h.repo_link(c.dbrepo.groups_and_repo)}
11 11 &raquo;
12 12 ${_('summary')}
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 16 ${self.menu('summary')}
17 17 </%def>
18 18
19 19 <%def name="head_extra()">
20 20 <link href="${h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('repo %s ATOM feed') % c.repo_name}" type="application/atom+xml" />
21 21 <link href="${h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('repo %s RSS feed') % c.repo_name}" type="application/rss+xml" />
22 22 </%def>
23 23
24 24 <%def name="main()">
25 25 <%
26 26 summary = lambda n:{False:'summary-short'}.get(n)
27 27 %>
28 28 %if c.show_stats:
29 29 <div class="box box-left">
30 30 %else:
31 31 <div class="box">
32 32 %endif
33 33 <!-- box / title -->
34 34 <div class="title">
35 35 ${self.breadcrumbs()}
36 36 </div>
37 37 <!-- end box / title -->
38 38 <div class="form">
39 39 <div id="summary" class="fields">
40 40
41 41 <div class="field">
42 42 <div class="label-summary">
43 43 <label>${_('Name')}:</label>
44 44 </div>
45 45 <div class="input ${summary(c.show_stats)}">
46 46 <div style="float:right;padding:5px 0px 0px 5px">
47 47 %if c.rhodecode_user.username != 'default':
48 48 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='rss_icon')}
49 49 ${h.link_to(_('ATOM'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='atom_icon')}
50 50 %else:
51 51 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name),class_='rss_icon')}
52 52 ${h.link_to(_('ATOM'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name),class_='atom_icon')}
53 53 %endif
54 54 </div>
55 55 %if c.rhodecode_user.username != 'default':
56 56 %if c.following:
57 57 <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
58 58 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
59 59 </span>
60 60 %else:
61 61 <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
62 62 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
63 63 </span>
64 64 %endif
65 65 %endif:
66 66 ##REPO TYPE
67 67 %if h.is_hg(c.dbrepo):
68 68 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
69 69 %endif
70 70 %if h.is_git(c.dbrepo):
71 71 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
72 72 %endif
73 73
74 74 ##PUBLIC/PRIVATE
75 75 %if c.dbrepo.private:
76 76 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
77 77 %else:
78 78 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
79 79 %endif
80 80
81 81 ##REPO NAME
82 82 <span class="repo_name" title="${_('Non changable ID %s') % c.dbrepo.repo_id}">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
83 83
84 84 ##FORK
85 85 %if c.dbrepo.fork:
86 86 <div style="margin-top:5px;clear:both"">
87 87 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}"><img class="icon" alt="${_('public')}" title="${_('Fork of')} ${c.dbrepo.fork.repo_name}" src="${h.url('/images/icons/arrow_divide.png')}"/>
88 88 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
89 89 </a>
90 90 </div>
91 91 %endif
92 92 ##REMOTE
93 93 %if c.dbrepo.clone_uri:
94 94 <div style="margin-top:5px;clear:both">
95 95 <a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}"><img class="icon" alt="${_('remote clone')}" title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}" src="${h.url('/images/icons/connect.png')}"/>
96 96 ${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
97 97 </a>
98 98 </div>
99 99 %endif
100 100 </div>
101 101 </div>
102 102
103 103 <div class="field">
104 104 <div class="label-summary">
105 105 <label>${_('Description')}:</label>
106 106 </div>
107 107 %if c.visual.stylify_metatags:
108 108 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.desc_stylize(c.dbrepo.description))}</div>
109 109 %else:
110 110 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(c.dbrepo.description)}</div>
111 %endif
111 %endif
112 112 </div>
113 113
114 114 <div class="field">
115 115 <div class="label-summary">
116 116 <label>${_('Contact')}:</label>
117 117 </div>
118 118 <div class="input ${summary(c.show_stats)}">
119 119 <div class="gravatar">
120 120 <img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
121 121 </div>
122 122 ${_('Username')}: ${c.dbrepo.user.username}<br/>
123 123 ${_('Name')}: ${c.dbrepo.user.name} ${c.dbrepo.user.lastname}<br/>
124 124 ${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
125 125 </div>
126 126 </div>
127 127
128 128 <div class="field">
129 129 <div class="label-summary">
130 130 <label>${_('Clone url')}:</label>
131 131 </div>
132 132 <div class="input ${summary(c.show_stats)}">
133 133 <div style="display:none" id="clone_by_name" class="ui-btn clone">${_('Show by Name')}</div>
134 134 <div id="clone_by_id" class="ui-btn clone">${_('Show by ID')}</div>
135 135 <input style="width:80%;margin-left:105px" type="text" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
136 136 <input style="display:none;width:80%;margin-left:105px" type="text" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}"/>
137 137 </div>
138 138 </div>
139 139
140 140 <div class="field">
141 141 <div class="label-summary">
142 142 <label>${_('Trending files')}:</label>
143 143 </div>
144 144 <div class="input ${summary(c.show_stats)}">
145 145 %if c.show_stats:
146 146 <div id="lang_stats"></div>
147 147 %else:
148 148 ${_('Statistics are disabled for this repository')}
149 149 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
150 150 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
151 151 %endif
152 152 %endif
153 153 </div>
154 154 </div>
155 155
156 156 <div class="field">
157 157 <div class="label-summary">
158 158 <label>${_('Download')}:</label>
159 159 </div>
160 160 <div class="input ${summary(c.show_stats)}">
161 161 %if len(c.rhodecode_repo.revisions) == 0:
162 162 ${_('There are no downloads yet')}
163 163 %elif c.enable_downloads is False:
164 164 ${_('Downloads are disabled for this repository')}
165 165 %if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
166 166 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
167 167 %endif
168 168 %else:
169 169 ${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
170 170 <span id="${'zip_link'}">${h.link_to(_('Download as zip'), h.url('files_archive_home',repo_name=c.dbrepo.repo_name,fname='tip.zip'),class_="archive_icon ui-btn")}</span>
171 171 <span style="vertical-align: bottom">
172 172 <input id="archive_subrepos" type="checkbox" name="subrepos" />
173 173 <label for="archive_subrepos" class="tooltip" title="${h.tooltip(_('Check this to download archive with subrepos'))}" >${_('with subrepos')}</label>
174 174 </span>
175 175 %endif
176 176 </div>
177 177 </div>
178 178 </div>
179 179 </div>
180 180 </div>
181 181
182 182 %if c.show_stats:
183 183 <div class="box box-right" style="min-height:455px">
184 184 <!-- box / title -->
185 185 <div class="title">
186 186 <h5>${_('Commit activity by day / author')}</h5>
187 187 </div>
188 188
189 189 <div class="graph">
190 190 <div style="padding:0 10px 10px 17px;">
191 191 %if c.no_data:
192 192 ${c.no_data_msg}
193 193 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
194 194 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
195 195 %endif
196 196 %else:
197 197 ${_('Stats gathered: ')} ${c.stats_percentage}%
198 198 %endif
199 199 </div>
200 200 <div id="commit_history" style="width:450px;height:300px;float:left"></div>
201 201 <div style="clear: both;height: 10px"></div>
202 202 <div id="overview" style="width:450px;height:100px;float:left"></div>
203 203
204 204 <div id="legend_data" style="clear:both;margin-top:10px;">
205 205 <div id="legend_container"></div>
206 206 <div id="legend_choices">
207 207 <table id="legend_choices_tables" class="noborder" style="font-size:smaller;color:#545454"></table>
208 208 </div>
209 209 </div>
210 210 </div>
211 211 </div>
212 212 %endif
213 213
214 214 <div class="box">
215 215 <div class="title">
216 216 <div class="breadcrumbs">
217 217 %if c.repo_changesets:
218 218 ${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}
219 219 %else:
220 220 ${_('Quick start')}
221 221 %endif
222 222 </div>
223 223 </div>
224 224 <div class="table">
225 225 <div id="shortlog_data">
226 226 <%include file='../shortlog/shortlog_data.html'/>
227 227 </div>
228 228 </div>
229 229 </div>
230 230
231 231 %if c.readme_data:
232 232 <div id="readme" class="box header-pos-fix" style="background-color: #FAFAFA">
233 233 <div id="readme" class="title" title="${_("Readme file at revision '%s'" % c.rhodecode_db_repo.landing_rev)}">
234 234 <div class="breadcrumbs">
235 235 <a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a>
236 236 <a class="permalink" href="#readme" title="${_('Permalink to this readme')}">&para;</a>
237 237 </div>
238 238 </div>
239 239 <div id="readme" class="readme">
240 240 <div class="readme_box">
241 241 ${c.readme_data|n}
242 242 </div>
243 243 </div>
244 244 </div>
245 245 %endif
246 246
247 247 <script type="text/javascript">
248 248 var clone_url = 'clone_url';
249 249 YUE.on(clone_url,'click',function(e){
250 250 if(YUD.hasClass(clone_url,'selected')){
251 251 return
252 252 }
253 253 else{
254 254 YUD.addClass(clone_url,'selected');
255 255 YUD.get(clone_url).select();
256 256 }
257 257 })
258 258
259 259 YUE.on('clone_by_name','click',function(e){
260 260 // show url by name and hide name button
261 261 YUD.setStyle('clone_url','display','');
262 262 YUD.setStyle('clone_by_name','display','none');
263 263
264 264 // hide url by id and show name button
265 265 YUD.setStyle('clone_by_id','display','');
266 266 YUD.setStyle('clone_url_id','display','none');
267 267
268 268 })
269 269 YUE.on('clone_by_id','click',function(e){
270 270
271 271 // show url by id and hide id button
272 272 YUD.setStyle('clone_by_id','display','none');
273 273 YUD.setStyle('clone_url_id','display','');
274 274
275 275 // hide url by name and show id button
276 276 YUD.setStyle('clone_by_name','display','');
277 277 YUD.setStyle('clone_url','display','none');
278 278 })
279 279
280 280
281 281 var tmpl_links = {};
282 282 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
283 283 tmpl_links["${archive['type']}"] = '${h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.dbrepo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='archive_icon ui-btn')}';
284 284 %endfor
285 285
286 286 YUE.on(['download_options','archive_subrepos'],'change',function(e){
287 287 var sm = YUD.get('download_options');
288 288 var new_cs = sm.options[sm.selectedIndex];
289 289
290 290 for(k in tmpl_links){
291 291 var s = YUD.get(k+'_link');
292 292 if(s){
293 293 var title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
294 294 title_tmpl= title_tmpl.replace('__CS_NAME__',new_cs.text);
295 295 title_tmpl = title_tmpl.replace('__CS_EXT__',k);
296 296
297 297 var url = tmpl_links[k].replace('__CS__',new_cs.value);
298 298 var subrepos = YUD.get('archive_subrepos').checked;
299 299 url = url.replace('__SUB__',subrepos);
300 300 url = url.replace('__NAME__',title_tmpl);
301 301 s.innerHTML = url
302 302 }
303 303 }
304 304 });
305 305 </script>
306 306 %if c.show_stats:
307 307 <script type="text/javascript">
308 308 var data = ${c.trending_languages|n};
309 309 var total = 0;
310 310 var no_data = true;
311 311 var tbl = document.createElement('table');
312 312 tbl.setAttribute('class','trending_language_tbl');
313 313 var cnt = 0;
314 314 for (var i=0;i<data.length;i++){
315 315 total+= data[i][1].count;
316 316 }
317 317 for (var i=0;i<data.length;i++){
318 318 cnt += 1;
319 319 no_data = false;
320 320
321 321 var hide = cnt>2;
322 322 var tr = document.createElement('tr');
323 323 if (hide){
324 324 tr.setAttribute('style','display:none');
325 325 tr.setAttribute('class','stats_hidden');
326 326 }
327 327 var k = data[i][0];
328 328 var obj = data[i][1];
329 329 var percentage = Math.round((obj.count/total*100),2);
330 330
331 331 var td1 = document.createElement('td');
332 332 td1.width = 150;
333 333 var trending_language_label = document.createElement('div');
334 334 trending_language_label.innerHTML = obj.desc+" ("+k+")";
335 335 td1.appendChild(trending_language_label);
336 336
337 337 var td2 = document.createElement('td');
338 338 td2.setAttribute('style','padding-right:14px !important');
339 339 var trending_language = document.createElement('div');
340 340 var nr_files = obj.count+" ${_('files')}";
341 341
342 342 trending_language.title = k+" "+nr_files;
343 343
344 344 if (percentage>22){
345 345 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
346 346 }
347 347 else{
348 348 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
349 349 }
350 350
351 351 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
352 352 trending_language.style.width=percentage+"%";
353 353 td2.appendChild(trending_language);
354 354
355 355 tr.appendChild(td1);
356 356 tr.appendChild(td2);
357 357 tbl.appendChild(tr);
358 358 if(cnt == 3){
359 359 var show_more = document.createElement('tr');
360 360 var td = document.createElement('td');
361 361 lnk = document.createElement('a');
362 362
363 363 lnk.href='#';
364 364 lnk.innerHTML = "${_('show more')}";
365 365 lnk.id='code_stats_show_more';
366 366 td.appendChild(lnk);
367 367
368 368 show_more.appendChild(td);
369 369 show_more.appendChild(document.createElement('td'));
370 370 tbl.appendChild(show_more);
371 371 }
372 372
373 373 }
374 374
375 375 YUD.get('lang_stats').appendChild(tbl);
376 376 YUE.on('code_stats_show_more','click',function(){
377 377 l = YUD.getElementsByClassName('stats_hidden')
378 378 for (e in l){
379 379 YUD.setStyle(l[e],'display','');
380 380 };
381 381 YUD.setStyle(YUD.get('code_stats_show_more'),
382 382 'display','none');
383 383 });
384 384 </script>
385 385 <script type="text/javascript">
386 386 /**
387 387 * Plots summary graph
388 388 *
389 389 * @class SummaryPlot
390 390 * @param {from} initial from for detailed graph
391 391 * @param {to} initial to for detailed graph
392 392 * @param {dataset}
393 393 * @param {overview_dataset}
394 394 */
395 395 function SummaryPlot(from,to,dataset,overview_dataset) {
396 396 var initial_ranges = {
397 397 "xaxis":{
398 398 "from":from,
399 399 "to":to,
400 400 },
401 401 };
402 402 var dataset = dataset;
403 403 var overview_dataset = [overview_dataset];
404 404 var choiceContainer = YUD.get("legend_choices");
405 405 var choiceContainerTable = YUD.get("legend_choices_tables");
406 406 var plotContainer = YUD.get('commit_history');
407 407 var overviewContainer = YUD.get('overview');
408 408
409 409 var plot_options = {
410 410 bars: {show:true,align:'center',lineWidth:4},
411 411 legend: {show:true, container:"legend_container"},
412 412 points: {show:true,radius:0,fill:false},
413 413 yaxis: {tickDecimals:0,},
414 414 xaxis: {
415 415 mode: "time",
416 416 timeformat: "%d/%m",
417 417 min:from,
418 418 max:to,
419 419 },
420 420 grid: {
421 421 hoverable: true,
422 422 clickable: true,
423 423 autoHighlight:true,
424 424 color: "#999"
425 425 },
426 426 //selection: {mode: "x"}
427 427 };
428 428 var overview_options = {
429 429 legend:{show:false},
430 430 bars: {show:true,barWidth: 2,},
431 431 shadowSize: 0,
432 432 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
433 433 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
434 434 grid: {color: "#999",},
435 435 selection: {mode: "x"}
436 436 };
437 437
438 438 /**
439 439 *get dummy data needed in few places
440 440 */
441 441 function getDummyData(label){
442 442 return {"label":label,
443 443 "data":[{"time":0,
444 444 "commits":0,
445 445 "added":0,
446 446 "changed":0,
447 447 "removed":0,
448 448 }],
449 449 "schema":["commits"],
450 450 "color":'#ffffff',
451 451 }
452 452 }
453 453
454 454 /**
455 455 * generate checkboxes accordindly to data
456 456 * @param keys
457 457 * @returns
458 458 */
459 459 function generateCheckboxes(data) {
460 460 //append checkboxes
461 461 var i = 0;
462 462 choiceContainerTable.innerHTML = '';
463 463 for(var pos in data) {
464 464
465 465 data[pos].color = i;
466 466 i++;
467 467 if(data[pos].label != ''){
468 468 choiceContainerTable.innerHTML +=
469 469 '<tr><td><input type="checkbox" id="id_user_{0}" name="{0}" checked="checked" /> \
470 470 <label for="id_user_{0}">{0}</label></td></tr>'.format(data[pos].label);
471 471 }
472 472 }
473 473 }
474 474
475 475 /**
476 476 * ToolTip show
477 477 */
478 478 function showTooltip(x, y, contents) {
479 479 var div=document.getElementById('tooltip');
480 480 if(!div) {
481 481 div = document.createElement('div');
482 482 div.id="tooltip";
483 483 div.style.position="absolute";
484 484 div.style.border='1px solid #fdd';
485 485 div.style.padding='2px';
486 486 div.style.backgroundColor='#fee';
487 487 document.body.appendChild(div);
488 488 }
489 489 YUD.setStyle(div, 'opacity', 0);
490 490 div.innerHTML = contents;
491 491 div.style.top=(y + 5) + "px";
492 492 div.style.left=(x + 5) + "px";
493 493
494 494 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
495 495 anim.animate();
496 496 }
497 497
498 498 /**
499 499 * This function will detect if selected period has some changesets
500 500 for this user if it does this data is then pushed for displaying
501 501 Additionally it will only display users that are selected by the checkbox
502 502 */
503 503 function getDataAccordingToRanges(ranges) {
504 504
505 505 var data = [];
506 506 var new_dataset = {};
507 507 var keys = [];
508 508 var max_commits = 0;
509 509 for(var key in dataset){
510 510
511 511 for(var ds in dataset[key].data){
512 512 commit_data = dataset[key].data[ds];
513 513 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
514 514
515 515 if(new_dataset[key] === undefined){
516 516 new_dataset[key] = {data:[],schema:["commits"],label:key};
517 517 }
518 518 new_dataset[key].data.push(commit_data);
519 519 }
520 520 }
521 521 if (new_dataset[key] !== undefined){
522 522 data.push(new_dataset[key]);
523 523 }
524 524 }
525 525
526 526 if (data.length > 0){
527 527 return data;
528 528 }
529 529 else{
530 530 //just return dummy data for graph to plot itself
531 531 return [getDummyData('')];
532 532 }
533 533 }
534 534
535 535 /**
536 536 * redraw using new checkbox data
537 537 */
538 538 function plotchoiced(e,args){
539 539 var cur_data = args[0];
540 540 var cur_ranges = args[1];
541 541
542 542 var new_data = [];
543 543 var inputs = choiceContainer.getElementsByTagName("input");
544 544
545 545 //show only checked labels
546 546 for(var i=0; i<inputs.length; i++) {
547 547 var checkbox_key = inputs[i].name;
548 548
549 549 if(inputs[i].checked){
550 550 for(var d in cur_data){
551 551 if(cur_data[d].label == checkbox_key){
552 552 new_data.push(cur_data[d]);
553 553 }
554 554 }
555 555 }
556 556 else{
557 557 //push dummy data to not hide the label
558 558 new_data.push(getDummyData(checkbox_key));
559 559 }
560 560 }
561 561
562 562 var new_options = YAHOO.lang.merge(plot_options, {
563 563 xaxis: {
564 564 min: cur_ranges.xaxis.from,
565 565 max: cur_ranges.xaxis.to,
566 566 mode:"time",
567 567 timeformat: "%d/%m",
568 568 },
569 569 });
570 570 if (!new_data){
571 571 new_data = [[0,1]];
572 572 }
573 573 // do the zooming
574 574 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
575 575
576 576 plot.subscribe("plotselected", plotselected);
577 577
578 578 //resubscribe plothover
579 579 plot.subscribe("plothover", plothover);
580 580
581 581 // don't fire event on the overview to prevent eternal loop
582 582 overview.setSelection(cur_ranges, true);
583 583
584 584 }
585 585
586 586 /**
587 587 * plot only selected items from overview
588 588 * @param ranges
589 589 * @returns
590 590 */
591 591 function plotselected(ranges,cur_data) {
592 592 //updates the data for new plot
593 593 var data = getDataAccordingToRanges(ranges);
594 594 generateCheckboxes(data);
595 595
596 596 var new_options = YAHOO.lang.merge(plot_options, {
597 597 xaxis: {
598 598 min: ranges.xaxis.from,
599 599 max: ranges.xaxis.to,
600 600 mode:"time",
601 601 timeformat: "%d/%m",
602 602 },
603 603 });
604 604 // do the zooming
605 605 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
606 606
607 607 plot.subscribe("plotselected", plotselected);
608 608
609 609 //resubscribe plothover
610 610 plot.subscribe("plothover", plothover);
611 611
612 612 // don't fire event on the overview to prevent eternal loop
613 613 overview.setSelection(ranges, true);
614 614
615 615 //resubscribe choiced
616 616 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
617 617 }
618 618
619 619 var previousPoint = null;
620 620
621 621 function plothover(o) {
622 622 var pos = o.pos;
623 623 var item = o.item;
624 624
625 625 //YUD.get("x").innerHTML = pos.x.toFixed(2);
626 626 //YUD.get("y").innerHTML = pos.y.toFixed(2);
627 627 if (item) {
628 628 if (previousPoint != item.datapoint) {
629 629 previousPoint = item.datapoint;
630 630
631 631 var tooltip = YUD.get("tooltip");
632 632 if(tooltip) {
633 633 tooltip.parentNode.removeChild(tooltip);
634 634 }
635 635 var x = item.datapoint.x.toFixed(2);
636 636 var y = item.datapoint.y.toFixed(2);
637 637
638 638 if (!item.series.label){
639 639 item.series.label = 'commits';
640 640 }
641 641 var d = new Date(x*1000);
642 642 var fd = d.toDateString()
643 643 var nr_commits = parseInt(y);
644 644
645 645 var cur_data = dataset[item.series.label].data[item.dataIndex];
646 646 var added = cur_data.added;
647 647 var changed = cur_data.changed;
648 648 var removed = cur_data.removed;
649 649
650 650 var nr_commits_suffix = " ${_('commits')} ";
651 651 var added_suffix = " ${_('files added')} ";
652 652 var changed_suffix = " ${_('files changed')} ";
653 653 var removed_suffix = " ${_('files removed')} ";
654 654
655 655
656 656 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
657 657 if(added==1){added_suffix=" ${_('file added')} ";}
658 658 if(changed==1){changed_suffix=" ${_('file changed')} ";}
659 659 if(removed==1){removed_suffix=" ${_('file removed')} ";}
660 660
661 661 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
662 662 +'<br/>'+
663 663 nr_commits + nr_commits_suffix+'<br/>'+
664 664 added + added_suffix +'<br/>'+
665 665 changed + changed_suffix + '<br/>'+
666 666 removed + removed_suffix + '<br/>');
667 667 }
668 668 }
669 669 else {
670 670 var tooltip = YUD.get("tooltip");
671 671
672 672 if(tooltip) {
673 673 tooltip.parentNode.removeChild(tooltip);
674 674 }
675 675 previousPoint = null;
676 676 }
677 677 }
678 678
679 679 /**
680 680 * MAIN EXECUTION
681 681 */
682 682
683 683 var data = getDataAccordingToRanges(initial_ranges);
684 684 generateCheckboxes(data);
685 685
686 686 //main plot
687 687 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
688 688
689 689 //overview
690 690 var overview = YAHOO.widget.Flot(overviewContainer,
691 691 overview_dataset, overview_options);
692 692
693 693 //show initial selection on overview
694 694 overview.setSelection(initial_ranges);
695 695
696 696 plot.subscribe("plotselected", plotselected);
697 697 plot.subscribe("plothover", plothover)
698 698
699 699 overview.subscribe("plotselected", function (ranges) {
700 700 plot.setSelection(ranges);
701 701 });
702 702
703 703 // user choices on overview
704 704 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
705 705 }
706 706 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
707 707 </script>
708 708 %endif
709 709
710 710 </%def>
@@ -1,438 +1,437 b''
1 1 import os
2 2 import unittest
3 3 from rhodecode.tests import *
4 4
5 5 from rhodecode.model.repos_group import ReposGroupModel
6 6 from rhodecode.model.repo import RepoModel
7 7 from rhodecode.model.db import RepoGroup, User, UsersGroupRepoGroupToPerm
8 8 from rhodecode.model.user import UserModel
9 9
10 10 from rhodecode.model.meta import Session
11 11 from rhodecode.model.users_group import UsersGroupModel
12 12 from rhodecode.lib.auth import AuthUser
13 13
14 14
15 15 def _make_group(path, desc='desc', parent_id=None,
16 16 skip_if_exists=False):
17 17
18 18 gr = RepoGroup.get_by_group_name(path)
19 19 if gr and skip_if_exists:
20 20 return gr
21 21
22 22 gr = ReposGroupModel().create(path, desc, parent_id)
23 23 return gr
24 24
25 25
26 26 class TestPermissions(unittest.TestCase):
27 27 def __init__(self, methodName='runTest'):
28 28 super(TestPermissions, self).__init__(methodName=methodName)
29 29
30 30 def setUp(self):
31 31 self.u1 = UserModel().create_or_update(
32 32 username=u'u1', password=u'qweqwe',
33 33 email=u'u1@rhodecode.org', firstname=u'u1', lastname=u'u1'
34 34 )
35 35 self.u2 = UserModel().create_or_update(
36 36 username=u'u2', password=u'qweqwe',
37 37 email=u'u2@rhodecode.org', firstname=u'u2', lastname=u'u2'
38 38 )
39 39 self.u3 = UserModel().create_or_update(
40 40 username=u'u3', password=u'qweqwe',
41 41 email=u'u3@rhodecode.org', firstname=u'u3', lastname=u'u3'
42 42 )
43 43 self.anon = User.get_by_username('default')
44 44 self.a1 = UserModel().create_or_update(
45 45 username=u'a1', password=u'qweqwe',
46 46 email=u'a1@rhodecode.org', firstname=u'a1', lastname=u'a1', admin=True
47 47 )
48 48 Session().commit()
49 49
50 50 def tearDown(self):
51 51 if hasattr(self, 'test_repo'):
52 52 RepoModel().delete(repo=self.test_repo)
53 53 UserModel().delete(self.u1)
54 54 UserModel().delete(self.u2)
55 55 UserModel().delete(self.u3)
56 56 UserModel().delete(self.a1)
57 57 if hasattr(self, 'g1'):
58 58 ReposGroupModel().delete(self.g1.group_id)
59 59 if hasattr(self, 'g2'):
60 60 ReposGroupModel().delete(self.g2.group_id)
61 61
62 62 if hasattr(self, 'ug1'):
63 63 UsersGroupModel().delete(self.ug1, force=True)
64 64
65 65 Session().commit()
66 66
67 67 def test_default_perms_set(self):
68 68 u1_auth = AuthUser(user_id=self.u1.user_id)
69 69 perms = {
70 70 'repositories_groups': {},
71 71 'global': set([u'hg.create.repository', u'repository.read',
72 72 u'hg.register.manual_activate']),
73 73 'repositories': {u'vcs_test_hg': u'repository.read'}
74 74 }
75 75 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
76 76 perms['repositories'][HG_REPO])
77 77 new_perm = 'repository.write'
78 78 RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1,
79 79 perm=new_perm)
80 80 Session().commit()
81 81
82 82 u1_auth = AuthUser(user_id=self.u1.user_id)
83 83 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
84 84 new_perm)
85 85
86 86 def test_default_admin_perms_set(self):
87 87 a1_auth = AuthUser(user_id=self.a1.user_id)
88 88 perms = {
89 89 'repositories_groups': {},
90 90 'global': set([u'hg.admin']),
91 91 'repositories': {u'vcs_test_hg': u'repository.admin'}
92 92 }
93 93 self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
94 94 perms['repositories'][HG_REPO])
95 95 new_perm = 'repository.write'
96 96 RepoModel().grant_user_permission(repo=HG_REPO, user=self.a1,
97 97 perm=new_perm)
98 98 Session().commit()
99 99 # cannot really downgrade admins permissions !? they still get's set as
100 100 # admin !
101 101 u1_auth = AuthUser(user_id=self.a1.user_id)
102 102 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
103 103 perms['repositories'][HG_REPO])
104 104
105 105 def test_default_group_perms(self):
106 106 self.g1 = _make_group('test1', skip_if_exists=True)
107 107 self.g2 = _make_group('test2', skip_if_exists=True)
108 108 u1_auth = AuthUser(user_id=self.u1.user_id)
109 109 perms = {
110 110 'repositories_groups': {u'test1': 'group.read', u'test2': 'group.read'},
111 111 'global': set([u'hg.create.repository', u'repository.read', u'hg.register.manual_activate']),
112 112 'repositories': {u'vcs_test_hg': u'repository.read'}
113 113 }
114 114 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
115 115 perms['repositories'][HG_REPO])
116 116 self.assertEqual(u1_auth.permissions['repositories_groups'],
117 117 perms['repositories_groups'])
118 118
119 119 def test_default_admin_group_perms(self):
120 120 self.g1 = _make_group('test1', skip_if_exists=True)
121 121 self.g2 = _make_group('test2', skip_if_exists=True)
122 122 a1_auth = AuthUser(user_id=self.a1.user_id)
123 123 perms = {
124 124 'repositories_groups': {u'test1': 'group.admin', u'test2': 'group.admin'},
125 125 'global': set(['hg.admin']),
126 126 'repositories': {u'vcs_test_hg': 'repository.admin'}
127 127 }
128 128
129 129 self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
130 130 perms['repositories'][HG_REPO])
131 131 self.assertEqual(a1_auth.permissions['repositories_groups'],
132 132 perms['repositories_groups'])
133 133
134 134 def test_propagated_permission_from_users_group_by_explicit_perms_exist(self):
135 135 # make group
136 136 self.ug1 = UsersGroupModel().create('G1')
137 137 # add user to group
138 138
139 139 UsersGroupModel().add_user_to_group(self.ug1, self.u1)
140 140
141 141 # set permission to lower
142 142 new_perm = 'repository.none'
143 143 RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm)
144 144 Session().commit()
145 145 u1_auth = AuthUser(user_id=self.u1.user_id)
146 146 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
147 147 new_perm)
148 148
149 149 # grant perm for group this should not override permission from user
150 150 # since it has explicitly set
151 151 new_perm_gr = 'repository.write'
152 152 RepoModel().grant_users_group_permission(repo=HG_REPO,
153 153 group_name=self.ug1,
154 154 perm=new_perm_gr)
155 155 # check perms
156 156 u1_auth = AuthUser(user_id=self.u1.user_id)
157 157 perms = {
158 158 'repositories_groups': {},
159 159 'global': set([u'hg.create.repository', u'repository.read',
160 160 u'hg.register.manual_activate']),
161 161 'repositories': {u'vcs_test_hg': u'repository.read'}
162 162 }
163 163 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
164 164 new_perm)
165 165 self.assertEqual(u1_auth.permissions['repositories_groups'],
166 166 perms['repositories_groups'])
167 167
168 168 def test_propagated_permission_from_users_group(self):
169 169 # make group
170 170 self.ug1 = UsersGroupModel().create('G1')
171 171 # add user to group
172 172
173 173 UsersGroupModel().add_user_to_group(self.ug1, self.u3)
174 174
175 175 # grant perm for group this should override default permission from user
176 176 new_perm_gr = 'repository.write'
177 177 RepoModel().grant_users_group_permission(repo=HG_REPO,
178 178 group_name=self.ug1,
179 179 perm=new_perm_gr)
180 180 # check perms
181 181 u3_auth = AuthUser(user_id=self.u3.user_id)
182 182 perms = {
183 183 'repositories_groups': {},
184 184 'global': set([u'hg.create.repository', u'repository.read',
185 185 u'hg.register.manual_activate']),
186 186 'repositories': {u'vcs_test_hg': u'repository.read'}
187 187 }
188 188 self.assertEqual(u3_auth.permissions['repositories'][HG_REPO],
189 189 new_perm_gr)
190 190 self.assertEqual(u3_auth.permissions['repositories_groups'],
191 191 perms['repositories_groups'])
192 192
193 193 def test_propagated_permission_from_users_group_lower_weight(self):
194 194 # make group
195 195 self.ug1 = UsersGroupModel().create('G1')
196 196 # add user to group
197 197 UsersGroupModel().add_user_to_group(self.ug1, self.u1)
198 198
199 199 # set permission to lower
200 200 new_perm_h = 'repository.write'
201 201 RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1,
202 202 perm=new_perm_h)
203 203 Session().commit()
204 204 u1_auth = AuthUser(user_id=self.u1.user_id)
205 205 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
206 206 new_perm_h)
207 207
208 208 # grant perm for group this should NOT override permission from user
209 209 # since it's lower than granted
210 210 new_perm_l = 'repository.read'
211 211 RepoModel().grant_users_group_permission(repo=HG_REPO,
212 212 group_name=self.ug1,
213 213 perm=new_perm_l)
214 214 # check perms
215 215 u1_auth = AuthUser(user_id=self.u1.user_id)
216 216 perms = {
217 217 'repositories_groups': {},
218 218 'global': set([u'hg.create.repository', u'repository.read',
219 219 u'hg.register.manual_activate']),
220 220 'repositories': {u'vcs_test_hg': u'repository.write'}
221 221 }
222 222 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
223 223 new_perm_h)
224 224 self.assertEqual(u1_auth.permissions['repositories_groups'],
225 225 perms['repositories_groups'])
226 226
227 227 def test_repo_in_group_permissions(self):
228 228 self.g1 = _make_group('group1', skip_if_exists=True)
229 229 self.g2 = _make_group('group2', skip_if_exists=True)
230 230 Session().commit()
231 231 # both perms should be read !
232 232 u1_auth = AuthUser(user_id=self.u1.user_id)
233 233 self.assertEqual(u1_auth.permissions['repositories_groups'],
234 234 {u'group1': u'group.read', u'group2': u'group.read'})
235 235
236 236 a1_auth = AuthUser(user_id=self.anon.user_id)
237 237 self.assertEqual(a1_auth.permissions['repositories_groups'],
238 238 {u'group1': u'group.read', u'group2': u'group.read'})
239 239
240 240 #Change perms to none for both groups
241 241 ReposGroupModel().grant_user_permission(repos_group=self.g1,
242 242 user=self.anon,
243 243 perm='group.none')
244 244 ReposGroupModel().grant_user_permission(repos_group=self.g2,
245 245 user=self.anon,
246 246 perm='group.none')
247 247
248 248 u1_auth = AuthUser(user_id=self.u1.user_id)
249 249 self.assertEqual(u1_auth.permissions['repositories_groups'],
250 250 {u'group1': u'group.none', u'group2': u'group.none'})
251 251
252 252 a1_auth = AuthUser(user_id=self.anon.user_id)
253 253 self.assertEqual(a1_auth.permissions['repositories_groups'],
254 254 {u'group1': u'group.none', u'group2': u'group.none'})
255 255
256 256 # add repo to group
257 257 name = RepoGroup.url_sep().join([self.g1.group_name, 'test_perm'])
258 258 self.test_repo = RepoModel().create_repo(
259 259 repo_name=name,
260 260 repo_type='hg',
261 261 description='',
262 262 repos_group=self.g1,
263 263 owner=self.u1,
264 264 )
265 265 Session().commit()
266 266
267 267 u1_auth = AuthUser(user_id=self.u1.user_id)
268 268 self.assertEqual(u1_auth.permissions['repositories_groups'],
269 269 {u'group1': u'group.none', u'group2': u'group.none'})
270 270
271 271 a1_auth = AuthUser(user_id=self.anon.user_id)
272 272 self.assertEqual(a1_auth.permissions['repositories_groups'],
273 273 {u'group1': u'group.none', u'group2': u'group.none'})
274 274
275 275 #grant permission for u2 !
276 276 ReposGroupModel().grant_user_permission(repos_group=self.g1,
277 277 user=self.u2,
278 278 perm='group.read')
279 279 ReposGroupModel().grant_user_permission(repos_group=self.g2,
280 280 user=self.u2,
281 281 perm='group.read')
282 282 Session().commit()
283 283 self.assertNotEqual(self.u1, self.u2)
284 284 #u1 and anon should have not change perms while u2 should !
285 285 u1_auth = AuthUser(user_id=self.u1.user_id)
286 286 self.assertEqual(u1_auth.permissions['repositories_groups'],
287 287 {u'group1': u'group.none', u'group2': u'group.none'})
288 288
289 289 u2_auth = AuthUser(user_id=self.u2.user_id)
290 290 self.assertEqual(u2_auth.permissions['repositories_groups'],
291 291 {u'group1': u'group.read', u'group2': u'group.read'})
292 292
293 293 a1_auth = AuthUser(user_id=self.anon.user_id)
294 294 self.assertEqual(a1_auth.permissions['repositories_groups'],
295 295 {u'group1': u'group.none', u'group2': u'group.none'})
296 296
297 297 def test_repo_group_user_as_user_group_member(self):
298 298 # create Group1
299 299 self.g1 = _make_group('group1', skip_if_exists=True)
300 300 Session().commit()
301 301 a1_auth = AuthUser(user_id=self.anon.user_id)
302 302
303 303 self.assertEqual(a1_auth.permissions['repositories_groups'],
304 304 {u'group1': u'group.read'})
305 305
306 306 # set default permission to none
307 307 ReposGroupModel().grant_user_permission(repos_group=self.g1,
308 308 user=self.anon,
309 309 perm='group.none')
310 310 # make group
311 311 self.ug1 = UsersGroupModel().create('G1')
312 312 # add user to group
313 313 UsersGroupModel().add_user_to_group(self.ug1, self.u1)
314 314 Session().commit()
315 315
316 316 # check if user is in the group
317 317 membrs = [x.user_id for x in UsersGroupModel().get(self.ug1.users_group_id).members]
318 318 self.assertEqual(membrs, [self.u1.user_id])
319 319 # add some user to that group
320 320
321 321 # check his permissions
322 322 a1_auth = AuthUser(user_id=self.anon.user_id)
323 323 self.assertEqual(a1_auth.permissions['repositories_groups'],
324 324 {u'group1': u'group.none'})
325 325
326 326 u1_auth = AuthUser(user_id=self.u1.user_id)
327 327 self.assertEqual(u1_auth.permissions['repositories_groups'],
328 328 {u'group1': u'group.none'})
329 329
330 330 # grant ug1 read permissions for
331 331 ReposGroupModel().grant_users_group_permission(repos_group=self.g1,
332 332 group_name=self.ug1,
333 333 perm='group.read')
334 334 Session().commit()
335 335 # check if the
336 336 obj = Session().query(UsersGroupRepoGroupToPerm)\
337 337 .filter(UsersGroupRepoGroupToPerm.group == self.g1)\
338 338 .filter(UsersGroupRepoGroupToPerm.users_group == self.ug1)\
339 339 .scalar()
340 340 self.assertEqual(obj.permission.permission_name, 'group.read')
341 341
342 342 a1_auth = AuthUser(user_id=self.anon.user_id)
343 343
344 344 self.assertEqual(a1_auth.permissions['repositories_groups'],
345 345 {u'group1': u'group.none'})
346 346
347 347 u1_auth = AuthUser(user_id=self.u1.user_id)
348 348 self.assertEqual(u1_auth.permissions['repositories_groups'],
349 349 {u'group1': u'group.read'})
350 350
351 351 def test_inherited_permissions_from_default_on_user_enabled(self):
352 352 user_model = UserModel()
353 353 # enable fork and create on default user
354 354 usr = 'default'
355 355 user_model.revoke_perm(usr, 'hg.create.none')
356 356 user_model.grant_perm(usr, 'hg.create.repository')
357 357 user_model.revoke_perm(usr, 'hg.fork.none')
358 358 user_model.grant_perm(usr, 'hg.fork.repository')
359 359 # make sure inherit flag is turned on
360 360 self.u1.inherit_default_permissions = True
361 361 Session().commit()
362 362 u1_auth = AuthUser(user_id=self.u1.user_id)
363 363 # this user will have inherited permissions from default user
364 364 self.assertEqual(u1_auth.permissions['global'],
365 365 set(['hg.create.repository', 'hg.fork.repository',
366 366 'hg.register.manual_activate',
367 367 'repository.read']))
368 368
369 369 def test_inherited_permissions_from_default_on_user_disabled(self):
370 370 user_model = UserModel()
371 371 # disable fork and create on default user
372 372 usr = 'default'
373 373 user_model.revoke_perm(usr, 'hg.create.repository')
374 374 user_model.grant_perm(usr, 'hg.create.none')
375 375 user_model.revoke_perm(usr, 'hg.fork.repository')
376 376 user_model.grant_perm(usr, 'hg.fork.none')
377 377 # make sure inherit flag is turned on
378 378 self.u1.inherit_default_permissions = True
379 379 Session().commit()
380 380 u1_auth = AuthUser(user_id=self.u1.user_id)
381 381 # this user will have inherited permissions from default user
382 382 self.assertEqual(u1_auth.permissions['global'],
383 383 set(['hg.create.none', 'hg.fork.none',
384 384 'hg.register.manual_activate',
385 385 'repository.read']))
386 386
387 387 def test_non_inherited_permissions_from_default_on_user_enabled(self):
388 388 user_model = UserModel()
389 389 # enable fork and create on default user
390 390 usr = 'default'
391 391 user_model.revoke_perm(usr, 'hg.create.none')
392 392 user_model.grant_perm(usr, 'hg.create.repository')
393 393 user_model.revoke_perm(usr, 'hg.fork.none')
394 394 user_model.grant_perm(usr, 'hg.fork.repository')
395 395
396 396 #disable global perms on specific user
397 397 user_model.revoke_perm(self.u1, 'hg.create.repository')
398 398 user_model.grant_perm(self.u1, 'hg.create.none')
399 399 user_model.revoke_perm(self.u1, 'hg.fork.repository')
400 400 user_model.grant_perm(self.u1, 'hg.fork.none')
401 401
402 402 # make sure inherit flag is turned off
403 403 self.u1.inherit_default_permissions = False
404 404 Session().commit()
405 405 u1_auth = AuthUser(user_id=self.u1.user_id)
406 406 # this user will have non inherited permissions from he's
407 407 # explicitly set permissions
408 408 self.assertEqual(u1_auth.permissions['global'],
409 409 set(['hg.create.none', 'hg.fork.none',
410 410 'hg.register.manual_activate',
411 411 'repository.read']))
412 412
413 413 def test_non_inherited_permissions_from_default_on_user_disabled(self):
414 414 user_model = UserModel()
415 415 # disable fork and create on default user
416 416 usr = 'default'
417 417 user_model.revoke_perm(usr, 'hg.create.repository')
418 418 user_model.grant_perm(usr, 'hg.create.none')
419 419 user_model.revoke_perm(usr, 'hg.fork.repository')
420 420 user_model.grant_perm(usr, 'hg.fork.none')
421 421
422 422 #enable global perms on specific user
423 423 user_model.revoke_perm(self.u1, 'hg.create.none')
424 424 user_model.grant_perm(self.u1, 'hg.create.repository')
425 425 user_model.revoke_perm(self.u1, 'hg.fork.none')
426 426 user_model.grant_perm(self.u1, 'hg.fork.repository')
427 427
428 428 # make sure inherit flag is turned off
429 429 self.u1.inherit_default_permissions = False
430 430 Session().commit()
431 431 u1_auth = AuthUser(user_id=self.u1.user_id)
432 432 # this user will have non inherited permissions from he's
433 433 # explicitly set permissions
434 434 self.assertEqual(u1_auth.permissions['global'],
435 435 set(['hg.create.repository', 'hg.fork.repository',
436 436 'hg.register.manual_activate',
437 437 'repository.read']))
438
General Comments 0
You need to be logged in to leave comments. Login now