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