##// END OF EJS Templates
Change git & hg hooks to post. They shouldn't block as they are used just for logging actions. Futhermore post hooks have access to changesets, so it's much better flexible
marcink -
r2407:8a68e029 beta
parent child Browse files
Show More
@@ -1,257 +1,257 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.hooks
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Hooks runned by rhodecode
7 7
8 8 :created_on: Aug 6, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import os
26 26 import sys
27 27 import binascii
28 28 from inspect import isfunction
29 29
30 30 from mercurial.scmutil import revrange
31 31 from mercurial.node import nullrev
32 32
33 33 from rhodecode.lib import helpers as h
34 34 from rhodecode.lib.utils import action_logger
35 35 from rhodecode.lib.vcs.backends.base import EmptyChangeset
36 36
37 37
38 38 def _get_scm_size(alias, root_path):
39 39
40 40 if not alias.startswith('.'):
41 41 alias += '.'
42 42
43 43 size_scm, size_root = 0, 0
44 44 for path, dirs, files in os.walk(root_path):
45 45 if path.find(alias) != -1:
46 46 for f in files:
47 47 try:
48 48 size_scm += os.path.getsize(os.path.join(path, f))
49 49 except OSError:
50 50 pass
51 51 else:
52 52 for f in files:
53 53 try:
54 54 size_root += os.path.getsize(os.path.join(path, f))
55 55 except OSError:
56 56 pass
57 57
58 58 size_scm_f = h.format_byte_size(size_scm)
59 59 size_root_f = h.format_byte_size(size_root)
60 60 size_total_f = h.format_byte_size(size_root + size_scm)
61 61
62 62 return size_scm_f, size_root_f, size_total_f
63 63
64 64
65 65 def repo_size(ui, repo, hooktype=None, **kwargs):
66 66 """
67 67 Presents size of repository after push
68 68
69 69 :param ui:
70 70 :param repo:
71 71 :param hooktype:
72 72 """
73 73
74 74 size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
75 75
76 76 last_cs = repo[len(repo) - 1]
77 77
78 78 msg = ('Repository size .hg:%s repo:%s total:%s\n'
79 79 'Last revision is now r%s:%s\n') % (
80 80 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
81 81 )
82 82
83 83 sys.stdout.write(msg)
84 84
85 85
86 86 def log_pull_action(ui, repo, **kwargs):
87 87 """
88 88 Logs user last pull action
89 89
90 90 :param ui:
91 91 :param repo:
92 92 """
93 93
94 94 extras = dict(repo.ui.configitems('rhodecode_extras'))
95 95 username = extras['username']
96 96 repository = extras['repository']
97 97 scm = extras['scm']
98 98 action = 'pull'
99 99
100 100 action_logger(username, action, repository, extras['ip'], commit=True)
101 101 # extension hook call
102 102 from rhodecode import EXTENSIONS
103 103 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
104 104
105 105 if isfunction(callback):
106 106 kw = {}
107 107 kw.update(extras)
108 108 callback(**kw)
109 109 return 0
110 110
111 111
112 112 def log_push_action(ui, repo, **kwargs):
113 113 """
114 114 Maps user last push action to new changeset id, from mercurial
115 115
116 116 :param ui:
117 117 :param repo: repo object containing the `ui` object
118 118 """
119 119
120 120 extras = dict(repo.ui.configitems('rhodecode_extras'))
121 121 username = extras['username']
122 122 repository = extras['repository']
123 123 action = extras['action'] + ':%s'
124 124 scm = extras['scm']
125 125
126 126 if scm == 'hg':
127 127 node = kwargs['node']
128 128
129 129 def get_revs(repo, rev_opt):
130 130 if rev_opt:
131 131 revs = revrange(repo, rev_opt)
132 132
133 133 if len(revs) == 0:
134 134 return (nullrev, nullrev)
135 135 return (max(revs), min(revs))
136 136 else:
137 137 return (len(repo) - 1, 0)
138 138
139 139 stop, start = get_revs(repo, [node + ':'])
140 140 h = binascii.hexlify
141 revs = (h(repo[r].node()) for r in xrange(start, stop + 1))
141 revs = [h(repo[r].node()) for r in xrange(start, stop + 1)]
142 142 elif scm == 'git':
143 143 revs = kwargs.get('_git_revs', [])
144 144 if '_git_revs' in kwargs:
145 145 kwargs.pop('_git_revs')
146 146
147 147 action = action % ','.join(revs)
148 148
149 149 action_logger(username, action, repository, extras['ip'], commit=True)
150 150
151 151 # extension hook call
152 152 from rhodecode import EXTENSIONS
153 153 callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
154 154 if isfunction(callback):
155 155 kw = {'pushed_revs': revs}
156 156 kw.update(extras)
157 157 callback(**kw)
158 158 return 0
159 159
160 160
161 161 def log_create_repository(repository_dict, created_by, **kwargs):
162 162 """
163 163 Post create repository Hook. This is a dummy function for admins to re-use
164 164 if needed. It's taken from rhodecode-extensions module and executed
165 165 if present
166 166
167 167 :param repository: dict dump of repository object
168 168 :param created_by: username who created repository
169 169 :param created_date: date of creation
170 170
171 171 available keys of repository_dict:
172 172
173 173 'repo_type',
174 174 'description',
175 175 'private',
176 176 'created_on',
177 177 'enable_downloads',
178 178 'repo_id',
179 179 'user_id',
180 180 'enable_statistics',
181 181 'clone_uri',
182 182 'fork_id',
183 183 'group_id',
184 184 'repo_name'
185 185
186 186 """
187 187 from rhodecode import EXTENSIONS
188 188 callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
189 189 if isfunction(callback):
190 190 kw = {}
191 191 kw.update(repository_dict)
192 192 kw.update({'created_by': created_by})
193 193 kw.update(kwargs)
194 194 return callback(**kw)
195 195
196 196 return 0
197 197
198 198
199 199 def handle_git_post_receive(repo_path, revs, env):
200 200 """
201 A really hacky method that is runned by git pre-receive hook and logs
201 A really hacky method that is runned by git post-receive hook and logs
202 202 an push action together with pushed revisions. It's runned by subprocess
203 203 thus needs all info to be able to create a temp pylons enviroment, connect
204 204 to database and run the logging code. Hacky as sh**t but works. ps.
205 205 GIT SUCKS
206 206
207 207 :param repo_path:
208 208 :type repo_path:
209 209 :param revs:
210 210 :type revs:
211 211 :param env:
212 212 :type env:
213 213 """
214 214 from paste.deploy import appconfig
215 215 from sqlalchemy import engine_from_config
216 216 from rhodecode.config.environment import load_environment
217 217 from rhodecode.model import init_model
218 218 from rhodecode.model.db import RhodeCodeUi
219 219 from rhodecode.lib.utils import make_ui
220 220 from rhodecode.model.db import Repository
221 221
222 222 path, ini_name = os.path.split(env['RHODECODE_CONFIG_FILE'])
223 223 conf = appconfig('config:%s' % ini_name, relative_to=path)
224 224 load_environment(conf.global_conf, conf.local_conf)
225 225
226 226 engine = engine_from_config(conf, 'sqlalchemy.db1.')
227 227 init_model(engine)
228 228
229 229 baseui = make_ui('db')
230 230 repo = Repository.get_by_full_path(repo_path)
231 231
232 232 _hooks = dict(baseui.configitems('hooks')) or {}
233 233 # if push hook is enabled via web interface
234 234 if _hooks.get(RhodeCodeUi.HOOK_PUSH):
235 235
236 236 extras = {
237 237 'username': env['RHODECODE_USER'],
238 238 'repository': repo.repo_name,
239 239 'scm': 'git',
240 240 'action': 'push',
241 241 'ip': env['RHODECODE_CONFIG_IP'],
242 242 }
243 243 for k, v in extras.items():
244 244 baseui.setconfig('rhodecode_extras', k, v)
245 245 repo = repo.scm_instance
246 246 repo.ui = baseui
247 247 old_rev, new_rev, ref = revs
248 248 if old_rev == EmptyChangeset().raw_id:
249 249 cmd = "for-each-ref --format='%(refname)' 'refs/heads/*'"
250 250 heads = repo.run_git_command(cmd)[0]
251 251 heads = heads.replace(ref, '')
252 252 cmd = 'log ' + new_rev + ' --reverse --pretty=format:"%H" --not ' + heads
253 253 else:
254 254 cmd = 'log ' + old_rev + '..' + new_rev + ' --reverse --pretty=format:"%H"'
255 255 git_revs = repo.run_git_command(cmd)[0].splitlines()
256 256
257 257 log_push_action(baseui, repo, _git_revs=git_revs)
@@ -1,304 +1,304 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.middleware.simplegit
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 SimpleGit middleware for handling git protocol request (push/clone etc.)
7 7 It's implemented with basic auth function
8 8
9 9 :created_on: Apr 28, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import re
29 29 import logging
30 30 import traceback
31 31
32 32 from dulwich import server as dulserver
33 33
34 34
35 35 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
36 36
37 37 def handle(self):
38 38 write = lambda x: self.proto.write_sideband(1, x)
39 39
40 40 graph_walker = dulserver.ProtocolGraphWalker(self,
41 41 self.repo.object_store,
42 42 self.repo.get_peeled)
43 43 objects_iter = self.repo.fetch_objects(
44 44 graph_walker.determine_wants, graph_walker, self.progress,
45 45 get_tagged=self.get_tagged)
46 46
47 47 # Did the process short-circuit (e.g. in a stateless RPC call)? Note
48 48 # that the client still expects a 0-object pack in most cases.
49 49 if objects_iter is None:
50 50 return
51 51
52 52 self.progress("counting objects: %d, done.\n" % len(objects_iter))
53 53 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
54 54 objects_iter)
55 55 messages = []
56 56 messages.append('thank you for using rhodecode')
57 57
58 58 for msg in messages:
59 59 self.progress(msg + "\n")
60 60 # we are done
61 61 self.proto.write("0000")
62 62
63 63
64 64 dulserver.DEFAULT_HANDLERS = {
65 65 #git-ls-remote, git-clone, git-fetch and git-pull
66 66 'git-upload-pack': SimpleGitUploadPackHandler,
67 67 #git-push
68 68 'git-receive-pack': dulserver.ReceivePackHandler,
69 69 }
70 70
71 71 # not used for now until dulwich get's fixed
72 72 #from dulwich.repo import Repo
73 73 #from dulwich.web import make_wsgi_chain
74 74
75 75 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
76 76
77 77 from rhodecode.lib.utils2 import safe_str
78 78 from rhodecode.lib.base import BaseVCSController
79 79 from rhodecode.lib.auth import get_container_username
80 80 from rhodecode.lib.utils import is_valid_repo, make_ui
81 81 from rhodecode.model.db import User, RhodeCodeUi
82 82
83 83 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
84 84
85 85 log = logging.getLogger(__name__)
86 86
87 87
88 88 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
89 89
90 90
91 91 def is_git(environ):
92 92 path_info = environ['PATH_INFO']
93 93 isgit_path = GIT_PROTO_PAT.match(path_info)
94 94 log.debug('pathinfo: %s detected as GIT %s' % (
95 95 path_info, isgit_path != None)
96 96 )
97 97 return isgit_path
98 98
99 99
100 100 class SimpleGit(BaseVCSController):
101 101
102 102 def _handle_request(self, environ, start_response):
103 103
104 104 if not is_git(environ):
105 105 return self.application(environ, start_response)
106 106
107 107 ipaddr = self._get_ip_addr(environ)
108 108 username = None
109 109 self._git_first_op = False
110 110 # skip passing error to error controller
111 111 environ['pylons.status_code_redirect'] = True
112 112
113 113 #======================================================================
114 114 # EXTRACT REPOSITORY NAME FROM ENV
115 115 #======================================================================
116 116 try:
117 117 repo_name = self.__get_repository(environ)
118 118 log.debug('Extracted repo name is %s' % repo_name)
119 119 except:
120 120 return HTTPInternalServerError()(environ, start_response)
121 121
122 122 # quick check if that dir exists...
123 123 if is_valid_repo(repo_name, self.basepath) is False:
124 124 return HTTPNotFound()(environ, start_response)
125 125
126 126 #======================================================================
127 127 # GET ACTION PULL or PUSH
128 128 #======================================================================
129 129 action = self.__get_action(environ)
130 130
131 131 #======================================================================
132 132 # CHECK ANONYMOUS PERMISSION
133 133 #======================================================================
134 134 if action in ['pull', 'push']:
135 135 anonymous_user = self.__get_user('default')
136 136 username = anonymous_user.username
137 137 anonymous_perm = self._check_permission(action, anonymous_user,
138 138 repo_name)
139 139
140 140 if anonymous_perm is not True or anonymous_user.active is False:
141 141 if anonymous_perm is not True:
142 142 log.debug('Not enough credentials to access this '
143 143 'repository as anonymous user')
144 144 if anonymous_user.active is False:
145 145 log.debug('Anonymous access is disabled, running '
146 146 'authentication')
147 147 #==============================================================
148 148 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
149 149 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
150 150 #==============================================================
151 151
152 152 # Attempting to retrieve username from the container
153 153 username = get_container_username(environ, self.config)
154 154
155 155 # If not authenticated by the container, running basic auth
156 156 if not username:
157 157 self.authenticate.realm = \
158 158 safe_str(self.config['rhodecode_realm'])
159 159 result = self.authenticate(environ)
160 160 if isinstance(result, str):
161 161 AUTH_TYPE.update(environ, 'basic')
162 162 REMOTE_USER.update(environ, result)
163 163 username = result
164 164 else:
165 165 return result.wsgi_application(environ, start_response)
166 166
167 167 #==============================================================
168 168 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
169 169 #==============================================================
170 170 if action in ['pull', 'push']:
171 171 try:
172 172 user = self.__get_user(username)
173 173 if user is None or not user.active:
174 174 return HTTPForbidden()(environ, start_response)
175 175 username = user.username
176 176 except:
177 177 log.error(traceback.format_exc())
178 178 return HTTPInternalServerError()(environ,
179 179 start_response)
180 180
181 181 #check permissions for this repository
182 182 perm = self._check_permission(action, user, repo_name)
183 183 if perm is not True:
184 184 return HTTPForbidden()(environ, start_response)
185 185 extras = {
186 186 'ip': ipaddr,
187 187 'username': username,
188 188 'action': action,
189 189 'repository': repo_name,
190 190 'scm': 'git',
191 191 }
192 192
193 193 #===================================================================
194 194 # GIT REQUEST HANDLING
195 195 #===================================================================
196 196 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
197 197 log.debug('Repository path is %s' % repo_path)
198 198
199 199 baseui = make_ui('db')
200 200 self.__inject_extras(repo_path, baseui, extras)
201 201
202 202 try:
203 203 # invalidate cache on push
204 204 if action == 'push':
205 205 self._invalidate_cache(repo_name)
206 206 self._handle_githooks(repo_name, action, baseui, environ)
207 207
208 208 log.info('%s action on GIT repo "%s"' % (action, repo_name))
209 209 app = self.__make_app(repo_name, repo_path, username)
210 210 return app(environ, start_response)
211 211 except Exception:
212 212 log.error(traceback.format_exc())
213 213 return HTTPInternalServerError()(environ, start_response)
214 214
215 215 def __make_app(self, repo_name, repo_path, username):
216 216 """
217 217 Make an wsgi application using dulserver
218 218
219 219 :param repo_name: name of the repository
220 220 :param repo_path: full path to the repository
221 221 """
222 222
223 223 from rhodecode.lib.middleware.pygrack import make_wsgi_app
224 224 app = make_wsgi_app(
225 225 repo_root=os.path.dirname(repo_path),
226 226 repo_name=repo_name,
227 227 username=username,
228 228 )
229 229 return app
230 230
231 231 def __get_repository(self, environ):
232 232 """
233 233 Get's repository name out of PATH_INFO header
234 234
235 235 :param environ: environ where PATH_INFO is stored
236 236 """
237 237 try:
238 238 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
239 239 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
240 240 except:
241 241 log.error(traceback.format_exc())
242 242 raise
243 243
244 244 return repo_name
245 245
246 246 def __get_user(self, username):
247 247 return User.get_by_username(username)
248 248
249 249 def __get_action(self, environ):
250 250 """
251 251 Maps git request commands into a pull or push command.
252 252
253 253 :param environ:
254 254 """
255 255 service = environ['QUERY_STRING'].split('=')
256 256
257 257 if len(service) > 1:
258 258 service_cmd = service[1]
259 259 mapping = {
260 260 'git-receive-pack': 'push',
261 261 'git-upload-pack': 'pull',
262 262 }
263 263 op = mapping[service_cmd]
264 264 self._git_stored_op = op
265 265 return op
266 266 else:
267 267 # try to fallback to stored variable as we don't know if the last
268 268 # operation is pull/push
269 269 op = getattr(self, '_git_stored_op', 'pull')
270 270 return op
271 271
272 272 def _handle_githooks(self, repo_name, action, baseui, environ):
273 273 """
274 Handles pull action, push is handled by pre-receive hook
274 Handles pull action, push is handled by post-receive hook
275 275 """
276 276 from rhodecode.lib.hooks import log_pull_action
277 277 service = environ['QUERY_STRING'].split('=')
278 278 if len(service) < 2:
279 279 return
280 280
281 281 from rhodecode.model.db import Repository
282 282 _repo = Repository.get_by_repo_name(repo_name)
283 283 _repo = _repo.scm_instance
284 284 _repo._repo.ui = baseui
285 285
286 286 _hooks = dict(baseui.configitems('hooks')) or {}
287 287 if action == 'pull' and _hooks.get(RhodeCodeUi.HOOK_PULL):
288 288 log_pull_action(ui=baseui, repo=_repo._repo)
289 289
290 290 def __inject_extras(self, repo_path, baseui, extras={}):
291 291 """
292 292 Injects some extra params into baseui instance
293 293
294 294 :param baseui: baseui instance
295 295 :param extras: dict with extra params to put into baseui
296 296 """
297 297
298 298 # make our hgweb quiet so it doesn't print output
299 299 baseui.setconfig('ui', 'quiet', 'true')
300 300
301 301 #inject some additional parameters that will be available in ui
302 302 #for hooks
303 303 for k, v in extras.items():
304 304 baseui.setconfig('rhodecode_extras', k, v)
@@ -1,1348 +1,1348 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 import hashlib
31 31 from collections import defaultdict
32 32
33 33 from sqlalchemy import *
34 34 from sqlalchemy.ext.hybrid import hybrid_property
35 35 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 36 from beaker.cache import cache_region, region_invalidate
37 37
38 38 from rhodecode.lib.vcs import get_backend
39 39 from rhodecode.lib.vcs.utils.helpers import get_scm
40 40 from rhodecode.lib.vcs.exceptions import VCSError
41 41 from rhodecode.lib.vcs.utils.lazy import LazyProperty
42 42
43 43 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
44 44 safe_unicode
45 45 from rhodecode.lib.compat import json
46 46 from rhodecode.lib.caching_query import FromCache
47 47 from rhodecode.model.meta import Base, Session
48 48
49 49
50 50 URL_SEP = '/'
51 51 log = logging.getLogger(__name__)
52 52
53 53 #==============================================================================
54 54 # BASE CLASSES
55 55 #==============================================================================
56 56
57 57 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
58 58
59 59
60 60 class ModelSerializer(json.JSONEncoder):
61 61 """
62 62 Simple Serializer for JSON,
63 63
64 64 usage::
65 65
66 66 to make object customized for serialization implement a __json__
67 67 method that will return a dict for serialization into json
68 68
69 69 example::
70 70
71 71 class Task(object):
72 72
73 73 def __init__(self, name, value):
74 74 self.name = name
75 75 self.value = value
76 76
77 77 def __json__(self):
78 78 return dict(name=self.name,
79 79 value=self.value)
80 80
81 81 """
82 82
83 83 def default(self, obj):
84 84
85 85 if hasattr(obj, '__json__'):
86 86 return obj.__json__()
87 87 else:
88 88 return json.JSONEncoder.default(self, obj)
89 89
90 90
91 91 class BaseModel(object):
92 92 """
93 93 Base Model for all classess
94 94 """
95 95
96 96 @classmethod
97 97 def _get_keys(cls):
98 98 """return column names for this model """
99 99 return class_mapper(cls).c.keys()
100 100
101 101 def get_dict(self):
102 102 """
103 103 return dict with keys and values corresponding
104 104 to this model data """
105 105
106 106 d = {}
107 107 for k in self._get_keys():
108 108 d[k] = getattr(self, k)
109 109
110 110 # also use __json__() if present to get additional fields
111 111 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
112 112 d[k] = val
113 113 return d
114 114
115 115 def get_appstruct(self):
116 116 """return list with keys and values tupples corresponding
117 117 to this model data """
118 118
119 119 l = []
120 120 for k in self._get_keys():
121 121 l.append((k, getattr(self, k),))
122 122 return l
123 123
124 124 def populate_obj(self, populate_dict):
125 125 """populate model with data from given populate_dict"""
126 126
127 127 for k in self._get_keys():
128 128 if k in populate_dict:
129 129 setattr(self, k, populate_dict[k])
130 130
131 131 @classmethod
132 132 def query(cls):
133 133 return Session.query(cls)
134 134
135 135 @classmethod
136 136 def get(cls, id_):
137 137 if id_:
138 138 return cls.query().get(id_)
139 139
140 140 @classmethod
141 141 def getAll(cls):
142 142 return cls.query().all()
143 143
144 144 @classmethod
145 145 def delete(cls, id_):
146 146 obj = cls.query().get(id_)
147 147 Session.delete(obj)
148 148
149 149 def __repr__(self):
150 150 if hasattr(self, '__unicode__'):
151 151 # python repr needs to return str
152 152 return safe_str(self.__unicode__())
153 153 return '<DB:%s>' % (self.__class__.__name__)
154 154
155 155
156 156 class RhodeCodeSetting(Base, BaseModel):
157 157 __tablename__ = 'rhodecode_settings'
158 158 __table_args__ = (
159 159 UniqueConstraint('app_settings_name'),
160 160 {'extend_existing': True, 'mysql_engine': 'InnoDB',
161 161 'mysql_charset': 'utf8'}
162 162 )
163 163 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
164 164 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
165 165 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
166 166
167 167 def __init__(self, k='', v=''):
168 168 self.app_settings_name = k
169 169 self.app_settings_value = v
170 170
171 171 @validates('_app_settings_value')
172 172 def validate_settings_value(self, key, val):
173 173 assert type(val) == unicode
174 174 return val
175 175
176 176 @hybrid_property
177 177 def app_settings_value(self):
178 178 v = self._app_settings_value
179 179 if self.app_settings_name == 'ldap_active':
180 180 v = str2bool(v)
181 181 return v
182 182
183 183 @app_settings_value.setter
184 184 def app_settings_value(self, val):
185 185 """
186 186 Setter that will always make sure we use unicode in app_settings_value
187 187
188 188 :param val:
189 189 """
190 190 self._app_settings_value = safe_unicode(val)
191 191
192 192 def __unicode__(self):
193 193 return u"<%s('%s:%s')>" % (
194 194 self.__class__.__name__,
195 195 self.app_settings_name, self.app_settings_value
196 196 )
197 197
198 198 @classmethod
199 199 def get_by_name(cls, ldap_key):
200 200 return cls.query()\
201 201 .filter(cls.app_settings_name == ldap_key).scalar()
202 202
203 203 @classmethod
204 204 def get_app_settings(cls, cache=False):
205 205
206 206 ret = cls.query()
207 207
208 208 if cache:
209 209 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
210 210
211 211 if not ret:
212 212 raise Exception('Could not get application settings !')
213 213 settings = {}
214 214 for each in ret:
215 215 settings['rhodecode_' + each.app_settings_name] = \
216 216 each.app_settings_value
217 217
218 218 return settings
219 219
220 220 @classmethod
221 221 def get_ldap_settings(cls, cache=False):
222 222 ret = cls.query()\
223 223 .filter(cls.app_settings_name.startswith('ldap_')).all()
224 224 fd = {}
225 225 for row in ret:
226 226 fd.update({row.app_settings_name: row.app_settings_value})
227 227
228 228 return fd
229 229
230 230
231 231 class RhodeCodeUi(Base, BaseModel):
232 232 __tablename__ = 'rhodecode_ui'
233 233 __table_args__ = (
234 234 UniqueConstraint('ui_key'),
235 235 {'extend_existing': True, 'mysql_engine': 'InnoDB',
236 236 'mysql_charset': 'utf8'}
237 237 )
238 238
239 239 HOOK_UPDATE = 'changegroup.update'
240 240 HOOK_REPO_SIZE = 'changegroup.repo_size'
241 HOOK_PUSH = 'pretxnchangegroup.push_logger'
241 HOOK_PUSH = 'changegroup.push_logger'
242 242 HOOK_PULL = 'preoutgoing.pull_logger'
243 243
244 244 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
245 245 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
246 246 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
247 247 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
248 248 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
249 249
250 250 @classmethod
251 251 def get_by_key(cls, key):
252 252 return cls.query().filter(cls.ui_key == key)
253 253
254 254 @classmethod
255 255 def get_builtin_hooks(cls):
256 256 q = cls.query()
257 257 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
258 258 cls.HOOK_REPO_SIZE,
259 259 cls.HOOK_PUSH, cls.HOOK_PULL]))
260 260 return q.all()
261 261
262 262 @classmethod
263 263 def get_custom_hooks(cls):
264 264 q = cls.query()
265 265 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
266 266 cls.HOOK_REPO_SIZE,
267 267 cls.HOOK_PUSH, cls.HOOK_PULL]))
268 268 q = q.filter(cls.ui_section == 'hooks')
269 269 return q.all()
270 270
271 271 @classmethod
272 272 def get_repos_location(cls):
273 273 return cls.get_by_key('/').one().ui_value
274 274
275 275 @classmethod
276 276 def create_or_update_hook(cls, key, val):
277 277 new_ui = cls.get_by_key(key).scalar() or cls()
278 278 new_ui.ui_section = 'hooks'
279 279 new_ui.ui_active = True
280 280 new_ui.ui_key = key
281 281 new_ui.ui_value = val
282 282
283 283 Session.add(new_ui)
284 284
285 285
286 286 class User(Base, BaseModel):
287 287 __tablename__ = 'users'
288 288 __table_args__ = (
289 289 UniqueConstraint('username'), UniqueConstraint('email'),
290 290 {'extend_existing': True, 'mysql_engine': 'InnoDB',
291 291 'mysql_charset': 'utf8'}
292 292 )
293 293 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
294 294 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
295 295 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
296 296 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
297 297 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
298 298 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
299 299 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
300 300 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
301 301 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
302 302 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
303 303 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
304 304
305 305 user_log = relationship('UserLog', cascade='all')
306 306 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
307 307
308 308 repositories = relationship('Repository')
309 309 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
310 310 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
311 311 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
312 312
313 313 group_member = relationship('UsersGroupMember', cascade='all')
314 314
315 315 notifications = relationship('UserNotification', cascade='all')
316 316 # notifications assigned to this user
317 317 user_created_notifications = relationship('Notification', cascade='all')
318 318 # comments created by this user
319 319 user_comments = relationship('ChangesetComment', cascade='all')
320 320
321 321 @hybrid_property
322 322 def email(self):
323 323 return self._email
324 324
325 325 @email.setter
326 326 def email(self, val):
327 327 self._email = val.lower() if val else None
328 328
329 329 @property
330 330 def full_name(self):
331 331 return '%s %s' % (self.name, self.lastname)
332 332
333 333 @property
334 334 def full_name_or_username(self):
335 335 return ('%s %s' % (self.name, self.lastname)
336 336 if (self.name and self.lastname) else self.username)
337 337
338 338 @property
339 339 def full_contact(self):
340 340 return '%s %s <%s>' % (self.name, self.lastname, self.email)
341 341
342 342 @property
343 343 def short_contact(self):
344 344 return '%s %s' % (self.name, self.lastname)
345 345
346 346 @property
347 347 def is_admin(self):
348 348 return self.admin
349 349
350 350 def __unicode__(self):
351 351 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
352 352 self.user_id, self.username)
353 353
354 354 @classmethod
355 355 def get_by_username(cls, username, case_insensitive=False, cache=False):
356 356 if case_insensitive:
357 357 q = cls.query().filter(cls.username.ilike(username))
358 358 else:
359 359 q = cls.query().filter(cls.username == username)
360 360
361 361 if cache:
362 362 q = q.options(FromCache(
363 363 "sql_cache_short",
364 364 "get_user_%s" % _hash_key(username)
365 365 )
366 366 )
367 367 return q.scalar()
368 368
369 369 @classmethod
370 370 def get_by_api_key(cls, api_key, cache=False):
371 371 q = cls.query().filter(cls.api_key == api_key)
372 372
373 373 if cache:
374 374 q = q.options(FromCache("sql_cache_short",
375 375 "get_api_key_%s" % api_key))
376 376 return q.scalar()
377 377
378 378 @classmethod
379 379 def get_by_email(cls, email, case_insensitive=False, cache=False):
380 380 if case_insensitive:
381 381 q = cls.query().filter(cls.email.ilike(email))
382 382 else:
383 383 q = cls.query().filter(cls.email == email)
384 384
385 385 if cache:
386 386 q = q.options(FromCache("sql_cache_short",
387 387 "get_api_key_%s" % email))
388 388 return q.scalar()
389 389
390 390 def update_lastlogin(self):
391 391 """Update user lastlogin"""
392 392 self.last_login = datetime.datetime.now()
393 393 Session.add(self)
394 394 log.debug('updated user %s lastlogin' % self.username)
395 395
396 396 def __json__(self):
397 397 return dict(
398 398 user_id=self.user_id,
399 399 first_name=self.name,
400 400 last_name=self.lastname,
401 401 email=self.email,
402 402 full_name=self.full_name,
403 403 full_name_or_username=self.full_name_or_username,
404 404 short_contact=self.short_contact,
405 405 full_contact=self.full_contact
406 406 )
407 407
408 408
409 409 class UserLog(Base, BaseModel):
410 410 __tablename__ = 'user_logs'
411 411 __table_args__ = (
412 412 {'extend_existing': True, 'mysql_engine': 'InnoDB',
413 413 'mysql_charset': 'utf8'},
414 414 )
415 415 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
416 416 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
417 417 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
418 418 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
419 419 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
420 420 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
421 421 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
422 422
423 423 @property
424 424 def action_as_day(self):
425 425 return datetime.date(*self.action_date.timetuple()[:3])
426 426
427 427 user = relationship('User')
428 428 repository = relationship('Repository', cascade='')
429 429
430 430
431 431 class UsersGroup(Base, BaseModel):
432 432 __tablename__ = 'users_groups'
433 433 __table_args__ = (
434 434 {'extend_existing': True, 'mysql_engine': 'InnoDB',
435 435 'mysql_charset': 'utf8'},
436 436 )
437 437
438 438 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
439 439 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
440 440 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
441 441
442 442 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
443 443 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
444 444 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
445 445
446 446 def __unicode__(self):
447 447 return u'<userGroup(%s)>' % (self.users_group_name)
448 448
449 449 @classmethod
450 450 def get_by_group_name(cls, group_name, cache=False,
451 451 case_insensitive=False):
452 452 if case_insensitive:
453 453 q = cls.query().filter(cls.users_group_name.ilike(group_name))
454 454 else:
455 455 q = cls.query().filter(cls.users_group_name == group_name)
456 456 if cache:
457 457 q = q.options(FromCache(
458 458 "sql_cache_short",
459 459 "get_user_%s" % _hash_key(group_name)
460 460 )
461 461 )
462 462 return q.scalar()
463 463
464 464 @classmethod
465 465 def get(cls, users_group_id, cache=False):
466 466 users_group = cls.query()
467 467 if cache:
468 468 users_group = users_group.options(FromCache("sql_cache_short",
469 469 "get_users_group_%s" % users_group_id))
470 470 return users_group.get(users_group_id)
471 471
472 472
473 473 class UsersGroupMember(Base, BaseModel):
474 474 __tablename__ = 'users_groups_members'
475 475 __table_args__ = (
476 476 {'extend_existing': True, 'mysql_engine': 'InnoDB',
477 477 'mysql_charset': 'utf8'},
478 478 )
479 479
480 480 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
481 481 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
482 482 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
483 483
484 484 user = relationship('User', lazy='joined')
485 485 users_group = relationship('UsersGroup')
486 486
487 487 def __init__(self, gr_id='', u_id=''):
488 488 self.users_group_id = gr_id
489 489 self.user_id = u_id
490 490
491 491
492 492 class Repository(Base, BaseModel):
493 493 __tablename__ = 'repositories'
494 494 __table_args__ = (
495 495 UniqueConstraint('repo_name'),
496 496 {'extend_existing': True, 'mysql_engine': 'InnoDB',
497 497 'mysql_charset': 'utf8'},
498 498 )
499 499
500 500 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
501 501 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
502 502 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
503 503 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
504 504 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
505 505 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
506 506 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
507 507 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
508 508 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
509 509 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
510 510
511 511 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
512 512 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
513 513
514 514 user = relationship('User')
515 515 fork = relationship('Repository', remote_side=repo_id)
516 516 group = relationship('RepoGroup')
517 517 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
518 518 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
519 519 stats = relationship('Statistics', cascade='all', uselist=False)
520 520
521 521 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
522 522
523 523 logs = relationship('UserLog')
524 524
525 525 def __unicode__(self):
526 526 return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id,
527 527 self.repo_name)
528 528
529 529 @classmethod
530 530 def url_sep(cls):
531 531 return URL_SEP
532 532
533 533 @classmethod
534 534 def get_by_repo_name(cls, repo_name):
535 535 q = Session.query(cls).filter(cls.repo_name == repo_name)
536 536 q = q.options(joinedload(Repository.fork))\
537 537 .options(joinedload(Repository.user))\
538 538 .options(joinedload(Repository.group))
539 539 return q.scalar()
540 540
541 541 @classmethod
542 542 def get_by_full_path(cls, repo_full_path):
543 543 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
544 544 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
545 545
546 546 @classmethod
547 547 def get_repo_forks(cls, repo_id):
548 548 return cls.query().filter(Repository.fork_id == repo_id)
549 549
550 550 @classmethod
551 551 def base_path(cls):
552 552 """
553 553 Returns base path when all repos are stored
554 554
555 555 :param cls:
556 556 """
557 557 q = Session.query(RhodeCodeUi)\
558 558 .filter(RhodeCodeUi.ui_key == cls.url_sep())
559 559 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
560 560 return q.one().ui_value
561 561
562 562 @property
563 563 def just_name(self):
564 564 return self.repo_name.split(Repository.url_sep())[-1]
565 565
566 566 @property
567 567 def groups_with_parents(self):
568 568 groups = []
569 569 if self.group is None:
570 570 return groups
571 571
572 572 cur_gr = self.group
573 573 groups.insert(0, cur_gr)
574 574 while 1:
575 575 gr = getattr(cur_gr, 'parent_group', None)
576 576 cur_gr = cur_gr.parent_group
577 577 if gr is None:
578 578 break
579 579 groups.insert(0, gr)
580 580
581 581 return groups
582 582
583 583 @property
584 584 def groups_and_repo(self):
585 585 return self.groups_with_parents, self.just_name
586 586
587 587 @LazyProperty
588 588 def repo_path(self):
589 589 """
590 590 Returns base full path for that repository means where it actually
591 591 exists on a filesystem
592 592 """
593 593 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
594 594 Repository.url_sep())
595 595 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
596 596 return q.one().ui_value
597 597
598 598 @property
599 599 def repo_full_path(self):
600 600 p = [self.repo_path]
601 601 # we need to split the name by / since this is how we store the
602 602 # names in the database, but that eventually needs to be converted
603 603 # into a valid system path
604 604 p += self.repo_name.split(Repository.url_sep())
605 605 return os.path.join(*p)
606 606
607 607 def get_new_name(self, repo_name):
608 608 """
609 609 returns new full repository name based on assigned group and new new
610 610
611 611 :param group_name:
612 612 """
613 613 path_prefix = self.group.full_path_splitted if self.group else []
614 614 return Repository.url_sep().join(path_prefix + [repo_name])
615 615
616 616 @property
617 617 def _ui(self):
618 618 """
619 619 Creates an db based ui object for this repository
620 620 """
621 621 from mercurial import ui
622 622 from mercurial import config
623 623 baseui = ui.ui()
624 624
625 625 #clean the baseui object
626 626 baseui._ocfg = config.config()
627 627 baseui._ucfg = config.config()
628 628 baseui._tcfg = config.config()
629 629
630 630 ret = RhodeCodeUi.query()\
631 631 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
632 632
633 633 hg_ui = ret
634 634 for ui_ in hg_ui:
635 635 if ui_.ui_active:
636 636 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
637 637 ui_.ui_key, ui_.ui_value)
638 638 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
639 639
640 640 return baseui
641 641
642 642 @classmethod
643 643 def is_valid(cls, repo_name):
644 644 """
645 645 returns True if given repo name is a valid filesystem repository
646 646
647 647 :param cls:
648 648 :param repo_name:
649 649 """
650 650 from rhodecode.lib.utils import is_valid_repo
651 651
652 652 return is_valid_repo(repo_name, cls.base_path())
653 653
654 654 #==========================================================================
655 655 # SCM PROPERTIES
656 656 #==========================================================================
657 657
658 658 def get_changeset(self, rev=None):
659 659 return get_changeset_safe(self.scm_instance, rev)
660 660
661 661 @property
662 662 def tip(self):
663 663 return self.get_changeset('tip')
664 664
665 665 @property
666 666 def author(self):
667 667 return self.tip.author
668 668
669 669 @property
670 670 def last_change(self):
671 671 return self.scm_instance.last_change
672 672
673 673 def comments(self, revisions=None):
674 674 """
675 675 Returns comments for this repository grouped by revisions
676 676
677 677 :param revisions: filter query by revisions only
678 678 """
679 679 cmts = ChangesetComment.query()\
680 680 .filter(ChangesetComment.repo == self)
681 681 if revisions:
682 682 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
683 683 grouped = defaultdict(list)
684 684 for cmt in cmts.all():
685 685 grouped[cmt.revision].append(cmt)
686 686 return grouped
687 687
688 688 #==========================================================================
689 689 # SCM CACHE INSTANCE
690 690 #==========================================================================
691 691
692 692 @property
693 693 def invalidate(self):
694 694 return CacheInvalidation.invalidate(self.repo_name)
695 695
696 696 def set_invalidate(self):
697 697 """
698 698 set a cache for invalidation for this instance
699 699 """
700 700 CacheInvalidation.set_invalidate(self.repo_name)
701 701
702 702 @LazyProperty
703 703 def scm_instance(self):
704 704 return self.__get_instance()
705 705
706 706 def scm_instance_cached(self, cache_map=None):
707 707 @cache_region('long_term')
708 708 def _c(repo_name):
709 709 return self.__get_instance()
710 710 rn = self.repo_name
711 711 log.debug('Getting cached instance of repo')
712 712
713 713 if cache_map:
714 714 # get using prefilled cache_map
715 715 invalidate_repo = cache_map[self.repo_name]
716 716 if invalidate_repo:
717 717 invalidate_repo = (None if invalidate_repo.cache_active
718 718 else invalidate_repo)
719 719 else:
720 720 # get from invalidate
721 721 invalidate_repo = self.invalidate
722 722
723 723 if invalidate_repo is not None:
724 724 region_invalidate(_c, None, rn)
725 725 # update our cache
726 726 CacheInvalidation.set_valid(invalidate_repo.cache_key)
727 727 return _c(rn)
728 728
729 729 def __get_instance(self):
730 730 repo_full_path = self.repo_full_path
731 731 try:
732 732 alias = get_scm(repo_full_path)[0]
733 733 log.debug('Creating instance of %s repository' % alias)
734 734 backend = get_backend(alias)
735 735 except VCSError:
736 736 log.error(traceback.format_exc())
737 737 log.error('Perhaps this repository is in db and not in '
738 738 'filesystem run rescan repositories with '
739 739 '"destroy old data " option from admin panel')
740 740 return
741 741
742 742 if alias == 'hg':
743 743
744 744 repo = backend(safe_str(repo_full_path), create=False,
745 745 baseui=self._ui)
746 746 # skip hidden web repository
747 747 if repo._get_hidden():
748 748 return
749 749 else:
750 750 repo = backend(repo_full_path, create=False)
751 751
752 752 return repo
753 753
754 754
755 755 class RepoGroup(Base, BaseModel):
756 756 __tablename__ = 'groups'
757 757 __table_args__ = (
758 758 UniqueConstraint('group_name', 'group_parent_id'),
759 759 CheckConstraint('group_id != group_parent_id'),
760 760 {'extend_existing': True, 'mysql_engine': 'InnoDB',
761 761 'mysql_charset': 'utf8'},
762 762 )
763 763 __mapper_args__ = {'order_by': 'group_name'}
764 764
765 765 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
766 766 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
767 767 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
768 768 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
769 769
770 770 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
771 771 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
772 772
773 773 parent_group = relationship('RepoGroup', remote_side=group_id)
774 774
775 775 def __init__(self, group_name='', parent_group=None):
776 776 self.group_name = group_name
777 777 self.parent_group = parent_group
778 778
779 779 def __unicode__(self):
780 780 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
781 781 self.group_name)
782 782
783 783 @classmethod
784 784 def groups_choices(cls):
785 785 from webhelpers.html import literal as _literal
786 786 repo_groups = [('', '')]
787 787 sep = ' &raquo; '
788 788 _name = lambda k: _literal(sep.join(k))
789 789
790 790 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
791 791 for x in cls.query().all()])
792 792
793 793 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
794 794 return repo_groups
795 795
796 796 @classmethod
797 797 def url_sep(cls):
798 798 return URL_SEP
799 799
800 800 @classmethod
801 801 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
802 802 if case_insensitive:
803 803 gr = cls.query()\
804 804 .filter(cls.group_name.ilike(group_name))
805 805 else:
806 806 gr = cls.query()\
807 807 .filter(cls.group_name == group_name)
808 808 if cache:
809 809 gr = gr.options(FromCache(
810 810 "sql_cache_short",
811 811 "get_group_%s" % _hash_key(group_name)
812 812 )
813 813 )
814 814 return gr.scalar()
815 815
816 816 @property
817 817 def parents(self):
818 818 parents_recursion_limit = 5
819 819 groups = []
820 820 if self.parent_group is None:
821 821 return groups
822 822 cur_gr = self.parent_group
823 823 groups.insert(0, cur_gr)
824 824 cnt = 0
825 825 while 1:
826 826 cnt += 1
827 827 gr = getattr(cur_gr, 'parent_group', None)
828 828 cur_gr = cur_gr.parent_group
829 829 if gr is None:
830 830 break
831 831 if cnt == parents_recursion_limit:
832 832 # this will prevent accidental infinit loops
833 833 log.error('group nested more than %s' %
834 834 parents_recursion_limit)
835 835 break
836 836
837 837 groups.insert(0, gr)
838 838 return groups
839 839
840 840 @property
841 841 def children(self):
842 842 return RepoGroup.query().filter(RepoGroup.parent_group == self)
843 843
844 844 @property
845 845 def name(self):
846 846 return self.group_name.split(RepoGroup.url_sep())[-1]
847 847
848 848 @property
849 849 def full_path(self):
850 850 return self.group_name
851 851
852 852 @property
853 853 def full_path_splitted(self):
854 854 return self.group_name.split(RepoGroup.url_sep())
855 855
856 856 @property
857 857 def repositories(self):
858 858 return Repository.query()\
859 859 .filter(Repository.group == self)\
860 860 .order_by(Repository.repo_name)
861 861
862 862 @property
863 863 def repositories_recursive_count(self):
864 864 cnt = self.repositories.count()
865 865
866 866 def children_count(group):
867 867 cnt = 0
868 868 for child in group.children:
869 869 cnt += child.repositories.count()
870 870 cnt += children_count(child)
871 871 return cnt
872 872
873 873 return cnt + children_count(self)
874 874
875 875 def get_new_name(self, group_name):
876 876 """
877 877 returns new full group name based on parent and new name
878 878
879 879 :param group_name:
880 880 """
881 881 path_prefix = (self.parent_group.full_path_splitted if
882 882 self.parent_group else [])
883 883 return RepoGroup.url_sep().join(path_prefix + [group_name])
884 884
885 885
886 886 class Permission(Base, BaseModel):
887 887 __tablename__ = 'permissions'
888 888 __table_args__ = (
889 889 {'extend_existing': True, 'mysql_engine': 'InnoDB',
890 890 'mysql_charset': 'utf8'},
891 891 )
892 892 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
893 893 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
894 894 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
895 895
896 896 def __unicode__(self):
897 897 return u"<%s('%s:%s')>" % (
898 898 self.__class__.__name__, self.permission_id, self.permission_name
899 899 )
900 900
901 901 @classmethod
902 902 def get_by_key(cls, key):
903 903 return cls.query().filter(cls.permission_name == key).scalar()
904 904
905 905 @classmethod
906 906 def get_default_perms(cls, default_user_id):
907 907 q = Session.query(UserRepoToPerm, Repository, cls)\
908 908 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
909 909 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
910 910 .filter(UserRepoToPerm.user_id == default_user_id)
911 911
912 912 return q.all()
913 913
914 914 @classmethod
915 915 def get_default_group_perms(cls, default_user_id):
916 916 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
917 917 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
918 918 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
919 919 .filter(UserRepoGroupToPerm.user_id == default_user_id)
920 920
921 921 return q.all()
922 922
923 923
924 924 class UserRepoToPerm(Base, BaseModel):
925 925 __tablename__ = 'repo_to_perm'
926 926 __table_args__ = (
927 927 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
928 928 {'extend_existing': True, 'mysql_engine': 'InnoDB',
929 929 'mysql_charset': 'utf8'}
930 930 )
931 931 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
932 932 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
933 933 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
934 934 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
935 935
936 936 user = relationship('User')
937 937 repository = relationship('Repository')
938 938 permission = relationship('Permission')
939 939
940 940 @classmethod
941 941 def create(cls, user, repository, permission):
942 942 n = cls()
943 943 n.user = user
944 944 n.repository = repository
945 945 n.permission = permission
946 946 Session.add(n)
947 947 return n
948 948
949 949 def __unicode__(self):
950 950 return u'<user:%s => %s >' % (self.user, self.repository)
951 951
952 952
953 953 class UserToPerm(Base, BaseModel):
954 954 __tablename__ = 'user_to_perm'
955 955 __table_args__ = (
956 956 UniqueConstraint('user_id', 'permission_id'),
957 957 {'extend_existing': True, 'mysql_engine': 'InnoDB',
958 958 'mysql_charset': 'utf8'}
959 959 )
960 960 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
961 961 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
962 962 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
963 963
964 964 user = relationship('User')
965 965 permission = relationship('Permission', lazy='joined')
966 966
967 967
968 968 class UsersGroupRepoToPerm(Base, BaseModel):
969 969 __tablename__ = 'users_group_repo_to_perm'
970 970 __table_args__ = (
971 971 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
972 972 {'extend_existing': True, 'mysql_engine': 'InnoDB',
973 973 'mysql_charset': 'utf8'}
974 974 )
975 975 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
976 976 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
977 977 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
978 978 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
979 979
980 980 users_group = relationship('UsersGroup')
981 981 permission = relationship('Permission')
982 982 repository = relationship('Repository')
983 983
984 984 @classmethod
985 985 def create(cls, users_group, repository, permission):
986 986 n = cls()
987 987 n.users_group = users_group
988 988 n.repository = repository
989 989 n.permission = permission
990 990 Session.add(n)
991 991 return n
992 992
993 993 def __unicode__(self):
994 994 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
995 995
996 996
997 997 class UsersGroupToPerm(Base, BaseModel):
998 998 __tablename__ = 'users_group_to_perm'
999 999 __table_args__ = (
1000 1000 UniqueConstraint('users_group_id', 'permission_id',),
1001 1001 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1002 1002 'mysql_charset': 'utf8'}
1003 1003 )
1004 1004 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1005 1005 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1006 1006 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1007 1007
1008 1008 users_group = relationship('UsersGroup')
1009 1009 permission = relationship('Permission')
1010 1010
1011 1011
1012 1012 class UserRepoGroupToPerm(Base, BaseModel):
1013 1013 __tablename__ = 'user_repo_group_to_perm'
1014 1014 __table_args__ = (
1015 1015 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1016 1016 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1017 1017 'mysql_charset': 'utf8'}
1018 1018 )
1019 1019
1020 1020 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1021 1021 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1022 1022 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1023 1023 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1024 1024
1025 1025 user = relationship('User')
1026 1026 group = relationship('RepoGroup')
1027 1027 permission = relationship('Permission')
1028 1028
1029 1029
1030 1030 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1031 1031 __tablename__ = 'users_group_repo_group_to_perm'
1032 1032 __table_args__ = (
1033 1033 UniqueConstraint('users_group_id', 'group_id'),
1034 1034 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1035 1035 'mysql_charset': 'utf8'}
1036 1036 )
1037 1037
1038 1038 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1039 1039 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1040 1040 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1041 1041 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1042 1042
1043 1043 users_group = relationship('UsersGroup')
1044 1044 permission = relationship('Permission')
1045 1045 group = relationship('RepoGroup')
1046 1046
1047 1047
1048 1048 class Statistics(Base, BaseModel):
1049 1049 __tablename__ = 'statistics'
1050 1050 __table_args__ = (
1051 1051 UniqueConstraint('repository_id'),
1052 1052 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1053 1053 'mysql_charset': 'utf8'}
1054 1054 )
1055 1055 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1056 1056 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1057 1057 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1058 1058 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1059 1059 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1060 1060 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1061 1061
1062 1062 repository = relationship('Repository', single_parent=True)
1063 1063
1064 1064
1065 1065 class UserFollowing(Base, BaseModel):
1066 1066 __tablename__ = 'user_followings'
1067 1067 __table_args__ = (
1068 1068 UniqueConstraint('user_id', 'follows_repository_id'),
1069 1069 UniqueConstraint('user_id', 'follows_user_id'),
1070 1070 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1071 1071 'mysql_charset': 'utf8'}
1072 1072 )
1073 1073
1074 1074 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1075 1075 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1076 1076 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1077 1077 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1078 1078 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1079 1079
1080 1080 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1081 1081
1082 1082 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1083 1083 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1084 1084
1085 1085 @classmethod
1086 1086 def get_repo_followers(cls, repo_id):
1087 1087 return cls.query().filter(cls.follows_repo_id == repo_id)
1088 1088
1089 1089
1090 1090 class CacheInvalidation(Base, BaseModel):
1091 1091 __tablename__ = 'cache_invalidation'
1092 1092 __table_args__ = (
1093 1093 UniqueConstraint('cache_key'),
1094 1094 Index('key_idx', 'cache_key'),
1095 1095 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1096 1096 'mysql_charset': 'utf8'},
1097 1097 )
1098 1098 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1099 1099 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1100 1100 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1101 1101 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1102 1102
1103 1103 def __init__(self, cache_key, cache_args=''):
1104 1104 self.cache_key = cache_key
1105 1105 self.cache_args = cache_args
1106 1106 self.cache_active = False
1107 1107
1108 1108 def __unicode__(self):
1109 1109 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1110 1110 self.cache_id, self.cache_key)
1111 1111
1112 1112 @classmethod
1113 1113 def clear_cache(cls):
1114 1114 cls.query().delete()
1115 1115
1116 1116 @classmethod
1117 1117 def _get_key(cls, key):
1118 1118 """
1119 1119 Wrapper for generating a key, together with a prefix
1120 1120
1121 1121 :param key:
1122 1122 """
1123 1123 import rhodecode
1124 1124 prefix = ''
1125 1125 iid = rhodecode.CONFIG.get('instance_id')
1126 1126 if iid:
1127 1127 prefix = iid
1128 1128 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1129 1129
1130 1130 @classmethod
1131 1131 def get_by_key(cls, key):
1132 1132 return cls.query().filter(cls.cache_key == key).scalar()
1133 1133
1134 1134 @classmethod
1135 1135 def _get_or_create_key(cls, key, prefix, org_key):
1136 1136 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1137 1137 if not inv_obj:
1138 1138 try:
1139 1139 inv_obj = CacheInvalidation(key, org_key)
1140 1140 Session.add(inv_obj)
1141 1141 Session.commit()
1142 1142 except Exception:
1143 1143 log.error(traceback.format_exc())
1144 1144 Session.rollback()
1145 1145 return inv_obj
1146 1146
1147 1147 @classmethod
1148 1148 def invalidate(cls, key):
1149 1149 """
1150 1150 Returns Invalidation object if this given key should be invalidated
1151 1151 None otherwise. `cache_active = False` means that this cache
1152 1152 state is not valid and needs to be invalidated
1153 1153
1154 1154 :param key:
1155 1155 """
1156 1156
1157 1157 key, _prefix, _org_key = cls._get_key(key)
1158 1158 inv = cls._get_or_create_key(key, _prefix, _org_key)
1159 1159
1160 1160 if inv and inv.cache_active is False:
1161 1161 return inv
1162 1162
1163 1163 @classmethod
1164 1164 def set_invalidate(cls, key):
1165 1165 """
1166 1166 Mark this Cache key for invalidation
1167 1167
1168 1168 :param key:
1169 1169 """
1170 1170
1171 1171 key, _prefix, _org_key = cls._get_key(key)
1172 1172 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1173 1173 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1174 1174 _org_key))
1175 1175 try:
1176 1176 for inv_obj in inv_objs:
1177 1177 if inv_obj:
1178 1178 inv_obj.cache_active = False
1179 1179
1180 1180 Session.add(inv_obj)
1181 1181 Session.commit()
1182 1182 except Exception:
1183 1183 log.error(traceback.format_exc())
1184 1184 Session.rollback()
1185 1185
1186 1186 @classmethod
1187 1187 def set_valid(cls, key):
1188 1188 """
1189 1189 Mark this cache key as active and currently cached
1190 1190
1191 1191 :param key:
1192 1192 """
1193 1193 inv_obj = cls.get_by_key(key)
1194 1194 inv_obj.cache_active = True
1195 1195 Session.add(inv_obj)
1196 1196 Session.commit()
1197 1197
1198 1198 @classmethod
1199 1199 def get_cache_map(cls):
1200 1200
1201 1201 class cachemapdict(dict):
1202 1202
1203 1203 def __init__(self, *args, **kwargs):
1204 1204 fixkey = kwargs.get('fixkey')
1205 1205 if fixkey:
1206 1206 del kwargs['fixkey']
1207 1207 self.fixkey = fixkey
1208 1208 super(cachemapdict, self).__init__(*args, **kwargs)
1209 1209
1210 1210 def __getattr__(self, name):
1211 1211 key = name
1212 1212 if self.fixkey:
1213 1213 key, _prefix, _org_key = cls._get_key(key)
1214 1214 if key in self.__dict__:
1215 1215 return self.__dict__[key]
1216 1216 else:
1217 1217 return self[key]
1218 1218
1219 1219 def __getitem__(self, key):
1220 1220 if self.fixkey:
1221 1221 key, _prefix, _org_key = cls._get_key(key)
1222 1222 try:
1223 1223 return super(cachemapdict, self).__getitem__(key)
1224 1224 except KeyError:
1225 1225 return
1226 1226
1227 1227 cache_map = cachemapdict(fixkey=True)
1228 1228 for obj in cls.query().all():
1229 1229 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1230 1230 return cache_map
1231 1231
1232 1232
1233 1233 class ChangesetComment(Base, BaseModel):
1234 1234 __tablename__ = 'changeset_comments'
1235 1235 __table_args__ = (
1236 1236 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1237 1237 'mysql_charset': 'utf8'},
1238 1238 )
1239 1239 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1240 1240 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1241 1241 revision = Column('revision', String(40), nullable=False)
1242 1242 line_no = Column('line_no', Unicode(10), nullable=True)
1243 1243 f_path = Column('f_path', Unicode(1000), nullable=True)
1244 1244 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1245 1245 text = Column('text', Unicode(25000), nullable=False)
1246 1246 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1247 1247
1248 1248 author = relationship('User', lazy='joined')
1249 1249 repo = relationship('Repository')
1250 1250
1251 1251 @classmethod
1252 1252 def get_users(cls, revision):
1253 1253 """
1254 1254 Returns user associated with this changesetComment. ie those
1255 1255 who actually commented
1256 1256
1257 1257 :param cls:
1258 1258 :param revision:
1259 1259 """
1260 1260 return Session.query(User)\
1261 1261 .filter(cls.revision == revision)\
1262 1262 .join(ChangesetComment.author).all()
1263 1263
1264 1264
1265 1265 class Notification(Base, BaseModel):
1266 1266 __tablename__ = 'notifications'
1267 1267 __table_args__ = (
1268 1268 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1269 1269 'mysql_charset': 'utf8'},
1270 1270 )
1271 1271
1272 1272 TYPE_CHANGESET_COMMENT = u'cs_comment'
1273 1273 TYPE_MESSAGE = u'message'
1274 1274 TYPE_MENTION = u'mention'
1275 1275 TYPE_REGISTRATION = u'registration'
1276 1276
1277 1277 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1278 1278 subject = Column('subject', Unicode(512), nullable=True)
1279 1279 body = Column('body', Unicode(50000), nullable=True)
1280 1280 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1281 1281 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1282 1282 type_ = Column('type', Unicode(256))
1283 1283
1284 1284 created_by_user = relationship('User')
1285 1285 notifications_to_users = relationship('UserNotification', lazy='joined',
1286 1286 cascade="all, delete, delete-orphan")
1287 1287
1288 1288 @property
1289 1289 def recipients(self):
1290 1290 return [x.user for x in UserNotification.query()\
1291 1291 .filter(UserNotification.notification == self)\
1292 1292 .order_by(UserNotification.user).all()]
1293 1293
1294 1294 @classmethod
1295 1295 def create(cls, created_by, subject, body, recipients, type_=None):
1296 1296 if type_ is None:
1297 1297 type_ = Notification.TYPE_MESSAGE
1298 1298
1299 1299 notification = cls()
1300 1300 notification.created_by_user = created_by
1301 1301 notification.subject = subject
1302 1302 notification.body = body
1303 1303 notification.type_ = type_
1304 1304 notification.created_on = datetime.datetime.now()
1305 1305
1306 1306 for u in recipients:
1307 1307 assoc = UserNotification()
1308 1308 assoc.notification = notification
1309 1309 u.notifications.append(assoc)
1310 1310 Session.add(notification)
1311 1311 return notification
1312 1312
1313 1313 @property
1314 1314 def description(self):
1315 1315 from rhodecode.model.notification import NotificationModel
1316 1316 return NotificationModel().make_description(self)
1317 1317
1318 1318
1319 1319 class UserNotification(Base, BaseModel):
1320 1320 __tablename__ = 'user_to_notification'
1321 1321 __table_args__ = (
1322 1322 UniqueConstraint('user_id', 'notification_id'),
1323 1323 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1324 1324 'mysql_charset': 'utf8'}
1325 1325 )
1326 1326 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1327 1327 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1328 1328 read = Column('read', Boolean, default=False)
1329 1329 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1330 1330
1331 1331 user = relationship('User', lazy="joined")
1332 1332 notification = relationship('Notification', lazy="joined",
1333 1333 order_by=lambda: Notification.created_on.desc(),)
1334 1334
1335 1335 def mark_as_read(self):
1336 1336 self.read = True
1337 1337 Session.add(self)
1338 1338
1339 1339
1340 1340 class DbMigrateVersion(Base, BaseModel):
1341 1341 __tablename__ = 'db_migrate_version'
1342 1342 __table_args__ = (
1343 1343 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1344 1344 'mysql_charset': 'utf8'},
1345 1345 )
1346 1346 repository_id = Column('repository_id', String(250), primary_key=True)
1347 1347 repository_path = Column('repository_path', Text)
1348 1348 version = Column('version', Integer)
@@ -1,773 +1,773 b''
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14 14
15 15
16 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 import os
23 23 import re
24 24 import logging
25 25 import traceback
26 26
27 27 import formencode
28 28 from formencode import All
29 29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
30 30 Email, Bool, StringBoolean, Set
31 31
32 32 from pylons.i18n.translation import _
33 33 from webhelpers.pylonslib.secure_form import authentication_token
34 34
35 35 from rhodecode.config.routing import ADMIN_PREFIX
36 36 from rhodecode.lib.utils import repo_name_slug
37 37 from rhodecode.lib.auth import authenticate, get_crypt_password
38 38 from rhodecode.lib.exceptions import LdapImportError
39 39 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
40 40 from rhodecode import BACKENDS
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44
45 45 #this is needed to translate the messages using _() in validators
46 46 class State_obj(object):
47 47 _ = staticmethod(_)
48 48
49 49
50 50 #==============================================================================
51 51 # VALIDATORS
52 52 #==============================================================================
53 53 class ValidAuthToken(formencode.validators.FancyValidator):
54 54 messages = {'invalid_token': _('Token mismatch')}
55 55
56 56 def validate_python(self, value, state):
57 57
58 58 if value != authentication_token():
59 59 raise formencode.Invalid(
60 60 self.message('invalid_token',
61 61 state, search_number=value),
62 62 value,
63 63 state
64 64 )
65 65
66 66
67 67 def ValidUsername(edit, old_data):
68 68 class _ValidUsername(formencode.validators.FancyValidator):
69 69
70 70 def validate_python(self, value, state):
71 71 if value in ['default', 'new_user']:
72 72 raise formencode.Invalid(_('Invalid username'), value, state)
73 73 #check if user is unique
74 74 old_un = None
75 75 if edit:
76 76 old_un = User.get(old_data.get('user_id')).username
77 77
78 78 if old_un != value or not edit:
79 79 if User.get_by_username(value, case_insensitive=True):
80 80 raise formencode.Invalid(_('This username already '
81 81 'exists') , value, state)
82 82
83 83 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
84 84 raise formencode.Invalid(
85 85 _('Username may only contain alphanumeric characters '
86 86 'underscores, periods or dashes and must begin with '
87 87 'alphanumeric character'),
88 88 value,
89 89 state
90 90 )
91 91
92 92 return _ValidUsername
93 93
94 94
95 95 def ValidUsersGroup(edit, old_data):
96 96
97 97 class _ValidUsersGroup(formencode.validators.FancyValidator):
98 98
99 99 def validate_python(self, value, state):
100 100 if value in ['default']:
101 101 raise formencode.Invalid(_('Invalid group name'), value, state)
102 102 #check if group is unique
103 103 old_ugname = None
104 104 if edit:
105 105 old_ugname = UsersGroup.get(
106 106 old_data.get('users_group_id')).users_group_name
107 107
108 108 if old_ugname != value or not edit:
109 109 if UsersGroup.get_by_group_name(value, cache=False,
110 110 case_insensitive=True):
111 111 raise formencode.Invalid(_('This users group '
112 112 'already exists'), value,
113 113 state)
114 114
115 115 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
116 116 raise formencode.Invalid(
117 117 _('RepoGroup name may only contain alphanumeric characters '
118 118 'underscores, periods or dashes and must begin with '
119 119 'alphanumeric character'),
120 120 value,
121 121 state
122 122 )
123 123
124 124 return _ValidUsersGroup
125 125
126 126
127 127 def ValidReposGroup(edit, old_data):
128 128 class _ValidReposGroup(formencode.validators.FancyValidator):
129 129
130 130 def validate_python(self, value, state):
131 131 # TODO WRITE VALIDATIONS
132 132 group_name = value.get('group_name')
133 133 group_parent_id = value.get('group_parent_id')
134 134
135 135 # slugify repo group just in case :)
136 136 slug = repo_name_slug(group_name)
137 137
138 138 # check for parent of self
139 139 parent_of_self = lambda: (
140 140 old_data['group_id'] == int(group_parent_id)
141 141 if group_parent_id else False
142 142 )
143 143 if edit and parent_of_self():
144 144 e_dict = {
145 145 'group_parent_id': _('Cannot assign this group as parent')
146 146 }
147 147 raise formencode.Invalid('', value, state,
148 148 error_dict=e_dict)
149 149
150 150 old_gname = None
151 151 if edit:
152 152 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
153 153
154 154 if old_gname != group_name or not edit:
155 155
156 156 # check group
157 157 gr = RepoGroup.query()\
158 158 .filter(RepoGroup.group_name == slug)\
159 159 .filter(RepoGroup.group_parent_id == group_parent_id)\
160 160 .scalar()
161 161
162 162 if gr:
163 163 e_dict = {
164 164 'group_name': _('This group already exists')
165 165 }
166 166 raise formencode.Invalid('', value, state,
167 167 error_dict=e_dict)
168 168
169 169 # check for same repo
170 170 repo = Repository.query()\
171 171 .filter(Repository.repo_name == slug)\
172 172 .scalar()
173 173
174 174 if repo:
175 175 e_dict = {
176 176 'group_name': _('Repository with this name already exists')
177 177 }
178 178 raise formencode.Invalid('', value, state,
179 179 error_dict=e_dict)
180 180
181 181 return _ValidReposGroup
182 182
183 183
184 184 class ValidPassword(formencode.validators.FancyValidator):
185 185
186 186 def to_python(self, value, state):
187 187
188 188 if not value:
189 189 return
190 190
191 191 if value.get('password'):
192 192 try:
193 193 value['password'] = get_crypt_password(value['password'])
194 194 except UnicodeEncodeError:
195 195 e_dict = {'password': _('Invalid characters in password')}
196 196 raise formencode.Invalid('', value, state, error_dict=e_dict)
197 197
198 198 if value.get('password_confirmation'):
199 199 try:
200 200 value['password_confirmation'] = \
201 201 get_crypt_password(value['password_confirmation'])
202 202 except UnicodeEncodeError:
203 203 e_dict = {
204 204 'password_confirmation': _('Invalid characters in password')
205 205 }
206 206 raise formencode.Invalid('', value, state, error_dict=e_dict)
207 207
208 208 if value.get('new_password'):
209 209 try:
210 210 value['new_password'] = \
211 211 get_crypt_password(value['new_password'])
212 212 except UnicodeEncodeError:
213 213 e_dict = {'new_password': _('Invalid characters in password')}
214 214 raise formencode.Invalid('', value, state, error_dict=e_dict)
215 215
216 216 return value
217 217
218 218
219 219 class ValidPasswordsMatch(formencode.validators.FancyValidator):
220 220
221 221 def validate_python(self, value, state):
222 222
223 223 pass_val = value.get('password') or value.get('new_password')
224 224 if pass_val != value['password_confirmation']:
225 225 e_dict = {'password_confirmation':
226 226 _('Passwords do not match')}
227 227 raise formencode.Invalid('', value, state, error_dict=e_dict)
228 228
229 229
230 230 class ValidAuth(formencode.validators.FancyValidator):
231 231 messages = {
232 232 'invalid_password':_('invalid password'),
233 233 'invalid_login':_('invalid user name'),
234 234 'disabled_account':_('Your account is disabled')
235 235 }
236 236
237 237 # error mapping
238 238 e_dict = {'username': messages['invalid_login'],
239 239 'password': messages['invalid_password']}
240 240 e_dict_disable = {'username': messages['disabled_account']}
241 241
242 242 def validate_python(self, value, state):
243 243 password = value['password']
244 244 username = value['username']
245 245 user = User.get_by_username(username)
246 246
247 247 if authenticate(username, password):
248 248 return value
249 249 else:
250 250 if user and user.active is False:
251 251 log.warning('user %s is disabled' % username)
252 252 raise formencode.Invalid(
253 253 self.message('disabled_account',
254 254 state=State_obj),
255 255 value, state,
256 256 error_dict=self.e_dict_disable
257 257 )
258 258 else:
259 259 log.warning('user %s failed to authenticate' % username)
260 260 raise formencode.Invalid(
261 261 self.message('invalid_password',
262 262 state=State_obj), value, state,
263 263 error_dict=self.e_dict
264 264 )
265 265
266 266
267 267 class ValidRepoUser(formencode.validators.FancyValidator):
268 268
269 269 def to_python(self, value, state):
270 270 try:
271 271 User.query().filter(User.active == True)\
272 272 .filter(User.username == value).one()
273 273 except Exception:
274 274 raise formencode.Invalid(_('This username is not valid'),
275 275 value, state)
276 276 return value
277 277
278 278
279 279 def ValidRepoName(edit, old_data):
280 280 class _ValidRepoName(formencode.validators.FancyValidator):
281 281 def to_python(self, value, state):
282 282
283 283 repo_name = value.get('repo_name')
284 284
285 285 slug = repo_name_slug(repo_name)
286 286 if slug in [ADMIN_PREFIX, '']:
287 287 e_dict = {'repo_name': _('This repository name is disallowed')}
288 288 raise formencode.Invalid('', value, state, error_dict=e_dict)
289 289
290 290 if value.get('repo_group'):
291 291 gr = RepoGroup.get(value.get('repo_group'))
292 292 group_path = gr.full_path
293 293 # value needs to be aware of group name in order to check
294 294 # db key This is an actual just the name to store in the
295 295 # database
296 296 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
297 297
298 298 else:
299 299 group_path = ''
300 300 repo_name_full = repo_name
301 301
302 302 value['repo_name_full'] = repo_name_full
303 303 rename = old_data.get('repo_name') != repo_name_full
304 304 create = not edit
305 305 if rename or create:
306 306
307 307 if group_path != '':
308 308 if Repository.get_by_repo_name(repo_name_full):
309 309 e_dict = {
310 310 'repo_name': _('This repository already exists in '
311 311 'a group "%s"') % gr.group_name
312 312 }
313 313 raise formencode.Invalid('', value, state,
314 314 error_dict=e_dict)
315 315 elif RepoGroup.get_by_group_name(repo_name_full):
316 316 e_dict = {
317 317 'repo_name': _('There is a group with this name '
318 318 'already "%s"') % repo_name_full
319 319 }
320 320 raise formencode.Invalid('', value, state,
321 321 error_dict=e_dict)
322 322
323 323 elif Repository.get_by_repo_name(repo_name_full):
324 324 e_dict = {'repo_name': _('This repository '
325 325 'already exists')}
326 326 raise formencode.Invalid('', value, state,
327 327 error_dict=e_dict)
328 328
329 329 return value
330 330
331 331 return _ValidRepoName
332 332
333 333
334 334 def ValidForkName(*args, **kwargs):
335 335 return ValidRepoName(*args, **kwargs)
336 336
337 337
338 338 def SlugifyName():
339 339 class _SlugifyName(formencode.validators.FancyValidator):
340 340
341 341 def to_python(self, value, state):
342 342 return repo_name_slug(value)
343 343
344 344 return _SlugifyName
345 345
346 346
347 347 def ValidCloneUri():
348 348 from rhodecode.lib.utils import make_ui
349 349
350 350 def url_handler(repo_type, url, proto, ui=None):
351 351 if repo_type == 'hg':
352 352 from mercurial.httprepo import httprepository, httpsrepository
353 353 if proto == 'https':
354 354 httpsrepository(make_ui('db'), url).capabilities
355 355 elif proto == 'http':
356 356 httprepository(make_ui('db'), url).capabilities
357 357 elif repo_type == 'git':
358 358 #TODO: write a git url validator
359 359 pass
360 360
361 361 class _ValidCloneUri(formencode.validators.FancyValidator):
362 362
363 363 def to_python(self, value, state):
364 364
365 365 repo_type = value.get('repo_type')
366 366 url = value.get('clone_uri')
367 367 e_dict = {'clone_uri': _('invalid clone url')}
368 368
369 369 if not url:
370 370 pass
371 371 elif url.startswith('https'):
372 372 try:
373 373 url_handler(repo_type, url, 'https', make_ui('db'))
374 374 except Exception:
375 375 log.error(traceback.format_exc())
376 376 raise formencode.Invalid('', value, state, error_dict=e_dict)
377 377 elif url.startswith('http'):
378 378 try:
379 379 url_handler(repo_type, url, 'http', make_ui('db'))
380 380 except Exception:
381 381 log.error(traceback.format_exc())
382 382 raise formencode.Invalid('', value, state, error_dict=e_dict)
383 383 else:
384 384 e_dict = {'clone_uri': _('Invalid clone url, provide a '
385 385 'valid clone http\s url')}
386 386 raise formencode.Invalid('', value, state, error_dict=e_dict)
387 387
388 388 return value
389 389
390 390 return _ValidCloneUri
391 391
392 392
393 393 def ValidForkType(old_data):
394 394 class _ValidForkType(formencode.validators.FancyValidator):
395 395
396 396 def to_python(self, value, state):
397 397 if old_data['repo_type'] != value:
398 398 raise formencode.Invalid(_('Fork have to be the same '
399 399 'type as original'), value, state)
400 400
401 401 return value
402 402 return _ValidForkType
403 403
404 404
405 405 def ValidPerms(type_='repo'):
406 406 if type_ == 'group':
407 407 EMPTY_PERM = 'group.none'
408 408 elif type_ == 'repo':
409 409 EMPTY_PERM = 'repository.none'
410 410
411 411 class _ValidPerms(formencode.validators.FancyValidator):
412 412 messages = {
413 413 'perm_new_member_name':
414 414 _('This username or users group name is not valid')
415 415 }
416 416
417 417 def to_python(self, value, state):
418 418 perms_update = []
419 419 perms_new = []
420 420 # build a list of permission to update and new permission to create
421 421 for k, v in value.items():
422 422 # means new added member to permissions
423 423 if k.startswith('perm_new_member'):
424 424 new_perm = value.get('perm_new_member', False)
425 425 new_member = value.get('perm_new_member_name', False)
426 426 new_type = value.get('perm_new_member_type')
427 427
428 428 if new_member and new_perm:
429 429 if (new_member, new_perm, new_type) not in perms_new:
430 430 perms_new.append((new_member, new_perm, new_type))
431 431 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
432 432 member = k[7:]
433 433 t = {'u': 'user',
434 434 'g': 'users_group'
435 435 }[k[0]]
436 436 if member == 'default':
437 437 if value.get('private'):
438 438 # set none for default when updating to private repo
439 439 v = EMPTY_PERM
440 440 perms_update.append((member, v, t))
441 441
442 442 value['perms_updates'] = perms_update
443 443 value['perms_new'] = perms_new
444 444
445 445 # update permissions
446 446 for k, v, t in perms_new:
447 447 try:
448 448 if t is 'user':
449 449 self.user_db = User.query()\
450 450 .filter(User.active == True)\
451 451 .filter(User.username == k).one()
452 452 if t is 'users_group':
453 453 self.user_db = UsersGroup.query()\
454 454 .filter(UsersGroup.users_group_active == True)\
455 455 .filter(UsersGroup.users_group_name == k).one()
456 456
457 457 except Exception:
458 458 msg = self.message('perm_new_member_name',
459 459 state=State_obj)
460 460 raise formencode.Invalid(
461 461 msg, value, state, error_dict={'perm_new_member_name': msg}
462 462 )
463 463 return value
464 464 return _ValidPerms
465 465
466 466
467 467 class ValidSettings(formencode.validators.FancyValidator):
468 468
469 469 def to_python(self, value, state):
470 470 # settings form can't edit user
471 471 if 'user' in value:
472 472 del['value']['user']
473 473 return value
474 474
475 475
476 476 class ValidPath(formencode.validators.FancyValidator):
477 477 def to_python(self, value, state):
478 478
479 479 if not os.path.isdir(value):
480 480 msg = _('This is not a valid path')
481 481 raise formencode.Invalid(msg, value, state,
482 482 error_dict={'paths_root_path': msg})
483 483 return value
484 484
485 485
486 486 def UniqSystemEmail(old_data):
487 487 class _UniqSystemEmail(formencode.validators.FancyValidator):
488 488 def to_python(self, value, state):
489 489 value = value.lower()
490 490 if (old_data.get('email') or '').lower() != value:
491 491 user = User.get_by_email(value, case_insensitive=True)
492 492 if user:
493 493 raise formencode.Invalid(
494 494 _("This e-mail address is already taken"), value, state
495 495 )
496 496 return value
497 497
498 498 return _UniqSystemEmail
499 499
500 500
501 501 class ValidSystemEmail(formencode.validators.FancyValidator):
502 502 def to_python(self, value, state):
503 503 value = value.lower()
504 504 user = User.get_by_email(value, case_insensitive=True)
505 505 if user is None:
506 506 raise formencode.Invalid(
507 507 _("This e-mail address doesn't exist."), value, state
508 508 )
509 509
510 510 return value
511 511
512 512
513 513 class LdapLibValidator(formencode.validators.FancyValidator):
514 514
515 515 def to_python(self, value, state):
516 516
517 517 try:
518 518 import ldap
519 519 except ImportError:
520 520 raise LdapImportError
521 521 return value
522 522
523 523
524 524 class AttrLoginValidator(formencode.validators.FancyValidator):
525 525
526 526 def to_python(self, value, state):
527 527
528 528 if not value or not isinstance(value, (str, unicode)):
529 529 raise formencode.Invalid(
530 530 _("The LDAP Login attribute of the CN must be specified - "
531 531 "this is the name of the attribute that is equivalent "
532 532 "to 'username'"), value, state
533 533 )
534 534
535 535 return value
536 536
537 537
538 538 #==============================================================================
539 539 # FORMS
540 540 #==============================================================================
541 541 class LoginForm(formencode.Schema):
542 542 allow_extra_fields = True
543 543 filter_extra_fields = True
544 544 username = UnicodeString(
545 545 strip=True,
546 546 min=1,
547 547 not_empty=True,
548 548 messages={
549 549 'empty': _('Please enter a login'),
550 550 'tooShort': _('Enter a value %(min)i characters long or more')}
551 551 )
552 552
553 553 password = UnicodeString(
554 554 strip=False,
555 555 min=3,
556 556 not_empty=True,
557 557 messages={
558 558 'empty': _('Please enter a password'),
559 559 'tooShort': _('Enter %(min)i characters or more')}
560 560 )
561 561
562 562 remember = StringBoolean(if_missing=False)
563 563
564 564 chained_validators = [ValidAuth]
565 565
566 566
567 567 def UserForm(edit=False, old_data={}):
568 568 class _UserForm(formencode.Schema):
569 569 allow_extra_fields = True
570 570 filter_extra_fields = True
571 571 username = All(UnicodeString(strip=True, min=1, not_empty=True),
572 572 ValidUsername(edit, old_data))
573 573 if edit:
574 574 new_password = All(UnicodeString(strip=False, min=6, not_empty=False))
575 575 password_confirmation = All(UnicodeString(strip=False, min=6,
576 576 not_empty=False))
577 577 admin = StringBoolean(if_missing=False)
578 578 else:
579 579 password = All(UnicodeString(strip=False, min=6, not_empty=True))
580 580 password_confirmation = All(UnicodeString(strip=False, min=6,
581 581 not_empty=False))
582 582
583 583 active = StringBoolean(if_missing=False)
584 584 name = UnicodeString(strip=True, min=1, not_empty=False)
585 585 lastname = UnicodeString(strip=True, min=1, not_empty=False)
586 586 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
587 587
588 588 chained_validators = [ValidPasswordsMatch, ValidPassword]
589 589
590 590 return _UserForm
591 591
592 592
593 593 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
594 594 class _UsersGroupForm(formencode.Schema):
595 595 allow_extra_fields = True
596 596 filter_extra_fields = True
597 597
598 598 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
599 599 ValidUsersGroup(edit, old_data))
600 600
601 601 users_group_active = StringBoolean(if_missing=False)
602 602
603 603 if edit:
604 604 users_group_members = OneOf(available_members, hideList=False,
605 605 testValueList=True,
606 606 if_missing=None, not_empty=False)
607 607
608 608 return _UsersGroupForm
609 609
610 610
611 611 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
612 612 class _ReposGroupForm(formencode.Schema):
613 613 allow_extra_fields = True
614 614 filter_extra_fields = False
615 615
616 616 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
617 617 SlugifyName())
618 618 group_description = UnicodeString(strip=True, min=1,
619 619 not_empty=True)
620 620 group_parent_id = OneOf(available_groups, hideList=False,
621 621 testValueList=True,
622 622 if_missing=None, not_empty=False)
623 623
624 624 chained_validators = [ValidReposGroup(edit, old_data), ValidPerms('group')]
625 625
626 626 return _ReposGroupForm
627 627
628 628
629 629 def RegisterForm(edit=False, old_data={}):
630 630 class _RegisterForm(formencode.Schema):
631 631 allow_extra_fields = True
632 632 filter_extra_fields = True
633 633 username = All(ValidUsername(edit, old_data),
634 634 UnicodeString(strip=True, min=1, not_empty=True))
635 635 password = All(UnicodeString(strip=False, min=6, not_empty=True))
636 636 password_confirmation = All(UnicodeString(strip=False, min=6, not_empty=True))
637 637 active = StringBoolean(if_missing=False)
638 638 name = UnicodeString(strip=True, min=1, not_empty=False)
639 639 lastname = UnicodeString(strip=True, min=1, not_empty=False)
640 640 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
641 641
642 642 chained_validators = [ValidPasswordsMatch, ValidPassword]
643 643
644 644 return _RegisterForm
645 645
646 646
647 647 def PasswordResetForm():
648 648 class _PasswordResetForm(formencode.Schema):
649 649 allow_extra_fields = True
650 650 filter_extra_fields = True
651 651 email = All(ValidSystemEmail(), Email(not_empty=True))
652 652 return _PasswordResetForm
653 653
654 654
655 655 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
656 656 repo_groups=[]):
657 657 class _RepoForm(formencode.Schema):
658 658 allow_extra_fields = True
659 659 filter_extra_fields = False
660 660 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
661 661 SlugifyName())
662 662 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False))
663 663 repo_group = OneOf(repo_groups, hideList=True)
664 664 repo_type = OneOf(supported_backends)
665 665 description = UnicodeString(strip=True, min=1, not_empty=True)
666 666 private = StringBoolean(if_missing=False)
667 667 enable_statistics = StringBoolean(if_missing=False)
668 668 enable_downloads = StringBoolean(if_missing=False)
669 669
670 670 if edit:
671 671 #this is repo owner
672 672 user = All(UnicodeString(not_empty=True), ValidRepoUser)
673 673
674 674 chained_validators = [ValidCloneUri()(),
675 675 ValidRepoName(edit, old_data),
676 676 ValidPerms()]
677 677 return _RepoForm
678 678
679 679
680 680 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
681 681 repo_groups=[]):
682 682 class _RepoForkForm(formencode.Schema):
683 683 allow_extra_fields = True
684 684 filter_extra_fields = False
685 685 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
686 686 SlugifyName())
687 687 repo_group = OneOf(repo_groups, hideList=True)
688 688 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
689 689 description = UnicodeString(strip=True, min=1, not_empty=True)
690 690 private = StringBoolean(if_missing=False)
691 691 copy_permissions = StringBoolean(if_missing=False)
692 692 update_after_clone = StringBoolean(if_missing=False)
693 693 fork_parent_id = UnicodeString()
694 694 chained_validators = [ValidForkName(edit, old_data)]
695 695
696 696 return _RepoForkForm
697 697
698 698
699 699 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
700 700 repo_groups=[]):
701 701 class _RepoForm(formencode.Schema):
702 702 allow_extra_fields = True
703 703 filter_extra_fields = False
704 704 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
705 705 SlugifyName())
706 706 description = UnicodeString(strip=True, min=1, not_empty=True)
707 707 repo_group = OneOf(repo_groups, hideList=True)
708 708 private = StringBoolean(if_missing=False)
709 709
710 710 chained_validators = [ValidRepoName(edit, old_data), ValidPerms(),
711 711 ValidSettings]
712 712 return _RepoForm
713 713
714 714
715 715 def ApplicationSettingsForm():
716 716 class _ApplicationSettingsForm(formencode.Schema):
717 717 allow_extra_fields = True
718 718 filter_extra_fields = False
719 719 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
720 720 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
721 721 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
722 722
723 723 return _ApplicationSettingsForm
724 724
725 725
726 726 def ApplicationUiSettingsForm():
727 727 class _ApplicationUiSettingsForm(formencode.Schema):
728 728 allow_extra_fields = True
729 729 filter_extra_fields = False
730 730 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
731 731 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
732 732 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
733 733 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
734 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
734 hooks_changegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
735 735 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
736 736
737 737 return _ApplicationUiSettingsForm
738 738
739 739
740 740 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
741 741 class _DefaultPermissionsForm(formencode.Schema):
742 742 allow_extra_fields = True
743 743 filter_extra_fields = True
744 744 overwrite_default = StringBoolean(if_missing=False)
745 745 anonymous = OneOf(['True', 'False'], if_missing=False)
746 746 default_perm = OneOf(perms_choices)
747 747 default_register = OneOf(register_choices)
748 748 default_create = OneOf(create_choices)
749 749
750 750 return _DefaultPermissionsForm
751 751
752 752
753 753 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
754 754 class _LdapSettingsForm(formencode.Schema):
755 755 allow_extra_fields = True
756 756 filter_extra_fields = True
757 757 #pre_validators = [LdapLibValidator]
758 758 ldap_active = StringBoolean(if_missing=False)
759 759 ldap_host = UnicodeString(strip=True,)
760 760 ldap_port = Number(strip=True,)
761 761 ldap_tls_kind = OneOf(tls_kind_choices)
762 762 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
763 763 ldap_dn_user = UnicodeString(strip=True,)
764 764 ldap_dn_pass = UnicodeString(strip=True,)
765 765 ldap_base_dn = UnicodeString(strip=True,)
766 766 ldap_filter = UnicodeString(strip=True,)
767 767 ldap_search_scope = OneOf(search_scope_choices)
768 768 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
769 769 ldap_attr_firstname = UnicodeString(strip=True,)
770 770 ldap_attr_lastname = UnicodeString(strip=True,)
771 771 ldap_attr_email = UnicodeString(strip=True,)
772 772
773 773 return _LdapSettingsForm
@@ -1,527 +1,527 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.repo
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Repository model for rhodecode
7 7
8 8 :created_on: Jun 5, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 from __future__ import with_statement
26 26 import os
27 27 import shutil
28 28 import logging
29 29 import traceback
30 30 import pkg_resources
31 31 from os.path import dirname as dn, join as jn
32 32 from datetime import datetime
33 33
34 34 from rhodecode.lib.vcs.backends import get_backend
35 35 from rhodecode.lib.compat import json
36 36 from rhodecode.lib.utils2 import LazyProperty, safe_str, safe_unicode
37 37 from rhodecode.lib.caching_query import FromCache
38 38 from rhodecode.lib.hooks import log_create_repository
39 39
40 40 from rhodecode.model import BaseModel
41 41 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
42 42 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup
43 43 from rhodecode.lib import helpers as h
44 44
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class RepoModel(BaseModel):
50 50
51 51 def __get_user(self, user):
52 52 return self._get_instance(User, user, callback=User.get_by_username)
53 53
54 54 def __get_users_group(self, users_group):
55 55 return self._get_instance(UsersGroup, users_group,
56 56 callback=UsersGroup.get_by_group_name)
57 57
58 58 def __get_repos_group(self, repos_group):
59 59 return self._get_instance(RepoGroup, repos_group,
60 60 callback=RepoGroup.get_by_group_name)
61 61
62 62 def __get_repo(self, repository):
63 63 return self._get_instance(Repository, repository,
64 64 callback=Repository.get_by_repo_name)
65 65
66 66 def __get_perm(self, permission):
67 67 return self._get_instance(Permission, permission,
68 68 callback=Permission.get_by_key)
69 69
70 70 @LazyProperty
71 71 def repos_path(self):
72 72 """
73 73 Get's the repositories root path from database
74 74 """
75 75
76 76 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
77 77 return q.ui_value
78 78
79 79 def get(self, repo_id, cache=False):
80 80 repo = self.sa.query(Repository)\
81 81 .filter(Repository.repo_id == repo_id)
82 82
83 83 if cache:
84 84 repo = repo.options(FromCache("sql_cache_short",
85 85 "get_repo_%s" % repo_id))
86 86 return repo.scalar()
87 87
88 88 def get_repo(self, repository):
89 89 return self.__get_repo(repository)
90 90
91 91 def get_by_repo_name(self, repo_name, cache=False):
92 92 repo = self.sa.query(Repository)\
93 93 .filter(Repository.repo_name == repo_name)
94 94
95 95 if cache:
96 96 repo = repo.options(FromCache("sql_cache_short",
97 97 "get_repo_%s" % repo_name))
98 98 return repo.scalar()
99 99
100 100 def get_users_js(self):
101 101 users = self.sa.query(User).filter(User.active == True).all()
102 102 return json.dumps([
103 103 {
104 104 'id': u.user_id,
105 105 'fname': u.name,
106 106 'lname': u.lastname,
107 107 'nname': u.username,
108 108 'gravatar_lnk': h.gravatar_url(u.email, 14)
109 109 } for u in users]
110 110 )
111 111
112 112 def get_users_groups_js(self):
113 113 users_groups = self.sa.query(UsersGroup)\
114 114 .filter(UsersGroup.users_group_active == True).all()
115 115
116 116 return json.dumps([
117 117 {
118 118 'id': gr.users_group_id,
119 119 'grname': gr.users_group_name,
120 120 'grmembers': len(gr.members),
121 121 } for gr in users_groups]
122 122 )
123 123
124 124 def _get_defaults(self, repo_name):
125 125 """
126 126 Get's information about repository, and returns a dict for
127 127 usage in forms
128 128
129 129 :param repo_name:
130 130 """
131 131
132 132 repo_info = Repository.get_by_repo_name(repo_name)
133 133
134 134 if repo_info is None:
135 135 return None
136 136
137 137 defaults = repo_info.get_dict()
138 138 group, repo_name = repo_info.groups_and_repo
139 139 defaults['repo_name'] = repo_name
140 140 defaults['repo_group'] = getattr(group[-1] if group else None,
141 141 'group_id', None)
142 142
143 143 # fill owner
144 144 if repo_info.user:
145 145 defaults.update({'user': repo_info.user.username})
146 146 else:
147 147 replacement_user = User.query().filter(User.admin ==
148 148 True).first().username
149 149 defaults.update({'user': replacement_user})
150 150
151 151 # fill repository users
152 152 for p in repo_info.repo_to_perm:
153 153 defaults.update({'u_perm_%s' % p.user.username:
154 154 p.permission.permission_name})
155 155
156 156 # fill repository groups
157 157 for p in repo_info.users_group_to_perm:
158 158 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
159 159 p.permission.permission_name})
160 160
161 161 return defaults
162 162
163 163 def update(self, repo_name, form_data):
164 164 try:
165 165 cur_repo = self.get_by_repo_name(repo_name, cache=False)
166 166
167 167 # update permissions
168 168 for member, perm, member_type in form_data['perms_updates']:
169 169 if member_type == 'user':
170 170 # this updates existing one
171 171 RepoModel().grant_user_permission(
172 172 repo=cur_repo, user=member, perm=perm
173 173 )
174 174 else:
175 175 RepoModel().grant_users_group_permission(
176 176 repo=cur_repo, group_name=member, perm=perm
177 177 )
178 178 # set new permissions
179 179 for member, perm, member_type in form_data['perms_new']:
180 180 if member_type == 'user':
181 181 RepoModel().grant_user_permission(
182 182 repo=cur_repo, user=member, perm=perm
183 183 )
184 184 else:
185 185 RepoModel().grant_users_group_permission(
186 186 repo=cur_repo, group_name=member, perm=perm
187 187 )
188 188
189 189 # update current repo
190 190 for k, v in form_data.items():
191 191 if k == 'user':
192 192 cur_repo.user = User.get_by_username(v)
193 193 elif k == 'repo_name':
194 194 pass
195 195 elif k == 'repo_group':
196 196 cur_repo.group = RepoGroup.get(v)
197 197
198 198 else:
199 199 setattr(cur_repo, k, v)
200 200
201 201 new_name = cur_repo.get_new_name(form_data['repo_name'])
202 202 cur_repo.repo_name = new_name
203 203
204 204 self.sa.add(cur_repo)
205 205
206 206 if repo_name != new_name:
207 207 # rename repository
208 208 self.__rename_repo(old=repo_name, new=new_name)
209 209
210 210 return cur_repo
211 211 except:
212 212 log.error(traceback.format_exc())
213 213 raise
214 214
215 215 def create(self, form_data, cur_user, just_db=False, fork=False):
216 216 from rhodecode.model.scm import ScmModel
217 217
218 218 try:
219 219 if fork:
220 220 fork_parent_id = form_data['fork_parent_id']
221 221
222 222 # repo name is just a name of repository
223 223 # while repo_name_full is a full qualified name that is combined
224 224 # with name and path of group
225 225 repo_name = form_data['repo_name']
226 226 repo_name_full = form_data['repo_name_full']
227 227
228 228 new_repo = Repository()
229 229 new_repo.enable_statistics = False
230 230
231 231 for k, v in form_data.items():
232 232 if k == 'repo_name':
233 233 v = repo_name_full
234 234 if k == 'repo_group':
235 235 k = 'group_id'
236 236 if k == 'description':
237 237 v = v or repo_name
238 238
239 239 setattr(new_repo, k, v)
240 240
241 241 if fork:
242 242 parent_repo = Repository.get(fork_parent_id)
243 243 new_repo.fork = parent_repo
244 244
245 245 new_repo.user_id = cur_user.user_id
246 246 self.sa.add(new_repo)
247 247
248 248 def _create_default_perms():
249 249 # create default permission
250 250 repo_to_perm = UserRepoToPerm()
251 251 default = 'repository.read'
252 252 for p in User.get_by_username('default').user_perms:
253 253 if p.permission.permission_name.startswith('repository.'):
254 254 default = p.permission.permission_name
255 255 break
256 256
257 257 default_perm = 'repository.none' if form_data['private'] else default
258 258
259 259 repo_to_perm.permission_id = self.sa.query(Permission)\
260 260 .filter(Permission.permission_name == default_perm)\
261 261 .one().permission_id
262 262
263 263 repo_to_perm.repository = new_repo
264 264 repo_to_perm.user_id = User.get_by_username('default').user_id
265 265
266 266 self.sa.add(repo_to_perm)
267 267
268 268 if fork:
269 269 if form_data.get('copy_permissions'):
270 270 repo = Repository.get(fork_parent_id)
271 271 user_perms = UserRepoToPerm.query()\
272 272 .filter(UserRepoToPerm.repository == repo).all()
273 273 group_perms = UsersGroupRepoToPerm.query()\
274 274 .filter(UsersGroupRepoToPerm.repository == repo).all()
275 275
276 276 for perm in user_perms:
277 277 UserRepoToPerm.create(perm.user, new_repo,
278 278 perm.permission)
279 279
280 280 for perm in group_perms:
281 281 UsersGroupRepoToPerm.create(perm.users_group, new_repo,
282 282 perm.permission)
283 283 else:
284 284 _create_default_perms()
285 285 else:
286 286 _create_default_perms()
287 287
288 288 if not just_db:
289 289 self.__create_repo(repo_name, form_data['repo_type'],
290 290 form_data['repo_group'],
291 291 form_data['clone_uri'])
292 292 log_create_repository(new_repo.get_dict(),
293 293 created_by=cur_user.username)
294 294
295 295 # now automatically start following this repository as owner
296 296 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
297 297 cur_user.user_id)
298 298 return new_repo
299 299 except:
300 300 log.error(traceback.format_exc())
301 301 raise
302 302
303 303 def create_fork(self, form_data, cur_user):
304 304 """
305 305 Simple wrapper into executing celery task for fork creation
306 306
307 307 :param form_data:
308 308 :param cur_user:
309 309 """
310 310 from rhodecode.lib.celerylib import tasks, run_task
311 311 run_task(tasks.create_repo_fork, form_data, cur_user)
312 312
313 313 def delete(self, repo):
314 314 repo = self.__get_repo(repo)
315 315 try:
316 316 self.sa.delete(repo)
317 317 self.__delete_repo(repo)
318 318 except:
319 319 log.error(traceback.format_exc())
320 320 raise
321 321
322 322 def grant_user_permission(self, repo, user, perm):
323 323 """
324 324 Grant permission for user on given repository, or update existing one
325 325 if found
326 326
327 327 :param repo: Instance of Repository, repository_id, or repository name
328 328 :param user: Instance of User, user_id or username
329 329 :param perm: Instance of Permission, or permission_name
330 330 """
331 331 user = self.__get_user(user)
332 332 repo = self.__get_repo(repo)
333 333 permission = self.__get_perm(perm)
334 334
335 335 # check if we have that permission already
336 336 obj = self.sa.query(UserRepoToPerm)\
337 337 .filter(UserRepoToPerm.user == user)\
338 338 .filter(UserRepoToPerm.repository == repo)\
339 339 .scalar()
340 340 if obj is None:
341 341 # create new !
342 342 obj = UserRepoToPerm()
343 343 obj.repository = repo
344 344 obj.user = user
345 345 obj.permission = permission
346 346 self.sa.add(obj)
347 347
348 348 def revoke_user_permission(self, repo, user):
349 349 """
350 350 Revoke permission for user on given repository
351 351
352 352 :param repo: Instance of Repository, repository_id, or repository name
353 353 :param user: Instance of User, user_id or username
354 354 """
355 355
356 356 user = self.__get_user(user)
357 357 repo = self.__get_repo(repo)
358 358
359 359 obj = self.sa.query(UserRepoToPerm)\
360 360 .filter(UserRepoToPerm.repository == repo)\
361 361 .filter(UserRepoToPerm.user == user)\
362 362 .one()
363 363 self.sa.delete(obj)
364 364
365 365 def grant_users_group_permission(self, repo, group_name, perm):
366 366 """
367 367 Grant permission for users group on given repository, or update
368 368 existing one if found
369 369
370 370 :param repo: Instance of Repository, repository_id, or repository name
371 371 :param group_name: Instance of UserGroup, users_group_id,
372 372 or users group name
373 373 :param perm: Instance of Permission, or permission_name
374 374 """
375 375 repo = self.__get_repo(repo)
376 376 group_name = self.__get_users_group(group_name)
377 377 permission = self.__get_perm(perm)
378 378
379 379 # check if we have that permission already
380 380 obj = self.sa.query(UsersGroupRepoToPerm)\
381 381 .filter(UsersGroupRepoToPerm.users_group == group_name)\
382 382 .filter(UsersGroupRepoToPerm.repository == repo)\
383 383 .scalar()
384 384
385 385 if obj is None:
386 386 # create new
387 387 obj = UsersGroupRepoToPerm()
388 388
389 389 obj.repository = repo
390 390 obj.users_group = group_name
391 391 obj.permission = permission
392 392 self.sa.add(obj)
393 393
394 394 def revoke_users_group_permission(self, repo, group_name):
395 395 """
396 396 Revoke permission for users group on given repository
397 397
398 398 :param repo: Instance of Repository, repository_id, or repository name
399 399 :param group_name: Instance of UserGroup, users_group_id,
400 400 or users group name
401 401 """
402 402 repo = self.__get_repo(repo)
403 403 group_name = self.__get_users_group(group_name)
404 404
405 405 obj = self.sa.query(UsersGroupRepoToPerm)\
406 406 .filter(UsersGroupRepoToPerm.repository == repo)\
407 407 .filter(UsersGroupRepoToPerm.users_group == group_name)\
408 408 .one()
409 409 self.sa.delete(obj)
410 410
411 411 def delete_stats(self, repo_name):
412 412 """
413 413 removes stats for given repo
414 414
415 415 :param repo_name:
416 416 """
417 417 try:
418 418 obj = self.sa.query(Statistics)\
419 419 .filter(Statistics.repository ==
420 420 self.get_by_repo_name(repo_name))\
421 421 .one()
422 422 self.sa.delete(obj)
423 423 except:
424 424 log.error(traceback.format_exc())
425 425 raise
426 426
427 427 def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
428 428 """
429 429 makes repository on filesystem. It's group aware means it'll create
430 430 a repository within a group, and alter the paths accordingly of
431 431 group location
432 432
433 433 :param repo_name:
434 434 :param alias:
435 435 :param parent_id:
436 436 :param clone_uri:
437 437 """
438 438 from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
439 439
440 440 if new_parent_id:
441 441 paths = RepoGroup.get(new_parent_id)\
442 442 .full_path.split(RepoGroup.url_sep())
443 443 new_parent_path = os.sep.join(paths)
444 444 else:
445 445 new_parent_path = ''
446 446
447 447 # we need to make it str for mercurial
448 448 repo_path = os.path.join(*map(lambda x: safe_str(x),
449 449 [self.repos_path, new_parent_path, repo_name]))
450 450
451 451 # check if this path is not a repository
452 452 if is_valid_repo(repo_path, self.repos_path):
453 453 raise Exception('This path %s is a valid repository' % repo_path)
454 454
455 455 # check if this path is a group
456 456 if is_valid_repos_group(repo_path, self.repos_path):
457 457 raise Exception('This path %s is a valid group' % repo_path)
458 458
459 459 log.info('creating repo %s in %s @ %s' % (
460 460 repo_name, safe_unicode(repo_path), clone_uri
461 461 )
462 462 )
463 463 backend = get_backend(alias)
464 464 if alias == 'hg':
465 465 backend(repo_path, create=True, src_url=clone_uri)
466 466 elif alias == 'git':
467 467 r = backend(repo_path, create=True, src_url=clone_uri, bare=True)
468 468 # add rhodecode hook into this repo
469 469
470 470 loc = jn(r.path, 'hooks')
471 471 if not r.bare:
472 472 loc = jn(r.path, '.git', 'hooks')
473 473 if not os.path.isdir(loc):
474 474 os.makedirs(loc)
475 475
476 476 tmpl = pkg_resources.resource_string(
477 477 'rhodecode', jn('config', 'pre_receive_tmpl.py')
478 478 )
479 _hook_file = jn(loc, 'pre-receive')
479 _hook_file = jn(loc, 'post-receive')
480 480 with open(_hook_file, 'wb') as f:
481 481 f.write(tmpl)
482 482 os.chmod(_hook_file, 0555)
483 483
484 484 else:
485 485 raise Exception('Undefined alias %s' % alias)
486 486
487 487 def __rename_repo(self, old, new):
488 488 """
489 489 renames repository on filesystem
490 490
491 491 :param old: old name
492 492 :param new: new name
493 493 """
494 494 log.info('renaming repo from %s to %s' % (old, new))
495 495
496 496 old_path = os.path.join(self.repos_path, old)
497 497 new_path = os.path.join(self.repos_path, new)
498 498 if os.path.isdir(new_path):
499 499 raise Exception(
500 500 'Was trying to rename to already existing dir %s' % new_path
501 501 )
502 502 shutil.move(old_path, new_path)
503 503
504 504 def __delete_repo(self, repo):
505 505 """
506 506 removes repo from filesystem, the removal is acctually made by
507 507 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
508 508 repository is no longer valid for rhodecode, can be undeleted later on
509 509 by reverting the renames on this repository
510 510
511 511 :param repo: repo object
512 512 """
513 513 rm_path = os.path.join(self.repos_path, repo.repo_name)
514 514 log.info("Removing %s" % (rm_path))
515 515 # disable hg/git internal that it doesn't get detected as repo
516 516 alias = repo.repo_type
517 517
518 518 bare = getattr(repo.scm_instance, 'bare', False)
519 519
520 520 if not bare:
521 521 # skip this for bare git repos
522 522 shutil.move(os.path.join(rm_path, '.%s' % alias),
523 523 os.path.join(rm_path, 'rm__.%s' % alias))
524 524 # disable repo
525 525 _d = 'rm__%s__%s' % (datetime.now().strftime('%Y%m%d_%H%M%S_%f'),
526 526 repo.repo_name)
527 527 shutil.move(rm_path, os.path.join(self.repos_path, _d))
@@ -1,246 +1,246 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Settings administration')} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Settings')}
10 10 </%def>
11 11
12 12 <%def name="page_nav()">
13 13 ${self.menu('admin')}
14 14 </%def>
15 15
16 16 <%def name="main()">
17 17 <div class="box">
18 18 <!-- box / title -->
19 19 <div class="title">
20 20 ${self.breadcrumbs()}
21 21 </div>
22 22 <!-- end box / title -->
23 23
24 24 <h3>${_('Remap and rescan repositories')}</h3>
25 25 ${h.form(url('admin_setting', setting_id='mapping'),method='put')}
26 26 <div class="form">
27 27 <!-- fields -->
28 28
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label label-checkbox">
32 32 <label for="destroy">${_('rescan option')}:</label>
33 33 </div>
34 34 <div class="checkboxes">
35 35 <div class="checkbox">
36 36 ${h.checkbox('destroy',True)}
37 37 <label for="destroy">
38 38 <span class="tooltip" title="${h.tooltip(_('In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it.'))}">
39 39 ${_('destroy old data')}</span> </label>
40 40 </div>
41 41 </div>
42 42 </div>
43 43
44 44 <div class="buttons">
45 45 ${h.submit('rescan',_('Rescan repositories'),class_="ui-button")}
46 46 </div>
47 47 </div>
48 48 </div>
49 49 ${h.end_form()}
50 50
51 51 <h3>${_('Whoosh indexing')}</h3>
52 52 ${h.form(url('admin_setting', setting_id='whoosh'),method='put')}
53 53 <div class="form">
54 54 <!-- fields -->
55 55
56 56 <div class="fields">
57 57 <div class="field">
58 58 <div class="label label-checkbox">
59 59 <label>${_('index build option')}:</label>
60 60 </div>
61 61 <div class="checkboxes">
62 62 <div class="checkbox">
63 63 ${h.checkbox('full_index',True)}
64 64 <label for="full_index">${_('build from scratch')}</label>
65 65 </div>
66 66 </div>
67 67 </div>
68 68
69 69 <div class="buttons">
70 70 ${h.submit('reindex',_('Reindex'),class_="ui-button")}
71 71 </div>
72 72 </div>
73 73 </div>
74 74 ${h.end_form()}
75 75
76 76 <h3>${_('Global application settings')}</h3>
77 77 ${h.form(url('admin_setting', setting_id='global'),method='put')}
78 78 <div class="form">
79 79 <!-- fields -->
80 80
81 81 <div class="fields">
82 82
83 83 <div class="field">
84 84 <div class="label">
85 85 <label for="rhodecode_title">${_('Application name')}:</label>
86 86 </div>
87 87 <div class="input">
88 88 ${h.text('rhodecode_title',size=30)}
89 89 </div>
90 90 </div>
91 91
92 92 <div class="field">
93 93 <div class="label">
94 94 <label for="rhodecode_realm">${_('Realm text')}:</label>
95 95 </div>
96 96 <div class="input">
97 97 ${h.text('rhodecode_realm',size=30)}
98 98 </div>
99 99 </div>
100 100
101 101 <div class="field">
102 102 <div class="label">
103 103 <label for="rhodecode_ga_code">${_('GA code')}:</label>
104 104 </div>
105 105 <div class="input">
106 106 ${h.text('rhodecode_ga_code',size=30)}
107 107 </div>
108 108 </div>
109 109
110 110 <div class="buttons">
111 111 ${h.submit('save',_('Save settings'),class_="ui-button")}
112 112 ${h.reset('reset',_('Reset'),class_="ui-button")}
113 113 </div>
114 114 </div>
115 115 </div>
116 116 ${h.end_form()}
117 117
118 118 <h3>${_('Mercurial settings')}</h3>
119 119 ${h.form(url('admin_setting', setting_id='mercurial'),method='put')}
120 120 <div class="form">
121 121 <!-- fields -->
122 122
123 123 <div class="fields">
124 124
125 125 <div class="field">
126 126 <div class="label label-checkbox">
127 127 <label>${_('Web')}:</label>
128 128 </div>
129 129 <div class="checkboxes">
130 130 <div class="checkbox">
131 131 ${h.checkbox('web_push_ssl','true')}
132 132 <label for="web_push_ssl">${_('require ssl for pushing')}</label>
133 133 </div>
134 134 </div>
135 135 </div>
136 136
137 137 <div class="field">
138 138 <div class="label label-checkbox">
139 139 <label>${_('Hooks')}:</label>
140 140 </div>
141 141 <div class="checkboxes">
142 142 <div class="checkbox">
143 143 ${h.checkbox('hooks_changegroup_update','True')}
144 144 <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
145 145 </div>
146 146 <div class="checkbox">
147 147 ${h.checkbox('hooks_changegroup_repo_size','True')}
148 148 <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
149 149 </div>
150 150 <div class="checkbox">
151 ${h.checkbox('hooks_pretxnchangegroup_push_logger','True')}
152 <label for="hooks_pretxnchangegroup_push_logger">${_('Log user push commands')}</label>
151 ${h.checkbox('hooks_changegroup_push_logger','True')}
152 <label for="hooks_changegroup_push_logger">${_('Log user push commands')}</label>
153 153 </div>
154 154 <div class="checkbox">
155 155 ${h.checkbox('hooks_preoutgoing_pull_logger','True')}
156 156 <label for="hooks_preoutgoing_pull_logger">${_('Log user pull commands')}</label>
157 157 </div>
158 158 </div>
159 159 <div class="input" style="margin-top:10px">
160 160 ${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'),class_="ui-btn")}
161 161 </div>
162 162 </div>
163 163 <div class="field">
164 164 <div class="label">
165 165 <label for="paths_root_path">${_('Repositories location')}:</label>
166 166 </div>
167 167 <div class="input">
168 168 ${h.text('paths_root_path',size=30,readonly="readonly")}
169 169 <span id="path_unlock" class="tooltip"
170 170 title="${h.tooltip(_('This a crucial application setting. If you are really sure you need to change this, you must restart application in order to make this setting take effect. Click this label to unlock.'))}">
171 171 ${_('unlock')}</span>
172 172 </div>
173 173 </div>
174 174
175 175 <div class="buttons">
176 176 ${h.submit('save',_('Save settings'),class_="ui-button")}
177 177 ${h.reset('reset',_('Reset'),class_="ui-button")}
178 178 </div>
179 179 </div>
180 180 </div>
181 181 ${h.end_form()}
182 182
183 183 <script type="text/javascript">
184 184 YAHOO.util.Event.onDOMReady(function(){
185 185 YAHOO.util.Event.addListener('path_unlock','click',function(){
186 186 YAHOO.util.Dom.get('paths_root_path').removeAttribute('readonly');
187 187 });
188 188 });
189 189 </script>
190 190
191 191 <h3>${_('Test Email')}</h3>
192 192 ${h.form(url('admin_setting', setting_id='email'),method='put')}
193 193 <div class="form">
194 194 <!-- fields -->
195 195
196 196 <div class="fields">
197 197 <div class="field">
198 198 <div class="label">
199 199 <label for="test_email">${_('Email to')}:</label>
200 200 </div>
201 201 <div class="input">
202 202 ${h.text('test_email',size=30)}
203 203 </div>
204 204 </div>
205 205
206 206 <div class="buttons">
207 207 ${h.submit('send',_('Send'),class_="ui-button")}
208 208 </div>
209 209 </div>
210 210 </div>
211 211 ${h.end_form()}
212 212
213 213 <h3>${_('System Info and Packages')}</h3>
214 214 <div class="form">
215 215 <div>
216 216 <h5 id="expand_modules" style="cursor: pointer">&darr; ${_('show')} &darr;</h5>
217 217 </div>
218 218 <div id="expand_modules_table" style="display:none">
219 219 <h5>Python - ${c.py_version}</h5>
220 220 <h5>System - ${c.platform}</h5>
221 221
222 222 <table class="table" style="margin:0px 0px 0px 20px">
223 223 <colgroup>
224 224 <col style="width:220px">
225 225 </colgroup>
226 226 <tbody>
227 227 %for key, value in c.modules:
228 228 <tr>
229 229 <th style="text-align: right;padding-right:5px;">${key}</th>
230 230 <td>${value}</td>
231 231 </tr>
232 232 %endfor
233 233 </tbody>
234 234 </table>
235 235 </div>
236 236 </div>
237 237
238 238 <script type="text/javascript">
239 239 YUE.on('expand_modules','click',function(e){
240 240 YUD.setStyle('expand_modules_table','display','');
241 241 YUD.setStyle('expand_modules','display','none');
242 242 })
243 243 </script>
244 244
245 245 </div>
246 246 </%def>
General Comments 0
You need to be logged in to leave comments. Login now