##// END OF EJS Templates
Fixed problem with new repos, and visibility on the main list.
marcink -
r1380:39ae0f09 beta
parent child Browse files
Show More
@@ -1,416 +1,416 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 from vcs.nodes import FileNode
42 42
43 43 from rhodecode import BACKENDS
44 44 from rhodecode.lib import helpers as h
45 45 from rhodecode.lib.auth import HasRepoPermissionAny
46 46 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
47 47 action_logger
48 48 from rhodecode.model import BaseModel
49 49 from rhodecode.model.user import UserModel
50 50 from rhodecode.model.repo import RepoModel
51 51 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
52 52 UserFollowing, UserLog
53 53 from rhodecode.model.caching_query import FromCache
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 class CachedRepoList(object):
74 74
75 75 def __init__(self, db_repo_list, invalidation_list, repos_path,
76 76 order_by=None):
77 77 self.db_repo_list = db_repo_list
78 78 self.invalidation_list = invalidation_list
79 79 self.repos_path = repos_path
80 80 self.order_by = order_by
81 81 self.reversed = (order_by or '').startswith('-')
82 82
83 83 def __len__(self):
84 84 return len(self.db_repo_list)
85 85
86 86 def __repr__(self):
87 87 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
88 88
89 89 def __iter__(self):
90 90 for db_repo in self.db_repo_list:
91 91 dbr = db_repo
92 92
93 93 # invalidate the repo cache if needed before getting the
94 94 # scm instance
95 95
96 96 scm_invalidate = False
97 97 if self.invalidation_list is not None:
98 98 scm_invalidate = dbr.repo_name in self.invalidation_list
99 99
100 100 if scm_invalidate:
101 101 log.info('invalidating cache for repository %s',
102 102 dbr.repo_name)
103 103 db_repo.set_invalidate
104 104
105 105 scmr = db_repo.scm_instance_cached
106 106
107 107 #check permission at this level
108 108 if not HasRepoPermissionAny('repository.read',
109 109 'repository.write',
110 110 'repository.admin')(dbr.repo_name,
111 111 'get repo check'):
112 112 continue
113 113
114 114
115 if not scmr:
115 if scmr is None:
116 116 log.error('%s this repository is present in database but it '
117 117 'cannot be created as an scm instance',
118 118 dbr.repo_name)
119 119 continue
120 120
121 121
122 122 last_change = scmr.last_change
123 123 tip = h.get_changeset_safe(scmr, 'tip')
124 124
125 125 tmp_d = {}
126 126 tmp_d['name'] = dbr.repo_name
127 127 tmp_d['name_sort'] = tmp_d['name'].lower()
128 128 tmp_d['description'] = dbr.description
129 129 tmp_d['description_sort'] = tmp_d['description']
130 130 tmp_d['last_change'] = last_change
131 131 tmp_d['last_change_sort'] = time.mktime(last_change \
132 132 .timetuple())
133 133 tmp_d['tip'] = tip.raw_id
134 134 tmp_d['tip_sort'] = tip.revision
135 135 tmp_d['rev'] = tip.revision
136 136 tmp_d['contact'] = dbr.user.full_contact
137 137 tmp_d['contact_sort'] = tmp_d['contact']
138 138 tmp_d['owner_sort'] = tmp_d['contact']
139 139 tmp_d['repo_archives'] = list(scmr._get_archives())
140 140 tmp_d['last_msg'] = tip.message
141 141 tmp_d['repo'] = scmr
142 142 tmp_d['dbrepo'] = dbr.get_dict()
143 143 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork \
144 144 else {}
145 145 yield tmp_d
146 146
147 147 class ScmModel(BaseModel):
148 148 """Generic Scm Model
149 149 """
150 150
151 151 @LazyProperty
152 152 def repos_path(self):
153 153 """Get's the repositories root path from database
154 154 """
155 155
156 156 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
157 157
158 158 return q.ui_value
159 159
160 160 def repo_scan(self, repos_path=None):
161 161 """Listing of repositories in given path. This path should not be a
162 162 repository itself. Return a dictionary of repository objects
163 163
164 164 :param repos_path: path to directory containing repositories
165 165 """
166 166
167 167 log.info('scanning for repositories in %s', repos_path)
168 168
169 169 if repos_path is None:
170 170 repos_path = self.repos_path
171 171
172 172 baseui = make_ui('db')
173 173 repos_list = {}
174 174
175 175 for name, path in get_filesystem_repos(repos_path, recursive=True):
176 176 try:
177 177 if name in repos_list:
178 178 raise RepositoryError('Duplicate repository name %s '
179 179 'found in %s' % (name, path))
180 180 else:
181 181
182 182 klass = get_backend(path[0])
183 183
184 184 if path[0] == 'hg' and path[0] in BACKENDS.keys():
185 185 repos_list[name] = klass(path[1], baseui=baseui)
186 186
187 187 if path[0] == 'git' and path[0] in BACKENDS.keys():
188 188 repos_list[name] = klass(path[1])
189 189 except OSError:
190 190 continue
191 191
192 192 return repos_list
193 193
194 194 def get_repos(self, all_repos=None, sort_key=None):
195 195 """
196 196 Get all repos from db and for each repo create it's
197 197 backend instance and fill that backed with information from database
198 198
199 199 :param all_repos: list of repository names as strings
200 200 give specific repositories list, good for filtering
201 201 """
202 202 if all_repos is None:
203 203 all_repos = self.sa.query(Repository)\
204 204 .filter(Repository.group_id == None)\
205 205 .order_by(Repository.repo_name).all()
206 206
207 207 #get the repositories that should be invalidated
208 208 invalidation_list = [str(x.cache_key) for x in \
209 209 self.sa.query(CacheInvalidation.cache_key)\
210 210 .filter(CacheInvalidation.cache_active == False)\
211 211 .all()]
212 212
213 213 repo_iter = CachedRepoList(all_repos, invalidation_list,
214 214 repos_path=self.repos_path,
215 215 order_by=sort_key)
216 216
217 217 return repo_iter
218 218
219 219 def mark_for_invalidation(self, repo_name):
220 220 """Puts cache invalidation task into db for
221 221 further global cache invalidation
222 222
223 223 :param repo_name: this repo that should invalidation take place
224 224 """
225 225
226 226 log.debug('marking %s for invalidation', repo_name)
227 227 cache = self.sa.query(CacheInvalidation)\
228 228 .filter(CacheInvalidation.cache_key == repo_name).scalar()
229 229
230 230 if cache:
231 231 #mark this cache as inactive
232 232 cache.cache_active = False
233 233 else:
234 234 log.debug('cache key not found in invalidation db -> creating one')
235 235 cache = CacheInvalidation(repo_name)
236 236
237 237 try:
238 238 self.sa.add(cache)
239 239 self.sa.commit()
240 240 except (DatabaseError,):
241 241 log.error(traceback.format_exc())
242 242 self.sa.rollback()
243 243
244 244 def toggle_following_repo(self, follow_repo_id, user_id):
245 245
246 246 f = self.sa.query(UserFollowing)\
247 247 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
248 248 .filter(UserFollowing.user_id == user_id).scalar()
249 249
250 250 if f is not None:
251 251
252 252 try:
253 253 self.sa.delete(f)
254 254 self.sa.commit()
255 255 action_logger(UserTemp(user_id),
256 256 'stopped_following_repo',
257 257 RepoTemp(follow_repo_id))
258 258 return
259 259 except:
260 260 log.error(traceback.format_exc())
261 261 self.sa.rollback()
262 262 raise
263 263
264 264 try:
265 265 f = UserFollowing()
266 266 f.user_id = user_id
267 267 f.follows_repo_id = follow_repo_id
268 268 self.sa.add(f)
269 269 self.sa.commit()
270 270 action_logger(UserTemp(user_id),
271 271 'started_following_repo',
272 272 RepoTemp(follow_repo_id))
273 273 except:
274 274 log.error(traceback.format_exc())
275 275 self.sa.rollback()
276 276 raise
277 277
278 278 def toggle_following_user(self, follow_user_id, user_id):
279 279 f = self.sa.query(UserFollowing)\
280 280 .filter(UserFollowing.follows_user_id == follow_user_id)\
281 281 .filter(UserFollowing.user_id == user_id).scalar()
282 282
283 283 if f is not None:
284 284 try:
285 285 self.sa.delete(f)
286 286 self.sa.commit()
287 287 return
288 288 except:
289 289 log.error(traceback.format_exc())
290 290 self.sa.rollback()
291 291 raise
292 292
293 293 try:
294 294 f = UserFollowing()
295 295 f.user_id = user_id
296 296 f.follows_user_id = follow_user_id
297 297 self.sa.add(f)
298 298 self.sa.commit()
299 299 except:
300 300 log.error(traceback.format_exc())
301 301 self.sa.rollback()
302 302 raise
303 303
304 304 def is_following_repo(self, repo_name, user_id, cache=False):
305 305 r = self.sa.query(Repository)\
306 306 .filter(Repository.repo_name == repo_name).scalar()
307 307
308 308 f = self.sa.query(UserFollowing)\
309 309 .filter(UserFollowing.follows_repository == r)\
310 310 .filter(UserFollowing.user_id == user_id).scalar()
311 311
312 312 return f is not None
313 313
314 314 def is_following_user(self, username, user_id, cache=False):
315 315 u = UserModel(self.sa).get_by_username(username)
316 316
317 317 f = self.sa.query(UserFollowing)\
318 318 .filter(UserFollowing.follows_user == u)\
319 319 .filter(UserFollowing.user_id == user_id).scalar()
320 320
321 321 return f is not None
322 322
323 323 def get_followers(self, repo_id):
324 324 if not isinstance(repo_id, int):
325 325 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
326 326
327 327 return self.sa.query(UserFollowing)\
328 328 .filter(UserFollowing.follows_repo_id == repo_id).count()
329 329
330 330 def get_forks(self, repo_id):
331 331 if not isinstance(repo_id, int):
332 332 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
333 333
334 334 return self.sa.query(Repository)\
335 335 .filter(Repository.fork_id == repo_id).count()
336 336
337 337 def pull_changes(self, repo_name, username):
338 338 dbrepo = Repository.by_repo_name(repo_name)
339 339 repo = dbrepo.scm_instance
340 340 try:
341 341 extras = {'ip': '',
342 342 'username': username,
343 343 'action': 'push_remote',
344 344 'repository': repo_name}
345 345
346 346 #inject ui extra param to log this action via push logger
347 347 for k, v in extras.items():
348 348 repo._repo.ui.setconfig('rhodecode_extras', k, v)
349 349
350 350 repo.pull(dbrepo.clone_uri)
351 351 self.mark_for_invalidation(repo_name)
352 352 except:
353 353 log.error(traceback.format_exc())
354 354 raise
355 355
356 356
357 357 def commit_change(self, repo, repo_name, cs, user, author, message, content,
358 358 f_path):
359 359
360 360 if repo.alias == 'hg':
361 361 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
362 362 elif repo.alias == 'git':
363 363 from vcs.backends.git import GitInMemoryChangeset as IMC
364 364
365 365 # decoding here will force that we have proper encoded values
366 366 # in any other case this will throw exceptions and deny commit
367 367 content = content.encode('utf8')
368 368 message = message.encode('utf8')
369 369 path = f_path.encode('utf8')
370 370 author = author.encode('utf8')
371 371 m = IMC(repo)
372 372 m.change(FileNode(path, content))
373 373 tip = m.commit(message=message,
374 374 author=author,
375 375 parents=[cs], branch=cs.branch)
376 376
377 377 new_cs = tip.short_id
378 378 action = 'push_local:%s' % new_cs
379 379
380 380 action_logger(user, action, repo_name)
381 381
382 382 self.mark_for_invalidation(repo_name)
383 383
384 384
385 385 def get_unread_journal(self):
386 386 return self.sa.query(UserLog).count()
387 387
388 388 def _should_invalidate(self, repo_name):
389 389 """Looks up database for invalidation signals for this repo_name
390 390
391 391 :param repo_name:
392 392 """
393 393
394 394 ret = self.sa.query(CacheInvalidation)\
395 395 .filter(CacheInvalidation.cache_key == repo_name)\
396 396 .filter(CacheInvalidation.cache_active == False)\
397 397 .scalar()
398 398
399 399 return ret
400 400
401 401 def _mark_invalidated(self, cache_key):
402 402 """ Marks all occurrences of cache to invalidation as already
403 403 invalidated
404 404
405 405 :param cache_key:
406 406 """
407 407
408 408 if cache_key:
409 409 log.debug('marking %s as already invalidated', cache_key)
410 410 try:
411 411 cache_key.cache_active = True
412 412 self.sa.add(cache_key)
413 413 self.sa.commit()
414 414 except (DatabaseError,):
415 415 log.error(traceback.format_exc())
416 416 self.sa.rollback()
General Comments 0
You need to be logged in to leave comments. Login now