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