##// END OF EJS Templates
py3: update mercurial changes for 6.3 release
super-admin -
r1039:35a2dc1e python3
parent child Browse files
Show More
@@ -1,43 +1,44 b''
1 ## dependencies
1 ## dependencies
2
2
3 # our custom configobj
3 # our custom configobj
4 configobj==5.0.8
4 configobj==5.0.8
5
5
6 dogpile.cache==1.1.8
6 dogpile.cache==1.1.8
7
7
8 decorator==5.1.1
8 decorator==5.1.1
9 dulwich==0.21.3
9 dulwich==0.21.3
10 hgsubversion==1.9.3
10 hgsubversion==1.9.3
11 hg-evolve==11.0.0
11 hg-evolve==11.0.0
12
12
13 mercurial==6.3.2
13 mercurial==6.3.2
14 msgpack-python==0.5.6
14 msgpack-python==0.5.6
15 more-itertools==9.1.0
15
16
16 pastedeploy==2.1.0
17 pastedeploy==2.1.0
17 pyramid==2.0.1
18 pyramid==2.0.1
18 pygit2==1.11.1
19 pygit2==1.11.1
19
20
20 repoze.lru==0.7
21 repoze.lru==0.7
21 redis==4.5.1
22 redis==4.5.1
22 simplejson==3.18.3
23 simplejson==3.18.3
23 subvertpy==0.11.0
24 subvertpy==0.11.0
24
25
25 translationstring==1.4
26 translationstring==1.4
26 webob==1.8.7
27 webob==1.8.7
27 zope.deprecation==4.4.0
28 zope.deprecation==4.4.0
28 zope.interface==5.5.2
29 zope.interface==5.5.2
29
30
30 ## http servers
31 ## http servers
31 #gevent==20.6.0
32 #gevent==20.6.0
32 #greenlet==0.4.16
33 #greenlet==0.4.16
33 gunicorn==20.1.0
34 gunicorn==20.1.0
34 waitress==2.1.2
35 waitress==2.1.2
35
36
36 ## test related requirements
37 ## test related requirements
37 -r requirements_test.txt
38 -r requirements_test.txt
38
39
39 ## uncomment to add the debug libraries
40 ## uncomment to add the debug libraries
40 #ipdb==0.13.2
41 #ipdb==0.13.2
41 #ipython==7.15.0
42 #ipython==7.15.0
42
43
43 #-r requirements_debug.txt
44 #-r requirements_debug.txt
@@ -1,693 +1,702 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-2020 RhodeCode GmbH
2 # Copyright (C) 2014-2020 RhodeCode 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 os
18 import os
19 import sys
19 import sys
20 import base64
20 import base64
21 import locale
21 import locale
22 import logging
22 import logging
23 import uuid
23 import uuid
24 import wsgiref.util
24 import wsgiref.util
25 import traceback
25 import traceback
26 import tempfile
26 import tempfile
27 from itertools import chain
27 from itertools import chain
28 from io import StringIO
28 from io import StringIO
29
29
30 import simplejson as json
30 import simplejson as json
31 import msgpack
31 import msgpack
32 import configparser
32 import configparser
33 from pyramid.config import Configurator
33 from pyramid.config import Configurator
34 from pyramid.settings import asbool, aslist
34 from pyramid.settings import asbool, aslist
35 from pyramid.wsgi import wsgiapp
35 from pyramid.wsgi import wsgiapp
36 from pyramid.response import Response
36 from pyramid.response import Response
37
37
38 from vcsserver.utils import safe_int
38 from vcsserver.utils import safe_int
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
42 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
43 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
43 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
44
44
45 try:
45 try:
46 locale.setlocale(locale.LC_ALL, '')
46 locale.setlocale(locale.LC_ALL, '')
47 except locale.Error as e:
47 except locale.Error as e:
48 log.error(
48 log.error(
49 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
49 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
50 os.environ['LC_ALL'] = 'C'
50 os.environ['LC_ALL'] = 'C'
51
51
52 import vcsserver
52 import vcsserver
53 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
53 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
54 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
54 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
55 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
55 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
56 from vcsserver.echo_stub.echo_app import EchoApp
56 from vcsserver.echo_stub.echo_app import EchoApp
57 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
57 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
58 from vcsserver.lib.exc_tracking import store_exception
58 from vcsserver.lib.exc_tracking import store_exception
59 from vcsserver.server import VcsServer
59 from vcsserver.server import VcsServer
60
60
61 strict_vcs = True
61
62
62 git_import_err = None
63 git_import_err = None
63 try:
64 try:
64 from vcsserver.git import GitFactory, GitRemote
65 from vcsserver.git import GitFactory, GitRemote
65 except ImportError as e:
66 except ImportError as e:
66 GitFactory = None
67 GitFactory = None
67 GitRemote = None
68 GitRemote = None
68 git_import_err = e
69 git_import_err = e
70 if strict_vcs:
71 raise
72
69
73
70 hg_import_err = None
74 hg_import_err = None
71 try:
75 try:
72 from vcsserver.hg import MercurialFactory, HgRemote
76 from vcsserver.hg import MercurialFactory, HgRemote
73 except ImportError as e:
77 except ImportError as e:
74 MercurialFactory = None
78 MercurialFactory = None
75 HgRemote = None
79 HgRemote = None
76 hg_import_err = e
80 hg_import_err = e
81 if strict_vcs:
82 raise
83
77
84
78 svn_import_err = None
85 svn_import_err = None
79 try:
86 try:
80 from vcsserver.svn import SubversionFactory, SvnRemote
87 from vcsserver.svn import SubversionFactory, SvnRemote
81 except ImportError as e:
88 except ImportError as e:
82 SubversionFactory = None
89 SubversionFactory = None
83 SvnRemote = None
90 SvnRemote = None
84 svn_import_err = e
91 svn_import_err = e
92 if strict_vcs:
93 raise
85
94
86
95
87 def _is_request_chunked(environ):
96 def _is_request_chunked(environ):
88 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
97 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
89 return stream
98 return stream
90
99
91
100
92 def _int_setting(settings, name, default):
101 def _int_setting(settings, name, default):
93 settings[name] = int(settings.get(name, default))
102 settings[name] = int(settings.get(name, default))
94 return settings[name]
103 return settings[name]
95
104
96
105
97 def _bool_setting(settings, name, default):
106 def _bool_setting(settings, name, default):
98 input_val = settings.get(name, default)
107 input_val = settings.get(name, default)
99 if isinstance(input_val, str):
108 if isinstance(input_val, str):
100 input_val = input_val.encode('utf8')
109 input_val = input_val.encode('utf8')
101 settings[name] = asbool(input_val)
110 settings[name] = asbool(input_val)
102 return settings[name]
111 return settings[name]
103
112
104
113
105 def _list_setting(settings, name, default):
114 def _list_setting(settings, name, default):
106 raw_value = settings.get(name, default)
115 raw_value = settings.get(name, default)
107
116
108 # Otherwise we assume it uses pyramids space/newline separation.
117 # Otherwise we assume it uses pyramids space/newline separation.
109 settings[name] = aslist(raw_value)
118 settings[name] = aslist(raw_value)
110 return settings[name]
119 return settings[name]
111
120
112
121
113 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
122 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
114 value = settings.get(name, default)
123 value = settings.get(name, default)
115
124
116 if default_when_empty and not value:
125 if default_when_empty and not value:
117 # use default value when value is empty
126 # use default value when value is empty
118 value = default
127 value = default
119
128
120 if lower:
129 if lower:
121 value = value.lower()
130 value = value.lower()
122 settings[name] = value
131 settings[name] = value
123 return settings[name]
132 return settings[name]
124
133
125
134
126 class VCS(object):
135 class VCS(object):
127 def __init__(self, locale_conf=None, cache_config=None):
136 def __init__(self, locale_conf=None, cache_config=None):
128 self.locale = locale_conf
137 self.locale = locale_conf
129 self.cache_config = cache_config
138 self.cache_config = cache_config
130 self._configure_locale()
139 self._configure_locale()
131
140
132 if GitFactory and GitRemote:
141 if GitFactory and GitRemote:
133 git_factory = GitFactory()
142 git_factory = GitFactory()
134 self._git_remote = GitRemote(git_factory)
143 self._git_remote = GitRemote(git_factory)
135 else:
144 else:
136 log.error("Git client import failed: %s", git_import_err)
145 log.error("Git client import failed: %s", git_import_err)
137
146
138 if MercurialFactory and HgRemote:
147 if MercurialFactory and HgRemote:
139 hg_factory = MercurialFactory()
148 hg_factory = MercurialFactory()
140 self._hg_remote = HgRemote(hg_factory)
149 self._hg_remote = HgRemote(hg_factory)
141 else:
150 else:
142 log.error("Mercurial client import failed: %s", hg_import_err)
151 log.error("Mercurial client import failed: %s", hg_import_err)
143
152
144 if SubversionFactory and SvnRemote:
153 if SubversionFactory and SvnRemote:
145 svn_factory = SubversionFactory()
154 svn_factory = SubversionFactory()
146
155
147 # hg factory is used for svn url validation
156 # hg factory is used for svn url validation
148 hg_factory = MercurialFactory()
157 hg_factory = MercurialFactory()
149 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
158 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
150 else:
159 else:
151 log.error("Subversion client import failed: %s", svn_import_err)
160 log.error("Subversion client import failed: %s", svn_import_err)
152
161
153 self._vcsserver = VcsServer()
162 self._vcsserver = VcsServer()
154
163
155 def _configure_locale(self):
164 def _configure_locale(self):
156 if self.locale:
165 if self.locale:
157 log.info('Settings locale: `LC_ALL` to %s', self.locale)
166 log.info('Settings locale: `LC_ALL` to %s', self.locale)
158 else:
167 else:
159 log.info('Configuring locale subsystem based on environment variables')
168 log.info('Configuring locale subsystem based on environment variables')
160 try:
169 try:
161 # If self.locale is the empty string, then the locale
170 # If self.locale is the empty string, then the locale
162 # module will use the environment variables. See the
171 # module will use the environment variables. See the
163 # documentation of the package `locale`.
172 # documentation of the package `locale`.
164 locale.setlocale(locale.LC_ALL, self.locale)
173 locale.setlocale(locale.LC_ALL, self.locale)
165
174
166 language_code, encoding = locale.getlocale()
175 language_code, encoding = locale.getlocale()
167 log.info(
176 log.info(
168 'Locale set to language code "%s" with encoding "%s".',
177 'Locale set to language code "%s" with encoding "%s".',
169 language_code, encoding)
178 language_code, encoding)
170 except locale.Error:
179 except locale.Error:
171 log.exception('Cannot set locale, not configuring the locale system')
180 log.exception('Cannot set locale, not configuring the locale system')
172
181
173
182
174 class WsgiProxy(object):
183 class WsgiProxy(object):
175 def __init__(self, wsgi):
184 def __init__(self, wsgi):
176 self.wsgi = wsgi
185 self.wsgi = wsgi
177
186
178 def __call__(self, environ, start_response):
187 def __call__(self, environ, start_response):
179 input_data = environ['wsgi.input'].read()
188 input_data = environ['wsgi.input'].read()
180 input_data = msgpack.unpackb(input_data)
189 input_data = msgpack.unpackb(input_data)
181
190
182 error = None
191 error = None
183 try:
192 try:
184 data, status, headers = self.wsgi.handle(
193 data, status, headers = self.wsgi.handle(
185 input_data['environment'], input_data['input_data'],
194 input_data['environment'], input_data['input_data'],
186 *input_data['args'], **input_data['kwargs'])
195 *input_data['args'], **input_data['kwargs'])
187 except Exception as e:
196 except Exception as e:
188 data, status, headers = [], None, None
197 data, status, headers = [], None, None
189 error = {
198 error = {
190 'message': str(e),
199 'message': str(e),
191 '_vcs_kind': getattr(e, '_vcs_kind', None)
200 '_vcs_kind': getattr(e, '_vcs_kind', None)
192 }
201 }
193
202
194 start_response(200, {})
203 start_response(200, {})
195 return self._iterator(error, status, headers, data)
204 return self._iterator(error, status, headers, data)
196
205
197 def _iterator(self, error, status, headers, data):
206 def _iterator(self, error, status, headers, data):
198 initial_data = [
207 initial_data = [
199 error,
208 error,
200 status,
209 status,
201 headers,
210 headers,
202 ]
211 ]
203
212
204 for d in chain(initial_data, data):
213 for d in chain(initial_data, data):
205 yield msgpack.packb(d)
214 yield msgpack.packb(d)
206
215
207
216
208 def not_found(request):
217 def not_found(request):
209 return {'status': '404 NOT FOUND'}
218 return {'status': '404 NOT FOUND'}
210
219
211
220
212 class VCSViewPredicate(object):
221 class VCSViewPredicate(object):
213 def __init__(self, val, config):
222 def __init__(self, val, config):
214 self.remotes = val
223 self.remotes = val
215
224
216 def text(self):
225 def text(self):
217 return 'vcs view method = %s' % (list(self.remotes.keys()),)
226 return 'vcs view method = %s' % (list(self.remotes.keys()),)
218
227
219 phash = text
228 phash = text
220
229
221 def __call__(self, context, request):
230 def __call__(self, context, request):
222 """
231 """
223 View predicate that returns true if given backend is supported by
232 View predicate that returns true if given backend is supported by
224 defined remotes.
233 defined remotes.
225 """
234 """
226 backend = request.matchdict.get('backend')
235 backend = request.matchdict.get('backend')
227 return backend in self.remotes
236 return backend in self.remotes
228
237
229
238
230 class HTTPApplication(object):
239 class HTTPApplication(object):
231 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
240 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
232
241
233 remote_wsgi = remote_wsgi
242 remote_wsgi = remote_wsgi
234 _use_echo_app = False
243 _use_echo_app = False
235
244
236 def __init__(self, settings=None, global_config=None):
245 def __init__(self, settings=None, global_config=None):
237 self._sanitize_settings_and_apply_defaults(settings)
246 self._sanitize_settings_and_apply_defaults(settings)
238
247
239 self.config = Configurator(settings=settings)
248 self.config = Configurator(settings=settings)
240 self.global_config = global_config
249 self.global_config = global_config
241 self.config.include('vcsserver.lib.rc_cache')
250 self.config.include('vcsserver.lib.rc_cache')
242
251
243 settings_locale = settings.get('locale', '') or 'en_US.UTF-8'
252 settings_locale = settings.get('locale', '') or 'en_US.UTF-8'
244 vcs = VCS(locale_conf=settings_locale, cache_config=settings)
253 vcs = VCS(locale_conf=settings_locale, cache_config=settings)
245 self._remotes = {
254 self._remotes = {
246 'hg': vcs._hg_remote,
255 'hg': vcs._hg_remote,
247 'git': vcs._git_remote,
256 'git': vcs._git_remote,
248 'svn': vcs._svn_remote,
257 'svn': vcs._svn_remote,
249 'server': vcs._vcsserver,
258 'server': vcs._vcsserver,
250 }
259 }
251 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
260 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
252 self._use_echo_app = True
261 self._use_echo_app = True
253 log.warning("Using EchoApp for VCS operations.")
262 log.warning("Using EchoApp for VCS operations.")
254 self.remote_wsgi = remote_wsgi_stub
263 self.remote_wsgi = remote_wsgi_stub
255
264
256 self._configure_settings(global_config, settings)
265 self._configure_settings(global_config, settings)
257 self._configure()
266 self._configure()
258
267
259 def _configure_settings(self, global_config, app_settings):
268 def _configure_settings(self, global_config, app_settings):
260 """
269 """
261 Configure the settings module.
270 Configure the settings module.
262 """
271 """
263 settings_merged = global_config.copy()
272 settings_merged = global_config.copy()
264 settings_merged.update(app_settings)
273 settings_merged.update(app_settings)
265
274
266 git_path = app_settings.get('git_path', None)
275 git_path = app_settings.get('git_path', None)
267 if git_path:
276 if git_path:
268 settings.GIT_EXECUTABLE = git_path
277 settings.GIT_EXECUTABLE = git_path
269 binary_dir = app_settings.get('core.binary_dir', None)
278 binary_dir = app_settings.get('core.binary_dir', None)
270 if binary_dir:
279 if binary_dir:
271 settings.BINARY_DIR = binary_dir
280 settings.BINARY_DIR = binary_dir
272
281
273 # Store the settings to make them available to other modules.
282 # Store the settings to make them available to other modules.
274 vcsserver.PYRAMID_SETTINGS = settings_merged
283 vcsserver.PYRAMID_SETTINGS = settings_merged
275 vcsserver.CONFIG = settings_merged
284 vcsserver.CONFIG = settings_merged
276
285
277 def _sanitize_settings_and_apply_defaults(self, settings):
286 def _sanitize_settings_and_apply_defaults(self, settings):
278 temp_store = tempfile.gettempdir()
287 temp_store = tempfile.gettempdir()
279 default_cache_dir = os.path.join(temp_store, 'rc_cache')
288 default_cache_dir = os.path.join(temp_store, 'rc_cache')
280
289
281 # save default, cache dir, and use it for all backends later.
290 # save default, cache dir, and use it for all backends later.
282 default_cache_dir = _string_setting(
291 default_cache_dir = _string_setting(
283 settings,
292 settings,
284 'cache_dir',
293 'cache_dir',
285 default_cache_dir, lower=False, default_when_empty=True)
294 default_cache_dir, lower=False, default_when_empty=True)
286
295
287 # ensure we have our dir created
296 # ensure we have our dir created
288 if not os.path.isdir(default_cache_dir):
297 if not os.path.isdir(default_cache_dir):
289 os.makedirs(default_cache_dir, mode=0o755)
298 os.makedirs(default_cache_dir, mode=0o755)
290
299
291 # exception store cache
300 # exception store cache
292 _string_setting(
301 _string_setting(
293 settings,
302 settings,
294 'exception_tracker.store_path',
303 'exception_tracker.store_path',
295 temp_store, lower=False, default_when_empty=True)
304 temp_store, lower=False, default_when_empty=True)
296
305
297 # repo_object cache
306 # repo_object cache
298 _string_setting(
307 _string_setting(
299 settings,
308 settings,
300 'rc_cache.repo_object.backend',
309 'rc_cache.repo_object.backend',
301 'dogpile.cache.rc.file_namespace', lower=False)
310 'dogpile.cache.rc.file_namespace', lower=False)
302 _int_setting(
311 _int_setting(
303 settings,
312 settings,
304 'rc_cache.repo_object.expiration_time',
313 'rc_cache.repo_object.expiration_time',
305 30 * 24 * 60 * 60)
314 30 * 24 * 60 * 60)
306 _string_setting(
315 _string_setting(
307 settings,
316 settings,
308 'rc_cache.repo_object.arguments.filename',
317 'rc_cache.repo_object.arguments.filename',
309 os.path.join(default_cache_dir, 'vcsserver_cache_1'), lower=False)
318 os.path.join(default_cache_dir, 'vcsserver_cache_1'), lower=False)
310
319
311 def _configure(self):
320 def _configure(self):
312 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
321 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
313
322
314 self.config.add_route('service', '/_service')
323 self.config.add_route('service', '/_service')
315 self.config.add_route('status', '/status')
324 self.config.add_route('status', '/status')
316 self.config.add_route('hg_proxy', '/proxy/hg')
325 self.config.add_route('hg_proxy', '/proxy/hg')
317 self.config.add_route('git_proxy', '/proxy/git')
326 self.config.add_route('git_proxy', '/proxy/git')
318
327
319 # rpc methods
328 # rpc methods
320 self.config.add_route('vcs', '/{backend}')
329 self.config.add_route('vcs', '/{backend}')
321
330
322 # streaming rpc remote methods
331 # streaming rpc remote methods
323 self.config.add_route('vcs_stream', '/{backend}/stream')
332 self.config.add_route('vcs_stream', '/{backend}/stream')
324
333
325 # vcs operations clone/push as streaming
334 # vcs operations clone/push as streaming
326 self.config.add_route('stream_git', '/stream/git/*repo_name')
335 self.config.add_route('stream_git', '/stream/git/*repo_name')
327 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
336 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
328
337
329 self.config.add_view(self.status_view, route_name='status', renderer='json')
338 self.config.add_view(self.status_view, route_name='status', renderer='json')
330 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
339 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
331
340
332 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
341 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
333 self.config.add_view(self.git_proxy(), route_name='git_proxy')
342 self.config.add_view(self.git_proxy(), route_name='git_proxy')
334 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
343 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
335 vcs_view=self._remotes)
344 vcs_view=self._remotes)
336 self.config.add_view(self.vcs_stream_view, route_name='vcs_stream',
345 self.config.add_view(self.vcs_stream_view, route_name='vcs_stream',
337 vcs_view=self._remotes)
346 vcs_view=self._remotes)
338
347
339 self.config.add_view(self.hg_stream(), route_name='stream_hg')
348 self.config.add_view(self.hg_stream(), route_name='stream_hg')
340 self.config.add_view(self.git_stream(), route_name='stream_git')
349 self.config.add_view(self.git_stream(), route_name='stream_git')
341
350
342 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
351 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
343
352
344 self.config.add_notfound_view(not_found, renderer='json')
353 self.config.add_notfound_view(not_found, renderer='json')
345
354
346 self.config.add_view(self.handle_vcs_exception, context=Exception)
355 self.config.add_view(self.handle_vcs_exception, context=Exception)
347
356
348 self.config.add_tween(
357 self.config.add_tween(
349 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
358 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
350 )
359 )
351 self.config.add_request_method(
360 self.config.add_request_method(
352 'vcsserver.lib.request_counter.get_request_counter',
361 'vcsserver.lib.request_counter.get_request_counter',
353 'request_count')
362 'request_count')
354
363
355 def wsgi_app(self):
364 def wsgi_app(self):
356 return self.config.make_wsgi_app()
365 return self.config.make_wsgi_app()
357
366
358 def _vcs_view_params(self, request):
367 def _vcs_view_params(self, request):
359 remote = self._remotes[request.matchdict['backend']]
368 remote = self._remotes[request.matchdict['backend']]
360 payload = msgpack.unpackb(request.body, use_list=True)
369 payload = msgpack.unpackb(request.body, use_list=True)
361 method = payload.get('method')
370 method = payload.get('method')
362 params = payload['params']
371 params = payload['params']
363 wire = params.get('wire')
372 wire = params.get('wire')
364 args = params.get('args')
373 args = params.get('args')
365 kwargs = params.get('kwargs')
374 kwargs = params.get('kwargs')
366 context_uid = None
375 context_uid = None
367
376
368 if wire:
377 if wire:
369 try:
378 try:
370 wire['context'] = context_uid = uuid.UUID(wire['context'])
379 wire['context'] = context_uid = uuid.UUID(wire['context'])
371 except KeyError:
380 except KeyError:
372 pass
381 pass
373 args.insert(0, wire)
382 args.insert(0, wire)
374 repo_state_uid = wire.get('repo_state_uid') if wire else None
383 repo_state_uid = wire.get('repo_state_uid') if wire else None
375
384
376 # NOTE(marcink): trading complexity for slight performance
385 # NOTE(marcink): trading complexity for slight performance
377 if log.isEnabledFor(logging.DEBUG):
386 if log.isEnabledFor(logging.DEBUG):
378 no_args_methods = [
387 no_args_methods = [
379 'archive_repo'
388 'archive_repo'
380 ]
389 ]
381 if method in no_args_methods:
390 if method in no_args_methods:
382 call_args = ''
391 call_args = ''
383 else:
392 else:
384 call_args = args[1:]
393 call_args = args[1:]
385
394
386 log.debug('method requested:%s with args:%s kwargs:%s context_uid: %s, repo_state_uid:%s',
395 log.debug('method requested:%s with args:%s kwargs:%s context_uid: %s, repo_state_uid:%s',
387 method, call_args, kwargs, context_uid, repo_state_uid)
396 method, call_args, kwargs, context_uid, repo_state_uid)
388
397
389 return payload, remote, method, args, kwargs
398 return payload, remote, method, args, kwargs
390
399
391 def vcs_view(self, request):
400 def vcs_view(self, request):
392
401
393 payload, remote, method, args, kwargs = self._vcs_view_params(request)
402 payload, remote, method, args, kwargs = self._vcs_view_params(request)
394 payload_id = payload.get('id')
403 payload_id = payload.get('id')
395
404
396 try:
405 try:
397 resp = getattr(remote, method)(*args, **kwargs)
406 resp = getattr(remote, method)(*args, **kwargs)
398 except Exception as e:
407 except Exception as e:
399 exc_info = list(sys.exc_info())
408 exc_info = list(sys.exc_info())
400 exc_type, exc_value, exc_traceback = exc_info
409 exc_type, exc_value, exc_traceback = exc_info
401
410
402 org_exc = getattr(e, '_org_exc', None)
411 org_exc = getattr(e, '_org_exc', None)
403 org_exc_name = None
412 org_exc_name = None
404 org_exc_tb = ''
413 org_exc_tb = ''
405 if org_exc:
414 if org_exc:
406 org_exc_name = org_exc.__class__.__name__
415 org_exc_name = org_exc.__class__.__name__
407 org_exc_tb = getattr(e, '_org_exc_tb', '')
416 org_exc_tb = getattr(e, '_org_exc_tb', '')
408 # replace our "faked" exception with our org
417 # replace our "faked" exception with our org
409 exc_info[0] = org_exc.__class__
418 exc_info[0] = org_exc.__class__
410 exc_info[1] = org_exc
419 exc_info[1] = org_exc
411
420
412 should_store_exc = True
421 should_store_exc = True
413 if org_exc:
422 if org_exc:
414 def get_exc_fqn(_exc_obj):
423 def get_exc_fqn(_exc_obj):
415 module_name = getattr(org_exc.__class__, '__module__', 'UNKNOWN')
424 module_name = getattr(org_exc.__class__, '__module__', 'UNKNOWN')
416 return module_name + '.' + org_exc_name
425 return module_name + '.' + org_exc_name
417
426
418 exc_fqn = get_exc_fqn(org_exc)
427 exc_fqn = get_exc_fqn(org_exc)
419
428
420 if exc_fqn in ['mercurial.error.RepoLookupError',
429 if exc_fqn in ['mercurial.error.RepoLookupError',
421 'vcsserver.exceptions.RefNotFoundException']:
430 'vcsserver.exceptions.RefNotFoundException']:
422 should_store_exc = False
431 should_store_exc = False
423
432
424 if should_store_exc:
433 if should_store_exc:
425 store_exception(id(exc_info), exc_info)
434 store_exception(id(exc_info), exc_info)
426
435
427 tb_info = ''.join(
436 tb_info = ''.join(
428 traceback.format_exception(exc_type, exc_value, exc_traceback))
437 traceback.format_exception(exc_type, exc_value, exc_traceback))
429
438
430 type_ = e.__class__.__name__
439 type_ = e.__class__.__name__
431 if type_ not in self.ALLOWED_EXCEPTIONS:
440 if type_ not in self.ALLOWED_EXCEPTIONS:
432 type_ = None
441 type_ = None
433
442
434 resp = {
443 resp = {
435 'id': payload_id,
444 'id': payload_id,
436 'error': {
445 'error': {
437 'message': e.message,
446 'message': e.message,
438 'traceback': tb_info,
447 'traceback': tb_info,
439 'org_exc': org_exc_name,
448 'org_exc': org_exc_name,
440 'org_exc_tb': org_exc_tb,
449 'org_exc_tb': org_exc_tb,
441 'type': type_
450 'type': type_
442 }
451 }
443 }
452 }
444 try:
453 try:
445 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
454 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
446 except AttributeError:
455 except AttributeError:
447 pass
456 pass
448 else:
457 else:
449 resp = {
458 resp = {
450 'id': payload_id,
459 'id': payload_id,
451 'result': resp
460 'result': resp
452 }
461 }
453
462
454 return resp
463 return resp
455
464
456 def vcs_stream_view(self, request):
465 def vcs_stream_view(self, request):
457 payload, remote, method, args, kwargs = self._vcs_view_params(request)
466 payload, remote, method, args, kwargs = self._vcs_view_params(request)
458 # this method has a stream: marker we remove it here
467 # this method has a stream: marker we remove it here
459 method = method.split('stream:')[-1]
468 method = method.split('stream:')[-1]
460 chunk_size = safe_int(payload.get('chunk_size')) or 4096
469 chunk_size = safe_int(payload.get('chunk_size')) or 4096
461
470
462 try:
471 try:
463 resp = getattr(remote, method)(*args, **kwargs)
472 resp = getattr(remote, method)(*args, **kwargs)
464 except Exception as e:
473 except Exception as e:
465 raise
474 raise
466
475
467 def get_chunked_data(method_resp):
476 def get_chunked_data(method_resp):
468 stream = StringIO(method_resp)
477 stream = StringIO(method_resp)
469 while 1:
478 while 1:
470 chunk = stream.read(chunk_size)
479 chunk = stream.read(chunk_size)
471 if not chunk:
480 if not chunk:
472 break
481 break
473 yield chunk
482 yield chunk
474
483
475 response = Response(app_iter=get_chunked_data(resp))
484 response = Response(app_iter=get_chunked_data(resp))
476 response.content_type = 'application/octet-stream'
485 response.content_type = 'application/octet-stream'
477
486
478 return response
487 return response
479
488
480 def status_view(self, request):
489 def status_view(self, request):
481 import vcsserver
490 import vcsserver
482 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
491 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
483 'pid': os.getpid()}
492 'pid': os.getpid()}
484
493
485 def service_view(self, request):
494 def service_view(self, request):
486 import vcsserver
495 import vcsserver
487
496
488 payload = msgpack.unpackb(request.body, use_list=True)
497 payload = msgpack.unpackb(request.body, use_list=True)
489 server_config, app_config = {}, {}
498 server_config, app_config = {}, {}
490
499
491 try:
500 try:
492 path = self.global_config['__file__']
501 path = self.global_config['__file__']
493 config = configparser.RawConfigParser()
502 config = configparser.RawConfigParser()
494
503
495 config.read(path)
504 config.read(path)
496
505
497 if config.has_section('server:main'):
506 if config.has_section('server:main'):
498 server_config = dict(config.items('server:main'))
507 server_config = dict(config.items('server:main'))
499 if config.has_section('app:main'):
508 if config.has_section('app:main'):
500 app_config = dict(config.items('app:main'))
509 app_config = dict(config.items('app:main'))
501
510
502 except Exception:
511 except Exception:
503 log.exception('Failed to read .ini file for display')
512 log.exception('Failed to read .ini file for display')
504
513
505 environ = list(os.environ.items())
514 environ = list(os.environ.items())
506
515
507 resp = {
516 resp = {
508 'id': payload.get('id'),
517 'id': payload.get('id'),
509 'result': dict(
518 'result': dict(
510 version=vcsserver.__version__,
519 version=vcsserver.__version__,
511 config=server_config,
520 config=server_config,
512 app_config=app_config,
521 app_config=app_config,
513 environ=environ,
522 environ=environ,
514 payload=payload,
523 payload=payload,
515 )
524 )
516 }
525 }
517 return resp
526 return resp
518
527
519 def _msgpack_renderer_factory(self, info):
528 def _msgpack_renderer_factory(self, info):
520 def _render(value, system):
529 def _render(value, system):
521 request = system.get('request')
530 request = system.get('request')
522 if request is not None:
531 if request is not None:
523 response = request.response
532 response = request.response
524 ct = response.content_type
533 ct = response.content_type
525 if ct == response.default_content_type:
534 if ct == response.default_content_type:
526 response.content_type = 'application/x-msgpack'
535 response.content_type = 'application/x-msgpack'
527 return msgpack.packb(value)
536 return msgpack.packb(value)
528 return _render
537 return _render
529
538
530 def set_env_from_config(self, environ, config):
539 def set_env_from_config(self, environ, config):
531 dict_conf = {}
540 dict_conf = {}
532 try:
541 try:
533 for elem in config:
542 for elem in config:
534 if elem[0] == 'rhodecode':
543 if elem[0] == 'rhodecode':
535 dict_conf = json.loads(elem[2])
544 dict_conf = json.loads(elem[2])
536 break
545 break
537 except Exception:
546 except Exception:
538 log.exception('Failed to fetch SCM CONFIG')
547 log.exception('Failed to fetch SCM CONFIG')
539 return
548 return
540
549
541 username = dict_conf.get('username')
550 username = dict_conf.get('username')
542 if username:
551 if username:
543 environ['REMOTE_USER'] = username
552 environ['REMOTE_USER'] = username
544 # mercurial specific, some extension api rely on this
553 # mercurial specific, some extension api rely on this
545 environ['HGUSER'] = username
554 environ['HGUSER'] = username
546
555
547 ip = dict_conf.get('ip')
556 ip = dict_conf.get('ip')
548 if ip:
557 if ip:
549 environ['REMOTE_HOST'] = ip
558 environ['REMOTE_HOST'] = ip
550
559
551 if _is_request_chunked(environ):
560 if _is_request_chunked(environ):
552 # set the compatibility flag for webob
561 # set the compatibility flag for webob
553 environ['wsgi.input_terminated'] = True
562 environ['wsgi.input_terminated'] = True
554
563
555 def hg_proxy(self):
564 def hg_proxy(self):
556 @wsgiapp
565 @wsgiapp
557 def _hg_proxy(environ, start_response):
566 def _hg_proxy(environ, start_response):
558 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
567 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
559 return app(environ, start_response)
568 return app(environ, start_response)
560 return _hg_proxy
569 return _hg_proxy
561
570
562 def git_proxy(self):
571 def git_proxy(self):
563 @wsgiapp
572 @wsgiapp
564 def _git_proxy(environ, start_response):
573 def _git_proxy(environ, start_response):
565 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
574 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
566 return app(environ, start_response)
575 return app(environ, start_response)
567 return _git_proxy
576 return _git_proxy
568
577
569 def hg_stream(self):
578 def hg_stream(self):
570 if self._use_echo_app:
579 if self._use_echo_app:
571 @wsgiapp
580 @wsgiapp
572 def _hg_stream(environ, start_response):
581 def _hg_stream(environ, start_response):
573 app = EchoApp('fake_path', 'fake_name', None)
582 app = EchoApp('fake_path', 'fake_name', None)
574 return app(environ, start_response)
583 return app(environ, start_response)
575 return _hg_stream
584 return _hg_stream
576 else:
585 else:
577 @wsgiapp
586 @wsgiapp
578 def _hg_stream(environ, start_response):
587 def _hg_stream(environ, start_response):
579 log.debug('http-app: handling hg stream')
588 log.debug('http-app: handling hg stream')
580 repo_path = environ['HTTP_X_RC_REPO_PATH']
589 repo_path = environ['HTTP_X_RC_REPO_PATH']
581 repo_name = environ['HTTP_X_RC_REPO_NAME']
590 repo_name = environ['HTTP_X_RC_REPO_NAME']
582 packed_config = base64.b64decode(
591 packed_config = base64.b64decode(
583 environ['HTTP_X_RC_REPO_CONFIG'])
592 environ['HTTP_X_RC_REPO_CONFIG'])
584 config = msgpack.unpackb(packed_config)
593 config = msgpack.unpackb(packed_config)
585 app = scm_app.create_hg_wsgi_app(
594 app = scm_app.create_hg_wsgi_app(
586 repo_path, repo_name, config)
595 repo_path, repo_name, config)
587
596
588 # Consistent path information for hgweb
597 # Consistent path information for hgweb
589 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
598 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
590 environ['REPO_NAME'] = repo_name
599 environ['REPO_NAME'] = repo_name
591 self.set_env_from_config(environ, config)
600 self.set_env_from_config(environ, config)
592
601
593 log.debug('http-app: starting app handler '
602 log.debug('http-app: starting app handler '
594 'with %s and process request', app)
603 'with %s and process request', app)
595 return app(environ, ResponseFilter(start_response))
604 return app(environ, ResponseFilter(start_response))
596 return _hg_stream
605 return _hg_stream
597
606
598 def git_stream(self):
607 def git_stream(self):
599 if self._use_echo_app:
608 if self._use_echo_app:
600 @wsgiapp
609 @wsgiapp
601 def _git_stream(environ, start_response):
610 def _git_stream(environ, start_response):
602 app = EchoApp('fake_path', 'fake_name', None)
611 app = EchoApp('fake_path', 'fake_name', None)
603 return app(environ, start_response)
612 return app(environ, start_response)
604 return _git_stream
613 return _git_stream
605 else:
614 else:
606 @wsgiapp
615 @wsgiapp
607 def _git_stream(environ, start_response):
616 def _git_stream(environ, start_response):
608 log.debug('http-app: handling git stream')
617 log.debug('http-app: handling git stream')
609 repo_path = environ['HTTP_X_RC_REPO_PATH']
618 repo_path = environ['HTTP_X_RC_REPO_PATH']
610 repo_name = environ['HTTP_X_RC_REPO_NAME']
619 repo_name = environ['HTTP_X_RC_REPO_NAME']
611 packed_config = base64.b64decode(
620 packed_config = base64.b64decode(
612 environ['HTTP_X_RC_REPO_CONFIG'])
621 environ['HTTP_X_RC_REPO_CONFIG'])
613 config = msgpack.unpackb(packed_config)
622 config = msgpack.unpackb(packed_config)
614
623
615 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
624 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
616 self.set_env_from_config(environ, config)
625 self.set_env_from_config(environ, config)
617
626
618 content_type = environ.get('CONTENT_TYPE', '')
627 content_type = environ.get('CONTENT_TYPE', '')
619
628
620 path = environ['PATH_INFO']
629 path = environ['PATH_INFO']
621 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
630 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
622 log.debug(
631 log.debug(
623 'LFS: Detecting if request `%s` is LFS server path based '
632 'LFS: Detecting if request `%s` is LFS server path based '
624 'on content type:`%s`, is_lfs:%s',
633 'on content type:`%s`, is_lfs:%s',
625 path, content_type, is_lfs_request)
634 path, content_type, is_lfs_request)
626
635
627 if not is_lfs_request:
636 if not is_lfs_request:
628 # fallback detection by path
637 # fallback detection by path
629 if GIT_LFS_PROTO_PAT.match(path):
638 if GIT_LFS_PROTO_PAT.match(path):
630 is_lfs_request = True
639 is_lfs_request = True
631 log.debug(
640 log.debug(
632 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
641 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
633 path, is_lfs_request)
642 path, is_lfs_request)
634
643
635 if is_lfs_request:
644 if is_lfs_request:
636 app = scm_app.create_git_lfs_wsgi_app(
645 app = scm_app.create_git_lfs_wsgi_app(
637 repo_path, repo_name, config)
646 repo_path, repo_name, config)
638 else:
647 else:
639 app = scm_app.create_git_wsgi_app(
648 app = scm_app.create_git_wsgi_app(
640 repo_path, repo_name, config)
649 repo_path, repo_name, config)
641
650
642 log.debug('http-app: starting app handler '
651 log.debug('http-app: starting app handler '
643 'with %s and process request', app)
652 'with %s and process request', app)
644
653
645 return app(environ, start_response)
654 return app(environ, start_response)
646
655
647 return _git_stream
656 return _git_stream
648
657
649 def handle_vcs_exception(self, exception, request):
658 def handle_vcs_exception(self, exception, request):
650 _vcs_kind = getattr(exception, '_vcs_kind', '')
659 _vcs_kind = getattr(exception, '_vcs_kind', '')
651 if _vcs_kind == 'repo_locked':
660 if _vcs_kind == 'repo_locked':
652 # Get custom repo-locked status code if present.
661 # Get custom repo-locked status code if present.
653 status_code = request.headers.get('X-RC-Locked-Status-Code')
662 status_code = request.headers.get('X-RC-Locked-Status-Code')
654 return HTTPRepoLocked(
663 return HTTPRepoLocked(
655 title=exception.message, status_code=status_code)
664 title=exception.message, status_code=status_code)
656
665
657 elif _vcs_kind == 'repo_branch_protected':
666 elif _vcs_kind == 'repo_branch_protected':
658 # Get custom repo-branch-protected status code if present.
667 # Get custom repo-branch-protected status code if present.
659 return HTTPRepoBranchProtected(title=exception.message)
668 return HTTPRepoBranchProtected(title=exception.message)
660
669
661 exc_info = request.exc_info
670 exc_info = request.exc_info
662 store_exception(id(exc_info), exc_info)
671 store_exception(id(exc_info), exc_info)
663
672
664 traceback_info = 'unavailable'
673 traceback_info = 'unavailable'
665 if request.exc_info:
674 if request.exc_info:
666 exc_type, exc_value, exc_tb = request.exc_info
675 exc_type, exc_value, exc_tb = request.exc_info
667 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
676 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
668
677
669 log.error(
678 log.error(
670 'error occurred handling this request for path: %s, \n tb: %s',
679 'error occurred handling this request for path: %s, \n tb: %s',
671 request.path, traceback_info)
680 request.path, traceback_info)
672 raise exception
681 raise exception
673
682
674
683
675 class ResponseFilter(object):
684 class ResponseFilter(object):
676
685
677 def __init__(self, start_response):
686 def __init__(self, start_response):
678 self._start_response = start_response
687 self._start_response = start_response
679
688
680 def __call__(self, status, response_headers, exc_info=None):
689 def __call__(self, status, response_headers, exc_info=None):
681 headers = tuple(
690 headers = tuple(
682 (h, v) for h, v in response_headers
691 (h, v) for h, v in response_headers
683 if not wsgiref.util.is_hop_by_hop(h))
692 if not wsgiref.util.is_hop_by_hop(h))
684 return self._start_response(status, headers, exc_info)
693 return self._start_response(status, headers, exc_info)
685
694
686
695
687 def main(global_config, **settings):
696 def main(global_config, **settings):
688 if MercurialFactory:
697 if MercurialFactory:
689 hgpatches.patch_largefiles_capabilities()
698 hgpatches.patch_largefiles_capabilities()
690 hgpatches.patch_subrepo_type_mapping()
699 hgpatches.patch_subrepo_type_mapping()
691
700
692 app = HTTPApplication(settings=settings, global_config=global_config)
701 app = HTTPApplication(settings=settings, global_config=global_config)
693 return app.wsgi_app()
702 return app.wsgi_app()
General Comments 0
You need to be logged in to leave comments. Login now