##// END OF EJS Templates
use os.environ as a fallback for getting special info from hooks, this will allow...
marcink -
r2716:4c716671 beta
parent child Browse files
Show More
@@ -1,278 +1,303 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 from rhodecode.lib.compat import json
36 37
37 38
38 39 def _get_scm_size(alias, root_path):
39 40
40 41 if not alias.startswith('.'):
41 42 alias += '.'
42 43
43 44 size_scm, size_root = 0, 0
44 45 for path, dirs, files in os.walk(root_path):
45 46 if path.find(alias) != -1:
46 47 for f in files:
47 48 try:
48 49 size_scm += os.path.getsize(os.path.join(path, f))
49 50 except OSError:
50 51 pass
51 52 else:
52 53 for f in files:
53 54 try:
54 55 size_root += os.path.getsize(os.path.join(path, f))
55 56 except OSError:
56 57 pass
57 58
58 59 size_scm_f = h.format_byte_size(size_scm)
59 60 size_root_f = h.format_byte_size(size_root)
60 61 size_total_f = h.format_byte_size(size_root + size_scm)
61 62
62 63 return size_scm_f, size_root_f, size_total_f
63 64
64 65
65 66 def repo_size(ui, repo, hooktype=None, **kwargs):
66 67 """
67 68 Presents size of repository after push
68 69
69 70 :param ui:
70 71 :param repo:
71 72 :param hooktype:
72 73 """
73 74
74 75 size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
75 76
76 77 last_cs = repo[len(repo) - 1]
77 78
78 79 msg = ('Repository size .hg:%s repo:%s total:%s\n'
79 80 'Last revision is now r%s:%s\n') % (
80 81 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
81 82 )
82 83
83 84 sys.stdout.write(msg)
84 85
85 86
86 87 def log_pull_action(ui, repo, **kwargs):
87 88 """
88 89 Logs user last pull action
89 90
90 91 :param ui:
91 92 :param repo:
92 93 """
94 try:
95 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
96 except:
97 rc_extras = {}
93 98 extras = dict(repo.ui.configitems('rhodecode_extras'))
94 username = extras['username']
95 repository = extras['repository']
96 scm = extras['scm']
99 if 'username' in extras:
100 username = extras['username']
101 repository = extras['repository']
102 scm = extras['scm']
103 elif 'username' in rc_extras:
104 username = rc_extras['username']
105 repository = rc_extras['repository']
106 scm = rc_extras['scm']
107 else:
108 raise Exception('Missing data in repo.ui and os.environ')
109
97 110 action = 'pull'
98
99 111 action_logger(username, action, repository, extras['ip'], commit=True)
100 112 # extension hook call
101 113 from rhodecode import EXTENSIONS
102 114 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
103 115
104 116 if isfunction(callback):
105 117 kw = {}
106 118 kw.update(extras)
107 119 callback(**kw)
108 120 return 0
109 121
110 122
111 123 def log_push_action(ui, repo, **kwargs):
112 124 """
113 125 Maps user last push action to new changeset id, from mercurial
114 126
115 127 :param ui:
116 128 :param repo: repo object containing the `ui` object
117 129 """
118 130
131 try:
132 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
133 except:
134 rc_extras = {}
135
119 136 extras = dict(repo.ui.configitems('rhodecode_extras'))
120 username = extras['username']
121 repository = extras['repository']
122 action = extras['action'] + ':%s'
123 scm = extras['scm']
137 if 'username' in extras:
138 username = extras['username']
139 repository = extras['repository']
140 scm = extras['scm']
141 elif 'username' in rc_extras:
142 username = rc_extras['username']
143 repository = rc_extras['repository']
144 scm = rc_extras['scm']
145 else:
146 raise Exception('Missing data in repo.ui and os.environ')
147
148 action = 'push' + ':%s'
124 149
125 150 if scm == 'hg':
126 151 node = kwargs['node']
127 152
128 153 def get_revs(repo, rev_opt):
129 154 if rev_opt:
130 155 revs = revrange(repo, rev_opt)
131 156
132 157 if len(revs) == 0:
133 158 return (nullrev, nullrev)
134 159 return (max(revs), min(revs))
135 160 else:
136 161 return (len(repo) - 1, 0)
137 162
138 163 stop, start = get_revs(repo, [node + ':'])
139 164 h = binascii.hexlify
140 165 revs = [h(repo[r].node()) for r in xrange(start, stop + 1)]
141 166 elif scm == 'git':
142 167 revs = kwargs.get('_git_revs', [])
143 168 if '_git_revs' in kwargs:
144 169 kwargs.pop('_git_revs')
145 170
146 171 action = action % ','.join(revs)
147 172
148 173 action_logger(username, action, repository, extras['ip'], commit=True)
149 174
150 175 # extension hook call
151 176 from rhodecode import EXTENSIONS
152 177 callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
153 178 if isfunction(callback):
154 179 kw = {'pushed_revs': revs}
155 180 kw.update(extras)
156 181 callback(**kw)
157 182 return 0
158 183
159 184
160 185 def log_create_repository(repository_dict, created_by, **kwargs):
161 186 """
162 187 Post create repository Hook. This is a dummy function for admins to re-use
163 188 if needed. It's taken from rhodecode-extensions module and executed
164 189 if present
165 190
166 191 :param repository: dict dump of repository object
167 192 :param created_by: username who created repository
168 193 :param created_date: date of creation
169 194
170 195 available keys of repository_dict:
171 196
172 197 'repo_type',
173 198 'description',
174 199 'private',
175 200 'created_on',
176 201 'enable_downloads',
177 202 'repo_id',
178 203 'user_id',
179 204 'enable_statistics',
180 205 'clone_uri',
181 206 'fork_id',
182 207 'group_id',
183 208 'repo_name'
184 209
185 210 """
186 211 from rhodecode import EXTENSIONS
187 212 callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
188 213 if isfunction(callback):
189 214 kw = {}
190 215 kw.update(repository_dict)
191 216 kw.update({'created_by': created_by})
192 217 kw.update(kwargs)
193 218 return callback(**kw)
194 219
195 220 return 0
196 221
197 222
198 223 def handle_git_post_receive(repo_path, revs, env):
199 224 """
200 225 A really hacky method that is runned by git post-receive hook and logs
201 226 an push action together with pushed revisions. It's executed by subprocess
202 227 thus needs all info to be able to create a on the fly pylons enviroment,
203 228 connect to database and run the logging code. Hacky as sh*t but works.
204 229
205 230 :param repo_path:
206 231 :type repo_path:
207 232 :param revs:
208 233 :type revs:
209 234 :param env:
210 235 :type env:
211 236 """
212 237 from paste.deploy import appconfig
213 238 from sqlalchemy import engine_from_config
214 239 from rhodecode.config.environment import load_environment
215 240 from rhodecode.model import init_model
216 241 from rhodecode.model.db import RhodeCodeUi
217 242 from rhodecode.lib.utils import make_ui
218 243 from rhodecode.model.db import Repository
219 244
220 245 path, ini_name = os.path.split(env['RHODECODE_CONFIG_FILE'])
221 246 conf = appconfig('config:%s' % ini_name, relative_to=path)
222 247 load_environment(conf.global_conf, conf.local_conf)
223 248
224 249 engine = engine_from_config(conf, 'sqlalchemy.db1.')
225 250 init_model(engine)
226 251
227 252 baseui = make_ui('db')
228 253 # fix if it's not a bare repo
229 254 if repo_path.endswith('.git'):
230 255 repo_path = repo_path[:-4]
231 256 repo = Repository.get_by_full_path(repo_path)
232 257 _hooks = dict(baseui.configitems('hooks')) or {}
233 258 # if push hook is enabled via web interface
234 259 if repo and _hooks.get(RhodeCodeUi.HOOK_PUSH):
235 260
236 261 extras = {
237 262 'username': env['RHODECODE_USER'],
238 263 'repository': repo.repo_name,
239 264 'scm': 'git',
240 265 'action': 'push',
241 266 'ip': env['RHODECODE_CONFIG_IP'],
242 267 }
243 268 for k, v in extras.items():
244 269 baseui.setconfig('rhodecode_extras', k, v)
245 270 repo = repo.scm_instance
246 271 repo.ui = baseui
247 272
248 273 rev_data = []
249 274 for l in revs:
250 275 old_rev, new_rev, ref = l.split(' ')
251 276 _ref_data = ref.split('/')
252 277 if _ref_data[1] in ['tags', 'heads']:
253 278 rev_data.append({'old_rev': old_rev,
254 279 'new_rev': new_rev,
255 280 'ref': ref,
256 281 'type': _ref_data[1],
257 282 'name': _ref_data[2].strip()})
258 283
259 284 git_revs = []
260 285 for push_ref in rev_data:
261 286 _type = push_ref['type']
262 287 if _type == 'heads':
263 288 if push_ref['old_rev'] == EmptyChangeset().raw_id:
264 289 cmd = "for-each-ref --format='%(refname)' 'refs/heads/*'"
265 290 heads = repo.run_git_command(cmd)[0]
266 291 heads = heads.replace(push_ref['ref'], '')
267 292 heads = ' '.join(map(lambda c: c.strip('\n').strip(),
268 293 heads.splitlines()))
269 294 cmd = (('log %(new_rev)s' % push_ref) +
270 295 ' --reverse --pretty=format:"%H" --not ' + heads)
271 296 else:
272 297 cmd = (('log %(old_rev)s..%(new_rev)s' % push_ref) +
273 298 ' --reverse --pretty=format:"%H"')
274 299 git_revs += repo.run_git_command(cmd)[0].splitlines()
275 300 elif _type == 'tags':
276 301 git_revs += [push_ref['name']]
277 302
278 303 log_push_action(baseui, repo, _git_revs=git_revs)
@@ -1,306 +1,308 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 from dulwich.web import LimitedInputFilter, GunzipFilter
34 34
35 35
36 36 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
37 37
38 38 def handle(self):
39 39 write = lambda x: self.proto.write_sideband(1, x)
40 40
41 41 graph_walker = dulserver.ProtocolGraphWalker(self,
42 42 self.repo.object_store,
43 43 self.repo.get_peeled)
44 44 objects_iter = self.repo.fetch_objects(
45 45 graph_walker.determine_wants, graph_walker, self.progress,
46 46 get_tagged=self.get_tagged)
47 47
48 48 # Did the process short-circuit (e.g. in a stateless RPC call)? Note
49 49 # that the client still expects a 0-object pack in most cases.
50 50 if objects_iter is None:
51 51 return
52 52
53 53 self.progress("counting objects: %d, done.\n" % len(objects_iter))
54 54 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
55 55 objects_iter)
56 56 messages = []
57 57 messages.append('thank you for using rhodecode')
58 58
59 59 for msg in messages:
60 60 self.progress(msg + "\n")
61 61 # we are done
62 62 self.proto.write("0000")
63 63
64 64
65 65 dulserver.DEFAULT_HANDLERS = {
66 66 #git-ls-remote, git-clone, git-fetch and git-pull
67 67 'git-upload-pack': SimpleGitUploadPackHandler,
68 68 #git-push
69 69 'git-receive-pack': dulserver.ReceivePackHandler,
70 70 }
71 71
72 72 # not used for now until dulwich get's fixed
73 73 #from dulwich.repo import Repo
74 74 #from dulwich.web import make_wsgi_chain
75 75
76 76 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
77 77 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
78 78 HTTPBadRequest, HTTPNotAcceptable
79 79
80 80 from rhodecode.lib.utils2 import safe_str
81 81 from rhodecode.lib.base import BaseVCSController
82 82 from rhodecode.lib.auth import get_container_username
83 83 from rhodecode.lib.utils import is_valid_repo, make_ui
84 from rhodecode.lib.compat import json
84 85 from rhodecode.model.db import User, RhodeCodeUi
85 86
86 87 log = logging.getLogger(__name__)
87 88
88 89
89 90 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
90 91
91 92
92 93 def is_git(environ):
93 94 path_info = environ['PATH_INFO']
94 95 isgit_path = GIT_PROTO_PAT.match(path_info)
95 96 log.debug('pathinfo: %s detected as GIT %s' % (
96 97 path_info, isgit_path != None)
97 98 )
98 99 return isgit_path
99 100
100 101
101 102 class SimpleGit(BaseVCSController):
102 103
103 104 def _handle_request(self, environ, start_response):
104 105
105 106 if not is_git(environ):
106 107 return self.application(environ, start_response)
107 108 if not self._check_ssl(environ, start_response):
108 109 return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
109 110 ipaddr = self._get_ip_addr(environ)
110 111 username = None
111 112 self._git_first_op = False
112 113 # skip passing error to error controller
113 114 environ['pylons.status_code_redirect'] = True
114 115
115 116 #======================================================================
116 117 # EXTRACT REPOSITORY NAME FROM ENV
117 118 #======================================================================
118 119 try:
119 120 repo_name = self.__get_repository(environ)
120 121 log.debug('Extracted repo name is %s' % repo_name)
121 122 except:
122 123 return HTTPInternalServerError()(environ, start_response)
123 124
124 125 # quick check if that dir exists...
125 if is_valid_repo(repo_name, self.basepath) is False:
126 if is_valid_repo(repo_name, self.basepath, 'git') is False:
126 127 return HTTPNotFound()(environ, start_response)
127 128
128 129 #======================================================================
129 130 # GET ACTION PULL or PUSH
130 131 #======================================================================
131 132 action = self.__get_action(environ)
132 133
133 134 #======================================================================
134 135 # CHECK ANONYMOUS PERMISSION
135 136 #======================================================================
136 137 if action in ['pull', 'push']:
137 138 anonymous_user = self.__get_user('default')
138 139 username = anonymous_user.username
139 140 anonymous_perm = self._check_permission(action, anonymous_user,
140 141 repo_name)
141 142
142 143 if anonymous_perm is not True or anonymous_user.active is False:
143 144 if anonymous_perm is not True:
144 145 log.debug('Not enough credentials to access this '
145 146 'repository as anonymous user')
146 147 if anonymous_user.active is False:
147 148 log.debug('Anonymous access is disabled, running '
148 149 'authentication')
149 150 #==============================================================
150 151 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
151 152 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
152 153 #==============================================================
153 154
154 155 # Attempting to retrieve username from the container
155 156 username = get_container_username(environ, self.config)
156 157
157 158 # If not authenticated by the container, running basic auth
158 159 if not username:
159 160 self.authenticate.realm = \
160 161 safe_str(self.config['rhodecode_realm'])
161 162 result = self.authenticate(environ)
162 163 if isinstance(result, str):
163 164 AUTH_TYPE.update(environ, 'basic')
164 165 REMOTE_USER.update(environ, result)
165 166 username = result
166 167 else:
167 168 return result.wsgi_application(environ, start_response)
168 169
169 170 #==============================================================
170 171 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
171 172 #==============================================================
172 173 try:
173 174 user = self.__get_user(username)
174 175 if user is None or not user.active:
175 176 return HTTPForbidden()(environ, start_response)
176 177 username = user.username
177 178 except:
178 179 log.error(traceback.format_exc())
179 180 return HTTPInternalServerError()(environ, start_response)
180 181
181 182 #check permissions for this repository
182 183 perm = self._check_permission(action, user, repo_name)
183 184 if perm is not True:
184 185 return HTTPForbidden()(environ, start_response)
185 186
186 187 extras = {
187 188 'ip': ipaddr,
188 189 'username': username,
189 190 'action': action,
190 191 'repository': repo_name,
191 192 'scm': 'git',
192 193 }
193
194 # set the environ variables for this request
195 os.environ['RC_SCM_DATA'] = json.dumps(extras)
194 196 #===================================================================
195 197 # GIT REQUEST HANDLING
196 198 #===================================================================
197 199 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
198 200 log.debug('Repository path is %s' % repo_path)
199 201
200 202 baseui = make_ui('db')
201 203 self.__inject_extras(repo_path, baseui, extras)
202 204
203 205 try:
204 206 # invalidate cache on push
205 207 if action == 'push':
206 208 self._invalidate_cache(repo_name)
207 209 self._handle_githooks(repo_name, action, baseui, environ)
208 210
209 211 log.info('%s action on GIT repo "%s"' % (action, repo_name))
210 212 app = self.__make_app(repo_name, repo_path, username)
211 213 return app(environ, start_response)
212 214 except Exception:
213 215 log.error(traceback.format_exc())
214 216 return HTTPInternalServerError()(environ, start_response)
215 217
216 218 def __make_app(self, repo_name, repo_path, username):
217 219 """
218 220 Make an wsgi application using dulserver
219 221
220 222 :param repo_name: name of the repository
221 223 :param repo_path: full path to the repository
222 224 """
223 225
224 226 from rhodecode.lib.middleware.pygrack import make_wsgi_app
225 227 app = make_wsgi_app(
226 228 repo_root=safe_str(self.basepath),
227 229 repo_name=repo_name,
228 230 username=username,
229 231 )
230 232 app = GunzipFilter(LimitedInputFilter(app))
231 233 return app
232 234
233 235 def __get_repository(self, environ):
234 236 """
235 237 Get's repository name out of PATH_INFO header
236 238
237 239 :param environ: environ where PATH_INFO is stored
238 240 """
239 241 try:
240 242 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
241 243 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
242 244 except:
243 245 log.error(traceback.format_exc())
244 246 raise
245 247
246 248 return repo_name
247 249
248 250 def __get_user(self, username):
249 251 return User.get_by_username(username)
250 252
251 253 def __get_action(self, environ):
252 254 """
253 255 Maps git request commands into a pull or push command.
254 256
255 257 :param environ:
256 258 """
257 259 service = environ['QUERY_STRING'].split('=')
258 260
259 261 if len(service) > 1:
260 262 service_cmd = service[1]
261 263 mapping = {
262 264 'git-receive-pack': 'push',
263 265 'git-upload-pack': 'pull',
264 266 }
265 267 op = mapping[service_cmd]
266 268 self._git_stored_op = op
267 269 return op
268 270 else:
269 271 # try to fallback to stored variable as we don't know if the last
270 272 # operation is pull/push
271 273 op = getattr(self, '_git_stored_op', 'pull')
272 274 return op
273 275
274 276 def _handle_githooks(self, repo_name, action, baseui, environ):
275 277 """
276 278 Handles pull action, push is handled by post-receive hook
277 279 """
278 280 from rhodecode.lib.hooks import log_pull_action
279 281 service = environ['QUERY_STRING'].split('=')
280 282 if len(service) < 2:
281 283 return
282 284
283 285 from rhodecode.model.db import Repository
284 286 _repo = Repository.get_by_repo_name(repo_name)
285 287 _repo = _repo.scm_instance
286 288 _repo._repo.ui = baseui
287 289
288 290 _hooks = dict(baseui.configitems('hooks')) or {}
289 291 if action == 'pull' and _hooks.get(RhodeCodeUi.HOOK_PULL):
290 292 log_pull_action(ui=baseui, repo=_repo._repo)
291 293
292 294 def __inject_extras(self, repo_path, baseui, extras={}):
293 295 """
294 296 Injects some extra params into baseui instance
295 297
296 298 :param baseui: baseui instance
297 299 :param extras: dict with extra params to put into baseui
298 300 """
299 301
300 302 # make our hgweb quiet so it doesn't print output
301 303 baseui.setconfig('ui', 'quiet', 'true')
302 304
303 305 #inject some additional parameters that will be available in ui
304 306 #for hooks
305 307 for k, v in extras.items():
306 308 baseui.setconfig('rhodecode_extras', k, v)
@@ -1,261 +1,263 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.middleware.simplehg
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 SimpleHG middleware for handling mercurial protocol request
7 7 (push/clone etc.). 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 logging
29 29 import traceback
30 30 import urllib
31 31
32 32 from mercurial.error import RepoError
33 33 from mercurial.hgweb import hgweb_mod
34 34
35 35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
36 36 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
37 37 HTTPBadRequest, HTTPNotAcceptable
38 38
39 39 from rhodecode.lib.utils2 import safe_str
40 40 from rhodecode.lib.base import BaseVCSController
41 41 from rhodecode.lib.auth import get_container_username
42 42 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
43 from rhodecode.lib.compat import json
43 44 from rhodecode.model.db import User
44 45
45 46
46 47 log = logging.getLogger(__name__)
47 48
48 49
49 50 def is_mercurial(environ):
50 51 """
51 52 Returns True if request's target is mercurial server - header
52 53 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
53 54 """
54 55 http_accept = environ.get('HTTP_ACCEPT')
55 56 path_info = environ['PATH_INFO']
56 57 if http_accept and http_accept.startswith('application/mercurial'):
57 58 ishg_path = True
58 59 else:
59 60 ishg_path = False
60 61
61 62 log.debug('pathinfo: %s detected as HG %s' % (
62 63 path_info, ishg_path)
63 64 )
64 65 return ishg_path
65 66
66 67
67 68 class SimpleHg(BaseVCSController):
68 69
69 70 def _handle_request(self, environ, start_response):
70 71 if not is_mercurial(environ):
71 72 return self.application(environ, start_response)
72 73 if not self._check_ssl(environ, start_response):
73 74 return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
74 75
75 76 ipaddr = self._get_ip_addr(environ)
76 77 username = None
77 78 # skip passing error to error controller
78 79 environ['pylons.status_code_redirect'] = True
79 80
80 81 #======================================================================
81 82 # EXTRACT REPOSITORY NAME FROM ENV
82 83 #======================================================================
83 84 try:
84 85 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
85 86 log.debug('Extracted repo name is %s' % repo_name)
86 87 except:
87 88 return HTTPInternalServerError()(environ, start_response)
88 89
89 90 # quick check if that dir exists...
90 if is_valid_repo(repo_name, self.basepath) is False:
91 if is_valid_repo(repo_name, self.basepath, 'hg') is False:
91 92 return HTTPNotFound()(environ, start_response)
92 93
93 94 #======================================================================
94 95 # GET ACTION PULL or PUSH
95 96 #======================================================================
96 97 action = self.__get_action(environ)
97 98
98 99 #======================================================================
99 100 # CHECK ANONYMOUS PERMISSION
100 101 #======================================================================
101 102 if action in ['pull', 'push']:
102 103 anonymous_user = self.__get_user('default')
103 104 username = anonymous_user.username
104 105 anonymous_perm = self._check_permission(action, anonymous_user,
105 106 repo_name)
106 107
107 108 if anonymous_perm is not True or anonymous_user.active is False:
108 109 if anonymous_perm is not True:
109 110 log.debug('Not enough credentials to access this '
110 111 'repository as anonymous user')
111 112 if anonymous_user.active is False:
112 113 log.debug('Anonymous access is disabled, running '
113 114 'authentication')
114 115 #==============================================================
115 116 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
116 117 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
117 118 #==============================================================
118 119
119 120 # Attempting to retrieve username from the container
120 121 username = get_container_username(environ, self.config)
121 122
122 123 # If not authenticated by the container, running basic auth
123 124 if not username:
124 125 self.authenticate.realm = \
125 126 safe_str(self.config['rhodecode_realm'])
126 127 result = self.authenticate(environ)
127 128 if isinstance(result, str):
128 129 AUTH_TYPE.update(environ, 'basic')
129 130 REMOTE_USER.update(environ, result)
130 131 username = result
131 132 else:
132 133 return result.wsgi_application(environ, start_response)
133 134
134 135 #==============================================================
135 136 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
136 137 #==============================================================
137 138 try:
138 139 user = self.__get_user(username)
139 140 if user is None or not user.active:
140 141 return HTTPForbidden()(environ, start_response)
141 142 username = user.username
142 143 except:
143 144 log.error(traceback.format_exc())
144 145 return HTTPInternalServerError()(environ, start_response)
145 146
146 147 #check permissions for this repository
147 148 perm = self._check_permission(action, user, repo_name)
148 149 if perm is not True:
149 150 return HTTPForbidden()(environ, start_response)
150 151
151 152 # extras are injected into mercurial UI object and later available
152 153 # in hg hooks executed by rhodecode
153 154 extras = {
154 155 'ip': ipaddr,
155 156 'username': username,
156 157 'action': action,
157 158 'repository': repo_name,
158 159 'scm': 'hg',
159 160 }
160
161 # set the environ variables for this request
162 os.environ['RC_SCM_DATA'] = json.dumps(extras)
161 163 #======================================================================
162 164 # MERCURIAL REQUEST HANDLING
163 165 #======================================================================
164 166 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
165 167 log.debug('Repository path is %s' % repo_path)
166 168
167 169 baseui = make_ui('db')
168 170 self.__inject_extras(repo_path, baseui, extras)
169 171
170 172 try:
171 173 # invalidate cache on push
172 174 if action == 'push':
173 175 self._invalidate_cache(repo_name)
174 176 log.info('%s action on HG repo "%s"' % (action, repo_name))
175 177 app = self.__make_app(repo_path, baseui, extras)
176 178 return app(environ, start_response)
177 179 except RepoError, e:
178 180 if str(e).find('not found') != -1:
179 181 return HTTPNotFound()(environ, start_response)
180 182 except Exception:
181 183 log.error(traceback.format_exc())
182 184 return HTTPInternalServerError()(environ, start_response)
183 185
184 186 def __make_app(self, repo_name, baseui, extras):
185 187 """
186 188 Make an wsgi application using hgweb, and inject generated baseui
187 189 instance, additionally inject some extras into ui object
188 190 """
189 191 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
190 192
191 193 def __get_repository(self, environ):
192 194 """
193 195 Get's repository name out of PATH_INFO header
194 196
195 197 :param environ: environ where PATH_INFO is stored
196 198 """
197 199 try:
198 200 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
199 201 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
200 202 if repo_name.endswith('/'):
201 203 repo_name = repo_name.rstrip('/')
202 204 except:
203 205 log.error(traceback.format_exc())
204 206 raise
205 207
206 208 return repo_name
207 209
208 210 def __get_user(self, username):
209 211 return User.get_by_username(username)
210 212
211 213 def __get_action(self, environ):
212 214 """
213 215 Maps mercurial request commands into a clone,pull or push command.
214 216 This should always return a valid command string
215 217
216 218 :param environ:
217 219 """
218 220 mapping = {'changegroup': 'pull',
219 221 'changegroupsubset': 'pull',
220 222 'stream_out': 'pull',
221 223 'listkeys': 'pull',
222 224 'unbundle': 'push',
223 225 'pushkey': 'push', }
224 226 for qry in environ['QUERY_STRING'].split('&'):
225 227 if qry.startswith('cmd'):
226 228 cmd = qry.split('=')[-1]
227 229 if cmd in mapping:
228 230 return mapping[cmd]
229 231
230 232 return 'pull'
231 233
232 234 raise Exception('Unable to detect pull/push action !!'
233 235 'Are you using non standard command or client ?')
234 236
235 237 def __inject_extras(self, repo_path, baseui, extras={}):
236 238 """
237 239 Injects some extra params into baseui instance
238 240
239 241 also overwrites global settings with those takes from local hgrc file
240 242
241 243 :param baseui: baseui instance
242 244 :param extras: dict with extra params to put into baseui
243 245 """
244 246
245 247 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
246 248
247 249 # make our hgweb quiet so it doesn't print output
248 250 baseui.setconfig('ui', 'quiet', 'true')
249 251
250 252 #inject some additional parameters that will be available in ui
251 253 #for hooks
252 254 for k, v in extras.items():
253 255 baseui.setconfig('rhodecode_extras', k, v)
254 256
255 257 repoui = make_ui('file', hgrc, False)
256 258
257 259 if repoui:
258 260 #overwrite our ui instance with the section from hgrc file
259 261 for section in ui_sections:
260 262 for k, v in repoui.configitems(section):
261 263 baseui.setconfig(section, k, v)
@@ -1,663 +1,668 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Utilities library for RhodeCode
7 7
8 8 :created_on: Apr 18, 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 re
28 28 import logging
29 29 import datetime
30 30 import traceback
31 31 import paste
32 32 import beaker
33 33 import tarfile
34 34 import shutil
35 35 from os.path import abspath
36 36 from os.path import dirname as dn, join as jn
37 37
38 38 from paste.script.command import Command, BadCommand
39 39
40 40 from mercurial import ui, config
41 41
42 42 from webhelpers.text import collapse, remove_formatting, strip_tags
43 43
44 44 from rhodecode.lib.vcs import get_backend
45 45 from rhodecode.lib.vcs.backends.base import BaseChangeset
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47 from rhodecode.lib.vcs.utils.helpers import get_scm
48 48 from rhodecode.lib.vcs.exceptions import VCSError
49 49
50 50 from rhodecode.lib.caching_query import FromCache
51 51
52 52 from rhodecode.model import meta
53 53 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
54 54 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
55 55 from rhodecode.model.meta import Session
56 56 from rhodecode.model.repos_group import ReposGroupModel
57 57 from rhodecode.lib.utils2 import safe_str, safe_unicode
58 58 from rhodecode.lib.vcs.utils.fakemod import create_module
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
63 63
64 64
65 65 def recursive_replace(str_, replace=' '):
66 66 """
67 67 Recursive replace of given sign to just one instance
68 68
69 69 :param str_: given string
70 70 :param replace: char to find and replace multiple instances
71 71
72 72 Examples::
73 73 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
74 74 'Mighty-Mighty-Bo-sstones'
75 75 """
76 76
77 77 if str_.find(replace * 2) == -1:
78 78 return str_
79 79 else:
80 80 str_ = str_.replace(replace * 2, replace)
81 81 return recursive_replace(str_, replace)
82 82
83 83
84 84 def repo_name_slug(value):
85 85 """
86 86 Return slug of name of repository
87 87 This function is called on each creation/modification
88 88 of repository to prevent bad names in repo
89 89 """
90 90
91 91 slug = remove_formatting(value)
92 92 slug = strip_tags(slug)
93 93
94 94 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
95 95 slug = slug.replace(c, '-')
96 96 slug = recursive_replace(slug, '-')
97 97 slug = collapse(slug, '-')
98 98 return slug
99 99
100 100
101 101 def get_repo_slug(request):
102 102 _repo = request.environ['pylons.routes_dict'].get('repo_name')
103 103 if _repo:
104 104 _repo = _repo.rstrip('/')
105 105 return _repo
106 106
107 107
108 108 def get_repos_group_slug(request):
109 109 _group = request.environ['pylons.routes_dict'].get('group_name')
110 110 if _group:
111 111 _group = _group.rstrip('/')
112 112 return _group
113 113
114 114
115 115 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
116 116 """
117 117 Action logger for various actions made by users
118 118
119 119 :param user: user that made this action, can be a unique username string or
120 120 object containing user_id attribute
121 121 :param action: action to log, should be on of predefined unique actions for
122 122 easy translations
123 123 :param repo: string name of repository or object containing repo_id,
124 124 that action was made on
125 125 :param ipaddr: optional ip address from what the action was made
126 126 :param sa: optional sqlalchemy session
127 127
128 128 """
129 129
130 130 if not sa:
131 131 sa = meta.Session()
132 132
133 133 try:
134 134 if hasattr(user, 'user_id'):
135 135 user_obj = user
136 136 elif isinstance(user, basestring):
137 137 user_obj = User.get_by_username(user)
138 138 else:
139 139 raise Exception('You have to provide user object or username')
140 140
141 141 if hasattr(repo, 'repo_id'):
142 142 repo_obj = Repository.get(repo.repo_id)
143 143 repo_name = repo_obj.repo_name
144 144 elif isinstance(repo, basestring):
145 145 repo_name = repo.lstrip('/')
146 146 repo_obj = Repository.get_by_repo_name(repo_name)
147 147 else:
148 148 repo_obj = None
149 149 repo_name = ''
150 150
151 151 user_log = UserLog()
152 152 user_log.user_id = user_obj.user_id
153 153 user_log.action = safe_unicode(action)
154 154
155 155 user_log.repository = repo_obj
156 156 user_log.repository_name = repo_name
157 157
158 158 user_log.action_date = datetime.datetime.now()
159 159 user_log.user_ip = ipaddr
160 160 sa.add(user_log)
161 161
162 162 log.info(
163 163 'Adding user %s, action %s on %s' % (user_obj, action,
164 164 safe_unicode(repo))
165 165 )
166 166 if commit:
167 167 sa.commit()
168 168 except:
169 169 log.error(traceback.format_exc())
170 170 raise
171 171
172 172
173 173 def get_repos(path, recursive=False):
174 174 """
175 175 Scans given path for repos and return (name,(type,path)) tuple
176 176
177 177 :param path: path to scan for repositories
178 178 :param recursive: recursive search and return names with subdirs in front
179 179 """
180 180
181 181 # remove ending slash for better results
182 182 path = path.rstrip(os.sep)
183 183
184 184 def _get_repos(p):
185 185 if not os.access(p, os.W_OK):
186 186 return
187 187 for dirpath in os.listdir(p):
188 188 if os.path.isfile(os.path.join(p, dirpath)):
189 189 continue
190 190 cur_path = os.path.join(p, dirpath)
191 191 try:
192 192 scm_info = get_scm(cur_path)
193 193 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
194 194 except VCSError:
195 195 if not recursive:
196 196 continue
197 197 #check if this dir containts other repos for recursive scan
198 198 rec_path = os.path.join(p, dirpath)
199 199 if os.path.isdir(rec_path):
200 200 for inner_scm in _get_repos(rec_path):
201 201 yield inner_scm
202 202
203 203 return _get_repos(path)
204 204
205 205
206 def is_valid_repo(repo_name, base_path):
206 def is_valid_repo(repo_name, base_path, scm=None):
207 207 """
208 Returns True if given path is a valid repository False otherwise
208 Returns True if given path is a valid repository False otherwise.
209 If scm param is given also compare if given scm is the same as expected
210 from scm parameter
209 211
210 212 :param repo_name:
211 213 :param base_path:
214 :param scm:
212 215
213 216 :return True: if given path is a valid repository
214 217 """
215 218 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
216 219
217 220 try:
218 get_scm(full_path)
221 scm_ = get_scm(full_path)
222 if scm:
223 return scm_[0] == scm
219 224 return True
220 225 except VCSError:
221 226 return False
222 227
223 228
224 229 def is_valid_repos_group(repos_group_name, base_path):
225 230 """
226 231 Returns True if given path is a repos group False otherwise
227 232
228 233 :param repo_name:
229 234 :param base_path:
230 235 """
231 236 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
232 237
233 238 # check if it's not a repo
234 239 if is_valid_repo(repos_group_name, base_path):
235 240 return False
236 241
237 242 try:
238 243 # we need to check bare git repos at higher level
239 244 # since we might match branches/hooks/info/objects or possible
240 245 # other things inside bare git repo
241 246 get_scm(os.path.dirname(full_path))
242 247 return False
243 248 except VCSError:
244 249 pass
245 250
246 251 # check if it's a valid path
247 252 if os.path.isdir(full_path):
248 253 return True
249 254
250 255 return False
251 256
252 257
253 258 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
254 259 while True:
255 260 ok = raw_input(prompt)
256 261 if ok in ('y', 'ye', 'yes'):
257 262 return True
258 263 if ok in ('n', 'no', 'nop', 'nope'):
259 264 return False
260 265 retries = retries - 1
261 266 if retries < 0:
262 267 raise IOError
263 268 print complaint
264 269
265 270 #propagated from mercurial documentation
266 271 ui_sections = ['alias', 'auth',
267 272 'decode/encode', 'defaults',
268 273 'diff', 'email',
269 274 'extensions', 'format',
270 275 'merge-patterns', 'merge-tools',
271 276 'hooks', 'http_proxy',
272 277 'smtp', 'patch',
273 278 'paths', 'profiling',
274 279 'server', 'trusted',
275 280 'ui', 'web', ]
276 281
277 282
278 283 def make_ui(read_from='file', path=None, checkpaths=True):
279 284 """
280 285 A function that will read python rc files or database
281 286 and make an mercurial ui object from read options
282 287
283 288 :param path: path to mercurial config file
284 289 :param checkpaths: check the path
285 290 :param read_from: read from 'file' or 'db'
286 291 """
287 292
288 293 baseui = ui.ui()
289 294
290 295 # clean the baseui object
291 296 baseui._ocfg = config.config()
292 297 baseui._ucfg = config.config()
293 298 baseui._tcfg = config.config()
294 299
295 300 if read_from == 'file':
296 301 if not os.path.isfile(path):
297 302 log.debug('hgrc file is not present at %s skipping...' % path)
298 303 return False
299 304 log.debug('reading hgrc from %s' % path)
300 305 cfg = config.config()
301 306 cfg.read(path)
302 307 for section in ui_sections:
303 308 for k, v in cfg.items(section):
304 309 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
305 310 baseui.setconfig(section, k, v)
306 311
307 312 elif read_from == 'db':
308 313 sa = meta.Session()
309 314 ret = sa.query(RhodeCodeUi)\
310 315 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
311 316 .all()
312 317
313 318 hg_ui = ret
314 319 for ui_ in hg_ui:
315 320 if ui_.ui_active:
316 321 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
317 322 ui_.ui_key, ui_.ui_value)
318 323 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
319 324 if ui_.ui_key == 'push_ssl':
320 325 # force set push_ssl requirement to False, rhodecode
321 326 # handles that
322 327 baseui.setconfig(ui_.ui_section, ui_.ui_key, False)
323 328
324 329 meta.Session.remove()
325 330 return baseui
326 331
327 332
328 333 def set_rhodecode_config(config):
329 334 """
330 335 Updates pylons config with new settings from database
331 336
332 337 :param config:
333 338 """
334 339 hgsettings = RhodeCodeSetting.get_app_settings()
335 340
336 341 for k, v in hgsettings.items():
337 342 config[k] = v
338 343
339 344
340 345 def invalidate_cache(cache_key, *args):
341 346 """
342 347 Puts cache invalidation task into db for
343 348 further global cache invalidation
344 349 """
345 350
346 351 from rhodecode.model.scm import ScmModel
347 352
348 353 if cache_key.startswith('get_repo_cached_'):
349 354 name = cache_key.split('get_repo_cached_')[-1]
350 355 ScmModel().mark_for_invalidation(name)
351 356
352 357
353 358 def map_groups(path):
354 359 """
355 360 Given a full path to a repository, create all nested groups that this
356 361 repo is inside. This function creates parent-child relationships between
357 362 groups and creates default perms for all new groups.
358 363
359 364 :param paths: full path to repository
360 365 """
361 366 sa = meta.Session()
362 367 groups = path.split(Repository.url_sep())
363 368 parent = None
364 369 group = None
365 370
366 371 # last element is repo in nested groups structure
367 372 groups = groups[:-1]
368 373 rgm = ReposGroupModel(sa)
369 374 for lvl, group_name in enumerate(groups):
370 375 group_name = '/'.join(groups[:lvl] + [group_name])
371 376 group = RepoGroup.get_by_group_name(group_name)
372 377 desc = '%s group' % group_name
373 378
374 379 # skip folders that are now removed repos
375 380 if REMOVED_REPO_PAT.match(group_name):
376 381 break
377 382
378 383 if group is None:
379 384 log.debug('creating group level: %s group_name: %s' % (lvl,
380 385 group_name))
381 386 group = RepoGroup(group_name, parent)
382 387 group.group_description = desc
383 388 sa.add(group)
384 389 rgm._create_default_perms(group)
385 390 sa.flush()
386 391 parent = group
387 392 return group
388 393
389 394
390 395 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
391 396 install_git_hook=False):
392 397 """
393 398 maps all repos given in initial_repo_list, non existing repositories
394 399 are created, if remove_obsolete is True it also check for db entries
395 400 that are not in initial_repo_list and removes them.
396 401
397 402 :param initial_repo_list: list of repositories found by scanning methods
398 403 :param remove_obsolete: check for obsolete entries in database
399 404 :param install_git_hook: if this is True, also check and install githook
400 405 for a repo if missing
401 406 """
402 407 from rhodecode.model.repo import RepoModel
403 408 from rhodecode.model.scm import ScmModel
404 409 sa = meta.Session()
405 410 rm = RepoModel()
406 411 user = sa.query(User).filter(User.admin == True).first()
407 412 if user is None:
408 413 raise Exception('Missing administrative account !')
409 414 added = []
410 415
411 416 for name, repo in initial_repo_list.items():
412 417 group = map_groups(name)
413 418 db_repo = rm.get_by_repo_name(name)
414 419 # found repo that is on filesystem not in RhodeCode database
415 420 if not db_repo:
416 421 log.info('repository %s not found creating now' % name)
417 422 added.append(name)
418 423 desc = (repo.description
419 424 if repo.description != 'unknown'
420 425 else '%s repository' % name)
421 426 new_repo = rm.create_repo(
422 427 repo_name=name,
423 428 repo_type=repo.alias,
424 429 description=desc,
425 430 repos_group=getattr(group, 'group_id', None),
426 431 owner=user,
427 432 just_db=True
428 433 )
429 434 # we added that repo just now, and make sure it has githook
430 435 # installed
431 436 if new_repo.repo_type == 'git':
432 437 ScmModel().install_git_hook(new_repo.scm_instance)
433 438 elif install_git_hook:
434 439 if db_repo.repo_type == 'git':
435 440 ScmModel().install_git_hook(db_repo.scm_instance)
436 441 sa.commit()
437 442 removed = []
438 443 if remove_obsolete:
439 444 # remove from database those repositories that are not in the filesystem
440 445 for repo in sa.query(Repository).all():
441 446 if repo.repo_name not in initial_repo_list.keys():
442 447 log.debug("Removing non existing repository found in db `%s`" %
443 448 repo.repo_name)
444 449 try:
445 450 sa.delete(repo)
446 451 sa.commit()
447 452 removed.append(repo.repo_name)
448 453 except:
449 454 #don't hold further removals on error
450 455 log.error(traceback.format_exc())
451 456 sa.rollback()
452 457
453 458 # clear cache keys
454 459 log.debug("Clearing cache keys now...")
455 460 CacheInvalidation.clear_cache()
456 461 sa.commit()
457 462 return added, removed
458 463
459 464
460 465 # set cache regions for beaker so celery can utilise it
461 466 def add_cache(settings):
462 467 cache_settings = {'regions': None}
463 468 for key in settings.keys():
464 469 for prefix in ['beaker.cache.', 'cache.']:
465 470 if key.startswith(prefix):
466 471 name = key.split(prefix)[1].strip()
467 472 cache_settings[name] = settings[key].strip()
468 473 if cache_settings['regions']:
469 474 for region in cache_settings['regions'].split(','):
470 475 region = region.strip()
471 476 region_settings = {}
472 477 for key, value in cache_settings.items():
473 478 if key.startswith(region):
474 479 region_settings[key.split('.')[1]] = value
475 480 region_settings['expire'] = int(region_settings.get('expire',
476 481 60))
477 482 region_settings.setdefault('lock_dir',
478 483 cache_settings.get('lock_dir'))
479 484 region_settings.setdefault('data_dir',
480 485 cache_settings.get('data_dir'))
481 486
482 487 if 'type' not in region_settings:
483 488 region_settings['type'] = cache_settings.get('type',
484 489 'memory')
485 490 beaker.cache.cache_regions[region] = region_settings
486 491
487 492
488 493 def load_rcextensions(root_path):
489 494 import rhodecode
490 495 from rhodecode.config import conf
491 496
492 497 path = os.path.join(root_path, 'rcextensions', '__init__.py')
493 498 if os.path.isfile(path):
494 499 rcext = create_module('rc', path)
495 500 EXT = rhodecode.EXTENSIONS = rcext
496 501 log.debug('Found rcextensions now loading %s...' % rcext)
497 502
498 503 # Additional mappings that are not present in the pygments lexers
499 504 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
500 505
501 506 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
502 507
503 508 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
504 509 log.debug('settings custom INDEX_EXTENSIONS')
505 510 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
506 511
507 512 #ADDITIONAL MAPPINGS
508 513 log.debug('adding extra into INDEX_EXTENSIONS')
509 514 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
510 515
511 516
512 517 #==============================================================================
513 518 # TEST FUNCTIONS AND CREATORS
514 519 #==============================================================================
515 520 def create_test_index(repo_location, config, full_index):
516 521 """
517 522 Makes default test index
518 523
519 524 :param config: test config
520 525 :param full_index:
521 526 """
522 527
523 528 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
524 529 from rhodecode.lib.pidlock import DaemonLock, LockHeld
525 530
526 531 repo_location = repo_location
527 532
528 533 index_location = os.path.join(config['app_conf']['index_dir'])
529 534 if not os.path.exists(index_location):
530 535 os.makedirs(index_location)
531 536
532 537 try:
533 538 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
534 539 WhooshIndexingDaemon(index_location=index_location,
535 540 repo_location=repo_location)\
536 541 .run(full_index=full_index)
537 542 l.release()
538 543 except LockHeld:
539 544 pass
540 545
541 546
542 547 def create_test_env(repos_test_path, config):
543 548 """
544 549 Makes a fresh database and
545 550 install test repository into tmp dir
546 551 """
547 552 from rhodecode.lib.db_manage import DbManage
548 553 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
549 554
550 555 # PART ONE create db
551 556 dbconf = config['sqlalchemy.db1.url']
552 557 log.debug('making test db %s' % dbconf)
553 558
554 559 # create test dir if it doesn't exist
555 560 if not os.path.isdir(repos_test_path):
556 561 log.debug('Creating testdir %s' % repos_test_path)
557 562 os.makedirs(repos_test_path)
558 563
559 564 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
560 565 tests=True)
561 566 dbmanage.create_tables(override=True)
562 567 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
563 568 dbmanage.create_default_user()
564 569 dbmanage.admin_prompt()
565 570 dbmanage.create_permissions()
566 571 dbmanage.populate_default_permissions()
567 572 Session().commit()
568 573 # PART TWO make test repo
569 574 log.debug('making test vcs repositories')
570 575
571 576 idx_path = config['app_conf']['index_dir']
572 577 data_path = config['app_conf']['cache_dir']
573 578
574 579 #clean index and data
575 580 if idx_path and os.path.exists(idx_path):
576 581 log.debug('remove %s' % idx_path)
577 582 shutil.rmtree(idx_path)
578 583
579 584 if data_path and os.path.exists(data_path):
580 585 log.debug('remove %s' % data_path)
581 586 shutil.rmtree(data_path)
582 587
583 588 #CREATE DEFAULT TEST REPOS
584 589 cur_dir = dn(dn(abspath(__file__)))
585 590 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
586 591 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
587 592 tar.close()
588 593
589 594 cur_dir = dn(dn(abspath(__file__)))
590 595 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
591 596 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
592 597 tar.close()
593 598
594 599 #LOAD VCS test stuff
595 600 from rhodecode.tests.vcs import setup_package
596 601 setup_package()
597 602
598 603
599 604 #==============================================================================
600 605 # PASTER COMMANDS
601 606 #==============================================================================
602 607 class BasePasterCommand(Command):
603 608 """
604 609 Abstract Base Class for paster commands.
605 610
606 611 The celery commands are somewhat aggressive about loading
607 612 celery.conf, and since our module sets the `CELERY_LOADER`
608 613 environment variable to our loader, we have to bootstrap a bit and
609 614 make sure we've had a chance to load the pylons config off of the
610 615 command line, otherwise everything fails.
611 616 """
612 617 min_args = 1
613 618 min_args_error = "Please provide a paster config file as an argument."
614 619 takes_config_file = 1
615 620 requires_config_file = True
616 621
617 622 def notify_msg(self, msg, log=False):
618 623 """Make a notification to user, additionally if logger is passed
619 624 it logs this action using given logger
620 625
621 626 :param msg: message that will be printed to user
622 627 :param log: logging instance, to use to additionally log this message
623 628
624 629 """
625 630 if log and isinstance(log, logging):
626 631 log(msg)
627 632
628 633 def run(self, args):
629 634 """
630 635 Overrides Command.run
631 636
632 637 Checks for a config file argument and loads it.
633 638 """
634 639 if len(args) < self.min_args:
635 640 raise BadCommand(
636 641 self.min_args_error % {'min_args': self.min_args,
637 642 'actual_args': len(args)})
638 643
639 644 # Decrement because we're going to lob off the first argument.
640 645 # @@ This is hacky
641 646 self.min_args -= 1
642 647 self.bootstrap_config(args[0])
643 648 self.update_parser()
644 649 return super(BasePasterCommand, self).run(args[1:])
645 650
646 651 def update_parser(self):
647 652 """
648 653 Abstract method. Allows for the class's parser to be updated
649 654 before the superclass's `run` method is called. Necessary to
650 655 allow options/arguments to be passed through to the underlying
651 656 celery command.
652 657 """
653 658 raise NotImplementedError("Abstract Method.")
654 659
655 660 def bootstrap_config(self, conf):
656 661 """
657 662 Loads the pylons configuration.
658 663 """
659 664 from pylons import config as pylonsconfig
660 665
661 666 self.path_to_ini_file = os.path.realpath(conf)
662 667 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
663 668 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
General Comments 0
You need to be logged in to leave comments. Login now