##// END OF EJS Templates
dashboard: show date of commit inside the tooltip.
marcink -
r2489:19c0bb76 default
parent child Browse files
Show More
@@ -1,1032 +1,1032 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import re
23 23 import shutil
24 24 import time
25 25 import logging
26 26 import traceback
27 27 import datetime
28 28
29 29 from pyramid.threadlocal import get_current_request
30 30 from zope.cachedescriptors.property import Lazy as LazyProperty
31 31
32 32 from rhodecode import events
33 33 from rhodecode.lib.auth import HasUserGroupPermissionAny
34 34 from rhodecode.lib.caching_query import FromCache
35 35 from rhodecode.lib.exceptions import AttachedForksError
36 36 from rhodecode.lib.hooks_base import log_delete_repository
37 37 from rhodecode.lib.user_log_filter import user_log_filter
38 38 from rhodecode.lib.utils import make_db_config
39 39 from rhodecode.lib.utils2 import (
40 40 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
41 41 get_current_rhodecode_user, safe_int, datetime_to_time,
42 42 action_logger_generic)
43 43 from rhodecode.lib.vcs.backends import get_backend
44 44 from rhodecode.model import BaseModel
45 45 from rhodecode.model.db import (
46 46 _hash_key, joinedload, or_, Repository, UserRepoToPerm, UserGroupRepoToPerm,
47 47 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
48 48 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
49 49
50 50 from rhodecode.model.settings import VcsSettingsModel
51 51
52 52
53 53 log = logging.getLogger(__name__)
54 54
55 55
56 56 class RepoModel(BaseModel):
57 57
58 58 cls = Repository
59 59
60 60 def _get_user_group(self, users_group):
61 61 return self._get_instance(UserGroup, users_group,
62 62 callback=UserGroup.get_by_group_name)
63 63
64 64 def _get_repo_group(self, repo_group):
65 65 return self._get_instance(RepoGroup, repo_group,
66 66 callback=RepoGroup.get_by_group_name)
67 67
68 68 def _create_default_perms(self, repository, private):
69 69 # create default permission
70 70 default = 'repository.read'
71 71 def_user = User.get_default_user()
72 72 for p in def_user.user_perms:
73 73 if p.permission.permission_name.startswith('repository.'):
74 74 default = p.permission.permission_name
75 75 break
76 76
77 77 default_perm = 'repository.none' if private else default
78 78
79 79 repo_to_perm = UserRepoToPerm()
80 80 repo_to_perm.permission = Permission.get_by_key(default_perm)
81 81
82 82 repo_to_perm.repository = repository
83 83 repo_to_perm.user_id = def_user.user_id
84 84
85 85 return repo_to_perm
86 86
87 87 @LazyProperty
88 88 def repos_path(self):
89 89 """
90 90 Gets the repositories root path from database
91 91 """
92 92 settings_model = VcsSettingsModel(sa=self.sa)
93 93 return settings_model.get_repos_location()
94 94
95 95 def get(self, repo_id, cache=False):
96 96 repo = self.sa.query(Repository) \
97 97 .filter(Repository.repo_id == repo_id)
98 98
99 99 if cache:
100 100 repo = repo.options(
101 101 FromCache("sql_cache_short", "get_repo_%s" % repo_id))
102 102 return repo.scalar()
103 103
104 104 def get_repo(self, repository):
105 105 return self._get_repo(repository)
106 106
107 107 def get_by_repo_name(self, repo_name, cache=False):
108 108 repo = self.sa.query(Repository) \
109 109 .filter(Repository.repo_name == repo_name)
110 110
111 111 if cache:
112 112 name_key = _hash_key(repo_name)
113 113 repo = repo.options(
114 114 FromCache("sql_cache_short", "get_repo_%s" % name_key))
115 115 return repo.scalar()
116 116
117 117 def _extract_id_from_repo_name(self, repo_name):
118 118 if repo_name.startswith('/'):
119 119 repo_name = repo_name.lstrip('/')
120 120 by_id_match = re.match(r'^_(\d{1,})', repo_name)
121 121 if by_id_match:
122 122 return by_id_match.groups()[0]
123 123
124 124 def get_repo_by_id(self, repo_name):
125 125 """
126 126 Extracts repo_name by id from special urls.
127 127 Example url is _11/repo_name
128 128
129 129 :param repo_name:
130 130 :return: repo object if matched else None
131 131 """
132 132
133 133 try:
134 134 _repo_id = self._extract_id_from_repo_name(repo_name)
135 135 if _repo_id:
136 136 return self.get(_repo_id)
137 137 except Exception:
138 138 log.exception('Failed to extract repo_name from URL')
139 139
140 140 return None
141 141
142 142 def get_repos_for_root(self, root, traverse=False):
143 143 if traverse:
144 144 like_expression = u'{}%'.format(safe_unicode(root))
145 145 repos = Repository.query().filter(
146 146 Repository.repo_name.like(like_expression)).all()
147 147 else:
148 148 if root and not isinstance(root, RepoGroup):
149 149 raise ValueError(
150 150 'Root must be an instance '
151 151 'of RepoGroup, got:{} instead'.format(type(root)))
152 152 repos = Repository.query().filter(Repository.group == root).all()
153 153 return repos
154 154
155 155 def get_url(self, repo, request=None, permalink=False):
156 156 if not request:
157 157 request = get_current_request()
158 158
159 159 if not request:
160 160 return
161 161
162 162 if permalink:
163 163 return request.route_url(
164 164 'repo_summary', repo_name='_{}'.format(safe_str(repo.repo_id)))
165 165 else:
166 166 return request.route_url(
167 167 'repo_summary', repo_name=safe_str(repo.repo_name))
168 168
169 169 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
170 170 if not request:
171 171 request = get_current_request()
172 172
173 173 if not request:
174 174 return
175 175
176 176 if permalink:
177 177 return request.route_url(
178 178 'repo_commit', repo_name=safe_str(repo.repo_id),
179 179 commit_id=commit_id)
180 180
181 181 else:
182 182 return request.route_url(
183 183 'repo_commit', repo_name=safe_str(repo.repo_name),
184 184 commit_id=commit_id)
185 185
186 186 def get_repo_log(self, repo, filter_term):
187 187 repo_log = UserLog.query()\
188 188 .filter(or_(UserLog.repository_id == repo.repo_id,
189 189 UserLog.repository_name == repo.repo_name))\
190 190 .options(joinedload(UserLog.user))\
191 191 .options(joinedload(UserLog.repository))\
192 192 .order_by(UserLog.action_date.desc())
193 193
194 194 repo_log = user_log_filter(repo_log, filter_term)
195 195 return repo_log
196 196
197 197 @classmethod
198 198 def update_repoinfo(cls, repositories=None):
199 199 if not repositories:
200 200 repositories = Repository.getAll()
201 201 for repo in repositories:
202 202 repo.update_commit_cache()
203 203
204 204 def get_repos_as_dict(self, repo_list=None, admin=False,
205 205 super_user_actions=False):
206 206 _render = get_current_request().get_partial_renderer(
207 207 'rhodecode:templates/data_table/_dt_elements.mako')
208 208 c = _render.get_call_context()
209 209
210 210 def quick_menu(repo_name):
211 211 return _render('quick_menu', repo_name)
212 212
213 213 def repo_lnk(name, rtype, rstate, private, fork_of):
214 214 return _render('repo_name', name, rtype, rstate, private, fork_of,
215 215 short_name=not admin, admin=False)
216 216
217 217 def last_change(last_change):
218 218 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
219 219 last_change = last_change + datetime.timedelta(seconds=
220 220 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
221 221 return _render("last_change", last_change)
222 222
223 223 def rss_lnk(repo_name):
224 224 return _render("rss", repo_name)
225 225
226 226 def atom_lnk(repo_name):
227 227 return _render("atom", repo_name)
228 228
229 229 def last_rev(repo_name, cs_cache):
230 230 return _render('revision', repo_name, cs_cache.get('revision'),
231 231 cs_cache.get('raw_id'), cs_cache.get('author'),
232 cs_cache.get('message'))
232 cs_cache.get('message'), cs_cache.get('date'))
233 233
234 234 def desc(desc):
235 235 return _render('repo_desc', desc, c.visual.stylify_metatags)
236 236
237 237 def state(repo_state):
238 238 return _render("repo_state", repo_state)
239 239
240 240 def repo_actions(repo_name):
241 241 return _render('repo_actions', repo_name, super_user_actions)
242 242
243 243 def user_profile(username):
244 244 return _render('user_profile', username)
245 245
246 246 repos_data = []
247 247 for repo in repo_list:
248 248 cs_cache = repo.changeset_cache
249 249 row = {
250 250 "menu": quick_menu(repo.repo_name),
251 251
252 252 "name": repo_lnk(repo.repo_name, repo.repo_type,
253 253 repo.repo_state, repo.private, repo.fork),
254 254 "name_raw": repo.repo_name.lower(),
255 255
256 256 "last_change": last_change(repo.last_db_change),
257 257 "last_change_raw": datetime_to_time(repo.last_db_change),
258 258
259 259 "last_changeset": last_rev(repo.repo_name, cs_cache),
260 260 "last_changeset_raw": cs_cache.get('revision'),
261 261
262 262 "desc": desc(repo.description_safe),
263 263 "owner": user_profile(repo.user.username),
264 264
265 265 "state": state(repo.repo_state),
266 266 "rss": rss_lnk(repo.repo_name),
267 267
268 268 "atom": atom_lnk(repo.repo_name),
269 269 }
270 270 if admin:
271 271 row.update({
272 272 "action": repo_actions(repo.repo_name),
273 273 })
274 274 repos_data.append(row)
275 275
276 276 return repos_data
277 277
278 278 def _get_defaults(self, repo_name):
279 279 """
280 280 Gets information about repository, and returns a dict for
281 281 usage in forms
282 282
283 283 :param repo_name:
284 284 """
285 285
286 286 repo_info = Repository.get_by_repo_name(repo_name)
287 287
288 288 if repo_info is None:
289 289 return None
290 290
291 291 defaults = repo_info.get_dict()
292 292 defaults['repo_name'] = repo_info.just_name
293 293
294 294 groups = repo_info.groups_with_parents
295 295 parent_group = groups[-1] if groups else None
296 296
297 297 # we use -1 as this is how in HTML, we mark an empty group
298 298 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
299 299
300 300 keys_to_process = (
301 301 {'k': 'repo_type', 'strip': False},
302 302 {'k': 'repo_enable_downloads', 'strip': True},
303 303 {'k': 'repo_description', 'strip': True},
304 304 {'k': 'repo_enable_locking', 'strip': True},
305 305 {'k': 'repo_landing_rev', 'strip': True},
306 306 {'k': 'clone_uri', 'strip': False},
307 307 {'k': 'repo_private', 'strip': True},
308 308 {'k': 'repo_enable_statistics', 'strip': True}
309 309 )
310 310
311 311 for item in keys_to_process:
312 312 attr = item['k']
313 313 if item['strip']:
314 314 attr = remove_prefix(item['k'], 'repo_')
315 315
316 316 val = defaults[attr]
317 317 if item['k'] == 'repo_landing_rev':
318 318 val = ':'.join(defaults[attr])
319 319 defaults[item['k']] = val
320 320 if item['k'] == 'clone_uri':
321 321 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
322 322
323 323 # fill owner
324 324 if repo_info.user:
325 325 defaults.update({'user': repo_info.user.username})
326 326 else:
327 327 replacement_user = User.get_first_super_admin().username
328 328 defaults.update({'user': replacement_user})
329 329
330 330 return defaults
331 331
332 332 def update(self, repo, **kwargs):
333 333 try:
334 334 cur_repo = self._get_repo(repo)
335 335 source_repo_name = cur_repo.repo_name
336 336 if 'user' in kwargs:
337 337 cur_repo.user = User.get_by_username(kwargs['user'])
338 338
339 339 if 'repo_group' in kwargs:
340 340 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
341 341 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
342 342
343 343 update_keys = [
344 344 (1, 'repo_description'),
345 345 (1, 'repo_landing_rev'),
346 346 (1, 'repo_private'),
347 347 (1, 'repo_enable_downloads'),
348 348 (1, 'repo_enable_locking'),
349 349 (1, 'repo_enable_statistics'),
350 350 (0, 'clone_uri'),
351 351 (0, 'fork_id')
352 352 ]
353 353 for strip, k in update_keys:
354 354 if k in kwargs:
355 355 val = kwargs[k]
356 356 if strip:
357 357 k = remove_prefix(k, 'repo_')
358 358
359 359 setattr(cur_repo, k, val)
360 360
361 361 new_name = cur_repo.get_new_name(kwargs['repo_name'])
362 362 cur_repo.repo_name = new_name
363 363
364 364 # if private flag is set, reset default permission to NONE
365 365 if kwargs.get('repo_private'):
366 366 EMPTY_PERM = 'repository.none'
367 367 RepoModel().grant_user_permission(
368 368 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
369 369 )
370 370
371 371 # handle extra fields
372 372 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
373 373 kwargs):
374 374 k = RepositoryField.un_prefix_key(field)
375 375 ex_field = RepositoryField.get_by_key_name(
376 376 key=k, repo=cur_repo)
377 377 if ex_field:
378 378 ex_field.field_value = kwargs[field]
379 379 self.sa.add(ex_field)
380 380 cur_repo.updated_on = datetime.datetime.now()
381 381 self.sa.add(cur_repo)
382 382
383 383 if source_repo_name != new_name:
384 384 # rename repository
385 385 self._rename_filesystem_repo(
386 386 old=source_repo_name, new=new_name)
387 387
388 388 return cur_repo
389 389 except Exception:
390 390 log.error(traceback.format_exc())
391 391 raise
392 392
393 393 def _create_repo(self, repo_name, repo_type, description, owner,
394 394 private=False, clone_uri=None, repo_group=None,
395 395 landing_rev='rev:tip', fork_of=None,
396 396 copy_fork_permissions=False, enable_statistics=False,
397 397 enable_locking=False, enable_downloads=False,
398 398 copy_group_permissions=False,
399 399 state=Repository.STATE_PENDING):
400 400 """
401 401 Create repository inside database with PENDING state, this should be
402 402 only executed by create() repo. With exception of importing existing
403 403 repos
404 404 """
405 405 from rhodecode.model.scm import ScmModel
406 406
407 407 owner = self._get_user(owner)
408 408 fork_of = self._get_repo(fork_of)
409 409 repo_group = self._get_repo_group(safe_int(repo_group))
410 410
411 411 try:
412 412 repo_name = safe_unicode(repo_name)
413 413 description = safe_unicode(description)
414 414 # repo name is just a name of repository
415 415 # while repo_name_full is a full qualified name that is combined
416 416 # with name and path of group
417 417 repo_name_full = repo_name
418 418 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
419 419
420 420 new_repo = Repository()
421 421 new_repo.repo_state = state
422 422 new_repo.enable_statistics = False
423 423 new_repo.repo_name = repo_name_full
424 424 new_repo.repo_type = repo_type
425 425 new_repo.user = owner
426 426 new_repo.group = repo_group
427 427 new_repo.description = description or repo_name
428 428 new_repo.private = private
429 429 new_repo.clone_uri = clone_uri
430 430 new_repo.landing_rev = landing_rev
431 431
432 432 new_repo.enable_statistics = enable_statistics
433 433 new_repo.enable_locking = enable_locking
434 434 new_repo.enable_downloads = enable_downloads
435 435
436 436 if repo_group:
437 437 new_repo.enable_locking = repo_group.enable_locking
438 438
439 439 if fork_of:
440 440 parent_repo = fork_of
441 441 new_repo.fork = parent_repo
442 442
443 443 events.trigger(events.RepoPreCreateEvent(new_repo))
444 444
445 445 self.sa.add(new_repo)
446 446
447 447 EMPTY_PERM = 'repository.none'
448 448 if fork_of and copy_fork_permissions:
449 449 repo = fork_of
450 450 user_perms = UserRepoToPerm.query() \
451 451 .filter(UserRepoToPerm.repository == repo).all()
452 452 group_perms = UserGroupRepoToPerm.query() \
453 453 .filter(UserGroupRepoToPerm.repository == repo).all()
454 454
455 455 for perm in user_perms:
456 456 UserRepoToPerm.create(
457 457 perm.user, new_repo, perm.permission)
458 458
459 459 for perm in group_perms:
460 460 UserGroupRepoToPerm.create(
461 461 perm.users_group, new_repo, perm.permission)
462 462 # in case we copy permissions and also set this repo to private
463 463 # override the default user permission to make it a private
464 464 # repo
465 465 if private:
466 466 RepoModel(self.sa).grant_user_permission(
467 467 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
468 468
469 469 elif repo_group and copy_group_permissions:
470 470 user_perms = UserRepoGroupToPerm.query() \
471 471 .filter(UserRepoGroupToPerm.group == repo_group).all()
472 472
473 473 group_perms = UserGroupRepoGroupToPerm.query() \
474 474 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
475 475
476 476 for perm in user_perms:
477 477 perm_name = perm.permission.permission_name.replace(
478 478 'group.', 'repository.')
479 479 perm_obj = Permission.get_by_key(perm_name)
480 480 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
481 481
482 482 for perm in group_perms:
483 483 perm_name = perm.permission.permission_name.replace(
484 484 'group.', 'repository.')
485 485 perm_obj = Permission.get_by_key(perm_name)
486 486 UserGroupRepoToPerm.create(
487 487 perm.users_group, new_repo, perm_obj)
488 488
489 489 if private:
490 490 RepoModel(self.sa).grant_user_permission(
491 491 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
492 492
493 493 else:
494 494 perm_obj = self._create_default_perms(new_repo, private)
495 495 self.sa.add(perm_obj)
496 496
497 497 # now automatically start following this repository as owner
498 498 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
499 499 owner.user_id)
500 500
501 501 # we need to flush here, in order to check if database won't
502 502 # throw any exceptions, create filesystem dirs at the very end
503 503 self.sa.flush()
504 504 events.trigger(events.RepoCreateEvent(new_repo))
505 505 return new_repo
506 506
507 507 except Exception:
508 508 log.error(traceback.format_exc())
509 509 raise
510 510
511 511 def create(self, form_data, cur_user):
512 512 """
513 513 Create repository using celery tasks
514 514
515 515 :param form_data:
516 516 :param cur_user:
517 517 """
518 518 from rhodecode.lib.celerylib import tasks, run_task
519 519 return run_task(tasks.create_repo, form_data, cur_user)
520 520
521 521 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
522 522 perm_deletions=None, check_perms=True,
523 523 cur_user=None):
524 524 if not perm_additions:
525 525 perm_additions = []
526 526 if not perm_updates:
527 527 perm_updates = []
528 528 if not perm_deletions:
529 529 perm_deletions = []
530 530
531 531 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
532 532
533 533 changes = {
534 534 'added': [],
535 535 'updated': [],
536 536 'deleted': []
537 537 }
538 538 # update permissions
539 539 for member_id, perm, member_type in perm_updates:
540 540 member_id = int(member_id)
541 541 if member_type == 'user':
542 542 member_name = User.get(member_id).username
543 543 # this updates also current one if found
544 544 self.grant_user_permission(
545 545 repo=repo, user=member_id, perm=perm)
546 546 else: # set for user group
547 547 # check if we have permissions to alter this usergroup
548 548 member_name = UserGroup.get(member_id).users_group_name
549 549 if not check_perms or HasUserGroupPermissionAny(
550 550 *req_perms)(member_name, user=cur_user):
551 551 self.grant_user_group_permission(
552 552 repo=repo, group_name=member_id, perm=perm)
553 553
554 554 changes['updated'].append({'type': member_type, 'id': member_id,
555 555 'name': member_name, 'new_perm': perm})
556 556
557 557 # set new permissions
558 558 for member_id, perm, member_type in perm_additions:
559 559 member_id = int(member_id)
560 560 if member_type == 'user':
561 561 member_name = User.get(member_id).username
562 562 self.grant_user_permission(
563 563 repo=repo, user=member_id, perm=perm)
564 564 else: # set for user group
565 565 # check if we have permissions to alter this usergroup
566 566 member_name = UserGroup.get(member_id).users_group_name
567 567 if not check_perms or HasUserGroupPermissionAny(
568 568 *req_perms)(member_name, user=cur_user):
569 569 self.grant_user_group_permission(
570 570 repo=repo, group_name=member_id, perm=perm)
571 571 changes['added'].append({'type': member_type, 'id': member_id,
572 572 'name': member_name, 'new_perm': perm})
573 573 # delete permissions
574 574 for member_id, perm, member_type in perm_deletions:
575 575 member_id = int(member_id)
576 576 if member_type == 'user':
577 577 member_name = User.get(member_id).username
578 578 self.revoke_user_permission(repo=repo, user=member_id)
579 579 else: # set for user group
580 580 # check if we have permissions to alter this usergroup
581 581 member_name = UserGroup.get(member_id).users_group_name
582 582 if not check_perms or HasUserGroupPermissionAny(
583 583 *req_perms)(member_name, user=cur_user):
584 584 self.revoke_user_group_permission(
585 585 repo=repo, group_name=member_id)
586 586
587 587 changes['deleted'].append({'type': member_type, 'id': member_id,
588 588 'name': member_name, 'new_perm': perm})
589 589 return changes
590 590
591 591 def create_fork(self, form_data, cur_user):
592 592 """
593 593 Simple wrapper into executing celery task for fork creation
594 594
595 595 :param form_data:
596 596 :param cur_user:
597 597 """
598 598 from rhodecode.lib.celerylib import tasks, run_task
599 599 return run_task(tasks.create_repo_fork, form_data, cur_user)
600 600
601 601 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
602 602 """
603 603 Delete given repository, forks parameter defines what do do with
604 604 attached forks. Throws AttachedForksError if deleted repo has attached
605 605 forks
606 606
607 607 :param repo:
608 608 :param forks: str 'delete' or 'detach'
609 609 :param fs_remove: remove(archive) repo from filesystem
610 610 """
611 611 if not cur_user:
612 612 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
613 613 repo = self._get_repo(repo)
614 614 if repo:
615 615 if forks == 'detach':
616 616 for r in repo.forks:
617 617 r.fork = None
618 618 self.sa.add(r)
619 619 elif forks == 'delete':
620 620 for r in repo.forks:
621 621 self.delete(r, forks='delete')
622 622 elif [f for f in repo.forks]:
623 623 raise AttachedForksError()
624 624
625 625 old_repo_dict = repo.get_dict()
626 626 events.trigger(events.RepoPreDeleteEvent(repo))
627 627 try:
628 628 self.sa.delete(repo)
629 629 if fs_remove:
630 630 self._delete_filesystem_repo(repo)
631 631 else:
632 632 log.debug('skipping removal from filesystem')
633 633 old_repo_dict.update({
634 634 'deleted_by': cur_user,
635 635 'deleted_on': time.time(),
636 636 })
637 637 log_delete_repository(**old_repo_dict)
638 638 events.trigger(events.RepoDeleteEvent(repo))
639 639 except Exception:
640 640 log.error(traceback.format_exc())
641 641 raise
642 642
643 643 def grant_user_permission(self, repo, user, perm):
644 644 """
645 645 Grant permission for user on given repository, or update existing one
646 646 if found
647 647
648 648 :param repo: Instance of Repository, repository_id, or repository name
649 649 :param user: Instance of User, user_id or username
650 650 :param perm: Instance of Permission, or permission_name
651 651 """
652 652 user = self._get_user(user)
653 653 repo = self._get_repo(repo)
654 654 permission = self._get_perm(perm)
655 655
656 656 # check if we have that permission already
657 657 obj = self.sa.query(UserRepoToPerm) \
658 658 .filter(UserRepoToPerm.user == user) \
659 659 .filter(UserRepoToPerm.repository == repo) \
660 660 .scalar()
661 661 if obj is None:
662 662 # create new !
663 663 obj = UserRepoToPerm()
664 664 obj.repository = repo
665 665 obj.user = user
666 666 obj.permission = permission
667 667 self.sa.add(obj)
668 668 log.debug('Granted perm %s to %s on %s', perm, user, repo)
669 669 action_logger_generic(
670 670 'granted permission: {} to user: {} on repo: {}'.format(
671 671 perm, user, repo), namespace='security.repo')
672 672 return obj
673 673
674 674 def revoke_user_permission(self, repo, user):
675 675 """
676 676 Revoke permission for user on given repository
677 677
678 678 :param repo: Instance of Repository, repository_id, or repository name
679 679 :param user: Instance of User, user_id or username
680 680 """
681 681
682 682 user = self._get_user(user)
683 683 repo = self._get_repo(repo)
684 684
685 685 obj = self.sa.query(UserRepoToPerm) \
686 686 .filter(UserRepoToPerm.repository == repo) \
687 687 .filter(UserRepoToPerm.user == user) \
688 688 .scalar()
689 689 if obj:
690 690 self.sa.delete(obj)
691 691 log.debug('Revoked perm on %s on %s', repo, user)
692 692 action_logger_generic(
693 693 'revoked permission from user: {} on repo: {}'.format(
694 694 user, repo), namespace='security.repo')
695 695
696 696 def grant_user_group_permission(self, repo, group_name, perm):
697 697 """
698 698 Grant permission for user group on given repository, or update
699 699 existing one if found
700 700
701 701 :param repo: Instance of Repository, repository_id, or repository name
702 702 :param group_name: Instance of UserGroup, users_group_id,
703 703 or user group name
704 704 :param perm: Instance of Permission, or permission_name
705 705 """
706 706 repo = self._get_repo(repo)
707 707 group_name = self._get_user_group(group_name)
708 708 permission = self._get_perm(perm)
709 709
710 710 # check if we have that permission already
711 711 obj = self.sa.query(UserGroupRepoToPerm) \
712 712 .filter(UserGroupRepoToPerm.users_group == group_name) \
713 713 .filter(UserGroupRepoToPerm.repository == repo) \
714 714 .scalar()
715 715
716 716 if obj is None:
717 717 # create new
718 718 obj = UserGroupRepoToPerm()
719 719
720 720 obj.repository = repo
721 721 obj.users_group = group_name
722 722 obj.permission = permission
723 723 self.sa.add(obj)
724 724 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
725 725 action_logger_generic(
726 726 'granted permission: {} to usergroup: {} on repo: {}'.format(
727 727 perm, group_name, repo), namespace='security.repo')
728 728
729 729 return obj
730 730
731 731 def revoke_user_group_permission(self, repo, group_name):
732 732 """
733 733 Revoke permission for user group on given repository
734 734
735 735 :param repo: Instance of Repository, repository_id, or repository name
736 736 :param group_name: Instance of UserGroup, users_group_id,
737 737 or user group name
738 738 """
739 739 repo = self._get_repo(repo)
740 740 group_name = self._get_user_group(group_name)
741 741
742 742 obj = self.sa.query(UserGroupRepoToPerm) \
743 743 .filter(UserGroupRepoToPerm.repository == repo) \
744 744 .filter(UserGroupRepoToPerm.users_group == group_name) \
745 745 .scalar()
746 746 if obj:
747 747 self.sa.delete(obj)
748 748 log.debug('Revoked perm to %s on %s', repo, group_name)
749 749 action_logger_generic(
750 750 'revoked permission from usergroup: {} on repo: {}'.format(
751 751 group_name, repo), namespace='security.repo')
752 752
753 753 def delete_stats(self, repo_name):
754 754 """
755 755 removes stats for given repo
756 756
757 757 :param repo_name:
758 758 """
759 759 repo = self._get_repo(repo_name)
760 760 try:
761 761 obj = self.sa.query(Statistics) \
762 762 .filter(Statistics.repository == repo).scalar()
763 763 if obj:
764 764 self.sa.delete(obj)
765 765 except Exception:
766 766 log.error(traceback.format_exc())
767 767 raise
768 768
769 769 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
770 770 field_type='str', field_desc=''):
771 771
772 772 repo = self._get_repo(repo_name)
773 773
774 774 new_field = RepositoryField()
775 775 new_field.repository = repo
776 776 new_field.field_key = field_key
777 777 new_field.field_type = field_type # python type
778 778 new_field.field_value = field_value
779 779 new_field.field_desc = field_desc
780 780 new_field.field_label = field_label
781 781 self.sa.add(new_field)
782 782 return new_field
783 783
784 784 def delete_repo_field(self, repo_name, field_key):
785 785 repo = self._get_repo(repo_name)
786 786 field = RepositoryField.get_by_key_name(field_key, repo)
787 787 if field:
788 788 self.sa.delete(field)
789 789
790 790 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
791 791 clone_uri=None, repo_store_location=None,
792 792 use_global_config=False):
793 793 """
794 794 makes repository on filesystem. It's group aware means it'll create
795 795 a repository within a group, and alter the paths accordingly of
796 796 group location
797 797
798 798 :param repo_name:
799 799 :param alias:
800 800 :param parent:
801 801 :param clone_uri:
802 802 :param repo_store_location:
803 803 """
804 804 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
805 805 from rhodecode.model.scm import ScmModel
806 806
807 807 if Repository.NAME_SEP in repo_name:
808 808 raise ValueError(
809 809 'repo_name must not contain groups got `%s`' % repo_name)
810 810
811 811 if isinstance(repo_group, RepoGroup):
812 812 new_parent_path = os.sep.join(repo_group.full_path_splitted)
813 813 else:
814 814 new_parent_path = repo_group or ''
815 815
816 816 if repo_store_location:
817 817 _paths = [repo_store_location]
818 818 else:
819 819 _paths = [self.repos_path, new_parent_path, repo_name]
820 820 # we need to make it str for mercurial
821 821 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
822 822
823 823 # check if this path is not a repository
824 824 if is_valid_repo(repo_path, self.repos_path):
825 825 raise Exception('This path %s is a valid repository' % repo_path)
826 826
827 827 # check if this path is a group
828 828 if is_valid_repo_group(repo_path, self.repos_path):
829 829 raise Exception('This path %s is a valid group' % repo_path)
830 830
831 831 log.info('creating repo %s in %s from url: `%s`',
832 832 repo_name, safe_unicode(repo_path),
833 833 obfuscate_url_pw(clone_uri))
834 834
835 835 backend = get_backend(repo_type)
836 836
837 837 config_repo = None if use_global_config else repo_name
838 838 if config_repo and new_parent_path:
839 839 config_repo = Repository.NAME_SEP.join(
840 840 (new_parent_path, config_repo))
841 841 config = make_db_config(clear_session=False, repo=config_repo)
842 842 config.set('extensions', 'largefiles', '')
843 843
844 844 # patch and reset hooks section of UI config to not run any
845 845 # hooks on creating remote repo
846 846 config.clear_section('hooks')
847 847
848 848 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
849 849 if repo_type == 'git':
850 850 repo = backend(
851 851 repo_path, config=config, create=True, src_url=clone_uri,
852 852 bare=True)
853 853 else:
854 854 repo = backend(
855 855 repo_path, config=config, create=True, src_url=clone_uri)
856 856
857 857 ScmModel().install_hooks(repo, repo_type=repo_type)
858 858
859 859 log.debug('Created repo %s with %s backend',
860 860 safe_unicode(repo_name), safe_unicode(repo_type))
861 861 return repo
862 862
863 863 def _rename_filesystem_repo(self, old, new):
864 864 """
865 865 renames repository on filesystem
866 866
867 867 :param old: old name
868 868 :param new: new name
869 869 """
870 870 log.info('renaming repo from %s to %s', old, new)
871 871
872 872 old_path = os.path.join(self.repos_path, old)
873 873 new_path = os.path.join(self.repos_path, new)
874 874 if os.path.isdir(new_path):
875 875 raise Exception(
876 876 'Was trying to rename to already existing dir %s' % new_path
877 877 )
878 878 shutil.move(old_path, new_path)
879 879
880 880 def _delete_filesystem_repo(self, repo):
881 881 """
882 882 removes repo from filesystem, the removal is acctually made by
883 883 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
884 884 repository is no longer valid for rhodecode, can be undeleted later on
885 885 by reverting the renames on this repository
886 886
887 887 :param repo: repo object
888 888 """
889 889 rm_path = os.path.join(self.repos_path, repo.repo_name)
890 890 repo_group = repo.group
891 891 log.info("Removing repository %s", rm_path)
892 892 # disable hg/git internal that it doesn't get detected as repo
893 893 alias = repo.repo_type
894 894
895 895 config = make_db_config(clear_session=False)
896 896 config.set('extensions', 'largefiles', '')
897 897 bare = getattr(repo.scm_instance(config=config), 'bare', False)
898 898
899 899 # skip this for bare git repos
900 900 if not bare:
901 901 # disable VCS repo
902 902 vcs_path = os.path.join(rm_path, '.%s' % alias)
903 903 if os.path.exists(vcs_path):
904 904 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
905 905
906 906 _now = datetime.datetime.now()
907 907 _ms = str(_now.microsecond).rjust(6, '0')
908 908 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
909 909 repo.just_name)
910 910 if repo_group:
911 911 # if repository is in group, prefix the removal path with the group
912 912 args = repo_group.full_path_splitted + [_d]
913 913 _d = os.path.join(*args)
914 914
915 915 if os.path.isdir(rm_path):
916 916 shutil.move(rm_path, os.path.join(self.repos_path, _d))
917 917
918 918
919 919 class ReadmeFinder:
920 920 """
921 921 Utility which knows how to find a readme for a specific commit.
922 922
923 923 The main idea is that this is a configurable algorithm. When creating an
924 924 instance you can define parameters, currently only the `default_renderer`.
925 925 Based on this configuration the method :meth:`search` behaves slightly
926 926 different.
927 927 """
928 928
929 929 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
930 930 path_re = re.compile(r'^docs?', re.IGNORECASE)
931 931
932 932 default_priorities = {
933 933 None: 0,
934 934 '.text': 2,
935 935 '.txt': 3,
936 936 '.rst': 1,
937 937 '.rest': 2,
938 938 '.md': 1,
939 939 '.mkdn': 2,
940 940 '.mdown': 3,
941 941 '.markdown': 4,
942 942 }
943 943
944 944 path_priority = {
945 945 'doc': 0,
946 946 'docs': 1,
947 947 }
948 948
949 949 FALLBACK_PRIORITY = 99
950 950
951 951 RENDERER_TO_EXTENSION = {
952 952 'rst': ['.rst', '.rest'],
953 953 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
954 954 }
955 955
956 956 def __init__(self, default_renderer=None):
957 957 self._default_renderer = default_renderer
958 958 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
959 959 default_renderer, [])
960 960
961 961 def search(self, commit, path='/'):
962 962 """
963 963 Find a readme in the given `commit`.
964 964 """
965 965 nodes = commit.get_nodes(path)
966 966 matches = self._match_readmes(nodes)
967 967 matches = self._sort_according_to_priority(matches)
968 968 if matches:
969 969 return matches[0].node
970 970
971 971 paths = self._match_paths(nodes)
972 972 paths = self._sort_paths_according_to_priority(paths)
973 973 for path in paths:
974 974 match = self.search(commit, path=path)
975 975 if match:
976 976 return match
977 977
978 978 return None
979 979
980 980 def _match_readmes(self, nodes):
981 981 for node in nodes:
982 982 if not node.is_file():
983 983 continue
984 984 path = node.path.rsplit('/', 1)[-1]
985 985 match = self.readme_re.match(path)
986 986 if match:
987 987 extension = match.group(1)
988 988 yield ReadmeMatch(node, match, self._priority(extension))
989 989
990 990 def _match_paths(self, nodes):
991 991 for node in nodes:
992 992 if not node.is_dir():
993 993 continue
994 994 match = self.path_re.match(node.path)
995 995 if match:
996 996 yield node.path
997 997
998 998 def _priority(self, extension):
999 999 renderer_priority = (
1000 1000 0 if extension in self._renderer_extensions else 1)
1001 1001 extension_priority = self.default_priorities.get(
1002 1002 extension, self.FALLBACK_PRIORITY)
1003 1003 return (renderer_priority, extension_priority)
1004 1004
1005 1005 def _sort_according_to_priority(self, matches):
1006 1006
1007 1007 def priority_and_path(match):
1008 1008 return (match.priority, match.path)
1009 1009
1010 1010 return sorted(matches, key=priority_and_path)
1011 1011
1012 1012 def _sort_paths_according_to_priority(self, paths):
1013 1013
1014 1014 def priority_and_path(path):
1015 1015 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1016 1016
1017 1017 return sorted(paths, key=priority_and_path)
1018 1018
1019 1019
1020 1020 class ReadmeMatch:
1021 1021
1022 1022 def __init__(self, node, match, priority):
1023 1023 self.node = node
1024 1024 self._match = match
1025 1025 self.priority = priority
1026 1026
1027 1027 @property
1028 1028 def path(self):
1029 1029 return self.node.path
1030 1030
1031 1031 def __repr__(self):
1032 1032 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
@@ -1,377 +1,377 b''
1 1 ## DATA TABLE RE USABLE ELEMENTS
2 2 ## usage:
3 3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 4 <%namespace name="base" file="/base/base.mako"/>
5 5
6 6 <%def name="metatags_help()">
7 7 <table>
8 8 <%
9 9 example_tags = [
10 10 ('state','[stable]'),
11 11 ('state','[stale]'),
12 12 ('state','[featured]'),
13 13 ('state','[dev]'),
14 14 ('state','[dead]'),
15 15 ('state','[deprecated]'),
16 16
17 17 ('label','[personal]'),
18 18 ('generic','[v2.0.0]'),
19 19
20 20 ('lang','[lang =&gt; JavaScript]'),
21 21 ('license','[license =&gt; LicenseName]'),
22 22
23 23 ('ref','[requires =&gt; RepoName]'),
24 24 ('ref','[recommends =&gt; GroupName]'),
25 25 ('ref','[conflicts =&gt; SomeName]'),
26 26 ('ref','[base =&gt; SomeName]'),
27 27 ('url','[url =&gt; [linkName](https://rhodecode.com)]'),
28 28 ('see','[see =&gt; http://rhodecode.com]'),
29 29 ]
30 30 %>
31 31 % for tag_type, tag in example_tags:
32 32 <tr>
33 33 <td>${tag|n}</td>
34 34 <td>${h.style_metatag(tag_type, tag)|n}</td>
35 35 </tr>
36 36 % endfor
37 37 </table>
38 38 </%def>
39 39
40 40 ## REPOSITORY RENDERERS
41 41 <%def name="quick_menu(repo_name)">
42 42 <i class="icon-more"></i>
43 43 <div class="menu_items_container hidden">
44 44 <ul class="menu_items">
45 45 <li>
46 46 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
47 47 <span>${_('Summary')}</span>
48 48 </a>
49 49 </li>
50 50 <li>
51 51 <a title="${_('Changelog')}" href="${h.route_path('repo_changelog',repo_name=repo_name)}">
52 52 <span>${_('Changelog')}</span>
53 53 </a>
54 54 </li>
55 55 <li>
56 56 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
57 57 <span>${_('Files')}</span>
58 58 </a>
59 59 </li>
60 60 <li>
61 61 <a title="${_('Fork')}" href="${h.route_path('repo_fork_new',repo_name=repo_name)}">
62 62 <span>${_('Fork')}</span>
63 63 </a>
64 64 </li>
65 65 </ul>
66 66 </div>
67 67 </%def>
68 68
69 69 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
70 70 <%
71 71 def get_name(name,short_name=short_name):
72 72 if short_name:
73 73 return name.split('/')[-1]
74 74 else:
75 75 return name
76 76 %>
77 77 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
78 78 ##NAME
79 79 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
80 80
81 81 ##TYPE OF REPO
82 82 %if h.is_hg(rtype):
83 83 <span title="${_('Mercurial repository')}"><i class="icon-hg" style="font-size: 14px;"></i></span>
84 84 %elif h.is_git(rtype):
85 85 <span title="${_('Git repository')}"><i class="icon-git" style="font-size: 14px"></i></span>
86 86 %elif h.is_svn(rtype):
87 87 <span title="${_('Subversion repository')}"><i class="icon-svn" style="font-size: 14px"></i></span>
88 88 %endif
89 89
90 90 ##PRIVATE/PUBLIC
91 91 %if private and c.visual.show_private_icon:
92 92 <i class="icon-lock" title="${_('Private repository')}"></i>
93 93 %elif not private and c.visual.show_public_icon:
94 94 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
95 95 %else:
96 96 <span></span>
97 97 %endif
98 98 ${get_name(name)}
99 99 </a>
100 100 %if fork_of:
101 101 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
102 102 %endif
103 103 %if rstate == 'repo_state_pending':
104 104 <span class="creation_in_progress tooltip" title="${_('This repository is being created in a background task')}">
105 105 (${_('creating...')})
106 106 </span>
107 107 %endif
108 108 </div>
109 109 </%def>
110 110
111 111 <%def name="repo_desc(description, stylify_metatags)">
112 112 <%
113 113 tags, description = h.extract_metatags(description)
114 114 %>
115 115
116 116 <div class="truncate-wrap">
117 117 % if stylify_metatags:
118 118 % for tag_type, tag in tags:
119 119 ${h.style_metatag(tag_type, tag)|n}
120 120 % endfor
121 121 % endif
122 122 ${description}
123 123 </div>
124 124
125 125 </%def>
126 126
127 127 <%def name="last_change(last_change)">
128 128 ${h.age_component(last_change)}
129 129 </%def>
130 130
131 <%def name="revision(name,rev,tip,author,last_msg)">
131 <%def name="revision(name,rev,tip,author,last_msg, commit_date)">
132 132 <div>
133 133 %if rev >= 0:
134 <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.route_path('repo_commit',repo_name=name,commit_id=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
134 <code><a title="${h.tooltip('%s\n%s\n\n%s' % (author, commit_date, last_msg))}" class="tooltip" href="${h.route_path('repo_commit',repo_name=name,commit_id=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
135 135 %else:
136 136 ${_('No commits yet')}
137 137 %endif
138 138 </div>
139 139 </%def>
140 140
141 141 <%def name="rss(name)">
142 142 %if c.rhodecode_user.username != h.DEFAULT_USER:
143 143 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
144 144 %else:
145 145 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
146 146 %endif
147 147 </%def>
148 148
149 149 <%def name="atom(name)">
150 150 %if c.rhodecode_user.username != h.DEFAULT_USER:
151 151 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
152 152 %else:
153 153 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
154 154 %endif
155 155 </%def>
156 156
157 157 <%def name="user_gravatar(email, size=16)">
158 158 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
159 159 ${base.gravatar(email, 16)}
160 160 </div>
161 161 </%def>
162 162
163 163 <%def name="repo_actions(repo_name, super_user=True)">
164 164 <div>
165 165 <div class="grid_edit">
166 166 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
167 167 <i class="icon-pencil"></i>Edit</a>
168 168 </div>
169 169 <div class="grid_delete">
170 170 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), request=request)}
171 171 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
172 172 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
173 173 ${h.end_form()}
174 174 </div>
175 175 </div>
176 176 </%def>
177 177
178 178 <%def name="repo_state(repo_state)">
179 179 <div>
180 180 %if repo_state == 'repo_state_pending':
181 181 <div class="tag tag4">${_('Creating')}</div>
182 182 %elif repo_state == 'repo_state_created':
183 183 <div class="tag tag1">${_('Created')}</div>
184 184 %else:
185 185 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
186 186 %endif
187 187 </div>
188 188 </%def>
189 189
190 190
191 191 ## REPO GROUP RENDERERS
192 192 <%def name="quick_repo_group_menu(repo_group_name)">
193 193 <i class="icon-more"></i>
194 194 <div class="menu_items_container hidden">
195 195 <ul class="menu_items">
196 196 <li>
197 197 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">${_('Summary')}</a>
198 198 </li>
199 199
200 200 </ul>
201 201 </div>
202 202 </%def>
203 203
204 204 <%def name="repo_group_name(repo_group_name, children_groups=None)">
205 205 <div>
206 206 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
207 207 <i class="icon-folder-close" title="${_('Repository group')}" style="font-size: 16px"></i>
208 208 %if children_groups:
209 209 ${h.literal(' &raquo; '.join(children_groups))}
210 210 %else:
211 211 ${repo_group_name}
212 212 %endif
213 213 </a>
214 214 </div>
215 215 </%def>
216 216
217 217 <%def name="repo_group_desc(description, personal, stylify_metatags)">
218 218
219 219 <%
220 220 tags, description = h.extract_metatags(description)
221 221 %>
222 222
223 223 <div class="truncate-wrap">
224 224 % if personal:
225 225 <div class="metatag" tag="personal">${_('personal')}</div>
226 226 % endif
227 227
228 228 % if stylify_metatags:
229 229 % for tag_type, tag in tags:
230 230 ${h.style_metatag(tag_type, tag)|n}
231 231 % endfor
232 232 % endif
233 233 ${description}
234 234 </div>
235 235
236 236 </%def>
237 237
238 238 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
239 239 <div class="grid_edit">
240 240 <a href="${h.route_path('edit_repo_group',repo_group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
241 241 </div>
242 242 <div class="grid_delete">
243 243 ${h.secure_form(h.route_path('edit_repo_group_advanced_delete', repo_group_name=repo_group_name), request=request)}
244 244 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
245 245 onclick="return confirm('"+_ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
246 246 ${h.end_form()}
247 247 </div>
248 248 </%def>
249 249
250 250
251 251 <%def name="user_actions(user_id, username)">
252 252 <div class="grid_edit">
253 253 <a href="${h.route_path('user_edit',user_id=user_id)}" title="${_('Edit')}">
254 254 <i class="icon-pencil"></i>${_('Edit')}</a>
255 255 </div>
256 256 <div class="grid_delete">
257 257 ${h.secure_form(h.route_path('user_delete', user_id=user_id), request=request)}
258 258 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
259 259 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
260 260 ${h.end_form()}
261 261 </div>
262 262 </%def>
263 263
264 264 <%def name="user_group_actions(user_group_id, user_group_name)">
265 265 <div class="grid_edit">
266 266 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
267 267 </div>
268 268 <div class="grid_delete">
269 269 ${h.secure_form(h.route_path('user_groups_delete', user_group_id=user_group_id), request=request)}
270 270 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
271 271 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
272 272 ${h.end_form()}
273 273 </div>
274 274 </%def>
275 275
276 276
277 277 <%def name="user_name(user_id, username)">
278 278 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.route_path('user_edit', user_id=user_id))}
279 279 </%def>
280 280
281 281 <%def name="user_profile(username)">
282 282 ${base.gravatar_with_user(username, 16)}
283 283 </%def>
284 284
285 285 <%def name="user_group_name(user_group_id, user_group_name)">
286 286 <div>
287 287 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}">
288 288 <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a>
289 289 </div>
290 290 </%def>
291 291
292 292
293 293 ## GISTS
294 294
295 295 <%def name="gist_gravatar(full_contact)">
296 296 <div class="gist_gravatar">
297 297 ${base.gravatar(full_contact, 30)}
298 298 </div>
299 299 </%def>
300 300
301 301 <%def name="gist_access_id(gist_access_id, full_contact)">
302 302 <div>
303 303 <b>
304 304 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
305 305 </b>
306 306 </div>
307 307 </%def>
308 308
309 309 <%def name="gist_author(full_contact, created_on, expires)">
310 310 ${base.gravatar_with_user(full_contact, 16)}
311 311 </%def>
312 312
313 313
314 314 <%def name="gist_created(created_on)">
315 315 <div class="created">
316 316 ${h.age_component(created_on, time_is_local=True)}
317 317 </div>
318 318 </%def>
319 319
320 320 <%def name="gist_expires(expires)">
321 321 <div class="created">
322 322 %if expires == -1:
323 323 ${_('never')}
324 324 %else:
325 325 ${h.age_component(h.time_to_utcdatetime(expires))}
326 326 %endif
327 327 </div>
328 328 </%def>
329 329
330 330 <%def name="gist_type(gist_type)">
331 331 %if gist_type != 'public':
332 332 <div class="tag">${_('Private')}</div>
333 333 %endif
334 334 </%def>
335 335
336 336 <%def name="gist_description(gist_description)">
337 337 ${gist_description}
338 338 </%def>
339 339
340 340
341 341 ## PULL REQUESTS GRID RENDERERS
342 342
343 343 <%def name="pullrequest_target_repo(repo_name)">
344 344 <div class="truncate">
345 345 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
346 346 </div>
347 347 </%def>
348 348 <%def name="pullrequest_status(status)">
349 349 <div class="${'flag_status %s' % status} pull-left"></div>
350 350 </%def>
351 351
352 352 <%def name="pullrequest_title(title, description)">
353 353 ${title} <br/>
354 354 ${h.shorter(description, 40)}
355 355 </%def>
356 356
357 357 <%def name="pullrequest_comments(comments_nr)">
358 358 <i class="icon-comment"></i> ${comments_nr}
359 359 </%def>
360 360
361 361 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
362 362 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
363 363 % if short:
364 364 #${pull_request_id}
365 365 % else:
366 366 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
367 367 % endif
368 368 </a>
369 369 </%def>
370 370
371 371 <%def name="pullrequest_updated_on(updated_on)">
372 372 ${h.age_component(h.time_to_utcdatetime(updated_on))}
373 373 </%def>
374 374
375 375 <%def name="pullrequest_author(full_contact)">
376 376 ${base.gravatar_with_user(full_contact, 16)}
377 377 </%def>
General Comments 0
You need to be logged in to leave comments. Login now