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