Show More
@@ -25,6 +25,8 b' import collections' | |||
|
25 | 25 | import importlib |
|
26 | 26 | import base64 |
|
27 | 27 | import msgpack |
|
28 | import dataclasses | |
|
29 | import pygit2 | |
|
28 | 30 | |
|
29 | 31 | from http.client import HTTPConnection |
|
30 | 32 | |
@@ -34,7 +36,8 b' import mercurial.node' | |||
|
34 | 36 | |
|
35 | 37 | from vcsserver.lib.rc_json import json |
|
36 | 38 | from vcsserver import exceptions, subprocessio, settings |
|
37 |
from vcsserver.str_utils import safe_ |
|
|
39 | from vcsserver.str_utils import ascii_str, safe_str | |
|
40 | from vcsserver.remote.git import Repository | |
|
38 | 41 | |
|
39 | 42 | log = logging.getLogger(__name__) |
|
40 | 43 | |
@@ -104,7 +107,7 b' class HgMessageWriter(RemoteMessageWrite' | |||
|
104 | 107 | def __init__(self, ui): |
|
105 | 108 | self.ui = ui |
|
106 | 109 | |
|
107 | def write(self, message): | |
|
110 | def write(self, message: str): | |
|
108 | 111 | # TODO: Check why the quiet flag is set by default. |
|
109 | 112 | old = self.ui.quiet |
|
110 | 113 | self.ui.quiet = False |
@@ -118,8 +121,8 b' class GitMessageWriter(RemoteMessageWrit' | |||
|
118 | 121 | def __init__(self, stdout=None): |
|
119 | 122 | self.stdout = stdout or sys.stdout |
|
120 | 123 | |
|
121 | def write(self, message): | |
|
122 |
self.stdout.write( |
|
|
124 | def write(self, message: str): | |
|
125 | self.stdout.write(message) | |
|
123 | 126 | |
|
124 | 127 | |
|
125 | 128 | class SvnMessageWriter(RemoteMessageWriter): |
@@ -147,8 +150,9 b' def _handle_exception(result):' | |||
|
147 | 150 | elif exception_class == 'RepositoryError': |
|
148 | 151 | raise exceptions.VcsException()(*result['exception_args']) |
|
149 | 152 | elif exception_class: |
|
150 | raise Exception('Got remote exception "%s" with args "%s"' % | |
|
151 |
|
|
|
153 | raise Exception( | |
|
154 | f"""Got remote exception "{exception_class}" with args "{result['exception_args']}" """ | |
|
155 | ) | |
|
152 | 156 | |
|
153 | 157 | |
|
154 | 158 | def _get_hooks_client(extras): |
@@ -167,7 +171,6 b' def _call_hook(hook_name, extras, writer' | |||
|
167 | 171 | log.debug('Hooks, using client:%s', hooks_client) |
|
168 | 172 | result = hooks_client(hook_name, extras) |
|
169 | 173 | log.debug('Hooks got result: %s', result) |
|
170 | ||
|
171 | 174 | _handle_exception(result) |
|
172 | 175 | writer.write(result['output']) |
|
173 | 176 | |
@@ -198,8 +201,8 b' def _rev_range_hash(repo, node, check_he' | |||
|
198 | 201 | for rev in range(start, end): |
|
199 | 202 | revs.append(rev) |
|
200 | 203 | ctx = get_ctx(repo, rev) |
|
201 | commit_id = mercurial.node.hex(ctx.node()) | |
|
202 | branch = ctx.branch() | |
|
204 | commit_id = ascii_str(mercurial.node.hex(ctx.node())) | |
|
205 | branch = safe_str(ctx.branch()) | |
|
203 | 206 | commits.append((commit_id, branch)) |
|
204 | 207 | |
|
205 | 208 | parent_heads = [] |
@@ -223,8 +226,8 b' def _check_heads(repo, start, end, commi' | |||
|
223 | 226 | for p in parents: |
|
224 | 227 | branch = get_ctx(repo, p).branch() |
|
225 | 228 | # The heads descending from that parent, on the same branch |
|
226 |
parent_heads = |
|
|
227 |
reachable = |
|
|
229 | parent_heads = {p} | |
|
230 | reachable = {p} | |
|
228 | 231 | for x in range(p + 1, end): |
|
229 | 232 | if get_ctx(repo, x).branch() != branch: |
|
230 | 233 | continue |
@@ -301,14 +304,16 b' def pre_push(ui, repo, node=None, **kwar' | |||
|
301 | 304 | detect_force_push = extras.get('detect_force_push') |
|
302 | 305 | |
|
303 | 306 | rev_data = [] |
|
304 | if node and kwargs.get('hooktype') == 'pretxnchangegroup': | |
|
307 | hook_type: str = safe_str(kwargs.get('hooktype')) | |
|
308 | ||
|
309 | if node and hook_type == 'pretxnchangegroup': | |
|
305 | 310 | branches = collections.defaultdict(list) |
|
306 | 311 | commits, _heads = _rev_range_hash(repo, node, check_heads=detect_force_push) |
|
307 | 312 | for commit_id, branch in commits: |
|
308 | 313 | branches[branch].append(commit_id) |
|
309 | 314 | |
|
310 | 315 | for branch, commits in branches.items(): |
|
311 | old_rev = kwargs.get('node_last') or commits[0] | |
|
316 | old_rev = ascii_str(kwargs.get('node_last')) or commits[0] | |
|
312 | 317 | rev_data.append({ |
|
313 | 318 | 'total_commits': len(commits), |
|
314 | 319 | 'old_rev': old_rev, |
@@ -325,10 +330,10 b' def pre_push(ui, repo, node=None, **kwar' | |||
|
325 | 330 | extras.get('repo_store', ''), extras.get('repository', '')) |
|
326 | 331 | push_ref['hg_env'] = _get_hg_env( |
|
327 | 332 | old_rev=push_ref['old_rev'], |
|
328 | new_rev=push_ref['new_rev'], txnid=kwargs.get('txnid'), | |
|
333 | new_rev=push_ref['new_rev'], txnid=ascii_str(kwargs.get('txnid')), | |
|
329 | 334 | repo_path=repo_path) |
|
330 | 335 | |
|
331 |
extras['hook_type'] = |
|
|
336 | extras['hook_type'] = hook_type or 'pre_push' | |
|
332 | 337 | extras['commit_ids'] = rev_data |
|
333 | 338 | |
|
334 | 339 | return _call_hook('pre_push', extras, HgMessageWriter(ui)) |
@@ -369,6 +374,7 b' def post_push(ui, repo, node, **kwargs):' | |||
|
369 | 374 | branches = [] |
|
370 | 375 | bookmarks = [] |
|
371 | 376 | tags = [] |
|
377 | hook_type: str = safe_str(kwargs.get('hooktype')) | |
|
372 | 378 | |
|
373 | 379 | commits, _heads = _rev_range_hash(repo, node) |
|
374 | 380 | for commit_id, branch in commits: |
@@ -376,11 +382,12 b' def post_push(ui, repo, node, **kwargs):' | |||
|
376 | 382 | if branch not in branches: |
|
377 | 383 | branches.append(branch) |
|
378 | 384 | |
|
379 |
if hasattr(ui, '_rc_pushkey_b |
|
|
380 |
bookmarks = ui._rc_pushkey_b |
|
|
385 | if hasattr(ui, '_rc_pushkey_bookmarks'): | |
|
386 | bookmarks = ui._rc_pushkey_bookmarks | |
|
381 | 387 | |
|
382 |
extras['hook_type'] = |
|
|
388 | extras['hook_type'] = hook_type or 'post_push' | |
|
383 | 389 | extras['commit_ids'] = commit_ids |
|
390 | ||
|
384 | 391 | extras['new_refs'] = { |
|
385 | 392 | 'branches': branches, |
|
386 | 393 | 'bookmarks': bookmarks, |
@@ -401,9 +408,10 b' def post_push_ssh(ui, repo, node, **kwar' | |||
|
401 | 408 | |
|
402 | 409 | def key_push(ui, repo, **kwargs): |
|
403 | 410 | from vcsserver.hgcompat import get_ctx |
|
404 | if kwargs['new'] != '0' and kwargs['namespace'] == 'bookmarks': | |
|
411 | ||
|
412 | if kwargs['new'] != b'0' and kwargs['namespace'] == b'bookmarks': | |
|
405 | 413 | # store new bookmarks in our UI object propagated later to post_push |
|
406 |
ui._rc_pushkey_b |
|
|
414 | ui._rc_pushkey_bookmarks = get_ctx(repo, kwargs['key']).bookmarks() | |
|
407 | 415 | return |
|
408 | 416 | |
|
409 | 417 | |
@@ -432,10 +440,13 b' def handle_git_post_receive(unused_repo_' | |||
|
432 | 440 | pass |
|
433 | 441 | |
|
434 | 442 | |
|
435 | HookResponse = collections.namedtuple('HookResponse', ('status', 'output')) | |
|
443 | @dataclasses.dataclass | |
|
444 | class HookResponse: | |
|
445 | status: int | |
|
446 | output: str | |
|
436 | 447 | |
|
437 | 448 | |
|
438 | def git_pre_pull(extras): | |
|
449 | def git_pre_pull(extras) -> HookResponse: | |
|
439 | 450 | """ |
|
440 | 451 | Pre pull hook. |
|
441 | 452 | |
@@ -449,19 +460,19 b' def git_pre_pull(extras):' | |||
|
449 | 460 | if 'pull' not in extras['hooks']: |
|
450 | 461 | return HookResponse(0, '') |
|
451 | 462 | |
|
452 |
stdout = io. |
|
|
463 | stdout = io.StringIO() | |
|
453 | 464 | try: |
|
454 | status = _call_hook('pre_pull', extras, GitMessageWriter(stdout)) | |
|
465 | status_code = _call_hook('pre_pull', extras, GitMessageWriter(stdout)) | |
|
455 | 466 | |
|
456 | 467 | except Exception as error: |
|
457 | 468 | log.exception('Failed to call pre_pull hook') |
|
458 | status = 128 | |
|
459 |
stdout.write |
|
|
469 | status_code = 128 | |
|
470 | stdout.write(f'ERROR: {error}\n') | |
|
460 | 471 | |
|
461 | return HookResponse(status, stdout.getvalue()) | |
|
472 | return HookResponse(status_code, stdout.getvalue()) | |
|
462 | 473 | |
|
463 | 474 | |
|
464 | def git_post_pull(extras): | |
|
475 | def git_post_pull(extras) -> HookResponse: | |
|
465 | 476 | """ |
|
466 | 477 | Post pull hook. |
|
467 | 478 | |
@@ -474,12 +485,12 b' def git_post_pull(extras):' | |||
|
474 | 485 | if 'pull' not in extras['hooks']: |
|
475 | 486 | return HookResponse(0, '') |
|
476 | 487 | |
|
477 |
stdout = io. |
|
|
488 | stdout = io.StringIO() | |
|
478 | 489 | try: |
|
479 | 490 | status = _call_hook('post_pull', extras, GitMessageWriter(stdout)) |
|
480 | 491 | except Exception as error: |
|
481 | 492 | status = 128 |
|
482 |
stdout.write |
|
|
493 | stdout.write(f'ERROR: {error}\n') | |
|
483 | 494 | |
|
484 | 495 | return HookResponse(status, stdout.getvalue()) |
|
485 | 496 | |
@@ -504,15 +515,11 b' def _parse_git_ref_lines(revision_lines)' | |||
|
504 | 515 | return rev_data |
|
505 | 516 | |
|
506 | 517 | |
|
507 | def git_pre_receive(unused_repo_path, revision_lines, env): | |
|
518 | def git_pre_receive(unused_repo_path, revision_lines, env) -> int: | |
|
508 | 519 | """ |
|
509 | 520 | Pre push hook. |
|
510 | 521 | |
|
511 | :param extras: dictionary containing the keys defined in simplevcs | |
|
512 | :type extras: dict | |
|
513 | ||
|
514 | 522 | :return: status code of the hook. 0 for success. |
|
515 | :rtype: int | |
|
516 | 523 | """ |
|
517 | 524 | extras = json.loads(env['RC_SCM_DATA']) |
|
518 | 525 | rev_data = _parse_git_ref_lines(revision_lines) |
@@ -545,18 +552,18 b' def git_pre_receive(unused_repo_path, re' | |||
|
545 | 552 | |
|
546 | 553 | extras['hook_type'] = 'pre_receive' |
|
547 | 554 | extras['commit_ids'] = rev_data |
|
548 | return _call_hook('pre_push', extras, GitMessageWriter()) | |
|
555 | ||
|
556 | stdout = sys.stdout | |
|
557 | status_code = _call_hook('pre_push', extras, GitMessageWriter(stdout)) | |
|
558 | ||
|
559 | return status_code | |
|
549 | 560 | |
|
550 | 561 | |
|
551 | def git_post_receive(unused_repo_path, revision_lines, env): | |
|
562 | def git_post_receive(unused_repo_path, revision_lines, env) -> int: | |
|
552 | 563 | """ |
|
553 | 564 | Post push hook. |
|
554 | 565 | |
|
555 | :param extras: dictionary containing the keys defined in simplevcs | |
|
556 | :type extras: dict | |
|
557 | ||
|
558 | 566 | :return: status code of the hook. 0 for success. |
|
559 | :rtype: int | |
|
560 | 567 | """ |
|
561 | 568 | extras = json.loads(env['RC_SCM_DATA']) |
|
562 | 569 | if 'push' not in extras['hooks']: |
@@ -576,26 +583,28 b' def git_post_receive(unused_repo_path, r' | |||
|
576 | 583 | type_ = push_ref['type'] |
|
577 | 584 | |
|
578 | 585 | if type_ == 'heads': |
|
586 | # starting new branch case | |
|
579 | 587 | if push_ref['old_rev'] == empty_commit_id: |
|
580 | # starting new branch case | |
|
581 | if push_ref['name'] not in branches: | |
|
582 | branches.append(push_ref['name']) | |
|
588 | push_ref_name = push_ref['name'] | |
|
589 | ||
|
590 | if push_ref_name not in branches: | |
|
591 | branches.append(push_ref_name) | |
|
583 | 592 | |
|
584 | # Fix up head revision if needed | |
|
585 | cmd = [settings.GIT_EXECUTABLE, 'show', 'HEAD'] | |
|
586 | try: | |
|
587 | subprocessio.run_command(cmd, env=os.environ.copy()) | |
|
588 |
except |
|
|
589 | push_ref_name = push_ref['name'] | |
|
590 | cmd = [settings.GIT_EXECUTABLE, 'symbolic-ref', '"HEAD"', f'"refs/heads/{push_ref_name}"'] | |
|
591 | print(f"Setting default branch to {push_ref_name}") | |
|
592 | subprocessio.run_command(cmd, env=os.environ.copy()) | |
|
593 | need_head_set = '' | |
|
594 | with Repository(os.getcwd()) as repo: | |
|
595 | try: | |
|
596 | repo.head | |
|
597 | except pygit2.GitError: | |
|
598 | need_head_set = f'refs/heads/{push_ref_name}' | |
|
593 | 599 | |
|
594 | cmd = [settings.GIT_EXECUTABLE, 'for-each-ref', | |
|
595 | '--format=%(refname)', 'refs/heads/*'] | |
|
600 | if need_head_set: | |
|
601 | repo.set_head(need_head_set) | |
|
602 | print(f"Setting default branch to {push_ref_name}") | |
|
603 | ||
|
604 | cmd = [settings.GIT_EXECUTABLE, 'for-each-ref', '--format=%(refname)', 'refs/heads/*'] | |
|
596 | 605 | stdout, stderr = subprocessio.run_command( |
|
597 | 606 | cmd, env=os.environ.copy()) |
|
598 | heads = stdout | |
|
607 | heads = safe_str(stdout) | |
|
599 | 608 | heads = heads.replace(push_ref['ref'], '') |
|
600 | 609 | heads = ' '.join(head for head |
|
601 | 610 | in heads.splitlines() if head) or '.' |
@@ -604,9 +613,10 b' def git_post_receive(unused_repo_path, r' | |||
|
604 | 613 | '--not', heads] |
|
605 | 614 | stdout, stderr = subprocessio.run_command( |
|
606 | 615 | cmd, env=os.environ.copy()) |
|
607 | git_revs.extend(stdout.splitlines()) | |
|
616 | git_revs.extend(list(map(ascii_str, stdout.splitlines()))) | |
|
617 | ||
|
618 | # delete branch case | |
|
608 | 619 | elif push_ref['new_rev'] == empty_commit_id: |
|
609 | # delete branch case | |
|
610 | 620 | git_revs.append('delete_branch=>%s' % push_ref['name']) |
|
611 | 621 | else: |
|
612 | 622 | if push_ref['name'] not in branches: |
@@ -617,7 +627,25 b' def git_post_receive(unused_repo_path, r' | |||
|
617 | 627 | '--reverse', '--pretty=format:%H'] |
|
618 | 628 | stdout, stderr = subprocessio.run_command( |
|
619 | 629 | cmd, env=os.environ.copy()) |
|
620 | git_revs.extend(stdout.splitlines()) | |
|
630 | # we get bytes from stdout, we need str to be consistent | |
|
631 | log_revs = list(map(ascii_str, stdout.splitlines())) | |
|
632 | git_revs.extend(log_revs) | |
|
633 | ||
|
634 | # Pure pygit2 impl. but still 2-3x slower :/ | |
|
635 | # results = [] | |
|
636 | # | |
|
637 | # with Repository(os.getcwd()) as repo: | |
|
638 | # repo_new_rev = repo[push_ref['new_rev']] | |
|
639 | # repo_old_rev = repo[push_ref['old_rev']] | |
|
640 | # walker = repo.walk(repo_new_rev.id, pygit2.GIT_SORT_TOPOLOGICAL) | |
|
641 | # | |
|
642 | # for commit in walker: | |
|
643 | # if commit.id == repo_old_rev.id: | |
|
644 | # break | |
|
645 | # results.append(commit.id.hex) | |
|
646 | # # reverse the order, can't use GIT_SORT_REVERSE | |
|
647 | # log_revs = results[::-1] | |
|
648 | ||
|
621 | 649 | elif type_ == 'tags': |
|
622 | 650 | if push_ref['name'] not in tags: |
|
623 | 651 | tags.append(push_ref['name']) |
@@ -631,13 +659,16 b' def git_post_receive(unused_repo_path, r' | |||
|
631 | 659 | 'tags': tags, |
|
632 | 660 | } |
|
633 | 661 | |
|
662 | stdout = sys.stdout | |
|
663 | ||
|
634 | 664 | if 'repo_size' in extras['hooks']: |
|
635 | 665 | try: |
|
636 | _call_hook('repo_size', extras, GitMessageWriter()) | |
|
666 | _call_hook('repo_size', extras, GitMessageWriter(stdout)) | |
|
637 | 667 | except Exception: |
|
638 | 668 | pass |
|
639 | 669 | |
|
640 |
|
|
|
670 | status_code = _call_hook('post_push', extras, GitMessageWriter(stdout)) | |
|
671 | return status_code | |
|
641 | 672 | |
|
642 | 673 | |
|
643 | 674 | def _get_extras_from_txn_id(path, txn_id): |
@@ -336,8 +336,9 b' class GitRepository(object):' | |||
|
336 | 336 | pre_pull_messages = '' |
|
337 | 337 | # Upload-pack == clone |
|
338 | 338 | if git_command == 'git-upload-pack': |
|
339 |
|
|
|
340 | if status != 0: | |
|
339 | hook_response = hooks.git_pre_pull(self.extras) | |
|
340 | if hook_response.status != 0: | |
|
341 | pre_pull_messages = hook_response.output | |
|
341 | 342 | resp.app_iter = self._build_failed_pre_pull_response( |
|
342 | 343 | capabilities, pre_pull_messages) |
|
343 | 344 | return resp |
@@ -385,8 +386,8 b' class GitRepository(object):' | |||
|
385 | 386 | |
|
386 | 387 | # Upload-pack == clone |
|
387 | 388 | if git_command == 'git-upload-pack': |
|
388 |
|
|
|
389 | ||
|
389 | hook_response = hooks.git_post_pull(self.extras) | |
|
390 | post_pull_messages = hook_response.output | |
|
390 | 391 | resp.app_iter = self._build_post_pull_response(out, capabilities, pre_pull_messages, post_pull_messages) |
|
391 | 392 | else: |
|
392 | 393 | resp.app_iter = out |
@@ -22,8 +22,9 b' import posixpath as vcspath' | |||
|
22 | 22 | import re |
|
23 | 23 | import stat |
|
24 | 24 | import traceback |
|
25 |
import urllib.request |
|
|
26 | import urllib.request, urllib.error, urllib.parse | |
|
25 | import urllib.request | |
|
26 | import urllib.parse | |
|
27 | import urllib.error | |
|
27 | 28 | from functools import wraps |
|
28 | 29 | |
|
29 | 30 | import more_itertools |
@@ -40,7 +41,7 b' from dulwich.repo import Repo as Dulwich' | |||
|
40 | 41 | from dulwich.server import update_server_info |
|
41 | 42 | |
|
42 | 43 | from vcsserver import exceptions, settings, subprocessio |
|
43 |
from vcsserver.str_utils import safe_str, safe_int, safe_bytes, |
|
|
44 | from vcsserver.str_utils import safe_str, safe_int, safe_bytes, ascii_bytes | |
|
44 | 45 | from vcsserver.base import RepoFactory, obfuscate_qs, ArchiveNode, archive_repo, BinaryEnvelope |
|
45 | 46 | from vcsserver.hgcompat import ( |
|
46 | 47 | hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler) |
@@ -69,7 +70,7 b' def reraise_safe_exceptions(func):' | |||
|
69 | 70 | except (HangupException, UnexpectedCommandError) as e: |
|
70 | 71 | exc = exceptions.VcsException(org_exc=e) |
|
71 | 72 | raise exc(safe_str(e)) |
|
72 |
except Exception |
|
|
73 | except Exception: | |
|
73 | 74 | # NOTE(marcink): because of how dulwich handles some exceptions |
|
74 | 75 | # (KeyError on empty repos), we cannot track this and catch all |
|
75 | 76 | # exceptions, it's an exceptions from other handlers |
@@ -107,7 +108,7 b' class GitFactory(RepoFactory):' | |||
|
107 | 108 | |
|
108 | 109 | def _create_repo(self, wire, create, use_libgit2=False): |
|
109 | 110 | if use_libgit2: |
|
110 |
re |
|
|
111 | repo = Repository(safe_bytes(wire['path'])) | |
|
111 | 112 | else: |
|
112 | 113 | # dulwich mode |
|
113 | 114 | repo_path = safe_str(wire['path'], to_encoding=settings.WIRE_ENCODING) |
General Comments 0
You need to be logged in to leave comments.
Login now