##// END OF EJS Templates
Fix for bug #4155. Modification last change date to add time zone.
Bartłomiej Wołyńczyk -
r1448:2ccf2ec6 default
parent child Browse files
Show More
@@ -1,1070 +1,1073 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 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 """
22 22 Repository model for rhodecode
23 23 """
24 24
25 25 import logging
26 26 import os
27 27 import re
28 28 import shutil
29 29 import time
30 30 import traceback
31 from datetime import datetime
31 from datetime import datetime, timedelta
32 32
33 33 from sqlalchemy.sql import func
34 34 from sqlalchemy.sql.expression import true, or_
35 35 from zope.cachedescriptors.property import Lazy as LazyProperty
36 36
37 37 from rhodecode import events
38 38 from rhodecode.lib import helpers as h
39 39 from rhodecode.lib.auth import HasUserGroupPermissionAny
40 40 from rhodecode.lib.caching_query import FromCache
41 41 from rhodecode.lib.exceptions import AttachedForksError
42 42 from rhodecode.lib.hooks_base import log_delete_repository
43 43 from rhodecode.lib.markup_renderer import MarkupRenderer
44 44 from rhodecode.lib.utils import make_db_config
45 45 from rhodecode.lib.utils2 import (
46 46 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
47 47 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
48 48 from rhodecode.lib.vcs.backends import get_backend
49 49 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
50 50 from rhodecode.model import BaseModel
51 51 from rhodecode.model.db import (
52 52 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
53 53 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
54 54 RepoGroup, RepositoryField)
55 55 from rhodecode.model.scm import UserGroupList
56 56 from rhodecode.model.settings import VcsSettingsModel
57 57
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61
62 62 class RepoModel(BaseModel):
63 63
64 64 cls = Repository
65 65
66 66 def _get_user_group(self, users_group):
67 67 return self._get_instance(UserGroup, users_group,
68 68 callback=UserGroup.get_by_group_name)
69 69
70 70 def _get_repo_group(self, repo_group):
71 71 return self._get_instance(RepoGroup, repo_group,
72 72 callback=RepoGroup.get_by_group_name)
73 73
74 74 def _create_default_perms(self, repository, private):
75 75 # create default permission
76 76 default = 'repository.read'
77 77 def_user = User.get_default_user()
78 78 for p in def_user.user_perms:
79 79 if p.permission.permission_name.startswith('repository.'):
80 80 default = p.permission.permission_name
81 81 break
82 82
83 83 default_perm = 'repository.none' if private else default
84 84
85 85 repo_to_perm = UserRepoToPerm()
86 86 repo_to_perm.permission = Permission.get_by_key(default_perm)
87 87
88 88 repo_to_perm.repository = repository
89 89 repo_to_perm.user_id = def_user.user_id
90 90
91 91 return repo_to_perm
92 92
93 93 @LazyProperty
94 94 def repos_path(self):
95 95 """
96 96 Gets the repositories root path from database
97 97 """
98 98 settings_model = VcsSettingsModel(sa=self.sa)
99 99 return settings_model.get_repos_location()
100 100
101 101 def get(self, repo_id, cache=False):
102 102 repo = self.sa.query(Repository) \
103 103 .filter(Repository.repo_id == repo_id)
104 104
105 105 if cache:
106 106 repo = repo.options(FromCache("sql_cache_short",
107 107 "get_repo_%s" % repo_id))
108 108 return repo.scalar()
109 109
110 110 def get_repo(self, repository):
111 111 return self._get_repo(repository)
112 112
113 113 def get_by_repo_name(self, repo_name, cache=False):
114 114 repo = self.sa.query(Repository) \
115 115 .filter(Repository.repo_name == repo_name)
116 116
117 117 if cache:
118 118 repo = repo.options(FromCache("sql_cache_short",
119 119 "get_repo_%s" % repo_name))
120 120 return repo.scalar()
121 121
122 122 def _extract_id_from_repo_name(self, repo_name):
123 123 if repo_name.startswith('/'):
124 124 repo_name = repo_name.lstrip('/')
125 125 by_id_match = re.match(r'^_(\d{1,})', repo_name)
126 126 if by_id_match:
127 127 return by_id_match.groups()[0]
128 128
129 129 def get_repo_by_id(self, repo_name):
130 130 """
131 131 Extracts repo_name by id from special urls.
132 132 Example url is _11/repo_name
133 133
134 134 :param repo_name:
135 135 :return: repo object if matched else None
136 136 """
137 137 try:
138 138 _repo_id = self._extract_id_from_repo_name(repo_name)
139 139 if _repo_id:
140 140 return self.get(_repo_id)
141 141 except Exception:
142 142 log.exception('Failed to extract repo_name from URL')
143 143
144 144 return None
145 145
146 146 def get_repos_for_root(self, root, traverse=False):
147 147 if traverse:
148 148 like_expression = u'{}%'.format(safe_unicode(root))
149 149 repos = Repository.query().filter(
150 150 Repository.repo_name.like(like_expression)).all()
151 151 else:
152 152 if root and not isinstance(root, RepoGroup):
153 153 raise ValueError(
154 154 'Root must be an instance '
155 155 'of RepoGroup, got:{} instead'.format(type(root)))
156 156 repos = Repository.query().filter(Repository.group == root).all()
157 157 return repos
158 158
159 159 def get_url(self, repo):
160 160 return h.url('summary_home', repo_name=safe_str(repo.repo_name),
161 161 qualified=True)
162 162
163 163 def get_users(self, name_contains=None, limit=20, only_active=True):
164 164
165 165 # TODO: mikhail: move this method to the UserModel.
166 166 query = self.sa.query(User)
167 167 if only_active:
168 168 query = query.filter(User.active == true())
169 169
170 170 if name_contains:
171 171 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
172 172 query = query.filter(
173 173 or_(
174 174 User.name.ilike(ilike_expression),
175 175 User.lastname.ilike(ilike_expression),
176 176 User.username.ilike(ilike_expression)
177 177 )
178 178 )
179 179 query = query.limit(limit)
180 180 users = query.all()
181 181
182 182 _users = [
183 183 {
184 184 'id': user.user_id,
185 185 'first_name': user.name,
186 186 'last_name': user.lastname,
187 187 'username': user.username,
188 188 'email': user.email,
189 189 'icon_link': h.gravatar_url(user.email, 30),
190 190 'value_display': h.person(user),
191 191 'value': user.username,
192 192 'value_type': 'user',
193 193 'active': user.active,
194 194 }
195 195 for user in users
196 196 ]
197 197 return _users
198 198
199 199 def get_user_groups(self, name_contains=None, limit=20, only_active=True):
200 200 # TODO: mikhail: move this method to the UserGroupModel.
201 201 query = self.sa.query(UserGroup)
202 202 if only_active:
203 203 query = query.filter(UserGroup.users_group_active == true())
204 204
205 205 if name_contains:
206 206 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
207 207 query = query.filter(
208 208 UserGroup.users_group_name.ilike(ilike_expression))\
209 209 .order_by(func.length(UserGroup.users_group_name))\
210 210 .order_by(UserGroup.users_group_name)
211 211
212 212 query = query.limit(limit)
213 213 user_groups = query.all()
214 214 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
215 215 user_groups = UserGroupList(user_groups, perm_set=perm_set)
216 216
217 217 _groups = [
218 218 {
219 219 'id': group.users_group_id,
220 220 # TODO: marcink figure out a way to generate the url for the
221 221 # icon
222 222 'icon_link': '',
223 223 'value_display': 'Group: %s (%d members)' % (
224 224 group.users_group_name, len(group.members),),
225 225 'value': group.users_group_name,
226 226 'value_type': 'user_group',
227 227 'active': group.users_group_active,
228 228 }
229 229 for group in user_groups
230 230 ]
231 231 return _groups
232 232
233 233 @classmethod
234 234 def update_repoinfo(cls, repositories=None):
235 235 if not repositories:
236 236 repositories = Repository.getAll()
237 237 for repo in repositories:
238 238 repo.update_commit_cache()
239 239
240 240 def get_repos_as_dict(self, repo_list=None, admin=False,
241 241 super_user_actions=False):
242 242
243 243 from rhodecode.lib.utils import PartialRenderer
244 244 _render = PartialRenderer('data_table/_dt_elements.mako')
245 245 c = _render.c
246 246
247 247 def quick_menu(repo_name):
248 248 return _render('quick_menu', repo_name)
249 249
250 250 def repo_lnk(name, rtype, rstate, private, fork_of):
251 251 return _render('repo_name', name, rtype, rstate, private, fork_of,
252 252 short_name=not admin, admin=False)
253 253
254 254 def last_change(last_change):
255 if isinstance(last_change, datetime) and not last_change.tzinfo:
256 last_change = last_change + timedelta(seconds=
257 (datetime.now() - datetime.utcnow()).seconds)
255 258 return _render("last_change", last_change)
256 259
257 260 def rss_lnk(repo_name):
258 261 return _render("rss", repo_name)
259 262
260 263 def atom_lnk(repo_name):
261 264 return _render("atom", repo_name)
262 265
263 266 def last_rev(repo_name, cs_cache):
264 267 return _render('revision', repo_name, cs_cache.get('revision'),
265 268 cs_cache.get('raw_id'), cs_cache.get('author'),
266 269 cs_cache.get('message'))
267 270
268 271 def desc(desc):
269 272 if c.visual.stylify_metatags:
270 273 desc = h.urlify_text(h.escaped_stylize(desc))
271 274 else:
272 275 desc = h.urlify_text(h.html_escape(desc))
273 276
274 277 return _render('repo_desc', desc)
275 278
276 279 def state(repo_state):
277 280 return _render("repo_state", repo_state)
278 281
279 282 def repo_actions(repo_name):
280 283 return _render('repo_actions', repo_name, super_user_actions)
281 284
282 285 def user_profile(username):
283 286 return _render('user_profile', username)
284 287
285 288 repos_data = []
286 289 for repo in repo_list:
287 290 cs_cache = repo.changeset_cache
288 291 row = {
289 292 "menu": quick_menu(repo.repo_name),
290 293
291 294 "name": repo_lnk(repo.repo_name, repo.repo_type,
292 295 repo.repo_state, repo.private, repo.fork),
293 296 "name_raw": repo.repo_name.lower(),
294 297
295 298 "last_change": last_change(repo.last_db_change),
296 299 "last_change_raw": datetime_to_time(repo.last_db_change),
297 300
298 301 "last_changeset": last_rev(repo.repo_name, cs_cache),
299 302 "last_changeset_raw": cs_cache.get('revision'),
300 303
301 304 "desc": desc(repo.description),
302 305 "owner": user_profile(repo.user.username),
303 306
304 307 "state": state(repo.repo_state),
305 308 "rss": rss_lnk(repo.repo_name),
306 309
307 310 "atom": atom_lnk(repo.repo_name),
308 311 }
309 312 if admin:
310 313 row.update({
311 314 "action": repo_actions(repo.repo_name),
312 315 })
313 316 repos_data.append(row)
314 317
315 318 return repos_data
316 319
317 320 def _get_defaults(self, repo_name):
318 321 """
319 322 Gets information about repository, and returns a dict for
320 323 usage in forms
321 324
322 325 :param repo_name:
323 326 """
324 327
325 328 repo_info = Repository.get_by_repo_name(repo_name)
326 329
327 330 if repo_info is None:
328 331 return None
329 332
330 333 defaults = repo_info.get_dict()
331 334 defaults['repo_name'] = repo_info.just_name
332 335
333 336 groups = repo_info.groups_with_parents
334 337 parent_group = groups[-1] if groups else None
335 338
336 339 # we use -1 as this is how in HTML, we mark an empty group
337 340 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
338 341
339 342 keys_to_process = (
340 343 {'k': 'repo_type', 'strip': False},
341 344 {'k': 'repo_enable_downloads', 'strip': True},
342 345 {'k': 'repo_description', 'strip': True},
343 346 {'k': 'repo_enable_locking', 'strip': True},
344 347 {'k': 'repo_landing_rev', 'strip': True},
345 348 {'k': 'clone_uri', 'strip': False},
346 349 {'k': 'repo_private', 'strip': True},
347 350 {'k': 'repo_enable_statistics', 'strip': True}
348 351 )
349 352
350 353 for item in keys_to_process:
351 354 attr = item['k']
352 355 if item['strip']:
353 356 attr = remove_prefix(item['k'], 'repo_')
354 357
355 358 val = defaults[attr]
356 359 if item['k'] == 'repo_landing_rev':
357 360 val = ':'.join(defaults[attr])
358 361 defaults[item['k']] = val
359 362 if item['k'] == 'clone_uri':
360 363 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
361 364
362 365 # fill owner
363 366 if repo_info.user:
364 367 defaults.update({'user': repo_info.user.username})
365 368 else:
366 369 replacement_user = User.get_first_super_admin().username
367 370 defaults.update({'user': replacement_user})
368 371
369 372 # fill repository users
370 373 for p in repo_info.repo_to_perm:
371 374 defaults.update({'u_perm_%s' % p.user.user_id:
372 375 p.permission.permission_name})
373 376
374 377 # fill repository groups
375 378 for p in repo_info.users_group_to_perm:
376 379 defaults.update({'g_perm_%s' % p.users_group.users_group_id:
377 380 p.permission.permission_name})
378 381
379 382 return defaults
380 383
381 384 def update(self, repo, **kwargs):
382 385 try:
383 386 cur_repo = self._get_repo(repo)
384 387 source_repo_name = cur_repo.repo_name
385 388 if 'user' in kwargs:
386 389 cur_repo.user = User.get_by_username(kwargs['user'])
387 390
388 391 if 'repo_group' in kwargs:
389 392 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
390 393 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
391 394
392 395 update_keys = [
393 396 (1, 'repo_description'),
394 397 (1, 'repo_landing_rev'),
395 398 (1, 'repo_private'),
396 399 (1, 'repo_enable_downloads'),
397 400 (1, 'repo_enable_locking'),
398 401 (1, 'repo_enable_statistics'),
399 402 (0, 'clone_uri'),
400 403 (0, 'fork_id')
401 404 ]
402 405 for strip, k in update_keys:
403 406 if k in kwargs:
404 407 val = kwargs[k]
405 408 if strip:
406 409 k = remove_prefix(k, 'repo_')
407 410 if k == 'clone_uri':
408 411 from rhodecode.model.validators import Missing
409 412 _change = kwargs.get('clone_uri_change')
410 413 if _change in [Missing, 'OLD']:
411 414 # we don't change the value, so use original one
412 415 val = cur_repo.clone_uri
413 416
414 417 setattr(cur_repo, k, val)
415 418
416 419 new_name = cur_repo.get_new_name(kwargs['repo_name'])
417 420 cur_repo.repo_name = new_name
418 421
419 422 # if private flag is set, reset default permission to NONE
420 423 if kwargs.get('repo_private'):
421 424 EMPTY_PERM = 'repository.none'
422 425 RepoModel().grant_user_permission(
423 426 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
424 427 )
425 428
426 429 # handle extra fields
427 430 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
428 431 kwargs):
429 432 k = RepositoryField.un_prefix_key(field)
430 433 ex_field = RepositoryField.get_by_key_name(
431 434 key=k, repo=cur_repo)
432 435 if ex_field:
433 436 ex_field.field_value = kwargs[field]
434 437 self.sa.add(ex_field)
435 438 self.sa.add(cur_repo)
436 439
437 440 if source_repo_name != new_name:
438 441 # rename repository
439 442 self._rename_filesystem_repo(
440 443 old=source_repo_name, new=new_name)
441 444
442 445 return cur_repo
443 446 except Exception:
444 447 log.error(traceback.format_exc())
445 448 raise
446 449
447 450 def _create_repo(self, repo_name, repo_type, description, owner,
448 451 private=False, clone_uri=None, repo_group=None,
449 452 landing_rev='rev:tip', fork_of=None,
450 453 copy_fork_permissions=False, enable_statistics=False,
451 454 enable_locking=False, enable_downloads=False,
452 455 copy_group_permissions=False,
453 456 state=Repository.STATE_PENDING):
454 457 """
455 458 Create repository inside database with PENDING state, this should be
456 459 only executed by create() repo. With exception of importing existing
457 460 repos
458 461 """
459 462 from rhodecode.model.scm import ScmModel
460 463
461 464 owner = self._get_user(owner)
462 465 fork_of = self._get_repo(fork_of)
463 466 repo_group = self._get_repo_group(safe_int(repo_group))
464 467
465 468 try:
466 469 repo_name = safe_unicode(repo_name)
467 470 description = safe_unicode(description)
468 471 # repo name is just a name of repository
469 472 # while repo_name_full is a full qualified name that is combined
470 473 # with name and path of group
471 474 repo_name_full = repo_name
472 475 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
473 476
474 477 new_repo = Repository()
475 478 new_repo.repo_state = state
476 479 new_repo.enable_statistics = False
477 480 new_repo.repo_name = repo_name_full
478 481 new_repo.repo_type = repo_type
479 482 new_repo.user = owner
480 483 new_repo.group = repo_group
481 484 new_repo.description = description or repo_name
482 485 new_repo.private = private
483 486 new_repo.clone_uri = clone_uri
484 487 new_repo.landing_rev = landing_rev
485 488
486 489 new_repo.enable_statistics = enable_statistics
487 490 new_repo.enable_locking = enable_locking
488 491 new_repo.enable_downloads = enable_downloads
489 492
490 493 if repo_group:
491 494 new_repo.enable_locking = repo_group.enable_locking
492 495
493 496 if fork_of:
494 497 parent_repo = fork_of
495 498 new_repo.fork = parent_repo
496 499
497 500 events.trigger(events.RepoPreCreateEvent(new_repo))
498 501
499 502 self.sa.add(new_repo)
500 503
501 504 EMPTY_PERM = 'repository.none'
502 505 if fork_of and copy_fork_permissions:
503 506 repo = fork_of
504 507 user_perms = UserRepoToPerm.query() \
505 508 .filter(UserRepoToPerm.repository == repo).all()
506 509 group_perms = UserGroupRepoToPerm.query() \
507 510 .filter(UserGroupRepoToPerm.repository == repo).all()
508 511
509 512 for perm in user_perms:
510 513 UserRepoToPerm.create(
511 514 perm.user, new_repo, perm.permission)
512 515
513 516 for perm in group_perms:
514 517 UserGroupRepoToPerm.create(
515 518 perm.users_group, new_repo, perm.permission)
516 519 # in case we copy permissions and also set this repo to private
517 520 # override the default user permission to make it a private
518 521 # repo
519 522 if private:
520 523 RepoModel(self.sa).grant_user_permission(
521 524 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
522 525
523 526 elif repo_group and copy_group_permissions:
524 527 user_perms = UserRepoGroupToPerm.query() \
525 528 .filter(UserRepoGroupToPerm.group == repo_group).all()
526 529
527 530 group_perms = UserGroupRepoGroupToPerm.query() \
528 531 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
529 532
530 533 for perm in user_perms:
531 534 perm_name = perm.permission.permission_name.replace(
532 535 'group.', 'repository.')
533 536 perm_obj = Permission.get_by_key(perm_name)
534 537 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
535 538
536 539 for perm in group_perms:
537 540 perm_name = perm.permission.permission_name.replace(
538 541 'group.', 'repository.')
539 542 perm_obj = Permission.get_by_key(perm_name)
540 543 UserGroupRepoToPerm.create(
541 544 perm.users_group, new_repo, perm_obj)
542 545
543 546 if private:
544 547 RepoModel(self.sa).grant_user_permission(
545 548 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
546 549
547 550 else:
548 551 perm_obj = self._create_default_perms(new_repo, private)
549 552 self.sa.add(perm_obj)
550 553
551 554 # now automatically start following this repository as owner
552 555 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
553 556 owner.user_id)
554 557
555 558 # we need to flush here, in order to check if database won't
556 559 # throw any exceptions, create filesystem dirs at the very end
557 560 self.sa.flush()
558 561 events.trigger(events.RepoCreateEvent(new_repo))
559 562 return new_repo
560 563
561 564 except Exception:
562 565 log.error(traceback.format_exc())
563 566 raise
564 567
565 568 def create(self, form_data, cur_user):
566 569 """
567 570 Create repository using celery tasks
568 571
569 572 :param form_data:
570 573 :param cur_user:
571 574 """
572 575 from rhodecode.lib.celerylib import tasks, run_task
573 576 return run_task(tasks.create_repo, form_data, cur_user)
574 577
575 578 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
576 579 perm_deletions=None, check_perms=True,
577 580 cur_user=None):
578 581 if not perm_additions:
579 582 perm_additions = []
580 583 if not perm_updates:
581 584 perm_updates = []
582 585 if not perm_deletions:
583 586 perm_deletions = []
584 587
585 588 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
586 589
587 590 # update permissions
588 591 for member_id, perm, member_type in perm_updates:
589 592 member_id = int(member_id)
590 593 if member_type == 'user':
591 594 # this updates also current one if found
592 595 self.grant_user_permission(
593 596 repo=repo, user=member_id, perm=perm)
594 597 else: # set for user group
595 598 # check if we have permissions to alter this usergroup
596 599 member_name = UserGroup.get(member_id).users_group_name
597 600 if not check_perms or HasUserGroupPermissionAny(
598 601 *req_perms)(member_name, user=cur_user):
599 602 self.grant_user_group_permission(
600 603 repo=repo, group_name=member_id, perm=perm)
601 604
602 605 # set new permissions
603 606 for member_id, perm, member_type in perm_additions:
604 607 member_id = int(member_id)
605 608 if member_type == 'user':
606 609 self.grant_user_permission(
607 610 repo=repo, user=member_id, perm=perm)
608 611 else: # set for user group
609 612 # check if we have permissions to alter this usergroup
610 613 member_name = UserGroup.get(member_id).users_group_name
611 614 if not check_perms or HasUserGroupPermissionAny(
612 615 *req_perms)(member_name, user=cur_user):
613 616 self.grant_user_group_permission(
614 617 repo=repo, group_name=member_id, perm=perm)
615 618
616 619 # delete permissions
617 620 for member_id, perm, member_type in perm_deletions:
618 621 member_id = int(member_id)
619 622 if member_type == 'user':
620 623 self.revoke_user_permission(repo=repo, user=member_id)
621 624 else: # set for user group
622 625 # check if we have permissions to alter this usergroup
623 626 member_name = UserGroup.get(member_id).users_group_name
624 627 if not check_perms or HasUserGroupPermissionAny(
625 628 *req_perms)(member_name, user=cur_user):
626 629 self.revoke_user_group_permission(
627 630 repo=repo, group_name=member_id)
628 631
629 632 def create_fork(self, form_data, cur_user):
630 633 """
631 634 Simple wrapper into executing celery task for fork creation
632 635
633 636 :param form_data:
634 637 :param cur_user:
635 638 """
636 639 from rhodecode.lib.celerylib import tasks, run_task
637 640 return run_task(tasks.create_repo_fork, form_data, cur_user)
638 641
639 642 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
640 643 """
641 644 Delete given repository, forks parameter defines what do do with
642 645 attached forks. Throws AttachedForksError if deleted repo has attached
643 646 forks
644 647
645 648 :param repo:
646 649 :param forks: str 'delete' or 'detach'
647 650 :param fs_remove: remove(archive) repo from filesystem
648 651 """
649 652 if not cur_user:
650 653 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
651 654 repo = self._get_repo(repo)
652 655 if repo:
653 656 if forks == 'detach':
654 657 for r in repo.forks:
655 658 r.fork = None
656 659 self.sa.add(r)
657 660 elif forks == 'delete':
658 661 for r in repo.forks:
659 662 self.delete(r, forks='delete')
660 663 elif [f for f in repo.forks]:
661 664 raise AttachedForksError()
662 665
663 666 old_repo_dict = repo.get_dict()
664 667 events.trigger(events.RepoPreDeleteEvent(repo))
665 668 try:
666 669 self.sa.delete(repo)
667 670 if fs_remove:
668 671 self._delete_filesystem_repo(repo)
669 672 else:
670 673 log.debug('skipping removal from filesystem')
671 674 old_repo_dict.update({
672 675 'deleted_by': cur_user,
673 676 'deleted_on': time.time(),
674 677 })
675 678 log_delete_repository(**old_repo_dict)
676 679 events.trigger(events.RepoDeleteEvent(repo))
677 680 except Exception:
678 681 log.error(traceback.format_exc())
679 682 raise
680 683
681 684 def grant_user_permission(self, repo, user, perm):
682 685 """
683 686 Grant permission for user on given repository, or update existing one
684 687 if found
685 688
686 689 :param repo: Instance of Repository, repository_id, or repository name
687 690 :param user: Instance of User, user_id or username
688 691 :param perm: Instance of Permission, or permission_name
689 692 """
690 693 user = self._get_user(user)
691 694 repo = self._get_repo(repo)
692 695 permission = self._get_perm(perm)
693 696
694 697 # check if we have that permission already
695 698 obj = self.sa.query(UserRepoToPerm) \
696 699 .filter(UserRepoToPerm.user == user) \
697 700 .filter(UserRepoToPerm.repository == repo) \
698 701 .scalar()
699 702 if obj is None:
700 703 # create new !
701 704 obj = UserRepoToPerm()
702 705 obj.repository = repo
703 706 obj.user = user
704 707 obj.permission = permission
705 708 self.sa.add(obj)
706 709 log.debug('Granted perm %s to %s on %s', perm, user, repo)
707 710 action_logger_generic(
708 711 'granted permission: {} to user: {} on repo: {}'.format(
709 712 perm, user, repo), namespace='security.repo')
710 713 return obj
711 714
712 715 def revoke_user_permission(self, repo, user):
713 716 """
714 717 Revoke permission for user on given repository
715 718
716 719 :param repo: Instance of Repository, repository_id, or repository name
717 720 :param user: Instance of User, user_id or username
718 721 """
719 722
720 723 user = self._get_user(user)
721 724 repo = self._get_repo(repo)
722 725
723 726 obj = self.sa.query(UserRepoToPerm) \
724 727 .filter(UserRepoToPerm.repository == repo) \
725 728 .filter(UserRepoToPerm.user == user) \
726 729 .scalar()
727 730 if obj:
728 731 self.sa.delete(obj)
729 732 log.debug('Revoked perm on %s on %s', repo, user)
730 733 action_logger_generic(
731 734 'revoked permission from user: {} on repo: {}'.format(
732 735 user, repo), namespace='security.repo')
733 736
734 737 def grant_user_group_permission(self, repo, group_name, perm):
735 738 """
736 739 Grant permission for user group on given repository, or update
737 740 existing one if found
738 741
739 742 :param repo: Instance of Repository, repository_id, or repository name
740 743 :param group_name: Instance of UserGroup, users_group_id,
741 744 or user group name
742 745 :param perm: Instance of Permission, or permission_name
743 746 """
744 747 repo = self._get_repo(repo)
745 748 group_name = self._get_user_group(group_name)
746 749 permission = self._get_perm(perm)
747 750
748 751 # check if we have that permission already
749 752 obj = self.sa.query(UserGroupRepoToPerm) \
750 753 .filter(UserGroupRepoToPerm.users_group == group_name) \
751 754 .filter(UserGroupRepoToPerm.repository == repo) \
752 755 .scalar()
753 756
754 757 if obj is None:
755 758 # create new
756 759 obj = UserGroupRepoToPerm()
757 760
758 761 obj.repository = repo
759 762 obj.users_group = group_name
760 763 obj.permission = permission
761 764 self.sa.add(obj)
762 765 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
763 766 action_logger_generic(
764 767 'granted permission: {} to usergroup: {} on repo: {}'.format(
765 768 perm, group_name, repo), namespace='security.repo')
766 769
767 770 return obj
768 771
769 772 def revoke_user_group_permission(self, repo, group_name):
770 773 """
771 774 Revoke permission for user group on given repository
772 775
773 776 :param repo: Instance of Repository, repository_id, or repository name
774 777 :param group_name: Instance of UserGroup, users_group_id,
775 778 or user group name
776 779 """
777 780 repo = self._get_repo(repo)
778 781 group_name = self._get_user_group(group_name)
779 782
780 783 obj = self.sa.query(UserGroupRepoToPerm) \
781 784 .filter(UserGroupRepoToPerm.repository == repo) \
782 785 .filter(UserGroupRepoToPerm.users_group == group_name) \
783 786 .scalar()
784 787 if obj:
785 788 self.sa.delete(obj)
786 789 log.debug('Revoked perm to %s on %s', repo, group_name)
787 790 action_logger_generic(
788 791 'revoked permission from usergroup: {} on repo: {}'.format(
789 792 group_name, repo), namespace='security.repo')
790 793
791 794 def delete_stats(self, repo_name):
792 795 """
793 796 removes stats for given repo
794 797
795 798 :param repo_name:
796 799 """
797 800 repo = self._get_repo(repo_name)
798 801 try:
799 802 obj = self.sa.query(Statistics) \
800 803 .filter(Statistics.repository == repo).scalar()
801 804 if obj:
802 805 self.sa.delete(obj)
803 806 except Exception:
804 807 log.error(traceback.format_exc())
805 808 raise
806 809
807 810 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
808 811 field_type='str', field_desc=''):
809 812
810 813 repo = self._get_repo(repo_name)
811 814
812 815 new_field = RepositoryField()
813 816 new_field.repository = repo
814 817 new_field.field_key = field_key
815 818 new_field.field_type = field_type # python type
816 819 new_field.field_value = field_value
817 820 new_field.field_desc = field_desc
818 821 new_field.field_label = field_label
819 822 self.sa.add(new_field)
820 823 return new_field
821 824
822 825 def delete_repo_field(self, repo_name, field_key):
823 826 repo = self._get_repo(repo_name)
824 827 field = RepositoryField.get_by_key_name(field_key, repo)
825 828 if field:
826 829 self.sa.delete(field)
827 830
828 831 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
829 832 clone_uri=None, repo_store_location=None,
830 833 use_global_config=False):
831 834 """
832 835 makes repository on filesystem. It's group aware means it'll create
833 836 a repository within a group, and alter the paths accordingly of
834 837 group location
835 838
836 839 :param repo_name:
837 840 :param alias:
838 841 :param parent:
839 842 :param clone_uri:
840 843 :param repo_store_location:
841 844 """
842 845 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
843 846 from rhodecode.model.scm import ScmModel
844 847
845 848 if Repository.NAME_SEP in repo_name:
846 849 raise ValueError(
847 850 'repo_name must not contain groups got `%s`' % repo_name)
848 851
849 852 if isinstance(repo_group, RepoGroup):
850 853 new_parent_path = os.sep.join(repo_group.full_path_splitted)
851 854 else:
852 855 new_parent_path = repo_group or ''
853 856
854 857 if repo_store_location:
855 858 _paths = [repo_store_location]
856 859 else:
857 860 _paths = [self.repos_path, new_parent_path, repo_name]
858 861 # we need to make it str for mercurial
859 862 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
860 863
861 864 # check if this path is not a repository
862 865 if is_valid_repo(repo_path, self.repos_path):
863 866 raise Exception('This path %s is a valid repository' % repo_path)
864 867
865 868 # check if this path is a group
866 869 if is_valid_repo_group(repo_path, self.repos_path):
867 870 raise Exception('This path %s is a valid group' % repo_path)
868 871
869 872 log.info('creating repo %s in %s from url: `%s`',
870 873 repo_name, safe_unicode(repo_path),
871 874 obfuscate_url_pw(clone_uri))
872 875
873 876 backend = get_backend(repo_type)
874 877
875 878 config_repo = None if use_global_config else repo_name
876 879 if config_repo and new_parent_path:
877 880 config_repo = Repository.NAME_SEP.join(
878 881 (new_parent_path, config_repo))
879 882 config = make_db_config(clear_session=False, repo=config_repo)
880 883 config.set('extensions', 'largefiles', '')
881 884
882 885 # patch and reset hooks section of UI config to not run any
883 886 # hooks on creating remote repo
884 887 config.clear_section('hooks')
885 888
886 889 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
887 890 if repo_type == 'git':
888 891 repo = backend(
889 892 repo_path, config=config, create=True, src_url=clone_uri,
890 893 bare=True)
891 894 else:
892 895 repo = backend(
893 896 repo_path, config=config, create=True, src_url=clone_uri)
894 897
895 898 ScmModel().install_hooks(repo, repo_type=repo_type)
896 899
897 900 log.debug('Created repo %s with %s backend',
898 901 safe_unicode(repo_name), safe_unicode(repo_type))
899 902 return repo
900 903
901 904 def _rename_filesystem_repo(self, old, new):
902 905 """
903 906 renames repository on filesystem
904 907
905 908 :param old: old name
906 909 :param new: new name
907 910 """
908 911 log.info('renaming repo from %s to %s', old, new)
909 912
910 913 old_path = os.path.join(self.repos_path, old)
911 914 new_path = os.path.join(self.repos_path, new)
912 915 if os.path.isdir(new_path):
913 916 raise Exception(
914 917 'Was trying to rename to already existing dir %s' % new_path
915 918 )
916 919 shutil.move(old_path, new_path)
917 920
918 921 def _delete_filesystem_repo(self, repo):
919 922 """
920 923 removes repo from filesystem, the removal is acctually made by
921 924 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
922 925 repository is no longer valid for rhodecode, can be undeleted later on
923 926 by reverting the renames on this repository
924 927
925 928 :param repo: repo object
926 929 """
927 930 rm_path = os.path.join(self.repos_path, repo.repo_name)
928 931 repo_group = repo.group
929 932 log.info("Removing repository %s", rm_path)
930 933 # disable hg/git internal that it doesn't get detected as repo
931 934 alias = repo.repo_type
932 935
933 936 config = make_db_config(clear_session=False)
934 937 config.set('extensions', 'largefiles', '')
935 938 bare = getattr(repo.scm_instance(config=config), 'bare', False)
936 939
937 940 # skip this for bare git repos
938 941 if not bare:
939 942 # disable VCS repo
940 943 vcs_path = os.path.join(rm_path, '.%s' % alias)
941 944 if os.path.exists(vcs_path):
942 945 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
943 946
944 947 _now = datetime.now()
945 948 _ms = str(_now.microsecond).rjust(6, '0')
946 949 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
947 950 repo.just_name)
948 951 if repo_group:
949 952 # if repository is in group, prefix the removal path with the group
950 953 args = repo_group.full_path_splitted + [_d]
951 954 _d = os.path.join(*args)
952 955
953 956 if os.path.isdir(rm_path):
954 957 shutil.move(rm_path, os.path.join(self.repos_path, _d))
955 958
956 959
957 960 class ReadmeFinder:
958 961 """
959 962 Utility which knows how to find a readme for a specific commit.
960 963
961 964 The main idea is that this is a configurable algorithm. When creating an
962 965 instance you can define parameters, currently only the `default_renderer`.
963 966 Based on this configuration the method :meth:`search` behaves slightly
964 967 different.
965 968 """
966 969
967 970 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
968 971 path_re = re.compile(r'^docs?', re.IGNORECASE)
969 972
970 973 default_priorities = {
971 974 None: 0,
972 975 '.text': 2,
973 976 '.txt': 3,
974 977 '.rst': 1,
975 978 '.rest': 2,
976 979 '.md': 1,
977 980 '.mkdn': 2,
978 981 '.mdown': 3,
979 982 '.markdown': 4,
980 983 }
981 984
982 985 path_priority = {
983 986 'doc': 0,
984 987 'docs': 1,
985 988 }
986 989
987 990 FALLBACK_PRIORITY = 99
988 991
989 992 RENDERER_TO_EXTENSION = {
990 993 'rst': ['.rst', '.rest'],
991 994 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
992 995 }
993 996
994 997 def __init__(self, default_renderer=None):
995 998 self._default_renderer = default_renderer
996 999 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
997 1000 default_renderer, [])
998 1001
999 1002 def search(self, commit, path='/'):
1000 1003 """
1001 1004 Find a readme in the given `commit`.
1002 1005 """
1003 1006 nodes = commit.get_nodes(path)
1004 1007 matches = self._match_readmes(nodes)
1005 1008 matches = self._sort_according_to_priority(matches)
1006 1009 if matches:
1007 1010 return matches[0].node
1008 1011
1009 1012 paths = self._match_paths(nodes)
1010 1013 paths = self._sort_paths_according_to_priority(paths)
1011 1014 for path in paths:
1012 1015 match = self.search(commit, path=path)
1013 1016 if match:
1014 1017 return match
1015 1018
1016 1019 return None
1017 1020
1018 1021 def _match_readmes(self, nodes):
1019 1022 for node in nodes:
1020 1023 if not node.is_file():
1021 1024 continue
1022 1025 path = node.path.rsplit('/', 1)[-1]
1023 1026 match = self.readme_re.match(path)
1024 1027 if match:
1025 1028 extension = match.group(1)
1026 1029 yield ReadmeMatch(node, match, self._priority(extension))
1027 1030
1028 1031 def _match_paths(self, nodes):
1029 1032 for node in nodes:
1030 1033 if not node.is_dir():
1031 1034 continue
1032 1035 match = self.path_re.match(node.path)
1033 1036 if match:
1034 1037 yield node.path
1035 1038
1036 1039 def _priority(self, extension):
1037 1040 renderer_priority = (
1038 1041 0 if extension in self._renderer_extensions else 1)
1039 1042 extension_priority = self.default_priorities.get(
1040 1043 extension, self.FALLBACK_PRIORITY)
1041 1044 return (renderer_priority, extension_priority)
1042 1045
1043 1046 def _sort_according_to_priority(self, matches):
1044 1047
1045 1048 def priority_and_path(match):
1046 1049 return (match.priority, match.path)
1047 1050
1048 1051 return sorted(matches, key=priority_and_path)
1049 1052
1050 1053 def _sort_paths_according_to_priority(self, paths):
1051 1054
1052 1055 def priority_and_path(path):
1053 1056 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1054 1057
1055 1058 return sorted(paths, key=priority_and_path)
1056 1059
1057 1060
1058 1061 class ReadmeMatch:
1059 1062
1060 1063 def __init__(self, node, match, priority):
1061 1064 self.node = node
1062 1065 self._match = match
1063 1066 self.priority = priority
1064 1067
1065 1068 @property
1066 1069 def path(self):
1067 1070 return self.node.path
1068 1071
1069 1072 def __repr__(self):
1070 1073 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
General Comments 0
You need to be logged in to leave comments. Login now