##// END OF EJS Templates
fixed default sorting on main page with sorting using wrapped lower() call on database level
marcink -
r2354:f3417f0d beta
parent child Browse files
Show More
@@ -1,470 +1,472 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 import os
26 26 import time
27 27 import traceback
28 28 import logging
29 29 import cStringIO
30 30
31 from sqlalchemy import func
32
31 33 from rhodecode.lib.vcs import get_backend
32 34 from rhodecode.lib.vcs.exceptions import RepositoryError
33 35 from rhodecode.lib.vcs.utils.lazy import LazyProperty
34 36 from rhodecode.lib.vcs.nodes import FileNode
35 37
36 38 from rhodecode import BACKENDS
37 39 from rhodecode.lib import helpers as h
38 40 from rhodecode.lib.utils2 import safe_str, safe_unicode
39 41 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
40 42 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
41 43 action_logger, EmptyChangeset, REMOVED_REPO_PAT
42 44 from rhodecode.model import BaseModel
43 45 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
44 46 UserFollowing, UserLog, User, RepoGroup
45 47
46 48 log = logging.getLogger(__name__)
47 49
48 50
49 51 class UserTemp(object):
50 52 def __init__(self, user_id):
51 53 self.user_id = user_id
52 54
53 55 def __repr__(self):
54 56 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
55 57
56 58
57 59 class RepoTemp(object):
58 60 def __init__(self, repo_id):
59 61 self.repo_id = repo_id
60 62
61 63 def __repr__(self):
62 64 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
63 65
64 66
65 67 class CachedRepoList(object):
66 68
67 69 def __init__(self, db_repo_list, repos_path, order_by=None):
68 70 self.db_repo_list = db_repo_list
69 71 self.repos_path = repos_path
70 72 self.order_by = order_by
71 73 self.reversed = (order_by or '').startswith('-')
72 74
73 75 def __len__(self):
74 76 return len(self.db_repo_list)
75 77
76 78 def __repr__(self):
77 79 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
78 80
79 81 def __iter__(self):
80 82 # pre-propagated cache_map to save executing select statements
81 83 # for each repo
82 84 cache_map = CacheInvalidation.get_cache_map()
83 85
84 86 for dbr in self.db_repo_list:
85 87 scmr = dbr.scm_instance_cached(cache_map)
86 88 # check permission at this level
87 89 if not HasRepoPermissionAny(
88 90 'repository.read', 'repository.write', 'repository.admin'
89 91 )(dbr.repo_name, 'get repo check'):
90 92 continue
91 93
92 94 if scmr is None:
93 95 log.error(
94 96 '%s this repository is present in database but it '
95 97 'cannot be created as an scm instance' % dbr.repo_name
96 98 )
97 99 continue
98 100
99 101 last_change = scmr.last_change
100 102 tip = h.get_changeset_safe(scmr, 'tip')
101 103
102 104 tmp_d = {}
103 105 tmp_d['name'] = dbr.repo_name
104 106 tmp_d['name_sort'] = tmp_d['name'].lower()
105 107 tmp_d['description'] = dbr.description
106 108 tmp_d['description_sort'] = tmp_d['description']
107 109 tmp_d['last_change'] = last_change
108 110 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
109 111 tmp_d['tip'] = tip.raw_id
110 112 tmp_d['tip_sort'] = tip.revision
111 113 tmp_d['rev'] = tip.revision
112 114 tmp_d['contact'] = dbr.user.full_contact
113 115 tmp_d['contact_sort'] = tmp_d['contact']
114 116 tmp_d['owner_sort'] = tmp_d['contact']
115 117 tmp_d['repo_archives'] = list(scmr._get_archives())
116 118 tmp_d['last_msg'] = tip.message
117 119 tmp_d['author'] = tip.author
118 120 tmp_d['dbrepo'] = dbr.get_dict()
119 121 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
120 122 yield tmp_d
121 123
122 124
123 125 class GroupList(object):
124 126
125 127 def __init__(self, db_repo_group_list):
126 128 self.db_repo_group_list = db_repo_group_list
127 129
128 130 def __len__(self):
129 131 return len(self.db_repo_group_list)
130 132
131 133 def __repr__(self):
132 134 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
133 135
134 136 def __iter__(self):
135 137 for dbgr in self.db_repo_group_list:
136 138 # check permission at this level
137 139 if not HasReposGroupPermissionAny(
138 140 'group.read', 'group.write', 'group.admin'
139 141 )(dbgr.group_name, 'get group repo check'):
140 142 continue
141 143
142 144 yield dbgr
143 145
144 146
145 147 class ScmModel(BaseModel):
146 148 """
147 149 Generic Scm Model
148 150 """
149 151
150 152 def __get_repo(self, instance):
151 153 cls = Repository
152 154 if isinstance(instance, cls):
153 155 return instance
154 156 elif isinstance(instance, int) or str(instance).isdigit():
155 157 return cls.get(instance)
156 158 elif isinstance(instance, basestring):
157 159 return cls.get_by_repo_name(instance)
158 160 elif instance:
159 161 raise Exception('given object must be int, basestr or Instance'
160 162 ' of %s got %s' % (type(cls), type(instance)))
161 163
162 164 @LazyProperty
163 165 def repos_path(self):
164 166 """
165 167 Get's the repositories root path from database
166 168 """
167 169
168 170 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
169 171
170 172 return q.ui_value
171 173
172 174 def repo_scan(self, repos_path=None):
173 175 """
174 176 Listing of repositories in given path. This path should not be a
175 177 repository itself. Return a dictionary of repository objects
176 178
177 179 :param repos_path: path to directory containing repositories
178 180 """
179 181
180 182 if repos_path is None:
181 183 repos_path = self.repos_path
182 184
183 185 log.info('scanning for repositories in %s' % repos_path)
184 186
185 187 baseui = make_ui('db')
186 188 repos = {}
187 189
188 190 for name, path in get_filesystem_repos(repos_path, recursive=True):
189 191 # skip removed repos
190 192 if REMOVED_REPO_PAT.match(name):
191 193 continue
192 194
193 195 # name need to be decomposed and put back together using the /
194 196 # since this is internal storage separator for rhodecode
195 197 name = Repository.url_sep().join(name.split(os.sep))
196 198
197 199 try:
198 200 if name in repos:
199 201 raise RepositoryError('Duplicate repository name %s '
200 202 'found in %s' % (name, path))
201 203 else:
202 204
203 205 klass = get_backend(path[0])
204 206
205 207 if path[0] == 'hg' and path[0] in BACKENDS.keys():
206 208 repos[name] = klass(safe_str(path[1]), baseui=baseui)
207 209
208 210 if path[0] == 'git' and path[0] in BACKENDS.keys():
209 211 repos[name] = klass(path[1])
210 212 except OSError:
211 213 continue
212 214
213 215 return repos
214 216
215 217 def get_repos(self, all_repos=None, sort_key=None):
216 218 """
217 219 Get all repos from db and for each repo create it's
218 220 backend instance and fill that backed with information from database
219 221
220 222 :param all_repos: list of repository names as strings
221 223 give specific repositories list, good for filtering
222 224 """
223 225 if all_repos is None:
224 226 all_repos = self.sa.query(Repository)\
225 227 .filter(Repository.group_id == None)\
226 .order_by(Repository.repo_name).all()
228 .order_by(func.lower(Repository.repo_name)).all()
227 229
228 230 repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
229 231 order_by=sort_key)
230 232
231 233 return repo_iter
232 234
233 235 def get_repos_groups(self, all_groups=None):
234 236 if all_groups is None:
235 237 all_groups = RepoGroup.query()\
236 238 .filter(RepoGroup.group_parent_id == None).all()
237 239 group_iter = GroupList(all_groups)
238 240
239 241 return group_iter
240 242
241 243 def mark_for_invalidation(self, repo_name):
242 244 """
243 245 Puts cache invalidation task into db for
244 246 further global cache invalidation
245 247
246 248 :param repo_name: this repo that should invalidation take place
247 249 """
248 250 CacheInvalidation.set_invalidate(repo_name)
249 251
250 252 def toggle_following_repo(self, follow_repo_id, user_id):
251 253
252 254 f = self.sa.query(UserFollowing)\
253 255 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
254 256 .filter(UserFollowing.user_id == user_id).scalar()
255 257
256 258 if f is not None:
257 259 try:
258 260 self.sa.delete(f)
259 261 action_logger(UserTemp(user_id),
260 262 'stopped_following_repo',
261 263 RepoTemp(follow_repo_id))
262 264 return
263 265 except:
264 266 log.error(traceback.format_exc())
265 267 raise
266 268
267 269 try:
268 270 f = UserFollowing()
269 271 f.user_id = user_id
270 272 f.follows_repo_id = follow_repo_id
271 273 self.sa.add(f)
272 274
273 275 action_logger(UserTemp(user_id),
274 276 'started_following_repo',
275 277 RepoTemp(follow_repo_id))
276 278 except:
277 279 log.error(traceback.format_exc())
278 280 raise
279 281
280 282 def toggle_following_user(self, follow_user_id, user_id):
281 283 f = self.sa.query(UserFollowing)\
282 284 .filter(UserFollowing.follows_user_id == follow_user_id)\
283 285 .filter(UserFollowing.user_id == user_id).scalar()
284 286
285 287 if f is not None:
286 288 try:
287 289 self.sa.delete(f)
288 290 return
289 291 except:
290 292 log.error(traceback.format_exc())
291 293 raise
292 294
293 295 try:
294 296 f = UserFollowing()
295 297 f.user_id = user_id
296 298 f.follows_user_id = follow_user_id
297 299 self.sa.add(f)
298 300 except:
299 301 log.error(traceback.format_exc())
300 302 raise
301 303
302 304 def is_following_repo(self, repo_name, user_id, cache=False):
303 305 r = self.sa.query(Repository)\
304 306 .filter(Repository.repo_name == repo_name).scalar()
305 307
306 308 f = self.sa.query(UserFollowing)\
307 309 .filter(UserFollowing.follows_repository == r)\
308 310 .filter(UserFollowing.user_id == user_id).scalar()
309 311
310 312 return f is not None
311 313
312 314 def is_following_user(self, username, user_id, cache=False):
313 315 u = User.get_by_username(username)
314 316
315 317 f = self.sa.query(UserFollowing)\
316 318 .filter(UserFollowing.follows_user == u)\
317 319 .filter(UserFollowing.user_id == user_id).scalar()
318 320
319 321 return f is not None
320 322
321 323 def get_followers(self, repo_id):
322 324 if not isinstance(repo_id, int):
323 325 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
324 326
325 327 return self.sa.query(UserFollowing)\
326 328 .filter(UserFollowing.follows_repo_id == repo_id).count()
327 329
328 330 def get_forks(self, repo_id):
329 331 if not isinstance(repo_id, int):
330 332 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
331 333
332 334 return self.sa.query(Repository)\
333 335 .filter(Repository.fork_id == repo_id).count()
334 336
335 337 def mark_as_fork(self, repo, fork, user):
336 338 repo = self.__get_repo(repo)
337 339 fork = self.__get_repo(fork)
338 340 repo.fork = fork
339 341 self.sa.add(repo)
340 342 return repo
341 343
342 344 def pull_changes(self, repo_name, username):
343 345 dbrepo = Repository.get_by_repo_name(repo_name)
344 346 clone_uri = dbrepo.clone_uri
345 347 if not clone_uri:
346 348 raise Exception("This repository doesn't have a clone uri")
347 349
348 350 repo = dbrepo.scm_instance
349 351 try:
350 352 extras = {
351 353 'ip': '',
352 354 'username': username,
353 355 'action': 'push_remote',
354 356 'repository': repo_name,
355 357 'scm': repo.alias,
356 358 }
357 359
358 360 # inject ui extra param to log this action via push logger
359 361 for k, v in extras.items():
360 362 repo._repo.ui.setconfig('rhodecode_extras', k, v)
361 363
362 364 repo.pull(clone_uri)
363 365 self.mark_for_invalidation(repo_name)
364 366 except:
365 367 log.error(traceback.format_exc())
366 368 raise
367 369
368 370 def commit_change(self, repo, repo_name, cs, user, author, message,
369 371 content, f_path):
370 372
371 373 if repo.alias == 'hg':
372 374 from rhodecode.lib.vcs.backends.hg import \
373 375 MercurialInMemoryChangeset as IMC
374 376 elif repo.alias == 'git':
375 377 from rhodecode.lib.vcs.backends.git import \
376 378 GitInMemoryChangeset as IMC
377 379
378 380 # decoding here will force that we have proper encoded values
379 381 # in any other case this will throw exceptions and deny commit
380 382 content = safe_str(content)
381 383 path = safe_str(f_path)
382 384 # message and author needs to be unicode
383 385 # proper backend should then translate that into required type
384 386 message = safe_unicode(message)
385 387 author = safe_unicode(author)
386 388 m = IMC(repo)
387 389 m.change(FileNode(path, content))
388 390 tip = m.commit(message=message,
389 391 author=author,
390 392 parents=[cs], branch=cs.branch)
391 393
392 394 new_cs = tip.short_id
393 395 action = 'push_local:%s' % new_cs
394 396
395 397 action_logger(user, action, repo_name)
396 398
397 399 self.mark_for_invalidation(repo_name)
398 400
399 401 def create_node(self, repo, repo_name, cs, user, author, message, content,
400 402 f_path):
401 403 if repo.alias == 'hg':
402 404 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
403 405 elif repo.alias == 'git':
404 406 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
405 407 # decoding here will force that we have proper encoded values
406 408 # in any other case this will throw exceptions and deny commit
407 409
408 410 if isinstance(content, (basestring,)):
409 411 content = safe_str(content)
410 412 elif isinstance(content, (file, cStringIO.OutputType,)):
411 413 content = content.read()
412 414 else:
413 415 raise Exception('Content is of unrecognized type %s' % (
414 416 type(content)
415 417 ))
416 418
417 419 message = safe_unicode(message)
418 420 author = safe_unicode(author)
419 421 path = safe_str(f_path)
420 422 m = IMC(repo)
421 423
422 424 if isinstance(cs, EmptyChangeset):
423 425 # EmptyChangeset means we we're editing empty repository
424 426 parents = None
425 427 else:
426 428 parents = [cs]
427 429
428 430 m.add(FileNode(path, content=content))
429 431 tip = m.commit(message=message,
430 432 author=author,
431 433 parents=parents, branch=cs.branch)
432 434 new_cs = tip.short_id
433 435 action = 'push_local:%s' % new_cs
434 436
435 437 action_logger(user, action, repo_name)
436 438
437 439 self.mark_for_invalidation(repo_name)
438 440
439 441 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
440 442 """
441 443 recursive walk in root dir and return a set of all path in that dir
442 444 based on repository walk function
443 445
444 446 :param repo_name: name of repository
445 447 :param revision: revision for which to list nodes
446 448 :param root_path: root path to list
447 449 :param flat: return as a list, if False returns a dict with decription
448 450
449 451 """
450 452 _files = list()
451 453 _dirs = list()
452 454 try:
453 455 _repo = self.__get_repo(repo_name)
454 456 changeset = _repo.scm_instance.get_changeset(revision)
455 457 root_path = root_path.lstrip('/')
456 458 for topnode, dirs, files in changeset.walk(root_path):
457 459 for f in files:
458 460 _files.append(f.path if flat else {"name": f.path,
459 461 "type": "file"})
460 462 for d in dirs:
461 463 _dirs.append(d.path if flat else {"name": d.path,
462 464 "type": "dir"})
463 465 except RepositoryError:
464 466 log.debug(traceback.format_exc())
465 467 raise
466 468
467 469 return _dirs, _files
468 470
469 471 def get_unread_journal(self):
470 472 return self.sa.query(UserLog).count()
General Comments 0
You need to be logged in to leave comments. Login now