##// END OF EJS Templates
added author to main page tooltip
marcink -
r1459:6691d409 beta
parent child Browse files
Show More
@@ -1,370 +1,370 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.scm
3 rhodecode.model.scm
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Scm model for RhodeCode
6 Scm model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import os
26 import time
26 import time
27 import traceback
27 import traceback
28 import logging
28 import logging
29
29
30 from sqlalchemy.exc import DatabaseError
30 from sqlalchemy.exc import DatabaseError
31
31
32 from vcs import get_backend
32 from vcs import get_backend
33 from vcs.exceptions import RepositoryError
33 from vcs.exceptions import RepositoryError
34 from vcs.utils.lazy import LazyProperty
34 from vcs.utils.lazy import LazyProperty
35 from vcs.nodes import FileNode
35 from vcs.nodes import FileNode
36
36
37 from rhodecode import BACKENDS
37 from rhodecode import BACKENDS
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib import safe_str
39 from rhodecode.lib import safe_str
40 from rhodecode.lib.auth import HasRepoPermissionAny
40 from rhodecode.lib.auth import HasRepoPermissionAny
41 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
41 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
42 action_logger
42 action_logger
43 from rhodecode.model import BaseModel
43 from rhodecode.model import BaseModel
44 from rhodecode.model.user import UserModel
44 from rhodecode.model.user import UserModel
45 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
45 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
46 UserFollowing, UserLog
46 UserFollowing, UserLog
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 class UserTemp(object):
51 class UserTemp(object):
52 def __init__(self, user_id):
52 def __init__(self, user_id):
53 self.user_id = user_id
53 self.user_id = user_id
54
54
55 def __repr__(self):
55 def __repr__(self):
56 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
56 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
57
57
58
58
59 class RepoTemp(object):
59 class RepoTemp(object):
60 def __init__(self, repo_id):
60 def __init__(self, repo_id):
61 self.repo_id = repo_id
61 self.repo_id = repo_id
62
62
63 def __repr__(self):
63 def __repr__(self):
64 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
64 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
65
65
66 class CachedRepoList(object):
66 class CachedRepoList(object):
67
67
68 def __init__(self, db_repo_list, repos_path, order_by=None):
68 def __init__(self, db_repo_list, repos_path, order_by=None):
69 self.db_repo_list = db_repo_list
69 self.db_repo_list = db_repo_list
70 self.repos_path = repos_path
70 self.repos_path = repos_path
71 self.order_by = order_by
71 self.order_by = order_by
72 self.reversed = (order_by or '').startswith('-')
72 self.reversed = (order_by or '').startswith('-')
73
73
74 def __len__(self):
74 def __len__(self):
75 return len(self.db_repo_list)
75 return len(self.db_repo_list)
76
76
77 def __repr__(self):
77 def __repr__(self):
78 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
78 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
79
79
80 def __iter__(self):
80 def __iter__(self):
81 for dbr in self.db_repo_list:
81 for dbr in self.db_repo_list:
82
82
83 scmr = dbr.scm_instance_cached
83 scmr = dbr.scm_instance_cached
84
84
85 # check permission at this level
85 # check permission at this level
86 if not HasRepoPermissionAny('repository.read', 'repository.write',
86 if not HasRepoPermissionAny('repository.read', 'repository.write',
87 'repository.admin')(dbr.repo_name,
87 'repository.admin')(dbr.repo_name,
88 'get repo check'):
88 'get repo check'):
89 continue
89 continue
90
90
91 if scmr is None:
91 if scmr is None:
92 log.error('%s this repository is present in database but it '
92 log.error('%s this repository is present in database but it '
93 'cannot be created as an scm instance',
93 'cannot be created as an scm instance',
94 dbr.repo_name)
94 dbr.repo_name)
95 continue
95 continue
96
96
97 last_change = scmr.last_change
97 last_change = scmr.last_change
98 tip = h.get_changeset_safe(scmr, 'tip')
98 tip = h.get_changeset_safe(scmr, 'tip')
99
99
100 tmp_d = {}
100 tmp_d = {}
101 tmp_d['name'] = dbr.repo_name
101 tmp_d['name'] = dbr.repo_name
102 tmp_d['name_sort'] = tmp_d['name'].lower()
102 tmp_d['name_sort'] = tmp_d['name'].lower()
103 tmp_d['description'] = dbr.description
103 tmp_d['description'] = dbr.description
104 tmp_d['description_sort'] = tmp_d['description']
104 tmp_d['description_sort'] = tmp_d['description']
105 tmp_d['last_change'] = last_change
105 tmp_d['last_change'] = last_change
106 tmp_d['last_change_sort'] = time.mktime(last_change \
106 tmp_d['last_change_sort'] = time.mktime(last_change \
107 .timetuple())
107 .timetuple())
108 tmp_d['tip'] = tip.raw_id
108 tmp_d['tip'] = tip.raw_id
109 tmp_d['tip_sort'] = tip.revision
109 tmp_d['tip_sort'] = tip.revision
110 tmp_d['rev'] = tip.revision
110 tmp_d['rev'] = tip.revision
111 tmp_d['contact'] = dbr.user.full_contact
111 tmp_d['contact'] = dbr.user.full_contact
112 tmp_d['contact_sort'] = tmp_d['contact']
112 tmp_d['contact_sort'] = tmp_d['contact']
113 tmp_d['owner_sort'] = tmp_d['contact']
113 tmp_d['owner_sort'] = tmp_d['contact']
114 tmp_d['repo_archives'] = list(scmr._get_archives())
114 tmp_d['repo_archives'] = list(scmr._get_archives())
115 tmp_d['last_msg'] = tip.message
115 tmp_d['last_msg'] = tip.message
116 tmp_d['repo'] = scmr
116 tmp_d['author'] = tip.author
117 tmp_d['dbrepo'] = dbr.get_dict()
117 tmp_d['dbrepo'] = dbr.get_dict()
118 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork \
118 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork \
119 else {}
119 else {}
120 yield tmp_d
120 yield tmp_d
121
121
122 class ScmModel(BaseModel):
122 class ScmModel(BaseModel):
123 """Generic Scm Model
123 """Generic Scm Model
124 """
124 """
125
125
126 @LazyProperty
126 @LazyProperty
127 def repos_path(self):
127 def repos_path(self):
128 """Get's the repositories root path from database
128 """Get's the repositories root path from database
129 """
129 """
130
130
131 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
131 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
132
132
133 return q.ui_value
133 return q.ui_value
134
134
135 def repo_scan(self, repos_path=None):
135 def repo_scan(self, repos_path=None):
136 """Listing of repositories in given path. This path should not be a
136 """Listing of repositories in given path. This path should not be a
137 repository itself. Return a dictionary of repository objects
137 repository itself. Return a dictionary of repository objects
138
138
139 :param repos_path: path to directory containing repositories
139 :param repos_path: path to directory containing repositories
140 """
140 """
141
141
142 log.info('scanning for repositories in %s', repos_path)
142 log.info('scanning for repositories in %s', repos_path)
143
143
144 if repos_path is None:
144 if repos_path is None:
145 repos_path = self.repos_path
145 repos_path = self.repos_path
146
146
147 baseui = make_ui('db')
147 baseui = make_ui('db')
148 repos_list = {}
148 repos_list = {}
149
149
150 for name, path in get_filesystem_repos(repos_path, recursive=True):
150 for name, path in get_filesystem_repos(repos_path, recursive=True):
151 try:
151 try:
152 if name in repos_list:
152 if name in repos_list:
153 raise RepositoryError('Duplicate repository name %s '
153 raise RepositoryError('Duplicate repository name %s '
154 'found in %s' % (name, path))
154 'found in %s' % (name, path))
155 else:
155 else:
156
156
157 klass = get_backend(path[0])
157 klass = get_backend(path[0])
158
158
159 if path[0] == 'hg' and path[0] in BACKENDS.keys():
159 if path[0] == 'hg' and path[0] in BACKENDS.keys():
160
160
161 # for mercurial we need to have an str path
161 # for mercurial we need to have an str path
162 repos_list[name] = klass(safe_str(path[1]),
162 repos_list[name] = klass(safe_str(path[1]),
163 baseui=baseui)
163 baseui=baseui)
164
164
165 if path[0] == 'git' and path[0] in BACKENDS.keys():
165 if path[0] == 'git' and path[0] in BACKENDS.keys():
166 repos_list[name] = klass(path[1])
166 repos_list[name] = klass(path[1])
167 except OSError:
167 except OSError:
168 continue
168 continue
169
169
170 return repos_list
170 return repos_list
171
171
172 def get_repos(self, all_repos=None, sort_key=None):
172 def get_repos(self, all_repos=None, sort_key=None):
173 """
173 """
174 Get all repos from db and for each repo create it's
174 Get all repos from db and for each repo create it's
175 backend instance and fill that backed with information from database
175 backend instance and fill that backed with information from database
176
176
177 :param all_repos: list of repository names as strings
177 :param all_repos: list of repository names as strings
178 give specific repositories list, good for filtering
178 give specific repositories list, good for filtering
179 """
179 """
180 if all_repos is None:
180 if all_repos is None:
181 all_repos = self.sa.query(Repository)\
181 all_repos = self.sa.query(Repository)\
182 .filter(Repository.group_id == None)\
182 .filter(Repository.group_id == None)\
183 .order_by(Repository.repo_name).all()
183 .order_by(Repository.repo_name).all()
184
184
185 repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
185 repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
186 order_by=sort_key)
186 order_by=sort_key)
187
187
188 return repo_iter
188 return repo_iter
189
189
190 def mark_for_invalidation(self, repo_name):
190 def mark_for_invalidation(self, repo_name):
191 """Puts cache invalidation task into db for
191 """Puts cache invalidation task into db for
192 further global cache invalidation
192 further global cache invalidation
193
193
194 :param repo_name: this repo that should invalidation take place
194 :param repo_name: this repo that should invalidation take place
195 """
195 """
196
196
197 log.debug('marking %s for invalidation', repo_name)
197 log.debug('marking %s for invalidation', repo_name)
198 cache = self.sa.query(CacheInvalidation)\
198 cache = self.sa.query(CacheInvalidation)\
199 .filter(CacheInvalidation.cache_key == repo_name).scalar()
199 .filter(CacheInvalidation.cache_key == repo_name).scalar()
200
200
201 if cache:
201 if cache:
202 # mark this cache as inactive
202 # mark this cache as inactive
203 cache.cache_active = False
203 cache.cache_active = False
204 else:
204 else:
205 log.debug('cache key not found in invalidation db -> creating one')
205 log.debug('cache key not found in invalidation db -> creating one')
206 cache = CacheInvalidation(repo_name)
206 cache = CacheInvalidation(repo_name)
207
207
208 try:
208 try:
209 self.sa.add(cache)
209 self.sa.add(cache)
210 self.sa.commit()
210 self.sa.commit()
211 except (DatabaseError,):
211 except (DatabaseError,):
212 log.error(traceback.format_exc())
212 log.error(traceback.format_exc())
213 self.sa.rollback()
213 self.sa.rollback()
214
214
215 def toggle_following_repo(self, follow_repo_id, user_id):
215 def toggle_following_repo(self, follow_repo_id, user_id):
216
216
217 f = self.sa.query(UserFollowing)\
217 f = self.sa.query(UserFollowing)\
218 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
218 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
219 .filter(UserFollowing.user_id == user_id).scalar()
219 .filter(UserFollowing.user_id == user_id).scalar()
220
220
221 if f is not None:
221 if f is not None:
222
222
223 try:
223 try:
224 self.sa.delete(f)
224 self.sa.delete(f)
225 self.sa.commit()
225 self.sa.commit()
226 action_logger(UserTemp(user_id),
226 action_logger(UserTemp(user_id),
227 'stopped_following_repo',
227 'stopped_following_repo',
228 RepoTemp(follow_repo_id))
228 RepoTemp(follow_repo_id))
229 return
229 return
230 except:
230 except:
231 log.error(traceback.format_exc())
231 log.error(traceback.format_exc())
232 self.sa.rollback()
232 self.sa.rollback()
233 raise
233 raise
234
234
235 try:
235 try:
236 f = UserFollowing()
236 f = UserFollowing()
237 f.user_id = user_id
237 f.user_id = user_id
238 f.follows_repo_id = follow_repo_id
238 f.follows_repo_id = follow_repo_id
239 self.sa.add(f)
239 self.sa.add(f)
240 self.sa.commit()
240 self.sa.commit()
241 action_logger(UserTemp(user_id),
241 action_logger(UserTemp(user_id),
242 'started_following_repo',
242 'started_following_repo',
243 RepoTemp(follow_repo_id))
243 RepoTemp(follow_repo_id))
244 except:
244 except:
245 log.error(traceback.format_exc())
245 log.error(traceback.format_exc())
246 self.sa.rollback()
246 self.sa.rollback()
247 raise
247 raise
248
248
249 def toggle_following_user(self, follow_user_id, user_id):
249 def toggle_following_user(self, follow_user_id, user_id):
250 f = self.sa.query(UserFollowing)\
250 f = self.sa.query(UserFollowing)\
251 .filter(UserFollowing.follows_user_id == follow_user_id)\
251 .filter(UserFollowing.follows_user_id == follow_user_id)\
252 .filter(UserFollowing.user_id == user_id).scalar()
252 .filter(UserFollowing.user_id == user_id).scalar()
253
253
254 if f is not None:
254 if f is not None:
255 try:
255 try:
256 self.sa.delete(f)
256 self.sa.delete(f)
257 self.sa.commit()
257 self.sa.commit()
258 return
258 return
259 except:
259 except:
260 log.error(traceback.format_exc())
260 log.error(traceback.format_exc())
261 self.sa.rollback()
261 self.sa.rollback()
262 raise
262 raise
263
263
264 try:
264 try:
265 f = UserFollowing()
265 f = UserFollowing()
266 f.user_id = user_id
266 f.user_id = user_id
267 f.follows_user_id = follow_user_id
267 f.follows_user_id = follow_user_id
268 self.sa.add(f)
268 self.sa.add(f)
269 self.sa.commit()
269 self.sa.commit()
270 except:
270 except:
271 log.error(traceback.format_exc())
271 log.error(traceback.format_exc())
272 self.sa.rollback()
272 self.sa.rollback()
273 raise
273 raise
274
274
275 def is_following_repo(self, repo_name, user_id, cache=False):
275 def is_following_repo(self, repo_name, user_id, cache=False):
276 r = self.sa.query(Repository)\
276 r = self.sa.query(Repository)\
277 .filter(Repository.repo_name == repo_name).scalar()
277 .filter(Repository.repo_name == repo_name).scalar()
278
278
279 f = self.sa.query(UserFollowing)\
279 f = self.sa.query(UserFollowing)\
280 .filter(UserFollowing.follows_repository == r)\
280 .filter(UserFollowing.follows_repository == r)\
281 .filter(UserFollowing.user_id == user_id).scalar()
281 .filter(UserFollowing.user_id == user_id).scalar()
282
282
283 return f is not None
283 return f is not None
284
284
285 def is_following_user(self, username, user_id, cache=False):
285 def is_following_user(self, username, user_id, cache=False):
286 u = UserModel(self.sa).get_by_username(username)
286 u = UserModel(self.sa).get_by_username(username)
287
287
288 f = self.sa.query(UserFollowing)\
288 f = self.sa.query(UserFollowing)\
289 .filter(UserFollowing.follows_user == u)\
289 .filter(UserFollowing.follows_user == u)\
290 .filter(UserFollowing.user_id == user_id).scalar()
290 .filter(UserFollowing.user_id == user_id).scalar()
291
291
292 return f is not None
292 return f is not None
293
293
294 def get_followers(self, repo_id):
294 def get_followers(self, repo_id):
295 if not isinstance(repo_id, int):
295 if not isinstance(repo_id, int):
296 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
296 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
297
297
298 return self.sa.query(UserFollowing)\
298 return self.sa.query(UserFollowing)\
299 .filter(UserFollowing.follows_repo_id == repo_id).count()
299 .filter(UserFollowing.follows_repo_id == repo_id).count()
300
300
301 def get_forks(self, repo_id):
301 def get_forks(self, repo_id):
302 if not isinstance(repo_id, int):
302 if not isinstance(repo_id, int):
303 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
303 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
304
304
305 return self.sa.query(Repository)\
305 return self.sa.query(Repository)\
306 .filter(Repository.fork_id == repo_id).count()
306 .filter(Repository.fork_id == repo_id).count()
307
307
308 def pull_changes(self, repo_name, username):
308 def pull_changes(self, repo_name, username):
309 dbrepo = Repository.by_repo_name(repo_name)
309 dbrepo = Repository.by_repo_name(repo_name)
310 repo = dbrepo.scm_instance
310 repo = dbrepo.scm_instance
311 try:
311 try:
312 extras = {'ip': '',
312 extras = {'ip': '',
313 'username': username,
313 'username': username,
314 'action': 'push_remote',
314 'action': 'push_remote',
315 'repository': repo_name}
315 'repository': repo_name}
316
316
317 #inject ui extra param to log this action via push logger
317 #inject ui extra param to log this action via push logger
318 for k, v in extras.items():
318 for k, v in extras.items():
319 repo._repo.ui.setconfig('rhodecode_extras', k, v)
319 repo._repo.ui.setconfig('rhodecode_extras', k, v)
320
320
321 repo.pull(dbrepo.clone_uri)
321 repo.pull(dbrepo.clone_uri)
322 self.mark_for_invalidation(repo_name)
322 self.mark_for_invalidation(repo_name)
323 except:
323 except:
324 log.error(traceback.format_exc())
324 log.error(traceback.format_exc())
325 raise
325 raise
326
326
327
327
328 def commit_change(self, repo, repo_name, cs, user, author, message, content,
328 def commit_change(self, repo, repo_name, cs, user, author, message, content,
329 f_path):
329 f_path):
330
330
331 if repo.alias == 'hg':
331 if repo.alias == 'hg':
332 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
332 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
333 elif repo.alias == 'git':
333 elif repo.alias == 'git':
334 from vcs.backends.git import GitInMemoryChangeset as IMC
334 from vcs.backends.git import GitInMemoryChangeset as IMC
335
335
336 # decoding here will force that we have proper encoded values
336 # decoding here will force that we have proper encoded values
337 # in any other case this will throw exceptions and deny commit
337 # in any other case this will throw exceptions and deny commit
338 content = safe_str(content)
338 content = safe_str(content)
339 message = safe_str(message)
339 message = safe_str(message)
340 path = safe_str(f_path)
340 path = safe_str(f_path)
341 author = safe_str(author)
341 author = safe_str(author)
342 m = IMC(repo)
342 m = IMC(repo)
343 m.change(FileNode(path, content))
343 m.change(FileNode(path, content))
344 tip = m.commit(message=message,
344 tip = m.commit(message=message,
345 author=author,
345 author=author,
346 parents=[cs], branch=cs.branch)
346 parents=[cs], branch=cs.branch)
347
347
348 new_cs = tip.short_id
348 new_cs = tip.short_id
349 action = 'push_local:%s' % new_cs
349 action = 'push_local:%s' % new_cs
350
350
351 action_logger(user, action, repo_name)
351 action_logger(user, action, repo_name)
352
352
353 self.mark_for_invalidation(repo_name)
353 self.mark_for_invalidation(repo_name)
354
354
355
355
356 def get_unread_journal(self):
356 def get_unread_journal(self):
357 return self.sa.query(UserLog).count()
357 return self.sa.query(UserLog).count()
358
358
359 def _should_invalidate(self, repo_name):
359 def _should_invalidate(self, repo_name):
360 """Looks up database for invalidation signals for this repo_name
360 """Looks up database for invalidation signals for this repo_name
361
361
362 :param repo_name:
362 :param repo_name:
363 """
363 """
364
364
365 ret = self.sa.query(CacheInvalidation)\
365 ret = self.sa.query(CacheInvalidation)\
366 .filter(CacheInvalidation.cache_key == repo_name)\
366 .filter(CacheInvalidation.cache_key == repo_name)\
367 .filter(CacheInvalidation.cache_active == False)\
367 .filter(CacheInvalidation.cache_active == False)\
368 .scalar()
368 .scalar()
369
369
370 return ret
370 return ret
General Comments 0
You need to be logged in to leave comments. Login now