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