##// END OF EJS Templates
logged local commit with special action via action_logger,
marcink -
r1312:70a5a9a5 beta
parent child Browse files
Show More
@@ -1,409 +1,410 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 c.cs = self.__get_cs_or_redirect(revision, repo_name)
221 c.cs = self.__get_cs_or_redirect(revision, repo_name)
222 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)
223
223
224 c.file_history = self._get_node_history(c.cs, f_path)
224 c.file_history = self._get_node_history(c.cs, f_path)
225 c.f_path = f_path
225 c.f_path = f_path
226
226
227 if r_post:
227 if r_post:
228
228
229 old_content = c.file.content
229 old_content = c.file.content
230 sl = old_content.splitlines(1)
230 sl = old_content.splitlines(1)
231 first_line = sl[0] if sl else ''
231 first_line = sl[0] if sl else ''
232 # modes: 0 - Unix, 1 - Mac, 2 - DOS
232 # modes: 0 - Unix, 1 - Mac, 2 - DOS
233 mode = detect_mode(first_line, 0)
233 mode = detect_mode(first_line, 0)
234 content = convert_line_endings(r_post.get('content'), mode)
234 content = convert_line_endings(r_post.get('content'), mode)
235
235
236 message = r_post.get('message') or (_('Edited %s via RhodeCode')
236 message = r_post.get('message') or (_('Edited %s via RhodeCode')
237 % (f_path))
237 % (f_path))
238 author = self.rhodecode_user.full_contact
238 author = self.rhodecode_user.full_contact
239
239
240 if content == old_content:
240 if content == old_content:
241 h.flash(_('No changes'),
241 h.flash(_('No changes'),
242 category='warning')
242 category='warning')
243 return redirect(url('changeset_home', repo_name=c.repo_name,
243 return redirect(url('changeset_home', repo_name=c.repo_name,
244 revision='tip'))
244 revision='tip'))
245
245
246 try:
246 try:
247 self.scm_model.commit_change(repo=c.rhodecode_repo,
247 self.scm_model.commit_change(repo=c.rhodecode_repo,
248 repo_name=repo_name, cs=c.cs,
248 repo_name=repo_name, cs=c.cs,
249 user=self.rhodecode_user,
249 author=author, message=message,
250 author=author, message=message,
250 content=content, f_path=f_path)
251 content=content, f_path=f_path)
251 h.flash(_('Successfully committed to %s' % f_path),
252 h.flash(_('Successfully committed to %s' % f_path),
252 category='success')
253 category='success')
253
254
254 except Exception:
255 except Exception:
255 log.error(traceback.format_exc())
256 log.error(traceback.format_exc())
256 h.flash(_('Error occurred during commit'), category='error')
257 h.flash(_('Error occurred during commit'), category='error')
257 return redirect(url('changeset_home',
258 return redirect(url('changeset_home',
258 repo_name=c.repo_name, revision='tip'))
259 repo_name=c.repo_name, revision='tip'))
259
260
260 return render('files/files_edit.html')
261 return render('files/files_edit.html')
261
262
262 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
263 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
263 'repository.admin')
264 'repository.admin')
264 def archivefile(self, repo_name, fname):
265 def archivefile(self, repo_name, fname):
265
266
266 fileformat = None
267 fileformat = None
267 revision = None
268 revision = None
268 ext = None
269 ext = None
269
270
270 for a_type, ext_data in ARCHIVE_SPECS.items():
271 for a_type, ext_data in ARCHIVE_SPECS.items():
271 archive_spec = fname.split(ext_data[1])
272 archive_spec = fname.split(ext_data[1])
272 if len(archive_spec) == 2 and archive_spec[1] == '':
273 if len(archive_spec) == 2 and archive_spec[1] == '':
273 fileformat = a_type or ext_data[1]
274 fileformat = a_type or ext_data[1]
274 revision = archive_spec[0]
275 revision = archive_spec[0]
275 ext = ext_data[1]
276 ext = ext_data[1]
276
277
277 try:
278 try:
278 dbrepo = RepoModel().get_by_repo_name(repo_name)
279 dbrepo = RepoModel().get_by_repo_name(repo_name)
279 if dbrepo.enable_downloads is False:
280 if dbrepo.enable_downloads is False:
280 return _('downloads disabled')
281 return _('downloads disabled')
281
282
282 cs = c.rhodecode_repo.get_changeset(revision)
283 cs = c.rhodecode_repo.get_changeset(revision)
283 content_type = ARCHIVE_SPECS[fileformat][0]
284 content_type = ARCHIVE_SPECS[fileformat][0]
284 except ChangesetDoesNotExistError:
285 except ChangesetDoesNotExistError:
285 return _('Unknown revision %s') % revision
286 return _('Unknown revision %s') % revision
286 except EmptyRepositoryError:
287 except EmptyRepositoryError:
287 return _('Empty repository')
288 return _('Empty repository')
288 except (ImproperArchiveTypeError, KeyError):
289 except (ImproperArchiveTypeError, KeyError):
289 return _('Unknown archive type')
290 return _('Unknown archive type')
290
291
291 response.content_type = content_type
292 response.content_type = content_type
292 response.content_disposition = 'attachment; filename=%s-%s%s' \
293 response.content_disposition = 'attachment; filename=%s-%s%s' \
293 % (repo_name, revision, ext)
294 % (repo_name, revision, ext)
294
295
295 import tempfile
296 import tempfile
296 archive = tempfile.mkstemp()[1]
297 archive = tempfile.mkstemp()[1]
297 t = open(archive, 'wb')
298 t = open(archive, 'wb')
298 cs.fill_archive(stream=t, kind=fileformat)
299 cs.fill_archive(stream=t, kind=fileformat)
299
300
300 def get_chunked_archive(archive):
301 def get_chunked_archive(archive):
301 stream = open(archive, 'rb')
302 stream = open(archive, 'rb')
302 while True:
303 while True:
303 data = stream.read(4096)
304 data = stream.read(4096)
304 if not data:
305 if not data:
305 os.remove(archive)
306 os.remove(archive)
306 break
307 break
307 yield data
308 yield data
308
309
309 return get_chunked_archive(archive)
310 return get_chunked_archive(archive)
310
311
311 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
312 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
312 'repository.admin')
313 'repository.admin')
313 def diff(self, repo_name, f_path):
314 def diff(self, repo_name, f_path):
314 diff1 = request.GET.get('diff1')
315 diff1 = request.GET.get('diff1')
315 diff2 = request.GET.get('diff2')
316 diff2 = request.GET.get('diff2')
316 c.action = request.GET.get('diff')
317 c.action = request.GET.get('diff')
317 c.no_changes = diff1 == diff2
318 c.no_changes = diff1 == diff2
318 c.f_path = f_path
319 c.f_path = f_path
319 c.big_diff = False
320 c.big_diff = False
320
321
321 try:
322 try:
322 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
323 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
323 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
324 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
324 node1 = c.changeset_1.get_node(f_path)
325 node1 = c.changeset_1.get_node(f_path)
325 else:
326 else:
326 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
327 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
327 node1 = FileNode('.', '', changeset=c.changeset_1)
328 node1 = FileNode('.', '', changeset=c.changeset_1)
328
329
329 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
330 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
330 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
331 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
331 node2 = c.changeset_2.get_node(f_path)
332 node2 = c.changeset_2.get_node(f_path)
332 else:
333 else:
333 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
334 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
334 node2 = FileNode('.', '', changeset=c.changeset_2)
335 node2 = FileNode('.', '', changeset=c.changeset_2)
335 except RepositoryError:
336 except RepositoryError:
336 return redirect(url('files_home',
337 return redirect(url('files_home',
337 repo_name=c.repo_name, f_path=f_path))
338 repo_name=c.repo_name, f_path=f_path))
338
339
339 if c.action == 'download':
340 if c.action == 'download':
340 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
341 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
341 format='gitdiff')
342 format='gitdiff')
342
343
343 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
344 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
344 response.content_type = 'text/plain'
345 response.content_type = 'text/plain'
345 response.content_disposition = 'attachment; filename=%s' \
346 response.content_disposition = 'attachment; filename=%s' \
346 % diff_name
347 % diff_name
347 return diff.raw_diff()
348 return diff.raw_diff()
348
349
349 elif c.action == 'raw':
350 elif c.action == 'raw':
350 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
351 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
351 format='gitdiff')
352 format='gitdiff')
352 response.content_type = 'text/plain'
353 response.content_type = 'text/plain'
353 return diff.raw_diff()
354 return diff.raw_diff()
354
355
355 elif c.action == 'diff':
356 elif c.action == 'diff':
356 if node1.is_binary or node2.is_binary:
357 if node1.is_binary or node2.is_binary:
357 c.cur_diff = _('Binary file')
358 c.cur_diff = _('Binary file')
358 elif node1.size > self.cut_off_limit or \
359 elif node1.size > self.cut_off_limit or \
359 node2.size > self.cut_off_limit:
360 node2.size > self.cut_off_limit:
360 c.cur_diff = ''
361 c.cur_diff = ''
361 c.big_diff = True
362 c.big_diff = True
362 else:
363 else:
363 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
364 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
364 format='gitdiff')
365 format='gitdiff')
365 c.cur_diff = diff.as_html()
366 c.cur_diff = diff.as_html()
366 else:
367 else:
367
368
368 #default option
369 #default option
369 if node1.is_binary or node2.is_binary:
370 if node1.is_binary or node2.is_binary:
370 c.cur_diff = _('Binary file')
371 c.cur_diff = _('Binary file')
371 elif node1.size > self.cut_off_limit or \
372 elif node1.size > self.cut_off_limit or \
372 node2.size > self.cut_off_limit:
373 node2.size > self.cut_off_limit:
373 c.cur_diff = ''
374 c.cur_diff = ''
374 c.big_diff = True
375 c.big_diff = True
375
376
376 else:
377 else:
377 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
378 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
378 format='gitdiff')
379 format='gitdiff')
379 c.cur_diff = diff.as_html()
380 c.cur_diff = diff.as_html()
380
381
381 if not c.cur_diff and not c.big_diff:
382 if not c.cur_diff and not c.big_diff:
382 c.no_changes = True
383 c.no_changes = True
383 return render('files/file_diff.html')
384 return render('files/file_diff.html')
384
385
385 def _get_node_history(self, cs, f_path):
386 def _get_node_history(self, cs, f_path):
386 changesets = cs.get_file_history(f_path)
387 changesets = cs.get_file_history(f_path)
387 hist_l = []
388 hist_l = []
388
389
389 changesets_group = ([], _("Changesets"))
390 changesets_group = ([], _("Changesets"))
390 branches_group = ([], _("Branches"))
391 branches_group = ([], _("Branches"))
391 tags_group = ([], _("Tags"))
392 tags_group = ([], _("Tags"))
392
393
393 for chs in changesets:
394 for chs in changesets:
394 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
395 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
395 changesets_group[0].append((chs.raw_id, n_desc,))
396 changesets_group[0].append((chs.raw_id, n_desc,))
396
397
397 hist_l.append(changesets_group)
398 hist_l.append(changesets_group)
398
399
399 for name, chs in c.rhodecode_repo.branches.items():
400 for name, chs in c.rhodecode_repo.branches.items():
400 #chs = chs.split(':')[-1]
401 #chs = chs.split(':')[-1]
401 branches_group[0].append((chs, name),)
402 branches_group[0].append((chs, name),)
402 hist_l.append(branches_group)
403 hist_l.append(branches_group)
403
404
404 for name, chs in c.rhodecode_repo.tags.items():
405 for name, chs in c.rhodecode_repo.tags.items():
405 #chs = chs.split(':')[-1]
406 #chs = chs.split(':')[-1]
406 tags_group[0].append((chs, name),)
407 tags_group[0].append((chs, name),)
407 hist_l.append(tags_group)
408 hist_l.append(tags_group)
408
409
409 return hist_l
410 return hist_l
@@ -1,691 +1,693 b''
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10
10
11 from datetime import datetime
11 from datetime import datetime
12 from pygments.formatters import HtmlFormatter
12 from pygments.formatters import HtmlFormatter
13 from pygments import highlight as code_highlight
13 from pygments import highlight as code_highlight
14 from pylons import url, request, config
14 from pylons import url, request, config
15 from pylons.i18n.translation import _, ungettext
15 from pylons.i18n.translation import _, ungettext
16
16
17 from webhelpers.html import literal, HTML, escape
17 from webhelpers.html import literal, HTML, escape
18 from webhelpers.html.tools import *
18 from webhelpers.html.tools import *
19 from webhelpers.html.builder import make_tag
19 from webhelpers.html.builder import make_tag
20 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
20 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
21 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
21 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
22 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
22 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
23 password, textarea, title, ul, xml_declaration, radio
23 password, textarea, title, ul, xml_declaration, radio
24 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
24 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
25 mail_to, strip_links, strip_tags, tag_re
25 mail_to, strip_links, strip_tags, tag_re
26 from webhelpers.number import format_byte_size, format_bit_size
26 from webhelpers.number import format_byte_size, format_bit_size
27 from webhelpers.pylonslib import Flash as _Flash
27 from webhelpers.pylonslib import Flash as _Flash
28 from webhelpers.pylonslib.secure_form import secure_form
28 from webhelpers.pylonslib.secure_form import secure_form
29 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
29 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
30 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
30 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
31 replace_whitespace, urlify, truncate, wrap_paragraphs
31 replace_whitespace, urlify, truncate, wrap_paragraphs
32 from webhelpers.date import time_ago_in_words
32 from webhelpers.date import time_ago_in_words
33 from webhelpers.paginate import Page
33 from webhelpers.paginate import Page
34 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
34 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
35 convert_boolean_attrs, NotGiven
35 convert_boolean_attrs, NotGiven
36
36
37 from vcs.utils.annotate import annotate_highlight
37 from vcs.utils.annotate import annotate_highlight
38 from rhodecode.lib.utils import repo_name_slug
38 from rhodecode.lib.utils import repo_name_slug
39 from rhodecode.lib import str2bool, safe_unicode
39 from rhodecode.lib import str2bool, safe_unicode
40
40
41 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
41 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
42 """
42 """
43 Reset button
43 Reset button
44 """
44 """
45 _set_input_attrs(attrs, type, name, value)
45 _set_input_attrs(attrs, type, name, value)
46 _set_id_attr(attrs, id, name)
46 _set_id_attr(attrs, id, name)
47 convert_boolean_attrs(attrs, ["disabled"])
47 convert_boolean_attrs(attrs, ["disabled"])
48 return HTML.input(**attrs)
48 return HTML.input(**attrs)
49
49
50 reset = _reset
50 reset = _reset
51
51
52
52
53 def get_token():
53 def get_token():
54 """Return the current authentication token, creating one if one doesn't
54 """Return the current authentication token, creating one if one doesn't
55 already exist.
55 already exist.
56 """
56 """
57 token_key = "_authentication_token"
57 token_key = "_authentication_token"
58 from pylons import session
58 from pylons import session
59 if not token_key in session:
59 if not token_key in session:
60 try:
60 try:
61 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
61 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
62 except AttributeError: # Python < 2.4
62 except AttributeError: # Python < 2.4
63 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
63 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
64 session[token_key] = token
64 session[token_key] = token
65 if hasattr(session, 'save'):
65 if hasattr(session, 'save'):
66 session.save()
66 session.save()
67 return session[token_key]
67 return session[token_key]
68
68
69 class _GetError(object):
69 class _GetError(object):
70 """Get error from form_errors, and represent it as span wrapped error
70 """Get error from form_errors, and represent it as span wrapped error
71 message
71 message
72
72
73 :param field_name: field to fetch errors for
73 :param field_name: field to fetch errors for
74 :param form_errors: form errors dict
74 :param form_errors: form errors dict
75 """
75 """
76
76
77 def __call__(self, field_name, form_errors):
77 def __call__(self, field_name, form_errors):
78 tmpl = """<span class="error_msg">%s</span>"""
78 tmpl = """<span class="error_msg">%s</span>"""
79 if form_errors and form_errors.has_key(field_name):
79 if form_errors and form_errors.has_key(field_name):
80 return literal(tmpl % form_errors.get(field_name))
80 return literal(tmpl % form_errors.get(field_name))
81
81
82 get_error = _GetError()
82 get_error = _GetError()
83
83
84 class _ToolTip(object):
84 class _ToolTip(object):
85
85
86 def __call__(self, tooltip_title, trim_at=50):
86 def __call__(self, tooltip_title, trim_at=50):
87 """Special function just to wrap our text into nice formatted
87 """Special function just to wrap our text into nice formatted
88 autowrapped text
88 autowrapped text
89
89
90 :param tooltip_title:
90 :param tooltip_title:
91 """
91 """
92
92
93 return wrap_paragraphs(escape(tooltip_title), trim_at)\
93 return wrap_paragraphs(escape(tooltip_title), trim_at)\
94 .replace('\n', '<br/>')
94 .replace('\n', '<br/>')
95
95
96 def activate(self):
96 def activate(self):
97 """Adds tooltip mechanism to the given Html all tooltips have to have
97 """Adds tooltip mechanism to the given Html all tooltips have to have
98 set class `tooltip` and set attribute `tooltip_title`.
98 set class `tooltip` and set attribute `tooltip_title`.
99 Then a tooltip will be generated based on that. All with yui js tooltip
99 Then a tooltip will be generated based on that. All with yui js tooltip
100 """
100 """
101
101
102 js = '''
102 js = '''
103 YAHOO.util.Event.onDOMReady(function(){
103 YAHOO.util.Event.onDOMReady(function(){
104 function toolTipsId(){
104 function toolTipsId(){
105 var ids = [];
105 var ids = [];
106 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
106 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
107
107
108 for (var i = 0; i < tts.length; i++) {
108 for (var i = 0; i < tts.length; i++) {
109 //if element doesn't not have and id autogenerate one for tooltip
109 //if element doesn't not have and id autogenerate one for tooltip
110
110
111 if (!tts[i].id){
111 if (!tts[i].id){
112 tts[i].id='tt'+i*100;
112 tts[i].id='tt'+i*100;
113 }
113 }
114 ids.push(tts[i].id);
114 ids.push(tts[i].id);
115 }
115 }
116 return ids
116 return ids
117 };
117 };
118 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
118 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
119 context: [[toolTipsId()],"tl","bl",null,[0,5]],
119 context: [[toolTipsId()],"tl","bl",null,[0,5]],
120 monitorresize:false,
120 monitorresize:false,
121 xyoffset :[0,0],
121 xyoffset :[0,0],
122 autodismissdelay:300000,
122 autodismissdelay:300000,
123 hidedelay:5,
123 hidedelay:5,
124 showdelay:20,
124 showdelay:20,
125 });
125 });
126
126
127 });
127 });
128 '''
128 '''
129 return literal(js)
129 return literal(js)
130
130
131 tooltip = _ToolTip()
131 tooltip = _ToolTip()
132
132
133 class _FilesBreadCrumbs(object):
133 class _FilesBreadCrumbs(object):
134
134
135 def __call__(self, repo_name, rev, paths):
135 def __call__(self, repo_name, rev, paths):
136 if isinstance(paths, str):
136 if isinstance(paths, str):
137 paths = safe_unicode(paths)
137 paths = safe_unicode(paths)
138 url_l = [link_to(repo_name, url('files_home',
138 url_l = [link_to(repo_name, url('files_home',
139 repo_name=repo_name,
139 repo_name=repo_name,
140 revision=rev, f_path=''))]
140 revision=rev, f_path=''))]
141 paths_l = paths.split('/')
141 paths_l = paths.split('/')
142 for cnt, p in enumerate(paths_l):
142 for cnt, p in enumerate(paths_l):
143 if p != '':
143 if p != '':
144 url_l.append(link_to(p, url('files_home',
144 url_l.append(link_to(p, url('files_home',
145 repo_name=repo_name,
145 repo_name=repo_name,
146 revision=rev,
146 revision=rev,
147 f_path='/'.join(paths_l[:cnt + 1]))))
147 f_path='/'.join(paths_l[:cnt + 1]))))
148
148
149 return literal('/'.join(url_l))
149 return literal('/'.join(url_l))
150
150
151 files_breadcrumbs = _FilesBreadCrumbs()
151 files_breadcrumbs = _FilesBreadCrumbs()
152
152
153 class CodeHtmlFormatter(HtmlFormatter):
153 class CodeHtmlFormatter(HtmlFormatter):
154 """My code Html Formatter for source codes
154 """My code Html Formatter for source codes
155 """
155 """
156
156
157 def wrap(self, source, outfile):
157 def wrap(self, source, outfile):
158 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
158 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
159
159
160 def _wrap_code(self, source):
160 def _wrap_code(self, source):
161 for cnt, it in enumerate(source):
161 for cnt, it in enumerate(source):
162 i, t = it
162 i, t = it
163 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
163 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
164 yield i, t
164 yield i, t
165
165
166 def _wrap_tablelinenos(self, inner):
166 def _wrap_tablelinenos(self, inner):
167 dummyoutfile = StringIO.StringIO()
167 dummyoutfile = StringIO.StringIO()
168 lncount = 0
168 lncount = 0
169 for t, line in inner:
169 for t, line in inner:
170 if t:
170 if t:
171 lncount += 1
171 lncount += 1
172 dummyoutfile.write(line)
172 dummyoutfile.write(line)
173
173
174 fl = self.linenostart
174 fl = self.linenostart
175 mw = len(str(lncount + fl - 1))
175 mw = len(str(lncount + fl - 1))
176 sp = self.linenospecial
176 sp = self.linenospecial
177 st = self.linenostep
177 st = self.linenostep
178 la = self.lineanchors
178 la = self.lineanchors
179 aln = self.anchorlinenos
179 aln = self.anchorlinenos
180 nocls = self.noclasses
180 nocls = self.noclasses
181 if sp:
181 if sp:
182 lines = []
182 lines = []
183
183
184 for i in range(fl, fl + lncount):
184 for i in range(fl, fl + lncount):
185 if i % st == 0:
185 if i % st == 0:
186 if i % sp == 0:
186 if i % sp == 0:
187 if aln:
187 if aln:
188 lines.append('<a href="#%s%d" class="special">%*d</a>' %
188 lines.append('<a href="#%s%d" class="special">%*d</a>' %
189 (la, i, mw, i))
189 (la, i, mw, i))
190 else:
190 else:
191 lines.append('<span class="special">%*d</span>' % (mw, i))
191 lines.append('<span class="special">%*d</span>' % (mw, i))
192 else:
192 else:
193 if aln:
193 if aln:
194 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
194 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
195 else:
195 else:
196 lines.append('%*d' % (mw, i))
196 lines.append('%*d' % (mw, i))
197 else:
197 else:
198 lines.append('')
198 lines.append('')
199 ls = '\n'.join(lines)
199 ls = '\n'.join(lines)
200 else:
200 else:
201 lines = []
201 lines = []
202 for i in range(fl, fl + lncount):
202 for i in range(fl, fl + lncount):
203 if i % st == 0:
203 if i % st == 0:
204 if aln:
204 if aln:
205 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
205 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
206 else:
206 else:
207 lines.append('%*d' % (mw, i))
207 lines.append('%*d' % (mw, i))
208 else:
208 else:
209 lines.append('')
209 lines.append('')
210 ls = '\n'.join(lines)
210 ls = '\n'.join(lines)
211
211
212 # in case you wonder about the seemingly redundant <div> here: since the
212 # in case you wonder about the seemingly redundant <div> here: since the
213 # content in the other cell also is wrapped in a div, some browsers in
213 # content in the other cell also is wrapped in a div, some browsers in
214 # some configurations seem to mess up the formatting...
214 # some configurations seem to mess up the formatting...
215 if nocls:
215 if nocls:
216 yield 0, ('<table class="%stable">' % self.cssclass +
216 yield 0, ('<table class="%stable">' % self.cssclass +
217 '<tr><td><div class="linenodiv" '
217 '<tr><td><div class="linenodiv" '
218 'style="background-color: #f0f0f0; padding-right: 10px">'
218 'style="background-color: #f0f0f0; padding-right: 10px">'
219 '<pre style="line-height: 125%">' +
219 '<pre style="line-height: 125%">' +
220 ls + '</pre></div></td><td class="code">')
220 ls + '</pre></div></td><td class="code">')
221 else:
221 else:
222 yield 0, ('<table class="%stable">' % self.cssclass +
222 yield 0, ('<table class="%stable">' % self.cssclass +
223 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
223 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
224 ls + '</pre></div></td><td class="code">')
224 ls + '</pre></div></td><td class="code">')
225 yield 0, dummyoutfile.getvalue()
225 yield 0, dummyoutfile.getvalue()
226 yield 0, '</td></tr></table>'
226 yield 0, '</td></tr></table>'
227
227
228
228
229 def pygmentize(filenode, **kwargs):
229 def pygmentize(filenode, **kwargs):
230 """pygmentize function using pygments
230 """pygmentize function using pygments
231
231
232 :param filenode:
232 :param filenode:
233 """
233 """
234
234
235 return literal(code_highlight(filenode.content,
235 return literal(code_highlight(filenode.content,
236 filenode.lexer, CodeHtmlFormatter(**kwargs)))
236 filenode.lexer, CodeHtmlFormatter(**kwargs)))
237
237
238 def pygmentize_annotation(repo_name, filenode, **kwargs):
238 def pygmentize_annotation(repo_name, filenode, **kwargs):
239 """pygmentize function for annotation
239 """pygmentize function for annotation
240
240
241 :param filenode:
241 :param filenode:
242 """
242 """
243
243
244 color_dict = {}
244 color_dict = {}
245 def gen_color(n=10000):
245 def gen_color(n=10000):
246 """generator for getting n of evenly distributed colors using
246 """generator for getting n of evenly distributed colors using
247 hsv color and golden ratio. It always return same order of colors
247 hsv color and golden ratio. It always return same order of colors
248
248
249 :returns: RGB tuple
249 :returns: RGB tuple
250 """
250 """
251 import colorsys
251 import colorsys
252 golden_ratio = 0.618033988749895
252 golden_ratio = 0.618033988749895
253 h = 0.22717784590367374
253 h = 0.22717784590367374
254
254
255 for c in xrange(n):
255 for c in xrange(n):
256 h += golden_ratio
256 h += golden_ratio
257 h %= 1
257 h %= 1
258 HSV_tuple = [h, 0.95, 0.95]
258 HSV_tuple = [h, 0.95, 0.95]
259 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
259 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
260 yield map(lambda x:str(int(x * 256)), RGB_tuple)
260 yield map(lambda x:str(int(x * 256)), RGB_tuple)
261
261
262 cgenerator = gen_color()
262 cgenerator = gen_color()
263
263
264 def get_color_string(cs):
264 def get_color_string(cs):
265 if color_dict.has_key(cs):
265 if color_dict.has_key(cs):
266 col = color_dict[cs]
266 col = color_dict[cs]
267 else:
267 else:
268 col = color_dict[cs] = cgenerator.next()
268 col = color_dict[cs] = cgenerator.next()
269 return "color: rgb(%s)! important;" % (', '.join(col))
269 return "color: rgb(%s)! important;" % (', '.join(col))
270
270
271 def url_func(repo_name):
271 def url_func(repo_name):
272 def _url_func(changeset):
272 def _url_func(changeset):
273 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
273 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
274 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
274 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
275
275
276 tooltip_html = tooltip_html % (changeset.author,
276 tooltip_html = tooltip_html % (changeset.author,
277 changeset.date,
277 changeset.date,
278 tooltip(changeset.message))
278 tooltip(changeset.message))
279 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
279 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
280 short_id(changeset.raw_id))
280 short_id(changeset.raw_id))
281 uri = link_to(
281 uri = link_to(
282 lnk_format,
282 lnk_format,
283 url('changeset_home', repo_name=repo_name,
283 url('changeset_home', repo_name=repo_name,
284 revision=changeset.raw_id),
284 revision=changeset.raw_id),
285 style=get_color_string(changeset.raw_id),
285 style=get_color_string(changeset.raw_id),
286 class_='tooltip',
286 class_='tooltip',
287 title=tooltip_html
287 title=tooltip_html
288 )
288 )
289
289
290 uri += '\n'
290 uri += '\n'
291 return uri
291 return uri
292 return _url_func
292 return _url_func
293
293
294 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
294 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
295
295
296 def get_changeset_safe(repo, rev):
296 def get_changeset_safe(repo, rev):
297 from vcs.backends.base import BaseRepository
297 from vcs.backends.base import BaseRepository
298 from vcs.exceptions import RepositoryError
298 from vcs.exceptions import RepositoryError
299 if not isinstance(repo, BaseRepository):
299 if not isinstance(repo, BaseRepository):
300 raise Exception('You must pass an Repository '
300 raise Exception('You must pass an Repository '
301 'object as first argument got %s', type(repo))
301 'object as first argument got %s', type(repo))
302
302
303 try:
303 try:
304 cs = repo.get_changeset(rev)
304 cs = repo.get_changeset(rev)
305 except RepositoryError:
305 except RepositoryError:
306 from rhodecode.lib.utils import EmptyChangeset
306 from rhodecode.lib.utils import EmptyChangeset
307 cs = EmptyChangeset()
307 cs = EmptyChangeset()
308 return cs
308 return cs
309
309
310
310
311 def is_following_repo(repo_name, user_id):
311 def is_following_repo(repo_name, user_id):
312 from rhodecode.model.scm import ScmModel
312 from rhodecode.model.scm import ScmModel
313 return ScmModel().is_following_repo(repo_name, user_id)
313 return ScmModel().is_following_repo(repo_name, user_id)
314
314
315 flash = _Flash()
315 flash = _Flash()
316
316
317
317
318 #==============================================================================
318 #==============================================================================
319 # MERCURIAL FILTERS available via h.
319 # MERCURIAL FILTERS available via h.
320 #==============================================================================
320 #==============================================================================
321 from mercurial import util
321 from mercurial import util
322 from mercurial.templatefilters import person as _person
322 from mercurial.templatefilters import person as _person
323
323
324 def _age(curdate):
324 def _age(curdate):
325 """turns a datetime into an age string."""
325 """turns a datetime into an age string."""
326
326
327 if not curdate:
327 if not curdate:
328 return ''
328 return ''
329
329
330 agescales = [("year", 3600 * 24 * 365),
330 agescales = [("year", 3600 * 24 * 365),
331 ("month", 3600 * 24 * 30),
331 ("month", 3600 * 24 * 30),
332 ("day", 3600 * 24),
332 ("day", 3600 * 24),
333 ("hour", 3600),
333 ("hour", 3600),
334 ("minute", 60),
334 ("minute", 60),
335 ("second", 1), ]
335 ("second", 1), ]
336
336
337 age = datetime.now() - curdate
337 age = datetime.now() - curdate
338 age_seconds = (age.days * agescales[2][1]) + age.seconds
338 age_seconds = (age.days * agescales[2][1]) + age.seconds
339 pos = 1
339 pos = 1
340 for scale in agescales:
340 for scale in agescales:
341 if scale[1] <= age_seconds:
341 if scale[1] <= age_seconds:
342 if pos == 6:pos = 5
342 if pos == 6:pos = 5
343 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
343 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
344 pos += 1
344 pos += 1
345
345
346 return _('just now')
346 return _('just now')
347
347
348 age = lambda x:_age(x)
348 age = lambda x:_age(x)
349 capitalize = lambda x: x.capitalize()
349 capitalize = lambda x: x.capitalize()
350 email = util.email
350 email = util.email
351 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
351 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
352 person = lambda x: _person(x)
352 person = lambda x: _person(x)
353 short_id = lambda x: x[:12]
353 short_id = lambda x: x[:12]
354
354
355
355
356 def bool2icon(value):
356 def bool2icon(value):
357 """Returns True/False values represented as small html image of true/false
357 """Returns True/False values represented as small html image of true/false
358 icons
358 icons
359
359
360 :param value: bool value
360 :param value: bool value
361 """
361 """
362
362
363 if value is True:
363 if value is True:
364 return HTML.tag('img', src=url("/images/icons/accept.png"),
364 return HTML.tag('img', src=url("/images/icons/accept.png"),
365 alt=_('True'))
365 alt=_('True'))
366
366
367 if value is False:
367 if value is False:
368 return HTML.tag('img', src=url("/images/icons/cancel.png"),
368 return HTML.tag('img', src=url("/images/icons/cancel.png"),
369 alt=_('False'))
369 alt=_('False'))
370
370
371 return value
371 return value
372
372
373
373
374 def action_parser(user_log, feed=False):
374 def action_parser(user_log, feed=False):
375 """This helper will action_map the specified string action into translated
375 """This helper will action_map the specified string action into translated
376 fancy names with icons and links
376 fancy names with icons and links
377
377
378 :param user_log: user log instance
378 :param user_log: user log instance
379 :param feed: use output for feeds (no html and fancy icons)
379 :param feed: use output for feeds (no html and fancy icons)
380 """
380 """
381
381
382 action = user_log.action
382 action = user_log.action
383 action_params = ' '
383 action_params = ' '
384
384
385 x = action.split(':')
385 x = action.split(':')
386
386
387 if len(x) > 1:
387 if len(x) > 1:
388 action, action_params = x
388 action, action_params = x
389
389
390 def get_cs_links():
390 def get_cs_links():
391 revs_limit = 5 #display this amount always
391 revs_limit = 5 #display this amount always
392 revs_top_limit = 50 #show upto this amount of changesets hidden
392 revs_top_limit = 50 #show upto this amount of changesets hidden
393 revs = action_params.split(',')
393 revs = action_params.split(',')
394 repo_name = user_log.repository.repo_name
394 repo_name = user_log.repository.repo_name
395
395
396 from rhodecode.model.scm import ScmModel
396 from rhodecode.model.scm import ScmModel
397 repo, dbrepo = ScmModel().get(repo_name, retval='repo',
397 repo, dbrepo = ScmModel().get(repo_name, retval='repo',
398 invalidation_list=[])
398 invalidation_list=[])
399
399
400 message = lambda rev: get_changeset_safe(repo, rev).message
400 message = lambda rev: get_changeset_safe(repo, rev).message
401
401
402 cs_links = " " + ', '.join ([link_to(rev,
402 cs_links = " " + ', '.join ([link_to(rev,
403 url('changeset_home',
403 url('changeset_home',
404 repo_name=repo_name,
404 repo_name=repo_name,
405 revision=rev), title=tooltip(message(rev)),
405 revision=rev), title=tooltip(message(rev)),
406 class_='tooltip') for rev in revs[:revs_limit] ])
406 class_='tooltip') for rev in revs[:revs_limit] ])
407
407
408 compare_view = (' <div class="compare_view tooltip" title="%s">'
408 compare_view = (' <div class="compare_view tooltip" title="%s">'
409 '<a href="%s">%s</a> '
409 '<a href="%s">%s</a> '
410 '</div>' % (_('Show all combined changesets %s->%s' \
410 '</div>' % (_('Show all combined changesets %s->%s' \
411 % (revs[0], revs[-1])),
411 % (revs[0], revs[-1])),
412 url('changeset_home', repo_name=repo_name,
412 url('changeset_home', repo_name=repo_name,
413 revision='%s...%s' % (revs[0], revs[-1])
413 revision='%s...%s' % (revs[0], revs[-1])
414 ),
414 ),
415 _('compare view'))
415 _('compare view'))
416 )
416 )
417
417
418 if len(revs) > revs_limit:
418 if len(revs) > revs_limit:
419 uniq_id = revs[0]
419 uniq_id = revs[0]
420 html_tmpl = ('<span> %s '
420 html_tmpl = ('<span> %s '
421 '<a class="show_more" id="_%s" href="#more">%s</a> '
421 '<a class="show_more" id="_%s" href="#more">%s</a> '
422 '%s</span>')
422 '%s</span>')
423 if not feed:
423 if not feed:
424 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
424 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
425 % (len(revs) - revs_limit),
425 % (len(revs) - revs_limit),
426 _('revisions'))
426 _('revisions'))
427
427
428 if not feed:
428 if not feed:
429 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
429 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
430 else:
430 else:
431 html_tmpl = '<span id="%s"> %s </span>'
431 html_tmpl = '<span id="%s"> %s </span>'
432
432
433 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
433 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
434 url('changeset_home',
434 url('changeset_home',
435 repo_name=repo_name, revision=rev),
435 repo_name=repo_name, revision=rev),
436 title=message(rev), class_='tooltip')
436 title=message(rev), class_='tooltip')
437 for rev in revs[revs_limit:revs_top_limit]]))
437 for rev in revs[revs_limit:revs_top_limit]]))
438 if len(revs) > 1:
438 if len(revs) > 1:
439 cs_links += compare_view
439 cs_links += compare_view
440 return cs_links
440 return cs_links
441
441
442 def get_fork_name():
442 def get_fork_name():
443 repo_name = action_params
443 repo_name = action_params
444 return _('fork name ') + str(link_to(action_params, url('summary_home',
444 return _('fork name ') + str(link_to(action_params, url('summary_home',
445 repo_name=repo_name,)))
445 repo_name=repo_name,)))
446
446
447 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
447 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
448 'user_created_repo':(_('[created] repository'), None),
448 'user_created_repo':(_('[created] repository'), None),
449 'user_forked_repo':(_('[forked] repository'), get_fork_name),
449 'user_forked_repo':(_('[forked] repository'), get_fork_name),
450 'user_updated_repo':(_('[updated] repository'), None),
450 'user_updated_repo':(_('[updated] repository'), None),
451 'admin_deleted_repo':(_('[delete] repository'), None),
451 'admin_deleted_repo':(_('[delete] repository'), None),
452 'admin_created_repo':(_('[created] repository'), None),
452 'admin_created_repo':(_('[created] repository'), None),
453 'admin_forked_repo':(_('[forked] repository'), None),
453 'admin_forked_repo':(_('[forked] repository'), None),
454 'admin_updated_repo':(_('[updated] repository'), None),
454 'admin_updated_repo':(_('[updated] repository'), None),
455 'push':(_('[pushed] into'), get_cs_links),
455 'push':(_('[pushed] into'), get_cs_links),
456 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
456 'push_remote':(_('[pulled from remote] into'), get_cs_links),
457 'push_remote':(_('[pulled from remote] into'), get_cs_links),
457 'pull':(_('[pulled] from'), None),
458 'pull':(_('[pulled] from'), None),
458 'started_following_repo':(_('[started following] repository'), None),
459 'started_following_repo':(_('[started following] repository'), None),
459 'stopped_following_repo':(_('[stopped following] repository'), None),
460 'stopped_following_repo':(_('[stopped following] repository'), None),
460 }
461 }
461
462
462 action_str = action_map.get(action, action)
463 action_str = action_map.get(action, action)
463 if feed:
464 if feed:
464 action = action_str[0].replace('[', '').replace(']', '')
465 action = action_str[0].replace('[', '').replace(']', '')
465 else:
466 else:
466 action = action_str[0].replace('[', '<span class="journal_highlight">')\
467 action = action_str[0].replace('[', '<span class="journal_highlight">')\
467 .replace(']', '</span>')
468 .replace(']', '</span>')
468
469
469 action_params_func = lambda :""
470 action_params_func = lambda :""
470
471
471 if callable(action_str[1]):
472 if callable(action_str[1]):
472 action_params_func = action_str[1]
473 action_params_func = action_str[1]
473
474
474 return [literal(action), action_params_func]
475 return [literal(action), action_params_func]
475
476
476 def action_parser_icon(user_log):
477 def action_parser_icon(user_log):
477 action = user_log.action
478 action = user_log.action
478 action_params = None
479 action_params = None
479 x = action.split(':')
480 x = action.split(':')
480
481
481 if len(x) > 1:
482 if len(x) > 1:
482 action, action_params = x
483 action, action_params = x
483
484
484 tmpl = """<img src="%s%s" alt="%s"/>"""
485 tmpl = """<img src="%s%s" alt="%s"/>"""
485 map = {'user_deleted_repo':'database_delete.png',
486 map = {'user_deleted_repo':'database_delete.png',
486 'user_created_repo':'database_add.png',
487 'user_created_repo':'database_add.png',
487 'user_forked_repo':'arrow_divide.png',
488 'user_forked_repo':'arrow_divide.png',
488 'user_updated_repo':'database_edit.png',
489 'user_updated_repo':'database_edit.png',
489 'admin_deleted_repo':'database_delete.png',
490 'admin_deleted_repo':'database_delete.png',
490 'admin_created_repo':'database_add.png',
491 'admin_created_repo':'database_add.png',
491 'admin_forked_repo':'arrow_divide.png',
492 'admin_forked_repo':'arrow_divide.png',
492 'admin_updated_repo':'database_edit.png',
493 'admin_updated_repo':'database_edit.png',
493 'push':'script_add.png',
494 'push':'script_add.png',
495 'push_local':'script_edit.png',
494 'push_remote':'connect.png',
496 'push_remote':'connect.png',
495 'pull':'down_16.png',
497 'pull':'down_16.png',
496 'started_following_repo':'heart_add.png',
498 'started_following_repo':'heart_add.png',
497 'stopped_following_repo':'heart_delete.png',
499 'stopped_following_repo':'heart_delete.png',
498 }
500 }
499 return literal(tmpl % ((url('/images/icons/')),
501 return literal(tmpl % ((url('/images/icons/')),
500 map.get(action, action), action))
502 map.get(action, action), action))
501
503
502
504
503 #==============================================================================
505 #==============================================================================
504 # PERMS
506 # PERMS
505 #==============================================================================
507 #==============================================================================
506 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
508 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
507 HasRepoPermissionAny, HasRepoPermissionAll
509 HasRepoPermissionAny, HasRepoPermissionAll
508
510
509 #==============================================================================
511 #==============================================================================
510 # GRAVATAR URL
512 # GRAVATAR URL
511 #==============================================================================
513 #==============================================================================
512
514
513 def gravatar_url(email_address, size=30):
515 def gravatar_url(email_address, size=30):
514 if not str2bool(config['app_conf'].get('use_gravatar')):
516 if not str2bool(config['app_conf'].get('use_gravatar')):
515 return "/images/user%s.png" % size
517 return "/images/user%s.png" % size
516
518
517 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
519 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
518 default = 'identicon'
520 default = 'identicon'
519 baseurl_nossl = "http://www.gravatar.com/avatar/"
521 baseurl_nossl = "http://www.gravatar.com/avatar/"
520 baseurl_ssl = "https://secure.gravatar.com/avatar/"
522 baseurl_ssl = "https://secure.gravatar.com/avatar/"
521 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
523 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
522
524
523 if isinstance(email_address, unicode):
525 if isinstance(email_address, unicode):
524 #hashlib crashes on unicode items
526 #hashlib crashes on unicode items
525 email_address = email_address.encode('utf8', 'replace')
527 email_address = email_address.encode('utf8', 'replace')
526 # construct the url
528 # construct the url
527 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
529 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
528 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
530 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
529
531
530 return gravatar_url
532 return gravatar_url
531
533
532
534
533 #==============================================================================
535 #==============================================================================
534 # REPO PAGER
536 # REPO PAGER
535 #==============================================================================
537 #==============================================================================
536 class RepoPage(Page):
538 class RepoPage(Page):
537
539
538 def __init__(self, collection, page=1, items_per_page=20,
540 def __init__(self, collection, page=1, items_per_page=20,
539 item_count=None, url=None, branch_name=None, **kwargs):
541 item_count=None, url=None, branch_name=None, **kwargs):
540
542
541 """Create a "RepoPage" instance. special pager for paging
543 """Create a "RepoPage" instance. special pager for paging
542 repository
544 repository
543 """
545 """
544 self._url_generator = url
546 self._url_generator = url
545
547
546 # Safe the kwargs class-wide so they can be used in the pager() method
548 # Safe the kwargs class-wide so they can be used in the pager() method
547 self.kwargs = kwargs
549 self.kwargs = kwargs
548
550
549 # Save a reference to the collection
551 # Save a reference to the collection
550 self.original_collection = collection
552 self.original_collection = collection
551
553
552 self.collection = collection
554 self.collection = collection
553
555
554 # The self.page is the number of the current page.
556 # The self.page is the number of the current page.
555 # The first page has the number 1!
557 # The first page has the number 1!
556 try:
558 try:
557 self.page = int(page) # make it int() if we get it as a string
559 self.page = int(page) # make it int() if we get it as a string
558 except (ValueError, TypeError):
560 except (ValueError, TypeError):
559 self.page = 1
561 self.page = 1
560
562
561 self.items_per_page = items_per_page
563 self.items_per_page = items_per_page
562
564
563 # Unless the user tells us how many items the collections has
565 # Unless the user tells us how many items the collections has
564 # we calculate that ourselves.
566 # we calculate that ourselves.
565 if item_count is not None:
567 if item_count is not None:
566 self.item_count = item_count
568 self.item_count = item_count
567 else:
569 else:
568 self.item_count = len(self.collection)
570 self.item_count = len(self.collection)
569
571
570 # Compute the number of the first and last available page
572 # Compute the number of the first and last available page
571 if self.item_count > 0:
573 if self.item_count > 0:
572 self.first_page = 1
574 self.first_page = 1
573 self.page_count = ((self.item_count - 1) / self.items_per_page) + 1
575 self.page_count = ((self.item_count - 1) / self.items_per_page) + 1
574 self.last_page = self.first_page + self.page_count - 1
576 self.last_page = self.first_page + self.page_count - 1
575
577
576 # Make sure that the requested page number is the range of valid pages
578 # Make sure that the requested page number is the range of valid pages
577 if self.page > self.last_page:
579 if self.page > self.last_page:
578 self.page = self.last_page
580 self.page = self.last_page
579 elif self.page < self.first_page:
581 elif self.page < self.first_page:
580 self.page = self.first_page
582 self.page = self.first_page
581
583
582 # Note: the number of items on this page can be less than
584 # Note: the number of items on this page can be less than
583 # items_per_page if the last page is not full
585 # items_per_page if the last page is not full
584 self.first_item = max(0, (self.item_count) - (self.page * items_per_page))
586 self.first_item = max(0, (self.item_count) - (self.page * items_per_page))
585 self.last_item = ((self.item_count - 1) - items_per_page * (self.page - 1))
587 self.last_item = ((self.item_count - 1) - items_per_page * (self.page - 1))
586
588
587 iterator = self.collection.get_changesets(start=self.first_item,
589 iterator = self.collection.get_changesets(start=self.first_item,
588 end=self.last_item,
590 end=self.last_item,
589 reverse=True,
591 reverse=True,
590 branch_name=branch_name)
592 branch_name=branch_name)
591 self.items = list(iterator)
593 self.items = list(iterator)
592
594
593 # Links to previous and next page
595 # Links to previous and next page
594 if self.page > self.first_page:
596 if self.page > self.first_page:
595 self.previous_page = self.page - 1
597 self.previous_page = self.page - 1
596 else:
598 else:
597 self.previous_page = None
599 self.previous_page = None
598
600
599 if self.page < self.last_page:
601 if self.page < self.last_page:
600 self.next_page = self.page + 1
602 self.next_page = self.page + 1
601 else:
603 else:
602 self.next_page = None
604 self.next_page = None
603
605
604 # No items available
606 # No items available
605 else:
607 else:
606 self.first_page = None
608 self.first_page = None
607 self.page_count = 0
609 self.page_count = 0
608 self.last_page = None
610 self.last_page = None
609 self.first_item = None
611 self.first_item = None
610 self.last_item = None
612 self.last_item = None
611 self.previous_page = None
613 self.previous_page = None
612 self.next_page = None
614 self.next_page = None
613 self.items = []
615 self.items = []
614
616
615 # This is a subclass of the 'list' type. Initialise the list now.
617 # This is a subclass of the 'list' type. Initialise the list now.
616 list.__init__(self, self.items)
618 list.__init__(self, self.items)
617
619
618
620
619 def changed_tooltip(nodes):
621 def changed_tooltip(nodes):
620 if nodes:
622 if nodes:
621 pref = ': <br/> '
623 pref = ': <br/> '
622 suf = ''
624 suf = ''
623 if len(nodes) > 30:
625 if len(nodes) > 30:
624 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
626 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
625 return literal(pref + '<br/> '.join([safe_unicode(x.path)
627 return literal(pref + '<br/> '.join([safe_unicode(x.path)
626 for x in nodes[:30]]) + suf)
628 for x in nodes[:30]]) + suf)
627 else:
629 else:
628 return ': ' + _('No Files')
630 return ': ' + _('No Files')
629
631
630
632
631
633
632 def repo_link(groups_and_repos):
634 def repo_link(groups_and_repos):
633 groups, repo_name = groups_and_repos
635 groups, repo_name = groups_and_repos
634
636
635 if not groups:
637 if not groups:
636 return repo_name
638 return repo_name
637 else:
639 else:
638 def make_link(group):
640 def make_link(group):
639 return link_to(group.group_name, url('repos_group',
641 return link_to(group.group_name, url('repos_group',
640 id=group.group_id))
642 id=group.group_id))
641 return literal(' &raquo; '.join(map(make_link, groups)) + \
643 return literal(' &raquo; '.join(map(make_link, groups)) + \
642 " &raquo; " + repo_name)
644 " &raquo; " + repo_name)
643
645
644
646
645 def fancy_file_stats(stats):
647 def fancy_file_stats(stats):
646 a, d, t = stats[0], stats[1], stats[0] + stats[1]
648 a, d, t = stats[0], stats[1], stats[0] + stats[1]
647 width = 100
649 width = 100
648 unit = float(width) / (t or 1)
650 unit = float(width) / (t or 1)
649
651
650 a_p = max(9, unit * a) if a > 0 else 0# needs > 9% to be visible
652 a_p = max(9, unit * a) if a > 0 else 0# needs > 9% to be visible
651 d_p = max(9, unit * d) if d > 0 else 0 # needs > 9% to be visible
653 d_p = max(9, unit * d) if d > 0 else 0 # needs > 9% to be visible
652 p_sum = a_p + d_p
654 p_sum = a_p + d_p
653
655
654 if p_sum > width:
656 if p_sum > width:
655 #adjust the percentage to be == 100% since we adjusted to 9
657 #adjust the percentage to be == 100% since we adjusted to 9
656 if a_p > d_p:
658 if a_p > d_p:
657 a_p = a_p - (p_sum - width)
659 a_p = a_p - (p_sum - width)
658 else:
660 else:
659 d_p = d_p - (p_sum - width)
661 d_p = d_p - (p_sum - width)
660
662
661 a_v = a if a > 0 else ''
663 a_v = a if a > 0 else ''
662 d_v = d if d > 0 else ''
664 d_v = d if d > 0 else ''
663
665
664
666
665 def cgen(l_type):
667 def cgen(l_type):
666 mapping = {'tr':'top-right-rounded-corner',
668 mapping = {'tr':'top-right-rounded-corner',
667 'tl':'top-left-rounded-corner',
669 'tl':'top-left-rounded-corner',
668 'br':'bottom-right-rounded-corner',
670 'br':'bottom-right-rounded-corner',
669 'bl':'bottom-left-rounded-corner'}
671 'bl':'bottom-left-rounded-corner'}
670 map_getter = lambda x:mapping[x]
672 map_getter = lambda x:mapping[x]
671
673
672 if l_type == 'a' and d_v:
674 if l_type == 'a' and d_v:
673 #case when added and deleted are present
675 #case when added and deleted are present
674 return ' '.join(map(map_getter, ['tl', 'bl']))
676 return ' '.join(map(map_getter, ['tl', 'bl']))
675
677
676 if l_type == 'a' and not d_v:
678 if l_type == 'a' and not d_v:
677 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
679 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
678
680
679 if l_type == 'd' and a_v:
681 if l_type == 'd' and a_v:
680 return ' '.join(map(map_getter, ['tr', 'br']))
682 return ' '.join(map(map_getter, ['tr', 'br']))
681
683
682 if l_type == 'd' and not a_v:
684 if l_type == 'd' and not a_v:
683 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
685 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
684
686
685
687
686
688
687 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
689 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
688 a_p, a_v)
690 a_p, a_v)
689 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
691 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
690 d_p, d_v)
692 d_p, d_v)
691 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
693 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
@@ -1,435 +1,440 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 from vcs.nodes import FileNode
42
42
43 from rhodecode import BACKENDS
43 from rhodecode import BACKENDS
44 from rhodecode.lib import helpers as h
44 from rhodecode.lib import helpers as h
45 from rhodecode.lib.auth import HasRepoPermissionAny
45 from rhodecode.lib.auth import HasRepoPermissionAny
46 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, \
47 action_logger
47 action_logger
48 from rhodecode.model import BaseModel
48 from rhodecode.model import BaseModel
49 from rhodecode.model.user import UserModel
49 from rhodecode.model.user import UserModel
50 from rhodecode.model.repo import RepoModel
50 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
51 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
52 UserFollowing, UserLog
52 UserFollowing, UserLog
53 from rhodecode.model.caching_query import FromCache
53 from rhodecode.model.caching_query import FromCache
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class UserTemp(object):
58 class UserTemp(object):
59 def __init__(self, user_id):
59 def __init__(self, user_id):
60 self.user_id = user_id
60 self.user_id = user_id
61
61
62 def __repr__(self):
62 def __repr__(self):
63 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
63 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
64
64
65
65
66 class RepoTemp(object):
66 class RepoTemp(object):
67 def __init__(self, repo_id):
67 def __init__(self, repo_id):
68 self.repo_id = repo_id
68 self.repo_id = repo_id
69
69
70 def __repr__(self):
70 def __repr__(self):
71 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
71 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
72
72
73
73
74 class ScmModel(BaseModel):
74 class ScmModel(BaseModel):
75 """Generic Scm Model
75 """Generic Scm Model
76 """
76 """
77
77
78 @LazyProperty
78 @LazyProperty
79 def repos_path(self):
79 def repos_path(self):
80 """Get's the repositories root path from database
80 """Get's the repositories root path from database
81 """
81 """
82
82
83 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
83 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
84
84
85 return q.ui_value
85 return q.ui_value
86
86
87 def repo_scan(self, repos_path=None):
87 def repo_scan(self, repos_path=None):
88 """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
89 repository itself. Return a dictionary of repository objects
89 repository itself. Return a dictionary of repository objects
90
90
91 :param repos_path: path to directory containing repositories
91 :param repos_path: path to directory containing repositories
92 """
92 """
93
93
94 log.info('scanning for repositories in %s', repos_path)
94 log.info('scanning for repositories in %s', repos_path)
95
95
96 if repos_path is None:
96 if repos_path is None:
97 repos_path = self.repos_path
97 repos_path = self.repos_path
98
98
99 baseui = make_ui('db')
99 baseui = make_ui('db')
100 repos_list = {}
100 repos_list = {}
101
101
102 for name, path in get_filesystem_repos(repos_path, recursive=True):
102 for name, path in get_filesystem_repos(repos_path, recursive=True):
103 try:
103 try:
104 if name in repos_list:
104 if name in repos_list:
105 raise RepositoryError('Duplicate repository name %s '
105 raise RepositoryError('Duplicate repository name %s '
106 'found in %s' % (name, path))
106 'found in %s' % (name, path))
107 else:
107 else:
108
108
109 klass = get_backend(path[0])
109 klass = get_backend(path[0])
110
110
111 if path[0] == 'hg' and path[0] in BACKENDS.keys():
111 if path[0] == 'hg' and path[0] in BACKENDS.keys():
112 repos_list[name] = klass(path[1], baseui=baseui)
112 repos_list[name] = klass(path[1], baseui=baseui)
113
113
114 if path[0] == 'git' and path[0] in BACKENDS.keys():
114 if path[0] == 'git' and path[0] in BACKENDS.keys():
115 repos_list[name] = klass(path[1])
115 repos_list[name] = klass(path[1])
116 except OSError:
116 except OSError:
117 continue
117 continue
118
118
119 return repos_list
119 return repos_list
120
120
121 def get_repos(self, all_repos=None):
121 def get_repos(self, all_repos=None):
122 """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
123 backend instance and fill that backed with information from database
123 backend instance and fill that backed with information from database
124
124
125 :param all_repos: give specific repositories list, good for filtering
125 :param all_repos: give specific repositories list, good for filtering
126 this have to be a list of just the repository names
126 this have to be a list of just the repository names
127 """
127 """
128 if all_repos is None:
128 if all_repos is None:
129 repos = self.sa.query(Repository)\
129 repos = self.sa.query(Repository)\
130 .order_by(Repository.repo_name).all()
130 .order_by(Repository.repo_name).all()
131 all_repos = [r.repo_name for r in repos]
131 all_repos = [r.repo_name for r in repos]
132
132
133 #get the repositories that should be invalidated
133 #get the repositories that should be invalidated
134 invalidation_list = [str(x.cache_key) for x in \
134 invalidation_list = [str(x.cache_key) for x in \
135 self.sa.query(CacheInvalidation.cache_key)\
135 self.sa.query(CacheInvalidation.cache_key)\
136 .filter(CacheInvalidation.cache_active == False)\
136 .filter(CacheInvalidation.cache_active == False)\
137 .all()]
137 .all()]
138 for r_name in all_repos:
138 for r_name in all_repos:
139 r_dbr = self.get(r_name, invalidation_list)
139 r_dbr = self.get(r_name, invalidation_list)
140 if r_dbr is not None:
140 if r_dbr is not None:
141 repo, dbrepo = r_dbr
141 repo, dbrepo = r_dbr
142
142
143 if repo is None or dbrepo is None:
143 if repo is None or dbrepo is None:
144 log.error('Repository "%s" looks somehow corrupted '
144 log.error('Repository "%s" looks somehow corrupted '
145 'fs-repo:%s,db-repo:%s both values should be '
145 'fs-repo:%s,db-repo:%s both values should be '
146 'present', r_name, repo, dbrepo)
146 'present', r_name, repo, dbrepo)
147 continue
147 continue
148 last_change = repo.last_change
148 last_change = repo.last_change
149 tip = h.get_changeset_safe(repo, 'tip')
149 tip = h.get_changeset_safe(repo, 'tip')
150
150
151 tmp_d = {}
151 tmp_d = {}
152 tmp_d['name'] = dbrepo.repo_name
152 tmp_d['name'] = dbrepo.repo_name
153 tmp_d['name_sort'] = tmp_d['name'].lower()
153 tmp_d['name_sort'] = tmp_d['name'].lower()
154 tmp_d['description'] = dbrepo.description
154 tmp_d['description'] = dbrepo.description
155 tmp_d['description_sort'] = tmp_d['description']
155 tmp_d['description_sort'] = tmp_d['description']
156 tmp_d['last_change'] = last_change
156 tmp_d['last_change'] = last_change
157 tmp_d['last_change_sort'] = time.mktime(last_change \
157 tmp_d['last_change_sort'] = time.mktime(last_change \
158 .timetuple())
158 .timetuple())
159 tmp_d['tip'] = tip.raw_id
159 tmp_d['tip'] = tip.raw_id
160 tmp_d['tip_sort'] = tip.revision
160 tmp_d['tip_sort'] = tip.revision
161 tmp_d['rev'] = tip.revision
161 tmp_d['rev'] = tip.revision
162 tmp_d['contact'] = dbrepo.user.full_contact
162 tmp_d['contact'] = dbrepo.user.full_contact
163 tmp_d['contact_sort'] = tmp_d['contact']
163 tmp_d['contact_sort'] = tmp_d['contact']
164 tmp_d['owner_sort'] = tmp_d['contact']
164 tmp_d['owner_sort'] = tmp_d['contact']
165 tmp_d['repo_archives'] = list(repo._get_archives())
165 tmp_d['repo_archives'] = list(repo._get_archives())
166 tmp_d['last_msg'] = tip.message
166 tmp_d['last_msg'] = tip.message
167 tmp_d['repo'] = repo
167 tmp_d['repo'] = repo
168 tmp_d['dbrepo'] = dbrepo.get_dict()
168 tmp_d['dbrepo'] = dbrepo.get_dict()
169 tmp_d['dbrepo_fork'] = dbrepo.fork.get_dict() if dbrepo.fork \
169 tmp_d['dbrepo_fork'] = dbrepo.fork.get_dict() if dbrepo.fork \
170 else {}
170 else {}
171 yield tmp_d
171 yield tmp_d
172
172
173 def get(self, repo_name, invalidation_list=None, retval='all'):
173 def get(self, repo_name, invalidation_list=None, retval='all'):
174 """Returns a tuple of Repository,DbRepository,
174 """Returns a tuple of Repository,DbRepository,
175 Get's repository from given name, creates BackendInstance and
175 Get's repository from given name, creates BackendInstance and
176 propagates it's data from database with all additional information
176 propagates it's data from database with all additional information
177
177
178 :param repo_name:
178 :param repo_name:
179 :param invalidation_list: if a invalidation list is given the get
179 :param invalidation_list: if a invalidation list is given the get
180 method should not manually check if this repository needs
180 method should not manually check if this repository needs
181 invalidation and just invalidate the repositories in list
181 invalidation and just invalidate the repositories in list
182 :param retval: string specifing what to return one of 'repo','dbrepo',
182 :param retval: string specifing what to return one of 'repo','dbrepo',
183 '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
184 and return None as the second
184 and return None as the second
185 """
185 """
186 if not HasRepoPermissionAny('repository.read', 'repository.write',
186 if not HasRepoPermissionAny('repository.read', 'repository.write',
187 'repository.admin')(repo_name, 'get repo check'):
187 'repository.admin')(repo_name, 'get repo check'):
188 return
188 return
189
189
190 #======================================================================
190 #======================================================================
191 # CACHE FUNCTION
191 # CACHE FUNCTION
192 #======================================================================
192 #======================================================================
193 @cache_region('long_term')
193 @cache_region('long_term')
194 def _get_repo(repo_name):
194 def _get_repo(repo_name):
195
195
196 repo_path = os.path.join(self.repos_path, repo_name)
196 repo_path = os.path.join(self.repos_path, repo_name)
197
197
198 try:
198 try:
199 alias = get_scm(repo_path)[0]
199 alias = get_scm(repo_path)[0]
200 log.debug('Creating instance of %s repository', alias)
200 log.debug('Creating instance of %s repository', alias)
201 backend = get_backend(alias)
201 backend = get_backend(alias)
202 except VCSError:
202 except VCSError:
203 log.error(traceback.format_exc())
203 log.error(traceback.format_exc())
204 log.error('Perhaps this repository is in db and not in '
204 log.error('Perhaps this repository is in db and not in '
205 'filesystem run rescan repositories with '
205 'filesystem run rescan repositories with '
206 '"destroy old data " option from admin panel')
206 '"destroy old data " option from admin panel')
207 return
207 return
208
208
209 if alias == 'hg':
209 if alias == 'hg':
210 repo = backend(repo_path, create=False, baseui=make_ui('db'))
210 repo = backend(repo_path, create=False, baseui=make_ui('db'))
211 #skip hidden web repository
211 #skip hidden web repository
212 if repo._get_hidden():
212 if repo._get_hidden():
213 return
213 return
214 else:
214 else:
215 repo = backend(repo_path, create=False)
215 repo = backend(repo_path, create=False)
216
216
217 return repo
217 return repo
218
218
219 pre_invalidate = True
219 pre_invalidate = True
220 dbinvalidate = False
220 dbinvalidate = False
221
221
222 if invalidation_list is not None:
222 if invalidation_list is not None:
223 pre_invalidate = repo_name in invalidation_list
223 pre_invalidate = repo_name in invalidation_list
224
224
225 if pre_invalidate:
225 if pre_invalidate:
226 #this returns object to invalidate
226 #this returns object to invalidate
227 invalidate = self._should_invalidate(repo_name)
227 invalidate = self._should_invalidate(repo_name)
228 if invalidate:
228 if invalidate:
229 log.info('invalidating cache for repository %s', repo_name)
229 log.info('invalidating cache for repository %s', repo_name)
230 region_invalidate(_get_repo, None, repo_name)
230 region_invalidate(_get_repo, None, repo_name)
231 self._mark_invalidated(invalidate)
231 self._mark_invalidated(invalidate)
232 dbinvalidate = True
232 dbinvalidate = True
233
233
234 r, dbr = None, None
234 r, dbr = None, None
235 if retval == 'repo' or 'all':
235 if retval == 'repo' or 'all':
236 r = _get_repo(repo_name)
236 r = _get_repo(repo_name)
237 if retval == 'dbrepo' or 'all':
237 if retval == 'dbrepo' or 'all':
238 dbr = RepoModel().get_full(repo_name, cache=True,
238 dbr = RepoModel().get_full(repo_name, cache=True,
239 invalidate=dbinvalidate)
239 invalidate=dbinvalidate)
240
240
241 return r, dbr
241 return r, dbr
242
242
243 def mark_for_invalidation(self, repo_name):
243 def mark_for_invalidation(self, repo_name):
244 """Puts cache invalidation task into db for
244 """Puts cache invalidation task into db for
245 further global cache invalidation
245 further global cache invalidation
246
246
247 :param repo_name: this repo that should invalidation take place
247 :param repo_name: this repo that should invalidation take place
248 """
248 """
249
249
250 log.debug('marking %s for invalidation', repo_name)
250 log.debug('marking %s for invalidation', repo_name)
251 cache = self.sa.query(CacheInvalidation)\
251 cache = self.sa.query(CacheInvalidation)\
252 .filter(CacheInvalidation.cache_key == repo_name).scalar()
252 .filter(CacheInvalidation.cache_key == repo_name).scalar()
253
253
254 if cache:
254 if cache:
255 #mark this cache as inactive
255 #mark this cache as inactive
256 cache.cache_active = False
256 cache.cache_active = False
257 else:
257 else:
258 log.debug('cache key not found in invalidation db -> creating one')
258 log.debug('cache key not found in invalidation db -> creating one')
259 cache = CacheInvalidation(repo_name)
259 cache = CacheInvalidation(repo_name)
260
260
261 try:
261 try:
262 self.sa.add(cache)
262 self.sa.add(cache)
263 self.sa.commit()
263 self.sa.commit()
264 except (DatabaseError,):
264 except (DatabaseError,):
265 log.error(traceback.format_exc())
265 log.error(traceback.format_exc())
266 self.sa.rollback()
266 self.sa.rollback()
267
267
268 def toggle_following_repo(self, follow_repo_id, user_id):
268 def toggle_following_repo(self, follow_repo_id, user_id):
269
269
270 f = self.sa.query(UserFollowing)\
270 f = self.sa.query(UserFollowing)\
271 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
271 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
272 .filter(UserFollowing.user_id == user_id).scalar()
272 .filter(UserFollowing.user_id == user_id).scalar()
273
273
274 if f is not None:
274 if f is not None:
275
275
276 try:
276 try:
277 self.sa.delete(f)
277 self.sa.delete(f)
278 self.sa.commit()
278 self.sa.commit()
279 action_logger(UserTemp(user_id),
279 action_logger(UserTemp(user_id),
280 'stopped_following_repo',
280 'stopped_following_repo',
281 RepoTemp(follow_repo_id))
281 RepoTemp(follow_repo_id))
282 return
282 return
283 except:
283 except:
284 log.error(traceback.format_exc())
284 log.error(traceback.format_exc())
285 self.sa.rollback()
285 self.sa.rollback()
286 raise
286 raise
287
287
288 try:
288 try:
289 f = UserFollowing()
289 f = UserFollowing()
290 f.user_id = user_id
290 f.user_id = user_id
291 f.follows_repo_id = follow_repo_id
291 f.follows_repo_id = follow_repo_id
292 self.sa.add(f)
292 self.sa.add(f)
293 self.sa.commit()
293 self.sa.commit()
294 action_logger(UserTemp(user_id),
294 action_logger(UserTemp(user_id),
295 'started_following_repo',
295 'started_following_repo',
296 RepoTemp(follow_repo_id))
296 RepoTemp(follow_repo_id))
297 except:
297 except:
298 log.error(traceback.format_exc())
298 log.error(traceback.format_exc())
299 self.sa.rollback()
299 self.sa.rollback()
300 raise
300 raise
301
301
302 def toggle_following_user(self, follow_user_id, user_id):
302 def toggle_following_user(self, follow_user_id, user_id):
303 f = self.sa.query(UserFollowing)\
303 f = self.sa.query(UserFollowing)\
304 .filter(UserFollowing.follows_user_id == follow_user_id)\
304 .filter(UserFollowing.follows_user_id == follow_user_id)\
305 .filter(UserFollowing.user_id == user_id).scalar()
305 .filter(UserFollowing.user_id == user_id).scalar()
306
306
307 if f is not None:
307 if f is not None:
308 try:
308 try:
309 self.sa.delete(f)
309 self.sa.delete(f)
310 self.sa.commit()
310 self.sa.commit()
311 return
311 return
312 except:
312 except:
313 log.error(traceback.format_exc())
313 log.error(traceback.format_exc())
314 self.sa.rollback()
314 self.sa.rollback()
315 raise
315 raise
316
316
317 try:
317 try:
318 f = UserFollowing()
318 f = UserFollowing()
319 f.user_id = user_id
319 f.user_id = user_id
320 f.follows_user_id = follow_user_id
320 f.follows_user_id = follow_user_id
321 self.sa.add(f)
321 self.sa.add(f)
322 self.sa.commit()
322 self.sa.commit()
323 except:
323 except:
324 log.error(traceback.format_exc())
324 log.error(traceback.format_exc())
325 self.sa.rollback()
325 self.sa.rollback()
326 raise
326 raise
327
327
328 def is_following_repo(self, repo_name, user_id, cache=False):
328 def is_following_repo(self, repo_name, user_id, cache=False):
329 r = self.sa.query(Repository)\
329 r = self.sa.query(Repository)\
330 .filter(Repository.repo_name == repo_name).scalar()
330 .filter(Repository.repo_name == repo_name).scalar()
331
331
332 f = self.sa.query(UserFollowing)\
332 f = self.sa.query(UserFollowing)\
333 .filter(UserFollowing.follows_repository == r)\
333 .filter(UserFollowing.follows_repository == r)\
334 .filter(UserFollowing.user_id == user_id).scalar()
334 .filter(UserFollowing.user_id == user_id).scalar()
335
335
336 return f is not None
336 return f is not None
337
337
338 def is_following_user(self, username, user_id, cache=False):
338 def is_following_user(self, username, user_id, cache=False):
339 u = UserModel(self.sa).get_by_username(username)
339 u = UserModel(self.sa).get_by_username(username)
340
340
341 f = self.sa.query(UserFollowing)\
341 f = self.sa.query(UserFollowing)\
342 .filter(UserFollowing.follows_user == u)\
342 .filter(UserFollowing.follows_user == u)\
343 .filter(UserFollowing.user_id == user_id).scalar()
343 .filter(UserFollowing.user_id == user_id).scalar()
344
344
345 return f is not None
345 return f is not None
346
346
347 def get_followers(self, repo_id):
347 def get_followers(self, repo_id):
348 if not isinstance(repo_id, int):
348 if not isinstance(repo_id, int):
349 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
349 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
350
350
351 return self.sa.query(UserFollowing)\
351 return self.sa.query(UserFollowing)\
352 .filter(UserFollowing.follows_repo_id == repo_id).count()
352 .filter(UserFollowing.follows_repo_id == repo_id).count()
353
353
354 def get_forks(self, repo_id):
354 def get_forks(self, repo_id):
355 if not isinstance(repo_id, int):
355 if not isinstance(repo_id, int):
356 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
356 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
357
357
358 return self.sa.query(Repository)\
358 return self.sa.query(Repository)\
359 .filter(Repository.fork_id == repo_id).count()
359 .filter(Repository.fork_id == repo_id).count()
360
360
361 def pull_changes(self, repo_name, username):
361 def pull_changes(self, repo_name, username):
362 repo, dbrepo = self.get(repo_name, retval='all')
362 repo, dbrepo = self.get(repo_name, retval='all')
363
363
364 try:
364 try:
365 extras = {'ip': '',
365 extras = {'ip': '',
366 'username': username,
366 'username': username,
367 'action': 'push_remote',
367 'action': 'push_remote',
368 'repository': repo_name}
368 'repository': repo_name}
369
369
370 #inject ui extra param to log this action via push logger
370 #inject ui extra param to log this action via push logger
371 for k, v in extras.items():
371 for k, v in extras.items():
372 repo._repo.ui.setconfig('rhodecode_extras', k, v)
372 repo._repo.ui.setconfig('rhodecode_extras', k, v)
373
373
374 repo.pull(dbrepo.clone_uri)
374 repo.pull(dbrepo.clone_uri)
375 self.mark_for_invalidation(repo_name)
375 self.mark_for_invalidation(repo_name)
376 except:
376 except:
377 log.error(traceback.format_exc())
377 log.error(traceback.format_exc())
378 raise
378 raise
379
379
380
380
381 def commit_change(self, repo, repo_name, cs, author, message, content,
381 def commit_change(self, repo, repo_name, cs, user, author, message, content,
382 f_path):
382 f_path):
383
383
384 if repo.alias == 'hg':
384 if repo.alias == 'hg':
385 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
385 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
386 elif repo.alias == 'git':
386 elif repo.alias == 'git':
387 from vcs.backends.git import GitInMemoryChangeset as IMC
387 from vcs.backends.git import GitInMemoryChangeset as IMC
388
388
389 # decoding here will force that we have proper encoded values
389 # decoding here will force that we have proper encoded values
390 # in any other case this will throw exceptions and deny commit
390 # in any other case this will throw exceptions and deny commit
391 content = content.encode('utf8')
391 content = content.encode('utf8')
392 message = message.encode('utf8')
392 message = message.encode('utf8')
393 path = f_path.encode('utf8')
393 path = f_path.encode('utf8')
394 author = author.encode('utf8')
394 author = author.encode('utf8')
395 m = IMC(repo)
395 m = IMC(repo)
396 m.change(FileNode(path, content))
396 m.change(FileNode(path, content))
397 m.commit(message=message,
397 tip = m.commit(message=message,
398 author=author,
398 author=author,
399 parents=[cs], branch=cs.branch)
399 parents=[cs], branch=cs.branch)
400
400
401 new_cs = tip.short_id
402 action = 'push_local:%s' % new_cs
403
404 action_logger(user, action, repo_name)
405
401 self.mark_for_invalidation(repo_name)
406 self.mark_for_invalidation(repo_name)
402
407
403
408
404 def get_unread_journal(self):
409 def get_unread_journal(self):
405 return self.sa.query(UserLog).count()
410 return self.sa.query(UserLog).count()
406
411
407 def _should_invalidate(self, repo_name):
412 def _should_invalidate(self, repo_name):
408 """Looks up database for invalidation signals for this repo_name
413 """Looks up database for invalidation signals for this repo_name
409
414
410 :param repo_name:
415 :param repo_name:
411 """
416 """
412
417
413 ret = self.sa.query(CacheInvalidation)\
418 ret = self.sa.query(CacheInvalidation)\
414 .filter(CacheInvalidation.cache_key == repo_name)\
419 .filter(CacheInvalidation.cache_key == repo_name)\
415 .filter(CacheInvalidation.cache_active == False)\
420 .filter(CacheInvalidation.cache_active == False)\
416 .scalar()
421 .scalar()
417
422
418 return ret
423 return ret
419
424
420 def _mark_invalidated(self, cache_key):
425 def _mark_invalidated(self, cache_key):
421 """ Marks all occurrences of cache to invalidation as already
426 """ Marks all occurrences of cache to invalidation as already
422 invalidated
427 invalidated
423
428
424 :param cache_key:
429 :param cache_key:
425 """
430 """
426
431
427 if cache_key:
432 if cache_key:
428 log.debug('marking %s as already invalidated', cache_key)
433 log.debug('marking %s as already invalidated', cache_key)
429 try:
434 try:
430 cache_key.cache_active = True
435 cache_key.cache_active = True
431 self.sa.add(cache_key)
436 self.sa.add(cache_key)
432 self.sa.commit()
437 self.sa.commit()
433 except (DatabaseError,):
438 except (DatabaseError,):
434 log.error(traceback.format_exc())
439 log.error(traceback.format_exc())
435 self.sa.rollback()
440 self.sa.rollback()
General Comments 0
You need to be logged in to leave comments. Login now