##// END OF EJS Templates
exception: store orginal tb and exc inside the new exception passed to rhodecode from vcsserver.
marcink -
r621:f216af0d default
parent child Browse files
Show More
@@ -1,91 +1,94 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2019 RhodeCode GmbH
3 3 #
4 4 # This program is free software; you can redistribute it and/or modify
5 5 # it under the terms of the GNU General Public License as published by
6 6 # the Free Software Foundation; either version 3 of the License, or
7 7 # (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software Foundation,
16 16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 17
18 18 import sys
19 19 import traceback
20 20 import logging
21 21 import urlparse
22 22
23 23 from vcsserver.lib.rc_cache import region_meta
24 24 log = logging.getLogger(__name__)
25 25
26 26
27 27 class RepoFactory(object):
28 28 """
29 29 Utility to create instances of repository
30 30
31 31 It provides internal caching of the `repo` object based on
32 32 the :term:`call context`.
33 33 """
34 34 repo_type = None
35 35
36 36 def __init__(self):
37 37 self._cache_region = region_meta.dogpile_cache_regions['repo_object']
38 38
39 39 def _create_config(self, path, config):
40 40 config = {}
41 41 return config
42 42
43 43 def _create_repo(self, wire, create):
44 44 raise NotImplementedError()
45 45
46 46 def repo(self, wire, create=False):
47 47 """
48 48 Get a repository instance for the given path.
49 49
50 50 Uses internally the low level beaker API since the decorators introduce
51 51 significant overhead.
52 52 """
53 53 region = self._cache_region
54 54 context = wire.get('context', None)
55 55 repo_path = wire.get('path', '')
56 56 context_uid = '{}'.format(context)
57 57 cache = wire.get('cache', True)
58 58 cache_on = context and cache
59 59
60 60 @region.conditional_cache_on_arguments(condition=cache_on)
61 61 def create_new_repo(_repo_type, _repo_path, _context_uid):
62 62 return self._create_repo(wire, create)
63 63
64 64 repo = create_new_repo(self.repo_type, repo_path, context_uid)
65 65 return repo
66 66
67 67
68 68 def obfuscate_qs(query_string):
69 69 if query_string is None:
70 70 return None
71 71
72 72 parsed = []
73 73 for k, v in urlparse.parse_qsl(query_string, keep_blank_values=True):
74 74 if k in ['auth_token', 'api_key']:
75 75 v = "*****"
76 76 parsed.append((k, v))
77 77
78 78 return '&'.join('{}{}'.format(
79 79 k, '={}'.format(v) if v else '') for k, v in parsed)
80 80
81 81
82 82 def raise_from_original(new_type):
83 83 """
84 84 Raise a new exception type with original args and traceback.
85 85 """
86 86 exc_type, exc_value, exc_traceback = sys.exc_info()
87 new_exc = new_type(*exc_value.args)
88 # store the original traceback into the new exc
89 new_exc._org_exc_tb = traceback.format_exc(exc_traceback)
87 90
88 91 try:
89 raise new_type(*exc_value.args), None, exc_traceback
92 raise new_exc, None, exc_traceback
90 93 finally:
91 94 del exc_traceback
@@ -1,116 +1,117 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2019 RhodeCode GmbH
3 3 #
4 4 # This program is free software; you can redistribute it and/or modify
5 5 # it under the terms of the GNU General Public License as published by
6 6 # the Free Software Foundation; either version 3 of the License, or
7 7 # (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software Foundation,
16 16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 17
18 18 """
19 19 Special exception handling over the wire.
20 20
21 21 Since we cannot assume that our client is able to import our exception classes,
22 22 this module provides a "wrapping" mechanism to raise plain exceptions
23 23 which contain an extra attribute `_vcs_kind` to allow a client to distinguish
24 24 different error conditions.
25 25 """
26 26
27 27 from pyramid.httpexceptions import HTTPLocked, HTTPForbidden
28 28
29 29
30 30 def _make_exception(kind, org_exc, *args):
31 31 """
32 32 Prepares a base `Exception` instance to be sent over the wire.
33 33
34 34 To give our caller a hint what this is about, it will attach an attribute
35 35 `_vcs_kind` to the exception.
36 36 """
37 37 exc = Exception(*args)
38 38 exc._vcs_kind = kind
39 39 exc._org_exc = org_exc
40 exc._org_exc_tb = ''
40 41 return exc
41 42
42 43
43 44 def AbortException(org_exc=None):
44 45 def _make_exception_wrapper(*args):
45 46 return _make_exception('abort', org_exc, *args)
46 47 return _make_exception_wrapper
47 48
48 49
49 50 def ArchiveException(org_exc=None):
50 51 def _make_exception_wrapper(*args):
51 52 return _make_exception('archive', org_exc, *args)
52 53 return _make_exception_wrapper
53 54
54 55
55 56 def LookupException(org_exc=None):
56 57 def _make_exception_wrapper(*args):
57 58 return _make_exception('lookup', org_exc, *args)
58 59 return _make_exception_wrapper
59 60
60 61
61 62 def VcsException(org_exc=None):
62 63 def _make_exception_wrapper(*args):
63 64 return _make_exception('error', org_exc, *args)
64 65 return _make_exception_wrapper
65 66
66 67
67 68 def RepositoryLockedException(org_exc=None):
68 69 def _make_exception_wrapper(*args):
69 70 return _make_exception('repo_locked', org_exc, *args)
70 71 return _make_exception_wrapper
71 72
72 73
73 74 def RepositoryBranchProtectedException(org_exc=None):
74 75 def _make_exception_wrapper(*args):
75 76 return _make_exception('repo_branch_protected', org_exc, *args)
76 77 return _make_exception_wrapper
77 78
78 79
79 80 def RequirementException(org_exc=None):
80 81 def _make_exception_wrapper(*args):
81 82 return _make_exception('requirement', org_exc, *args)
82 83 return _make_exception_wrapper
83 84
84 85
85 86 def UnhandledException(org_exc=None):
86 87 def _make_exception_wrapper(*args):
87 88 return _make_exception('unhandled', org_exc, *args)
88 89 return _make_exception_wrapper
89 90
90 91
91 92 def URLError(org_exc=None):
92 93 def _make_exception_wrapper(*args):
93 94 return _make_exception('url_error', org_exc, *args)
94 95 return _make_exception_wrapper
95 96
96 97
97 98 def SubrepoMergeException(org_exc=None):
98 99 def _make_exception_wrapper(*args):
99 100 return _make_exception('subrepo_merge_error', org_exc, *args)
100 101 return _make_exception_wrapper
101 102
102 103
103 104 class HTTPRepoLocked(HTTPLocked):
104 105 """
105 106 Subclass of HTTPLocked response that allows to set the title and status
106 107 code via constructor arguments.
107 108 """
108 109 def __init__(self, title, status_code=None, **kwargs):
109 110 self.code = status_code or HTTPLocked.code
110 111 self.title = title
111 112 super(HTTPRepoLocked, self).__init__(**kwargs)
112 113
113 114
114 115 class HTTPRepoBranchProtected(HTTPForbidden):
115 116 def __init__(self, *args, **kwargs):
116 117 super(HTTPForbidden, self).__init__(*args, **kwargs)
@@ -1,607 +1,610 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2019 RhodeCode GmbH
3 3 #
4 4 # This program is free software; you can redistribute it and/or modify
5 5 # it under the terms of the GNU General Public License as published by
6 6 # the Free Software Foundation; either version 3 of the License, or
7 7 # (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software Foundation,
16 16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 17
18 18 import os
19 19 import sys
20 20 import base64
21 21 import locale
22 22 import logging
23 23 import uuid
24 24 import wsgiref.util
25 25 import traceback
26 26 import tempfile
27 27 from itertools import chain
28 28
29 29 import simplejson as json
30 30 import msgpack
31 31 from pyramid.config import Configurator
32 32 from pyramid.settings import asbool, aslist
33 33 from pyramid.wsgi import wsgiapp
34 34 from pyramid.compat import configparser
35 35
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
40 40 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
41 41
42 42 try:
43 43 locale.setlocale(locale.LC_ALL, '')
44 44 except locale.Error as e:
45 45 log.error(
46 46 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
47 47 os.environ['LC_ALL'] = 'C'
48 48
49 49 import vcsserver
50 50 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
51 51 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
52 52 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
53 53 from vcsserver.echo_stub.echo_app import EchoApp
54 54 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
55 55 from vcsserver.lib.exc_tracking import store_exception
56 56 from vcsserver.server import VcsServer
57 57
58 58 try:
59 59 from vcsserver.git import GitFactory, GitRemote
60 60 except ImportError:
61 61 GitFactory = None
62 62 GitRemote = None
63 63
64 64 try:
65 65 from vcsserver.hg import MercurialFactory, HgRemote
66 66 except ImportError:
67 67 MercurialFactory = None
68 68 HgRemote = None
69 69
70 70 try:
71 71 from vcsserver.svn import SubversionFactory, SvnRemote
72 72 except ImportError:
73 73 SubversionFactory = None
74 74 SvnRemote = None
75 75
76 76
77 77 def _is_request_chunked(environ):
78 78 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
79 79 return stream
80 80
81 81
82 82 def _int_setting(settings, name, default):
83 83 settings[name] = int(settings.get(name, default))
84 84 return settings[name]
85 85
86 86
87 87 def _bool_setting(settings, name, default):
88 88 input_val = settings.get(name, default)
89 89 if isinstance(input_val, unicode):
90 90 input_val = input_val.encode('utf8')
91 91 settings[name] = asbool(input_val)
92 92 return settings[name]
93 93
94 94
95 95 def _list_setting(settings, name, default):
96 96 raw_value = settings.get(name, default)
97 97
98 98 # Otherwise we assume it uses pyramids space/newline separation.
99 99 settings[name] = aslist(raw_value)
100 100 return settings[name]
101 101
102 102
103 103 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
104 104 value = settings.get(name, default)
105 105
106 106 if default_when_empty and not value:
107 107 # use default value when value is empty
108 108 value = default
109 109
110 110 if lower:
111 111 value = value.lower()
112 112 settings[name] = value
113 113 return settings[name]
114 114
115 115
116 116 class VCS(object):
117 117 def __init__(self, locale=None, cache_config=None):
118 118 self.locale = locale
119 119 self.cache_config = cache_config
120 120 self._configure_locale()
121 121
122 122 if GitFactory and GitRemote:
123 123 git_factory = GitFactory()
124 124 self._git_remote = GitRemote(git_factory)
125 125 else:
126 126 log.info("Git client import failed")
127 127
128 128 if MercurialFactory and HgRemote:
129 129 hg_factory = MercurialFactory()
130 130 self._hg_remote = HgRemote(hg_factory)
131 131 else:
132 132 log.info("Mercurial client import failed")
133 133
134 134 if SubversionFactory and SvnRemote:
135 135 svn_factory = SubversionFactory()
136 136
137 137 # hg factory is used for svn url validation
138 138 hg_factory = MercurialFactory()
139 139 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
140 140 else:
141 141 log.info("Subversion client import failed")
142 142
143 143 self._vcsserver = VcsServer()
144 144
145 145 def _configure_locale(self):
146 146 if self.locale:
147 147 log.info('Settings locale: `LC_ALL` to %s', self.locale)
148 148 else:
149 149 log.info(
150 150 'Configuring locale subsystem based on environment variables')
151 151 try:
152 152 # If self.locale is the empty string, then the locale
153 153 # module will use the environment variables. See the
154 154 # documentation of the package `locale`.
155 155 locale.setlocale(locale.LC_ALL, self.locale)
156 156
157 157 language_code, encoding = locale.getlocale()
158 158 log.info(
159 159 'Locale set to language code "%s" with encoding "%s".',
160 160 language_code, encoding)
161 161 except locale.Error:
162 162 log.exception(
163 163 'Cannot set locale, not configuring the locale system')
164 164
165 165
166 166 class WsgiProxy(object):
167 167 def __init__(self, wsgi):
168 168 self.wsgi = wsgi
169 169
170 170 def __call__(self, environ, start_response):
171 171 input_data = environ['wsgi.input'].read()
172 172 input_data = msgpack.unpackb(input_data)
173 173
174 174 error = None
175 175 try:
176 176 data, status, headers = self.wsgi.handle(
177 177 input_data['environment'], input_data['input_data'],
178 178 *input_data['args'], **input_data['kwargs'])
179 179 except Exception as e:
180 180 data, status, headers = [], None, None
181 181 error = {
182 182 'message': str(e),
183 183 '_vcs_kind': getattr(e, '_vcs_kind', None)
184 184 }
185 185
186 186 start_response(200, {})
187 187 return self._iterator(error, status, headers, data)
188 188
189 189 def _iterator(self, error, status, headers, data):
190 190 initial_data = [
191 191 error,
192 192 status,
193 193 headers,
194 194 ]
195 195
196 196 for d in chain(initial_data, data):
197 197 yield msgpack.packb(d)
198 198
199 199
200 200 def not_found(request):
201 201 return {'status': '404 NOT FOUND'}
202 202
203 203
204 204 class VCSViewPredicate(object):
205 205 def __init__(self, val, config):
206 206 self.remotes = val
207 207
208 208 def text(self):
209 209 return 'vcs view method = %s' % (self.remotes.keys(),)
210 210
211 211 phash = text
212 212
213 213 def __call__(self, context, request):
214 214 """
215 215 View predicate that returns true if given backend is supported by
216 216 defined remotes.
217 217 """
218 218 backend = request.matchdict.get('backend')
219 219 return backend in self.remotes
220 220
221 221
222 222 class HTTPApplication(object):
223 223 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
224 224
225 225 remote_wsgi = remote_wsgi
226 226 _use_echo_app = False
227 227
228 228 def __init__(self, settings=None, global_config=None):
229 229 self._sanitize_settings_and_apply_defaults(settings)
230 230
231 231 self.config = Configurator(settings=settings)
232 232 self.global_config = global_config
233 233 self.config.include('vcsserver.lib.rc_cache')
234 234
235 235 locale = settings.get('locale', '') or 'en_US.UTF-8'
236 236 vcs = VCS(locale=locale, cache_config=settings)
237 237 self._remotes = {
238 238 'hg': vcs._hg_remote,
239 239 'git': vcs._git_remote,
240 240 'svn': vcs._svn_remote,
241 241 'server': vcs._vcsserver,
242 242 }
243 243 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
244 244 self._use_echo_app = True
245 245 log.warning("Using EchoApp for VCS operations.")
246 246 self.remote_wsgi = remote_wsgi_stub
247 247
248 248 self._configure_settings(global_config, settings)
249 249 self._configure()
250 250
251 251 def _configure_settings(self, global_config, app_settings):
252 252 """
253 253 Configure the settings module.
254 254 """
255 255 settings_merged = global_config.copy()
256 256 settings_merged.update(app_settings)
257 257
258 258 git_path = app_settings.get('git_path', None)
259 259 if git_path:
260 260 settings.GIT_EXECUTABLE = git_path
261 261 binary_dir = app_settings.get('core.binary_dir', None)
262 262 if binary_dir:
263 263 settings.BINARY_DIR = binary_dir
264 264
265 265 # Store the settings to make them available to other modules.
266 266 vcsserver.PYRAMID_SETTINGS = settings_merged
267 267 vcsserver.CONFIG = settings_merged
268 268
269 269 def _sanitize_settings_and_apply_defaults(self, settings):
270 270 temp_store = tempfile.gettempdir()
271 271 default_cache_dir = os.path.join(temp_store, 'rc_cache')
272 272
273 273 # save default, cache dir, and use it for all backends later.
274 274 default_cache_dir = _string_setting(
275 275 settings,
276 276 'cache_dir',
277 277 default_cache_dir, lower=False, default_when_empty=True)
278 278
279 279 # ensure we have our dir created
280 280 if not os.path.isdir(default_cache_dir):
281 281 os.makedirs(default_cache_dir, mode=0o755)
282 282
283 283 # exception store cache
284 284 _string_setting(
285 285 settings,
286 286 'exception_tracker.store_path',
287 287 temp_store, lower=False, default_when_empty=True)
288 288
289 289 # repo_object cache
290 290 _string_setting(
291 291 settings,
292 292 'rc_cache.repo_object.backend',
293 293 'dogpile.cache.rc.memory_lru')
294 294 _int_setting(
295 295 settings,
296 296 'rc_cache.repo_object.expiration_time',
297 297 300)
298 298 _int_setting(
299 299 settings,
300 300 'rc_cache.repo_object.max_size',
301 301 1024)
302 302
303 303 def _configure(self):
304 304 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
305 305
306 306 self.config.add_route('service', '/_service')
307 307 self.config.add_route('status', '/status')
308 308 self.config.add_route('hg_proxy', '/proxy/hg')
309 309 self.config.add_route('git_proxy', '/proxy/git')
310 310 self.config.add_route('vcs', '/{backend}')
311 311 self.config.add_route('stream_git', '/stream/git/*repo_name')
312 312 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
313 313
314 314 self.config.add_view(self.status_view, route_name='status', renderer='json')
315 315 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
316 316
317 317 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
318 318 self.config.add_view(self.git_proxy(), route_name='git_proxy')
319 319 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
320 320 vcs_view=self._remotes)
321 321
322 322 self.config.add_view(self.hg_stream(), route_name='stream_hg')
323 323 self.config.add_view(self.git_stream(), route_name='stream_git')
324 324
325 325 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
326 326
327 327 self.config.add_notfound_view(not_found, renderer='json')
328 328
329 329 self.config.add_view(self.handle_vcs_exception, context=Exception)
330 330
331 331 self.config.add_tween(
332 332 'vcsserver.tweens.RequestWrapperTween',
333 333 )
334 334
335 335 def wsgi_app(self):
336 336 return self.config.make_wsgi_app()
337 337
338 338 def vcs_view(self, request):
339 339 remote = self._remotes[request.matchdict['backend']]
340 340 payload = msgpack.unpackb(request.body, use_list=True)
341 341 method = payload.get('method')
342 342 params = payload.get('params')
343 343 wire = params.get('wire')
344 344 args = params.get('args')
345 345 kwargs = params.get('kwargs')
346 346 context_uid = None
347 347
348 348 if wire:
349 349 try:
350 350 wire['context'] = context_uid = uuid.UUID(wire['context'])
351 351 except KeyError:
352 352 pass
353 353 args.insert(0, wire)
354 354
355 355 log.debug('method called:%s with kwargs:%s context_uid: %s',
356 356 method, kwargs, context_uid)
357 357 try:
358 358 resp = getattr(remote, method)(*args, **kwargs)
359 359 except Exception as e:
360 360 exc_info = list(sys.exc_info())
361 361 exc_type, exc_value, exc_traceback = exc_info
362 362
363 363 org_exc = getattr(e, '_org_exc', None)
364 364 org_exc_name = None
365 org_exc_tb = ''
365 366 if org_exc:
366 367 org_exc_name = org_exc.__class__.__name__
368 org_exc_tb = getattr(e, '_org_exc_tb', '')
367 369 # replace our "faked" exception with our org
368 370 exc_info[0] = org_exc.__class__
369 371 exc_info[1] = org_exc
370 372
371 373 store_exception(id(exc_info), exc_info)
372 374
373 375 tb_info = ''.join(
374 376 traceback.format_exception(exc_type, exc_value, exc_traceback))
375 377
376 378 type_ = e.__class__.__name__
377 379 if type_ not in self.ALLOWED_EXCEPTIONS:
378 380 type_ = None
379 381
380 382 resp = {
381 383 'id': payload.get('id'),
382 384 'error': {
383 385 'message': e.message,
384 386 'traceback': tb_info,
385 387 'org_exc': org_exc_name,
388 'org_exc_tb': org_exc_tb,
386 389 'type': type_
387 390 }
388 391 }
389 392 try:
390 393 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
391 394 except AttributeError:
392 395 pass
393 396 else:
394 397 resp = {
395 398 'id': payload.get('id'),
396 399 'result': resp
397 400 }
398 401
399 402 return resp
400 403
401 404 def status_view(self, request):
402 405 import vcsserver
403 406 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
404 407 'pid': os.getpid()}
405 408
406 409 def service_view(self, request):
407 410 import vcsserver
408 411
409 412 payload = msgpack.unpackb(request.body, use_list=True)
410 413
411 414 try:
412 415 path = self.global_config['__file__']
413 416 config = configparser.ConfigParser()
414 417 config.read(path)
415 418 parsed_ini = config
416 419 if parsed_ini.has_section('server:main'):
417 420 parsed_ini = dict(parsed_ini.items('server:main'))
418 421 except Exception:
419 422 log.exception('Failed to read .ini file for display')
420 423 parsed_ini = {}
421 424
422 425 resp = {
423 426 'id': payload.get('id'),
424 427 'result': dict(
425 428 version=vcsserver.__version__,
426 429 config=parsed_ini,
427 430 payload=payload,
428 431 )
429 432 }
430 433 return resp
431 434
432 435 def _msgpack_renderer_factory(self, info):
433 436 def _render(value, system):
434 437 value = msgpack.packb(value)
435 438 request = system.get('request')
436 439 if request is not None:
437 440 response = request.response
438 441 ct = response.content_type
439 442 if ct == response.default_content_type:
440 443 response.content_type = 'application/x-msgpack'
441 444 return value
442 445 return _render
443 446
444 447 def set_env_from_config(self, environ, config):
445 448 dict_conf = {}
446 449 try:
447 450 for elem in config:
448 451 if elem[0] == 'rhodecode':
449 452 dict_conf = json.loads(elem[2])
450 453 break
451 454 except Exception:
452 455 log.exception('Failed to fetch SCM CONFIG')
453 456 return
454 457
455 458 username = dict_conf.get('username')
456 459 if username:
457 460 environ['REMOTE_USER'] = username
458 461 # mercurial specific, some extension api rely on this
459 462 environ['HGUSER'] = username
460 463
461 464 ip = dict_conf.get('ip')
462 465 if ip:
463 466 environ['REMOTE_HOST'] = ip
464 467
465 468 if _is_request_chunked(environ):
466 469 # set the compatibility flag for webob
467 470 environ['wsgi.input_terminated'] = True
468 471
469 472 def hg_proxy(self):
470 473 @wsgiapp
471 474 def _hg_proxy(environ, start_response):
472 475 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
473 476 return app(environ, start_response)
474 477 return _hg_proxy
475 478
476 479 def git_proxy(self):
477 480 @wsgiapp
478 481 def _git_proxy(environ, start_response):
479 482 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
480 483 return app(environ, start_response)
481 484 return _git_proxy
482 485
483 486 def hg_stream(self):
484 487 if self._use_echo_app:
485 488 @wsgiapp
486 489 def _hg_stream(environ, start_response):
487 490 app = EchoApp('fake_path', 'fake_name', None)
488 491 return app(environ, start_response)
489 492 return _hg_stream
490 493 else:
491 494 @wsgiapp
492 495 def _hg_stream(environ, start_response):
493 496 log.debug('http-app: handling hg stream')
494 497 repo_path = environ['HTTP_X_RC_REPO_PATH']
495 498 repo_name = environ['HTTP_X_RC_REPO_NAME']
496 499 packed_config = base64.b64decode(
497 500 environ['HTTP_X_RC_REPO_CONFIG'])
498 501 config = msgpack.unpackb(packed_config)
499 502 app = scm_app.create_hg_wsgi_app(
500 503 repo_path, repo_name, config)
501 504
502 505 # Consistent path information for hgweb
503 506 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
504 507 environ['REPO_NAME'] = repo_name
505 508 self.set_env_from_config(environ, config)
506 509
507 510 log.debug('http-app: starting app handler '
508 511 'with %s and process request', app)
509 512 return app(environ, ResponseFilter(start_response))
510 513 return _hg_stream
511 514
512 515 def git_stream(self):
513 516 if self._use_echo_app:
514 517 @wsgiapp
515 518 def _git_stream(environ, start_response):
516 519 app = EchoApp('fake_path', 'fake_name', None)
517 520 return app(environ, start_response)
518 521 return _git_stream
519 522 else:
520 523 @wsgiapp
521 524 def _git_stream(environ, start_response):
522 525 log.debug('http-app: handling git stream')
523 526 repo_path = environ['HTTP_X_RC_REPO_PATH']
524 527 repo_name = environ['HTTP_X_RC_REPO_NAME']
525 528 packed_config = base64.b64decode(
526 529 environ['HTTP_X_RC_REPO_CONFIG'])
527 530 config = msgpack.unpackb(packed_config)
528 531
529 532 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
530 533 self.set_env_from_config(environ, config)
531 534
532 535 content_type = environ.get('CONTENT_TYPE', '')
533 536
534 537 path = environ['PATH_INFO']
535 538 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
536 539 log.debug(
537 540 'LFS: Detecting if request `%s` is LFS server path based '
538 541 'on content type:`%s`, is_lfs:%s',
539 542 path, content_type, is_lfs_request)
540 543
541 544 if not is_lfs_request:
542 545 # fallback detection by path
543 546 if GIT_LFS_PROTO_PAT.match(path):
544 547 is_lfs_request = True
545 548 log.debug(
546 549 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
547 550 path, is_lfs_request)
548 551
549 552 if is_lfs_request:
550 553 app = scm_app.create_git_lfs_wsgi_app(
551 554 repo_path, repo_name, config)
552 555 else:
553 556 app = scm_app.create_git_wsgi_app(
554 557 repo_path, repo_name, config)
555 558
556 559 log.debug('http-app: starting app handler '
557 560 'with %s and process request', app)
558 561
559 562 return app(environ, start_response)
560 563
561 564 return _git_stream
562 565
563 566 def handle_vcs_exception(self, exception, request):
564 567 _vcs_kind = getattr(exception, '_vcs_kind', '')
565 568 if _vcs_kind == 'repo_locked':
566 569 # Get custom repo-locked status code if present.
567 570 status_code = request.headers.get('X-RC-Locked-Status-Code')
568 571 return HTTPRepoLocked(
569 572 title=exception.message, status_code=status_code)
570 573
571 574 elif _vcs_kind == 'repo_branch_protected':
572 575 # Get custom repo-branch-protected status code if present.
573 576 return HTTPRepoBranchProtected(title=exception.message)
574 577
575 578 exc_info = request.exc_info
576 579 store_exception(id(exc_info), exc_info)
577 580
578 581 traceback_info = 'unavailable'
579 582 if request.exc_info:
580 583 exc_type, exc_value, exc_tb = request.exc_info
581 584 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
582 585
583 586 log.error(
584 587 'error occurred handling this request for path: %s, \n tb: %s',
585 588 request.path, traceback_info)
586 589 raise exception
587 590
588 591
589 592 class ResponseFilter(object):
590 593
591 594 def __init__(self, start_response):
592 595 self._start_response = start_response
593 596
594 597 def __call__(self, status, response_headers, exc_info=None):
595 598 headers = tuple(
596 599 (h, v) for h, v in response_headers
597 600 if not wsgiref.util.is_hop_by_hop(h))
598 601 return self._start_response(status, headers, exc_info)
599 602
600 603
601 604 def main(global_config, **settings):
602 605 if MercurialFactory:
603 606 hgpatches.patch_largefiles_capabilities()
604 607 hgpatches.patch_subrepo_type_mapping()
605 608
606 609 app = HTTPApplication(settings=settings, global_config=global_config)
607 610 return app.wsgi_app()
General Comments 0
You need to be logged in to leave comments. Login now