##// END OF EJS Templates
exception: store orginal tb and exc inside the new exception passed to rhodecode from vcsserver.
marcink -
r621:f216af0d default
parent child Browse files
Show More
@@ -1,91 +1,94 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-2019 RhodeCode GmbH
2 # Copyright (C) 2014-2019 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 sys
18 import sys
19 import traceback
19 import traceback
20 import logging
20 import logging
21 import urlparse
21 import urlparse
22
22
23 from vcsserver.lib.rc_cache import region_meta
23 from vcsserver.lib.rc_cache import region_meta
24 log = logging.getLogger(__name__)
24 log = logging.getLogger(__name__)
25
25
26
26
27 class RepoFactory(object):
27 class RepoFactory(object):
28 """
28 """
29 Utility to create instances of repository
29 Utility to create instances of repository
30
30
31 It provides internal caching of the `repo` object based on
31 It provides internal caching of the `repo` object based on
32 the :term:`call context`.
32 the :term:`call context`.
33 """
33 """
34 repo_type = None
34 repo_type = None
35
35
36 def __init__(self):
36 def __init__(self):
37 self._cache_region = region_meta.dogpile_cache_regions['repo_object']
37 self._cache_region = region_meta.dogpile_cache_regions['repo_object']
38
38
39 def _create_config(self, path, config):
39 def _create_config(self, path, config):
40 config = {}
40 config = {}
41 return config
41 return config
42
42
43 def _create_repo(self, wire, create):
43 def _create_repo(self, wire, create):
44 raise NotImplementedError()
44 raise NotImplementedError()
45
45
46 def repo(self, wire, create=False):
46 def repo(self, wire, create=False):
47 """
47 """
48 Get a repository instance for the given path.
48 Get a repository instance for the given path.
49
49
50 Uses internally the low level beaker API since the decorators introduce
50 Uses internally the low level beaker API since the decorators introduce
51 significant overhead.
51 significant overhead.
52 """
52 """
53 region = self._cache_region
53 region = self._cache_region
54 context = wire.get('context', None)
54 context = wire.get('context', None)
55 repo_path = wire.get('path', '')
55 repo_path = wire.get('path', '')
56 context_uid = '{}'.format(context)
56 context_uid = '{}'.format(context)
57 cache = wire.get('cache', True)
57 cache = wire.get('cache', True)
58 cache_on = context and cache
58 cache_on = context and cache
59
59
60 @region.conditional_cache_on_arguments(condition=cache_on)
60 @region.conditional_cache_on_arguments(condition=cache_on)
61 def create_new_repo(_repo_type, _repo_path, _context_uid):
61 def create_new_repo(_repo_type, _repo_path, _context_uid):
62 return self._create_repo(wire, create)
62 return self._create_repo(wire, create)
63
63
64 repo = create_new_repo(self.repo_type, repo_path, context_uid)
64 repo = create_new_repo(self.repo_type, repo_path, context_uid)
65 return repo
65 return repo
66
66
67
67
68 def obfuscate_qs(query_string):
68 def obfuscate_qs(query_string):
69 if query_string is None:
69 if query_string is None:
70 return None
70 return None
71
71
72 parsed = []
72 parsed = []
73 for k, v in urlparse.parse_qsl(query_string, keep_blank_values=True):
73 for k, v in urlparse.parse_qsl(query_string, keep_blank_values=True):
74 if k in ['auth_token', 'api_key']:
74 if k in ['auth_token', 'api_key']:
75 v = "*****"
75 v = "*****"
76 parsed.append((k, v))
76 parsed.append((k, v))
77
77
78 return '&'.join('{}{}'.format(
78 return '&'.join('{}{}'.format(
79 k, '={}'.format(v) if v else '') for k, v in parsed)
79 k, '={}'.format(v) if v else '') for k, v in parsed)
80
80
81
81
82 def raise_from_original(new_type):
82 def raise_from_original(new_type):
83 """
83 """
84 Raise a new exception type with original args and traceback.
84 Raise a new exception type with original args and traceback.
85 """
85 """
86 exc_type, exc_value, exc_traceback = sys.exc_info()
86 exc_type, exc_value, exc_traceback = sys.exc_info()
87 new_exc = new_type(*exc_value.args)
88 # store the original traceback into the new exc
89 new_exc._org_exc_tb = traceback.format_exc(exc_traceback)
87
90
88 try:
91 try:
89 raise new_type(*exc_value.args), None, exc_traceback
92 raise new_exc, None, exc_traceback
90 finally:
93 finally:
91 del exc_traceback
94 del exc_traceback
@@ -1,116 +1,117 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-2019 RhodeCode GmbH
2 # Copyright (C) 2014-2019 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 """
18 """
19 Special exception handling over the wire.
19 Special exception handling over the wire.
20
20
21 Since we cannot assume that our client is able to import our exception classes,
21 Since we cannot assume that our client is able to import our exception classes,
22 this module provides a "wrapping" mechanism to raise plain exceptions
22 this module provides a "wrapping" mechanism to raise plain exceptions
23 which contain an extra attribute `_vcs_kind` to allow a client to distinguish
23 which contain an extra attribute `_vcs_kind` to allow a client to distinguish
24 different error conditions.
24 different error conditions.
25 """
25 """
26
26
27 from pyramid.httpexceptions import HTTPLocked, HTTPForbidden
27 from pyramid.httpexceptions import HTTPLocked, HTTPForbidden
28
28
29
29
30 def _make_exception(kind, org_exc, *args):
30 def _make_exception(kind, org_exc, *args):
31 """
31 """
32 Prepares a base `Exception` instance to be sent over the wire.
32 Prepares a base `Exception` instance to be sent over the wire.
33
33
34 To give our caller a hint what this is about, it will attach an attribute
34 To give our caller a hint what this is about, it will attach an attribute
35 `_vcs_kind` to the exception.
35 `_vcs_kind` to the exception.
36 """
36 """
37 exc = Exception(*args)
37 exc = Exception(*args)
38 exc._vcs_kind = kind
38 exc._vcs_kind = kind
39 exc._org_exc = org_exc
39 exc._org_exc = org_exc
40 exc._org_exc_tb = ''
40 return exc
41 return exc
41
42
42
43
43 def AbortException(org_exc=None):
44 def AbortException(org_exc=None):
44 def _make_exception_wrapper(*args):
45 def _make_exception_wrapper(*args):
45 return _make_exception('abort', org_exc, *args)
46 return _make_exception('abort', org_exc, *args)
46 return _make_exception_wrapper
47 return _make_exception_wrapper
47
48
48
49
49 def ArchiveException(org_exc=None):
50 def ArchiveException(org_exc=None):
50 def _make_exception_wrapper(*args):
51 def _make_exception_wrapper(*args):
51 return _make_exception('archive', org_exc, *args)
52 return _make_exception('archive', org_exc, *args)
52 return _make_exception_wrapper
53 return _make_exception_wrapper
53
54
54
55
55 def LookupException(org_exc=None):
56 def LookupException(org_exc=None):
56 def _make_exception_wrapper(*args):
57 def _make_exception_wrapper(*args):
57 return _make_exception('lookup', org_exc, *args)
58 return _make_exception('lookup', org_exc, *args)
58 return _make_exception_wrapper
59 return _make_exception_wrapper
59
60
60
61
61 def VcsException(org_exc=None):
62 def VcsException(org_exc=None):
62 def _make_exception_wrapper(*args):
63 def _make_exception_wrapper(*args):
63 return _make_exception('error', org_exc, *args)
64 return _make_exception('error', org_exc, *args)
64 return _make_exception_wrapper
65 return _make_exception_wrapper
65
66
66
67
67 def RepositoryLockedException(org_exc=None):
68 def RepositoryLockedException(org_exc=None):
68 def _make_exception_wrapper(*args):
69 def _make_exception_wrapper(*args):
69 return _make_exception('repo_locked', org_exc, *args)
70 return _make_exception('repo_locked', org_exc, *args)
70 return _make_exception_wrapper
71 return _make_exception_wrapper
71
72
72
73
73 def RepositoryBranchProtectedException(org_exc=None):
74 def RepositoryBranchProtectedException(org_exc=None):
74 def _make_exception_wrapper(*args):
75 def _make_exception_wrapper(*args):
75 return _make_exception('repo_branch_protected', org_exc, *args)
76 return _make_exception('repo_branch_protected', org_exc, *args)
76 return _make_exception_wrapper
77 return _make_exception_wrapper
77
78
78
79
79 def RequirementException(org_exc=None):
80 def RequirementException(org_exc=None):
80 def _make_exception_wrapper(*args):
81 def _make_exception_wrapper(*args):
81 return _make_exception('requirement', org_exc, *args)
82 return _make_exception('requirement', org_exc, *args)
82 return _make_exception_wrapper
83 return _make_exception_wrapper
83
84
84
85
85 def UnhandledException(org_exc=None):
86 def UnhandledException(org_exc=None):
86 def _make_exception_wrapper(*args):
87 def _make_exception_wrapper(*args):
87 return _make_exception('unhandled', org_exc, *args)
88 return _make_exception('unhandled', org_exc, *args)
88 return _make_exception_wrapper
89 return _make_exception_wrapper
89
90
90
91
91 def URLError(org_exc=None):
92 def URLError(org_exc=None):
92 def _make_exception_wrapper(*args):
93 def _make_exception_wrapper(*args):
93 return _make_exception('url_error', org_exc, *args)
94 return _make_exception('url_error', org_exc, *args)
94 return _make_exception_wrapper
95 return _make_exception_wrapper
95
96
96
97
97 def SubrepoMergeException(org_exc=None):
98 def SubrepoMergeException(org_exc=None):
98 def _make_exception_wrapper(*args):
99 def _make_exception_wrapper(*args):
99 return _make_exception('subrepo_merge_error', org_exc, *args)
100 return _make_exception('subrepo_merge_error', org_exc, *args)
100 return _make_exception_wrapper
101 return _make_exception_wrapper
101
102
102
103
103 class HTTPRepoLocked(HTTPLocked):
104 class HTTPRepoLocked(HTTPLocked):
104 """
105 """
105 Subclass of HTTPLocked response that allows to set the title and status
106 Subclass of HTTPLocked response that allows to set the title and status
106 code via constructor arguments.
107 code via constructor arguments.
107 """
108 """
108 def __init__(self, title, status_code=None, **kwargs):
109 def __init__(self, title, status_code=None, **kwargs):
109 self.code = status_code or HTTPLocked.code
110 self.code = status_code or HTTPLocked.code
110 self.title = title
111 self.title = title
111 super(HTTPRepoLocked, self).__init__(**kwargs)
112 super(HTTPRepoLocked, self).__init__(**kwargs)
112
113
113
114
114 class HTTPRepoBranchProtected(HTTPForbidden):
115 class HTTPRepoBranchProtected(HTTPForbidden):
115 def __init__(self, *args, **kwargs):
116 def __init__(self, *args, **kwargs):
116 super(HTTPForbidden, self).__init__(*args, **kwargs)
117 super(HTTPForbidden, self).__init__(*args, **kwargs)
@@ -1,607 +1,610 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-2019 RhodeCode GmbH
2 # Copyright (C) 2014-2019 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
28
29 import simplejson as json
29 import simplejson as json
30 import msgpack
30 import msgpack
31 from pyramid.config import Configurator
31 from pyramid.config import Configurator
32 from pyramid.settings import asbool, aslist
32 from pyramid.settings import asbool, aslist
33 from pyramid.wsgi import wsgiapp
33 from pyramid.wsgi import wsgiapp
34 from pyramid.compat import configparser
34 from pyramid.compat import configparser
35
35
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
39 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
40 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
40 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
41
41
42 try:
42 try:
43 locale.setlocale(locale.LC_ALL, '')
43 locale.setlocale(locale.LC_ALL, '')
44 except locale.Error as e:
44 except locale.Error as e:
45 log.error(
45 log.error(
46 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
46 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
47 os.environ['LC_ALL'] = 'C'
47 os.environ['LC_ALL'] = 'C'
48
48
49 import vcsserver
49 import vcsserver
50 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
50 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
51 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
51 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
52 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
52 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
53 from vcsserver.echo_stub.echo_app import EchoApp
53 from vcsserver.echo_stub.echo_app import EchoApp
54 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
54 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
55 from vcsserver.lib.exc_tracking import store_exception
55 from vcsserver.lib.exc_tracking import store_exception
56 from vcsserver.server import VcsServer
56 from vcsserver.server import VcsServer
57
57
58 try:
58 try:
59 from vcsserver.git import GitFactory, GitRemote
59 from vcsserver.git import GitFactory, GitRemote
60 except ImportError:
60 except ImportError:
61 GitFactory = None
61 GitFactory = None
62 GitRemote = None
62 GitRemote = None
63
63
64 try:
64 try:
65 from vcsserver.hg import MercurialFactory, HgRemote
65 from vcsserver.hg import MercurialFactory, HgRemote
66 except ImportError:
66 except ImportError:
67 MercurialFactory = None
67 MercurialFactory = None
68 HgRemote = None
68 HgRemote = None
69
69
70 try:
70 try:
71 from vcsserver.svn import SubversionFactory, SvnRemote
71 from vcsserver.svn import SubversionFactory, SvnRemote
72 except ImportError:
72 except ImportError:
73 SubversionFactory = None
73 SubversionFactory = None
74 SvnRemote = None
74 SvnRemote = None
75
75
76
76
77 def _is_request_chunked(environ):
77 def _is_request_chunked(environ):
78 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
78 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
79 return stream
79 return stream
80
80
81
81
82 def _int_setting(settings, name, default):
82 def _int_setting(settings, name, default):
83 settings[name] = int(settings.get(name, default))
83 settings[name] = int(settings.get(name, default))
84 return settings[name]
84 return settings[name]
85
85
86
86
87 def _bool_setting(settings, name, default):
87 def _bool_setting(settings, name, default):
88 input_val = settings.get(name, default)
88 input_val = settings.get(name, default)
89 if isinstance(input_val, unicode):
89 if isinstance(input_val, unicode):
90 input_val = input_val.encode('utf8')
90 input_val = input_val.encode('utf8')
91 settings[name] = asbool(input_val)
91 settings[name] = asbool(input_val)
92 return settings[name]
92 return settings[name]
93
93
94
94
95 def _list_setting(settings, name, default):
95 def _list_setting(settings, name, default):
96 raw_value = settings.get(name, default)
96 raw_value = settings.get(name, default)
97
97
98 # Otherwise we assume it uses pyramids space/newline separation.
98 # Otherwise we assume it uses pyramids space/newline separation.
99 settings[name] = aslist(raw_value)
99 settings[name] = aslist(raw_value)
100 return settings[name]
100 return settings[name]
101
101
102
102
103 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
103 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
104 value = settings.get(name, default)
104 value = settings.get(name, default)
105
105
106 if default_when_empty and not value:
106 if default_when_empty and not value:
107 # use default value when value is empty
107 # use default value when value is empty
108 value = default
108 value = default
109
109
110 if lower:
110 if lower:
111 value = value.lower()
111 value = value.lower()
112 settings[name] = value
112 settings[name] = value
113 return settings[name]
113 return settings[name]
114
114
115
115
116 class VCS(object):
116 class VCS(object):
117 def __init__(self, locale=None, cache_config=None):
117 def __init__(self, locale=None, cache_config=None):
118 self.locale = locale
118 self.locale = locale
119 self.cache_config = cache_config
119 self.cache_config = cache_config
120 self._configure_locale()
120 self._configure_locale()
121
121
122 if GitFactory and GitRemote:
122 if GitFactory and GitRemote:
123 git_factory = GitFactory()
123 git_factory = GitFactory()
124 self._git_remote = GitRemote(git_factory)
124 self._git_remote = GitRemote(git_factory)
125 else:
125 else:
126 log.info("Git client import failed")
126 log.info("Git client import failed")
127
127
128 if MercurialFactory and HgRemote:
128 if MercurialFactory and HgRemote:
129 hg_factory = MercurialFactory()
129 hg_factory = MercurialFactory()
130 self._hg_remote = HgRemote(hg_factory)
130 self._hg_remote = HgRemote(hg_factory)
131 else:
131 else:
132 log.info("Mercurial client import failed")
132 log.info("Mercurial client import failed")
133
133
134 if SubversionFactory and SvnRemote:
134 if SubversionFactory and SvnRemote:
135 svn_factory = SubversionFactory()
135 svn_factory = SubversionFactory()
136
136
137 # hg factory is used for svn url validation
137 # hg factory is used for svn url validation
138 hg_factory = MercurialFactory()
138 hg_factory = MercurialFactory()
139 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
139 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
140 else:
140 else:
141 log.info("Subversion client import failed")
141 log.info("Subversion client import failed")
142
142
143 self._vcsserver = VcsServer()
143 self._vcsserver = VcsServer()
144
144
145 def _configure_locale(self):
145 def _configure_locale(self):
146 if self.locale:
146 if self.locale:
147 log.info('Settings locale: `LC_ALL` to %s', self.locale)
147 log.info('Settings locale: `LC_ALL` to %s', self.locale)
148 else:
148 else:
149 log.info(
149 log.info(
150 'Configuring locale subsystem based on environment variables')
150 'Configuring locale subsystem based on environment variables')
151 try:
151 try:
152 # If self.locale is the empty string, then the locale
152 # If self.locale is the empty string, then the locale
153 # module will use the environment variables. See the
153 # module will use the environment variables. See the
154 # documentation of the package `locale`.
154 # documentation of the package `locale`.
155 locale.setlocale(locale.LC_ALL, self.locale)
155 locale.setlocale(locale.LC_ALL, self.locale)
156
156
157 language_code, encoding = locale.getlocale()
157 language_code, encoding = locale.getlocale()
158 log.info(
158 log.info(
159 'Locale set to language code "%s" with encoding "%s".',
159 'Locale set to language code "%s" with encoding "%s".',
160 language_code, encoding)
160 language_code, encoding)
161 except locale.Error:
161 except locale.Error:
162 log.exception(
162 log.exception(
163 'Cannot set locale, not configuring the locale system')
163 'Cannot set locale, not configuring the locale system')
164
164
165
165
166 class WsgiProxy(object):
166 class WsgiProxy(object):
167 def __init__(self, wsgi):
167 def __init__(self, wsgi):
168 self.wsgi = wsgi
168 self.wsgi = wsgi
169
169
170 def __call__(self, environ, start_response):
170 def __call__(self, environ, start_response):
171 input_data = environ['wsgi.input'].read()
171 input_data = environ['wsgi.input'].read()
172 input_data = msgpack.unpackb(input_data)
172 input_data = msgpack.unpackb(input_data)
173
173
174 error = None
174 error = None
175 try:
175 try:
176 data, status, headers = self.wsgi.handle(
176 data, status, headers = self.wsgi.handle(
177 input_data['environment'], input_data['input_data'],
177 input_data['environment'], input_data['input_data'],
178 *input_data['args'], **input_data['kwargs'])
178 *input_data['args'], **input_data['kwargs'])
179 except Exception as e:
179 except Exception as e:
180 data, status, headers = [], None, None
180 data, status, headers = [], None, None
181 error = {
181 error = {
182 'message': str(e),
182 'message': str(e),
183 '_vcs_kind': getattr(e, '_vcs_kind', None)
183 '_vcs_kind': getattr(e, '_vcs_kind', None)
184 }
184 }
185
185
186 start_response(200, {})
186 start_response(200, {})
187 return self._iterator(error, status, headers, data)
187 return self._iterator(error, status, headers, data)
188
188
189 def _iterator(self, error, status, headers, data):
189 def _iterator(self, error, status, headers, data):
190 initial_data = [
190 initial_data = [
191 error,
191 error,
192 status,
192 status,
193 headers,
193 headers,
194 ]
194 ]
195
195
196 for d in chain(initial_data, data):
196 for d in chain(initial_data, data):
197 yield msgpack.packb(d)
197 yield msgpack.packb(d)
198
198
199
199
200 def not_found(request):
200 def not_found(request):
201 return {'status': '404 NOT FOUND'}
201 return {'status': '404 NOT FOUND'}
202
202
203
203
204 class VCSViewPredicate(object):
204 class VCSViewPredicate(object):
205 def __init__(self, val, config):
205 def __init__(self, val, config):
206 self.remotes = val
206 self.remotes = val
207
207
208 def text(self):
208 def text(self):
209 return 'vcs view method = %s' % (self.remotes.keys(),)
209 return 'vcs view method = %s' % (self.remotes.keys(),)
210
210
211 phash = text
211 phash = text
212
212
213 def __call__(self, context, request):
213 def __call__(self, context, request):
214 """
214 """
215 View predicate that returns true if given backend is supported by
215 View predicate that returns true if given backend is supported by
216 defined remotes.
216 defined remotes.
217 """
217 """
218 backend = request.matchdict.get('backend')
218 backend = request.matchdict.get('backend')
219 return backend in self.remotes
219 return backend in self.remotes
220
220
221
221
222 class HTTPApplication(object):
222 class HTTPApplication(object):
223 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
223 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
224
224
225 remote_wsgi = remote_wsgi
225 remote_wsgi = remote_wsgi
226 _use_echo_app = False
226 _use_echo_app = False
227
227
228 def __init__(self, settings=None, global_config=None):
228 def __init__(self, settings=None, global_config=None):
229 self._sanitize_settings_and_apply_defaults(settings)
229 self._sanitize_settings_and_apply_defaults(settings)
230
230
231 self.config = Configurator(settings=settings)
231 self.config = Configurator(settings=settings)
232 self.global_config = global_config
232 self.global_config = global_config
233 self.config.include('vcsserver.lib.rc_cache')
233 self.config.include('vcsserver.lib.rc_cache')
234
234
235 locale = settings.get('locale', '') or 'en_US.UTF-8'
235 locale = settings.get('locale', '') or 'en_US.UTF-8'
236 vcs = VCS(locale=locale, cache_config=settings)
236 vcs = VCS(locale=locale, cache_config=settings)
237 self._remotes = {
237 self._remotes = {
238 'hg': vcs._hg_remote,
238 'hg': vcs._hg_remote,
239 'git': vcs._git_remote,
239 'git': vcs._git_remote,
240 'svn': vcs._svn_remote,
240 'svn': vcs._svn_remote,
241 'server': vcs._vcsserver,
241 'server': vcs._vcsserver,
242 }
242 }
243 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
243 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
244 self._use_echo_app = True
244 self._use_echo_app = True
245 log.warning("Using EchoApp for VCS operations.")
245 log.warning("Using EchoApp for VCS operations.")
246 self.remote_wsgi = remote_wsgi_stub
246 self.remote_wsgi = remote_wsgi_stub
247
247
248 self._configure_settings(global_config, settings)
248 self._configure_settings(global_config, settings)
249 self._configure()
249 self._configure()
250
250
251 def _configure_settings(self, global_config, app_settings):
251 def _configure_settings(self, global_config, app_settings):
252 """
252 """
253 Configure the settings module.
253 Configure the settings module.
254 """
254 """
255 settings_merged = global_config.copy()
255 settings_merged = global_config.copy()
256 settings_merged.update(app_settings)
256 settings_merged.update(app_settings)
257
257
258 git_path = app_settings.get('git_path', None)
258 git_path = app_settings.get('git_path', None)
259 if git_path:
259 if git_path:
260 settings.GIT_EXECUTABLE = git_path
260 settings.GIT_EXECUTABLE = git_path
261 binary_dir = app_settings.get('core.binary_dir', None)
261 binary_dir = app_settings.get('core.binary_dir', None)
262 if binary_dir:
262 if binary_dir:
263 settings.BINARY_DIR = binary_dir
263 settings.BINARY_DIR = binary_dir
264
264
265 # Store the settings to make them available to other modules.
265 # Store the settings to make them available to other modules.
266 vcsserver.PYRAMID_SETTINGS = settings_merged
266 vcsserver.PYRAMID_SETTINGS = settings_merged
267 vcsserver.CONFIG = settings_merged
267 vcsserver.CONFIG = settings_merged
268
268
269 def _sanitize_settings_and_apply_defaults(self, settings):
269 def _sanitize_settings_and_apply_defaults(self, settings):
270 temp_store = tempfile.gettempdir()
270 temp_store = tempfile.gettempdir()
271 default_cache_dir = os.path.join(temp_store, 'rc_cache')
271 default_cache_dir = os.path.join(temp_store, 'rc_cache')
272
272
273 # save default, cache dir, and use it for all backends later.
273 # save default, cache dir, and use it for all backends later.
274 default_cache_dir = _string_setting(
274 default_cache_dir = _string_setting(
275 settings,
275 settings,
276 'cache_dir',
276 'cache_dir',
277 default_cache_dir, lower=False, default_when_empty=True)
277 default_cache_dir, lower=False, default_when_empty=True)
278
278
279 # ensure we have our dir created
279 # ensure we have our dir created
280 if not os.path.isdir(default_cache_dir):
280 if not os.path.isdir(default_cache_dir):
281 os.makedirs(default_cache_dir, mode=0o755)
281 os.makedirs(default_cache_dir, mode=0o755)
282
282
283 # exception store cache
283 # exception store cache
284 _string_setting(
284 _string_setting(
285 settings,
285 settings,
286 'exception_tracker.store_path',
286 'exception_tracker.store_path',
287 temp_store, lower=False, default_when_empty=True)
287 temp_store, lower=False, default_when_empty=True)
288
288
289 # repo_object cache
289 # repo_object cache
290 _string_setting(
290 _string_setting(
291 settings,
291 settings,
292 'rc_cache.repo_object.backend',
292 'rc_cache.repo_object.backend',
293 'dogpile.cache.rc.memory_lru')
293 'dogpile.cache.rc.memory_lru')
294 _int_setting(
294 _int_setting(
295 settings,
295 settings,
296 'rc_cache.repo_object.expiration_time',
296 'rc_cache.repo_object.expiration_time',
297 300)
297 300)
298 _int_setting(
298 _int_setting(
299 settings,
299 settings,
300 'rc_cache.repo_object.max_size',
300 'rc_cache.repo_object.max_size',
301 1024)
301 1024)
302
302
303 def _configure(self):
303 def _configure(self):
304 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
304 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
305
305
306 self.config.add_route('service', '/_service')
306 self.config.add_route('service', '/_service')
307 self.config.add_route('status', '/status')
307 self.config.add_route('status', '/status')
308 self.config.add_route('hg_proxy', '/proxy/hg')
308 self.config.add_route('hg_proxy', '/proxy/hg')
309 self.config.add_route('git_proxy', '/proxy/git')
309 self.config.add_route('git_proxy', '/proxy/git')
310 self.config.add_route('vcs', '/{backend}')
310 self.config.add_route('vcs', '/{backend}')
311 self.config.add_route('stream_git', '/stream/git/*repo_name')
311 self.config.add_route('stream_git', '/stream/git/*repo_name')
312 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
312 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
313
313
314 self.config.add_view(self.status_view, route_name='status', renderer='json')
314 self.config.add_view(self.status_view, route_name='status', renderer='json')
315 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
315 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
316
316
317 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
317 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
318 self.config.add_view(self.git_proxy(), route_name='git_proxy')
318 self.config.add_view(self.git_proxy(), route_name='git_proxy')
319 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
319 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
320 vcs_view=self._remotes)
320 vcs_view=self._remotes)
321
321
322 self.config.add_view(self.hg_stream(), route_name='stream_hg')
322 self.config.add_view(self.hg_stream(), route_name='stream_hg')
323 self.config.add_view(self.git_stream(), route_name='stream_git')
323 self.config.add_view(self.git_stream(), route_name='stream_git')
324
324
325 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
325 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
326
326
327 self.config.add_notfound_view(not_found, renderer='json')
327 self.config.add_notfound_view(not_found, renderer='json')
328
328
329 self.config.add_view(self.handle_vcs_exception, context=Exception)
329 self.config.add_view(self.handle_vcs_exception, context=Exception)
330
330
331 self.config.add_tween(
331 self.config.add_tween(
332 'vcsserver.tweens.RequestWrapperTween',
332 'vcsserver.tweens.RequestWrapperTween',
333 )
333 )
334
334
335 def wsgi_app(self):
335 def wsgi_app(self):
336 return self.config.make_wsgi_app()
336 return self.config.make_wsgi_app()
337
337
338 def vcs_view(self, request):
338 def vcs_view(self, request):
339 remote = self._remotes[request.matchdict['backend']]
339 remote = self._remotes[request.matchdict['backend']]
340 payload = msgpack.unpackb(request.body, use_list=True)
340 payload = msgpack.unpackb(request.body, use_list=True)
341 method = payload.get('method')
341 method = payload.get('method')
342 params = payload.get('params')
342 params = payload.get('params')
343 wire = params.get('wire')
343 wire = params.get('wire')
344 args = params.get('args')
344 args = params.get('args')
345 kwargs = params.get('kwargs')
345 kwargs = params.get('kwargs')
346 context_uid = None
346 context_uid = None
347
347
348 if wire:
348 if wire:
349 try:
349 try:
350 wire['context'] = context_uid = uuid.UUID(wire['context'])
350 wire['context'] = context_uid = uuid.UUID(wire['context'])
351 except KeyError:
351 except KeyError:
352 pass
352 pass
353 args.insert(0, wire)
353 args.insert(0, wire)
354
354
355 log.debug('method called:%s with kwargs:%s context_uid: %s',
355 log.debug('method called:%s with kwargs:%s context_uid: %s',
356 method, kwargs, context_uid)
356 method, kwargs, context_uid)
357 try:
357 try:
358 resp = getattr(remote, method)(*args, **kwargs)
358 resp = getattr(remote, method)(*args, **kwargs)
359 except Exception as e:
359 except Exception as e:
360 exc_info = list(sys.exc_info())
360 exc_info = list(sys.exc_info())
361 exc_type, exc_value, exc_traceback = exc_info
361 exc_type, exc_value, exc_traceback = exc_info
362
362
363 org_exc = getattr(e, '_org_exc', None)
363 org_exc = getattr(e, '_org_exc', None)
364 org_exc_name = None
364 org_exc_name = None
365 org_exc_tb = ''
365 if org_exc:
366 if org_exc:
366 org_exc_name = org_exc.__class__.__name__
367 org_exc_name = org_exc.__class__.__name__
368 org_exc_tb = getattr(e, '_org_exc_tb', '')
367 # replace our "faked" exception with our org
369 # replace our "faked" exception with our org
368 exc_info[0] = org_exc.__class__
370 exc_info[0] = org_exc.__class__
369 exc_info[1] = org_exc
371 exc_info[1] = org_exc
370
372
371 store_exception(id(exc_info), exc_info)
373 store_exception(id(exc_info), exc_info)
372
374
373 tb_info = ''.join(
375 tb_info = ''.join(
374 traceback.format_exception(exc_type, exc_value, exc_traceback))
376 traceback.format_exception(exc_type, exc_value, exc_traceback))
375
377
376 type_ = e.__class__.__name__
378 type_ = e.__class__.__name__
377 if type_ not in self.ALLOWED_EXCEPTIONS:
379 if type_ not in self.ALLOWED_EXCEPTIONS:
378 type_ = None
380 type_ = None
379
381
380 resp = {
382 resp = {
381 'id': payload.get('id'),
383 'id': payload.get('id'),
382 'error': {
384 'error': {
383 'message': e.message,
385 'message': e.message,
384 'traceback': tb_info,
386 'traceback': tb_info,
385 'org_exc': org_exc_name,
387 'org_exc': org_exc_name,
388 'org_exc_tb': org_exc_tb,
386 'type': type_
389 'type': type_
387 }
390 }
388 }
391 }
389 try:
392 try:
390 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
393 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
391 except AttributeError:
394 except AttributeError:
392 pass
395 pass
393 else:
396 else:
394 resp = {
397 resp = {
395 'id': payload.get('id'),
398 'id': payload.get('id'),
396 'result': resp
399 'result': resp
397 }
400 }
398
401
399 return resp
402 return resp
400
403
401 def status_view(self, request):
404 def status_view(self, request):
402 import vcsserver
405 import vcsserver
403 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
406 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
404 'pid': os.getpid()}
407 'pid': os.getpid()}
405
408
406 def service_view(self, request):
409 def service_view(self, request):
407 import vcsserver
410 import vcsserver
408
411
409 payload = msgpack.unpackb(request.body, use_list=True)
412 payload = msgpack.unpackb(request.body, use_list=True)
410
413
411 try:
414 try:
412 path = self.global_config['__file__']
415 path = self.global_config['__file__']
413 config = configparser.ConfigParser()
416 config = configparser.ConfigParser()
414 config.read(path)
417 config.read(path)
415 parsed_ini = config
418 parsed_ini = config
416 if parsed_ini.has_section('server:main'):
419 if parsed_ini.has_section('server:main'):
417 parsed_ini = dict(parsed_ini.items('server:main'))
420 parsed_ini = dict(parsed_ini.items('server:main'))
418 except Exception:
421 except Exception:
419 log.exception('Failed to read .ini file for display')
422 log.exception('Failed to read .ini file for display')
420 parsed_ini = {}
423 parsed_ini = {}
421
424
422 resp = {
425 resp = {
423 'id': payload.get('id'),
426 'id': payload.get('id'),
424 'result': dict(
427 'result': dict(
425 version=vcsserver.__version__,
428 version=vcsserver.__version__,
426 config=parsed_ini,
429 config=parsed_ini,
427 payload=payload,
430 payload=payload,
428 )
431 )
429 }
432 }
430 return resp
433 return resp
431
434
432 def _msgpack_renderer_factory(self, info):
435 def _msgpack_renderer_factory(self, info):
433 def _render(value, system):
436 def _render(value, system):
434 value = msgpack.packb(value)
437 value = msgpack.packb(value)
435 request = system.get('request')
438 request = system.get('request')
436 if request is not None:
439 if request is not None:
437 response = request.response
440 response = request.response
438 ct = response.content_type
441 ct = response.content_type
439 if ct == response.default_content_type:
442 if ct == response.default_content_type:
440 response.content_type = 'application/x-msgpack'
443 response.content_type = 'application/x-msgpack'
441 return value
444 return value
442 return _render
445 return _render
443
446
444 def set_env_from_config(self, environ, config):
447 def set_env_from_config(self, environ, config):
445 dict_conf = {}
448 dict_conf = {}
446 try:
449 try:
447 for elem in config:
450 for elem in config:
448 if elem[0] == 'rhodecode':
451 if elem[0] == 'rhodecode':
449 dict_conf = json.loads(elem[2])
452 dict_conf = json.loads(elem[2])
450 break
453 break
451 except Exception:
454 except Exception:
452 log.exception('Failed to fetch SCM CONFIG')
455 log.exception('Failed to fetch SCM CONFIG')
453 return
456 return
454
457
455 username = dict_conf.get('username')
458 username = dict_conf.get('username')
456 if username:
459 if username:
457 environ['REMOTE_USER'] = username
460 environ['REMOTE_USER'] = username
458 # mercurial specific, some extension api rely on this
461 # mercurial specific, some extension api rely on this
459 environ['HGUSER'] = username
462 environ['HGUSER'] = username
460
463
461 ip = dict_conf.get('ip')
464 ip = dict_conf.get('ip')
462 if ip:
465 if ip:
463 environ['REMOTE_HOST'] = ip
466 environ['REMOTE_HOST'] = ip
464
467
465 if _is_request_chunked(environ):
468 if _is_request_chunked(environ):
466 # set the compatibility flag for webob
469 # set the compatibility flag for webob
467 environ['wsgi.input_terminated'] = True
470 environ['wsgi.input_terminated'] = True
468
471
469 def hg_proxy(self):
472 def hg_proxy(self):
470 @wsgiapp
473 @wsgiapp
471 def _hg_proxy(environ, start_response):
474 def _hg_proxy(environ, start_response):
472 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
475 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
473 return app(environ, start_response)
476 return app(environ, start_response)
474 return _hg_proxy
477 return _hg_proxy
475
478
476 def git_proxy(self):
479 def git_proxy(self):
477 @wsgiapp
480 @wsgiapp
478 def _git_proxy(environ, start_response):
481 def _git_proxy(environ, start_response):
479 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
482 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
480 return app(environ, start_response)
483 return app(environ, start_response)
481 return _git_proxy
484 return _git_proxy
482
485
483 def hg_stream(self):
486 def hg_stream(self):
484 if self._use_echo_app:
487 if self._use_echo_app:
485 @wsgiapp
488 @wsgiapp
486 def _hg_stream(environ, start_response):
489 def _hg_stream(environ, start_response):
487 app = EchoApp('fake_path', 'fake_name', None)
490 app = EchoApp('fake_path', 'fake_name', None)
488 return app(environ, start_response)
491 return app(environ, start_response)
489 return _hg_stream
492 return _hg_stream
490 else:
493 else:
491 @wsgiapp
494 @wsgiapp
492 def _hg_stream(environ, start_response):
495 def _hg_stream(environ, start_response):
493 log.debug('http-app: handling hg stream')
496 log.debug('http-app: handling hg stream')
494 repo_path = environ['HTTP_X_RC_REPO_PATH']
497 repo_path = environ['HTTP_X_RC_REPO_PATH']
495 repo_name = environ['HTTP_X_RC_REPO_NAME']
498 repo_name = environ['HTTP_X_RC_REPO_NAME']
496 packed_config = base64.b64decode(
499 packed_config = base64.b64decode(
497 environ['HTTP_X_RC_REPO_CONFIG'])
500 environ['HTTP_X_RC_REPO_CONFIG'])
498 config = msgpack.unpackb(packed_config)
501 config = msgpack.unpackb(packed_config)
499 app = scm_app.create_hg_wsgi_app(
502 app = scm_app.create_hg_wsgi_app(
500 repo_path, repo_name, config)
503 repo_path, repo_name, config)
501
504
502 # Consistent path information for hgweb
505 # Consistent path information for hgweb
503 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
506 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
504 environ['REPO_NAME'] = repo_name
507 environ['REPO_NAME'] = repo_name
505 self.set_env_from_config(environ, config)
508 self.set_env_from_config(environ, config)
506
509
507 log.debug('http-app: starting app handler '
510 log.debug('http-app: starting app handler '
508 'with %s and process request', app)
511 'with %s and process request', app)
509 return app(environ, ResponseFilter(start_response))
512 return app(environ, ResponseFilter(start_response))
510 return _hg_stream
513 return _hg_stream
511
514
512 def git_stream(self):
515 def git_stream(self):
513 if self._use_echo_app:
516 if self._use_echo_app:
514 @wsgiapp
517 @wsgiapp
515 def _git_stream(environ, start_response):
518 def _git_stream(environ, start_response):
516 app = EchoApp('fake_path', 'fake_name', None)
519 app = EchoApp('fake_path', 'fake_name', None)
517 return app(environ, start_response)
520 return app(environ, start_response)
518 return _git_stream
521 return _git_stream
519 else:
522 else:
520 @wsgiapp
523 @wsgiapp
521 def _git_stream(environ, start_response):
524 def _git_stream(environ, start_response):
522 log.debug('http-app: handling git stream')
525 log.debug('http-app: handling git stream')
523 repo_path = environ['HTTP_X_RC_REPO_PATH']
526 repo_path = environ['HTTP_X_RC_REPO_PATH']
524 repo_name = environ['HTTP_X_RC_REPO_NAME']
527 repo_name = environ['HTTP_X_RC_REPO_NAME']
525 packed_config = base64.b64decode(
528 packed_config = base64.b64decode(
526 environ['HTTP_X_RC_REPO_CONFIG'])
529 environ['HTTP_X_RC_REPO_CONFIG'])
527 config = msgpack.unpackb(packed_config)
530 config = msgpack.unpackb(packed_config)
528
531
529 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
532 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
530 self.set_env_from_config(environ, config)
533 self.set_env_from_config(environ, config)
531
534
532 content_type = environ.get('CONTENT_TYPE', '')
535 content_type = environ.get('CONTENT_TYPE', '')
533
536
534 path = environ['PATH_INFO']
537 path = environ['PATH_INFO']
535 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
538 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
536 log.debug(
539 log.debug(
537 'LFS: Detecting if request `%s` is LFS server path based '
540 'LFS: Detecting if request `%s` is LFS server path based '
538 'on content type:`%s`, is_lfs:%s',
541 'on content type:`%s`, is_lfs:%s',
539 path, content_type, is_lfs_request)
542 path, content_type, is_lfs_request)
540
543
541 if not is_lfs_request:
544 if not is_lfs_request:
542 # fallback detection by path
545 # fallback detection by path
543 if GIT_LFS_PROTO_PAT.match(path):
546 if GIT_LFS_PROTO_PAT.match(path):
544 is_lfs_request = True
547 is_lfs_request = True
545 log.debug(
548 log.debug(
546 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
549 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
547 path, is_lfs_request)
550 path, is_lfs_request)
548
551
549 if is_lfs_request:
552 if is_lfs_request:
550 app = scm_app.create_git_lfs_wsgi_app(
553 app = scm_app.create_git_lfs_wsgi_app(
551 repo_path, repo_name, config)
554 repo_path, repo_name, config)
552 else:
555 else:
553 app = scm_app.create_git_wsgi_app(
556 app = scm_app.create_git_wsgi_app(
554 repo_path, repo_name, config)
557 repo_path, repo_name, config)
555
558
556 log.debug('http-app: starting app handler '
559 log.debug('http-app: starting app handler '
557 'with %s and process request', app)
560 'with %s and process request', app)
558
561
559 return app(environ, start_response)
562 return app(environ, start_response)
560
563
561 return _git_stream
564 return _git_stream
562
565
563 def handle_vcs_exception(self, exception, request):
566 def handle_vcs_exception(self, exception, request):
564 _vcs_kind = getattr(exception, '_vcs_kind', '')
567 _vcs_kind = getattr(exception, '_vcs_kind', '')
565 if _vcs_kind == 'repo_locked':
568 if _vcs_kind == 'repo_locked':
566 # Get custom repo-locked status code if present.
569 # Get custom repo-locked status code if present.
567 status_code = request.headers.get('X-RC-Locked-Status-Code')
570 status_code = request.headers.get('X-RC-Locked-Status-Code')
568 return HTTPRepoLocked(
571 return HTTPRepoLocked(
569 title=exception.message, status_code=status_code)
572 title=exception.message, status_code=status_code)
570
573
571 elif _vcs_kind == 'repo_branch_protected':
574 elif _vcs_kind == 'repo_branch_protected':
572 # Get custom repo-branch-protected status code if present.
575 # Get custom repo-branch-protected status code if present.
573 return HTTPRepoBranchProtected(title=exception.message)
576 return HTTPRepoBranchProtected(title=exception.message)
574
577
575 exc_info = request.exc_info
578 exc_info = request.exc_info
576 store_exception(id(exc_info), exc_info)
579 store_exception(id(exc_info), exc_info)
577
580
578 traceback_info = 'unavailable'
581 traceback_info = 'unavailable'
579 if request.exc_info:
582 if request.exc_info:
580 exc_type, exc_value, exc_tb = request.exc_info
583 exc_type, exc_value, exc_tb = request.exc_info
581 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
584 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
582
585
583 log.error(
586 log.error(
584 'error occurred handling this request for path: %s, \n tb: %s',
587 'error occurred handling this request for path: %s, \n tb: %s',
585 request.path, traceback_info)
588 request.path, traceback_info)
586 raise exception
589 raise exception
587
590
588
591
589 class ResponseFilter(object):
592 class ResponseFilter(object):
590
593
591 def __init__(self, start_response):
594 def __init__(self, start_response):
592 self._start_response = start_response
595 self._start_response = start_response
593
596
594 def __call__(self, status, response_headers, exc_info=None):
597 def __call__(self, status, response_headers, exc_info=None):
595 headers = tuple(
598 headers = tuple(
596 (h, v) for h, v in response_headers
599 (h, v) for h, v in response_headers
597 if not wsgiref.util.is_hop_by_hop(h))
600 if not wsgiref.util.is_hop_by_hop(h))
598 return self._start_response(status, headers, exc_info)
601 return self._start_response(status, headers, exc_info)
599
602
600
603
601 def main(global_config, **settings):
604 def main(global_config, **settings):
602 if MercurialFactory:
605 if MercurialFactory:
603 hgpatches.patch_largefiles_capabilities()
606 hgpatches.patch_largefiles_capabilities()
604 hgpatches.patch_subrepo_type_mapping()
607 hgpatches.patch_subrepo_type_mapping()
605
608
606 app = HTTPApplication(settings=settings, global_config=global_config)
609 app = HTTPApplication(settings=settings, global_config=global_config)
607 return app.wsgi_app()
610 return app.wsgi_app()
General Comments 0
You need to be logged in to leave comments. Login now