##// END OF EJS Templates
hg: Include mercurial patching when using the http app.
Martin Bornhold -
r35:e472f942 default
parent child Browse files
Show More
@@ -1,335 +1,337 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 from itertools import chain
23 from itertools import chain
24
24
25 import msgpack
25 import msgpack
26 from beaker.cache import CacheManager
26 from beaker.cache import CacheManager
27 from beaker.util import parse_cache_config_options
27 from beaker.util import parse_cache_config_options
28 from pyramid.config import Configurator
28 from pyramid.config import Configurator
29 from pyramid.wsgi import wsgiapp
29 from pyramid.wsgi import wsgiapp
30
30
31 from vcsserver import remote_wsgi, scm_app, settings
31 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
32 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
32 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
33 from vcsserver.echo_stub.echo_app import EchoApp
33 from vcsserver.echo_stub.echo_app import EchoApp
34 from vcsserver.server import VcsServer
34 from vcsserver.server import VcsServer
35
35
36 try:
36 try:
37 from vcsserver.git import GitFactory, GitRemote
37 from vcsserver.git import GitFactory, GitRemote
38 except ImportError:
38 except ImportError:
39 GitFactory = None
39 GitFactory = None
40 GitRemote = None
40 GitRemote = None
41 try:
41 try:
42 from vcsserver.hg import MercurialFactory, HgRemote
42 from vcsserver.hg import MercurialFactory, HgRemote
43 except ImportError:
43 except ImportError:
44 MercurialFactory = None
44 MercurialFactory = None
45 HgRemote = None
45 HgRemote = None
46 try:
46 try:
47 from vcsserver.svn import SubversionFactory, SvnRemote
47 from vcsserver.svn import SubversionFactory, SvnRemote
48 except ImportError:
48 except ImportError:
49 SubversionFactory = None
49 SubversionFactory = None
50 SvnRemote = None
50 SvnRemote = None
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 class VCS(object):
55 class VCS(object):
56 def __init__(self, locale=None, cache_config=None):
56 def __init__(self, locale=None, cache_config=None):
57 self.locale = locale
57 self.locale = locale
58 self.cache_config = cache_config
58 self.cache_config = cache_config
59 self._configure_locale()
59 self._configure_locale()
60 self._initialize_cache()
60 self._initialize_cache()
61
61
62 if GitFactory and GitRemote:
62 if GitFactory and GitRemote:
63 git_repo_cache = self.cache.get_cache_region(
63 git_repo_cache = self.cache.get_cache_region(
64 'git', region='repo_object')
64 'git', region='repo_object')
65 git_factory = GitFactory(git_repo_cache)
65 git_factory = GitFactory(git_repo_cache)
66 self._git_remote = GitRemote(git_factory)
66 self._git_remote = GitRemote(git_factory)
67 else:
67 else:
68 log.info("Git client import failed")
68 log.info("Git client import failed")
69
69
70 if MercurialFactory and HgRemote:
70 if MercurialFactory and HgRemote:
71 hg_repo_cache = self.cache.get_cache_region(
71 hg_repo_cache = self.cache.get_cache_region(
72 'hg', region='repo_object')
72 'hg', region='repo_object')
73 hg_factory = MercurialFactory(hg_repo_cache)
73 hg_factory = MercurialFactory(hg_repo_cache)
74 self._hg_remote = HgRemote(hg_factory)
74 self._hg_remote = HgRemote(hg_factory)
75 else:
75 else:
76 log.info("Mercurial client import failed")
76 log.info("Mercurial client import failed")
77
77
78 if SubversionFactory and SvnRemote:
78 if SubversionFactory and SvnRemote:
79 svn_repo_cache = self.cache.get_cache_region(
79 svn_repo_cache = self.cache.get_cache_region(
80 'svn', region='repo_object')
80 'svn', region='repo_object')
81 svn_factory = SubversionFactory(svn_repo_cache)
81 svn_factory = SubversionFactory(svn_repo_cache)
82 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
82 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
83 else:
83 else:
84 log.info("Subversion client import failed")
84 log.info("Subversion client import failed")
85
85
86 self._vcsserver = VcsServer()
86 self._vcsserver = VcsServer()
87
87
88 def _initialize_cache(self):
88 def _initialize_cache(self):
89 cache_config = parse_cache_config_options(self.cache_config)
89 cache_config = parse_cache_config_options(self.cache_config)
90 log.info('Initializing beaker cache: %s' % cache_config)
90 log.info('Initializing beaker cache: %s' % cache_config)
91 self.cache = CacheManager(**cache_config)
91 self.cache = CacheManager(**cache_config)
92
92
93 def _configure_locale(self):
93 def _configure_locale(self):
94 if self.locale:
94 if self.locale:
95 log.info('Settings locale: `LC_ALL` to %s' % self.locale)
95 log.info('Settings locale: `LC_ALL` to %s' % self.locale)
96 else:
96 else:
97 log.info(
97 log.info(
98 'Configuring locale subsystem based on environment variables')
98 'Configuring locale subsystem based on environment variables')
99 try:
99 try:
100 # If self.locale is the empty string, then the locale
100 # If self.locale is the empty string, then the locale
101 # module will use the environment variables. See the
101 # module will use the environment variables. See the
102 # documentation of the package `locale`.
102 # documentation of the package `locale`.
103 locale.setlocale(locale.LC_ALL, self.locale)
103 locale.setlocale(locale.LC_ALL, self.locale)
104
104
105 language_code, encoding = locale.getlocale()
105 language_code, encoding = locale.getlocale()
106 log.info(
106 log.info(
107 'Locale set to language code "%s" with encoding "%s".',
107 'Locale set to language code "%s" with encoding "%s".',
108 language_code, encoding)
108 language_code, encoding)
109 except locale.Error:
109 except locale.Error:
110 log.exception(
110 log.exception(
111 'Cannot set locale, not configuring the locale system')
111 'Cannot set locale, not configuring the locale system')
112
112
113
113
114 class WsgiProxy(object):
114 class WsgiProxy(object):
115 def __init__(self, wsgi):
115 def __init__(self, wsgi):
116 self.wsgi = wsgi
116 self.wsgi = wsgi
117
117
118 def __call__(self, environ, start_response):
118 def __call__(self, environ, start_response):
119 input_data = environ['wsgi.input'].read()
119 input_data = environ['wsgi.input'].read()
120 input_data = msgpack.unpackb(input_data)
120 input_data = msgpack.unpackb(input_data)
121
121
122 error = None
122 error = None
123 try:
123 try:
124 data, status, headers = self.wsgi.handle(
124 data, status, headers = self.wsgi.handle(
125 input_data['environment'], input_data['input_data'],
125 input_data['environment'], input_data['input_data'],
126 *input_data['args'], **input_data['kwargs'])
126 *input_data['args'], **input_data['kwargs'])
127 except Exception as e:
127 except Exception as e:
128 data, status, headers = [], None, None
128 data, status, headers = [], None, None
129 error = {
129 error = {
130 'message': str(e),
130 'message': str(e),
131 '_vcs_kind': getattr(e, '_vcs_kind', None)
131 '_vcs_kind': getattr(e, '_vcs_kind', None)
132 }
132 }
133
133
134 start_response(200, {})
134 start_response(200, {})
135 return self._iterator(error, status, headers, data)
135 return self._iterator(error, status, headers, data)
136
136
137 def _iterator(self, error, status, headers, data):
137 def _iterator(self, error, status, headers, data):
138 initial_data = [
138 initial_data = [
139 error,
139 error,
140 status,
140 status,
141 headers,
141 headers,
142 ]
142 ]
143
143
144 for d in chain(initial_data, data):
144 for d in chain(initial_data, data):
145 yield msgpack.packb(d)
145 yield msgpack.packb(d)
146
146
147
147
148 class HTTPApplication(object):
148 class HTTPApplication(object):
149 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
149 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
150
150
151 remote_wsgi = remote_wsgi
151 remote_wsgi = remote_wsgi
152 _use_echo_app = False
152 _use_echo_app = False
153
153
154 def __init__(self, settings=None):
154 def __init__(self, settings=None):
155 self.config = Configurator(settings=settings)
155 self.config = Configurator(settings=settings)
156 locale = settings.get('', 'en_US.UTF-8')
156 locale = settings.get('', 'en_US.UTF-8')
157 vcs = VCS(locale=locale, cache_config=settings)
157 vcs = VCS(locale=locale, cache_config=settings)
158 self._remotes = {
158 self._remotes = {
159 'hg': vcs._hg_remote,
159 'hg': vcs._hg_remote,
160 'git': vcs._git_remote,
160 'git': vcs._git_remote,
161 'svn': vcs._svn_remote,
161 'svn': vcs._svn_remote,
162 'server': vcs._vcsserver,
162 'server': vcs._vcsserver,
163 }
163 }
164 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
164 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
165 self._use_echo_app = True
165 self._use_echo_app = True
166 log.warning("Using EchoApp for VCS operations.")
166 log.warning("Using EchoApp for VCS operations.")
167 self.remote_wsgi = remote_wsgi_stub
167 self.remote_wsgi = remote_wsgi_stub
168 self._configure_settings(settings)
168 self._configure_settings(settings)
169 self._configure()
169 self._configure()
170
170
171 def _configure_settings(self, app_settings):
171 def _configure_settings(self, app_settings):
172 """
172 """
173 Configure the settings module.
173 Configure the settings module.
174 """
174 """
175 git_path = app_settings.get('git_path', None)
175 git_path = app_settings.get('git_path', None)
176 if git_path:
176 if git_path:
177 settings.GIT_EXECUTABLE = git_path
177 settings.GIT_EXECUTABLE = git_path
178
178
179 def _configure(self):
179 def _configure(self):
180 self.config.add_renderer(
180 self.config.add_renderer(
181 name='msgpack',
181 name='msgpack',
182 factory=self._msgpack_renderer_factory)
182 factory=self._msgpack_renderer_factory)
183
183
184 self.config.add_route('status', '/status')
184 self.config.add_route('status', '/status')
185 self.config.add_route('hg_proxy', '/proxy/hg')
185 self.config.add_route('hg_proxy', '/proxy/hg')
186 self.config.add_route('git_proxy', '/proxy/git')
186 self.config.add_route('git_proxy', '/proxy/git')
187 self.config.add_route('vcs', '/{backend}')
187 self.config.add_route('vcs', '/{backend}')
188 self.config.add_route('stream_git', '/stream/git/*repo_name')
188 self.config.add_route('stream_git', '/stream/git/*repo_name')
189 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
189 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
190
190
191 self.config.add_view(
191 self.config.add_view(
192 self.status_view, route_name='status', renderer='json')
192 self.status_view, route_name='status', renderer='json')
193 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
193 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
194 self.config.add_view(self.git_proxy(), route_name='git_proxy')
194 self.config.add_view(self.git_proxy(), route_name='git_proxy')
195 self.config.add_view(
195 self.config.add_view(
196 self.vcs_view, route_name='vcs', renderer='msgpack')
196 self.vcs_view, route_name='vcs', renderer='msgpack')
197
197
198 self.config.add_view(self.hg_stream(), route_name='stream_hg')
198 self.config.add_view(self.hg_stream(), route_name='stream_hg')
199 self.config.add_view(self.git_stream(), route_name='stream_git')
199 self.config.add_view(self.git_stream(), route_name='stream_git')
200
200
201 def wsgi_app(self):
201 def wsgi_app(self):
202 return self.config.make_wsgi_app()
202 return self.config.make_wsgi_app()
203
203
204 def vcs_view(self, request):
204 def vcs_view(self, request):
205 remote = self._remotes[request.matchdict['backend']]
205 remote = self._remotes[request.matchdict['backend']]
206 payload = msgpack.unpackb(request.body, use_list=True)
206 payload = msgpack.unpackb(request.body, use_list=True)
207 method = payload.get('method')
207 method = payload.get('method')
208 params = payload.get('params')
208 params = payload.get('params')
209 wire = params.get('wire')
209 wire = params.get('wire')
210 args = params.get('args')
210 args = params.get('args')
211 kwargs = params.get('kwargs')
211 kwargs = params.get('kwargs')
212 if wire:
212 if wire:
213 try:
213 try:
214 wire['context'] = uuid.UUID(wire['context'])
214 wire['context'] = uuid.UUID(wire['context'])
215 except KeyError:
215 except KeyError:
216 pass
216 pass
217 args.insert(0, wire)
217 args.insert(0, wire)
218
218
219 try:
219 try:
220 resp = getattr(remote, method)(*args, **kwargs)
220 resp = getattr(remote, method)(*args, **kwargs)
221 except Exception as e:
221 except Exception as e:
222 type_ = e.__class__.__name__
222 type_ = e.__class__.__name__
223 if type_ not in self.ALLOWED_EXCEPTIONS:
223 if type_ not in self.ALLOWED_EXCEPTIONS:
224 type_ = None
224 type_ = None
225
225
226 resp = {
226 resp = {
227 'id': payload.get('id'),
227 'id': payload.get('id'),
228 'error': {
228 'error': {
229 'message': e.message,
229 'message': e.message,
230 'type': type_
230 'type': type_
231 }
231 }
232 }
232 }
233 try:
233 try:
234 resp['error']['_vcs_kind'] = e._vcs_kind
234 resp['error']['_vcs_kind'] = e._vcs_kind
235 except AttributeError:
235 except AttributeError:
236 pass
236 pass
237 else:
237 else:
238 resp = {
238 resp = {
239 'id': payload.get('id'),
239 'id': payload.get('id'),
240 'result': resp
240 'result': resp
241 }
241 }
242
242
243 return resp
243 return resp
244
244
245 def status_view(self, request):
245 def status_view(self, request):
246 return {'status': 'OK'}
246 return {'status': 'OK'}
247
247
248 def _msgpack_renderer_factory(self, info):
248 def _msgpack_renderer_factory(self, info):
249 def _render(value, system):
249 def _render(value, system):
250 value = msgpack.packb(value)
250 value = msgpack.packb(value)
251 request = system.get('request')
251 request = system.get('request')
252 if request is not None:
252 if request is not None:
253 response = request.response
253 response = request.response
254 ct = response.content_type
254 ct = response.content_type
255 if ct == response.default_content_type:
255 if ct == response.default_content_type:
256 response.content_type = 'application/x-msgpack'
256 response.content_type = 'application/x-msgpack'
257 return value
257 return value
258 return _render
258 return _render
259
259
260 def hg_proxy(self):
260 def hg_proxy(self):
261 @wsgiapp
261 @wsgiapp
262 def _hg_proxy(environ, start_response):
262 def _hg_proxy(environ, start_response):
263 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
263 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
264 return app(environ, start_response)
264 return app(environ, start_response)
265 return _hg_proxy
265 return _hg_proxy
266
266
267 def git_proxy(self):
267 def git_proxy(self):
268 @wsgiapp
268 @wsgiapp
269 def _git_proxy(environ, start_response):
269 def _git_proxy(environ, start_response):
270 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
270 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
271 return app(environ, start_response)
271 return app(environ, start_response)
272 return _git_proxy
272 return _git_proxy
273
273
274 def hg_stream(self):
274 def hg_stream(self):
275 if self._use_echo_app:
275 if self._use_echo_app:
276 @wsgiapp
276 @wsgiapp
277 def _hg_stream(environ, start_response):
277 def _hg_stream(environ, start_response):
278 app = EchoApp('fake_path', 'fake_name', None)
278 app = EchoApp('fake_path', 'fake_name', None)
279 return app(environ, start_response)
279 return app(environ, start_response)
280 return _hg_stream
280 return _hg_stream
281 else:
281 else:
282 @wsgiapp
282 @wsgiapp
283 def _hg_stream(environ, start_response):
283 def _hg_stream(environ, start_response):
284 repo_path = environ['HTTP_X_RC_REPO_PATH']
284 repo_path = environ['HTTP_X_RC_REPO_PATH']
285 repo_name = environ['HTTP_X_RC_REPO_NAME']
285 repo_name = environ['HTTP_X_RC_REPO_NAME']
286 packed_config = base64.b64decode(
286 packed_config = base64.b64decode(
287 environ['HTTP_X_RC_REPO_CONFIG'])
287 environ['HTTP_X_RC_REPO_CONFIG'])
288 config = msgpack.unpackb(packed_config)
288 config = msgpack.unpackb(packed_config)
289 app = scm_app.create_hg_wsgi_app(
289 app = scm_app.create_hg_wsgi_app(
290 repo_path, repo_name, config)
290 repo_path, repo_name, config)
291
291
292 # Consitent path information for hgweb
292 # Consitent path information for hgweb
293 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
293 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
294 environ['REPO_NAME'] = repo_name
294 environ['REPO_NAME'] = repo_name
295 return app(environ, ResponseFilter(start_response))
295 return app(environ, ResponseFilter(start_response))
296 return _hg_stream
296 return _hg_stream
297
297
298 def git_stream(self):
298 def git_stream(self):
299 if self._use_echo_app:
299 if self._use_echo_app:
300 @wsgiapp
300 @wsgiapp
301 def _git_stream(environ, start_response):
301 def _git_stream(environ, start_response):
302 app = EchoApp('fake_path', 'fake_name', None)
302 app = EchoApp('fake_path', 'fake_name', None)
303 return app(environ, start_response)
303 return app(environ, start_response)
304 return _git_stream
304 return _git_stream
305 else:
305 else:
306 @wsgiapp
306 @wsgiapp
307 def _git_stream(environ, start_response):
307 def _git_stream(environ, start_response):
308 repo_path = environ['HTTP_X_RC_REPO_PATH']
308 repo_path = environ['HTTP_X_RC_REPO_PATH']
309 repo_name = environ['HTTP_X_RC_REPO_NAME']
309 repo_name = environ['HTTP_X_RC_REPO_NAME']
310 packed_config = base64.b64decode(
310 packed_config = base64.b64decode(
311 environ['HTTP_X_RC_REPO_CONFIG'])
311 environ['HTTP_X_RC_REPO_CONFIG'])
312 config = msgpack.unpackb(packed_config)
312 config = msgpack.unpackb(packed_config)
313
313
314 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
314 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
315 app = scm_app.create_git_wsgi_app(
315 app = scm_app.create_git_wsgi_app(
316 repo_path, repo_name, config)
316 repo_path, repo_name, config)
317 return app(environ, start_response)
317 return app(environ, start_response)
318 return _git_stream
318 return _git_stream
319
319
320
320
321 class ResponseFilter(object):
321 class ResponseFilter(object):
322
322
323 def __init__(self, start_response):
323 def __init__(self, start_response):
324 self._start_response = start_response
324 self._start_response = start_response
325
325
326 def __call__(self, status, response_headers, exc_info=None):
326 def __call__(self, status, response_headers, exc_info=None):
327 headers = tuple(
327 headers = tuple(
328 (h, v) for h, v in response_headers
328 (h, v) for h, v in response_headers
329 if not wsgiref.util.is_hop_by_hop(h))
329 if not wsgiref.util.is_hop_by_hop(h))
330 return self._start_response(status, headers, exc_info)
330 return self._start_response(status, headers, exc_info)
331
331
332
332
333 def main(global_config, **settings):
333 def main(global_config, **settings):
334 if MercurialFactory:
335 hgpatches.patch_largefiles_capabilities()
334 app = HTTPApplication(settings=settings)
336 app = HTTPApplication(settings=settings)
335 return app.wsgi_app()
337 return app.wsgi_app()
General Comments 0
You need to be logged in to leave comments. Login now