##// END OF EJS Templates
moved out commit into scm model, and added cache invalidation after commit.
marcink -
r1311:6705eeeb beta
parent child Browse files
Show More
@@ -1,420 +1,409 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.files
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Files controller for RhodeCode
7 7
8 8 :created_on: Apr 21, 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
26 26 import os
27 27 import logging
28 28 import mimetypes
29 29 import traceback
30 30
31 31 from pylons import request, response, session, tmpl_context as c, url
32 32 from pylons.i18n.translation import _
33 33 from pylons.controllers.util import redirect
34 34
35 35 from vcs.backends import ARCHIVE_SPECS
36 36 from vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
37 37 EmptyRepositoryError, ImproperArchiveTypeError, VCSError
38 38 from vcs.nodes import FileNode, NodeKind
39 39 from vcs.utils import diffs as differ
40 40
41 41 from rhodecode.lib import convert_line_endings, detect_mode
42 42 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
43 43 from rhodecode.lib.base import BaseRepoController, render
44 44 from rhodecode.lib.utils import EmptyChangeset
45 45 import rhodecode.lib.helpers as h
46 46 from rhodecode.model.repo import RepoModel
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50
51 51 class FilesController(BaseRepoController):
52 52
53 53 @LoginRequired()
54 54 def __before__(self):
55 55 super(FilesController, self).__before__()
56 56 c.cut_off_limit = self.cut_off_limit
57 57
58 58 def __get_cs_or_redirect(self, rev, repo_name):
59 59 """
60 60 Safe way to get changeset if error occur it redirects to tip with
61 61 proper message
62 62
63 63 :param rev: revision to fetch
64 64 :param repo_name: repo name to redirect after
65 65 """
66 66
67 67 try:
68 68 return c.rhodecode_repo.get_changeset(rev)
69 69 except EmptyRepositoryError, e:
70 70 h.flash(_('There are no files yet'), category='warning')
71 71 redirect(h.url('summary_home', repo_name=repo_name))
72 72
73 73 except RepositoryError, e:
74 74 h.flash(str(e), category='warning')
75 75 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
76 76
77 77 def __get_filenode_or_redirect(self, repo_name, cs, path):
78 78 """
79 79 Returns file_node, if error occurs or given path is directory,
80 80 it'll redirect to top level path
81 81
82 82 :param repo_name: repo_name
83 83 :param cs: given changeset
84 84 :param path: path to lookup
85 85 """
86 86
87 87 try:
88 88 file_node = cs.get_node(path)
89 89 if file_node.is_dir():
90 90 raise RepositoryError('given path is a directory')
91 91 except RepositoryError, e:
92 92 h.flash(str(e), category='warning')
93 93 redirect(h.url('files_home', repo_name=repo_name,
94 94 revision=cs.raw_id))
95 95
96 96 return file_node
97 97
98 98 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
99 99 'repository.admin')
100 100 def index(self, repo_name, revision, f_path):
101 101 #reditect to given revision from form if given
102 102 post_revision = request.POST.get('at_rev', None)
103 103 if post_revision:
104 104 cs = self.__get_cs_or_redirect(post_revision, repo_name)
105 105 redirect(url('files_home', repo_name=c.repo_name,
106 106 revision=cs.raw_id, f_path=f_path))
107 107
108 108 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
109 109 c.branch = request.GET.get('branch', None)
110 110 c.f_path = f_path
111 111
112 112 cur_rev = c.changeset.revision
113 113
114 114 #prev link
115 115 try:
116 116 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
117 117 c.url_prev = url('files_home', repo_name=c.repo_name,
118 118 revision=prev_rev.raw_id, f_path=f_path)
119 119 if c.branch:
120 120 c.url_prev += '?branch=%s' % c.branch
121 121 except (ChangesetDoesNotExistError, VCSError):
122 122 c.url_prev = '#'
123 123
124 124 #next link
125 125 try:
126 126 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
127 127 c.url_next = url('files_home', repo_name=c.repo_name,
128 128 revision=next_rev.raw_id, f_path=f_path)
129 129 if c.branch:
130 130 c.url_next += '?branch=%s' % c.branch
131 131 except (ChangesetDoesNotExistError, VCSError):
132 132 c.url_next = '#'
133 133
134 134 #files or dirs
135 135 try:
136 136 c.files_list = c.changeset.get_node(f_path)
137 137
138 138 if c.files_list.is_file():
139 139 c.file_history = self._get_node_history(c.changeset, f_path)
140 140 else:
141 141 c.file_history = []
142 142 except RepositoryError, e:
143 143 h.flash(str(e), category='warning')
144 144 redirect(h.url('files_home', repo_name=repo_name,
145 145 revision=revision))
146 146
147 147 return render('files/files.html')
148 148
149 149 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
150 150 'repository.admin')
151 151 def rawfile(self, repo_name, revision, f_path):
152 152 cs = self.__get_cs_or_redirect(revision, repo_name)
153 153 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
154 154
155 155 response.content_disposition = 'attachment; filename=%s' % \
156 156 f_path.split(os.sep)[-1].encode('utf8', 'replace')
157 157
158 158 response.content_type = file_node.mimetype
159 159 return file_node.content
160 160
161 161 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
162 162 'repository.admin')
163 163 def raw(self, repo_name, revision, f_path):
164 164 cs = self.__get_cs_or_redirect(revision, repo_name)
165 165 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
166 166
167 167 raw_mimetype_mapping = {
168 168 # map original mimetype to a mimetype used for "show as raw"
169 169 # you can also provide a content-disposition to override the
170 170 # default "attachment" disposition.
171 171 # orig_type: (new_type, new_dispo)
172 172
173 173 # show images inline:
174 174 'image/x-icon': ('image/x-icon', 'inline'),
175 175 'image/png': ('image/png', 'inline'),
176 176 'image/gif': ('image/gif', 'inline'),
177 177 'image/jpeg': ('image/jpeg', 'inline'),
178 178 'image/svg+xml': ('image/svg+xml', 'inline'),
179 179 }
180 180
181 181 mimetype = file_node.mimetype
182 182 try:
183 183 mimetype, dispo = raw_mimetype_mapping[mimetype]
184 184 except KeyError:
185 185 # we don't know anything special about this, handle it safely
186 186 if file_node.is_binary:
187 187 # do same as download raw for binary files
188 188 mimetype, dispo = 'application/octet-stream', 'attachment'
189 189 else:
190 190 # do not just use the original mimetype, but force text/plain,
191 191 # otherwise it would serve text/html and that might be unsafe.
192 192 # Note: underlying vcs library fakes text/plain mimetype if the
193 193 # mimetype can not be determined and it thinks it is not
194 194 # binary.This might lead to erroneous text display in some
195 195 # cases, but helps in other cases, like with text files
196 196 # without extension.
197 197 mimetype, dispo = 'text/plain', 'inline'
198 198
199 199 if dispo == 'attachment':
200 200 dispo = 'attachment; filename=%s' % \
201 201 f_path.split(os.sep)[-1].encode('utf8', 'replace')
202 202
203 203 response.content_disposition = dispo
204 204 response.content_type = mimetype
205 205 return file_node.content
206 206
207 207 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
208 208 'repository.admin')
209 209 def annotate(self, repo_name, revision, f_path):
210 210 c.cs = self.__get_cs_or_redirect(revision, repo_name)
211 211 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
212 212
213 213 c.file_history = self._get_node_history(c.cs, f_path)
214 214 c.f_path = f_path
215 215 return render('files/files_annotate.html')
216 216
217 217 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
218 218 def edit(self, repo_name, revision, f_path):
219 219 r_post = request.POST
220 220
221 if c.rhodecode_repo.alias == 'hg':
222 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
223 elif c.rhodecode_repo.alias == 'git':
224 from vcs.backends.git import GitInMemoryChangeset as IMC
225
226 221 c.cs = self.__get_cs_or_redirect(revision, repo_name)
227 222 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
228 223
229 224 c.file_history = self._get_node_history(c.cs, f_path)
230 225 c.f_path = f_path
231 226
232 227 if r_post:
233 228
234 229 old_content = c.file.content
235 230 sl = old_content.splitlines(1)
236 231 first_line = sl[0] if sl else ''
237 232 # modes: 0 - Unix, 1 - Mac, 2 - DOS
238 233 mode = detect_mode(first_line, 0)
239 234 content = convert_line_endings(r_post.get('content'), mode)
240 235
241 236 message = r_post.get('message') or (_('Edited %s via RhodeCode')
242 237 % (f_path))
238 author = self.rhodecode_user.full_contact
243 239
244 240 if content == old_content:
245 241 h.flash(_('No changes'),
246 242 category='warning')
247 243 return redirect(url('changeset_home', repo_name=c.repo_name,
248 244 revision='tip'))
249 245
250 246 try:
251 # decoding here will force that we have proper encoded values
252 # in any other case this will throw exceptions and deny commit
253 content = content.encode('utf8')
254 message = message.encode('utf8')
255 path = f_path.encode('utf8')
256 author = self.rhodecode_user.full_contact.encode('utf8')
257 m = IMC(c.rhodecode_repo)
258 m.change(FileNode(path, content))
259 m.commit(message=message,
260 author=author,
261 parents=[c.cs], branch=c.cs.branch)
247 self.scm_model.commit_change(repo=c.rhodecode_repo,
248 repo_name=repo_name, cs=c.cs,
249 author=author, message=message,
250 content=content, f_path=f_path)
262 251 h.flash(_('Successfully committed to %s' % f_path),
263 252 category='success')
264 253
265 except Exception, e:
254 except Exception:
266 255 log.error(traceback.format_exc())
267 256 h.flash(_('Error occurred during commit'), category='error')
268 257 return redirect(url('changeset_home',
269 258 repo_name=c.repo_name, revision='tip'))
270 259
271 260 return render('files/files_edit.html')
272 261
273 262 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
274 263 'repository.admin')
275 264 def archivefile(self, repo_name, fname):
276 265
277 266 fileformat = None
278 267 revision = None
279 268 ext = None
280 269
281 270 for a_type, ext_data in ARCHIVE_SPECS.items():
282 271 archive_spec = fname.split(ext_data[1])
283 272 if len(archive_spec) == 2 and archive_spec[1] == '':
284 273 fileformat = a_type or ext_data[1]
285 274 revision = archive_spec[0]
286 275 ext = ext_data[1]
287 276
288 277 try:
289 278 dbrepo = RepoModel().get_by_repo_name(repo_name)
290 279 if dbrepo.enable_downloads is False:
291 280 return _('downloads disabled')
292 281
293 282 cs = c.rhodecode_repo.get_changeset(revision)
294 283 content_type = ARCHIVE_SPECS[fileformat][0]
295 284 except ChangesetDoesNotExistError:
296 285 return _('Unknown revision %s') % revision
297 286 except EmptyRepositoryError:
298 287 return _('Empty repository')
299 288 except (ImproperArchiveTypeError, KeyError):
300 289 return _('Unknown archive type')
301 290
302 291 response.content_type = content_type
303 292 response.content_disposition = 'attachment; filename=%s-%s%s' \
304 293 % (repo_name, revision, ext)
305 294
306 295 import tempfile
307 296 archive = tempfile.mkstemp()[1]
308 297 t = open(archive, 'wb')
309 298 cs.fill_archive(stream=t, kind=fileformat)
310 299
311 300 def get_chunked_archive(archive):
312 301 stream = open(archive, 'rb')
313 302 while True:
314 303 data = stream.read(4096)
315 304 if not data:
316 305 os.remove(archive)
317 306 break
318 307 yield data
319 308
320 309 return get_chunked_archive(archive)
321 310
322 311 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
323 312 'repository.admin')
324 313 def diff(self, repo_name, f_path):
325 314 diff1 = request.GET.get('diff1')
326 315 diff2 = request.GET.get('diff2')
327 316 c.action = request.GET.get('diff')
328 317 c.no_changes = diff1 == diff2
329 318 c.f_path = f_path
330 319 c.big_diff = False
331 320
332 321 try:
333 322 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
334 323 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
335 324 node1 = c.changeset_1.get_node(f_path)
336 325 else:
337 326 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
338 327 node1 = FileNode('.', '', changeset=c.changeset_1)
339 328
340 329 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
341 330 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
342 331 node2 = c.changeset_2.get_node(f_path)
343 332 else:
344 333 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
345 334 node2 = FileNode('.', '', changeset=c.changeset_2)
346 335 except RepositoryError:
347 336 return redirect(url('files_home',
348 337 repo_name=c.repo_name, f_path=f_path))
349 338
350 339 if c.action == 'download':
351 340 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
352 341 format='gitdiff')
353 342
354 343 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
355 344 response.content_type = 'text/plain'
356 345 response.content_disposition = 'attachment; filename=%s' \
357 346 % diff_name
358 347 return diff.raw_diff()
359 348
360 349 elif c.action == 'raw':
361 350 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
362 351 format='gitdiff')
363 352 response.content_type = 'text/plain'
364 353 return diff.raw_diff()
365 354
366 355 elif c.action == 'diff':
367 356 if node1.is_binary or node2.is_binary:
368 357 c.cur_diff = _('Binary file')
369 358 elif node1.size > self.cut_off_limit or \
370 359 node2.size > self.cut_off_limit:
371 360 c.cur_diff = ''
372 361 c.big_diff = True
373 362 else:
374 363 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
375 364 format='gitdiff')
376 365 c.cur_diff = diff.as_html()
377 366 else:
378 367
379 368 #default option
380 369 if node1.is_binary or node2.is_binary:
381 370 c.cur_diff = _('Binary file')
382 371 elif node1.size > self.cut_off_limit or \
383 372 node2.size > self.cut_off_limit:
384 373 c.cur_diff = ''
385 374 c.big_diff = True
386 375
387 376 else:
388 377 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
389 378 format='gitdiff')
390 379 c.cur_diff = diff.as_html()
391 380
392 381 if not c.cur_diff and not c.big_diff:
393 382 c.no_changes = True
394 383 return render('files/file_diff.html')
395 384
396 385 def _get_node_history(self, cs, f_path):
397 386 changesets = cs.get_file_history(f_path)
398 387 hist_l = []
399 388
400 389 changesets_group = ([], _("Changesets"))
401 390 branches_group = ([], _("Branches"))
402 391 tags_group = ([], _("Tags"))
403 392
404 393 for chs in changesets:
405 394 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
406 395 changesets_group[0].append((chs.raw_id, n_desc,))
407 396
408 397 hist_l.append(changesets_group)
409 398
410 399 for name, chs in c.rhodecode_repo.branches.items():
411 400 #chs = chs.split(':')[-1]
412 401 branches_group[0].append((chs, name),)
413 402 hist_l.append(branches_group)
414 403
415 404 for name, chs in c.rhodecode_repo.tags.items():
416 405 #chs = chs.split(':')[-1]
417 406 tags_group[0].append((chs, name),)
418 407 hist_l.append(tags_group)
419 408
420 409 return hist_l
@@ -1,410 +1,435 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 from vcs.nodes import FileNode
41 42
42 43 from rhodecode import BACKENDS
43 44 from rhodecode.lib import helpers as h
44 45 from rhodecode.lib.auth import HasRepoPermissionAny
45 46 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
46 47 action_logger
47 48 from rhodecode.model import BaseModel
48 49 from rhodecode.model.user import UserModel
49 50 from rhodecode.model.repo import RepoModel
50 51 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
51 52 UserFollowing, UserLog
52 53 from rhodecode.model.caching_query import FromCache
53 54
54 55 log = logging.getLogger(__name__)
55 56
56 57
57 58 class UserTemp(object):
58 59 def __init__(self, user_id):
59 60 self.user_id = user_id
60 61
61 62 def __repr__(self):
62 63 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
63 64
64 65
65 66 class RepoTemp(object):
66 67 def __init__(self, repo_id):
67 68 self.repo_id = repo_id
68 69
69 70 def __repr__(self):
70 71 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
71 72
72 73
73 74 class ScmModel(BaseModel):
74 75 """Generic Scm Model
75 76 """
76 77
77 78 @LazyProperty
78 79 def repos_path(self):
79 80 """Get's the repositories root path from database
80 81 """
81 82
82 83 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
83 84
84 85 return q.ui_value
85 86
86 87 def repo_scan(self, repos_path=None):
87 88 """Listing of repositories in given path. This path should not be a
88 89 repository itself. Return a dictionary of repository objects
89 90
90 91 :param repos_path: path to directory containing repositories
91 92 """
92 93
93 94 log.info('scanning for repositories in %s', repos_path)
94 95
95 96 if repos_path is None:
96 97 repos_path = self.repos_path
97 98
98 99 baseui = make_ui('db')
99 100 repos_list = {}
100 101
101 102 for name, path in get_filesystem_repos(repos_path, recursive=True):
102 103 try:
103 104 if name in repos_list:
104 105 raise RepositoryError('Duplicate repository name %s '
105 106 'found in %s' % (name, path))
106 107 else:
107 108
108 109 klass = get_backend(path[0])
109 110
110 111 if path[0] == 'hg' and path[0] in BACKENDS.keys():
111 112 repos_list[name] = klass(path[1], baseui=baseui)
112 113
113 114 if path[0] == 'git' and path[0] in BACKENDS.keys():
114 115 repos_list[name] = klass(path[1])
115 116 except OSError:
116 117 continue
117 118
118 119 return repos_list
119 120
120 121 def get_repos(self, all_repos=None):
121 122 """Get all repos from db and for each repo create it's
122 123 backend instance and fill that backed with information from database
123 124
124 125 :param all_repos: give specific repositories list, good for filtering
125 126 this have to be a list of just the repository names
126 127 """
127 128 if all_repos is None:
128 129 repos = self.sa.query(Repository)\
129 130 .order_by(Repository.repo_name).all()
130 131 all_repos = [r.repo_name for r in repos]
131 132
132 133 #get the repositories that should be invalidated
133 134 invalidation_list = [str(x.cache_key) for x in \
134 135 self.sa.query(CacheInvalidation.cache_key)\
135 136 .filter(CacheInvalidation.cache_active == False)\
136 137 .all()]
137 138 for r_name in all_repos:
138 139 r_dbr = self.get(r_name, invalidation_list)
139 140 if r_dbr is not None:
140 141 repo, dbrepo = r_dbr
141 142
142 143 if repo is None or dbrepo is None:
143 144 log.error('Repository "%s" looks somehow corrupted '
144 145 'fs-repo:%s,db-repo:%s both values should be '
145 146 'present', r_name, repo, dbrepo)
146 147 continue
147 148 last_change = repo.last_change
148 149 tip = h.get_changeset_safe(repo, 'tip')
149 150
150 151 tmp_d = {}
151 152 tmp_d['name'] = dbrepo.repo_name
152 153 tmp_d['name_sort'] = tmp_d['name'].lower()
153 154 tmp_d['description'] = dbrepo.description
154 155 tmp_d['description_sort'] = tmp_d['description']
155 156 tmp_d['last_change'] = last_change
156 157 tmp_d['last_change_sort'] = time.mktime(last_change \
157 158 .timetuple())
158 159 tmp_d['tip'] = tip.raw_id
159 160 tmp_d['tip_sort'] = tip.revision
160 161 tmp_d['rev'] = tip.revision
161 162 tmp_d['contact'] = dbrepo.user.full_contact
162 163 tmp_d['contact_sort'] = tmp_d['contact']
163 164 tmp_d['owner_sort'] = tmp_d['contact']
164 165 tmp_d['repo_archives'] = list(repo._get_archives())
165 166 tmp_d['last_msg'] = tip.message
166 167 tmp_d['repo'] = repo
167 168 tmp_d['dbrepo'] = dbrepo.get_dict()
168 169 tmp_d['dbrepo_fork'] = dbrepo.fork.get_dict() if dbrepo.fork \
169 170 else {}
170 171 yield tmp_d
171 172
172 173 def get(self, repo_name, invalidation_list=None, retval='all'):
173 174 """Returns a tuple of Repository,DbRepository,
174 175 Get's repository from given name, creates BackendInstance and
175 176 propagates it's data from database with all additional information
176 177
177 178 :param repo_name:
178 179 :param invalidation_list: if a invalidation list is given the get
179 180 method should not manually check if this repository needs
180 181 invalidation and just invalidate the repositories in list
181 182 :param retval: string specifing what to return one of 'repo','dbrepo',
182 183 'all'if repo or dbrepo is given it'll just lazy load chosen type
183 184 and return None as the second
184 185 """
185 186 if not HasRepoPermissionAny('repository.read', 'repository.write',
186 187 'repository.admin')(repo_name, 'get repo check'):
187 188 return
188 189
189 190 #======================================================================
190 191 # CACHE FUNCTION
191 192 #======================================================================
192 193 @cache_region('long_term')
193 194 def _get_repo(repo_name):
194 195
195 196 repo_path = os.path.join(self.repos_path, repo_name)
196 197
197 198 try:
198 199 alias = get_scm(repo_path)[0]
199 200 log.debug('Creating instance of %s repository', alias)
200 201 backend = get_backend(alias)
201 202 except VCSError:
202 203 log.error(traceback.format_exc())
203 204 log.error('Perhaps this repository is in db and not in '
204 205 'filesystem run rescan repositories with '
205 206 '"destroy old data " option from admin panel')
206 207 return
207 208
208 209 if alias == 'hg':
209 210 repo = backend(repo_path, create=False, baseui=make_ui('db'))
210 211 #skip hidden web repository
211 212 if repo._get_hidden():
212 213 return
213 214 else:
214 215 repo = backend(repo_path, create=False)
215 216
216 217 return repo
217 218
218 219 pre_invalidate = True
219 220 dbinvalidate = False
220 221
221 222 if invalidation_list is not None:
222 223 pre_invalidate = repo_name in invalidation_list
223 224
224 225 if pre_invalidate:
225 226 #this returns object to invalidate
226 227 invalidate = self._should_invalidate(repo_name)
227 228 if invalidate:
228 229 log.info('invalidating cache for repository %s', repo_name)
229 230 region_invalidate(_get_repo, None, repo_name)
230 231 self._mark_invalidated(invalidate)
231 232 dbinvalidate = True
232 233
233 234 r, dbr = None, None
234 235 if retval == 'repo' or 'all':
235 236 r = _get_repo(repo_name)
236 237 if retval == 'dbrepo' or 'all':
237 238 dbr = RepoModel().get_full(repo_name, cache=True,
238 239 invalidate=dbinvalidate)
239 240
240 241 return r, dbr
241 242
242 243 def mark_for_invalidation(self, repo_name):
243 244 """Puts cache invalidation task into db for
244 245 further global cache invalidation
245 246
246 247 :param repo_name: this repo that should invalidation take place
247 248 """
248 249
249 250 log.debug('marking %s for invalidation', repo_name)
250 251 cache = self.sa.query(CacheInvalidation)\
251 252 .filter(CacheInvalidation.cache_key == repo_name).scalar()
252 253
253 254 if cache:
254 255 #mark this cache as inactive
255 256 cache.cache_active = False
256 257 else:
257 258 log.debug('cache key not found in invalidation db -> creating one')
258 259 cache = CacheInvalidation(repo_name)
259 260
260 261 try:
261 262 self.sa.add(cache)
262 263 self.sa.commit()
263 264 except (DatabaseError,):
264 265 log.error(traceback.format_exc())
265 266 self.sa.rollback()
266 267
267 268 def toggle_following_repo(self, follow_repo_id, user_id):
268 269
269 270 f = self.sa.query(UserFollowing)\
270 271 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
271 272 .filter(UserFollowing.user_id == user_id).scalar()
272 273
273 274 if f is not None:
274 275
275 276 try:
276 277 self.sa.delete(f)
277 278 self.sa.commit()
278 279 action_logger(UserTemp(user_id),
279 280 'stopped_following_repo',
280 281 RepoTemp(follow_repo_id))
281 282 return
282 283 except:
283 284 log.error(traceback.format_exc())
284 285 self.sa.rollback()
285 286 raise
286 287
287 288 try:
288 289 f = UserFollowing()
289 290 f.user_id = user_id
290 291 f.follows_repo_id = follow_repo_id
291 292 self.sa.add(f)
292 293 self.sa.commit()
293 294 action_logger(UserTemp(user_id),
294 295 'started_following_repo',
295 296 RepoTemp(follow_repo_id))
296 297 except:
297 298 log.error(traceback.format_exc())
298 299 self.sa.rollback()
299 300 raise
300 301
301 302 def toggle_following_user(self, follow_user_id, user_id):
302 303 f = self.sa.query(UserFollowing)\
303 304 .filter(UserFollowing.follows_user_id == follow_user_id)\
304 305 .filter(UserFollowing.user_id == user_id).scalar()
305 306
306 307 if f is not None:
307 308 try:
308 309 self.sa.delete(f)
309 310 self.sa.commit()
310 311 return
311 312 except:
312 313 log.error(traceback.format_exc())
313 314 self.sa.rollback()
314 315 raise
315 316
316 317 try:
317 318 f = UserFollowing()
318 319 f.user_id = user_id
319 320 f.follows_user_id = follow_user_id
320 321 self.sa.add(f)
321 322 self.sa.commit()
322 323 except:
323 324 log.error(traceback.format_exc())
324 325 self.sa.rollback()
325 326 raise
326 327
327 328 def is_following_repo(self, repo_name, user_id, cache=False):
328 329 r = self.sa.query(Repository)\
329 330 .filter(Repository.repo_name == repo_name).scalar()
330 331
331 332 f = self.sa.query(UserFollowing)\
332 333 .filter(UserFollowing.follows_repository == r)\
333 334 .filter(UserFollowing.user_id == user_id).scalar()
334 335
335 336 return f is not None
336 337
337 338 def is_following_user(self, username, user_id, cache=False):
338 339 u = UserModel(self.sa).get_by_username(username)
339 340
340 341 f = self.sa.query(UserFollowing)\
341 342 .filter(UserFollowing.follows_user == u)\
342 343 .filter(UserFollowing.user_id == user_id).scalar()
343 344
344 345 return f is not None
345 346
346 347 def get_followers(self, repo_id):
347 348 if not isinstance(repo_id, int):
348 349 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
349 350
350 351 return self.sa.query(UserFollowing)\
351 352 .filter(UserFollowing.follows_repo_id == repo_id).count()
352 353
353 354 def get_forks(self, repo_id):
354 355 if not isinstance(repo_id, int):
355 356 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
356 357
357 358 return self.sa.query(Repository)\
358 359 .filter(Repository.fork_id == repo_id).count()
359 360
360 361 def pull_changes(self, repo_name, username):
361 362 repo, dbrepo = self.get(repo_name, retval='all')
362 363
363 364 try:
364 365 extras = {'ip': '',
365 366 'username': username,
366 367 'action': 'push_remote',
367 368 'repository': repo_name}
368 369
369 370 #inject ui extra param to log this action via push logger
370 371 for k, v in extras.items():
371 372 repo._repo.ui.setconfig('rhodecode_extras', k, v)
372 373
373 374 repo.pull(dbrepo.clone_uri)
374 375 self.mark_for_invalidation(repo_name)
375 376 except:
376 377 log.error(traceback.format_exc())
377 378 raise
378 379
380
381 def commit_change(self, repo, repo_name, cs, author, message, content,
382 f_path):
383
384 if repo.alias == 'hg':
385 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
386 elif repo.alias == 'git':
387 from vcs.backends.git import GitInMemoryChangeset as IMC
388
389 # decoding here will force that we have proper encoded values
390 # in any other case this will throw exceptions and deny commit
391 content = content.encode('utf8')
392 message = message.encode('utf8')
393 path = f_path.encode('utf8')
394 author = author.encode('utf8')
395 m = IMC(repo)
396 m.change(FileNode(path, content))
397 m.commit(message=message,
398 author=author,
399 parents=[cs], branch=cs.branch)
400
401 self.mark_for_invalidation(repo_name)
402
403
379 404 def get_unread_journal(self):
380 405 return self.sa.query(UserLog).count()
381 406
382 407 def _should_invalidate(self, repo_name):
383 408 """Looks up database for invalidation signals for this repo_name
384 409
385 410 :param repo_name:
386 411 """
387 412
388 413 ret = self.sa.query(CacheInvalidation)\
389 414 .filter(CacheInvalidation.cache_key == repo_name)\
390 415 .filter(CacheInvalidation.cache_active == False)\
391 416 .scalar()
392 417
393 418 return ret
394 419
395 420 def _mark_invalidated(self, cache_key):
396 421 """ Marks all occurrences of cache to invalidation as already
397 422 invalidated
398 423
399 424 :param cache_key:
400 425 """
401 426
402 427 if cache_key:
403 428 log.debug('marking %s as already invalidated', cache_key)
404 429 try:
405 430 cache_key.cache_active = True
406 431 self.sa.add(cache_key)
407 432 self.sa.commit()
408 433 except (DatabaseError,):
409 434 log.error(traceback.format_exc())
410 435 self.sa.rollback()
General Comments 0
You need to be logged in to leave comments. Login now