##// END OF EJS Templates
Implemented #628: Pass server URL to rc-extensions hooks...
marcink -
r2969:5085e51f beta
parent child Browse files
Show More
@@ -1,112 +1,118 b''
1 1 # Additional mappings that are not present in the pygments lexers
2 2 # used for building stats
3 3 # format is {'ext':['Names']} eg. {'py':['Python']} note: there can be
4 4 # more than one name for extension
5 5 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
6 6 # build by pygments
7 7 EXTRA_MAPPINGS = {}
8 8
9 9 #==============================================================================
10 10 # WHOOSH INDEX EXTENSIONS
11 11 #==============================================================================
12 12 # if INDEX_EXTENSIONS is [] it'll use pygments lexers extensions by default.
13 13 # To set your own just add to this list extensions to index with content
14 14 INDEX_EXTENSIONS = []
15 15
16 16 # additional extensions for indexing besides the default from pygments
17 17 # those get's added to INDEX_EXTENSIONS
18 18 EXTRA_INDEX_EXTENSIONS = []
19 19
20 20
21 21 #==============================================================================
22 22 # POST CREATE REPOSITORY HOOK
23 23 #==============================================================================
24 24 # this function will be executed after each repository is created
25 25 def _crhook(*args, **kwargs):
26 26 """
27 27 Post create repository HOOK
28 28 kwargs available:
29 29 :param repo_name:
30 30 :param repo_type:
31 31 :param description:
32 32 :param private:
33 33 :param created_on:
34 34 :param enable_downloads:
35 35 :param repo_id:
36 36 :param user_id:
37 37 :param enable_statistics:
38 38 :param clone_uri:
39 39 :param fork_id:
40 40 :param group_id:
41 41 :param created_by:
42 42 """
43 43 return 0
44 44 CREATE_REPO_HOOK = _crhook
45 45
46 46
47 47 #==============================================================================
48 48 # POST DELETE REPOSITORY HOOK
49 49 #==============================================================================
50 50 # this function will be executed after each repository deletion
51 51 def _dlhook(*args, **kwargs):
52 52 """
53 53 Post create repository HOOK
54 54 kwargs available:
55 55 :param repo_name:
56 56 :param repo_type:
57 57 :param description:
58 58 :param private:
59 59 :param created_on:
60 60 :param enable_downloads:
61 61 :param repo_id:
62 62 :param user_id:
63 63 :param enable_statistics:
64 64 :param clone_uri:
65 65 :param fork_id:
66 66 :param group_id:
67 67 :param deleted_by:
68 68 :param deleted_on:
69 69 """
70 70 return 0
71 71 DELETE_REPO_HOOK = _dlhook
72 72
73 73
74 74 #==============================================================================
75 75 # POST PUSH HOOK
76 76 #==============================================================================
77 77
78 # this function will be executed after each push it's runned after the build-in
79 # hook that rhodecode uses for logging pushes
78 # this function will be executed after each push it's executed after the
79 # build-in hook that RhodeCode uses for logging pushes
80 80 def _pushhook(*args, **kwargs):
81 81 """
82 82 Post push hook
83 83 kwargs available:
84 84
85 :param server_url: url of instance that triggered this hook
86 :param config: path to .ini config used
87 :param scm: type of VS 'git' or 'hg'
85 88 :param username: name of user who pushed
86 89 :param ip: ip of who pushed
87 :param action: pull
90 :param action: push
88 91 :param repository: repository name
89 :param pushed_revs: generator of pushed revisions
92 :param pushed_revs: list of pushed revisions
90 93 """
91 94 return 0
92 95 PUSH_HOOK = _pushhook
93 96
94 97
95 98 #==============================================================================
96 99 # POST PULL HOOK
97 100 #==============================================================================
98 101
99 # this function will be executed after each push it's runned after the build-in
100 # hook that rhodecode uses for logging pushes
102 # this function will be executed after each push it's executed after the
103 # build-in hook that RhodeCode uses for logging pulls
101 104 def _pullhook(*args, **kwargs):
102 105 """
103 106 Post pull hook
104 107 kwargs available::
105 108
109 :param server_url: url of instance that triggered this hook
110 :param config: path to .ini config used
111 :param scm: type of VS 'git' or 'hg'
106 112 :param username: name of user who pulled
107 :param ip: ip of who pushed
113 :param ip: ip of who pulled
108 114 :param action: pull
109 115 :param repository: repository name
110 116 """
111 117 return 0
112 118 PULL_HOOK = _pullhook
@@ -1,338 +1,340 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 from rhodecode.lib.exceptions import HTTPLockedRC
35 35 from rhodecode.lib.hooks import pre_pull
36 36
37 37
38 38 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
39 39
40 40 def handle(self):
41 41 write = lambda x: self.proto.write_sideband(1, x)
42 42
43 43 graph_walker = dulserver.ProtocolGraphWalker(self,
44 44 self.repo.object_store,
45 45 self.repo.get_peeled)
46 46 objects_iter = self.repo.fetch_objects(
47 47 graph_walker.determine_wants, graph_walker, self.progress,
48 48 get_tagged=self.get_tagged)
49 49
50 50 # Did the process short-circuit (e.g. in a stateless RPC call)? Note
51 51 # that the client still expects a 0-object pack in most cases.
52 52 if objects_iter is None:
53 53 return
54 54
55 55 self.progress("counting objects: %d, done.\n" % len(objects_iter))
56 56 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
57 57 objects_iter)
58 58 messages = []
59 59 messages.append('thank you for using rhodecode')
60 60
61 61 for msg in messages:
62 62 self.progress(msg + "\n")
63 63 # we are done
64 64 self.proto.write("0000")
65 65
66 66
67 67 dulserver.DEFAULT_HANDLERS = {
68 68 #git-ls-remote, git-clone, git-fetch and git-pull
69 69 'git-upload-pack': SimpleGitUploadPackHandler,
70 70 #git-push
71 71 'git-receive-pack': dulserver.ReceivePackHandler,
72 72 }
73 73
74 74 # not used for now until dulwich get's fixed
75 75 #from dulwich.repo import Repo
76 76 #from dulwich.web import make_wsgi_chain
77 77
78 78 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
79 79 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
80 80 HTTPBadRequest, HTTPNotAcceptable
81 81
82 from rhodecode.lib.utils2 import safe_str, fix_PATH
82 from rhodecode.lib.utils2 import safe_str, fix_PATH, get_server_url
83 83 from rhodecode.lib.base import BaseVCSController
84 84 from rhodecode.lib.auth import get_container_username
85 85 from rhodecode.lib.utils import is_valid_repo, make_ui
86 86 from rhodecode.lib.compat import json
87 87 from rhodecode.model.db import User, RhodeCodeUi
88 88
89 89 log = logging.getLogger(__name__)
90 90
91 91
92 92 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
93 93
94 94
95 95 def is_git(environ):
96 96 path_info = environ['PATH_INFO']
97 97 isgit_path = GIT_PROTO_PAT.match(path_info)
98 98 log.debug('pathinfo: %s detected as GIT %s' % (
99 99 path_info, isgit_path != None)
100 100 )
101 101 return isgit_path
102 102
103 103
104 104 class SimpleGit(BaseVCSController):
105 105
106 106 def _handle_request(self, environ, start_response):
107 107 if not is_git(environ):
108 108 return self.application(environ, start_response)
109 109 if not self._check_ssl(environ, start_response):
110 110 return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
111 111
112 112 ipaddr = self._get_ip_addr(environ)
113 113 username = None
114 114 self._git_first_op = False
115 115 # skip passing error to error controller
116 116 environ['pylons.status_code_redirect'] = True
117 117
118 118 #======================================================================
119 119 # EXTRACT REPOSITORY NAME FROM ENV
120 120 #======================================================================
121 121 try:
122 122 repo_name = self.__get_repository(environ)
123 123 log.debug('Extracted repo name is %s' % repo_name)
124 124 except:
125 125 return HTTPInternalServerError()(environ, start_response)
126 126
127 127 # quick check if that dir exists...
128 128 if is_valid_repo(repo_name, self.basepath, 'git') is False:
129 129 return HTTPNotFound()(environ, start_response)
130 130
131 131 #======================================================================
132 132 # GET ACTION PULL or PUSH
133 133 #======================================================================
134 134 action = self.__get_action(environ)
135 135
136 136 #======================================================================
137 137 # CHECK ANONYMOUS PERMISSION
138 138 #======================================================================
139 139 if action in ['pull', 'push']:
140 140 anonymous_user = self.__get_user('default')
141 141 username = anonymous_user.username
142 142 anonymous_perm = self._check_permission(action, anonymous_user,
143 143 repo_name)
144 144
145 145 if anonymous_perm is not True or anonymous_user.active is False:
146 146 if anonymous_perm is not True:
147 147 log.debug('Not enough credentials to access this '
148 148 'repository as anonymous user')
149 149 if anonymous_user.active is False:
150 150 log.debug('Anonymous access is disabled, running '
151 151 'authentication')
152 152 #==============================================================
153 153 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
154 154 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
155 155 #==============================================================
156 156
157 157 # Attempting to retrieve username from the container
158 158 username = get_container_username(environ, self.config)
159 159
160 160 # If not authenticated by the container, running basic auth
161 161 if not username:
162 162 self.authenticate.realm = \
163 163 safe_str(self.config['rhodecode_realm'])
164 164 result = self.authenticate(environ)
165 165 if isinstance(result, str):
166 166 AUTH_TYPE.update(environ, 'basic')
167 167 REMOTE_USER.update(environ, result)
168 168 username = result
169 169 else:
170 170 return result.wsgi_application(environ, start_response)
171 171
172 172 #==============================================================
173 173 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
174 174 #==============================================================
175 175 try:
176 176 user = self.__get_user(username)
177 177 if user is None or not user.active:
178 178 return HTTPForbidden()(environ, start_response)
179 179 username = user.username
180 180 except:
181 181 log.error(traceback.format_exc())
182 182 return HTTPInternalServerError()(environ, start_response)
183 183
184 184 #check permissions for this repository
185 185 perm = self._check_permission(action, user, repo_name)
186 186 if perm is not True:
187 187 return HTTPForbidden()(environ, start_response)
188 188
189 189 # extras are injected into UI object and later available
190 190 # in hooks executed by rhodecode
191 191 from rhodecode import CONFIG
192 server_url = get_server_url(environ)
192 193 extras = {
193 194 'ip': ipaddr,
194 195 'username': username,
195 196 'action': action,
196 197 'repository': repo_name,
197 198 'scm': 'git',
198 199 'config': CONFIG['__file__'],
200 'server_url': server_url,
199 201 'make_lock': None,
200 202 'locked_by': [None, None]
201 203 }
202 204
203 205 #===================================================================
204 206 # GIT REQUEST HANDLING
205 207 #===================================================================
206 208 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
207 209 log.debug('Repository path is %s' % repo_path)
208 210
209 211 # CHECK LOCKING only if it's not ANONYMOUS USER
210 212 if username != User.DEFAULT_USER:
211 213 log.debug('Checking locking on repository')
212 214 (make_lock,
213 215 locked,
214 216 locked_by) = self._check_locking_state(
215 217 environ=environ, action=action,
216 218 repo=repo_name, user_id=user.user_id
217 219 )
218 220 # store the make_lock for later evaluation in hooks
219 221 extras.update({'make_lock': make_lock,
220 222 'locked_by': locked_by})
221 223 # set the environ variables for this request
222 224 os.environ['RC_SCM_DATA'] = json.dumps(extras)
223 225 fix_PATH()
224 226 log.debug('HOOKS extras is %s' % extras)
225 227 baseui = make_ui('db')
226 228 self.__inject_extras(repo_path, baseui, extras)
227 229
228 230 try:
229 231 # invalidate cache on push
230 232 if action == 'push':
231 233 self._invalidate_cache(repo_name)
232 234 self._handle_githooks(repo_name, action, baseui, environ)
233 235
234 236 log.info('%s action on GIT repo "%s"' % (action, repo_name))
235 237 app = self.__make_app(repo_name, repo_path, extras)
236 238 return app(environ, start_response)
237 239 except HTTPLockedRC, e:
238 240 log.debug('Repositry LOCKED ret code 423!')
239 241 return e(environ, start_response)
240 242 except Exception:
241 243 log.error(traceback.format_exc())
242 244 return HTTPInternalServerError()(environ, start_response)
243 245
244 246 def __make_app(self, repo_name, repo_path, extras):
245 247 """
246 248 Make an wsgi application using dulserver
247 249
248 250 :param repo_name: name of the repository
249 251 :param repo_path: full path to the repository
250 252 """
251 253
252 254 from rhodecode.lib.middleware.pygrack import make_wsgi_app
253 255 app = make_wsgi_app(
254 256 repo_root=safe_str(self.basepath),
255 257 repo_name=repo_name,
256 258 extras=extras,
257 259 )
258 260 app = GunzipFilter(LimitedInputFilter(app))
259 261 return app
260 262
261 263 def __get_repository(self, environ):
262 264 """
263 265 Get's repository name out of PATH_INFO header
264 266
265 267 :param environ: environ where PATH_INFO is stored
266 268 """
267 269 try:
268 270 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
269 271 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
270 272 except:
271 273 log.error(traceback.format_exc())
272 274 raise
273 275
274 276 return repo_name
275 277
276 278 def __get_user(self, username):
277 279 return User.get_by_username(username)
278 280
279 281 def __get_action(self, environ):
280 282 """
281 283 Maps git request commands into a pull or push command.
282 284
283 285 :param environ:
284 286 """
285 287 service = environ['QUERY_STRING'].split('=')
286 288
287 289 if len(service) > 1:
288 290 service_cmd = service[1]
289 291 mapping = {
290 292 'git-receive-pack': 'push',
291 293 'git-upload-pack': 'pull',
292 294 }
293 295 op = mapping[service_cmd]
294 296 self._git_stored_op = op
295 297 return op
296 298 else:
297 299 # try to fallback to stored variable as we don't know if the last
298 300 # operation is pull/push
299 301 op = getattr(self, '_git_stored_op', 'pull')
300 302 return op
301 303
302 304 def _handle_githooks(self, repo_name, action, baseui, environ):
303 305 """
304 306 Handles pull action, push is handled by post-receive hook
305 307 """
306 308 from rhodecode.lib.hooks import log_pull_action
307 309 service = environ['QUERY_STRING'].split('=')
308 310
309 311 if len(service) < 2:
310 312 return
311 313
312 314 from rhodecode.model.db import Repository
313 315 _repo = Repository.get_by_repo_name(repo_name)
314 316 _repo = _repo.scm_instance
315 317 _repo._repo.ui = baseui
316 318
317 319 _hooks = dict(baseui.configitems('hooks')) or {}
318 320 if action == 'pull':
319 321 # stupid git, emulate pre-pull hook !
320 322 pre_pull(ui=baseui, repo=_repo._repo)
321 323 if action == 'pull' and _hooks.get(RhodeCodeUi.HOOK_PULL):
322 324 log_pull_action(ui=baseui, repo=_repo._repo)
323 325
324 326 def __inject_extras(self, repo_path, baseui, extras={}):
325 327 """
326 328 Injects some extra params into baseui instance
327 329
328 330 :param baseui: baseui instance
329 331 :param extras: dict with extra params to put into baseui
330 332 """
331 333
332 334 # make our hgweb quiet so it doesn't print output
333 335 baseui.setconfig('ui', 'quiet', 'true')
334 336
335 337 #inject some additional parameters that will be available in ui
336 338 #for hooks
337 339 for k, v in extras.items():
338 340 baseui.setconfig('rhodecode_extras', k, v)
@@ -1,285 +1,287 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
31 31 from mercurial.error import RepoError
32 32 from mercurial.hgweb import hgweb_mod
33 33
34 34 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 35 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
36 36 HTTPBadRequest, HTTPNotAcceptable
37 37
38 from rhodecode.lib.utils2 import safe_str, fix_PATH
38 from rhodecode.lib.utils2 import safe_str, fix_PATH, get_server_url
39 39 from rhodecode.lib.base import BaseVCSController
40 40 from rhodecode.lib.auth import get_container_username
41 41 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
42 42 from rhodecode.lib.compat import json
43 43 from rhodecode.model.db import User
44 44 from rhodecode.lib.exceptions import HTTPLockedRC
45 45
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 def is_mercurial(environ):
51 51 """
52 52 Returns True if request's target is mercurial server - header
53 53 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
54 54 """
55 55 http_accept = environ.get('HTTP_ACCEPT')
56 56 path_info = environ['PATH_INFO']
57 57 if http_accept and http_accept.startswith('application/mercurial'):
58 58 ishg_path = True
59 59 else:
60 60 ishg_path = False
61 61
62 62 log.debug('pathinfo: %s detected as HG %s' % (
63 63 path_info, ishg_path)
64 64 )
65 65 return ishg_path
66 66
67 67
68 68 class SimpleHg(BaseVCSController):
69 69
70 70 def _handle_request(self, environ, start_response):
71 71 if not is_mercurial(environ):
72 72 return self.application(environ, start_response)
73 73 if not self._check_ssl(environ, start_response):
74 74 return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
75 75
76 76 ipaddr = self._get_ip_addr(environ)
77 77 username = None
78 78 # skip passing error to error controller
79 79 environ['pylons.status_code_redirect'] = True
80 80
81 81 #======================================================================
82 82 # EXTRACT REPOSITORY NAME FROM ENV
83 83 #======================================================================
84 84 try:
85 85 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
86 86 log.debug('Extracted repo name is %s' % repo_name)
87 87 except:
88 88 return HTTPInternalServerError()(environ, start_response)
89 89
90 90 # quick check if that dir exists...
91 91 if is_valid_repo(repo_name, self.basepath, 'hg') is False:
92 92 return HTTPNotFound()(environ, start_response)
93 93
94 94 #======================================================================
95 95 # GET ACTION PULL or PUSH
96 96 #======================================================================
97 97 action = self.__get_action(environ)
98 98
99 99 #======================================================================
100 100 # CHECK ANONYMOUS PERMISSION
101 101 #======================================================================
102 102 if action in ['pull', 'push']:
103 103 anonymous_user = self.__get_user('default')
104 104 username = anonymous_user.username
105 105 anonymous_perm = self._check_permission(action, anonymous_user,
106 106 repo_name)
107 107
108 108 if anonymous_perm is not True or anonymous_user.active is False:
109 109 if anonymous_perm is not True:
110 110 log.debug('Not enough credentials to access this '
111 111 'repository as anonymous user')
112 112 if anonymous_user.active is False:
113 113 log.debug('Anonymous access is disabled, running '
114 114 'authentication')
115 115 #==============================================================
116 116 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
117 117 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
118 118 #==============================================================
119 119
120 120 # Attempting to retrieve username from the container
121 121 username = get_container_username(environ, self.config)
122 122
123 123 # If not authenticated by the container, running basic auth
124 124 if not username:
125 125 self.authenticate.realm = \
126 126 safe_str(self.config['rhodecode_realm'])
127 127 result = self.authenticate(environ)
128 128 if isinstance(result, str):
129 129 AUTH_TYPE.update(environ, 'basic')
130 130 REMOTE_USER.update(environ, result)
131 131 username = result
132 132 else:
133 133 return result.wsgi_application(environ, start_response)
134 134
135 135 #==============================================================
136 136 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
137 137 #==============================================================
138 138 try:
139 139 user = self.__get_user(username)
140 140 if user is None or not user.active:
141 141 return HTTPForbidden()(environ, start_response)
142 142 username = user.username
143 143 except:
144 144 log.error(traceback.format_exc())
145 145 return HTTPInternalServerError()(environ, start_response)
146 146
147 147 #check permissions for this repository
148 148 perm = self._check_permission(action, user, repo_name)
149 149 if perm is not True:
150 150 return HTTPForbidden()(environ, start_response)
151 151
152 152 # extras are injected into mercurial UI object and later available
153 153 # in hg hooks executed by rhodecode
154 154 from rhodecode import CONFIG
155 server_url = get_server_url(environ)
155 156 extras = {
156 157 'ip': ipaddr,
157 158 'username': username,
158 159 'action': action,
159 160 'repository': repo_name,
160 161 'scm': 'hg',
161 162 'config': CONFIG['__file__'],
163 'server_url': server_url,
162 164 'make_lock': None,
163 165 'locked_by': [None, None]
164 166 }
165 167 #======================================================================
166 168 # MERCURIAL REQUEST HANDLING
167 169 #======================================================================
168 170 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
169 171 log.debug('Repository path is %s' % repo_path)
170 172
171 173 # CHECK LOCKING only if it's not ANONYMOUS USER
172 174 if username != User.DEFAULT_USER:
173 175 log.debug('Checking locking on repository')
174 176 (make_lock,
175 177 locked,
176 178 locked_by) = self._check_locking_state(
177 179 environ=environ, action=action,
178 180 repo=repo_name, user_id=user.user_id
179 181 )
180 182 # store the make_lock for later evaluation in hooks
181 183 extras.update({'make_lock': make_lock,
182 184 'locked_by': locked_by})
183 185
184 186 # set the environ variables for this request
185 187 os.environ['RC_SCM_DATA'] = json.dumps(extras)
186 188 fix_PATH()
187 189 log.debug('HOOKS extras is %s' % extras)
188 190 baseui = make_ui('db')
189 191 self.__inject_extras(repo_path, baseui, extras)
190 192
191 193 try:
192 194 # invalidate cache on push
193 195 if action == 'push':
194 196 self._invalidate_cache(repo_name)
195 197 log.info('%s action on HG repo "%s"' % (action, repo_name))
196 198 app = self.__make_app(repo_path, baseui, extras)
197 199 return app(environ, start_response)
198 200 except RepoError, e:
199 201 if str(e).find('not found') != -1:
200 202 return HTTPNotFound()(environ, start_response)
201 203 except HTTPLockedRC, e:
202 204 log.debug('Repositry LOCKED ret code 423!')
203 205 return e(environ, start_response)
204 206 except Exception:
205 207 log.error(traceback.format_exc())
206 208 return HTTPInternalServerError()(environ, start_response)
207 209
208 210 def __make_app(self, repo_name, baseui, extras):
209 211 """
210 212 Make an wsgi application using hgweb, and inject generated baseui
211 213 instance, additionally inject some extras into ui object
212 214 """
213 215 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
214 216
215 217 def __get_repository(self, environ):
216 218 """
217 219 Get's repository name out of PATH_INFO header
218 220
219 221 :param environ: environ where PATH_INFO is stored
220 222 """
221 223 try:
222 224 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
223 225 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
224 226 if repo_name.endswith('/'):
225 227 repo_name = repo_name.rstrip('/')
226 228 except:
227 229 log.error(traceback.format_exc())
228 230 raise
229 231
230 232 return repo_name
231 233
232 234 def __get_user(self, username):
233 235 return User.get_by_username(username)
234 236
235 237 def __get_action(self, environ):
236 238 """
237 239 Maps mercurial request commands into a clone,pull or push command.
238 240 This should always return a valid command string
239 241
240 242 :param environ:
241 243 """
242 244 mapping = {'changegroup': 'pull',
243 245 'changegroupsubset': 'pull',
244 246 'stream_out': 'pull',
245 247 'listkeys': 'pull',
246 248 'unbundle': 'push',
247 249 'pushkey': 'push', }
248 250 for qry in environ['QUERY_STRING'].split('&'):
249 251 if qry.startswith('cmd'):
250 252 cmd = qry.split('=')[-1]
251 253 if cmd in mapping:
252 254 return mapping[cmd]
253 255
254 256 return 'pull'
255 257
256 258 raise Exception('Unable to detect pull/push action !!'
257 259 'Are you using non standard command or client ?')
258 260
259 261 def __inject_extras(self, repo_path, baseui, extras={}):
260 262 """
261 263 Injects some extra params into baseui instance
262 264
263 265 also overwrites global settings with those takes from local hgrc file
264 266
265 267 :param baseui: baseui instance
266 268 :param extras: dict with extra params to put into baseui
267 269 """
268 270
269 271 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
270 272
271 273 # make our hgweb quiet so it doesn't print output
272 274 baseui.setconfig('ui', 'quiet', 'true')
273 275
274 276 #inject some additional parameters that will be available in ui
275 277 #for hooks
276 278 for k, v in extras.items():
277 279 baseui.setconfig('rhodecode_extras', k, v)
278 280
279 281 repoui = make_ui('file', hgrc, False)
280 282
281 283 if repoui:
282 284 #overwrite our ui instance with the section from hgrc file
283 285 for section in ui_sections:
284 286 for k, v in repoui.configitems(section):
285 287 baseui.setconfig(section, k, v)
@@ -1,518 +1,525 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Some simple helper functions
7 7
8 8 :created_on: Jan 5, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2011-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 re
27 27 import time
28 28 import datetime
29 import webob
30
29 31 from pylons.i18n.translation import _, ungettext
30 32 from rhodecode.lib.vcs.utils.lazy import LazyProperty
31 33
32 34
33 35 def __get_lem():
34 36 """
35 37 Get language extension map based on what's inside pygments lexers
36 38 """
37 39 from pygments import lexers
38 40 from string import lower
39 41 from collections import defaultdict
40 42
41 43 d = defaultdict(lambda: [])
42 44
43 45 def __clean(s):
44 46 s = s.lstrip('*')
45 47 s = s.lstrip('.')
46 48
47 49 if s.find('[') != -1:
48 50 exts = []
49 51 start, stop = s.find('['), s.find(']')
50 52
51 53 for suffix in s[start + 1:stop]:
52 54 exts.append(s[:s.find('[')] + suffix)
53 55 return map(lower, exts)
54 56 else:
55 57 return map(lower, [s])
56 58
57 59 for lx, t in sorted(lexers.LEXERS.items()):
58 60 m = map(__clean, t[-2])
59 61 if m:
60 62 m = reduce(lambda x, y: x + y, m)
61 63 for ext in m:
62 64 desc = lx.replace('Lexer', '')
63 65 d[ext].append(desc)
64 66
65 67 return dict(d)
66 68
67 69 def str2bool(_str):
68 70 """
69 71 returs True/False value from given string, it tries to translate the
70 72 string into boolean
71 73
72 74 :param _str: string value to translate into boolean
73 75 :rtype: boolean
74 76 :returns: boolean from given string
75 77 """
76 78 if _str is None:
77 79 return False
78 80 if _str in (True, False):
79 81 return _str
80 82 _str = str(_str).strip().lower()
81 83 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
82 84
83 85
84 86 def convert_line_endings(line, mode):
85 87 """
86 88 Converts a given line "line end" accordingly to given mode
87 89
88 90 Available modes are::
89 91 0 - Unix
90 92 1 - Mac
91 93 2 - DOS
92 94
93 95 :param line: given line to convert
94 96 :param mode: mode to convert to
95 97 :rtype: str
96 98 :return: converted line according to mode
97 99 """
98 100 from string import replace
99 101
100 102 if mode == 0:
101 103 line = replace(line, '\r\n', '\n')
102 104 line = replace(line, '\r', '\n')
103 105 elif mode == 1:
104 106 line = replace(line, '\r\n', '\r')
105 107 line = replace(line, '\n', '\r')
106 108 elif mode == 2:
107 109 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
108 110 return line
109 111
110 112
111 113 def detect_mode(line, default):
112 114 """
113 115 Detects line break for given line, if line break couldn't be found
114 116 given default value is returned
115 117
116 118 :param line: str line
117 119 :param default: default
118 120 :rtype: int
119 121 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
120 122 """
121 123 if line.endswith('\r\n'):
122 124 return 2
123 125 elif line.endswith('\n'):
124 126 return 0
125 127 elif line.endswith('\r'):
126 128 return 1
127 129 else:
128 130 return default
129 131
130 132
131 133 def generate_api_key(username, salt=None):
132 134 """
133 135 Generates unique API key for given username, if salt is not given
134 136 it'll be generated from some random string
135 137
136 138 :param username: username as string
137 139 :param salt: salt to hash generate KEY
138 140 :rtype: str
139 141 :returns: sha1 hash from username+salt
140 142 """
141 143 from tempfile import _RandomNameSequence
142 144 import hashlib
143 145
144 146 if salt is None:
145 147 salt = _RandomNameSequence().next()
146 148
147 149 return hashlib.sha1(username + salt).hexdigest()
148 150
149 151
150 152 def safe_int(val, default=None):
151 153 """
152 154 Returns int() of val if val is not convertable to int use default
153 155 instead
154 156
155 157 :param val:
156 158 :param default:
157 159 """
158 160
159 161 try:
160 162 val = int(val)
161 163 except ValueError:
162 164 val = default
163 165
164 166 return val
165 167
166 168
167 169 def safe_unicode(str_, from_encoding=None):
168 170 """
169 171 safe unicode function. Does few trick to turn str_ into unicode
170 172
171 173 In case of UnicodeDecode error we try to return it with encoding detected
172 174 by chardet library if it fails fallback to unicode with errors replaced
173 175
174 176 :param str_: string to decode
175 177 :rtype: unicode
176 178 :returns: unicode object
177 179 """
178 180 if isinstance(str_, unicode):
179 181 return str_
180 182
181 183 if not from_encoding:
182 184 import rhodecode
183 185 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
184 186 from_encoding = DEFAULT_ENCODING
185 187
186 188 try:
187 189 return unicode(str_)
188 190 except UnicodeDecodeError:
189 191 pass
190 192
191 193 try:
192 194 return unicode(str_, from_encoding)
193 195 except UnicodeDecodeError:
194 196 pass
195 197
196 198 try:
197 199 import chardet
198 200 encoding = chardet.detect(str_)['encoding']
199 201 if encoding is None:
200 202 raise Exception()
201 203 return str_.decode(encoding)
202 204 except (ImportError, UnicodeDecodeError, Exception):
203 205 return unicode(str_, from_encoding, 'replace')
204 206
205 207
206 208 def safe_str(unicode_, to_encoding=None):
207 209 """
208 210 safe str function. Does few trick to turn unicode_ into string
209 211
210 212 In case of UnicodeEncodeError we try to return it with encoding detected
211 213 by chardet library if it fails fallback to string with errors replaced
212 214
213 215 :param unicode_: unicode to encode
214 216 :rtype: str
215 217 :returns: str object
216 218 """
217 219
218 220 # if it's not basestr cast to str
219 221 if not isinstance(unicode_, basestring):
220 222 return str(unicode_)
221 223
222 224 if isinstance(unicode_, str):
223 225 return unicode_
224 226
225 227 if not to_encoding:
226 228 import rhodecode
227 229 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
228 230 to_encoding = DEFAULT_ENCODING
229 231
230 232 try:
231 233 return unicode_.encode(to_encoding)
232 234 except UnicodeEncodeError:
233 235 pass
234 236
235 237 try:
236 238 import chardet
237 239 encoding = chardet.detect(unicode_)['encoding']
238 240 if encoding is None:
239 241 raise UnicodeEncodeError()
240 242
241 243 return unicode_.encode(encoding)
242 244 except (ImportError, UnicodeEncodeError):
243 245 return unicode_.encode(to_encoding, 'replace')
244 246
245 247 return safe_str
246 248
247 249
248 250 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
249 251 """
250 252 Custom engine_from_config functions that makes sure we use NullPool for
251 253 file based sqlite databases. This prevents errors on sqlite. This only
252 254 applies to sqlalchemy versions < 0.7.0
253 255
254 256 """
255 257 import sqlalchemy
256 258 from sqlalchemy import engine_from_config as efc
257 259 import logging
258 260
259 261 if int(sqlalchemy.__version__.split('.')[1]) < 7:
260 262
261 263 # This solution should work for sqlalchemy < 0.7.0, and should use
262 264 # proxy=TimerProxy() for execution time profiling
263 265
264 266 from sqlalchemy.pool import NullPool
265 267 url = configuration[prefix + 'url']
266 268
267 269 if url.startswith('sqlite'):
268 270 kwargs.update({'poolclass': NullPool})
269 271 return efc(configuration, prefix, **kwargs)
270 272 else:
271 273 import time
272 274 from sqlalchemy import event
273 275 from sqlalchemy.engine import Engine
274 276
275 277 log = logging.getLogger('sqlalchemy.engine')
276 278 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
277 279 engine = efc(configuration, prefix, **kwargs)
278 280
279 281 def color_sql(sql):
280 282 COLOR_SEQ = "\033[1;%dm"
281 283 COLOR_SQL = YELLOW
282 284 normal = '\x1b[0m'
283 285 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
284 286
285 287 if configuration['debug']:
286 288 #attach events only for debug configuration
287 289
288 290 def before_cursor_execute(conn, cursor, statement,
289 291 parameters, context, executemany):
290 292 context._query_start_time = time.time()
291 293 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
292 294
293 295 def after_cursor_execute(conn, cursor, statement,
294 296 parameters, context, executemany):
295 297 total = time.time() - context._query_start_time
296 298 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
297 299
298 300 event.listen(engine, "before_cursor_execute",
299 301 before_cursor_execute)
300 302 event.listen(engine, "after_cursor_execute",
301 303 after_cursor_execute)
302 304
303 305 return engine
304 306
305 307
306 308 def age(prevdate):
307 309 """
308 310 turns a datetime into an age string.
309 311
310 312 :param prevdate: datetime object
311 313 :rtype: unicode
312 314 :returns: unicode words describing age
313 315 """
314 316
315 317 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
316 318 deltas = {}
317 319 future = False
318 320
319 321 # Get date parts deltas
320 322 now = datetime.datetime.now()
321 323 if prevdate > now:
322 324 now, prevdate = prevdate, now
323 325 future = True
324 326
325 327 for part in order:
326 328 deltas[part] = getattr(now, part) - getattr(prevdate, part)
327 329
328 330 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
329 331 # not 1 hour, -59 minutes and -59 seconds)
330 332
331 333 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
332 334 part = order[num]
333 335 carry_part = order[num - 1]
334 336
335 337 if deltas[part] < 0:
336 338 deltas[part] += length
337 339 deltas[carry_part] -= 1
338 340
339 341 # Same thing for days except that the increment depends on the (variable)
340 342 # number of days in the month
341 343 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
342 344 if deltas['day'] < 0:
343 345 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
344 346 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
345 347 deltas['day'] += 29
346 348 else:
347 349 deltas['day'] += month_lengths[prevdate.month - 1]
348 350
349 351 deltas['month'] -= 1
350 352
351 353 if deltas['month'] < 0:
352 354 deltas['month'] += 12
353 355 deltas['year'] -= 1
354 356
355 357 # Format the result
356 358 fmt_funcs = {
357 359 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
358 360 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
359 361 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
360 362 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
361 363 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
362 364 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
363 365 }
364 366
365 367 for i, part in enumerate(order):
366 368 value = deltas[part]
367 369 if value == 0:
368 370 continue
369 371
370 372 if i < 5:
371 373 sub_part = order[i + 1]
372 374 sub_value = deltas[sub_part]
373 375 else:
374 376 sub_value = 0
375 377
376 378 if sub_value == 0:
377 379 if future:
378 380 return _(u'in %s') % fmt_funcs[part](value)
379 381 else:
380 382 return _(u'%s ago') % fmt_funcs[part](value)
381 383 if future:
382 384 return _(u'in %s and %s') % (fmt_funcs[part](value),
383 385 fmt_funcs[sub_part](sub_value))
384 386 else:
385 387 return _(u'%s and %s ago') % (fmt_funcs[part](value),
386 388 fmt_funcs[sub_part](sub_value))
387 389
388 390 return _(u'just now')
389 391
390 392
391 393 def uri_filter(uri):
392 394 """
393 395 Removes user:password from given url string
394 396
395 397 :param uri:
396 398 :rtype: unicode
397 399 :returns: filtered list of strings
398 400 """
399 401 if not uri:
400 402 return ''
401 403
402 404 proto = ''
403 405
404 406 for pat in ('https://', 'http://'):
405 407 if uri.startswith(pat):
406 408 uri = uri[len(pat):]
407 409 proto = pat
408 410 break
409 411
410 412 # remove passwords and username
411 413 uri = uri[uri.find('@') + 1:]
412 414
413 415 # get the port
414 416 cred_pos = uri.find(':')
415 417 if cred_pos == -1:
416 418 host, port = uri, None
417 419 else:
418 420 host, port = uri[:cred_pos], uri[cred_pos + 1:]
419 421
420 422 return filter(None, [proto, host, port])
421 423
422 424
423 425 def credentials_filter(uri):
424 426 """
425 427 Returns a url with removed credentials
426 428
427 429 :param uri:
428 430 """
429 431
430 432 uri = uri_filter(uri)
431 433 #check if we have port
432 434 if len(uri) > 2 and uri[2]:
433 435 uri[2] = ':' + uri[2]
434 436
435 437 return ''.join(uri)
436 438
437 439
438 440 def get_changeset_safe(repo, rev):
439 441 """
440 442 Safe version of get_changeset if this changeset doesn't exists for a
441 443 repo it returns a Dummy one instead
442 444
443 445 :param repo:
444 446 :param rev:
445 447 """
446 448 from rhodecode.lib.vcs.backends.base import BaseRepository
447 449 from rhodecode.lib.vcs.exceptions import RepositoryError
448 450 from rhodecode.lib.vcs.backends.base import EmptyChangeset
449 451 if not isinstance(repo, BaseRepository):
450 452 raise Exception('You must pass an Repository '
451 453 'object as first argument got %s', type(repo))
452 454
453 455 try:
454 456 cs = repo.get_changeset(rev)
455 457 except RepositoryError:
456 458 cs = EmptyChangeset(requested_revision=rev)
457 459 return cs
458 460
459 461
460 462 def datetime_to_time(dt):
461 463 if dt:
462 464 return time.mktime(dt.timetuple())
463 465
464 466
465 467 def time_to_datetime(tm):
466 468 if tm:
467 469 if isinstance(tm, basestring):
468 470 try:
469 471 tm = float(tm)
470 472 except ValueError:
471 473 return
472 474 return datetime.datetime.fromtimestamp(tm)
473 475
474 476 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
475 477
476 478
477 479 def extract_mentioned_users(s):
478 480 """
479 481 Returns unique usernames from given string s that have @mention
480 482
481 483 :param s: string to get mentions
482 484 """
483 485 usrs = set()
484 486 for username in re.findall(MENTIONS_REGEX, s):
485 487 usrs.add(username)
486 488
487 489 return sorted(list(usrs), key=lambda k: k.lower())
488 490
489 491
490 492 class AttributeDict(dict):
491 493 def __getattr__(self, attr):
492 494 return self.get(attr, None)
493 495 __setattr__ = dict.__setitem__
494 496 __delattr__ = dict.__delitem__
495 497
496 498
497 499 def fix_PATH(os_=None):
498 500 """
499 501 Get current active python path, and append it to PATH variable to fix issues
500 502 of subprocess calls and different python versions
501 503 """
502 504 import sys
503 505 if os_ is None:
504 506 import os
505 507 else:
506 508 os = os_
507 509
508 510 cur_path = os.path.split(sys.executable)[0]
509 511 if not os.environ['PATH'].startswith(cur_path):
510 512 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
511 513
512 514
513 515 def obfuscate_url_pw(engine):
514 516 from sqlalchemy.engine import url
515 517 url = url.make_url(engine)
516 518 if url.password:
517 519 url.password = 'XXXXX'
518 520 return str(url)
521
522
523 def get_server_url(environ):
524 req = webob.Request(environ)
525 return req.host_url + req.script_name
General Comments 0
You need to be logged in to leave comments. Login now