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