##// END OF EJS Templates
deps: bumped msgpack to latest release and by default use byte only protocol
super-admin -
r1069:e378f012 python3
parent child Browse files
Show More
@@ -1,53 +1,53 b''
1 # deps, generated via pipdeptree --exclude setuptools,wheel,pipdeptree,pip -f | tr '[:upper:]' '[:lower:]'
1 # deps, generated via pipdeptree --exclude setuptools,wheel,pipdeptree,pip -f | tr '[:upper:]' '[:lower:]'
2
2
3 atomicwrites==1.4.1
3 atomicwrites==1.4.1
4 contextlib2==21.6.0
4 contextlib2==21.6.0
5 cov-core==1.15.0
5 cov-core==1.15.0
6 coverage==7.2.1
6 coverage==7.2.1
7 dogpile.cache==1.1.8
7 dogpile.cache==1.1.8
8 decorator==5.1.1
8 decorator==5.1.1
9 stevedore==5.0.0
9 stevedore==5.0.0
10 pbr==5.11.1
10 pbr==5.11.1
11 dulwich==0.21.3
11 dulwich==0.21.3
12 urllib3==1.26.14
12 urllib3==1.26.14
13 gunicorn==20.1.0
13 gunicorn==20.1.0
14 hg-evolve==11.0.0
14 hg-evolve==11.0.0
15 importlib-metadata==6.0.0
15 importlib-metadata==6.0.0
16 zipp==3.15.0
16 zipp==3.15.0
17 mercurial==6.3.3
17 mercurial==6.3.3
18 mock==5.0.1
18 mock==5.0.1
19 more-itertools==9.1.0
19 more-itertools==9.1.0
20 msgpack-python==0.5.6
20 msgpack==1.0.5
21 orjson==3.8.7
21 orjson==3.8.7
22 psutil==5.9.4
22 psutil==5.9.4
23 py==1.11.0
23 py==1.11.0
24 pygit2==1.11.1
24 pygit2==1.11.1
25 cffi==1.15.1
25 cffi==1.15.1
26 pycparser==2.21
26 pycparser==2.21
27 pygments==2.14.0
27 pygments==2.14.0
28 pyparsing==3.0.9
28 pyparsing==3.0.9
29 pyramid==2.0.1
29 pyramid==2.0.1
30 hupper==1.11
30 hupper==1.11
31 plaster==1.1.2
31 plaster==1.1.2
32 plaster-pastedeploy==1.0.1
32 plaster-pastedeploy==1.0.1
33 pastedeploy==3.0.1
33 pastedeploy==3.0.1
34 plaster==1.1.2
34 plaster==1.1.2
35 translationstring==1.4
35 translationstring==1.4
36 venusian==3.0.0
36 venusian==3.0.0
37 webob==1.8.7
37 webob==1.8.7
38 zope.deprecation==4.4.0
38 zope.deprecation==4.4.0
39 zope.interface==5.5.2
39 zope.interface==5.5.2
40 redis==4.5.1
40 redis==4.5.1
41 async-timeout==4.0.2
41 async-timeout==4.0.2
42 repoze.lru==0.7
42 repoze.lru==0.7
43 scandir==1.10.0
43 scandir==1.10.0
44 setproctitle==1.3.2
44 setproctitle==1.3.2
45 subvertpy==0.11.0
45 subvertpy==0.11.0
46 wcwidth==0.2.6
46 wcwidth==0.2.6
47
47
48
48
49 ## test related requirements
49 ## test related requirements
50 -r requirements_test.txt
50 -r requirements_test.txt
51
51
52 ## uncomment to add the debug libraries
52 ## uncomment to add the debug libraries
53 #-r requirements_debug.txt
53 #-r requirements_debug.txt
@@ -1,738 +1,738 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # RhodeCode VCSServer provides access to different vcs backends via network.
3 # RhodeCode VCSServer provides access to different vcs backends via network.
4 # Copyright (C) 2014-2020 RhodeCode GmbH
4 # Copyright (C) 2014-2020 RhodeCode GmbH
5 #
5 #
6 # This program is free software; you can redistribute it and/or modify
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
9 # (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software Foundation,
17 # along with this program; if not, write to the Free Software Foundation,
18 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
19
20 import io
20 import io
21 import os
21 import os
22 import sys
22 import sys
23 import logging
23 import logging
24 import collections
24 import collections
25 import importlib
25 import importlib
26 import base64
26 import base64
27 import msgpack
27 import msgpack
28
28
29 from http.client import HTTPConnection
29 from http.client import HTTPConnection
30
30
31
31
32 import mercurial.scmutil
32 import mercurial.scmutil
33 import mercurial.node
33 import mercurial.node
34
34
35 from vcsserver.lib.rc_json import json
35 from vcsserver.lib.rc_json import json
36 from vcsserver import exceptions, subprocessio, settings
36 from vcsserver import exceptions, subprocessio, settings
37 from vcsserver.str_utils import safe_bytes
37 from vcsserver.str_utils import safe_bytes
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 class HooksHttpClient(object):
42 class HooksHttpClient(object):
43 proto = 'msgpack.v1'
43 proto = 'msgpack.v1'
44 connection = None
44 connection = None
45
45
46 def __init__(self, hooks_uri):
46 def __init__(self, hooks_uri):
47 self.hooks_uri = hooks_uri
47 self.hooks_uri = hooks_uri
48
48
49 def __call__(self, method, extras):
49 def __call__(self, method, extras):
50 connection = HTTPConnection(self.hooks_uri)
50 connection = HTTPConnection(self.hooks_uri)
51 # binary msgpack body
51 # binary msgpack body
52 headers, body = self._serialize(method, extras)
52 headers, body = self._serialize(method, extras)
53 try:
53 try:
54 connection.request('POST', '/', body, headers)
54 connection.request('POST', '/', body, headers)
55 except Exception as error:
55 except Exception as error:
56 log.error('Hooks calling Connection failed on %s, org error: %s', connection.__dict__, error)
56 log.error('Hooks calling Connection failed on %s, org error: %s', connection.__dict__, error)
57 raise
57 raise
58 response = connection.getresponse()
58 response = connection.getresponse()
59 try:
59 try:
60 return msgpack.load(response, raw=False)
60 return msgpack.load(response)
61 except Exception:
61 except Exception:
62 response_data = response.read()
62 response_data = response.read()
63 log.exception('Failed to decode hook response json data. '
63 log.exception('Failed to decode hook response json data. '
64 'response_code:%s, raw_data:%s',
64 'response_code:%s, raw_data:%s',
65 response.status, response_data)
65 response.status, response_data)
66 raise
66 raise
67
67
68 @classmethod
68 @classmethod
69 def _serialize(cls, hook_name, extras):
69 def _serialize(cls, hook_name, extras):
70 data = {
70 data = {
71 'method': hook_name,
71 'method': hook_name,
72 'extras': extras
72 'extras': extras
73 }
73 }
74 headers = {
74 headers = {
75 'rc-hooks-protocol': cls.proto
75 'rc-hooks-protocol': cls.proto
76 }
76 }
77 return headers, msgpack.packb(data)
77 return headers, msgpack.packb(data)
78
78
79
79
80 class HooksDummyClient(object):
80 class HooksDummyClient(object):
81 def __init__(self, hooks_module):
81 def __init__(self, hooks_module):
82 self._hooks_module = importlib.import_module(hooks_module)
82 self._hooks_module = importlib.import_module(hooks_module)
83
83
84 def __call__(self, hook_name, extras):
84 def __call__(self, hook_name, extras):
85 with self._hooks_module.Hooks() as hooks:
85 with self._hooks_module.Hooks() as hooks:
86 return getattr(hooks, hook_name)(extras)
86 return getattr(hooks, hook_name)(extras)
87
87
88
88
89 class HooksShadowRepoClient(object):
89 class HooksShadowRepoClient(object):
90
90
91 def __call__(self, hook_name, extras):
91 def __call__(self, hook_name, extras):
92 return {'output': '', 'status': 0}
92 return {'output': '', 'status': 0}
93
93
94
94
95 class RemoteMessageWriter(object):
95 class RemoteMessageWriter(object):
96 """Writer base class."""
96 """Writer base class."""
97 def write(self, message):
97 def write(self, message):
98 raise NotImplementedError()
98 raise NotImplementedError()
99
99
100
100
101 class HgMessageWriter(RemoteMessageWriter):
101 class HgMessageWriter(RemoteMessageWriter):
102 """Writer that knows how to send messages to mercurial clients."""
102 """Writer that knows how to send messages to mercurial clients."""
103
103
104 def __init__(self, ui):
104 def __init__(self, ui):
105 self.ui = ui
105 self.ui = ui
106
106
107 def write(self, message):
107 def write(self, message):
108 # TODO: Check why the quiet flag is set by default.
108 # TODO: Check why the quiet flag is set by default.
109 old = self.ui.quiet
109 old = self.ui.quiet
110 self.ui.quiet = False
110 self.ui.quiet = False
111 self.ui.status(message.encode('utf-8'))
111 self.ui.status(message.encode('utf-8'))
112 self.ui.quiet = old
112 self.ui.quiet = old
113
113
114
114
115 class GitMessageWriter(RemoteMessageWriter):
115 class GitMessageWriter(RemoteMessageWriter):
116 """Writer that knows how to send messages to git clients."""
116 """Writer that knows how to send messages to git clients."""
117
117
118 def __init__(self, stdout=None):
118 def __init__(self, stdout=None):
119 self.stdout = stdout or sys.stdout
119 self.stdout = stdout or sys.stdout
120
120
121 def write(self, message):
121 def write(self, message):
122 self.stdout.write(safe_bytes(message))
122 self.stdout.write(safe_bytes(message))
123
123
124
124
125 class SvnMessageWriter(RemoteMessageWriter):
125 class SvnMessageWriter(RemoteMessageWriter):
126 """Writer that knows how to send messages to svn clients."""
126 """Writer that knows how to send messages to svn clients."""
127
127
128 def __init__(self, stderr=None):
128 def __init__(self, stderr=None):
129 # SVN needs data sent to stderr for back-to-client messaging
129 # SVN needs data sent to stderr for back-to-client messaging
130 self.stderr = stderr or sys.stderr
130 self.stderr = stderr or sys.stderr
131
131
132 def write(self, message):
132 def write(self, message):
133 self.stderr.write(message.encode('utf-8'))
133 self.stderr.write(message.encode('utf-8'))
134
134
135
135
136 def _handle_exception(result):
136 def _handle_exception(result):
137 exception_class = result.get('exception')
137 exception_class = result.get('exception')
138 exception_traceback = result.get('exception_traceback')
138 exception_traceback = result.get('exception_traceback')
139
139
140 if exception_traceback:
140 if exception_traceback:
141 log.error('Got traceback from remote call:%s', exception_traceback)
141 log.error('Got traceback from remote call:%s', exception_traceback)
142
142
143 if exception_class == 'HTTPLockedRC':
143 if exception_class == 'HTTPLockedRC':
144 raise exceptions.RepositoryLockedException()(*result['exception_args'])
144 raise exceptions.RepositoryLockedException()(*result['exception_args'])
145 elif exception_class == 'HTTPBranchProtected':
145 elif exception_class == 'HTTPBranchProtected':
146 raise exceptions.RepositoryBranchProtectedException()(*result['exception_args'])
146 raise exceptions.RepositoryBranchProtectedException()(*result['exception_args'])
147 elif exception_class == 'RepositoryError':
147 elif exception_class == 'RepositoryError':
148 raise exceptions.VcsException()(*result['exception_args'])
148 raise exceptions.VcsException()(*result['exception_args'])
149 elif exception_class:
149 elif exception_class:
150 raise Exception('Got remote exception "%s" with args "%s"' %
150 raise Exception('Got remote exception "%s" with args "%s"' %
151 (exception_class, result['exception_args']))
151 (exception_class, result['exception_args']))
152
152
153
153
154 def _get_hooks_client(extras):
154 def _get_hooks_client(extras):
155 hooks_uri = extras.get('hooks_uri')
155 hooks_uri = extras.get('hooks_uri')
156 is_shadow_repo = extras.get('is_shadow_repo')
156 is_shadow_repo = extras.get('is_shadow_repo')
157 if hooks_uri:
157 if hooks_uri:
158 return HooksHttpClient(extras['hooks_uri'])
158 return HooksHttpClient(extras['hooks_uri'])
159 elif is_shadow_repo:
159 elif is_shadow_repo:
160 return HooksShadowRepoClient()
160 return HooksShadowRepoClient()
161 else:
161 else:
162 return HooksDummyClient(extras['hooks_module'])
162 return HooksDummyClient(extras['hooks_module'])
163
163
164
164
165 def _call_hook(hook_name, extras, writer):
165 def _call_hook(hook_name, extras, writer):
166 hooks_client = _get_hooks_client(extras)
166 hooks_client = _get_hooks_client(extras)
167 log.debug('Hooks, using client:%s', hooks_client)
167 log.debug('Hooks, using client:%s', hooks_client)
168 result = hooks_client(hook_name, extras)
168 result = hooks_client(hook_name, extras)
169 log.debug('Hooks got result: %s', result)
169 log.debug('Hooks got result: %s', result)
170
170
171 _handle_exception(result)
171 _handle_exception(result)
172 writer.write(result['output'])
172 writer.write(result['output'])
173
173
174 return result['status']
174 return result['status']
175
175
176
176
177 def _extras_from_ui(ui):
177 def _extras_from_ui(ui):
178 hook_data = ui.config(b'rhodecode', b'RC_SCM_DATA')
178 hook_data = ui.config(b'rhodecode', b'RC_SCM_DATA')
179 if not hook_data:
179 if not hook_data:
180 # maybe it's inside environ ?
180 # maybe it's inside environ ?
181 env_hook_data = os.environ.get('RC_SCM_DATA')
181 env_hook_data = os.environ.get('RC_SCM_DATA')
182 if env_hook_data:
182 if env_hook_data:
183 hook_data = env_hook_data
183 hook_data = env_hook_data
184
184
185 extras = {}
185 extras = {}
186 if hook_data:
186 if hook_data:
187 extras = json.loads(hook_data)
187 extras = json.loads(hook_data)
188 return extras
188 return extras
189
189
190
190
191 def _rev_range_hash(repo, node, check_heads=False):
191 def _rev_range_hash(repo, node, check_heads=False):
192 from vcsserver.hgcompat import get_ctx
192 from vcsserver.hgcompat import get_ctx
193
193
194 commits = []
194 commits = []
195 revs = []
195 revs = []
196 start = get_ctx(repo, node).rev()
196 start = get_ctx(repo, node).rev()
197 end = len(repo)
197 end = len(repo)
198 for rev in range(start, end):
198 for rev in range(start, end):
199 revs.append(rev)
199 revs.append(rev)
200 ctx = get_ctx(repo, rev)
200 ctx = get_ctx(repo, rev)
201 commit_id = mercurial.node.hex(ctx.node())
201 commit_id = mercurial.node.hex(ctx.node())
202 branch = ctx.branch()
202 branch = ctx.branch()
203 commits.append((commit_id, branch))
203 commits.append((commit_id, branch))
204
204
205 parent_heads = []
205 parent_heads = []
206 if check_heads:
206 if check_heads:
207 parent_heads = _check_heads(repo, start, end, revs)
207 parent_heads = _check_heads(repo, start, end, revs)
208 return commits, parent_heads
208 return commits, parent_heads
209
209
210
210
211 def _check_heads(repo, start, end, commits):
211 def _check_heads(repo, start, end, commits):
212 from vcsserver.hgcompat import get_ctx
212 from vcsserver.hgcompat import get_ctx
213 changelog = repo.changelog
213 changelog = repo.changelog
214 parents = set()
214 parents = set()
215
215
216 for new_rev in commits:
216 for new_rev in commits:
217 for p in changelog.parentrevs(new_rev):
217 for p in changelog.parentrevs(new_rev):
218 if p == mercurial.node.nullrev:
218 if p == mercurial.node.nullrev:
219 continue
219 continue
220 if p < start:
220 if p < start:
221 parents.add(p)
221 parents.add(p)
222
222
223 for p in parents:
223 for p in parents:
224 branch = get_ctx(repo, p).branch()
224 branch = get_ctx(repo, p).branch()
225 # The heads descending from that parent, on the same branch
225 # The heads descending from that parent, on the same branch
226 parent_heads = set([p])
226 parent_heads = set([p])
227 reachable = set([p])
227 reachable = set([p])
228 for x in range(p + 1, end):
228 for x in range(p + 1, end):
229 if get_ctx(repo, x).branch() != branch:
229 if get_ctx(repo, x).branch() != branch:
230 continue
230 continue
231 for pp in changelog.parentrevs(x):
231 for pp in changelog.parentrevs(x):
232 if pp in reachable:
232 if pp in reachable:
233 reachable.add(x)
233 reachable.add(x)
234 parent_heads.discard(pp)
234 parent_heads.discard(pp)
235 parent_heads.add(x)
235 parent_heads.add(x)
236 # More than one head? Suggest merging
236 # More than one head? Suggest merging
237 if len(parent_heads) > 1:
237 if len(parent_heads) > 1:
238 return list(parent_heads)
238 return list(parent_heads)
239
239
240 return []
240 return []
241
241
242
242
243 def _get_git_env():
243 def _get_git_env():
244 env = {}
244 env = {}
245 for k, v in os.environ.items():
245 for k, v in os.environ.items():
246 if k.startswith('GIT'):
246 if k.startswith('GIT'):
247 env[k] = v
247 env[k] = v
248
248
249 # serialized version
249 # serialized version
250 return [(k, v) for k, v in env.items()]
250 return [(k, v) for k, v in env.items()]
251
251
252
252
253 def _get_hg_env(old_rev, new_rev, txnid, repo_path):
253 def _get_hg_env(old_rev, new_rev, txnid, repo_path):
254 env = {}
254 env = {}
255 for k, v in os.environ.items():
255 for k, v in os.environ.items():
256 if k.startswith('HG'):
256 if k.startswith('HG'):
257 env[k] = v
257 env[k] = v
258
258
259 env['HG_NODE'] = old_rev
259 env['HG_NODE'] = old_rev
260 env['HG_NODE_LAST'] = new_rev
260 env['HG_NODE_LAST'] = new_rev
261 env['HG_TXNID'] = txnid
261 env['HG_TXNID'] = txnid
262 env['HG_PENDING'] = repo_path
262 env['HG_PENDING'] = repo_path
263
263
264 return [(k, v) for k, v in env.items()]
264 return [(k, v) for k, v in env.items()]
265
265
266
266
267 def repo_size(ui, repo, **kwargs):
267 def repo_size(ui, repo, **kwargs):
268 extras = _extras_from_ui(ui)
268 extras = _extras_from_ui(ui)
269 return _call_hook('repo_size', extras, HgMessageWriter(ui))
269 return _call_hook('repo_size', extras, HgMessageWriter(ui))
270
270
271
271
272 def pre_pull(ui, repo, **kwargs):
272 def pre_pull(ui, repo, **kwargs):
273 extras = _extras_from_ui(ui)
273 extras = _extras_from_ui(ui)
274 return _call_hook('pre_pull', extras, HgMessageWriter(ui))
274 return _call_hook('pre_pull', extras, HgMessageWriter(ui))
275
275
276
276
277 def pre_pull_ssh(ui, repo, **kwargs):
277 def pre_pull_ssh(ui, repo, **kwargs):
278 extras = _extras_from_ui(ui)
278 extras = _extras_from_ui(ui)
279 if extras and extras.get('SSH'):
279 if extras and extras.get('SSH'):
280 return pre_pull(ui, repo, **kwargs)
280 return pre_pull(ui, repo, **kwargs)
281 return 0
281 return 0
282
282
283
283
284 def post_pull(ui, repo, **kwargs):
284 def post_pull(ui, repo, **kwargs):
285 extras = _extras_from_ui(ui)
285 extras = _extras_from_ui(ui)
286 return _call_hook('post_pull', extras, HgMessageWriter(ui))
286 return _call_hook('post_pull', extras, HgMessageWriter(ui))
287
287
288
288
289 def post_pull_ssh(ui, repo, **kwargs):
289 def post_pull_ssh(ui, repo, **kwargs):
290 extras = _extras_from_ui(ui)
290 extras = _extras_from_ui(ui)
291 if extras and extras.get('SSH'):
291 if extras and extras.get('SSH'):
292 return post_pull(ui, repo, **kwargs)
292 return post_pull(ui, repo, **kwargs)
293 return 0
293 return 0
294
294
295
295
296 def pre_push(ui, repo, node=None, **kwargs):
296 def pre_push(ui, repo, node=None, **kwargs):
297 """
297 """
298 Mercurial pre_push hook
298 Mercurial pre_push hook
299 """
299 """
300 extras = _extras_from_ui(ui)
300 extras = _extras_from_ui(ui)
301 detect_force_push = extras.get('detect_force_push')
301 detect_force_push = extras.get('detect_force_push')
302
302
303 rev_data = []
303 rev_data = []
304 if node and kwargs.get('hooktype') == 'pretxnchangegroup':
304 if node and kwargs.get('hooktype') == 'pretxnchangegroup':
305 branches = collections.defaultdict(list)
305 branches = collections.defaultdict(list)
306 commits, _heads = _rev_range_hash(repo, node, check_heads=detect_force_push)
306 commits, _heads = _rev_range_hash(repo, node, check_heads=detect_force_push)
307 for commit_id, branch in commits:
307 for commit_id, branch in commits:
308 branches[branch].append(commit_id)
308 branches[branch].append(commit_id)
309
309
310 for branch, commits in branches.items():
310 for branch, commits in branches.items():
311 old_rev = kwargs.get('node_last') or commits[0]
311 old_rev = kwargs.get('node_last') or commits[0]
312 rev_data.append({
312 rev_data.append({
313 'total_commits': len(commits),
313 'total_commits': len(commits),
314 'old_rev': old_rev,
314 'old_rev': old_rev,
315 'new_rev': commits[-1],
315 'new_rev': commits[-1],
316 'ref': '',
316 'ref': '',
317 'type': 'branch',
317 'type': 'branch',
318 'name': branch,
318 'name': branch,
319 })
319 })
320
320
321 for push_ref in rev_data:
321 for push_ref in rev_data:
322 push_ref['multiple_heads'] = _heads
322 push_ref['multiple_heads'] = _heads
323
323
324 repo_path = os.path.join(
324 repo_path = os.path.join(
325 extras.get('repo_store', ''), extras.get('repository', ''))
325 extras.get('repo_store', ''), extras.get('repository', ''))
326 push_ref['hg_env'] = _get_hg_env(
326 push_ref['hg_env'] = _get_hg_env(
327 old_rev=push_ref['old_rev'],
327 old_rev=push_ref['old_rev'],
328 new_rev=push_ref['new_rev'], txnid=kwargs.get('txnid'),
328 new_rev=push_ref['new_rev'], txnid=kwargs.get('txnid'),
329 repo_path=repo_path)
329 repo_path=repo_path)
330
330
331 extras['hook_type'] = kwargs.get('hooktype', 'pre_push')
331 extras['hook_type'] = kwargs.get('hooktype', 'pre_push')
332 extras['commit_ids'] = rev_data
332 extras['commit_ids'] = rev_data
333
333
334 return _call_hook('pre_push', extras, HgMessageWriter(ui))
334 return _call_hook('pre_push', extras, HgMessageWriter(ui))
335
335
336
336
337 def pre_push_ssh(ui, repo, node=None, **kwargs):
337 def pre_push_ssh(ui, repo, node=None, **kwargs):
338 extras = _extras_from_ui(ui)
338 extras = _extras_from_ui(ui)
339 if extras.get('SSH'):
339 if extras.get('SSH'):
340 return pre_push(ui, repo, node, **kwargs)
340 return pre_push(ui, repo, node, **kwargs)
341
341
342 return 0
342 return 0
343
343
344
344
345 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
345 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
346 """
346 """
347 Mercurial pre_push hook for SSH
347 Mercurial pre_push hook for SSH
348 """
348 """
349 extras = _extras_from_ui(ui)
349 extras = _extras_from_ui(ui)
350 if extras.get('SSH'):
350 if extras.get('SSH'):
351 permission = extras['SSH_PERMISSIONS']
351 permission = extras['SSH_PERMISSIONS']
352
352
353 if 'repository.write' == permission or 'repository.admin' == permission:
353 if 'repository.write' == permission or 'repository.admin' == permission:
354 return 0
354 return 0
355
355
356 # non-zero ret code
356 # non-zero ret code
357 return 1
357 return 1
358
358
359 return 0
359 return 0
360
360
361
361
362 def post_push(ui, repo, node, **kwargs):
362 def post_push(ui, repo, node, **kwargs):
363 """
363 """
364 Mercurial post_push hook
364 Mercurial post_push hook
365 """
365 """
366 extras = _extras_from_ui(ui)
366 extras = _extras_from_ui(ui)
367
367
368 commit_ids = []
368 commit_ids = []
369 branches = []
369 branches = []
370 bookmarks = []
370 bookmarks = []
371 tags = []
371 tags = []
372
372
373 commits, _heads = _rev_range_hash(repo, node)
373 commits, _heads = _rev_range_hash(repo, node)
374 for commit_id, branch in commits:
374 for commit_id, branch in commits:
375 commit_ids.append(commit_id)
375 commit_ids.append(commit_id)
376 if branch not in branches:
376 if branch not in branches:
377 branches.append(branch)
377 branches.append(branch)
378
378
379 if hasattr(ui, '_rc_pushkey_branches'):
379 if hasattr(ui, '_rc_pushkey_branches'):
380 bookmarks = ui._rc_pushkey_branches
380 bookmarks = ui._rc_pushkey_branches
381
381
382 extras['hook_type'] = kwargs.get('hooktype', 'post_push')
382 extras['hook_type'] = kwargs.get('hooktype', 'post_push')
383 extras['commit_ids'] = commit_ids
383 extras['commit_ids'] = commit_ids
384 extras['new_refs'] = {
384 extras['new_refs'] = {
385 'branches': branches,
385 'branches': branches,
386 'bookmarks': bookmarks,
386 'bookmarks': bookmarks,
387 'tags': tags
387 'tags': tags
388 }
388 }
389
389
390 return _call_hook('post_push', extras, HgMessageWriter(ui))
390 return _call_hook('post_push', extras, HgMessageWriter(ui))
391
391
392
392
393 def post_push_ssh(ui, repo, node, **kwargs):
393 def post_push_ssh(ui, repo, node, **kwargs):
394 """
394 """
395 Mercurial post_push hook for SSH
395 Mercurial post_push hook for SSH
396 """
396 """
397 if _extras_from_ui(ui).get('SSH'):
397 if _extras_from_ui(ui).get('SSH'):
398 return post_push(ui, repo, node, **kwargs)
398 return post_push(ui, repo, node, **kwargs)
399 return 0
399 return 0
400
400
401
401
402 def key_push(ui, repo, **kwargs):
402 def key_push(ui, repo, **kwargs):
403 from vcsserver.hgcompat import get_ctx
403 from vcsserver.hgcompat import get_ctx
404 if kwargs['new'] != '0' and kwargs['namespace'] == 'bookmarks':
404 if kwargs['new'] != '0' and kwargs['namespace'] == 'bookmarks':
405 # store new bookmarks in our UI object propagated later to post_push
405 # store new bookmarks in our UI object propagated later to post_push
406 ui._rc_pushkey_branches = get_ctx(repo, kwargs['key']).bookmarks()
406 ui._rc_pushkey_branches = get_ctx(repo, kwargs['key']).bookmarks()
407 return
407 return
408
408
409
409
410 # backward compat
410 # backward compat
411 log_pull_action = post_pull
411 log_pull_action = post_pull
412
412
413 # backward compat
413 # backward compat
414 log_push_action = post_push
414 log_push_action = post_push
415
415
416
416
417 def handle_git_pre_receive(unused_repo_path, unused_revs, unused_env):
417 def handle_git_pre_receive(unused_repo_path, unused_revs, unused_env):
418 """
418 """
419 Old hook name: keep here for backward compatibility.
419 Old hook name: keep here for backward compatibility.
420
420
421 This is only required when the installed git hooks are not upgraded.
421 This is only required when the installed git hooks are not upgraded.
422 """
422 """
423 pass
423 pass
424
424
425
425
426 def handle_git_post_receive(unused_repo_path, unused_revs, unused_env):
426 def handle_git_post_receive(unused_repo_path, unused_revs, unused_env):
427 """
427 """
428 Old hook name: keep here for backward compatibility.
428 Old hook name: keep here for backward compatibility.
429
429
430 This is only required when the installed git hooks are not upgraded.
430 This is only required when the installed git hooks are not upgraded.
431 """
431 """
432 pass
432 pass
433
433
434
434
435 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
435 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
436
436
437
437
438 def git_pre_pull(extras):
438 def git_pre_pull(extras):
439 """
439 """
440 Pre pull hook.
440 Pre pull hook.
441
441
442 :param extras: dictionary containing the keys defined in simplevcs
442 :param extras: dictionary containing the keys defined in simplevcs
443 :type extras: dict
443 :type extras: dict
444
444
445 :return: status code of the hook. 0 for success.
445 :return: status code of the hook. 0 for success.
446 :rtype: int
446 :rtype: int
447 """
447 """
448
448
449 if 'pull' not in extras['hooks']:
449 if 'pull' not in extras['hooks']:
450 return HookResponse(0, '')
450 return HookResponse(0, '')
451
451
452 stdout = io.BytesIO()
452 stdout = io.BytesIO()
453 try:
453 try:
454 status = _call_hook('pre_pull', extras, GitMessageWriter(stdout))
454 status = _call_hook('pre_pull', extras, GitMessageWriter(stdout))
455
455
456 except Exception as error:
456 except Exception as error:
457 log.exception('Failed to call pre_pull hook')
457 log.exception('Failed to call pre_pull hook')
458 status = 128
458 status = 128
459 stdout.write(safe_bytes(f'ERROR: {error}\n'))
459 stdout.write(safe_bytes(f'ERROR: {error}\n'))
460
460
461 return HookResponse(status, stdout.getvalue())
461 return HookResponse(status, stdout.getvalue())
462
462
463
463
464 def git_post_pull(extras):
464 def git_post_pull(extras):
465 """
465 """
466 Post pull hook.
466 Post pull hook.
467
467
468 :param extras: dictionary containing the keys defined in simplevcs
468 :param extras: dictionary containing the keys defined in simplevcs
469 :type extras: dict
469 :type extras: dict
470
470
471 :return: status code of the hook. 0 for success.
471 :return: status code of the hook. 0 for success.
472 :rtype: int
472 :rtype: int
473 """
473 """
474 if 'pull' not in extras['hooks']:
474 if 'pull' not in extras['hooks']:
475 return HookResponse(0, '')
475 return HookResponse(0, '')
476
476
477 stdout = io.BytesIO()
477 stdout = io.BytesIO()
478 try:
478 try:
479 status = _call_hook('post_pull', extras, GitMessageWriter(stdout))
479 status = _call_hook('post_pull', extras, GitMessageWriter(stdout))
480 except Exception as error:
480 except Exception as error:
481 status = 128
481 status = 128
482 stdout.write(safe_bytes(f'ERROR: {error}\n'))
482 stdout.write(safe_bytes(f'ERROR: {error}\n'))
483
483
484 return HookResponse(status, stdout.getvalue())
484 return HookResponse(status, stdout.getvalue())
485
485
486
486
487 def _parse_git_ref_lines(revision_lines):
487 def _parse_git_ref_lines(revision_lines):
488 rev_data = []
488 rev_data = []
489 for revision_line in revision_lines or []:
489 for revision_line in revision_lines or []:
490 old_rev, new_rev, ref = revision_line.strip().split(' ')
490 old_rev, new_rev, ref = revision_line.strip().split(' ')
491 ref_data = ref.split('/', 2)
491 ref_data = ref.split('/', 2)
492 if ref_data[1] in ('tags', 'heads'):
492 if ref_data[1] in ('tags', 'heads'):
493 rev_data.append({
493 rev_data.append({
494 # NOTE(marcink):
494 # NOTE(marcink):
495 # we're unable to tell total_commits for git at this point
495 # we're unable to tell total_commits for git at this point
496 # but we set the variable for consistency with GIT
496 # but we set the variable for consistency with GIT
497 'total_commits': -1,
497 'total_commits': -1,
498 'old_rev': old_rev,
498 'old_rev': old_rev,
499 'new_rev': new_rev,
499 'new_rev': new_rev,
500 'ref': ref,
500 'ref': ref,
501 'type': ref_data[1],
501 'type': ref_data[1],
502 'name': ref_data[2],
502 'name': ref_data[2],
503 })
503 })
504 return rev_data
504 return rev_data
505
505
506
506
507 def git_pre_receive(unused_repo_path, revision_lines, env):
507 def git_pre_receive(unused_repo_path, revision_lines, env):
508 """
508 """
509 Pre push hook.
509 Pre push hook.
510
510
511 :param extras: dictionary containing the keys defined in simplevcs
511 :param extras: dictionary containing the keys defined in simplevcs
512 :type extras: dict
512 :type extras: dict
513
513
514 :return: status code of the hook. 0 for success.
514 :return: status code of the hook. 0 for success.
515 :rtype: int
515 :rtype: int
516 """
516 """
517 extras = json.loads(env['RC_SCM_DATA'])
517 extras = json.loads(env['RC_SCM_DATA'])
518 rev_data = _parse_git_ref_lines(revision_lines)
518 rev_data = _parse_git_ref_lines(revision_lines)
519 if 'push' not in extras['hooks']:
519 if 'push' not in extras['hooks']:
520 return 0
520 return 0
521 empty_commit_id = '0' * 40
521 empty_commit_id = '0' * 40
522
522
523 detect_force_push = extras.get('detect_force_push')
523 detect_force_push = extras.get('detect_force_push')
524
524
525 for push_ref in rev_data:
525 for push_ref in rev_data:
526 # store our git-env which holds the temp store
526 # store our git-env which holds the temp store
527 push_ref['git_env'] = _get_git_env()
527 push_ref['git_env'] = _get_git_env()
528 push_ref['pruned_sha'] = ''
528 push_ref['pruned_sha'] = ''
529 if not detect_force_push:
529 if not detect_force_push:
530 # don't check for forced-push when we don't need to
530 # don't check for forced-push when we don't need to
531 continue
531 continue
532
532
533 type_ = push_ref['type']
533 type_ = push_ref['type']
534 new_branch = push_ref['old_rev'] == empty_commit_id
534 new_branch = push_ref['old_rev'] == empty_commit_id
535 delete_branch = push_ref['new_rev'] == empty_commit_id
535 delete_branch = push_ref['new_rev'] == empty_commit_id
536 if type_ == 'heads' and not (new_branch or delete_branch):
536 if type_ == 'heads' and not (new_branch or delete_branch):
537 old_rev = push_ref['old_rev']
537 old_rev = push_ref['old_rev']
538 new_rev = push_ref['new_rev']
538 new_rev = push_ref['new_rev']
539 cmd = [settings.GIT_EXECUTABLE, 'rev-list', old_rev, '^{}'.format(new_rev)]
539 cmd = [settings.GIT_EXECUTABLE, 'rev-list', old_rev, '^{}'.format(new_rev)]
540 stdout, stderr = subprocessio.run_command(
540 stdout, stderr = subprocessio.run_command(
541 cmd, env=os.environ.copy())
541 cmd, env=os.environ.copy())
542 # means we're having some non-reachable objects, this forced push was used
542 # means we're having some non-reachable objects, this forced push was used
543 if stdout:
543 if stdout:
544 push_ref['pruned_sha'] = stdout.splitlines()
544 push_ref['pruned_sha'] = stdout.splitlines()
545
545
546 extras['hook_type'] = 'pre_receive'
546 extras['hook_type'] = 'pre_receive'
547 extras['commit_ids'] = rev_data
547 extras['commit_ids'] = rev_data
548 return _call_hook('pre_push', extras, GitMessageWriter())
548 return _call_hook('pre_push', extras, GitMessageWriter())
549
549
550
550
551 def git_post_receive(unused_repo_path, revision_lines, env):
551 def git_post_receive(unused_repo_path, revision_lines, env):
552 """
552 """
553 Post push hook.
553 Post push hook.
554
554
555 :param extras: dictionary containing the keys defined in simplevcs
555 :param extras: dictionary containing the keys defined in simplevcs
556 :type extras: dict
556 :type extras: dict
557
557
558 :return: status code of the hook. 0 for success.
558 :return: status code of the hook. 0 for success.
559 :rtype: int
559 :rtype: int
560 """
560 """
561 extras = json.loads(env['RC_SCM_DATA'])
561 extras = json.loads(env['RC_SCM_DATA'])
562 if 'push' not in extras['hooks']:
562 if 'push' not in extras['hooks']:
563 return 0
563 return 0
564
564
565 rev_data = _parse_git_ref_lines(revision_lines)
565 rev_data = _parse_git_ref_lines(revision_lines)
566
566
567 git_revs = []
567 git_revs = []
568
568
569 # N.B.(skreft): it is ok to just call git, as git before calling a
569 # N.B.(skreft): it is ok to just call git, as git before calling a
570 # subcommand sets the PATH environment variable so that it point to the
570 # subcommand sets the PATH environment variable so that it point to the
571 # correct version of the git executable.
571 # correct version of the git executable.
572 empty_commit_id = '0' * 40
572 empty_commit_id = '0' * 40
573 branches = []
573 branches = []
574 tags = []
574 tags = []
575 for push_ref in rev_data:
575 for push_ref in rev_data:
576 type_ = push_ref['type']
576 type_ = push_ref['type']
577
577
578 if type_ == 'heads':
578 if type_ == 'heads':
579 if push_ref['old_rev'] == empty_commit_id:
579 if push_ref['old_rev'] == empty_commit_id:
580 # starting new branch case
580 # starting new branch case
581 if push_ref['name'] not in branches:
581 if push_ref['name'] not in branches:
582 branches.append(push_ref['name'])
582 branches.append(push_ref['name'])
583
583
584 # Fix up head revision if needed
584 # Fix up head revision if needed
585 cmd = [settings.GIT_EXECUTABLE, 'show', 'HEAD']
585 cmd = [settings.GIT_EXECUTABLE, 'show', 'HEAD']
586 try:
586 try:
587 subprocessio.run_command(cmd, env=os.environ.copy())
587 subprocessio.run_command(cmd, env=os.environ.copy())
588 except Exception:
588 except Exception:
589 cmd = [settings.GIT_EXECUTABLE, 'symbolic-ref', '"HEAD"',
589 cmd = [settings.GIT_EXECUTABLE, 'symbolic-ref', '"HEAD"',
590 '"refs/heads/%s"' % push_ref['name']]
590 '"refs/heads/%s"' % push_ref['name']]
591 print(("Setting default branch to %s" % push_ref['name']))
591 print(("Setting default branch to %s" % push_ref['name']))
592 subprocessio.run_command(cmd, env=os.environ.copy())
592 subprocessio.run_command(cmd, env=os.environ.copy())
593
593
594 cmd = [settings.GIT_EXECUTABLE, 'for-each-ref',
594 cmd = [settings.GIT_EXECUTABLE, 'for-each-ref',
595 '--format=%(refname)', 'refs/heads/*']
595 '--format=%(refname)', 'refs/heads/*']
596 stdout, stderr = subprocessio.run_command(
596 stdout, stderr = subprocessio.run_command(
597 cmd, env=os.environ.copy())
597 cmd, env=os.environ.copy())
598 heads = stdout
598 heads = stdout
599 heads = heads.replace(push_ref['ref'], '')
599 heads = heads.replace(push_ref['ref'], '')
600 heads = ' '.join(head for head
600 heads = ' '.join(head for head
601 in heads.splitlines() if head) or '.'
601 in heads.splitlines() if head) or '.'
602 cmd = [settings.GIT_EXECUTABLE, 'log', '--reverse',
602 cmd = [settings.GIT_EXECUTABLE, 'log', '--reverse',
603 '--pretty=format:%H', '--', push_ref['new_rev'],
603 '--pretty=format:%H', '--', push_ref['new_rev'],
604 '--not', heads]
604 '--not', heads]
605 stdout, stderr = subprocessio.run_command(
605 stdout, stderr = subprocessio.run_command(
606 cmd, env=os.environ.copy())
606 cmd, env=os.environ.copy())
607 git_revs.extend(stdout.splitlines())
607 git_revs.extend(stdout.splitlines())
608 elif push_ref['new_rev'] == empty_commit_id:
608 elif push_ref['new_rev'] == empty_commit_id:
609 # delete branch case
609 # delete branch case
610 git_revs.append('delete_branch=>%s' % push_ref['name'])
610 git_revs.append('delete_branch=>%s' % push_ref['name'])
611 else:
611 else:
612 if push_ref['name'] not in branches:
612 if push_ref['name'] not in branches:
613 branches.append(push_ref['name'])
613 branches.append(push_ref['name'])
614
614
615 cmd = [settings.GIT_EXECUTABLE, 'log',
615 cmd = [settings.GIT_EXECUTABLE, 'log',
616 '{old_rev}..{new_rev}'.format(**push_ref),
616 '{old_rev}..{new_rev}'.format(**push_ref),
617 '--reverse', '--pretty=format:%H']
617 '--reverse', '--pretty=format:%H']
618 stdout, stderr = subprocessio.run_command(
618 stdout, stderr = subprocessio.run_command(
619 cmd, env=os.environ.copy())
619 cmd, env=os.environ.copy())
620 git_revs.extend(stdout.splitlines())
620 git_revs.extend(stdout.splitlines())
621 elif type_ == 'tags':
621 elif type_ == 'tags':
622 if push_ref['name'] not in tags:
622 if push_ref['name'] not in tags:
623 tags.append(push_ref['name'])
623 tags.append(push_ref['name'])
624 git_revs.append('tag=>%s' % push_ref['name'])
624 git_revs.append('tag=>%s' % push_ref['name'])
625
625
626 extras['hook_type'] = 'post_receive'
626 extras['hook_type'] = 'post_receive'
627 extras['commit_ids'] = git_revs
627 extras['commit_ids'] = git_revs
628 extras['new_refs'] = {
628 extras['new_refs'] = {
629 'branches': branches,
629 'branches': branches,
630 'bookmarks': [],
630 'bookmarks': [],
631 'tags': tags,
631 'tags': tags,
632 }
632 }
633
633
634 if 'repo_size' in extras['hooks']:
634 if 'repo_size' in extras['hooks']:
635 try:
635 try:
636 _call_hook('repo_size', extras, GitMessageWriter())
636 _call_hook('repo_size', extras, GitMessageWriter())
637 except:
637 except:
638 pass
638 pass
639
639
640 return _call_hook('post_push', extras, GitMessageWriter())
640 return _call_hook('post_push', extras, GitMessageWriter())
641
641
642
642
643 def _get_extras_from_txn_id(path, txn_id):
643 def _get_extras_from_txn_id(path, txn_id):
644 extras = {}
644 extras = {}
645 try:
645 try:
646 cmd = [settings.SVNLOOK_EXECUTABLE, 'pget',
646 cmd = [settings.SVNLOOK_EXECUTABLE, 'pget',
647 '-t', txn_id,
647 '-t', txn_id,
648 '--revprop', path, 'rc-scm-extras']
648 '--revprop', path, 'rc-scm-extras']
649 stdout, stderr = subprocessio.run_command(
649 stdout, stderr = subprocessio.run_command(
650 cmd, env=os.environ.copy())
650 cmd, env=os.environ.copy())
651 extras = json.loads(base64.urlsafe_b64decode(stdout))
651 extras = json.loads(base64.urlsafe_b64decode(stdout))
652 except Exception:
652 except Exception:
653 log.exception('Failed to extract extras info from txn_id')
653 log.exception('Failed to extract extras info from txn_id')
654
654
655 return extras
655 return extras
656
656
657
657
658 def _get_extras_from_commit_id(commit_id, path):
658 def _get_extras_from_commit_id(commit_id, path):
659 extras = {}
659 extras = {}
660 try:
660 try:
661 cmd = [settings.SVNLOOK_EXECUTABLE, 'pget',
661 cmd = [settings.SVNLOOK_EXECUTABLE, 'pget',
662 '-r', commit_id,
662 '-r', commit_id,
663 '--revprop', path, 'rc-scm-extras']
663 '--revprop', path, 'rc-scm-extras']
664 stdout, stderr = subprocessio.run_command(
664 stdout, stderr = subprocessio.run_command(
665 cmd, env=os.environ.copy())
665 cmd, env=os.environ.copy())
666 extras = json.loads(base64.urlsafe_b64decode(stdout))
666 extras = json.loads(base64.urlsafe_b64decode(stdout))
667 except Exception:
667 except Exception:
668 log.exception('Failed to extract extras info from commit_id')
668 log.exception('Failed to extract extras info from commit_id')
669
669
670 return extras
670 return extras
671
671
672
672
673 def svn_pre_commit(repo_path, commit_data, env):
673 def svn_pre_commit(repo_path, commit_data, env):
674 path, txn_id = commit_data
674 path, txn_id = commit_data
675 branches = []
675 branches = []
676 tags = []
676 tags = []
677
677
678 if env.get('RC_SCM_DATA'):
678 if env.get('RC_SCM_DATA'):
679 extras = json.loads(env['RC_SCM_DATA'])
679 extras = json.loads(env['RC_SCM_DATA'])
680 else:
680 else:
681 # fallback method to read from TXN-ID stored data
681 # fallback method to read from TXN-ID stored data
682 extras = _get_extras_from_txn_id(path, txn_id)
682 extras = _get_extras_from_txn_id(path, txn_id)
683 if not extras:
683 if not extras:
684 return 0
684 return 0
685
685
686 extras['hook_type'] = 'pre_commit'
686 extras['hook_type'] = 'pre_commit'
687 extras['commit_ids'] = [txn_id]
687 extras['commit_ids'] = [txn_id]
688 extras['txn_id'] = txn_id
688 extras['txn_id'] = txn_id
689 extras['new_refs'] = {
689 extras['new_refs'] = {
690 'total_commits': 1,
690 'total_commits': 1,
691 'branches': branches,
691 'branches': branches,
692 'bookmarks': [],
692 'bookmarks': [],
693 'tags': tags,
693 'tags': tags,
694 }
694 }
695
695
696 return _call_hook('pre_push', extras, SvnMessageWriter())
696 return _call_hook('pre_push', extras, SvnMessageWriter())
697
697
698
698
699 def svn_post_commit(repo_path, commit_data, env):
699 def svn_post_commit(repo_path, commit_data, env):
700 """
700 """
701 commit_data is path, rev, txn_id
701 commit_data is path, rev, txn_id
702 """
702 """
703 if len(commit_data) == 3:
703 if len(commit_data) == 3:
704 path, commit_id, txn_id = commit_data
704 path, commit_id, txn_id = commit_data
705 elif len(commit_data) == 2:
705 elif len(commit_data) == 2:
706 log.error('Failed to extract txn_id from commit_data using legacy method. '
706 log.error('Failed to extract txn_id from commit_data using legacy method. '
707 'Some functionality might be limited')
707 'Some functionality might be limited')
708 path, commit_id = commit_data
708 path, commit_id = commit_data
709 txn_id = None
709 txn_id = None
710
710
711 branches = []
711 branches = []
712 tags = []
712 tags = []
713
713
714 if env.get('RC_SCM_DATA'):
714 if env.get('RC_SCM_DATA'):
715 extras = json.loads(env['RC_SCM_DATA'])
715 extras = json.loads(env['RC_SCM_DATA'])
716 else:
716 else:
717 # fallback method to read from TXN-ID stored data
717 # fallback method to read from TXN-ID stored data
718 extras = _get_extras_from_commit_id(commit_id, path)
718 extras = _get_extras_from_commit_id(commit_id, path)
719 if not extras:
719 if not extras:
720 return 0
720 return 0
721
721
722 extras['hook_type'] = 'post_commit'
722 extras['hook_type'] = 'post_commit'
723 extras['commit_ids'] = [commit_id]
723 extras['commit_ids'] = [commit_id]
724 extras['txn_id'] = txn_id
724 extras['txn_id'] = txn_id
725 extras['new_refs'] = {
725 extras['new_refs'] = {
726 'branches': branches,
726 'branches': branches,
727 'bookmarks': [],
727 'bookmarks': [],
728 'tags': tags,
728 'tags': tags,
729 'total_commits': 1,
729 'total_commits': 1,
730 }
730 }
731
731
732 if 'repo_size' in extras['hooks']:
732 if 'repo_size' in extras['hooks']:
733 try:
733 try:
734 _call_hook('repo_size', extras, SvnMessageWriter())
734 _call_hook('repo_size', extras, SvnMessageWriter())
735 except Exception:
735 except Exception:
736 pass
736 pass
737
737
738 return _call_hook('post_push', extras, SvnMessageWriter())
738 return _call_hook('post_push', extras, SvnMessageWriter())
@@ -1,740 +1,741 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 os
19 import os
20 import sys
20 import sys
21 import base64
21 import base64
22 import locale
22 import locale
23 import logging
23 import logging
24 import uuid
24 import uuid
25 import time
25 import time
26 import wsgiref.util
26 import wsgiref.util
27 import traceback
27 import traceback
28 import tempfile
28 import tempfile
29 import psutil
29 import psutil
30
30
31 from itertools import chain
31 from itertools import chain
32
32
33 import msgpack
33 import msgpack
34 import configparser
34 import configparser
35
35
36 from pyramid.config import Configurator
36 from pyramid.config import Configurator
37 from pyramid.wsgi import wsgiapp
37 from pyramid.wsgi import wsgiapp
38 from pyramid.response import Response
38 from pyramid.response import Response
39
39
40 from vcsserver.lib.rc_json import json
40 from vcsserver.lib.rc_json import json
41 from vcsserver.config.settings_maker import SettingsMaker
41 from vcsserver.config.settings_maker import SettingsMaker
42 from vcsserver.str_utils import safe_int
42 from vcsserver.str_utils import safe_int
43 from vcsserver.lib.statsd_client import StatsdClient
43 from vcsserver.lib.statsd_client import StatsdClient
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47 # 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
48 # 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
49
49
50 try:
50 try:
51 locale.setlocale(locale.LC_ALL, '')
51 locale.setlocale(locale.LC_ALL, '')
52 except locale.Error as e:
52 except locale.Error as e:
53 log.error(
53 log.error(
54 '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)
55 os.environ['LC_ALL'] = 'C'
55 os.environ['LC_ALL'] = 'C'
56
56
57
57
58 import vcsserver
58 import vcsserver
59 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
59 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
60 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
61 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
61 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
62 from vcsserver.echo_stub.echo_app import EchoApp
62 from vcsserver.echo_stub.echo_app import EchoApp
63 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
63 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
64 from vcsserver.lib.exc_tracking import store_exception
64 from vcsserver.lib.exc_tracking import store_exception
65 from vcsserver.server import VcsServer
65 from vcsserver.server import VcsServer
66
66
67 strict_vcs = True
67 strict_vcs = True
68
68
69 git_import_err = None
69 git_import_err = None
70 try:
70 try:
71 from vcsserver.remote.git import GitFactory, GitRemote
71 from vcsserver.remote.git import GitFactory, GitRemote
72 except ImportError as e:
72 except ImportError as e:
73 GitFactory = None
73 GitFactory = None
74 GitRemote = None
74 GitRemote = None
75 git_import_err = e
75 git_import_err = e
76 if strict_vcs:
76 if strict_vcs:
77 raise
77 raise
78
78
79
79
80 hg_import_err = None
80 hg_import_err = None
81 try:
81 try:
82 from vcsserver.remote.hg import MercurialFactory, HgRemote
82 from vcsserver.remote.hg import MercurialFactory, HgRemote
83 except ImportError as e:
83 except ImportError as e:
84 MercurialFactory = None
84 MercurialFactory = None
85 HgRemote = None
85 HgRemote = None
86 hg_import_err = e
86 hg_import_err = e
87 if strict_vcs:
87 if strict_vcs:
88 raise
88 raise
89
89
90
90
91 svn_import_err = None
91 svn_import_err = None
92 try:
92 try:
93 from vcsserver.remote.svn import SubversionFactory, SvnRemote
93 from vcsserver.remote.svn import SubversionFactory, SvnRemote
94 except ImportError as e:
94 except ImportError as e:
95 SubversionFactory = None
95 SubversionFactory = None
96 SvnRemote = None
96 SvnRemote = None
97 svn_import_err = e
97 svn_import_err = e
98 if strict_vcs:
98 if strict_vcs:
99 raise
99 raise
100
100
101
101
102 def _is_request_chunked(environ):
102 def _is_request_chunked(environ):
103 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
103 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
104 return stream
104 return stream
105
105
106
106
107 def log_max_fd():
107 def log_max_fd():
108 try:
108 try:
109 maxfd = psutil.Process().rlimit(psutil.RLIMIT_NOFILE)[1]
109 maxfd = psutil.Process().rlimit(psutil.RLIMIT_NOFILE)[1]
110 log.info('Max file descriptors value: %s', maxfd)
110 log.info('Max file descriptors value: %s', maxfd)
111 except Exception:
111 except Exception:
112 pass
112 pass
113
113
114
114
115 class VCS(object):
115 class VCS(object):
116 def __init__(self, locale_conf=None, cache_config=None):
116 def __init__(self, locale_conf=None, cache_config=None):
117 self.locale = locale_conf
117 self.locale = locale_conf
118 self.cache_config = cache_config
118 self.cache_config = cache_config
119 self._configure_locale()
119 self._configure_locale()
120
120
121 log_max_fd()
121 log_max_fd()
122
122
123 if GitFactory and GitRemote:
123 if GitFactory and GitRemote:
124 git_factory = GitFactory()
124 git_factory = GitFactory()
125 self._git_remote = GitRemote(git_factory)
125 self._git_remote = GitRemote(git_factory)
126 else:
126 else:
127 log.error("Git client import failed: %s", git_import_err)
127 log.error("Git client import failed: %s", git_import_err)
128
128
129 if MercurialFactory and HgRemote:
129 if MercurialFactory and HgRemote:
130 hg_factory = MercurialFactory()
130 hg_factory = MercurialFactory()
131 self._hg_remote = HgRemote(hg_factory)
131 self._hg_remote = HgRemote(hg_factory)
132 else:
132 else:
133 log.error("Mercurial client import failed: %s", hg_import_err)
133 log.error("Mercurial client import failed: %s", hg_import_err)
134
134
135 if SubversionFactory and SvnRemote:
135 if SubversionFactory and SvnRemote:
136 svn_factory = SubversionFactory()
136 svn_factory = SubversionFactory()
137
137
138 # hg factory is used for svn url validation
138 # hg factory is used for svn url validation
139 hg_factory = MercurialFactory()
139 hg_factory = MercurialFactory()
140 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
140 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
141 else:
141 else:
142 log.error("Subversion client import failed: %s", svn_import_err)
142 log.error("Subversion client import failed: %s", svn_import_err)
143
143
144 self._vcsserver = VcsServer()
144 self._vcsserver = VcsServer()
145
145
146 def _configure_locale(self):
146 def _configure_locale(self):
147 if self.locale:
147 if self.locale:
148 log.info('Settings locale: `LC_ALL` to %s', self.locale)
148 log.info('Settings locale: `LC_ALL` to %s', self.locale)
149 else:
149 else:
150 log.info('Configuring locale subsystem based on environment variables')
150 log.info('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('Cannot set locale, not configuring the locale system')
162 log.exception('Cannot set locale, not configuring the locale system')
163
163
164
164
165 class WsgiProxy(object):
165 class WsgiProxy(object):
166 def __init__(self, wsgi):
166 def __init__(self, wsgi):
167 self.wsgi = wsgi
167 self.wsgi = wsgi
168
168
169 def __call__(self, environ, start_response):
169 def __call__(self, environ, start_response):
170 input_data = environ['wsgi.input'].read()
170 input_data = environ['wsgi.input'].read()
171 input_data = msgpack.unpackb(input_data)
171 input_data = msgpack.unpackb(input_data)
172
172
173 error = None
173 error = None
174 try:
174 try:
175 data, status, headers = self.wsgi.handle(
175 data, status, headers = self.wsgi.handle(
176 input_data['environment'], input_data['input_data'],
176 input_data['environment'], input_data['input_data'],
177 *input_data['args'], **input_data['kwargs'])
177 *input_data['args'], **input_data['kwargs'])
178 except Exception as e:
178 except Exception as e:
179 data, status, headers = [], None, None
179 data, status, headers = [], None, None
180 error = {
180 error = {
181 'message': str(e),
181 'message': str(e),
182 '_vcs_kind': getattr(e, '_vcs_kind', None)
182 '_vcs_kind': getattr(e, '_vcs_kind', None)
183 }
183 }
184
184
185 start_response(200, {})
185 start_response(200, {})
186 return self._iterator(error, status, headers, data)
186 return self._iterator(error, status, headers, data)
187
187
188 def _iterator(self, error, status, headers, data):
188 def _iterator(self, error, status, headers, data):
189 initial_data = [
189 initial_data = [
190 error,
190 error,
191 status,
191 status,
192 headers,
192 headers,
193 ]
193 ]
194
194
195 for d in chain(initial_data, data):
195 for d in chain(initial_data, data):
196 yield msgpack.packb(d)
196 yield msgpack.packb(d)
197
197
198
198
199 def not_found(request):
199 def not_found(request):
200 return {'status': '404 NOT FOUND'}
200 return {'status': '404 NOT FOUND'}
201
201
202
202
203 class VCSViewPredicate(object):
203 class VCSViewPredicate(object):
204 def __init__(self, val, config):
204 def __init__(self, val, config):
205 self.remotes = val
205 self.remotes = val
206
206
207 def text(self):
207 def text(self):
208 return 'vcs view method = %s' % (list(self.remotes.keys()),)
208 return 'vcs view method = %s' % (list(self.remotes.keys()),)
209
209
210 phash = text
210 phash = text
211
211
212 def __call__(self, context, request):
212 def __call__(self, context, request):
213 """
213 """
214 View predicate that returns true if given backend is supported by
214 View predicate that returns true if given backend is supported by
215 defined remotes.
215 defined remotes.
216 """
216 """
217 backend = request.matchdict.get('backend')
217 backend = request.matchdict.get('backend')
218 return backend in self.remotes
218 return backend in self.remotes
219
219
220
220
221 class HTTPApplication(object):
221 class HTTPApplication(object):
222 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
222 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
223
223
224 remote_wsgi = remote_wsgi
224 remote_wsgi = remote_wsgi
225 _use_echo_app = False
225 _use_echo_app = False
226
226
227 def __init__(self, settings=None, global_config=None):
227 def __init__(self, settings=None, global_config=None):
228
228
229 self.config = Configurator(settings=settings)
229 self.config = Configurator(settings=settings)
230 # Init our statsd at very start
230 # Init our statsd at very start
231 self.config.registry.statsd = StatsdClient.statsd
231 self.config.registry.statsd = StatsdClient.statsd
232
232
233 self.global_config = global_config
233 self.global_config = global_config
234 self.config.include('vcsserver.lib.rc_cache')
234 self.config.include('vcsserver.lib.rc_cache')
235
235
236 settings_locale = settings.get('locale', '') or 'en_US.UTF-8'
236 settings_locale = settings.get('locale', '') or 'en_US.UTF-8'
237 vcs = VCS(locale_conf=settings_locale, cache_config=settings)
237 vcs = VCS(locale_conf=settings_locale, cache_config=settings)
238 self._remotes = {
238 self._remotes = {
239 'hg': vcs._hg_remote,
239 'hg': vcs._hg_remote,
240 'git': vcs._git_remote,
240 'git': vcs._git_remote,
241 'svn': vcs._svn_remote,
241 'svn': vcs._svn_remote,
242 'server': vcs._vcsserver,
242 'server': vcs._vcsserver,
243 }
243 }
244 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
244 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
245 self._use_echo_app = True
245 self._use_echo_app = True
246 log.warning("Using EchoApp for VCS operations.")
246 log.warning("Using EchoApp for VCS operations.")
247 self.remote_wsgi = remote_wsgi_stub
247 self.remote_wsgi = remote_wsgi_stub
248
248
249 self._configure_settings(global_config, settings)
249 self._configure_settings(global_config, settings)
250
250
251 self._configure()
251 self._configure()
252
252
253 def _configure_settings(self, global_config, app_settings):
253 def _configure_settings(self, global_config, app_settings):
254 """
254 """
255 Configure the settings module.
255 Configure the settings module.
256 """
256 """
257 settings_merged = global_config.copy()
257 settings_merged = global_config.copy()
258 settings_merged.update(app_settings)
258 settings_merged.update(app_settings)
259
259
260 git_path = app_settings.get('git_path', None)
260 git_path = app_settings.get('git_path', None)
261 if git_path:
261 if git_path:
262 settings.GIT_EXECUTABLE = git_path
262 settings.GIT_EXECUTABLE = git_path
263 binary_dir = app_settings.get('core.binary_dir', None)
263 binary_dir = app_settings.get('core.binary_dir', None)
264 if binary_dir:
264 if binary_dir:
265 settings.BINARY_DIR = binary_dir
265 settings.BINARY_DIR = binary_dir
266
266
267 # Store the settings to make them available to other modules.
267 # Store the settings to make them available to other modules.
268 vcsserver.PYRAMID_SETTINGS = settings_merged
268 vcsserver.PYRAMID_SETTINGS = settings_merged
269 vcsserver.CONFIG = settings_merged
269 vcsserver.CONFIG = settings_merged
270
270
271 def _configure(self):
271 def _configure(self):
272 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
272 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
273
273
274 self.config.add_route('service', '/_service')
274 self.config.add_route('service', '/_service')
275 self.config.add_route('status', '/status')
275 self.config.add_route('status', '/status')
276 self.config.add_route('hg_proxy', '/proxy/hg')
276 self.config.add_route('hg_proxy', '/proxy/hg')
277 self.config.add_route('git_proxy', '/proxy/git')
277 self.config.add_route('git_proxy', '/proxy/git')
278
278
279 # rpc methods
279 # rpc methods
280 self.config.add_route('vcs', '/{backend}')
280 self.config.add_route('vcs', '/{backend}')
281
281
282 # streaming rpc remote methods
282 # streaming rpc remote methods
283 self.config.add_route('vcs_stream', '/{backend}/stream')
283 self.config.add_route('vcs_stream', '/{backend}/stream')
284
284
285 # vcs operations clone/push as streaming
285 # vcs operations clone/push as streaming
286 self.config.add_route('stream_git', '/stream/git/*repo_name')
286 self.config.add_route('stream_git', '/stream/git/*repo_name')
287 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
287 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
288
288
289 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')
290 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')
291
291
292 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
292 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
293 self.config.add_view(self.git_proxy(), route_name='git_proxy')
293 self.config.add_view(self.git_proxy(), route_name='git_proxy')
294 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',
295 vcs_view=self._remotes)
295 vcs_view=self._remotes)
296 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',
297 vcs_view=self._remotes)
297 vcs_view=self._remotes)
298
298
299 self.config.add_view(self.hg_stream(), route_name='stream_hg')
299 self.config.add_view(self.hg_stream(), route_name='stream_hg')
300 self.config.add_view(self.git_stream(), route_name='stream_git')
300 self.config.add_view(self.git_stream(), route_name='stream_git')
301
301
302 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
302 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
303
303
304 self.config.add_notfound_view(not_found, renderer='json')
304 self.config.add_notfound_view(not_found, renderer='json')
305
305
306 self.config.add_view(self.handle_vcs_exception, context=Exception)
306 self.config.add_view(self.handle_vcs_exception, context=Exception)
307
307
308 self.config.add_tween(
308 self.config.add_tween(
309 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
309 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
310 )
310 )
311 self.config.add_request_method(
311 self.config.add_request_method(
312 'vcsserver.lib.request_counter.get_request_counter',
312 'vcsserver.lib.request_counter.get_request_counter',
313 'request_count')
313 'request_count')
314
314
315 def wsgi_app(self):
315 def wsgi_app(self):
316 return self.config.make_wsgi_app()
316 return self.config.make_wsgi_app()
317
317
318 def _vcs_view_params(self, request):
318 def _vcs_view_params(self, request):
319 remote = self._remotes[request.matchdict['backend']]
319 remote = self._remotes[request.matchdict['backend']]
320 payload = msgpack.unpackb(request.body, use_list=True, raw=False)
320 payload = msgpack.unpackb(request.body, use_list=True)
321
321
322 method = payload.get('method')
322 method = payload.get('method')
323 params = payload['params']
323 params = payload['params']
324 wire = params.get('wire')
324 wire = params.get('wire')
325 args = params.get('args')
325 args = params.get('args')
326 kwargs = params.get('kwargs')
326 kwargs = params.get('kwargs')
327 context_uid = None
327 context_uid = None
328
328
329 if wire:
329 if wire:
330 try:
330 try:
331 wire['context'] = context_uid = uuid.UUID(wire['context'])
331 wire['context'] = context_uid = uuid.UUID(wire['context'])
332 except KeyError:
332 except KeyError:
333 pass
333 pass
334 args.insert(0, wire)
334 args.insert(0, wire)
335 repo_state_uid = wire.get('repo_state_uid') if wire else None
335 repo_state_uid = wire.get('repo_state_uid') if wire else None
336
336
337 # NOTE(marcink): trading complexity for slight performance
337 # NOTE(marcink): trading complexity for slight performance
338 if log.isEnabledFor(logging.DEBUG):
338 if log.isEnabledFor(logging.DEBUG):
339 no_args_methods = [
339 no_args_methods = [
340
340
341 ]
341 ]
342 if method in no_args_methods:
342 if method in no_args_methods:
343 call_args = ''
343 call_args = ''
344 else:
344 else:
345 call_args = args[1:]
345 call_args = args[1:]
346
346
347 log.debug('Method requested:`%s` with args:%s kwargs:%s context_uid: %s, repo_state_uid:%s',
347 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)
348 method, call_args, kwargs, context_uid, repo_state_uid)
349
349
350 statsd = request.registry.statsd
350 statsd = request.registry.statsd
351 if statsd:
351 if statsd:
352 statsd.incr(
352 statsd.incr(
353 'vcsserver_method_total', tags=[
353 'vcsserver_method_total', tags=[
354 "method:{}".format(method),
354 "method:{}".format(method),
355 ])
355 ])
356 return payload, remote, method, args, kwargs
356 return payload, remote, method, args, kwargs
357
357
358 def vcs_view(self, request):
358 def vcs_view(self, request):
359
359
360 payload, remote, method, args, kwargs = self._vcs_view_params(request)
360 payload, remote, method, args, kwargs = self._vcs_view_params(request)
361 payload_id = payload.get('id')
361 payload_id = payload.get('id')
362
362
363 try:
363 try:
364 resp = getattr(remote, method)(*args, **kwargs)
364 resp = getattr(remote, method)(*args, **kwargs)
365 except Exception as e:
365 except Exception as e:
366 exc_info = list(sys.exc_info())
366 exc_info = list(sys.exc_info())
367 exc_type, exc_value, exc_traceback = exc_info
367 exc_type, exc_value, exc_traceback = exc_info
368
368
369 org_exc = getattr(e, '_org_exc', None)
369 org_exc = getattr(e, '_org_exc', None)
370 org_exc_name = None
370 org_exc_name = None
371 org_exc_tb = ''
371 org_exc_tb = ''
372 if org_exc:
372 if org_exc:
373 org_exc_name = org_exc.__class__.__name__
373 org_exc_name = org_exc.__class__.__name__
374 org_exc_tb = getattr(e, '_org_exc_tb', '')
374 org_exc_tb = getattr(e, '_org_exc_tb', '')
375 # replace our "faked" exception with our org
375 # replace our "faked" exception with our org
376 exc_info[0] = org_exc.__class__
376 exc_info[0] = org_exc.__class__
377 exc_info[1] = org_exc
377 exc_info[1] = org_exc
378
378
379 should_store_exc = True
379 should_store_exc = True
380 if org_exc:
380 if org_exc:
381 def get_exc_fqn(_exc_obj):
381 def get_exc_fqn(_exc_obj):
382 module_name = getattr(org_exc.__class__, '__module__', 'UNKNOWN')
382 module_name = getattr(org_exc.__class__, '__module__', 'UNKNOWN')
383 return module_name + '.' + org_exc_name
383 return module_name + '.' + org_exc_name
384
384
385 exc_fqn = get_exc_fqn(org_exc)
385 exc_fqn = get_exc_fqn(org_exc)
386
386
387 if exc_fqn in ['mercurial.error.RepoLookupError',
387 if exc_fqn in ['mercurial.error.RepoLookupError',
388 'vcsserver.exceptions.RefNotFoundException']:
388 'vcsserver.exceptions.RefNotFoundException']:
389 should_store_exc = False
389 should_store_exc = False
390
390
391 if should_store_exc:
391 if should_store_exc:
392 store_exception(id(exc_info), exc_info, request_path=request.path)
392 store_exception(id(exc_info), exc_info, request_path=request.path)
393
393
394 tb_info = ''.join(
394 tb_info = ''.join(
395 traceback.format_exception(exc_type, exc_value, exc_traceback))
395 traceback.format_exception(exc_type, exc_value, exc_traceback))
396
396
397 type_ = e.__class__.__name__
397 type_ = e.__class__.__name__
398 if type_ not in self.ALLOWED_EXCEPTIONS:
398 if type_ not in self.ALLOWED_EXCEPTIONS:
399 type_ = None
399 type_ = None
400
400
401 resp = {
401 resp = {
402 'id': payload_id,
402 'id': payload_id,
403 'error': {
403 'error': {
404 'message': str(e),
404 'message': str(e),
405 'traceback': tb_info,
405 'traceback': tb_info,
406 'org_exc': org_exc_name,
406 'org_exc': org_exc_name,
407 'org_exc_tb': org_exc_tb,
407 'org_exc_tb': org_exc_tb,
408 'type': type_
408 'type': type_
409 }
409 }
410 }
410 }
411
411
412 try:
412 try:
413 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
413 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
414 except AttributeError:
414 except AttributeError:
415 pass
415 pass
416 else:
416 else:
417 resp = {
417 resp = {
418 'id': payload_id,
418 'id': payload_id,
419 'result': resp
419 'result': resp
420 }
420 }
421
421
422 return resp
422 return resp
423
423
424 def vcs_stream_view(self, request):
424 def vcs_stream_view(self, request):
425 payload, remote, method, args, kwargs = self._vcs_view_params(request)
425 payload, remote, method, args, kwargs = self._vcs_view_params(request)
426 # this method has a stream: marker we remove it here
426 # this method has a stream: marker we remove it here
427 method = method.split('stream:')[-1]
427 method = method.split('stream:')[-1]
428 chunk_size = safe_int(payload.get('chunk_size')) or 4096
428 chunk_size = safe_int(payload.get('chunk_size')) or 4096
429
429
430 try:
430 try:
431 resp = getattr(remote, method)(*args, **kwargs)
431 resp = getattr(remote, method)(*args, **kwargs)
432 except Exception as e:
432 except Exception as e:
433 raise
433 raise
434
434
435 def get_chunked_data(method_resp):
435 def get_chunked_data(method_resp):
436 stream = io.BytesIO(method_resp)
436 stream = io.BytesIO(method_resp)
437 while 1:
437 while 1:
438 chunk = stream.read(chunk_size)
438 chunk = stream.read(chunk_size)
439 if not chunk:
439 if not chunk:
440 break
440 break
441 yield chunk
441 yield chunk
442
442
443 response = Response(app_iter=get_chunked_data(resp))
443 response = Response(app_iter=get_chunked_data(resp))
444 response.content_type = 'application/octet-stream'
444 response.content_type = 'application/octet-stream'
445
445
446 return response
446 return response
447
447
448 def status_view(self, request):
448 def status_view(self, request):
449 import vcsserver
449 import vcsserver
450 return {'status': 'OK', 'vcsserver_version': str(vcsserver.__version__),
450 return {'status': 'OK', 'vcsserver_version': safe_str(vcsserver.__version__),
451 'pid': os.getpid()}
451 'pid': os.getpid()}
452
452
453 def service_view(self, request):
453 def service_view(self, request):
454 import vcsserver
454 import vcsserver
455
455
456 payload = msgpack.unpackb(request.body, use_list=True)
456 payload = msgpack.unpackb(request.body, use_list=True)
457 server_config, app_config = {}, {}
457 server_config, app_config = {}, {}
458
458
459 try:
459 try:
460 path = self.global_config['__file__']
460 path = self.global_config['__file__']
461 config = configparser.RawConfigParser()
461 config = configparser.RawConfigParser()
462
462
463 config.read(path)
463 config.read(path)
464
464
465 if config.has_section('server:main'):
465 if config.has_section('server:main'):
466 server_config = dict(config.items('server:main'))
466 server_config = dict(config.items('server:main'))
467 if config.has_section('app:main'):
467 if config.has_section('app:main'):
468 app_config = dict(config.items('app:main'))
468 app_config = dict(config.items('app:main'))
469
469
470 except Exception:
470 except Exception:
471 log.exception('Failed to read .ini file for display')
471 log.exception('Failed to read .ini file for display')
472
472
473 environ = list(os.environ.items())
473 environ = list(os.environ.items())
474
474
475 resp = {
475 resp = {
476 'id': payload.get('id'),
476 'id': payload.get('id'),
477 'result': dict(
477 'result': dict(
478 version=vcsserver.__version__,
478 version=safe_str(vcsserver.__version__),
479 config=server_config,
479 config=server_config,
480 app_config=app_config,
480 app_config=app_config,
481 environ=environ,
481 environ=environ,
482 payload=payload,
482 payload=payload,
483 )
483 )
484 }
484 }
485 return resp
485 return resp
486
486
487 def _msgpack_renderer_factory(self, info):
487 def _msgpack_renderer_factory(self, info):
488 def _render(value, system):
488 def _render(value, system):
489 request = system.get('request')
489 request = system.get('request')
490 if request is not None:
490 if request is not None:
491 response = request.response
491 response = request.response
492 ct = response.content_type
492 ct = response.content_type
493 if ct == response.default_content_type:
493 if ct == response.default_content_type:
494 response.content_type = 'application/x-msgpack'
494 response.content_type = 'application/x-msgpack'
495 return msgpack.packb(value)
495
496 return msgpack.packb(value, use_bin_type=False)
496 return _render
497 return _render
497
498
498 def set_env_from_config(self, environ, config):
499 def set_env_from_config(self, environ, config):
499 dict_conf = {}
500 dict_conf = {}
500 try:
501 try:
501 for elem in config:
502 for elem in config:
502 if elem[0] == 'rhodecode':
503 if elem[0] == 'rhodecode':
503 dict_conf = json.loads(elem[2])
504 dict_conf = json.loads(elem[2])
504 break
505 break
505 except Exception:
506 except Exception:
506 log.exception('Failed to fetch SCM CONFIG')
507 log.exception('Failed to fetch SCM CONFIG')
507 return
508 return
508
509
509 username = dict_conf.get('username')
510 username = dict_conf.get('username')
510 if username:
511 if username:
511 environ['REMOTE_USER'] = username
512 environ['REMOTE_USER'] = username
512 # mercurial specific, some extension api rely on this
513 # mercurial specific, some extension api rely on this
513 environ['HGUSER'] = username
514 environ['HGUSER'] = username
514
515
515 ip = dict_conf.get('ip')
516 ip = dict_conf.get('ip')
516 if ip:
517 if ip:
517 environ['REMOTE_HOST'] = ip
518 environ['REMOTE_HOST'] = ip
518
519
519 if _is_request_chunked(environ):
520 if _is_request_chunked(environ):
520 # set the compatibility flag for webob
521 # set the compatibility flag for webob
521 environ['wsgi.input_terminated'] = True
522 environ['wsgi.input_terminated'] = True
522
523
523 def hg_proxy(self):
524 def hg_proxy(self):
524 @wsgiapp
525 @wsgiapp
525 def _hg_proxy(environ, start_response):
526 def _hg_proxy(environ, start_response):
526 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
527 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
527 return app(environ, start_response)
528 return app(environ, start_response)
528 return _hg_proxy
529 return _hg_proxy
529
530
530 def git_proxy(self):
531 def git_proxy(self):
531 @wsgiapp
532 @wsgiapp
532 def _git_proxy(environ, start_response):
533 def _git_proxy(environ, start_response):
533 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
534 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
534 return app(environ, start_response)
535 return app(environ, start_response)
535 return _git_proxy
536 return _git_proxy
536
537
537 def hg_stream(self):
538 def hg_stream(self):
538 if self._use_echo_app:
539 if self._use_echo_app:
539 @wsgiapp
540 @wsgiapp
540 def _hg_stream(environ, start_response):
541 def _hg_stream(environ, start_response):
541 app = EchoApp('fake_path', 'fake_name', None)
542 app = EchoApp('fake_path', 'fake_name', None)
542 return app(environ, start_response)
543 return app(environ, start_response)
543 return _hg_stream
544 return _hg_stream
544 else:
545 else:
545 @wsgiapp
546 @wsgiapp
546 def _hg_stream(environ, start_response):
547 def _hg_stream(environ, start_response):
547 log.debug('http-app: handling hg stream')
548 log.debug('http-app: handling hg stream')
548 repo_path = environ['HTTP_X_RC_REPO_PATH']
549 repo_path = environ['HTTP_X_RC_REPO_PATH']
549 repo_name = environ['HTTP_X_RC_REPO_NAME']
550 repo_name = environ['HTTP_X_RC_REPO_NAME']
550 packed_config = base64.b64decode(
551 packed_config = base64.b64decode(
551 environ['HTTP_X_RC_REPO_CONFIG'])
552 environ['HTTP_X_RC_REPO_CONFIG'])
552 config = msgpack.unpackb(packed_config)
553 config = msgpack.unpackb(packed_config)
553 app = scm_app.create_hg_wsgi_app(
554 app = scm_app.create_hg_wsgi_app(
554 repo_path, repo_name, config)
555 repo_path, repo_name, config)
555
556
556 # Consistent path information for hgweb
557 # Consistent path information for hgweb
557 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
558 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
558 environ['REPO_NAME'] = repo_name
559 environ['REPO_NAME'] = repo_name
559 self.set_env_from_config(environ, config)
560 self.set_env_from_config(environ, config)
560
561
561 log.debug('http-app: starting app handler '
562 log.debug('http-app: starting app handler '
562 'with %s and process request', app)
563 'with %s and process request', app)
563 return app(environ, ResponseFilter(start_response))
564 return app(environ, ResponseFilter(start_response))
564 return _hg_stream
565 return _hg_stream
565
566
566 def git_stream(self):
567 def git_stream(self):
567 if self._use_echo_app:
568 if self._use_echo_app:
568 @wsgiapp
569 @wsgiapp
569 def _git_stream(environ, start_response):
570 def _git_stream(environ, start_response):
570 app = EchoApp('fake_path', 'fake_name', None)
571 app = EchoApp('fake_path', 'fake_name', None)
571 return app(environ, start_response)
572 return app(environ, start_response)
572 return _git_stream
573 return _git_stream
573 else:
574 else:
574 @wsgiapp
575 @wsgiapp
575 def _git_stream(environ, start_response):
576 def _git_stream(environ, start_response):
576 log.debug('http-app: handling git stream')
577 log.debug('http-app: handling git stream')
577 repo_path = environ['HTTP_X_RC_REPO_PATH']
578 repo_path = environ['HTTP_X_RC_REPO_PATH']
578 repo_name = environ['HTTP_X_RC_REPO_NAME']
579 repo_name = environ['HTTP_X_RC_REPO_NAME']
579 packed_config = base64.b64decode(
580 packed_config = base64.b64decode(
580 environ['HTTP_X_RC_REPO_CONFIG'])
581 environ['HTTP_X_RC_REPO_CONFIG'])
581 config = msgpack.unpackb(packed_config, raw=False)
582 config = msgpack.unpackb(packed_config)
582
583
583 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
584 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
584 self.set_env_from_config(environ, config)
585 self.set_env_from_config(environ, config)
585
586
586 content_type = environ.get('CONTENT_TYPE', '')
587 content_type = environ.get('CONTENT_TYPE', '')
587
588
588 path = environ['PATH_INFO']
589 path = environ['PATH_INFO']
589 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
590 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
590 log.debug(
591 log.debug(
591 'LFS: Detecting if request `%s` is LFS server path based '
592 'LFS: Detecting if request `%s` is LFS server path based '
592 'on content type:`%s`, is_lfs:%s',
593 'on content type:`%s`, is_lfs:%s',
593 path, content_type, is_lfs_request)
594 path, content_type, is_lfs_request)
594
595
595 if not is_lfs_request:
596 if not is_lfs_request:
596 # fallback detection by path
597 # fallback detection by path
597 if GIT_LFS_PROTO_PAT.match(path):
598 if GIT_LFS_PROTO_PAT.match(path):
598 is_lfs_request = True
599 is_lfs_request = True
599 log.debug(
600 log.debug(
600 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
601 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
601 path, is_lfs_request)
602 path, is_lfs_request)
602
603
603 if is_lfs_request:
604 if is_lfs_request:
604 app = scm_app.create_git_lfs_wsgi_app(
605 app = scm_app.create_git_lfs_wsgi_app(
605 repo_path, repo_name, config)
606 repo_path, repo_name, config)
606 else:
607 else:
607 app = scm_app.create_git_wsgi_app(
608 app = scm_app.create_git_wsgi_app(
608 repo_path, repo_name, config)
609 repo_path, repo_name, config)
609
610
610 log.debug('http-app: starting app handler '
611 log.debug('http-app: starting app handler '
611 'with %s and process request', app)
612 'with %s and process request', app)
612
613
613 return app(environ, start_response)
614 return app(environ, start_response)
614
615
615 return _git_stream
616 return _git_stream
616
617
617 def handle_vcs_exception(self, exception, request):
618 def handle_vcs_exception(self, exception, request):
618 _vcs_kind = getattr(exception, '_vcs_kind', '')
619 _vcs_kind = getattr(exception, '_vcs_kind', '')
619 if _vcs_kind == 'repo_locked':
620 if _vcs_kind == 'repo_locked':
620 # Get custom repo-locked status code if present.
621 # Get custom repo-locked status code if present.
621 status_code = request.headers.get('X-RC-Locked-Status-Code')
622 status_code = request.headers.get('X-RC-Locked-Status-Code')
622 return HTTPRepoLocked(
623 return HTTPRepoLocked(
623 title=exception.message, status_code=status_code)
624 title=exception.message, status_code=status_code)
624
625
625 elif _vcs_kind == 'repo_branch_protected':
626 elif _vcs_kind == 'repo_branch_protected':
626 # Get custom repo-branch-protected status code if present.
627 # Get custom repo-branch-protected status code if present.
627 return HTTPRepoBranchProtected(title=exception.message)
628 return HTTPRepoBranchProtected(title=exception.message)
628
629
629 exc_info = request.exc_info
630 exc_info = request.exc_info
630 store_exception(id(exc_info), exc_info)
631 store_exception(id(exc_info), exc_info)
631
632
632 traceback_info = 'unavailable'
633 traceback_info = 'unavailable'
633 if request.exc_info:
634 if request.exc_info:
634 exc_type, exc_value, exc_tb = request.exc_info
635 exc_type, exc_value, exc_tb = request.exc_info
635 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
636 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
636
637
637 log.error(
638 log.error(
638 'error occurred handling this request for path: %s, \n tb: %s',
639 'error occurred handling this request for path: %s, \n tb: %s',
639 request.path, traceback_info)
640 request.path, traceback_info)
640
641
641 statsd = request.registry.statsd
642 statsd = request.registry.statsd
642 if statsd:
643 if statsd:
643 exc_type = "{}.{}".format(exception.__class__.__module__, exception.__class__.__name__)
644 exc_type = "{}.{}".format(exception.__class__.__module__, exception.__class__.__name__)
644 statsd.incr('vcsserver_exception_total',
645 statsd.incr('vcsserver_exception_total',
645 tags=["type:{}".format(exc_type)])
646 tags=["type:{}".format(exc_type)])
646 raise exception
647 raise exception
647
648
648
649
649 class ResponseFilter(object):
650 class ResponseFilter(object):
650
651
651 def __init__(self, start_response):
652 def __init__(self, start_response):
652 self._start_response = start_response
653 self._start_response = start_response
653
654
654 def __call__(self, status, response_headers, exc_info=None):
655 def __call__(self, status, response_headers, exc_info=None):
655 headers = tuple(
656 headers = tuple(
656 (h, v) for h, v in response_headers
657 (h, v) for h, v in response_headers
657 if not wsgiref.util.is_hop_by_hop(h))
658 if not wsgiref.util.is_hop_by_hop(h))
658 return self._start_response(status, headers, exc_info)
659 return self._start_response(status, headers, exc_info)
659
660
660
661
661 def sanitize_settings_and_apply_defaults(global_config, settings):
662 def sanitize_settings_and_apply_defaults(global_config, settings):
662 global_settings_maker = SettingsMaker(global_config)
663 global_settings_maker = SettingsMaker(global_config)
663 settings_maker = SettingsMaker(settings)
664 settings_maker = SettingsMaker(settings)
664
665
665 settings_maker.make_setting('logging.autoconfigure', False, parser='bool')
666 settings_maker.make_setting('logging.autoconfigure', False, parser='bool')
666
667
667 logging_conf = os.path.join(os.path.dirname(global_config.get('__file__')), 'logging.ini')
668 logging_conf = os.path.join(os.path.dirname(global_config.get('__file__')), 'logging.ini')
668 settings_maker.enable_logging(logging_conf)
669 settings_maker.enable_logging(logging_conf)
669
670
670 # Default includes, possible to change as a user
671 # Default includes, possible to change as a user
671 pyramid_includes = settings_maker.make_setting('pyramid.includes', [], parser='list:newline')
672 pyramid_includes = settings_maker.make_setting('pyramid.includes', [], parser='list:newline')
672 log.debug("Using the following pyramid.includes: %s", pyramid_includes)
673 log.debug("Using the following pyramid.includes: %s", pyramid_includes)
673
674
674 settings_maker.make_setting('__file__', global_config.get('__file__'))
675 settings_maker.make_setting('__file__', global_config.get('__file__'))
675
676
676 settings_maker.make_setting('pyramid.default_locale_name', 'en')
677 settings_maker.make_setting('pyramid.default_locale_name', 'en')
677 settings_maker.make_setting('locale', 'en_US.UTF-8')
678 settings_maker.make_setting('locale', 'en_US.UTF-8')
678
679
679 settings_maker.make_setting('core.binary_dir', '')
680 settings_maker.make_setting('core.binary_dir', '')
680
681
681 temp_store = tempfile.gettempdir()
682 temp_store = tempfile.gettempdir()
682 default_cache_dir = os.path.join(temp_store, 'rc_cache')
683 default_cache_dir = os.path.join(temp_store, 'rc_cache')
683 # save default, cache dir, and use it for all backends later.
684 # save default, cache dir, and use it for all backends later.
684 default_cache_dir = settings_maker.make_setting(
685 default_cache_dir = settings_maker.make_setting(
685 'cache_dir',
686 'cache_dir',
686 default=default_cache_dir, default_when_empty=True,
687 default=default_cache_dir, default_when_empty=True,
687 parser='dir:ensured')
688 parser='dir:ensured')
688
689
689 # exception store cache
690 # exception store cache
690 settings_maker.make_setting(
691 settings_maker.make_setting(
691 'exception_tracker.store_path',
692 'exception_tracker.store_path',
692 default=os.path.join(default_cache_dir, 'exc_store'), default_when_empty=True,
693 default=os.path.join(default_cache_dir, 'exc_store'), default_when_empty=True,
693 parser='dir:ensured'
694 parser='dir:ensured'
694 )
695 )
695
696
696 # repo_object cache defaults
697 # repo_object cache defaults
697 settings_maker.make_setting(
698 settings_maker.make_setting(
698 'rc_cache.repo_object.backend',
699 'rc_cache.repo_object.backend',
699 default='dogpile.cache.rc.file_namespace',
700 default='dogpile.cache.rc.file_namespace',
700 parser='string')
701 parser='string')
701 settings_maker.make_setting(
702 settings_maker.make_setting(
702 'rc_cache.repo_object.expiration_time',
703 'rc_cache.repo_object.expiration_time',
703 default=30 * 24 * 60 * 60, # 30days
704 default=30 * 24 * 60 * 60, # 30days
704 parser='int')
705 parser='int')
705 settings_maker.make_setting(
706 settings_maker.make_setting(
706 'rc_cache.repo_object.arguments.filename',
707 'rc_cache.repo_object.arguments.filename',
707 default=os.path.join(default_cache_dir, 'vcsserver_cache_repo_object.db'),
708 default=os.path.join(default_cache_dir, 'vcsserver_cache_repo_object.db'),
708 parser='string')
709 parser='string')
709
710
710 # statsd
711 # statsd
711 settings_maker.make_setting('statsd.enabled', False, parser='bool')
712 settings_maker.make_setting('statsd.enabled', False, parser='bool')
712 settings_maker.make_setting('statsd.statsd_host', 'statsd-exporter', parser='string')
713 settings_maker.make_setting('statsd.statsd_host', 'statsd-exporter', parser='string')
713 settings_maker.make_setting('statsd.statsd_port', 9125, parser='int')
714 settings_maker.make_setting('statsd.statsd_port', 9125, parser='int')
714 settings_maker.make_setting('statsd.statsd_prefix', '')
715 settings_maker.make_setting('statsd.statsd_prefix', '')
715 settings_maker.make_setting('statsd.statsd_ipv6', False, parser='bool')
716 settings_maker.make_setting('statsd.statsd_ipv6', False, parser='bool')
716
717
717 settings_maker.env_expand()
718 settings_maker.env_expand()
718
719
719
720
720 def main(global_config, **settings):
721 def main(global_config, **settings):
721 start_time = time.time()
722 start_time = time.time()
722 log.info('Pyramid app config starting')
723 log.info('Pyramid app config starting')
723
724
724 if MercurialFactory:
725 if MercurialFactory:
725 hgpatches.patch_largefiles_capabilities()
726 hgpatches.patch_largefiles_capabilities()
726 hgpatches.patch_subrepo_type_mapping()
727 hgpatches.patch_subrepo_type_mapping()
727
728
728 # Fill in and sanitize the defaults & do ENV expansion
729 # Fill in and sanitize the defaults & do ENV expansion
729 sanitize_settings_and_apply_defaults(global_config, settings)
730 sanitize_settings_and_apply_defaults(global_config, settings)
730
731
731 # init and bootstrap StatsdClient
732 # init and bootstrap StatsdClient
732 StatsdClient.setup(settings)
733 StatsdClient.setup(settings)
733
734
734 pyramid_app = HTTPApplication(settings=settings, global_config=global_config).wsgi_app()
735 pyramid_app = HTTPApplication(settings=settings, global_config=global_config).wsgi_app()
735 total_time = time.time() - start_time
736 total_time = time.time() - start_time
736 log.info('Pyramid app `%s` created and configured in %.2fs',
737 log.info('Pyramid app `%s` created and configured in %.2fs',
737 getattr(pyramid_app, 'func_name', 'pyramid_app'), total_time)
738 getattr(pyramid_app, 'func_name', 'pyramid_app'), total_time)
738 return pyramid_app
739 return pyramid_app
739
740
740
741
General Comments 0
You need to be logged in to leave comments. Login now