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