##// END OF EJS Templates
http-mode: expose tracebacks back to the client.
marcink -
r146:e9421363 default
parent child Browse files
Show More
@@ -1,376 +1,380 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2016 RodeCode GmbH
2 # Copyright (C) 2014-2016 RodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import base64
18 import base64
19 import locale
19 import locale
20 import logging
20 import logging
21 import uuid
21 import uuid
22 import wsgiref.util
22 import wsgiref.util
23 import traceback
23 from itertools import chain
24 from itertools import chain
24
25
25 import msgpack
26 import msgpack
26 from beaker.cache import CacheManager
27 from beaker.cache import CacheManager
27 from beaker.util import parse_cache_config_options
28 from beaker.util import parse_cache_config_options
28 from pyramid.config import Configurator
29 from pyramid.config import Configurator
29 from pyramid.wsgi import wsgiapp
30 from pyramid.wsgi import wsgiapp
30
31
31 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
32 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
32 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
33 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
33 from vcsserver.echo_stub.echo_app import EchoApp
34 from vcsserver.echo_stub.echo_app import EchoApp
34 from vcsserver.exceptions import HTTPRepoLocked
35 from vcsserver.exceptions import HTTPRepoLocked
35 from vcsserver.server import VcsServer
36 from vcsserver.server import VcsServer
36
37
37 try:
38 try:
38 from vcsserver.git import GitFactory, GitRemote
39 from vcsserver.git import GitFactory, GitRemote
39 except ImportError:
40 except ImportError:
40 GitFactory = None
41 GitFactory = None
41 GitRemote = None
42 GitRemote = None
42 try:
43 try:
43 from vcsserver.hg import MercurialFactory, HgRemote
44 from vcsserver.hg import MercurialFactory, HgRemote
44 except ImportError:
45 except ImportError:
45 MercurialFactory = None
46 MercurialFactory = None
46 HgRemote = None
47 HgRemote = None
47 try:
48 try:
48 from vcsserver.svn import SubversionFactory, SvnRemote
49 from vcsserver.svn import SubversionFactory, SvnRemote
49 except ImportError:
50 except ImportError:
50 SubversionFactory = None
51 SubversionFactory = None
51 SvnRemote = None
52 SvnRemote = None
52
53
53 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
54
55
55
56
56 class VCS(object):
57 class VCS(object):
57 def __init__(self, locale=None, cache_config=None):
58 def __init__(self, locale=None, cache_config=None):
58 self.locale = locale
59 self.locale = locale
59 self.cache_config = cache_config
60 self.cache_config = cache_config
60 self._configure_locale()
61 self._configure_locale()
61 self._initialize_cache()
62 self._initialize_cache()
62
63
63 if GitFactory and GitRemote:
64 if GitFactory and GitRemote:
64 git_repo_cache = self.cache.get_cache_region(
65 git_repo_cache = self.cache.get_cache_region(
65 'git', region='repo_object')
66 'git', region='repo_object')
66 git_factory = GitFactory(git_repo_cache)
67 git_factory = GitFactory(git_repo_cache)
67 self._git_remote = GitRemote(git_factory)
68 self._git_remote = GitRemote(git_factory)
68 else:
69 else:
69 log.info("Git client import failed")
70 log.info("Git client import failed")
70
71
71 if MercurialFactory and HgRemote:
72 if MercurialFactory and HgRemote:
72 hg_repo_cache = self.cache.get_cache_region(
73 hg_repo_cache = self.cache.get_cache_region(
73 'hg', region='repo_object')
74 'hg', region='repo_object')
74 hg_factory = MercurialFactory(hg_repo_cache)
75 hg_factory = MercurialFactory(hg_repo_cache)
75 self._hg_remote = HgRemote(hg_factory)
76 self._hg_remote = HgRemote(hg_factory)
76 else:
77 else:
77 log.info("Mercurial client import failed")
78 log.info("Mercurial client import failed")
78
79
79 if SubversionFactory and SvnRemote:
80 if SubversionFactory and SvnRemote:
80 svn_repo_cache = self.cache.get_cache_region(
81 svn_repo_cache = self.cache.get_cache_region(
81 'svn', region='repo_object')
82 'svn', region='repo_object')
82 svn_factory = SubversionFactory(svn_repo_cache)
83 svn_factory = SubversionFactory(svn_repo_cache)
83 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
84 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
84 else:
85 else:
85 log.info("Subversion client import failed")
86 log.info("Subversion client import failed")
86
87
87 self._vcsserver = VcsServer()
88 self._vcsserver = VcsServer()
88
89
89 def _initialize_cache(self):
90 def _initialize_cache(self):
90 cache_config = parse_cache_config_options(self.cache_config)
91 cache_config = parse_cache_config_options(self.cache_config)
91 log.info('Initializing beaker cache: %s' % cache_config)
92 log.info('Initializing beaker cache: %s' % cache_config)
92 self.cache = CacheManager(**cache_config)
93 self.cache = CacheManager(**cache_config)
93
94
94 def _configure_locale(self):
95 def _configure_locale(self):
95 if self.locale:
96 if self.locale:
96 log.info('Settings locale: `LC_ALL` to %s' % self.locale)
97 log.info('Settings locale: `LC_ALL` to %s' % self.locale)
97 else:
98 else:
98 log.info(
99 log.info(
99 'Configuring locale subsystem based on environment variables')
100 'Configuring locale subsystem based on environment variables')
100 try:
101 try:
101 # If self.locale is the empty string, then the locale
102 # If self.locale is the empty string, then the locale
102 # module will use the environment variables. See the
103 # module will use the environment variables. See the
103 # documentation of the package `locale`.
104 # documentation of the package `locale`.
104 locale.setlocale(locale.LC_ALL, self.locale)
105 locale.setlocale(locale.LC_ALL, self.locale)
105
106
106 language_code, encoding = locale.getlocale()
107 language_code, encoding = locale.getlocale()
107 log.info(
108 log.info(
108 'Locale set to language code "%s" with encoding "%s".',
109 'Locale set to language code "%s" with encoding "%s".',
109 language_code, encoding)
110 language_code, encoding)
110 except locale.Error:
111 except locale.Error:
111 log.exception(
112 log.exception(
112 'Cannot set locale, not configuring the locale system')
113 'Cannot set locale, not configuring the locale system')
113
114
114
115
115 class WsgiProxy(object):
116 class WsgiProxy(object):
116 def __init__(self, wsgi):
117 def __init__(self, wsgi):
117 self.wsgi = wsgi
118 self.wsgi = wsgi
118
119
119 def __call__(self, environ, start_response):
120 def __call__(self, environ, start_response):
120 input_data = environ['wsgi.input'].read()
121 input_data = environ['wsgi.input'].read()
121 input_data = msgpack.unpackb(input_data)
122 input_data = msgpack.unpackb(input_data)
122
123
123 error = None
124 error = None
124 try:
125 try:
125 data, status, headers = self.wsgi.handle(
126 data, status, headers = self.wsgi.handle(
126 input_data['environment'], input_data['input_data'],
127 input_data['environment'], input_data['input_data'],
127 *input_data['args'], **input_data['kwargs'])
128 *input_data['args'], **input_data['kwargs'])
128 except Exception as e:
129 except Exception as e:
129 data, status, headers = [], None, None
130 data, status, headers = [], None, None
130 error = {
131 error = {
131 'message': str(e),
132 'message': str(e),
132 '_vcs_kind': getattr(e, '_vcs_kind', None)
133 '_vcs_kind': getattr(e, '_vcs_kind', None)
133 }
134 }
134
135
135 start_response(200, {})
136 start_response(200, {})
136 return self._iterator(error, status, headers, data)
137 return self._iterator(error, status, headers, data)
137
138
138 def _iterator(self, error, status, headers, data):
139 def _iterator(self, error, status, headers, data):
139 initial_data = [
140 initial_data = [
140 error,
141 error,
141 status,
142 status,
142 headers,
143 headers,
143 ]
144 ]
144
145
145 for d in chain(initial_data, data):
146 for d in chain(initial_data, data):
146 yield msgpack.packb(d)
147 yield msgpack.packb(d)
147
148
148
149
149 class HTTPApplication(object):
150 class HTTPApplication(object):
150 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
151 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
151
152
152 remote_wsgi = remote_wsgi
153 remote_wsgi = remote_wsgi
153 _use_echo_app = False
154 _use_echo_app = False
154
155
155 def __init__(self, settings=None):
156 def __init__(self, settings=None):
156 self.config = Configurator(settings=settings)
157 self.config = Configurator(settings=settings)
157 locale = settings.get('', 'en_US.UTF-8')
158 locale = settings.get('', 'en_US.UTF-8')
158 vcs = VCS(locale=locale, cache_config=settings)
159 vcs = VCS(locale=locale, cache_config=settings)
159 self._remotes = {
160 self._remotes = {
160 'hg': vcs._hg_remote,
161 'hg': vcs._hg_remote,
161 'git': vcs._git_remote,
162 'git': vcs._git_remote,
162 'svn': vcs._svn_remote,
163 'svn': vcs._svn_remote,
163 'server': vcs._vcsserver,
164 'server': vcs._vcsserver,
164 }
165 }
165 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
166 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
166 self._use_echo_app = True
167 self._use_echo_app = True
167 log.warning("Using EchoApp for VCS operations.")
168 log.warning("Using EchoApp for VCS operations.")
168 self.remote_wsgi = remote_wsgi_stub
169 self.remote_wsgi = remote_wsgi_stub
169 self._configure_settings(settings)
170 self._configure_settings(settings)
170 self._configure()
171 self._configure()
171
172
172 def _configure_settings(self, app_settings):
173 def _configure_settings(self, app_settings):
173 """
174 """
174 Configure the settings module.
175 Configure the settings module.
175 """
176 """
176 git_path = app_settings.get('git_path', None)
177 git_path = app_settings.get('git_path', None)
177 if git_path:
178 if git_path:
178 settings.GIT_EXECUTABLE = git_path
179 settings.GIT_EXECUTABLE = git_path
179
180
180 def _configure(self):
181 def _configure(self):
181 self.config.add_renderer(
182 self.config.add_renderer(
182 name='msgpack',
183 name='msgpack',
183 factory=self._msgpack_renderer_factory)
184 factory=self._msgpack_renderer_factory)
184
185
185 self.config.add_route('service', '/_service')
186 self.config.add_route('service', '/_service')
186 self.config.add_route('status', '/status')
187 self.config.add_route('status', '/status')
187 self.config.add_route('hg_proxy', '/proxy/hg')
188 self.config.add_route('hg_proxy', '/proxy/hg')
188 self.config.add_route('git_proxy', '/proxy/git')
189 self.config.add_route('git_proxy', '/proxy/git')
189 self.config.add_route('vcs', '/{backend}')
190 self.config.add_route('vcs', '/{backend}')
190 self.config.add_route('stream_git', '/stream/git/*repo_name')
191 self.config.add_route('stream_git', '/stream/git/*repo_name')
191 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
192 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
192
193
193 self.config.add_view(
194 self.config.add_view(
194 self.status_view, route_name='status', renderer='json')
195 self.status_view, route_name='status', renderer='json')
195 self.config.add_view(
196 self.config.add_view(
196 self.service_view, route_name='service', renderer='msgpack')
197 self.service_view, route_name='service', renderer='msgpack')
197
198
198 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
199 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
199 self.config.add_view(self.git_proxy(), route_name='git_proxy')
200 self.config.add_view(self.git_proxy(), route_name='git_proxy')
200 self.config.add_view(
201 self.config.add_view(
201 self.vcs_view, route_name='vcs', renderer='msgpack')
202 self.vcs_view, route_name='vcs', renderer='msgpack')
202
203
203 self.config.add_view(self.hg_stream(), route_name='stream_hg')
204 self.config.add_view(self.hg_stream(), route_name='stream_hg')
204 self.config.add_view(self.git_stream(), route_name='stream_git')
205 self.config.add_view(self.git_stream(), route_name='stream_git')
205 self.config.add_view(
206 self.config.add_view(
206 self.handle_vcs_exception, context=Exception,
207 self.handle_vcs_exception, context=Exception,
207 custom_predicates=[self.is_vcs_exception])
208 custom_predicates=[self.is_vcs_exception])
208
209
209 def wsgi_app(self):
210 def wsgi_app(self):
210 return self.config.make_wsgi_app()
211 return self.config.make_wsgi_app()
211
212
212 def vcs_view(self, request):
213 def vcs_view(self, request):
213 remote = self._remotes[request.matchdict['backend']]
214 remote = self._remotes[request.matchdict['backend']]
214 payload = msgpack.unpackb(request.body, use_list=True)
215 payload = msgpack.unpackb(request.body, use_list=True)
215 method = payload.get('method')
216 method = payload.get('method')
216 params = payload.get('params')
217 params = payload.get('params')
217 wire = params.get('wire')
218 wire = params.get('wire')
218 args = params.get('args')
219 args = params.get('args')
219 kwargs = params.get('kwargs')
220 kwargs = params.get('kwargs')
220 if wire:
221 if wire:
221 try:
222 try:
222 wire['context'] = uuid.UUID(wire['context'])
223 wire['context'] = uuid.UUID(wire['context'])
223 except KeyError:
224 except KeyError:
224 pass
225 pass
225 args.insert(0, wire)
226 args.insert(0, wire)
226
227
227 try:
228 try:
228 resp = getattr(remote, method)(*args, **kwargs)
229 resp = getattr(remote, method)(*args, **kwargs)
229 except Exception as e:
230 except Exception as e:
231 tb_info = traceback.format_exc()
232
230 type_ = e.__class__.__name__
233 type_ = e.__class__.__name__
231 if type_ not in self.ALLOWED_EXCEPTIONS:
234 if type_ not in self.ALLOWED_EXCEPTIONS:
232 type_ = None
235 type_ = None
233
236
234 resp = {
237 resp = {
235 'id': payload.get('id'),
238 'id': payload.get('id'),
236 'error': {
239 'error': {
237 'message': e.message,
240 'message': e.message,
241 'traceback': tb_info,
238 'type': type_
242 'type': type_
239 }
243 }
240 }
244 }
241 try:
245 try:
242 resp['error']['_vcs_kind'] = e._vcs_kind
246 resp['error']['_vcs_kind'] = e._vcs_kind
243 except AttributeError:
247 except AttributeError:
244 pass
248 pass
245 else:
249 else:
246 resp = {
250 resp = {
247 'id': payload.get('id'),
251 'id': payload.get('id'),
248 'result': resp
252 'result': resp
249 }
253 }
250
254
251 return resp
255 return resp
252
256
253 def status_view(self, request):
257 def status_view(self, request):
254 return {'status': 'OK'}
258 return {'status': 'OK'}
255
259
256 def service_view(self, request):
260 def service_view(self, request):
257 import vcsserver
261 import vcsserver
258 payload = msgpack.unpackb(request.body, use_list=True)
262 payload = msgpack.unpackb(request.body, use_list=True)
259 resp = {
263 resp = {
260 'id': payload.get('id'),
264 'id': payload.get('id'),
261 'result': dict(
265 'result': dict(
262 version=vcsserver.__version__,
266 version=vcsserver.__version__,
263 config={},
267 config={},
264 payload=payload,
268 payload=payload,
265 )
269 )
266 }
270 }
267 return resp
271 return resp
268
272
269 def _msgpack_renderer_factory(self, info):
273 def _msgpack_renderer_factory(self, info):
270 def _render(value, system):
274 def _render(value, system):
271 value = msgpack.packb(value)
275 value = msgpack.packb(value)
272 request = system.get('request')
276 request = system.get('request')
273 if request is not None:
277 if request is not None:
274 response = request.response
278 response = request.response
275 ct = response.content_type
279 ct = response.content_type
276 if ct == response.default_content_type:
280 if ct == response.default_content_type:
277 response.content_type = 'application/x-msgpack'
281 response.content_type = 'application/x-msgpack'
278 return value
282 return value
279 return _render
283 return _render
280
284
281 def hg_proxy(self):
285 def hg_proxy(self):
282 @wsgiapp
286 @wsgiapp
283 def _hg_proxy(environ, start_response):
287 def _hg_proxy(environ, start_response):
284 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
288 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
285 return app(environ, start_response)
289 return app(environ, start_response)
286 return _hg_proxy
290 return _hg_proxy
287
291
288 def git_proxy(self):
292 def git_proxy(self):
289 @wsgiapp
293 @wsgiapp
290 def _git_proxy(environ, start_response):
294 def _git_proxy(environ, start_response):
291 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
295 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
292 return app(environ, start_response)
296 return app(environ, start_response)
293 return _git_proxy
297 return _git_proxy
294
298
295 def hg_stream(self):
299 def hg_stream(self):
296 if self._use_echo_app:
300 if self._use_echo_app:
297 @wsgiapp
301 @wsgiapp
298 def _hg_stream(environ, start_response):
302 def _hg_stream(environ, start_response):
299 app = EchoApp('fake_path', 'fake_name', None)
303 app = EchoApp('fake_path', 'fake_name', None)
300 return app(environ, start_response)
304 return app(environ, start_response)
301 return _hg_stream
305 return _hg_stream
302 else:
306 else:
303 @wsgiapp
307 @wsgiapp
304 def _hg_stream(environ, start_response):
308 def _hg_stream(environ, start_response):
305 repo_path = environ['HTTP_X_RC_REPO_PATH']
309 repo_path = environ['HTTP_X_RC_REPO_PATH']
306 repo_name = environ['HTTP_X_RC_REPO_NAME']
310 repo_name = environ['HTTP_X_RC_REPO_NAME']
307 packed_config = base64.b64decode(
311 packed_config = base64.b64decode(
308 environ['HTTP_X_RC_REPO_CONFIG'])
312 environ['HTTP_X_RC_REPO_CONFIG'])
309 config = msgpack.unpackb(packed_config)
313 config = msgpack.unpackb(packed_config)
310 app = scm_app.create_hg_wsgi_app(
314 app = scm_app.create_hg_wsgi_app(
311 repo_path, repo_name, config)
315 repo_path, repo_name, config)
312
316
313 # Consitent path information for hgweb
317 # Consitent path information for hgweb
314 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
318 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
315 environ['REPO_NAME'] = repo_name
319 environ['REPO_NAME'] = repo_name
316 return app(environ, ResponseFilter(start_response))
320 return app(environ, ResponseFilter(start_response))
317 return _hg_stream
321 return _hg_stream
318
322
319 def git_stream(self):
323 def git_stream(self):
320 if self._use_echo_app:
324 if self._use_echo_app:
321 @wsgiapp
325 @wsgiapp
322 def _git_stream(environ, start_response):
326 def _git_stream(environ, start_response):
323 app = EchoApp('fake_path', 'fake_name', None)
327 app = EchoApp('fake_path', 'fake_name', None)
324 return app(environ, start_response)
328 return app(environ, start_response)
325 return _git_stream
329 return _git_stream
326 else:
330 else:
327 @wsgiapp
331 @wsgiapp
328 def _git_stream(environ, start_response):
332 def _git_stream(environ, start_response):
329 repo_path = environ['HTTP_X_RC_REPO_PATH']
333 repo_path = environ['HTTP_X_RC_REPO_PATH']
330 repo_name = environ['HTTP_X_RC_REPO_NAME']
334 repo_name = environ['HTTP_X_RC_REPO_NAME']
331 packed_config = base64.b64decode(
335 packed_config = base64.b64decode(
332 environ['HTTP_X_RC_REPO_CONFIG'])
336 environ['HTTP_X_RC_REPO_CONFIG'])
333 config = msgpack.unpackb(packed_config)
337 config = msgpack.unpackb(packed_config)
334
338
335 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
339 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
336 app = scm_app.create_git_wsgi_app(
340 app = scm_app.create_git_wsgi_app(
337 repo_path, repo_name, config)
341 repo_path, repo_name, config)
338 return app(environ, start_response)
342 return app(environ, start_response)
339 return _git_stream
343 return _git_stream
340
344
341 def is_vcs_exception(self, context, request):
345 def is_vcs_exception(self, context, request):
342 """
346 """
343 View predicate that returns true if the context object is a VCS
347 View predicate that returns true if the context object is a VCS
344 exception.
348 exception.
345 """
349 """
346 return hasattr(context, '_vcs_kind')
350 return hasattr(context, '_vcs_kind')
347
351
348 def handle_vcs_exception(self, exception, request):
352 def handle_vcs_exception(self, exception, request):
349 if exception._vcs_kind == 'repo_locked':
353 if exception._vcs_kind == 'repo_locked':
350 # Get custom repo-locked status code if present.
354 # Get custom repo-locked status code if present.
351 status_code = request.headers.get('X-RC-Locked-Status-Code')
355 status_code = request.headers.get('X-RC-Locked-Status-Code')
352 return HTTPRepoLocked(
356 return HTTPRepoLocked(
353 title=exception.message, status_code=status_code)
357 title=exception.message, status_code=status_code)
354
358
355 # Re-raise exception if we can not handle it.
359 # Re-raise exception if we can not handle it.
356 raise exception
360 raise exception
357
361
358
362
359 class ResponseFilter(object):
363 class ResponseFilter(object):
360
364
361 def __init__(self, start_response):
365 def __init__(self, start_response):
362 self._start_response = start_response
366 self._start_response = start_response
363
367
364 def __call__(self, status, response_headers, exc_info=None):
368 def __call__(self, status, response_headers, exc_info=None):
365 headers = tuple(
369 headers = tuple(
366 (h, v) for h, v in response_headers
370 (h, v) for h, v in response_headers
367 if not wsgiref.util.is_hop_by_hop(h))
371 if not wsgiref.util.is_hop_by_hop(h))
368 return self._start_response(status, headers, exc_info)
372 return self._start_response(status, headers, exc_info)
369
373
370
374
371 def main(global_config, **settings):
375 def main(global_config, **settings):
372 if MercurialFactory:
376 if MercurialFactory:
373 hgpatches.patch_largefiles_capabilities()
377 hgpatches.patch_largefiles_capabilities()
374 hgpatches.patch_subrepo_type_mapping()
378 hgpatches.patch_subrepo_type_mapping()
375 app = HTTPApplication(settings=settings)
379 app = HTTPApplication(settings=settings)
376 return app.wsgi_app()
380 return app.wsgi_app()
General Comments 0
You need to be logged in to leave comments. Login now