##// END OF EJS Templates
store download archive actions in journal
marcink -
r3868:e262494c beta
parent child Browse files
Show More
@@ -1,658 +1,661 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) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 from __future__ import with_statement
25 from __future__ import with_statement
26 import os
26 import os
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import tempfile
29 import tempfile
30 import shutil
30 import shutil
31
31
32 from pylons import request, response, tmpl_context as c, url
32 from pylons import request, response, tmpl_context as c, url
33 from pylons.i18n.translation import _
33 from pylons.i18n.translation import _
34 from pylons.controllers.util import redirect
34 from pylons.controllers.util import redirect
35 from rhodecode.lib.utils import jsonify
35 from rhodecode.lib.utils import jsonify, action_logger
36
36
37 from rhodecode.lib import diffs
37 from rhodecode.lib import diffs
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39
39
40 from rhodecode.lib.compat import OrderedDict
40 from rhodecode.lib.compat import OrderedDict
41 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str,\
41 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str,\
42 str2bool
42 str2bool
43 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
43 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
44 from rhodecode.lib.base import BaseRepoController, render
44 from rhodecode.lib.base import BaseRepoController, render
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
46 from rhodecode.lib.vcs.conf import settings
46 from rhodecode.lib.vcs.conf import settings
47 from rhodecode.lib.vcs.exceptions import RepositoryError, \
47 from rhodecode.lib.vcs.exceptions import RepositoryError, \
48 ChangesetDoesNotExistError, EmptyRepositoryError, \
48 ChangesetDoesNotExistError, EmptyRepositoryError, \
49 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,\
49 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,\
50 NodeDoesNotExistError, ChangesetError, NodeError
50 NodeDoesNotExistError, ChangesetError, NodeError
51 from rhodecode.lib.vcs.nodes import FileNode
51 from rhodecode.lib.vcs.nodes import FileNode
52
52
53 from rhodecode.model.repo import RepoModel
53 from rhodecode.model.repo import RepoModel
54 from rhodecode.model.scm import ScmModel
54 from rhodecode.model.scm import ScmModel
55 from rhodecode.model.db import Repository
55 from rhodecode.model.db import Repository
56
56
57 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
57 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
58 _context_url, get_line_ctx, get_ignore_ws
58 _context_url, get_line_ctx, get_ignore_ws
59 from webob.exc import HTTPNotFound
59 from webob.exc import HTTPNotFound
60 from rhodecode.lib.exceptions import NonRelativePathError
60 from rhodecode.lib.exceptions import NonRelativePathError
61
61
62
62
63 log = logging.getLogger(__name__)
63 log = logging.getLogger(__name__)
64
64
65
65
66 class FilesController(BaseRepoController):
66 class FilesController(BaseRepoController):
67
67
68 def __before__(self):
68 def __before__(self):
69 super(FilesController, self).__before__()
69 super(FilesController, self).__before__()
70 c.cut_off_limit = self.cut_off_limit
70 c.cut_off_limit = self.cut_off_limit
71
71
72 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
72 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
73 """
73 """
74 Safe way to get changeset if error occur it redirects to tip with
74 Safe way to get changeset if error occur it redirects to tip with
75 proper message
75 proper message
76
76
77 :param rev: revision to fetch
77 :param rev: revision to fetch
78 :param repo_name: repo name to redirect after
78 :param repo_name: repo name to redirect after
79 """
79 """
80
80
81 try:
81 try:
82 return c.rhodecode_repo.get_changeset(rev)
82 return c.rhodecode_repo.get_changeset(rev)
83 except EmptyRepositoryError, e:
83 except EmptyRepositoryError, e:
84 if not redirect_after:
84 if not redirect_after:
85 return None
85 return None
86 url_ = url('files_add_home',
86 url_ = url('files_add_home',
87 repo_name=c.repo_name,
87 repo_name=c.repo_name,
88 revision=0, f_path='')
88 revision=0, f_path='')
89 add_new = h.link_to(_('Click here to add new file'), url_)
89 add_new = h.link_to(_('Click here to add new file'), url_)
90 h.flash(h.literal(_('There are no files yet %s') % add_new),
90 h.flash(h.literal(_('There are no files yet %s') % add_new),
91 category='warning')
91 category='warning')
92 redirect(h.url('summary_home', repo_name=repo_name))
92 redirect(h.url('summary_home', repo_name=repo_name))
93
93
94 except RepositoryError, e: # including ChangesetDoesNotExistError
94 except RepositoryError, e: # including ChangesetDoesNotExistError
95 h.flash(str(e), category='error')
95 h.flash(str(e), category='error')
96 raise HTTPNotFound()
96 raise HTTPNotFound()
97
97
98 def __get_filenode_or_redirect(self, repo_name, cs, path):
98 def __get_filenode_or_redirect(self, repo_name, cs, path):
99 """
99 """
100 Returns file_node, if error occurs or given path is directory,
100 Returns file_node, if error occurs or given path is directory,
101 it'll redirect to top level path
101 it'll redirect to top level path
102
102
103 :param repo_name: repo_name
103 :param repo_name: repo_name
104 :param cs: given changeset
104 :param cs: given changeset
105 :param path: path to lookup
105 :param path: path to lookup
106 """
106 """
107
107
108 try:
108 try:
109 file_node = cs.get_node(path)
109 file_node = cs.get_node(path)
110 if file_node.is_dir():
110 if file_node.is_dir():
111 raise RepositoryError('given path is a directory')
111 raise RepositoryError('given path is a directory')
112 except RepositoryError, e:
112 except RepositoryError, e:
113 h.flash(str(e), category='error')
113 h.flash(str(e), category='error')
114 raise HTTPNotFound()
114 raise HTTPNotFound()
115
115
116 return file_node
116 return file_node
117
117
118 @LoginRequired()
118 @LoginRequired()
119 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
119 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
120 'repository.admin')
120 'repository.admin')
121 def index(self, repo_name, revision, f_path, annotate=False):
121 def index(self, repo_name, revision, f_path, annotate=False):
122 # redirect to given revision from form if given
122 # redirect to given revision from form if given
123 post_revision = request.POST.get('at_rev', None)
123 post_revision = request.POST.get('at_rev', None)
124 if post_revision:
124 if post_revision:
125 cs = self.__get_cs_or_redirect(post_revision, repo_name)
125 cs = self.__get_cs_or_redirect(post_revision, repo_name)
126
126
127 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
127 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
128 c.branch = request.GET.get('branch', None)
128 c.branch = request.GET.get('branch', None)
129 c.f_path = f_path
129 c.f_path = f_path
130 c.annotate = annotate
130 c.annotate = annotate
131 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
131 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
132 cur_rev = c.changeset.revision
132 cur_rev = c.changeset.revision
133
133
134 # prev link
134 # prev link
135 try:
135 try:
136 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
136 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
137 c.url_prev = url('files_home', repo_name=c.repo_name,
137 c.url_prev = url('files_home', repo_name=c.repo_name,
138 revision=prev_rev.raw_id, f_path=f_path)
138 revision=prev_rev.raw_id, f_path=f_path)
139 if c.branch:
139 if c.branch:
140 c.url_prev += '?branch=%s' % c.branch
140 c.url_prev += '?branch=%s' % c.branch
141 except (ChangesetDoesNotExistError, VCSError):
141 except (ChangesetDoesNotExistError, VCSError):
142 c.url_prev = '#'
142 c.url_prev = '#'
143
143
144 # next link
144 # next link
145 try:
145 try:
146 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
146 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
147 c.url_next = url('files_home', repo_name=c.repo_name,
147 c.url_next = url('files_home', repo_name=c.repo_name,
148 revision=next_rev.raw_id, f_path=f_path)
148 revision=next_rev.raw_id, f_path=f_path)
149 if c.branch:
149 if c.branch:
150 c.url_next += '?branch=%s' % c.branch
150 c.url_next += '?branch=%s' % c.branch
151 except (ChangesetDoesNotExistError, VCSError):
151 except (ChangesetDoesNotExistError, VCSError):
152 c.url_next = '#'
152 c.url_next = '#'
153
153
154 # files or dirs
154 # files or dirs
155 try:
155 try:
156 c.file = c.changeset.get_node(f_path)
156 c.file = c.changeset.get_node(f_path)
157
157
158 if c.file.is_file():
158 if c.file.is_file():
159 c.load_full_history = False
159 c.load_full_history = False
160 file_last_cs = c.file.last_changeset
160 file_last_cs = c.file.last_changeset
161 c.file_changeset = (c.changeset
161 c.file_changeset = (c.changeset
162 if c.changeset.revision < file_last_cs.revision
162 if c.changeset.revision < file_last_cs.revision
163 else file_last_cs)
163 else file_last_cs)
164 #determine if we're on branch head
164 #determine if we're on branch head
165 _branches = c.rhodecode_repo.branches
165 _branches = c.rhodecode_repo.branches
166 c.on_branch_head = revision in _branches.keys() + _branches.values()
166 c.on_branch_head = revision in _branches.keys() + _branches.values()
167 _hist = []
167 _hist = []
168 c.file_history = []
168 c.file_history = []
169 if c.load_full_history:
169 if c.load_full_history:
170 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
170 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
171
171
172 c.authors = []
172 c.authors = []
173 for a in set([x.author for x in _hist]):
173 for a in set([x.author for x in _hist]):
174 c.authors.append((h.email(a), h.person(a)))
174 c.authors.append((h.email(a), h.person(a)))
175 else:
175 else:
176 c.authors = c.file_history = []
176 c.authors = c.file_history = []
177 except RepositoryError, e:
177 except RepositoryError, e:
178 h.flash(str(e), category='error')
178 h.flash(str(e), category='error')
179 raise HTTPNotFound()
179 raise HTTPNotFound()
180
180
181 if request.environ.get('HTTP_X_PARTIAL_XHR'):
181 if request.environ.get('HTTP_X_PARTIAL_XHR'):
182 return render('files/files_ypjax.html')
182 return render('files/files_ypjax.html')
183
183
184 return render('files/files.html')
184 return render('files/files.html')
185
185
186 @LoginRequired()
186 @LoginRequired()
187 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
187 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
188 'repository.admin')
188 'repository.admin')
189 def history(self, repo_name, revision, f_path, annotate=False):
189 def history(self, repo_name, revision, f_path, annotate=False):
190 if request.environ.get('HTTP_X_PARTIAL_XHR'):
190 if request.environ.get('HTTP_X_PARTIAL_XHR'):
191 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
191 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
192 c.f_path = f_path
192 c.f_path = f_path
193 c.annotate = annotate
193 c.annotate = annotate
194 c.file = c.changeset.get_node(f_path)
194 c.file = c.changeset.get_node(f_path)
195 if c.file.is_file():
195 if c.file.is_file():
196 file_last_cs = c.file.last_changeset
196 file_last_cs = c.file.last_changeset
197 c.file_changeset = (c.changeset
197 c.file_changeset = (c.changeset
198 if c.changeset.revision < file_last_cs.revision
198 if c.changeset.revision < file_last_cs.revision
199 else file_last_cs)
199 else file_last_cs)
200 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
200 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
201 c.authors = []
201 c.authors = []
202 for a in set([x.author for x in _hist]):
202 for a in set([x.author for x in _hist]):
203 c.authors.append((h.email(a), h.person(a)))
203 c.authors.append((h.email(a), h.person(a)))
204 return render('files/files_history_box.html')
204 return render('files/files_history_box.html')
205
205
206 @LoginRequired()
206 @LoginRequired()
207 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
207 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
208 'repository.admin')
208 'repository.admin')
209 def rawfile(self, repo_name, revision, f_path):
209 def rawfile(self, repo_name, revision, f_path):
210 cs = self.__get_cs_or_redirect(revision, repo_name)
210 cs = self.__get_cs_or_redirect(revision, repo_name)
211 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
211 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
212
212
213 response.content_disposition = 'attachment; filename=%s' % \
213 response.content_disposition = 'attachment; filename=%s' % \
214 safe_str(f_path.split(Repository.url_sep())[-1])
214 safe_str(f_path.split(Repository.url_sep())[-1])
215
215
216 response.content_type = file_node.mimetype
216 response.content_type = file_node.mimetype
217 return file_node.content
217 return file_node.content
218
218
219 @LoginRequired()
219 @LoginRequired()
220 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
220 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
221 'repository.admin')
221 'repository.admin')
222 def raw(self, repo_name, revision, f_path):
222 def raw(self, repo_name, revision, f_path):
223 cs = self.__get_cs_or_redirect(revision, repo_name)
223 cs = self.__get_cs_or_redirect(revision, repo_name)
224 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
224 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
225
225
226 raw_mimetype_mapping = {
226 raw_mimetype_mapping = {
227 # map original mimetype to a mimetype used for "show as raw"
227 # map original mimetype to a mimetype used for "show as raw"
228 # you can also provide a content-disposition to override the
228 # you can also provide a content-disposition to override the
229 # default "attachment" disposition.
229 # default "attachment" disposition.
230 # orig_type: (new_type, new_dispo)
230 # orig_type: (new_type, new_dispo)
231
231
232 # show images inline:
232 # show images inline:
233 'image/x-icon': ('image/x-icon', 'inline'),
233 'image/x-icon': ('image/x-icon', 'inline'),
234 'image/png': ('image/png', 'inline'),
234 'image/png': ('image/png', 'inline'),
235 'image/gif': ('image/gif', 'inline'),
235 'image/gif': ('image/gif', 'inline'),
236 'image/jpeg': ('image/jpeg', 'inline'),
236 'image/jpeg': ('image/jpeg', 'inline'),
237 'image/svg+xml': ('image/svg+xml', 'inline'),
237 'image/svg+xml': ('image/svg+xml', 'inline'),
238 }
238 }
239
239
240 mimetype = file_node.mimetype
240 mimetype = file_node.mimetype
241 try:
241 try:
242 mimetype, dispo = raw_mimetype_mapping[mimetype]
242 mimetype, dispo = raw_mimetype_mapping[mimetype]
243 except KeyError:
243 except KeyError:
244 # we don't know anything special about this, handle it safely
244 # we don't know anything special about this, handle it safely
245 if file_node.is_binary:
245 if file_node.is_binary:
246 # do same as download raw for binary files
246 # do same as download raw for binary files
247 mimetype, dispo = 'application/octet-stream', 'attachment'
247 mimetype, dispo = 'application/octet-stream', 'attachment'
248 else:
248 else:
249 # do not just use the original mimetype, but force text/plain,
249 # do not just use the original mimetype, but force text/plain,
250 # otherwise it would serve text/html and that might be unsafe.
250 # otherwise it would serve text/html and that might be unsafe.
251 # Note: underlying vcs library fakes text/plain mimetype if the
251 # Note: underlying vcs library fakes text/plain mimetype if the
252 # mimetype can not be determined and it thinks it is not
252 # mimetype can not be determined and it thinks it is not
253 # binary.This might lead to erroneous text display in some
253 # binary.This might lead to erroneous text display in some
254 # cases, but helps in other cases, like with text files
254 # cases, but helps in other cases, like with text files
255 # without extension.
255 # without extension.
256 mimetype, dispo = 'text/plain', 'inline'
256 mimetype, dispo = 'text/plain', 'inline'
257
257
258 if dispo == 'attachment':
258 if dispo == 'attachment':
259 dispo = 'attachment; filename=%s' % \
259 dispo = 'attachment; filename=%s' % \
260 safe_str(f_path.split(os.sep)[-1])
260 safe_str(f_path.split(os.sep)[-1])
261
261
262 response.content_disposition = dispo
262 response.content_disposition = dispo
263 response.content_type = mimetype
263 response.content_type = mimetype
264 return file_node.content
264 return file_node.content
265
265
266 @LoginRequired()
266 @LoginRequired()
267 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
267 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
268 def edit(self, repo_name, revision, f_path):
268 def edit(self, repo_name, revision, f_path):
269 repo = c.rhodecode_db_repo
269 repo = c.rhodecode_db_repo
270 if repo.enable_locking and repo.locked[0]:
270 if repo.enable_locking and repo.locked[0]:
271 h.flash(_('This repository is has been locked by %s on %s')
271 h.flash(_('This repository is has been locked by %s on %s')
272 % (h.person_by_id(repo.locked[0]),
272 % (h.person_by_id(repo.locked[0]),
273 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
273 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
274 'warning')
274 'warning')
275 return redirect(h.url('files_home',
275 return redirect(h.url('files_home',
276 repo_name=repo_name, revision='tip'))
276 repo_name=repo_name, revision='tip'))
277
277
278 # check if revision is a branch identifier- basically we cannot
278 # check if revision is a branch identifier- basically we cannot
279 # create multiple heads via file editing
279 # create multiple heads via file editing
280 _branches = repo.scm_instance.branches
280 _branches = repo.scm_instance.branches
281 # check if revision is a branch name or branch hash
281 # check if revision is a branch name or branch hash
282 if revision not in _branches.keys() + _branches.values():
282 if revision not in _branches.keys() + _branches.values():
283 h.flash(_('You can only edit files with revision '
283 h.flash(_('You can only edit files with revision '
284 'being a valid branch '), category='warning')
284 'being a valid branch '), category='warning')
285 return redirect(h.url('files_home',
285 return redirect(h.url('files_home',
286 repo_name=repo_name, revision='tip',
286 repo_name=repo_name, revision='tip',
287 f_path=f_path))
287 f_path=f_path))
288
288
289 r_post = request.POST
289 r_post = request.POST
290
290
291 c.cs = self.__get_cs_or_redirect(revision, repo_name)
291 c.cs = self.__get_cs_or_redirect(revision, repo_name)
292 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
292 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
293
293
294 if c.file.is_binary:
294 if c.file.is_binary:
295 return redirect(url('files_home', repo_name=c.repo_name,
295 return redirect(url('files_home', repo_name=c.repo_name,
296 revision=c.cs.raw_id, f_path=f_path))
296 revision=c.cs.raw_id, f_path=f_path))
297 c.default_message = _('Edited file %s via RhodeCode') % (f_path)
297 c.default_message = _('Edited file %s via RhodeCode') % (f_path)
298 c.f_path = f_path
298 c.f_path = f_path
299
299
300 if r_post:
300 if r_post:
301
301
302 old_content = c.file.content
302 old_content = c.file.content
303 sl = old_content.splitlines(1)
303 sl = old_content.splitlines(1)
304 first_line = sl[0] if sl else ''
304 first_line = sl[0] if sl else ''
305 # modes: 0 - Unix, 1 - Mac, 2 - DOS
305 # modes: 0 - Unix, 1 - Mac, 2 - DOS
306 mode = detect_mode(first_line, 0)
306 mode = detect_mode(first_line, 0)
307 content = convert_line_endings(r_post.get('content', ''), mode)
307 content = convert_line_endings(r_post.get('content', ''), mode)
308
308
309 message = r_post.get('message') or c.default_message
309 message = r_post.get('message') or c.default_message
310 author = self.rhodecode_user.full_contact
310 author = self.rhodecode_user.full_contact
311
311
312 if content == old_content:
312 if content == old_content:
313 h.flash(_('No changes'), category='warning')
313 h.flash(_('No changes'), category='warning')
314 return redirect(url('changeset_home', repo_name=c.repo_name,
314 return redirect(url('changeset_home', repo_name=c.repo_name,
315 revision='tip'))
315 revision='tip'))
316 try:
316 try:
317 self.scm_model.commit_change(repo=c.rhodecode_repo,
317 self.scm_model.commit_change(repo=c.rhodecode_repo,
318 repo_name=repo_name, cs=c.cs,
318 repo_name=repo_name, cs=c.cs,
319 user=self.rhodecode_user.user_id,
319 user=self.rhodecode_user.user_id,
320 author=author, message=message,
320 author=author, message=message,
321 content=content, f_path=f_path)
321 content=content, f_path=f_path)
322 h.flash(_('Successfully committed to %s') % f_path,
322 h.flash(_('Successfully committed to %s') % f_path,
323 category='success')
323 category='success')
324
324
325 except Exception:
325 except Exception:
326 log.error(traceback.format_exc())
326 log.error(traceback.format_exc())
327 h.flash(_('Error occurred during commit'), category='error')
327 h.flash(_('Error occurred during commit'), category='error')
328 return redirect(url('changeset_home',
328 return redirect(url('changeset_home',
329 repo_name=c.repo_name, revision='tip'))
329 repo_name=c.repo_name, revision='tip'))
330
330
331 return render('files/files_edit.html')
331 return render('files/files_edit.html')
332
332
333 @LoginRequired()
333 @LoginRequired()
334 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
334 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
335 def add(self, repo_name, revision, f_path):
335 def add(self, repo_name, revision, f_path):
336
336
337 repo = Repository.get_by_repo_name(repo_name)
337 repo = Repository.get_by_repo_name(repo_name)
338 if repo.enable_locking and repo.locked[0]:
338 if repo.enable_locking and repo.locked[0]:
339 h.flash(_('This repository is has been locked by %s on %s')
339 h.flash(_('This repository is has been locked by %s on %s')
340 % (h.person_by_id(repo.locked[0]),
340 % (h.person_by_id(repo.locked[0]),
341 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
341 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
342 'warning')
342 'warning')
343 return redirect(h.url('files_home',
343 return redirect(h.url('files_home',
344 repo_name=repo_name, revision='tip'))
344 repo_name=repo_name, revision='tip'))
345
345
346 r_post = request.POST
346 r_post = request.POST
347 c.cs = self.__get_cs_or_redirect(revision, repo_name,
347 c.cs = self.__get_cs_or_redirect(revision, repo_name,
348 redirect_after=False)
348 redirect_after=False)
349 if c.cs is None:
349 if c.cs is None:
350 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
350 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
351 c.default_message = (_('Added file via RhodeCode'))
351 c.default_message = (_('Added file via RhodeCode'))
352 c.f_path = f_path
352 c.f_path = f_path
353
353
354 if r_post:
354 if r_post:
355 unix_mode = 0
355 unix_mode = 0
356 content = convert_line_endings(r_post.get('content', ''), unix_mode)
356 content = convert_line_endings(r_post.get('content', ''), unix_mode)
357
357
358 message = r_post.get('message') or c.default_message
358 message = r_post.get('message') or c.default_message
359 filename = r_post.get('filename')
359 filename = r_post.get('filename')
360 location = r_post.get('location', '')
360 location = r_post.get('location', '')
361 file_obj = r_post.get('upload_file', None)
361 file_obj = r_post.get('upload_file', None)
362
362
363 if file_obj is not None and hasattr(file_obj, 'filename'):
363 if file_obj is not None and hasattr(file_obj, 'filename'):
364 filename = file_obj.filename
364 filename = file_obj.filename
365 content = file_obj.file
365 content = file_obj.file
366
366
367 if not content:
367 if not content:
368 h.flash(_('No content'), category='warning')
368 h.flash(_('No content'), category='warning')
369 return redirect(url('changeset_home', repo_name=c.repo_name,
369 return redirect(url('changeset_home', repo_name=c.repo_name,
370 revision='tip'))
370 revision='tip'))
371 if not filename:
371 if not filename:
372 h.flash(_('No filename'), category='warning')
372 h.flash(_('No filename'), category='warning')
373 return redirect(url('changeset_home', repo_name=c.repo_name,
373 return redirect(url('changeset_home', repo_name=c.repo_name,
374 revision='tip'))
374 revision='tip'))
375 #strip all crap out of file, just leave the basename
375 #strip all crap out of file, just leave the basename
376 filename = os.path.basename(filename)
376 filename = os.path.basename(filename)
377 node_path = os.path.join(location, filename)
377 node_path = os.path.join(location, filename)
378 author = self.rhodecode_user.full_contact
378 author = self.rhodecode_user.full_contact
379
379
380 try:
380 try:
381 nodes = {
381 nodes = {
382 node_path: {
382 node_path: {
383 'content': content
383 'content': content
384 }
384 }
385 }
385 }
386 self.scm_model.create_nodes(
386 self.scm_model.create_nodes(
387 user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
387 user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
388 message=message,
388 message=message,
389 nodes=nodes,
389 nodes=nodes,
390 parent_cs=c.cs,
390 parent_cs=c.cs,
391 author=author,
391 author=author,
392 )
392 )
393
393
394 h.flash(_('Successfully committed to %s') % node_path,
394 h.flash(_('Successfully committed to %s') % node_path,
395 category='success')
395 category='success')
396 except NonRelativePathError, e:
396 except NonRelativePathError, e:
397 h.flash(_('Location must be relative path and must not '
397 h.flash(_('Location must be relative path and must not '
398 'contain .. in path'), category='warning')
398 'contain .. in path'), category='warning')
399 return redirect(url('changeset_home', repo_name=c.repo_name,
399 return redirect(url('changeset_home', repo_name=c.repo_name,
400 revision='tip'))
400 revision='tip'))
401 except (NodeError, NodeAlreadyExistsError), e:
401 except (NodeError, NodeAlreadyExistsError), e:
402 h.flash(_(e), category='error')
402 h.flash(_(e), category='error')
403 except Exception:
403 except Exception:
404 log.error(traceback.format_exc())
404 log.error(traceback.format_exc())
405 h.flash(_('Error occurred during commit'), category='error')
405 h.flash(_('Error occurred during commit'), category='error')
406 return redirect(url('changeset_home',
406 return redirect(url('changeset_home',
407 repo_name=c.repo_name, revision='tip'))
407 repo_name=c.repo_name, revision='tip'))
408
408
409 return render('files/files_add.html')
409 return render('files/files_add.html')
410
410
411 @LoginRequired()
411 @LoginRequired()
412 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
412 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
413 'repository.admin')
413 'repository.admin')
414 def archivefile(self, repo_name, fname):
414 def archivefile(self, repo_name, fname):
415
415
416 fileformat = None
416 fileformat = None
417 revision = None
417 revision = None
418 ext = None
418 ext = None
419 subrepos = request.GET.get('subrepos') == 'true'
419 subrepos = request.GET.get('subrepos') == 'true'
420
420
421 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
421 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
422 archive_spec = fname.split(ext_data[1])
422 archive_spec = fname.split(ext_data[1])
423 if len(archive_spec) == 2 and archive_spec[1] == '':
423 if len(archive_spec) == 2 and archive_spec[1] == '':
424 fileformat = a_type or ext_data[1]
424 fileformat = a_type or ext_data[1]
425 revision = archive_spec[0]
425 revision = archive_spec[0]
426 ext = ext_data[1]
426 ext = ext_data[1]
427
427
428 try:
428 try:
429 dbrepo = RepoModel().get_by_repo_name(repo_name)
429 dbrepo = RepoModel().get_by_repo_name(repo_name)
430 if not dbrepo.enable_downloads:
430 if not dbrepo.enable_downloads:
431 return _('Downloads disabled')
431 return _('Downloads disabled')
432
432
433 if c.rhodecode_repo.alias == 'hg':
433 if c.rhodecode_repo.alias == 'hg':
434 # patch and reset hooks section of UI config to not run any
434 # patch and reset hooks section of UI config to not run any
435 # hooks on fetching archives with subrepos
435 # hooks on fetching archives with subrepos
436 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
436 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
437 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
437 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
438
438
439 cs = c.rhodecode_repo.get_changeset(revision)
439 cs = c.rhodecode_repo.get_changeset(revision)
440 content_type = settings.ARCHIVE_SPECS[fileformat][0]
440 content_type = settings.ARCHIVE_SPECS[fileformat][0]
441 except ChangesetDoesNotExistError:
441 except ChangesetDoesNotExistError:
442 return _('Unknown revision %s') % revision
442 return _('Unknown revision %s') % revision
443 except EmptyRepositoryError:
443 except EmptyRepositoryError:
444 return _('Empty repository')
444 return _('Empty repository')
445 except (ImproperArchiveTypeError, KeyError):
445 except (ImproperArchiveTypeError, KeyError):
446 return _('Unknown archive type')
446 return _('Unknown archive type')
447 # archive cache
447 # archive cache
448 from rhodecode import CONFIG
448 from rhodecode import CONFIG
449 rev_name = cs.raw_id[:12]
449 rev_name = cs.raw_id[:12]
450 archive_name = '%s-%s%s' % (safe_str(repo_name.replace('/', '_')),
450 archive_name = '%s-%s%s' % (safe_str(repo_name.replace('/', '_')),
451 safe_str(rev_name), ext)
451 safe_str(rev_name), ext)
452
452
453 use_cached_archive = False # defines if we use cached version of archive
453 use_cached_archive = False # defines if we use cached version of archive
454 archive_cache_enabled = CONFIG.get('archive_cache_dir')
454 archive_cache_enabled = CONFIG.get('archive_cache_dir')
455 if not subrepos and archive_cache_enabled:
455 if not subrepos and archive_cache_enabled:
456 #check if we it's ok to write
456 #check if we it's ok to write
457 if not os.path.isdir(CONFIG['archive_cache_dir']):
457 if not os.path.isdir(CONFIG['archive_cache_dir']):
458 os.makedirs(CONFIG['archive_cache_dir'])
458 os.makedirs(CONFIG['archive_cache_dir'])
459 cached_archive_path = os.path.join(CONFIG['archive_cache_dir'], archive_name)
459 cached_archive_path = os.path.join(CONFIG['archive_cache_dir'], archive_name)
460 if os.path.isfile(cached_archive_path):
460 if os.path.isfile(cached_archive_path):
461 log.debug('Found cached archive in %s' % cached_archive_path)
461 log.debug('Found cached archive in %s' % cached_archive_path)
462 fd, archive = None, cached_archive_path
462 fd, archive = None, cached_archive_path
463 use_cached_archive = True
463 use_cached_archive = True
464 else:
464 else:
465 log.debug('Archive %s is not yet cached' % (archive_name))
465 log.debug('Archive %s is not yet cached' % (archive_name))
466
466
467 if not use_cached_archive:
467 if not use_cached_archive:
468 #generate new archive
468 #generate new archive
469 try:
469 try:
470 fd, archive = tempfile.mkstemp()
470 fd, archive = tempfile.mkstemp()
471 t = open(archive, 'wb')
471 t = open(archive, 'wb')
472 log.debug('Creating new temp archive in %s' % archive)
472 log.debug('Creating new temp archive in %s' % archive)
473 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
473 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
474 if archive_cache_enabled:
474 if archive_cache_enabled:
475 #if we generated the archive and use cache rename that
475 #if we generated the archive and use cache rename that
476 log.debug('Storing new archive in %s' % cached_archive_path)
476 log.debug('Storing new archive in %s' % cached_archive_path)
477 shutil.move(archive, cached_archive_path)
477 shutil.move(archive, cached_archive_path)
478 archive = cached_archive_path
478 archive = cached_archive_path
479 finally:
479 finally:
480 t.close()
480 t.close()
481
481
482 def get_chunked_archive(archive):
482 def get_chunked_archive(archive):
483 stream = open(archive, 'rb')
483 stream = open(archive, 'rb')
484 while True:
484 while True:
485 data = stream.read(16 * 1024)
485 data = stream.read(16 * 1024)
486 if not data:
486 if not data:
487 stream.close()
487 stream.close()
488 if fd: # fd means we used temporary file
488 if fd: # fd means we used temporary file
489 os.close(fd)
489 os.close(fd)
490 if not archive_cache_enabled:
490 if not archive_cache_enabled:
491 log.debug('Destroing temp archive %s' % archive)
491 log.debug('Destroing temp archive %s' % archive)
492 os.remove(archive)
492 os.remove(archive)
493 break
493 break
494 yield data
494 yield data
495
495 # store download action
496 action_logger(user=c.rhodecode_user,
497 action='user_downloaded_archive:%s' % (archive_name),
498 repo=repo_name, ipaddr=self.ip_addr, commit=True)
496 response.content_disposition = str('attachment; filename=%s' % (archive_name))
499 response.content_disposition = str('attachment; filename=%s' % (archive_name))
497 response.content_type = str(content_type)
500 response.content_type = str(content_type)
498 return get_chunked_archive(archive)
501 return get_chunked_archive(archive)
499
502
500 @LoginRequired()
503 @LoginRequired()
501 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
504 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
502 'repository.admin')
505 'repository.admin')
503 def diff(self, repo_name, f_path):
506 def diff(self, repo_name, f_path):
504 ignore_whitespace = request.GET.get('ignorews') == '1'
507 ignore_whitespace = request.GET.get('ignorews') == '1'
505 line_context = request.GET.get('context', 3)
508 line_context = request.GET.get('context', 3)
506 diff1 = request.GET.get('diff1', '')
509 diff1 = request.GET.get('diff1', '')
507 diff2 = request.GET.get('diff2', '')
510 diff2 = request.GET.get('diff2', '')
508 c.action = request.GET.get('diff')
511 c.action = request.GET.get('diff')
509 c.no_changes = diff1 == diff2
512 c.no_changes = diff1 == diff2
510 c.f_path = f_path
513 c.f_path = f_path
511 c.big_diff = False
514 c.big_diff = False
512 c.anchor_url = anchor_url
515 c.anchor_url = anchor_url
513 c.ignorews_url = _ignorews_url
516 c.ignorews_url = _ignorews_url
514 c.context_url = _context_url
517 c.context_url = _context_url
515 c.changes = OrderedDict()
518 c.changes = OrderedDict()
516 c.changes[diff2] = []
519 c.changes[diff2] = []
517
520
518 #special case if we want a show rev only, it's impl here
521 #special case if we want a show rev only, it's impl here
519 #to reduce JS and callbacks
522 #to reduce JS and callbacks
520
523
521 if request.GET.get('show_rev'):
524 if request.GET.get('show_rev'):
522 if str2bool(request.GET.get('annotate', 'False')):
525 if str2bool(request.GET.get('annotate', 'False')):
523 _url = url('files_annotate_home', repo_name=c.repo_name,
526 _url = url('files_annotate_home', repo_name=c.repo_name,
524 revision=diff1, f_path=c.f_path)
527 revision=diff1, f_path=c.f_path)
525 else:
528 else:
526 _url = url('files_home', repo_name=c.repo_name,
529 _url = url('files_home', repo_name=c.repo_name,
527 revision=diff1, f_path=c.f_path)
530 revision=diff1, f_path=c.f_path)
528
531
529 return redirect(_url)
532 return redirect(_url)
530 try:
533 try:
531 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
534 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
532 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
535 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
533 try:
536 try:
534 node1 = c.changeset_1.get_node(f_path)
537 node1 = c.changeset_1.get_node(f_path)
535 if node1.is_dir():
538 if node1.is_dir():
536 raise NodeError('%s path is a %s not a file'
539 raise NodeError('%s path is a %s not a file'
537 % (node1, type(node1)))
540 % (node1, type(node1)))
538 except NodeDoesNotExistError:
541 except NodeDoesNotExistError:
539 c.changeset_1 = EmptyChangeset(cs=diff1,
542 c.changeset_1 = EmptyChangeset(cs=diff1,
540 revision=c.changeset_1.revision,
543 revision=c.changeset_1.revision,
541 repo=c.rhodecode_repo)
544 repo=c.rhodecode_repo)
542 node1 = FileNode(f_path, '', changeset=c.changeset_1)
545 node1 = FileNode(f_path, '', changeset=c.changeset_1)
543 else:
546 else:
544 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
547 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
545 node1 = FileNode(f_path, '', changeset=c.changeset_1)
548 node1 = FileNode(f_path, '', changeset=c.changeset_1)
546
549
547 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
550 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
548 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
551 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
549 try:
552 try:
550 node2 = c.changeset_2.get_node(f_path)
553 node2 = c.changeset_2.get_node(f_path)
551 if node2.is_dir():
554 if node2.is_dir():
552 raise NodeError('%s path is a %s not a file'
555 raise NodeError('%s path is a %s not a file'
553 % (node2, type(node2)))
556 % (node2, type(node2)))
554 except NodeDoesNotExistError:
557 except NodeDoesNotExistError:
555 c.changeset_2 = EmptyChangeset(cs=diff2,
558 c.changeset_2 = EmptyChangeset(cs=diff2,
556 revision=c.changeset_2.revision,
559 revision=c.changeset_2.revision,
557 repo=c.rhodecode_repo)
560 repo=c.rhodecode_repo)
558 node2 = FileNode(f_path, '', changeset=c.changeset_2)
561 node2 = FileNode(f_path, '', changeset=c.changeset_2)
559 else:
562 else:
560 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
563 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
561 node2 = FileNode(f_path, '', changeset=c.changeset_2)
564 node2 = FileNode(f_path, '', changeset=c.changeset_2)
562 except (RepositoryError, NodeError):
565 except (RepositoryError, NodeError):
563 log.error(traceback.format_exc())
566 log.error(traceback.format_exc())
564 return redirect(url('files_home', repo_name=c.repo_name,
567 return redirect(url('files_home', repo_name=c.repo_name,
565 f_path=f_path))
568 f_path=f_path))
566
569
567 if c.action == 'download':
570 if c.action == 'download':
568 _diff = diffs.get_gitdiff(node1, node2,
571 _diff = diffs.get_gitdiff(node1, node2,
569 ignore_whitespace=ignore_whitespace,
572 ignore_whitespace=ignore_whitespace,
570 context=line_context)
573 context=line_context)
571 diff = diffs.DiffProcessor(_diff, format='gitdiff')
574 diff = diffs.DiffProcessor(_diff, format='gitdiff')
572
575
573 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
576 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
574 response.content_type = 'text/plain'
577 response.content_type = 'text/plain'
575 response.content_disposition = (
578 response.content_disposition = (
576 'attachment; filename=%s' % diff_name
579 'attachment; filename=%s' % diff_name
577 )
580 )
578 return diff.as_raw()
581 return diff.as_raw()
579
582
580 elif c.action == 'raw':
583 elif c.action == 'raw':
581 _diff = diffs.get_gitdiff(node1, node2,
584 _diff = diffs.get_gitdiff(node1, node2,
582 ignore_whitespace=ignore_whitespace,
585 ignore_whitespace=ignore_whitespace,
583 context=line_context)
586 context=line_context)
584 diff = diffs.DiffProcessor(_diff, format='gitdiff')
587 diff = diffs.DiffProcessor(_diff, format='gitdiff')
585 response.content_type = 'text/plain'
588 response.content_type = 'text/plain'
586 return diff.as_raw()
589 return diff.as_raw()
587
590
588 else:
591 else:
589 fid = h.FID(diff2, node2.path)
592 fid = h.FID(diff2, node2.path)
590 line_context_lcl = get_line_ctx(fid, request.GET)
593 line_context_lcl = get_line_ctx(fid, request.GET)
591 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
594 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
592
595
593 lim = request.GET.get('fulldiff') or self.cut_off_limit
596 lim = request.GET.get('fulldiff') or self.cut_off_limit
594 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
597 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
595 filenode_new=node2,
598 filenode_new=node2,
596 cut_off_limit=lim,
599 cut_off_limit=lim,
597 ignore_whitespace=ign_whitespace_lcl,
600 ignore_whitespace=ign_whitespace_lcl,
598 line_context=line_context_lcl,
601 line_context=line_context_lcl,
599 enable_comments=False)
602 enable_comments=False)
600 op = ''
603 op = ''
601 filename = node1.path
604 filename = node1.path
602 cs_changes = {
605 cs_changes = {
603 'fid': [cs1, cs2, op, filename, diff, st]
606 'fid': [cs1, cs2, op, filename, diff, st]
604 }
607 }
605 c.changes = cs_changes
608 c.changes = cs_changes
606
609
607 return render('files/file_diff.html')
610 return render('files/file_diff.html')
608
611
609 def _get_node_history(self, cs, f_path, changesets=None):
612 def _get_node_history(self, cs, f_path, changesets=None):
610 """
613 """
611 get changesets history for given node
614 get changesets history for given node
612
615
613 :param cs: changeset to calculate history
616 :param cs: changeset to calculate history
614 :param f_path: path for node to calculate history for
617 :param f_path: path for node to calculate history for
615 :param changesets: if passed don't calculate history and take
618 :param changesets: if passed don't calculate history and take
616 changesets defined in this list
619 changesets defined in this list
617 """
620 """
618 # calculate history based on tip
621 # calculate history based on tip
619 tip_cs = c.rhodecode_repo.get_changeset()
622 tip_cs = c.rhodecode_repo.get_changeset()
620 if changesets is None:
623 if changesets is None:
621 try:
624 try:
622 changesets = tip_cs.get_file_history(f_path)
625 changesets = tip_cs.get_file_history(f_path)
623 except (NodeDoesNotExistError, ChangesetError):
626 except (NodeDoesNotExistError, ChangesetError):
624 #this node is not present at tip !
627 #this node is not present at tip !
625 changesets = cs.get_file_history(f_path)
628 changesets = cs.get_file_history(f_path)
626 hist_l = []
629 hist_l = []
627
630
628 changesets_group = ([], _("Changesets"))
631 changesets_group = ([], _("Changesets"))
629 branches_group = ([], _("Branches"))
632 branches_group = ([], _("Branches"))
630 tags_group = ([], _("Tags"))
633 tags_group = ([], _("Tags"))
631 _hg = cs.repository.alias == 'hg'
634 _hg = cs.repository.alias == 'hg'
632 for chs in changesets:
635 for chs in changesets:
633 #_branch = '(%s)' % chs.branch if _hg else ''
636 #_branch = '(%s)' % chs.branch if _hg else ''
634 _branch = chs.branch
637 _branch = chs.branch
635 n_desc = 'r%s:%s (%s)' % (chs.revision, chs.short_id, _branch)
638 n_desc = 'r%s:%s (%s)' % (chs.revision, chs.short_id, _branch)
636 changesets_group[0].append((chs.raw_id, n_desc,))
639 changesets_group[0].append((chs.raw_id, n_desc,))
637 hist_l.append(changesets_group)
640 hist_l.append(changesets_group)
638
641
639 for name, chs in c.rhodecode_repo.branches.items():
642 for name, chs in c.rhodecode_repo.branches.items():
640 branches_group[0].append((chs, name),)
643 branches_group[0].append((chs, name),)
641 hist_l.append(branches_group)
644 hist_l.append(branches_group)
642
645
643 for name, chs in c.rhodecode_repo.tags.items():
646 for name, chs in c.rhodecode_repo.tags.items():
644 tags_group[0].append((chs, name),)
647 tags_group[0].append((chs, name),)
645 hist_l.append(tags_group)
648 hist_l.append(tags_group)
646
649
647 return hist_l, changesets
650 return hist_l, changesets
648
651
649 @LoginRequired()
652 @LoginRequired()
650 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
653 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
651 'repository.admin')
654 'repository.admin')
652 @jsonify
655 @jsonify
653 def nodelist(self, repo_name, revision, f_path):
656 def nodelist(self, repo_name, revision, f_path):
654 if request.environ.get('HTTP_X_PARTIAL_XHR'):
657 if request.environ.get('HTTP_X_PARTIAL_XHR'):
655 cs = self.__get_cs_or_redirect(revision, repo_name)
658 cs = self.__get_cs_or_redirect(revision, repo_name)
656 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
659 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
657 flat=False)
660 flat=False)
658 return {'nodes': _d + _f}
661 return {'nodes': _d + _f}
@@ -1,1371 +1,1377 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 import math
10 import math
11 import logging
11 import logging
12 import re
12 import re
13 import urlparse
13 import urlparse
14 import textwrap
14 import textwrap
15
15
16 from datetime import datetime
16 from datetime import datetime
17 from pygments.formatters.html import HtmlFormatter
17 from pygments.formatters.html import HtmlFormatter
18 from pygments import highlight as code_highlight
18 from pygments import highlight as code_highlight
19 from pylons import url, request, config
19 from pylons import url, request, config
20 from pylons.i18n.translation import _, ungettext
20 from pylons.i18n.translation import _, ungettext
21 from hashlib import md5
21 from hashlib import md5
22
22
23 from webhelpers.html import literal, HTML, escape
23 from webhelpers.html import literal, HTML, escape
24 from webhelpers.html.tools import *
24 from webhelpers.html.tools import *
25 from webhelpers.html.builder import make_tag
25 from webhelpers.html.builder import make_tag
26 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
26 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
27 end_form, file, form, hidden, image, javascript_link, link_to, \
27 end_form, file, form, hidden, image, javascript_link, link_to, \
28 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
28 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
29 submit, text, password, textarea, title, ul, xml_declaration, radio
29 submit, text, password, textarea, title, ul, xml_declaration, radio
30 from webhelpers.html.tools import auto_link, button_to, highlight, \
30 from webhelpers.html.tools import auto_link, button_to, highlight, \
31 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
31 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
32 from webhelpers.number import format_byte_size, format_bit_size
32 from webhelpers.number import format_byte_size, format_bit_size
33 from webhelpers.pylonslib import Flash as _Flash
33 from webhelpers.pylonslib import Flash as _Flash
34 from webhelpers.pylonslib.secure_form import secure_form
34 from webhelpers.pylonslib.secure_form import secure_form
35 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
35 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
36 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
36 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
37 replace_whitespace, urlify, truncate, wrap_paragraphs
37 replace_whitespace, urlify, truncate, wrap_paragraphs
38 from webhelpers.date import time_ago_in_words
38 from webhelpers.date import time_ago_in_words
39 from webhelpers.paginate import Page as _Page
39 from webhelpers.paginate import Page as _Page
40 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
40 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
41 convert_boolean_attrs, NotGiven, _make_safe_id_component
41 convert_boolean_attrs, NotGiven, _make_safe_id_component
42
42
43 from rhodecode.lib.annotate import annotate_highlight
43 from rhodecode.lib.annotate import annotate_highlight
44 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
44 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
45 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
45 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
46 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict,\
46 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict,\
47 safe_int
47 safe_int
48 from rhodecode.lib.markup_renderer import MarkupRenderer
48 from rhodecode.lib.markup_renderer import MarkupRenderer
49 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
49 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
50 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
50 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
51 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
51 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
52 from rhodecode.model.changeset_status import ChangesetStatusModel
52 from rhodecode.model.changeset_status import ChangesetStatusModel
53 from rhodecode.model.db import URL_SEP, Permission
53 from rhodecode.model.db import URL_SEP, Permission
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 html_escape_table = {
58 html_escape_table = {
59 "&": "&amp;",
59 "&": "&amp;",
60 '"': "&quot;",
60 '"': "&quot;",
61 "'": "&apos;",
61 "'": "&apos;",
62 ">": "&gt;",
62 ">": "&gt;",
63 "<": "&lt;",
63 "<": "&lt;",
64 }
64 }
65
65
66
66
67 def html_escape(text):
67 def html_escape(text):
68 """Produce entities within text."""
68 """Produce entities within text."""
69 return "".join(html_escape_table.get(c, c) for c in text)
69 return "".join(html_escape_table.get(c, c) for c in text)
70
70
71
71
72 def shorter(text, size=20):
72 def shorter(text, size=20):
73 postfix = '...'
73 postfix = '...'
74 if len(text) > size:
74 if len(text) > size:
75 return text[:size - len(postfix)] + postfix
75 return text[:size - len(postfix)] + postfix
76 return text
76 return text
77
77
78
78
79 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
79 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
80 """
80 """
81 Reset button
81 Reset button
82 """
82 """
83 _set_input_attrs(attrs, type, name, value)
83 _set_input_attrs(attrs, type, name, value)
84 _set_id_attr(attrs, id, name)
84 _set_id_attr(attrs, id, name)
85 convert_boolean_attrs(attrs, ["disabled"])
85 convert_boolean_attrs(attrs, ["disabled"])
86 return HTML.input(**attrs)
86 return HTML.input(**attrs)
87
87
88 reset = _reset
88 reset = _reset
89 safeid = _make_safe_id_component
89 safeid = _make_safe_id_component
90
90
91
91
92 def FID(raw_id, path):
92 def FID(raw_id, path):
93 """
93 """
94 Creates a uniqe ID for filenode based on it's hash of path and revision
94 Creates a uniqe ID for filenode based on it's hash of path and revision
95 it's safe to use in urls
95 it's safe to use in urls
96
96
97 :param raw_id:
97 :param raw_id:
98 :param path:
98 :param path:
99 """
99 """
100
100
101 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
101 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
102
102
103
103
104 def get_token():
104 def get_token():
105 """Return the current authentication token, creating one if one doesn't
105 """Return the current authentication token, creating one if one doesn't
106 already exist.
106 already exist.
107 """
107 """
108 token_key = "_authentication_token"
108 token_key = "_authentication_token"
109 from pylons import session
109 from pylons import session
110 if not token_key in session:
110 if not token_key in session:
111 try:
111 try:
112 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
112 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
113 except AttributeError: # Python < 2.4
113 except AttributeError: # Python < 2.4
114 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
114 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
115 session[token_key] = token
115 session[token_key] = token
116 if hasattr(session, 'save'):
116 if hasattr(session, 'save'):
117 session.save()
117 session.save()
118 return session[token_key]
118 return session[token_key]
119
119
120
120
121 class _GetError(object):
121 class _GetError(object):
122 """Get error from form_errors, and represent it as span wrapped error
122 """Get error from form_errors, and represent it as span wrapped error
123 message
123 message
124
124
125 :param field_name: field to fetch errors for
125 :param field_name: field to fetch errors for
126 :param form_errors: form errors dict
126 :param form_errors: form errors dict
127 """
127 """
128
128
129 def __call__(self, field_name, form_errors):
129 def __call__(self, field_name, form_errors):
130 tmpl = """<span class="error_msg">%s</span>"""
130 tmpl = """<span class="error_msg">%s</span>"""
131 if form_errors and field_name in form_errors:
131 if form_errors and field_name in form_errors:
132 return literal(tmpl % form_errors.get(field_name))
132 return literal(tmpl % form_errors.get(field_name))
133
133
134 get_error = _GetError()
134 get_error = _GetError()
135
135
136
136
137 class _ToolTip(object):
137 class _ToolTip(object):
138
138
139 def __call__(self, tooltip_title, trim_at=50):
139 def __call__(self, tooltip_title, trim_at=50):
140 """
140 """
141 Special function just to wrap our text into nice formatted
141 Special function just to wrap our text into nice formatted
142 autowrapped text
142 autowrapped text
143
143
144 :param tooltip_title:
144 :param tooltip_title:
145 """
145 """
146 tooltip_title = escape(tooltip_title)
146 tooltip_title = escape(tooltip_title)
147 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
147 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
148 return tooltip_title
148 return tooltip_title
149 tooltip = _ToolTip()
149 tooltip = _ToolTip()
150
150
151
151
152 class _FilesBreadCrumbs(object):
152 class _FilesBreadCrumbs(object):
153
153
154 def __call__(self, repo_name, rev, paths):
154 def __call__(self, repo_name, rev, paths):
155 if isinstance(paths, str):
155 if isinstance(paths, str):
156 paths = safe_unicode(paths)
156 paths = safe_unicode(paths)
157 url_l = [link_to(repo_name, url('files_home',
157 url_l = [link_to(repo_name, url('files_home',
158 repo_name=repo_name,
158 repo_name=repo_name,
159 revision=rev, f_path=''),
159 revision=rev, f_path=''),
160 class_='ypjax-link')]
160 class_='ypjax-link')]
161 paths_l = paths.split('/')
161 paths_l = paths.split('/')
162 for cnt, p in enumerate(paths_l):
162 for cnt, p in enumerate(paths_l):
163 if p != '':
163 if p != '':
164 url_l.append(link_to(p,
164 url_l.append(link_to(p,
165 url('files_home',
165 url('files_home',
166 repo_name=repo_name,
166 repo_name=repo_name,
167 revision=rev,
167 revision=rev,
168 f_path='/'.join(paths_l[:cnt + 1])
168 f_path='/'.join(paths_l[:cnt + 1])
169 ),
169 ),
170 class_='ypjax-link'
170 class_='ypjax-link'
171 )
171 )
172 )
172 )
173
173
174 return literal('/'.join(url_l))
174 return literal('/'.join(url_l))
175
175
176 files_breadcrumbs = _FilesBreadCrumbs()
176 files_breadcrumbs = _FilesBreadCrumbs()
177
177
178
178
179 class CodeHtmlFormatter(HtmlFormatter):
179 class CodeHtmlFormatter(HtmlFormatter):
180 """
180 """
181 My code Html Formatter for source codes
181 My code Html Formatter for source codes
182 """
182 """
183
183
184 def wrap(self, source, outfile):
184 def wrap(self, source, outfile):
185 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
185 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
186
186
187 def _wrap_code(self, source):
187 def _wrap_code(self, source):
188 for cnt, it in enumerate(source):
188 for cnt, it in enumerate(source):
189 i, t = it
189 i, t = it
190 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
190 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
191 yield i, t
191 yield i, t
192
192
193 def _wrap_tablelinenos(self, inner):
193 def _wrap_tablelinenos(self, inner):
194 dummyoutfile = StringIO.StringIO()
194 dummyoutfile = StringIO.StringIO()
195 lncount = 0
195 lncount = 0
196 for t, line in inner:
196 for t, line in inner:
197 if t:
197 if t:
198 lncount += 1
198 lncount += 1
199 dummyoutfile.write(line)
199 dummyoutfile.write(line)
200
200
201 fl = self.linenostart
201 fl = self.linenostart
202 mw = len(str(lncount + fl - 1))
202 mw = len(str(lncount + fl - 1))
203 sp = self.linenospecial
203 sp = self.linenospecial
204 st = self.linenostep
204 st = self.linenostep
205 la = self.lineanchors
205 la = self.lineanchors
206 aln = self.anchorlinenos
206 aln = self.anchorlinenos
207 nocls = self.noclasses
207 nocls = self.noclasses
208 if sp:
208 if sp:
209 lines = []
209 lines = []
210
210
211 for i in range(fl, fl + lncount):
211 for i in range(fl, fl + lncount):
212 if i % st == 0:
212 if i % st == 0:
213 if i % sp == 0:
213 if i % sp == 0:
214 if aln:
214 if aln:
215 lines.append('<a href="#%s%d" class="special">%*d</a>' %
215 lines.append('<a href="#%s%d" class="special">%*d</a>' %
216 (la, i, mw, i))
216 (la, i, mw, i))
217 else:
217 else:
218 lines.append('<span class="special">%*d</span>' % (mw, i))
218 lines.append('<span class="special">%*d</span>' % (mw, i))
219 else:
219 else:
220 if aln:
220 if aln:
221 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
221 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
222 else:
222 else:
223 lines.append('%*d' % (mw, i))
223 lines.append('%*d' % (mw, i))
224 else:
224 else:
225 lines.append('')
225 lines.append('')
226 ls = '\n'.join(lines)
226 ls = '\n'.join(lines)
227 else:
227 else:
228 lines = []
228 lines = []
229 for i in range(fl, fl + lncount):
229 for i in range(fl, fl + lncount):
230 if i % st == 0:
230 if i % st == 0:
231 if aln:
231 if aln:
232 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
232 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
233 else:
233 else:
234 lines.append('%*d' % (mw, i))
234 lines.append('%*d' % (mw, i))
235 else:
235 else:
236 lines.append('')
236 lines.append('')
237 ls = '\n'.join(lines)
237 ls = '\n'.join(lines)
238
238
239 # in case you wonder about the seemingly redundant <div> here: since the
239 # in case you wonder about the seemingly redundant <div> here: since the
240 # content in the other cell also is wrapped in a div, some browsers in
240 # content in the other cell also is wrapped in a div, some browsers in
241 # some configurations seem to mess up the formatting...
241 # some configurations seem to mess up the formatting...
242 if nocls:
242 if nocls:
243 yield 0, ('<table class="%stable">' % self.cssclass +
243 yield 0, ('<table class="%stable">' % self.cssclass +
244 '<tr><td><div class="linenodiv" '
244 '<tr><td><div class="linenodiv" '
245 'style="background-color: #f0f0f0; padding-right: 10px">'
245 'style="background-color: #f0f0f0; padding-right: 10px">'
246 '<pre style="line-height: 125%">' +
246 '<pre style="line-height: 125%">' +
247 ls + '</pre></div></td><td id="hlcode" class="code">')
247 ls + '</pre></div></td><td id="hlcode" class="code">')
248 else:
248 else:
249 yield 0, ('<table class="%stable">' % self.cssclass +
249 yield 0, ('<table class="%stable">' % self.cssclass +
250 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
250 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
251 ls + '</pre></div></td><td id="hlcode" class="code">')
251 ls + '</pre></div></td><td id="hlcode" class="code">')
252 yield 0, dummyoutfile.getvalue()
252 yield 0, dummyoutfile.getvalue()
253 yield 0, '</td></tr></table>'
253 yield 0, '</td></tr></table>'
254
254
255
255
256 def pygmentize(filenode, **kwargs):
256 def pygmentize(filenode, **kwargs):
257 """
257 """
258 pygmentize function using pygments
258 pygmentize function using pygments
259
259
260 :param filenode:
260 :param filenode:
261 """
261 """
262 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
262 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
263 return literal(code_highlight(filenode.content, lexer,
263 return literal(code_highlight(filenode.content, lexer,
264 CodeHtmlFormatter(**kwargs)))
264 CodeHtmlFormatter(**kwargs)))
265
265
266
266
267 def pygmentize_annotation(repo_name, filenode, **kwargs):
267 def pygmentize_annotation(repo_name, filenode, **kwargs):
268 """
268 """
269 pygmentize function for annotation
269 pygmentize function for annotation
270
270
271 :param filenode:
271 :param filenode:
272 """
272 """
273
273
274 color_dict = {}
274 color_dict = {}
275
275
276 def gen_color(n=10000):
276 def gen_color(n=10000):
277 """generator for getting n of evenly distributed colors using
277 """generator for getting n of evenly distributed colors using
278 hsv color and golden ratio. It always return same order of colors
278 hsv color and golden ratio. It always return same order of colors
279
279
280 :returns: RGB tuple
280 :returns: RGB tuple
281 """
281 """
282
282
283 def hsv_to_rgb(h, s, v):
283 def hsv_to_rgb(h, s, v):
284 if s == 0.0:
284 if s == 0.0:
285 return v, v, v
285 return v, v, v
286 i = int(h * 6.0) # XXX assume int() truncates!
286 i = int(h * 6.0) # XXX assume int() truncates!
287 f = (h * 6.0) - i
287 f = (h * 6.0) - i
288 p = v * (1.0 - s)
288 p = v * (1.0 - s)
289 q = v * (1.0 - s * f)
289 q = v * (1.0 - s * f)
290 t = v * (1.0 - s * (1.0 - f))
290 t = v * (1.0 - s * (1.0 - f))
291 i = i % 6
291 i = i % 6
292 if i == 0:
292 if i == 0:
293 return v, t, p
293 return v, t, p
294 if i == 1:
294 if i == 1:
295 return q, v, p
295 return q, v, p
296 if i == 2:
296 if i == 2:
297 return p, v, t
297 return p, v, t
298 if i == 3:
298 if i == 3:
299 return p, q, v
299 return p, q, v
300 if i == 4:
300 if i == 4:
301 return t, p, v
301 return t, p, v
302 if i == 5:
302 if i == 5:
303 return v, p, q
303 return v, p, q
304
304
305 golden_ratio = 0.618033988749895
305 golden_ratio = 0.618033988749895
306 h = 0.22717784590367374
306 h = 0.22717784590367374
307
307
308 for _ in xrange(n):
308 for _ in xrange(n):
309 h += golden_ratio
309 h += golden_ratio
310 h %= 1
310 h %= 1
311 HSV_tuple = [h, 0.95, 0.95]
311 HSV_tuple = [h, 0.95, 0.95]
312 RGB_tuple = hsv_to_rgb(*HSV_tuple)
312 RGB_tuple = hsv_to_rgb(*HSV_tuple)
313 yield map(lambda x: str(int(x * 256)), RGB_tuple)
313 yield map(lambda x: str(int(x * 256)), RGB_tuple)
314
314
315 cgenerator = gen_color()
315 cgenerator = gen_color()
316
316
317 def get_color_string(cs):
317 def get_color_string(cs):
318 if cs in color_dict:
318 if cs in color_dict:
319 col = color_dict[cs]
319 col = color_dict[cs]
320 else:
320 else:
321 col = color_dict[cs] = cgenerator.next()
321 col = color_dict[cs] = cgenerator.next()
322 return "color: rgb(%s)! important;" % (', '.join(col))
322 return "color: rgb(%s)! important;" % (', '.join(col))
323
323
324 def url_func(repo_name):
324 def url_func(repo_name):
325
325
326 def _url_func(changeset):
326 def _url_func(changeset):
327 author = changeset.author
327 author = changeset.author
328 date = changeset.date
328 date = changeset.date
329 message = tooltip(changeset.message)
329 message = tooltip(changeset.message)
330
330
331 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
331 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
332 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
332 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
333 "</b> %s<br/></div>")
333 "</b> %s<br/></div>")
334
334
335 tooltip_html = tooltip_html % (author, date, message)
335 tooltip_html = tooltip_html % (author, date, message)
336 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
336 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
337 short_id(changeset.raw_id))
337 short_id(changeset.raw_id))
338 uri = link_to(
338 uri = link_to(
339 lnk_format,
339 lnk_format,
340 url('changeset_home', repo_name=repo_name,
340 url('changeset_home', repo_name=repo_name,
341 revision=changeset.raw_id),
341 revision=changeset.raw_id),
342 style=get_color_string(changeset.raw_id),
342 style=get_color_string(changeset.raw_id),
343 class_='tooltip',
343 class_='tooltip',
344 title=tooltip_html
344 title=tooltip_html
345 )
345 )
346
346
347 uri += '\n'
347 uri += '\n'
348 return uri
348 return uri
349 return _url_func
349 return _url_func
350
350
351 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
351 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
352
352
353
353
354 def is_following_repo(repo_name, user_id):
354 def is_following_repo(repo_name, user_id):
355 from rhodecode.model.scm import ScmModel
355 from rhodecode.model.scm import ScmModel
356 return ScmModel().is_following_repo(repo_name, user_id)
356 return ScmModel().is_following_repo(repo_name, user_id)
357
357
358 flash = _Flash()
358 flash = _Flash()
359
359
360 #==============================================================================
360 #==============================================================================
361 # SCM FILTERS available via h.
361 # SCM FILTERS available via h.
362 #==============================================================================
362 #==============================================================================
363 from rhodecode.lib.vcs.utils import author_name, author_email
363 from rhodecode.lib.vcs.utils import author_name, author_email
364 from rhodecode.lib.utils2 import credentials_filter, age as _age
364 from rhodecode.lib.utils2 import credentials_filter, age as _age
365 from rhodecode.model.db import User, ChangesetStatus
365 from rhodecode.model.db import User, ChangesetStatus
366
366
367 age = lambda x, y=False: _age(x, y)
367 age = lambda x, y=False: _age(x, y)
368 capitalize = lambda x: x.capitalize()
368 capitalize = lambda x: x.capitalize()
369 email = author_email
369 email = author_email
370 short_id = lambda x: x[:12]
370 short_id = lambda x: x[:12]
371 hide_credentials = lambda x: ''.join(credentials_filter(x))
371 hide_credentials = lambda x: ''.join(credentials_filter(x))
372
372
373
373
374 def show_id(cs):
374 def show_id(cs):
375 """
375 """
376 Configurable function that shows ID
376 Configurable function that shows ID
377 by default it's r123:fffeeefffeee
377 by default it's r123:fffeeefffeee
378
378
379 :param cs: changeset instance
379 :param cs: changeset instance
380 """
380 """
381 from rhodecode import CONFIG
381 from rhodecode import CONFIG
382 def_len = safe_int(CONFIG.get('show_sha_length', 12))
382 def_len = safe_int(CONFIG.get('show_sha_length', 12))
383 show_rev = str2bool(CONFIG.get('show_revision_number', True))
383 show_rev = str2bool(CONFIG.get('show_revision_number', True))
384
384
385 raw_id = cs.raw_id[:def_len]
385 raw_id = cs.raw_id[:def_len]
386 if show_rev:
386 if show_rev:
387 return 'r%s:%s' % (cs.revision, raw_id)
387 return 'r%s:%s' % (cs.revision, raw_id)
388 else:
388 else:
389 return '%s' % (raw_id)
389 return '%s' % (raw_id)
390
390
391
391
392 def fmt_date(date):
392 def fmt_date(date):
393 if date:
393 if date:
394 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
394 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
395 return date.strftime(_fmt).decode('utf8')
395 return date.strftime(_fmt).decode('utf8')
396
396
397 return ""
397 return ""
398
398
399
399
400 def is_git(repository):
400 def is_git(repository):
401 if hasattr(repository, 'alias'):
401 if hasattr(repository, 'alias'):
402 _type = repository.alias
402 _type = repository.alias
403 elif hasattr(repository, 'repo_type'):
403 elif hasattr(repository, 'repo_type'):
404 _type = repository.repo_type
404 _type = repository.repo_type
405 else:
405 else:
406 _type = repository
406 _type = repository
407 return _type == 'git'
407 return _type == 'git'
408
408
409
409
410 def is_hg(repository):
410 def is_hg(repository):
411 if hasattr(repository, 'alias'):
411 if hasattr(repository, 'alias'):
412 _type = repository.alias
412 _type = repository.alias
413 elif hasattr(repository, 'repo_type'):
413 elif hasattr(repository, 'repo_type'):
414 _type = repository.repo_type
414 _type = repository.repo_type
415 else:
415 else:
416 _type = repository
416 _type = repository
417 return _type == 'hg'
417 return _type == 'hg'
418
418
419
419
420 def email_or_none(author):
420 def email_or_none(author):
421 # extract email from the commit string
421 # extract email from the commit string
422 _email = email(author)
422 _email = email(author)
423 if _email != '':
423 if _email != '':
424 # check it against RhodeCode database, and use the MAIN email for this
424 # check it against RhodeCode database, and use the MAIN email for this
425 # user
425 # user
426 user = User.get_by_email(_email, case_insensitive=True, cache=True)
426 user = User.get_by_email(_email, case_insensitive=True, cache=True)
427 if user is not None:
427 if user is not None:
428 return user.email
428 return user.email
429 return _email
429 return _email
430
430
431 # See if it contains a username we can get an email from
431 # See if it contains a username we can get an email from
432 user = User.get_by_username(author_name(author), case_insensitive=True,
432 user = User.get_by_username(author_name(author), case_insensitive=True,
433 cache=True)
433 cache=True)
434 if user is not None:
434 if user is not None:
435 return user.email
435 return user.email
436
436
437 # No valid email, not a valid user in the system, none!
437 # No valid email, not a valid user in the system, none!
438 return None
438 return None
439
439
440
440
441 def person(author, show_attr="username_and_name"):
441 def person(author, show_attr="username_and_name"):
442 # attr to return from fetched user
442 # attr to return from fetched user
443 person_getter = lambda usr: getattr(usr, show_attr)
443 person_getter = lambda usr: getattr(usr, show_attr)
444
444
445 # Valid email in the attribute passed, see if they're in the system
445 # Valid email in the attribute passed, see if they're in the system
446 _email = email(author)
446 _email = email(author)
447 if _email != '':
447 if _email != '':
448 user = User.get_by_email(_email, case_insensitive=True, cache=True)
448 user = User.get_by_email(_email, case_insensitive=True, cache=True)
449 if user is not None:
449 if user is not None:
450 return person_getter(user)
450 return person_getter(user)
451
451
452 # Maybe it's a username?
452 # Maybe it's a username?
453 _author = author_name(author)
453 _author = author_name(author)
454 user = User.get_by_username(_author, case_insensitive=True,
454 user = User.get_by_username(_author, case_insensitive=True,
455 cache=True)
455 cache=True)
456 if user is not None:
456 if user is not None:
457 return person_getter(user)
457 return person_getter(user)
458
458
459 # Still nothing? Just pass back the author name if any, else the email
459 # Still nothing? Just pass back the author name if any, else the email
460 return _author or _email
460 return _author or _email
461
461
462
462
463 def person_by_id(id_, show_attr="username_and_name"):
463 def person_by_id(id_, show_attr="username_and_name"):
464 # attr to return from fetched user
464 # attr to return from fetched user
465 person_getter = lambda usr: getattr(usr, show_attr)
465 person_getter = lambda usr: getattr(usr, show_attr)
466
466
467 #maybe it's an ID ?
467 #maybe it's an ID ?
468 if str(id_).isdigit() or isinstance(id_, int):
468 if str(id_).isdigit() or isinstance(id_, int):
469 id_ = int(id_)
469 id_ = int(id_)
470 user = User.get(id_)
470 user = User.get(id_)
471 if user is not None:
471 if user is not None:
472 return person_getter(user)
472 return person_getter(user)
473 return id_
473 return id_
474
474
475
475
476 def desc_stylize(value):
476 def desc_stylize(value):
477 """
477 """
478 converts tags from value into html equivalent
478 converts tags from value into html equivalent
479
479
480 :param value:
480 :param value:
481 """
481 """
482 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
482 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
483 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
483 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
484 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
484 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
485 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
485 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
486 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
486 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
487 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
487 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
488 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
488 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
489 '<div class="metatag" tag="lang">\\2</div>', value)
489 '<div class="metatag" tag="lang">\\2</div>', value)
490 value = re.sub(r'\[([a-z]+)\]',
490 value = re.sub(r'\[([a-z]+)\]',
491 '<div class="metatag" tag="\\1">\\1</div>', value)
491 '<div class="metatag" tag="\\1">\\1</div>', value)
492
492
493 return value
493 return value
494
494
495
495
496 def boolicon(value):
496 def boolicon(value):
497 """Returns boolean value of a value, represented as small html image of true/false
497 """Returns boolean value of a value, represented as small html image of true/false
498 icons
498 icons
499
499
500 :param value: value
500 :param value: value
501 """
501 """
502
502
503 if value:
503 if value:
504 return HTML.tag('img', src=url("/images/icons/accept.png"),
504 return HTML.tag('img', src=url("/images/icons/accept.png"),
505 alt=_('True'))
505 alt=_('True'))
506 else:
506 else:
507 return HTML.tag('img', src=url("/images/icons/cancel.png"),
507 return HTML.tag('img', src=url("/images/icons/cancel.png"),
508 alt=_('False'))
508 alt=_('False'))
509
509
510
510
511 def action_parser(user_log, feed=False, parse_cs=False):
511 def action_parser(user_log, feed=False, parse_cs=False):
512 """
512 """
513 This helper will action_map the specified string action into translated
513 This helper will action_map the specified string action into translated
514 fancy names with icons and links
514 fancy names with icons and links
515
515
516 :param user_log: user log instance
516 :param user_log: user log instance
517 :param feed: use output for feeds (no html and fancy icons)
517 :param feed: use output for feeds (no html and fancy icons)
518 :param parse_cs: parse Changesets into VCS instances
518 :param parse_cs: parse Changesets into VCS instances
519 """
519 """
520
520
521 action = user_log.action
521 action = user_log.action
522 action_params = ' '
522 action_params = ' '
523
523
524 x = action.split(':')
524 x = action.split(':')
525
525
526 if len(x) > 1:
526 if len(x) > 1:
527 action, action_params = x
527 action, action_params = x
528
528
529 def get_cs_links():
529 def get_cs_links():
530 revs_limit = 3 # display this amount always
530 revs_limit = 3 # display this amount always
531 revs_top_limit = 50 # show upto this amount of changesets hidden
531 revs_top_limit = 50 # show upto this amount of changesets hidden
532 revs_ids = action_params.split(',')
532 revs_ids = action_params.split(',')
533 deleted = user_log.repository is None
533 deleted = user_log.repository is None
534 if deleted:
534 if deleted:
535 return ','.join(revs_ids)
535 return ','.join(revs_ids)
536
536
537 repo_name = user_log.repository.repo_name
537 repo_name = user_log.repository.repo_name
538
538
539 def lnk(rev, repo_name):
539 def lnk(rev, repo_name):
540 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
540 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
541 lazy_cs = True
541 lazy_cs = True
542 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
542 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
543 lazy_cs = False
543 lazy_cs = False
544 lbl = '?'
544 lbl = '?'
545 if rev.op == 'delete_branch':
545 if rev.op == 'delete_branch':
546 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
546 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
547 title = ''
547 title = ''
548 elif rev.op == 'tag':
548 elif rev.op == 'tag':
549 lbl = '%s' % _('Created tag: %s') % rev.ref_name
549 lbl = '%s' % _('Created tag: %s') % rev.ref_name
550 title = ''
550 title = ''
551 _url = '#'
551 _url = '#'
552
552
553 else:
553 else:
554 lbl = '%s' % (rev.short_id[:8])
554 lbl = '%s' % (rev.short_id[:8])
555 _url = url('changeset_home', repo_name=repo_name,
555 _url = url('changeset_home', repo_name=repo_name,
556 revision=rev.raw_id)
556 revision=rev.raw_id)
557 title = tooltip(rev.message)
557 title = tooltip(rev.message)
558 else:
558 else:
559 ## changeset cannot be found/striped/removed etc.
559 ## changeset cannot be found/striped/removed etc.
560 lbl = ('%s' % rev)[:12]
560 lbl = ('%s' % rev)[:12]
561 _url = '#'
561 _url = '#'
562 title = _('Changeset not found')
562 title = _('Changeset not found')
563 if parse_cs:
563 if parse_cs:
564 return link_to(lbl, _url, title=title, class_='tooltip')
564 return link_to(lbl, _url, title=title, class_='tooltip')
565 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
565 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
566 class_='lazy-cs' if lazy_cs else '')
566 class_='lazy-cs' if lazy_cs else '')
567
567
568 def _get_op(rev_txt):
568 def _get_op(rev_txt):
569 _op = None
569 _op = None
570 _name = rev_txt
570 _name = rev_txt
571 if len(rev_txt.split('=>')) == 2:
571 if len(rev_txt.split('=>')) == 2:
572 _op, _name = rev_txt.split('=>')
572 _op, _name = rev_txt.split('=>')
573 return _op, _name
573 return _op, _name
574
574
575 revs = []
575 revs = []
576 if len(filter(lambda v: v != '', revs_ids)) > 0:
576 if len(filter(lambda v: v != '', revs_ids)) > 0:
577 repo = None
577 repo = None
578 for rev in revs_ids[:revs_top_limit]:
578 for rev in revs_ids[:revs_top_limit]:
579 _op, _name = _get_op(rev)
579 _op, _name = _get_op(rev)
580
580
581 # we want parsed changesets, or new log store format is bad
581 # we want parsed changesets, or new log store format is bad
582 if parse_cs:
582 if parse_cs:
583 try:
583 try:
584 if repo is None:
584 if repo is None:
585 repo = user_log.repository.scm_instance
585 repo = user_log.repository.scm_instance
586 _rev = repo.get_changeset(rev)
586 _rev = repo.get_changeset(rev)
587 revs.append(_rev)
587 revs.append(_rev)
588 except ChangesetDoesNotExistError:
588 except ChangesetDoesNotExistError:
589 log.error('cannot find revision %s in this repo' % rev)
589 log.error('cannot find revision %s in this repo' % rev)
590 revs.append(rev)
590 revs.append(rev)
591 continue
591 continue
592 else:
592 else:
593 _rev = AttributeDict({
593 _rev = AttributeDict({
594 'short_id': rev[:12],
594 'short_id': rev[:12],
595 'raw_id': rev,
595 'raw_id': rev,
596 'message': '',
596 'message': '',
597 'op': _op,
597 'op': _op,
598 'ref_name': _name
598 'ref_name': _name
599 })
599 })
600 revs.append(_rev)
600 revs.append(_rev)
601 cs_links = []
601 cs_links = []
602 cs_links.append(" " + ', '.join(
602 cs_links.append(" " + ', '.join(
603 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
603 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
604 )
604 )
605 )
605 )
606 _op1, _name1 = _get_op(revs_ids[0])
606 _op1, _name1 = _get_op(revs_ids[0])
607 _op2, _name2 = _get_op(revs_ids[-1])
607 _op2, _name2 = _get_op(revs_ids[-1])
608
608
609 _rev = '%s...%s' % (_name1, _name2)
609 _rev = '%s...%s' % (_name1, _name2)
610
610
611 compare_view = (
611 compare_view = (
612 ' <div class="compare_view tooltip" title="%s">'
612 ' <div class="compare_view tooltip" title="%s">'
613 '<a href="%s">%s</a> </div>' % (
613 '<a href="%s">%s</a> </div>' % (
614 _('Show all combined changesets %s->%s') % (
614 _('Show all combined changesets %s->%s') % (
615 revs_ids[0][:12], revs_ids[-1][:12]
615 revs_ids[0][:12], revs_ids[-1][:12]
616 ),
616 ),
617 url('changeset_home', repo_name=repo_name,
617 url('changeset_home', repo_name=repo_name,
618 revision=_rev
618 revision=_rev
619 ),
619 ),
620 _('compare view')
620 _('compare view')
621 )
621 )
622 )
622 )
623
623
624 # if we have exactly one more than normally displayed
624 # if we have exactly one more than normally displayed
625 # just display it, takes less space than displaying
625 # just display it, takes less space than displaying
626 # "and 1 more revisions"
626 # "and 1 more revisions"
627 if len(revs_ids) == revs_limit + 1:
627 if len(revs_ids) == revs_limit + 1:
628 rev = revs[revs_limit]
628 rev = revs[revs_limit]
629 cs_links.append(", " + lnk(rev, repo_name))
629 cs_links.append(", " + lnk(rev, repo_name))
630
630
631 # hidden-by-default ones
631 # hidden-by-default ones
632 if len(revs_ids) > revs_limit + 1:
632 if len(revs_ids) > revs_limit + 1:
633 uniq_id = revs_ids[0]
633 uniq_id = revs_ids[0]
634 html_tmpl = (
634 html_tmpl = (
635 '<span> %s <a class="show_more" id="_%s" '
635 '<span> %s <a class="show_more" id="_%s" '
636 'href="#more">%s</a> %s</span>'
636 'href="#more">%s</a> %s</span>'
637 )
637 )
638 if not feed:
638 if not feed:
639 cs_links.append(html_tmpl % (
639 cs_links.append(html_tmpl % (
640 _('and'),
640 _('and'),
641 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
641 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
642 _('revisions')
642 _('revisions')
643 )
643 )
644 )
644 )
645
645
646 if not feed:
646 if not feed:
647 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
647 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
648 else:
648 else:
649 html_tmpl = '<span id="%s"> %s </span>'
649 html_tmpl = '<span id="%s"> %s </span>'
650
650
651 morelinks = ', '.join(
651 morelinks = ', '.join(
652 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
652 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
653 )
653 )
654
654
655 if len(revs_ids) > revs_top_limit:
655 if len(revs_ids) > revs_top_limit:
656 morelinks += ', ...'
656 morelinks += ', ...'
657
657
658 cs_links.append(html_tmpl % (uniq_id, morelinks))
658 cs_links.append(html_tmpl % (uniq_id, morelinks))
659 if len(revs) > 1:
659 if len(revs) > 1:
660 cs_links.append(compare_view)
660 cs_links.append(compare_view)
661 return ''.join(cs_links)
661 return ''.join(cs_links)
662
662
663 def get_fork_name():
663 def get_fork_name():
664 repo_name = action_params
664 repo_name = action_params
665 _url = url('summary_home', repo_name=repo_name)
665 _url = url('summary_home', repo_name=repo_name)
666 return _('fork name %s') % link_to(action_params, _url)
666 return _('fork name %s') % link_to(action_params, _url)
667
667
668 def get_user_name():
668 def get_user_name():
669 user_name = action_params
669 user_name = action_params
670 return user_name
670 return user_name
671
671
672 def get_users_group():
672 def get_users_group():
673 group_name = action_params
673 group_name = action_params
674 return group_name
674 return group_name
675
675
676 def get_pull_request():
676 def get_pull_request():
677 pull_request_id = action_params
677 pull_request_id = action_params
678 deleted = user_log.repository is None
678 deleted = user_log.repository is None
679 if deleted:
679 if deleted:
680 repo_name = user_log.repository_name
680 repo_name = user_log.repository_name
681 else:
681 else:
682 repo_name = user_log.repository.repo_name
682 repo_name = user_log.repository.repo_name
683 return link_to(_('Pull request #%s') % pull_request_id,
683 return link_to(_('Pull request #%s') % pull_request_id,
684 url('pullrequest_show', repo_name=repo_name,
684 url('pullrequest_show', repo_name=repo_name,
685 pull_request_id=pull_request_id))
685 pull_request_id=pull_request_id))
686
686
687 def get_archive_name():
688 archive_name = action_params
689 return archive_name
690
687 # action : translated str, callback(extractor), icon
691 # action : translated str, callback(extractor), icon
688 action_map = {
692 action_map = {
689 'user_deleted_repo': (_('[deleted] repository'),
693 'user_deleted_repo': (_('[deleted] repository'),
690 None, 'database_delete.png'),
694 None, 'database_delete.png'),
691 'user_created_repo': (_('[created] repository'),
695 'user_created_repo': (_('[created] repository'),
692 None, 'database_add.png'),
696 None, 'database_add.png'),
693 'user_created_fork': (_('[created] repository as fork'),
697 'user_created_fork': (_('[created] repository as fork'),
694 None, 'arrow_divide.png'),
698 None, 'arrow_divide.png'),
695 'user_forked_repo': (_('[forked] repository'),
699 'user_forked_repo': (_('[forked] repository'),
696 get_fork_name, 'arrow_divide.png'),
700 get_fork_name, 'arrow_divide.png'),
697 'user_updated_repo': (_('[updated] repository'),
701 'user_updated_repo': (_('[updated] repository'),
698 None, 'database_edit.png'),
702 None, 'database_edit.png'),
703 'user_downloaded_archive': (_('[downloaded] archive from repository'),
704 get_archive_name, 'page_white_compressed.png'),
699 'admin_deleted_repo': (_('[delete] repository'),
705 'admin_deleted_repo': (_('[delete] repository'),
700 None, 'database_delete.png'),
706 None, 'database_delete.png'),
701 'admin_created_repo': (_('[created] repository'),
707 'admin_created_repo': (_('[created] repository'),
702 None, 'database_add.png'),
708 None, 'database_add.png'),
703 'admin_forked_repo': (_('[forked] repository'),
709 'admin_forked_repo': (_('[forked] repository'),
704 None, 'arrow_divide.png'),
710 None, 'arrow_divide.png'),
705 'admin_updated_repo': (_('[updated] repository'),
711 'admin_updated_repo': (_('[updated] repository'),
706 None, 'database_edit.png'),
712 None, 'database_edit.png'),
707 'admin_created_user': (_('[created] user'),
713 'admin_created_user': (_('[created] user'),
708 get_user_name, 'user_add.png'),
714 get_user_name, 'user_add.png'),
709 'admin_updated_user': (_('[updated] user'),
715 'admin_updated_user': (_('[updated] user'),
710 get_user_name, 'user_edit.png'),
716 get_user_name, 'user_edit.png'),
711 'admin_created_users_group': (_('[created] user group'),
717 'admin_created_users_group': (_('[created] user group'),
712 get_users_group, 'group_add.png'),
718 get_users_group, 'group_add.png'),
713 'admin_updated_users_group': (_('[updated] user group'),
719 'admin_updated_users_group': (_('[updated] user group'),
714 get_users_group, 'group_edit.png'),
720 get_users_group, 'group_edit.png'),
715 'user_commented_revision': (_('[commented] on revision in repository'),
721 'user_commented_revision': (_('[commented] on revision in repository'),
716 get_cs_links, 'comment_add.png'),
722 get_cs_links, 'comment_add.png'),
717 'user_commented_pull_request': (_('[commented] on pull request for'),
723 'user_commented_pull_request': (_('[commented] on pull request for'),
718 get_pull_request, 'comment_add.png'),
724 get_pull_request, 'comment_add.png'),
719 'user_closed_pull_request': (_('[closed] pull request for'),
725 'user_closed_pull_request': (_('[closed] pull request for'),
720 get_pull_request, 'tick.png'),
726 get_pull_request, 'tick.png'),
721 'push': (_('[pushed] into'),
727 'push': (_('[pushed] into'),
722 get_cs_links, 'script_add.png'),
728 get_cs_links, 'script_add.png'),
723 'push_local': (_('[committed via RhodeCode] into repository'),
729 'push_local': (_('[committed via RhodeCode] into repository'),
724 get_cs_links, 'script_edit.png'),
730 get_cs_links, 'script_edit.png'),
725 'push_remote': (_('[pulled from remote] into repository'),
731 'push_remote': (_('[pulled from remote] into repository'),
726 get_cs_links, 'connect.png'),
732 get_cs_links, 'connect.png'),
727 'pull': (_('[pulled] from'),
733 'pull': (_('[pulled] from'),
728 None, 'down_16.png'),
734 None, 'down_16.png'),
729 'started_following_repo': (_('[started following] repository'),
735 'started_following_repo': (_('[started following] repository'),
730 None, 'heart_add.png'),
736 None, 'heart_add.png'),
731 'stopped_following_repo': (_('[stopped following] repository'),
737 'stopped_following_repo': (_('[stopped following] repository'),
732 None, 'heart_delete.png'),
738 None, 'heart_delete.png'),
733 }
739 }
734
740
735 action_str = action_map.get(action, action)
741 action_str = action_map.get(action, action)
736 if feed:
742 if feed:
737 action = action_str[0].replace('[', '').replace(']', '')
743 action = action_str[0].replace('[', '').replace(']', '')
738 else:
744 else:
739 action = action_str[0]\
745 action = action_str[0]\
740 .replace('[', '<span class="journal_highlight">')\
746 .replace('[', '<span class="journal_highlight">')\
741 .replace(']', '</span>')
747 .replace(']', '</span>')
742
748
743 action_params_func = lambda: ""
749 action_params_func = lambda: ""
744
750
745 if callable(action_str[1]):
751 if callable(action_str[1]):
746 action_params_func = action_str[1]
752 action_params_func = action_str[1]
747
753
748 def action_parser_icon():
754 def action_parser_icon():
749 action = user_log.action
755 action = user_log.action
750 action_params = None
756 action_params = None
751 x = action.split(':')
757 x = action.split(':')
752
758
753 if len(x) > 1:
759 if len(x) > 1:
754 action, action_params = x
760 action, action_params = x
755
761
756 tmpl = """<img src="%s%s" alt="%s"/>"""
762 tmpl = """<img src="%s%s" alt="%s"/>"""
757 ico = action_map.get(action, ['', '', ''])[2]
763 ico = action_map.get(action, ['', '', ''])[2]
758 return literal(tmpl % ((url('/images/icons/')), ico, action))
764 return literal(tmpl % ((url('/images/icons/')), ico, action))
759
765
760 # returned callbacks we need to call to get
766 # returned callbacks we need to call to get
761 return [lambda: literal(action), action_params_func, action_parser_icon]
767 return [lambda: literal(action), action_params_func, action_parser_icon]
762
768
763
769
764
770
765 #==============================================================================
771 #==============================================================================
766 # PERMS
772 # PERMS
767 #==============================================================================
773 #==============================================================================
768 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
774 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
769 HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \
775 HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \
770 HasReposGroupPermissionAny
776 HasReposGroupPermissionAny
771
777
772
778
773 #==============================================================================
779 #==============================================================================
774 # GRAVATAR URL
780 # GRAVATAR URL
775 #==============================================================================
781 #==============================================================================
776
782
777 def gravatar_url(email_address, size=30):
783 def gravatar_url(email_address, size=30):
778 from pylons import url # doh, we need to re-import url to mock it later
784 from pylons import url # doh, we need to re-import url to mock it later
779 _def = 'anonymous@rhodecode.org'
785 _def = 'anonymous@rhodecode.org'
780 use_gravatar = str2bool(config['app_conf'].get('use_gravatar'))
786 use_gravatar = str2bool(config['app_conf'].get('use_gravatar'))
781 email_address = email_address or _def
787 email_address = email_address or _def
782 if (not use_gravatar or not email_address or email_address == _def):
788 if (not use_gravatar or not email_address or email_address == _def):
783 f = lambda a, l: min(l, key=lambda x: abs(x - a))
789 f = lambda a, l: min(l, key=lambda x: abs(x - a))
784 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
790 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
785
791
786 if use_gravatar and config['app_conf'].get('alternative_gravatar_url'):
792 if use_gravatar and config['app_conf'].get('alternative_gravatar_url'):
787 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
793 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
788 parsed_url = urlparse.urlparse(url.current(qualified=True))
794 parsed_url = urlparse.urlparse(url.current(qualified=True))
789 tmpl = tmpl.replace('{email}', email_address)\
795 tmpl = tmpl.replace('{email}', email_address)\
790 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
796 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
791 .replace('{netloc}', parsed_url.netloc)\
797 .replace('{netloc}', parsed_url.netloc)\
792 .replace('{scheme}', parsed_url.scheme)\
798 .replace('{scheme}', parsed_url.scheme)\
793 .replace('{size}', str(size))
799 .replace('{size}', str(size))
794 return tmpl
800 return tmpl
795
801
796 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
802 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
797 default = 'identicon'
803 default = 'identicon'
798 baseurl_nossl = "http://www.gravatar.com/avatar/"
804 baseurl_nossl = "http://www.gravatar.com/avatar/"
799 baseurl_ssl = "https://secure.gravatar.com/avatar/"
805 baseurl_ssl = "https://secure.gravatar.com/avatar/"
800 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
806 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
801
807
802 if isinstance(email_address, unicode):
808 if isinstance(email_address, unicode):
803 #hashlib crashes on unicode items
809 #hashlib crashes on unicode items
804 email_address = safe_str(email_address)
810 email_address = safe_str(email_address)
805 # construct the url
811 # construct the url
806 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
812 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
807 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
813 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
808
814
809 return gravatar_url
815 return gravatar_url
810
816
811
817
812 class Page(_Page):
818 class Page(_Page):
813 """
819 """
814 Custom pager to match rendering style with YUI paginator
820 Custom pager to match rendering style with YUI paginator
815 """
821 """
816
822
817 def _get_pos(self, cur_page, max_page, items):
823 def _get_pos(self, cur_page, max_page, items):
818 edge = (items / 2) + 1
824 edge = (items / 2) + 1
819 if (cur_page <= edge):
825 if (cur_page <= edge):
820 radius = max(items / 2, items - cur_page)
826 radius = max(items / 2, items - cur_page)
821 elif (max_page - cur_page) < edge:
827 elif (max_page - cur_page) < edge:
822 radius = (items - 1) - (max_page - cur_page)
828 radius = (items - 1) - (max_page - cur_page)
823 else:
829 else:
824 radius = items / 2
830 radius = items / 2
825
831
826 left = max(1, (cur_page - (radius)))
832 left = max(1, (cur_page - (radius)))
827 right = min(max_page, cur_page + (radius))
833 right = min(max_page, cur_page + (radius))
828 return left, cur_page, right
834 return left, cur_page, right
829
835
830 def _range(self, regexp_match):
836 def _range(self, regexp_match):
831 """
837 """
832 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
838 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
833
839
834 Arguments:
840 Arguments:
835
841
836 regexp_match
842 regexp_match
837 A "re" (regular expressions) match object containing the
843 A "re" (regular expressions) match object containing the
838 radius of linked pages around the current page in
844 radius of linked pages around the current page in
839 regexp_match.group(1) as a string
845 regexp_match.group(1) as a string
840
846
841 This function is supposed to be called as a callable in
847 This function is supposed to be called as a callable in
842 re.sub.
848 re.sub.
843
849
844 """
850 """
845 radius = int(regexp_match.group(1))
851 radius = int(regexp_match.group(1))
846
852
847 # Compute the first and last page number within the radius
853 # Compute the first and last page number within the radius
848 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
854 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
849 # -> leftmost_page = 5
855 # -> leftmost_page = 5
850 # -> rightmost_page = 9
856 # -> rightmost_page = 9
851 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
857 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
852 self.last_page,
858 self.last_page,
853 (radius * 2) + 1)
859 (radius * 2) + 1)
854 nav_items = []
860 nav_items = []
855
861
856 # Create a link to the first page (unless we are on the first page
862 # Create a link to the first page (unless we are on the first page
857 # or there would be no need to insert '..' spacers)
863 # or there would be no need to insert '..' spacers)
858 if self.page != self.first_page and self.first_page < leftmost_page:
864 if self.page != self.first_page and self.first_page < leftmost_page:
859 nav_items.append(self._pagerlink(self.first_page, self.first_page))
865 nav_items.append(self._pagerlink(self.first_page, self.first_page))
860
866
861 # Insert dots if there are pages between the first page
867 # Insert dots if there are pages between the first page
862 # and the currently displayed page range
868 # and the currently displayed page range
863 if leftmost_page - self.first_page > 1:
869 if leftmost_page - self.first_page > 1:
864 # Wrap in a SPAN tag if nolink_attr is set
870 # Wrap in a SPAN tag if nolink_attr is set
865 text = '..'
871 text = '..'
866 if self.dotdot_attr:
872 if self.dotdot_attr:
867 text = HTML.span(c=text, **self.dotdot_attr)
873 text = HTML.span(c=text, **self.dotdot_attr)
868 nav_items.append(text)
874 nav_items.append(text)
869
875
870 for thispage in xrange(leftmost_page, rightmost_page + 1):
876 for thispage in xrange(leftmost_page, rightmost_page + 1):
871 # Hilight the current page number and do not use a link
877 # Hilight the current page number and do not use a link
872 if thispage == self.page:
878 if thispage == self.page:
873 text = '%s' % (thispage,)
879 text = '%s' % (thispage,)
874 # Wrap in a SPAN tag if nolink_attr is set
880 # Wrap in a SPAN tag if nolink_attr is set
875 if self.curpage_attr:
881 if self.curpage_attr:
876 text = HTML.span(c=text, **self.curpage_attr)
882 text = HTML.span(c=text, **self.curpage_attr)
877 nav_items.append(text)
883 nav_items.append(text)
878 # Otherwise create just a link to that page
884 # Otherwise create just a link to that page
879 else:
885 else:
880 text = '%s' % (thispage,)
886 text = '%s' % (thispage,)
881 nav_items.append(self._pagerlink(thispage, text))
887 nav_items.append(self._pagerlink(thispage, text))
882
888
883 # Insert dots if there are pages between the displayed
889 # Insert dots if there are pages between the displayed
884 # page numbers and the end of the page range
890 # page numbers and the end of the page range
885 if self.last_page - rightmost_page > 1:
891 if self.last_page - rightmost_page > 1:
886 text = '..'
892 text = '..'
887 # Wrap in a SPAN tag if nolink_attr is set
893 # Wrap in a SPAN tag if nolink_attr is set
888 if self.dotdot_attr:
894 if self.dotdot_attr:
889 text = HTML.span(c=text, **self.dotdot_attr)
895 text = HTML.span(c=text, **self.dotdot_attr)
890 nav_items.append(text)
896 nav_items.append(text)
891
897
892 # Create a link to the very last page (unless we are on the last
898 # Create a link to the very last page (unless we are on the last
893 # page or there would be no need to insert '..' spacers)
899 # page or there would be no need to insert '..' spacers)
894 if self.page != self.last_page and rightmost_page < self.last_page:
900 if self.page != self.last_page and rightmost_page < self.last_page:
895 nav_items.append(self._pagerlink(self.last_page, self.last_page))
901 nav_items.append(self._pagerlink(self.last_page, self.last_page))
896
902
897 return self.separator.join(nav_items)
903 return self.separator.join(nav_items)
898
904
899 def pager(self, format='~2~', page_param='page', partial_param='partial',
905 def pager(self, format='~2~', page_param='page', partial_param='partial',
900 show_if_single_page=False, separator=' ', onclick=None,
906 show_if_single_page=False, separator=' ', onclick=None,
901 symbol_first='<<', symbol_last='>>',
907 symbol_first='<<', symbol_last='>>',
902 symbol_previous='<', symbol_next='>',
908 symbol_previous='<', symbol_next='>',
903 link_attr={'class': 'pager_link'},
909 link_attr={'class': 'pager_link'},
904 curpage_attr={'class': 'pager_curpage'},
910 curpage_attr={'class': 'pager_curpage'},
905 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
911 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
906
912
907 self.curpage_attr = curpage_attr
913 self.curpage_attr = curpage_attr
908 self.separator = separator
914 self.separator = separator
909 self.pager_kwargs = kwargs
915 self.pager_kwargs = kwargs
910 self.page_param = page_param
916 self.page_param = page_param
911 self.partial_param = partial_param
917 self.partial_param = partial_param
912 self.onclick = onclick
918 self.onclick = onclick
913 self.link_attr = link_attr
919 self.link_attr = link_attr
914 self.dotdot_attr = dotdot_attr
920 self.dotdot_attr = dotdot_attr
915
921
916 # Don't show navigator if there is no more than one page
922 # Don't show navigator if there is no more than one page
917 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
923 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
918 return ''
924 return ''
919
925
920 from string import Template
926 from string import Template
921 # Replace ~...~ in token format by range of pages
927 # Replace ~...~ in token format by range of pages
922 result = re.sub(r'~(\d+)~', self._range, format)
928 result = re.sub(r'~(\d+)~', self._range, format)
923
929
924 # Interpolate '%' variables
930 # Interpolate '%' variables
925 result = Template(result).safe_substitute({
931 result = Template(result).safe_substitute({
926 'first_page': self.first_page,
932 'first_page': self.first_page,
927 'last_page': self.last_page,
933 'last_page': self.last_page,
928 'page': self.page,
934 'page': self.page,
929 'page_count': self.page_count,
935 'page_count': self.page_count,
930 'items_per_page': self.items_per_page,
936 'items_per_page': self.items_per_page,
931 'first_item': self.first_item,
937 'first_item': self.first_item,
932 'last_item': self.last_item,
938 'last_item': self.last_item,
933 'item_count': self.item_count,
939 'item_count': self.item_count,
934 'link_first': self.page > self.first_page and \
940 'link_first': self.page > self.first_page and \
935 self._pagerlink(self.first_page, symbol_first) or '',
941 self._pagerlink(self.first_page, symbol_first) or '',
936 'link_last': self.page < self.last_page and \
942 'link_last': self.page < self.last_page and \
937 self._pagerlink(self.last_page, symbol_last) or '',
943 self._pagerlink(self.last_page, symbol_last) or '',
938 'link_previous': self.previous_page and \
944 'link_previous': self.previous_page and \
939 self._pagerlink(self.previous_page, symbol_previous) \
945 self._pagerlink(self.previous_page, symbol_previous) \
940 or HTML.span(symbol_previous, class_="yui-pg-previous"),
946 or HTML.span(symbol_previous, class_="yui-pg-previous"),
941 'link_next': self.next_page and \
947 'link_next': self.next_page and \
942 self._pagerlink(self.next_page, symbol_next) \
948 self._pagerlink(self.next_page, symbol_next) \
943 or HTML.span(symbol_next, class_="yui-pg-next")
949 or HTML.span(symbol_next, class_="yui-pg-next")
944 })
950 })
945
951
946 return literal(result)
952 return literal(result)
947
953
948
954
949 #==============================================================================
955 #==============================================================================
950 # REPO PAGER, PAGER FOR REPOSITORY
956 # REPO PAGER, PAGER FOR REPOSITORY
951 #==============================================================================
957 #==============================================================================
952 class RepoPage(Page):
958 class RepoPage(Page):
953
959
954 def __init__(self, collection, page=1, items_per_page=20,
960 def __init__(self, collection, page=1, items_per_page=20,
955 item_count=None, url=None, **kwargs):
961 item_count=None, url=None, **kwargs):
956
962
957 """Create a "RepoPage" instance. special pager for paging
963 """Create a "RepoPage" instance. special pager for paging
958 repository
964 repository
959 """
965 """
960 self._url_generator = url
966 self._url_generator = url
961
967
962 # Safe the kwargs class-wide so they can be used in the pager() method
968 # Safe the kwargs class-wide so they can be used in the pager() method
963 self.kwargs = kwargs
969 self.kwargs = kwargs
964
970
965 # Save a reference to the collection
971 # Save a reference to the collection
966 self.original_collection = collection
972 self.original_collection = collection
967
973
968 self.collection = collection
974 self.collection = collection
969
975
970 # The self.page is the number of the current page.
976 # The self.page is the number of the current page.
971 # The first page has the number 1!
977 # The first page has the number 1!
972 try:
978 try:
973 self.page = int(page) # make it int() if we get it as a string
979 self.page = int(page) # make it int() if we get it as a string
974 except (ValueError, TypeError):
980 except (ValueError, TypeError):
975 self.page = 1
981 self.page = 1
976
982
977 self.items_per_page = items_per_page
983 self.items_per_page = items_per_page
978
984
979 # Unless the user tells us how many items the collections has
985 # Unless the user tells us how many items the collections has
980 # we calculate that ourselves.
986 # we calculate that ourselves.
981 if item_count is not None:
987 if item_count is not None:
982 self.item_count = item_count
988 self.item_count = item_count
983 else:
989 else:
984 self.item_count = len(self.collection)
990 self.item_count = len(self.collection)
985
991
986 # Compute the number of the first and last available page
992 # Compute the number of the first and last available page
987 if self.item_count > 0:
993 if self.item_count > 0:
988 self.first_page = 1
994 self.first_page = 1
989 self.page_count = int(math.ceil(float(self.item_count) /
995 self.page_count = int(math.ceil(float(self.item_count) /
990 self.items_per_page))
996 self.items_per_page))
991 self.last_page = self.first_page + self.page_count - 1
997 self.last_page = self.first_page + self.page_count - 1
992
998
993 # Make sure that the requested page number is the range of
999 # Make sure that the requested page number is the range of
994 # valid pages
1000 # valid pages
995 if self.page > self.last_page:
1001 if self.page > self.last_page:
996 self.page = self.last_page
1002 self.page = self.last_page
997 elif self.page < self.first_page:
1003 elif self.page < self.first_page:
998 self.page = self.first_page
1004 self.page = self.first_page
999
1005
1000 # Note: the number of items on this page can be less than
1006 # Note: the number of items on this page can be less than
1001 # items_per_page if the last page is not full
1007 # items_per_page if the last page is not full
1002 self.first_item = max(0, (self.item_count) - (self.page *
1008 self.first_item = max(0, (self.item_count) - (self.page *
1003 items_per_page))
1009 items_per_page))
1004 self.last_item = ((self.item_count - 1) - items_per_page *
1010 self.last_item = ((self.item_count - 1) - items_per_page *
1005 (self.page - 1))
1011 (self.page - 1))
1006
1012
1007 self.items = list(self.collection[self.first_item:self.last_item + 1])
1013 self.items = list(self.collection[self.first_item:self.last_item + 1])
1008
1014
1009 # Links to previous and next page
1015 # Links to previous and next page
1010 if self.page > self.first_page:
1016 if self.page > self.first_page:
1011 self.previous_page = self.page - 1
1017 self.previous_page = self.page - 1
1012 else:
1018 else:
1013 self.previous_page = None
1019 self.previous_page = None
1014
1020
1015 if self.page < self.last_page:
1021 if self.page < self.last_page:
1016 self.next_page = self.page + 1
1022 self.next_page = self.page + 1
1017 else:
1023 else:
1018 self.next_page = None
1024 self.next_page = None
1019
1025
1020 # No items available
1026 # No items available
1021 else:
1027 else:
1022 self.first_page = None
1028 self.first_page = None
1023 self.page_count = 0
1029 self.page_count = 0
1024 self.last_page = None
1030 self.last_page = None
1025 self.first_item = None
1031 self.first_item = None
1026 self.last_item = None
1032 self.last_item = None
1027 self.previous_page = None
1033 self.previous_page = None
1028 self.next_page = None
1034 self.next_page = None
1029 self.items = []
1035 self.items = []
1030
1036
1031 # This is a subclass of the 'list' type. Initialise the list now.
1037 # This is a subclass of the 'list' type. Initialise the list now.
1032 list.__init__(self, reversed(self.items))
1038 list.__init__(self, reversed(self.items))
1033
1039
1034
1040
1035 def changed_tooltip(nodes):
1041 def changed_tooltip(nodes):
1036 """
1042 """
1037 Generates a html string for changed nodes in changeset page.
1043 Generates a html string for changed nodes in changeset page.
1038 It limits the output to 30 entries
1044 It limits the output to 30 entries
1039
1045
1040 :param nodes: LazyNodesGenerator
1046 :param nodes: LazyNodesGenerator
1041 """
1047 """
1042 if nodes:
1048 if nodes:
1043 pref = ': <br/> '
1049 pref = ': <br/> '
1044 suf = ''
1050 suf = ''
1045 if len(nodes) > 30:
1051 if len(nodes) > 30:
1046 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
1052 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
1047 return literal(pref + '<br/> '.join([safe_unicode(x.path)
1053 return literal(pref + '<br/> '.join([safe_unicode(x.path)
1048 for x in nodes[:30]]) + suf)
1054 for x in nodes[:30]]) + suf)
1049 else:
1055 else:
1050 return ': ' + _('No Files')
1056 return ': ' + _('No Files')
1051
1057
1052
1058
1053 def repo_link(groups_and_repos):
1059 def repo_link(groups_and_repos):
1054 """
1060 """
1055 Makes a breadcrumbs link to repo within a group
1061 Makes a breadcrumbs link to repo within a group
1056 joins &raquo; on each group to create a fancy link
1062 joins &raquo; on each group to create a fancy link
1057
1063
1058 ex::
1064 ex::
1059 group >> subgroup >> repo
1065 group >> subgroup >> repo
1060
1066
1061 :param groups_and_repos:
1067 :param groups_and_repos:
1062 :param last_url:
1068 :param last_url:
1063 """
1069 """
1064 groups, just_name, repo_name = groups_and_repos
1070 groups, just_name, repo_name = groups_and_repos
1065 last_url = url('summary_home', repo_name=repo_name)
1071 last_url = url('summary_home', repo_name=repo_name)
1066 last_link = link_to(just_name, last_url)
1072 last_link = link_to(just_name, last_url)
1067
1073
1068 def make_link(group):
1074 def make_link(group):
1069 return link_to(group.name,
1075 return link_to(group.name,
1070 url('repos_group_home', group_name=group.group_name))
1076 url('repos_group_home', group_name=group.group_name))
1071 return literal(' &raquo; '.join(map(make_link, groups) + ['<span>%s</span>' % last_link]))
1077 return literal(' &raquo; '.join(map(make_link, groups) + ['<span>%s</span>' % last_link]))
1072
1078
1073
1079
1074 def fancy_file_stats(stats):
1080 def fancy_file_stats(stats):
1075 """
1081 """
1076 Displays a fancy two colored bar for number of added/deleted
1082 Displays a fancy two colored bar for number of added/deleted
1077 lines of code on file
1083 lines of code on file
1078
1084
1079 :param stats: two element list of added/deleted lines of code
1085 :param stats: two element list of added/deleted lines of code
1080 """
1086 """
1081 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
1087 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
1082 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
1088 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
1083
1089
1084 def cgen(l_type, a_v, d_v):
1090 def cgen(l_type, a_v, d_v):
1085 mapping = {'tr': 'top-right-rounded-corner-mid',
1091 mapping = {'tr': 'top-right-rounded-corner-mid',
1086 'tl': 'top-left-rounded-corner-mid',
1092 'tl': 'top-left-rounded-corner-mid',
1087 'br': 'bottom-right-rounded-corner-mid',
1093 'br': 'bottom-right-rounded-corner-mid',
1088 'bl': 'bottom-left-rounded-corner-mid'}
1094 'bl': 'bottom-left-rounded-corner-mid'}
1089 map_getter = lambda x: mapping[x]
1095 map_getter = lambda x: mapping[x]
1090
1096
1091 if l_type == 'a' and d_v:
1097 if l_type == 'a' and d_v:
1092 #case when added and deleted are present
1098 #case when added and deleted are present
1093 return ' '.join(map(map_getter, ['tl', 'bl']))
1099 return ' '.join(map(map_getter, ['tl', 'bl']))
1094
1100
1095 if l_type == 'a' and not d_v:
1101 if l_type == 'a' and not d_v:
1096 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1102 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1097
1103
1098 if l_type == 'd' and a_v:
1104 if l_type == 'd' and a_v:
1099 return ' '.join(map(map_getter, ['tr', 'br']))
1105 return ' '.join(map(map_getter, ['tr', 'br']))
1100
1106
1101 if l_type == 'd' and not a_v:
1107 if l_type == 'd' and not a_v:
1102 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1108 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1103
1109
1104 a, d = stats['added'], stats['deleted']
1110 a, d = stats['added'], stats['deleted']
1105 width = 100
1111 width = 100
1106
1112
1107 if stats['binary']:
1113 if stats['binary']:
1108 #binary mode
1114 #binary mode
1109 lbl = ''
1115 lbl = ''
1110 bin_op = 1
1116 bin_op = 1
1111
1117
1112 if BIN_FILENODE in stats['ops']:
1118 if BIN_FILENODE in stats['ops']:
1113 lbl = 'bin+'
1119 lbl = 'bin+'
1114
1120
1115 if NEW_FILENODE in stats['ops']:
1121 if NEW_FILENODE in stats['ops']:
1116 lbl += _('new file')
1122 lbl += _('new file')
1117 bin_op = NEW_FILENODE
1123 bin_op = NEW_FILENODE
1118 elif MOD_FILENODE in stats['ops']:
1124 elif MOD_FILENODE in stats['ops']:
1119 lbl += _('mod')
1125 lbl += _('mod')
1120 bin_op = MOD_FILENODE
1126 bin_op = MOD_FILENODE
1121 elif DEL_FILENODE in stats['ops']:
1127 elif DEL_FILENODE in stats['ops']:
1122 lbl += _('del')
1128 lbl += _('del')
1123 bin_op = DEL_FILENODE
1129 bin_op = DEL_FILENODE
1124 elif RENAMED_FILENODE in stats['ops']:
1130 elif RENAMED_FILENODE in stats['ops']:
1125 lbl += _('rename')
1131 lbl += _('rename')
1126 bin_op = RENAMED_FILENODE
1132 bin_op = RENAMED_FILENODE
1127
1133
1128 #chmod can go with other operations
1134 #chmod can go with other operations
1129 if CHMOD_FILENODE in stats['ops']:
1135 if CHMOD_FILENODE in stats['ops']:
1130 _org_lbl = _('chmod')
1136 _org_lbl = _('chmod')
1131 lbl += _org_lbl if lbl.endswith('+') else '+%s' % _org_lbl
1137 lbl += _org_lbl if lbl.endswith('+') else '+%s' % _org_lbl
1132
1138
1133 #import ipdb;ipdb.set_trace()
1139 #import ipdb;ipdb.set_trace()
1134 b_d = '<div class="bin bin%s %s" style="width:100%%">%s</div>' % (bin_op, cgen('a', a_v='', d_v=0), lbl)
1140 b_d = '<div class="bin bin%s %s" style="width:100%%">%s</div>' % (bin_op, cgen('a', a_v='', d_v=0), lbl)
1135 b_a = '<div class="bin bin1" style="width:0%%"></div>'
1141 b_a = '<div class="bin bin1" style="width:0%%"></div>'
1136 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
1142 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
1137
1143
1138 t = stats['added'] + stats['deleted']
1144 t = stats['added'] + stats['deleted']
1139 unit = float(width) / (t or 1)
1145 unit = float(width) / (t or 1)
1140
1146
1141 # needs > 9% of width to be visible or 0 to be hidden
1147 # needs > 9% of width to be visible or 0 to be hidden
1142 a_p = max(9, unit * a) if a > 0 else 0
1148 a_p = max(9, unit * a) if a > 0 else 0
1143 d_p = max(9, unit * d) if d > 0 else 0
1149 d_p = max(9, unit * d) if d > 0 else 0
1144 p_sum = a_p + d_p
1150 p_sum = a_p + d_p
1145
1151
1146 if p_sum > width:
1152 if p_sum > width:
1147 #adjust the percentage to be == 100% since we adjusted to 9
1153 #adjust the percentage to be == 100% since we adjusted to 9
1148 if a_p > d_p:
1154 if a_p > d_p:
1149 a_p = a_p - (p_sum - width)
1155 a_p = a_p - (p_sum - width)
1150 else:
1156 else:
1151 d_p = d_p - (p_sum - width)
1157 d_p = d_p - (p_sum - width)
1152
1158
1153 a_v = a if a > 0 else ''
1159 a_v = a if a > 0 else ''
1154 d_v = d if d > 0 else ''
1160 d_v = d if d > 0 else ''
1155
1161
1156 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
1162 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
1157 cgen('a', a_v, d_v), a_p, a_v
1163 cgen('a', a_v, d_v), a_p, a_v
1158 )
1164 )
1159 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
1165 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
1160 cgen('d', a_v, d_v), d_p, d_v
1166 cgen('d', a_v, d_v), d_p, d_v
1161 )
1167 )
1162 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
1168 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
1163
1169
1164
1170
1165 def urlify_text(text_, safe=True):
1171 def urlify_text(text_, safe=True):
1166 """
1172 """
1167 Extrac urls from text and make html links out of them
1173 Extrac urls from text and make html links out of them
1168
1174
1169 :param text_:
1175 :param text_:
1170 """
1176 """
1171
1177
1172 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
1178 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
1173 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1179 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1174
1180
1175 def url_func(match_obj):
1181 def url_func(match_obj):
1176 url_full = match_obj.groups()[0]
1182 url_full = match_obj.groups()[0]
1177 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1183 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1178 _newtext = url_pat.sub(url_func, text_)
1184 _newtext = url_pat.sub(url_func, text_)
1179 if safe:
1185 if safe:
1180 return literal(_newtext)
1186 return literal(_newtext)
1181 return _newtext
1187 return _newtext
1182
1188
1183
1189
1184 def urlify_changesets(text_, repository):
1190 def urlify_changesets(text_, repository):
1185 """
1191 """
1186 Extract revision ids from changeset and make link from them
1192 Extract revision ids from changeset and make link from them
1187
1193
1188 :param text_:
1194 :param text_:
1189 :param repository: repo name to build the URL with
1195 :param repository: repo name to build the URL with
1190 """
1196 """
1191 from pylons import url # doh, we need to re-import url to mock it later
1197 from pylons import url # doh, we need to re-import url to mock it later
1192 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1198 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1193
1199
1194 def url_func(match_obj):
1200 def url_func(match_obj):
1195 rev = match_obj.groups()[1]
1201 rev = match_obj.groups()[1]
1196 pref = match_obj.groups()[0]
1202 pref = match_obj.groups()[0]
1197 suf = match_obj.groups()[2]
1203 suf = match_obj.groups()[2]
1198
1204
1199 tmpl = (
1205 tmpl = (
1200 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1206 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1201 '%(rev)s</a>%(suf)s'
1207 '%(rev)s</a>%(suf)s'
1202 )
1208 )
1203 return tmpl % {
1209 return tmpl % {
1204 'pref': pref,
1210 'pref': pref,
1205 'cls': 'revision-link',
1211 'cls': 'revision-link',
1206 'url': url('changeset_home', repo_name=repository, revision=rev),
1212 'url': url('changeset_home', repo_name=repository, revision=rev),
1207 'rev': rev,
1213 'rev': rev,
1208 'suf': suf
1214 'suf': suf
1209 }
1215 }
1210
1216
1211 newtext = URL_PAT.sub(url_func, text_)
1217 newtext = URL_PAT.sub(url_func, text_)
1212
1218
1213 return newtext
1219 return newtext
1214
1220
1215
1221
1216 def urlify_commit(text_, repository=None, link_=None):
1222 def urlify_commit(text_, repository=None, link_=None):
1217 """
1223 """
1218 Parses given text message and makes proper links.
1224 Parses given text message and makes proper links.
1219 issues are linked to given issue-server, and rest is a changeset link
1225 issues are linked to given issue-server, and rest is a changeset link
1220 if link_ is given, in other case it's a plain text
1226 if link_ is given, in other case it's a plain text
1221
1227
1222 :param text_:
1228 :param text_:
1223 :param repository:
1229 :param repository:
1224 :param link_: changeset link
1230 :param link_: changeset link
1225 """
1231 """
1226 import traceback
1232 import traceback
1227 from pylons import url # doh, we need to re-import url to mock it later
1233 from pylons import url # doh, we need to re-import url to mock it later
1228
1234
1229 def escaper(string):
1235 def escaper(string):
1230 return string.replace('<', '&lt;').replace('>', '&gt;')
1236 return string.replace('<', '&lt;').replace('>', '&gt;')
1231
1237
1232 def linkify_others(t, l):
1238 def linkify_others(t, l):
1233 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1239 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1234 links = []
1240 links = []
1235 for e in urls.split(t):
1241 for e in urls.split(t):
1236 if not urls.match(e):
1242 if not urls.match(e):
1237 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1243 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1238 else:
1244 else:
1239 links.append(e)
1245 links.append(e)
1240
1246
1241 return ''.join(links)
1247 return ''.join(links)
1242
1248
1243 # urlify changesets - extrac revisions and make link out of them
1249 # urlify changesets - extrac revisions and make link out of them
1244 newtext = urlify_changesets(escaper(text_), repository)
1250 newtext = urlify_changesets(escaper(text_), repository)
1245
1251
1246 # extract http/https links and make them real urls
1252 # extract http/https links and make them real urls
1247 newtext = urlify_text(newtext, safe=False)
1253 newtext = urlify_text(newtext, safe=False)
1248
1254
1249 try:
1255 try:
1250 from rhodecode import CONFIG
1256 from rhodecode import CONFIG
1251 conf = CONFIG
1257 conf = CONFIG
1252
1258
1253 # allow multiple issue servers to be used
1259 # allow multiple issue servers to be used
1254 valid_indices = [
1260 valid_indices = [
1255 x.group(1)
1261 x.group(1)
1256 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1262 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1257 if x and 'issue_server_link%s' % x.group(1) in conf
1263 if x and 'issue_server_link%s' % x.group(1) in conf
1258 and 'issue_prefix%s' % x.group(1) in conf
1264 and 'issue_prefix%s' % x.group(1) in conf
1259 ]
1265 ]
1260
1266
1261 log.debug('found issue server suffixes `%s` during valuation of: %s'
1267 log.debug('found issue server suffixes `%s` during valuation of: %s'
1262 % (','.join(valid_indices), newtext))
1268 % (','.join(valid_indices), newtext))
1263
1269
1264 for pattern_index in valid_indices:
1270 for pattern_index in valid_indices:
1265 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1271 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1266 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1272 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1267 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1273 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1268
1274
1269 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1275 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1270 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1276 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1271 ISSUE_PREFIX))
1277 ISSUE_PREFIX))
1272
1278
1273 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1279 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1274
1280
1275 def url_func(match_obj):
1281 def url_func(match_obj):
1276 pref = ''
1282 pref = ''
1277 if match_obj.group().startswith(' '):
1283 if match_obj.group().startswith(' '):
1278 pref = ' '
1284 pref = ' '
1279
1285
1280 issue_id = ''.join(match_obj.groups())
1286 issue_id = ''.join(match_obj.groups())
1281 tmpl = (
1287 tmpl = (
1282 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1288 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1283 '%(issue-prefix)s%(id-repr)s'
1289 '%(issue-prefix)s%(id-repr)s'
1284 '</a>'
1290 '</a>'
1285 )
1291 )
1286 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1292 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1287 if repository:
1293 if repository:
1288 url = url.replace('{repo}', repository)
1294 url = url.replace('{repo}', repository)
1289 repo_name = repository.split(URL_SEP)[-1]
1295 repo_name = repository.split(URL_SEP)[-1]
1290 url = url.replace('{repo_name}', repo_name)
1296 url = url.replace('{repo_name}', repo_name)
1291
1297
1292 return tmpl % {
1298 return tmpl % {
1293 'pref': pref,
1299 'pref': pref,
1294 'cls': 'issue-tracker-link',
1300 'cls': 'issue-tracker-link',
1295 'url': url,
1301 'url': url,
1296 'id-repr': issue_id,
1302 'id-repr': issue_id,
1297 'issue-prefix': ISSUE_PREFIX,
1303 'issue-prefix': ISSUE_PREFIX,
1298 'serv': ISSUE_SERVER_LNK,
1304 'serv': ISSUE_SERVER_LNK,
1299 }
1305 }
1300 newtext = URL_PAT.sub(url_func, newtext)
1306 newtext = URL_PAT.sub(url_func, newtext)
1301 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1307 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1302
1308
1303 # if we actually did something above
1309 # if we actually did something above
1304 if link_:
1310 if link_:
1305 # wrap not links into final link => link_
1311 # wrap not links into final link => link_
1306 newtext = linkify_others(newtext, link_)
1312 newtext = linkify_others(newtext, link_)
1307 except Exception:
1313 except Exception:
1308 log.error(traceback.format_exc())
1314 log.error(traceback.format_exc())
1309 pass
1315 pass
1310
1316
1311 return literal(newtext)
1317 return literal(newtext)
1312
1318
1313
1319
1314 def rst(source):
1320 def rst(source):
1315 return literal('<div class="rst-block">%s</div>' %
1321 return literal('<div class="rst-block">%s</div>' %
1316 MarkupRenderer.rst(source))
1322 MarkupRenderer.rst(source))
1317
1323
1318
1324
1319 def rst_w_mentions(source):
1325 def rst_w_mentions(source):
1320 """
1326 """
1321 Wrapped rst renderer with @mention highlighting
1327 Wrapped rst renderer with @mention highlighting
1322
1328
1323 :param source:
1329 :param source:
1324 """
1330 """
1325 return literal('<div class="rst-block">%s</div>' %
1331 return literal('<div class="rst-block">%s</div>' %
1326 MarkupRenderer.rst_with_mentions(source))
1332 MarkupRenderer.rst_with_mentions(source))
1327
1333
1328
1334
1329 def changeset_status(repo, revision):
1335 def changeset_status(repo, revision):
1330 return ChangesetStatusModel().get_status(repo, revision)
1336 return ChangesetStatusModel().get_status(repo, revision)
1331
1337
1332
1338
1333 def changeset_status_lbl(changeset_status):
1339 def changeset_status_lbl(changeset_status):
1334 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1340 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1335
1341
1336
1342
1337 def get_permission_name(key):
1343 def get_permission_name(key):
1338 return dict(Permission.PERMS).get(key)
1344 return dict(Permission.PERMS).get(key)
1339
1345
1340
1346
1341 def journal_filter_help():
1347 def journal_filter_help():
1342 return _(textwrap.dedent('''
1348 return _(textwrap.dedent('''
1343 Example filter terms:
1349 Example filter terms:
1344 repository:vcs
1350 repository:vcs
1345 username:marcin
1351 username:marcin
1346 action:*push*
1352 action:*push*
1347 ip:127.0.0.1
1353 ip:127.0.0.1
1348 date:20120101
1354 date:20120101
1349 date:[20120101100000 TO 20120102]
1355 date:[20120101100000 TO 20120102]
1350
1356
1351 Generate wildcards using '*' character:
1357 Generate wildcards using '*' character:
1352 "repositroy:vcs*" - search everything starting with 'vcs'
1358 "repositroy:vcs*" - search everything starting with 'vcs'
1353 "repository:*vcs*" - search for repository containing 'vcs'
1359 "repository:*vcs*" - search for repository containing 'vcs'
1354
1360
1355 Optional AND / OR operators in queries
1361 Optional AND / OR operators in queries
1356 "repository:vcs OR repository:test"
1362 "repository:vcs OR repository:test"
1357 "username:test AND repository:test*"
1363 "username:test AND repository:test*"
1358 '''))
1364 '''))
1359
1365
1360
1366
1361 def not_mapped_error(repo_name):
1367 def not_mapped_error(repo_name):
1362 flash(_('%s repository is not mapped to db perhaps'
1368 flash(_('%s repository is not mapped to db perhaps'
1363 ' it was created or renamed from the filesystem'
1369 ' it was created or renamed from the filesystem'
1364 ' please run the application again'
1370 ' please run the application again'
1365 ' in order to rescan repositories') % repo_name, category='error')
1371 ' in order to rescan repositories') % repo_name, category='error')
1366
1372
1367
1373
1368 def ip_range(ip_addr):
1374 def ip_range(ip_addr):
1369 from rhodecode.model.db import UserIpMap
1375 from rhodecode.model.db import UserIpMap
1370 s, e = UserIpMap._get_ip_range(ip_addr)
1376 s, e = UserIpMap._get_ip_range(ip_addr)
1371 return '%s - %s' % (s, e)
1377 return '%s - %s' % (s, e)
@@ -1,2218 +1,2223 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 time
27 import time
28 import logging
28 import logging
29 import datetime
29 import datetime
30 import traceback
30 import traceback
31 import hashlib
31 import hashlib
32 import collections
32 import collections
33
33
34 from sqlalchemy import *
34 from sqlalchemy import *
35 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.ext.hybrid import hybrid_property
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 from sqlalchemy.exc import DatabaseError
37 from sqlalchemy.exc import DatabaseError
38 from beaker.cache import cache_region, region_invalidate
38 from beaker.cache import cache_region, region_invalidate
39 from webob.exc import HTTPNotFound
39 from webob.exc import HTTPNotFound
40
40
41 from pylons.i18n.translation import lazy_ugettext as _
41 from pylons.i18n.translation import lazy_ugettext as _
42
42
43 from rhodecode.lib.vcs import get_backend
43 from rhodecode.lib.vcs import get_backend
44 from rhodecode.lib.vcs.utils.helpers import get_scm
44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 from rhodecode.lib.vcs.exceptions import VCSError
45 from rhodecode.lib.vcs.exceptions import VCSError
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48
48
49 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
50 safe_unicode, remove_suffix, remove_prefix, time_to_datetime, _set_extras
50 safe_unicode, remove_suffix, remove_prefix, time_to_datetime, _set_extras
51 from rhodecode.lib.compat import json
51 from rhodecode.lib.compat import json
52 from rhodecode.lib.caching_query import FromCache
52 from rhodecode.lib.caching_query import FromCache
53
53
54 from rhodecode.model.meta import Base, Session
54 from rhodecode.model.meta import Base, Session
55
55
56 URL_SEP = '/'
56 URL_SEP = '/'
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59 #==============================================================================
59 #==============================================================================
60 # BASE CLASSES
60 # BASE CLASSES
61 #==============================================================================
61 #==============================================================================
62
62
63 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
64
64
65
65
66 class BaseModel(object):
66 class BaseModel(object):
67 """
67 """
68 Base Model for all classess
68 Base Model for all classess
69 """
69 """
70
70
71 @classmethod
71 @classmethod
72 def _get_keys(cls):
72 def _get_keys(cls):
73 """return column names for this model """
73 """return column names for this model """
74 return class_mapper(cls).c.keys()
74 return class_mapper(cls).c.keys()
75
75
76 def get_dict(self):
76 def get_dict(self):
77 """
77 """
78 return dict with keys and values corresponding
78 return dict with keys and values corresponding
79 to this model data """
79 to this model data """
80
80
81 d = {}
81 d = {}
82 for k in self._get_keys():
82 for k in self._get_keys():
83 d[k] = getattr(self, k)
83 d[k] = getattr(self, k)
84
84
85 # also use __json__() if present to get additional fields
85 # also use __json__() if present to get additional fields
86 _json_attr = getattr(self, '__json__', None)
86 _json_attr = getattr(self, '__json__', None)
87 if _json_attr:
87 if _json_attr:
88 # update with attributes from __json__
88 # update with attributes from __json__
89 if callable(_json_attr):
89 if callable(_json_attr):
90 _json_attr = _json_attr()
90 _json_attr = _json_attr()
91 for k, val in _json_attr.iteritems():
91 for k, val in _json_attr.iteritems():
92 d[k] = val
92 d[k] = val
93 return d
93 return d
94
94
95 def get_appstruct(self):
95 def get_appstruct(self):
96 """return list with keys and values tupples corresponding
96 """return list with keys and values tupples corresponding
97 to this model data """
97 to this model data """
98
98
99 l = []
99 l = []
100 for k in self._get_keys():
100 for k in self._get_keys():
101 l.append((k, getattr(self, k),))
101 l.append((k, getattr(self, k),))
102 return l
102 return l
103
103
104 def populate_obj(self, populate_dict):
104 def populate_obj(self, populate_dict):
105 """populate model with data from given populate_dict"""
105 """populate model with data from given populate_dict"""
106
106
107 for k in self._get_keys():
107 for k in self._get_keys():
108 if k in populate_dict:
108 if k in populate_dict:
109 setattr(self, k, populate_dict[k])
109 setattr(self, k, populate_dict[k])
110
110
111 @classmethod
111 @classmethod
112 def query(cls):
112 def query(cls):
113 return Session().query(cls)
113 return Session().query(cls)
114
114
115 @classmethod
115 @classmethod
116 def get(cls, id_):
116 def get(cls, id_):
117 if id_:
117 if id_:
118 return cls.query().get(id_)
118 return cls.query().get(id_)
119
119
120 @classmethod
120 @classmethod
121 def get_or_404(cls, id_):
121 def get_or_404(cls, id_):
122 try:
122 try:
123 id_ = int(id_)
123 id_ = int(id_)
124 except (TypeError, ValueError):
124 except (TypeError, ValueError):
125 raise HTTPNotFound
125 raise HTTPNotFound
126
126
127 res = cls.query().get(id_)
127 res = cls.query().get(id_)
128 if not res:
128 if not res:
129 raise HTTPNotFound
129 raise HTTPNotFound
130 return res
130 return res
131
131
132 @classmethod
132 @classmethod
133 def getAll(cls):
133 def getAll(cls):
134 # deprecated and left for backward compatibility
134 # deprecated and left for backward compatibility
135 return cls.get_all()
135 return cls.get_all()
136
136
137 @classmethod
137 @classmethod
138 def get_all(cls):
138 def get_all(cls):
139 return cls.query().all()
139 return cls.query().all()
140
140
141 @classmethod
141 @classmethod
142 def delete(cls, id_):
142 def delete(cls, id_):
143 obj = cls.query().get(id_)
143 obj = cls.query().get(id_)
144 Session().delete(obj)
144 Session().delete(obj)
145
145
146 def __repr__(self):
146 def __repr__(self):
147 if hasattr(self, '__unicode__'):
147 if hasattr(self, '__unicode__'):
148 # python repr needs to return str
148 # python repr needs to return str
149 return safe_str(self.__unicode__())
149 return safe_str(self.__unicode__())
150 return '<DB:%s>' % (self.__class__.__name__)
150 return '<DB:%s>' % (self.__class__.__name__)
151
151
152
152
153 class RhodeCodeSetting(Base, BaseModel):
153 class RhodeCodeSetting(Base, BaseModel):
154 __tablename__ = 'rhodecode_settings'
154 __tablename__ = 'rhodecode_settings'
155 __table_args__ = (
155 __table_args__ = (
156 UniqueConstraint('app_settings_name'),
156 UniqueConstraint('app_settings_name'),
157 {'extend_existing': True, 'mysql_engine': 'InnoDB',
157 {'extend_existing': True, 'mysql_engine': 'InnoDB',
158 'mysql_charset': 'utf8'}
158 'mysql_charset': 'utf8'}
159 )
159 )
160 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
160 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
161 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
161 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
162 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
162 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
163
163
164 def __init__(self, k='', v=''):
164 def __init__(self, k='', v=''):
165 self.app_settings_name = k
165 self.app_settings_name = k
166 self.app_settings_value = v
166 self.app_settings_value = v
167
167
168 @validates('_app_settings_value')
168 @validates('_app_settings_value')
169 def validate_settings_value(self, key, val):
169 def validate_settings_value(self, key, val):
170 assert type(val) == unicode
170 assert type(val) == unicode
171 return val
171 return val
172
172
173 @hybrid_property
173 @hybrid_property
174 def app_settings_value(self):
174 def app_settings_value(self):
175 v = self._app_settings_value
175 v = self._app_settings_value
176 if self.app_settings_name in ["ldap_active",
176 if self.app_settings_name in ["ldap_active",
177 "default_repo_enable_statistics",
177 "default_repo_enable_statistics",
178 "default_repo_enable_locking",
178 "default_repo_enable_locking",
179 "default_repo_private",
179 "default_repo_private",
180 "default_repo_enable_downloads"]:
180 "default_repo_enable_downloads"]:
181 v = str2bool(v)
181 v = str2bool(v)
182 return v
182 return v
183
183
184 @app_settings_value.setter
184 @app_settings_value.setter
185 def app_settings_value(self, val):
185 def app_settings_value(self, val):
186 """
186 """
187 Setter that will always make sure we use unicode in app_settings_value
187 Setter that will always make sure we use unicode in app_settings_value
188
188
189 :param val:
189 :param val:
190 """
190 """
191 self._app_settings_value = safe_unicode(val)
191 self._app_settings_value = safe_unicode(val)
192
192
193 def __unicode__(self):
193 def __unicode__(self):
194 return u"<%s('%s:%s')>" % (
194 return u"<%s('%s:%s')>" % (
195 self.__class__.__name__,
195 self.__class__.__name__,
196 self.app_settings_name, self.app_settings_value
196 self.app_settings_name, self.app_settings_value
197 )
197 )
198
198
199 @classmethod
199 @classmethod
200 def get_by_name(cls, key):
200 def get_by_name(cls, key):
201 return cls.query()\
201 return cls.query()\
202 .filter(cls.app_settings_name == key).scalar()
202 .filter(cls.app_settings_name == key).scalar()
203
203
204 @classmethod
204 @classmethod
205 def get_by_name_or_create(cls, key):
205 def get_by_name_or_create(cls, key):
206 res = cls.get_by_name(key)
206 res = cls.get_by_name(key)
207 if not res:
207 if not res:
208 res = cls(key)
208 res = cls(key)
209 return res
209 return res
210
210
211 @classmethod
211 @classmethod
212 def get_app_settings(cls, cache=False):
212 def get_app_settings(cls, cache=False):
213
213
214 ret = cls.query()
214 ret = cls.query()
215
215
216 if cache:
216 if cache:
217 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
217 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
218
218
219 if not ret:
219 if not ret:
220 raise Exception('Could not get application settings !')
220 raise Exception('Could not get application settings !')
221 settings = {}
221 settings = {}
222 for each in ret:
222 for each in ret:
223 settings['rhodecode_' + each.app_settings_name] = \
223 settings['rhodecode_' + each.app_settings_name] = \
224 each.app_settings_value
224 each.app_settings_value
225
225
226 return settings
226 return settings
227
227
228 @classmethod
228 @classmethod
229 def get_ldap_settings(cls, cache=False):
229 def get_ldap_settings(cls, cache=False):
230 ret = cls.query()\
230 ret = cls.query()\
231 .filter(cls.app_settings_name.startswith('ldap_')).all()
231 .filter(cls.app_settings_name.startswith('ldap_')).all()
232 fd = {}
232 fd = {}
233 for row in ret:
233 for row in ret:
234 fd.update({row.app_settings_name: row.app_settings_value})
234 fd.update({row.app_settings_name: row.app_settings_value})
235
235
236 return fd
236 return fd
237
237
238 @classmethod
238 @classmethod
239 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
239 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
240 ret = cls.query()\
240 ret = cls.query()\
241 .filter(cls.app_settings_name.startswith('default_')).all()
241 .filter(cls.app_settings_name.startswith('default_')).all()
242 fd = {}
242 fd = {}
243 for row in ret:
243 for row in ret:
244 key = row.app_settings_name
244 key = row.app_settings_name
245 if strip_prefix:
245 if strip_prefix:
246 key = remove_prefix(key, prefix='default_')
246 key = remove_prefix(key, prefix='default_')
247 fd.update({key: row.app_settings_value})
247 fd.update({key: row.app_settings_value})
248
248
249 return fd
249 return fd
250
250
251
251
252 class RhodeCodeUi(Base, BaseModel):
252 class RhodeCodeUi(Base, BaseModel):
253 __tablename__ = 'rhodecode_ui'
253 __tablename__ = 'rhodecode_ui'
254 __table_args__ = (
254 __table_args__ = (
255 UniqueConstraint('ui_key'),
255 UniqueConstraint('ui_key'),
256 {'extend_existing': True, 'mysql_engine': 'InnoDB',
256 {'extend_existing': True, 'mysql_engine': 'InnoDB',
257 'mysql_charset': 'utf8'}
257 'mysql_charset': 'utf8'}
258 )
258 )
259
259
260 HOOK_UPDATE = 'changegroup.update'
260 HOOK_UPDATE = 'changegroup.update'
261 HOOK_REPO_SIZE = 'changegroup.repo_size'
261 HOOK_REPO_SIZE = 'changegroup.repo_size'
262 HOOK_PUSH = 'changegroup.push_logger'
262 HOOK_PUSH = 'changegroup.push_logger'
263 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
263 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
264 HOOK_PULL = 'outgoing.pull_logger'
264 HOOK_PULL = 'outgoing.pull_logger'
265 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
265 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
266
266
267 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
267 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
268 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
270 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
270 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
271 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
271 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
272
272
273 @classmethod
273 @classmethod
274 def get_by_key(cls, key):
274 def get_by_key(cls, key):
275 return cls.query().filter(cls.ui_key == key).scalar()
275 return cls.query().filter(cls.ui_key == key).scalar()
276
276
277 @classmethod
277 @classmethod
278 def get_builtin_hooks(cls):
278 def get_builtin_hooks(cls):
279 q = cls.query()
279 q = cls.query()
280 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
280 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
281 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
281 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
282 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
282 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
283 return q.all()
283 return q.all()
284
284
285 @classmethod
285 @classmethod
286 def get_custom_hooks(cls):
286 def get_custom_hooks(cls):
287 q = cls.query()
287 q = cls.query()
288 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
288 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
289 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
289 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
290 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
290 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
291 q = q.filter(cls.ui_section == 'hooks')
291 q = q.filter(cls.ui_section == 'hooks')
292 return q.all()
292 return q.all()
293
293
294 @classmethod
294 @classmethod
295 def get_repos_location(cls):
295 def get_repos_location(cls):
296 return cls.get_by_key('/').ui_value
296 return cls.get_by_key('/').ui_value
297
297
298 @classmethod
298 @classmethod
299 def create_or_update_hook(cls, key, val):
299 def create_or_update_hook(cls, key, val):
300 new_ui = cls.get_by_key(key) or cls()
300 new_ui = cls.get_by_key(key) or cls()
301 new_ui.ui_section = 'hooks'
301 new_ui.ui_section = 'hooks'
302 new_ui.ui_active = True
302 new_ui.ui_active = True
303 new_ui.ui_key = key
303 new_ui.ui_key = key
304 new_ui.ui_value = val
304 new_ui.ui_value = val
305
305
306 Session().add(new_ui)
306 Session().add(new_ui)
307
307
308 def __repr__(self):
308 def __repr__(self):
309 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
309 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
310 self.ui_value)
310 self.ui_value)
311
311
312
312
313 class User(Base, BaseModel):
313 class User(Base, BaseModel):
314 __tablename__ = 'users'
314 __tablename__ = 'users'
315 __table_args__ = (
315 __table_args__ = (
316 UniqueConstraint('username'), UniqueConstraint('email'),
316 UniqueConstraint('username'), UniqueConstraint('email'),
317 Index('u_username_idx', 'username'),
317 Index('u_username_idx', 'username'),
318 Index('u_email_idx', 'email'),
318 Index('u_email_idx', 'email'),
319 {'extend_existing': True, 'mysql_engine': 'InnoDB',
319 {'extend_existing': True, 'mysql_engine': 'InnoDB',
320 'mysql_charset': 'utf8'}
320 'mysql_charset': 'utf8'}
321 )
321 )
322 DEFAULT_USER = 'default'
322 DEFAULT_USER = 'default'
323
323
324 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
324 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
325 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
325 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
326 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
326 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
327 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
328 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
328 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
329 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
332 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
333 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
333 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
334 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
334 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
335 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
335 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
336
336
337 user_log = relationship('UserLog')
337 user_log = relationship('UserLog')
338 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
338 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
339
339
340 repositories = relationship('Repository')
340 repositories = relationship('Repository')
341 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
341 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
342 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
342 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
343
343
344 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
344 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
345 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
345 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
346
346
347 group_member = relationship('UserGroupMember', cascade='all')
347 group_member = relationship('UserGroupMember', cascade='all')
348
348
349 notifications = relationship('UserNotification', cascade='all')
349 notifications = relationship('UserNotification', cascade='all')
350 # notifications assigned to this user
350 # notifications assigned to this user
351 user_created_notifications = relationship('Notification', cascade='all')
351 user_created_notifications = relationship('Notification', cascade='all')
352 # comments created by this user
352 # comments created by this user
353 user_comments = relationship('ChangesetComment', cascade='all')
353 user_comments = relationship('ChangesetComment', cascade='all')
354 #extra emails for this user
354 #extra emails for this user
355 user_emails = relationship('UserEmailMap', cascade='all')
355 user_emails = relationship('UserEmailMap', cascade='all')
356
356
357 @hybrid_property
357 @hybrid_property
358 def email(self):
358 def email(self):
359 return self._email
359 return self._email
360
360
361 @email.setter
361 @email.setter
362 def email(self, val):
362 def email(self, val):
363 self._email = val.lower() if val else None
363 self._email = val.lower() if val else None
364
364
365 @property
365 @property
366 def firstname(self):
366 def firstname(self):
367 # alias for future
367 # alias for future
368 return self.name
368 return self.name
369
369
370 @property
370 @property
371 def emails(self):
371 def emails(self):
372 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
372 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
373 return [self.email] + [x.email for x in other]
373 return [self.email] + [x.email for x in other]
374
374
375 @property
375 @property
376 def ip_addresses(self):
376 def ip_addresses(self):
377 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
377 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
378 return [x.ip_addr for x in ret]
378 return [x.ip_addr for x in ret]
379
379
380 @property
380 @property
381 def username_and_name(self):
381 def username_and_name(self):
382 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
382 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
383
383
384 @property
384 @property
385 def full_name(self):
385 def full_name(self):
386 return '%s %s' % (self.firstname, self.lastname)
386 return '%s %s' % (self.firstname, self.lastname)
387
387
388 @property
388 @property
389 def full_name_or_username(self):
389 def full_name_or_username(self):
390 return ('%s %s' % (self.firstname, self.lastname)
390 return ('%s %s' % (self.firstname, self.lastname)
391 if (self.firstname and self.lastname) else self.username)
391 if (self.firstname and self.lastname) else self.username)
392
392
393 @property
393 @property
394 def full_contact(self):
394 def full_contact(self):
395 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
395 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
396
396
397 @property
397 @property
398 def short_contact(self):
398 def short_contact(self):
399 return '%s %s' % (self.firstname, self.lastname)
399 return '%s %s' % (self.firstname, self.lastname)
400
400
401 @property
401 @property
402 def is_admin(self):
402 def is_admin(self):
403 return self.admin
403 return self.admin
404
404
405 @property
405 @property
406 def AuthUser(self):
406 def AuthUser(self):
407 """
407 """
408 Returns instance of AuthUser for this user
408 Returns instance of AuthUser for this user
409 """
409 """
410 from rhodecode.lib.auth import AuthUser
410 from rhodecode.lib.auth import AuthUser
411 return AuthUser(user_id=self.user_id, api_key=self.api_key,
411 return AuthUser(user_id=self.user_id, api_key=self.api_key,
412 username=self.username)
412 username=self.username)
413
413
414 def __unicode__(self):
414 def __unicode__(self):
415 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
415 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
416 self.user_id, self.username)
416 self.user_id, self.username)
417
417
418 @classmethod
418 @classmethod
419 def get_by_username(cls, username, case_insensitive=False, cache=False):
419 def get_by_username(cls, username, case_insensitive=False, cache=False):
420 if case_insensitive:
420 if case_insensitive:
421 q = cls.query().filter(cls.username.ilike(username))
421 q = cls.query().filter(cls.username.ilike(username))
422 else:
422 else:
423 q = cls.query().filter(cls.username == username)
423 q = cls.query().filter(cls.username == username)
424
424
425 if cache:
425 if cache:
426 q = q.options(FromCache(
426 q = q.options(FromCache(
427 "sql_cache_short",
427 "sql_cache_short",
428 "get_user_%s" % _hash_key(username)
428 "get_user_%s" % _hash_key(username)
429 )
429 )
430 )
430 )
431 return q.scalar()
431 return q.scalar()
432
432
433 @classmethod
433 @classmethod
434 def get_by_api_key(cls, api_key, cache=False):
434 def get_by_api_key(cls, api_key, cache=False):
435 q = cls.query().filter(cls.api_key == api_key)
435 q = cls.query().filter(cls.api_key == api_key)
436
436
437 if cache:
437 if cache:
438 q = q.options(FromCache("sql_cache_short",
438 q = q.options(FromCache("sql_cache_short",
439 "get_api_key_%s" % api_key))
439 "get_api_key_%s" % api_key))
440 return q.scalar()
440 return q.scalar()
441
441
442 @classmethod
442 @classmethod
443 def get_by_email(cls, email, case_insensitive=False, cache=False):
443 def get_by_email(cls, email, case_insensitive=False, cache=False):
444 if case_insensitive:
444 if case_insensitive:
445 q = cls.query().filter(cls.email.ilike(email))
445 q = cls.query().filter(cls.email.ilike(email))
446 else:
446 else:
447 q = cls.query().filter(cls.email == email)
447 q = cls.query().filter(cls.email == email)
448
448
449 if cache:
449 if cache:
450 q = q.options(FromCache("sql_cache_short",
450 q = q.options(FromCache("sql_cache_short",
451 "get_email_key_%s" % email))
451 "get_email_key_%s" % email))
452
452
453 ret = q.scalar()
453 ret = q.scalar()
454 if ret is None:
454 if ret is None:
455 q = UserEmailMap.query()
455 q = UserEmailMap.query()
456 # try fetching in alternate email map
456 # try fetching in alternate email map
457 if case_insensitive:
457 if case_insensitive:
458 q = q.filter(UserEmailMap.email.ilike(email))
458 q = q.filter(UserEmailMap.email.ilike(email))
459 else:
459 else:
460 q = q.filter(UserEmailMap.email == email)
460 q = q.filter(UserEmailMap.email == email)
461 q = q.options(joinedload(UserEmailMap.user))
461 q = q.options(joinedload(UserEmailMap.user))
462 if cache:
462 if cache:
463 q = q.options(FromCache("sql_cache_short",
463 q = q.options(FromCache("sql_cache_short",
464 "get_email_map_key_%s" % email))
464 "get_email_map_key_%s" % email))
465 ret = getattr(q.scalar(), 'user', None)
465 ret = getattr(q.scalar(), 'user', None)
466
466
467 return ret
467 return ret
468
468
469 @classmethod
469 @classmethod
470 def get_from_cs_author(cls, author):
470 def get_from_cs_author(cls, author):
471 """
471 """
472 Tries to get User objects out of commit author string
472 Tries to get User objects out of commit author string
473
473
474 :param author:
474 :param author:
475 """
475 """
476 from rhodecode.lib.helpers import email, author_name
476 from rhodecode.lib.helpers import email, author_name
477 # Valid email in the attribute passed, see if they're in the system
477 # Valid email in the attribute passed, see if they're in the system
478 _email = email(author)
478 _email = email(author)
479 if _email:
479 if _email:
480 user = cls.get_by_email(_email, case_insensitive=True)
480 user = cls.get_by_email(_email, case_insensitive=True)
481 if user:
481 if user:
482 return user
482 return user
483 # Maybe we can match by username?
483 # Maybe we can match by username?
484 _author = author_name(author)
484 _author = author_name(author)
485 user = cls.get_by_username(_author, case_insensitive=True)
485 user = cls.get_by_username(_author, case_insensitive=True)
486 if user:
486 if user:
487 return user
487 return user
488
488
489 def update_lastlogin(self):
489 def update_lastlogin(self):
490 """Update user lastlogin"""
490 """Update user lastlogin"""
491 self.last_login = datetime.datetime.now()
491 self.last_login = datetime.datetime.now()
492 Session().add(self)
492 Session().add(self)
493 log.debug('updated user %s lastlogin' % self.username)
493 log.debug('updated user %s lastlogin' % self.username)
494
494
495 @classmethod
495 @classmethod
496 def get_first_admin(cls):
496 def get_first_admin(cls):
497 user = User.query().filter(User.admin == True).first()
497 user = User.query().filter(User.admin == True).first()
498 if user is None:
498 if user is None:
499 raise Exception('Missing administrative account!')
499 raise Exception('Missing administrative account!')
500 return user
500 return user
501
501
502 @classmethod
502 @classmethod
503 def get_default_user(cls, cache=False):
503 def get_default_user(cls, cache=False):
504 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
504 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
505 if user is None:
505 if user is None:
506 raise Exception('Missing default account!')
506 raise Exception('Missing default account!')
507 return user
507 return user
508
508
509 def get_api_data(self):
509 def get_api_data(self):
510 """
510 """
511 Common function for generating user related data for API
511 Common function for generating user related data for API
512 """
512 """
513 user = self
513 user = self
514 data = dict(
514 data = dict(
515 user_id=user.user_id,
515 user_id=user.user_id,
516 username=user.username,
516 username=user.username,
517 firstname=user.name,
517 firstname=user.name,
518 lastname=user.lastname,
518 lastname=user.lastname,
519 email=user.email,
519 email=user.email,
520 emails=user.emails,
520 emails=user.emails,
521 api_key=user.api_key,
521 api_key=user.api_key,
522 active=user.active,
522 active=user.active,
523 admin=user.admin,
523 admin=user.admin,
524 ldap_dn=user.ldap_dn,
524 ldap_dn=user.ldap_dn,
525 last_login=user.last_login,
525 last_login=user.last_login,
526 ip_addresses=user.ip_addresses
526 ip_addresses=user.ip_addresses
527 )
527 )
528 return data
528 return data
529
529
530 def __json__(self):
530 def __json__(self):
531 data = dict(
531 data = dict(
532 full_name=self.full_name,
532 full_name=self.full_name,
533 full_name_or_username=self.full_name_or_username,
533 full_name_or_username=self.full_name_or_username,
534 short_contact=self.short_contact,
534 short_contact=self.short_contact,
535 full_contact=self.full_contact
535 full_contact=self.full_contact
536 )
536 )
537 data.update(self.get_api_data())
537 data.update(self.get_api_data())
538 return data
538 return data
539
539
540
540
541 class UserEmailMap(Base, BaseModel):
541 class UserEmailMap(Base, BaseModel):
542 __tablename__ = 'user_email_map'
542 __tablename__ = 'user_email_map'
543 __table_args__ = (
543 __table_args__ = (
544 Index('uem_email_idx', 'email'),
544 Index('uem_email_idx', 'email'),
545 UniqueConstraint('email'),
545 UniqueConstraint('email'),
546 {'extend_existing': True, 'mysql_engine': 'InnoDB',
546 {'extend_existing': True, 'mysql_engine': 'InnoDB',
547 'mysql_charset': 'utf8'}
547 'mysql_charset': 'utf8'}
548 )
548 )
549 __mapper_args__ = {}
549 __mapper_args__ = {}
550
550
551 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
551 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
552 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
552 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
553 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
553 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
554 user = relationship('User', lazy='joined')
554 user = relationship('User', lazy='joined')
555
555
556 @validates('_email')
556 @validates('_email')
557 def validate_email(self, key, email):
557 def validate_email(self, key, email):
558 # check if this email is not main one
558 # check if this email is not main one
559 main_email = Session().query(User).filter(User.email == email).scalar()
559 main_email = Session().query(User).filter(User.email == email).scalar()
560 if main_email is not None:
560 if main_email is not None:
561 raise AttributeError('email %s is present is user table' % email)
561 raise AttributeError('email %s is present is user table' % email)
562 return email
562 return email
563
563
564 @hybrid_property
564 @hybrid_property
565 def email(self):
565 def email(self):
566 return self._email
566 return self._email
567
567
568 @email.setter
568 @email.setter
569 def email(self, val):
569 def email(self, val):
570 self._email = val.lower() if val else None
570 self._email = val.lower() if val else None
571
571
572
572
573 class UserIpMap(Base, BaseModel):
573 class UserIpMap(Base, BaseModel):
574 __tablename__ = 'user_ip_map'
574 __tablename__ = 'user_ip_map'
575 __table_args__ = (
575 __table_args__ = (
576 UniqueConstraint('user_id', 'ip_addr'),
576 UniqueConstraint('user_id', 'ip_addr'),
577 {'extend_existing': True, 'mysql_engine': 'InnoDB',
577 {'extend_existing': True, 'mysql_engine': 'InnoDB',
578 'mysql_charset': 'utf8'}
578 'mysql_charset': 'utf8'}
579 )
579 )
580 __mapper_args__ = {}
580 __mapper_args__ = {}
581
581
582 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
582 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
583 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
583 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
584 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
584 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
585 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
585 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
586 user = relationship('User', lazy='joined')
586 user = relationship('User', lazy='joined')
587
587
588 @classmethod
588 @classmethod
589 def _get_ip_range(cls, ip_addr):
589 def _get_ip_range(cls, ip_addr):
590 from rhodecode.lib import ipaddr
590 from rhodecode.lib import ipaddr
591 net = ipaddr.IPNetwork(address=ip_addr)
591 net = ipaddr.IPNetwork(address=ip_addr)
592 return [str(net.network), str(net.broadcast)]
592 return [str(net.network), str(net.broadcast)]
593
593
594 def __json__(self):
594 def __json__(self):
595 return dict(
595 return dict(
596 ip_addr=self.ip_addr,
596 ip_addr=self.ip_addr,
597 ip_range=self._get_ip_range(self.ip_addr)
597 ip_range=self._get_ip_range(self.ip_addr)
598 )
598 )
599
599
600
600
601 class UserLog(Base, BaseModel):
601 class UserLog(Base, BaseModel):
602 __tablename__ = 'user_logs'
602 __tablename__ = 'user_logs'
603 __table_args__ = (
603 __table_args__ = (
604 {'extend_existing': True, 'mysql_engine': 'InnoDB',
604 {'extend_existing': True, 'mysql_engine': 'InnoDB',
605 'mysql_charset': 'utf8'},
605 'mysql_charset': 'utf8'},
606 )
606 )
607 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
607 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
608 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
608 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
609 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
609 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
610 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
610 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
611 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
611 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
612 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
612 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
613 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
613 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
614 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
614 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
615
615
616 def __unicode__(self):
617 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
618 self.repository_name,
619 self.action)
620
616 @property
621 @property
617 def action_as_day(self):
622 def action_as_day(self):
618 return datetime.date(*self.action_date.timetuple()[:3])
623 return datetime.date(*self.action_date.timetuple()[:3])
619
624
620 user = relationship('User')
625 user = relationship('User')
621 repository = relationship('Repository', cascade='')
626 repository = relationship('Repository', cascade='')
622
627
623
628
624 class UserGroup(Base, BaseModel):
629 class UserGroup(Base, BaseModel):
625 __tablename__ = 'users_groups'
630 __tablename__ = 'users_groups'
626 __table_args__ = (
631 __table_args__ = (
627 {'extend_existing': True, 'mysql_engine': 'InnoDB',
632 {'extend_existing': True, 'mysql_engine': 'InnoDB',
628 'mysql_charset': 'utf8'},
633 'mysql_charset': 'utf8'},
629 )
634 )
630
635
631 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
636 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
632 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
637 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
633 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
638 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
634 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
639 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
635 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
640 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
636
641
637 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
642 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
638 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
643 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
639 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
644 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
640 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
645 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
641 user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
646 user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
642 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
647 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
643
648
644 user = relationship('User')
649 user = relationship('User')
645
650
646 def __unicode__(self):
651 def __unicode__(self):
647 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
652 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
648 self.users_group_id,
653 self.users_group_id,
649 self.users_group_name)
654 self.users_group_name)
650
655
651 @classmethod
656 @classmethod
652 def get_by_group_name(cls, group_name, cache=False,
657 def get_by_group_name(cls, group_name, cache=False,
653 case_insensitive=False):
658 case_insensitive=False):
654 if case_insensitive:
659 if case_insensitive:
655 q = cls.query().filter(cls.users_group_name.ilike(group_name))
660 q = cls.query().filter(cls.users_group_name.ilike(group_name))
656 else:
661 else:
657 q = cls.query().filter(cls.users_group_name == group_name)
662 q = cls.query().filter(cls.users_group_name == group_name)
658 if cache:
663 if cache:
659 q = q.options(FromCache(
664 q = q.options(FromCache(
660 "sql_cache_short",
665 "sql_cache_short",
661 "get_user_%s" % _hash_key(group_name)
666 "get_user_%s" % _hash_key(group_name)
662 )
667 )
663 )
668 )
664 return q.scalar()
669 return q.scalar()
665
670
666 @classmethod
671 @classmethod
667 def get(cls, users_group_id, cache=False):
672 def get(cls, users_group_id, cache=False):
668 users_group = cls.query()
673 users_group = cls.query()
669 if cache:
674 if cache:
670 users_group = users_group.options(FromCache("sql_cache_short",
675 users_group = users_group.options(FromCache("sql_cache_short",
671 "get_users_group_%s" % users_group_id))
676 "get_users_group_%s" % users_group_id))
672 return users_group.get(users_group_id)
677 return users_group.get(users_group_id)
673
678
674 def get_api_data(self):
679 def get_api_data(self):
675 users_group = self
680 users_group = self
676
681
677 data = dict(
682 data = dict(
678 users_group_id=users_group.users_group_id,
683 users_group_id=users_group.users_group_id,
679 group_name=users_group.users_group_name,
684 group_name=users_group.users_group_name,
680 active=users_group.users_group_active,
685 active=users_group.users_group_active,
681 )
686 )
682
687
683 return data
688 return data
684
689
685
690
686 class UserGroupMember(Base, BaseModel):
691 class UserGroupMember(Base, BaseModel):
687 __tablename__ = 'users_groups_members'
692 __tablename__ = 'users_groups_members'
688 __table_args__ = (
693 __table_args__ = (
689 {'extend_existing': True, 'mysql_engine': 'InnoDB',
694 {'extend_existing': True, 'mysql_engine': 'InnoDB',
690 'mysql_charset': 'utf8'},
695 'mysql_charset': 'utf8'},
691 )
696 )
692
697
693 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
698 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
694 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
699 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
695 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
700 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
696
701
697 user = relationship('User', lazy='joined')
702 user = relationship('User', lazy='joined')
698 users_group = relationship('UserGroup')
703 users_group = relationship('UserGroup')
699
704
700 def __init__(self, gr_id='', u_id=''):
705 def __init__(self, gr_id='', u_id=''):
701 self.users_group_id = gr_id
706 self.users_group_id = gr_id
702 self.user_id = u_id
707 self.user_id = u_id
703
708
704
709
705 class RepositoryField(Base, BaseModel):
710 class RepositoryField(Base, BaseModel):
706 __tablename__ = 'repositories_fields'
711 __tablename__ = 'repositories_fields'
707 __table_args__ = (
712 __table_args__ = (
708 UniqueConstraint('repository_id', 'field_key'), # no-multi field
713 UniqueConstraint('repository_id', 'field_key'), # no-multi field
709 {'extend_existing': True, 'mysql_engine': 'InnoDB',
714 {'extend_existing': True, 'mysql_engine': 'InnoDB',
710 'mysql_charset': 'utf8'},
715 'mysql_charset': 'utf8'},
711 )
716 )
712 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
717 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
713
718
714 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
719 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
715 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
720 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
716 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
721 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
717 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
722 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
718 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
723 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
719 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
724 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
720 field_type = Column("field_type", String(256), nullable=False, unique=None)
725 field_type = Column("field_type", String(256), nullable=False, unique=None)
721 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
726 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
722
727
723 repository = relationship('Repository')
728 repository = relationship('Repository')
724
729
725 @property
730 @property
726 def field_key_prefixed(self):
731 def field_key_prefixed(self):
727 return 'ex_%s' % self.field_key
732 return 'ex_%s' % self.field_key
728
733
729 @classmethod
734 @classmethod
730 def un_prefix_key(cls, key):
735 def un_prefix_key(cls, key):
731 if key.startswith(cls.PREFIX):
736 if key.startswith(cls.PREFIX):
732 return key[len(cls.PREFIX):]
737 return key[len(cls.PREFIX):]
733 return key
738 return key
734
739
735 @classmethod
740 @classmethod
736 def get_by_key_name(cls, key, repo):
741 def get_by_key_name(cls, key, repo):
737 row = cls.query()\
742 row = cls.query()\
738 .filter(cls.repository == repo)\
743 .filter(cls.repository == repo)\
739 .filter(cls.field_key == key).scalar()
744 .filter(cls.field_key == key).scalar()
740 return row
745 return row
741
746
742
747
743 class Repository(Base, BaseModel):
748 class Repository(Base, BaseModel):
744 __tablename__ = 'repositories'
749 __tablename__ = 'repositories'
745 __table_args__ = (
750 __table_args__ = (
746 UniqueConstraint('repo_name'),
751 UniqueConstraint('repo_name'),
747 Index('r_repo_name_idx', 'repo_name'),
752 Index('r_repo_name_idx', 'repo_name'),
748 {'extend_existing': True, 'mysql_engine': 'InnoDB',
753 {'extend_existing': True, 'mysql_engine': 'InnoDB',
749 'mysql_charset': 'utf8'},
754 'mysql_charset': 'utf8'},
750 )
755 )
751
756
752 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
757 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
753 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
758 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
754 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
759 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
755 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
760 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
756 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
761 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
757 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
762 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
758 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
763 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
759 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
764 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
760 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
765 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
761 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
766 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
762 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
767 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
763 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
768 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
764 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
769 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
765 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
770 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
766 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
771 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
767
772
768 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
773 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
769 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
774 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
770
775
771 user = relationship('User')
776 user = relationship('User')
772 fork = relationship('Repository', remote_side=repo_id)
777 fork = relationship('Repository', remote_side=repo_id)
773 group = relationship('RepoGroup')
778 group = relationship('RepoGroup')
774 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
779 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
775 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
780 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
776 stats = relationship('Statistics', cascade='all', uselist=False)
781 stats = relationship('Statistics', cascade='all', uselist=False)
777
782
778 followers = relationship('UserFollowing',
783 followers = relationship('UserFollowing',
779 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
784 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
780 cascade='all')
785 cascade='all')
781 extra_fields = relationship('RepositoryField',
786 extra_fields = relationship('RepositoryField',
782 cascade="all, delete, delete-orphan")
787 cascade="all, delete, delete-orphan")
783
788
784 logs = relationship('UserLog')
789 logs = relationship('UserLog')
785 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
790 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
786
791
787 pull_requests_org = relationship('PullRequest',
792 pull_requests_org = relationship('PullRequest',
788 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
793 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
789 cascade="all, delete, delete-orphan")
794 cascade="all, delete, delete-orphan")
790
795
791 pull_requests_other = relationship('PullRequest',
796 pull_requests_other = relationship('PullRequest',
792 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
797 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
793 cascade="all, delete, delete-orphan")
798 cascade="all, delete, delete-orphan")
794
799
795 def __unicode__(self):
800 def __unicode__(self):
796 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
801 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
797 self.repo_name)
802 self.repo_name)
798
803
799 @hybrid_property
804 @hybrid_property
800 def locked(self):
805 def locked(self):
801 # always should return [user_id, timelocked]
806 # always should return [user_id, timelocked]
802 if self._locked:
807 if self._locked:
803 _lock_info = self._locked.split(':')
808 _lock_info = self._locked.split(':')
804 return int(_lock_info[0]), _lock_info[1]
809 return int(_lock_info[0]), _lock_info[1]
805 return [None, None]
810 return [None, None]
806
811
807 @locked.setter
812 @locked.setter
808 def locked(self, val):
813 def locked(self, val):
809 if val and isinstance(val, (list, tuple)):
814 if val and isinstance(val, (list, tuple)):
810 self._locked = ':'.join(map(str, val))
815 self._locked = ':'.join(map(str, val))
811 else:
816 else:
812 self._locked = None
817 self._locked = None
813
818
814 @hybrid_property
819 @hybrid_property
815 def changeset_cache(self):
820 def changeset_cache(self):
816 from rhodecode.lib.vcs.backends.base import EmptyChangeset
821 from rhodecode.lib.vcs.backends.base import EmptyChangeset
817 dummy = EmptyChangeset().__json__()
822 dummy = EmptyChangeset().__json__()
818 if not self._changeset_cache:
823 if not self._changeset_cache:
819 return dummy
824 return dummy
820 try:
825 try:
821 return json.loads(self._changeset_cache)
826 return json.loads(self._changeset_cache)
822 except TypeError:
827 except TypeError:
823 return dummy
828 return dummy
824
829
825 @changeset_cache.setter
830 @changeset_cache.setter
826 def changeset_cache(self, val):
831 def changeset_cache(self, val):
827 try:
832 try:
828 self._changeset_cache = json.dumps(val)
833 self._changeset_cache = json.dumps(val)
829 except Exception:
834 except Exception:
830 log.error(traceback.format_exc())
835 log.error(traceback.format_exc())
831
836
832 @classmethod
837 @classmethod
833 def url_sep(cls):
838 def url_sep(cls):
834 return URL_SEP
839 return URL_SEP
835
840
836 @classmethod
841 @classmethod
837 def normalize_repo_name(cls, repo_name):
842 def normalize_repo_name(cls, repo_name):
838 """
843 """
839 Normalizes os specific repo_name to the format internally stored inside
844 Normalizes os specific repo_name to the format internally stored inside
840 dabatabase using URL_SEP
845 dabatabase using URL_SEP
841
846
842 :param cls:
847 :param cls:
843 :param repo_name:
848 :param repo_name:
844 """
849 """
845 return cls.url_sep().join(repo_name.split(os.sep))
850 return cls.url_sep().join(repo_name.split(os.sep))
846
851
847 @classmethod
852 @classmethod
848 def get_by_repo_name(cls, repo_name):
853 def get_by_repo_name(cls, repo_name):
849 q = Session().query(cls).filter(cls.repo_name == repo_name)
854 q = Session().query(cls).filter(cls.repo_name == repo_name)
850 q = q.options(joinedload(Repository.fork))\
855 q = q.options(joinedload(Repository.fork))\
851 .options(joinedload(Repository.user))\
856 .options(joinedload(Repository.user))\
852 .options(joinedload(Repository.group))
857 .options(joinedload(Repository.group))
853 return q.scalar()
858 return q.scalar()
854
859
855 @classmethod
860 @classmethod
856 def get_by_full_path(cls, repo_full_path):
861 def get_by_full_path(cls, repo_full_path):
857 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
862 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
858 repo_name = cls.normalize_repo_name(repo_name)
863 repo_name = cls.normalize_repo_name(repo_name)
859 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
864 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
860
865
861 @classmethod
866 @classmethod
862 def get_repo_forks(cls, repo_id):
867 def get_repo_forks(cls, repo_id):
863 return cls.query().filter(Repository.fork_id == repo_id)
868 return cls.query().filter(Repository.fork_id == repo_id)
864
869
865 @classmethod
870 @classmethod
866 def base_path(cls):
871 def base_path(cls):
867 """
872 """
868 Returns base path when all repos are stored
873 Returns base path when all repos are stored
869
874
870 :param cls:
875 :param cls:
871 """
876 """
872 q = Session().query(RhodeCodeUi)\
877 q = Session().query(RhodeCodeUi)\
873 .filter(RhodeCodeUi.ui_key == cls.url_sep())
878 .filter(RhodeCodeUi.ui_key == cls.url_sep())
874 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
879 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
875 return q.one().ui_value
880 return q.one().ui_value
876
881
877 @property
882 @property
878 def forks(self):
883 def forks(self):
879 """
884 """
880 Return forks of this repo
885 Return forks of this repo
881 """
886 """
882 return Repository.get_repo_forks(self.repo_id)
887 return Repository.get_repo_forks(self.repo_id)
883
888
884 @property
889 @property
885 def parent(self):
890 def parent(self):
886 """
891 """
887 Returns fork parent
892 Returns fork parent
888 """
893 """
889 return self.fork
894 return self.fork
890
895
891 @property
896 @property
892 def just_name(self):
897 def just_name(self):
893 return self.repo_name.split(Repository.url_sep())[-1]
898 return self.repo_name.split(Repository.url_sep())[-1]
894
899
895 @property
900 @property
896 def groups_with_parents(self):
901 def groups_with_parents(self):
897 groups = []
902 groups = []
898 if self.group is None:
903 if self.group is None:
899 return groups
904 return groups
900
905
901 cur_gr = self.group
906 cur_gr = self.group
902 groups.insert(0, cur_gr)
907 groups.insert(0, cur_gr)
903 while 1:
908 while 1:
904 gr = getattr(cur_gr, 'parent_group', None)
909 gr = getattr(cur_gr, 'parent_group', None)
905 cur_gr = cur_gr.parent_group
910 cur_gr = cur_gr.parent_group
906 if gr is None:
911 if gr is None:
907 break
912 break
908 groups.insert(0, gr)
913 groups.insert(0, gr)
909
914
910 return groups
915 return groups
911
916
912 @property
917 @property
913 def groups_and_repo(self):
918 def groups_and_repo(self):
914 return self.groups_with_parents, self.just_name, self.repo_name
919 return self.groups_with_parents, self.just_name, self.repo_name
915
920
916 @LazyProperty
921 @LazyProperty
917 def repo_path(self):
922 def repo_path(self):
918 """
923 """
919 Returns base full path for that repository means where it actually
924 Returns base full path for that repository means where it actually
920 exists on a filesystem
925 exists on a filesystem
921 """
926 """
922 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
927 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
923 Repository.url_sep())
928 Repository.url_sep())
924 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
929 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
925 return q.one().ui_value
930 return q.one().ui_value
926
931
927 @property
932 @property
928 def repo_full_path(self):
933 def repo_full_path(self):
929 p = [self.repo_path]
934 p = [self.repo_path]
930 # we need to split the name by / since this is how we store the
935 # we need to split the name by / since this is how we store the
931 # names in the database, but that eventually needs to be converted
936 # names in the database, but that eventually needs to be converted
932 # into a valid system path
937 # into a valid system path
933 p += self.repo_name.split(Repository.url_sep())
938 p += self.repo_name.split(Repository.url_sep())
934 return os.path.join(*map(safe_unicode, p))
939 return os.path.join(*map(safe_unicode, p))
935
940
936 @property
941 @property
937 def cache_keys(self):
942 def cache_keys(self):
938 """
943 """
939 Returns associated cache keys for that repo
944 Returns associated cache keys for that repo
940 """
945 """
941 return CacheInvalidation.query()\
946 return CacheInvalidation.query()\
942 .filter(CacheInvalidation.cache_args == self.repo_name)\
947 .filter(CacheInvalidation.cache_args == self.repo_name)\
943 .order_by(CacheInvalidation.cache_key)\
948 .order_by(CacheInvalidation.cache_key)\
944 .all()
949 .all()
945
950
946 def get_new_name(self, repo_name):
951 def get_new_name(self, repo_name):
947 """
952 """
948 returns new full repository name based on assigned group and new new
953 returns new full repository name based on assigned group and new new
949
954
950 :param group_name:
955 :param group_name:
951 """
956 """
952 path_prefix = self.group.full_path_splitted if self.group else []
957 path_prefix = self.group.full_path_splitted if self.group else []
953 return Repository.url_sep().join(path_prefix + [repo_name])
958 return Repository.url_sep().join(path_prefix + [repo_name])
954
959
955 @property
960 @property
956 def _ui(self):
961 def _ui(self):
957 """
962 """
958 Creates an db based ui object for this repository
963 Creates an db based ui object for this repository
959 """
964 """
960 from rhodecode.lib.utils import make_ui
965 from rhodecode.lib.utils import make_ui
961 return make_ui('db', clear_session=False)
966 return make_ui('db', clear_session=False)
962
967
963 @classmethod
968 @classmethod
964 def is_valid(cls, repo_name):
969 def is_valid(cls, repo_name):
965 """
970 """
966 returns True if given repo name is a valid filesystem repository
971 returns True if given repo name is a valid filesystem repository
967
972
968 :param cls:
973 :param cls:
969 :param repo_name:
974 :param repo_name:
970 """
975 """
971 from rhodecode.lib.utils import is_valid_repo
976 from rhodecode.lib.utils import is_valid_repo
972
977
973 return is_valid_repo(repo_name, cls.base_path())
978 return is_valid_repo(repo_name, cls.base_path())
974
979
975 def get_api_data(self):
980 def get_api_data(self):
976 """
981 """
977 Common function for generating repo api data
982 Common function for generating repo api data
978
983
979 """
984 """
980 repo = self
985 repo = self
981 data = dict(
986 data = dict(
982 repo_id=repo.repo_id,
987 repo_id=repo.repo_id,
983 repo_name=repo.repo_name,
988 repo_name=repo.repo_name,
984 repo_type=repo.repo_type,
989 repo_type=repo.repo_type,
985 clone_uri=repo.clone_uri,
990 clone_uri=repo.clone_uri,
986 private=repo.private,
991 private=repo.private,
987 created_on=repo.created_on,
992 created_on=repo.created_on,
988 description=repo.description,
993 description=repo.description,
989 landing_rev=repo.landing_rev,
994 landing_rev=repo.landing_rev,
990 owner=repo.user.username,
995 owner=repo.user.username,
991 fork_of=repo.fork.repo_name if repo.fork else None,
996 fork_of=repo.fork.repo_name if repo.fork else None,
992 enable_statistics=repo.enable_statistics,
997 enable_statistics=repo.enable_statistics,
993 enable_locking=repo.enable_locking,
998 enable_locking=repo.enable_locking,
994 enable_downloads=repo.enable_downloads,
999 enable_downloads=repo.enable_downloads,
995 last_changeset=repo.changeset_cache,
1000 last_changeset=repo.changeset_cache,
996 locked_by=User.get(self.locked[0]).get_api_data() \
1001 locked_by=User.get(self.locked[0]).get_api_data() \
997 if self.locked[0] else None,
1002 if self.locked[0] else None,
998 locked_date=time_to_datetime(self.locked[1]) \
1003 locked_date=time_to_datetime(self.locked[1]) \
999 if self.locked[1] else None
1004 if self.locked[1] else None
1000 )
1005 )
1001 rc_config = RhodeCodeSetting.get_app_settings()
1006 rc_config = RhodeCodeSetting.get_app_settings()
1002 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
1007 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
1003 if repository_fields:
1008 if repository_fields:
1004 for f in self.extra_fields:
1009 for f in self.extra_fields:
1005 data[f.field_key_prefixed] = f.field_value
1010 data[f.field_key_prefixed] = f.field_value
1006
1011
1007 return data
1012 return data
1008
1013
1009 @classmethod
1014 @classmethod
1010 def lock(cls, repo, user_id, lock_time=None):
1015 def lock(cls, repo, user_id, lock_time=None):
1011 if not lock_time:
1016 if not lock_time:
1012 lock_time = time.time()
1017 lock_time = time.time()
1013 repo.locked = [user_id, lock_time]
1018 repo.locked = [user_id, lock_time]
1014 Session().add(repo)
1019 Session().add(repo)
1015 Session().commit()
1020 Session().commit()
1016
1021
1017 @classmethod
1022 @classmethod
1018 def unlock(cls, repo):
1023 def unlock(cls, repo):
1019 repo.locked = None
1024 repo.locked = None
1020 Session().add(repo)
1025 Session().add(repo)
1021 Session().commit()
1026 Session().commit()
1022
1027
1023 @classmethod
1028 @classmethod
1024 def getlock(cls, repo):
1029 def getlock(cls, repo):
1025 return repo.locked
1030 return repo.locked
1026
1031
1027 @property
1032 @property
1028 def last_db_change(self):
1033 def last_db_change(self):
1029 return self.updated_on
1034 return self.updated_on
1030
1035
1031 def clone_url(self, **override):
1036 def clone_url(self, **override):
1032 from pylons import url
1037 from pylons import url
1033 from urlparse import urlparse
1038 from urlparse import urlparse
1034 import urllib
1039 import urllib
1035 parsed_url = urlparse(url('home', qualified=True))
1040 parsed_url = urlparse(url('home', qualified=True))
1036 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1041 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1037 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1042 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1038 args = {
1043 args = {
1039 'user': '',
1044 'user': '',
1040 'pass': '',
1045 'pass': '',
1041 'scheme': parsed_url.scheme,
1046 'scheme': parsed_url.scheme,
1042 'netloc': parsed_url.netloc,
1047 'netloc': parsed_url.netloc,
1043 'prefix': decoded_path,
1048 'prefix': decoded_path,
1044 'path': self.repo_name
1049 'path': self.repo_name
1045 }
1050 }
1046
1051
1047 args.update(override)
1052 args.update(override)
1048 return default_clone_uri % args
1053 return default_clone_uri % args
1049
1054
1050 #==========================================================================
1055 #==========================================================================
1051 # SCM PROPERTIES
1056 # SCM PROPERTIES
1052 #==========================================================================
1057 #==========================================================================
1053
1058
1054 def get_changeset(self, rev=None):
1059 def get_changeset(self, rev=None):
1055 return get_changeset_safe(self.scm_instance, rev)
1060 return get_changeset_safe(self.scm_instance, rev)
1056
1061
1057 def get_landing_changeset(self):
1062 def get_landing_changeset(self):
1058 """
1063 """
1059 Returns landing changeset, or if that doesn't exist returns the tip
1064 Returns landing changeset, or if that doesn't exist returns the tip
1060 """
1065 """
1061 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1066 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1062 return cs
1067 return cs
1063
1068
1064 def update_changeset_cache(self, cs_cache=None):
1069 def update_changeset_cache(self, cs_cache=None):
1065 """
1070 """
1066 Update cache of last changeset for repository, keys should be::
1071 Update cache of last changeset for repository, keys should be::
1067
1072
1068 short_id
1073 short_id
1069 raw_id
1074 raw_id
1070 revision
1075 revision
1071 message
1076 message
1072 date
1077 date
1073 author
1078 author
1074
1079
1075 :param cs_cache:
1080 :param cs_cache:
1076 """
1081 """
1077 from rhodecode.lib.vcs.backends.base import BaseChangeset
1082 from rhodecode.lib.vcs.backends.base import BaseChangeset
1078 if cs_cache is None:
1083 if cs_cache is None:
1079 cs_cache = EmptyChangeset()
1084 cs_cache = EmptyChangeset()
1080 # use no-cache version here
1085 # use no-cache version here
1081 scm_repo = self.scm_instance_no_cache()
1086 scm_repo = self.scm_instance_no_cache()
1082 if scm_repo:
1087 if scm_repo:
1083 cs_cache = scm_repo.get_changeset()
1088 cs_cache = scm_repo.get_changeset()
1084
1089
1085 if isinstance(cs_cache, BaseChangeset):
1090 if isinstance(cs_cache, BaseChangeset):
1086 cs_cache = cs_cache.__json__()
1091 cs_cache = cs_cache.__json__()
1087
1092
1088 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1093 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1089 _default = datetime.datetime.fromtimestamp(0)
1094 _default = datetime.datetime.fromtimestamp(0)
1090 last_change = cs_cache.get('date') or _default
1095 last_change = cs_cache.get('date') or _default
1091 log.debug('updated repo %s with new cs cache %s'
1096 log.debug('updated repo %s with new cs cache %s'
1092 % (self.repo_name, cs_cache))
1097 % (self.repo_name, cs_cache))
1093 self.updated_on = last_change
1098 self.updated_on = last_change
1094 self.changeset_cache = cs_cache
1099 self.changeset_cache = cs_cache
1095 Session().add(self)
1100 Session().add(self)
1096 Session().commit()
1101 Session().commit()
1097 else:
1102 else:
1098 log.debug('Skipping repo:%s already with latest changes'
1103 log.debug('Skipping repo:%s already with latest changes'
1099 % self.repo_name)
1104 % self.repo_name)
1100
1105
1101 @property
1106 @property
1102 def tip(self):
1107 def tip(self):
1103 return self.get_changeset('tip')
1108 return self.get_changeset('tip')
1104
1109
1105 @property
1110 @property
1106 def author(self):
1111 def author(self):
1107 return self.tip.author
1112 return self.tip.author
1108
1113
1109 @property
1114 @property
1110 def last_change(self):
1115 def last_change(self):
1111 return self.scm_instance.last_change
1116 return self.scm_instance.last_change
1112
1117
1113 def get_comments(self, revisions=None):
1118 def get_comments(self, revisions=None):
1114 """
1119 """
1115 Returns comments for this repository grouped by revisions
1120 Returns comments for this repository grouped by revisions
1116
1121
1117 :param revisions: filter query by revisions only
1122 :param revisions: filter query by revisions only
1118 """
1123 """
1119 cmts = ChangesetComment.query()\
1124 cmts = ChangesetComment.query()\
1120 .filter(ChangesetComment.repo == self)
1125 .filter(ChangesetComment.repo == self)
1121 if revisions:
1126 if revisions:
1122 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1127 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1123 grouped = collections.defaultdict(list)
1128 grouped = collections.defaultdict(list)
1124 for cmt in cmts.all():
1129 for cmt in cmts.all():
1125 grouped[cmt.revision].append(cmt)
1130 grouped[cmt.revision].append(cmt)
1126 return grouped
1131 return grouped
1127
1132
1128 def statuses(self, revisions=None):
1133 def statuses(self, revisions=None):
1129 """
1134 """
1130 Returns statuses for this repository
1135 Returns statuses for this repository
1131
1136
1132 :param revisions: list of revisions to get statuses for
1137 :param revisions: list of revisions to get statuses for
1133 """
1138 """
1134
1139
1135 statuses = ChangesetStatus.query()\
1140 statuses = ChangesetStatus.query()\
1136 .filter(ChangesetStatus.repo == self)\
1141 .filter(ChangesetStatus.repo == self)\
1137 .filter(ChangesetStatus.version == 0)
1142 .filter(ChangesetStatus.version == 0)
1138 if revisions:
1143 if revisions:
1139 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1144 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1140 grouped = {}
1145 grouped = {}
1141
1146
1142 #maybe we have open new pullrequest without a status ?
1147 #maybe we have open new pullrequest without a status ?
1143 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1148 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1144 status_lbl = ChangesetStatus.get_status_lbl(stat)
1149 status_lbl = ChangesetStatus.get_status_lbl(stat)
1145 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1150 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1146 for rev in pr.revisions:
1151 for rev in pr.revisions:
1147 pr_id = pr.pull_request_id
1152 pr_id = pr.pull_request_id
1148 pr_repo = pr.other_repo.repo_name
1153 pr_repo = pr.other_repo.repo_name
1149 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1154 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1150
1155
1151 for stat in statuses.all():
1156 for stat in statuses.all():
1152 pr_id = pr_repo = None
1157 pr_id = pr_repo = None
1153 if stat.pull_request:
1158 if stat.pull_request:
1154 pr_id = stat.pull_request.pull_request_id
1159 pr_id = stat.pull_request.pull_request_id
1155 pr_repo = stat.pull_request.other_repo.repo_name
1160 pr_repo = stat.pull_request.other_repo.repo_name
1156 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1161 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1157 pr_id, pr_repo]
1162 pr_id, pr_repo]
1158 return grouped
1163 return grouped
1159
1164
1160 def _repo_size(self):
1165 def _repo_size(self):
1161 from rhodecode.lib import helpers as h
1166 from rhodecode.lib import helpers as h
1162 log.debug('calculating repository size...')
1167 log.debug('calculating repository size...')
1163 return h.format_byte_size(self.scm_instance.size)
1168 return h.format_byte_size(self.scm_instance.size)
1164
1169
1165 #==========================================================================
1170 #==========================================================================
1166 # SCM CACHE INSTANCE
1171 # SCM CACHE INSTANCE
1167 #==========================================================================
1172 #==========================================================================
1168
1173
1169 def set_invalidate(self):
1174 def set_invalidate(self):
1170 """
1175 """
1171 Mark caches of this repo as invalid.
1176 Mark caches of this repo as invalid.
1172 """
1177 """
1173 CacheInvalidation.set_invalidate(self.repo_name)
1178 CacheInvalidation.set_invalidate(self.repo_name)
1174
1179
1175 def scm_instance_no_cache(self):
1180 def scm_instance_no_cache(self):
1176 return self.__get_instance()
1181 return self.__get_instance()
1177
1182
1178 @property
1183 @property
1179 def scm_instance(self):
1184 def scm_instance(self):
1180 import rhodecode
1185 import rhodecode
1181 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1186 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1182 if full_cache:
1187 if full_cache:
1183 return self.scm_instance_cached()
1188 return self.scm_instance_cached()
1184 return self.__get_instance()
1189 return self.__get_instance()
1185
1190
1186 def scm_instance_cached(self, valid_cache_keys=None):
1191 def scm_instance_cached(self, valid_cache_keys=None):
1187 @cache_region('long_term')
1192 @cache_region('long_term')
1188 def _c(repo_name):
1193 def _c(repo_name):
1189 return self.__get_instance()
1194 return self.__get_instance()
1190 rn = self.repo_name
1195 rn = self.repo_name
1191
1196
1192 valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
1197 valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
1193 if not valid:
1198 if not valid:
1194 log.debug('Cache for %s invalidated, getting new object' % (rn))
1199 log.debug('Cache for %s invalidated, getting new object' % (rn))
1195 region_invalidate(_c, None, rn)
1200 region_invalidate(_c, None, rn)
1196 else:
1201 else:
1197 log.debug('Getting obj for %s from cache' % (rn))
1202 log.debug('Getting obj for %s from cache' % (rn))
1198 return _c(rn)
1203 return _c(rn)
1199
1204
1200 def __get_instance(self):
1205 def __get_instance(self):
1201 repo_full_path = self.repo_full_path
1206 repo_full_path = self.repo_full_path
1202 try:
1207 try:
1203 alias = get_scm(repo_full_path)[0]
1208 alias = get_scm(repo_full_path)[0]
1204 log.debug('Creating instance of %s repository from %s'
1209 log.debug('Creating instance of %s repository from %s'
1205 % (alias, repo_full_path))
1210 % (alias, repo_full_path))
1206 backend = get_backend(alias)
1211 backend = get_backend(alias)
1207 except VCSError:
1212 except VCSError:
1208 log.error(traceback.format_exc())
1213 log.error(traceback.format_exc())
1209 log.error('Perhaps this repository is in db and not in '
1214 log.error('Perhaps this repository is in db and not in '
1210 'filesystem run rescan repositories with '
1215 'filesystem run rescan repositories with '
1211 '"destroy old data " option from admin panel')
1216 '"destroy old data " option from admin panel')
1212 return
1217 return
1213
1218
1214 if alias == 'hg':
1219 if alias == 'hg':
1215
1220
1216 repo = backend(safe_str(repo_full_path), create=False,
1221 repo = backend(safe_str(repo_full_path), create=False,
1217 baseui=self._ui)
1222 baseui=self._ui)
1218 # skip hidden web repository
1223 # skip hidden web repository
1219 if repo._get_hidden():
1224 if repo._get_hidden():
1220 return
1225 return
1221 else:
1226 else:
1222 repo = backend(repo_full_path, create=False)
1227 repo = backend(repo_full_path, create=False)
1223
1228
1224 return repo
1229 return repo
1225
1230
1226
1231
1227 class RepoGroup(Base, BaseModel):
1232 class RepoGroup(Base, BaseModel):
1228 __tablename__ = 'groups'
1233 __tablename__ = 'groups'
1229 __table_args__ = (
1234 __table_args__ = (
1230 UniqueConstraint('group_name', 'group_parent_id'),
1235 UniqueConstraint('group_name', 'group_parent_id'),
1231 CheckConstraint('group_id != group_parent_id'),
1236 CheckConstraint('group_id != group_parent_id'),
1232 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1237 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1233 'mysql_charset': 'utf8'},
1238 'mysql_charset': 'utf8'},
1234 )
1239 )
1235 __mapper_args__ = {'order_by': 'group_name'}
1240 __mapper_args__ = {'order_by': 'group_name'}
1236
1241
1237 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1242 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1238 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1243 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1239 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1244 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1240 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1245 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1241 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1246 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1242 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1247 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1243
1248
1244 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1249 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1245 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1250 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1246 parent_group = relationship('RepoGroup', remote_side=group_id)
1251 parent_group = relationship('RepoGroup', remote_side=group_id)
1247 user = relationship('User')
1252 user = relationship('User')
1248
1253
1249 def __init__(self, group_name='', parent_group=None):
1254 def __init__(self, group_name='', parent_group=None):
1250 self.group_name = group_name
1255 self.group_name = group_name
1251 self.parent_group = parent_group
1256 self.parent_group = parent_group
1252
1257
1253 def __unicode__(self):
1258 def __unicode__(self):
1254 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
1259 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
1255 self.group_name)
1260 self.group_name)
1256
1261
1257 @classmethod
1262 @classmethod
1258 def groups_choices(cls, groups=None, show_empty_group=True):
1263 def groups_choices(cls, groups=None, show_empty_group=True):
1259 from webhelpers.html import literal as _literal
1264 from webhelpers.html import literal as _literal
1260 if not groups:
1265 if not groups:
1261 groups = cls.query().all()
1266 groups = cls.query().all()
1262
1267
1263 repo_groups = []
1268 repo_groups = []
1264 if show_empty_group:
1269 if show_empty_group:
1265 repo_groups = [('-1', '-- %s --' % _('top level'))]
1270 repo_groups = [('-1', '-- %s --' % _('top level'))]
1266 sep = ' &raquo; '
1271 sep = ' &raquo; '
1267 _name = lambda k: _literal(sep.join(k))
1272 _name = lambda k: _literal(sep.join(k))
1268
1273
1269 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1274 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1270 for x in groups])
1275 for x in groups])
1271
1276
1272 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1277 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1273 return repo_groups
1278 return repo_groups
1274
1279
1275 @classmethod
1280 @classmethod
1276 def url_sep(cls):
1281 def url_sep(cls):
1277 return URL_SEP
1282 return URL_SEP
1278
1283
1279 @classmethod
1284 @classmethod
1280 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1285 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1281 if case_insensitive:
1286 if case_insensitive:
1282 gr = cls.query()\
1287 gr = cls.query()\
1283 .filter(cls.group_name.ilike(group_name))
1288 .filter(cls.group_name.ilike(group_name))
1284 else:
1289 else:
1285 gr = cls.query()\
1290 gr = cls.query()\
1286 .filter(cls.group_name == group_name)
1291 .filter(cls.group_name == group_name)
1287 if cache:
1292 if cache:
1288 gr = gr.options(FromCache(
1293 gr = gr.options(FromCache(
1289 "sql_cache_short",
1294 "sql_cache_short",
1290 "get_group_%s" % _hash_key(group_name)
1295 "get_group_%s" % _hash_key(group_name)
1291 )
1296 )
1292 )
1297 )
1293 return gr.scalar()
1298 return gr.scalar()
1294
1299
1295 @property
1300 @property
1296 def parents(self):
1301 def parents(self):
1297 parents_recursion_limit = 5
1302 parents_recursion_limit = 5
1298 groups = []
1303 groups = []
1299 if self.parent_group is None:
1304 if self.parent_group is None:
1300 return groups
1305 return groups
1301 cur_gr = self.parent_group
1306 cur_gr = self.parent_group
1302 groups.insert(0, cur_gr)
1307 groups.insert(0, cur_gr)
1303 cnt = 0
1308 cnt = 0
1304 while 1:
1309 while 1:
1305 cnt += 1
1310 cnt += 1
1306 gr = getattr(cur_gr, 'parent_group', None)
1311 gr = getattr(cur_gr, 'parent_group', None)
1307 cur_gr = cur_gr.parent_group
1312 cur_gr = cur_gr.parent_group
1308 if gr is None:
1313 if gr is None:
1309 break
1314 break
1310 if cnt == parents_recursion_limit:
1315 if cnt == parents_recursion_limit:
1311 # this will prevent accidental infinit loops
1316 # this will prevent accidental infinit loops
1312 log.error('group nested more than %s' %
1317 log.error('group nested more than %s' %
1313 parents_recursion_limit)
1318 parents_recursion_limit)
1314 break
1319 break
1315
1320
1316 groups.insert(0, gr)
1321 groups.insert(0, gr)
1317 return groups
1322 return groups
1318
1323
1319 @property
1324 @property
1320 def children(self):
1325 def children(self):
1321 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1326 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1322
1327
1323 @property
1328 @property
1324 def name(self):
1329 def name(self):
1325 return self.group_name.split(RepoGroup.url_sep())[-1]
1330 return self.group_name.split(RepoGroup.url_sep())[-1]
1326
1331
1327 @property
1332 @property
1328 def full_path(self):
1333 def full_path(self):
1329 return self.group_name
1334 return self.group_name
1330
1335
1331 @property
1336 @property
1332 def full_path_splitted(self):
1337 def full_path_splitted(self):
1333 return self.group_name.split(RepoGroup.url_sep())
1338 return self.group_name.split(RepoGroup.url_sep())
1334
1339
1335 @property
1340 @property
1336 def repositories(self):
1341 def repositories(self):
1337 return Repository.query()\
1342 return Repository.query()\
1338 .filter(Repository.group == self)\
1343 .filter(Repository.group == self)\
1339 .order_by(Repository.repo_name)
1344 .order_by(Repository.repo_name)
1340
1345
1341 @property
1346 @property
1342 def repositories_recursive_count(self):
1347 def repositories_recursive_count(self):
1343 cnt = self.repositories.count()
1348 cnt = self.repositories.count()
1344
1349
1345 def children_count(group):
1350 def children_count(group):
1346 cnt = 0
1351 cnt = 0
1347 for child in group.children:
1352 for child in group.children:
1348 cnt += child.repositories.count()
1353 cnt += child.repositories.count()
1349 cnt += children_count(child)
1354 cnt += children_count(child)
1350 return cnt
1355 return cnt
1351
1356
1352 return cnt + children_count(self)
1357 return cnt + children_count(self)
1353
1358
1354 def _recursive_objects(self, include_repos=True):
1359 def _recursive_objects(self, include_repos=True):
1355 all_ = []
1360 all_ = []
1356
1361
1357 def _get_members(root_gr):
1362 def _get_members(root_gr):
1358 if include_repos:
1363 if include_repos:
1359 for r in root_gr.repositories:
1364 for r in root_gr.repositories:
1360 all_.append(r)
1365 all_.append(r)
1361 childs = root_gr.children.all()
1366 childs = root_gr.children.all()
1362 if childs:
1367 if childs:
1363 for gr in childs:
1368 for gr in childs:
1364 all_.append(gr)
1369 all_.append(gr)
1365 _get_members(gr)
1370 _get_members(gr)
1366
1371
1367 _get_members(self)
1372 _get_members(self)
1368 return [self] + all_
1373 return [self] + all_
1369
1374
1370 def recursive_groups_and_repos(self):
1375 def recursive_groups_and_repos(self):
1371 """
1376 """
1372 Recursive return all groups, with repositories in those groups
1377 Recursive return all groups, with repositories in those groups
1373 """
1378 """
1374 return self._recursive_objects()
1379 return self._recursive_objects()
1375
1380
1376 def recursive_groups(self):
1381 def recursive_groups(self):
1377 """
1382 """
1378 Returns all children groups for this group including children of children
1383 Returns all children groups for this group including children of children
1379 """
1384 """
1380 return self._recursive_objects(include_repos=False)
1385 return self._recursive_objects(include_repos=False)
1381
1386
1382 def get_new_name(self, group_name):
1387 def get_new_name(self, group_name):
1383 """
1388 """
1384 returns new full group name based on parent and new name
1389 returns new full group name based on parent and new name
1385
1390
1386 :param group_name:
1391 :param group_name:
1387 """
1392 """
1388 path_prefix = (self.parent_group.full_path_splitted if
1393 path_prefix = (self.parent_group.full_path_splitted if
1389 self.parent_group else [])
1394 self.parent_group else [])
1390 return RepoGroup.url_sep().join(path_prefix + [group_name])
1395 return RepoGroup.url_sep().join(path_prefix + [group_name])
1391
1396
1392
1397
1393 class Permission(Base, BaseModel):
1398 class Permission(Base, BaseModel):
1394 __tablename__ = 'permissions'
1399 __tablename__ = 'permissions'
1395 __table_args__ = (
1400 __table_args__ = (
1396 Index('p_perm_name_idx', 'permission_name'),
1401 Index('p_perm_name_idx', 'permission_name'),
1397 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1402 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1398 'mysql_charset': 'utf8'},
1403 'mysql_charset': 'utf8'},
1399 )
1404 )
1400 PERMS = [
1405 PERMS = [
1401 ('hg.admin', _('RhodeCode Administrator')),
1406 ('hg.admin', _('RhodeCode Administrator')),
1402
1407
1403 ('repository.none', _('Repository no access')),
1408 ('repository.none', _('Repository no access')),
1404 ('repository.read', _('Repository read access')),
1409 ('repository.read', _('Repository read access')),
1405 ('repository.write', _('Repository write access')),
1410 ('repository.write', _('Repository write access')),
1406 ('repository.admin', _('Repository admin access')),
1411 ('repository.admin', _('Repository admin access')),
1407
1412
1408 ('group.none', _('Repository group no access')),
1413 ('group.none', _('Repository group no access')),
1409 ('group.read', _('Repository group read access')),
1414 ('group.read', _('Repository group read access')),
1410 ('group.write', _('Repository group write access')),
1415 ('group.write', _('Repository group write access')),
1411 ('group.admin', _('Repository group admin access')),
1416 ('group.admin', _('Repository group admin access')),
1412
1417
1413 ('usergroup.none', _('User group no access')),
1418 ('usergroup.none', _('User group no access')),
1414 ('usergroup.read', _('User group read access')),
1419 ('usergroup.read', _('User group read access')),
1415 ('usergroup.write', _('User group write access')),
1420 ('usergroup.write', _('User group write access')),
1416 ('usergroup.admin', _('User group admin access')),
1421 ('usergroup.admin', _('User group admin access')),
1417
1422
1418 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
1423 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
1419 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
1424 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
1420
1425
1421 ('hg.usergroup.create.false', _('User Group creation disabled')),
1426 ('hg.usergroup.create.false', _('User Group creation disabled')),
1422 ('hg.usergroup.create.true', _('User Group creation enabled')),
1427 ('hg.usergroup.create.true', _('User Group creation enabled')),
1423
1428
1424 ('hg.create.none', _('Repository creation disabled')),
1429 ('hg.create.none', _('Repository creation disabled')),
1425 ('hg.create.repository', _('Repository creation enabled')),
1430 ('hg.create.repository', _('Repository creation enabled')),
1426
1431
1427 ('hg.fork.none', _('Repository forking disabled')),
1432 ('hg.fork.none', _('Repository forking disabled')),
1428 ('hg.fork.repository', _('Repository forking enabled')),
1433 ('hg.fork.repository', _('Repository forking enabled')),
1429
1434
1430 ('hg.register.none', _('Registration disabled')),
1435 ('hg.register.none', _('Registration disabled')),
1431 ('hg.register.manual_activate', _('User Registration with manual account activation')),
1436 ('hg.register.manual_activate', _('User Registration with manual account activation')),
1432 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
1437 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
1433
1438
1434 ('hg.extern_activate.manual', _('Manual activation of external account')),
1439 ('hg.extern_activate.manual', _('Manual activation of external account')),
1435 ('hg.extern_activate.auto', _('Automatic activation of external account')),
1440 ('hg.extern_activate.auto', _('Automatic activation of external account')),
1436
1441
1437 ]
1442 ]
1438
1443
1439 #definition of system default permissions for DEFAULT user
1444 #definition of system default permissions for DEFAULT user
1440 DEFAULT_USER_PERMISSIONS = [
1445 DEFAULT_USER_PERMISSIONS = [
1441 'repository.read',
1446 'repository.read',
1442 'group.read',
1447 'group.read',
1443 'usergroup.read',
1448 'usergroup.read',
1444 'hg.create.repository',
1449 'hg.create.repository',
1445 'hg.fork.repository',
1450 'hg.fork.repository',
1446 'hg.register.manual_activate',
1451 'hg.register.manual_activate',
1447 'hg.extern_activate.auto',
1452 'hg.extern_activate.auto',
1448 ]
1453 ]
1449
1454
1450 # defines which permissions are more important higher the more important
1455 # defines which permissions are more important higher the more important
1451 # Weight defines which permissions are more important.
1456 # Weight defines which permissions are more important.
1452 # The higher number the more important.
1457 # The higher number the more important.
1453 PERM_WEIGHTS = {
1458 PERM_WEIGHTS = {
1454 'repository.none': 0,
1459 'repository.none': 0,
1455 'repository.read': 1,
1460 'repository.read': 1,
1456 'repository.write': 3,
1461 'repository.write': 3,
1457 'repository.admin': 4,
1462 'repository.admin': 4,
1458
1463
1459 'group.none': 0,
1464 'group.none': 0,
1460 'group.read': 1,
1465 'group.read': 1,
1461 'group.write': 3,
1466 'group.write': 3,
1462 'group.admin': 4,
1467 'group.admin': 4,
1463
1468
1464 'usergroup.none': 0,
1469 'usergroup.none': 0,
1465 'usergroup.read': 1,
1470 'usergroup.read': 1,
1466 'usergroup.write': 3,
1471 'usergroup.write': 3,
1467 'usergroup.admin': 4,
1472 'usergroup.admin': 4,
1468 'hg.repogroup.create.false': 0,
1473 'hg.repogroup.create.false': 0,
1469 'hg.repogroup.create.true': 1,
1474 'hg.repogroup.create.true': 1,
1470
1475
1471 'hg.usergroup.create.false': 0,
1476 'hg.usergroup.create.false': 0,
1472 'hg.usergroup.create.true': 1,
1477 'hg.usergroup.create.true': 1,
1473
1478
1474 'hg.fork.none': 0,
1479 'hg.fork.none': 0,
1475 'hg.fork.repository': 1,
1480 'hg.fork.repository': 1,
1476 'hg.create.none': 0,
1481 'hg.create.none': 0,
1477 'hg.create.repository': 1
1482 'hg.create.repository': 1
1478 }
1483 }
1479
1484
1480 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1485 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1481 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1486 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1482 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1487 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1483
1488
1484 def __unicode__(self):
1489 def __unicode__(self):
1485 return u"<%s('%s:%s')>" % (
1490 return u"<%s('%s:%s')>" % (
1486 self.__class__.__name__, self.permission_id, self.permission_name
1491 self.__class__.__name__, self.permission_id, self.permission_name
1487 )
1492 )
1488
1493
1489 @classmethod
1494 @classmethod
1490 def get_by_key(cls, key):
1495 def get_by_key(cls, key):
1491 return cls.query().filter(cls.permission_name == key).scalar()
1496 return cls.query().filter(cls.permission_name == key).scalar()
1492
1497
1493 @classmethod
1498 @classmethod
1494 def get_default_perms(cls, default_user_id):
1499 def get_default_perms(cls, default_user_id):
1495 q = Session().query(UserRepoToPerm, Repository, cls)\
1500 q = Session().query(UserRepoToPerm, Repository, cls)\
1496 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1501 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1497 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1502 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1498 .filter(UserRepoToPerm.user_id == default_user_id)
1503 .filter(UserRepoToPerm.user_id == default_user_id)
1499
1504
1500 return q.all()
1505 return q.all()
1501
1506
1502 @classmethod
1507 @classmethod
1503 def get_default_group_perms(cls, default_user_id):
1508 def get_default_group_perms(cls, default_user_id):
1504 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1509 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1505 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1510 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1506 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1511 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1507 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1512 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1508
1513
1509 return q.all()
1514 return q.all()
1510
1515
1511 @classmethod
1516 @classmethod
1512 def get_default_user_group_perms(cls, default_user_id):
1517 def get_default_user_group_perms(cls, default_user_id):
1513 q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
1518 q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
1514 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
1519 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
1515 .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
1520 .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
1516 .filter(UserUserGroupToPerm.user_id == default_user_id)
1521 .filter(UserUserGroupToPerm.user_id == default_user_id)
1517
1522
1518 return q.all()
1523 return q.all()
1519
1524
1520
1525
1521 class UserRepoToPerm(Base, BaseModel):
1526 class UserRepoToPerm(Base, BaseModel):
1522 __tablename__ = 'repo_to_perm'
1527 __tablename__ = 'repo_to_perm'
1523 __table_args__ = (
1528 __table_args__ = (
1524 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1529 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1525 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1530 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1526 'mysql_charset': 'utf8'}
1531 'mysql_charset': 'utf8'}
1527 )
1532 )
1528 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1533 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1529 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1534 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1530 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1535 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1531 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1536 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1532
1537
1533 user = relationship('User')
1538 user = relationship('User')
1534 repository = relationship('Repository')
1539 repository = relationship('Repository')
1535 permission = relationship('Permission')
1540 permission = relationship('Permission')
1536
1541
1537 @classmethod
1542 @classmethod
1538 def create(cls, user, repository, permission):
1543 def create(cls, user, repository, permission):
1539 n = cls()
1544 n = cls()
1540 n.user = user
1545 n.user = user
1541 n.repository = repository
1546 n.repository = repository
1542 n.permission = permission
1547 n.permission = permission
1543 Session().add(n)
1548 Session().add(n)
1544 return n
1549 return n
1545
1550
1546 def __unicode__(self):
1551 def __unicode__(self):
1547 return u'<%s => %s >' % (self.user, self.repository)
1552 return u'<%s => %s >' % (self.user, self.repository)
1548
1553
1549
1554
1550 class UserUserGroupToPerm(Base, BaseModel):
1555 class UserUserGroupToPerm(Base, BaseModel):
1551 __tablename__ = 'user_user_group_to_perm'
1556 __tablename__ = 'user_user_group_to_perm'
1552 __table_args__ = (
1557 __table_args__ = (
1553 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
1558 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
1554 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1559 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1555 'mysql_charset': 'utf8'}
1560 'mysql_charset': 'utf8'}
1556 )
1561 )
1557 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1562 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1558 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1563 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1559 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1564 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1560 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1565 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1561
1566
1562 user = relationship('User')
1567 user = relationship('User')
1563 user_group = relationship('UserGroup')
1568 user_group = relationship('UserGroup')
1564 permission = relationship('Permission')
1569 permission = relationship('Permission')
1565
1570
1566 @classmethod
1571 @classmethod
1567 def create(cls, user, user_group, permission):
1572 def create(cls, user, user_group, permission):
1568 n = cls()
1573 n = cls()
1569 n.user = user
1574 n.user = user
1570 n.user_group = user_group
1575 n.user_group = user_group
1571 n.permission = permission
1576 n.permission = permission
1572 Session().add(n)
1577 Session().add(n)
1573 return n
1578 return n
1574
1579
1575 def __unicode__(self):
1580 def __unicode__(self):
1576 return u'<%s => %s >' % (self.user, self.user_group)
1581 return u'<%s => %s >' % (self.user, self.user_group)
1577
1582
1578
1583
1579 class UserToPerm(Base, BaseModel):
1584 class UserToPerm(Base, BaseModel):
1580 __tablename__ = 'user_to_perm'
1585 __tablename__ = 'user_to_perm'
1581 __table_args__ = (
1586 __table_args__ = (
1582 UniqueConstraint('user_id', 'permission_id'),
1587 UniqueConstraint('user_id', 'permission_id'),
1583 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1588 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1584 'mysql_charset': 'utf8'}
1589 'mysql_charset': 'utf8'}
1585 )
1590 )
1586 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1591 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1587 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1588 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1593 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1589
1594
1590 user = relationship('User')
1595 user = relationship('User')
1591 permission = relationship('Permission', lazy='joined')
1596 permission = relationship('Permission', lazy='joined')
1592
1597
1593 def __unicode__(self):
1598 def __unicode__(self):
1594 return u'<%s => %s >' % (self.user, self.permission)
1599 return u'<%s => %s >' % (self.user, self.permission)
1595
1600
1596
1601
1597 class UserGroupRepoToPerm(Base, BaseModel):
1602 class UserGroupRepoToPerm(Base, BaseModel):
1598 __tablename__ = 'users_group_repo_to_perm'
1603 __tablename__ = 'users_group_repo_to_perm'
1599 __table_args__ = (
1604 __table_args__ = (
1600 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1605 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1601 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1606 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1602 'mysql_charset': 'utf8'}
1607 'mysql_charset': 'utf8'}
1603 )
1608 )
1604 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1609 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1605 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1610 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1606 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1611 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1607 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1612 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1608
1613
1609 users_group = relationship('UserGroup')
1614 users_group = relationship('UserGroup')
1610 permission = relationship('Permission')
1615 permission = relationship('Permission')
1611 repository = relationship('Repository')
1616 repository = relationship('Repository')
1612
1617
1613 @classmethod
1618 @classmethod
1614 def create(cls, users_group, repository, permission):
1619 def create(cls, users_group, repository, permission):
1615 n = cls()
1620 n = cls()
1616 n.users_group = users_group
1621 n.users_group = users_group
1617 n.repository = repository
1622 n.repository = repository
1618 n.permission = permission
1623 n.permission = permission
1619 Session().add(n)
1624 Session().add(n)
1620 return n
1625 return n
1621
1626
1622 def __unicode__(self):
1627 def __unicode__(self):
1623 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
1628 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
1624
1629
1625
1630
1626 class UserGroupUserGroupToPerm(Base, BaseModel):
1631 class UserGroupUserGroupToPerm(Base, BaseModel):
1627 __tablename__ = 'user_group_user_group_to_perm'
1632 __tablename__ = 'user_group_user_group_to_perm'
1628 __table_args__ = (
1633 __table_args__ = (
1629 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
1634 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
1630 CheckConstraint('target_user_group_id != user_group_id'),
1635 CheckConstraint('target_user_group_id != user_group_id'),
1631 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1636 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1632 'mysql_charset': 'utf8'}
1637 'mysql_charset': 'utf8'}
1633 )
1638 )
1634 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1639 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1635 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1640 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1636 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1641 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1637 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1642 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1638
1643
1639 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
1644 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
1640 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
1645 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
1641 permission = relationship('Permission')
1646 permission = relationship('Permission')
1642
1647
1643 @classmethod
1648 @classmethod
1644 def create(cls, target_user_group, user_group, permission):
1649 def create(cls, target_user_group, user_group, permission):
1645 n = cls()
1650 n = cls()
1646 n.target_user_group = target_user_group
1651 n.target_user_group = target_user_group
1647 n.user_group = user_group
1652 n.user_group = user_group
1648 n.permission = permission
1653 n.permission = permission
1649 Session().add(n)
1654 Session().add(n)
1650 return n
1655 return n
1651
1656
1652 def __unicode__(self):
1657 def __unicode__(self):
1653 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
1658 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
1654
1659
1655
1660
1656 class UserGroupToPerm(Base, BaseModel):
1661 class UserGroupToPerm(Base, BaseModel):
1657 __tablename__ = 'users_group_to_perm'
1662 __tablename__ = 'users_group_to_perm'
1658 __table_args__ = (
1663 __table_args__ = (
1659 UniqueConstraint('users_group_id', 'permission_id',),
1664 UniqueConstraint('users_group_id', 'permission_id',),
1660 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1665 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1661 'mysql_charset': 'utf8'}
1666 'mysql_charset': 'utf8'}
1662 )
1667 )
1663 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1668 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1664 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1669 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1665 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1670 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1666
1671
1667 users_group = relationship('UserGroup')
1672 users_group = relationship('UserGroup')
1668 permission = relationship('Permission')
1673 permission = relationship('Permission')
1669
1674
1670
1675
1671 class UserRepoGroupToPerm(Base, BaseModel):
1676 class UserRepoGroupToPerm(Base, BaseModel):
1672 __tablename__ = 'user_repo_group_to_perm'
1677 __tablename__ = 'user_repo_group_to_perm'
1673 __table_args__ = (
1678 __table_args__ = (
1674 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1679 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1675 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1680 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1676 'mysql_charset': 'utf8'}
1681 'mysql_charset': 'utf8'}
1677 )
1682 )
1678
1683
1679 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1684 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1680 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1685 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1681 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1686 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1682 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1687 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1683
1688
1684 user = relationship('User')
1689 user = relationship('User')
1685 group = relationship('RepoGroup')
1690 group = relationship('RepoGroup')
1686 permission = relationship('Permission')
1691 permission = relationship('Permission')
1687
1692
1688
1693
1689 class UserGroupRepoGroupToPerm(Base, BaseModel):
1694 class UserGroupRepoGroupToPerm(Base, BaseModel):
1690 __tablename__ = 'users_group_repo_group_to_perm'
1695 __tablename__ = 'users_group_repo_group_to_perm'
1691 __table_args__ = (
1696 __table_args__ = (
1692 UniqueConstraint('users_group_id', 'group_id'),
1697 UniqueConstraint('users_group_id', 'group_id'),
1693 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1698 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1694 'mysql_charset': 'utf8'}
1699 'mysql_charset': 'utf8'}
1695 )
1700 )
1696
1701
1697 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1702 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1698 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1703 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1699 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1704 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1700 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1705 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1701
1706
1702 users_group = relationship('UserGroup')
1707 users_group = relationship('UserGroup')
1703 permission = relationship('Permission')
1708 permission = relationship('Permission')
1704 group = relationship('RepoGroup')
1709 group = relationship('RepoGroup')
1705
1710
1706
1711
1707 class Statistics(Base, BaseModel):
1712 class Statistics(Base, BaseModel):
1708 __tablename__ = 'statistics'
1713 __tablename__ = 'statistics'
1709 __table_args__ = (
1714 __table_args__ = (
1710 UniqueConstraint('repository_id'),
1715 UniqueConstraint('repository_id'),
1711 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1716 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1712 'mysql_charset': 'utf8'}
1717 'mysql_charset': 'utf8'}
1713 )
1718 )
1714 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1719 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1715 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1720 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1716 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1721 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1717 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1722 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1718 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1723 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1719 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1724 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1720
1725
1721 repository = relationship('Repository', single_parent=True)
1726 repository = relationship('Repository', single_parent=True)
1722
1727
1723
1728
1724 class UserFollowing(Base, BaseModel):
1729 class UserFollowing(Base, BaseModel):
1725 __tablename__ = 'user_followings'
1730 __tablename__ = 'user_followings'
1726 __table_args__ = (
1731 __table_args__ = (
1727 UniqueConstraint('user_id', 'follows_repository_id'),
1732 UniqueConstraint('user_id', 'follows_repository_id'),
1728 UniqueConstraint('user_id', 'follows_user_id'),
1733 UniqueConstraint('user_id', 'follows_user_id'),
1729 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1734 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1730 'mysql_charset': 'utf8'}
1735 'mysql_charset': 'utf8'}
1731 )
1736 )
1732
1737
1733 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1738 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1734 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1739 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1735 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1740 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1736 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1741 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1737 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1742 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1738
1743
1739 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1744 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1740
1745
1741 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1746 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1742 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1747 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1743
1748
1744 @classmethod
1749 @classmethod
1745 def get_repo_followers(cls, repo_id):
1750 def get_repo_followers(cls, repo_id):
1746 return cls.query().filter(cls.follows_repo_id == repo_id)
1751 return cls.query().filter(cls.follows_repo_id == repo_id)
1747
1752
1748
1753
1749 class CacheInvalidation(Base, BaseModel):
1754 class CacheInvalidation(Base, BaseModel):
1750 __tablename__ = 'cache_invalidation'
1755 __tablename__ = 'cache_invalidation'
1751 __table_args__ = (
1756 __table_args__ = (
1752 UniqueConstraint('cache_key'),
1757 UniqueConstraint('cache_key'),
1753 Index('key_idx', 'cache_key'),
1758 Index('key_idx', 'cache_key'),
1754 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1759 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1755 'mysql_charset': 'utf8'},
1760 'mysql_charset': 'utf8'},
1756 )
1761 )
1757 # cache_id, not used
1762 # cache_id, not used
1758 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1763 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1759 # cache_key as created by _get_cache_key
1764 # cache_key as created by _get_cache_key
1760 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1765 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1761 # cache_args is a repo_name
1766 # cache_args is a repo_name
1762 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1767 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1763 # instance sets cache_active True when it is caching,
1768 # instance sets cache_active True when it is caching,
1764 # other instances set cache_active to False to indicate that this cache is invalid
1769 # other instances set cache_active to False to indicate that this cache is invalid
1765 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1770 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1766
1771
1767 def __init__(self, cache_key, repo_name=''):
1772 def __init__(self, cache_key, repo_name=''):
1768 self.cache_key = cache_key
1773 self.cache_key = cache_key
1769 self.cache_args = repo_name
1774 self.cache_args = repo_name
1770 self.cache_active = False
1775 self.cache_active = False
1771
1776
1772 def __unicode__(self):
1777 def __unicode__(self):
1773 return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
1778 return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
1774 self.cache_id, self.cache_key, self.cache_active)
1779 self.cache_id, self.cache_key, self.cache_active)
1775
1780
1776 def _cache_key_partition(self):
1781 def _cache_key_partition(self):
1777 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
1782 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
1778 return prefix, repo_name, suffix
1783 return prefix, repo_name, suffix
1779
1784
1780 def get_prefix(self):
1785 def get_prefix(self):
1781 """
1786 """
1782 get prefix that might have been used in _get_cache_key to
1787 get prefix that might have been used in _get_cache_key to
1783 generate self.cache_key. Only used for informational purposes
1788 generate self.cache_key. Only used for informational purposes
1784 in repo_edit.html.
1789 in repo_edit.html.
1785 """
1790 """
1786 # prefix, repo_name, suffix
1791 # prefix, repo_name, suffix
1787 return self._cache_key_partition()[0]
1792 return self._cache_key_partition()[0]
1788
1793
1789 def get_suffix(self):
1794 def get_suffix(self):
1790 """
1795 """
1791 get suffix that might have been used in _get_cache_key to
1796 get suffix that might have been used in _get_cache_key to
1792 generate self.cache_key. Only used for informational purposes
1797 generate self.cache_key. Only used for informational purposes
1793 in repo_edit.html.
1798 in repo_edit.html.
1794 """
1799 """
1795 # prefix, repo_name, suffix
1800 # prefix, repo_name, suffix
1796 return self._cache_key_partition()[2]
1801 return self._cache_key_partition()[2]
1797
1802
1798 @classmethod
1803 @classmethod
1799 def clear_cache(cls):
1804 def clear_cache(cls):
1800 """
1805 """
1801 Delete all cache keys from database.
1806 Delete all cache keys from database.
1802 Should only be run when all instances are down and all entries thus stale.
1807 Should only be run when all instances are down and all entries thus stale.
1803 """
1808 """
1804 cls.query().delete()
1809 cls.query().delete()
1805 Session().commit()
1810 Session().commit()
1806
1811
1807 @classmethod
1812 @classmethod
1808 def _get_cache_key(cls, key):
1813 def _get_cache_key(cls, key):
1809 """
1814 """
1810 Wrapper for generating a unique cache key for this instance and "key".
1815 Wrapper for generating a unique cache key for this instance and "key".
1811 key must / will start with a repo_name which will be stored in .cache_args .
1816 key must / will start with a repo_name which will be stored in .cache_args .
1812 """
1817 """
1813 import rhodecode
1818 import rhodecode
1814 prefix = rhodecode.CONFIG.get('instance_id', '')
1819 prefix = rhodecode.CONFIG.get('instance_id', '')
1815 return "%s%s" % (prefix, key)
1820 return "%s%s" % (prefix, key)
1816
1821
1817 @classmethod
1822 @classmethod
1818 def set_invalidate(cls, repo_name):
1823 def set_invalidate(cls, repo_name):
1819 """
1824 """
1820 Mark all caches of a repo as invalid in the database.
1825 Mark all caches of a repo as invalid in the database.
1821 """
1826 """
1822 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1827 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1823
1828
1824 try:
1829 try:
1825 for inv_obj in inv_objs:
1830 for inv_obj in inv_objs:
1826 log.debug('marking %s key for invalidation based on repo_name=%s'
1831 log.debug('marking %s key for invalidation based on repo_name=%s'
1827 % (inv_obj, safe_str(repo_name)))
1832 % (inv_obj, safe_str(repo_name)))
1828 inv_obj.cache_active = False
1833 inv_obj.cache_active = False
1829 Session().add(inv_obj)
1834 Session().add(inv_obj)
1830 Session().commit()
1835 Session().commit()
1831 except Exception:
1836 except Exception:
1832 log.error(traceback.format_exc())
1837 log.error(traceback.format_exc())
1833 Session().rollback()
1838 Session().rollback()
1834
1839
1835 @classmethod
1840 @classmethod
1836 def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
1841 def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
1837 """
1842 """
1838 Mark this cache key as active and currently cached.
1843 Mark this cache key as active and currently cached.
1839 Return True if the existing cache registration still was valid.
1844 Return True if the existing cache registration still was valid.
1840 Return False to indicate that it had been invalidated and caches should be refreshed.
1845 Return False to indicate that it had been invalidated and caches should be refreshed.
1841 """
1846 """
1842
1847
1843 key = (repo_name + '_' + kind) if kind else repo_name
1848 key = (repo_name + '_' + kind) if kind else repo_name
1844 cache_key = cls._get_cache_key(key)
1849 cache_key = cls._get_cache_key(key)
1845
1850
1846 if valid_cache_keys and cache_key in valid_cache_keys:
1851 if valid_cache_keys and cache_key in valid_cache_keys:
1847 return True
1852 return True
1848
1853
1849 try:
1854 try:
1850 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
1855 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
1851 if not inv_obj:
1856 if not inv_obj:
1852 inv_obj = CacheInvalidation(cache_key, repo_name)
1857 inv_obj = CacheInvalidation(cache_key, repo_name)
1853 was_valid = inv_obj.cache_active
1858 was_valid = inv_obj.cache_active
1854 inv_obj.cache_active = True
1859 inv_obj.cache_active = True
1855 Session().add(inv_obj)
1860 Session().add(inv_obj)
1856 Session().commit()
1861 Session().commit()
1857 return was_valid
1862 return was_valid
1858 except Exception:
1863 except Exception:
1859 log.error(traceback.format_exc())
1864 log.error(traceback.format_exc())
1860 Session().rollback()
1865 Session().rollback()
1861 return False
1866 return False
1862
1867
1863 @classmethod
1868 @classmethod
1864 def get_valid_cache_keys(cls):
1869 def get_valid_cache_keys(cls):
1865 """
1870 """
1866 Return opaque object with information of which caches still are valid
1871 Return opaque object with information of which caches still are valid
1867 and can be used without checking for invalidation.
1872 and can be used without checking for invalidation.
1868 """
1873 """
1869 return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
1874 return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
1870
1875
1871
1876
1872 class ChangesetComment(Base, BaseModel):
1877 class ChangesetComment(Base, BaseModel):
1873 __tablename__ = 'changeset_comments'
1878 __tablename__ = 'changeset_comments'
1874 __table_args__ = (
1879 __table_args__ = (
1875 Index('cc_revision_idx', 'revision'),
1880 Index('cc_revision_idx', 'revision'),
1876 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1881 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1877 'mysql_charset': 'utf8'},
1882 'mysql_charset': 'utf8'},
1878 )
1883 )
1879 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1884 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1880 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1885 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1881 revision = Column('revision', String(40), nullable=True)
1886 revision = Column('revision', String(40), nullable=True)
1882 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1887 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1883 line_no = Column('line_no', Unicode(10), nullable=True)
1888 line_no = Column('line_no', Unicode(10), nullable=True)
1884 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1889 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1885 f_path = Column('f_path', Unicode(1000), nullable=True)
1890 f_path = Column('f_path', Unicode(1000), nullable=True)
1886 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1891 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1887 text = Column('text', UnicodeText(25000), nullable=False)
1892 text = Column('text', UnicodeText(25000), nullable=False)
1888 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1893 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1889 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1894 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1890
1895
1891 author = relationship('User', lazy='joined')
1896 author = relationship('User', lazy='joined')
1892 repo = relationship('Repository')
1897 repo = relationship('Repository')
1893 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1898 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1894 pull_request = relationship('PullRequest', lazy='joined')
1899 pull_request = relationship('PullRequest', lazy='joined')
1895
1900
1896 @classmethod
1901 @classmethod
1897 def get_users(cls, revision=None, pull_request_id=None):
1902 def get_users(cls, revision=None, pull_request_id=None):
1898 """
1903 """
1899 Returns user associated with this ChangesetComment. ie those
1904 Returns user associated with this ChangesetComment. ie those
1900 who actually commented
1905 who actually commented
1901
1906
1902 :param cls:
1907 :param cls:
1903 :param revision:
1908 :param revision:
1904 """
1909 """
1905 q = Session().query(User)\
1910 q = Session().query(User)\
1906 .join(ChangesetComment.author)
1911 .join(ChangesetComment.author)
1907 if revision:
1912 if revision:
1908 q = q.filter(cls.revision == revision)
1913 q = q.filter(cls.revision == revision)
1909 elif pull_request_id:
1914 elif pull_request_id:
1910 q = q.filter(cls.pull_request_id == pull_request_id)
1915 q = q.filter(cls.pull_request_id == pull_request_id)
1911 return q.all()
1916 return q.all()
1912
1917
1913
1918
1914 class ChangesetStatus(Base, BaseModel):
1919 class ChangesetStatus(Base, BaseModel):
1915 __tablename__ = 'changeset_statuses'
1920 __tablename__ = 'changeset_statuses'
1916 __table_args__ = (
1921 __table_args__ = (
1917 Index('cs_revision_idx', 'revision'),
1922 Index('cs_revision_idx', 'revision'),
1918 Index('cs_version_idx', 'version'),
1923 Index('cs_version_idx', 'version'),
1919 UniqueConstraint('repo_id', 'revision', 'version'),
1924 UniqueConstraint('repo_id', 'revision', 'version'),
1920 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1925 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1921 'mysql_charset': 'utf8'}
1926 'mysql_charset': 'utf8'}
1922 )
1927 )
1923 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1928 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1924 STATUS_APPROVED = 'approved'
1929 STATUS_APPROVED = 'approved'
1925 STATUS_REJECTED = 'rejected'
1930 STATUS_REJECTED = 'rejected'
1926 STATUS_UNDER_REVIEW = 'under_review'
1931 STATUS_UNDER_REVIEW = 'under_review'
1927
1932
1928 STATUSES = [
1933 STATUSES = [
1929 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1934 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1930 (STATUS_APPROVED, _("Approved")),
1935 (STATUS_APPROVED, _("Approved")),
1931 (STATUS_REJECTED, _("Rejected")),
1936 (STATUS_REJECTED, _("Rejected")),
1932 (STATUS_UNDER_REVIEW, _("Under Review")),
1937 (STATUS_UNDER_REVIEW, _("Under Review")),
1933 ]
1938 ]
1934
1939
1935 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1940 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1936 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1941 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1937 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1942 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1938 revision = Column('revision', String(40), nullable=False)
1943 revision = Column('revision', String(40), nullable=False)
1939 status = Column('status', String(128), nullable=False, default=DEFAULT)
1944 status = Column('status', String(128), nullable=False, default=DEFAULT)
1940 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1945 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1941 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1946 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1942 version = Column('version', Integer(), nullable=False, default=0)
1947 version = Column('version', Integer(), nullable=False, default=0)
1943 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1948 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1944
1949
1945 author = relationship('User', lazy='joined')
1950 author = relationship('User', lazy='joined')
1946 repo = relationship('Repository')
1951 repo = relationship('Repository')
1947 comment = relationship('ChangesetComment', lazy='joined')
1952 comment = relationship('ChangesetComment', lazy='joined')
1948 pull_request = relationship('PullRequest', lazy='joined')
1953 pull_request = relationship('PullRequest', lazy='joined')
1949
1954
1950 def __unicode__(self):
1955 def __unicode__(self):
1951 return u"<%s('%s:%s')>" % (
1956 return u"<%s('%s:%s')>" % (
1952 self.__class__.__name__,
1957 self.__class__.__name__,
1953 self.status, self.author
1958 self.status, self.author
1954 )
1959 )
1955
1960
1956 @classmethod
1961 @classmethod
1957 def get_status_lbl(cls, value):
1962 def get_status_lbl(cls, value):
1958 return dict(cls.STATUSES).get(value)
1963 return dict(cls.STATUSES).get(value)
1959
1964
1960 @property
1965 @property
1961 def status_lbl(self):
1966 def status_lbl(self):
1962 return ChangesetStatus.get_status_lbl(self.status)
1967 return ChangesetStatus.get_status_lbl(self.status)
1963
1968
1964
1969
1965 class PullRequest(Base, BaseModel):
1970 class PullRequest(Base, BaseModel):
1966 __tablename__ = 'pull_requests'
1971 __tablename__ = 'pull_requests'
1967 __table_args__ = (
1972 __table_args__ = (
1968 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1973 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1969 'mysql_charset': 'utf8'},
1974 'mysql_charset': 'utf8'},
1970 )
1975 )
1971
1976
1972 STATUS_NEW = u'new'
1977 STATUS_NEW = u'new'
1973 STATUS_OPEN = u'open'
1978 STATUS_OPEN = u'open'
1974 STATUS_CLOSED = u'closed'
1979 STATUS_CLOSED = u'closed'
1975
1980
1976 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1981 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1977 title = Column('title', Unicode(256), nullable=True)
1982 title = Column('title', Unicode(256), nullable=True)
1978 description = Column('description', UnicodeText(10240), nullable=True)
1983 description = Column('description', UnicodeText(10240), nullable=True)
1979 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1984 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1980 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1985 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1981 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1986 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1982 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1987 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1983 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1988 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1984 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1989 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1985 org_ref = Column('org_ref', Unicode(256), nullable=False)
1990 org_ref = Column('org_ref', Unicode(256), nullable=False)
1986 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1991 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1987 other_ref = Column('other_ref', Unicode(256), nullable=False)
1992 other_ref = Column('other_ref', Unicode(256), nullable=False)
1988
1993
1989 @hybrid_property
1994 @hybrid_property
1990 def revisions(self):
1995 def revisions(self):
1991 return self._revisions.split(':')
1996 return self._revisions.split(':')
1992
1997
1993 @revisions.setter
1998 @revisions.setter
1994 def revisions(self, val):
1999 def revisions(self, val):
1995 self._revisions = ':'.join(val)
2000 self._revisions = ':'.join(val)
1996
2001
1997 @property
2002 @property
1998 def org_ref_parts(self):
2003 def org_ref_parts(self):
1999 return self.org_ref.split(':')
2004 return self.org_ref.split(':')
2000
2005
2001 @property
2006 @property
2002 def other_ref_parts(self):
2007 def other_ref_parts(self):
2003 return self.other_ref.split(':')
2008 return self.other_ref.split(':')
2004
2009
2005 author = relationship('User', lazy='joined')
2010 author = relationship('User', lazy='joined')
2006 reviewers = relationship('PullRequestReviewers',
2011 reviewers = relationship('PullRequestReviewers',
2007 cascade="all, delete, delete-orphan")
2012 cascade="all, delete, delete-orphan")
2008 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
2013 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
2009 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
2014 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
2010 statuses = relationship('ChangesetStatus')
2015 statuses = relationship('ChangesetStatus')
2011 comments = relationship('ChangesetComment',
2016 comments = relationship('ChangesetComment',
2012 cascade="all, delete, delete-orphan")
2017 cascade="all, delete, delete-orphan")
2013
2018
2014 def is_closed(self):
2019 def is_closed(self):
2015 return self.status == self.STATUS_CLOSED
2020 return self.status == self.STATUS_CLOSED
2016
2021
2017 @property
2022 @property
2018 def last_review_status(self):
2023 def last_review_status(self):
2019 return self.statuses[-1].status if self.statuses else ''
2024 return self.statuses[-1].status if self.statuses else ''
2020
2025
2021 def __json__(self):
2026 def __json__(self):
2022 return dict(
2027 return dict(
2023 revisions=self.revisions
2028 revisions=self.revisions
2024 )
2029 )
2025
2030
2026
2031
2027 class PullRequestReviewers(Base, BaseModel):
2032 class PullRequestReviewers(Base, BaseModel):
2028 __tablename__ = 'pull_request_reviewers'
2033 __tablename__ = 'pull_request_reviewers'
2029 __table_args__ = (
2034 __table_args__ = (
2030 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2035 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2031 'mysql_charset': 'utf8'},
2036 'mysql_charset': 'utf8'},
2032 )
2037 )
2033
2038
2034 def __init__(self, user=None, pull_request=None):
2039 def __init__(self, user=None, pull_request=None):
2035 self.user = user
2040 self.user = user
2036 self.pull_request = pull_request
2041 self.pull_request = pull_request
2037
2042
2038 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
2043 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
2039 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
2044 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
2040 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
2045 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
2041
2046
2042 user = relationship('User')
2047 user = relationship('User')
2043 pull_request = relationship('PullRequest')
2048 pull_request = relationship('PullRequest')
2044
2049
2045
2050
2046 class Notification(Base, BaseModel):
2051 class Notification(Base, BaseModel):
2047 __tablename__ = 'notifications'
2052 __tablename__ = 'notifications'
2048 __table_args__ = (
2053 __table_args__ = (
2049 Index('notification_type_idx', 'type'),
2054 Index('notification_type_idx', 'type'),
2050 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2055 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2051 'mysql_charset': 'utf8'},
2056 'mysql_charset': 'utf8'},
2052 )
2057 )
2053
2058
2054 TYPE_CHANGESET_COMMENT = u'cs_comment'
2059 TYPE_CHANGESET_COMMENT = u'cs_comment'
2055 TYPE_MESSAGE = u'message'
2060 TYPE_MESSAGE = u'message'
2056 TYPE_MENTION = u'mention'
2061 TYPE_MENTION = u'mention'
2057 TYPE_REGISTRATION = u'registration'
2062 TYPE_REGISTRATION = u'registration'
2058 TYPE_PULL_REQUEST = u'pull_request'
2063 TYPE_PULL_REQUEST = u'pull_request'
2059 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
2064 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
2060
2065
2061 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
2066 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
2062 subject = Column('subject', Unicode(512), nullable=True)
2067 subject = Column('subject', Unicode(512), nullable=True)
2063 body = Column('body', UnicodeText(50000), nullable=True)
2068 body = Column('body', UnicodeText(50000), nullable=True)
2064 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
2069 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
2065 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2070 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2066 type_ = Column('type', Unicode(256))
2071 type_ = Column('type', Unicode(256))
2067
2072
2068 created_by_user = relationship('User')
2073 created_by_user = relationship('User')
2069 notifications_to_users = relationship('UserNotification', lazy='joined',
2074 notifications_to_users = relationship('UserNotification', lazy='joined',
2070 cascade="all, delete, delete-orphan")
2075 cascade="all, delete, delete-orphan")
2071
2076
2072 @property
2077 @property
2073 def recipients(self):
2078 def recipients(self):
2074 return [x.user for x in UserNotification.query()\
2079 return [x.user for x in UserNotification.query()\
2075 .filter(UserNotification.notification == self)\
2080 .filter(UserNotification.notification == self)\
2076 .order_by(UserNotification.user_id.asc()).all()]
2081 .order_by(UserNotification.user_id.asc()).all()]
2077
2082
2078 @classmethod
2083 @classmethod
2079 def create(cls, created_by, subject, body, recipients, type_=None):
2084 def create(cls, created_by, subject, body, recipients, type_=None):
2080 if type_ is None:
2085 if type_ is None:
2081 type_ = Notification.TYPE_MESSAGE
2086 type_ = Notification.TYPE_MESSAGE
2082
2087
2083 notification = cls()
2088 notification = cls()
2084 notification.created_by_user = created_by
2089 notification.created_by_user = created_by
2085 notification.subject = subject
2090 notification.subject = subject
2086 notification.body = body
2091 notification.body = body
2087 notification.type_ = type_
2092 notification.type_ = type_
2088 notification.created_on = datetime.datetime.now()
2093 notification.created_on = datetime.datetime.now()
2089
2094
2090 for u in recipients:
2095 for u in recipients:
2091 assoc = UserNotification()
2096 assoc = UserNotification()
2092 assoc.notification = notification
2097 assoc.notification = notification
2093 u.notifications.append(assoc)
2098 u.notifications.append(assoc)
2094 Session().add(notification)
2099 Session().add(notification)
2095 return notification
2100 return notification
2096
2101
2097 @property
2102 @property
2098 def description(self):
2103 def description(self):
2099 from rhodecode.model.notification import NotificationModel
2104 from rhodecode.model.notification import NotificationModel
2100 return NotificationModel().make_description(self)
2105 return NotificationModel().make_description(self)
2101
2106
2102
2107
2103 class UserNotification(Base, BaseModel):
2108 class UserNotification(Base, BaseModel):
2104 __tablename__ = 'user_to_notification'
2109 __tablename__ = 'user_to_notification'
2105 __table_args__ = (
2110 __table_args__ = (
2106 UniqueConstraint('user_id', 'notification_id'),
2111 UniqueConstraint('user_id', 'notification_id'),
2107 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2112 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2108 'mysql_charset': 'utf8'}
2113 'mysql_charset': 'utf8'}
2109 )
2114 )
2110 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2115 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2111 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2116 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2112 read = Column('read', Boolean, default=False)
2117 read = Column('read', Boolean, default=False)
2113 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2118 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2114
2119
2115 user = relationship('User', lazy="joined")
2120 user = relationship('User', lazy="joined")
2116 notification = relationship('Notification', lazy="joined",
2121 notification = relationship('Notification', lazy="joined",
2117 order_by=lambda: Notification.created_on.desc(),)
2122 order_by=lambda: Notification.created_on.desc(),)
2118
2123
2119 def mark_as_read(self):
2124 def mark_as_read(self):
2120 self.read = True
2125 self.read = True
2121 Session().add(self)
2126 Session().add(self)
2122
2127
2123
2128
2124 class Gist(Base, BaseModel):
2129 class Gist(Base, BaseModel):
2125 __tablename__ = 'gists'
2130 __tablename__ = 'gists'
2126 __table_args__ = (
2131 __table_args__ = (
2127 Index('g_gist_access_id_idx', 'gist_access_id'),
2132 Index('g_gist_access_id_idx', 'gist_access_id'),
2128 Index('g_created_on_idx', 'created_on'),
2133 Index('g_created_on_idx', 'created_on'),
2129 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2134 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2130 'mysql_charset': 'utf8'}
2135 'mysql_charset': 'utf8'}
2131 )
2136 )
2132 GIST_PUBLIC = u'public'
2137 GIST_PUBLIC = u'public'
2133 GIST_PRIVATE = u'private'
2138 GIST_PRIVATE = u'private'
2134
2139
2135 gist_id = Column('gist_id', Integer(), primary_key=True)
2140 gist_id = Column('gist_id', Integer(), primary_key=True)
2136 gist_access_id = Column('gist_access_id', UnicodeText(1024))
2141 gist_access_id = Column('gist_access_id', UnicodeText(1024))
2137 gist_description = Column('gist_description', UnicodeText(1024))
2142 gist_description = Column('gist_description', UnicodeText(1024))
2138 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
2143 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
2139 gist_expires = Column('gist_expires', Float(), nullable=False)
2144 gist_expires = Column('gist_expires', Float(), nullable=False)
2140 gist_type = Column('gist_type', Unicode(128), nullable=False)
2145 gist_type = Column('gist_type', Unicode(128), nullable=False)
2141 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2146 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2142 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2147 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2143
2148
2144 owner = relationship('User')
2149 owner = relationship('User')
2145
2150
2146 @classmethod
2151 @classmethod
2147 def get_or_404(cls, id_):
2152 def get_or_404(cls, id_):
2148 res = cls.query().filter(cls.gist_access_id == id_).scalar()
2153 res = cls.query().filter(cls.gist_access_id == id_).scalar()
2149 if not res:
2154 if not res:
2150 raise HTTPNotFound
2155 raise HTTPNotFound
2151 return res
2156 return res
2152
2157
2153 @classmethod
2158 @classmethod
2154 def get_by_access_id(cls, gist_access_id):
2159 def get_by_access_id(cls, gist_access_id):
2155 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
2160 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
2156
2161
2157 def gist_url(self):
2162 def gist_url(self):
2158 import rhodecode
2163 import rhodecode
2159 alias_url = rhodecode.CONFIG.get('gist_alias_url')
2164 alias_url = rhodecode.CONFIG.get('gist_alias_url')
2160 if alias_url:
2165 if alias_url:
2161 return alias_url.replace('{gistid}', self.gist_access_id)
2166 return alias_url.replace('{gistid}', self.gist_access_id)
2162
2167
2163 from pylons import url
2168 from pylons import url
2164 return url('gist', gist_id=self.gist_access_id, qualified=True)
2169 return url('gist', gist_id=self.gist_access_id, qualified=True)
2165
2170
2166 @classmethod
2171 @classmethod
2167 def base_path(cls):
2172 def base_path(cls):
2168 """
2173 """
2169 Returns base path when all gists are stored
2174 Returns base path when all gists are stored
2170
2175
2171 :param cls:
2176 :param cls:
2172 """
2177 """
2173 from rhodecode.model.gist import GIST_STORE_LOC
2178 from rhodecode.model.gist import GIST_STORE_LOC
2174 q = Session().query(RhodeCodeUi)\
2179 q = Session().query(RhodeCodeUi)\
2175 .filter(RhodeCodeUi.ui_key == URL_SEP)
2180 .filter(RhodeCodeUi.ui_key == URL_SEP)
2176 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
2181 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
2177 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
2182 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
2178
2183
2179 def get_api_data(self):
2184 def get_api_data(self):
2180 """
2185 """
2181 Common function for generating gist related data for API
2186 Common function for generating gist related data for API
2182 """
2187 """
2183 gist = self
2188 gist = self
2184 data = dict(
2189 data = dict(
2185 gist_id=gist.gist_id,
2190 gist_id=gist.gist_id,
2186 type=gist.gist_type,
2191 type=gist.gist_type,
2187 access_id=gist.gist_access_id,
2192 access_id=gist.gist_access_id,
2188 description=gist.gist_description,
2193 description=gist.gist_description,
2189 url=gist.gist_url(),
2194 url=gist.gist_url(),
2190 expires=gist.gist_expires,
2195 expires=gist.gist_expires,
2191 created_on=gist.created_on,
2196 created_on=gist.created_on,
2192 )
2197 )
2193 return data
2198 return data
2194
2199
2195 def __json__(self):
2200 def __json__(self):
2196 data = dict(
2201 data = dict(
2197 )
2202 )
2198 data.update(self.get_api_data())
2203 data.update(self.get_api_data())
2199 return data
2204 return data
2200 ## SCM functions
2205 ## SCM functions
2201
2206
2202 @property
2207 @property
2203 def scm_instance(self):
2208 def scm_instance(self):
2204 from rhodecode.lib.vcs import get_repo
2209 from rhodecode.lib.vcs import get_repo
2205 base_path = self.base_path()
2210 base_path = self.base_path()
2206 return get_repo(os.path.join(*map(safe_str,
2211 return get_repo(os.path.join(*map(safe_str,
2207 [base_path, self.gist_access_id])))
2212 [base_path, self.gist_access_id])))
2208
2213
2209
2214
2210 class DbMigrateVersion(Base, BaseModel):
2215 class DbMigrateVersion(Base, BaseModel):
2211 __tablename__ = 'db_migrate_version'
2216 __tablename__ = 'db_migrate_version'
2212 __table_args__ = (
2217 __table_args__ = (
2213 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2218 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2214 'mysql_charset': 'utf8'},
2219 'mysql_charset': 'utf8'},
2215 )
2220 )
2216 repository_id = Column('repository_id', String(250), primary_key=True)
2221 repository_id = Column('repository_id', String(250), primary_key=True)
2217 repository_path = Column('repository_path', Text)
2222 repository_path = Column('repository_path', Text)
2218 version = Column('version', Integer)
2223 version = Column('version', Integer)
General Comments 0
You need to be logged in to leave comments. Login now