##// END OF EJS Templates
iterate over repos groups for bool() calls to actually work on it....
marcink -
r3418:9fe4543b beta
parent child Browse files
Show More
@@ -1,633 +1,631 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.scm
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Scm model for RhodeCode
7 7
8 8 :created_on: Apr 9, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 from __future__ import with_statement
26 26 import os
27 27 import re
28 28 import time
29 29 import traceback
30 30 import logging
31 31 import cStringIO
32 32 import pkg_resources
33 33 from os.path import dirname as dn, join as jn
34 34
35 35 from sqlalchemy import func
36 36 from pylons.i18n.translation import _
37 37
38 38 import rhodecode
39 39 from rhodecode.lib.vcs import get_backend
40 40 from rhodecode.lib.vcs.exceptions import RepositoryError
41 41 from rhodecode.lib.vcs.utils.lazy import LazyProperty
42 42 from rhodecode.lib.vcs.nodes import FileNode
43 43 from rhodecode.lib.vcs.backends.base import EmptyChangeset
44 44
45 45 from rhodecode import BACKENDS
46 46 from rhodecode.lib import helpers as h
47 47 from rhodecode.lib.utils2 import safe_str, safe_unicode
48 48 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
49 49 from rhodecode.lib.utils import get_filesystem_repos, make_ui, \
50 50 action_logger, REMOVED_REPO_PAT
51 51 from rhodecode.model import BaseModel
52 52 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
53 53 UserFollowing, UserLog, User, RepoGroup, PullRequest
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class UserTemp(object):
59 59 def __init__(self, user_id):
60 60 self.user_id = user_id
61 61
62 62 def __repr__(self):
63 63 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
64 64
65 65
66 66 class RepoTemp(object):
67 67 def __init__(self, repo_id):
68 68 self.repo_id = repo_id
69 69
70 70 def __repr__(self):
71 71 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
72 72
73 73
74 74 class CachedRepoList(object):
75 75 """
76 76 Cached repo list, uses in-memory cache after initialization, that is
77 77 super fast
78 78 """
79 79
80 80 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
81 81 self.db_repo_list = db_repo_list
82 82 self.repos_path = repos_path
83 83 self.order_by = order_by
84 84 self.reversed = (order_by or '').startswith('-')
85 85 if not perm_set:
86 86 perm_set = ['repository.read', 'repository.write',
87 87 'repository.admin']
88 88 self.perm_set = perm_set
89 89
90 90 def __len__(self):
91 91 return len(self.db_repo_list)
92 92
93 93 def __repr__(self):
94 94 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
95 95
96 96 def __iter__(self):
97 97 # pre-propagated cache_map to save executing select statements
98 98 # for each repo
99 99 cache_map = CacheInvalidation.get_cache_map()
100 100
101 101 for dbr in self.db_repo_list:
102 102 scmr = dbr.scm_instance_cached(cache_map)
103 103 # check permission at this level
104 104 if not HasRepoPermissionAny(
105 105 *self.perm_set
106 106 )(dbr.repo_name, 'get repo check'):
107 107 continue
108 108
109 109 try:
110 110 last_change = scmr.last_change
111 111 tip = h.get_changeset_safe(scmr, 'tip')
112 112 except Exception:
113 113 log.error(
114 114 '%s this repository is present in database but it '
115 115 'cannot be created as an scm instance, org_exc:%s'
116 116 % (dbr.repo_name, traceback.format_exc())
117 117 )
118 118 continue
119 119
120 120 tmp_d = {}
121 121 tmp_d['name'] = dbr.repo_name
122 122 tmp_d['name_sort'] = tmp_d['name'].lower()
123 123 tmp_d['raw_name'] = tmp_d['name'].lower()
124 124 tmp_d['description'] = dbr.description
125 125 tmp_d['description_sort'] = tmp_d['description'].lower()
126 126 tmp_d['last_change'] = last_change
127 127 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
128 128 tmp_d['tip'] = tip.raw_id
129 129 tmp_d['tip_sort'] = tip.revision
130 130 tmp_d['rev'] = tip.revision
131 131 tmp_d['contact'] = dbr.user.full_contact
132 132 tmp_d['contact_sort'] = tmp_d['contact']
133 133 tmp_d['owner_sort'] = tmp_d['contact']
134 134 tmp_d['repo_archives'] = list(scmr._get_archives())
135 135 tmp_d['last_msg'] = tip.message
136 136 tmp_d['author'] = tip.author
137 137 tmp_d['dbrepo'] = dbr.get_dict()
138 138 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
139 139 yield tmp_d
140 140
141 141
142 142 class SimpleCachedRepoList(CachedRepoList):
143 143 """
144 144 Lighter version of CachedRepoList without the scm initialisation
145 145 """
146 146
147 147 def __iter__(self):
148 148 for dbr in self.db_repo_list:
149 149 # check permission at this level
150 150 if not HasRepoPermissionAny(
151 151 *self.perm_set
152 152 )(dbr.repo_name, 'get repo check'):
153 153 continue
154 154
155 155 tmp_d = {}
156 156 tmp_d['name'] = dbr.repo_name
157 157 tmp_d['name_sort'] = tmp_d['name'].lower()
158 158 tmp_d['raw_name'] = tmp_d['name'].lower()
159 159 tmp_d['description'] = dbr.description
160 160 tmp_d['description_sort'] = tmp_d['description'].lower()
161 161 tmp_d['dbrepo'] = dbr.get_dict()
162 162 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
163 163 yield tmp_d
164 164
165 165
166 166 class GroupList(object):
167 167
168 168 def __init__(self, db_repo_group_list, perm_set=None):
169 169 """
170 170 Creates iterator from given list of group objects, additionally
171 171 checking permission for them from perm_set var
172 172
173 173 :param db_repo_group_list:
174 174 :param perm_set: list of permissons to check
175 175 """
176 176 self.db_repo_group_list = db_repo_group_list
177 177 if not perm_set:
178 178 perm_set = ['group.read', 'group.write', 'group.admin']
179 179 self.perm_set = perm_set
180 180
181 181 def __len__(self):
182 182 return len(self.db_repo_group_list)
183 183
184 184 def __repr__(self):
185 185 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
186 186
187 187 def __iter__(self):
188 188 for dbgr in self.db_repo_group_list:
189 189 # check permission at this level
190 190 if not HasReposGroupPermissionAny(
191 191 *self.perm_set
192 192 )(dbgr.group_name, 'get group repo check'):
193 193 continue
194 194
195 195 yield dbgr
196 196
197 197
198 198 class ScmModel(BaseModel):
199 199 """
200 200 Generic Scm Model
201 201 """
202 202
203 203 def __get_repo(self, instance):
204 204 cls = Repository
205 205 if isinstance(instance, cls):
206 206 return instance
207 207 elif isinstance(instance, int) or safe_str(instance).isdigit():
208 208 return cls.get(instance)
209 209 elif isinstance(instance, basestring):
210 210 return cls.get_by_repo_name(instance)
211 211 elif instance:
212 212 raise Exception('given object must be int, basestr or Instance'
213 213 ' of %s got %s' % (type(cls), type(instance)))
214 214
215 215 @LazyProperty
216 216 def repos_path(self):
217 217 """
218 218 Get's the repositories root path from database
219 219 """
220 220
221 221 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
222 222
223 223 return q.ui_value
224 224
225 225 def repo_scan(self, repos_path=None):
226 226 """
227 227 Listing of repositories in given path. This path should not be a
228 228 repository itself. Return a dictionary of repository objects
229 229
230 230 :param repos_path: path to directory containing repositories
231 231 """
232 232
233 233 if repos_path is None:
234 234 repos_path = self.repos_path
235 235
236 236 log.info('scanning for repositories in %s' % repos_path)
237 237
238 238 baseui = make_ui('db')
239 239 repos = {}
240 240
241 241 for name, path in get_filesystem_repos(repos_path, recursive=True):
242 242 # name need to be decomposed and put back together using the /
243 243 # since this is internal storage separator for rhodecode
244 244 name = Repository.normalize_repo_name(name)
245 245
246 246 try:
247 247 if name in repos:
248 248 raise RepositoryError('Duplicate repository name %s '
249 249 'found in %s' % (name, path))
250 250 else:
251 251
252 252 klass = get_backend(path[0])
253 253
254 254 if path[0] == 'hg' and path[0] in BACKENDS.keys():
255 255 repos[name] = klass(safe_str(path[1]), baseui=baseui)
256 256
257 257 if path[0] == 'git' and path[0] in BACKENDS.keys():
258 258 repos[name] = klass(path[1])
259 259 except OSError:
260 260 continue
261 261 log.debug('found %s paths with repositories' % (len(repos)))
262 262 return repos
263 263
264 264 def get_repos(self, all_repos=None, sort_key=None, simple=False):
265 265 """
266 266 Get all repos from db and for each repo create it's
267 267 backend instance and fill that backed with information from database
268 268
269 269 :param all_repos: list of repository names as strings
270 270 give specific repositories list, good for filtering
271 271
272 272 :param sort_key: initial sorting of repos
273 273 :param simple: use SimpleCachedList - one without the SCM info
274 274 """
275 275 if all_repos is None:
276 276 all_repos = self.sa.query(Repository)\
277 277 .filter(Repository.group_id == None)\
278 278 .order_by(func.lower(Repository.repo_name)).all()
279 279 if simple:
280 280 repo_iter = SimpleCachedRepoList(all_repos,
281 281 repos_path=self.repos_path,
282 282 order_by=sort_key)
283 283 else:
284 284 repo_iter = CachedRepoList(all_repos,
285 285 repos_path=self.repos_path,
286 286 order_by=sort_key)
287 287
288 288 return repo_iter
289 289
290 290 def get_repos_groups(self, all_groups=None):
291 291 if all_groups is None:
292 292 all_groups = RepoGroup.query()\
293 293 .filter(RepoGroup.group_parent_id == None).all()
294 group_iter = GroupList(all_groups)
295
296 return group_iter
294 return [x for x in GroupList(all_groups)]
297 295
298 296 def mark_for_invalidation(self, repo_name):
299 297 """
300 298 Puts cache invalidation task into db for
301 299 further global cache invalidation
302 300
303 301 :param repo_name: this repo that should invalidation take place
304 302 """
305 303 invalidated_keys = CacheInvalidation.set_invalidate(repo_name=repo_name)
306 304 repo = Repository.get_by_repo_name(repo_name)
307 305 if repo:
308 306 repo.update_changeset_cache()
309 307 return invalidated_keys
310 308
311 309 def toggle_following_repo(self, follow_repo_id, user_id):
312 310
313 311 f = self.sa.query(UserFollowing)\
314 312 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
315 313 .filter(UserFollowing.user_id == user_id).scalar()
316 314
317 315 if f is not None:
318 316 try:
319 317 self.sa.delete(f)
320 318 action_logger(UserTemp(user_id),
321 319 'stopped_following_repo',
322 320 RepoTemp(follow_repo_id))
323 321 return
324 322 except:
325 323 log.error(traceback.format_exc())
326 324 raise
327 325
328 326 try:
329 327 f = UserFollowing()
330 328 f.user_id = user_id
331 329 f.follows_repo_id = follow_repo_id
332 330 self.sa.add(f)
333 331
334 332 action_logger(UserTemp(user_id),
335 333 'started_following_repo',
336 334 RepoTemp(follow_repo_id))
337 335 except:
338 336 log.error(traceback.format_exc())
339 337 raise
340 338
341 339 def toggle_following_user(self, follow_user_id, user_id):
342 340 f = self.sa.query(UserFollowing)\
343 341 .filter(UserFollowing.follows_user_id == follow_user_id)\
344 342 .filter(UserFollowing.user_id == user_id).scalar()
345 343
346 344 if f is not None:
347 345 try:
348 346 self.sa.delete(f)
349 347 return
350 348 except:
351 349 log.error(traceback.format_exc())
352 350 raise
353 351
354 352 try:
355 353 f = UserFollowing()
356 354 f.user_id = user_id
357 355 f.follows_user_id = follow_user_id
358 356 self.sa.add(f)
359 357 except:
360 358 log.error(traceback.format_exc())
361 359 raise
362 360
363 361 def is_following_repo(self, repo_name, user_id, cache=False):
364 362 r = self.sa.query(Repository)\
365 363 .filter(Repository.repo_name == repo_name).scalar()
366 364
367 365 f = self.sa.query(UserFollowing)\
368 366 .filter(UserFollowing.follows_repository == r)\
369 367 .filter(UserFollowing.user_id == user_id).scalar()
370 368
371 369 return f is not None
372 370
373 371 def is_following_user(self, username, user_id, cache=False):
374 372 u = User.get_by_username(username)
375 373
376 374 f = self.sa.query(UserFollowing)\
377 375 .filter(UserFollowing.follows_user == u)\
378 376 .filter(UserFollowing.user_id == user_id).scalar()
379 377
380 378 return f is not None
381 379
382 380 def get_followers(self, repo):
383 381 repo = self._get_repo(repo)
384 382
385 383 return self.sa.query(UserFollowing)\
386 384 .filter(UserFollowing.follows_repository == repo).count()
387 385
388 386 def get_forks(self, repo):
389 387 repo = self._get_repo(repo)
390 388 return self.sa.query(Repository)\
391 389 .filter(Repository.fork == repo).count()
392 390
393 391 def get_pull_requests(self, repo):
394 392 repo = self._get_repo(repo)
395 393 return self.sa.query(PullRequest)\
396 394 .filter(PullRequest.other_repo == repo).count()
397 395
398 396 def mark_as_fork(self, repo, fork, user):
399 397 repo = self.__get_repo(repo)
400 398 fork = self.__get_repo(fork)
401 399 if fork and repo.repo_id == fork.repo_id:
402 400 raise Exception("Cannot set repository as fork of itself")
403 401 repo.fork = fork
404 402 self.sa.add(repo)
405 403 return repo
406 404
407 405 def pull_changes(self, repo, username):
408 406 dbrepo = self.__get_repo(repo)
409 407 clone_uri = dbrepo.clone_uri
410 408 if not clone_uri:
411 409 raise Exception("This repository doesn't have a clone uri")
412 410
413 411 repo = dbrepo.scm_instance
414 412 from rhodecode import CONFIG
415 413 try:
416 414 extras = {
417 415 'ip': '',
418 416 'username': username,
419 417 'action': 'push_remote',
420 418 'repository': dbrepo.repo_name,
421 419 'scm': repo.alias,
422 420 'config': CONFIG['__file__'],
423 421 'make_lock': None,
424 422 'locked_by': [None, None]
425 423 }
426 424
427 425 Repository.inject_ui(repo, extras=extras)
428 426
429 427 if repo.alias == 'git':
430 428 repo.fetch(clone_uri)
431 429 else:
432 430 repo.pull(clone_uri)
433 431 self.mark_for_invalidation(dbrepo.repo_name)
434 432 except:
435 433 log.error(traceback.format_exc())
436 434 raise
437 435
438 436 def commit_change(self, repo, repo_name, cs, user, author, message,
439 437 content, f_path):
440 438 """
441 439 Commits changes
442 440
443 441 :param repo: SCM instance
444 442
445 443 """
446 444
447 445 if repo.alias == 'hg':
448 446 from rhodecode.lib.vcs.backends.hg import \
449 447 MercurialInMemoryChangeset as IMC
450 448 elif repo.alias == 'git':
451 449 from rhodecode.lib.vcs.backends.git import \
452 450 GitInMemoryChangeset as IMC
453 451
454 452 # decoding here will force that we have proper encoded values
455 453 # in any other case this will throw exceptions and deny commit
456 454 content = safe_str(content)
457 455 path = safe_str(f_path)
458 456 # message and author needs to be unicode
459 457 # proper backend should then translate that into required type
460 458 message = safe_unicode(message)
461 459 author = safe_unicode(author)
462 460 m = IMC(repo)
463 461 m.change(FileNode(path, content))
464 462 tip = m.commit(message=message,
465 463 author=author,
466 464 parents=[cs], branch=cs.branch)
467 465
468 466 action = 'push_local:%s' % tip.raw_id
469 467 action_logger(user, action, repo_name)
470 468 self.mark_for_invalidation(repo_name)
471 469 return tip
472 470
473 471 def create_node(self, repo, repo_name, cs, user, author, message, content,
474 472 f_path):
475 473 if repo.alias == 'hg':
476 474 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
477 475 elif repo.alias == 'git':
478 476 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
479 477 # decoding here will force that we have proper encoded values
480 478 # in any other case this will throw exceptions and deny commit
481 479
482 480 if isinstance(content, (basestring,)):
483 481 content = safe_str(content)
484 482 elif isinstance(content, (file, cStringIO.OutputType,)):
485 483 content = content.read()
486 484 else:
487 485 raise Exception('Content is of unrecognized type %s' % (
488 486 type(content)
489 487 ))
490 488
491 489 message = safe_unicode(message)
492 490 author = safe_unicode(author)
493 491 path = safe_str(f_path)
494 492 m = IMC(repo)
495 493
496 494 if isinstance(cs, EmptyChangeset):
497 495 # EmptyChangeset means we we're editing empty repository
498 496 parents = None
499 497 else:
500 498 parents = [cs]
501 499
502 500 m.add(FileNode(path, content=content))
503 501 tip = m.commit(message=message,
504 502 author=author,
505 503 parents=parents, branch=cs.branch)
506 504
507 505 action = 'push_local:%s' % tip.raw_id
508 506 action_logger(user, action, repo_name)
509 507 self.mark_for_invalidation(repo_name)
510 508 return tip
511 509
512 510 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
513 511 """
514 512 recursive walk in root dir and return a set of all path in that dir
515 513 based on repository walk function
516 514
517 515 :param repo_name: name of repository
518 516 :param revision: revision for which to list nodes
519 517 :param root_path: root path to list
520 518 :param flat: return as a list, if False returns a dict with decription
521 519
522 520 """
523 521 _files = list()
524 522 _dirs = list()
525 523 try:
526 524 _repo = self.__get_repo(repo_name)
527 525 changeset = _repo.scm_instance.get_changeset(revision)
528 526 root_path = root_path.lstrip('/')
529 527 for topnode, dirs, files in changeset.walk(root_path):
530 528 for f in files:
531 529 _files.append(f.path if flat else {"name": f.path,
532 530 "type": "file"})
533 531 for d in dirs:
534 532 _dirs.append(d.path if flat else {"name": d.path,
535 533 "type": "dir"})
536 534 except RepositoryError:
537 535 log.debug(traceback.format_exc())
538 536 raise
539 537
540 538 return _dirs, _files
541 539
542 540 def get_unread_journal(self):
543 541 return self.sa.query(UserLog).count()
544 542
545 543 def get_repo_landing_revs(self, repo=None):
546 544 """
547 545 Generates select option with tags branches and bookmarks (for hg only)
548 546 grouped by type
549 547
550 548 :param repo:
551 549 :type repo:
552 550 """
553 551
554 552 hist_l = []
555 553 choices = []
556 554 repo = self.__get_repo(repo)
557 555 hist_l.append(['tip', _('latest tip')])
558 556 choices.append('tip')
559 557 if not repo:
560 558 return choices, hist_l
561 559
562 560 repo = repo.scm_instance
563 561
564 562 branches_group = ([(k, k) for k, v in
565 563 repo.branches.iteritems()], _("Branches"))
566 564 hist_l.append(branches_group)
567 565 choices.extend([x[0] for x in branches_group[0]])
568 566
569 567 if repo.alias == 'hg':
570 568 bookmarks_group = ([(k, k) for k, v in
571 569 repo.bookmarks.iteritems()], _("Bookmarks"))
572 570 hist_l.append(bookmarks_group)
573 571 choices.extend([x[0] for x in bookmarks_group[0]])
574 572
575 573 tags_group = ([(k, k) for k, v in
576 574 repo.tags.iteritems()], _("Tags"))
577 575 hist_l.append(tags_group)
578 576 choices.extend([x[0] for x in tags_group[0]])
579 577
580 578 return choices, hist_l
581 579
582 580 def install_git_hook(self, repo, force_create=False):
583 581 """
584 582 Creates a rhodecode hook inside a git repository
585 583
586 584 :param repo: Instance of VCS repo
587 585 :param force_create: Create even if same name hook exists
588 586 """
589 587
590 588 loc = jn(repo.path, 'hooks')
591 589 if not repo.bare:
592 590 loc = jn(repo.path, '.git', 'hooks')
593 591 if not os.path.isdir(loc):
594 592 os.makedirs(loc)
595 593
596 594 tmpl_post = pkg_resources.resource_string(
597 595 'rhodecode', jn('config', 'post_receive_tmpl.py')
598 596 )
599 597 tmpl_pre = pkg_resources.resource_string(
600 598 'rhodecode', jn('config', 'pre_receive_tmpl.py')
601 599 )
602 600
603 601 for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
604 602 _hook_file = jn(loc, '%s-receive' % h_type)
605 603 _rhodecode_hook = False
606 604 log.debug('Installing git hook in repo %s' % repo)
607 605 if os.path.exists(_hook_file):
608 606 # let's take a look at this hook, maybe it's rhodecode ?
609 607 log.debug('hook exists, checking if it is from rhodecode')
610 608 _HOOK_VER_PAT = re.compile(r'^RC_HOOK_VER')
611 609 with open(_hook_file, 'rb') as f:
612 610 data = f.read()
613 611 matches = re.compile(r'(?:%s)\s*=\s*(.*)'
614 612 % 'RC_HOOK_VER').search(data)
615 613 if matches:
616 614 try:
617 615 ver = matches.groups()[0]
618 616 log.debug('got %s it is rhodecode' % (ver))
619 617 _rhodecode_hook = True
620 618 except:
621 619 log.error(traceback.format_exc())
622 620 else:
623 621 # there is no hook in this dir, so we want to create one
624 622 _rhodecode_hook = True
625 623
626 624 if _rhodecode_hook or force_create:
627 625 log.debug('writing %s hook file !' % h_type)
628 626 with open(_hook_file, 'wb') as f:
629 627 tmpl = tmpl.replace('_TMPL_', rhodecode.__version__)
630 628 f.write(tmpl)
631 629 os.chmod(_hook_file, 0755)
632 630 else:
633 631 log.debug('skipping writing hook file')
General Comments 0
You need to be logged in to leave comments. Login now