##// END OF EJS Templates
merge
marcink -
r1230:146b7309 merge beta
parent child Browse files
Show More
@@ -1,412 +1,412 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) 2009-2011 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 import os
26 26 import time
27 27 import traceback
28 28 import logging
29 29
30 30 from mercurial import ui
31 31
32 32 from sqlalchemy.exc import DatabaseError
33 33 from sqlalchemy.orm import make_transient
34 34
35 35 from beaker.cache import cache_region, region_invalidate
36 36
37 37 from vcs import get_backend
38 38 from vcs.utils.helpers import get_scm
39 39 from vcs.exceptions import RepositoryError, VCSError
40 40 from vcs.utils.lazy import LazyProperty
41 41
42 42 from rhodecode import BACKENDS
43 43 from rhodecode.lib import helpers as h
44 44 from rhodecode.lib.auth import HasRepoPermissionAny
45 45 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
46 46 action_logger
47 47 from rhodecode.model import BaseModel
48 48 from rhodecode.model.user import UserModel
49 49 from rhodecode.model.repo import RepoModel
50 50 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
51 51 UserFollowing, UserLog
52 52 from rhodecode.model.caching_query import FromCache
53 53
54 54 log = logging.getLogger(__name__)
55 55
56 56
57 57 class UserTemp(object):
58 58 def __init__(self, user_id):
59 59 self.user_id = user_id
60 60
61 61 def __repr__(self):
62 62 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
63 63
64 64
65 65 class RepoTemp(object):
66 66 def __init__(self, repo_id):
67 67 self.repo_id = repo_id
68 68
69 69 def __repr__(self):
70 70 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
71 71
72 72
73 73 class ScmModel(BaseModel):
74 74 """Generic Scm Model
75 75 """
76 76
77 77 @LazyProperty
78 78 def repos_path(self):
79 79 """Get's the repositories root path from database
80 80 """
81 81
82 82 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
83 83
84 84 return q.ui_value
85 85
86 86 def repo_scan(self, repos_path=None):
87 87 """Listing of repositories in given path. This path should not be a
88 88 repository itself. Return a dictionary of repository objects
89 89
90 90 :param repos_path: path to directory containing repositories
91 91 """
92 92
93 93 log.info('scanning for repositories in %s', repos_path)
94 94
95 95 if repos_path is None:
96 96 repos_path = self.repos_path
97 97
98 98 baseui = make_ui('db')
99 99 repos_list = {}
100 100
101 101 for name, path in get_filesystem_repos(repos_path, recursive=True):
102 102 try:
103 103 if name in repos_list:
104 104 raise RepositoryError('Duplicate repository name %s '
105 105 'found in %s' % (name, path))
106 106 else:
107 107
108 108 klass = get_backend(path[0])
109 109
110 110 if path[0] == 'hg' and path[0] in BACKENDS.keys():
111 111 repos_list[name] = klass(path[1], baseui=baseui)
112 112
113 113 if path[0] == 'git' and path[0] in BACKENDS.keys():
114 114 repos_list[name] = klass(path[1])
115 115 except OSError:
116 116 continue
117 117
118 118 return repos_list
119 119
120 120 def get_repos(self, all_repos=None):
121 121 """Get all repos from db and for each repo create it's
122 122 backend instance and fill that backed with information from database
123 123
124 124 :param all_repos: give specific repositories list, good for filtering
125 125 this have to be a list of just the repository names
126 126 """
127 127 if all_repos is None:
128 128 repos = self.sa.query(Repository)\
129 129 .order_by(Repository.repo_name).all()
130 130 all_repos = [r.repo_name for r in repos]
131 131
132 132 #get the repositories that should be invalidated
133 133 invalidation_list = [str(x.cache_key) for x in \
134 134 self.sa.query(CacheInvalidation.cache_key)\
135 135 .filter(CacheInvalidation.cache_active == False)\
136 136 .all()]
137 137 for r_name in all_repos:
138 138 r_dbr = self.get(r_name, invalidation_list)
139 139 if r_dbr is not None:
140 140 repo, dbrepo = r_dbr
141 141
142 if not repo and dbrepo:
142 if repo is None and dbrepo is None:
143 143 log.error('Repository %s looks somehow corrupted', r_name)
144 144 continue
145 145 last_change = repo.last_change
146 146 tip = h.get_changeset_safe(repo, 'tip')
147 147
148 148 tmp_d = {}
149 149 tmp_d['name'] = dbrepo.repo_name
150 150 tmp_d['name_sort'] = tmp_d['name'].lower()
151 151 tmp_d['description'] = dbrepo.description
152 152 tmp_d['description_sort'] = tmp_d['description']
153 153 tmp_d['last_change'] = last_change
154 154 tmp_d['last_change_sort'] = time.mktime(last_change \
155 155 .timetuple())
156 156 tmp_d['tip'] = tip.raw_id
157 157 tmp_d['tip_sort'] = tip.revision
158 158 tmp_d['rev'] = tip.revision
159 159 tmp_d['contact'] = dbrepo.user.full_contact
160 160 tmp_d['contact_sort'] = tmp_d['contact']
161 161 tmp_d['owner_sort'] = tmp_d['contact']
162 162 tmp_d['repo_archives'] = list(repo._get_archives())
163 163 tmp_d['last_msg'] = tip.message
164 164 tmp_d['repo'] = repo
165 165 tmp_d['dbrepo'] = dbrepo.get_dict()
166 166 tmp_d['dbrepo_fork'] = dbrepo.fork.get_dict() if dbrepo.fork \
167 167 else {}
168 168 yield tmp_d
169 169
170 170 def get(self, repo_name, invalidation_list=None, retval='all'):
171 171 """Returns a tuple of Repository,DbRepository,
172 172 Get's repository from given name, creates BackendInstance and
173 173 propagates it's data from database with all additional information
174 174
175 175 :param repo_name:
176 176 :param invalidation_list: if a invalidation list is given the get
177 177 method should not manually check if this repository needs
178 178 invalidation and just invalidate the repositories in list
179 179 :param retval: string specifing what to return one of 'repo','dbrepo',
180 180 'all'if repo or dbrepo is given it'll just lazy load chosen type
181 181 and return None as the second
182 182 """
183 183 if not HasRepoPermissionAny('repository.read', 'repository.write',
184 184 'repository.admin')(repo_name, 'get repo check'):
185 185 return
186 186
187 187 #======================================================================
188 188 # CACHE FUNCTION
189 189 #======================================================================
190 190 @cache_region('long_term')
191 191 def _get_repo(repo_name):
192 192
193 193 repo_path = os.path.join(self.repos_path, repo_name)
194 194
195 195 try:
196 196 alias = get_scm(repo_path)[0]
197 197 log.debug('Creating instance of %s repository', alias)
198 198 backend = get_backend(alias)
199 199 except VCSError:
200 200 log.error(traceback.format_exc())
201 201 log.error('Perhaps this repository is in db and not in '
202 202 'filesystem run rescan repositories with '
203 203 '"destroy old data " option from admin panel')
204 204 return
205 205
206 206 if alias == 'hg':
207 207 repo = backend(repo_path, create=False, baseui=make_ui('db'))
208 208 #skip hidden web repository
209 209 if repo._get_hidden():
210 210 return
211 211 else:
212 212 repo = backend(repo_path, create=False)
213 213
214 214 return repo
215 215
216 216 pre_invalidate = True
217 217 dbinvalidate = False
218 218
219 219 if invalidation_list is not None:
220 220 pre_invalidate = repo_name in invalidation_list
221 221
222 222 if pre_invalidate:
223 223 #this returns object to invalidate
224 224 invalidate = self._should_invalidate(repo_name)
225 225 if invalidate:
226 226 log.info('invalidating cache for repository %s', repo_name)
227 227 region_invalidate(_get_repo, None, repo_name)
228 228 self._mark_invalidated(invalidate)
229 229 dbinvalidate = True
230 230
231 231 r, dbr = None, None
232 232 if retval == 'repo' or 'all':
233 233 r = _get_repo(repo_name)
234 234 if retval == 'dbrepo' or 'all':
235 235 dbr = RepoModel().get_full(repo_name, cache=True,
236 236 invalidate=dbinvalidate)
237 237
238 238 return r, dbr
239 239
240 240 def mark_for_invalidation(self, repo_name):
241 241 """Puts cache invalidation task into db for
242 242 further global cache invalidation
243 243
244 244 :param repo_name: this repo that should invalidation take place
245 245 """
246 246
247 247 log.debug('marking %s for invalidation', repo_name)
248 248 cache = self.sa.query(CacheInvalidation)\
249 249 .filter(CacheInvalidation.cache_key == repo_name).scalar()
250 250
251 251 if cache:
252 252 #mark this cache as inactive
253 253 cache.cache_active = False
254 254 else:
255 255 log.debug('cache key not found in invalidation db -> creating one')
256 256 cache = CacheInvalidation(repo_name)
257 257
258 258 try:
259 259 self.sa.add(cache)
260 260 self.sa.commit()
261 261 except (DatabaseError,):
262 262 log.error(traceback.format_exc())
263 263 self.sa.rollback()
264 264
265 265 def toggle_following_repo(self, follow_repo_id, user_id):
266 266
267 267 f = self.sa.query(UserFollowing)\
268 268 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
269 269 .filter(UserFollowing.user_id == user_id).scalar()
270 270
271 271 if f is not None:
272 272
273 273 try:
274 274 self.sa.delete(f)
275 275 self.sa.commit()
276 276 action_logger(UserTemp(user_id),
277 277 'stopped_following_repo',
278 278 RepoTemp(follow_repo_id))
279 279 return
280 280 except:
281 281 log.error(traceback.format_exc())
282 282 self.sa.rollback()
283 283 raise
284 284
285 285 try:
286 286 f = UserFollowing()
287 287 f.user_id = user_id
288 288 f.follows_repo_id = follow_repo_id
289 289 self.sa.add(f)
290 290 self.sa.commit()
291 291 action_logger(UserTemp(user_id),
292 292 'started_following_repo',
293 293 RepoTemp(follow_repo_id))
294 294 except:
295 295 log.error(traceback.format_exc())
296 296 self.sa.rollback()
297 297 raise
298 298
299 299 def toggle_following_user(self, follow_user_id, user_id):
300 300 f = self.sa.query(UserFollowing)\
301 301 .filter(UserFollowing.follows_user_id == follow_user_id)\
302 302 .filter(UserFollowing.user_id == user_id).scalar()
303 303
304 304 if f is not None:
305 305 try:
306 306 self.sa.delete(f)
307 307 self.sa.commit()
308 308 return
309 309 except:
310 310 log.error(traceback.format_exc())
311 311 self.sa.rollback()
312 312 raise
313 313
314 314 try:
315 315 f = UserFollowing()
316 316 f.user_id = user_id
317 317 f.follows_user_id = follow_user_id
318 318 self.sa.add(f)
319 319 self.sa.commit()
320 320 except:
321 321 log.error(traceback.format_exc())
322 322 self.sa.rollback()
323 323 raise
324 324
325 325 def is_following_repo(self, repo_name, user_id, cache=False):
326 326 r = self.sa.query(Repository)\
327 327 .filter(Repository.repo_name == repo_name).scalar()
328 328
329 329 f = self.sa.query(UserFollowing)\
330 330 .filter(UserFollowing.follows_repository == r)\
331 331 .filter(UserFollowing.user_id == user_id).scalar()
332 332
333 333 return f is not None
334 334
335 335 def is_following_user(self, username, user_id, cache=False):
336 336 u = UserModel(self.sa).get_by_username(username)
337 337
338 338 f = self.sa.query(UserFollowing)\
339 339 .filter(UserFollowing.follows_user == u)\
340 340 .filter(UserFollowing.user_id == user_id).scalar()
341 341
342 342 return f is not None
343 343
344 344 def get_followers(self, repo_id):
345 345 if isinstance(repo_id, int):
346 346 return self.sa.query(UserFollowing)\
347 347 .filter(UserFollowing.follows_repo_id == repo_id).count()
348 348 else:
349 349 return self.sa.query(UserFollowing)\
350 350 .filter(UserFollowing.follows_repository \
351 351 == RepoModel().get_by_repo_name(repo_id)).count()
352 352
353 353 def get_forks(self, repo_id):
354 354 if isinstance(repo_id, int):
355 355 return self.sa.query(Repository)\
356 356 .filter(Repository.fork_id == repo_id).count()
357 357 else:
358 358 return self.sa.query(Repository)\
359 359 .filter(Repository.fork \
360 360 == RepoModel().get_by_repo_name(repo_id)).count()
361 361
362 362 def pull_changes(self, repo_name, username):
363 363 repo, dbrepo = self.get(repo_name, retval='all')
364 364
365 365 try:
366 366 extras = {'ip': '',
367 367 'username': username,
368 368 'action': 'push_remote',
369 369 'repository': repo_name}
370 370
371 371 #inject ui extra param to log this action via push logger
372 372 for k, v in extras.items():
373 373 repo._repo.ui.setconfig('rhodecode_extras', k, v)
374 374
375 375 repo.pull(dbrepo.clone_uri)
376 376 self.mark_for_invalidation(repo_name)
377 377 except:
378 378 log.error(traceback.format_exc())
379 379 raise
380 380
381 381 def get_unread_journal(self):
382 382 return self.sa.query(UserLog).count()
383 383
384 384 def _should_invalidate(self, repo_name):
385 385 """Looks up database for invalidation signals for this repo_name
386 386
387 387 :param repo_name:
388 388 """
389 389
390 390 ret = self.sa.query(CacheInvalidation)\
391 391 .filter(CacheInvalidation.cache_key == repo_name)\
392 392 .filter(CacheInvalidation.cache_active == False)\
393 393 .scalar()
394 394
395 395 return ret
396 396
397 397 def _mark_invalidated(self, cache_key):
398 398 """ Marks all occurrences of cache to invalidation as already
399 399 invalidated
400 400
401 401 :param cache_key:
402 402 """
403 403
404 404 if cache_key:
405 405 log.debug('marking %s as already invalidated', cache_key)
406 406 try:
407 407 cache_key.cache_active = True
408 408 self.sa.add(cache_key)
409 409 self.sa.commit()
410 410 except (DatabaseError,):
411 411 log.error(traceback.format_exc())
412 412 self.sa.rollback()
General Comments 0
You need to be logged in to leave comments. Login now