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