##// END OF EJS Templates
core: move git/svn/hg into submodule
super-admin -
r1043:89733dce python3
parent child Browse files
Show More
@@ -0,0 +1,17 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2020 RhodeCode GmbH
3 #
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
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
@@ -1,740 +1,739 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 time
24 import time
25 import wsgiref.util
25 import wsgiref.util
26 import traceback
26 import traceback
27 import tempfile
27 import tempfile
28 import psutil
28 import psutil
29
29
30 from itertools import chain
30 from itertools import chain
31 from io import StringIO
31 from io import StringIO
32
32
33 import simplejson as json
33 import simplejson as json
34 import msgpack
34 import msgpack
35 import configparser
35 import configparser
36
36
37 from pyramid.config import Configurator
37 from pyramid.config import Configurator
38 from pyramid.settings import asbool, aslist
39 from pyramid.wsgi import wsgiapp
38 from pyramid.wsgi import wsgiapp
40 from pyramid.response import Response
39 from pyramid.response import Response
41
40
42 from vcsserver.config.settings_maker import SettingsMaker
41 from vcsserver.config.settings_maker import SettingsMaker
43 from vcsserver.utils import safe_int
42 from vcsserver.utils import safe_int
44 from vcsserver.lib.statsd_client import StatsdClient
43 from vcsserver.lib.statsd_client import StatsdClient
45
44
46 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
47
46
48 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
47 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
49 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
48 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
50
49
51 try:
50 try:
52 locale.setlocale(locale.LC_ALL, '')
51 locale.setlocale(locale.LC_ALL, '')
53 except locale.Error as e:
52 except locale.Error as e:
54 log.error(
53 log.error(
55 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
54 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
56 os.environ['LC_ALL'] = 'C'
55 os.environ['LC_ALL'] = 'C'
57
56
58
57
59 import vcsserver
58 import vcsserver
60 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
59 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
61 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
60 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
62 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
61 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
63 from vcsserver.echo_stub.echo_app import EchoApp
62 from vcsserver.echo_stub.echo_app import EchoApp
64 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
63 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
65 from vcsserver.lib.exc_tracking import store_exception
64 from vcsserver.lib.exc_tracking import store_exception
66 from vcsserver.server import VcsServer
65 from vcsserver.server import VcsServer
67
66
68 strict_vcs = True
67 strict_vcs = True
69
68
70 git_import_err = None
69 git_import_err = None
71 try:
70 try:
72 from vcsserver.git import GitFactory, GitRemote
71 from vcsserver.remote.git import GitFactory, GitRemote
73 except ImportError as e:
72 except ImportError as e:
74 GitFactory = None
73 GitFactory = None
75 GitRemote = None
74 GitRemote = None
76 git_import_err = e
75 git_import_err = e
77 if strict_vcs:
76 if strict_vcs:
78 raise
77 raise
79
78
80
79
81 hg_import_err = None
80 hg_import_err = None
82 try:
81 try:
83 from vcsserver.hg import MercurialFactory, HgRemote
82 from vcsserver.remote.hg import MercurialFactory, HgRemote
84 except ImportError as e:
83 except ImportError as e:
85 MercurialFactory = None
84 MercurialFactory = None
86 HgRemote = None
85 HgRemote = None
87 hg_import_err = e
86 hg_import_err = e
88 if strict_vcs:
87 if strict_vcs:
89 raise
88 raise
90
89
91
90
92 svn_import_err = None
91 svn_import_err = None
93 try:
92 try:
94 from vcsserver.svn import SubversionFactory, SvnRemote
93 from vcsserver.remote.svn import SubversionFactory, SvnRemote
95 except ImportError as e:
94 except ImportError as e:
96 SubversionFactory = None
95 SubversionFactory = None
97 SvnRemote = None
96 SvnRemote = None
98 svn_import_err = e
97 svn_import_err = e
99 if strict_vcs:
98 if strict_vcs:
100 raise
99 raise
101
100
102
101
103 def _is_request_chunked(environ):
102 def _is_request_chunked(environ):
104 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
103 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
105 return stream
104 return stream
106
105
107
106
108 def log_max_fd():
107 def log_max_fd():
109 try:
108 try:
110 maxfd = psutil.Process().rlimit(psutil.RLIMIT_NOFILE)[1]
109 maxfd = psutil.Process().rlimit(psutil.RLIMIT_NOFILE)[1]
111 log.info('Max file descriptors value: %s', maxfd)
110 log.info('Max file descriptors value: %s', maxfd)
112 except Exception:
111 except Exception:
113 pass
112 pass
114
113
115
114
116 class VCS(object):
115 class VCS(object):
117 def __init__(self, locale_conf=None, cache_config=None):
116 def __init__(self, locale_conf=None, cache_config=None):
118 self.locale = locale_conf
117 self.locale = locale_conf
119 self.cache_config = cache_config
118 self.cache_config = cache_config
120 self._configure_locale()
119 self._configure_locale()
121
120
122 log_max_fd()
121 log_max_fd()
123
122
124 if GitFactory and GitRemote:
123 if GitFactory and GitRemote:
125 git_factory = GitFactory()
124 git_factory = GitFactory()
126 self._git_remote = GitRemote(git_factory)
125 self._git_remote = GitRemote(git_factory)
127 else:
126 else:
128 log.error("Git client import failed: %s", git_import_err)
127 log.error("Git client import failed: %s", git_import_err)
129
128
130 if MercurialFactory and HgRemote:
129 if MercurialFactory and HgRemote:
131 hg_factory = MercurialFactory()
130 hg_factory = MercurialFactory()
132 self._hg_remote = HgRemote(hg_factory)
131 self._hg_remote = HgRemote(hg_factory)
133 else:
132 else:
134 log.error("Mercurial client import failed: %s", hg_import_err)
133 log.error("Mercurial client import failed: %s", hg_import_err)
135
134
136 if SubversionFactory and SvnRemote:
135 if SubversionFactory and SvnRemote:
137 svn_factory = SubversionFactory()
136 svn_factory = SubversionFactory()
138
137
139 # hg factory is used for svn url validation
138 # hg factory is used for svn url validation
140 hg_factory = MercurialFactory()
139 hg_factory = MercurialFactory()
141 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
140 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
142 else:
141 else:
143 log.error("Subversion client import failed: %s", svn_import_err)
142 log.error("Subversion client import failed: %s", svn_import_err)
144
143
145 self._vcsserver = VcsServer()
144 self._vcsserver = VcsServer()
146
145
147 def _configure_locale(self):
146 def _configure_locale(self):
148 if self.locale:
147 if self.locale:
149 log.info('Settings locale: `LC_ALL` to %s', self.locale)
148 log.info('Settings locale: `LC_ALL` to %s', self.locale)
150 else:
149 else:
151 log.info('Configuring locale subsystem based on environment variables')
150 log.info('Configuring locale subsystem based on environment variables')
152 try:
151 try:
153 # If self.locale is the empty string, then the locale
152 # If self.locale is the empty string, then the locale
154 # module will use the environment variables. See the
153 # module will use the environment variables. See the
155 # documentation of the package `locale`.
154 # documentation of the package `locale`.
156 locale.setlocale(locale.LC_ALL, self.locale)
155 locale.setlocale(locale.LC_ALL, self.locale)
157
156
158 language_code, encoding = locale.getlocale()
157 language_code, encoding = locale.getlocale()
159 log.info(
158 log.info(
160 'Locale set to language code "%s" with encoding "%s".',
159 'Locale set to language code "%s" with encoding "%s".',
161 language_code, encoding)
160 language_code, encoding)
162 except locale.Error:
161 except locale.Error:
163 log.exception('Cannot set locale, not configuring the locale system')
162 log.exception('Cannot set locale, not configuring the locale system')
164
163
165
164
166 class WsgiProxy(object):
165 class WsgiProxy(object):
167 def __init__(self, wsgi):
166 def __init__(self, wsgi):
168 self.wsgi = wsgi
167 self.wsgi = wsgi
169
168
170 def __call__(self, environ, start_response):
169 def __call__(self, environ, start_response):
171 input_data = environ['wsgi.input'].read()
170 input_data = environ['wsgi.input'].read()
172 input_data = msgpack.unpackb(input_data)
171 input_data = msgpack.unpackb(input_data)
173
172
174 error = None
173 error = None
175 try:
174 try:
176 data, status, headers = self.wsgi.handle(
175 data, status, headers = self.wsgi.handle(
177 input_data['environment'], input_data['input_data'],
176 input_data['environment'], input_data['input_data'],
178 *input_data['args'], **input_data['kwargs'])
177 *input_data['args'], **input_data['kwargs'])
179 except Exception as e:
178 except Exception as e:
180 data, status, headers = [], None, None
179 data, status, headers = [], None, None
181 error = {
180 error = {
182 'message': str(e),
181 'message': str(e),
183 '_vcs_kind': getattr(e, '_vcs_kind', None)
182 '_vcs_kind': getattr(e, '_vcs_kind', None)
184 }
183 }
185
184
186 start_response(200, {})
185 start_response(200, {})
187 return self._iterator(error, status, headers, data)
186 return self._iterator(error, status, headers, data)
188
187
189 def _iterator(self, error, status, headers, data):
188 def _iterator(self, error, status, headers, data):
190 initial_data = [
189 initial_data = [
191 error,
190 error,
192 status,
191 status,
193 headers,
192 headers,
194 ]
193 ]
195
194
196 for d in chain(initial_data, data):
195 for d in chain(initial_data, data):
197 yield msgpack.packb(d)
196 yield msgpack.packb(d)
198
197
199
198
200 def not_found(request):
199 def not_found(request):
201 return {'status': '404 NOT FOUND'}
200 return {'status': '404 NOT FOUND'}
202
201
203
202
204 class VCSViewPredicate(object):
203 class VCSViewPredicate(object):
205 def __init__(self, val, config):
204 def __init__(self, val, config):
206 self.remotes = val
205 self.remotes = val
207
206
208 def text(self):
207 def text(self):
209 return 'vcs view method = %s' % (list(self.remotes.keys()),)
208 return 'vcs view method = %s' % (list(self.remotes.keys()),)
210
209
211 phash = text
210 phash = text
212
211
213 def __call__(self, context, request):
212 def __call__(self, context, request):
214 """
213 """
215 View predicate that returns true if given backend is supported by
214 View predicate that returns true if given backend is supported by
216 defined remotes.
215 defined remotes.
217 """
216 """
218 backend = request.matchdict.get('backend')
217 backend = request.matchdict.get('backend')
219 return backend in self.remotes
218 return backend in self.remotes
220
219
221
220
222 class HTTPApplication(object):
221 class HTTPApplication(object):
223 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
222 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
224
223
225 remote_wsgi = remote_wsgi
224 remote_wsgi = remote_wsgi
226 _use_echo_app = False
225 _use_echo_app = False
227
226
228 def __init__(self, settings=None, global_config=None):
227 def __init__(self, settings=None, global_config=None):
229
228
230 self.config = Configurator(settings=settings)
229 self.config = Configurator(settings=settings)
231 # Init our statsd at very start
230 # Init our statsd at very start
232 self.config.registry.statsd = StatsdClient.statsd
231 self.config.registry.statsd = StatsdClient.statsd
233
232
234 self.global_config = global_config
233 self.global_config = global_config
235 self.config.include('vcsserver.lib.rc_cache')
234 self.config.include('vcsserver.lib.rc_cache')
236
235
237 settings_locale = settings.get('locale', '') or 'en_US.UTF-8'
236 settings_locale = settings.get('locale', '') or 'en_US.UTF-8'
238 vcs = VCS(locale_conf=settings_locale, cache_config=settings)
237 vcs = VCS(locale_conf=settings_locale, cache_config=settings)
239 self._remotes = {
238 self._remotes = {
240 'hg': vcs._hg_remote,
239 'hg': vcs._hg_remote,
241 'git': vcs._git_remote,
240 'git': vcs._git_remote,
242 'svn': vcs._svn_remote,
241 'svn': vcs._svn_remote,
243 'server': vcs._vcsserver,
242 'server': vcs._vcsserver,
244 }
243 }
245 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
244 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
246 self._use_echo_app = True
245 self._use_echo_app = True
247 log.warning("Using EchoApp for VCS operations.")
246 log.warning("Using EchoApp for VCS operations.")
248 self.remote_wsgi = remote_wsgi_stub
247 self.remote_wsgi = remote_wsgi_stub
249
248
250 self._configure_settings(global_config, settings)
249 self._configure_settings(global_config, settings)
251
250
252 self._configure()
251 self._configure()
253
252
254 def _configure_settings(self, global_config, app_settings):
253 def _configure_settings(self, global_config, app_settings):
255 """
254 """
256 Configure the settings module.
255 Configure the settings module.
257 """
256 """
258 settings_merged = global_config.copy()
257 settings_merged = global_config.copy()
259 settings_merged.update(app_settings)
258 settings_merged.update(app_settings)
260
259
261 git_path = app_settings.get('git_path', None)
260 git_path = app_settings.get('git_path', None)
262 if git_path:
261 if git_path:
263 settings.GIT_EXECUTABLE = git_path
262 settings.GIT_EXECUTABLE = git_path
264 binary_dir = app_settings.get('core.binary_dir', None)
263 binary_dir = app_settings.get('core.binary_dir', None)
265 if binary_dir:
264 if binary_dir:
266 settings.BINARY_DIR = binary_dir
265 settings.BINARY_DIR = binary_dir
267
266
268 # Store the settings to make them available to other modules.
267 # Store the settings to make them available to other modules.
269 vcsserver.PYRAMID_SETTINGS = settings_merged
268 vcsserver.PYRAMID_SETTINGS = settings_merged
270 vcsserver.CONFIG = settings_merged
269 vcsserver.CONFIG = settings_merged
271
270
272 def _configure(self):
271 def _configure(self):
273 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
272 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
274
273
275 self.config.add_route('service', '/_service')
274 self.config.add_route('service', '/_service')
276 self.config.add_route('status', '/status')
275 self.config.add_route('status', '/status')
277 self.config.add_route('hg_proxy', '/proxy/hg')
276 self.config.add_route('hg_proxy', '/proxy/hg')
278 self.config.add_route('git_proxy', '/proxy/git')
277 self.config.add_route('git_proxy', '/proxy/git')
279
278
280 # rpc methods
279 # rpc methods
281 self.config.add_route('vcs', '/{backend}')
280 self.config.add_route('vcs', '/{backend}')
282
281
283 # streaming rpc remote methods
282 # streaming rpc remote methods
284 self.config.add_route('vcs_stream', '/{backend}/stream')
283 self.config.add_route('vcs_stream', '/{backend}/stream')
285
284
286 # vcs operations clone/push as streaming
285 # vcs operations clone/push as streaming
287 self.config.add_route('stream_git', '/stream/git/*repo_name')
286 self.config.add_route('stream_git', '/stream/git/*repo_name')
288 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
287 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
289
288
290 self.config.add_view(self.status_view, route_name='status', renderer='json')
289 self.config.add_view(self.status_view, route_name='status', renderer='json')
291 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
290 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
292
291
293 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
292 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
294 self.config.add_view(self.git_proxy(), route_name='git_proxy')
293 self.config.add_view(self.git_proxy(), route_name='git_proxy')
295 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
294 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
296 vcs_view=self._remotes)
295 vcs_view=self._remotes)
297 self.config.add_view(self.vcs_stream_view, route_name='vcs_stream',
296 self.config.add_view(self.vcs_stream_view, route_name='vcs_stream',
298 vcs_view=self._remotes)
297 vcs_view=self._remotes)
299
298
300 self.config.add_view(self.hg_stream(), route_name='stream_hg')
299 self.config.add_view(self.hg_stream(), route_name='stream_hg')
301 self.config.add_view(self.git_stream(), route_name='stream_git')
300 self.config.add_view(self.git_stream(), route_name='stream_git')
302
301
303 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
302 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
304
303
305 self.config.add_notfound_view(not_found, renderer='json')
304 self.config.add_notfound_view(not_found, renderer='json')
306
305
307 self.config.add_view(self.handle_vcs_exception, context=Exception)
306 self.config.add_view(self.handle_vcs_exception, context=Exception)
308
307
309 self.config.add_tween(
308 self.config.add_tween(
310 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
309 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
311 )
310 )
312 self.config.add_request_method(
311 self.config.add_request_method(
313 'vcsserver.lib.request_counter.get_request_counter',
312 'vcsserver.lib.request_counter.get_request_counter',
314 'request_count')
313 'request_count')
315
314
316 def wsgi_app(self):
315 def wsgi_app(self):
317 return self.config.make_wsgi_app()
316 return self.config.make_wsgi_app()
318
317
319 def _vcs_view_params(self, request):
318 def _vcs_view_params(self, request):
320 remote = self._remotes[request.matchdict['backend']]
319 remote = self._remotes[request.matchdict['backend']]
321 payload = msgpack.unpackb(request.body, use_list=True)
320 payload = msgpack.unpackb(request.body, use_list=True)
322 method = payload.get('method')
321 method = payload.get('method')
323 params = payload['params']
322 params = payload['params']
324 wire = params.get('wire')
323 wire = params.get('wire')
325 args = params.get('args')
324 args = params.get('args')
326 kwargs = params.get('kwargs')
325 kwargs = params.get('kwargs')
327 context_uid = None
326 context_uid = None
328
327
329 if wire:
328 if wire:
330 try:
329 try:
331 wire['context'] = context_uid = uuid.UUID(wire['context'])
330 wire['context'] = context_uid = uuid.UUID(wire['context'])
332 except KeyError:
331 except KeyError:
333 pass
332 pass
334 args.insert(0, wire)
333 args.insert(0, wire)
335 repo_state_uid = wire.get('repo_state_uid') if wire else None
334 repo_state_uid = wire.get('repo_state_uid') if wire else None
336
335
337 # NOTE(marcink): trading complexity for slight performance
336 # NOTE(marcink): trading complexity for slight performance
338 if log.isEnabledFor(logging.DEBUG):
337 if log.isEnabledFor(logging.DEBUG):
339 no_args_methods = [
338 no_args_methods = [
340
339
341 ]
340 ]
342 if method in no_args_methods:
341 if method in no_args_methods:
343 call_args = ''
342 call_args = ''
344 else:
343 else:
345 call_args = args[1:]
344 call_args = args[1:]
346
345
347 log.debug('Method requested:`%s` with args:%s kwargs:%s context_uid: %s, repo_state_uid:%s',
346 log.debug('Method requested:`%s` with args:%s kwargs:%s context_uid: %s, repo_state_uid:%s',
348 method, call_args, kwargs, context_uid, repo_state_uid)
347 method, call_args, kwargs, context_uid, repo_state_uid)
349
348
350 statsd = request.registry.statsd
349 statsd = request.registry.statsd
351 if statsd:
350 if statsd:
352 statsd.incr(
351 statsd.incr(
353 'vcsserver_method_total', tags=[
352 'vcsserver_method_total', tags=[
354 "method:{}".format(method),
353 "method:{}".format(method),
355 ])
354 ])
356 return payload, remote, method, args, kwargs
355 return payload, remote, method, args, kwargs
357
356
358 def vcs_view(self, request):
357 def vcs_view(self, request):
359
358
360 payload, remote, method, args, kwargs = self._vcs_view_params(request)
359 payload, remote, method, args, kwargs = self._vcs_view_params(request)
361 payload_id = payload.get('id')
360 payload_id = payload.get('id')
362
361
363 try:
362 try:
364 resp = getattr(remote, method)(*args, **kwargs)
363 resp = getattr(remote, method)(*args, **kwargs)
365 except Exception as e:
364 except Exception as e:
366 exc_info = list(sys.exc_info())
365 exc_info = list(sys.exc_info())
367 exc_type, exc_value, exc_traceback = exc_info
366 exc_type, exc_value, exc_traceback = exc_info
368
367
369 org_exc = getattr(e, '_org_exc', None)
368 org_exc = getattr(e, '_org_exc', None)
370 org_exc_name = None
369 org_exc_name = None
371 org_exc_tb = ''
370 org_exc_tb = ''
372 if org_exc:
371 if org_exc:
373 org_exc_name = org_exc.__class__.__name__
372 org_exc_name = org_exc.__class__.__name__
374 org_exc_tb = getattr(e, '_org_exc_tb', '')
373 org_exc_tb = getattr(e, '_org_exc_tb', '')
375 # replace our "faked" exception with our org
374 # replace our "faked" exception with our org
376 exc_info[0] = org_exc.__class__
375 exc_info[0] = org_exc.__class__
377 exc_info[1] = org_exc
376 exc_info[1] = org_exc
378
377
379 should_store_exc = True
378 should_store_exc = True
380 if org_exc:
379 if org_exc:
381 def get_exc_fqn(_exc_obj):
380 def get_exc_fqn(_exc_obj):
382 module_name = getattr(org_exc.__class__, '__module__', 'UNKNOWN')
381 module_name = getattr(org_exc.__class__, '__module__', 'UNKNOWN')
383 return module_name + '.' + org_exc_name
382 return module_name + '.' + org_exc_name
384
383
385 exc_fqn = get_exc_fqn(org_exc)
384 exc_fqn = get_exc_fqn(org_exc)
386
385
387 if exc_fqn in ['mercurial.error.RepoLookupError',
386 if exc_fqn in ['mercurial.error.RepoLookupError',
388 'vcsserver.exceptions.RefNotFoundException']:
387 'vcsserver.exceptions.RefNotFoundException']:
389 should_store_exc = False
388 should_store_exc = False
390
389
391 if should_store_exc:
390 if should_store_exc:
392 store_exception(id(exc_info), exc_info, request_path=request.path)
391 store_exception(id(exc_info), exc_info, request_path=request.path)
393
392
394 tb_info = ''.join(
393 tb_info = ''.join(
395 traceback.format_exception(exc_type, exc_value, exc_traceback))
394 traceback.format_exception(exc_type, exc_value, exc_traceback))
396
395
397 type_ = e.__class__.__name__
396 type_ = e.__class__.__name__
398 if type_ not in self.ALLOWED_EXCEPTIONS:
397 if type_ not in self.ALLOWED_EXCEPTIONS:
399 type_ = None
398 type_ = None
400
399
401 resp = {
400 resp = {
402 'id': payload_id,
401 'id': payload_id,
403 'error': {
402 'error': {
404 'message': e.message,
403 'message': e.message,
405 'traceback': tb_info,
404 'traceback': tb_info,
406 'org_exc': org_exc_name,
405 'org_exc': org_exc_name,
407 'org_exc_tb': org_exc_tb,
406 'org_exc_tb': org_exc_tb,
408 'type': type_
407 'type': type_
409 }
408 }
410 }
409 }
411
410
412 try:
411 try:
413 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
412 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
414 except AttributeError:
413 except AttributeError:
415 pass
414 pass
416 else:
415 else:
417 resp = {
416 resp = {
418 'id': payload_id,
417 'id': payload_id,
419 'result': resp
418 'result': resp
420 }
419 }
421
420
422 return resp
421 return resp
423
422
424 def vcs_stream_view(self, request):
423 def vcs_stream_view(self, request):
425 payload, remote, method, args, kwargs = self._vcs_view_params(request)
424 payload, remote, method, args, kwargs = self._vcs_view_params(request)
426 # this method has a stream: marker we remove it here
425 # this method has a stream: marker we remove it here
427 method = method.split('stream:')[-1]
426 method = method.split('stream:')[-1]
428 chunk_size = safe_int(payload.get('chunk_size')) or 4096
427 chunk_size = safe_int(payload.get('chunk_size')) or 4096
429
428
430 try:
429 try:
431 resp = getattr(remote, method)(*args, **kwargs)
430 resp = getattr(remote, method)(*args, **kwargs)
432 except Exception as e:
431 except Exception as e:
433 raise
432 raise
434
433
435 def get_chunked_data(method_resp):
434 def get_chunked_data(method_resp):
436 stream = StringIO(method_resp)
435 stream = StringIO(method_resp)
437 while 1:
436 while 1:
438 chunk = stream.read(chunk_size)
437 chunk = stream.read(chunk_size)
439 if not chunk:
438 if not chunk:
440 break
439 break
441 yield chunk
440 yield chunk
442
441
443 response = Response(app_iter=get_chunked_data(resp))
442 response = Response(app_iter=get_chunked_data(resp))
444 response.content_type = 'application/octet-stream'
443 response.content_type = 'application/octet-stream'
445
444
446 return response
445 return response
447
446
448 def status_view(self, request):
447 def status_view(self, request):
449 import vcsserver
448 import vcsserver
450 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
449 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
451 'pid': os.getpid()}
450 'pid': os.getpid()}
452
451
453 def service_view(self, request):
452 def service_view(self, request):
454 import vcsserver
453 import vcsserver
455
454
456 payload = msgpack.unpackb(request.body, use_list=True)
455 payload = msgpack.unpackb(request.body, use_list=True)
457 server_config, app_config = {}, {}
456 server_config, app_config = {}, {}
458
457
459 try:
458 try:
460 path = self.global_config['__file__']
459 path = self.global_config['__file__']
461 config = configparser.RawConfigParser()
460 config = configparser.RawConfigParser()
462
461
463 config.read(path)
462 config.read(path)
464
463
465 if config.has_section('server:main'):
464 if config.has_section('server:main'):
466 server_config = dict(config.items('server:main'))
465 server_config = dict(config.items('server:main'))
467 if config.has_section('app:main'):
466 if config.has_section('app:main'):
468 app_config = dict(config.items('app:main'))
467 app_config = dict(config.items('app:main'))
469
468
470 except Exception:
469 except Exception:
471 log.exception('Failed to read .ini file for display')
470 log.exception('Failed to read .ini file for display')
472
471
473 environ = list(os.environ.items())
472 environ = list(os.environ.items())
474
473
475 resp = {
474 resp = {
476 'id': payload.get('id'),
475 'id': payload.get('id'),
477 'result': dict(
476 'result': dict(
478 version=vcsserver.__version__,
477 version=vcsserver.__version__,
479 config=server_config,
478 config=server_config,
480 app_config=app_config,
479 app_config=app_config,
481 environ=environ,
480 environ=environ,
482 payload=payload,
481 payload=payload,
483 )
482 )
484 }
483 }
485 return resp
484 return resp
486
485
487 def _msgpack_renderer_factory(self, info):
486 def _msgpack_renderer_factory(self, info):
488 def _render(value, system):
487 def _render(value, system):
489 request = system.get('request')
488 request = system.get('request')
490 if request is not None:
489 if request is not None:
491 response = request.response
490 response = request.response
492 ct = response.content_type
491 ct = response.content_type
493 if ct == response.default_content_type:
492 if ct == response.default_content_type:
494 response.content_type = 'application/x-msgpack'
493 response.content_type = 'application/x-msgpack'
495 return msgpack.packb(value)
494 return msgpack.packb(value)
496 return _render
495 return _render
497
496
498 def set_env_from_config(self, environ, config):
497 def set_env_from_config(self, environ, config):
499 dict_conf = {}
498 dict_conf = {}
500 try:
499 try:
501 for elem in config:
500 for elem in config:
502 if elem[0] == 'rhodecode':
501 if elem[0] == 'rhodecode':
503 dict_conf = json.loads(elem[2])
502 dict_conf = json.loads(elem[2])
504 break
503 break
505 except Exception:
504 except Exception:
506 log.exception('Failed to fetch SCM CONFIG')
505 log.exception('Failed to fetch SCM CONFIG')
507 return
506 return
508
507
509 username = dict_conf.get('username')
508 username = dict_conf.get('username')
510 if username:
509 if username:
511 environ['REMOTE_USER'] = username
510 environ['REMOTE_USER'] = username
512 # mercurial specific, some extension api rely on this
511 # mercurial specific, some extension api rely on this
513 environ['HGUSER'] = username
512 environ['HGUSER'] = username
514
513
515 ip = dict_conf.get('ip')
514 ip = dict_conf.get('ip')
516 if ip:
515 if ip:
517 environ['REMOTE_HOST'] = ip
516 environ['REMOTE_HOST'] = ip
518
517
519 if _is_request_chunked(environ):
518 if _is_request_chunked(environ):
520 # set the compatibility flag for webob
519 # set the compatibility flag for webob
521 environ['wsgi.input_terminated'] = True
520 environ['wsgi.input_terminated'] = True
522
521
523 def hg_proxy(self):
522 def hg_proxy(self):
524 @wsgiapp
523 @wsgiapp
525 def _hg_proxy(environ, start_response):
524 def _hg_proxy(environ, start_response):
526 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
525 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
527 return app(environ, start_response)
526 return app(environ, start_response)
528 return _hg_proxy
527 return _hg_proxy
529
528
530 def git_proxy(self):
529 def git_proxy(self):
531 @wsgiapp
530 @wsgiapp
532 def _git_proxy(environ, start_response):
531 def _git_proxy(environ, start_response):
533 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
532 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
534 return app(environ, start_response)
533 return app(environ, start_response)
535 return _git_proxy
534 return _git_proxy
536
535
537 def hg_stream(self):
536 def hg_stream(self):
538 if self._use_echo_app:
537 if self._use_echo_app:
539 @wsgiapp
538 @wsgiapp
540 def _hg_stream(environ, start_response):
539 def _hg_stream(environ, start_response):
541 app = EchoApp('fake_path', 'fake_name', None)
540 app = EchoApp('fake_path', 'fake_name', None)
542 return app(environ, start_response)
541 return app(environ, start_response)
543 return _hg_stream
542 return _hg_stream
544 else:
543 else:
545 @wsgiapp
544 @wsgiapp
546 def _hg_stream(environ, start_response):
545 def _hg_stream(environ, start_response):
547 log.debug('http-app: handling hg stream')
546 log.debug('http-app: handling hg stream')
548 repo_path = environ['HTTP_X_RC_REPO_PATH']
547 repo_path = environ['HTTP_X_RC_REPO_PATH']
549 repo_name = environ['HTTP_X_RC_REPO_NAME']
548 repo_name = environ['HTTP_X_RC_REPO_NAME']
550 packed_config = base64.b64decode(
549 packed_config = base64.b64decode(
551 environ['HTTP_X_RC_REPO_CONFIG'])
550 environ['HTTP_X_RC_REPO_CONFIG'])
552 config = msgpack.unpackb(packed_config)
551 config = msgpack.unpackb(packed_config)
553 app = scm_app.create_hg_wsgi_app(
552 app = scm_app.create_hg_wsgi_app(
554 repo_path, repo_name, config)
553 repo_path, repo_name, config)
555
554
556 # Consistent path information for hgweb
555 # Consistent path information for hgweb
557 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
556 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
558 environ['REPO_NAME'] = repo_name
557 environ['REPO_NAME'] = repo_name
559 self.set_env_from_config(environ, config)
558 self.set_env_from_config(environ, config)
560
559
561 log.debug('http-app: starting app handler '
560 log.debug('http-app: starting app handler '
562 'with %s and process request', app)
561 'with %s and process request', app)
563 return app(environ, ResponseFilter(start_response))
562 return app(environ, ResponseFilter(start_response))
564 return _hg_stream
563 return _hg_stream
565
564
566 def git_stream(self):
565 def git_stream(self):
567 if self._use_echo_app:
566 if self._use_echo_app:
568 @wsgiapp
567 @wsgiapp
569 def _git_stream(environ, start_response):
568 def _git_stream(environ, start_response):
570 app = EchoApp('fake_path', 'fake_name', None)
569 app = EchoApp('fake_path', 'fake_name', None)
571 return app(environ, start_response)
570 return app(environ, start_response)
572 return _git_stream
571 return _git_stream
573 else:
572 else:
574 @wsgiapp
573 @wsgiapp
575 def _git_stream(environ, start_response):
574 def _git_stream(environ, start_response):
576 log.debug('http-app: handling git stream')
575 log.debug('http-app: handling git stream')
577 repo_path = environ['HTTP_X_RC_REPO_PATH']
576 repo_path = environ['HTTP_X_RC_REPO_PATH']
578 repo_name = environ['HTTP_X_RC_REPO_NAME']
577 repo_name = environ['HTTP_X_RC_REPO_NAME']
579 packed_config = base64.b64decode(
578 packed_config = base64.b64decode(
580 environ['HTTP_X_RC_REPO_CONFIG'])
579 environ['HTTP_X_RC_REPO_CONFIG'])
581 config = msgpack.unpackb(packed_config)
580 config = msgpack.unpackb(packed_config)
582
581
583 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
582 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
584 self.set_env_from_config(environ, config)
583 self.set_env_from_config(environ, config)
585
584
586 content_type = environ.get('CONTENT_TYPE', '')
585 content_type = environ.get('CONTENT_TYPE', '')
587
586
588 path = environ['PATH_INFO']
587 path = environ['PATH_INFO']
589 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
588 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
590 log.debug(
589 log.debug(
591 'LFS: Detecting if request `%s` is LFS server path based '
590 'LFS: Detecting if request `%s` is LFS server path based '
592 'on content type:`%s`, is_lfs:%s',
591 'on content type:`%s`, is_lfs:%s',
593 path, content_type, is_lfs_request)
592 path, content_type, is_lfs_request)
594
593
595 if not is_lfs_request:
594 if not is_lfs_request:
596 # fallback detection by path
595 # fallback detection by path
597 if GIT_LFS_PROTO_PAT.match(path):
596 if GIT_LFS_PROTO_PAT.match(path):
598 is_lfs_request = True
597 is_lfs_request = True
599 log.debug(
598 log.debug(
600 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
599 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
601 path, is_lfs_request)
600 path, is_lfs_request)
602
601
603 if is_lfs_request:
602 if is_lfs_request:
604 app = scm_app.create_git_lfs_wsgi_app(
603 app = scm_app.create_git_lfs_wsgi_app(
605 repo_path, repo_name, config)
604 repo_path, repo_name, config)
606 else:
605 else:
607 app = scm_app.create_git_wsgi_app(
606 app = scm_app.create_git_wsgi_app(
608 repo_path, repo_name, config)
607 repo_path, repo_name, config)
609
608
610 log.debug('http-app: starting app handler '
609 log.debug('http-app: starting app handler '
611 'with %s and process request', app)
610 'with %s and process request', app)
612
611
613 return app(environ, start_response)
612 return app(environ, start_response)
614
613
615 return _git_stream
614 return _git_stream
616
615
617 def handle_vcs_exception(self, exception, request):
616 def handle_vcs_exception(self, exception, request):
618 _vcs_kind = getattr(exception, '_vcs_kind', '')
617 _vcs_kind = getattr(exception, '_vcs_kind', '')
619 if _vcs_kind == 'repo_locked':
618 if _vcs_kind == 'repo_locked':
620 # Get custom repo-locked status code if present.
619 # Get custom repo-locked status code if present.
621 status_code = request.headers.get('X-RC-Locked-Status-Code')
620 status_code = request.headers.get('X-RC-Locked-Status-Code')
622 return HTTPRepoLocked(
621 return HTTPRepoLocked(
623 title=exception.message, status_code=status_code)
622 title=exception.message, status_code=status_code)
624
623
625 elif _vcs_kind == 'repo_branch_protected':
624 elif _vcs_kind == 'repo_branch_protected':
626 # Get custom repo-branch-protected status code if present.
625 # Get custom repo-branch-protected status code if present.
627 return HTTPRepoBranchProtected(title=exception.message)
626 return HTTPRepoBranchProtected(title=exception.message)
628
627
629 exc_info = request.exc_info
628 exc_info = request.exc_info
630 store_exception(id(exc_info), exc_info)
629 store_exception(id(exc_info), exc_info)
631
630
632 traceback_info = 'unavailable'
631 traceback_info = 'unavailable'
633 if request.exc_info:
632 if request.exc_info:
634 exc_type, exc_value, exc_tb = request.exc_info
633 exc_type, exc_value, exc_tb = request.exc_info
635 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
634 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
636
635
637 log.error(
636 log.error(
638 'error occurred handling this request for path: %s, \n tb: %s',
637 'error occurred handling this request for path: %s, \n tb: %s',
639 request.path, traceback_info)
638 request.path, traceback_info)
640
639
641 statsd = request.registry.statsd
640 statsd = request.registry.statsd
642 if statsd:
641 if statsd:
643 exc_type = "{}.{}".format(exception.__class__.__module__, exception.__class__.__name__)
642 exc_type = "{}.{}".format(exception.__class__.__module__, exception.__class__.__name__)
644 statsd.incr('vcsserver_exception_total',
643 statsd.incr('vcsserver_exception_total',
645 tags=["type:{}".format(exc_type)])
644 tags=["type:{}".format(exc_type)])
646 raise exception
645 raise exception
647
646
648
647
649 class ResponseFilter(object):
648 class ResponseFilter(object):
650
649
651 def __init__(self, start_response):
650 def __init__(self, start_response):
652 self._start_response = start_response
651 self._start_response = start_response
653
652
654 def __call__(self, status, response_headers, exc_info=None):
653 def __call__(self, status, response_headers, exc_info=None):
655 headers = tuple(
654 headers = tuple(
656 (h, v) for h, v in response_headers
655 (h, v) for h, v in response_headers
657 if not wsgiref.util.is_hop_by_hop(h))
656 if not wsgiref.util.is_hop_by_hop(h))
658 return self._start_response(status, headers, exc_info)
657 return self._start_response(status, headers, exc_info)
659
658
660
659
661 def sanitize_settings_and_apply_defaults(global_config, settings):
660 def sanitize_settings_and_apply_defaults(global_config, settings):
662 global_settings_maker = SettingsMaker(global_config)
661 global_settings_maker = SettingsMaker(global_config)
663 settings_maker = SettingsMaker(settings)
662 settings_maker = SettingsMaker(settings)
664
663
665 settings_maker.make_setting('logging.autoconfigure', False, parser='bool')
664 settings_maker.make_setting('logging.autoconfigure', False, parser='bool')
666
665
667 logging_conf = os.path.join(os.path.dirname(global_config.get('__file__')), 'logging.ini')
666 logging_conf = os.path.join(os.path.dirname(global_config.get('__file__')), 'logging.ini')
668 settings_maker.enable_logging(logging_conf)
667 settings_maker.enable_logging(logging_conf)
669
668
670 # Default includes, possible to change as a user
669 # Default includes, possible to change as a user
671 pyramid_includes = settings_maker.make_setting('pyramid.includes', [], parser='list:newline')
670 pyramid_includes = settings_maker.make_setting('pyramid.includes', [], parser='list:newline')
672 log.debug("Using the following pyramid.includes: %s", pyramid_includes)
671 log.debug("Using the following pyramid.includes: %s", pyramid_includes)
673
672
674 settings_maker.make_setting('__file__', global_config.get('__file__'))
673 settings_maker.make_setting('__file__', global_config.get('__file__'))
675
674
676 settings_maker.make_setting('pyramid.default_locale_name', 'en')
675 settings_maker.make_setting('pyramid.default_locale_name', 'en')
677 settings_maker.make_setting('locale', 'en_US.UTF-8')
676 settings_maker.make_setting('locale', 'en_US.UTF-8')
678
677
679 settings_maker.make_setting('core.binary_dir', '')
678 settings_maker.make_setting('core.binary_dir', '')
680
679
681 temp_store = tempfile.gettempdir()
680 temp_store = tempfile.gettempdir()
682 default_cache_dir = os.path.join(temp_store, 'rc_cache')
681 default_cache_dir = os.path.join(temp_store, 'rc_cache')
683 # save default, cache dir, and use it for all backends later.
682 # save default, cache dir, and use it for all backends later.
684 default_cache_dir = settings_maker.make_setting(
683 default_cache_dir = settings_maker.make_setting(
685 'cache_dir',
684 'cache_dir',
686 default=default_cache_dir, default_when_empty=True,
685 default=default_cache_dir, default_when_empty=True,
687 parser='dir:ensured')
686 parser='dir:ensured')
688
687
689 # exception store cache
688 # exception store cache
690 settings_maker.make_setting(
689 settings_maker.make_setting(
691 'exception_tracker.store_path',
690 'exception_tracker.store_path',
692 default=os.path.join(default_cache_dir, 'exc_store'), default_when_empty=True,
691 default=os.path.join(default_cache_dir, 'exc_store'), default_when_empty=True,
693 parser='dir:ensured'
692 parser='dir:ensured'
694 )
693 )
695
694
696 # repo_object cache defaults
695 # repo_object cache defaults
697 settings_maker.make_setting(
696 settings_maker.make_setting(
698 'rc_cache.repo_object.backend',
697 'rc_cache.repo_object.backend',
699 default='dogpile.cache.rc.file_namespace',
698 default='dogpile.cache.rc.file_namespace',
700 parser='string')
699 parser='string')
701 settings_maker.make_setting(
700 settings_maker.make_setting(
702 'rc_cache.repo_object.expiration_time',
701 'rc_cache.repo_object.expiration_time',
703 default=30 * 24 * 60 * 60, # 30days
702 default=30 * 24 * 60 * 60, # 30days
704 parser='int')
703 parser='int')
705 settings_maker.make_setting(
704 settings_maker.make_setting(
706 'rc_cache.repo_object.arguments.filename',
705 'rc_cache.repo_object.arguments.filename',
707 default=os.path.join(default_cache_dir, 'vcsserver_cache_repo_object.db'),
706 default=os.path.join(default_cache_dir, 'vcsserver_cache_repo_object.db'),
708 parser='string')
707 parser='string')
709
708
710 # statsd
709 # statsd
711 settings_maker.make_setting('statsd.enabled', False, parser='bool')
710 settings_maker.make_setting('statsd.enabled', False, parser='bool')
712 settings_maker.make_setting('statsd.statsd_host', 'statsd-exporter', parser='string')
711 settings_maker.make_setting('statsd.statsd_host', 'statsd-exporter', parser='string')
713 settings_maker.make_setting('statsd.statsd_port', 9125, parser='int')
712 settings_maker.make_setting('statsd.statsd_port', 9125, parser='int')
714 settings_maker.make_setting('statsd.statsd_prefix', '')
713 settings_maker.make_setting('statsd.statsd_prefix', '')
715 settings_maker.make_setting('statsd.statsd_ipv6', False, parser='bool')
714 settings_maker.make_setting('statsd.statsd_ipv6', False, parser='bool')
716
715
717 settings_maker.env_expand()
716 settings_maker.env_expand()
718
717
719
718
720 def main(global_config, **settings):
719 def main(global_config, **settings):
721 start_time = time.time()
720 start_time = time.time()
722 log.info('Pyramid app config starting')
721 log.info('Pyramid app config starting')
723
722
724 if MercurialFactory:
723 if MercurialFactory:
725 hgpatches.patch_largefiles_capabilities()
724 hgpatches.patch_largefiles_capabilities()
726 hgpatches.patch_subrepo_type_mapping()
725 hgpatches.patch_subrepo_type_mapping()
727
726
728 # Fill in and sanitize the defaults & do ENV expansion
727 # Fill in and sanitize the defaults & do ENV expansion
729 sanitize_settings_and_apply_defaults(global_config, settings)
728 sanitize_settings_and_apply_defaults(global_config, settings)
730
729
731 # init and bootstrap StatsdClient
730 # init and bootstrap StatsdClient
732 StatsdClient.setup(settings)
731 StatsdClient.setup(settings)
733
732
734 pyramid_app = HTTPApplication(settings=settings, global_config=global_config).wsgi_app()
733 pyramid_app = HTTPApplication(settings=settings, global_config=global_config).wsgi_app()
735 total_time = time.time() - start_time
734 total_time = time.time() - start_time
736 log.info('Pyramid app `%s` created and configured in %.2fs',
735 log.info('Pyramid app `%s` created and configured in %.2fs',
737 getattr(pyramid_app, 'func_name', 'pyramid_app'), total_time)
736 getattr(pyramid_app, 'func_name', 'pyramid_app'), total_time)
738 return pyramid_app
737 return pyramid_app
739
738
740
739
1 NO CONTENT: file renamed from vcsserver/git.py to vcsserver/remote/git.py
NO CONTENT: file renamed from vcsserver/git.py to vcsserver/remote/git.py
1 NO CONTENT: file renamed from vcsserver/hg.py to vcsserver/remote/hg.py
NO CONTENT: file renamed from vcsserver/hg.py to vcsserver/remote/hg.py
1 NO CONTENT: file renamed from vcsserver/svn.py to vcsserver/remote/svn.py
NO CONTENT: file renamed from vcsserver/svn.py to vcsserver/remote/svn.py
@@ -1,160 +1,159 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 inspect
18 import inspect
19
19
20 import pytest
20 import pytest
21 import dulwich.errors
21 import dulwich.errors
22 from mock import Mock, patch
22 from mock import Mock, patch
23
23
24 from vcsserver import git
24 from vcsserver.remote import git
25
26
25
27 SAMPLE_REFS = {
26 SAMPLE_REFS = {
28 'HEAD': 'fd627b9e0dd80b47be81af07c4a98518244ed2f7',
27 'HEAD': 'fd627b9e0dd80b47be81af07c4a98518244ed2f7',
29 'refs/tags/v0.1.9': '341d28f0eec5ddf0b6b77871e13c2bbd6bec685c',
28 'refs/tags/v0.1.9': '341d28f0eec5ddf0b6b77871e13c2bbd6bec685c',
30 'refs/tags/v0.1.8': '74ebce002c088b8a5ecf40073db09375515ecd68',
29 'refs/tags/v0.1.8': '74ebce002c088b8a5ecf40073db09375515ecd68',
31 'refs/tags/v0.1.1': 'e6ea6d16e2f26250124a1f4b4fe37a912f9d86a0',
30 'refs/tags/v0.1.1': 'e6ea6d16e2f26250124a1f4b4fe37a912f9d86a0',
32 'refs/tags/v0.1.3': '5a3a8fb005554692b16e21dee62bf02667d8dc3e',
31 'refs/tags/v0.1.3': '5a3a8fb005554692b16e21dee62bf02667d8dc3e',
33 }
32 }
34
33
35
34
36 @pytest.fixture
35 @pytest.fixture
37 def git_remote():
36 def git_remote():
38 """
37 """
39 A GitRemote instance with a mock factory.
38 A GitRemote instance with a mock factory.
40 """
39 """
41 factory = Mock()
40 factory = Mock()
42 remote = git.GitRemote(factory)
41 remote = git.GitRemote(factory)
43 return remote
42 return remote
44
43
45
44
46 def test_discover_git_version(git_remote):
45 def test_discover_git_version(git_remote):
47 version = git_remote.discover_git_version()
46 version = git_remote.discover_git_version()
48 assert version
47 assert version
49
48
50
49
51 class TestGitFetch(object):
50 class TestGitFetch(object):
52 def setup(self):
51 def setup(self):
53 self.mock_repo = Mock()
52 self.mock_repo = Mock()
54 factory = Mock()
53 factory = Mock()
55 factory.repo = Mock(return_value=self.mock_repo)
54 factory.repo = Mock(return_value=self.mock_repo)
56 self.remote_git = git.GitRemote(factory)
55 self.remote_git = git.GitRemote(factory)
57
56
58 def test_fetches_all_when_no_commit_ids_specified(self):
57 def test_fetches_all_when_no_commit_ids_specified(self):
59 def side_effect(determine_wants, *args, **kwargs):
58 def side_effect(determine_wants, *args, **kwargs):
60 determine_wants(SAMPLE_REFS)
59 determine_wants(SAMPLE_REFS)
61
60
62 with patch('dulwich.client.LocalGitClient.fetch') as mock_fetch:
61 with patch('dulwich.client.LocalGitClient.fetch') as mock_fetch:
63 mock_fetch.side_effect = side_effect
62 mock_fetch.side_effect = side_effect
64 self.remote_git.pull(wire={}, url='/tmp/', apply_refs=False)
63 self.remote_git.pull(wire={}, url='/tmp/', apply_refs=False)
65 determine_wants = self.mock_repo.object_store.determine_wants_all
64 determine_wants = self.mock_repo.object_store.determine_wants_all
66 determine_wants.assert_called_once_with(SAMPLE_REFS)
65 determine_wants.assert_called_once_with(SAMPLE_REFS)
67
66
68 def test_fetches_specified_commits(self):
67 def test_fetches_specified_commits(self):
69 selected_refs = {
68 selected_refs = {
70 'refs/tags/v0.1.8': '74ebce002c088b8a5ecf40073db09375515ecd68',
69 'refs/tags/v0.1.8': '74ebce002c088b8a5ecf40073db09375515ecd68',
71 'refs/tags/v0.1.3': '5a3a8fb005554692b16e21dee62bf02667d8dc3e',
70 'refs/tags/v0.1.3': '5a3a8fb005554692b16e21dee62bf02667d8dc3e',
72 }
71 }
73
72
74 def side_effect(determine_wants, *args, **kwargs):
73 def side_effect(determine_wants, *args, **kwargs):
75 result = determine_wants(SAMPLE_REFS)
74 result = determine_wants(SAMPLE_REFS)
76 assert sorted(result) == sorted(selected_refs.values())
75 assert sorted(result) == sorted(selected_refs.values())
77 return result
76 return result
78
77
79 with patch('dulwich.client.LocalGitClient.fetch') as mock_fetch:
78 with patch('dulwich.client.LocalGitClient.fetch') as mock_fetch:
80 mock_fetch.side_effect = side_effect
79 mock_fetch.side_effect = side_effect
81 self.remote_git.pull(
80 self.remote_git.pull(
82 wire={}, url='/tmp/', apply_refs=False,
81 wire={}, url='/tmp/', apply_refs=False,
83 refs=selected_refs.keys())
82 refs=selected_refs.keys())
84 determine_wants = self.mock_repo.object_store.determine_wants_all
83 determine_wants = self.mock_repo.object_store.determine_wants_all
85 assert determine_wants.call_count == 0
84 assert determine_wants.call_count == 0
86
85
87 def test_get_remote_refs(self):
86 def test_get_remote_refs(self):
88 factory = Mock()
87 factory = Mock()
89 remote_git = git.GitRemote(factory)
88 remote_git = git.GitRemote(factory)
90 url = 'http://example.com/test/test.git'
89 url = 'http://example.com/test/test.git'
91 sample_refs = {
90 sample_refs = {
92 'refs/tags/v0.1.8': '74ebce002c088b8a5ecf40073db09375515ecd68',
91 'refs/tags/v0.1.8': '74ebce002c088b8a5ecf40073db09375515ecd68',
93 'refs/tags/v0.1.3': '5a3a8fb005554692b16e21dee62bf02667d8dc3e',
92 'refs/tags/v0.1.3': '5a3a8fb005554692b16e21dee62bf02667d8dc3e',
94 }
93 }
95
94
96 with patch('vcsserver.git.Repo', create=False) as mock_repo:
95 with patch('vcsserver.git.Repo', create=False) as mock_repo:
97 mock_repo().get_refs.return_value = sample_refs
96 mock_repo().get_refs.return_value = sample_refs
98 remote_refs = remote_git.get_remote_refs(wire={}, url=url)
97 remote_refs = remote_git.get_remote_refs(wire={}, url=url)
99 mock_repo().get_refs.assert_called_once_with()
98 mock_repo().get_refs.assert_called_once_with()
100 assert remote_refs == sample_refs
99 assert remote_refs == sample_refs
101
100
102
101
103 class TestReraiseSafeExceptions(object):
102 class TestReraiseSafeExceptions(object):
104
103
105 def test_method_decorated_with_reraise_safe_exceptions(self):
104 def test_method_decorated_with_reraise_safe_exceptions(self):
106 factory = Mock()
105 factory = Mock()
107 git_remote = git.GitRemote(factory)
106 git_remote = git.GitRemote(factory)
108
107
109 def fake_function():
108 def fake_function():
110 return None
109 return None
111
110
112 decorator = git.reraise_safe_exceptions(fake_function)
111 decorator = git.reraise_safe_exceptions(fake_function)
113
112
114 methods = inspect.getmembers(git_remote, predicate=inspect.ismethod)
113 methods = inspect.getmembers(git_remote, predicate=inspect.ismethod)
115 for method_name, method in methods:
114 for method_name, method in methods:
116 if not method_name.startswith('_') and method_name not in ['vcsserver_invalidate_cache']:
115 if not method_name.startswith('_') and method_name not in ['vcsserver_invalidate_cache']:
117 assert method.im_func.__code__ == decorator.__code__
116 assert method.im_func.__code__ == decorator.__code__
118
117
119 @pytest.mark.parametrize('side_effect, expected_type', [
118 @pytest.mark.parametrize('side_effect, expected_type', [
120 (dulwich.errors.ChecksumMismatch('0000000', 'deadbeef'), 'lookup'),
119 (dulwich.errors.ChecksumMismatch('0000000', 'deadbeef'), 'lookup'),
121 (dulwich.errors.NotCommitError('deadbeef'), 'lookup'),
120 (dulwich.errors.NotCommitError('deadbeef'), 'lookup'),
122 (dulwich.errors.MissingCommitError('deadbeef'), 'lookup'),
121 (dulwich.errors.MissingCommitError('deadbeef'), 'lookup'),
123 (dulwich.errors.ObjectMissing('deadbeef'), 'lookup'),
122 (dulwich.errors.ObjectMissing('deadbeef'), 'lookup'),
124 (dulwich.errors.HangupException(), 'error'),
123 (dulwich.errors.HangupException(), 'error'),
125 (dulwich.errors.UnexpectedCommandError('test-cmd'), 'error'),
124 (dulwich.errors.UnexpectedCommandError('test-cmd'), 'error'),
126 ])
125 ])
127 def test_safe_exceptions_reraised(self, side_effect, expected_type):
126 def test_safe_exceptions_reraised(self, side_effect, expected_type):
128 @git.reraise_safe_exceptions
127 @git.reraise_safe_exceptions
129 def fake_method():
128 def fake_method():
130 raise side_effect
129 raise side_effect
131
130
132 with pytest.raises(Exception) as exc_info:
131 with pytest.raises(Exception) as exc_info:
133 fake_method()
132 fake_method()
134 assert type(exc_info.value) == Exception
133 assert type(exc_info.value) == Exception
135 assert exc_info.value._vcs_kind == expected_type
134 assert exc_info.value._vcs_kind == expected_type
136
135
137
136
138 class TestDulwichRepoWrapper(object):
137 class TestDulwichRepoWrapper(object):
139 def test_calls_close_on_delete(self):
138 def test_calls_close_on_delete(self):
140 isdir_patcher = patch('dulwich.repo.os.path.isdir', return_value=True)
139 isdir_patcher = patch('dulwich.repo.os.path.isdir', return_value=True)
141 with isdir_patcher:
140 with isdir_patcher:
142 repo = git.Repo('/tmp/abcde')
141 repo = git.Repo('/tmp/abcde')
143 with patch.object(git.DulwichRepo, 'close') as close_mock:
142 with patch.object(git.DulwichRepo, 'close') as close_mock:
144 del repo
143 del repo
145 close_mock.assert_called_once_with()
144 close_mock.assert_called_once_with()
146
145
147
146
148 class TestGitFactory(object):
147 class TestGitFactory(object):
149 def test_create_repo_returns_dulwich_wrapper(self):
148 def test_create_repo_returns_dulwich_wrapper(self):
150
149
151 with patch('vcsserver.lib.rc_cache.region_meta.dogpile_cache_regions') as mock:
150 with patch('vcsserver.lib.rc_cache.region_meta.dogpile_cache_regions') as mock:
152 mock.side_effect = {'repo_objects': ''}
151 mock.side_effect = {'repo_objects': ''}
153 factory = git.GitFactory()
152 factory = git.GitFactory()
154 wire = {
153 wire = {
155 'path': '/tmp/abcde'
154 'path': '/tmp/abcde'
156 }
155 }
157 isdir_patcher = patch('dulwich.repo.os.path.isdir', return_value=True)
156 isdir_patcher = patch('dulwich.repo.os.path.isdir', return_value=True)
158 with isdir_patcher:
157 with isdir_patcher:
159 result = factory._create_repo(wire, True)
158 result = factory._create_repo(wire, True)
160 assert isinstance(result, git.Repo)
159 assert isinstance(result, git.Repo)
@@ -1,108 +1,109 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 inspect
18 import inspect
19 import sys
19 import sys
20 import traceback
20 import traceback
21
21
22 import pytest
22 import pytest
23 from mercurial.error import LookupError
23 from mercurial.error import LookupError
24 from mock import Mock, MagicMock, patch
24 from mock import Mock, patch
25
25
26 from vcsserver import exceptions, hg, hgcompat
26 from vcsserver import exceptions, hgcompat
27 from vcsserver.remote import hg
27
28
28
29
29 class TestDiff(object):
30 class TestDiff(object):
30 def test_raising_safe_exception_when_lookup_failed(self):
31 def test_raising_safe_exception_when_lookup_failed(self):
31
32
32 factory = Mock()
33 factory = Mock()
33 hg_remote = hg.HgRemote(factory)
34 hg_remote = hg.HgRemote(factory)
34 with patch('mercurial.patch.diff') as diff_mock:
35 with patch('mercurial.patch.diff') as diff_mock:
35 diff_mock.side_effect = LookupError(
36 diff_mock.side_effect = LookupError(
36 'deadbeef', 'index', 'message')
37 'deadbeef', 'index', 'message')
37 with pytest.raises(Exception) as exc_info:
38 with pytest.raises(Exception) as exc_info:
38 hg_remote.diff(
39 hg_remote.diff(
39 wire={}, commit_id_1='deadbeef', commit_id_2='deadbee1',
40 wire={}, commit_id_1='deadbeef', commit_id_2='deadbee1',
40 file_filter=None, opt_git=True, opt_ignorews=True,
41 file_filter=None, opt_git=True, opt_ignorews=True,
41 context=3)
42 context=3)
42 assert type(exc_info.value) == Exception
43 assert type(exc_info.value) == Exception
43 assert exc_info.value._vcs_kind == 'lookup'
44 assert exc_info.value._vcs_kind == 'lookup'
44
45
45
46
46 class TestReraiseSafeExceptions(object):
47 class TestReraiseSafeExceptions(object):
47 def test_method_decorated_with_reraise_safe_exceptions(self):
48 def test_method_decorated_with_reraise_safe_exceptions(self):
48 factory = Mock()
49 factory = Mock()
49 hg_remote = hg.HgRemote(factory)
50 hg_remote = hg.HgRemote(factory)
50 methods = inspect.getmembers(hg_remote, predicate=inspect.ismethod)
51 methods = inspect.getmembers(hg_remote, predicate=inspect.ismethod)
51 decorator = hg.reraise_safe_exceptions(None)
52 decorator = hg.reraise_safe_exceptions(None)
52 for method_name, method in methods:
53 for method_name, method in methods:
53 if not method_name.startswith('_') and method_name not in ['vcsserver_invalidate_cache']:
54 if not method_name.startswith('_') and method_name not in ['vcsserver_invalidate_cache']:
54 assert method.im_func.__code__ == decorator.__code__
55 assert method.im_func.__code__ == decorator.__code__
55
56
56 @pytest.mark.parametrize('side_effect, expected_type', [
57 @pytest.mark.parametrize('side_effect, expected_type', [
57 (hgcompat.Abort(), 'abort'),
58 (hgcompat.Abort(), 'abort'),
58 (hgcompat.InterventionRequired(), 'abort'),
59 (hgcompat.InterventionRequired(), 'abort'),
59 (hgcompat.RepoLookupError(), 'lookup'),
60 (hgcompat.RepoLookupError(), 'lookup'),
60 (hgcompat.LookupError('deadbeef', 'index', 'message'), 'lookup'),
61 (hgcompat.LookupError('deadbeef', 'index', 'message'), 'lookup'),
61 (hgcompat.RepoError(), 'error'),
62 (hgcompat.RepoError(), 'error'),
62 (hgcompat.RequirementError(), 'requirement'),
63 (hgcompat.RequirementError(), 'requirement'),
63 ])
64 ])
64 def test_safe_exceptions_reraised(self, side_effect, expected_type):
65 def test_safe_exceptions_reraised(self, side_effect, expected_type):
65 @hg.reraise_safe_exceptions
66 @hg.reraise_safe_exceptions
66 def fake_method():
67 def fake_method():
67 raise side_effect
68 raise side_effect
68
69
69 with pytest.raises(Exception) as exc_info:
70 with pytest.raises(Exception) as exc_info:
70 fake_method()
71 fake_method()
71 assert type(exc_info.value) == Exception
72 assert type(exc_info.value) == Exception
72 assert exc_info.value._vcs_kind == expected_type
73 assert exc_info.value._vcs_kind == expected_type
73
74
74 def test_keeps_original_traceback(self):
75 def test_keeps_original_traceback(self):
75 @hg.reraise_safe_exceptions
76 @hg.reraise_safe_exceptions
76 def fake_method():
77 def fake_method():
77 try:
78 try:
78 raise hgcompat.Abort()
79 raise hgcompat.Abort()
79 except:
80 except:
80 self.original_traceback = traceback.format_tb(
81 self.original_traceback = traceback.format_tb(
81 sys.exc_info()[2])
82 sys.exc_info()[2])
82 raise
83 raise
83
84
84 try:
85 try:
85 fake_method()
86 fake_method()
86 except Exception:
87 except Exception:
87 new_traceback = traceback.format_tb(sys.exc_info()[2])
88 new_traceback = traceback.format_tb(sys.exc_info()[2])
88
89
89 new_traceback_tail = new_traceback[-len(self.original_traceback):]
90 new_traceback_tail = new_traceback[-len(self.original_traceback):]
90 assert new_traceback_tail == self.original_traceback
91 assert new_traceback_tail == self.original_traceback
91
92
92 def test_maps_unknow_exceptions_to_unhandled(self):
93 def test_maps_unknow_exceptions_to_unhandled(self):
93 @hg.reraise_safe_exceptions
94 @hg.reraise_safe_exceptions
94 def stub_method():
95 def stub_method():
95 raise ValueError('stub')
96 raise ValueError('stub')
96
97
97 with pytest.raises(Exception) as exc_info:
98 with pytest.raises(Exception) as exc_info:
98 stub_method()
99 stub_method()
99 assert exc_info.value._vcs_kind == 'unhandled'
100 assert exc_info.value._vcs_kind == 'unhandled'
100
101
101 def test_does_not_map_known_exceptions(self):
102 def test_does_not_map_known_exceptions(self):
102 @hg.reraise_safe_exceptions
103 @hg.reraise_safe_exceptions
103 def stub_method():
104 def stub_method():
104 raise exceptions.LookupException()('stub')
105 raise exceptions.LookupException()('stub')
105
106
106 with pytest.raises(Exception) as exc_info:
107 with pytest.raises(Exception) as exc_info:
107 stub_method()
108 stub_method()
108 assert exc_info.value._vcs_kind == 'lookup'
109 assert exc_info.value._vcs_kind == 'lookup'
@@ -1,87 +1,86 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 io
18 import io
19 import mock
19 import mock
20 import pytest
20 import pytest
21 import sys
21 import sys
22
22
23
23
24 class MockPopen(object):
24 class MockPopen(object):
25 def __init__(self, stderr):
25 def __init__(self, stderr):
26 self.stdout = io.BytesIO('')
26 self.stdout = io.BytesIO('')
27 self.stderr = io.BytesIO(stderr)
27 self.stderr = io.BytesIO(stderr)
28 self.returncode = 1
28 self.returncode = 1
29
29
30 def wait(self):
30 def wait(self):
31 pass
31 pass
32
32
33
33
34 INVALID_CERTIFICATE_STDERR = '\n'.join([
34 INVALID_CERTIFICATE_STDERR = '\n'.join([
35 'svnrdump: E230001: Unable to connect to a repository at URL url',
35 'svnrdump: E230001: Unable to connect to a repository at URL url',
36 'svnrdump: E230001: Server SSL certificate verification failed: issuer is not trusted',
36 'svnrdump: E230001: Server SSL certificate verification failed: issuer is not trusted',
37 ])
37 ])
38
38
39
39
40 @pytest.mark.parametrize('stderr,expected_reason', [
40 @pytest.mark.parametrize('stderr,expected_reason', [
41 (INVALID_CERTIFICATE_STDERR, 'INVALID_CERTIFICATE'),
41 (INVALID_CERTIFICATE_STDERR, 'INVALID_CERTIFICATE'),
42 ('svnrdump: E123456', 'UNKNOWN:svnrdump: E123456'),
42 ('svnrdump: E123456', 'UNKNOWN:svnrdump: E123456'),
43 ], ids=['invalid-cert-stderr', 'svnrdump-err-123456'])
43 ], ids=['invalid-cert-stderr', 'svnrdump-err-123456'])
44 @pytest.mark.xfail(sys.platform == "cygwin",
44 @pytest.mark.xfail(sys.platform == "cygwin",
45 reason="SVN not packaged for Cygwin")
45 reason="SVN not packaged for Cygwin")
46 def test_import_remote_repository_certificate_error(stderr, expected_reason):
46 def test_import_remote_repository_certificate_error(stderr, expected_reason):
47 from vcsserver import svn
47 from vcsserver.remote import svn
48 factory = mock.Mock()
48 factory = mock.Mock()
49 factory.repo = mock.Mock(return_value=mock.Mock())
49 factory.repo = mock.Mock(return_value=mock.Mock())
50
50
51 remote = svn.SvnRemote(factory)
51 remote = svn.SvnRemote(factory)
52 remote.is_path_valid_repository = lambda wire, path: True
52 remote.is_path_valid_repository = lambda wire, path: True
53
53
54 with mock.patch('subprocess.Popen',
54 with mock.patch('subprocess.Popen',
55 return_value=MockPopen(stderr)):
55 return_value=MockPopen(stderr)):
56 with pytest.raises(Exception) as excinfo:
56 with pytest.raises(Exception) as excinfo:
57 remote.import_remote_repository({'path': 'path'}, 'url')
57 remote.import_remote_repository({'path': 'path'}, 'url')
58
58
59 expected_error_args = (
59 expected_error_args = (
60 'Failed to dump the remote repository from url. Reason:{}'.format(expected_reason),)
60 'Failed to dump the remote repository from url. Reason:{}'.format(expected_reason),)
61
61
62 assert excinfo.value.args == expected_error_args
62 assert excinfo.value.args == expected_error_args
63
63
64
64
65 def test_svn_libraries_can_be_imported():
65 def test_svn_libraries_can_be_imported():
66 import svn
67 import svn.client
66 import svn.client
68 assert svn.client is not None
67 assert svn.client is not None
69
68
70
69
71 @pytest.mark.parametrize('example_url, parts', [
70 @pytest.mark.parametrize('example_url, parts', [
72 ('http://server.com', (None, None, 'http://server.com')),
71 ('http://server.com', (None, None, 'http://server.com')),
73 ('http://user@server.com', ('user', None, 'http://user@server.com')),
72 ('http://user@server.com', ('user', None, 'http://user@server.com')),
74 ('http://user:pass@server.com', ('user', 'pass', 'http://user:pass@server.com')),
73 ('http://user:pass@server.com', ('user', 'pass', 'http://user:pass@server.com')),
75 ('<script>', (None, None, '<script>')),
74 ('<script>', (None, None, '<script>')),
76 ('http://', (None, None, 'http://')),
75 ('http://', (None, None, 'http://')),
77 ])
76 ])
78 def test_username_password_extraction_from_url(example_url, parts):
77 def test_username_password_extraction_from_url(example_url, parts):
79 from vcsserver import svn
78 from vcsserver.remote import svn
80
79
81 factory = mock.Mock()
80 factory = mock.Mock()
82 factory.repo = mock.Mock(return_value=mock.Mock())
81 factory.repo = mock.Mock(return_value=mock.Mock())
83
82
84 remote = svn.SvnRemote(factory)
83 remote = svn.SvnRemote(factory)
85 remote.is_path_valid_repository = lambda wire, path: True
84 remote.is_path_valid_repository = lambda wire, path: True
86
85
87 assert remote.get_url_and_credentials(example_url) == parts
86 assert remote.get_url_and_credentials(example_url) == parts
General Comments 0
You need to be logged in to leave comments. Login now