##// END OF EJS Templates
added some info to corrupted repo message that helps diagnose the cause from log itself.
marcink -
r1265:08ac2c3a beta
parent child Browse files
Show More
@@ -1,412 +1,414 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 142 if repo is None or dbrepo is None:
143 log.error('Repository %s looks somehow corrupted', r_name)
143 log.error('Repository "%s" looks somehow corrupted '
144 'fs-repo:%s,db-repo:%s both values should be '
145 'present', r_name, repo, dbrepo)
144 146 continue
145 147 last_change = repo.last_change
146 148 tip = h.get_changeset_safe(repo, 'tip')
147 149
148 150 tmp_d = {}
149 151 tmp_d['name'] = dbrepo.repo_name
150 152 tmp_d['name_sort'] = tmp_d['name'].lower()
151 153 tmp_d['description'] = dbrepo.description
152 154 tmp_d['description_sort'] = tmp_d['description']
153 155 tmp_d['last_change'] = last_change
154 156 tmp_d['last_change_sort'] = time.mktime(last_change \
155 157 .timetuple())
156 158 tmp_d['tip'] = tip.raw_id
157 159 tmp_d['tip_sort'] = tip.revision
158 160 tmp_d['rev'] = tip.revision
159 161 tmp_d['contact'] = dbrepo.user.full_contact
160 162 tmp_d['contact_sort'] = tmp_d['contact']
161 163 tmp_d['owner_sort'] = tmp_d['contact']
162 164 tmp_d['repo_archives'] = list(repo._get_archives())
163 165 tmp_d['last_msg'] = tip.message
164 166 tmp_d['repo'] = repo
165 167 tmp_d['dbrepo'] = dbrepo.get_dict()
166 168 tmp_d['dbrepo_fork'] = dbrepo.fork.get_dict() if dbrepo.fork \
167 169 else {}
168 170 yield tmp_d
169 171
170 172 def get(self, repo_name, invalidation_list=None, retval='all'):
171 173 """Returns a tuple of Repository,DbRepository,
172 174 Get's repository from given name, creates BackendInstance and
173 175 propagates it's data from database with all additional information
174 176
175 177 :param repo_name:
176 178 :param invalidation_list: if a invalidation list is given the get
177 179 method should not manually check if this repository needs
178 180 invalidation and just invalidate the repositories in list
179 181 :param retval: string specifing what to return one of 'repo','dbrepo',
180 182 'all'if repo or dbrepo is given it'll just lazy load chosen type
181 183 and return None as the second
182 184 """
183 185 if not HasRepoPermissionAny('repository.read', 'repository.write',
184 186 'repository.admin')(repo_name, 'get repo check'):
185 187 return
186 188
187 189 #======================================================================
188 190 # CACHE FUNCTION
189 191 #======================================================================
190 192 @cache_region('long_term')
191 193 def _get_repo(repo_name):
192 194
193 195 repo_path = os.path.join(self.repos_path, repo_name)
194 196
195 197 try:
196 198 alias = get_scm(repo_path)[0]
197 199 log.debug('Creating instance of %s repository', alias)
198 200 backend = get_backend(alias)
199 201 except VCSError:
200 202 log.error(traceback.format_exc())
201 203 log.error('Perhaps this repository is in db and not in '
202 204 'filesystem run rescan repositories with '
203 205 '"destroy old data " option from admin panel')
204 206 return
205 207
206 208 if alias == 'hg':
207 209 repo = backend(repo_path, create=False, baseui=make_ui('db'))
208 210 #skip hidden web repository
209 211 if repo._get_hidden():
210 212 return
211 213 else:
212 214 repo = backend(repo_path, create=False)
213 215
214 216 return repo
215 217
216 218 pre_invalidate = True
217 219 dbinvalidate = False
218 220
219 221 if invalidation_list is not None:
220 222 pre_invalidate = repo_name in invalidation_list
221 223
222 224 if pre_invalidate:
223 225 #this returns object to invalidate
224 226 invalidate = self._should_invalidate(repo_name)
225 227 if invalidate:
226 228 log.info('invalidating cache for repository %s', repo_name)
227 229 region_invalidate(_get_repo, None, repo_name)
228 230 self._mark_invalidated(invalidate)
229 231 dbinvalidate = True
230 232
231 233 r, dbr = None, None
232 234 if retval == 'repo' or 'all':
233 235 r = _get_repo(repo_name)
234 236 if retval == 'dbrepo' or 'all':
235 237 dbr = RepoModel().get_full(repo_name, cache=True,
236 238 invalidate=dbinvalidate)
237 239
238 240 return r, dbr
239 241
240 242 def mark_for_invalidation(self, repo_name):
241 243 """Puts cache invalidation task into db for
242 244 further global cache invalidation
243 245
244 246 :param repo_name: this repo that should invalidation take place
245 247 """
246 248
247 249 log.debug('marking %s for invalidation', repo_name)
248 250 cache = self.sa.query(CacheInvalidation)\
249 251 .filter(CacheInvalidation.cache_key == repo_name).scalar()
250 252
251 253 if cache:
252 254 #mark this cache as inactive
253 255 cache.cache_active = False
254 256 else:
255 257 log.debug('cache key not found in invalidation db -> creating one')
256 258 cache = CacheInvalidation(repo_name)
257 259
258 260 try:
259 261 self.sa.add(cache)
260 262 self.sa.commit()
261 263 except (DatabaseError,):
262 264 log.error(traceback.format_exc())
263 265 self.sa.rollback()
264 266
265 267 def toggle_following_repo(self, follow_repo_id, user_id):
266 268
267 269 f = self.sa.query(UserFollowing)\
268 270 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
269 271 .filter(UserFollowing.user_id == user_id).scalar()
270 272
271 273 if f is not None:
272 274
273 275 try:
274 276 self.sa.delete(f)
275 277 self.sa.commit()
276 278 action_logger(UserTemp(user_id),
277 279 'stopped_following_repo',
278 280 RepoTemp(follow_repo_id))
279 281 return
280 282 except:
281 283 log.error(traceback.format_exc())
282 284 self.sa.rollback()
283 285 raise
284 286
285 287 try:
286 288 f = UserFollowing()
287 289 f.user_id = user_id
288 290 f.follows_repo_id = follow_repo_id
289 291 self.sa.add(f)
290 292 self.sa.commit()
291 293 action_logger(UserTemp(user_id),
292 294 'started_following_repo',
293 295 RepoTemp(follow_repo_id))
294 296 except:
295 297 log.error(traceback.format_exc())
296 298 self.sa.rollback()
297 299 raise
298 300
299 301 def toggle_following_user(self, follow_user_id, user_id):
300 302 f = self.sa.query(UserFollowing)\
301 303 .filter(UserFollowing.follows_user_id == follow_user_id)\
302 304 .filter(UserFollowing.user_id == user_id).scalar()
303 305
304 306 if f is not None:
305 307 try:
306 308 self.sa.delete(f)
307 309 self.sa.commit()
308 310 return
309 311 except:
310 312 log.error(traceback.format_exc())
311 313 self.sa.rollback()
312 314 raise
313 315
314 316 try:
315 317 f = UserFollowing()
316 318 f.user_id = user_id
317 319 f.follows_user_id = follow_user_id
318 320 self.sa.add(f)
319 321 self.sa.commit()
320 322 except:
321 323 log.error(traceback.format_exc())
322 324 self.sa.rollback()
323 325 raise
324 326
325 327 def is_following_repo(self, repo_name, user_id, cache=False):
326 328 r = self.sa.query(Repository)\
327 329 .filter(Repository.repo_name == repo_name).scalar()
328 330
329 331 f = self.sa.query(UserFollowing)\
330 332 .filter(UserFollowing.follows_repository == r)\
331 333 .filter(UserFollowing.user_id == user_id).scalar()
332 334
333 335 return f is not None
334 336
335 337 def is_following_user(self, username, user_id, cache=False):
336 338 u = UserModel(self.sa).get_by_username(username)
337 339
338 340 f = self.sa.query(UserFollowing)\
339 341 .filter(UserFollowing.follows_user == u)\
340 342 .filter(UserFollowing.user_id == user_id).scalar()
341 343
342 344 return f is not None
343 345
344 346 def get_followers(self, repo_id):
345 347 if isinstance(repo_id, int):
346 348 return self.sa.query(UserFollowing)\
347 349 .filter(UserFollowing.follows_repo_id == repo_id).count()
348 350 else:
349 351 return self.sa.query(UserFollowing)\
350 352 .filter(UserFollowing.follows_repository \
351 353 == RepoModel().get_by_repo_name(repo_id)).count()
352 354
353 355 def get_forks(self, repo_id):
354 356 if isinstance(repo_id, int):
355 357 return self.sa.query(Repository)\
356 358 .filter(Repository.fork_id == repo_id).count()
357 359 else:
358 360 return self.sa.query(Repository)\
359 361 .filter(Repository.fork \
360 362 == RepoModel().get_by_repo_name(repo_id)).count()
361 363
362 364 def pull_changes(self, repo_name, username):
363 365 repo, dbrepo = self.get(repo_name, retval='all')
364 366
365 367 try:
366 368 extras = {'ip': '',
367 369 'username': username,
368 370 'action': 'push_remote',
369 371 'repository': repo_name}
370 372
371 373 #inject ui extra param to log this action via push logger
372 374 for k, v in extras.items():
373 375 repo._repo.ui.setconfig('rhodecode_extras', k, v)
374 376
375 377 repo.pull(dbrepo.clone_uri)
376 378 self.mark_for_invalidation(repo_name)
377 379 except:
378 380 log.error(traceback.format_exc())
379 381 raise
380 382
381 383 def get_unread_journal(self):
382 384 return self.sa.query(UserLog).count()
383 385
384 386 def _should_invalidate(self, repo_name):
385 387 """Looks up database for invalidation signals for this repo_name
386 388
387 389 :param repo_name:
388 390 """
389 391
390 392 ret = self.sa.query(CacheInvalidation)\
391 393 .filter(CacheInvalidation.cache_key == repo_name)\
392 394 .filter(CacheInvalidation.cache_active == False)\
393 395 .scalar()
394 396
395 397 return ret
396 398
397 399 def _mark_invalidated(self, cache_key):
398 400 """ Marks all occurrences of cache to invalidation as already
399 401 invalidated
400 402
401 403 :param cache_key:
402 404 """
403 405
404 406 if cache_key:
405 407 log.debug('marking %s as already invalidated', cache_key)
406 408 try:
407 409 cache_key.cache_active = True
408 410 self.sa.add(cache_key)
409 411 self.sa.commit()
410 412 except (DatabaseError,):
411 413 log.error(traceback.format_exc())
412 414 self.sa.rollback()
General Comments 0
You need to be logged in to leave comments. Login now