##// END OF EJS Templates
logging: improved logging levels to not leak info to clients
super-admin -
r1325:5ec15a34 default
parent child Browse files
Show More
@@ -1,797 +1,798 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-2024 RhodeCode GmbH
2 # Copyright (C) 2014-2024 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 logging
21 import logging
22 import collections
22 import collections
23 import base64
23 import base64
24 import msgpack
24 import msgpack
25 import dataclasses
25 import dataclasses
26 import pygit2
26 import pygit2
27
27
28 import http.client
28 import http.client
29 from celery import Celery
29 from celery import Celery
30
30
31 import mercurial.scmutil
31 import mercurial.scmutil
32 import mercurial.node
32 import mercurial.node
33
33
34 from vcsserver import exceptions, subprocessio, settings
34 from vcsserver import exceptions, subprocessio, settings
35 from vcsserver.lib.ext_json import json
35 from vcsserver.lib.ext_json import json
36 from vcsserver.lib.str_utils import ascii_str, safe_str
36 from vcsserver.lib.str_utils import ascii_str, safe_str
37 from vcsserver.lib.svn_txn_utils import get_txn_id_from_store
37 from vcsserver.lib.svn_txn_utils import get_txn_id_from_store
38 from vcsserver.remote.git_remote import Repository
38 from vcsserver.remote.git_remote import Repository
39
39
40 celery_app = Celery('__vcsserver__')
40 celery_app = Celery('__vcsserver__')
41 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
42
42
43
43
44 class HooksCeleryClient:
44 class HooksCeleryClient:
45 TASK_TIMEOUT = 60 # time in seconds
45 TASK_TIMEOUT = 60 # time in seconds
46 custom_opts = {}
46 custom_opts = {}
47
47
48 def __init__(self, broker_url, result_backend, **kwargs):
48 def __init__(self, broker_url, result_backend, **kwargs):
49 _celery_opts = {
49 _celery_opts = {
50 'broker_url': broker_url,
50 'broker_url': broker_url,
51 'result_backend': result_backend,
51 'result_backend': result_backend,
52 'broker_connection_retry_on_startup': True,
52 'broker_connection_retry_on_startup': True,
53 'task_serializer': 'json',
53 'task_serializer': 'json',
54 'accept_content': ['json', 'msgpack'],
54 'accept_content': ['json', 'msgpack'],
55 'result_serializer': 'json',
55 'result_serializer': 'json',
56 'result_accept_content': ['json', 'msgpack']
56 'result_accept_content': ['json', 'msgpack']
57 }
57 }
58 if custom_opts := kwargs.pop('_celery_opts', {}):
58 if custom_opts := kwargs.pop('_celery_opts', {}):
59 _celery_opts.update(custom_opts)
59 _celery_opts.update(custom_opts)
60 self.custom_opts = custom_opts
60 self.custom_opts = custom_opts
61 celery_app.config_from_object(_celery_opts)
61 celery_app.config_from_object(_celery_opts)
62
62
63 self.celery_app = celery_app
63 self.celery_app = celery_app
64
64
65 def __call__(self, method, extras):
65 def __call__(self, method, extras):
66 # NOTE: exception handling for those tasks executed is in
66 # NOTE: exception handling for those tasks executed is in
67 # @adapt_for_celery decorator
67 # @adapt_for_celery decorator
68 # also see: _maybe_handle_exception which is handling exceptions
68 # also see: _maybe_handle_exception which is handling exceptions
69
69
70 inquired_task = self.celery_app.signature(f'rhodecode.lib.celerylib.tasks.{method}')
70 inquired_task = self.celery_app.signature(f'rhodecode.lib.celerylib.tasks.{method}')
71 task_meta = inquired_task.apply_async(args=(extras,))
71 task_meta = inquired_task.apply_async(args=(extras,))
72 result = task_meta.get(timeout=self.TASK_TIMEOUT)
72 result = task_meta.get(timeout=self.TASK_TIMEOUT)
73
73
74 return result
74 return result
75
75
76 def __repr__(self):
76 def __repr__(self):
77 return f'HooksCeleryClient(opts={self.custom_opts})'
77 return f'HooksCeleryClient(opts={self.custom_opts})'
78
78
79 class HooksShadowRepoClient:
79 class HooksShadowRepoClient:
80
80
81 def __call__(self, hook_name, extras):
81 def __call__(self, hook_name, extras):
82 return {'output': '', 'status': 0}
82 return {'output': '', 'status': 0}
83
83
84
84
85 class RemoteMessageWriter:
85 class RemoteMessageWriter:
86 """Writer base class."""
86 """Writer base class."""
87 def write(self, message):
87 def write(self, message):
88 raise NotImplementedError()
88 raise NotImplementedError()
89
89
90
90
91 class HgMessageWriter(RemoteMessageWriter):
91 class HgMessageWriter(RemoteMessageWriter):
92 """Writer that knows how to send messages to mercurial clients."""
92 """Writer that knows how to send messages to mercurial clients."""
93
93
94 def __init__(self, ui):
94 def __init__(self, ui):
95 self.ui = ui
95 self.ui = ui
96
96
97 def write(self, message: str):
97 def write(self, message: str):
98 args = (message.encode('utf-8'),)
98 args = (message.encode('utf-8'),)
99 self.ui._writemsg(self.ui._fmsgerr, type=b'status', *args)
99 self.ui._writemsg(self.ui._fmsgerr, type=b'status', *args)
100
100
101
101
102 class GitMessageWriter(RemoteMessageWriter):
102 class GitMessageWriter(RemoteMessageWriter):
103 """Writer that knows how to send messages to git clients."""
103 """Writer that knows how to send messages to git clients."""
104
104
105 def __init__(self, stdout=None):
105 def __init__(self, stdout=None):
106 self.stdout = stdout or sys.stdout
106 self.stdout = stdout or sys.stdout
107
107
108 def write(self, message: str):
108 def write(self, message: str):
109 self.stdout.write(message + "\n" if message else "")
109 self.stdout.write(message + "\n" if message else "")
110
110
111
111
112 class SvnMessageWriter(RemoteMessageWriter):
112 class SvnMessageWriter(RemoteMessageWriter):
113 """Writer that knows how to send messages to svn clients."""
113 """Writer that knows how to send messages to svn clients."""
114
114
115 def __init__(self, stderr=None):
115 def __init__(self, stderr=None):
116 # SVN needs data sent to stderr for back-to-client messaging
116 # SVN needs data sent to stderr for back-to-client messaging
117 self.stderr = stderr or sys.stderr
117 self.stderr = stderr or sys.stderr
118
118
119 def write(self, message):
119 def write(self, message):
120 self.stderr.write(message)
120 self.stderr.write(message)
121
121
122
122
123 def _maybe_handle_exception(writer, result):
123 def _maybe_handle_exception(writer, result):
124 """
124 """
125 adopt_for_celery defines the exception/exception_traceback
125 adopt_for_celery defines the exception/exception_traceback
126 Ths result is a direct output from a celery task
126 Ths result is a direct output from a celery task
127 """
127 """
128
128
129 exception_class = result.get('exception')
129 exception_class = result.get('exception')
130 exception_traceback = result.get('exception_traceback')
130 exception_traceback = result.get('exception_traceback')
131
131
132 match exception_class:
132 match exception_class:
133 # NOTE: the underlying exceptions are setting _vcs_kind special marker
133 # NOTE: the underlying exceptions are setting _vcs_kind special marker
134 # which is later handled by `handle_vcs_exception` and translated into a special HTTP exception
134 # which is later handled by `handle_vcs_exception` and translated into a special HTTP exception
135 # propagated later to the client
135 # propagated later to the client
136 case 'HTTPLockedRepo':
136 case 'HTTPLockedRepo':
137 raise exceptions.LockedRepoException()(*result['exception_args'])
137 raise exceptions.LockedRepoException()(*result['exception_args'])
138 case 'ClientNotSupported':
138 case 'ClientNotSupported':
139 raise exceptions.ClientNotSupportedException()(*result['exception_args'])
139 raise exceptions.ClientNotSupportedException()(*result['exception_args'])
140 case 'HTTPBranchProtected':
140 case 'HTTPBranchProtected':
141 raise exceptions.RepositoryBranchProtectedException()(*result['exception_args'])
141 raise exceptions.RepositoryBranchProtectedException()(*result['exception_args'])
142 case 'RepositoryError':
142 case 'RepositoryError':
143 raise exceptions.VcsException()(*result['exception_args'])
143 raise exceptions.VcsException()(*result['exception_args'])
144 case _:
144 case _:
145 if exception_class:
145 if exception_class:
146 log.error('Handling hook-call exception. Got traceback from remote call:%s', exception_traceback)
146 # level here should be info/debug so the remote client wouldn't see this as a stderr message
147 log.info('ERROR: Handling hook-call exception. Got traceback from remote call:%s', exception_traceback)
147 raise Exception(
148 raise Exception(
148 f"""Got remote exception "{exception_class}" with args "{result['exception_args']}" """
149 f"""Got remote exception "{exception_class}" with args "{result['exception_args']}" """
149 )
150 )
150
151
151
152
152 def _get_hooks_client(extras):
153 def _get_hooks_client(extras):
153 is_shadow_repo = extras.get('is_shadow_repo')
154 is_shadow_repo = extras.get('is_shadow_repo')
154 hooks_protocol = extras.get('hooks_protocol')
155 hooks_protocol = extras.get('hooks_protocol')
155
156
156 if hooks_protocol == 'celery':
157 if hooks_protocol == 'celery':
157 try:
158 try:
158 celery_config = extras['hooks_config']
159 celery_config = extras['hooks_config']
159 broker_url = celery_config['broker_url']
160 broker_url = celery_config['broker_url']
160 result_backend = celery_config['result_backend']
161 result_backend = celery_config['result_backend']
161 except Exception:
162 except Exception:
162 log.exception("Failed to get celery task queue and backend")
163 log.exception("Failed to get celery task queue and backend")
163 raise
164 raise
164 return HooksCeleryClient(broker_url, result_backend, _celery_opts=celery_config)
165 return HooksCeleryClient(broker_url, result_backend, _celery_opts=celery_config)
165 elif is_shadow_repo:
166 elif is_shadow_repo:
166 return HooksShadowRepoClient()
167 return HooksShadowRepoClient()
167 else:
168 else:
168 raise Exception("Hooks client not found!")
169 raise Exception("Hooks client not found!")
169
170
170
171
171 def _call_hook(hook_name, extras, writer):
172 def _call_hook(hook_name, extras, writer):
172 hooks_client = _get_hooks_client(extras)
173 hooks_client = _get_hooks_client(extras)
173 log.debug('Hooks, using client:%s', hooks_client)
174 log.debug('Hooks, using client:%s', hooks_client)
174 result = hooks_client(hook_name, extras)
175 result = hooks_client(hook_name, extras)
175 log.debug('Hooks got result: %s', result)
176 log.debug('Hooks got result: %s', result)
176 _maybe_handle_exception(writer, result)
177 _maybe_handle_exception(writer, result)
177 writer.write(result['output'])
178 writer.write(result['output'])
178
179
179 return result['status']
180 return result['status']
180
181
181
182
182 def _extras_from_ui(ui):
183 def _extras_from_ui(ui):
183 hook_data = ui.config(b'rhodecode', b'RC_SCM_DATA')
184 hook_data = ui.config(b'rhodecode', b'RC_SCM_DATA')
184 if not hook_data:
185 if not hook_data:
185 # maybe it's inside environ ?
186 # maybe it's inside environ ?
186 env_hook_data = os.environ.get('RC_SCM_DATA')
187 env_hook_data = os.environ.get('RC_SCM_DATA')
187 if env_hook_data:
188 if env_hook_data:
188 hook_data = env_hook_data
189 hook_data = env_hook_data
189
190
190 extras = {}
191 extras = {}
191 if hook_data:
192 if hook_data:
192 extras = json.loads(hook_data)
193 extras = json.loads(hook_data)
193 return extras
194 return extras
194
195
195
196
196 def _rev_range_hash(repo, node, check_heads=False):
197 def _rev_range_hash(repo, node, check_heads=False):
197 from vcsserver.hgcompat import get_ctx
198 from vcsserver.hgcompat import get_ctx
198
199
199 commits = []
200 commits = []
200 revs = []
201 revs = []
201 start = get_ctx(repo, node).rev()
202 start = get_ctx(repo, node).rev()
202 end = len(repo)
203 end = len(repo)
203 for rev in range(start, end):
204 for rev in range(start, end):
204 revs.append(rev)
205 revs.append(rev)
205 ctx = get_ctx(repo, rev)
206 ctx = get_ctx(repo, rev)
206 commit_id = ascii_str(mercurial.node.hex(ctx.node()))
207 commit_id = ascii_str(mercurial.node.hex(ctx.node()))
207 branch = safe_str(ctx.branch())
208 branch = safe_str(ctx.branch())
208 commits.append((commit_id, branch))
209 commits.append((commit_id, branch))
209
210
210 parent_heads = []
211 parent_heads = []
211 if check_heads:
212 if check_heads:
212 parent_heads = _check_heads(repo, start, end, revs)
213 parent_heads = _check_heads(repo, start, end, revs)
213 return commits, parent_heads
214 return commits, parent_heads
214
215
215
216
216 def _check_heads(repo, start, end, commits):
217 def _check_heads(repo, start, end, commits):
217 from vcsserver.hgcompat import get_ctx
218 from vcsserver.hgcompat import get_ctx
218 changelog = repo.changelog
219 changelog = repo.changelog
219 parents = set()
220 parents = set()
220
221
221 for new_rev in commits:
222 for new_rev in commits:
222 for p in changelog.parentrevs(new_rev):
223 for p in changelog.parentrevs(new_rev):
223 if p == mercurial.node.nullrev:
224 if p == mercurial.node.nullrev:
224 continue
225 continue
225 if p < start:
226 if p < start:
226 parents.add(p)
227 parents.add(p)
227
228
228 for p in parents:
229 for p in parents:
229 branch = get_ctx(repo, p).branch()
230 branch = get_ctx(repo, p).branch()
230 # The heads descending from that parent, on the same branch
231 # The heads descending from that parent, on the same branch
231 parent_heads = {p}
232 parent_heads = {p}
232 reachable = {p}
233 reachable = {p}
233 for x in range(p + 1, end):
234 for x in range(p + 1, end):
234 if get_ctx(repo, x).branch() != branch:
235 if get_ctx(repo, x).branch() != branch:
235 continue
236 continue
236 for pp in changelog.parentrevs(x):
237 for pp in changelog.parentrevs(x):
237 if pp in reachable:
238 if pp in reachable:
238 reachable.add(x)
239 reachable.add(x)
239 parent_heads.discard(pp)
240 parent_heads.discard(pp)
240 parent_heads.add(x)
241 parent_heads.add(x)
241 # More than one head? Suggest merging
242 # More than one head? Suggest merging
242 if len(parent_heads) > 1:
243 if len(parent_heads) > 1:
243 return list(parent_heads)
244 return list(parent_heads)
244
245
245 return []
246 return []
246
247
247
248
248 def _get_git_env():
249 def _get_git_env():
249 env = {}
250 env = {}
250 for k, v in os.environ.items():
251 for k, v in os.environ.items():
251 if k.startswith('GIT'):
252 if k.startswith('GIT'):
252 env[k] = v
253 env[k] = v
253
254
254 # serialized version
255 # serialized version
255 return [(k, v) for k, v in env.items()]
256 return [(k, v) for k, v in env.items()]
256
257
257
258
258 def _get_hg_env(old_rev, new_rev, txnid, repo_path):
259 def _get_hg_env(old_rev, new_rev, txnid, repo_path):
259 env = {}
260 env = {}
260 for k, v in os.environ.items():
261 for k, v in os.environ.items():
261 if k.startswith('HG'):
262 if k.startswith('HG'):
262 env[k] = v
263 env[k] = v
263
264
264 env['HG_NODE'] = old_rev
265 env['HG_NODE'] = old_rev
265 env['HG_NODE_LAST'] = new_rev
266 env['HG_NODE_LAST'] = new_rev
266 env['HG_TXNID'] = txnid
267 env['HG_TXNID'] = txnid
267 env['HG_PENDING'] = repo_path
268 env['HG_PENDING'] = repo_path
268
269
269 return [(k, v) for k, v in env.items()]
270 return [(k, v) for k, v in env.items()]
270
271
271
272
272 def _get_ini_settings(ini_file):
273 def _get_ini_settings(ini_file):
273 from vcsserver.http_main import sanitize_settings_and_apply_defaults
274 from vcsserver.http_main import sanitize_settings_and_apply_defaults
274 from vcsserver.lib.config_utils import get_app_config_lightweight, configure_and_store_settings
275 from vcsserver.lib.config_utils import get_app_config_lightweight, configure_and_store_settings
275
276
276 global_config = {'__file__': ini_file}
277 global_config = {'__file__': ini_file}
277 ini_settings = get_app_config_lightweight(ini_file)
278 ini_settings = get_app_config_lightweight(ini_file)
278 sanitize_settings_and_apply_defaults(global_config, ini_settings)
279 sanitize_settings_and_apply_defaults(global_config, ini_settings)
279 configure_and_store_settings(global_config, ini_settings)
280 configure_and_store_settings(global_config, ini_settings)
280
281
281 return ini_settings
282 return ini_settings
282
283
283
284
284 def _fix_hooks_executables(ini_path=''):
285 def _fix_hooks_executables(ini_path=''):
285 """
286 """
286 This is a trick to set proper settings.EXECUTABLE paths for certain execution patterns
287 This is a trick to set proper settings.EXECUTABLE paths for certain execution patterns
287 especially for subversion where hooks strip entire env, and calling just 'svn' command will most likely fail
288 especially for subversion where hooks strip entire env, and calling just 'svn' command will most likely fail
288 because svn is not on PATH
289 because svn is not on PATH
289 """
290 """
290 # set defaults, in case we can't read from ini_file
291 # set defaults, in case we can't read from ini_file
291 core_binary_dir = settings.BINARY_DIR or '/usr/local/bin/rhodecode_bin/vcs_bin'
292 core_binary_dir = settings.BINARY_DIR or '/usr/local/bin/rhodecode_bin/vcs_bin'
292 if ini_path:
293 if ini_path:
293 ini_settings = _get_ini_settings(ini_path)
294 ini_settings = _get_ini_settings(ini_path)
294 core_binary_dir = ini_settings['core.binary_dir']
295 core_binary_dir = ini_settings['core.binary_dir']
295
296
296 settings.BINARY_DIR = core_binary_dir
297 settings.BINARY_DIR = core_binary_dir
297
298
298
299
299 def repo_size(ui, repo, **kwargs):
300 def repo_size(ui, repo, **kwargs):
300 extras = _extras_from_ui(ui)
301 extras = _extras_from_ui(ui)
301 return _call_hook('repo_size', extras, HgMessageWriter(ui))
302 return _call_hook('repo_size', extras, HgMessageWriter(ui))
302
303
303
304
304 def pre_pull(ui, repo, **kwargs):
305 def pre_pull(ui, repo, **kwargs):
305 extras = _extras_from_ui(ui)
306 extras = _extras_from_ui(ui)
306 return _call_hook('pre_pull', extras, HgMessageWriter(ui))
307 return _call_hook('pre_pull', extras, HgMessageWriter(ui))
307
308
308
309
309 def pre_pull_ssh(ui, repo, **kwargs):
310 def pre_pull_ssh(ui, repo, **kwargs):
310 extras = _extras_from_ui(ui)
311 extras = _extras_from_ui(ui)
311 if extras and extras.get('SSH'):
312 if extras and extras.get('SSH'):
312 return pre_pull(ui, repo, **kwargs)
313 return pre_pull(ui, repo, **kwargs)
313 return 0
314 return 0
314
315
315
316
316 def post_pull(ui, repo, **kwargs):
317 def post_pull(ui, repo, **kwargs):
317 extras = _extras_from_ui(ui)
318 extras = _extras_from_ui(ui)
318 return _call_hook('post_pull', extras, HgMessageWriter(ui))
319 return _call_hook('post_pull', extras, HgMessageWriter(ui))
319
320
320
321
321 def post_pull_ssh(ui, repo, **kwargs):
322 def post_pull_ssh(ui, repo, **kwargs):
322 extras = _extras_from_ui(ui)
323 extras = _extras_from_ui(ui)
323 if extras and extras.get('SSH'):
324 if extras and extras.get('SSH'):
324 return post_pull(ui, repo, **kwargs)
325 return post_pull(ui, repo, **kwargs)
325 return 0
326 return 0
326
327
327
328
328 def pre_push(ui, repo, node=None, **kwargs):
329 def pre_push(ui, repo, node=None, **kwargs):
329 """
330 """
330 Mercurial pre_push hook
331 Mercurial pre_push hook
331 """
332 """
332 extras = _extras_from_ui(ui)
333 extras = _extras_from_ui(ui)
333 detect_force_push = extras.get('detect_force_push')
334 detect_force_push = extras.get('detect_force_push')
334
335
335 rev_data = []
336 rev_data = []
336 hook_type: str = safe_str(kwargs.get('hooktype'))
337 hook_type: str = safe_str(kwargs.get('hooktype'))
337
338
338 if node and hook_type == 'pretxnchangegroup':
339 if node and hook_type == 'pretxnchangegroup':
339 branches = collections.defaultdict(list)
340 branches = collections.defaultdict(list)
340 commits, _heads = _rev_range_hash(repo, node, check_heads=detect_force_push)
341 commits, _heads = _rev_range_hash(repo, node, check_heads=detect_force_push)
341 for commit_id, branch in commits:
342 for commit_id, branch in commits:
342 branches[branch].append(commit_id)
343 branches[branch].append(commit_id)
343
344
344 for branch, commits in branches.items():
345 for branch, commits in branches.items():
345 old_rev = ascii_str(kwargs.get('node_last')) or commits[0]
346 old_rev = ascii_str(kwargs.get('node_last')) or commits[0]
346 rev_data.append({
347 rev_data.append({
347 'total_commits': len(commits),
348 'total_commits': len(commits),
348 'old_rev': old_rev,
349 'old_rev': old_rev,
349 'new_rev': commits[-1],
350 'new_rev': commits[-1],
350 'ref': '',
351 'ref': '',
351 'type': 'branch',
352 'type': 'branch',
352 'name': branch,
353 'name': branch,
353 })
354 })
354
355
355 for push_ref in rev_data:
356 for push_ref in rev_data:
356 push_ref['multiple_heads'] = _heads
357 push_ref['multiple_heads'] = _heads
357
358
358 repo_path = os.path.join(
359 repo_path = os.path.join(
359 extras.get('repo_store', ''), extras.get('repository', ''))
360 extras.get('repo_store', ''), extras.get('repository', ''))
360 push_ref['hg_env'] = _get_hg_env(
361 push_ref['hg_env'] = _get_hg_env(
361 old_rev=push_ref['old_rev'],
362 old_rev=push_ref['old_rev'],
362 new_rev=push_ref['new_rev'], txnid=ascii_str(kwargs.get('txnid')),
363 new_rev=push_ref['new_rev'], txnid=ascii_str(kwargs.get('txnid')),
363 repo_path=repo_path)
364 repo_path=repo_path)
364
365
365 extras['hook_type'] = hook_type or 'pre_push'
366 extras['hook_type'] = hook_type or 'pre_push'
366 extras['commit_ids'] = rev_data
367 extras['commit_ids'] = rev_data
367
368
368 return _call_hook('pre_push', extras, HgMessageWriter(ui))
369 return _call_hook('pre_push', extras, HgMessageWriter(ui))
369
370
370
371
371 def pre_push_ssh(ui, repo, node=None, **kwargs):
372 def pre_push_ssh(ui, repo, node=None, **kwargs):
372 extras = _extras_from_ui(ui)
373 extras = _extras_from_ui(ui)
373 if extras.get('SSH'):
374 if extras.get('SSH'):
374 return pre_push(ui, repo, node, **kwargs)
375 return pre_push(ui, repo, node, **kwargs)
375
376
376 return 0
377 return 0
377
378
378
379
379 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
380 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
380 """
381 """
381 Mercurial pre_push hook for SSH
382 Mercurial pre_push hook for SSH
382 """
383 """
383 extras = _extras_from_ui(ui)
384 extras = _extras_from_ui(ui)
384 if extras.get('SSH'):
385 if extras.get('SSH'):
385 permission = extras['SSH_PERMISSIONS']
386 permission = extras['SSH_PERMISSIONS']
386
387
387 if 'repository.write' == permission or 'repository.admin' == permission:
388 if 'repository.write' == permission or 'repository.admin' == permission:
388 return 0
389 return 0
389
390
390 # non-zero ret code
391 # non-zero ret code
391 return 1
392 return 1
392
393
393 return 0
394 return 0
394
395
395
396
396 def post_push(ui, repo, node, **kwargs):
397 def post_push(ui, repo, node, **kwargs):
397 """
398 """
398 Mercurial post_push hook
399 Mercurial post_push hook
399 """
400 """
400 extras = _extras_from_ui(ui)
401 extras = _extras_from_ui(ui)
401
402
402 commit_ids = []
403 commit_ids = []
403 branches = []
404 branches = []
404 bookmarks = []
405 bookmarks = []
405 tags = []
406 tags = []
406 hook_type: str = safe_str(kwargs.get('hooktype'))
407 hook_type: str = safe_str(kwargs.get('hooktype'))
407
408
408 commits, _heads = _rev_range_hash(repo, node)
409 commits, _heads = _rev_range_hash(repo, node)
409 for commit_id, branch in commits:
410 for commit_id, branch in commits:
410 commit_ids.append(commit_id)
411 commit_ids.append(commit_id)
411 if branch not in branches:
412 if branch not in branches:
412 branches.append(branch)
413 branches.append(branch)
413
414
414 if hasattr(ui, '_rc_pushkey_bookmarks'):
415 if hasattr(ui, '_rc_pushkey_bookmarks'):
415 bookmarks = ui._rc_pushkey_bookmarks
416 bookmarks = ui._rc_pushkey_bookmarks
416
417
417 extras['hook_type'] = hook_type or 'post_push'
418 extras['hook_type'] = hook_type or 'post_push'
418 extras['commit_ids'] = commit_ids
419 extras['commit_ids'] = commit_ids
419
420
420 extras['new_refs'] = {
421 extras['new_refs'] = {
421 'branches': branches,
422 'branches': branches,
422 'bookmarks': bookmarks,
423 'bookmarks': bookmarks,
423 'tags': tags
424 'tags': tags
424 }
425 }
425
426
426 return _call_hook('post_push', extras, HgMessageWriter(ui))
427 return _call_hook('post_push', extras, HgMessageWriter(ui))
427
428
428
429
429 def post_push_ssh(ui, repo, node, **kwargs):
430 def post_push_ssh(ui, repo, node, **kwargs):
430 """
431 """
431 Mercurial post_push hook for SSH
432 Mercurial post_push hook for SSH
432 """
433 """
433 if _extras_from_ui(ui).get('SSH'):
434 if _extras_from_ui(ui).get('SSH'):
434 return post_push(ui, repo, node, **kwargs)
435 return post_push(ui, repo, node, **kwargs)
435 return 0
436 return 0
436
437
437
438
438 def key_push(ui, repo, **kwargs):
439 def key_push(ui, repo, **kwargs):
439 from vcsserver.hgcompat import get_ctx
440 from vcsserver.hgcompat import get_ctx
440
441
441 if kwargs['new'] != b'0' and kwargs['namespace'] == b'bookmarks':
442 if kwargs['new'] != b'0' and kwargs['namespace'] == b'bookmarks':
442 # store new bookmarks in our UI object propagated later to post_push
443 # store new bookmarks in our UI object propagated later to post_push
443 ui._rc_pushkey_bookmarks = get_ctx(repo, kwargs['key']).bookmarks()
444 ui._rc_pushkey_bookmarks = get_ctx(repo, kwargs['key']).bookmarks()
444 return
445 return
445
446
446
447
447 # backward compat
448 # backward compat
448 log_pull_action = post_pull
449 log_pull_action = post_pull
449
450
450 # backward compat
451 # backward compat
451 log_push_action = post_push
452 log_push_action = post_push
452
453
453
454
454 def handle_git_pre_receive(unused_repo_path, unused_revs, unused_env):
455 def handle_git_pre_receive(unused_repo_path, unused_revs, unused_env):
455 """
456 """
456 Old hook name: keep here for backward compatibility.
457 Old hook name: keep here for backward compatibility.
457
458
458 This is only required when the installed git hooks are not upgraded.
459 This is only required when the installed git hooks are not upgraded.
459 """
460 """
460 pass
461 pass
461
462
462
463
463 def handle_git_post_receive(unused_repo_path, unused_revs, unused_env):
464 def handle_git_post_receive(unused_repo_path, unused_revs, unused_env):
464 """
465 """
465 Old hook name: keep here for backward compatibility.
466 Old hook name: keep here for backward compatibility.
466
467
467 This is only required when the installed git hooks are not upgraded.
468 This is only required when the installed git hooks are not upgraded.
468 """
469 """
469 pass
470 pass
470
471
471
472
472 @dataclasses.dataclass
473 @dataclasses.dataclass
473 class HookResponse:
474 class HookResponse:
474 status: int
475 status: int
475 output: str
476 output: str
476
477
477
478
478 def git_pre_pull(extras) -> HookResponse:
479 def git_pre_pull(extras) -> HookResponse:
479 """
480 """
480 Pre pull hook.
481 Pre pull hook.
481
482
482 :param extras: dictionary containing the keys defined in simplevcs
483 :param extras: dictionary containing the keys defined in simplevcs
483 :type extras: dict
484 :type extras: dict
484
485
485 :return: status code of the hook. 0 for success.
486 :return: status code of the hook. 0 for success.
486 :rtype: int
487 :rtype: int
487 """
488 """
488
489
489 if 'pull' not in extras['hooks']:
490 if 'pull' not in extras['hooks']:
490 return HookResponse(0, '')
491 return HookResponse(0, '')
491
492
492 stdout = io.StringIO()
493 stdout = io.StringIO()
493 try:
494 try:
494 status_code = _call_hook('pre_pull', extras, GitMessageWriter(stdout))
495 status_code = _call_hook('pre_pull', extras, GitMessageWriter(stdout))
495
496
496 except Exception as error:
497 except Exception as error:
497 log.exception('Failed to call pre_pull hook')
498 log.exception('Failed to call pre_pull hook')
498 status_code = 128
499 status_code = 128
499 stdout.write(f'ERROR: {error}\n')
500 stdout.write(f'ERROR: {error}\n')
500
501
501 return HookResponse(status_code, stdout.getvalue())
502 return HookResponse(status_code, stdout.getvalue())
502
503
503
504
504 def git_post_pull(extras) -> HookResponse:
505 def git_post_pull(extras) -> HookResponse:
505 """
506 """
506 Post pull hook.
507 Post pull hook.
507
508
508 :param extras: dictionary containing the keys defined in simplevcs
509 :param extras: dictionary containing the keys defined in simplevcs
509 :type extras: dict
510 :type extras: dict
510
511
511 :return: status code of the hook. 0 for success.
512 :return: status code of the hook. 0 for success.
512 :rtype: int
513 :rtype: int
513 """
514 """
514 if 'pull' not in extras['hooks']:
515 if 'pull' not in extras['hooks']:
515 return HookResponse(0, '')
516 return HookResponse(0, '')
516
517
517 stdout = io.StringIO()
518 stdout = io.StringIO()
518 try:
519 try:
519 status = _call_hook('post_pull', extras, GitMessageWriter(stdout))
520 status = _call_hook('post_pull', extras, GitMessageWriter(stdout))
520 except Exception as error:
521 except Exception as error:
521 status = 128
522 status = 128
522 stdout.write(f'ERROR: {error}\n')
523 stdout.write(f'ERROR: {error}\n')
523
524
524 return HookResponse(status, stdout.getvalue())
525 return HookResponse(status, stdout.getvalue())
525
526
526
527
527 def _parse_git_ref_lines(revision_lines):
528 def _parse_git_ref_lines(revision_lines):
528 rev_data = []
529 rev_data = []
529 for revision_line in revision_lines or []:
530 for revision_line in revision_lines or []:
530 old_rev, new_rev, ref = revision_line.strip().split(' ')
531 old_rev, new_rev, ref = revision_line.strip().split(' ')
531 ref_data = ref.split('/', 2)
532 ref_data = ref.split('/', 2)
532 if ref_data[1] in ('tags', 'heads'):
533 if ref_data[1] in ('tags', 'heads'):
533 rev_data.append({
534 rev_data.append({
534 # NOTE(marcink):
535 # NOTE(marcink):
535 # we're unable to tell total_commits for git at this point
536 # we're unable to tell total_commits for git at this point
536 # but we set the variable for consistency with GIT
537 # but we set the variable for consistency with GIT
537 'total_commits': -1,
538 'total_commits': -1,
538 'old_rev': old_rev,
539 'old_rev': old_rev,
539 'new_rev': new_rev,
540 'new_rev': new_rev,
540 'ref': ref,
541 'ref': ref,
541 'type': ref_data[1],
542 'type': ref_data[1],
542 'name': ref_data[2],
543 'name': ref_data[2],
543 })
544 })
544 return rev_data
545 return rev_data
545
546
546
547
547 def git_pre_receive(unused_repo_path, revision_lines, env) -> int:
548 def git_pre_receive(unused_repo_path, revision_lines, env) -> int:
548 """
549 """
549 Pre push hook.
550 Pre push hook.
550
551
551 :return: status code of the hook. 0 for success.
552 :return: status code of the hook. 0 for success.
552 """
553 """
553 extras = json.loads(env['RC_SCM_DATA'])
554 extras = json.loads(env['RC_SCM_DATA'])
554 rev_data = _parse_git_ref_lines(revision_lines)
555 rev_data = _parse_git_ref_lines(revision_lines)
555 if 'push' not in extras['hooks']:
556 if 'push' not in extras['hooks']:
556 return 0
557 return 0
557 _fix_hooks_executables(env.get('RC_INI_FILE'))
558 _fix_hooks_executables(env.get('RC_INI_FILE'))
558
559
559 empty_commit_id = '0' * 40
560 empty_commit_id = '0' * 40
560
561
561 detect_force_push = extras.get('detect_force_push')
562 detect_force_push = extras.get('detect_force_push')
562
563
563 for push_ref in rev_data:
564 for push_ref in rev_data:
564 # store our git-env which holds the temp store
565 # store our git-env which holds the temp store
565 push_ref['git_env'] = _get_git_env()
566 push_ref['git_env'] = _get_git_env()
566 push_ref['pruned_sha'] = ''
567 push_ref['pruned_sha'] = ''
567 if not detect_force_push:
568 if not detect_force_push:
568 # don't check for forced-push when we don't need to
569 # don't check for forced-push when we don't need to
569 continue
570 continue
570
571
571 type_ = push_ref['type']
572 type_ = push_ref['type']
572 new_branch = push_ref['old_rev'] == empty_commit_id
573 new_branch = push_ref['old_rev'] == empty_commit_id
573 delete_branch = push_ref['new_rev'] == empty_commit_id
574 delete_branch = push_ref['new_rev'] == empty_commit_id
574 if type_ == 'heads' and not (new_branch or delete_branch):
575 if type_ == 'heads' and not (new_branch or delete_branch):
575 old_rev = push_ref['old_rev']
576 old_rev = push_ref['old_rev']
576 new_rev = push_ref['new_rev']
577 new_rev = push_ref['new_rev']
577 cmd = [settings.GIT_EXECUTABLE(), 'rev-list', old_rev, f'^{new_rev}']
578 cmd = [settings.GIT_EXECUTABLE(), 'rev-list', old_rev, f'^{new_rev}']
578 stdout, stderr = subprocessio.run_command(
579 stdout, stderr = subprocessio.run_command(
579 cmd, env=os.environ.copy())
580 cmd, env=os.environ.copy())
580 # means we're having some non-reachable objects, this forced push was used
581 # means we're having some non-reachable objects, this forced push was used
581 if stdout:
582 if stdout:
582 push_ref['pruned_sha'] = stdout.splitlines()
583 push_ref['pruned_sha'] = stdout.splitlines()
583
584
584 extras['hook_type'] = 'pre_receive'
585 extras['hook_type'] = 'pre_receive'
585 extras['commit_ids'] = rev_data
586 extras['commit_ids'] = rev_data
586
587
587 stdout = sys.stdout
588 stdout = sys.stdout
588 status_code = _call_hook('pre_push', extras, GitMessageWriter(stdout))
589 status_code = _call_hook('pre_push', extras, GitMessageWriter(stdout))
589
590
590 return status_code
591 return status_code
591
592
592
593
593 def git_post_receive(unused_repo_path, revision_lines, env) -> int:
594 def git_post_receive(unused_repo_path, revision_lines, env) -> int:
594 """
595 """
595 Post push hook.
596 Post push hook.
596
597
597 :return: status code of the hook. 0 for success.
598 :return: status code of the hook. 0 for success.
598 """
599 """
599 extras = json.loads(env['RC_SCM_DATA'])
600 extras = json.loads(env['RC_SCM_DATA'])
600 if 'push' not in extras['hooks']:
601 if 'push' not in extras['hooks']:
601 return 0
602 return 0
602
603
603 _fix_hooks_executables(env.get('RC_INI_FILE'))
604 _fix_hooks_executables(env.get('RC_INI_FILE'))
604
605
605 rev_data = _parse_git_ref_lines(revision_lines)
606 rev_data = _parse_git_ref_lines(revision_lines)
606
607
607 git_revs = []
608 git_revs = []
608
609
609 # N.B.(skreft): it is ok to just call git, as git before calling a
610 # N.B.(skreft): it is ok to just call git, as git before calling a
610 # subcommand sets the PATH environment variable so that it point to the
611 # subcommand sets the PATH environment variable so that it point to the
611 # correct version of the git executable.
612 # correct version of the git executable.
612 empty_commit_id = '0' * 40
613 empty_commit_id = '0' * 40
613 branches = []
614 branches = []
614 tags = []
615 tags = []
615 for push_ref in rev_data:
616 for push_ref in rev_data:
616 type_ = push_ref['type']
617 type_ = push_ref['type']
617
618
618 if type_ == 'heads':
619 if type_ == 'heads':
619 # starting new branch case
620 # starting new branch case
620 if push_ref['old_rev'] == empty_commit_id:
621 if push_ref['old_rev'] == empty_commit_id:
621 push_ref_name = push_ref['name']
622 push_ref_name = push_ref['name']
622
623
623 if push_ref_name not in branches:
624 if push_ref_name not in branches:
624 branches.append(push_ref_name)
625 branches.append(push_ref_name)
625
626
626 need_head_set = ''
627 need_head_set = ''
627 with Repository(os.getcwd()) as repo:
628 with Repository(os.getcwd()) as repo:
628 try:
629 try:
629 repo.head
630 repo.head
630 except pygit2.GitError:
631 except pygit2.GitError:
631 need_head_set = f'refs/heads/{push_ref_name}'
632 need_head_set = f'refs/heads/{push_ref_name}'
632
633
633 if need_head_set:
634 if need_head_set:
634 repo.set_head(need_head_set)
635 repo.set_head(need_head_set)
635 print(f"Setting default branch to {push_ref_name}")
636 print(f"Setting default branch to {push_ref_name}")
636
637
637 cmd = [settings.GIT_EXECUTABLE(), 'for-each-ref', '--format=%(refname)', 'refs/heads/*']
638 cmd = [settings.GIT_EXECUTABLE(), 'for-each-ref', '--format=%(refname)', 'refs/heads/*']
638 stdout, stderr = subprocessio.run_command(
639 stdout, stderr = subprocessio.run_command(
639 cmd, env=os.environ.copy())
640 cmd, env=os.environ.copy())
640 heads = safe_str(stdout)
641 heads = safe_str(stdout)
641 heads = heads.replace(push_ref['ref'], '')
642 heads = heads.replace(push_ref['ref'], '')
642 heads = ' '.join(head for head
643 heads = ' '.join(head for head
643 in heads.splitlines() if head) or '.'
644 in heads.splitlines() if head) or '.'
644 cmd = [settings.GIT_EXECUTABLE(), 'log', '--reverse',
645 cmd = [settings.GIT_EXECUTABLE(), 'log', '--reverse',
645 '--pretty=format:%H', '--', push_ref['new_rev'],
646 '--pretty=format:%H', '--', push_ref['new_rev'],
646 '--not', heads]
647 '--not', heads]
647 stdout, stderr = subprocessio.run_command(
648 stdout, stderr = subprocessio.run_command(
648 cmd, env=os.environ.copy())
649 cmd, env=os.environ.copy())
649 git_revs.extend(list(map(ascii_str, stdout.splitlines())))
650 git_revs.extend(list(map(ascii_str, stdout.splitlines())))
650
651
651 # delete branch case
652 # delete branch case
652 elif push_ref['new_rev'] == empty_commit_id:
653 elif push_ref['new_rev'] == empty_commit_id:
653 git_revs.append(f'delete_branch=>{push_ref["name"]}')
654 git_revs.append(f'delete_branch=>{push_ref["name"]}')
654 else:
655 else:
655 if push_ref['name'] not in branches:
656 if push_ref['name'] not in branches:
656 branches.append(push_ref['name'])
657 branches.append(push_ref['name'])
657
658
658 cmd = [settings.GIT_EXECUTABLE(), 'log',
659 cmd = [settings.GIT_EXECUTABLE(), 'log',
659 f'{push_ref["old_rev"]}..{push_ref["new_rev"]}',
660 f'{push_ref["old_rev"]}..{push_ref["new_rev"]}',
660 '--reverse', '--pretty=format:%H']
661 '--reverse', '--pretty=format:%H']
661 stdout, stderr = subprocessio.run_command(
662 stdout, stderr = subprocessio.run_command(
662 cmd, env=os.environ.copy())
663 cmd, env=os.environ.copy())
663 # we get bytes from stdout, we need str to be consistent
664 # we get bytes from stdout, we need str to be consistent
664 log_revs = list(map(ascii_str, stdout.splitlines()))
665 log_revs = list(map(ascii_str, stdout.splitlines()))
665 git_revs.extend(log_revs)
666 git_revs.extend(log_revs)
666
667
667 # Pure pygit2 impl. but still 2-3x slower :/
668 # Pure pygit2 impl. but still 2-3x slower :/
668 # results = []
669 # results = []
669 #
670 #
670 # with Repository(os.getcwd()) as repo:
671 # with Repository(os.getcwd()) as repo:
671 # repo_new_rev = repo[push_ref['new_rev']]
672 # repo_new_rev = repo[push_ref['new_rev']]
672 # repo_old_rev = repo[push_ref['old_rev']]
673 # repo_old_rev = repo[push_ref['old_rev']]
673 # walker = repo.walk(repo_new_rev.id, pygit2.GIT_SORT_TOPOLOGICAL)
674 # walker = repo.walk(repo_new_rev.id, pygit2.GIT_SORT_TOPOLOGICAL)
674 #
675 #
675 # for commit in walker:
676 # for commit in walker:
676 # if commit.id == repo_old_rev.id:
677 # if commit.id == repo_old_rev.id:
677 # break
678 # break
678 # results.append(commit.id.hex)
679 # results.append(commit.id.hex)
679 # # reverse the order, can't use GIT_SORT_REVERSE
680 # # reverse the order, can't use GIT_SORT_REVERSE
680 # log_revs = results[::-1]
681 # log_revs = results[::-1]
681
682
682 elif type_ == 'tags':
683 elif type_ == 'tags':
683 if push_ref['name'] not in tags:
684 if push_ref['name'] not in tags:
684 tags.append(push_ref['name'])
685 tags.append(push_ref['name'])
685 git_revs.append(f'tag=>{push_ref["name"]}')
686 git_revs.append(f'tag=>{push_ref["name"]}')
686
687
687 extras['hook_type'] = 'post_receive'
688 extras['hook_type'] = 'post_receive'
688 extras['commit_ids'] = git_revs
689 extras['commit_ids'] = git_revs
689 extras['new_refs'] = {
690 extras['new_refs'] = {
690 'branches': branches,
691 'branches': branches,
691 'bookmarks': [],
692 'bookmarks': [],
692 'tags': tags,
693 'tags': tags,
693 }
694 }
694
695
695 stdout = sys.stdout
696 stdout = sys.stdout
696
697
697 if 'repo_size' in extras['hooks']:
698 if 'repo_size' in extras['hooks']:
698 try:
699 try:
699 _call_hook('repo_size', extras, GitMessageWriter(stdout))
700 _call_hook('repo_size', extras, GitMessageWriter(stdout))
700 except Exception:
701 except Exception:
701 pass
702 pass
702
703
703 status_code = _call_hook('post_push', extras, GitMessageWriter(stdout))
704 status_code = _call_hook('post_push', extras, GitMessageWriter(stdout))
704 return status_code
705 return status_code
705
706
706
707
707 def get_extras_from_txn_id(repo_path, txn_id):
708 def get_extras_from_txn_id(repo_path, txn_id):
708 extras = get_txn_id_from_store(repo_path, txn_id)
709 extras = get_txn_id_from_store(repo_path, txn_id)
709 return extras
710 return extras
710
711
711
712
712 def svn_pre_commit(repo_path, commit_data, env):
713 def svn_pre_commit(repo_path, commit_data, env):
713
714
714 path, txn_id = commit_data
715 path, txn_id = commit_data
715 branches = []
716 branches = []
716 tags = []
717 tags = []
717
718
718 if env.get('RC_SCM_DATA'):
719 if env.get('RC_SCM_DATA'):
719 extras = json.loads(env['RC_SCM_DATA'])
720 extras = json.loads(env['RC_SCM_DATA'])
720 else:
721 else:
721 ini_path = env.get('RC_INI_FILE')
722 ini_path = env.get('RC_INI_FILE')
722 if ini_path:
723 if ini_path:
723 _get_ini_settings(ini_path)
724 _get_ini_settings(ini_path)
724 # fallback method to read from TXN-ID stored data
725 # fallback method to read from TXN-ID stored data
725 extras = get_extras_from_txn_id(path, txn_id)
726 extras = get_extras_from_txn_id(path, txn_id)
726
727
727 if not extras:
728 if not extras:
728 raise ValueError('SVN-PRE-COMMIT: Failed to extract context data in called extras for hook execution')
729 raise ValueError('SVN-PRE-COMMIT: Failed to extract context data in called extras for hook execution')
729
730
730 if extras.get('rc_internal_commit'):
731 if extras.get('rc_internal_commit'):
731 # special marker for internal commit, we don't call hooks client
732 # special marker for internal commit, we don't call hooks client
732 return 0
733 return 0
733
734
734 extras['hook_type'] = 'pre_commit'
735 extras['hook_type'] = 'pre_commit'
735 extras['commit_ids'] = [txn_id]
736 extras['commit_ids'] = [txn_id]
736 extras['txn_id'] = txn_id
737 extras['txn_id'] = txn_id
737 extras['new_refs'] = {
738 extras['new_refs'] = {
738 'total_commits': 1,
739 'total_commits': 1,
739 'branches': branches,
740 'branches': branches,
740 'bookmarks': [],
741 'bookmarks': [],
741 'tags': tags,
742 'tags': tags,
742 }
743 }
743
744
744 return _call_hook('pre_push', extras, SvnMessageWriter())
745 return _call_hook('pre_push', extras, SvnMessageWriter())
745
746
746
747
747 def svn_post_commit(repo_path, commit_data, env):
748 def svn_post_commit(repo_path, commit_data, env):
748 """
749 """
749 commit_data is path, rev, txn_id
750 commit_data is path, rev, txn_id
750 """
751 """
751
752
752 if len(commit_data) == 3:
753 if len(commit_data) == 3:
753 path, commit_id, txn_id = commit_data
754 path, commit_id, txn_id = commit_data
754 elif len(commit_data) == 2:
755 elif len(commit_data) == 2:
755 log.error('Failed to extract txn_id from commit_data using legacy method. '
756 log.error('Failed to extract txn_id from commit_data using legacy method. '
756 'Some functionality might be limited')
757 'Some functionality might be limited')
757 path, commit_id = commit_data
758 path, commit_id = commit_data
758 txn_id = None
759 txn_id = None
759 else:
760 else:
760 return 0
761 return 0
761
762
762 branches = []
763 branches = []
763 tags = []
764 tags = []
764
765
765 if env.get('RC_SCM_DATA'):
766 if env.get('RC_SCM_DATA'):
766 extras = json.loads(env['RC_SCM_DATA'])
767 extras = json.loads(env['RC_SCM_DATA'])
767 else:
768 else:
768 ini_path = env.get('RC_INI_FILE')
769 ini_path = env.get('RC_INI_FILE')
769 if ini_path:
770 if ini_path:
770 _get_ini_settings(ini_path)
771 _get_ini_settings(ini_path)
771 # fallback method to read from TXN-ID stored data
772 # fallback method to read from TXN-ID stored data
772 extras = get_extras_from_txn_id(path, txn_id)
773 extras = get_extras_from_txn_id(path, txn_id)
773
774
774 if not extras and txn_id:
775 if not extras and txn_id:
775 raise ValueError('SVN-POST-COMMIT: Failed to extract context data in called extras for hook execution')
776 raise ValueError('SVN-POST-COMMIT: Failed to extract context data in called extras for hook execution')
776
777
777 if extras.get('rc_internal_commit'):
778 if extras.get('rc_internal_commit'):
778 # special marker for internal commit, we don't call hooks client
779 # special marker for internal commit, we don't call hooks client
779 return 0
780 return 0
780
781
781 extras['hook_type'] = 'post_commit'
782 extras['hook_type'] = 'post_commit'
782 extras['commit_ids'] = [commit_id]
783 extras['commit_ids'] = [commit_id]
783 extras['txn_id'] = txn_id
784 extras['txn_id'] = txn_id
784 extras['new_refs'] = {
785 extras['new_refs'] = {
785 'branches': branches,
786 'branches': branches,
786 'bookmarks': [],
787 'bookmarks': [],
787 'tags': tags,
788 'tags': tags,
788 'total_commits': 1,
789 'total_commits': 1,
789 }
790 }
790
791
791 if 'repo_size' in extras['hooks']:
792 if 'repo_size' in extras['hooks']:
792 try:
793 try:
793 _call_hook('repo_size', extras, SvnMessageWriter())
794 _call_hook('repo_size', extras, SvnMessageWriter())
794 except Exception:
795 except Exception:
795 pass
796 pass
796
797
797 return _call_hook('post_push', extras, SvnMessageWriter())
798 return _call_hook('post_push', extras, SvnMessageWriter())
General Comments 0
You need to be logged in to leave comments. Login now