##// END OF EJS Templates
Unicode fixes, added safe_str method for global str() operations +better test sandboxing
marcink -
r1401:b7563ad4 beta
parent child Browse files
Show More
@@ -1,80 +1,80 b''
1 """Pylons environment configuration"""
1 """Pylons environment configuration"""
2
2
3 import os
3 import os
4 import logging
4 import logging
5
5
6 from mako.lookup import TemplateLookup
6 from mako.lookup import TemplateLookup
7 from pylons.configuration import PylonsConfig
7 from pylons.configuration import PylonsConfig
8 from pylons.error import handle_mako_error
8 from pylons.error import handle_mako_error
9
9
10 import rhodecode.lib.app_globals as app_globals
10 import rhodecode.lib.app_globals as app_globals
11 import rhodecode.lib.helpers
11 import rhodecode.lib.helpers
12
12
13 from rhodecode.config.routing import make_map
13 from rhodecode.config.routing import make_map
14 from rhodecode.lib import celerypylons
14 from rhodecode.lib import celerypylons
15 from rhodecode.lib import engine_from_config
15 from rhodecode.lib import engine_from_config
16 from rhodecode.lib.timerproxy import TimerProxy
16 from rhodecode.lib.timerproxy import TimerProxy
17 from rhodecode.lib.auth import set_available_permissions
17 from rhodecode.lib.auth import set_available_permissions
18 from rhodecode.lib.utils import repo2db_mapper, make_ui, set_rhodecode_config
18 from rhodecode.lib.utils import repo2db_mapper, make_ui, set_rhodecode_config
19 from rhodecode.model import init_model
19 from rhodecode.model import init_model
20 from rhodecode.model.scm import ScmModel
20 from rhodecode.model.scm import ScmModel
21
21
22 log = logging.getLogger(__name__)
22 log = logging.getLogger(__name__)
23
23
24
24
25 def load_environment(global_conf, app_conf, initial=False):
25 def load_environment(global_conf, app_conf, initial=False):
26 """Configure the Pylons environment via the ``pylons.config``
26 """Configure the Pylons environment via the ``pylons.config``
27 object
27 object
28 """
28 """
29 config = PylonsConfig()
29 config = PylonsConfig()
30
30
31 # Pylons paths
31 # Pylons paths
32 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
32 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
33 paths = dict(root=root,
33 paths = dict(root=root,
34 controllers=os.path.join(root, 'controllers'),
34 controllers=os.path.join(root, 'controllers'),
35 static_files=os.path.join(root, 'public'),
35 static_files=os.path.join(root, 'public'),
36 templates=[os.path.join(root, 'templates')])
36 templates=[os.path.join(root, 'templates')])
37
37
38 # Initialize config with the basic options
38 # Initialize config with the basic options
39 config.init_app(global_conf, app_conf, package='rhodecode', paths=paths)
39 config.init_app(global_conf, app_conf, package='rhodecode', paths=paths)
40
40
41 config['routes.map'] = make_map(config)
41 config['routes.map'] = make_map(config)
42 config['pylons.app_globals'] = app_globals.Globals(config)
42 config['pylons.app_globals'] = app_globals.Globals(config)
43 config['pylons.h'] = rhodecode.lib.helpers
43 config['pylons.h'] = rhodecode.lib.helpers
44
44
45 # Setup cache object as early as possible
45 # Setup cache object as early as possible
46 import pylons
46 import pylons
47 pylons.cache._push_object(config['pylons.app_globals'].cache)
47 pylons.cache._push_object(config['pylons.app_globals'].cache)
48
48
49 # Create the Mako TemplateLookup, with the default auto-escaping
49 # Create the Mako TemplateLookup, with the default auto-escaping
50 config['pylons.app_globals'].mako_lookup = TemplateLookup(
50 config['pylons.app_globals'].mako_lookup = TemplateLookup(
51 directories=paths['templates'],
51 directories=paths['templates'],
52 error_handler=handle_mako_error,
52 error_handler=handle_mako_error,
53 module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
53 module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
54 input_encoding='utf-8', default_filters=['escape'],
54 input_encoding='utf-8', default_filters=['escape'],
55 imports=['from webhelpers.html import escape'])
55 imports=['from webhelpers.html import escape'])
56
56
57 #sets the c attribute access when don't existing attribute are accessed
57 #sets the c attribute access when don't existing attribute are accessed
58 config['pylons.strict_tmpl_context'] = True
58 config['pylons.strict_tmpl_context'] = True
59 test = os.path.split(config['__file__'])[-1] == 'test.ini'
59 test = os.path.split(config['__file__'])[-1] == 'test.ini'
60 if test:
60 if test:
61 from rhodecode.lib.utils import create_test_env, create_test_index
61 from rhodecode.lib.utils import create_test_env, create_test_index
62 from rhodecode.tests import TESTS_TMP_PATH
62 from rhodecode.tests import TESTS_TMP_PATH
63 create_test_env(TESTS_TMP_PATH, config)
63 create_test_env(TESTS_TMP_PATH, config)
64 create_test_index(TESTS_TMP_PATH, True)
64 create_test_index(TESTS_TMP_PATH, config, True)
65
65
66 #MULTIPLE DB configs
66 #MULTIPLE DB configs
67 # Setup the SQLAlchemy database engine
67 # Setup the SQLAlchemy database engine
68 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.')
68 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.')
69
69
70 init_model(sa_engine_db1)
70 init_model(sa_engine_db1)
71
71
72 repos_path = make_ui('db').configitems('paths')[0][1]
72 repos_path = make_ui('db').configitems('paths')[0][1]
73 repo2db_mapper(ScmModel().repo_scan(repos_path))
73 repo2db_mapper(ScmModel().repo_scan(repos_path))
74 set_available_permissions(config)
74 set_available_permissions(config)
75 config['base_path'] = repos_path
75 config['base_path'] = repos_path
76 set_rhodecode_config(config)
76 set_rhodecode_config(config)
77 # CONFIGURATION OPTIONS HERE (note: all config options will override
77 # CONFIGURATION OPTIONS HERE (note: all config options will override
78 # any Pylons config options)
78 # any Pylons config options)
79
79
80 return config
80 return config
@@ -1,414 +1,414 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.files
3 rhodecode.controllers.files
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Files controller for RhodeCode
6 Files controller for RhodeCode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import mimetypes
28 import mimetypes
29 import traceback
29 import traceback
30
30
31 from pylons import request, response, session, tmpl_context as c, url
31 from pylons import request, response, session, tmpl_context as c, url
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34
34
35 from vcs.backends import ARCHIVE_SPECS
35 from vcs.backends import ARCHIVE_SPECS
36 from vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
36 from vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
37 EmptyRepositoryError, ImproperArchiveTypeError, VCSError
37 EmptyRepositoryError, ImproperArchiveTypeError, VCSError
38 from vcs.nodes import FileNode, NodeKind
38 from vcs.nodes import FileNode, NodeKind
39 from vcs.utils import diffs as differ
39 from vcs.utils import diffs as differ
40
40
41 from rhodecode.lib import convert_line_endings, detect_mode
41 from rhodecode.lib import convert_line_endings, detect_mode, safe_str
42 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
43 from rhodecode.lib.base import BaseRepoController, render
43 from rhodecode.lib.base import BaseRepoController, render
44 from rhodecode.lib.utils import EmptyChangeset
44 from rhodecode.lib.utils import EmptyChangeset
45 import rhodecode.lib.helpers as h
45 import rhodecode.lib.helpers as h
46 from rhodecode.model.repo import RepoModel
46 from rhodecode.model.repo import RepoModel
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 class FilesController(BaseRepoController):
51 class FilesController(BaseRepoController):
52
52
53 @LoginRequired()
53 @LoginRequired()
54 def __before__(self):
54 def __before__(self):
55 super(FilesController, self).__before__()
55 super(FilesController, self).__before__()
56 c.cut_off_limit = self.cut_off_limit
56 c.cut_off_limit = self.cut_off_limit
57
57
58 def __get_cs_or_redirect(self, rev, repo_name):
58 def __get_cs_or_redirect(self, rev, repo_name):
59 """
59 """
60 Safe way to get changeset if error occur it redirects to tip with
60 Safe way to get changeset if error occur it redirects to tip with
61 proper message
61 proper message
62
62
63 :param rev: revision to fetch
63 :param rev: revision to fetch
64 :param repo_name: repo name to redirect after
64 :param repo_name: repo name to redirect after
65 """
65 """
66
66
67 try:
67 try:
68 return c.rhodecode_repo.get_changeset(rev)
68 return c.rhodecode_repo.get_changeset(rev)
69 except EmptyRepositoryError, e:
69 except EmptyRepositoryError, e:
70 h.flash(_('There are no files yet'), category='warning')
70 h.flash(_('There are no files yet'), category='warning')
71 redirect(h.url('summary_home', repo_name=repo_name))
71 redirect(h.url('summary_home', repo_name=repo_name))
72
72
73 except RepositoryError, e:
73 except RepositoryError, e:
74 h.flash(str(e), category='warning')
74 h.flash(str(e), category='warning')
75 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
75 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
76
76
77 def __get_filenode_or_redirect(self, repo_name, cs, path):
77 def __get_filenode_or_redirect(self, repo_name, cs, path):
78 """
78 """
79 Returns file_node, if error occurs or given path is directory,
79 Returns file_node, if error occurs or given path is directory,
80 it'll redirect to top level path
80 it'll redirect to top level path
81
81
82 :param repo_name: repo_name
82 :param repo_name: repo_name
83 :param cs: given changeset
83 :param cs: given changeset
84 :param path: path to lookup
84 :param path: path to lookup
85 """
85 """
86
86
87 try:
87 try:
88 file_node = cs.get_node(path)
88 file_node = cs.get_node(path)
89 if file_node.is_dir():
89 if file_node.is_dir():
90 raise RepositoryError('given path is a directory')
90 raise RepositoryError('given path is a directory')
91 except RepositoryError, e:
91 except RepositoryError, e:
92 h.flash(str(e), category='warning')
92 h.flash(str(e), category='warning')
93 redirect(h.url('files_home', repo_name=repo_name,
93 redirect(h.url('files_home', repo_name=repo_name,
94 revision=cs.raw_id))
94 revision=cs.raw_id))
95
95
96 return file_node
96 return file_node
97
97
98 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
98 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
99 'repository.admin')
99 'repository.admin')
100 def index(self, repo_name, revision, f_path):
100 def index(self, repo_name, revision, f_path):
101 #reditect to given revision from form if given
101 #reditect to given revision from form if given
102 post_revision = request.POST.get('at_rev', None)
102 post_revision = request.POST.get('at_rev', None)
103 if post_revision:
103 if post_revision:
104 cs = self.__get_cs_or_redirect(post_revision, repo_name)
104 cs = self.__get_cs_or_redirect(post_revision, repo_name)
105 redirect(url('files_home', repo_name=c.repo_name,
105 redirect(url('files_home', repo_name=c.repo_name,
106 revision=cs.raw_id, f_path=f_path))
106 revision=cs.raw_id, f_path=f_path))
107
107
108 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
108 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
109 c.branch = request.GET.get('branch', None)
109 c.branch = request.GET.get('branch', None)
110 c.f_path = f_path
110 c.f_path = f_path
111
111
112 cur_rev = c.changeset.revision
112 cur_rev = c.changeset.revision
113
113
114 #prev link
114 #prev link
115 try:
115 try:
116 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
116 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
117 c.url_prev = url('files_home', repo_name=c.repo_name,
117 c.url_prev = url('files_home', repo_name=c.repo_name,
118 revision=prev_rev.raw_id, f_path=f_path)
118 revision=prev_rev.raw_id, f_path=f_path)
119 if c.branch:
119 if c.branch:
120 c.url_prev += '?branch=%s' % c.branch
120 c.url_prev += '?branch=%s' % c.branch
121 except (ChangesetDoesNotExistError, VCSError):
121 except (ChangesetDoesNotExistError, VCSError):
122 c.url_prev = '#'
122 c.url_prev = '#'
123
123
124 #next link
124 #next link
125 try:
125 try:
126 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
126 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
127 c.url_next = url('files_home', repo_name=c.repo_name,
127 c.url_next = url('files_home', repo_name=c.repo_name,
128 revision=next_rev.raw_id, f_path=f_path)
128 revision=next_rev.raw_id, f_path=f_path)
129 if c.branch:
129 if c.branch:
130 c.url_next += '?branch=%s' % c.branch
130 c.url_next += '?branch=%s' % c.branch
131 except (ChangesetDoesNotExistError, VCSError):
131 except (ChangesetDoesNotExistError, VCSError):
132 c.url_next = '#'
132 c.url_next = '#'
133
133
134 #files or dirs
134 #files or dirs
135 try:
135 try:
136 c.files_list = c.changeset.get_node(f_path)
136 c.files_list = c.changeset.get_node(f_path)
137
137
138 if c.files_list.is_file():
138 if c.files_list.is_file():
139 c.file_history = self._get_node_history(c.changeset, f_path)
139 c.file_history = self._get_node_history(c.changeset, f_path)
140 else:
140 else:
141 c.file_history = []
141 c.file_history = []
142 except RepositoryError, e:
142 except RepositoryError, e:
143 h.flash(str(e), category='warning')
143 h.flash(str(e), category='warning')
144 redirect(h.url('files_home', repo_name=repo_name,
144 redirect(h.url('files_home', repo_name=repo_name,
145 revision=revision))
145 revision=revision))
146
146
147 return render('files/files.html')
147 return render('files/files.html')
148
148
149 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
149 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
150 'repository.admin')
150 'repository.admin')
151 def rawfile(self, repo_name, revision, f_path):
151 def rawfile(self, repo_name, revision, f_path):
152 cs = self.__get_cs_or_redirect(revision, repo_name)
152 cs = self.__get_cs_or_redirect(revision, repo_name)
153 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
153 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
154
154
155 response.content_disposition = 'attachment; filename=%s' % \
155 response.content_disposition = 'attachment; filename=%s' % \
156 f_path.split(os.sep)[-1].encode('utf8', 'replace')
156 safe_str(f_path.split(os.sep)[-1])
157
157
158 response.content_type = file_node.mimetype
158 response.content_type = file_node.mimetype
159 return file_node.content
159 return file_node.content
160
160
161 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
161 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
162 'repository.admin')
162 'repository.admin')
163 def raw(self, repo_name, revision, f_path):
163 def raw(self, repo_name, revision, f_path):
164 cs = self.__get_cs_or_redirect(revision, repo_name)
164 cs = self.__get_cs_or_redirect(revision, repo_name)
165 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
165 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
166
166
167 raw_mimetype_mapping = {
167 raw_mimetype_mapping = {
168 # map original mimetype to a mimetype used for "show as raw"
168 # map original mimetype to a mimetype used for "show as raw"
169 # you can also provide a content-disposition to override the
169 # you can also provide a content-disposition to override the
170 # default "attachment" disposition.
170 # default "attachment" disposition.
171 # orig_type: (new_type, new_dispo)
171 # orig_type: (new_type, new_dispo)
172
172
173 # show images inline:
173 # show images inline:
174 'image/x-icon': ('image/x-icon', 'inline'),
174 'image/x-icon': ('image/x-icon', 'inline'),
175 'image/png': ('image/png', 'inline'),
175 'image/png': ('image/png', 'inline'),
176 'image/gif': ('image/gif', 'inline'),
176 'image/gif': ('image/gif', 'inline'),
177 'image/jpeg': ('image/jpeg', 'inline'),
177 'image/jpeg': ('image/jpeg', 'inline'),
178 'image/svg+xml': ('image/svg+xml', 'inline'),
178 'image/svg+xml': ('image/svg+xml', 'inline'),
179 }
179 }
180
180
181 mimetype = file_node.mimetype
181 mimetype = file_node.mimetype
182 try:
182 try:
183 mimetype, dispo = raw_mimetype_mapping[mimetype]
183 mimetype, dispo = raw_mimetype_mapping[mimetype]
184 except KeyError:
184 except KeyError:
185 # we don't know anything special about this, handle it safely
185 # we don't know anything special about this, handle it safely
186 if file_node.is_binary:
186 if file_node.is_binary:
187 # do same as download raw for binary files
187 # do same as download raw for binary files
188 mimetype, dispo = 'application/octet-stream', 'attachment'
188 mimetype, dispo = 'application/octet-stream', 'attachment'
189 else:
189 else:
190 # do not just use the original mimetype, but force text/plain,
190 # do not just use the original mimetype, but force text/plain,
191 # otherwise it would serve text/html and that might be unsafe.
191 # otherwise it would serve text/html and that might be unsafe.
192 # Note: underlying vcs library fakes text/plain mimetype if the
192 # Note: underlying vcs library fakes text/plain mimetype if the
193 # mimetype can not be determined and it thinks it is not
193 # mimetype can not be determined and it thinks it is not
194 # binary.This might lead to erroneous text display in some
194 # binary.This might lead to erroneous text display in some
195 # cases, but helps in other cases, like with text files
195 # cases, but helps in other cases, like with text files
196 # without extension.
196 # without extension.
197 mimetype, dispo = 'text/plain', 'inline'
197 mimetype, dispo = 'text/plain', 'inline'
198
198
199 if dispo == 'attachment':
199 if dispo == 'attachment':
200 dispo = 'attachment; filename=%s' % \
200 dispo = 'attachment; filename=%s' % \
201 f_path.split(os.sep)[-1].encode('utf8', 'replace')
201 safe_str(f_path.split(os.sep)[-1])
202
202
203 response.content_disposition = dispo
203 response.content_disposition = dispo
204 response.content_type = mimetype
204 response.content_type = mimetype
205 return file_node.content
205 return file_node.content
206
206
207 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
207 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
208 'repository.admin')
208 'repository.admin')
209 def annotate(self, repo_name, revision, f_path):
209 def annotate(self, repo_name, revision, f_path):
210 c.cs = self.__get_cs_or_redirect(revision, repo_name)
210 c.cs = self.__get_cs_or_redirect(revision, repo_name)
211 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
211 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
212
212
213 c.file_history = self._get_node_history(c.cs, f_path)
213 c.file_history = self._get_node_history(c.cs, f_path)
214 c.f_path = f_path
214 c.f_path = f_path
215 return render('files/files_annotate.html')
215 return render('files/files_annotate.html')
216
216
217 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
217 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
218 def edit(self, repo_name, revision, f_path):
218 def edit(self, repo_name, revision, f_path):
219 r_post = request.POST
219 r_post = request.POST
220
220
221 c.cs = self.__get_cs_or_redirect(revision, repo_name)
221 c.cs = self.__get_cs_or_redirect(revision, repo_name)
222 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
222 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
223
223
224 if c.file.is_binary:
224 if c.file.is_binary:
225 return redirect(url('files_home', repo_name=c.repo_name,
225 return redirect(url('files_home', repo_name=c.repo_name,
226 revision=c.cs.raw_id, f_path=f_path))
226 revision=c.cs.raw_id, f_path=f_path))
227
227
228 c.file_history = self._get_node_history(c.cs, f_path)
228 c.file_history = self._get_node_history(c.cs, f_path)
229 c.f_path = f_path
229 c.f_path = f_path
230
230
231 if r_post:
231 if r_post:
232
232
233 old_content = c.file.content
233 old_content = c.file.content
234 sl = old_content.splitlines(1)
234 sl = old_content.splitlines(1)
235 first_line = sl[0] if sl else ''
235 first_line = sl[0] if sl else ''
236 # modes: 0 - Unix, 1 - Mac, 2 - DOS
236 # modes: 0 - Unix, 1 - Mac, 2 - DOS
237 mode = detect_mode(first_line, 0)
237 mode = detect_mode(first_line, 0)
238 content = convert_line_endings(r_post.get('content'), mode)
238 content = convert_line_endings(r_post.get('content'), mode)
239
239
240 message = r_post.get('message') or (_('Edited %s via RhodeCode')
240 message = r_post.get('message') or (_('Edited %s via RhodeCode')
241 % (f_path))
241 % (f_path))
242 author = self.rhodecode_user.full_contact
242 author = self.rhodecode_user.full_contact
243
243
244 if content == old_content:
244 if content == old_content:
245 h.flash(_('No changes'),
245 h.flash(_('No changes'),
246 category='warning')
246 category='warning')
247 return redirect(url('changeset_home', repo_name=c.repo_name,
247 return redirect(url('changeset_home', repo_name=c.repo_name,
248 revision='tip'))
248 revision='tip'))
249
249
250 try:
250 try:
251 self.scm_model.commit_change(repo=c.rhodecode_repo,
251 self.scm_model.commit_change(repo=c.rhodecode_repo,
252 repo_name=repo_name, cs=c.cs,
252 repo_name=repo_name, cs=c.cs,
253 user=self.rhodecode_user,
253 user=self.rhodecode_user,
254 author=author, message=message,
254 author=author, message=message,
255 content=content, f_path=f_path)
255 content=content, f_path=f_path)
256 h.flash(_('Successfully committed to %s' % f_path),
256 h.flash(_('Successfully committed to %s' % f_path),
257 category='success')
257 category='success')
258
258
259 except Exception:
259 except Exception:
260 log.error(traceback.format_exc())
260 log.error(traceback.format_exc())
261 h.flash(_('Error occurred during commit'), category='error')
261 h.flash(_('Error occurred during commit'), category='error')
262 return redirect(url('changeset_home',
262 return redirect(url('changeset_home',
263 repo_name=c.repo_name, revision='tip'))
263 repo_name=c.repo_name, revision='tip'))
264
264
265 return render('files/files_edit.html')
265 return render('files/files_edit.html')
266
266
267 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
267 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
268 'repository.admin')
268 'repository.admin')
269 def archivefile(self, repo_name, fname):
269 def archivefile(self, repo_name, fname):
270
270
271 fileformat = None
271 fileformat = None
272 revision = None
272 revision = None
273 ext = None
273 ext = None
274
274
275 for a_type, ext_data in ARCHIVE_SPECS.items():
275 for a_type, ext_data in ARCHIVE_SPECS.items():
276 archive_spec = fname.split(ext_data[1])
276 archive_spec = fname.split(ext_data[1])
277 if len(archive_spec) == 2 and archive_spec[1] == '':
277 if len(archive_spec) == 2 and archive_spec[1] == '':
278 fileformat = a_type or ext_data[1]
278 fileformat = a_type or ext_data[1]
279 revision = archive_spec[0]
279 revision = archive_spec[0]
280 ext = ext_data[1]
280 ext = ext_data[1]
281
281
282 try:
282 try:
283 dbrepo = RepoModel().get_by_repo_name(repo_name)
283 dbrepo = RepoModel().get_by_repo_name(repo_name)
284 if dbrepo.enable_downloads is False:
284 if dbrepo.enable_downloads is False:
285 return _('downloads disabled')
285 return _('downloads disabled')
286
286
287 cs = c.rhodecode_repo.get_changeset(revision)
287 cs = c.rhodecode_repo.get_changeset(revision)
288 content_type = ARCHIVE_SPECS[fileformat][0]
288 content_type = ARCHIVE_SPECS[fileformat][0]
289 except ChangesetDoesNotExistError:
289 except ChangesetDoesNotExistError:
290 return _('Unknown revision %s') % revision
290 return _('Unknown revision %s') % revision
291 except EmptyRepositoryError:
291 except EmptyRepositoryError:
292 return _('Empty repository')
292 return _('Empty repository')
293 except (ImproperArchiveTypeError, KeyError):
293 except (ImproperArchiveTypeError, KeyError):
294 return _('Unknown archive type')
294 return _('Unknown archive type')
295
295
296 response.content_type = content_type
296 response.content_type = content_type
297 response.content_disposition = 'attachment; filename=%s-%s%s' \
297 response.content_disposition = 'attachment; filename=%s-%s%s' \
298 % (repo_name, revision, ext)
298 % (repo_name, revision, ext)
299
299
300 import tempfile
300 import tempfile
301 archive = tempfile.mkstemp()[1]
301 archive = tempfile.mkstemp()[1]
302 t = open(archive, 'wb')
302 t = open(archive, 'wb')
303 cs.fill_archive(stream=t, kind=fileformat)
303 cs.fill_archive(stream=t, kind=fileformat)
304
304
305 def get_chunked_archive(archive):
305 def get_chunked_archive(archive):
306 stream = open(archive, 'rb')
306 stream = open(archive, 'rb')
307 while True:
307 while True:
308 data = stream.read(4096)
308 data = stream.read(4096)
309 if not data:
309 if not data:
310 os.remove(archive)
310 os.remove(archive)
311 break
311 break
312 yield data
312 yield data
313
313
314 return get_chunked_archive(archive)
314 return get_chunked_archive(archive)
315
315
316 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
316 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
317 'repository.admin')
317 'repository.admin')
318 def diff(self, repo_name, f_path):
318 def diff(self, repo_name, f_path):
319 diff1 = request.GET.get('diff1')
319 diff1 = request.GET.get('diff1')
320 diff2 = request.GET.get('diff2')
320 diff2 = request.GET.get('diff2')
321 c.action = request.GET.get('diff')
321 c.action = request.GET.get('diff')
322 c.no_changes = diff1 == diff2
322 c.no_changes = diff1 == diff2
323 c.f_path = f_path
323 c.f_path = f_path
324 c.big_diff = False
324 c.big_diff = False
325
325
326 try:
326 try:
327 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
327 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
328 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
328 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
329 node1 = c.changeset_1.get_node(f_path)
329 node1 = c.changeset_1.get_node(f_path)
330 else:
330 else:
331 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
331 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
332 node1 = FileNode('.', '', changeset=c.changeset_1)
332 node1 = FileNode('.', '', changeset=c.changeset_1)
333
333
334 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
334 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
335 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
335 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
336 node2 = c.changeset_2.get_node(f_path)
336 node2 = c.changeset_2.get_node(f_path)
337 else:
337 else:
338 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
338 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
339 node2 = FileNode('.', '', changeset=c.changeset_2)
339 node2 = FileNode('.', '', changeset=c.changeset_2)
340 except RepositoryError:
340 except RepositoryError:
341 return redirect(url('files_home',
341 return redirect(url('files_home',
342 repo_name=c.repo_name, f_path=f_path))
342 repo_name=c.repo_name, f_path=f_path))
343
343
344 if c.action == 'download':
344 if c.action == 'download':
345 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
345 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
346 format='gitdiff')
346 format='gitdiff')
347
347
348 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
348 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
349 response.content_type = 'text/plain'
349 response.content_type = 'text/plain'
350 response.content_disposition = 'attachment; filename=%s' \
350 response.content_disposition = 'attachment; filename=%s' \
351 % diff_name
351 % diff_name
352 return diff.raw_diff()
352 return diff.raw_diff()
353
353
354 elif c.action == 'raw':
354 elif c.action == 'raw':
355 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
355 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
356 format='gitdiff')
356 format='gitdiff')
357 response.content_type = 'text/plain'
357 response.content_type = 'text/plain'
358 return diff.raw_diff()
358 return diff.raw_diff()
359
359
360 elif c.action == 'diff':
360 elif c.action == 'diff':
361 if node1.is_binary or node2.is_binary:
361 if node1.is_binary or node2.is_binary:
362 c.cur_diff = _('Binary file')
362 c.cur_diff = _('Binary file')
363 elif node1.size > self.cut_off_limit or \
363 elif node1.size > self.cut_off_limit or \
364 node2.size > self.cut_off_limit:
364 node2.size > self.cut_off_limit:
365 c.cur_diff = ''
365 c.cur_diff = ''
366 c.big_diff = True
366 c.big_diff = True
367 else:
367 else:
368 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
368 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
369 format='gitdiff')
369 format='gitdiff')
370 c.cur_diff = diff.as_html()
370 c.cur_diff = diff.as_html()
371 else:
371 else:
372
372
373 #default option
373 #default option
374 if node1.is_binary or node2.is_binary:
374 if node1.is_binary or node2.is_binary:
375 c.cur_diff = _('Binary file')
375 c.cur_diff = _('Binary file')
376 elif node1.size > self.cut_off_limit or \
376 elif node1.size > self.cut_off_limit or \
377 node2.size > self.cut_off_limit:
377 node2.size > self.cut_off_limit:
378 c.cur_diff = ''
378 c.cur_diff = ''
379 c.big_diff = True
379 c.big_diff = True
380
380
381 else:
381 else:
382 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
382 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
383 format='gitdiff')
383 format='gitdiff')
384 c.cur_diff = diff.as_html()
384 c.cur_diff = diff.as_html()
385
385
386 if not c.cur_diff and not c.big_diff:
386 if not c.cur_diff and not c.big_diff:
387 c.no_changes = True
387 c.no_changes = True
388 return render('files/file_diff.html')
388 return render('files/file_diff.html')
389
389
390 def _get_node_history(self, cs, f_path):
390 def _get_node_history(self, cs, f_path):
391 changesets = cs.get_file_history(f_path)
391 changesets = cs.get_file_history(f_path)
392 hist_l = []
392 hist_l = []
393
393
394 changesets_group = ([], _("Changesets"))
394 changesets_group = ([], _("Changesets"))
395 branches_group = ([], _("Branches"))
395 branches_group = ([], _("Branches"))
396 tags_group = ([], _("Tags"))
396 tags_group = ([], _("Tags"))
397
397
398 for chs in changesets:
398 for chs in changesets:
399 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
399 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
400 changesets_group[0].append((chs.raw_id, n_desc,))
400 changesets_group[0].append((chs.raw_id, n_desc,))
401
401
402 hist_l.append(changesets_group)
402 hist_l.append(changesets_group)
403
403
404 for name, chs in c.rhodecode_repo.branches.items():
404 for name, chs in c.rhodecode_repo.branches.items():
405 #chs = chs.split(':')[-1]
405 #chs = chs.split(':')[-1]
406 branches_group[0].append((chs, name),)
406 branches_group[0].append((chs, name),)
407 hist_l.append(branches_group)
407 hist_l.append(branches_group)
408
408
409 for name, chs in c.rhodecode_repo.tags.items():
409 for name, chs in c.rhodecode_repo.tags.items():
410 #chs = chs.split(':')[-1]
410 #chs = chs.split(':')[-1]
411 tags_group[0].append((chs, name),)
411 tags_group[0].append((chs, name),)
412 hist_l.append(tags_group)
412 hist_l.append(tags_group)
413
413
414 return hist_l
414 return hist_l
@@ -1,322 +1,344 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.__init__
3 rhodecode.lib.__init__
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Some simple helper functions
6 Some simple helper functions
7
7
8 :created_on: Jan 5, 2011
8 :created_on: Jan 5, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2010 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
26
27 try:
27 try:
28 import json
28 import json
29 except ImportError:
29 except ImportError:
30 #python 2.5 compatibility
30 #python 2.5 compatibility
31 import simplejson as json
31 import simplejson as json
32
32
33
33
34 def __get_lem():
34 def __get_lem():
35 from pygments import lexers
35 from pygments import lexers
36 from string import lower
36 from string import lower
37 from collections import defaultdict
37 from collections import defaultdict
38
38
39 d = defaultdict(lambda: [])
39 d = defaultdict(lambda: [])
40
40
41 def __clean(s):
41 def __clean(s):
42 s = s.lstrip('*')
42 s = s.lstrip('*')
43 s = s.lstrip('.')
43 s = s.lstrip('.')
44
44
45 if s.find('[') != -1:
45 if s.find('[') != -1:
46 exts = []
46 exts = []
47 start, stop = s.find('['), s.find(']')
47 start, stop = s.find('['), s.find(']')
48
48
49 for suffix in s[start + 1:stop]:
49 for suffix in s[start + 1:stop]:
50 exts.append(s[:s.find('[')] + suffix)
50 exts.append(s[:s.find('[')] + suffix)
51 return map(lower, exts)
51 return map(lower, exts)
52 else:
52 else:
53 return map(lower, [s])
53 return map(lower, [s])
54
54
55 for lx, t in sorted(lexers.LEXERS.items()):
55 for lx, t in sorted(lexers.LEXERS.items()):
56 m = map(__clean, t[-2])
56 m = map(__clean, t[-2])
57 if m:
57 if m:
58 m = reduce(lambda x, y: x + y, m)
58 m = reduce(lambda x, y: x + y, m)
59 for ext in m:
59 for ext in m:
60 desc = lx.replace('Lexer', '')
60 desc = lx.replace('Lexer', '')
61 d[ext].append(desc)
61 d[ext].append(desc)
62
62
63 return dict(d)
63 return dict(d)
64
64
65 # language map is also used by whoosh indexer, which for those specified
65 # language map is also used by whoosh indexer, which for those specified
66 # extensions will index it's content
66 # extensions will index it's content
67 LANGUAGES_EXTENSIONS_MAP = __get_lem()
67 LANGUAGES_EXTENSIONS_MAP = __get_lem()
68
68
69 # Additional mappings that are not present in the pygments lexers
69 # Additional mappings that are not present in the pygments lexers
70 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
70 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
71 ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}
71 ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}
72
72
73 LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
73 LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
74
74
75
75
76 def str2bool(_str):
76 def str2bool(_str):
77 """
77 """
78 returs True/False value from given string, it tries to translate the
78 returs True/False value from given string, it tries to translate the
79 string into boolean
79 string into boolean
80
80
81 :param _str: string value to translate into boolean
81 :param _str: string value to translate into boolean
82 :rtype: boolean
82 :rtype: boolean
83 :returns: boolean from given string
83 :returns: boolean from given string
84 """
84 """
85 if _str is None:
85 if _str is None:
86 return False
86 return False
87 if _str in (True, False):
87 if _str in (True, False):
88 return _str
88 return _str
89 _str = str(_str).strip().lower()
89 _str = str(_str).strip().lower()
90 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
90 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
91
91
92
92
93 def convert_line_endings(line, mode):
93 def convert_line_endings(line, mode):
94 """
94 """
95 Converts a given line "line end" accordingly to given mode
95 Converts a given line "line end" accordingly to given mode
96
96
97 Available modes are::
97 Available modes are::
98 0 - Unix
98 0 - Unix
99 1 - Mac
99 1 - Mac
100 2 - DOS
100 2 - DOS
101
101
102 :param line: given line to convert
102 :param line: given line to convert
103 :param mode: mode to convert to
103 :param mode: mode to convert to
104 :rtype: str
104 :rtype: str
105 :return: converted line according to mode
105 :return: converted line according to mode
106 """
106 """
107 from string import replace
107 from string import replace
108
108
109 if mode == 0:
109 if mode == 0:
110 line = replace(line, '\r\n', '\n')
110 line = replace(line, '\r\n', '\n')
111 line = replace(line, '\r', '\n')
111 line = replace(line, '\r', '\n')
112 elif mode == 1:
112 elif mode == 1:
113 line = replace(line, '\r\n', '\r')
113 line = replace(line, '\r\n', '\r')
114 line = replace(line, '\n', '\r')
114 line = replace(line, '\n', '\r')
115 elif mode == 2:
115 elif mode == 2:
116 import re
116 import re
117 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
117 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
118 return line
118 return line
119
119
120
120
121 def detect_mode(line, default):
121 def detect_mode(line, default):
122 """
122 """
123 Detects line break for given line, if line break couldn't be found
123 Detects line break for given line, if line break couldn't be found
124 given default value is returned
124 given default value is returned
125
125
126 :param line: str line
126 :param line: str line
127 :param default: default
127 :param default: default
128 :rtype: int
128 :rtype: int
129 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
129 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
130 """
130 """
131 if line.endswith('\r\n'):
131 if line.endswith('\r\n'):
132 return 2
132 return 2
133 elif line.endswith('\n'):
133 elif line.endswith('\n'):
134 return 0
134 return 0
135 elif line.endswith('\r'):
135 elif line.endswith('\r'):
136 return 1
136 return 1
137 else:
137 else:
138 return default
138 return default
139
139
140
140
141 def generate_api_key(username, salt=None):
141 def generate_api_key(username, salt=None):
142 """
142 """
143 Generates unique API key for given username,if salt is not given
143 Generates unique API key for given username,if salt is not given
144 it'll be generated from some random string
144 it'll be generated from some random string
145
145
146 :param username: username as string
146 :param username: username as string
147 :param salt: salt to hash generate KEY
147 :param salt: salt to hash generate KEY
148 :rtype: str
148 :rtype: str
149 :returns: sha1 hash from username+salt
149 :returns: sha1 hash from username+salt
150 """
150 """
151 from tempfile import _RandomNameSequence
151 from tempfile import _RandomNameSequence
152 import hashlib
152 import hashlib
153
153
154 if salt is None:
154 if salt is None:
155 salt = _RandomNameSequence().next()
155 salt = _RandomNameSequence().next()
156
156
157 return hashlib.sha1(username + salt).hexdigest()
157 return hashlib.sha1(username + salt).hexdigest()
158
158
159
159
160 def safe_unicode(_str, from_encoding='utf8'):
160 def safe_unicode(_str, from_encoding='utf8'):
161 """
161 """
162 safe unicode function. In case of UnicodeDecode error we try to return
162 safe unicode function. In case of UnicodeDecode error we try to return
163 unicode with errors replace
163 unicode with errors replaceed
164
164
165 :param _str: string to decode
165 :param _str: string to decode
166 :rtype: unicode
166 :rtype: unicode
167 :returns: unicode object
167 :returns: unicode object
168 """
168 """
169
169
170 if isinstance(_str, unicode):
170 if isinstance(_str, unicode):
171 return _str
171 return _str
172
172
173 try:
173 try:
174 u_str = unicode(_str, from_encoding)
174 u_str = unicode(_str, from_encoding)
175 except UnicodeDecodeError:
175 except UnicodeDecodeError:
176 u_str = unicode(_str, from_encoding, 'replace')
176 u_str = unicode(_str, from_encoding, 'replace')
177
177
178 return u_str
178 return u_str
179
179
180
180
181 def safe_str(_unicode, to_encoding='utf8'):
182 """
183 safe str function. In case of UnicodeEncode error we try to return
184 str with errors replaceed
185
186 :param _unicode: unicode to encode
187 :rtype: str
188 :returns: str object
189 """
190
191 if isinstance(_unicode, str):
192 return _unicode
193
194 try:
195 safe_str = str(_unicode)
196 except UnicodeEncodeError:
197 safe_str = _unicode.encode(to_encoding, 'replace')
198
199 return safe_str
200
201
202
181 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
203 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
182 """
204 """
183 Custom engine_from_config functions that makes sure we use NullPool for
205 Custom engine_from_config functions that makes sure we use NullPool for
184 file based sqlite databases. This prevents errors on sqlite. This only
206 file based sqlite databases. This prevents errors on sqlite. This only
185 applies to sqlalchemy versions < 0.7.0
207 applies to sqlalchemy versions < 0.7.0
186
208
187 """
209 """
188 import sqlalchemy
210 import sqlalchemy
189 from sqlalchemy import engine_from_config as efc
211 from sqlalchemy import engine_from_config as efc
190 import logging
212 import logging
191
213
192 if int(sqlalchemy.__version__.split('.')[1]) < 7:
214 if int(sqlalchemy.__version__.split('.')[1]) < 7:
193
215
194 # This solution should work for sqlalchemy < 0.7.0, and should use
216 # This solution should work for sqlalchemy < 0.7.0, and should use
195 # proxy=TimerProxy() for execution time profiling
217 # proxy=TimerProxy() for execution time profiling
196
218
197 from sqlalchemy.pool import NullPool
219 from sqlalchemy.pool import NullPool
198 url = configuration[prefix + 'url']
220 url = configuration[prefix + 'url']
199
221
200 if url.startswith('sqlite'):
222 if url.startswith('sqlite'):
201 kwargs.update({'poolclass': NullPool})
223 kwargs.update({'poolclass': NullPool})
202 return efc(configuration, prefix, **kwargs)
224 return efc(configuration, prefix, **kwargs)
203 else:
225 else:
204 import time
226 import time
205 from sqlalchemy import event
227 from sqlalchemy import event
206 from sqlalchemy.engine import Engine
228 from sqlalchemy.engine import Engine
207
229
208 log = logging.getLogger('sqlalchemy.engine')
230 log = logging.getLogger('sqlalchemy.engine')
209 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
231 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
210 engine = efc(configuration, prefix, **kwargs)
232 engine = efc(configuration, prefix, **kwargs)
211
233
212 def color_sql(sql):
234 def color_sql(sql):
213 COLOR_SEQ = "\033[1;%dm"
235 COLOR_SEQ = "\033[1;%dm"
214 COLOR_SQL = YELLOW
236 COLOR_SQL = YELLOW
215 normal = '\x1b[0m'
237 normal = '\x1b[0m'
216 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
238 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
217
239
218 if configuration['debug']:
240 if configuration['debug']:
219 #attach events only for debug configuration
241 #attach events only for debug configuration
220
242
221 def before_cursor_execute(conn, cursor, statement,
243 def before_cursor_execute(conn, cursor, statement,
222 parameters, context, executemany):
244 parameters, context, executemany):
223 context._query_start_time = time.time()
245 context._query_start_time = time.time()
224 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
246 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
225
247
226
248
227 def after_cursor_execute(conn, cursor, statement,
249 def after_cursor_execute(conn, cursor, statement,
228 parameters, context, executemany):
250 parameters, context, executemany):
229 total = time.time() - context._query_start_time
251 total = time.time() - context._query_start_time
230 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
252 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
231
253
232 event.listen(engine, "before_cursor_execute",
254 event.listen(engine, "before_cursor_execute",
233 before_cursor_execute)
255 before_cursor_execute)
234 event.listen(engine, "after_cursor_execute",
256 event.listen(engine, "after_cursor_execute",
235 after_cursor_execute)
257 after_cursor_execute)
236
258
237 return engine
259 return engine
238
260
239
261
240 def age(curdate):
262 def age(curdate):
241 """
263 """
242 turns a datetime into an age string.
264 turns a datetime into an age string.
243
265
244 :param curdate: datetime object
266 :param curdate: datetime object
245 :rtype: unicode
267 :rtype: unicode
246 :returns: unicode words describing age
268 :returns: unicode words describing age
247 """
269 """
248
270
249 from datetime import datetime
271 from datetime import datetime
250 from webhelpers.date import time_ago_in_words
272 from webhelpers.date import time_ago_in_words
251
273
252 _ = lambda s:s
274 _ = lambda s:s
253
275
254 if not curdate:
276 if not curdate:
255 return ''
277 return ''
256
278
257 agescales = [(_(u"year"), 3600 * 24 * 365),
279 agescales = [(_(u"year"), 3600 * 24 * 365),
258 (_(u"month"), 3600 * 24 * 30),
280 (_(u"month"), 3600 * 24 * 30),
259 (_(u"day"), 3600 * 24),
281 (_(u"day"), 3600 * 24),
260 (_(u"hour"), 3600),
282 (_(u"hour"), 3600),
261 (_(u"minute"), 60),
283 (_(u"minute"), 60),
262 (_(u"second"), 1), ]
284 (_(u"second"), 1), ]
263
285
264 age = datetime.now() - curdate
286 age = datetime.now() - curdate
265 age_seconds = (age.days * agescales[2][1]) + age.seconds
287 age_seconds = (age.days * agescales[2][1]) + age.seconds
266 pos = 1
288 pos = 1
267 for scale in agescales:
289 for scale in agescales:
268 if scale[1] <= age_seconds:
290 if scale[1] <= age_seconds:
269 if pos == 6:pos = 5
291 if pos == 6:pos = 5
270 return '%s %s' % (time_ago_in_words(curdate,
292 return '%s %s' % (time_ago_in_words(curdate,
271 agescales[pos][0]), _('ago'))
293 agescales[pos][0]), _('ago'))
272 pos += 1
294 pos += 1
273
295
274 return _(u'just now')
296 return _(u'just now')
275
297
276
298
277 def uri_filter(uri):
299 def uri_filter(uri):
278 """
300 """
279 Removes user:password from given url string
301 Removes user:password from given url string
280
302
281 :param uri:
303 :param uri:
282 :rtype: unicode
304 :rtype: unicode
283 :returns: filtered list of strings
305 :returns: filtered list of strings
284 """
306 """
285 if not uri:
307 if not uri:
286 return ''
308 return ''
287
309
288 proto = ''
310 proto = ''
289
311
290 for pat in ('https://', 'http://'):
312 for pat in ('https://', 'http://'):
291 if uri.startswith(pat):
313 if uri.startswith(pat):
292 uri = uri[len(pat):]
314 uri = uri[len(pat):]
293 proto = pat
315 proto = pat
294 break
316 break
295
317
296 # remove passwords and username
318 # remove passwords and username
297 uri = uri[uri.find('@') + 1:]
319 uri = uri[uri.find('@') + 1:]
298
320
299 # get the port
321 # get the port
300 cred_pos = uri.find(':')
322 cred_pos = uri.find(':')
301 if cred_pos == -1:
323 if cred_pos == -1:
302 host, port = uri, None
324 host, port = uri, None
303 else:
325 else:
304 host, port = uri[:cred_pos], uri[cred_pos + 1:]
326 host, port = uri[:cred_pos], uri[cred_pos + 1:]
305
327
306 return filter(None, [proto, host, port])
328 return filter(None, [proto, host, port])
307
329
308
330
309 def credentials_filter(uri):
331 def credentials_filter(uri):
310 """
332 """
311 Returns a url with removed credentials
333 Returns a url with removed credentials
312
334
313 :param uri:
335 :param uri:
314 """
336 """
315
337
316 uri = uri_filter(uri)
338 uri = uri_filter(uri)
317 #check if we have port
339 #check if we have port
318 if len(uri) > 2 and uri[2]:
340 if len(uri) > 2 and uri[2]:
319 uri[2] = ':' + uri[2]
341 uri[2] = ':' + uri[2]
320
342
321 return ''.join(uri)
343 return ''.join(uri)
322
344
@@ -1,377 +1,374 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.celerylib.tasks
3 rhodecode.lib.celerylib.tasks
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 RhodeCode task modules, containing all task that suppose to be run
6 RhodeCode task modules, containing all task that suppose to be run
7 by celery daemon
7 by celery daemon
8
8
9 :created_on: Oct 6, 2010
9 :created_on: Oct 6, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 from celery.decorators import task
26 from celery.decorators import task
27
27
28 import os
28 import os
29 import traceback
29 import traceback
30 import logging
30 import logging
31 from os.path import dirname as dn, join as jn
31 from os.path import dirname as dn, join as jn
32
32
33 from time import mktime
33 from time import mktime
34 from operator import itemgetter
34 from operator import itemgetter
35 from string import lower
35 from string import lower
36
36
37 from pylons import config
37 from pylons import config
38 from pylons.i18n.translation import _
38 from pylons.i18n.translation import _
39
39
40 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP
40 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
41 from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
41 from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
42 __get_lockkey, LockHeld, DaemonLock
42 __get_lockkey, LockHeld, DaemonLock
43 from rhodecode.lib.helpers import person
43 from rhodecode.lib.helpers import person
44 from rhodecode.lib.smtp_mailer import SmtpMailer
44 from rhodecode.lib.smtp_mailer import SmtpMailer
45 from rhodecode.lib.utils import add_cache
45 from rhodecode.lib.utils import add_cache
46 from rhodecode.lib.odict import OrderedDict
46 from rhodecode.lib.odict import OrderedDict
47 from rhodecode.model import init_model
47 from rhodecode.model import init_model
48 from rhodecode.model import meta
48 from rhodecode.model import meta
49 from rhodecode.model.db import RhodeCodeUi, Statistics, Repository
49 from rhodecode.model.db import RhodeCodeUi, Statistics, Repository
50
50
51 from vcs.backends import get_repo
51 from vcs.backends import get_repo
52
52
53 from sqlalchemy import engine_from_config
53 from sqlalchemy import engine_from_config
54
54
55 add_cache(config)
55 add_cache(config)
56
56
57 try:
57 try:
58 import json
58 import json
59 except ImportError:
59 except ImportError:
60 #python 2.5 compatibility
60 #python 2.5 compatibility
61 import simplejson as json
61 import simplejson as json
62
62
63 __all__ = ['whoosh_index', 'get_commits_stats',
63 __all__ = ['whoosh_index', 'get_commits_stats',
64 'reset_user_password', 'send_email']
64 'reset_user_password', 'send_email']
65
65
66 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
66 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
67
67
68
68
69
70 def get_session():
69 def get_session():
71 if CELERY_ON:
70 if CELERY_ON:
72 engine = engine_from_config(config, 'sqlalchemy.db1.')
71 engine = engine_from_config(config, 'sqlalchemy.db1.')
73 init_model(engine)
72 init_model(engine)
74 sa = meta.Session()
73 sa = meta.Session()
75 return sa
74 return sa
76
75
77
76
78 def get_repos_path():
77 def get_repos_path():
79 sa = get_session()
78 sa = get_session()
80 q = sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
79 q = sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
81 return q.ui_value
80 return q.ui_value
82
81
83
82
84 @task(ignore_result=True)
83 @task(ignore_result=True)
85 @locked_task
84 @locked_task
86 def whoosh_index(repo_location, full_index):
85 def whoosh_index(repo_location, full_index):
87 #log = whoosh_index.get_logger()
86 #log = whoosh_index.get_logger()
88 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
87 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
89 index_location = config['index_dir']
88 index_location = config['index_dir']
90 WhooshIndexingDaemon(index_location=index_location,
89 WhooshIndexingDaemon(index_location=index_location,
91 repo_location=repo_location, sa=get_session())\
90 repo_location=repo_location, sa=get_session())\
92 .run(full_index=full_index)
91 .run(full_index=full_index)
93
92
94
93
95 @task(ignore_result=True)
94 @task(ignore_result=True)
96 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
95 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
97 try:
96 try:
98 log = get_commits_stats.get_logger()
97 log = get_commits_stats.get_logger()
99 except:
98 except:
100 log = logging.getLogger(__name__)
99 log = logging.getLogger(__name__)
101
100
102 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
101 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
103 ts_max_y)
102 ts_max_y)
104 lockkey_path = dn(dn(dn(dn(os.path.abspath(__file__)))))
103 lockkey_path = dn(dn(dn(dn(os.path.abspath(__file__)))))
105 log.info('running task with lockkey %s', lockkey)
104 log.info('running task with lockkey %s', lockkey)
106 try:
105 try:
107 lock = l = DaemonLock(jn(lockkey_path, lockkey))
106 lock = l = DaemonLock(jn(lockkey_path, lockkey))
108
107
109 #for js data compatibilty cleans the key for person from '
108 #for js data compatibilty cleans the key for person from '
110 akc = lambda k: person(k).replace('"', "")
109 akc = lambda k: person(k).replace('"', "")
111
110
112 co_day_auth_aggr = {}
111 co_day_auth_aggr = {}
113 commits_by_day_aggregate = {}
112 commits_by_day_aggregate = {}
114 repos_path = get_repos_path()
113 repos_path = get_repos_path()
115 p = os.path.join(repos_path, repo_name)
114 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
116 repo = get_repo(p)
117 repo_size = len(repo.revisions)
115 repo_size = len(repo.revisions)
118 #return if repo have no revisions
116 #return if repo have no revisions
119 if repo_size < 1:
117 if repo_size < 1:
120 lock.release()
118 lock.release()
121 return True
119 return True
122
120
123 skip_date_limit = True
121 skip_date_limit = True
124 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
122 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
125 last_rev = 0
123 last_rev = 0
126 last_cs = None
124 last_cs = None
127 timegetter = itemgetter('time')
125 timegetter = itemgetter('time')
128
126
129 sa = get_session()
127 sa = get_session()
130
128
131 dbrepo = sa.query(Repository)\
129 dbrepo = sa.query(Repository)\
132 .filter(Repository.repo_name == repo_name).scalar()
130 .filter(Repository.repo_name == repo_name).scalar()
133 cur_stats = sa.query(Statistics)\
131 cur_stats = sa.query(Statistics)\
134 .filter(Statistics.repository == dbrepo).scalar()
132 .filter(Statistics.repository == dbrepo).scalar()
135
133
136 if cur_stats is not None:
134 if cur_stats is not None:
137 last_rev = cur_stats.stat_on_revision
135 last_rev = cur_stats.stat_on_revision
138
136
139 if last_rev == repo.get_changeset().revision and repo_size > 1:
137 if last_rev == repo.get_changeset().revision and repo_size > 1:
140 #pass silently without any work if we're not on first revision or
138 #pass silently without any work if we're not on first revision or
141 #current state of parsing revision(from db marker) is the
139 #current state of parsing revision(from db marker) is the
142 #last revision
140 #last revision
143 lock.release()
141 lock.release()
144 return True
142 return True
145
143
146 if cur_stats:
144 if cur_stats:
147 commits_by_day_aggregate = OrderedDict(json.loads(
145 commits_by_day_aggregate = OrderedDict(json.loads(
148 cur_stats.commit_activity_combined))
146 cur_stats.commit_activity_combined))
149 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
147 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
150
148
151 log.debug('starting parsing %s', parse_limit)
149 log.debug('starting parsing %s', parse_limit)
152 lmktime = mktime
150 lmktime = mktime
153
151
154 last_rev = last_rev + 1 if last_rev > 0 else last_rev
152 last_rev = last_rev + 1 if last_rev > 0 else last_rev
155
153
156 for cs in repo[last_rev:last_rev + parse_limit]:
154 for cs in repo[last_rev:last_rev + parse_limit]:
157 last_cs = cs # remember last parsed changeset
155 last_cs = cs # remember last parsed changeset
158 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
156 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
159 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
157 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
160
158
161 if akc(cs.author) in co_day_auth_aggr:
159 if akc(cs.author) in co_day_auth_aggr:
162 try:
160 try:
163 l = [timegetter(x) for x in
161 l = [timegetter(x) for x in
164 co_day_auth_aggr[akc(cs.author)]['data']]
162 co_day_auth_aggr[akc(cs.author)]['data']]
165 time_pos = l.index(k)
163 time_pos = l.index(k)
166 except ValueError:
164 except ValueError:
167 time_pos = False
165 time_pos = False
168
166
169 if time_pos >= 0 and time_pos is not False:
167 if time_pos >= 0 and time_pos is not False:
170
168
171 datadict = \
169 datadict = \
172 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
170 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
173
171
174 datadict["commits"] += 1
172 datadict["commits"] += 1
175 datadict["added"] += len(cs.added)
173 datadict["added"] += len(cs.added)
176 datadict["changed"] += len(cs.changed)
174 datadict["changed"] += len(cs.changed)
177 datadict["removed"] += len(cs.removed)
175 datadict["removed"] += len(cs.removed)
178
176
179 else:
177 else:
180 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
178 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
181
179
182 datadict = {"time": k,
180 datadict = {"time": k,
183 "commits": 1,
181 "commits": 1,
184 "added": len(cs.added),
182 "added": len(cs.added),
185 "changed": len(cs.changed),
183 "changed": len(cs.changed),
186 "removed": len(cs.removed),
184 "removed": len(cs.removed),
187 }
185 }
188 co_day_auth_aggr[akc(cs.author)]['data']\
186 co_day_auth_aggr[akc(cs.author)]['data']\
189 .append(datadict)
187 .append(datadict)
190
188
191 else:
189 else:
192 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
190 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
193 co_day_auth_aggr[akc(cs.author)] = {
191 co_day_auth_aggr[akc(cs.author)] = {
194 "label": akc(cs.author),
192 "label": akc(cs.author),
195 "data": [{"time":k,
193 "data": [{"time":k,
196 "commits":1,
194 "commits":1,
197 "added":len(cs.added),
195 "added":len(cs.added),
198 "changed":len(cs.changed),
196 "changed":len(cs.changed),
199 "removed":len(cs.removed),
197 "removed":len(cs.removed),
200 }],
198 }],
201 "schema": ["commits"],
199 "schema": ["commits"],
202 }
200 }
203
201
204 #gather all data by day
202 #gather all data by day
205 if k in commits_by_day_aggregate:
203 if k in commits_by_day_aggregate:
206 commits_by_day_aggregate[k] += 1
204 commits_by_day_aggregate[k] += 1
207 else:
205 else:
208 commits_by_day_aggregate[k] = 1
206 commits_by_day_aggregate[k] = 1
209
207
210 overview_data = sorted(commits_by_day_aggregate.items(),
208 overview_data = sorted(commits_by_day_aggregate.items(),
211 key=itemgetter(0))
209 key=itemgetter(0))
212
210
213 if not co_day_auth_aggr:
211 if not co_day_auth_aggr:
214 co_day_auth_aggr[akc(repo.contact)] = {
212 co_day_auth_aggr[akc(repo.contact)] = {
215 "label": akc(repo.contact),
213 "label": akc(repo.contact),
216 "data": [0, 1],
214 "data": [0, 1],
217 "schema": ["commits"],
215 "schema": ["commits"],
218 }
216 }
219
217
220 stats = cur_stats if cur_stats else Statistics()
218 stats = cur_stats if cur_stats else Statistics()
221 stats.commit_activity = json.dumps(co_day_auth_aggr)
219 stats.commit_activity = json.dumps(co_day_auth_aggr)
222 stats.commit_activity_combined = json.dumps(overview_data)
220 stats.commit_activity_combined = json.dumps(overview_data)
223
221
224 log.debug('last revison %s', last_rev)
222 log.debug('last revison %s', last_rev)
225 leftovers = len(repo.revisions[last_rev:])
223 leftovers = len(repo.revisions[last_rev:])
226 log.debug('revisions to parse %s', leftovers)
224 log.debug('revisions to parse %s', leftovers)
227
225
228 if last_rev == 0 or leftovers < parse_limit:
226 if last_rev == 0 or leftovers < parse_limit:
229 log.debug('getting code trending stats')
227 log.debug('getting code trending stats')
230 stats.languages = json.dumps(__get_codes_stats(repo_name))
228 stats.languages = json.dumps(__get_codes_stats(repo_name))
231
229
232 try:
230 try:
233 stats.repository = dbrepo
231 stats.repository = dbrepo
234 stats.stat_on_revision = last_cs.revision if last_cs else 0
232 stats.stat_on_revision = last_cs.revision if last_cs else 0
235 sa.add(stats)
233 sa.add(stats)
236 sa.commit()
234 sa.commit()
237 except:
235 except:
238 log.error(traceback.format_exc())
236 log.error(traceback.format_exc())
239 sa.rollback()
237 sa.rollback()
240 lock.release()
238 lock.release()
241 return False
239 return False
242
240
243 #final release
241 #final release
244 lock.release()
242 lock.release()
245
243
246 #execute another task if celery is enabled
244 #execute another task if celery is enabled
247 if len(repo.revisions) > 1 and CELERY_ON:
245 if len(repo.revisions) > 1 and CELERY_ON:
248 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
246 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
249 return True
247 return True
250 except LockHeld:
248 except LockHeld:
251 log.info('LockHeld')
249 log.info('LockHeld')
252 return 'Task with key %s already running' % lockkey
250 return 'Task with key %s already running' % lockkey
253
251
254
252
255 @task(ignore_result=True)
253 @task(ignore_result=True)
256 def reset_user_password(user_email):
254 def reset_user_password(user_email):
257 try:
255 try:
258 log = reset_user_password.get_logger()
256 log = reset_user_password.get_logger()
259 except:
257 except:
260 log = logging.getLogger(__name__)
258 log = logging.getLogger(__name__)
261
259
262 from rhodecode.lib import auth
260 from rhodecode.lib import auth
263 from rhodecode.model.db import User
261 from rhodecode.model.db import User
264
262
265 try:
263 try:
266 try:
264 try:
267 sa = get_session()
265 sa = get_session()
268 user = sa.query(User).filter(User.email == user_email).scalar()
266 user = sa.query(User).filter(User.email == user_email).scalar()
269 new_passwd = auth.PasswordGenerator().gen_password(8,
267 new_passwd = auth.PasswordGenerator().gen_password(8,
270 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
268 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
271 if user:
269 if user:
272 user.password = auth.get_crypt_password(new_passwd)
270 user.password = auth.get_crypt_password(new_passwd)
273 user.api_key = auth.generate_api_key(user.username)
271 user.api_key = auth.generate_api_key(user.username)
274 sa.add(user)
272 sa.add(user)
275 sa.commit()
273 sa.commit()
276 log.info('change password for %s', user_email)
274 log.info('change password for %s', user_email)
277 if new_passwd is None:
275 if new_passwd is None:
278 raise Exception('unable to generate new password')
276 raise Exception('unable to generate new password')
279
277
280 except:
278 except:
281 log.error(traceback.format_exc())
279 log.error(traceback.format_exc())
282 sa.rollback()
280 sa.rollback()
283
281
284 run_task(send_email, user_email,
282 run_task(send_email, user_email,
285 "Your new rhodecode password",
283 "Your new rhodecode password",
286 'Your new rhodecode password:%s' % (new_passwd))
284 'Your new rhodecode password:%s' % (new_passwd))
287 log.info('send new password mail to %s', user_email)
285 log.info('send new password mail to %s', user_email)
288
286
289 except:
287 except:
290 log.error('Failed to update user password')
288 log.error('Failed to update user password')
291 log.error(traceback.format_exc())
289 log.error(traceback.format_exc())
292
290
293 return True
291 return True
294
292
295
293
296 @task(ignore_result=True)
294 @task(ignore_result=True)
297 def send_email(recipients, subject, body):
295 def send_email(recipients, subject, body):
298 """
296 """
299 Sends an email with defined parameters from the .ini files.
297 Sends an email with defined parameters from the .ini files.
300
298
301 :param recipients: list of recipients, it this is empty the defined email
299 :param recipients: list of recipients, it this is empty the defined email
302 address from field 'email_to' is used instead
300 address from field 'email_to' is used instead
303 :param subject: subject of the mail
301 :param subject: subject of the mail
304 :param body: body of the mail
302 :param body: body of the mail
305 """
303 """
306 try:
304 try:
307 log = send_email.get_logger()
305 log = send_email.get_logger()
308 except:
306 except:
309 log = logging.getLogger(__name__)
307 log = logging.getLogger(__name__)
310
308
311 email_config = config
309 email_config = config
312
310
313 if not recipients:
311 if not recipients:
314 recipients = [email_config.get('email_to')]
312 recipients = [email_config.get('email_to')]
315
313
316 mail_from = email_config.get('app_email_from')
314 mail_from = email_config.get('app_email_from')
317 user = email_config.get('smtp_username')
315 user = email_config.get('smtp_username')
318 passwd = email_config.get('smtp_password')
316 passwd = email_config.get('smtp_password')
319 mail_server = email_config.get('smtp_server')
317 mail_server = email_config.get('smtp_server')
320 mail_port = email_config.get('smtp_port')
318 mail_port = email_config.get('smtp_port')
321 tls = str2bool(email_config.get('smtp_use_tls'))
319 tls = str2bool(email_config.get('smtp_use_tls'))
322 ssl = str2bool(email_config.get('smtp_use_ssl'))
320 ssl = str2bool(email_config.get('smtp_use_ssl'))
323 debug = str2bool(config.get('debug'))
321 debug = str2bool(config.get('debug'))
324
322
325 try:
323 try:
326 m = SmtpMailer(mail_from, user, passwd, mail_server,
324 m = SmtpMailer(mail_from, user, passwd, mail_server,
327 mail_port, ssl, tls, debug=debug)
325 mail_port, ssl, tls, debug=debug)
328 m.send(recipients, subject, body)
326 m.send(recipients, subject, body)
329 except:
327 except:
330 log.error('Mail sending failed')
328 log.error('Mail sending failed')
331 log.error(traceback.format_exc())
329 log.error(traceback.format_exc())
332 return False
330 return False
333 return True
331 return True
334
332
335
333
336 @task(ignore_result=True)
334 @task(ignore_result=True)
337 def create_repo_fork(form_data, cur_user):
335 def create_repo_fork(form_data, cur_user):
338 from rhodecode.model.repo import RepoModel
336 from rhodecode.model.repo import RepoModel
339 from vcs import get_backend
337 from vcs import get_backend
340
338
341 try:
339 try:
342 log = create_repo_fork.get_logger()
340 log = create_repo_fork.get_logger()
343 except:
341 except:
344 log = logging.getLogger(__name__)
342 log = logging.getLogger(__name__)
345
343
346 repo_model = RepoModel(get_session())
344 repo_model = RepoModel(get_session())
347 repo_model.create(form_data, cur_user, just_db=True, fork=True)
345 repo_model.create(form_data, cur_user, just_db=True, fork=True)
348 repo_name = form_data['repo_name']
346 repo_name = form_data['repo_name']
349 repos_path = get_repos_path()
347 repos_path = get_repos_path()
350 repo_path = os.path.join(repos_path, repo_name)
348 repo_path = os.path.join(repos_path, repo_name)
351 repo_fork_path = os.path.join(repos_path, form_data['fork_name'])
349 repo_fork_path = os.path.join(repos_path, form_data['fork_name'])
352 alias = form_data['repo_type']
350 alias = form_data['repo_type']
353
351
354 log.info('creating repo fork %s as %s', repo_name, repo_path)
352 log.info('creating repo fork %s as %s', repo_name, repo_path)
355 backend = get_backend(alias)
353 backend = get_backend(alias)
356 backend(str(repo_fork_path), create=True, src_url=str(repo_path))
354 backend(str(repo_fork_path), create=True, src_url=str(repo_path))
357
355
358
356
359 def __get_codes_stats(repo_name):
357 def __get_codes_stats(repo_name):
360 repos_path = get_repos_path()
358 repos_path = get_repos_path()
361 p = os.path.join(repos_path, repo_name)
359 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
362 repo = get_repo(p)
363 tip = repo.get_changeset()
360 tip = repo.get_changeset()
364 code_stats = {}
361 code_stats = {}
365
362
366 def aggregate(cs):
363 def aggregate(cs):
367 for f in cs[2]:
364 for f in cs[2]:
368 ext = lower(f.extension)
365 ext = lower(f.extension)
369 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
366 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
370 if ext in code_stats:
367 if ext in code_stats:
371 code_stats[ext] += 1
368 code_stats[ext] += 1
372 else:
369 else:
373 code_stats[ext] = 1
370 code_stats[ext] = 1
374
371
375 map(aggregate, tip.walk('/'))
372 map(aggregate, tip.walk('/'))
376
373
377 return code_stats or {}
374 return code_stats or {}
@@ -1,694 +1,694 b''
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10
10
11 from datetime import datetime
11 from datetime import datetime
12 from pygments.formatters import HtmlFormatter
12 from pygments.formatters import HtmlFormatter
13 from pygments import highlight as code_highlight
13 from pygments import highlight as code_highlight
14 from pylons import url, request, config
14 from pylons import url, request, config
15 from pylons.i18n.translation import _, ungettext
15 from pylons.i18n.translation import _, ungettext
16
16
17 from webhelpers.html import literal, HTML, escape
17 from webhelpers.html import literal, HTML, escape
18 from webhelpers.html.tools import *
18 from webhelpers.html.tools import *
19 from webhelpers.html.builder import make_tag
19 from webhelpers.html.builder import make_tag
20 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
20 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
21 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
21 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
22 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
22 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
23 password, textarea, title, ul, xml_declaration, radio
23 password, textarea, title, ul, xml_declaration, radio
24 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
24 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
25 mail_to, strip_links, strip_tags, tag_re
25 mail_to, strip_links, strip_tags, tag_re
26 from webhelpers.number import format_byte_size, format_bit_size
26 from webhelpers.number import format_byte_size, format_bit_size
27 from webhelpers.pylonslib import Flash as _Flash
27 from webhelpers.pylonslib import Flash as _Flash
28 from webhelpers.pylonslib.secure_form import secure_form
28 from webhelpers.pylonslib.secure_form import secure_form
29 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
29 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
30 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
30 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
31 replace_whitespace, urlify, truncate, wrap_paragraphs
31 replace_whitespace, urlify, truncate, wrap_paragraphs
32 from webhelpers.date import time_ago_in_words
32 from webhelpers.date import time_ago_in_words
33 from webhelpers.paginate import Page
33 from webhelpers.paginate import Page
34 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
34 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
35 convert_boolean_attrs, NotGiven
35 convert_boolean_attrs, NotGiven
36
36
37 from vcs.utils.annotate import annotate_highlight
37 from vcs.utils.annotate import annotate_highlight
38 from rhodecode.lib.utils import repo_name_slug
38 from rhodecode.lib.utils import repo_name_slug
39 from rhodecode.lib import str2bool, safe_unicode
39 from rhodecode.lib import str2bool, safe_unicode, safe_str
40
40
41 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
41 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
42 """
42 """
43 Reset button
43 Reset button
44 """
44 """
45 _set_input_attrs(attrs, type, name, value)
45 _set_input_attrs(attrs, type, name, value)
46 _set_id_attr(attrs, id, name)
46 _set_id_attr(attrs, id, name)
47 convert_boolean_attrs(attrs, ["disabled"])
47 convert_boolean_attrs(attrs, ["disabled"])
48 return HTML.input(**attrs)
48 return HTML.input(**attrs)
49
49
50 reset = _reset
50 reset = _reset
51
51
52
52
53 def get_token():
53 def get_token():
54 """Return the current authentication token, creating one if one doesn't
54 """Return the current authentication token, creating one if one doesn't
55 already exist.
55 already exist.
56 """
56 """
57 token_key = "_authentication_token"
57 token_key = "_authentication_token"
58 from pylons import session
58 from pylons import session
59 if not token_key in session:
59 if not token_key in session:
60 try:
60 try:
61 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
61 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
62 except AttributeError: # Python < 2.4
62 except AttributeError: # Python < 2.4
63 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
63 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
64 session[token_key] = token
64 session[token_key] = token
65 if hasattr(session, 'save'):
65 if hasattr(session, 'save'):
66 session.save()
66 session.save()
67 return session[token_key]
67 return session[token_key]
68
68
69 class _GetError(object):
69 class _GetError(object):
70 """Get error from form_errors, and represent it as span wrapped error
70 """Get error from form_errors, and represent it as span wrapped error
71 message
71 message
72
72
73 :param field_name: field to fetch errors for
73 :param field_name: field to fetch errors for
74 :param form_errors: form errors dict
74 :param form_errors: form errors dict
75 """
75 """
76
76
77 def __call__(self, field_name, form_errors):
77 def __call__(self, field_name, form_errors):
78 tmpl = """<span class="error_msg">%s</span>"""
78 tmpl = """<span class="error_msg">%s</span>"""
79 if form_errors and form_errors.has_key(field_name):
79 if form_errors and form_errors.has_key(field_name):
80 return literal(tmpl % form_errors.get(field_name))
80 return literal(tmpl % form_errors.get(field_name))
81
81
82 get_error = _GetError()
82 get_error = _GetError()
83
83
84 class _ToolTip(object):
84 class _ToolTip(object):
85
85
86 def __call__(self, tooltip_title, trim_at=50):
86 def __call__(self, tooltip_title, trim_at=50):
87 """Special function just to wrap our text into nice formatted
87 """Special function just to wrap our text into nice formatted
88 autowrapped text
88 autowrapped text
89
89
90 :param tooltip_title:
90 :param tooltip_title:
91 """
91 """
92
92
93 return escape(tooltip_title)
93 return escape(tooltip_title)
94
94
95 def activate(self):
95 def activate(self):
96 """Adds tooltip mechanism to the given Html all tooltips have to have
96 """Adds tooltip mechanism to the given Html all tooltips have to have
97 set class `tooltip` and set attribute `tooltip_title`.
97 set class `tooltip` and set attribute `tooltip_title`.
98 Then a tooltip will be generated based on that. All with yui js tooltip
98 Then a tooltip will be generated based on that. All with yui js tooltip
99 """
99 """
100
100
101 js = '''
101 js = '''
102 YAHOO.util.Event.onDOMReady(function(){
102 YAHOO.util.Event.onDOMReady(function(){
103 function toolTipsId(){
103 function toolTipsId(){
104 var ids = [];
104 var ids = [];
105 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
105 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
106
106
107 for (var i = 0; i < tts.length; i++) {
107 for (var i = 0; i < tts.length; i++) {
108 //if element doesn't not have and id autogenerate one for tooltip
108 //if element doesn't not have and id autogenerate one for tooltip
109
109
110 if (!tts[i].id){
110 if (!tts[i].id){
111 tts[i].id='tt'+i*100;
111 tts[i].id='tt'+i*100;
112 }
112 }
113 ids.push(tts[i].id);
113 ids.push(tts[i].id);
114 }
114 }
115 return ids
115 return ids
116 };
116 };
117 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
117 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
118 context: [[toolTipsId()],"tl","bl",null,[0,5]],
118 context: [[toolTipsId()],"tl","bl",null,[0,5]],
119 monitorresize:false,
119 monitorresize:false,
120 xyoffset :[0,0],
120 xyoffset :[0,0],
121 autodismissdelay:300000,
121 autodismissdelay:300000,
122 hidedelay:5,
122 hidedelay:5,
123 showdelay:20,
123 showdelay:20,
124 });
124 });
125
125
126 });
126 });
127 '''
127 '''
128 return literal(js)
128 return literal(js)
129
129
130 tooltip = _ToolTip()
130 tooltip = _ToolTip()
131
131
132 class _FilesBreadCrumbs(object):
132 class _FilesBreadCrumbs(object):
133
133
134 def __call__(self, repo_name, rev, paths):
134 def __call__(self, repo_name, rev, paths):
135 if isinstance(paths, str):
135 if isinstance(paths, str):
136 paths = safe_unicode(paths)
136 paths = safe_unicode(paths)
137 url_l = [link_to(repo_name, url('files_home',
137 url_l = [link_to(repo_name, url('files_home',
138 repo_name=repo_name,
138 repo_name=repo_name,
139 revision=rev, f_path=''))]
139 revision=rev, f_path=''))]
140 paths_l = paths.split('/')
140 paths_l = paths.split('/')
141 for cnt, p in enumerate(paths_l):
141 for cnt, p in enumerate(paths_l):
142 if p != '':
142 if p != '':
143 url_l.append(link_to(p, url('files_home',
143 url_l.append(link_to(p, url('files_home',
144 repo_name=repo_name,
144 repo_name=repo_name,
145 revision=rev,
145 revision=rev,
146 f_path='/'.join(paths_l[:cnt + 1]))))
146 f_path='/'.join(paths_l[:cnt + 1]))))
147
147
148 return literal('/'.join(url_l))
148 return literal('/'.join(url_l))
149
149
150 files_breadcrumbs = _FilesBreadCrumbs()
150 files_breadcrumbs = _FilesBreadCrumbs()
151
151
152 class CodeHtmlFormatter(HtmlFormatter):
152 class CodeHtmlFormatter(HtmlFormatter):
153 """My code Html Formatter for source codes
153 """My code Html Formatter for source codes
154 """
154 """
155
155
156 def wrap(self, source, outfile):
156 def wrap(self, source, outfile):
157 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
157 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
158
158
159 def _wrap_code(self, source):
159 def _wrap_code(self, source):
160 for cnt, it in enumerate(source):
160 for cnt, it in enumerate(source):
161 i, t = it
161 i, t = it
162 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
162 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
163 yield i, t
163 yield i, t
164
164
165 def _wrap_tablelinenos(self, inner):
165 def _wrap_tablelinenos(self, inner):
166 dummyoutfile = StringIO.StringIO()
166 dummyoutfile = StringIO.StringIO()
167 lncount = 0
167 lncount = 0
168 for t, line in inner:
168 for t, line in inner:
169 if t:
169 if t:
170 lncount += 1
170 lncount += 1
171 dummyoutfile.write(line)
171 dummyoutfile.write(line)
172
172
173 fl = self.linenostart
173 fl = self.linenostart
174 mw = len(str(lncount + fl - 1))
174 mw = len(str(lncount + fl - 1))
175 sp = self.linenospecial
175 sp = self.linenospecial
176 st = self.linenostep
176 st = self.linenostep
177 la = self.lineanchors
177 la = self.lineanchors
178 aln = self.anchorlinenos
178 aln = self.anchorlinenos
179 nocls = self.noclasses
179 nocls = self.noclasses
180 if sp:
180 if sp:
181 lines = []
181 lines = []
182
182
183 for i in range(fl, fl + lncount):
183 for i in range(fl, fl + lncount):
184 if i % st == 0:
184 if i % st == 0:
185 if i % sp == 0:
185 if i % sp == 0:
186 if aln:
186 if aln:
187 lines.append('<a href="#%s%d" class="special">%*d</a>' %
187 lines.append('<a href="#%s%d" class="special">%*d</a>' %
188 (la, i, mw, i))
188 (la, i, mw, i))
189 else:
189 else:
190 lines.append('<span class="special">%*d</span>' % (mw, i))
190 lines.append('<span class="special">%*d</span>' % (mw, i))
191 else:
191 else:
192 if aln:
192 if aln:
193 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
193 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
194 else:
194 else:
195 lines.append('%*d' % (mw, i))
195 lines.append('%*d' % (mw, i))
196 else:
196 else:
197 lines.append('')
197 lines.append('')
198 ls = '\n'.join(lines)
198 ls = '\n'.join(lines)
199 else:
199 else:
200 lines = []
200 lines = []
201 for i in range(fl, fl + lncount):
201 for i in range(fl, fl + lncount):
202 if i % st == 0:
202 if i % st == 0:
203 if aln:
203 if aln:
204 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
204 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
205 else:
205 else:
206 lines.append('%*d' % (mw, i))
206 lines.append('%*d' % (mw, i))
207 else:
207 else:
208 lines.append('')
208 lines.append('')
209 ls = '\n'.join(lines)
209 ls = '\n'.join(lines)
210
210
211 # in case you wonder about the seemingly redundant <div> here: since the
211 # in case you wonder about the seemingly redundant <div> here: since the
212 # content in the other cell also is wrapped in a div, some browsers in
212 # content in the other cell also is wrapped in a div, some browsers in
213 # some configurations seem to mess up the formatting...
213 # some configurations seem to mess up the formatting...
214 if nocls:
214 if nocls:
215 yield 0, ('<table class="%stable">' % self.cssclass +
215 yield 0, ('<table class="%stable">' % self.cssclass +
216 '<tr><td><div class="linenodiv" '
216 '<tr><td><div class="linenodiv" '
217 'style="background-color: #f0f0f0; padding-right: 10px">'
217 'style="background-color: #f0f0f0; padding-right: 10px">'
218 '<pre style="line-height: 125%">' +
218 '<pre style="line-height: 125%">' +
219 ls + '</pre></div></td><td id="hlcode" class="code">')
219 ls + '</pre></div></td><td id="hlcode" class="code">')
220 else:
220 else:
221 yield 0, ('<table class="%stable">' % self.cssclass +
221 yield 0, ('<table class="%stable">' % self.cssclass +
222 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
222 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
223 ls + '</pre></div></td><td id="hlcode" class="code">')
223 ls + '</pre></div></td><td id="hlcode" class="code">')
224 yield 0, dummyoutfile.getvalue()
224 yield 0, dummyoutfile.getvalue()
225 yield 0, '</td></tr></table>'
225 yield 0, '</td></tr></table>'
226
226
227
227
228 def pygmentize(filenode, **kwargs):
228 def pygmentize(filenode, **kwargs):
229 """pygmentize function using pygments
229 """pygmentize function using pygments
230
230
231 :param filenode:
231 :param filenode:
232 """
232 """
233
233
234 return literal(code_highlight(filenode.content,
234 return literal(code_highlight(filenode.content,
235 filenode.lexer, CodeHtmlFormatter(**kwargs)))
235 filenode.lexer, CodeHtmlFormatter(**kwargs)))
236
236
237 def pygmentize_annotation(repo_name, filenode, **kwargs):
237 def pygmentize_annotation(repo_name, filenode, **kwargs):
238 """pygmentize function for annotation
238 """pygmentize function for annotation
239
239
240 :param filenode:
240 :param filenode:
241 """
241 """
242
242
243 color_dict = {}
243 color_dict = {}
244 def gen_color(n=10000):
244 def gen_color(n=10000):
245 """generator for getting n of evenly distributed colors using
245 """generator for getting n of evenly distributed colors using
246 hsv color and golden ratio. It always return same order of colors
246 hsv color and golden ratio. It always return same order of colors
247
247
248 :returns: RGB tuple
248 :returns: RGB tuple
249 """
249 """
250 import colorsys
250 import colorsys
251 golden_ratio = 0.618033988749895
251 golden_ratio = 0.618033988749895
252 h = 0.22717784590367374
252 h = 0.22717784590367374
253
253
254 for _ in xrange(n):
254 for _ in xrange(n):
255 h += golden_ratio
255 h += golden_ratio
256 h %= 1
256 h %= 1
257 HSV_tuple = [h, 0.95, 0.95]
257 HSV_tuple = [h, 0.95, 0.95]
258 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
258 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
259 yield map(lambda x:str(int(x * 256)), RGB_tuple)
259 yield map(lambda x:str(int(x * 256)), RGB_tuple)
260
260
261 cgenerator = gen_color()
261 cgenerator = gen_color()
262
262
263 def get_color_string(cs):
263 def get_color_string(cs):
264 if color_dict.has_key(cs):
264 if color_dict.has_key(cs):
265 col = color_dict[cs]
265 col = color_dict[cs]
266 else:
266 else:
267 col = color_dict[cs] = cgenerator.next()
267 col = color_dict[cs] = cgenerator.next()
268 return "color: rgb(%s)! important;" % (', '.join(col))
268 return "color: rgb(%s)! important;" % (', '.join(col))
269
269
270 def url_func(repo_name):
270 def url_func(repo_name):
271
271
272 def _url_func(changeset):
272 def _url_func(changeset):
273 author = changeset.author
273 author = changeset.author
274 date = changeset.date
274 date = changeset.date
275 message = tooltip(changeset.message)
275 message = tooltip(changeset.message)
276
276
277 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
277 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
278 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
278 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
279 "</b> %s<br/></div>")
279 "</b> %s<br/></div>")
280
280
281 tooltip_html = tooltip_html % (author, date, message)
281 tooltip_html = tooltip_html % (author, date, message)
282 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
282 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
283 short_id(changeset.raw_id))
283 short_id(changeset.raw_id))
284 uri = link_to(
284 uri = link_to(
285 lnk_format,
285 lnk_format,
286 url('changeset_home', repo_name=repo_name,
286 url('changeset_home', repo_name=repo_name,
287 revision=changeset.raw_id),
287 revision=changeset.raw_id),
288 style=get_color_string(changeset.raw_id),
288 style=get_color_string(changeset.raw_id),
289 class_='tooltip',
289 class_='tooltip',
290 title=tooltip_html
290 title=tooltip_html
291 )
291 )
292
292
293 uri += '\n'
293 uri += '\n'
294 return uri
294 return uri
295 return _url_func
295 return _url_func
296
296
297 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
297 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
298
298
299 def get_changeset_safe(repo, rev):
299 def get_changeset_safe(repo, rev):
300 from vcs.backends.base import BaseRepository
300 from vcs.backends.base import BaseRepository
301 from vcs.exceptions import RepositoryError
301 from vcs.exceptions import RepositoryError
302 if not isinstance(repo, BaseRepository):
302 if not isinstance(repo, BaseRepository):
303 raise Exception('You must pass an Repository '
303 raise Exception('You must pass an Repository '
304 'object as first argument got %s', type(repo))
304 'object as first argument got %s', type(repo))
305
305
306 try:
306 try:
307 cs = repo.get_changeset(rev)
307 cs = repo.get_changeset(rev)
308 except RepositoryError:
308 except RepositoryError:
309 from rhodecode.lib.utils import EmptyChangeset
309 from rhodecode.lib.utils import EmptyChangeset
310 cs = EmptyChangeset()
310 cs = EmptyChangeset()
311 return cs
311 return cs
312
312
313
313
314 def is_following_repo(repo_name, user_id):
314 def is_following_repo(repo_name, user_id):
315 from rhodecode.model.scm import ScmModel
315 from rhodecode.model.scm import ScmModel
316 return ScmModel().is_following_repo(repo_name, user_id)
316 return ScmModel().is_following_repo(repo_name, user_id)
317
317
318 flash = _Flash()
318 flash = _Flash()
319
319
320 #==============================================================================
320 #==============================================================================
321 # SCM FILTERS available via h.
321 # SCM FILTERS available via h.
322 #==============================================================================
322 #==============================================================================
323 from vcs.utils import author_name, author_email
323 from vcs.utils import author_name, author_email
324 from rhodecode.lib import credentials_filter, age as _age
324 from rhodecode.lib import credentials_filter, age as _age
325
325
326 age = lambda x:_age(x)
326 age = lambda x:_age(x)
327 capitalize = lambda x: x.capitalize()
327 capitalize = lambda x: x.capitalize()
328 email = author_email
328 email = author_email
329 email_or_none = lambda x: email(x) if email(x) != x else None
329 email_or_none = lambda x: email(x) if email(x) != x else None
330 person = lambda x: author_name(x)
330 person = lambda x: author_name(x)
331 short_id = lambda x: x[:12]
331 short_id = lambda x: x[:12]
332 hide_credentials = lambda x: ''.join(credentials_filter(x))
332 hide_credentials = lambda x: ''.join(credentials_filter(x))
333
333
334 def bool2icon(value):
334 def bool2icon(value):
335 """Returns True/False values represented as small html image of true/false
335 """Returns True/False values represented as small html image of true/false
336 icons
336 icons
337
337
338 :param value: bool value
338 :param value: bool value
339 """
339 """
340
340
341 if value is True:
341 if value is True:
342 return HTML.tag('img', src=url("/images/icons/accept.png"),
342 return HTML.tag('img', src=url("/images/icons/accept.png"),
343 alt=_('True'))
343 alt=_('True'))
344
344
345 if value is False:
345 if value is False:
346 return HTML.tag('img', src=url("/images/icons/cancel.png"),
346 return HTML.tag('img', src=url("/images/icons/cancel.png"),
347 alt=_('False'))
347 alt=_('False'))
348
348
349 return value
349 return value
350
350
351
351
352 def action_parser(user_log, feed=False):
352 def action_parser(user_log, feed=False):
353 """This helper will action_map the specified string action into translated
353 """This helper will action_map the specified string action into translated
354 fancy names with icons and links
354 fancy names with icons and links
355
355
356 :param user_log: user log instance
356 :param user_log: user log instance
357 :param feed: use output for feeds (no html and fancy icons)
357 :param feed: use output for feeds (no html and fancy icons)
358 """
358 """
359
359
360 action = user_log.action
360 action = user_log.action
361 action_params = ' '
361 action_params = ' '
362
362
363 x = action.split(':')
363 x = action.split(':')
364
364
365 if len(x) > 1:
365 if len(x) > 1:
366 action, action_params = x
366 action, action_params = x
367
367
368 def get_cs_links():
368 def get_cs_links():
369 revs_limit = 5 #display this amount always
369 revs_limit = 5 #display this amount always
370 revs_top_limit = 50 #show upto this amount of changesets hidden
370 revs_top_limit = 50 #show upto this amount of changesets hidden
371 revs = action_params.split(',')
371 revs = action_params.split(',')
372 repo_name = user_log.repository.repo_name
372 repo_name = user_log.repository.repo_name
373
373
374 from rhodecode.model.scm import ScmModel
374 from rhodecode.model.scm import ScmModel
375 repo = user_log.repository.scm_instance
375 repo = user_log.repository.scm_instance
376
376
377 message = lambda rev: get_changeset_safe(repo, rev).message
377 message = lambda rev: get_changeset_safe(repo, rev).message
378 cs_links = []
378 cs_links = []
379 cs_links.append(" " + ', '.join ([link_to(rev,
379 cs_links.append(" " + ', '.join ([link_to(rev,
380 url('changeset_home',
380 url('changeset_home',
381 repo_name=repo_name,
381 repo_name=repo_name,
382 revision=rev), title=tooltip(message(rev)),
382 revision=rev), title=tooltip(message(rev)),
383 class_='tooltip') for rev in revs[:revs_limit] ]))
383 class_='tooltip') for rev in revs[:revs_limit] ]))
384
384
385 compare_view = (' <div class="compare_view tooltip" title="%s">'
385 compare_view = (' <div class="compare_view tooltip" title="%s">'
386 '<a href="%s">%s</a> '
386 '<a href="%s">%s</a> '
387 '</div>' % (_('Show all combined changesets %s->%s' \
387 '</div>' % (_('Show all combined changesets %s->%s' \
388 % (revs[0], revs[-1])),
388 % (revs[0], revs[-1])),
389 url('changeset_home', repo_name=repo_name,
389 url('changeset_home', repo_name=repo_name,
390 revision='%s...%s' % (revs[0], revs[-1])
390 revision='%s...%s' % (revs[0], revs[-1])
391 ),
391 ),
392 _('compare view'))
392 _('compare view'))
393 )
393 )
394
394
395 if len(revs) > revs_limit:
395 if len(revs) > revs_limit:
396 uniq_id = revs[0]
396 uniq_id = revs[0]
397 html_tmpl = ('<span> %s '
397 html_tmpl = ('<span> %s '
398 '<a class="show_more" id="_%s" href="#more">%s</a> '
398 '<a class="show_more" id="_%s" href="#more">%s</a> '
399 '%s</span>')
399 '%s</span>')
400 if not feed:
400 if not feed:
401 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
401 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
402 % (len(revs) - revs_limit),
402 % (len(revs) - revs_limit),
403 _('revisions')))
403 _('revisions')))
404
404
405 if not feed:
405 if not feed:
406 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
406 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
407 else:
407 else:
408 html_tmpl = '<span id="%s"> %s </span>'
408 html_tmpl = '<span id="%s"> %s </span>'
409
409
410 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
410 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
411 url('changeset_home',
411 url('changeset_home',
412 repo_name=repo_name, revision=rev),
412 repo_name=repo_name, revision=rev),
413 title=message(rev), class_='tooltip')
413 title=message(rev), class_='tooltip')
414 for rev in revs[revs_limit:revs_top_limit]])))
414 for rev in revs[revs_limit:revs_top_limit]])))
415 if len(revs) > 1:
415 if len(revs) > 1:
416 cs_links.append(compare_view)
416 cs_links.append(compare_view)
417 return ''.join(cs_links)
417 return ''.join(cs_links)
418
418
419 def get_fork_name():
419 def get_fork_name():
420 repo_name = action_params
420 repo_name = action_params
421 return _('fork name ') + str(link_to(action_params, url('summary_home',
421 return _('fork name ') + str(link_to(action_params, url('summary_home',
422 repo_name=repo_name,)))
422 repo_name=repo_name,)))
423
423
424 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
424 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
425 'user_created_repo':(_('[created] repository'), None),
425 'user_created_repo':(_('[created] repository'), None),
426 'user_forked_repo':(_('[forked] repository'), get_fork_name),
426 'user_forked_repo':(_('[forked] repository'), get_fork_name),
427 'user_updated_repo':(_('[updated] repository'), None),
427 'user_updated_repo':(_('[updated] repository'), None),
428 'admin_deleted_repo':(_('[delete] repository'), None),
428 'admin_deleted_repo':(_('[delete] repository'), None),
429 'admin_created_repo':(_('[created] repository'), None),
429 'admin_created_repo':(_('[created] repository'), None),
430 'admin_forked_repo':(_('[forked] repository'), None),
430 'admin_forked_repo':(_('[forked] repository'), None),
431 'admin_updated_repo':(_('[updated] repository'), None),
431 'admin_updated_repo':(_('[updated] repository'), None),
432 'push':(_('[pushed] into'), get_cs_links),
432 'push':(_('[pushed] into'), get_cs_links),
433 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
433 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
434 'push_remote':(_('[pulled from remote] into'), get_cs_links),
434 'push_remote':(_('[pulled from remote] into'), get_cs_links),
435 'pull':(_('[pulled] from'), None),
435 'pull':(_('[pulled] from'), None),
436 'started_following_repo':(_('[started following] repository'), None),
436 'started_following_repo':(_('[started following] repository'), None),
437 'stopped_following_repo':(_('[stopped following] repository'), None),
437 'stopped_following_repo':(_('[stopped following] repository'), None),
438 }
438 }
439
439
440 action_str = action_map.get(action, action)
440 action_str = action_map.get(action, action)
441 if feed:
441 if feed:
442 action = action_str[0].replace('[', '').replace(']', '')
442 action = action_str[0].replace('[', '').replace(']', '')
443 else:
443 else:
444 action = action_str[0].replace('[', '<span class="journal_highlight">')\
444 action = action_str[0].replace('[', '<span class="journal_highlight">')\
445 .replace(']', '</span>')
445 .replace(']', '</span>')
446
446
447 action_params_func = lambda :""
447 action_params_func = lambda :""
448
448
449 if callable(action_str[1]):
449 if callable(action_str[1]):
450 action_params_func = action_str[1]
450 action_params_func = action_str[1]
451
451
452 return [literal(action), action_params_func]
452 return [literal(action), action_params_func]
453
453
454 def action_parser_icon(user_log):
454 def action_parser_icon(user_log):
455 action = user_log.action
455 action = user_log.action
456 action_params = None
456 action_params = None
457 x = action.split(':')
457 x = action.split(':')
458
458
459 if len(x) > 1:
459 if len(x) > 1:
460 action, action_params = x
460 action, action_params = x
461
461
462 tmpl = """<img src="%s%s" alt="%s"/>"""
462 tmpl = """<img src="%s%s" alt="%s"/>"""
463 map = {'user_deleted_repo':'database_delete.png',
463 map = {'user_deleted_repo':'database_delete.png',
464 'user_created_repo':'database_add.png',
464 'user_created_repo':'database_add.png',
465 'user_forked_repo':'arrow_divide.png',
465 'user_forked_repo':'arrow_divide.png',
466 'user_updated_repo':'database_edit.png',
466 'user_updated_repo':'database_edit.png',
467 'admin_deleted_repo':'database_delete.png',
467 'admin_deleted_repo':'database_delete.png',
468 'admin_created_repo':'database_add.png',
468 'admin_created_repo':'database_add.png',
469 'admin_forked_repo':'arrow_divide.png',
469 'admin_forked_repo':'arrow_divide.png',
470 'admin_updated_repo':'database_edit.png',
470 'admin_updated_repo':'database_edit.png',
471 'push':'script_add.png',
471 'push':'script_add.png',
472 'push_local':'script_edit.png',
472 'push_local':'script_edit.png',
473 'push_remote':'connect.png',
473 'push_remote':'connect.png',
474 'pull':'down_16.png',
474 'pull':'down_16.png',
475 'started_following_repo':'heart_add.png',
475 'started_following_repo':'heart_add.png',
476 'stopped_following_repo':'heart_delete.png',
476 'stopped_following_repo':'heart_delete.png',
477 }
477 }
478 return literal(tmpl % ((url('/images/icons/')),
478 return literal(tmpl % ((url('/images/icons/')),
479 map.get(action, action), action))
479 map.get(action, action), action))
480
480
481
481
482 #==============================================================================
482 #==============================================================================
483 # PERMS
483 # PERMS
484 #==============================================================================
484 #==============================================================================
485 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
485 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
486 HasRepoPermissionAny, HasRepoPermissionAll
486 HasRepoPermissionAny, HasRepoPermissionAll
487
487
488 #==============================================================================
488 #==============================================================================
489 # GRAVATAR URL
489 # GRAVATAR URL
490 #==============================================================================
490 #==============================================================================
491
491
492 def gravatar_url(email_address, size=30):
492 def gravatar_url(email_address, size=30):
493 if not str2bool(config['app_conf'].get('use_gravatar')) or \
493 if not str2bool(config['app_conf'].get('use_gravatar')) or \
494 email_address == 'anonymous@rhodecode.org':
494 email_address == 'anonymous@rhodecode.org':
495 return "/images/user%s.png" % size
495 return "/images/user%s.png" % size
496
496
497 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
497 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
498 default = 'identicon'
498 default = 'identicon'
499 baseurl_nossl = "http://www.gravatar.com/avatar/"
499 baseurl_nossl = "http://www.gravatar.com/avatar/"
500 baseurl_ssl = "https://secure.gravatar.com/avatar/"
500 baseurl_ssl = "https://secure.gravatar.com/avatar/"
501 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
501 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
502
502
503 if isinstance(email_address, unicode):
503 if isinstance(email_address, unicode):
504 #hashlib crashes on unicode items
504 #hashlib crashes on unicode items
505 email_address = email_address.encode('utf8', 'replace')
505 email_address = safe_str(email_address)
506 # construct the url
506 # construct the url
507 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
507 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
508 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
508 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
509
509
510 return gravatar_url
510 return gravatar_url
511
511
512
512
513 #==============================================================================
513 #==============================================================================
514 # REPO PAGER, PAGER FOR REPOSITORY
514 # REPO PAGER, PAGER FOR REPOSITORY
515 #==============================================================================
515 #==============================================================================
516 class RepoPage(Page):
516 class RepoPage(Page):
517
517
518 def __init__(self, collection, page=1, items_per_page=20,
518 def __init__(self, collection, page=1, items_per_page=20,
519 item_count=None, url=None, branch_name=None, **kwargs):
519 item_count=None, url=None, branch_name=None, **kwargs):
520
520
521 """Create a "RepoPage" instance. special pager for paging
521 """Create a "RepoPage" instance. special pager for paging
522 repository
522 repository
523 """
523 """
524 self._url_generator = url
524 self._url_generator = url
525
525
526 # Safe the kwargs class-wide so they can be used in the pager() method
526 # Safe the kwargs class-wide so they can be used in the pager() method
527 self.kwargs = kwargs
527 self.kwargs = kwargs
528
528
529 # Save a reference to the collection
529 # Save a reference to the collection
530 self.original_collection = collection
530 self.original_collection = collection
531
531
532 self.collection = collection
532 self.collection = collection
533
533
534 # The self.page is the number of the current page.
534 # The self.page is the number of the current page.
535 # The first page has the number 1!
535 # The first page has the number 1!
536 try:
536 try:
537 self.page = int(page) # make it int() if we get it as a string
537 self.page = int(page) # make it int() if we get it as a string
538 except (ValueError, TypeError):
538 except (ValueError, TypeError):
539 self.page = 1
539 self.page = 1
540
540
541 self.items_per_page = items_per_page
541 self.items_per_page = items_per_page
542
542
543 # Unless the user tells us how many items the collections has
543 # Unless the user tells us how many items the collections has
544 # we calculate that ourselves.
544 # we calculate that ourselves.
545 if item_count is not None:
545 if item_count is not None:
546 self.item_count = item_count
546 self.item_count = item_count
547 else:
547 else:
548 self.item_count = len(self.collection)
548 self.item_count = len(self.collection)
549
549
550 # Compute the number of the first and last available page
550 # Compute the number of the first and last available page
551 if self.item_count > 0:
551 if self.item_count > 0:
552 self.first_page = 1
552 self.first_page = 1
553 self.page_count = ((self.item_count - 1) / self.items_per_page) + 1
553 self.page_count = ((self.item_count - 1) / self.items_per_page) + 1
554 self.last_page = self.first_page + self.page_count - 1
554 self.last_page = self.first_page + self.page_count - 1
555
555
556 # Make sure that the requested page number is the range of valid pages
556 # Make sure that the requested page number is the range of valid pages
557 if self.page > self.last_page:
557 if self.page > self.last_page:
558 self.page = self.last_page
558 self.page = self.last_page
559 elif self.page < self.first_page:
559 elif self.page < self.first_page:
560 self.page = self.first_page
560 self.page = self.first_page
561
561
562 # Note: the number of items on this page can be less than
562 # Note: the number of items on this page can be less than
563 # items_per_page if the last page is not full
563 # items_per_page if the last page is not full
564 self.first_item = max(0, (self.item_count) - (self.page * items_per_page))
564 self.first_item = max(0, (self.item_count) - (self.page * items_per_page))
565 self.last_item = ((self.item_count - 1) - items_per_page * (self.page - 1))
565 self.last_item = ((self.item_count - 1) - items_per_page * (self.page - 1))
566
566
567 iterator = self.collection.get_changesets(start=self.first_item,
567 iterator = self.collection.get_changesets(start=self.first_item,
568 end=self.last_item,
568 end=self.last_item,
569 reverse=True,
569 reverse=True,
570 branch_name=branch_name)
570 branch_name=branch_name)
571 self.items = list(iterator)
571 self.items = list(iterator)
572
572
573 # Links to previous and next page
573 # Links to previous and next page
574 if self.page > self.first_page:
574 if self.page > self.first_page:
575 self.previous_page = self.page - 1
575 self.previous_page = self.page - 1
576 else:
576 else:
577 self.previous_page = None
577 self.previous_page = None
578
578
579 if self.page < self.last_page:
579 if self.page < self.last_page:
580 self.next_page = self.page + 1
580 self.next_page = self.page + 1
581 else:
581 else:
582 self.next_page = None
582 self.next_page = None
583
583
584 # No items available
584 # No items available
585 else:
585 else:
586 self.first_page = None
586 self.first_page = None
587 self.page_count = 0
587 self.page_count = 0
588 self.last_page = None
588 self.last_page = None
589 self.first_item = None
589 self.first_item = None
590 self.last_item = None
590 self.last_item = None
591 self.previous_page = None
591 self.previous_page = None
592 self.next_page = None
592 self.next_page = None
593 self.items = []
593 self.items = []
594
594
595 # This is a subclass of the 'list' type. Initialise the list now.
595 # This is a subclass of the 'list' type. Initialise the list now.
596 list.__init__(self, self.items)
596 list.__init__(self, self.items)
597
597
598
598
599 def changed_tooltip(nodes):
599 def changed_tooltip(nodes):
600 """
600 """
601 Generates a html string for changed nodes in changeset page.
601 Generates a html string for changed nodes in changeset page.
602 It limits the output to 30 entries
602 It limits the output to 30 entries
603
603
604 :param nodes: LazyNodesGenerator
604 :param nodes: LazyNodesGenerator
605 """
605 """
606 if nodes:
606 if nodes:
607 pref = ': <br/> '
607 pref = ': <br/> '
608 suf = ''
608 suf = ''
609 if len(nodes) > 30:
609 if len(nodes) > 30:
610 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
610 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
611 return literal(pref + '<br/> '.join([safe_unicode(x.path)
611 return literal(pref + '<br/> '.join([safe_unicode(x.path)
612 for x in nodes[:30]]) + suf)
612 for x in nodes[:30]]) + suf)
613 else:
613 else:
614 return ': ' + _('No Files')
614 return ': ' + _('No Files')
615
615
616
616
617
617
618 def repo_link(groups_and_repos):
618 def repo_link(groups_and_repos):
619 """
619 """
620 Makes a breadcrumbs link to repo within a group
620 Makes a breadcrumbs link to repo within a group
621 joins &raquo; on each group to create a fancy link
621 joins &raquo; on each group to create a fancy link
622
622
623 ex::
623 ex::
624 group >> subgroup >> repo
624 group >> subgroup >> repo
625
625
626 :param groups_and_repos:
626 :param groups_and_repos:
627 """
627 """
628 groups, repo_name = groups_and_repos
628 groups, repo_name = groups_and_repos
629
629
630 if not groups:
630 if not groups:
631 return repo_name
631 return repo_name
632 else:
632 else:
633 def make_link(group):
633 def make_link(group):
634 return link_to(group.group_name, url('repos_group',
634 return link_to(group.group_name, url('repos_group',
635 id=group.group_id))
635 id=group.group_id))
636 return literal(' &raquo; '.join(map(make_link, groups)) + \
636 return literal(' &raquo; '.join(map(make_link, groups)) + \
637 " &raquo; " + repo_name)
637 " &raquo; " + repo_name)
638
638
639
639
640 def fancy_file_stats(stats):
640 def fancy_file_stats(stats):
641 """
641 """
642 Displays a fancy two colored bar for number of added/deleted
642 Displays a fancy two colored bar for number of added/deleted
643 lines of code on file
643 lines of code on file
644
644
645 :param stats: two element list of added/deleted lines of code
645 :param stats: two element list of added/deleted lines of code
646 """
646 """
647
647
648 a, d, t = stats[0], stats[1], stats[0] + stats[1]
648 a, d, t = stats[0], stats[1], stats[0] + stats[1]
649 width = 100
649 width = 100
650 unit = float(width) / (t or 1)
650 unit = float(width) / (t or 1)
651
651
652 # needs > 9% of width to be visible or 0 to be hidden
652 # needs > 9% of width to be visible or 0 to be hidden
653 a_p = max(9, unit * a) if a > 0 else 0
653 a_p = max(9, unit * a) if a > 0 else 0
654 d_p = max(9, unit * d) if d > 0 else 0
654 d_p = max(9, unit * d) if d > 0 else 0
655 p_sum = a_p + d_p
655 p_sum = a_p + d_p
656
656
657 if p_sum > width:
657 if p_sum > width:
658 #adjust the percentage to be == 100% since we adjusted to 9
658 #adjust the percentage to be == 100% since we adjusted to 9
659 if a_p > d_p:
659 if a_p > d_p:
660 a_p = a_p - (p_sum - width)
660 a_p = a_p - (p_sum - width)
661 else:
661 else:
662 d_p = d_p - (p_sum - width)
662 d_p = d_p - (p_sum - width)
663
663
664 a_v = a if a > 0 else ''
664 a_v = a if a > 0 else ''
665 d_v = d if d > 0 else ''
665 d_v = d if d > 0 else ''
666
666
667
667
668 def cgen(l_type):
668 def cgen(l_type):
669 mapping = {'tr':'top-right-rounded-corner',
669 mapping = {'tr':'top-right-rounded-corner',
670 'tl':'top-left-rounded-corner',
670 'tl':'top-left-rounded-corner',
671 'br':'bottom-right-rounded-corner',
671 'br':'bottom-right-rounded-corner',
672 'bl':'bottom-left-rounded-corner'}
672 'bl':'bottom-left-rounded-corner'}
673 map_getter = lambda x:mapping[x]
673 map_getter = lambda x:mapping[x]
674
674
675 if l_type == 'a' and d_v:
675 if l_type == 'a' and d_v:
676 #case when added and deleted are present
676 #case when added and deleted are present
677 return ' '.join(map(map_getter, ['tl', 'bl']))
677 return ' '.join(map(map_getter, ['tl', 'bl']))
678
678
679 if l_type == 'a' and not d_v:
679 if l_type == 'a' and not d_v:
680 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
680 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
681
681
682 if l_type == 'd' and a_v:
682 if l_type == 'd' and a_v:
683 return ' '.join(map(map_getter, ['tr', 'br']))
683 return ' '.join(map(map_getter, ['tr', 'br']))
684
684
685 if l_type == 'd' and not a_v:
685 if l_type == 'd' and not a_v:
686 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
686 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
687
687
688
688
689
689
690 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
690 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
691 a_p, a_v)
691 a_p, a_v)
692 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
692 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
693 d_p, d_v)
693 d_p, d_v)
694 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
694 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
@@ -1,275 +1,276 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplegit
3 rhodecode.lib.middleware.simplegit
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleGit middleware for handling git protocol request (push/clone etc.)
6 SimpleGit middleware for handling git protocol request (push/clone etc.)
7 It's implemented with basic auth function
7 It's implemented with basic auth function
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30
30
31 from dulwich import server as dulserver
31 from dulwich import server as dulserver
32
32
33
33
34 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
34 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
35
35
36 def handle(self):
36 def handle(self):
37 write = lambda x: self.proto.write_sideband(1, x)
37 write = lambda x: self.proto.write_sideband(1, x)
38
38
39 graph_walker = dulserver.ProtocolGraphWalker(self,
39 graph_walker = dulserver.ProtocolGraphWalker(self,
40 self.repo.object_store,
40 self.repo.object_store,
41 self.repo.get_peeled)
41 self.repo.get_peeled)
42 objects_iter = self.repo.fetch_objects(
42 objects_iter = self.repo.fetch_objects(
43 graph_walker.determine_wants, graph_walker, self.progress,
43 graph_walker.determine_wants, graph_walker, self.progress,
44 get_tagged=self.get_tagged)
44 get_tagged=self.get_tagged)
45
45
46 # Do they want any objects?
46 # Do they want any objects?
47 if len(objects_iter) == 0:
47 if len(objects_iter) == 0:
48 return
48 return
49
49
50 self.progress("counting objects: %d, done.\n" % len(objects_iter))
50 self.progress("counting objects: %d, done.\n" % len(objects_iter))
51 dulserver.write_pack_data(dulserver.ProtocolFile(None, write),
51 dulserver.write_pack_data(dulserver.ProtocolFile(None, write),
52 objects_iter, len(objects_iter))
52 objects_iter, len(objects_iter))
53 messages = []
53 messages = []
54 messages.append('thank you for using rhodecode')
54 messages.append('thank you for using rhodecode')
55
55
56 for msg in messages:
56 for msg in messages:
57 self.progress(msg + "\n")
57 self.progress(msg + "\n")
58 # we are done
58 # we are done
59 self.proto.write("0000")
59 self.proto.write("0000")
60
60
61 dulserver.DEFAULT_HANDLERS = {
61 dulserver.DEFAULT_HANDLERS = {
62 'git-upload-pack': SimpleGitUploadPackHandler,
62 'git-upload-pack': SimpleGitUploadPackHandler,
63 'git-receive-pack': dulserver.ReceivePackHandler,
63 'git-receive-pack': dulserver.ReceivePackHandler,
64 }
64 }
65
65
66 from dulwich.repo import Repo
66 from dulwich.repo import Repo
67 from dulwich.web import HTTPGitApplication
67 from dulwich.web import HTTPGitApplication
68
68
69 from paste.auth.basic import AuthBasicAuthenticator
69 from paste.auth.basic import AuthBasicAuthenticator
70 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
70 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
71
71
72 from rhodecode.lib import safe_str
72 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
73 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
73 from rhodecode.lib.utils import invalidate_cache, check_repo_fast
74 from rhodecode.lib.utils import invalidate_cache, check_repo_fast
74 from rhodecode.model.user import UserModel
75 from rhodecode.model.user import UserModel
75
76
76 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
77 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
77
78
78 log = logging.getLogger(__name__)
79 log = logging.getLogger(__name__)
79
80
80
81
81 def is_git(environ):
82 def is_git(environ):
82 """Returns True if request's target is git server.
83 """Returns True if request's target is git server.
83 ``HTTP_USER_AGENT`` would then have git client version given.
84 ``HTTP_USER_AGENT`` would then have git client version given.
84
85
85 :param environ:
86 :param environ:
86 """
87 """
87 http_user_agent = environ.get('HTTP_USER_AGENT')
88 http_user_agent = environ.get('HTTP_USER_AGENT')
88 if http_user_agent and http_user_agent.startswith('git'):
89 if http_user_agent and http_user_agent.startswith('git'):
89 return True
90 return True
90 return False
91 return False
91
92
92
93
93 class SimpleGit(object):
94 class SimpleGit(object):
94
95
95 def __init__(self, application, config):
96 def __init__(self, application, config):
96 self.application = application
97 self.application = application
97 self.config = config
98 self.config = config
98 #authenticate this git request using
99 #authenticate this git request using
99 self.authenticate = AuthBasicAuthenticator('', authfunc)
100 self.authenticate = AuthBasicAuthenticator('', authfunc)
100 self.ipaddr = '0.0.0.0'
101 self.ipaddr = '0.0.0.0'
101 self.repo_name = None
102 self.repo_name = None
102 self.username = None
103 self.username = None
103 self.action = None
104 self.action = None
104
105
105 def __call__(self, environ, start_response):
106 def __call__(self, environ, start_response):
106 if not is_git(environ):
107 if not is_git(environ):
107 return self.application(environ, start_response)
108 return self.application(environ, start_response)
108
109
109 proxy_key = 'HTTP_X_REAL_IP'
110 proxy_key = 'HTTP_X_REAL_IP'
110 def_key = 'REMOTE_ADDR'
111 def_key = 'REMOTE_ADDR'
111 self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
112 self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
112 # skip passing error to error controller
113 # skip passing error to error controller
113 environ['pylons.status_code_redirect'] = True
114 environ['pylons.status_code_redirect'] = True
114
115
115 #======================================================================
116 #======================================================================
116 # GET ACTION PULL or PUSH
117 # GET ACTION PULL or PUSH
117 #======================================================================
118 #======================================================================
118 self.action = self.__get_action(environ)
119 self.action = self.__get_action(environ)
119 try:
120 try:
120 #==================================================================
121 #==================================================================
121 # GET REPOSITORY NAME
122 # GET REPOSITORY NAME
122 #==================================================================
123 #==================================================================
123 self.repo_name = self.__get_repository(environ)
124 self.repo_name = self.__get_repository(environ)
124 except:
125 except:
125 return HTTPInternalServerError()(environ, start_response)
126 return HTTPInternalServerError()(environ, start_response)
126
127
127 #======================================================================
128 #======================================================================
128 # CHECK ANONYMOUS PERMISSION
129 # CHECK ANONYMOUS PERMISSION
129 #======================================================================
130 #======================================================================
130 if self.action in ['pull', 'push']:
131 if self.action in ['pull', 'push']:
131 anonymous_user = self.__get_user('default')
132 anonymous_user = self.__get_user('default')
132 self.username = anonymous_user.username
133 self.username = anonymous_user.username
133 anonymous_perm = self.__check_permission(self.action,
134 anonymous_perm = self.__check_permission(self.action,
134 anonymous_user,
135 anonymous_user,
135 self.repo_name)
136 self.repo_name)
136
137
137 if anonymous_perm is not True or anonymous_user.active is False:
138 if anonymous_perm is not True or anonymous_user.active is False:
138 if anonymous_perm is not True:
139 if anonymous_perm is not True:
139 log.debug('Not enough credentials to access this '
140 log.debug('Not enough credentials to access this '
140 'repository as anonymous user')
141 'repository as anonymous user')
141 if anonymous_user.active is False:
142 if anonymous_user.active is False:
142 log.debug('Anonymous access is disabled, running '
143 log.debug('Anonymous access is disabled, running '
143 'authentication')
144 'authentication')
144 #==============================================================
145 #==============================================================
145 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
146 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
146 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
147 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
147 #==============================================================
148 #==============================================================
148
149
149 if not REMOTE_USER(environ):
150 if not REMOTE_USER(environ):
150 self.authenticate.realm = self.config['rhodecode_realm'].\
151 self.authenticate.realm = \
151 encode('utf8', 'replace')
152 safe_str(self.config['rhodecode_realm'])
152 result = self.authenticate(environ)
153 result = self.authenticate(environ)
153 if isinstance(result, str):
154 if isinstance(result, str):
154 AUTH_TYPE.update(environ, 'basic')
155 AUTH_TYPE.update(environ, 'basic')
155 REMOTE_USER.update(environ, result)
156 REMOTE_USER.update(environ, result)
156 else:
157 else:
157 return result.wsgi_application(environ, start_response)
158 return result.wsgi_application(environ, start_response)
158
159
159 #==============================================================
160 #==============================================================
160 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
161 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
161 # BASIC AUTH
162 # BASIC AUTH
162 #==============================================================
163 #==============================================================
163
164
164 if self.action in ['pull', 'push']:
165 if self.action in ['pull', 'push']:
165 username = REMOTE_USER(environ)
166 username = REMOTE_USER(environ)
166 try:
167 try:
167 user = self.__get_user(username)
168 user = self.__get_user(username)
168 self.username = user.username
169 self.username = user.username
169 except:
170 except:
170 log.error(traceback.format_exc())
171 log.error(traceback.format_exc())
171 return HTTPInternalServerError()(environ,
172 return HTTPInternalServerError()(environ,
172 start_response)
173 start_response)
173
174
174 #check permissions for this repository
175 #check permissions for this repository
175 perm = self.__check_permission(self.action, user,
176 perm = self.__check_permission(self.action, user,
176 self.repo_name)
177 self.repo_name)
177 if perm is not True:
178 if perm is not True:
178 return HTTPForbidden()(environ, start_response)
179 return HTTPForbidden()(environ, start_response)
179
180
180 self.extras = {'ip': self.ipaddr,
181 self.extras = {'ip': self.ipaddr,
181 'username': self.username,
182 'username': self.username,
182 'action': self.action,
183 'action': self.action,
183 'repository': self.repo_name}
184 'repository': self.repo_name}
184
185
185 #===================================================================
186 #===================================================================
186 # GIT REQUEST HANDLING
187 # GIT REQUEST HANDLING
187 #===================================================================
188 #===================================================================
188 self.basepath = self.config['base_path']
189 self.basepath = self.config['base_path']
189 self.repo_path = os.path.join(self.basepath, self.repo_name)
190 self.repo_path = os.path.join(self.basepath, self.repo_name)
190 #quick check if that dir exists...
191 #quick check if that dir exists...
191 if check_repo_fast(self.repo_name, self.basepath):
192 if check_repo_fast(self.repo_name, self.basepath):
192 return HTTPNotFound()(environ, start_response)
193 return HTTPNotFound()(environ, start_response)
193 try:
194 try:
194 app = self.__make_app()
195 app = self.__make_app()
195 except:
196 except:
196 log.error(traceback.format_exc())
197 log.error(traceback.format_exc())
197 return HTTPInternalServerError()(environ, start_response)
198 return HTTPInternalServerError()(environ, start_response)
198
199
199 #invalidate cache on push
200 #invalidate cache on push
200 if self.action == 'push':
201 if self.action == 'push':
201 self.__invalidate_cache(self.repo_name)
202 self.__invalidate_cache(self.repo_name)
202
203
203 return app(environ, start_response)
204 return app(environ, start_response)
204
205
205 def __make_app(self):
206 def __make_app(self):
206 _d = {'/' + self.repo_name: Repo(self.repo_path)}
207 _d = {'/' + self.repo_name: Repo(self.repo_path)}
207 backend = dulserver.DictBackend(_d)
208 backend = dulserver.DictBackend(_d)
208 gitserve = HTTPGitApplication(backend)
209 gitserve = HTTPGitApplication(backend)
209
210
210 return gitserve
211 return gitserve
211
212
212 def __check_permission(self, action, user, repo_name):
213 def __check_permission(self, action, user, repo_name):
213 """Checks permissions using action (push/pull) user and repository
214 """Checks permissions using action (push/pull) user and repository
214 name
215 name
215
216
216 :param action: push or pull action
217 :param action: push or pull action
217 :param user: user instance
218 :param user: user instance
218 :param repo_name: repository name
219 :param repo_name: repository name
219 """
220 """
220 if action == 'push':
221 if action == 'push':
221 if not HasPermissionAnyMiddleware('repository.write',
222 if not HasPermissionAnyMiddleware('repository.write',
222 'repository.admin')(user,
223 'repository.admin')(user,
223 repo_name):
224 repo_name):
224 return False
225 return False
225
226
226 else:
227 else:
227 #any other action need at least read permission
228 #any other action need at least read permission
228 if not HasPermissionAnyMiddleware('repository.read',
229 if not HasPermissionAnyMiddleware('repository.read',
229 'repository.write',
230 'repository.write',
230 'repository.admin')(user,
231 'repository.admin')(user,
231 repo_name):
232 repo_name):
232 return False
233 return False
233
234
234 return True
235 return True
235
236
236 def __get_repository(self, environ):
237 def __get_repository(self, environ):
237 """Get's repository name out of PATH_INFO header
238 """Get's repository name out of PATH_INFO header
238
239
239 :param environ: environ where PATH_INFO is stored
240 :param environ: environ where PATH_INFO is stored
240 """
241 """
241 try:
242 try:
242 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
243 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
243 if repo_name.endswith('/'):
244 if repo_name.endswith('/'):
244 repo_name = repo_name.rstrip('/')
245 repo_name = repo_name.rstrip('/')
245 except:
246 except:
246 log.error(traceback.format_exc())
247 log.error(traceback.format_exc())
247 raise
248 raise
248 repo_name = repo_name.split('/')[0]
249 repo_name = repo_name.split('/')[0]
249 return repo_name
250 return repo_name
250
251
251 def __get_user(self, username):
252 def __get_user(self, username):
252 return UserModel().get_by_username(username, cache=True)
253 return UserModel().get_by_username(username, cache=True)
253
254
254 def __get_action(self, environ):
255 def __get_action(self, environ):
255 """Maps git request commands into a pull or push command.
256 """Maps git request commands into a pull or push command.
256
257
257 :param environ:
258 :param environ:
258 """
259 """
259 service = environ['QUERY_STRING'].split('=')
260 service = environ['QUERY_STRING'].split('=')
260 if len(service) > 1:
261 if len(service) > 1:
261 service_cmd = service[1]
262 service_cmd = service[1]
262 mapping = {'git-receive-pack': 'push',
263 mapping = {'git-receive-pack': 'push',
263 'git-upload-pack': 'pull',
264 'git-upload-pack': 'pull',
264 }
265 }
265
266
266 return mapping.get(service_cmd,
267 return mapping.get(service_cmd,
267 service_cmd if service_cmd else 'other')
268 service_cmd if service_cmd else 'other')
268 else:
269 else:
269 return 'other'
270 return 'other'
270
271
271 def __invalidate_cache(self, repo_name):
272 def __invalidate_cache(self, repo_name):
272 """we know that some change was made to repositories and we should
273 """we know that some change was made to repositories and we should
273 invalidate the cache to see the changes right away but only for
274 invalidate the cache to see the changes right away but only for
274 push requests"""
275 push requests"""
275 invalidate_cache('get_repo_cached_%s' % repo_name)
276 invalidate_cache('get_repo_cached_%s' % repo_name)
@@ -1,279 +1,280 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplehg
3 rhodecode.lib.middleware.simplehg
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleHG middleware for handling mercurial protocol request
6 SimpleHG middleware for handling mercurial protocol request
7 (push/clone etc.). It's implemented with basic auth function
7 (push/clone etc.). It's implemented with basic auth function
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30
30
31 from mercurial.error import RepoError
31 from mercurial.error import RepoError
32 from mercurial.hgweb import hgweb
32 from mercurial.hgweb import hgweb
33 from mercurial.hgweb.request import wsgiapplication
33 from mercurial.hgweb.request import wsgiapplication
34
34
35 from paste.auth.basic import AuthBasicAuthenticator
35 from paste.auth.basic import AuthBasicAuthenticator
36 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
36 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
37
37
38 from rhodecode.lib import safe_str
38 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
39 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
39 from rhodecode.lib.utils import make_ui, invalidate_cache, \
40 from rhodecode.lib.utils import make_ui, invalidate_cache, \
40 check_repo_fast, ui_sections
41 check_repo_fast, ui_sections
41 from rhodecode.model.user import UserModel
42 from rhodecode.model.user import UserModel
42
43
43 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
44 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
44
45
45 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
46
47
47
48
48 def is_mercurial(environ):
49 def is_mercurial(environ):
49 """Returns True if request's target is mercurial server - header
50 """Returns True if request's target is mercurial server - header
50 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
51 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
51 """
52 """
52 http_accept = environ.get('HTTP_ACCEPT')
53 http_accept = environ.get('HTTP_ACCEPT')
53 if http_accept and http_accept.startswith('application/mercurial'):
54 if http_accept and http_accept.startswith('application/mercurial'):
54 return True
55 return True
55 return False
56 return False
56
57
57
58
58 class SimpleHg(object):
59 class SimpleHg(object):
59
60
60 def __init__(self, application, config):
61 def __init__(self, application, config):
61 self.application = application
62 self.application = application
62 self.config = config
63 self.config = config
63 #authenticate this mercurial request using authfunc
64 #authenticate this mercurial request using authfunc
64 self.authenticate = AuthBasicAuthenticator('', authfunc)
65 self.authenticate = AuthBasicAuthenticator('', authfunc)
65 self.ipaddr = '0.0.0.0'
66 self.ipaddr = '0.0.0.0'
66 self.repo_name = None
67 self.repo_name = None
67 self.username = None
68 self.username = None
68 self.action = None
69 self.action = None
69
70
70 def __call__(self, environ, start_response):
71 def __call__(self, environ, start_response):
71 if not is_mercurial(environ):
72 if not is_mercurial(environ):
72 return self.application(environ, start_response)
73 return self.application(environ, start_response)
73
74
74 proxy_key = 'HTTP_X_REAL_IP'
75 proxy_key = 'HTTP_X_REAL_IP'
75 def_key = 'REMOTE_ADDR'
76 def_key = 'REMOTE_ADDR'
76 self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
77 self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
77 # skip passing error to error controller
78 # skip passing error to error controller
78 environ['pylons.status_code_redirect'] = True
79 environ['pylons.status_code_redirect'] = True
79
80
80 #======================================================================
81 #======================================================================
81 # GET ACTION PULL or PUSH
82 # GET ACTION PULL or PUSH
82 #======================================================================
83 #======================================================================
83 self.action = self.__get_action(environ)
84 self.action = self.__get_action(environ)
84 try:
85 try:
85 #==================================================================
86 #==================================================================
86 # GET REPOSITORY NAME
87 # GET REPOSITORY NAME
87 #==================================================================
88 #==================================================================
88 self.repo_name = self.__get_repository(environ)
89 self.repo_name = self.__get_repository(environ)
89 except:
90 except:
90 return HTTPInternalServerError()(environ, start_response)
91 return HTTPInternalServerError()(environ, start_response)
91
92
92 #======================================================================
93 #======================================================================
93 # CHECK ANONYMOUS PERMISSION
94 # CHECK ANONYMOUS PERMISSION
94 #======================================================================
95 #======================================================================
95 if self.action in ['pull', 'push']:
96 if self.action in ['pull', 'push']:
96 anonymous_user = self.__get_user('default')
97 anonymous_user = self.__get_user('default')
97 self.username = anonymous_user.username
98 self.username = anonymous_user.username
98 anonymous_perm = self.__check_permission(self.action,
99 anonymous_perm = self.__check_permission(self.action,
99 anonymous_user,
100 anonymous_user,
100 self.repo_name)
101 self.repo_name)
101
102
102 if anonymous_perm is not True or anonymous_user.active is False:
103 if anonymous_perm is not True or anonymous_user.active is False:
103 if anonymous_perm is not True:
104 if anonymous_perm is not True:
104 log.debug('Not enough credentials to access this '
105 log.debug('Not enough credentials to access this '
105 'repository as anonymous user')
106 'repository as anonymous user')
106 if anonymous_user.active is False:
107 if anonymous_user.active is False:
107 log.debug('Anonymous access is disabled, running '
108 log.debug('Anonymous access is disabled, running '
108 'authentication')
109 'authentication')
109 #==============================================================
110 #==============================================================
110 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
111 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
111 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
112 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
112 #==============================================================
113 #==============================================================
113
114
114 if not REMOTE_USER(environ):
115 if not REMOTE_USER(environ):
115 self.authenticate.realm = self.config['rhodecode_realm'].\
116 self.authenticate.realm = \
116 encode('utf8', 'replace')
117 safe_str(self.config['rhodecode_realm'])
117 result = self.authenticate(environ)
118 result = self.authenticate(environ)
118 if isinstance(result, str):
119 if isinstance(result, str):
119 AUTH_TYPE.update(environ, 'basic')
120 AUTH_TYPE.update(environ, 'basic')
120 REMOTE_USER.update(environ, result)
121 REMOTE_USER.update(environ, result)
121 else:
122 else:
122 return result.wsgi_application(environ, start_response)
123 return result.wsgi_application(environ, start_response)
123
124
124 #==============================================================
125 #==============================================================
125 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
126 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
126 # BASIC AUTH
127 # BASIC AUTH
127 #==============================================================
128 #==============================================================
128
129
129 if self.action in ['pull', 'push']:
130 if self.action in ['pull', 'push']:
130 username = REMOTE_USER(environ)
131 username = REMOTE_USER(environ)
131 try:
132 try:
132 user = self.__get_user(username)
133 user = self.__get_user(username)
133 self.username = user.username
134 self.username = user.username
134 except:
135 except:
135 log.error(traceback.format_exc())
136 log.error(traceback.format_exc())
136 return HTTPInternalServerError()(environ,
137 return HTTPInternalServerError()(environ,
137 start_response)
138 start_response)
138
139
139 #check permissions for this repository
140 #check permissions for this repository
140 perm = self.__check_permission(self.action, user,
141 perm = self.__check_permission(self.action, user,
141 self.repo_name)
142 self.repo_name)
142 if perm is not True:
143 if perm is not True:
143 return HTTPForbidden()(environ, start_response)
144 return HTTPForbidden()(environ, start_response)
144
145
145 self.extras = {'ip': self.ipaddr,
146 self.extras = {'ip': self.ipaddr,
146 'username': self.username,
147 'username': self.username,
147 'action': self.action,
148 'action': self.action,
148 'repository': self.repo_name}
149 'repository': self.repo_name}
149
150
150 #======================================================================
151 #======================================================================
151 # MERCURIAL REQUEST HANDLING
152 # MERCURIAL REQUEST HANDLING
152 #======================================================================
153 #======================================================================
153 environ['PATH_INFO'] = '/' # since we wrap into hgweb, reset the path
154 environ['PATH_INFO'] = '/' # since we wrap into hgweb, reset the path
154 self.baseui = make_ui('db')
155 self.baseui = make_ui('db')
155 self.basepath = self.config['base_path']
156 self.basepath = self.config['base_path']
156 self.repo_path = os.path.join(self.basepath, self.repo_name)
157 self.repo_path = os.path.join(self.basepath, self.repo_name)
157
158
158 #quick check if that dir exists...
159 #quick check if that dir exists...
159 if check_repo_fast(self.repo_name, self.basepath):
160 if check_repo_fast(self.repo_name, self.basepath):
160 return HTTPNotFound()(environ, start_response)
161 return HTTPNotFound()(environ, start_response)
161 try:
162 try:
162 app = wsgiapplication(self.__make_app)
163 app = wsgiapplication(self.__make_app)
163 except RepoError, e:
164 except RepoError, e:
164 if str(e).find('not found') != -1:
165 if str(e).find('not found') != -1:
165 return HTTPNotFound()(environ, start_response)
166 return HTTPNotFound()(environ, start_response)
166 except Exception:
167 except Exception:
167 log.error(traceback.format_exc())
168 log.error(traceback.format_exc())
168 return HTTPInternalServerError()(environ, start_response)
169 return HTTPInternalServerError()(environ, start_response)
169
170
170 #invalidate cache on push
171 #invalidate cache on push
171 if self.action == 'push':
172 if self.action == 'push':
172 self.__invalidate_cache(self.repo_name)
173 self.__invalidate_cache(self.repo_name)
173
174
174 return app(environ, start_response)
175 return app(environ, start_response)
175
176
176 def __make_app(self):
177 def __make_app(self):
177 """
178 """
178 Make an wsgi application using hgweb, and inject generated baseui
179 Make an wsgi application using hgweb, and inject generated baseui
179 instance, additionally inject some extras into ui object
180 instance, additionally inject some extras into ui object
180 """
181 """
181 self.__inject_extras(self.baseui, self.extras)
182 self.__inject_extras(self.baseui, self.extras)
182 return hgweb(str(self.repo_path), baseui=self.baseui)
183 return hgweb(str(self.repo_path), baseui=self.baseui)
183
184
184
185
185 def __check_permission(self, action, user, repo_name):
186 def __check_permission(self, action, user, repo_name):
186 """
187 """
187 Checks permissions using action (push/pull) user and repository
188 Checks permissions using action (push/pull) user and repository
188 name
189 name
189
190
190 :param action: push or pull action
191 :param action: push or pull action
191 :param user: user instance
192 :param user: user instance
192 :param repo_name: repository name
193 :param repo_name: repository name
193 """
194 """
194 if action == 'push':
195 if action == 'push':
195 if not HasPermissionAnyMiddleware('repository.write',
196 if not HasPermissionAnyMiddleware('repository.write',
196 'repository.admin')(user,
197 'repository.admin')(user,
197 repo_name):
198 repo_name):
198 return False
199 return False
199
200
200 else:
201 else:
201 #any other action need at least read permission
202 #any other action need at least read permission
202 if not HasPermissionAnyMiddleware('repository.read',
203 if not HasPermissionAnyMiddleware('repository.read',
203 'repository.write',
204 'repository.write',
204 'repository.admin')(user,
205 'repository.admin')(user,
205 repo_name):
206 repo_name):
206 return False
207 return False
207
208
208 return True
209 return True
209
210
210 def __get_repository(self, environ):
211 def __get_repository(self, environ):
211 """
212 """
212 Get's repository name out of PATH_INFO header
213 Get's repository name out of PATH_INFO header
213
214
214 :param environ: environ where PATH_INFO is stored
215 :param environ: environ where PATH_INFO is stored
215 """
216 """
216 try:
217 try:
217 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
218 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
218 if repo_name.endswith('/'):
219 if repo_name.endswith('/'):
219 repo_name = repo_name.rstrip('/')
220 repo_name = repo_name.rstrip('/')
220 except:
221 except:
221 log.error(traceback.format_exc())
222 log.error(traceback.format_exc())
222 raise
223 raise
223
224
224 return repo_name
225 return repo_name
225
226
226 def __get_user(self, username):
227 def __get_user(self, username):
227 return UserModel().get_by_username(username, cache=True)
228 return UserModel().get_by_username(username, cache=True)
228
229
229 def __get_action(self, environ):
230 def __get_action(self, environ):
230 """
231 """
231 Maps mercurial request commands into a clone,pull or push command.
232 Maps mercurial request commands into a clone,pull or push command.
232 This should always return a valid command string
233 This should always return a valid command string
233
234
234 :param environ:
235 :param environ:
235 """
236 """
236 mapping = {'changegroup': 'pull',
237 mapping = {'changegroup': 'pull',
237 'changegroupsubset': 'pull',
238 'changegroupsubset': 'pull',
238 'stream_out': 'pull',
239 'stream_out': 'pull',
239 'listkeys': 'pull',
240 'listkeys': 'pull',
240 'unbundle': 'push',
241 'unbundle': 'push',
241 'pushkey': 'push', }
242 'pushkey': 'push', }
242 for qry in environ['QUERY_STRING'].split('&'):
243 for qry in environ['QUERY_STRING'].split('&'):
243 if qry.startswith('cmd'):
244 if qry.startswith('cmd'):
244 cmd = qry.split('=')[-1]
245 cmd = qry.split('=')[-1]
245 if cmd in mapping:
246 if cmd in mapping:
246 return mapping[cmd]
247 return mapping[cmd]
247 else:
248 else:
248 return 'pull'
249 return 'pull'
249
250
250 def __invalidate_cache(self, repo_name):
251 def __invalidate_cache(self, repo_name):
251 """we know that some change was made to repositories and we should
252 """we know that some change was made to repositories and we should
252 invalidate the cache to see the changes right away but only for
253 invalidate the cache to see the changes right away but only for
253 push requests"""
254 push requests"""
254 invalidate_cache('get_repo_cached_%s' % repo_name)
255 invalidate_cache('get_repo_cached_%s' % repo_name)
255
256
256 def __inject_extras(self, baseui, extras={}):
257 def __inject_extras(self, baseui, extras={}):
257 """
258 """
258 Injects some extra params into baseui instance
259 Injects some extra params into baseui instance
259
260
260 also overwrites global settings with those takes from local hgrc file
261 also overwrites global settings with those takes from local hgrc file
261
262
262 :param baseui: baseui instance
263 :param baseui: baseui instance
263 :param extras: dict with extra params to put into baseui
264 :param extras: dict with extra params to put into baseui
264 """
265 """
265
266
266 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
267 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
267
268
268 #inject some additional parameters that will be available in ui
269 #inject some additional parameters that will be available in ui
269 #for hooks
270 #for hooks
270 for k, v in extras.items():
271 for k, v in extras.items():
271 baseui.setconfig('rhodecode_extras', k, v)
272 baseui.setconfig('rhodecode_extras', k, v)
272
273
273 repoui = make_ui('file', hgrc, False)
274 repoui = make_ui('file', hgrc, False)
274
275
275 if repoui:
276 if repoui:
276 #overwrite our ui instance with the section from hgrc file
277 #overwrite our ui instance with the section from hgrc file
277 for section in ui_sections:
278 for section in ui_sections:
278 for k, v in repoui.configitems(section):
279 for k, v in repoui.configitems(section):
279 baseui.setconfig(section, k, v)
280 baseui.setconfig(section, k, v)
@@ -1,624 +1,628 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Utilities library for RhodeCode
6 Utilities library for RhodeCode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 import paste
30 import paste
31 import beaker
31 import beaker
32 from os.path import dirname as dn, join as jn
32 from os.path import dirname as dn, join as jn
33
33
34 from paste.script.command import Command, BadCommand
34 from paste.script.command import Command, BadCommand
35
35
36 from UserDict import DictMixin
36 from UserDict import DictMixin
37
37
38 from mercurial import ui, config, hg
38 from mercurial import ui, config, hg
39 from mercurial.error import RepoError
39 from mercurial.error import RepoError
40
40
41 from webhelpers.text import collapse, remove_formatting, strip_tags
41 from webhelpers.text import collapse, remove_formatting, strip_tags
42
42
43 from vcs.backends.base import BaseChangeset
43 from vcs.backends.base import BaseChangeset
44 from vcs.utils.lazy import LazyProperty
44 from vcs.utils.lazy import LazyProperty
45
45
46 from rhodecode.model import meta
46 from rhodecode.model import meta
47 from rhodecode.model.caching_query import FromCache
47 from rhodecode.model.caching_query import FromCache
48 from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, Group, \
48 from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, Group, \
49 RhodeCodeSettings
49 RhodeCodeSettings
50 from rhodecode.model.repo import RepoModel
50 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.user import UserModel
51 from rhodecode.model.user import UserModel
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 def recursive_replace(str, replace=' '):
56 def recursive_replace(str, replace=' '):
57 """Recursive replace of given sign to just one instance
57 """Recursive replace of given sign to just one instance
58
58
59 :param str: given string
59 :param str: given string
60 :param replace: char to find and replace multiple instances
60 :param replace: char to find and replace multiple instances
61
61
62 Examples::
62 Examples::
63 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
63 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
64 'Mighty-Mighty-Bo-sstones'
64 'Mighty-Mighty-Bo-sstones'
65 """
65 """
66
66
67 if str.find(replace * 2) == -1:
67 if str.find(replace * 2) == -1:
68 return str
68 return str
69 else:
69 else:
70 str = str.replace(replace * 2, replace)
70 str = str.replace(replace * 2, replace)
71 return recursive_replace(str, replace)
71 return recursive_replace(str, replace)
72
72
73
73
74 def repo_name_slug(value):
74 def repo_name_slug(value):
75 """Return slug of name of repository
75 """Return slug of name of repository
76 This function is called on each creation/modification
76 This function is called on each creation/modification
77 of repository to prevent bad names in repo
77 of repository to prevent bad names in repo
78 """
78 """
79
79
80 slug = remove_formatting(value)
80 slug = remove_formatting(value)
81 slug = strip_tags(slug)
81 slug = strip_tags(slug)
82
82
83 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
83 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
84 slug = slug.replace(c, '-')
84 slug = slug.replace(c, '-')
85 slug = recursive_replace(slug, '-')
85 slug = recursive_replace(slug, '-')
86 slug = collapse(slug, '-')
86 slug = collapse(slug, '-')
87 return slug
87 return slug
88
88
89
89
90 def get_repo_slug(request):
90 def get_repo_slug(request):
91 return request.environ['pylons.routes_dict'].get('repo_name')
91 return request.environ['pylons.routes_dict'].get('repo_name')
92
92
93
93
94 def action_logger(user, action, repo, ipaddr='', sa=None):
94 def action_logger(user, action, repo, ipaddr='', sa=None):
95 """
95 """
96 Action logger for various actions made by users
96 Action logger for various actions made by users
97
97
98 :param user: user that made this action, can be a unique username string or
98 :param user: user that made this action, can be a unique username string or
99 object containing user_id attribute
99 object containing user_id attribute
100 :param action: action to log, should be on of predefined unique actions for
100 :param action: action to log, should be on of predefined unique actions for
101 easy translations
101 easy translations
102 :param repo: string name of repository or object containing repo_id,
102 :param repo: string name of repository or object containing repo_id,
103 that action was made on
103 that action was made on
104 :param ipaddr: optional ip address from what the action was made
104 :param ipaddr: optional ip address from what the action was made
105 :param sa: optional sqlalchemy session
105 :param sa: optional sqlalchemy session
106
106
107 """
107 """
108
108
109 if not sa:
109 if not sa:
110 sa = meta.Session()
110 sa = meta.Session()
111
111
112 try:
112 try:
113 um = UserModel()
113 um = UserModel()
114 if hasattr(user, 'user_id'):
114 if hasattr(user, 'user_id'):
115 user_obj = user
115 user_obj = user
116 elif isinstance(user, basestring):
116 elif isinstance(user, basestring):
117 user_obj = um.get_by_username(user, cache=False)
117 user_obj = um.get_by_username(user, cache=False)
118 else:
118 else:
119 raise Exception('You have to provide user object or username')
119 raise Exception('You have to provide user object or username')
120
120
121 rm = RepoModel()
121 rm = RepoModel()
122 if hasattr(repo, 'repo_id'):
122 if hasattr(repo, 'repo_id'):
123 repo_obj = rm.get(repo.repo_id, cache=False)
123 repo_obj = rm.get(repo.repo_id, cache=False)
124 repo_name = repo_obj.repo_name
124 repo_name = repo_obj.repo_name
125 elif isinstance(repo, basestring):
125 elif isinstance(repo, basestring):
126 repo_name = repo.lstrip('/')
126 repo_name = repo.lstrip('/')
127 repo_obj = rm.get_by_repo_name(repo_name, cache=False)
127 repo_obj = rm.get_by_repo_name(repo_name, cache=False)
128 else:
128 else:
129 raise Exception('You have to provide repository to action logger')
129 raise Exception('You have to provide repository to action logger')
130
130
131 user_log = UserLog()
131 user_log = UserLog()
132 user_log.user_id = user_obj.user_id
132 user_log.user_id = user_obj.user_id
133 user_log.action = action
133 user_log.action = action
134
134
135 user_log.repository_id = repo_obj.repo_id
135 user_log.repository_id = repo_obj.repo_id
136 user_log.repository_name = repo_name
136 user_log.repository_name = repo_name
137
137
138 user_log.action_date = datetime.datetime.now()
138 user_log.action_date = datetime.datetime.now()
139 user_log.user_ip = ipaddr
139 user_log.user_ip = ipaddr
140 sa.add(user_log)
140 sa.add(user_log)
141 sa.commit()
141 sa.commit()
142
142
143 log.info('Adding user %s, action %s on %s', user_obj, action, repo)
143 log.info('Adding user %s, action %s on %s', user_obj, action, repo)
144 except:
144 except:
145 log.error(traceback.format_exc())
145 log.error(traceback.format_exc())
146 sa.rollback()
146 sa.rollback()
147
147
148
148
149 def get_repos(path, recursive=False):
149 def get_repos(path, recursive=False):
150 """
150 """
151 Scans given path for repos and return (name,(type,path)) tuple
151 Scans given path for repos and return (name,(type,path)) tuple
152
152
153 :param path: path to scann for repositories
153 :param path: path to scann for repositories
154 :param recursive: recursive search and return names with subdirs in front
154 :param recursive: recursive search and return names with subdirs in front
155 """
155 """
156 from vcs.utils.helpers import get_scm
156 from vcs.utils.helpers import get_scm
157 from vcs.exceptions import VCSError
157 from vcs.exceptions import VCSError
158
158
159 if path.endswith(os.sep):
159 if path.endswith(os.sep):
160 #remove ending slash for better results
160 #remove ending slash for better results
161 path = path[:-1]
161 path = path[:-1]
162
162
163 def _get_repos(p):
163 def _get_repos(p):
164 if not os.access(p, os.W_OK):
164 if not os.access(p, os.W_OK):
165 return
165 return
166 for dirpath in os.listdir(p):
166 for dirpath in os.listdir(p):
167 if os.path.isfile(os.path.join(p, dirpath)):
167 if os.path.isfile(os.path.join(p, dirpath)):
168 continue
168 continue
169 cur_path = os.path.join(p, dirpath)
169 cur_path = os.path.join(p, dirpath)
170 try:
170 try:
171 scm_info = get_scm(cur_path)
171 scm_info = get_scm(cur_path)
172 yield scm_info[1].split(path)[-1].lstrip(os.sep), scm_info
172 yield scm_info[1].split(path)[-1].lstrip(os.sep), scm_info
173 except VCSError:
173 except VCSError:
174 if not recursive:
174 if not recursive:
175 continue
175 continue
176 #check if this dir containts other repos for recursive scan
176 #check if this dir containts other repos for recursive scan
177 rec_path = os.path.join(p, dirpath)
177 rec_path = os.path.join(p, dirpath)
178 if os.path.isdir(rec_path):
178 if os.path.isdir(rec_path):
179 for inner_scm in _get_repos(rec_path):
179 for inner_scm in _get_repos(rec_path):
180 yield inner_scm
180 yield inner_scm
181
181
182 return _get_repos(path)
182 return _get_repos(path)
183
183
184
184
185 def check_repo_fast(repo_name, base_path):
185 def check_repo_fast(repo_name, base_path):
186 """
186 """
187 Check given path for existence of directory
187 Check given path for existence of directory
188 :param repo_name:
188 :param repo_name:
189 :param base_path:
189 :param base_path:
190
190
191 :return False: if this directory is present
191 :return False: if this directory is present
192 """
192 """
193 if os.path.isdir(os.path.join(base_path, repo_name)):
193 if os.path.isdir(os.path.join(base_path, repo_name)):
194 return False
194 return False
195 return True
195 return True
196
196
197
197
198 def check_repo(repo_name, base_path, verify=True):
198 def check_repo(repo_name, base_path, verify=True):
199
199
200 repo_path = os.path.join(base_path, repo_name)
200 repo_path = os.path.join(base_path, repo_name)
201
201
202 try:
202 try:
203 if not check_repo_fast(repo_name, base_path):
203 if not check_repo_fast(repo_name, base_path):
204 return False
204 return False
205 r = hg.repository(ui.ui(), repo_path)
205 r = hg.repository(ui.ui(), repo_path)
206 if verify:
206 if verify:
207 hg.verify(r)
207 hg.verify(r)
208 #here we hnow that repo exists it was verified
208 #here we hnow that repo exists it was verified
209 log.info('%s repo is already created', repo_name)
209 log.info('%s repo is already created', repo_name)
210 return False
210 return False
211 except RepoError:
211 except RepoError:
212 #it means that there is no valid repo there...
212 #it means that there is no valid repo there...
213 log.info('%s repo is free for creation', repo_name)
213 log.info('%s repo is free for creation', repo_name)
214 return True
214 return True
215
215
216
216
217 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
217 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
218 while True:
218 while True:
219 ok = raw_input(prompt)
219 ok = raw_input(prompt)
220 if ok in ('y', 'ye', 'yes'):
220 if ok in ('y', 'ye', 'yes'):
221 return True
221 return True
222 if ok in ('n', 'no', 'nop', 'nope'):
222 if ok in ('n', 'no', 'nop', 'nope'):
223 return False
223 return False
224 retries = retries - 1
224 retries = retries - 1
225 if retries < 0:
225 if retries < 0:
226 raise IOError
226 raise IOError
227 print complaint
227 print complaint
228
228
229 #propagated from mercurial documentation
229 #propagated from mercurial documentation
230 ui_sections = ['alias', 'auth',
230 ui_sections = ['alias', 'auth',
231 'decode/encode', 'defaults',
231 'decode/encode', 'defaults',
232 'diff', 'email',
232 'diff', 'email',
233 'extensions', 'format',
233 'extensions', 'format',
234 'merge-patterns', 'merge-tools',
234 'merge-patterns', 'merge-tools',
235 'hooks', 'http_proxy',
235 'hooks', 'http_proxy',
236 'smtp', 'patch',
236 'smtp', 'patch',
237 'paths', 'profiling',
237 'paths', 'profiling',
238 'server', 'trusted',
238 'server', 'trusted',
239 'ui', 'web', ]
239 'ui', 'web', ]
240
240
241
241
242 def make_ui(read_from='file', path=None, checkpaths=True):
242 def make_ui(read_from='file', path=None, checkpaths=True):
243 """A function that will read python rc files or database
243 """A function that will read python rc files or database
244 and make an mercurial ui object from read options
244 and make an mercurial ui object from read options
245
245
246 :param path: path to mercurial config file
246 :param path: path to mercurial config file
247 :param checkpaths: check the path
247 :param checkpaths: check the path
248 :param read_from: read from 'file' or 'db'
248 :param read_from: read from 'file' or 'db'
249 """
249 """
250
250
251 baseui = ui.ui()
251 baseui = ui.ui()
252
252
253 #clean the baseui object
253 #clean the baseui object
254 baseui._ocfg = config.config()
254 baseui._ocfg = config.config()
255 baseui._ucfg = config.config()
255 baseui._ucfg = config.config()
256 baseui._tcfg = config.config()
256 baseui._tcfg = config.config()
257
257
258 if read_from == 'file':
258 if read_from == 'file':
259 if not os.path.isfile(path):
259 if not os.path.isfile(path):
260 log.warning('Unable to read config file %s' % path)
260 log.warning('Unable to read config file %s' % path)
261 return False
261 return False
262 log.debug('reading hgrc from %s', path)
262 log.debug('reading hgrc from %s', path)
263 cfg = config.config()
263 cfg = config.config()
264 cfg.read(path)
264 cfg.read(path)
265 for section in ui_sections:
265 for section in ui_sections:
266 for k, v in cfg.items(section):
266 for k, v in cfg.items(section):
267 log.debug('settings ui from file[%s]%s:%s', section, k, v)
267 log.debug('settings ui from file[%s]%s:%s', section, k, v)
268 baseui.setconfig(section, k, v)
268 baseui.setconfig(section, k, v)
269
269
270 elif read_from == 'db':
270 elif read_from == 'db':
271 sa = meta.Session()
271 sa = meta.Session()
272 ret = sa.query(RhodeCodeUi)\
272 ret = sa.query(RhodeCodeUi)\
273 .options(FromCache("sql_cache_short",
273 .options(FromCache("sql_cache_short",
274 "get_hg_ui_settings")).all()
274 "get_hg_ui_settings")).all()
275
275
276 hg_ui = ret
276 hg_ui = ret
277 for ui_ in hg_ui:
277 for ui_ in hg_ui:
278 if ui_.ui_active:
278 if ui_.ui_active:
279 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
279 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
280 ui_.ui_key, ui_.ui_value)
280 ui_.ui_key, ui_.ui_value)
281 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
281 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
282
282
283 meta.Session.remove()
283 meta.Session.remove()
284 return baseui
284 return baseui
285
285
286
286
287 def set_rhodecode_config(config):
287 def set_rhodecode_config(config):
288 """Updates pylons config with new settings from database
288 """Updates pylons config with new settings from database
289
289
290 :param config:
290 :param config:
291 """
291 """
292 hgsettings = RhodeCodeSettings.get_app_settings()
292 hgsettings = RhodeCodeSettings.get_app_settings()
293
293
294 for k, v in hgsettings.items():
294 for k, v in hgsettings.items():
295 config[k] = v
295 config[k] = v
296
296
297
297
298 def invalidate_cache(cache_key, *args):
298 def invalidate_cache(cache_key, *args):
299 """Puts cache invalidation task into db for
299 """Puts cache invalidation task into db for
300 further global cache invalidation
300 further global cache invalidation
301 """
301 """
302
302
303 from rhodecode.model.scm import ScmModel
303 from rhodecode.model.scm import ScmModel
304
304
305 if cache_key.startswith('get_repo_cached_'):
305 if cache_key.startswith('get_repo_cached_'):
306 name = cache_key.split('get_repo_cached_')[-1]
306 name = cache_key.split('get_repo_cached_')[-1]
307 ScmModel().mark_for_invalidation(name)
307 ScmModel().mark_for_invalidation(name)
308
308
309
309
310 class EmptyChangeset(BaseChangeset):
310 class EmptyChangeset(BaseChangeset):
311 """
311 """
312 An dummy empty changeset. It's possible to pass hash when creating
312 An dummy empty changeset. It's possible to pass hash when creating
313 an EmptyChangeset
313 an EmptyChangeset
314 """
314 """
315
315
316 def __init__(self, cs='0' * 40, repo=None):
316 def __init__(self, cs='0' * 40, repo=None):
317 self._empty_cs = cs
317 self._empty_cs = cs
318 self.revision = -1
318 self.revision = -1
319 self.message = ''
319 self.message = ''
320 self.author = ''
320 self.author = ''
321 self.date = ''
321 self.date = ''
322 self.repository = repo
322 self.repository = repo
323
323
324 @LazyProperty
324 @LazyProperty
325 def raw_id(self):
325 def raw_id(self):
326 """Returns raw string identifying this changeset, useful for web
326 """Returns raw string identifying this changeset, useful for web
327 representation.
327 representation.
328 """
328 """
329
329
330 return self._empty_cs
330 return self._empty_cs
331
331
332 @LazyProperty
332 @LazyProperty
333 def short_id(self):
333 def short_id(self):
334 return self.raw_id[:12]
334 return self.raw_id[:12]
335
335
336 def get_file_changeset(self, path):
336 def get_file_changeset(self, path):
337 return self
337 return self
338
338
339 def get_file_content(self, path):
339 def get_file_content(self, path):
340 return u''
340 return u''
341
341
342 def get_file_size(self, path):
342 def get_file_size(self, path):
343 return 0
343 return 0
344
344
345
345
346 def map_groups(groups):
346 def map_groups(groups):
347 """Checks for groups existence, and creates groups structures.
347 """Checks for groups existence, and creates groups structures.
348 It returns last group in structure
348 It returns last group in structure
349
349
350 :param groups: list of groups structure
350 :param groups: list of groups structure
351 """
351 """
352 sa = meta.Session()
352 sa = meta.Session()
353
353
354 parent = None
354 parent = None
355 group = None
355 group = None
356 for lvl, group_name in enumerate(groups[:-1]):
356 for lvl, group_name in enumerate(groups[:-1]):
357 group = sa.query(Group).filter(Group.group_name == group_name).scalar()
357 group = sa.query(Group).filter(Group.group_name == group_name).scalar()
358
358
359 if group is None:
359 if group is None:
360 group = Group(group_name, parent)
360 group = Group(group_name, parent)
361 sa.add(group)
361 sa.add(group)
362 sa.commit()
362 sa.commit()
363
363
364 parent = group
364 parent = group
365
365
366 return group
366 return group
367
367
368
368
369 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
369 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
370 """maps all repos given in initial_repo_list, non existing repositories
370 """maps all repos given in initial_repo_list, non existing repositories
371 are created, if remove_obsolete is True it also check for db entries
371 are created, if remove_obsolete is True it also check for db entries
372 that are not in initial_repo_list and removes them.
372 that are not in initial_repo_list and removes them.
373
373
374 :param initial_repo_list: list of repositories found by scanning methods
374 :param initial_repo_list: list of repositories found by scanning methods
375 :param remove_obsolete: check for obsolete entries in database
375 :param remove_obsolete: check for obsolete entries in database
376 """
376 """
377
377
378 sa = meta.Session()
378 sa = meta.Session()
379 rm = RepoModel()
379 rm = RepoModel()
380 user = sa.query(User).filter(User.admin == True).first()
380 user = sa.query(User).filter(User.admin == True).first()
381 added = []
381 added = []
382 for name, repo in initial_repo_list.items():
382 for name, repo in initial_repo_list.items():
383 group = map_groups(name.split('/'))
383 group = map_groups(name.split('/'))
384 if not rm.get_by_repo_name(name, cache=False):
384 if not rm.get_by_repo_name(name, cache=False):
385 log.info('repository %s not found creating default', name)
385 log.info('repository %s not found creating default', name)
386 added.append(name)
386 added.append(name)
387 form_data = {
387 form_data = {
388 'repo_name': name,
388 'repo_name': name,
389 'repo_name_full': name,
389 'repo_name_full': name,
390 'repo_type': repo.alias,
390 'repo_type': repo.alias,
391 'description': repo.description \
391 'description': repo.description \
392 if repo.description != 'unknown' else \
392 if repo.description != 'unknown' else \
393 '%s repository' % name,
393 '%s repository' % name,
394 'private': False,
394 'private': False,
395 'group_id': getattr(group, 'group_id', None)
395 'group_id': getattr(group, 'group_id', None)
396 }
396 }
397 rm.create(form_data, user, just_db=True)
397 rm.create(form_data, user, just_db=True)
398
398
399 removed = []
399 removed = []
400 if remove_obsolete:
400 if remove_obsolete:
401 #remove from database those repositories that are not in the filesystem
401 #remove from database those repositories that are not in the filesystem
402 for repo in sa.query(Repository).all():
402 for repo in sa.query(Repository).all():
403 if repo.repo_name not in initial_repo_list.keys():
403 if repo.repo_name not in initial_repo_list.keys():
404 removed.append(repo.repo_name)
404 removed.append(repo.repo_name)
405 sa.delete(repo)
405 sa.delete(repo)
406 sa.commit()
406 sa.commit()
407
407
408 return added, removed
408 return added, removed
409
409
410 #set cache regions for beaker so celery can utilise it
410 #set cache regions for beaker so celery can utilise it
411 def add_cache(settings):
411 def add_cache(settings):
412 cache_settings = {'regions': None}
412 cache_settings = {'regions': None}
413 for key in settings.keys():
413 for key in settings.keys():
414 for prefix in ['beaker.cache.', 'cache.']:
414 for prefix in ['beaker.cache.', 'cache.']:
415 if key.startswith(prefix):
415 if key.startswith(prefix):
416 name = key.split(prefix)[1].strip()
416 name = key.split(prefix)[1].strip()
417 cache_settings[name] = settings[key].strip()
417 cache_settings[name] = settings[key].strip()
418 if cache_settings['regions']:
418 if cache_settings['regions']:
419 for region in cache_settings['regions'].split(','):
419 for region in cache_settings['regions'].split(','):
420 region = region.strip()
420 region = region.strip()
421 region_settings = {}
421 region_settings = {}
422 for key, value in cache_settings.items():
422 for key, value in cache_settings.items():
423 if key.startswith(region):
423 if key.startswith(region):
424 region_settings[key.split('.')[1]] = value
424 region_settings[key.split('.')[1]] = value
425 region_settings['expire'] = int(region_settings.get('expire',
425 region_settings['expire'] = int(region_settings.get('expire',
426 60))
426 60))
427 region_settings.setdefault('lock_dir',
427 region_settings.setdefault('lock_dir',
428 cache_settings.get('lock_dir'))
428 cache_settings.get('lock_dir'))
429 region_settings.setdefault('data_dir',
429 region_settings.setdefault('data_dir',
430 cache_settings.get('data_dir'))
430 cache_settings.get('data_dir'))
431
431
432 if 'type' not in region_settings:
432 if 'type' not in region_settings:
433 region_settings['type'] = cache_settings.get('type',
433 region_settings['type'] = cache_settings.get('type',
434 'memory')
434 'memory')
435 beaker.cache.cache_regions[region] = region_settings
435 beaker.cache.cache_regions[region] = region_settings
436
436
437
437
438 def get_current_revision():
438 def get_current_revision():
439 """Returns tuple of (number, id) from repository containing this package
439 """Returns tuple of (number, id) from repository containing this package
440 or None if repository could not be found.
440 or None if repository could not be found.
441 """
441 """
442
442
443 try:
443 try:
444 from vcs import get_repo
444 from vcs import get_repo
445 from vcs.utils.helpers import get_scm
445 from vcs.utils.helpers import get_scm
446 from vcs.exceptions import RepositoryError, VCSError
446 from vcs.exceptions import RepositoryError, VCSError
447 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
447 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
448 scm = get_scm(repopath)[0]
448 scm = get_scm(repopath)[0]
449 repo = get_repo(path=repopath, alias=scm)
449 repo = get_repo(path=repopath, alias=scm)
450 tip = repo.get_changeset()
450 tip = repo.get_changeset()
451 return (tip.revision, tip.short_id)
451 return (tip.revision, tip.short_id)
452 except (ImportError, RepositoryError, VCSError), err:
452 except (ImportError, RepositoryError, VCSError), err:
453 logging.debug("Cannot retrieve rhodecode's revision. Original error "
453 logging.debug("Cannot retrieve rhodecode's revision. Original error "
454 "was: %s" % err)
454 "was: %s" % err)
455 return None
455 return None
456
456
457
457
458 #==============================================================================
458 #==============================================================================
459 # TEST FUNCTIONS AND CREATORS
459 # TEST FUNCTIONS AND CREATORS
460 #==============================================================================
460 #==============================================================================
461 def create_test_index(repo_location, full_index):
461 def create_test_index(repo_location, config, full_index):
462 """Makes default test index
462 """
463 :param repo_location:
463 Makes default test index
464
465 :param config: test config
464 :param full_index:
466 :param full_index:
465 """
467 """
468
466 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
469 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
467 from rhodecode.lib.pidlock import DaemonLock, LockHeld
470 from rhodecode.lib.pidlock import DaemonLock, LockHeld
468 import shutil
471
472 repo_location = repo_location
469
473
470 index_location = os.path.join(repo_location, 'index')
474 index_location = os.path.join(config['app_conf']['index_dir'], 'index')
471 if os.path.exists(index_location):
475 if not os.path.exists(index_location):
472 shutil.rmtree(index_location)
476 os.makedirs(index_location)
473
477
474 try:
478 try:
475 l = DaemonLock(file=jn(dn(index_location), 'make_index.lock'))
479 l = DaemonLock(file=jn(dn(index_location), 'make_index.lock'))
476 WhooshIndexingDaemon(index_location=index_location,
480 WhooshIndexingDaemon(index_location=index_location,
477 repo_location=repo_location)\
481 repo_location=repo_location)\
478 .run(full_index=full_index)
482 .run(full_index=full_index)
479 l.release()
483 l.release()
480 except LockHeld:
484 except LockHeld:
481 pass
485 pass
482
486
483
487
484 def create_test_env(repos_test_path, config):
488 def create_test_env(repos_test_path, config):
485 """Makes a fresh database and
489 """Makes a fresh database and
486 install test repository into tmp dir
490 install test repository into tmp dir
487 """
491 """
488 from rhodecode.lib.db_manage import DbManage
492 from rhodecode.lib.db_manage import DbManage
489 from rhodecode.tests import HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, \
493 from rhodecode.tests import HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, \
490 HG_FORK, GIT_FORK, TESTS_TMP_PATH
494 HG_FORK, GIT_FORK, TESTS_TMP_PATH
491 import tarfile
495 import tarfile
492 import shutil
496 import shutil
493 from os.path import dirname as dn, join as jn, abspath
497 from os.path import dirname as dn, join as jn, abspath
494
498
495 log = logging.getLogger('TestEnvCreator')
499 log = logging.getLogger('TestEnvCreator')
496 # create logger
500 # create logger
497 log.setLevel(logging.DEBUG)
501 log.setLevel(logging.DEBUG)
498 log.propagate = True
502 log.propagate = True
499 # create console handler and set level to debug
503 # create console handler and set level to debug
500 ch = logging.StreamHandler()
504 ch = logging.StreamHandler()
501 ch.setLevel(logging.DEBUG)
505 ch.setLevel(logging.DEBUG)
502
506
503 # create formatter
507 # create formatter
504 formatter = logging.Formatter("%(asctime)s - %(name)s -"
508 formatter = logging.Formatter("%(asctime)s - %(name)s -"
505 " %(levelname)s - %(message)s")
509 " %(levelname)s - %(message)s")
506
510
507 # add formatter to ch
511 # add formatter to ch
508 ch.setFormatter(formatter)
512 ch.setFormatter(formatter)
509
513
510 # add ch to logger
514 # add ch to logger
511 log.addHandler(ch)
515 log.addHandler(ch)
512
516
513 #PART ONE create db
517 #PART ONE create db
514 dbconf = config['sqlalchemy.db1.url']
518 dbconf = config['sqlalchemy.db1.url']
515 log.debug('making test db %s', dbconf)
519 log.debug('making test db %s', dbconf)
516
520
517 # create test dir if it doesn't exist
521 # create test dir if it doesn't exist
518 if not os.path.isdir(repos_test_path):
522 if not os.path.isdir(repos_test_path):
519 log.debug('Creating testdir %s' % repos_test_path)
523 log.debug('Creating testdir %s' % repos_test_path)
520 os.makedirs(repos_test_path)
524 os.makedirs(repos_test_path)
521
525
522 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
526 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
523 tests=True)
527 tests=True)
524 dbmanage.create_tables(override=True)
528 dbmanage.create_tables(override=True)
525 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
529 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
526 dbmanage.create_default_user()
530 dbmanage.create_default_user()
527 dbmanage.admin_prompt()
531 dbmanage.admin_prompt()
528 dbmanage.create_permissions()
532 dbmanage.create_permissions()
529 dbmanage.populate_default_permissions()
533 dbmanage.populate_default_permissions()
530
534
531 #PART TWO make test repo
535 #PART TWO make test repo
532 log.debug('making test vcs repositories')
536 log.debug('making test vcs repositories')
533
537
534 #remove old one from previos tests
538 #remove old one from previos tests
535 for r in [HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, HG_FORK, GIT_FORK]:
539 for r in [HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, HG_FORK, GIT_FORK]:
536
540
537 if os.path.isdir(jn(TESTS_TMP_PATH, r)):
541 if os.path.isdir(jn(TESTS_TMP_PATH, r)):
538 log.debug('removing %s', r)
542 log.debug('removing %s', r)
539 shutil.rmtree(jn(TESTS_TMP_PATH, r))
543 shutil.rmtree(jn(TESTS_TMP_PATH, r))
540
544
541 idx_path = config['app_conf']['index_dir']
545 idx_path = config['app_conf']['index_dir']
542 data_path = config['app_conf']['cache_dir']
546 data_path = config['app_conf']['cache_dir']
543
547
544 #clean index and data
548 #clean index and data
545 if idx_path and os.path.exists(idx_path):
549 if idx_path and os.path.exists(idx_path):
546 log.debug('remove %s' % idx_path)
550 log.debug('remove %s' % idx_path)
547 shutil.rmtree(idx_path)
551 shutil.rmtree(idx_path)
548
552
549 if data_path and os.path.exists(data_path):
553 if data_path and os.path.exists(data_path):
550 log.debug('remove %s' % data_path)
554 log.debug('remove %s' % data_path)
551 shutil.rmtree(data_path)
555 shutil.rmtree(data_path)
552
556
553 #CREATE DEFAULT HG REPOSITORY
557 #CREATE DEFAULT HG REPOSITORY
554 cur_dir = dn(dn(abspath(__file__)))
558 cur_dir = dn(dn(abspath(__file__)))
555 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
559 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
556 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
560 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
557 tar.close()
561 tar.close()
558
562
559
563
560 #==============================================================================
564 #==============================================================================
561 # PASTER COMMANDS
565 # PASTER COMMANDS
562 #==============================================================================
566 #==============================================================================
563 class BasePasterCommand(Command):
567 class BasePasterCommand(Command):
564 """
568 """
565 Abstract Base Class for paster commands.
569 Abstract Base Class for paster commands.
566
570
567 The celery commands are somewhat aggressive about loading
571 The celery commands are somewhat aggressive about loading
568 celery.conf, and since our module sets the `CELERY_LOADER`
572 celery.conf, and since our module sets the `CELERY_LOADER`
569 environment variable to our loader, we have to bootstrap a bit and
573 environment variable to our loader, we have to bootstrap a bit and
570 make sure we've had a chance to load the pylons config off of the
574 make sure we've had a chance to load the pylons config off of the
571 command line, otherwise everything fails.
575 command line, otherwise everything fails.
572 """
576 """
573 min_args = 1
577 min_args = 1
574 min_args_error = "Please provide a paster config file as an argument."
578 min_args_error = "Please provide a paster config file as an argument."
575 takes_config_file = 1
579 takes_config_file = 1
576 requires_config_file = True
580 requires_config_file = True
577
581
578 def notify_msg(self, msg, log=False):
582 def notify_msg(self, msg, log=False):
579 """Make a notification to user, additionally if logger is passed
583 """Make a notification to user, additionally if logger is passed
580 it logs this action using given logger
584 it logs this action using given logger
581
585
582 :param msg: message that will be printed to user
586 :param msg: message that will be printed to user
583 :param log: logging instance, to use to additionally log this message
587 :param log: logging instance, to use to additionally log this message
584
588
585 """
589 """
586 if log and isinstance(log, logging):
590 if log and isinstance(log, logging):
587 log(msg)
591 log(msg)
588
592
589 def run(self, args):
593 def run(self, args):
590 """
594 """
591 Overrides Command.run
595 Overrides Command.run
592
596
593 Checks for a config file argument and loads it.
597 Checks for a config file argument and loads it.
594 """
598 """
595 if len(args) < self.min_args:
599 if len(args) < self.min_args:
596 raise BadCommand(
600 raise BadCommand(
597 self.min_args_error % {'min_args': self.min_args,
601 self.min_args_error % {'min_args': self.min_args,
598 'actual_args': len(args)})
602 'actual_args': len(args)})
599
603
600 # Decrement because we're going to lob off the first argument.
604 # Decrement because we're going to lob off the first argument.
601 # @@ This is hacky
605 # @@ This is hacky
602 self.min_args -= 1
606 self.min_args -= 1
603 self.bootstrap_config(args[0])
607 self.bootstrap_config(args[0])
604 self.update_parser()
608 self.update_parser()
605 return super(BasePasterCommand, self).run(args[1:])
609 return super(BasePasterCommand, self).run(args[1:])
606
610
607 def update_parser(self):
611 def update_parser(self):
608 """
612 """
609 Abstract method. Allows for the class's parser to be updated
613 Abstract method. Allows for the class's parser to be updated
610 before the superclass's `run` method is called. Necessary to
614 before the superclass's `run` method is called. Necessary to
611 allow options/arguments to be passed through to the underlying
615 allow options/arguments to be passed through to the underlying
612 celery command.
616 celery command.
613 """
617 """
614 raise NotImplementedError("Abstract Method.")
618 raise NotImplementedError("Abstract Method.")
615
619
616 def bootstrap_config(self, conf):
620 def bootstrap_config(self, conf):
617 """
621 """
618 Loads the pylons configuration.
622 Loads the pylons configuration.
619 """
623 """
620 from pylons import config as pylonsconfig
624 from pylons import config as pylonsconfig
621
625
622 path_to_ini_file = os.path.realpath(conf)
626 path_to_ini_file = os.path.realpath(conf)
623 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
627 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
624 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
628 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
@@ -1,790 +1,795 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) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 from datetime import date
30 from datetime import date
31
31
32 from sqlalchemy import *
32 from sqlalchemy import *
33 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.exc import DatabaseError
34 from sqlalchemy.orm import relationship, backref, joinedload, class_mapper
34 from sqlalchemy.orm import relationship, backref, joinedload, class_mapper
35 from sqlalchemy.orm.interfaces import MapperExtension
35 from sqlalchemy.orm.interfaces import MapperExtension
36
36
37 from beaker.cache import cache_region, region_invalidate
37 from beaker.cache import cache_region, region_invalidate
38
38
39 from vcs import get_backend
39 from vcs import get_backend
40 from vcs.utils.helpers import get_scm
40 from vcs.utils.helpers import get_scm
41 from vcs.exceptions import RepositoryError, VCSError
41 from vcs.exceptions import RepositoryError, VCSError
42 from vcs.utils.lazy import LazyProperty
42 from vcs.utils.lazy import LazyProperty
43 from vcs.nodes import FileNode
43 from vcs.nodes import FileNode
44
44
45 from rhodecode.lib import str2bool, json
45 from rhodecode.lib import str2bool, json, safe_str
46 from rhodecode.model.meta import Base, Session
46 from rhodecode.model.meta import Base, Session
47 from rhodecode.model.caching_query import FromCache
47 from rhodecode.model.caching_query import FromCache
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51 #==============================================================================
51 #==============================================================================
52 # BASE CLASSES
52 # BASE CLASSES
53 #==============================================================================
53 #==============================================================================
54
54
55 class ModelSerializer(json.JSONEncoder):
55 class ModelSerializer(json.JSONEncoder):
56 """
56 """
57 Simple Serializer for JSON,
57 Simple Serializer for JSON,
58
58
59 usage::
59 usage::
60
60
61 to make object customized for serialization implement a __json__
61 to make object customized for serialization implement a __json__
62 method that will return a dict for serialization into json
62 method that will return a dict for serialization into json
63
63
64 example::
64 example::
65
65
66 class Task(object):
66 class Task(object):
67
67
68 def __init__(self, name, value):
68 def __init__(self, name, value):
69 self.name = name
69 self.name = name
70 self.value = value
70 self.value = value
71
71
72 def __json__(self):
72 def __json__(self):
73 return dict(name=self.name,
73 return dict(name=self.name,
74 value=self.value)
74 value=self.value)
75
75
76 """
76 """
77
77
78 def default(self, obj):
78 def default(self, obj):
79
79
80 if hasattr(obj, '__json__'):
80 if hasattr(obj, '__json__'):
81 return obj.__json__()
81 return obj.__json__()
82 else:
82 else:
83 return json.JSONEncoder.default(self, obj)
83 return json.JSONEncoder.default(self, obj)
84
84
85 class BaseModel(object):
85 class BaseModel(object):
86 """Base Model for all classess
86 """Base Model for all classess
87
87
88 """
88 """
89
89
90 @classmethod
90 @classmethod
91 def _get_keys(cls):
91 def _get_keys(cls):
92 """return column names for this model """
92 """return column names for this model """
93 return class_mapper(cls).c.keys()
93 return class_mapper(cls).c.keys()
94
94
95 def get_dict(self):
95 def get_dict(self):
96 """return dict with keys and values corresponding
96 """return dict with keys and values corresponding
97 to this model data """
97 to this model data """
98
98
99 d = {}
99 d = {}
100 for k in self._get_keys():
100 for k in self._get_keys():
101 d[k] = getattr(self, k)
101 d[k] = getattr(self, k)
102 return d
102 return d
103
103
104 def get_appstruct(self):
104 def get_appstruct(self):
105 """return list with keys and values tupples corresponding
105 """return list with keys and values tupples corresponding
106 to this model data """
106 to this model data """
107
107
108 l = []
108 l = []
109 for k in self._get_keys():
109 for k in self._get_keys():
110 l.append((k, getattr(self, k),))
110 l.append((k, getattr(self, k),))
111 return l
111 return l
112
112
113 def populate_obj(self, populate_dict):
113 def populate_obj(self, populate_dict):
114 """populate model with data from given populate_dict"""
114 """populate model with data from given populate_dict"""
115
115
116 for k in self._get_keys():
116 for k in self._get_keys():
117 if k in populate_dict:
117 if k in populate_dict:
118 setattr(self, k, populate_dict[k])
118 setattr(self, k, populate_dict[k])
119
119
120 @classmethod
120 @classmethod
121 def query(cls):
121 def query(cls):
122 return Session.query(cls)
122 return Session.query(cls)
123
123
124 @classmethod
124 @classmethod
125 def get(cls, id_):
125 def get(cls, id_):
126 return Session.query(cls).get(id_)
126 return Session.query(cls).get(id_)
127
127
128
128
129 class RhodeCodeSettings(Base, BaseModel):
129 class RhodeCodeSettings(Base, BaseModel):
130 __tablename__ = 'rhodecode_settings'
130 __tablename__ = 'rhodecode_settings'
131 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
131 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
132 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
132 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
133 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
133 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
134 app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
134 app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
135
135
136 def __init__(self, k='', v=''):
136 def __init__(self, k='', v=''):
137 self.app_settings_name = k
137 self.app_settings_name = k
138 self.app_settings_value = v
138 self.app_settings_value = v
139
139
140 def __repr__(self):
140 def __repr__(self):
141 return "<%s('%s:%s')>" % (self.__class__.__name__,
141 return "<%s('%s:%s')>" % (self.__class__.__name__,
142 self.app_settings_name, self.app_settings_value)
142 self.app_settings_name, self.app_settings_value)
143
143
144
144
145 @classmethod
145 @classmethod
146 def get_by_name(cls, ldap_key):
146 def get_by_name(cls, ldap_key):
147 return Session.query(cls)\
147 return Session.query(cls)\
148 .filter(cls.app_settings_name == ldap_key).scalar()
148 .filter(cls.app_settings_name == ldap_key).scalar()
149
149
150 @classmethod
150 @classmethod
151 def get_app_settings(cls, cache=False):
151 def get_app_settings(cls, cache=False):
152
152
153 ret = Session.query(cls)
153 ret = Session.query(cls)
154
154
155 if cache:
155 if cache:
156 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
156 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
157
157
158 if not ret:
158 if not ret:
159 raise Exception('Could not get application settings !')
159 raise Exception('Could not get application settings !')
160 settings = {}
160 settings = {}
161 for each in ret:
161 for each in ret:
162 settings['rhodecode_' + each.app_settings_name] = \
162 settings['rhodecode_' + each.app_settings_name] = \
163 each.app_settings_value
163 each.app_settings_value
164
164
165 return settings
165 return settings
166
166
167 @classmethod
167 @classmethod
168 def get_ldap_settings(cls, cache=False):
168 def get_ldap_settings(cls, cache=False):
169 ret = Session.query(cls)\
169 ret = Session.query(cls)\
170 .filter(cls.app_settings_name.startswith('ldap_'))\
170 .filter(cls.app_settings_name.startswith('ldap_'))\
171 .all()
171 .all()
172 fd = {}
172 fd = {}
173 for row in ret:
173 for row in ret:
174 fd.update({row.app_settings_name:row.app_settings_value})
174 fd.update({row.app_settings_name:row.app_settings_value})
175
175
176 fd.update({'ldap_active':str2bool(fd.get('ldap_active'))})
176 fd.update({'ldap_active':str2bool(fd.get('ldap_active'))})
177
177
178 return fd
178 return fd
179
179
180
180
181 class RhodeCodeUi(Base, BaseModel):
181 class RhodeCodeUi(Base, BaseModel):
182 __tablename__ = 'rhodecode_ui'
182 __tablename__ = 'rhodecode_ui'
183 __table_args__ = {'extend_existing':True}
183 __table_args__ = {'extend_existing':True}
184 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
184 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
185 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
185 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
186 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
186 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
187 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
187 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
188 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
188 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
189
189
190
190
191 @classmethod
191 @classmethod
192 def get_by_key(cls, key):
192 def get_by_key(cls, key):
193 return Session.query(cls).filter(cls.ui_key == key)
193 return Session.query(cls).filter(cls.ui_key == key)
194
194
195
195
196 class User(Base, BaseModel):
196 class User(Base, BaseModel):
197 __tablename__ = 'users'
197 __tablename__ = 'users'
198 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
198 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
199 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
199 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
200 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
200 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
201 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
201 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
202 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
202 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
203 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
203 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
204 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
204 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
205 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
205 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
206 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
206 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
207 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
207 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
208 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
208 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
209 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
209 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
210
210
211 user_log = relationship('UserLog', cascade='all')
211 user_log = relationship('UserLog', cascade='all')
212 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
212 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
213
213
214 repositories = relationship('Repository')
214 repositories = relationship('Repository')
215 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
215 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
216 repo_to_perm = relationship('RepoToPerm', primaryjoin='RepoToPerm.user_id==User.user_id', cascade='all')
216 repo_to_perm = relationship('RepoToPerm', primaryjoin='RepoToPerm.user_id==User.user_id', cascade='all')
217
217
218 group_member = relationship('UsersGroupMember', cascade='all')
218 group_member = relationship('UsersGroupMember', cascade='all')
219
219
220 @property
220 @property
221 def full_contact(self):
221 def full_contact(self):
222 return '%s %s <%s>' % (self.name, self.lastname, self.email)
222 return '%s %s <%s>' % (self.name, self.lastname, self.email)
223
223
224 @property
224 @property
225 def short_contact(self):
225 def short_contact(self):
226 return '%s %s' % (self.name, self.lastname)
226 return '%s %s' % (self.name, self.lastname)
227
227
228 @property
228 @property
229 def is_admin(self):
229 def is_admin(self):
230 return self.admin
230 return self.admin
231
231
232 def __repr__(self):
232 def __repr__(self):
233 try:
233 try:
234 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
234 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
235 self.user_id, self.username)
235 self.user_id, self.username)
236 except:
236 except:
237 return self.__class__.__name__
237 return self.__class__.__name__
238
238
239 @classmethod
239 @classmethod
240 def by_username(cls, username, case_insensitive=False):
240 def by_username(cls, username, case_insensitive=False):
241 if case_insensitive:
241 if case_insensitive:
242 return Session.query(cls).filter(cls.username.like(username)).one()
242 return Session.query(cls).filter(cls.username.like(username)).one()
243 else:
243 else:
244 return Session.query(cls).filter(cls.username == username).one()
244 return Session.query(cls).filter(cls.username == username).one()
245
245
246 def update_lastlogin(self):
246 def update_lastlogin(self):
247 """Update user lastlogin"""
247 """Update user lastlogin"""
248
248
249 self.last_login = datetime.datetime.now()
249 self.last_login = datetime.datetime.now()
250 Session.add(self)
250 Session.add(self)
251 Session.commit()
251 Session.commit()
252 log.debug('updated user %s lastlogin', self.username)
252 log.debug('updated user %s lastlogin', self.username)
253
253
254
254
255 class UserLog(Base, BaseModel):
255 class UserLog(Base, BaseModel):
256 __tablename__ = 'user_logs'
256 __tablename__ = 'user_logs'
257 __table_args__ = {'extend_existing':True}
257 __table_args__ = {'extend_existing':True}
258 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
258 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
259 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
259 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
260 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
260 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
261 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
261 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
262 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
262 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
263 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
263 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
264 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
265
265
266 @property
266 @property
267 def action_as_day(self):
267 def action_as_day(self):
268 return date(*self.action_date.timetuple()[:3])
268 return date(*self.action_date.timetuple()[:3])
269
269
270 user = relationship('User')
270 user = relationship('User')
271 repository = relationship('Repository')
271 repository = relationship('Repository')
272
272
273
273
274 class UsersGroup(Base, BaseModel):
274 class UsersGroup(Base, BaseModel):
275 __tablename__ = 'users_groups'
275 __tablename__ = 'users_groups'
276 __table_args__ = {'extend_existing':True}
276 __table_args__ = {'extend_existing':True}
277
277
278 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
278 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
279 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
279 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
280 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
280 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
281
281
282 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
282 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
283
283
284
284
285 @classmethod
285 @classmethod
286 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
286 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
287 if case_insensitive:
287 if case_insensitive:
288 gr = Session.query(cls)\
288 gr = Session.query(cls)\
289 .filter(cls.users_group_name.ilike(group_name))
289 .filter(cls.users_group_name.ilike(group_name))
290 else:
290 else:
291 gr = Session.query(UsersGroup)\
291 gr = Session.query(UsersGroup)\
292 .filter(UsersGroup.users_group_name == group_name)
292 .filter(UsersGroup.users_group_name == group_name)
293 if cache:
293 if cache:
294 gr = gr.options(FromCache("sql_cache_short",
294 gr = gr.options(FromCache("sql_cache_short",
295 "get_user_%s" % group_name))
295 "get_user_%s" % group_name))
296 return gr.scalar()
296 return gr.scalar()
297
297
298 class UsersGroupMember(Base, BaseModel):
298 class UsersGroupMember(Base, BaseModel):
299 __tablename__ = 'users_groups_members'
299 __tablename__ = 'users_groups_members'
300 __table_args__ = {'extend_existing':True}
300 __table_args__ = {'extend_existing':True}
301
301
302 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
302 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
303 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
303 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
304 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
304 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
305
305
306 user = relationship('User', lazy='joined')
306 user = relationship('User', lazy='joined')
307 users_group = relationship('UsersGroup')
307 users_group = relationship('UsersGroup')
308
308
309 def __init__(self, gr_id='', u_id=''):
309 def __init__(self, gr_id='', u_id=''):
310 self.users_group_id = gr_id
310 self.users_group_id = gr_id
311 self.user_id = u_id
311 self.user_id = u_id
312
312
313 class Repository(Base, BaseModel):
313 class Repository(Base, BaseModel):
314 __tablename__ = 'repositories'
314 __tablename__ = 'repositories'
315 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
315 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
316
316
317 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
317 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
318 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
318 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
319 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
319 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
320 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
320 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
321 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
321 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
322 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
322 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
323 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
323 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
324 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
324 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
325 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
325 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
326 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
326 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
327
327
328 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
328 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
329 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
329 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
330
330
331
331
332 user = relationship('User')
332 user = relationship('User')
333 fork = relationship('Repository', remote_side=repo_id)
333 fork = relationship('Repository', remote_side=repo_id)
334 group = relationship('Group')
334 group = relationship('Group')
335 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
335 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
336 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
336 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
337 stats = relationship('Statistics', cascade='all', uselist=False)
337 stats = relationship('Statistics', cascade='all', uselist=False)
338
338
339 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
339 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
340
340
341 logs = relationship('UserLog', cascade='all')
341 logs = relationship('UserLog', cascade='all')
342
342
343 def __repr__(self):
343 def __repr__(self):
344 return "<%s('%s:%s')>" % (self.__class__.__name__,
344 return "<%s('%s:%s')>" % (self.__class__.__name__,
345 self.repo_id, self.repo_name)
345 self.repo_id, self.repo_name)
346
346
347 @classmethod
347 @classmethod
348 def by_repo_name(cls, repo_name):
348 def by_repo_name(cls, repo_name):
349 q = Session.query(cls).filter(cls.repo_name == repo_name)
349 q = Session.query(cls).filter(cls.repo_name == repo_name)
350
350
351 q = q.options(joinedload(Repository.fork))\
351 q = q.options(joinedload(Repository.fork))\
352 .options(joinedload(Repository.user))\
352 .options(joinedload(Repository.user))\
353 .options(joinedload(Repository.group))\
353 .options(joinedload(Repository.group))\
354
354
355 return q.one()
355 return q.one()
356
356
357 @classmethod
357 @classmethod
358 def get_repo_forks(cls, repo_id):
358 def get_repo_forks(cls, repo_id):
359 return Session.query(cls).filter(Repository.fork_id == repo_id)
359 return Session.query(cls).filter(Repository.fork_id == repo_id)
360
360
361 @property
361 @property
362 def just_name(self):
362 def just_name(self):
363 return self.repo_name.split(os.sep)[-1]
363 return self.repo_name.split(os.sep)[-1]
364
364
365 @property
365 @property
366 def groups_with_parents(self):
366 def groups_with_parents(self):
367 groups = []
367 groups = []
368 if self.group is None:
368 if self.group is None:
369 return groups
369 return groups
370
370
371 cur_gr = self.group
371 cur_gr = self.group
372 groups.insert(0, cur_gr)
372 groups.insert(0, cur_gr)
373 while 1:
373 while 1:
374 gr = getattr(cur_gr, 'parent_group', None)
374 gr = getattr(cur_gr, 'parent_group', None)
375 cur_gr = cur_gr.parent_group
375 cur_gr = cur_gr.parent_group
376 if gr is None:
376 if gr is None:
377 break
377 break
378 groups.insert(0, gr)
378 groups.insert(0, gr)
379
379
380 return groups
380 return groups
381
381
382 @property
382 @property
383 def groups_and_repo(self):
383 def groups_and_repo(self):
384 return self.groups_with_parents, self.just_name
384 return self.groups_with_parents, self.just_name
385
385
386 @LazyProperty
386 @LazyProperty
387 def repo_path(self):
387 def repo_path(self):
388 """
388 """
389 Returns base full path for that repository means where it actually
389 Returns base full path for that repository means where it actually
390 exists on a filesystem
390 exists on a filesystem
391 """
391 """
392 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/')
392 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/')
393 q.options(FromCache("sql_cache_short", "repository_repo_path"))
393 q.options(FromCache("sql_cache_short", "repository_repo_path"))
394 return q.one().ui_value
394 return q.one().ui_value
395
395
396 @property
396 @property
397 def repo_full_path(self):
397 def repo_full_path(self):
398 p = [self.repo_path]
398 p = [self.repo_path]
399 # we need to split the name by / since this is how we store the
399 # we need to split the name by / since this is how we store the
400 # names in the database, but that eventually needs to be converted
400 # names in the database, but that eventually needs to be converted
401 # into a valid system path
401 # into a valid system path
402 p += self.repo_name.split('/')
402 p += self.repo_name.split('/')
403 return os.path.join(*p)
403 return os.path.join(*p)
404
404
405 @property
405 @property
406 def _ui(self):
406 def _ui(self):
407 """
407 """
408 Creates an db based ui object for this repository
408 Creates an db based ui object for this repository
409 """
409 """
410 from mercurial import ui
410 from mercurial import ui
411 from mercurial import config
411 from mercurial import config
412 baseui = ui.ui()
412 baseui = ui.ui()
413
413
414 #clean the baseui object
414 #clean the baseui object
415 baseui._ocfg = config.config()
415 baseui._ocfg = config.config()
416 baseui._ucfg = config.config()
416 baseui._ucfg = config.config()
417 baseui._tcfg = config.config()
417 baseui._tcfg = config.config()
418
418
419
419
420 ret = Session.query(RhodeCodeUi)\
420 ret = Session.query(RhodeCodeUi)\
421 .options(FromCache("sql_cache_short",
421 .options(FromCache("sql_cache_short",
422 "repository_repo_ui")).all()
422 "repository_repo_ui")).all()
423
423
424 hg_ui = ret
424 hg_ui = ret
425 for ui_ in hg_ui:
425 for ui_ in hg_ui:
426 if ui_.ui_active:
426 if ui_.ui_active:
427 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
427 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
428 ui_.ui_key, ui_.ui_value)
428 ui_.ui_key, ui_.ui_value)
429 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
429 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
430
430
431 return baseui
431 return baseui
432
432
433 #==========================================================================
433 #==========================================================================
434 # SCM CACHE INSTANCE
434 # SCM CACHE INSTANCE
435 #==========================================================================
435 #==========================================================================
436
436
437 @property
437 @property
438 def invalidate(self):
438 def invalidate(self):
439 """
439 """
440 Returns Invalidation object if this repo should be invalidated
440 Returns Invalidation object if this repo should be invalidated
441 None otherwise. `cache_active = False` means that this cache
441 None otherwise. `cache_active = False` means that this cache
442 state is not valid and needs to be invalidated
442 state is not valid and needs to be invalidated
443 """
443 """
444 return Session.query(CacheInvalidation)\
444 return Session.query(CacheInvalidation)\
445 .filter(CacheInvalidation.cache_key == self.repo_name)\
445 .filter(CacheInvalidation.cache_key == self.repo_name)\
446 .filter(CacheInvalidation.cache_active == False)\
446 .filter(CacheInvalidation.cache_active == False)\
447 .scalar()
447 .scalar()
448
448
449 @property
449 @property
450 def set_invalidate(self):
450 def set_invalidate(self):
451 """
451 """
452 set a cache for invalidation for this instance
452 set a cache for invalidation for this instance
453 """
453 """
454 inv = Session.query(CacheInvalidation)\
454 inv = Session.query(CacheInvalidation)\
455 .filter(CacheInvalidation.cache_key == self.repo_name)\
455 .filter(CacheInvalidation.cache_key == self.repo_name)\
456 .scalar()
456 .scalar()
457
457
458 if inv is None:
458 if inv is None:
459 inv = CacheInvalidation(self.repo_name)
459 inv = CacheInvalidation(self.repo_name)
460 inv.cache_active = True
460 inv.cache_active = True
461 Session.add(inv)
461 Session.add(inv)
462 Session.commit()
462 Session.commit()
463
463
464 @property
464 @property
465 def scm_instance(self):
465 def scm_instance(self):
466 return self.__get_instance()
466 return self.__get_instance()
467
467
468 @property
468 @property
469 def scm_instance_cached(self):
469 def scm_instance_cached(self):
470 @cache_region('long_term')
470 @cache_region('long_term')
471 def _c(repo_name):
471 def _c(repo_name):
472 return self.__get_instance()
472 return self.__get_instance()
473
473
474 inv = self.invalidate
474 inv = self.invalidate
475 if inv:
475 if inv:
476 region_invalidate(_c, None, self.repo_name)
476 region_invalidate(_c, None, self.repo_name)
477 #update our cache
477 #update our cache
478 inv.cache_key.cache_active = True
478 inv.cache_key.cache_active = True
479 Session.add(inv)
479 Session.add(inv)
480 Session.commit()
480 Session.commit()
481
481
482 return _c(self.repo_name)
482 # TODO: remove this trick when beaker 1.6 is released
483 # and have fixed this issue
484 rn = safe_str(self.repo_name)
485
486 return _c(rn)
483
487
484 def __get_instance(self):
488 def __get_instance(self):
485
489
486 repo_full_path = self.repo_full_path
490 repo_full_path = self.repo_full_path
487
491
488 try:
492 try:
489 alias = get_scm(repo_full_path)[0]
493 alias = get_scm(repo_full_path)[0]
490 log.debug('Creating instance of %s repository', alias)
494 log.debug('Creating instance of %s repository', alias)
491 backend = get_backend(alias)
495 backend = get_backend(alias)
492 except VCSError:
496 except VCSError:
493 log.error(traceback.format_exc())
497 log.error(traceback.format_exc())
494 log.error('Perhaps this repository is in db and not in '
498 log.error('Perhaps this repository is in db and not in '
495 'filesystem run rescan repositories with '
499 'filesystem run rescan repositories with '
496 '"destroy old data " option from admin panel')
500 '"destroy old data " option from admin panel')
497 return
501 return
498
502
499 if alias == 'hg':
503 if alias == 'hg':
500 repo = backend(repo_full_path, create=False,
504
505 repo = backend(safe_str(repo_full_path), create=False,
501 baseui=self._ui)
506 baseui=self._ui)
502 #skip hidden web repository
507 #skip hidden web repository
503 if repo._get_hidden():
508 if repo._get_hidden():
504 return
509 return
505 else:
510 else:
506 repo = backend(repo_full_path, create=False)
511 repo = backend(repo_full_path, create=False)
507
512
508 return repo
513 return repo
509
514
510
515
511 class Group(Base, BaseModel):
516 class Group(Base, BaseModel):
512 __tablename__ = 'groups'
517 __tablename__ = 'groups'
513 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
518 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
514 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
519 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
515 __mapper_args__ = {'order_by':'group_name'}
520 __mapper_args__ = {'order_by':'group_name'}
516
521
517 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
522 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
518 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
523 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
519 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
524 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
520 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
525 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
521
526
522 parent_group = relationship('Group', remote_side=group_id)
527 parent_group = relationship('Group', remote_side=group_id)
523
528
524
529
525 def __init__(self, group_name='', parent_group=None):
530 def __init__(self, group_name='', parent_group=None):
526 self.group_name = group_name
531 self.group_name = group_name
527 self.parent_group = parent_group
532 self.parent_group = parent_group
528
533
529 def __repr__(self):
534 def __repr__(self):
530 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
535 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
531 self.group_name)
536 self.group_name)
532
537
533 @classmethod
538 @classmethod
534 def url_sep(cls):
539 def url_sep(cls):
535 return '/'
540 return '/'
536
541
537 @property
542 @property
538 def parents(self):
543 def parents(self):
539 parents_recursion_limit = 5
544 parents_recursion_limit = 5
540 groups = []
545 groups = []
541 if self.parent_group is None:
546 if self.parent_group is None:
542 return groups
547 return groups
543 cur_gr = self.parent_group
548 cur_gr = self.parent_group
544 groups.insert(0, cur_gr)
549 groups.insert(0, cur_gr)
545 cnt = 0
550 cnt = 0
546 while 1:
551 while 1:
547 cnt += 1
552 cnt += 1
548 gr = getattr(cur_gr, 'parent_group', None)
553 gr = getattr(cur_gr, 'parent_group', None)
549 cur_gr = cur_gr.parent_group
554 cur_gr = cur_gr.parent_group
550 if gr is None:
555 if gr is None:
551 break
556 break
552 if cnt == parents_recursion_limit:
557 if cnt == parents_recursion_limit:
553 # this will prevent accidental infinit loops
558 # this will prevent accidental infinit loops
554 log.error('group nested more than %s' %
559 log.error('group nested more than %s' %
555 parents_recursion_limit)
560 parents_recursion_limit)
556 break
561 break
557
562
558 groups.insert(0, gr)
563 groups.insert(0, gr)
559 return groups
564 return groups
560
565
561 @property
566 @property
562 def children(self):
567 def children(self):
563 return Session.query(Group).filter(Group.parent_group == self)
568 return Session.query(Group).filter(Group.parent_group == self)
564
569
565 @property
570 @property
566 def full_path(self):
571 def full_path(self):
567 return Group.url_sep().join([g.group_name for g in self.parents] +
572 return Group.url_sep().join([g.group_name for g in self.parents] +
568 [self.group_name])
573 [self.group_name])
569
574
570 @property
575 @property
571 def repositories(self):
576 def repositories(self):
572 return Session.query(Repository).filter(Repository.group == self)
577 return Session.query(Repository).filter(Repository.group == self)
573
578
574 @property
579 @property
575 def repositories_recursive_count(self):
580 def repositories_recursive_count(self):
576 cnt = self.repositories.count()
581 cnt = self.repositories.count()
577
582
578 def children_count(group):
583 def children_count(group):
579 cnt = 0
584 cnt = 0
580 for child in group.children:
585 for child in group.children:
581 cnt += child.repositories.count()
586 cnt += child.repositories.count()
582 cnt += children_count(child)
587 cnt += children_count(child)
583 return cnt
588 return cnt
584
589
585 return cnt + children_count(self)
590 return cnt + children_count(self)
586
591
587 class Permission(Base, BaseModel):
592 class Permission(Base, BaseModel):
588 __tablename__ = 'permissions'
593 __tablename__ = 'permissions'
589 __table_args__ = {'extend_existing':True}
594 __table_args__ = {'extend_existing':True}
590 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
595 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
591 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
596 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
592 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
597 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
593
598
594 def __repr__(self):
599 def __repr__(self):
595 return "<%s('%s:%s')>" % (self.__class__.__name__,
600 return "<%s('%s:%s')>" % (self.__class__.__name__,
596 self.permission_id, self.permission_name)
601 self.permission_id, self.permission_name)
597
602
598 @classmethod
603 @classmethod
599 def get_by_key(cls, key):
604 def get_by_key(cls, key):
600 return Session.query(cls).filter(cls.permission_name == key).scalar()
605 return Session.query(cls).filter(cls.permission_name == key).scalar()
601
606
602 class RepoToPerm(Base, BaseModel):
607 class RepoToPerm(Base, BaseModel):
603 __tablename__ = 'repo_to_perm'
608 __tablename__ = 'repo_to_perm'
604 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
609 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
605 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
610 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
606 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
611 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
607 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
612 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
608 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
613 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
609
614
610 user = relationship('User')
615 user = relationship('User')
611 permission = relationship('Permission')
616 permission = relationship('Permission')
612 repository = relationship('Repository')
617 repository = relationship('Repository')
613
618
614 class UserToPerm(Base, BaseModel):
619 class UserToPerm(Base, BaseModel):
615 __tablename__ = 'user_to_perm'
620 __tablename__ = 'user_to_perm'
616 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
621 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
617 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
622 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
618 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
623 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
619 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
624 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
620
625
621 user = relationship('User')
626 user = relationship('User')
622 permission = relationship('Permission')
627 permission = relationship('Permission')
623
628
624 @classmethod
629 @classmethod
625 def has_perm(cls, user_id, perm):
630 def has_perm(cls, user_id, perm):
626 if not isinstance(perm, Permission):
631 if not isinstance(perm, Permission):
627 raise Exception('perm needs to be an instance of Permission class')
632 raise Exception('perm needs to be an instance of Permission class')
628
633
629 return Session.query(cls).filter(cls.user_id == user_id)\
634 return Session.query(cls).filter(cls.user_id == user_id)\
630 .filter(cls.permission == perm).scalar() is not None
635 .filter(cls.permission == perm).scalar() is not None
631
636
632 @classmethod
637 @classmethod
633 def grant_perm(cls, user_id, perm):
638 def grant_perm(cls, user_id, perm):
634 if not isinstance(perm, Permission):
639 if not isinstance(perm, Permission):
635 raise Exception('perm needs to be an instance of Permission class')
640 raise Exception('perm needs to be an instance of Permission class')
636
641
637 new = cls()
642 new = cls()
638 new.user_id = user_id
643 new.user_id = user_id
639 new.permission = perm
644 new.permission = perm
640 try:
645 try:
641 Session.add(new)
646 Session.add(new)
642 Session.commit()
647 Session.commit()
643 except:
648 except:
644 Session.rollback()
649 Session.rollback()
645
650
646
651
647 @classmethod
652 @classmethod
648 def revoke_perm(cls, user_id, perm):
653 def revoke_perm(cls, user_id, perm):
649 if not isinstance(perm, Permission):
654 if not isinstance(perm, Permission):
650 raise Exception('perm needs to be an instance of Permission class')
655 raise Exception('perm needs to be an instance of Permission class')
651
656
652 try:
657 try:
653 Session.query(cls).filter(cls.user_id == user_id)\
658 Session.query(cls).filter(cls.user_id == user_id)\
654 .filter(cls.permission == perm).delete()
659 .filter(cls.permission == perm).delete()
655 Session.commit()
660 Session.commit()
656 except:
661 except:
657 Session.rollback()
662 Session.rollback()
658
663
659 class UsersGroupRepoToPerm(Base, BaseModel):
664 class UsersGroupRepoToPerm(Base, BaseModel):
660 __tablename__ = 'users_group_repo_to_perm'
665 __tablename__ = 'users_group_repo_to_perm'
661 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
666 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
662 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
667 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
663 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
668 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
664 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
669 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
665 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
670 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
666
671
667 users_group = relationship('UsersGroup')
672 users_group = relationship('UsersGroup')
668 permission = relationship('Permission')
673 permission = relationship('Permission')
669 repository = relationship('Repository')
674 repository = relationship('Repository')
670
675
671
676
672 class UsersGroupToPerm(Base, BaseModel):
677 class UsersGroupToPerm(Base, BaseModel):
673 __tablename__ = 'users_group_to_perm'
678 __tablename__ = 'users_group_to_perm'
674 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
679 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
675 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
680 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
676 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
681 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
677
682
678 users_group = relationship('UsersGroup')
683 users_group = relationship('UsersGroup')
679 permission = relationship('Permission')
684 permission = relationship('Permission')
680
685
681
686
682 @classmethod
687 @classmethod
683 def has_perm(cls, users_group_id, perm):
688 def has_perm(cls, users_group_id, perm):
684 if not isinstance(perm, Permission):
689 if not isinstance(perm, Permission):
685 raise Exception('perm needs to be an instance of Permission class')
690 raise Exception('perm needs to be an instance of Permission class')
686
691
687 return Session.query(cls).filter(cls.users_group_id ==
692 return Session.query(cls).filter(cls.users_group_id ==
688 users_group_id)\
693 users_group_id)\
689 .filter(cls.permission == perm)\
694 .filter(cls.permission == perm)\
690 .scalar() is not None
695 .scalar() is not None
691
696
692 @classmethod
697 @classmethod
693 def grant_perm(cls, users_group_id, perm):
698 def grant_perm(cls, users_group_id, perm):
694 if not isinstance(perm, Permission):
699 if not isinstance(perm, Permission):
695 raise Exception('perm needs to be an instance of Permission class')
700 raise Exception('perm needs to be an instance of Permission class')
696
701
697 new = cls()
702 new = cls()
698 new.users_group_id = users_group_id
703 new.users_group_id = users_group_id
699 new.permission = perm
704 new.permission = perm
700 try:
705 try:
701 Session.add(new)
706 Session.add(new)
702 Session.commit()
707 Session.commit()
703 except:
708 except:
704 Session.rollback()
709 Session.rollback()
705
710
706
711
707 @classmethod
712 @classmethod
708 def revoke_perm(cls, users_group_id, perm):
713 def revoke_perm(cls, users_group_id, perm):
709 if not isinstance(perm, Permission):
714 if not isinstance(perm, Permission):
710 raise Exception('perm needs to be an instance of Permission class')
715 raise Exception('perm needs to be an instance of Permission class')
711
716
712 try:
717 try:
713 Session.query(cls).filter(cls.users_group_id == users_group_id)\
718 Session.query(cls).filter(cls.users_group_id == users_group_id)\
714 .filter(cls.permission == perm).delete()
719 .filter(cls.permission == perm).delete()
715 Session.commit()
720 Session.commit()
716 except:
721 except:
717 Session.rollback()
722 Session.rollback()
718
723
719
724
720 class GroupToPerm(Base, BaseModel):
725 class GroupToPerm(Base, BaseModel):
721 __tablename__ = 'group_to_perm'
726 __tablename__ = 'group_to_perm'
722 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
727 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
723
728
724 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
729 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
725 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
730 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
726 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
731 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
727 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
732 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
728
733
729 user = relationship('User')
734 user = relationship('User')
730 permission = relationship('Permission')
735 permission = relationship('Permission')
731 group = relationship('Group')
736 group = relationship('Group')
732
737
733 class Statistics(Base, BaseModel):
738 class Statistics(Base, BaseModel):
734 __tablename__ = 'statistics'
739 __tablename__ = 'statistics'
735 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
740 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
736 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
741 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
737 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
742 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
738 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
743 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
739 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
744 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
740 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
745 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
741 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
746 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
742
747
743 repository = relationship('Repository', single_parent=True)
748 repository = relationship('Repository', single_parent=True)
744
749
745 class UserFollowing(Base, BaseModel):
750 class UserFollowing(Base, BaseModel):
746 __tablename__ = 'user_followings'
751 __tablename__ = 'user_followings'
747 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
752 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
748 UniqueConstraint('user_id', 'follows_user_id')
753 UniqueConstraint('user_id', 'follows_user_id')
749 , {'extend_existing':True})
754 , {'extend_existing':True})
750
755
751 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
756 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
752 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
757 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
753 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
758 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
754 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
759 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
755 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
760 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
756
761
757 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
762 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
758
763
759 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
764 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
760 follows_repository = relationship('Repository', order_by='Repository.repo_name')
765 follows_repository = relationship('Repository', order_by='Repository.repo_name')
761
766
762
767
763 @classmethod
768 @classmethod
764 def get_repo_followers(cls, repo_id):
769 def get_repo_followers(cls, repo_id):
765 return Session.query(cls).filter(cls.follows_repo_id == repo_id)
770 return Session.query(cls).filter(cls.follows_repo_id == repo_id)
766
771
767 class CacheInvalidation(Base, BaseModel):
772 class CacheInvalidation(Base, BaseModel):
768 __tablename__ = 'cache_invalidation'
773 __tablename__ = 'cache_invalidation'
769 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
774 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
770 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
775 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
771 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
776 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
772 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
777 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
773 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
778 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
774
779
775
780
776 def __init__(self, cache_key, cache_args=''):
781 def __init__(self, cache_key, cache_args=''):
777 self.cache_key = cache_key
782 self.cache_key = cache_key
778 self.cache_args = cache_args
783 self.cache_args = cache_args
779 self.cache_active = False
784 self.cache_active = False
780
785
781 def __repr__(self):
786 def __repr__(self):
782 return "<%s('%s:%s')>" % (self.__class__.__name__,
787 return "<%s('%s:%s')>" % (self.__class__.__name__,
783 self.cache_id, self.cache_key)
788 self.cache_id, self.cache_key)
784
789
785 class DbMigrateVersion(Base, BaseModel):
790 class DbMigrateVersion(Base, BaseModel):
786 __tablename__ = 'db_migrate_version'
791 __tablename__ = 'db_migrate_version'
787 __table_args__ = {'extend_existing':True}
792 __table_args__ = {'extend_existing':True}
788 repository_id = Column('repository_id', String(250), primary_key=True)
793 repository_id = Column('repository_id', String(250), primary_key=True)
789 repository_path = Column('repository_path', Text)
794 repository_path = Column('repository_path', Text)
790 version = Column('version', Integer)
795 version = Column('version', Integer)
@@ -1,355 +1,358 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.repo
3 rhodecode.model.repo
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 Repository model for rhodecode
6 Repository model for rhodecode
7
7
8 :created_on: Jun 5, 2010
8 :created_on: Jun 5, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import os
26 import shutil
26 import shutil
27 import logging
27 import logging
28 import traceback
28 import traceback
29 from datetime import datetime
29 from datetime import datetime
30
30
31 from sqlalchemy.orm import joinedload, make_transient
31 from sqlalchemy.orm import joinedload, make_transient
32
32
33 from vcs.utils.lazy import LazyProperty
33 from vcs.utils.lazy import LazyProperty
34 from vcs.backends import get_backend
34 from vcs.backends import get_backend
35
35
36 from rhodecode.lib import safe_str
37
36 from rhodecode.model import BaseModel
38 from rhodecode.model import BaseModel
37 from rhodecode.model.caching_query import FromCache
39 from rhodecode.model.caching_query import FromCache
38 from rhodecode.model.db import Repository, RepoToPerm, User, Permission, \
40 from rhodecode.model.db import Repository, RepoToPerm, User, Permission, \
39 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, Group
41 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, Group
40 from rhodecode.model.user import UserModel
42 from rhodecode.model.user import UserModel
41
43
42 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
43
45
44
46
45 class RepoModel(BaseModel):
47 class RepoModel(BaseModel):
46
48
47 @LazyProperty
49 @LazyProperty
48 def repos_path(self):
50 def repos_path(self):
49 """Get's the repositories root path from database
51 """Get's the repositories root path from database
50 """
52 """
51
53
52 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
54 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
53 return q.ui_value
55 return q.ui_value
54
56
55 def get(self, repo_id, cache=False):
57 def get(self, repo_id, cache=False):
56 repo = self.sa.query(Repository)\
58 repo = self.sa.query(Repository)\
57 .filter(Repository.repo_id == repo_id)
59 .filter(Repository.repo_id == repo_id)
58
60
59 if cache:
61 if cache:
60 repo = repo.options(FromCache("sql_cache_short",
62 repo = repo.options(FromCache("sql_cache_short",
61 "get_repo_%s" % repo_id))
63 "get_repo_%s" % repo_id))
62 return repo.scalar()
64 return repo.scalar()
63
65
64 def get_by_repo_name(self, repo_name, cache=False):
66 def get_by_repo_name(self, repo_name, cache=False):
65 repo = self.sa.query(Repository)\
67 repo = self.sa.query(Repository)\
66 .filter(Repository.repo_name == repo_name)
68 .filter(Repository.repo_name == repo_name)
67
69
68 if cache:
70 if cache:
69 repo = repo.options(FromCache("sql_cache_short",
71 repo = repo.options(FromCache("sql_cache_short",
70 "get_repo_%s" % repo_name))
72 "get_repo_%s" % repo_name))
71 return repo.scalar()
73 return repo.scalar()
72
74
73
75
74 def get_users_js(self):
76 def get_users_js(self):
75
77
76 users = self.sa.query(User).filter(User.active == True).all()
78 users = self.sa.query(User).filter(User.active == True).all()
77 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
79 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
78 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
80 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
79 u.lastname, u.username)
81 u.lastname, u.username)
80 for u in users])
82 for u in users])
81 return users_array
83 return users_array
82
84
83 def get_users_groups_js(self):
85 def get_users_groups_js(self):
84 users_groups = self.sa.query(UsersGroup)\
86 users_groups = self.sa.query(UsersGroup)\
85 .filter(UsersGroup.users_group_active == True).all()
87 .filter(UsersGroup.users_group_active == True).all()
86
88
87 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
89 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
88
90
89 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
91 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
90 (gr.users_group_id, gr.users_group_name,
92 (gr.users_group_id, gr.users_group_name,
91 len(gr.members))
93 len(gr.members))
92 for gr in users_groups])
94 for gr in users_groups])
93 return users_groups_array
95 return users_groups_array
94
96
95 def update(self, repo_name, form_data):
97 def update(self, repo_name, form_data):
96 try:
98 try:
97 cur_repo = self.get_by_repo_name(repo_name, cache=False)
99 cur_repo = self.get_by_repo_name(repo_name, cache=False)
98
100
99 #update permissions
101 #update permissions
100 for member, perm, member_type in form_data['perms_updates']:
102 for member, perm, member_type in form_data['perms_updates']:
101 if member_type == 'user':
103 if member_type == 'user':
102 r2p = self.sa.query(RepoToPerm)\
104 r2p = self.sa.query(RepoToPerm)\
103 .filter(RepoToPerm.user == User.by_username(member))\
105 .filter(RepoToPerm.user == User.by_username(member))\
104 .filter(RepoToPerm.repository == cur_repo)\
106 .filter(RepoToPerm.repository == cur_repo)\
105 .one()
107 .one()
106
108
107 r2p.permission = self.sa.query(Permission)\
109 r2p.permission = self.sa.query(Permission)\
108 .filter(Permission.permission_name ==
110 .filter(Permission.permission_name ==
109 perm).scalar()
111 perm).scalar()
110 self.sa.add(r2p)
112 self.sa.add(r2p)
111 else:
113 else:
112 g2p = self.sa.query(UsersGroupRepoToPerm)\
114 g2p = self.sa.query(UsersGroupRepoToPerm)\
113 .filter(UsersGroupRepoToPerm.users_group ==
115 .filter(UsersGroupRepoToPerm.users_group ==
114 UsersGroup.get_by_group_name(member))\
116 UsersGroup.get_by_group_name(member))\
115 .filter(UsersGroupRepoToPerm.repository ==
117 .filter(UsersGroupRepoToPerm.repository ==
116 cur_repo).one()
118 cur_repo).one()
117
119
118 g2p.permission = self.sa.query(Permission)\
120 g2p.permission = self.sa.query(Permission)\
119 .filter(Permission.permission_name ==
121 .filter(Permission.permission_name ==
120 perm).scalar()
122 perm).scalar()
121 self.sa.add(g2p)
123 self.sa.add(g2p)
122
124
123 #set new permissions
125 #set new permissions
124 for member, perm, member_type in form_data['perms_new']:
126 for member, perm, member_type in form_data['perms_new']:
125 if member_type == 'user':
127 if member_type == 'user':
126 r2p = RepoToPerm()
128 r2p = RepoToPerm()
127 r2p.repository = cur_repo
129 r2p.repository = cur_repo
128 r2p.user = User.by_username(member)
130 r2p.user = User.by_username(member)
129
131
130 r2p.permission = self.sa.query(Permission)\
132 r2p.permission = self.sa.query(Permission)\
131 .filter(Permission.
133 .filter(Permission.
132 permission_name == perm)\
134 permission_name == perm)\
133 .scalar()
135 .scalar()
134 self.sa.add(r2p)
136 self.sa.add(r2p)
135 else:
137 else:
136 g2p = UsersGroupRepoToPerm()
138 g2p = UsersGroupRepoToPerm()
137 g2p.repository = cur_repo
139 g2p.repository = cur_repo
138 g2p.users_group = UsersGroup.get_by_group_name(member)
140 g2p.users_group = UsersGroup.get_by_group_name(member)
139 g2p.permission = self.sa.query(Permission)\
141 g2p.permission = self.sa.query(Permission)\
140 .filter(Permission.
142 .filter(Permission.
141 permission_name == perm)\
143 permission_name == perm)\
142 .scalar()
144 .scalar()
143 self.sa.add(g2p)
145 self.sa.add(g2p)
144
146
145 #update current repo
147 #update current repo
146 for k, v in form_data.items():
148 for k, v in form_data.items():
147 if k == 'user':
149 if k == 'user':
148 cur_repo.user = User.by_username(v)
150 cur_repo.user = User.by_username(v)
149 elif k == 'repo_name':
151 elif k == 'repo_name':
150 cur_repo.repo_name = form_data['repo_name_full']
152 cur_repo.repo_name = form_data['repo_name_full']
151 elif k == 'repo_group':
153 elif k == 'repo_group':
152 cur_repo.group_id = v
154 cur_repo.group_id = v
153
155
154 else:
156 else:
155 setattr(cur_repo, k, v)
157 setattr(cur_repo, k, v)
156
158
157 self.sa.add(cur_repo)
159 self.sa.add(cur_repo)
158
160
159 if repo_name != form_data['repo_name_full']:
161 if repo_name != form_data['repo_name_full']:
160 # rename repository
162 # rename repository
161 self.__rename_repo(old=repo_name,
163 self.__rename_repo(old=repo_name,
162 new=form_data['repo_name_full'])
164 new=form_data['repo_name_full'])
163
165
164 self.sa.commit()
166 self.sa.commit()
165 except:
167 except:
166 log.error(traceback.format_exc())
168 log.error(traceback.format_exc())
167 self.sa.rollback()
169 self.sa.rollback()
168 raise
170 raise
169
171
170 def create(self, form_data, cur_user, just_db=False, fork=False):
172 def create(self, form_data, cur_user, just_db=False, fork=False):
171
173
172 try:
174 try:
173 if fork:
175 if fork:
174 #force str since hg doesn't go with unicode
176 repo_name = form_data['fork_name']
175 repo_name = str(form_data['fork_name'])
177 org_name = form_data['repo_name']
176 org_name = str(form_data['repo_name'])
178 org_full_name = org_name
177 org_full_name = org_name#str(form_data['fork_name_full'])
178
179
179 else:
180 else:
180 org_name = repo_name = str(form_data['repo_name'])
181 org_name = repo_name = form_data['repo_name']
181 repo_name_full = form_data['repo_name_full']
182 repo_name_full = form_data['repo_name_full']
182
183
183 new_repo = Repository()
184 new_repo = Repository()
184 new_repo.enable_statistics = False
185 new_repo.enable_statistics = False
185 for k, v in form_data.items():
186 for k, v in form_data.items():
186 if k == 'repo_name':
187 if k == 'repo_name':
187 if fork:
188 if fork:
188 v = repo_name
189 v = repo_name
189 else:
190 else:
190 v = repo_name_full
191 v = repo_name_full
191 if k == 'repo_group':
192 if k == 'repo_group':
192 k = 'group_id'
193 k = 'group_id'
193
194
194 setattr(new_repo, k, v)
195 setattr(new_repo, k, v)
195
196
196 if fork:
197 if fork:
197 parent_repo = self.sa.query(Repository)\
198 parent_repo = self.sa.query(Repository)\
198 .filter(Repository.repo_name == org_full_name).one()
199 .filter(Repository.repo_name == org_full_name).one()
199 new_repo.fork = parent_repo
200 new_repo.fork = parent_repo
200
201
201 new_repo.user_id = cur_user.user_id
202 new_repo.user_id = cur_user.user_id
202 self.sa.add(new_repo)
203 self.sa.add(new_repo)
203
204
204 #create default permission
205 #create default permission
205 repo_to_perm = RepoToPerm()
206 repo_to_perm = RepoToPerm()
206 default = 'repository.read'
207 default = 'repository.read'
207 for p in UserModel(self.sa).get_by_username('default',
208 for p in UserModel(self.sa).get_by_username('default',
208 cache=False).user_perms:
209 cache=False).user_perms:
209 if p.permission.permission_name.startswith('repository.'):
210 if p.permission.permission_name.startswith('repository.'):
210 default = p.permission.permission_name
211 default = p.permission.permission_name
211 break
212 break
212
213
213 default_perm = 'repository.none' if form_data['private'] else default
214 default_perm = 'repository.none' if form_data['private'] else default
214
215
215 repo_to_perm.permission_id = self.sa.query(Permission)\
216 repo_to_perm.permission_id = self.sa.query(Permission)\
216 .filter(Permission.permission_name == default_perm)\
217 .filter(Permission.permission_name == default_perm)\
217 .one().permission_id
218 .one().permission_id
218
219
219 repo_to_perm.repository = new_repo
220 repo_to_perm.repository = new_repo
220 repo_to_perm.user_id = UserModel(self.sa)\
221 repo_to_perm.user_id = UserModel(self.sa)\
221 .get_by_username('default', cache=False).user_id
222 .get_by_username('default', cache=False).user_id
222
223
223 self.sa.add(repo_to_perm)
224 self.sa.add(repo_to_perm)
224
225
225 if not just_db:
226 if not just_db:
226 self.__create_repo(repo_name, form_data['repo_type'],
227 self.__create_repo(repo_name, form_data['repo_type'],
227 form_data['repo_group'],
228 form_data['repo_group'],
228 form_data['clone_uri'])
229 form_data['clone_uri'])
229
230
230 self.sa.commit()
231 self.sa.commit()
231
232
232 #now automatically start following this repository as owner
233 #now automatically start following this repository as owner
233 from rhodecode.model.scm import ScmModel
234 from rhodecode.model.scm import ScmModel
234 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
235 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
235 cur_user.user_id)
236 cur_user.user_id)
236
237
237 except:
238 except:
238 log.error(traceback.format_exc())
239 log.error(traceback.format_exc())
239 self.sa.rollback()
240 self.sa.rollback()
240 raise
241 raise
241
242
242 def create_fork(self, form_data, cur_user):
243 def create_fork(self, form_data, cur_user):
243 from rhodecode.lib.celerylib import tasks, run_task
244 from rhodecode.lib.celerylib import tasks, run_task
244 run_task(tasks.create_repo_fork, form_data, cur_user)
245 run_task(tasks.create_repo_fork, form_data, cur_user)
245
246
246 def delete(self, repo):
247 def delete(self, repo):
247 try:
248 try:
248 self.sa.delete(repo)
249 self.sa.delete(repo)
249 self.__delete_repo(repo)
250 self.__delete_repo(repo)
250 self.sa.commit()
251 self.sa.commit()
251 except:
252 except:
252 log.error(traceback.format_exc())
253 log.error(traceback.format_exc())
253 self.sa.rollback()
254 self.sa.rollback()
254 raise
255 raise
255
256
256 def delete_perm_user(self, form_data, repo_name):
257 def delete_perm_user(self, form_data, repo_name):
257 try:
258 try:
258 self.sa.query(RepoToPerm)\
259 self.sa.query(RepoToPerm)\
259 .filter(RepoToPerm.repository \
260 .filter(RepoToPerm.repository \
260 == self.get_by_repo_name(repo_name))\
261 == self.get_by_repo_name(repo_name))\
261 .filter(RepoToPerm.user_id == form_data['user_id']).delete()
262 .filter(RepoToPerm.user_id == form_data['user_id']).delete()
262 self.sa.commit()
263 self.sa.commit()
263 except:
264 except:
264 log.error(traceback.format_exc())
265 log.error(traceback.format_exc())
265 self.sa.rollback()
266 self.sa.rollback()
266 raise
267 raise
267
268
268 def delete_perm_users_group(self, form_data, repo_name):
269 def delete_perm_users_group(self, form_data, repo_name):
269 try:
270 try:
270 self.sa.query(UsersGroupRepoToPerm)\
271 self.sa.query(UsersGroupRepoToPerm)\
271 .filter(UsersGroupRepoToPerm.repository \
272 .filter(UsersGroupRepoToPerm.repository \
272 == self.get_by_repo_name(repo_name))\
273 == self.get_by_repo_name(repo_name))\
273 .filter(UsersGroupRepoToPerm.users_group_id \
274 .filter(UsersGroupRepoToPerm.users_group_id \
274 == form_data['users_group_id']).delete()
275 == form_data['users_group_id']).delete()
275 self.sa.commit()
276 self.sa.commit()
276 except:
277 except:
277 log.error(traceback.format_exc())
278 log.error(traceback.format_exc())
278 self.sa.rollback()
279 self.sa.rollback()
279 raise
280 raise
280
281
281 def delete_stats(self, repo_name):
282 def delete_stats(self, repo_name):
282 try:
283 try:
283 self.sa.query(Statistics)\
284 self.sa.query(Statistics)\
284 .filter(Statistics.repository == \
285 .filter(Statistics.repository == \
285 self.get_by_repo_name(repo_name)).delete()
286 self.get_by_repo_name(repo_name)).delete()
286 self.sa.commit()
287 self.sa.commit()
287 except:
288 except:
288 log.error(traceback.format_exc())
289 log.error(traceback.format_exc())
289 self.sa.rollback()
290 self.sa.rollback()
290 raise
291 raise
291
292
292 def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
293 def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
293 """
294 """
294 makes repository on filesystem. It's group aware means it'll create
295 makes repository on filesystem. It's group aware means it'll create
295 a repository within a group, and alter the paths accordingly of
296 a repository within a group, and alter the paths accordingly of
296 group location
297 group location
297
298
298 :param repo_name:
299 :param repo_name:
299 :param alias:
300 :param alias:
300 :param parent_id:
301 :param parent_id:
301 :param clone_uri:
302 :param clone_uri:
302 """
303 """
303 from rhodecode.lib.utils import check_repo
304 from rhodecode.lib.utils import check_repo
304
305
305
306 if new_parent_id:
306 if new_parent_id:
307 paths = Group.get(new_parent_id).full_path.split(Group.url_sep())
307 paths = Group.get(new_parent_id).full_path.split(Group.url_sep())
308 new_parent_path = os.sep.join(paths)
308 new_parent_path = os.sep.join(paths)
309 else:
309 else:
310 new_parent_path = ''
310 new_parent_path = ''
311
311
312 repo_path = os.path.join(self.repos_path, new_parent_path, repo_name)
312 repo_path = os.path.join(*map(lambda x:safe_str(x),
313 [self.repos_path, new_parent_path, repo_name]))
313
314
314 if check_repo(repo_name, self.repos_path):
315 if check_repo(repo_path, self.repos_path):
315 log.info('creating repo %s in %s @ %s', repo_name, repo_path,
316 log.info('creating repo %s in %s @ %s', repo_name, repo_path,
316 clone_uri)
317 clone_uri)
317 backend = get_backend(alias)
318 backend = get_backend(alias)
319
318 backend(repo_path, create=True, src_url=clone_uri)
320 backend(repo_path, create=True, src_url=clone_uri)
319
321
322
320 def __rename_repo(self, old, new):
323 def __rename_repo(self, old, new):
321 """
324 """
322 renames repository on filesystem
325 renames repository on filesystem
323
326
324 :param old: old name
327 :param old: old name
325 :param new: new name
328 :param new: new name
326 """
329 """
327 log.info('renaming repo from %s to %s', old, new)
330 log.info('renaming repo from %s to %s', old, new)
328
331
329 old_path = os.path.join(self.repos_path, old)
332 old_path = os.path.join(self.repos_path, old)
330 new_path = os.path.join(self.repos_path, new)
333 new_path = os.path.join(self.repos_path, new)
331 if os.path.isdir(new_path):
334 if os.path.isdir(new_path):
332 raise Exception('Was trying to rename to already existing dir %s',
335 raise Exception('Was trying to rename to already existing dir %s',
333 new_path)
336 new_path)
334 shutil.move(old_path, new_path)
337 shutil.move(old_path, new_path)
335
338
336 def __delete_repo(self, repo):
339 def __delete_repo(self, repo):
337 """
340 """
338 removes repo from filesystem, the removal is acctually made by
341 removes repo from filesystem, the removal is acctually made by
339 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
342 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
340 repository is no longer valid for rhodecode, can be undeleted later on
343 repository is no longer valid for rhodecode, can be undeleted later on
341 by reverting the renames on this repository
344 by reverting the renames on this repository
342
345
343 :param repo: repo object
346 :param repo: repo object
344 """
347 """
345 rm_path = os.path.join(self.repos_path, repo.repo_name)
348 rm_path = os.path.join(self.repos_path, repo.repo_name)
346 log.info("Removing %s", rm_path)
349 log.info("Removing %s", rm_path)
347 #disable hg/git
350 #disable hg/git
348 alias = repo.repo_type
351 alias = repo.repo_type
349 shutil.move(os.path.join(rm_path, '.%s' % alias),
352 shutil.move(os.path.join(rm_path, '.%s' % alias),
350 os.path.join(rm_path, 'rm__.%s' % alias))
353 os.path.join(rm_path, 'rm__.%s' % alias))
351 #disable repo
354 #disable repo
352 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
355 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
353 % (datetime.today()\
356 % (datetime.today()\
354 .strftime('%Y%m%d_%H%M%S_%f'),
357 .strftime('%Y%m%d_%H%M%S_%f'),
355 repo.repo_name)))
358 repo.repo_name)))
@@ -1,416 +1,413 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.scm
3 rhodecode.model.scm
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Scm model for RhodeCode
6 Scm model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import os
26 import time
26 import time
27 import traceback
27 import traceback
28 import logging
28 import logging
29
29
30 from mercurial import ui
31
32 from sqlalchemy.exc import DatabaseError
30 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.orm import make_transient
34
35 from beaker.cache import cache_region, region_invalidate
36
31
37 from vcs import get_backend
32 from vcs import get_backend
38 from vcs.utils.helpers import get_scm
33 from vcs.utils.helpers import get_scm
39 from vcs.exceptions import RepositoryError, VCSError
34 from vcs.exceptions import RepositoryError, VCSError
40 from vcs.utils.lazy import LazyProperty
35 from vcs.utils.lazy import LazyProperty
41 from vcs.nodes import FileNode
36 from vcs.nodes import FileNode
42
37
43 from rhodecode import BACKENDS
38 from rhodecode import BACKENDS
44 from rhodecode.lib import helpers as h
39 from rhodecode.lib import helpers as h
40 from rhodecode.lib import safe_str
45 from rhodecode.lib.auth import HasRepoPermissionAny
41 from rhodecode.lib.auth import HasRepoPermissionAny
46 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
42 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
47 action_logger
43 action_logger
48 from rhodecode.model import BaseModel
44 from rhodecode.model import BaseModel
49 from rhodecode.model.user import UserModel
45 from rhodecode.model.user import UserModel
50 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
46 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
52 UserFollowing, UserLog
47 UserFollowing, UserLog
53 from rhodecode.model.caching_query import FromCache
54
48
55 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
56
50
57
51
58 class UserTemp(object):
52 class UserTemp(object):
59 def __init__(self, user_id):
53 def __init__(self, user_id):
60 self.user_id = user_id
54 self.user_id = user_id
61
55
62 def __repr__(self):
56 def __repr__(self):
63 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
57 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
64
58
65
59
66 class RepoTemp(object):
60 class RepoTemp(object):
67 def __init__(self, repo_id):
61 def __init__(self, repo_id):
68 self.repo_id = repo_id
62 self.repo_id = repo_id
69
63
70 def __repr__(self):
64 def __repr__(self):
71 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
65 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
72
66
73 class CachedRepoList(object):
67 class CachedRepoList(object):
74
68
75 def __init__(self, db_repo_list, invalidation_list, repos_path,
69 def __init__(self, db_repo_list, invalidation_list, repos_path,
76 order_by=None):
70 order_by=None):
77 self.db_repo_list = db_repo_list
71 self.db_repo_list = db_repo_list
78 self.invalidation_list = invalidation_list
72 self.invalidation_list = invalidation_list
79 self.repos_path = repos_path
73 self.repos_path = repos_path
80 self.order_by = order_by
74 self.order_by = order_by
81 self.reversed = (order_by or '').startswith('-')
75 self.reversed = (order_by or '').startswith('-')
82
76
83 def __len__(self):
77 def __len__(self):
84 return len(self.db_repo_list)
78 return len(self.db_repo_list)
85
79
86 def __repr__(self):
80 def __repr__(self):
87 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
81 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
88
82
89 def __iter__(self):
83 def __iter__(self):
90 for db_repo in self.db_repo_list:
84 for db_repo in self.db_repo_list:
91 dbr = db_repo
85 dbr = db_repo
92
86
93 # invalidate the repo cache if needed before getting the
87 # invalidate the repo cache if needed before getting the
94 # scm instance
88 # scm instance
95
89
96 scm_invalidate = False
90 scm_invalidate = False
97 if self.invalidation_list is not None:
91 if self.invalidation_list is not None:
98 scm_invalidate = dbr.repo_name in self.invalidation_list
92 scm_invalidate = dbr.repo_name in self.invalidation_list
99
93
100 if scm_invalidate:
94 if scm_invalidate:
101 log.info('invalidating cache for repository %s',
95 log.info('invalidating cache for repository %s',
102 dbr.repo_name)
96 dbr.repo_name)
103 db_repo.set_invalidate
97 db_repo.set_invalidate
104
98
105 scmr = db_repo.scm_instance_cached
99 scmr = db_repo.scm_instance_cached
106
100
107 #check permission at this level
101 #check permission at this level
108 if not HasRepoPermissionAny('repository.read',
102 if not HasRepoPermissionAny('repository.read',
109 'repository.write',
103 'repository.write',
110 'repository.admin')(dbr.repo_name,
104 'repository.admin')(dbr.repo_name,
111 'get repo check'):
105 'get repo check'):
112 continue
106 continue
113
107
114
108
115 if scmr is None:
109 if scmr is None:
116 log.error('%s this repository is present in database but it '
110 log.error('%s this repository is present in database but it '
117 'cannot be created as an scm instance',
111 'cannot be created as an scm instance',
118 dbr.repo_name)
112 dbr.repo_name)
119 continue
113 continue
120
114
121
115
122 last_change = scmr.last_change
116 last_change = scmr.last_change
123 tip = h.get_changeset_safe(scmr, 'tip')
117 tip = h.get_changeset_safe(scmr, 'tip')
124
118
125 tmp_d = {}
119 tmp_d = {}
126 tmp_d['name'] = dbr.repo_name
120 tmp_d['name'] = dbr.repo_name
127 tmp_d['name_sort'] = tmp_d['name'].lower()
121 tmp_d['name_sort'] = tmp_d['name'].lower()
128 tmp_d['description'] = dbr.description
122 tmp_d['description'] = dbr.description
129 tmp_d['description_sort'] = tmp_d['description']
123 tmp_d['description_sort'] = tmp_d['description']
130 tmp_d['last_change'] = last_change
124 tmp_d['last_change'] = last_change
131 tmp_d['last_change_sort'] = time.mktime(last_change \
125 tmp_d['last_change_sort'] = time.mktime(last_change \
132 .timetuple())
126 .timetuple())
133 tmp_d['tip'] = tip.raw_id
127 tmp_d['tip'] = tip.raw_id
134 tmp_d['tip_sort'] = tip.revision
128 tmp_d['tip_sort'] = tip.revision
135 tmp_d['rev'] = tip.revision
129 tmp_d['rev'] = tip.revision
136 tmp_d['contact'] = dbr.user.full_contact
130 tmp_d['contact'] = dbr.user.full_contact
137 tmp_d['contact_sort'] = tmp_d['contact']
131 tmp_d['contact_sort'] = tmp_d['contact']
138 tmp_d['owner_sort'] = tmp_d['contact']
132 tmp_d['owner_sort'] = tmp_d['contact']
139 tmp_d['repo_archives'] = list(scmr._get_archives())
133 tmp_d['repo_archives'] = list(scmr._get_archives())
140 tmp_d['last_msg'] = tip.message
134 tmp_d['last_msg'] = tip.message
141 tmp_d['repo'] = scmr
135 tmp_d['repo'] = scmr
142 tmp_d['dbrepo'] = dbr.get_dict()
136 tmp_d['dbrepo'] = dbr.get_dict()
143 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork \
137 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork \
144 else {}
138 else {}
145 yield tmp_d
139 yield tmp_d
146
140
147 class ScmModel(BaseModel):
141 class ScmModel(BaseModel):
148 """Generic Scm Model
142 """Generic Scm Model
149 """
143 """
150
144
151 @LazyProperty
145 @LazyProperty
152 def repos_path(self):
146 def repos_path(self):
153 """Get's the repositories root path from database
147 """Get's the repositories root path from database
154 """
148 """
155
149
156 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
150 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
157
151
158 return q.ui_value
152 return q.ui_value
159
153
160 def repo_scan(self, repos_path=None):
154 def repo_scan(self, repos_path=None):
161 """Listing of repositories in given path. This path should not be a
155 """Listing of repositories in given path. This path should not be a
162 repository itself. Return a dictionary of repository objects
156 repository itself. Return a dictionary of repository objects
163
157
164 :param repos_path: path to directory containing repositories
158 :param repos_path: path to directory containing repositories
165 """
159 """
166
160
167 log.info('scanning for repositories in %s', repos_path)
161 log.info('scanning for repositories in %s', repos_path)
168
162
169 if repos_path is None:
163 if repos_path is None:
170 repos_path = self.repos_path
164 repos_path = self.repos_path
171
165
172 baseui = make_ui('db')
166 baseui = make_ui('db')
173 repos_list = {}
167 repos_list = {}
174
168
175 for name, path in get_filesystem_repos(repos_path, recursive=True):
169 for name, path in get_filesystem_repos(repos_path, recursive=True):
176 try:
170 try:
177 if name in repos_list:
171 if name in repos_list:
178 raise RepositoryError('Duplicate repository name %s '
172 raise RepositoryError('Duplicate repository name %s '
179 'found in %s' % (name, path))
173 'found in %s' % (name, path))
180 else:
174 else:
181
175
182 klass = get_backend(path[0])
176 klass = get_backend(path[0])
183
177
184 if path[0] == 'hg' and path[0] in BACKENDS.keys():
178 if path[0] == 'hg' and path[0] in BACKENDS.keys():
185 repos_list[name] = klass(path[1], baseui=baseui)
179
180 # for mercurial we need to have an str path
181 repos_list[name] = klass(safe_str(path[1]),
182 baseui=baseui)
186
183
187 if path[0] == 'git' and path[0] in BACKENDS.keys():
184 if path[0] == 'git' and path[0] in BACKENDS.keys():
188 repos_list[name] = klass(path[1])
185 repos_list[name] = klass(path[1])
189 except OSError:
186 except OSError:
190 continue
187 continue
191
188
192 return repos_list
189 return repos_list
193
190
194 def get_repos(self, all_repos=None, sort_key=None):
191 def get_repos(self, all_repos=None, sort_key=None):
195 """
192 """
196 Get all repos from db and for each repo create it's
193 Get all repos from db and for each repo create it's
197 backend instance and fill that backed with information from database
194 backend instance and fill that backed with information from database
198
195
199 :param all_repos: list of repository names as strings
196 :param all_repos: list of repository names as strings
200 give specific repositories list, good for filtering
197 give specific repositories list, good for filtering
201 """
198 """
202 if all_repos is None:
199 if all_repos is None:
203 all_repos = self.sa.query(Repository)\
200 all_repos = self.sa.query(Repository)\
204 .filter(Repository.group_id == None)\
201 .filter(Repository.group_id == None)\
205 .order_by(Repository.repo_name).all()
202 .order_by(Repository.repo_name).all()
206
203
207 #get the repositories that should be invalidated
204 #get the repositories that should be invalidated
208 invalidation_list = [str(x.cache_key) for x in \
205 invalidation_list = [str(x.cache_key) for x in \
209 self.sa.query(CacheInvalidation.cache_key)\
206 self.sa.query(CacheInvalidation.cache_key)\
210 .filter(CacheInvalidation.cache_active == False)\
207 .filter(CacheInvalidation.cache_active == False)\
211 .all()]
208 .all()]
212
209
213 repo_iter = CachedRepoList(all_repos, invalidation_list,
210 repo_iter = CachedRepoList(all_repos, invalidation_list,
214 repos_path=self.repos_path,
211 repos_path=self.repos_path,
215 order_by=sort_key)
212 order_by=sort_key)
216
213
217 return repo_iter
214 return repo_iter
218
215
219 def mark_for_invalidation(self, repo_name):
216 def mark_for_invalidation(self, repo_name):
220 """Puts cache invalidation task into db for
217 """Puts cache invalidation task into db for
221 further global cache invalidation
218 further global cache invalidation
222
219
223 :param repo_name: this repo that should invalidation take place
220 :param repo_name: this repo that should invalidation take place
224 """
221 """
225
222
226 log.debug('marking %s for invalidation', repo_name)
223 log.debug('marking %s for invalidation', repo_name)
227 cache = self.sa.query(CacheInvalidation)\
224 cache = self.sa.query(CacheInvalidation)\
228 .filter(CacheInvalidation.cache_key == repo_name).scalar()
225 .filter(CacheInvalidation.cache_key == repo_name).scalar()
229
226
230 if cache:
227 if cache:
231 #mark this cache as inactive
228 #mark this cache as inactive
232 cache.cache_active = False
229 cache.cache_active = False
233 else:
230 else:
234 log.debug('cache key not found in invalidation db -> creating one')
231 log.debug('cache key not found in invalidation db -> creating one')
235 cache = CacheInvalidation(repo_name)
232 cache = CacheInvalidation(repo_name)
236
233
237 try:
234 try:
238 self.sa.add(cache)
235 self.sa.add(cache)
239 self.sa.commit()
236 self.sa.commit()
240 except (DatabaseError,):
237 except (DatabaseError,):
241 log.error(traceback.format_exc())
238 log.error(traceback.format_exc())
242 self.sa.rollback()
239 self.sa.rollback()
243
240
244 def toggle_following_repo(self, follow_repo_id, user_id):
241 def toggle_following_repo(self, follow_repo_id, user_id):
245
242
246 f = self.sa.query(UserFollowing)\
243 f = self.sa.query(UserFollowing)\
247 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
244 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
248 .filter(UserFollowing.user_id == user_id).scalar()
245 .filter(UserFollowing.user_id == user_id).scalar()
249
246
250 if f is not None:
247 if f is not None:
251
248
252 try:
249 try:
253 self.sa.delete(f)
250 self.sa.delete(f)
254 self.sa.commit()
251 self.sa.commit()
255 action_logger(UserTemp(user_id),
252 action_logger(UserTemp(user_id),
256 'stopped_following_repo',
253 'stopped_following_repo',
257 RepoTemp(follow_repo_id))
254 RepoTemp(follow_repo_id))
258 return
255 return
259 except:
256 except:
260 log.error(traceback.format_exc())
257 log.error(traceback.format_exc())
261 self.sa.rollback()
258 self.sa.rollback()
262 raise
259 raise
263
260
264 try:
261 try:
265 f = UserFollowing()
262 f = UserFollowing()
266 f.user_id = user_id
263 f.user_id = user_id
267 f.follows_repo_id = follow_repo_id
264 f.follows_repo_id = follow_repo_id
268 self.sa.add(f)
265 self.sa.add(f)
269 self.sa.commit()
266 self.sa.commit()
270 action_logger(UserTemp(user_id),
267 action_logger(UserTemp(user_id),
271 'started_following_repo',
268 'started_following_repo',
272 RepoTemp(follow_repo_id))
269 RepoTemp(follow_repo_id))
273 except:
270 except:
274 log.error(traceback.format_exc())
271 log.error(traceback.format_exc())
275 self.sa.rollback()
272 self.sa.rollback()
276 raise
273 raise
277
274
278 def toggle_following_user(self, follow_user_id, user_id):
275 def toggle_following_user(self, follow_user_id, user_id):
279 f = self.sa.query(UserFollowing)\
276 f = self.sa.query(UserFollowing)\
280 .filter(UserFollowing.follows_user_id == follow_user_id)\
277 .filter(UserFollowing.follows_user_id == follow_user_id)\
281 .filter(UserFollowing.user_id == user_id).scalar()
278 .filter(UserFollowing.user_id == user_id).scalar()
282
279
283 if f is not None:
280 if f is not None:
284 try:
281 try:
285 self.sa.delete(f)
282 self.sa.delete(f)
286 self.sa.commit()
283 self.sa.commit()
287 return
284 return
288 except:
285 except:
289 log.error(traceback.format_exc())
286 log.error(traceback.format_exc())
290 self.sa.rollback()
287 self.sa.rollback()
291 raise
288 raise
292
289
293 try:
290 try:
294 f = UserFollowing()
291 f = UserFollowing()
295 f.user_id = user_id
292 f.user_id = user_id
296 f.follows_user_id = follow_user_id
293 f.follows_user_id = follow_user_id
297 self.sa.add(f)
294 self.sa.add(f)
298 self.sa.commit()
295 self.sa.commit()
299 except:
296 except:
300 log.error(traceback.format_exc())
297 log.error(traceback.format_exc())
301 self.sa.rollback()
298 self.sa.rollback()
302 raise
299 raise
303
300
304 def is_following_repo(self, repo_name, user_id, cache=False):
301 def is_following_repo(self, repo_name, user_id, cache=False):
305 r = self.sa.query(Repository)\
302 r = self.sa.query(Repository)\
306 .filter(Repository.repo_name == repo_name).scalar()
303 .filter(Repository.repo_name == repo_name).scalar()
307
304
308 f = self.sa.query(UserFollowing)\
305 f = self.sa.query(UserFollowing)\
309 .filter(UserFollowing.follows_repository == r)\
306 .filter(UserFollowing.follows_repository == r)\
310 .filter(UserFollowing.user_id == user_id).scalar()
307 .filter(UserFollowing.user_id == user_id).scalar()
311
308
312 return f is not None
309 return f is not None
313
310
314 def is_following_user(self, username, user_id, cache=False):
311 def is_following_user(self, username, user_id, cache=False):
315 u = UserModel(self.sa).get_by_username(username)
312 u = UserModel(self.sa).get_by_username(username)
316
313
317 f = self.sa.query(UserFollowing)\
314 f = self.sa.query(UserFollowing)\
318 .filter(UserFollowing.follows_user == u)\
315 .filter(UserFollowing.follows_user == u)\
319 .filter(UserFollowing.user_id == user_id).scalar()
316 .filter(UserFollowing.user_id == user_id).scalar()
320
317
321 return f is not None
318 return f is not None
322
319
323 def get_followers(self, repo_id):
320 def get_followers(self, repo_id):
324 if not isinstance(repo_id, int):
321 if not isinstance(repo_id, int):
325 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
322 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
326
323
327 return self.sa.query(UserFollowing)\
324 return self.sa.query(UserFollowing)\
328 .filter(UserFollowing.follows_repo_id == repo_id).count()
325 .filter(UserFollowing.follows_repo_id == repo_id).count()
329
326
330 def get_forks(self, repo_id):
327 def get_forks(self, repo_id):
331 if not isinstance(repo_id, int):
328 if not isinstance(repo_id, int):
332 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
329 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
333
330
334 return self.sa.query(Repository)\
331 return self.sa.query(Repository)\
335 .filter(Repository.fork_id == repo_id).count()
332 .filter(Repository.fork_id == repo_id).count()
336
333
337 def pull_changes(self, repo_name, username):
334 def pull_changes(self, repo_name, username):
338 dbrepo = Repository.by_repo_name(repo_name)
335 dbrepo = Repository.by_repo_name(repo_name)
339 repo = dbrepo.scm_instance
336 repo = dbrepo.scm_instance
340 try:
337 try:
341 extras = {'ip': '',
338 extras = {'ip': '',
342 'username': username,
339 'username': username,
343 'action': 'push_remote',
340 'action': 'push_remote',
344 'repository': repo_name}
341 'repository': repo_name}
345
342
346 #inject ui extra param to log this action via push logger
343 #inject ui extra param to log this action via push logger
347 for k, v in extras.items():
344 for k, v in extras.items():
348 repo._repo.ui.setconfig('rhodecode_extras', k, v)
345 repo._repo.ui.setconfig('rhodecode_extras', k, v)
349
346
350 repo.pull(dbrepo.clone_uri)
347 repo.pull(dbrepo.clone_uri)
351 self.mark_for_invalidation(repo_name)
348 self.mark_for_invalidation(repo_name)
352 except:
349 except:
353 log.error(traceback.format_exc())
350 log.error(traceback.format_exc())
354 raise
351 raise
355
352
356
353
357 def commit_change(self, repo, repo_name, cs, user, author, message, content,
354 def commit_change(self, repo, repo_name, cs, user, author, message, content,
358 f_path):
355 f_path):
359
356
360 if repo.alias == 'hg':
357 if repo.alias == 'hg':
361 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
358 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
362 elif repo.alias == 'git':
359 elif repo.alias == 'git':
363 from vcs.backends.git import GitInMemoryChangeset as IMC
360 from vcs.backends.git import GitInMemoryChangeset as IMC
364
361
365 # decoding here will force that we have proper encoded values
362 # decoding here will force that we have proper encoded values
366 # in any other case this will throw exceptions and deny commit
363 # in any other case this will throw exceptions and deny commit
367 content = content.encode('utf8')
364 content = safe_str(content)
368 message = message.encode('utf8')
365 message = safe_str(message)
369 path = f_path.encode('utf8')
366 path = safe_str(f_path)
370 author = author.encode('utf8')
367 author = safe_str(author)
371 m = IMC(repo)
368 m = IMC(repo)
372 m.change(FileNode(path, content))
369 m.change(FileNode(path, content))
373 tip = m.commit(message=message,
370 tip = m.commit(message=message,
374 author=author,
371 author=author,
375 parents=[cs], branch=cs.branch)
372 parents=[cs], branch=cs.branch)
376
373
377 new_cs = tip.short_id
374 new_cs = tip.short_id
378 action = 'push_local:%s' % new_cs
375 action = 'push_local:%s' % new_cs
379
376
380 action_logger(user, action, repo_name)
377 action_logger(user, action, repo_name)
381
378
382 self.mark_for_invalidation(repo_name)
379 self.mark_for_invalidation(repo_name)
383
380
384
381
385 def get_unread_journal(self):
382 def get_unread_journal(self):
386 return self.sa.query(UserLog).count()
383 return self.sa.query(UserLog).count()
387
384
388 def _should_invalidate(self, repo_name):
385 def _should_invalidate(self, repo_name):
389 """Looks up database for invalidation signals for this repo_name
386 """Looks up database for invalidation signals for this repo_name
390
387
391 :param repo_name:
388 :param repo_name:
392 """
389 """
393
390
394 ret = self.sa.query(CacheInvalidation)\
391 ret = self.sa.query(CacheInvalidation)\
395 .filter(CacheInvalidation.cache_key == repo_name)\
392 .filter(CacheInvalidation.cache_key == repo_name)\
396 .filter(CacheInvalidation.cache_active == False)\
393 .filter(CacheInvalidation.cache_active == False)\
397 .scalar()
394 .scalar()
398
395
399 return ret
396 return ret
400
397
401 def _mark_invalidated(self, cache_key):
398 def _mark_invalidated(self, cache_key):
402 """ Marks all occurrences of cache to invalidation as already
399 """ Marks all occurrences of cache to invalidation as already
403 invalidated
400 invalidated
404
401
405 :param cache_key:
402 :param cache_key:
406 """
403 """
407
404
408 if cache_key:
405 if cache_key:
409 log.debug('marking %s as already invalidated', cache_key)
406 log.debug('marking %s as already invalidated', cache_key)
410 try:
407 try:
411 cache_key.cache_active = True
408 cache_key.cache_active = True
412 self.sa.add(cache_key)
409 self.sa.add(cache_key)
413 self.sa.commit()
410 self.sa.commit()
414 except (DatabaseError,):
411 except (DatabaseError,):
415 log.error(traceback.format_exc())
412 log.error(traceback.format_exc())
416 self.sa.rollback()
413 self.sa.rollback()
@@ -1,35 +1,37 b''
1 from rhodecode.tests import *
1 from rhodecode.tests import *
2 import os
2 import os
3 from nose.plugins.skip import SkipTest
3 from nose.plugins.skip import SkipTest
4
4
5 class TestSearchController(TestController):
5 class TestSearchController(TestController):
6
6
7 def test_index(self):
7 def test_index(self):
8 self.log_user()
8 self.log_user()
9 response = self.app.get(url(controller='search', action='index'))
9 response = self.app.get(url(controller='search', action='index'))
10 print response.body
10
11 assert 'class="small" id="q" name="q" type="text"' in response.body, 'Search box content error'
11 self.assertTrue('class="small" id="q" name="q" type="text"' in
12 response.body)
12 # Test response...
13 # Test response...
13
14
14 def test_empty_search(self):
15 def test_empty_search(self):
15 if os.path.isdir(self.index_location):
16 if os.path.isdir(self.index_location):
16 raise SkipTest('skipped due to existing index')
17 raise SkipTest('skipped due to existing index')
17 else:
18 else:
18 self.log_user()
19 self.log_user()
19 response = self.app.get(url(controller='search', action='index'), {'q':HG_REPO})
20 response = self.app.get(url(controller='search', action='index'),
20 assert 'There is no index to search in. Please run whoosh indexer' in response.body, 'No error message about empty index'
21 {'q':HG_REPO})
22 self.assertTrue('There is no index to search in. '
23 'Please run whoosh indexer' in response.body)
21
24
22 def test_normal_search(self):
25 def test_normal_search(self):
23 self.log_user()
26 self.log_user()
24 response = self.app.get(url(controller='search', action='index'), {'q':'def repo'})
27 response = self.app.get(url(controller='search', action='index'),
25 print response.body
28 {'q':'def repo'})
26 assert '10 results' in response.body, 'no message about proper search results'
29 self.assertTrue('10 results' in response.body)
27 assert 'Permission denied' not in response.body, 'Wrong permissions settings for that repo and user'
30 self.assertTrue('Permission denied' not in response.body)
28
29
31
30 def test_repo_search(self):
32 def test_repo_search(self):
31 self.log_user()
33 self.log_user()
32 response = self.app.get(url(controller='search', action='index'), {'q':'repository:%s def test' % HG_REPO})
34 response = self.app.get(url(controller='search', action='index'),
33 print response.body
35 {'q':'repository:%s def test' % HG_REPO})
34 assert '4 results' in response.body, 'no message about proper search results'
36 self.assertTrue('4 results' in response.body)
35 assert 'Permission denied' not in response.body, 'Wrong permissions settings for that repo and user'
37 self.assertTrue('Permission denied' not in response.body)
General Comments 0
You need to be logged in to leave comments. Login now