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