##// END OF EJS Templates
code: fixed typos
super-admin -
r1326:f4bef470 default
parent child Browse files
Show More
@@ -1,798 +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 # @adopt_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 This 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 # level here should be info/debug so the remote client wouldn't see this as a stderr message
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 log.info('ERROR: Handling hook-call exception. Got traceback from remote call:%s', exception_traceback)
148 raise Exception(
148 raise Exception(
149 f"""Got remote exception "{exception_class}" with args "{result['exception_args']}" """
149 f"""Got remote exception "{exception_class}" with args "{result['exception_args']}" """
150 )
150 )
151
151
152
152
153 def _get_hooks_client(extras):
153 def _get_hooks_client(extras):
154 is_shadow_repo = extras.get('is_shadow_repo')
154 is_shadow_repo = extras.get('is_shadow_repo')
155 hooks_protocol = extras.get('hooks_protocol')
155 hooks_protocol = extras.get('hooks_protocol')
156
156
157 if hooks_protocol == 'celery':
157 if hooks_protocol == 'celery':
158 try:
158 try:
159 celery_config = extras['hooks_config']
159 celery_config = extras['hooks_config']
160 broker_url = celery_config['broker_url']
160 broker_url = celery_config['broker_url']
161 result_backend = celery_config['result_backend']
161 result_backend = celery_config['result_backend']
162 except Exception:
162 except Exception:
163 log.exception("Failed to get celery task queue and backend")
163 log.exception("Failed to get celery task queue and backend")
164 raise
164 raise
165 return HooksCeleryClient(broker_url, result_backend, _celery_opts=celery_config)
165 return HooksCeleryClient(broker_url, result_backend, _celery_opts=celery_config)
166 elif is_shadow_repo:
166 elif is_shadow_repo:
167 return HooksShadowRepoClient()
167 return HooksShadowRepoClient()
168 else:
168 else:
169 raise Exception("Hooks client not found!")
169 raise Exception("Hooks client not found!")
170
170
171
171
172 def _call_hook(hook_name, extras, writer):
172 def _call_hook(hook_name, extras, writer):
173 hooks_client = _get_hooks_client(extras)
173 hooks_client = _get_hooks_client(extras)
174 log.debug('Hooks, using client:%s', hooks_client)
174 log.debug('Hooks, using client:%s', hooks_client)
175 result = hooks_client(hook_name, extras)
175 result = hooks_client(hook_name, extras)
176 log.debug('Hooks got result: %s', result)
176 log.debug('Hooks got result: %s', result)
177 _maybe_handle_exception(writer, result)
177 _maybe_handle_exception(writer, result)
178 writer.write(result['output'])
178 writer.write(result['output'])
179
179
180 return result['status']
180 return result['status']
181
181
182
182
183 def _extras_from_ui(ui):
183 def _extras_from_ui(ui):
184 hook_data = ui.config(b'rhodecode', b'RC_SCM_DATA')
184 hook_data = ui.config(b'rhodecode', b'RC_SCM_DATA')
185 if not hook_data:
185 if not hook_data:
186 # maybe it's inside environ ?
186 # maybe it's inside environ ?
187 env_hook_data = os.environ.get('RC_SCM_DATA')
187 env_hook_data = os.environ.get('RC_SCM_DATA')
188 if env_hook_data:
188 if env_hook_data:
189 hook_data = env_hook_data
189 hook_data = env_hook_data
190
190
191 extras = {}
191 extras = {}
192 if hook_data:
192 if hook_data:
193 extras = json.loads(hook_data)
193 extras = json.loads(hook_data)
194 return extras
194 return extras
195
195
196
196
197 def _rev_range_hash(repo, node, check_heads=False):
197 def _rev_range_hash(repo, node, check_heads=False):
198 from vcsserver.hgcompat import get_ctx
198 from vcsserver.hgcompat import get_ctx
199
199
200 commits = []
200 commits = []
201 revs = []
201 revs = []
202 start = get_ctx(repo, node).rev()
202 start = get_ctx(repo, node).rev()
203 end = len(repo)
203 end = len(repo)
204 for rev in range(start, end):
204 for rev in range(start, end):
205 revs.append(rev)
205 revs.append(rev)
206 ctx = get_ctx(repo, rev)
206 ctx = get_ctx(repo, rev)
207 commit_id = ascii_str(mercurial.node.hex(ctx.node()))
207 commit_id = ascii_str(mercurial.node.hex(ctx.node()))
208 branch = safe_str(ctx.branch())
208 branch = safe_str(ctx.branch())
209 commits.append((commit_id, branch))
209 commits.append((commit_id, branch))
210
210
211 parent_heads = []
211 parent_heads = []
212 if check_heads:
212 if check_heads:
213 parent_heads = _check_heads(repo, start, end, revs)
213 parent_heads = _check_heads(repo, start, end, revs)
214 return commits, parent_heads
214 return commits, parent_heads
215
215
216
216
217 def _check_heads(repo, start, end, commits):
217 def _check_heads(repo, start, end, commits):
218 from vcsserver.hgcompat import get_ctx
218 from vcsserver.hgcompat import get_ctx
219 changelog = repo.changelog
219 changelog = repo.changelog
220 parents = set()
220 parents = set()
221
221
222 for new_rev in commits:
222 for new_rev in commits:
223 for p in changelog.parentrevs(new_rev):
223 for p in changelog.parentrevs(new_rev):
224 if p == mercurial.node.nullrev:
224 if p == mercurial.node.nullrev:
225 continue
225 continue
226 if p < start:
226 if p < start:
227 parents.add(p)
227 parents.add(p)
228
228
229 for p in parents:
229 for p in parents:
230 branch = get_ctx(repo, p).branch()
230 branch = get_ctx(repo, p).branch()
231 # The heads descending from that parent, on the same branch
231 # The heads descending from that parent, on the same branch
232 parent_heads = {p}
232 parent_heads = {p}
233 reachable = {p}
233 reachable = {p}
234 for x in range(p + 1, end):
234 for x in range(p + 1, end):
235 if get_ctx(repo, x).branch() != branch:
235 if get_ctx(repo, x).branch() != branch:
236 continue
236 continue
237 for pp in changelog.parentrevs(x):
237 for pp in changelog.parentrevs(x):
238 if pp in reachable:
238 if pp in reachable:
239 reachable.add(x)
239 reachable.add(x)
240 parent_heads.discard(pp)
240 parent_heads.discard(pp)
241 parent_heads.add(x)
241 parent_heads.add(x)
242 # More than one head? Suggest merging
242 # More than one head? Suggest merging
243 if len(parent_heads) > 1:
243 if len(parent_heads) > 1:
244 return list(parent_heads)
244 return list(parent_heads)
245
245
246 return []
246 return []
247
247
248
248
249 def _get_git_env():
249 def _get_git_env():
250 env = {}
250 env = {}
251 for k, v in os.environ.items():
251 for k, v in os.environ.items():
252 if k.startswith('GIT'):
252 if k.startswith('GIT'):
253 env[k] = v
253 env[k] = v
254
254
255 # serialized version
255 # serialized version
256 return [(k, v) for k, v in env.items()]
256 return [(k, v) for k, v in env.items()]
257
257
258
258
259 def _get_hg_env(old_rev, new_rev, txnid, repo_path):
259 def _get_hg_env(old_rev, new_rev, txnid, repo_path):
260 env = {}
260 env = {}
261 for k, v in os.environ.items():
261 for k, v in os.environ.items():
262 if k.startswith('HG'):
262 if k.startswith('HG'):
263 env[k] = v
263 env[k] = v
264
264
265 env['HG_NODE'] = old_rev
265 env['HG_NODE'] = old_rev
266 env['HG_NODE_LAST'] = new_rev
266 env['HG_NODE_LAST'] = new_rev
267 env['HG_TXNID'] = txnid
267 env['HG_TXNID'] = txnid
268 env['HG_PENDING'] = repo_path
268 env['HG_PENDING'] = repo_path
269
269
270 return [(k, v) for k, v in env.items()]
270 return [(k, v) for k, v in env.items()]
271
271
272
272
273 def _get_ini_settings(ini_file):
273 def _get_ini_settings(ini_file):
274 from vcsserver.http_main import sanitize_settings_and_apply_defaults
274 from vcsserver.http_main import sanitize_settings_and_apply_defaults
275 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
276
276
277 global_config = {'__file__': ini_file}
277 global_config = {'__file__': ini_file}
278 ini_settings = get_app_config_lightweight(ini_file)
278 ini_settings = get_app_config_lightweight(ini_file)
279 sanitize_settings_and_apply_defaults(global_config, ini_settings)
279 sanitize_settings_and_apply_defaults(global_config, ini_settings)
280 configure_and_store_settings(global_config, ini_settings)
280 configure_and_store_settings(global_config, ini_settings)
281
281
282 return ini_settings
282 return ini_settings
283
283
284
284
285 def _fix_hooks_executables(ini_path=''):
285 def _fix_hooks_executables(ini_path=''):
286 """
286 """
287 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
288 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
289 because svn is not on PATH
289 because svn is not on PATH
290 """
290 """
291 # set defaults, in case we can't read from ini_file
291 # set defaults, in case we can't read from ini_file
292 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'
293 if ini_path:
293 if ini_path:
294 ini_settings = _get_ini_settings(ini_path)
294 ini_settings = _get_ini_settings(ini_path)
295 core_binary_dir = ini_settings['core.binary_dir']
295 core_binary_dir = ini_settings['core.binary_dir']
296
296
297 settings.BINARY_DIR = core_binary_dir
297 settings.BINARY_DIR = core_binary_dir
298
298
299
299
300 def repo_size(ui, repo, **kwargs):
300 def repo_size(ui, repo, **kwargs):
301 extras = _extras_from_ui(ui)
301 extras = _extras_from_ui(ui)
302 return _call_hook('repo_size', extras, HgMessageWriter(ui))
302 return _call_hook('repo_size', extras, HgMessageWriter(ui))
303
303
304
304
305 def pre_pull(ui, repo, **kwargs):
305 def pre_pull(ui, repo, **kwargs):
306 extras = _extras_from_ui(ui)
306 extras = _extras_from_ui(ui)
307 return _call_hook('pre_pull', extras, HgMessageWriter(ui))
307 return _call_hook('pre_pull', extras, HgMessageWriter(ui))
308
308
309
309
310 def pre_pull_ssh(ui, repo, **kwargs):
310 def pre_pull_ssh(ui, repo, **kwargs):
311 extras = _extras_from_ui(ui)
311 extras = _extras_from_ui(ui)
312 if extras and extras.get('SSH'):
312 if extras and extras.get('SSH'):
313 return pre_pull(ui, repo, **kwargs)
313 return pre_pull(ui, repo, **kwargs)
314 return 0
314 return 0
315
315
316
316
317 def post_pull(ui, repo, **kwargs):
317 def post_pull(ui, repo, **kwargs):
318 extras = _extras_from_ui(ui)
318 extras = _extras_from_ui(ui)
319 return _call_hook('post_pull', extras, HgMessageWriter(ui))
319 return _call_hook('post_pull', extras, HgMessageWriter(ui))
320
320
321
321
322 def post_pull_ssh(ui, repo, **kwargs):
322 def post_pull_ssh(ui, repo, **kwargs):
323 extras = _extras_from_ui(ui)
323 extras = _extras_from_ui(ui)
324 if extras and extras.get('SSH'):
324 if extras and extras.get('SSH'):
325 return post_pull(ui, repo, **kwargs)
325 return post_pull(ui, repo, **kwargs)
326 return 0
326 return 0
327
327
328
328
329 def pre_push(ui, repo, node=None, **kwargs):
329 def pre_push(ui, repo, node=None, **kwargs):
330 """
330 """
331 Mercurial pre_push hook
331 Mercurial pre_push hook
332 """
332 """
333 extras = _extras_from_ui(ui)
333 extras = _extras_from_ui(ui)
334 detect_force_push = extras.get('detect_force_push')
334 detect_force_push = extras.get('detect_force_push')
335
335
336 rev_data = []
336 rev_data = []
337 hook_type: str = safe_str(kwargs.get('hooktype'))
337 hook_type: str = safe_str(kwargs.get('hooktype'))
338
338
339 if node and hook_type == 'pretxnchangegroup':
339 if node and hook_type == 'pretxnchangegroup':
340 branches = collections.defaultdict(list)
340 branches = collections.defaultdict(list)
341 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)
342 for commit_id, branch in commits:
342 for commit_id, branch in commits:
343 branches[branch].append(commit_id)
343 branches[branch].append(commit_id)
344
344
345 for branch, commits in branches.items():
345 for branch, commits in branches.items():
346 old_rev = ascii_str(kwargs.get('node_last')) or commits[0]
346 old_rev = ascii_str(kwargs.get('node_last')) or commits[0]
347 rev_data.append({
347 rev_data.append({
348 'total_commits': len(commits),
348 'total_commits': len(commits),
349 'old_rev': old_rev,
349 'old_rev': old_rev,
350 'new_rev': commits[-1],
350 'new_rev': commits[-1],
351 'ref': '',
351 'ref': '',
352 'type': 'branch',
352 'type': 'branch',
353 'name': branch,
353 'name': branch,
354 })
354 })
355
355
356 for push_ref in rev_data:
356 for push_ref in rev_data:
357 push_ref['multiple_heads'] = _heads
357 push_ref['multiple_heads'] = _heads
358
358
359 repo_path = os.path.join(
359 repo_path = os.path.join(
360 extras.get('repo_store', ''), extras.get('repository', ''))
360 extras.get('repo_store', ''), extras.get('repository', ''))
361 push_ref['hg_env'] = _get_hg_env(
361 push_ref['hg_env'] = _get_hg_env(
362 old_rev=push_ref['old_rev'],
362 old_rev=push_ref['old_rev'],
363 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')),
364 repo_path=repo_path)
364 repo_path=repo_path)
365
365
366 extras['hook_type'] = hook_type or 'pre_push'
366 extras['hook_type'] = hook_type or 'pre_push'
367 extras['commit_ids'] = rev_data
367 extras['commit_ids'] = rev_data
368
368
369 return _call_hook('pre_push', extras, HgMessageWriter(ui))
369 return _call_hook('pre_push', extras, HgMessageWriter(ui))
370
370
371
371
372 def pre_push_ssh(ui, repo, node=None, **kwargs):
372 def pre_push_ssh(ui, repo, node=None, **kwargs):
373 extras = _extras_from_ui(ui)
373 extras = _extras_from_ui(ui)
374 if extras.get('SSH'):
374 if extras.get('SSH'):
375 return pre_push(ui, repo, node, **kwargs)
375 return pre_push(ui, repo, node, **kwargs)
376
376
377 return 0
377 return 0
378
378
379
379
380 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
380 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
381 """
381 """
382 Mercurial pre_push hook for SSH
382 Mercurial pre_push hook for SSH
383 """
383 """
384 extras = _extras_from_ui(ui)
384 extras = _extras_from_ui(ui)
385 if extras.get('SSH'):
385 if extras.get('SSH'):
386 permission = extras['SSH_PERMISSIONS']
386 permission = extras['SSH_PERMISSIONS']
387
387
388 if 'repository.write' == permission or 'repository.admin' == permission:
388 if 'repository.write' == permission or 'repository.admin' == permission:
389 return 0
389 return 0
390
390
391 # non-zero ret code
391 # non-zero ret code
392 return 1
392 return 1
393
393
394 return 0
394 return 0
395
395
396
396
397 def post_push(ui, repo, node, **kwargs):
397 def post_push(ui, repo, node, **kwargs):
398 """
398 """
399 Mercurial post_push hook
399 Mercurial post_push hook
400 """
400 """
401 extras = _extras_from_ui(ui)
401 extras = _extras_from_ui(ui)
402
402
403 commit_ids = []
403 commit_ids = []
404 branches = []
404 branches = []
405 bookmarks = []
405 bookmarks = []
406 tags = []
406 tags = []
407 hook_type: str = safe_str(kwargs.get('hooktype'))
407 hook_type: str = safe_str(kwargs.get('hooktype'))
408
408
409 commits, _heads = _rev_range_hash(repo, node)
409 commits, _heads = _rev_range_hash(repo, node)
410 for commit_id, branch in commits:
410 for commit_id, branch in commits:
411 commit_ids.append(commit_id)
411 commit_ids.append(commit_id)
412 if branch not in branches:
412 if branch not in branches:
413 branches.append(branch)
413 branches.append(branch)
414
414
415 if hasattr(ui, '_rc_pushkey_bookmarks'):
415 if hasattr(ui, '_rc_pushkey_bookmarks'):
416 bookmarks = ui._rc_pushkey_bookmarks
416 bookmarks = ui._rc_pushkey_bookmarks
417
417
418 extras['hook_type'] = hook_type or 'post_push'
418 extras['hook_type'] = hook_type or 'post_push'
419 extras['commit_ids'] = commit_ids
419 extras['commit_ids'] = commit_ids
420
420
421 extras['new_refs'] = {
421 extras['new_refs'] = {
422 'branches': branches,
422 'branches': branches,
423 'bookmarks': bookmarks,
423 'bookmarks': bookmarks,
424 'tags': tags
424 'tags': tags
425 }
425 }
426
426
427 return _call_hook('post_push', extras, HgMessageWriter(ui))
427 return _call_hook('post_push', extras, HgMessageWriter(ui))
428
428
429
429
430 def post_push_ssh(ui, repo, node, **kwargs):
430 def post_push_ssh(ui, repo, node, **kwargs):
431 """
431 """
432 Mercurial post_push hook for SSH
432 Mercurial post_push hook for SSH
433 """
433 """
434 if _extras_from_ui(ui).get('SSH'):
434 if _extras_from_ui(ui).get('SSH'):
435 return post_push(ui, repo, node, **kwargs)
435 return post_push(ui, repo, node, **kwargs)
436 return 0
436 return 0
437
437
438
438
439 def key_push(ui, repo, **kwargs):
439 def key_push(ui, repo, **kwargs):
440 from vcsserver.hgcompat import get_ctx
440 from vcsserver.hgcompat import get_ctx
441
441
442 if kwargs['new'] != b'0' and kwargs['namespace'] == b'bookmarks':
442 if kwargs['new'] != b'0' and kwargs['namespace'] == b'bookmarks':
443 # 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
444 ui._rc_pushkey_bookmarks = get_ctx(repo, kwargs['key']).bookmarks()
444 ui._rc_pushkey_bookmarks = get_ctx(repo, kwargs['key']).bookmarks()
445 return
445 return
446
446
447
447
448 # backward compat
448 # backward compat
449 log_pull_action = post_pull
449 log_pull_action = post_pull
450
450
451 # backward compat
451 # backward compat
452 log_push_action = post_push
452 log_push_action = post_push
453
453
454
454
455 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):
456 """
456 """
457 Old hook name: keep here for backward compatibility.
457 Old hook name: keep here for backward compatibility.
458
458
459 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.
460 """
460 """
461 pass
461 pass
462
462
463
463
464 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):
465 """
465 """
466 Old hook name: keep here for backward compatibility.
466 Old hook name: keep here for backward compatibility.
467
467
468 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.
469 """
469 """
470 pass
470 pass
471
471
472
472
473 @dataclasses.dataclass
473 @dataclasses.dataclass
474 class HookResponse:
474 class HookResponse:
475 status: int
475 status: int
476 output: str
476 output: str
477
477
478
478
479 def git_pre_pull(extras) -> HookResponse:
479 def git_pre_pull(extras) -> HookResponse:
480 """
480 """
481 Pre pull hook.
481 Pre pull hook.
482
482
483 :param extras: dictionary containing the keys defined in simplevcs
483 :param extras: dictionary containing the keys defined in simplevcs
484 :type extras: dict
484 :type extras: dict
485
485
486 :return: status code of the hook. 0 for success.
486 :return: status code of the hook. 0 for success.
487 :rtype: int
487 :rtype: int
488 """
488 """
489
489
490 if 'pull' not in extras['hooks']:
490 if 'pull' not in extras['hooks']:
491 return HookResponse(0, '')
491 return HookResponse(0, '')
492
492
493 stdout = io.StringIO()
493 stdout = io.StringIO()
494 try:
494 try:
495 status_code = _call_hook('pre_pull', extras, GitMessageWriter(stdout))
495 status_code = _call_hook('pre_pull', extras, GitMessageWriter(stdout))
496
496
497 except Exception as error:
497 except Exception as error:
498 log.exception('Failed to call pre_pull hook')
498 log.exception('Failed to call pre_pull hook')
499 status_code = 128
499 status_code = 128
500 stdout.write(f'ERROR: {error}\n')
500 stdout.write(f'ERROR: {error}\n')
501
501
502 return HookResponse(status_code, stdout.getvalue())
502 return HookResponse(status_code, stdout.getvalue())
503
503
504
504
505 def git_post_pull(extras) -> HookResponse:
505 def git_post_pull(extras) -> HookResponse:
506 """
506 """
507 Post pull hook.
507 Post pull hook.
508
508
509 :param extras: dictionary containing the keys defined in simplevcs
509 :param extras: dictionary containing the keys defined in simplevcs
510 :type extras: dict
510 :type extras: dict
511
511
512 :return: status code of the hook. 0 for success.
512 :return: status code of the hook. 0 for success.
513 :rtype: int
513 :rtype: int
514 """
514 """
515 if 'pull' not in extras['hooks']:
515 if 'pull' not in extras['hooks']:
516 return HookResponse(0, '')
516 return HookResponse(0, '')
517
517
518 stdout = io.StringIO()
518 stdout = io.StringIO()
519 try:
519 try:
520 status = _call_hook('post_pull', extras, GitMessageWriter(stdout))
520 status = _call_hook('post_pull', extras, GitMessageWriter(stdout))
521 except Exception as error:
521 except Exception as error:
522 status = 128
522 status = 128
523 stdout.write(f'ERROR: {error}\n')
523 stdout.write(f'ERROR: {error}\n')
524
524
525 return HookResponse(status, stdout.getvalue())
525 return HookResponse(status, stdout.getvalue())
526
526
527
527
528 def _parse_git_ref_lines(revision_lines):
528 def _parse_git_ref_lines(revision_lines):
529 rev_data = []
529 rev_data = []
530 for revision_line in revision_lines or []:
530 for revision_line in revision_lines or []:
531 old_rev, new_rev, ref = revision_line.strip().split(' ')
531 old_rev, new_rev, ref = revision_line.strip().split(' ')
532 ref_data = ref.split('/', 2)
532 ref_data = ref.split('/', 2)
533 if ref_data[1] in ('tags', 'heads'):
533 if ref_data[1] in ('tags', 'heads'):
534 rev_data.append({
534 rev_data.append({
535 # NOTE(marcink):
535 # NOTE(marcink):
536 # 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
537 # but we set the variable for consistency with GIT
537 # but we set the variable for consistency with GIT
538 'total_commits': -1,
538 'total_commits': -1,
539 'old_rev': old_rev,
539 'old_rev': old_rev,
540 'new_rev': new_rev,
540 'new_rev': new_rev,
541 'ref': ref,
541 'ref': ref,
542 'type': ref_data[1],
542 'type': ref_data[1],
543 'name': ref_data[2],
543 'name': ref_data[2],
544 })
544 })
545 return rev_data
545 return rev_data
546
546
547
547
548 def git_pre_receive(unused_repo_path, revision_lines, env) -> int:
548 def git_pre_receive(unused_repo_path, revision_lines, env) -> int:
549 """
549 """
550 Pre push hook.
550 Pre push hook.
551
551
552 :return: status code of the hook. 0 for success.
552 :return: status code of the hook. 0 for success.
553 """
553 """
554 extras = json.loads(env['RC_SCM_DATA'])
554 extras = json.loads(env['RC_SCM_DATA'])
555 rev_data = _parse_git_ref_lines(revision_lines)
555 rev_data = _parse_git_ref_lines(revision_lines)
556 if 'push' not in extras['hooks']:
556 if 'push' not in extras['hooks']:
557 return 0
557 return 0
558 _fix_hooks_executables(env.get('RC_INI_FILE'))
558 _fix_hooks_executables(env.get('RC_INI_FILE'))
559
559
560 empty_commit_id = '0' * 40
560 empty_commit_id = '0' * 40
561
561
562 detect_force_push = extras.get('detect_force_push')
562 detect_force_push = extras.get('detect_force_push')
563
563
564 for push_ref in rev_data:
564 for push_ref in rev_data:
565 # store our git-env which holds the temp store
565 # store our git-env which holds the temp store
566 push_ref['git_env'] = _get_git_env()
566 push_ref['git_env'] = _get_git_env()
567 push_ref['pruned_sha'] = ''
567 push_ref['pruned_sha'] = ''
568 if not detect_force_push:
568 if not detect_force_push:
569 # 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
570 continue
570 continue
571
571
572 type_ = push_ref['type']
572 type_ = push_ref['type']
573 new_branch = push_ref['old_rev'] == empty_commit_id
573 new_branch = push_ref['old_rev'] == empty_commit_id
574 delete_branch = push_ref['new_rev'] == empty_commit_id
574 delete_branch = push_ref['new_rev'] == empty_commit_id
575 if type_ == 'heads' and not (new_branch or delete_branch):
575 if type_ == 'heads' and not (new_branch or delete_branch):
576 old_rev = push_ref['old_rev']
576 old_rev = push_ref['old_rev']
577 new_rev = push_ref['new_rev']
577 new_rev = push_ref['new_rev']
578 cmd = [settings.GIT_EXECUTABLE(), 'rev-list', old_rev, f'^{new_rev}']
578 cmd = [settings.GIT_EXECUTABLE(), 'rev-list', old_rev, f'^{new_rev}']
579 stdout, stderr = subprocessio.run_command(
579 stdout, stderr = subprocessio.run_command(
580 cmd, env=os.environ.copy())
580 cmd, env=os.environ.copy())
581 # 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
582 if stdout:
582 if stdout:
583 push_ref['pruned_sha'] = stdout.splitlines()
583 push_ref['pruned_sha'] = stdout.splitlines()
584
584
585 extras['hook_type'] = 'pre_receive'
585 extras['hook_type'] = 'pre_receive'
586 extras['commit_ids'] = rev_data
586 extras['commit_ids'] = rev_data
587
587
588 stdout = sys.stdout
588 stdout = sys.stdout
589 status_code = _call_hook('pre_push', extras, GitMessageWriter(stdout))
589 status_code = _call_hook('pre_push', extras, GitMessageWriter(stdout))
590
590
591 return status_code
591 return status_code
592
592
593
593
594 def git_post_receive(unused_repo_path, revision_lines, env) -> int:
594 def git_post_receive(unused_repo_path, revision_lines, env) -> int:
595 """
595 """
596 Post push hook.
596 Post push hook.
597
597
598 :return: status code of the hook. 0 for success.
598 :return: status code of the hook. 0 for success.
599 """
599 """
600 extras = json.loads(env['RC_SCM_DATA'])
600 extras = json.loads(env['RC_SCM_DATA'])
601 if 'push' not in extras['hooks']:
601 if 'push' not in extras['hooks']:
602 return 0
602 return 0
603
603
604 _fix_hooks_executables(env.get('RC_INI_FILE'))
604 _fix_hooks_executables(env.get('RC_INI_FILE'))
605
605
606 rev_data = _parse_git_ref_lines(revision_lines)
606 rev_data = _parse_git_ref_lines(revision_lines)
607
607
608 git_revs = []
608 git_revs = []
609
609
610 # 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
611 # 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
612 # correct version of the git executable.
612 # correct version of the git executable.
613 empty_commit_id = '0' * 40
613 empty_commit_id = '0' * 40
614 branches = []
614 branches = []
615 tags = []
615 tags = []
616 for push_ref in rev_data:
616 for push_ref in rev_data:
617 type_ = push_ref['type']
617 type_ = push_ref['type']
618
618
619 if type_ == 'heads':
619 if type_ == 'heads':
620 # starting new branch case
620 # starting new branch case
621 if push_ref['old_rev'] == empty_commit_id:
621 if push_ref['old_rev'] == empty_commit_id:
622 push_ref_name = push_ref['name']
622 push_ref_name = push_ref['name']
623
623
624 if push_ref_name not in branches:
624 if push_ref_name not in branches:
625 branches.append(push_ref_name)
625 branches.append(push_ref_name)
626
626
627 need_head_set = ''
627 need_head_set = ''
628 with Repository(os.getcwd()) as repo:
628 with Repository(os.getcwd()) as repo:
629 try:
629 try:
630 repo.head
630 repo.head
631 except pygit2.GitError:
631 except pygit2.GitError:
632 need_head_set = f'refs/heads/{push_ref_name}'
632 need_head_set = f'refs/heads/{push_ref_name}'
633
633
634 if need_head_set:
634 if need_head_set:
635 repo.set_head(need_head_set)
635 repo.set_head(need_head_set)
636 print(f"Setting default branch to {push_ref_name}")
636 print(f"Setting default branch to {push_ref_name}")
637
637
638 cmd = [settings.GIT_EXECUTABLE(), 'for-each-ref', '--format=%(refname)', 'refs/heads/*']
638 cmd = [settings.GIT_EXECUTABLE(), 'for-each-ref', '--format=%(refname)', 'refs/heads/*']
639 stdout, stderr = subprocessio.run_command(
639 stdout, stderr = subprocessio.run_command(
640 cmd, env=os.environ.copy())
640 cmd, env=os.environ.copy())
641 heads = safe_str(stdout)
641 heads = safe_str(stdout)
642 heads = heads.replace(push_ref['ref'], '')
642 heads = heads.replace(push_ref['ref'], '')
643 heads = ' '.join(head for head
643 heads = ' '.join(head for head
644 in heads.splitlines() if head) or '.'
644 in heads.splitlines() if head) or '.'
645 cmd = [settings.GIT_EXECUTABLE(), 'log', '--reverse',
645 cmd = [settings.GIT_EXECUTABLE(), 'log', '--reverse',
646 '--pretty=format:%H', '--', push_ref['new_rev'],
646 '--pretty=format:%H', '--', push_ref['new_rev'],
647 '--not', heads]
647 '--not', heads]
648 stdout, stderr = subprocessio.run_command(
648 stdout, stderr = subprocessio.run_command(
649 cmd, env=os.environ.copy())
649 cmd, env=os.environ.copy())
650 git_revs.extend(list(map(ascii_str, stdout.splitlines())))
650 git_revs.extend(list(map(ascii_str, stdout.splitlines())))
651
651
652 # delete branch case
652 # delete branch case
653 elif push_ref['new_rev'] == empty_commit_id:
653 elif push_ref['new_rev'] == empty_commit_id:
654 git_revs.append(f'delete_branch=>{push_ref["name"]}')
654 git_revs.append(f'delete_branch=>{push_ref["name"]}')
655 else:
655 else:
656 if push_ref['name'] not in branches:
656 if push_ref['name'] not in branches:
657 branches.append(push_ref['name'])
657 branches.append(push_ref['name'])
658
658
659 cmd = [settings.GIT_EXECUTABLE(), 'log',
659 cmd = [settings.GIT_EXECUTABLE(), 'log',
660 f'{push_ref["old_rev"]}..{push_ref["new_rev"]}',
660 f'{push_ref["old_rev"]}..{push_ref["new_rev"]}',
661 '--reverse', '--pretty=format:%H']
661 '--reverse', '--pretty=format:%H']
662 stdout, stderr = subprocessio.run_command(
662 stdout, stderr = subprocessio.run_command(
663 cmd, env=os.environ.copy())
663 cmd, env=os.environ.copy())
664 # we get bytes from stdout, we need str to be consistent
664 # we get bytes from stdout, we need str to be consistent
665 log_revs = list(map(ascii_str, stdout.splitlines()))
665 log_revs = list(map(ascii_str, stdout.splitlines()))
666 git_revs.extend(log_revs)
666 git_revs.extend(log_revs)
667
667
668 # Pure pygit2 impl. but still 2-3x slower :/
668 # Pure pygit2 impl. but still 2-3x slower :/
669 # results = []
669 # results = []
670 #
670 #
671 # with Repository(os.getcwd()) as repo:
671 # with Repository(os.getcwd()) as repo:
672 # repo_new_rev = repo[push_ref['new_rev']]
672 # repo_new_rev = repo[push_ref['new_rev']]
673 # repo_old_rev = repo[push_ref['old_rev']]
673 # repo_old_rev = repo[push_ref['old_rev']]
674 # walker = repo.walk(repo_new_rev.id, pygit2.GIT_SORT_TOPOLOGICAL)
674 # walker = repo.walk(repo_new_rev.id, pygit2.GIT_SORT_TOPOLOGICAL)
675 #
675 #
676 # for commit in walker:
676 # for commit in walker:
677 # if commit.id == repo_old_rev.id:
677 # if commit.id == repo_old_rev.id:
678 # break
678 # break
679 # results.append(commit.id.hex)
679 # results.append(commit.id.hex)
680 # # reverse the order, can't use GIT_SORT_REVERSE
680 # # reverse the order, can't use GIT_SORT_REVERSE
681 # log_revs = results[::-1]
681 # log_revs = results[::-1]
682
682
683 elif type_ == 'tags':
683 elif type_ == 'tags':
684 if push_ref['name'] not in tags:
684 if push_ref['name'] not in tags:
685 tags.append(push_ref['name'])
685 tags.append(push_ref['name'])
686 git_revs.append(f'tag=>{push_ref["name"]}')
686 git_revs.append(f'tag=>{push_ref["name"]}')
687
687
688 extras['hook_type'] = 'post_receive'
688 extras['hook_type'] = 'post_receive'
689 extras['commit_ids'] = git_revs
689 extras['commit_ids'] = git_revs
690 extras['new_refs'] = {
690 extras['new_refs'] = {
691 'branches': branches,
691 'branches': branches,
692 'bookmarks': [],
692 'bookmarks': [],
693 'tags': tags,
693 'tags': tags,
694 }
694 }
695
695
696 stdout = sys.stdout
696 stdout = sys.stdout
697
697
698 if 'repo_size' in extras['hooks']:
698 if 'repo_size' in extras['hooks']:
699 try:
699 try:
700 _call_hook('repo_size', extras, GitMessageWriter(stdout))
700 _call_hook('repo_size', extras, GitMessageWriter(stdout))
701 except Exception:
701 except Exception:
702 pass
702 pass
703
703
704 status_code = _call_hook('post_push', extras, GitMessageWriter(stdout))
704 status_code = _call_hook('post_push', extras, GitMessageWriter(stdout))
705 return status_code
705 return status_code
706
706
707
707
708 def get_extras_from_txn_id(repo_path, txn_id):
708 def get_extras_from_txn_id(repo_path, txn_id):
709 extras = get_txn_id_from_store(repo_path, txn_id)
709 extras = get_txn_id_from_store(repo_path, txn_id)
710 return extras
710 return extras
711
711
712
712
713 def svn_pre_commit(repo_path, commit_data, env):
713 def svn_pre_commit(repo_path, commit_data, env):
714
714
715 path, txn_id = commit_data
715 path, txn_id = commit_data
716 branches = []
716 branches = []
717 tags = []
717 tags = []
718
718
719 if env.get('RC_SCM_DATA'):
719 if env.get('RC_SCM_DATA'):
720 extras = json.loads(env['RC_SCM_DATA'])
720 extras = json.loads(env['RC_SCM_DATA'])
721 else:
721 else:
722 ini_path = env.get('RC_INI_FILE')
722 ini_path = env.get('RC_INI_FILE')
723 if ini_path:
723 if ini_path:
724 _get_ini_settings(ini_path)
724 _get_ini_settings(ini_path)
725 # fallback method to read from TXN-ID stored data
725 # fallback method to read from TXN-ID stored data
726 extras = get_extras_from_txn_id(path, txn_id)
726 extras = get_extras_from_txn_id(path, txn_id)
727
727
728 if not extras:
728 if not extras:
729 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')
730
730
731 if extras.get('rc_internal_commit'):
731 if extras.get('rc_internal_commit'):
732 # special marker for internal commit, we don't call hooks client
732 # special marker for internal commit, we don't call hooks client
733 return 0
733 return 0
734
734
735 extras['hook_type'] = 'pre_commit'
735 extras['hook_type'] = 'pre_commit'
736 extras['commit_ids'] = [txn_id]
736 extras['commit_ids'] = [txn_id]
737 extras['txn_id'] = txn_id
737 extras['txn_id'] = txn_id
738 extras['new_refs'] = {
738 extras['new_refs'] = {
739 'total_commits': 1,
739 'total_commits': 1,
740 'branches': branches,
740 'branches': branches,
741 'bookmarks': [],
741 'bookmarks': [],
742 'tags': tags,
742 'tags': tags,
743 }
743 }
744
744
745 return _call_hook('pre_push', extras, SvnMessageWriter())
745 return _call_hook('pre_push', extras, SvnMessageWriter())
746
746
747
747
748 def svn_post_commit(repo_path, commit_data, env):
748 def svn_post_commit(repo_path, commit_data, env):
749 """
749 """
750 commit_data is path, rev, txn_id
750 commit_data is path, rev, txn_id
751 """
751 """
752
752
753 if len(commit_data) == 3:
753 if len(commit_data) == 3:
754 path, commit_id, txn_id = commit_data
754 path, commit_id, txn_id = commit_data
755 elif len(commit_data) == 2:
755 elif len(commit_data) == 2:
756 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. '
757 'Some functionality might be limited')
757 'Some functionality might be limited')
758 path, commit_id = commit_data
758 path, commit_id = commit_data
759 txn_id = None
759 txn_id = None
760 else:
760 else:
761 return 0
761 return 0
762
762
763 branches = []
763 branches = []
764 tags = []
764 tags = []
765
765
766 if env.get('RC_SCM_DATA'):
766 if env.get('RC_SCM_DATA'):
767 extras = json.loads(env['RC_SCM_DATA'])
767 extras = json.loads(env['RC_SCM_DATA'])
768 else:
768 else:
769 ini_path = env.get('RC_INI_FILE')
769 ini_path = env.get('RC_INI_FILE')
770 if ini_path:
770 if ini_path:
771 _get_ini_settings(ini_path)
771 _get_ini_settings(ini_path)
772 # fallback method to read from TXN-ID stored data
772 # fallback method to read from TXN-ID stored data
773 extras = get_extras_from_txn_id(path, txn_id)
773 extras = get_extras_from_txn_id(path, txn_id)
774
774
775 if not extras and txn_id:
775 if not extras and txn_id:
776 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')
777
777
778 if extras.get('rc_internal_commit'):
778 if extras.get('rc_internal_commit'):
779 # special marker for internal commit, we don't call hooks client
779 # special marker for internal commit, we don't call hooks client
780 return 0
780 return 0
781
781
782 extras['hook_type'] = 'post_commit'
782 extras['hook_type'] = 'post_commit'
783 extras['commit_ids'] = [commit_id]
783 extras['commit_ids'] = [commit_id]
784 extras['txn_id'] = txn_id
784 extras['txn_id'] = txn_id
785 extras['new_refs'] = {
785 extras['new_refs'] = {
786 'branches': branches,
786 'branches': branches,
787 'bookmarks': [],
787 'bookmarks': [],
788 'tags': tags,
788 'tags': tags,
789 'total_commits': 1,
789 'total_commits': 1,
790 }
790 }
791
791
792 if 'repo_size' in extras['hooks']:
792 if 'repo_size' in extras['hooks']:
793 try:
793 try:
794 _call_hook('repo_size', extras, SvnMessageWriter())
794 _call_hook('repo_size', extras, SvnMessageWriter())
795 except Exception:
795 except Exception:
796 pass
796 pass
797
797
798 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