##// END OF EJS Templates
fixed bug introduced in latest commit
marcink -
r792:4c16ed1e beta
parent child Browse files
Show More
@@ -1,379 +1,378
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 package.rhodecode.model.scm
3 package.rhodecode.model.scm
4 ~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~
5
5
6 scm model for RhodeCode
6 scm model for RhodeCode
7 :created_on: Apr 9, 2010
7 :created_on: Apr 9, 2010
8 :author: marcink
8 :author: marcink
9 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
9 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
10 :license: GPLv3, see COPYING for more details.
10 :license: GPLv3, see COPYING for more details.
11 """
11 """
12 # This program is free software; you can redistribute it and/or
12 # This program is free software; you can redistribute it and/or
13 # modify it under the terms of the GNU General Public License
13 # modify it under the terms of the GNU General Public License
14 # as published by the Free Software Foundation; version 2
14 # as published by the Free Software Foundation; version 2
15 # of the License or (at your opinion) any later version of the license.
15 # of the License or (at your opinion) any later version of the license.
16 #
16 #
17 # This program is distributed in the hope that it will be useful,
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
20 # GNU General Public License for more details.
21 #
21 #
22 # You should have received a copy of the GNU General Public License
22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the Free Software
23 # along with this program; if not, write to the Free Software
24 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
24 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # MA 02110-1301, USA.
25 # MA 02110-1301, USA.
26
26
27 import os
27 import os
28 import time
28 import time
29 import traceback
29 import traceback
30 import logging
30 import logging
31
31
32 from vcs import get_backend
32 from vcs import get_backend
33 from vcs.utils.helpers import get_scm
33 from vcs.utils.helpers import get_scm
34 from vcs.exceptions import RepositoryError, VCSError
34 from vcs.exceptions import RepositoryError, VCSError
35 from vcs.utils.lazy import LazyProperty
35 from vcs.utils.lazy import LazyProperty
36
36
37 from mercurial import ui
37 from mercurial import ui
38
38
39 from beaker.cache import cache_region, region_invalidate
39 from beaker.cache import cache_region, region_invalidate
40
40
41 from rhodecode import BACKENDS
41 from rhodecode import BACKENDS
42 from rhodecode.lib import helpers as h
42 from rhodecode.lib import helpers as h
43 from rhodecode.lib.auth import HasRepoPermissionAny
43 from rhodecode.lib.auth import HasRepoPermissionAny
44 from rhodecode.lib.utils import get_repos, make_ui, action_logger
44 from rhodecode.lib.utils import get_repos, make_ui, action_logger
45 from rhodecode.model import BaseModel
45 from rhodecode.model import BaseModel
46 from rhodecode.model.user import UserModel
46 from rhodecode.model.user import UserModel
47
47
48 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
48 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
49 UserFollowing, UserLog
49 UserFollowing, UserLog
50 from rhodecode.model.caching_query import FromCache
50 from rhodecode.model.caching_query import FromCache
51
51
52 from sqlalchemy.orm import joinedload
52 from sqlalchemy.orm import joinedload
53 from sqlalchemy.orm.session import make_transient
53 from sqlalchemy.orm.session import make_transient
54 from sqlalchemy.exc import DatabaseError
54 from sqlalchemy.exc import DatabaseError
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 class UserTemp(object):
59 class UserTemp(object):
60 def __init__(self, user_id):
60 def __init__(self, user_id):
61 self.user_id = user_id
61 self.user_id = user_id
62 class RepoTemp(object):
62 class RepoTemp(object):
63 def __init__(self, repo_id):
63 def __init__(self, repo_id):
64 self.repo_id = repo_id
64 self.repo_id = repo_id
65
65
66 class ScmModel(BaseModel):
66 class ScmModel(BaseModel):
67 """
67 """
68 Mercurial Model
68 Mercurial Model
69 """
69 """
70
70
71 @LazyProperty
71 @LazyProperty
72 def repos_path(self):
72 def repos_path(self):
73 """
73 """
74 Get's the repositories root path from database
74 Get's the repositories root path from database
75 """
75 """
76 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
76 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
77
77
78 return q.ui_value
78 return q.ui_value
79
79
80 def repo_scan(self, repos_path, baseui):
80 def repo_scan(self, repos_path, baseui):
81 """
81 """
82 Listing of repositories in given path. This path should not be a
82 Listing of repositories in given path. This path should not be a
83 repository itself. Return a dictionary of repository objects
83 repository itself. Return a dictionary of repository objects
84
84
85 :param repos_path: path to directory containing repositories
85 :param repos_path: path to directory containing repositories
86 :param baseui
86 :param baseui
87 """
87 """
88 log.info('scanning for repositories in %s', repos_path)
88 log.info('scanning for repositories in %s', repos_path)
89
89
90 if not isinstance(baseui, ui.ui):
90 if not isinstance(baseui, ui.ui):
91 baseui = make_ui('db')
91 baseui = make_ui('db')
92 repos_list = {}
92 repos_list = {}
93
93
94 for name, path in get_repos(repos_path):
94 for name, path in get_repos(repos_path):
95 try:
95 try:
96 if repos_list.has_key(name):
96 if repos_list.has_key(name):
97 raise RepositoryError('Duplicate repository name %s '
97 raise RepositoryError('Duplicate repository name %s '
98 'found in %s' % (name, path))
98 'found in %s' % (name, path))
99 else:
99 else:
100
100
101 klass = get_backend(path[0])
101 klass = get_backend(path[0])
102
102
103 if path[0] == 'hg' and path[0] in BACKENDS.keys():
103 if path[0] == 'hg' and path[0] in BACKENDS.keys():
104 repos_list[name] = klass(path[1], baseui=baseui)
104 repos_list[name] = klass(path[1], baseui=baseui)
105
105
106 if path[0] == 'git' and path[0] in BACKENDS.keys():
106 if path[0] == 'git' and path[0] in BACKENDS.keys():
107 repos_list[name] = klass(path[1])
107 repos_list[name] = klass(path[1])
108 except OSError:
108 except OSError:
109 continue
109 continue
110
110
111 return repos_list
111 return repos_list
112
112
113 def get_repos(self, all_repos=None):
113 def get_repos(self, all_repos=None):
114 """
114 """
115 Get all repos from db and for each repo create it's backend instance.
115 Get all repos from db and for each repo create it's backend instance.
116 and fill that backed with information from database
116 and fill that backed with information from database
117
117
118 :param all_repos: give specific repositories list, good for filtering
118 :param all_repos: give specific repositories list, good for filtering
119 """
119 """
120 if all_repos is None:
120 if all_repos is None:
121 all_repos = self.sa.query(Repository)\
121 all_repos = self.sa.query(Repository)\
122 .order_by(Repository.repo_name).all()
122 .order_by(Repository.repo_name).all()
123
123
124 #get the repositories that should be invalidated
124 #get the repositories that should be invalidated
125 invalidation_list = [str(x.cache_key) for x in \
125 invalidation_list = [str(x.cache_key) for x in \
126 self.sa.query(CacheInvalidation.cache_key)\
126 self.sa.query(CacheInvalidation.cache_key)\
127 .filter(CacheInvalidation.cache_active == False)\
127 .filter(CacheInvalidation.cache_active == False)\
128 .all()]
128 .all()]
129
129
130 for r in all_repos:
130 for r in all_repos:
131
131
132 repo = self.get(r.repo_name, invalidation_list)
132 repo = self.get(r.repo_name, invalidation_list)
133
133
134 if repo is not None:
134 if repo is not None:
135 last_change = repo.last_change
135 last_change = repo.last_change
136 tip = h.get_changeset_safe(repo, 'tip')
136 tip = h.get_changeset_safe(repo, 'tip')
137
137
138 tmp_d = {}
138 tmp_d = {}
139 tmp_d['name'] = repo.name
139 tmp_d['name'] = repo.name
140 tmp_d['name_sort'] = tmp_d['name'].lower()
140 tmp_d['name_sort'] = tmp_d['name'].lower()
141 tmp_d['description'] = repo.dbrepo.description
141 tmp_d['description'] = repo.dbrepo.description
142 tmp_d['description_sort'] = tmp_d['description']
142 tmp_d['description_sort'] = tmp_d['description']
143 tmp_d['last_change'] = last_change
143 tmp_d['last_change'] = last_change
144 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
144 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
145 tmp_d['tip'] = tip.raw_id
145 tmp_d['tip'] = tip.raw_id
146 tmp_d['tip_sort'] = tip.revision
146 tmp_d['tip_sort'] = tip.revision
147 tmp_d['rev'] = tip.revision
147 tmp_d['rev'] = tip.revision
148 tmp_d['contact'] = repo.dbrepo.user.full_contact
148 tmp_d['contact'] = repo.dbrepo.user.full_contact
149 tmp_d['contact_sort'] = tmp_d['contact']
149 tmp_d['contact_sort'] = tmp_d['contact']
150 tmp_d['repo_archives'] = list(repo._get_archives())
150 tmp_d['repo_archives'] = list(repo._get_archives())
151 tmp_d['last_msg'] = tip.message
151 tmp_d['last_msg'] = tip.message
152 tmp_d['repo'] = repo
152 tmp_d['repo'] = repo
153 yield tmp_d
153 yield tmp_d
154
154
155 def get_repo(self, repo_name):
155 def get_repo(self, repo_name):
156 return self.get(repo_name)
156 return self.get(repo_name)
157
157
158 def get(self, repo_name, invalidation_list=None):
158 def get(self, repo_name, invalidation_list=None):
159 """
159 """
160 Get's repository from given name, creates BackendInstance and
160 Get's repository from given name, creates BackendInstance and
161 propagates it's data from database with all additional information
161 propagates it's data from database with all additional information
162
162
163 :param repo_name:
163 :param repo_name:
164 :param invalidation_list: if a invalidation list is given the get
164 :param invalidation_list: if a invalidation list is given the get
165 method should not manually check if this repository needs
165 method should not manually check if this repository needs
166 invalidation and just invalidate the repositories in list
166 invalidation and just invalidate the repositories in list
167
167
168 """
168 """
169 if not HasRepoPermissionAny('repository.read', 'repository.write',
169 if not HasRepoPermissionAny('repository.read', 'repository.write',
170 'repository.admin')(repo_name, 'get repo check'):
170 'repository.admin')(repo_name, 'get repo check'):
171 return
171 return
172
172
173 pre_invalidate = True
174 if invalidation_list is not None:
175 pre_invalidate = repo_name in invalidation_list
176
177 if pre_invalidate:
178 invalidate = self._should_invalidate(repo_name)
179
180 if invalidate:
181 log.info('invalidating cache for repository %s', repo_name)
182 region_invalidate(_get_repo, None, repo_name)
183 self._mark_invalidated(invalidate)
184
185
186 #======================================================================
173 #======================================================================
187 # CACHE FUNCTION
174 # CACHE FUNCTION
188 #======================================================================
175 #======================================================================
189 @cache_region('long_term')
176 @cache_region('long_term')
190 def _get_repo(repo_name):
177 def _get_repo(repo_name):
191
178
192 repo_path = os.path.join(self.repos_path, repo_name)
179 repo_path = os.path.join(self.repos_path, repo_name)
193
180
194 try:
181 try:
195 alias = get_scm(repo_path)[0]
182 alias = get_scm(repo_path)[0]
196
183
197 log.debug('Creating instance of %s repository', alias)
184 log.debug('Creating instance of %s repository', alias)
198 backend = get_backend(alias)
185 backend = get_backend(alias)
199 except VCSError:
186 except VCSError:
200 log.error(traceback.format_exc())
187 log.error(traceback.format_exc())
201 return
188 return
202
189
203 if alias == 'hg':
190 if alias == 'hg':
204 from pylons import app_globals as g
191 from pylons import app_globals as g
205 repo = backend(repo_path, create=False, baseui=g.baseui)
192 repo = backend(repo_path, create=False, baseui=g.baseui)
206 #skip hidden web repository
193 #skip hidden web repository
207 if repo._get_hidden():
194 if repo._get_hidden():
208 return
195 return
209 else:
196 else:
210 repo = backend(repo_path, create=False)
197 repo = backend(repo_path, create=False)
211
198
212 dbrepo = self.sa.query(Repository)\
199 dbrepo = self.sa.query(Repository)\
213 .options(joinedload(Repository.fork))\
200 .options(joinedload(Repository.fork))\
214 .options(joinedload(Repository.user))\
201 .options(joinedload(Repository.user))\
215 .filter(Repository.repo_name == repo_name)\
202 .filter(Repository.repo_name == repo_name)\
216 .scalar()
203 .scalar()
217
204
218 make_transient(dbrepo)
205 make_transient(dbrepo)
219 if dbrepo.user:
206 if dbrepo.user:
220 make_transient(dbrepo.user)
207 make_transient(dbrepo.user)
221 if dbrepo.fork:
208 if dbrepo.fork:
222 make_transient(dbrepo.fork)
209 make_transient(dbrepo.fork)
223
210
224 repo.dbrepo = dbrepo
211 repo.dbrepo = dbrepo
225 return repo
212 return repo
226
213
214 pre_invalidate = True
215 if invalidation_list is not None:
216 pre_invalidate = repo_name in invalidation_list
217
218 if pre_invalidate:
219 invalidate = self._should_invalidate(repo_name)
220
221 if invalidate:
222 log.info('invalidating cache for repository %s', repo_name)
223 region_invalidate(_get_repo, None, repo_name)
224 self._mark_invalidated(invalidate)
225
227 return _get_repo(repo_name)
226 return _get_repo(repo_name)
228
227
229
228
230
229
231 def mark_for_invalidation(self, repo_name):
230 def mark_for_invalidation(self, repo_name):
232 """
231 """
233 Puts cache invalidation task into db for
232 Puts cache invalidation task into db for
234 further global cache invalidation
233 further global cache invalidation
235
234
236 :param repo_name: this repo that should invalidation take place
235 :param repo_name: this repo that should invalidation take place
237 """
236 """
238 log.debug('marking %s for invalidation', repo_name)
237 log.debug('marking %s for invalidation', repo_name)
239 cache = self.sa.query(CacheInvalidation)\
238 cache = self.sa.query(CacheInvalidation)\
240 .filter(CacheInvalidation.cache_key == repo_name).scalar()
239 .filter(CacheInvalidation.cache_key == repo_name).scalar()
241
240
242 if cache:
241 if cache:
243 #mark this cache as inactive
242 #mark this cache as inactive
244 cache.cache_active = False
243 cache.cache_active = False
245 else:
244 else:
246 log.debug('cache key not found in invalidation db -> creating one')
245 log.debug('cache key not found in invalidation db -> creating one')
247 cache = CacheInvalidation(repo_name)
246 cache = CacheInvalidation(repo_name)
248
247
249 try:
248 try:
250 self.sa.add(cache)
249 self.sa.add(cache)
251 self.sa.commit()
250 self.sa.commit()
252 except (DatabaseError,):
251 except (DatabaseError,):
253 log.error(traceback.format_exc())
252 log.error(traceback.format_exc())
254 self.sa.rollback()
253 self.sa.rollback()
255
254
256
255
257 def toggle_following_repo(self, follow_repo_id, user_id):
256 def toggle_following_repo(self, follow_repo_id, user_id):
258
257
259 f = self.sa.query(UserFollowing)\
258 f = self.sa.query(UserFollowing)\
260 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
259 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
261 .filter(UserFollowing.user_id == user_id).scalar()
260 .filter(UserFollowing.user_id == user_id).scalar()
262
261
263 if f is not None:
262 if f is not None:
264
263
265 try:
264 try:
266 self.sa.delete(f)
265 self.sa.delete(f)
267 self.sa.commit()
266 self.sa.commit()
268 action_logger(UserTemp(user_id),
267 action_logger(UserTemp(user_id),
269 'stopped_following_repo',
268 'stopped_following_repo',
270 RepoTemp(follow_repo_id))
269 RepoTemp(follow_repo_id))
271 return
270 return
272 except:
271 except:
273 log.error(traceback.format_exc())
272 log.error(traceback.format_exc())
274 self.sa.rollback()
273 self.sa.rollback()
275 raise
274 raise
276
275
277
276
278 try:
277 try:
279 f = UserFollowing()
278 f = UserFollowing()
280 f.user_id = user_id
279 f.user_id = user_id
281 f.follows_repo_id = follow_repo_id
280 f.follows_repo_id = follow_repo_id
282 self.sa.add(f)
281 self.sa.add(f)
283 self.sa.commit()
282 self.sa.commit()
284 action_logger(UserTemp(user_id),
283 action_logger(UserTemp(user_id),
285 'started_following_repo',
284 'started_following_repo',
286 RepoTemp(follow_repo_id))
285 RepoTemp(follow_repo_id))
287 except:
286 except:
288 log.error(traceback.format_exc())
287 log.error(traceback.format_exc())
289 self.sa.rollback()
288 self.sa.rollback()
290 raise
289 raise
291
290
292 def toggle_following_user(self, follow_user_id , user_id):
291 def toggle_following_user(self, follow_user_id , user_id):
293 f = self.sa.query(UserFollowing)\
292 f = self.sa.query(UserFollowing)\
294 .filter(UserFollowing.follows_user_id == follow_user_id)\
293 .filter(UserFollowing.follows_user_id == follow_user_id)\
295 .filter(UserFollowing.user_id == user_id).scalar()
294 .filter(UserFollowing.user_id == user_id).scalar()
296
295
297 if f is not None:
296 if f is not None:
298 try:
297 try:
299 self.sa.delete(f)
298 self.sa.delete(f)
300 self.sa.commit()
299 self.sa.commit()
301 return
300 return
302 except:
301 except:
303 log.error(traceback.format_exc())
302 log.error(traceback.format_exc())
304 self.sa.rollback()
303 self.sa.rollback()
305 raise
304 raise
306
305
307 try:
306 try:
308 f = UserFollowing()
307 f = UserFollowing()
309 f.user_id = user_id
308 f.user_id = user_id
310 f.follows_user_id = follow_user_id
309 f.follows_user_id = follow_user_id
311 self.sa.add(f)
310 self.sa.add(f)
312 self.sa.commit()
311 self.sa.commit()
313 except:
312 except:
314 log.error(traceback.format_exc())
313 log.error(traceback.format_exc())
315 self.sa.rollback()
314 self.sa.rollback()
316 raise
315 raise
317
316
318 def is_following_repo(self, repo_name, user_id):
317 def is_following_repo(self, repo_name, user_id):
319 r = self.sa.query(Repository)\
318 r = self.sa.query(Repository)\
320 .filter(Repository.repo_name == repo_name).scalar()
319 .filter(Repository.repo_name == repo_name).scalar()
321
320
322 f = self.sa.query(UserFollowing)\
321 f = self.sa.query(UserFollowing)\
323 .filter(UserFollowing.follows_repository == r)\
322 .filter(UserFollowing.follows_repository == r)\
324 .filter(UserFollowing.user_id == user_id).scalar()
323 .filter(UserFollowing.user_id == user_id).scalar()
325
324
326 return f is not None
325 return f is not None
327
326
328 def is_following_user(self, username, user_id):
327 def is_following_user(self, username, user_id):
329 u = UserModel(self.sa).get_by_username(username)
328 u = UserModel(self.sa).get_by_username(username)
330
329
331 f = self.sa.query(UserFollowing)\
330 f = self.sa.query(UserFollowing)\
332 .filter(UserFollowing.follows_user == u)\
331 .filter(UserFollowing.follows_user == u)\
333 .filter(UserFollowing.user_id == user_id).scalar()
332 .filter(UserFollowing.user_id == user_id).scalar()
334
333
335 return f is not None
334 return f is not None
336
335
337 def get_followers(self, repo_id):
336 def get_followers(self, repo_id):
338 return self.sa.query(UserFollowing)\
337 return self.sa.query(UserFollowing)\
339 .filter(UserFollowing.follows_repo_id == repo_id).count()
338 .filter(UserFollowing.follows_repo_id == repo_id).count()
340
339
341 def get_forks(self, repo_id):
340 def get_forks(self, repo_id):
342 return self.sa.query(Repository)\
341 return self.sa.query(Repository)\
343 .filter(Repository.fork_id == repo_id).count()
342 .filter(Repository.fork_id == repo_id).count()
344
343
345
344
346 def get_unread_journal(self):
345 def get_unread_journal(self):
347 return self.sa.query(UserLog).count()
346 return self.sa.query(UserLog).count()
348
347
349
348
350 def _should_invalidate(self, repo_name):
349 def _should_invalidate(self, repo_name):
351 """
350 """
352 Looks up database for invalidation signals for this repo_name
351 Looks up database for invalidation signals for this repo_name
353 :param repo_name:
352 :param repo_name:
354 """
353 """
355
354
356 ret = self.sa.query(CacheInvalidation)\
355 ret = self.sa.query(CacheInvalidation)\
357 .options(FromCache('sql_cache_short',
356 .options(FromCache('sql_cache_short',
358 'get_invalidation_%s' % repo_name))\
357 'get_invalidation_%s' % repo_name))\
359 .filter(CacheInvalidation.cache_key == repo_name)\
358 .filter(CacheInvalidation.cache_key == repo_name)\
360 .filter(CacheInvalidation.cache_active == False)\
359 .filter(CacheInvalidation.cache_active == False)\
361 .scalar()
360 .scalar()
362
361
363 return ret
362 return ret
364
363
365 def _mark_invalidated(self, cache_key):
364 def _mark_invalidated(self, cache_key):
366 """
365 """
367 Marks all occurences of cache to invaldation as already invalidated
366 Marks all occurences of cache to invaldation as already invalidated
368 :param repo_name:
367 :param repo_name:
369 """
368 """
370 if cache_key:
369 if cache_key:
371 log.debug('marking %s as already invalidated', cache_key)
370 log.debug('marking %s as already invalidated', cache_key)
372 try:
371 try:
373 cache_key.cache_active = True
372 cache_key.cache_active = True
374 self.sa.add(cache_key)
373 self.sa.add(cache_key)
375 self.sa.commit()
374 self.sa.commit()
376 except (DatabaseError,):
375 except (DatabaseError,):
377 log.error(traceback.format_exc())
376 log.error(traceback.format_exc())
378 self.sa.rollback()
377 self.sa.rollback()
379
378
General Comments 0
You need to be logged in to leave comments. Login now