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