##// END OF EJS Templates
vcs: Refactor the get_repo function....
Martin Bornhold -
r483:b7e26a5f default
parent child Browse files
Show More
@@ -1,290 +1,290 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Various version Control System version lib (vcs) management abstraction layer
23 23 for Python. Build with server client architecture.
24 24 """
25 25
26 26
27 27 VERSION = (0, 5, 0, 'dev')
28 28
29 29 __version__ = '.'.join((str(each) for each in VERSION[:4]))
30 30
31 31 __all__ = [
32 'get_version', 'get_repo', 'get_backend',
32 'get_version', 'get_vcs_instance', 'get_backend',
33 33 'VCSError', 'RepositoryError', 'CommitError'
34 34 ]
35 35
36 36 import atexit
37 37 import logging
38 38 import subprocess
39 39 import time
40 40 import urlparse
41 41 from cStringIO import StringIO
42 42
43 43 import Pyro4
44 44 from Pyro4.errors import CommunicationError
45 45
46 46 from rhodecode.lib.vcs.conf import settings
47 from rhodecode.lib.vcs.backends import get_repo, get_backend
47 from rhodecode.lib.vcs.backends import get_vcs_instance, get_backend
48 48 from rhodecode.lib.vcs.exceptions import (
49 49 VCSError, RepositoryError, CommitError)
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53 # The pycurl library directly accesses C API functions and is not patched by
54 54 # gevent. This will potentially lead to deadlocks due to incompatibility to
55 55 # gevent. Therefore we check if gevent is active and import a gevent compatible
56 56 # wrapper in that case.
57 57 try:
58 58 from gevent import monkey
59 59 if monkey.is_module_patched('__builtin__'):
60 60 import geventcurl as pycurl
61 61 log.debug('Using gevent comapatible pycurl: %s', pycurl)
62 62 else:
63 63 import pycurl
64 64 except ImportError:
65 65 import pycurl
66 66
67 67
68 68 def get_version():
69 69 """
70 70 Returns shorter version (digit parts only) as string.
71 71 """
72 72 return '.'.join((str(each) for each in VERSION[:3]))
73 73
74 74
75 75 def connect_pyro4(server_and_port):
76 76 from rhodecode.lib.vcs import connection, client
77 77 from rhodecode.lib.middleware.utils import scm_app
78 78
79 79 git_remote = client.RequestScopeProxyFactory(
80 80 settings.pyro_remote(settings.PYRO_GIT, server_and_port))
81 81 hg_remote = client.RequestScopeProxyFactory(
82 82 settings.pyro_remote(settings.PYRO_HG, server_and_port))
83 83 svn_remote = client.RequestScopeProxyFactory(
84 84 settings.pyro_remote(settings.PYRO_SVN, server_and_port))
85 85
86 86 connection.Git = client.RepoMaker(proxy_factory=git_remote)
87 87 connection.Hg = client.RepoMaker(proxy_factory=hg_remote)
88 88 connection.Svn = client.RepoMaker(proxy_factory=svn_remote)
89 89
90 90 scm_app.GIT_REMOTE_WSGI = Pyro4.Proxy(
91 91 settings.pyro_remote(
92 92 settings.PYRO_GIT_REMOTE_WSGI, server_and_port))
93 93 scm_app.HG_REMOTE_WSGI = Pyro4.Proxy(
94 94 settings.pyro_remote(
95 95 settings.PYRO_HG_REMOTE_WSGI, server_and_port))
96 96
97 97 @atexit.register
98 98 def free_connection_resources():
99 99 connection.Git = None
100 100 connection.Hg = None
101 101 connection.Svn = None
102 102
103 103
104 104 def connect_http(server_and_port):
105 105 from rhodecode.lib.vcs import connection, client_http
106 106 from rhodecode.lib.middleware.utils import scm_app
107 107
108 108 session_factory = client_http.ThreadlocalSessionFactory()
109 109
110 110 connection.Git = client_http.RepoMaker(
111 111 server_and_port, '/git', session_factory)
112 112 connection.Hg = client_http.RepoMaker(
113 113 server_and_port, '/hg', session_factory)
114 114 connection.Svn = client_http.RepoMaker(
115 115 server_and_port, '/svn', session_factory)
116 116
117 117 scm_app.HG_REMOTE_WSGI = client_http.VcsHttpProxy(
118 118 server_and_port, '/proxy/hg')
119 119 scm_app.GIT_REMOTE_WSGI = client_http.VcsHttpProxy(
120 120 server_and_port, '/proxy/git')
121 121
122 122 @atexit.register
123 123 def free_connection_resources():
124 124 connection.Git = None
125 125 connection.Hg = None
126 126 connection.Svn = None
127 127
128 128
129 129 def connect_vcs(server_and_port, protocol='pyro4'):
130 130 """
131 131 Initializes the connection to the vcs server.
132 132
133 133 :param server_and_port: str, e.g. "localhost:9900"
134 134 :param protocol: str, "pyro4" or "http"
135 135 """
136 136 if protocol == 'pyro4':
137 137 connect_pyro4(server_and_port)
138 138 elif protocol == 'http':
139 139 connect_http(server_and_port)
140 140
141 141
142 142 # TODO: johbo: This function should be moved into our test suite, there is
143 143 # no reason to support starting the vcsserver in Enterprise itself.
144 144 def start_vcs_server(server_and_port, protocol='pyro4', log_level=None):
145 145 """
146 146 Starts the vcs server in a subprocess.
147 147 """
148 148 log.info('Starting VCSServer as a sub process with %s protocol', protocol)
149 149 if protocol == 'http':
150 150 return _start_http_vcs_server(server_and_port, log_level)
151 151 elif protocol == 'pyro4':
152 152 return _start_pyro4_vcs_server(server_and_port, log_level)
153 153
154 154
155 155 def _start_pyro4_vcs_server(server_and_port, log_level=None):
156 156 _try_to_shutdown_running_server(server_and_port)
157 157 host, port = server_and_port.rsplit(":", 1)
158 158 host = host.strip('[]')
159 159 args = [
160 160 'vcsserver', '--port', port, '--host', host, '--locale', 'en_US.UTF-8',
161 161 '--threadpool', '32']
162 162 if log_level:
163 163 args += ['--log-level', log_level]
164 164 proc = subprocess.Popen(args)
165 165
166 166 def cleanup_server_process():
167 167 proc.kill()
168 168 atexit.register(cleanup_server_process)
169 169
170 170 server = create_vcsserver_proxy(server_and_port, protocol='pyro4')
171 171 _wait_until_vcs_server_is_reachable(server)
172 172
173 173
174 174 def _start_http_vcs_server(server_and_port, log_level=None):
175 175 # TODO: mikhail: shutdown if an http server already runs
176 176
177 177 host, port = server_and_port.rsplit(":", 1)
178 178 args = [
179 179 'pserve', 'vcsserver/development_pyramid.ini',
180 180 'http_port=%s' % (port, ), 'http_host=%s' % (host, )]
181 181 proc = subprocess.Popen(args)
182 182
183 183 def cleanup_server_process():
184 184 proc.kill()
185 185 atexit.register(cleanup_server_process)
186 186
187 187 server = create_vcsserver_proxy(server_and_port, protocol='http')
188 188 _wait_until_vcs_server_is_reachable(server)
189 189
190 190
191 191 def _wait_until_vcs_server_is_reachable(server):
192 192 while xrange(80): # max 40s of sleep
193 193 try:
194 194 server.ping()
195 195 break
196 196 except (CommunicationError, pycurl.error):
197 197 pass
198 198 time.sleep(0.5)
199 199
200 200
201 201 def _try_to_shutdown_running_server(server_and_port):
202 202 server = create_vcsserver_proxy(server_and_port)
203 203 try:
204 204 server.shutdown()
205 205 except (CommunicationError, pycurl.error):
206 206 return
207 207
208 208 # TODO: Not sure why this is important, but without it the following start
209 209 # of the server fails.
210 210 server = create_vcsserver_proxy(server_and_port)
211 211 server.ping()
212 212
213 213
214 214 def create_vcsserver_proxy(server_and_port, protocol='pyro4'):
215 215 if protocol == 'pyro4':
216 216 return _create_vcsserver_proxy_pyro4(server_and_port)
217 217 elif protocol == 'http':
218 218 return _create_vcsserver_proxy_http(server_and_port)
219 219
220 220
221 221 def _create_vcsserver_proxy_pyro4(server_and_port):
222 222 server = Pyro4.Proxy(
223 223 settings.pyro_remote(settings.PYRO_VCSSERVER, server_and_port))
224 224 return server
225 225
226 226
227 227 def _create_vcsserver_proxy_http(server_and_port):
228 228 from rhodecode.lib.vcs import client_http
229 229
230 230 session = _create_http_rpc_session()
231 231 url = urlparse.urljoin('http://%s' % server_and_port, '/server')
232 232 return client_http.RemoteObject(url, session)
233 233
234 234
235 235 class CurlSession(object):
236 236 """
237 237 Modeled so that it provides a subset of the requests interface.
238 238
239 239 This has been created so that it does only provide a minimal API for our
240 240 needs. The parts which it provides are based on the API of the library
241 241 `requests` which allows us to easily benchmark against it.
242 242
243 243 Please have a look at the class :class:`requests.Session` when you extend
244 244 it.
245 245 """
246 246
247 247 def __init__(self):
248 248 curl = pycurl.Curl()
249 249 # TODO: johbo: I did test with 7.19 of libcurl. This version has
250 250 # trouble with 100 - continue being set in the expect header. This
251 251 # can lead to massive performance drops, switching it off here.
252 252 curl.setopt(curl.HTTPHEADER, ["Expect:"])
253 253 curl.setopt(curl.TCP_NODELAY, True)
254 254 curl.setopt(curl.PROTOCOLS, curl.PROTO_HTTP)
255 255 self._curl = curl
256 256
257 257 def post(self, url, data, allow_redirects=False):
258 258 response_buffer = StringIO()
259 259
260 260 curl = self._curl
261 261 curl.setopt(curl.URL, url)
262 262 curl.setopt(curl.POST, True)
263 263 curl.setopt(curl.POSTFIELDS, data)
264 264 curl.setopt(curl.FOLLOWLOCATION, allow_redirects)
265 265 curl.setopt(curl.WRITEDATA, response_buffer)
266 266 curl.perform()
267 267
268 268 return CurlResponse(response_buffer)
269 269
270 270
271 271 class CurlResponse(object):
272 272 """
273 273 The response of a request, modeled after the requests API.
274 274
275 275 This class provides a subset of the response interface known from the
276 276 library `requests`. It is intentionally kept similar, so that we can use
277 277 `requests` as a drop in replacement for benchmarking purposes.
278 278 """
279 279
280 280 def __init__(self, response_buffer):
281 281 self._response_buffer = response_buffer
282 282
283 283 @property
284 284 def content(self):
285 285 return self._response_buffer.getvalue()
286 286
287 287
288 288 def _create_http_rpc_session():
289 289 session = CurlSession()
290 290 return session
@@ -1,79 +1,78 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 VCS Backends module
23 23 """
24 24
25 import os
25 import logging
26
26 27 from pprint import pformat
27 28
28 29 from rhodecode.lib.vcs.conf import settings
29 30 from rhodecode.lib.vcs.exceptions import VCSError
30 31 from rhodecode.lib.vcs.utils.helpers import get_scm
31 32 from rhodecode.lib.vcs.utils.imports import import_class
32 33
33 34
34 def get_repo(path=None, alias=None, create=False):
35 log = logging.getLogger(__name__)
36
37
38 def get_vcs_instance(repo_path, *args, **kwargs):
35 39 """
36 Returns ``Repository`` object of type linked with given ``alias`` at
37 the specified ``path``. If ``alias`` is not given it will try to guess it
38 using get_scm method
40 Given a path to a repository an instance of the corresponding vcs backend
41 repository class is created and returned. If no repository can be found
42 for the path it returns None. Arguments and keyword arguments are passed
43 to the vcs backend repository class.
39 44 """
40 if create:
41 if not (path or alias):
42 raise TypeError(
43 "If create is specified, we need path and scm type")
44 return get_backend(alias)(path, create=True)
45 if path is None:
46 path = os.path.abspath(os.path.curdir)
47 45 try:
48 scm, path = get_scm(path, search_path_up=True)
49 path = os.path.abspath(path)
50 alias = scm
46 vcs_alias = get_scm(repo_path)[0]
47 log.debug(
48 'Creating instance of %s repository from %s', vcs_alias, repo_path)
49 backend = get_backend(vcs_alias)
51 50 except VCSError:
52 raise VCSError("No scm found at %s" % path)
53 if alias is None:
54 alias = get_scm(path)[0]
51 log.exception(
52 'Perhaps this repository is in db and not in '
53 'filesystem run rescan repositories with '
54 '"destroy old data" option from admin panel')
55 return None
55 56
56 backend = get_backend(alias)
57 repo = backend(path, create=create)
58 return repo
57 return backend(repo_path=repo_path, *args, **kwargs)
59 58
60 59
61 60 def get_backend(alias):
62 61 """
63 62 Returns ``Repository`` class identified by the given alias or raises
64 63 VCSError if alias is not recognized or backend class cannot be imported.
65 64 """
66 65 if alias not in settings.BACKENDS:
67 66 raise VCSError(
68 67 "Given alias '%s' is not recognized! Allowed aliases:\n%s" %
69 68 (alias, pformat(settings.BACKENDS.keys())))
70 69 backend_path = settings.BACKENDS[alias]
71 70 klass = import_class(backend_path)
72 71 return klass
73 72
74 73
75 74 def get_supported_backends():
76 75 """
77 76 Returns list of aliases of supported backends.
78 77 """
79 78 return settings.BACKENDS.keys()
General Comments 0
You need to be logged in to leave comments. Login now