diff options
| -rwxr-xr-x | repo | 242 |
1 files changed, 104 insertions, 138 deletions
| @@ -285,6 +285,49 @@ def _GitcInitOptions(init_optparse_arg): | |||
| 285 | help='The name of the gitc_client instance to create or modify.') | 285 | help='The name of the gitc_client instance to create or modify.') |
| 286 | 286 | ||
| 287 | 287 | ||
| 288 | # This is a poor replacement for subprocess.run until we require Python 3.6+. | ||
| 289 | RunResult = collections.namedtuple( | ||
| 290 | 'RunResult', ('returncode', 'stdout', 'stderr')) | ||
| 291 | |||
| 292 | |||
| 293 | class RunError(Exception): | ||
| 294 | """Error when running a command failed.""" | ||
| 295 | |||
| 296 | |||
| 297 | def run_command(cmd, **kwargs): | ||
| 298 | """Run |cmd| and return its output.""" | ||
| 299 | check = kwargs.pop('check', False) | ||
| 300 | if kwargs.pop('capture_output', False): | ||
| 301 | kwargs.setdefault('stdout', subprocess.PIPE) | ||
| 302 | kwargs.setdefault('stderr', subprocess.PIPE) | ||
| 303 | cmd_input = kwargs.pop('input', None) | ||
| 304 | |||
| 305 | # Run & package the results. | ||
| 306 | proc = subprocess.Popen(cmd, **kwargs) | ||
| 307 | (stdout, stderr) = proc.communicate(input=cmd_input) | ||
| 308 | if stdout is not None: | ||
| 309 | stdout = stdout.decode('utf-8') | ||
| 310 | if stderr is not None: | ||
| 311 | stderr = stderr.decode('utf-8') | ||
| 312 | ret = RunResult(proc.returncode, stdout, stderr) | ||
| 313 | |||
| 314 | # If things failed, print useful debugging output. | ||
| 315 | if check and ret.returncode: | ||
| 316 | print('repo: error: "%s" failed with exit status %s' % | ||
| 317 | (cmd[0], ret.returncode), file=sys.stderr) | ||
| 318 | print(' cwd: %s\n cmd: %r' % | ||
| 319 | (kwargs.get('cwd', os.getcwd()), cmd), file=sys.stderr) | ||
| 320 | def _print_output(name, output): | ||
| 321 | if output: | ||
| 322 | print(' %s:\n >> %s' % (name, '\n >> '.join(output.splitlines())), | ||
| 323 | file=sys.stderr) | ||
| 324 | _print_output('stdout', ret.stdout) | ||
| 325 | _print_output('stderr', ret.stderr) | ||
| 326 | raise RunError(ret) | ||
| 327 | |||
| 328 | return ret | ||
| 329 | |||
| 330 | |||
| 288 | _gitc_manifest_dir = None | 331 | _gitc_manifest_dir = None |
| 289 | 332 | ||
| 290 | 333 | ||
| @@ -420,6 +463,24 @@ def _Init(args, gitc_init=False): | |||
| 420 | raise | 463 | raise |
| 421 | 464 | ||
| 422 | 465 | ||
| 466 | def run_git(*args, **kwargs): | ||
| 467 | """Run git and return execution details.""" | ||
| 468 | kwargs.setdefault('capture_output', True) | ||
| 469 | kwargs.setdefault('check', True) | ||
| 470 | try: | ||
| 471 | return run_command([GIT] + list(args), **kwargs) | ||
| 472 | except OSError as e: | ||
| 473 | print(file=sys.stderr) | ||
| 474 | print('repo: error: "%s" is not available' % GIT, file=sys.stderr) | ||
| 475 | print('repo: error: %s' % e, file=sys.stderr) | ||
| 476 | print(file=sys.stderr) | ||
| 477 | print('Please make sure %s is installed and in your path.' % GIT, | ||
| 478 | file=sys.stderr) | ||
| 479 | sys.exit(1) | ||
| 480 | except RunError: | ||
| 481 | raise CloneFailure() | ||
| 482 | |||
| 483 | |||
| 423 | # The git version info broken down into components for easy analysis. | 484 | # The git version info broken down into components for easy analysis. |
| 424 | # Similar to Python's sys.version_info. | 485 | # Similar to Python's sys.version_info. |
| 425 | GitVersion = collections.namedtuple( | 486 | GitVersion = collections.namedtuple( |
| @@ -429,7 +490,7 @@ GitVersion = collections.namedtuple( | |||
| 429 | def ParseGitVersion(ver_str=None): | 490 | def ParseGitVersion(ver_str=None): |
| 430 | if ver_str is None: | 491 | if ver_str is None: |
| 431 | # Load the version ourselves. | 492 | # Load the version ourselves. |
| 432 | ver_str = _GetGitVersion() | 493 | ver_str = run_git('--version').stdout |
| 433 | 494 | ||
| 434 | if not ver_str.startswith('git version '): | 495 | if not ver_str.startswith('git version '): |
| 435 | return None | 496 | return None |
| @@ -446,31 +507,8 @@ def ParseGitVersion(ver_str=None): | |||
| 446 | return GitVersion(*to_tuple) | 507 | return GitVersion(*to_tuple) |
| 447 | 508 | ||
| 448 | 509 | ||
| 449 | def _GetGitVersion(): | ||
| 450 | cmd = [GIT, '--version'] | ||
| 451 | try: | ||
| 452 | proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) | ||
| 453 | except OSError as e: | ||
| 454 | print(file=sys.stderr) | ||
| 455 | print("fatal: '%s' is not available" % GIT, file=sys.stderr) | ||
| 456 | print('fatal: %s' % e, file=sys.stderr) | ||
| 457 | print(file=sys.stderr) | ||
| 458 | print('Please make sure %s is installed and in your path.' % GIT, | ||
| 459 | file=sys.stderr) | ||
| 460 | raise | ||
| 461 | |||
| 462 | ver_str = proc.stdout.read().strip() | ||
| 463 | proc.stdout.close() | ||
| 464 | proc.wait() | ||
| 465 | return ver_str.decode('utf-8') | ||
| 466 | |||
| 467 | |||
| 468 | def _CheckGitVersion(): | 510 | def _CheckGitVersion(): |
| 469 | try: | 511 | ver_act = ParseGitVersion() |
| 470 | ver_act = ParseGitVersion() | ||
| 471 | except OSError: | ||
| 472 | raise CloneFailure() | ||
| 473 | |||
| 474 | if ver_act is None: | 512 | if ver_act is None: |
| 475 | print('fatal: unable to detect git version', file=sys.stderr) | 513 | print('fatal: unable to detect git version', file=sys.stderr) |
| 476 | raise CloneFailure() | 514 | raise CloneFailure() |
| @@ -554,9 +592,9 @@ def SetupGnuPG(quiet): | |||
| 554 | 592 | ||
| 555 | cmd = ['gpg', '--import'] | 593 | cmd = ['gpg', '--import'] |
| 556 | try: | 594 | try: |
| 557 | proc = subprocess.Popen(cmd, | 595 | ret = run_command(cmd, env=env, stdin=subprocess.PIPE, |
| 558 | env=env, | 596 | capture_output=quiet, |
| 559 | stdin=subprocess.PIPE) | 597 | input=MAINTAINER_KEYS.encode('utf-8')) |
| 560 | except OSError: | 598 | except OSError: |
| 561 | if not quiet: | 599 | if not quiet: |
| 562 | print('warning: gpg (GnuPG) is not available.', file=sys.stderr) | 600 | print('warning: gpg (GnuPG) is not available.', file=sys.stderr) |
| @@ -564,25 +602,18 @@ def SetupGnuPG(quiet): | |||
| 564 | print(file=sys.stderr) | 602 | print(file=sys.stderr) |
| 565 | return False | 603 | return False |
| 566 | 604 | ||
| 567 | proc.stdin.write(MAINTAINER_KEYS.encode('utf-8')) | 605 | if not quiet: |
| 568 | proc.stdin.close() | 606 | print() |
| 569 | |||
| 570 | if proc.wait() != 0: | ||
| 571 | print('fatal: registering repo maintainer keys failed', file=sys.stderr) | ||
| 572 | sys.exit(1) | ||
| 573 | print() | ||
| 574 | 607 | ||
| 575 | with open(os.path.join(home_dot_repo, 'keyring-version'), 'w') as fd: | 608 | with open(os.path.join(home_dot_repo, 'keyring-version'), 'w') as fd: |
| 576 | fd.write('.'.join(map(str, KEYRING_VERSION)) + '\n') | 609 | fd.write('.'.join(map(str, KEYRING_VERSION)) + '\n') |
| 577 | return True | 610 | return True |
| 578 | 611 | ||
| 579 | 612 | ||
| 580 | def _SetConfig(local, name, value): | 613 | def _SetConfig(cwd, name, value): |
| 581 | """Set a git configuration option to the specified value. | 614 | """Set a git configuration option to the specified value. |
| 582 | """ | 615 | """ |
| 583 | cmd = [GIT, 'config', name, value] | 616 | run_git('config', name, value, cwd=cwd) |
| 584 | if subprocess.Popen(cmd, cwd=local).wait() != 0: | ||
| 585 | raise CloneFailure() | ||
| 586 | 617 | ||
| 587 | 618 | ||
| 588 | def _InitHttp(): | 619 | def _InitHttp(): |
| @@ -610,11 +641,11 @@ def _InitHttp(): | |||
| 610 | urllib.request.install_opener(urllib.request.build_opener(*handlers)) | 641 | urllib.request.install_opener(urllib.request.build_opener(*handlers)) |
| 611 | 642 | ||
| 612 | 643 | ||
| 613 | def _Fetch(url, local, src, quiet): | 644 | def _Fetch(url, cwd, src, quiet): |
| 614 | if not quiet: | 645 | if not quiet: |
| 615 | print('Get %s' % url, file=sys.stderr) | 646 | print('Get %s' % url, file=sys.stderr) |
| 616 | 647 | ||
| 617 | cmd = [GIT, 'fetch'] | 648 | cmd = ['fetch'] |
| 618 | if quiet: | 649 | if quiet: |
| 619 | cmd.append('--quiet') | 650 | cmd.append('--quiet') |
| 620 | err = subprocess.PIPE | 651 | err = subprocess.PIPE |
| @@ -623,26 +654,17 @@ def _Fetch(url, local, src, quiet): | |||
| 623 | cmd.append(src) | 654 | cmd.append(src) |
| 624 | cmd.append('+refs/heads/*:refs/remotes/origin/*') | 655 | cmd.append('+refs/heads/*:refs/remotes/origin/*') |
| 625 | cmd.append('+refs/tags/*:refs/tags/*') | 656 | cmd.append('+refs/tags/*:refs/tags/*') |
| 626 | 657 | run_git(*cmd, stderr=err, cwd=cwd) | |
| 627 | proc = subprocess.Popen(cmd, cwd=local, stderr=err) | ||
| 628 | if err: | ||
| 629 | proc.stderr.read() | ||
| 630 | proc.stderr.close() | ||
| 631 | if proc.wait() != 0: | ||
| 632 | raise CloneFailure() | ||
| 633 | 658 | ||
| 634 | 659 | ||
| 635 | def _DownloadBundle(url, local, quiet): | 660 | def _DownloadBundle(url, cwd, quiet): |
| 636 | if not url.endswith('/'): | 661 | if not url.endswith('/'): |
| 637 | url += '/' | 662 | url += '/' |
| 638 | url += 'clone.bundle' | 663 | url += 'clone.bundle' |
| 639 | 664 | ||
| 640 | proc = subprocess.Popen( | 665 | ret = run_git('config', '--get-regexp', 'url.*.insteadof', cwd=cwd, |
| 641 | [GIT, 'config', '--get-regexp', 'url.*.insteadof'], | 666 | check=False) |
| 642 | cwd=local, | 667 | for line in ret.stdout.splitlines(): |
| 643 | stdout=subprocess.PIPE) | ||
| 644 | for line in proc.stdout: | ||
| 645 | line = line.decode('utf-8') | ||
| 646 | m = re.compile(r'^url\.(.*)\.insteadof (.*)$').match(line) | 668 | m = re.compile(r'^url\.(.*)\.insteadof (.*)$').match(line) |
| 647 | if m: | 669 | if m: |
| 648 | new_url = m.group(1) | 670 | new_url = m.group(1) |
| @@ -650,13 +672,11 @@ def _DownloadBundle(url, local, quiet): | |||
| 650 | if url.startswith(old_url): | 672 | if url.startswith(old_url): |
| 651 | url = new_url + url[len(old_url):] | 673 | url = new_url + url[len(old_url):] |
| 652 | break | 674 | break |
| 653 | proc.stdout.close() | ||
| 654 | proc.wait() | ||
| 655 | 675 | ||
| 656 | if not url.startswith('http:') and not url.startswith('https:'): | 676 | if not url.startswith('http:') and not url.startswith('https:'): |
| 657 | return False | 677 | return False |
| 658 | 678 | ||
| 659 | dest = open(os.path.join(local, '.git', 'clone.bundle'), 'w+b') | 679 | dest = open(os.path.join(cwd, '.git', 'clone.bundle'), 'w+b') |
| 660 | try: | 680 | try: |
| 661 | try: | 681 | try: |
| 662 | r = urllib.request.urlopen(url) | 682 | r = urllib.request.urlopen(url) |
| @@ -684,67 +704,45 @@ def _DownloadBundle(url, local, quiet): | |||
| 684 | dest.close() | 704 | dest.close() |
| 685 | 705 | ||
| 686 | 706 | ||
| 687 | def _ImportBundle(local): | 707 | def _ImportBundle(cwd): |
| 688 | path = os.path.join(local, '.git', 'clone.bundle') | 708 | path = os.path.join(cwd, '.git', 'clone.bundle') |
| 689 | try: | 709 | try: |
| 690 | _Fetch(local, local, path, True) | 710 | _Fetch(cwd, cwd, path, True) |
| 691 | finally: | 711 | finally: |
| 692 | os.remove(path) | 712 | os.remove(path) |
| 693 | 713 | ||
| 694 | 714 | ||
| 695 | def _Clone(url, local, quiet, clone_bundle): | 715 | def _Clone(url, cwd, quiet, clone_bundle): |
| 696 | """Clones a git repository to a new subdirectory of repodir | 716 | """Clones a git repository to a new subdirectory of repodir |
| 697 | """ | 717 | """ |
| 698 | try: | 718 | try: |
| 699 | os.mkdir(local) | 719 | os.mkdir(cwd) |
| 700 | except OSError as e: | 720 | except OSError as e: |
| 701 | print('fatal: cannot make %s directory: %s' % (local, e.strerror), | 721 | print('fatal: cannot make %s directory: %s' % (cwd, e.strerror), |
| 702 | file=sys.stderr) | 722 | file=sys.stderr) |
| 703 | raise CloneFailure() | 723 | raise CloneFailure() |
| 704 | 724 | ||
| 705 | cmd = [GIT, 'init', '--quiet'] | 725 | run_git('init', '--quiet', cwd=cwd) |
| 706 | try: | ||
| 707 | proc = subprocess.Popen(cmd, cwd=local) | ||
| 708 | except OSError as e: | ||
| 709 | print(file=sys.stderr) | ||
| 710 | print("fatal: '%s' is not available" % GIT, file=sys.stderr) | ||
| 711 | print('fatal: %s' % e, file=sys.stderr) | ||
| 712 | print(file=sys.stderr) | ||
| 713 | print('Please make sure %s is installed and in your path.' % GIT, | ||
| 714 | file=sys.stderr) | ||
| 715 | raise CloneFailure() | ||
| 716 | if proc.wait() != 0: | ||
| 717 | print('fatal: could not create %s' % local, file=sys.stderr) | ||
| 718 | raise CloneFailure() | ||
| 719 | 726 | ||
| 720 | _InitHttp() | 727 | _InitHttp() |
| 721 | _SetConfig(local, 'remote.origin.url', url) | 728 | _SetConfig(cwd, 'remote.origin.url', url) |
| 722 | _SetConfig(local, | 729 | _SetConfig(cwd, |
| 723 | 'remote.origin.fetch', | 730 | 'remote.origin.fetch', |
| 724 | '+refs/heads/*:refs/remotes/origin/*') | 731 | '+refs/heads/*:refs/remotes/origin/*') |
| 725 | if clone_bundle and _DownloadBundle(url, local, quiet): | 732 | if clone_bundle and _DownloadBundle(url, cwd, quiet): |
| 726 | _ImportBundle(local) | 733 | _ImportBundle(cwd) |
| 727 | _Fetch(url, local, 'origin', quiet) | 734 | _Fetch(url, cwd, 'origin', quiet) |
| 728 | 735 | ||
| 729 | 736 | ||
| 730 | def _Verify(cwd, branch, quiet): | 737 | def _Verify(cwd, branch, quiet): |
| 731 | """Verify the branch has been signed by a tag. | 738 | """Verify the branch has been signed by a tag. |
| 732 | """ | 739 | """ |
| 733 | cmd = [GIT, 'describe', 'origin/%s' % branch] | 740 | try: |
| 734 | proc = subprocess.Popen(cmd, | 741 | ret = run_git('describe', 'origin/%s' % branch, cwd=cwd) |
| 735 | stdout=subprocess.PIPE, | 742 | cur = ret.stdout.strip() |
| 736 | stderr=subprocess.PIPE, | 743 | except CloneFailure: |
| 737 | cwd=cwd) | ||
| 738 | cur = proc.stdout.read().strip().decode('utf-8') | ||
| 739 | proc.stdout.close() | ||
| 740 | |||
| 741 | proc.stderr.read() | ||
| 742 | proc.stderr.close() | ||
| 743 | |||
| 744 | if proc.wait() != 0 or not cur: | ||
| 745 | print(file=sys.stderr) | ||
| 746 | print("fatal: branch '%s' has not been signed" % branch, file=sys.stderr) | 744 | print("fatal: branch '%s' has not been signed" % branch, file=sys.stderr) |
| 747 | raise CloneFailure() | 745 | raise |
| 748 | 746 | ||
| 749 | m = re.compile(r'^(.*)-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur) | 747 | m = re.compile(r'^(.*)-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur) |
| 750 | if m: | 748 | if m: |
| @@ -757,48 +755,25 @@ def _Verify(cwd, branch, quiet): | |||
| 757 | 755 | ||
| 758 | env = os.environ.copy() | 756 | env = os.environ.copy() |
| 759 | _setenv('GNUPGHOME', gpg_dir, env) | 757 | _setenv('GNUPGHOME', gpg_dir, env) |
| 760 | 758 | run_git('tag', '-v', cur, cwd=cwd, env=env) | |
| 761 | cmd = [GIT, 'tag', '-v', cur] | ||
| 762 | proc = subprocess.Popen(cmd, | ||
| 763 | stdout=subprocess.PIPE, | ||
| 764 | stderr=subprocess.PIPE, | ||
| 765 | cwd=cwd, | ||
| 766 | env=env) | ||
| 767 | out = proc.stdout.read().decode('utf-8') | ||
| 768 | proc.stdout.close() | ||
| 769 | |||
| 770 | err = proc.stderr.read().decode('utf-8') | ||
| 771 | proc.stderr.close() | ||
| 772 | |||
| 773 | if proc.wait() != 0: | ||
| 774 | print(file=sys.stderr) | ||
| 775 | print(out, file=sys.stderr) | ||
| 776 | print(err, file=sys.stderr) | ||
| 777 | print(file=sys.stderr) | ||
| 778 | raise CloneFailure() | ||
| 779 | return '%s^0' % cur | 759 | return '%s^0' % cur |
| 780 | 760 | ||
| 781 | 761 | ||
| 782 | def _Checkout(cwd, branch, rev, quiet): | 762 | def _Checkout(cwd, branch, rev, quiet): |
| 783 | """Checkout an upstream branch into the repository and track it. | 763 | """Checkout an upstream branch into the repository and track it. |
| 784 | """ | 764 | """ |
| 785 | cmd = [GIT, 'update-ref', 'refs/heads/default', rev] | 765 | run_git('update-ref', 'refs/heads/default', rev, cwd=cwd) |
| 786 | if subprocess.Popen(cmd, cwd=cwd).wait() != 0: | ||
| 787 | raise CloneFailure() | ||
| 788 | 766 | ||
| 789 | _SetConfig(cwd, 'branch.default.remote', 'origin') | 767 | _SetConfig(cwd, 'branch.default.remote', 'origin') |
| 790 | _SetConfig(cwd, 'branch.default.merge', 'refs/heads/%s' % branch) | 768 | _SetConfig(cwd, 'branch.default.merge', 'refs/heads/%s' % branch) |
| 791 | 769 | ||
| 792 | cmd = [GIT, 'symbolic-ref', 'HEAD', 'refs/heads/default'] | 770 | run_git('symbolic-ref', 'HEAD', 'refs/heads/default', cwd=cwd) |
| 793 | if subprocess.Popen(cmd, cwd=cwd).wait() != 0: | ||
| 794 | raise CloneFailure() | ||
| 795 | 771 | ||
| 796 | cmd = [GIT, 'read-tree', '--reset', '-u'] | 772 | cmd = ['read-tree', '--reset', '-u'] |
| 797 | if not quiet: | 773 | if not quiet: |
| 798 | cmd.append('-v') | 774 | cmd.append('-v') |
| 799 | cmd.append('HEAD') | 775 | cmd.append('HEAD') |
| 800 | if subprocess.Popen(cmd, cwd=cwd).wait() != 0: | 776 | run_git(*cmd, cwd=cwd) |
| 801 | raise CloneFailure() | ||
| 802 | 777 | ||
| 803 | 778 | ||
| 804 | def _FindRepo(): | 779 | def _FindRepo(): |
| @@ -923,19 +898,10 @@ def _SetDefaultsTo(gitdir): | |||
| 923 | global REPO_REV | 898 | global REPO_REV |
| 924 | 899 | ||
| 925 | REPO_URL = gitdir | 900 | REPO_URL = gitdir |
| 926 | proc = subprocess.Popen([GIT, | 901 | try: |
| 927 | '--git-dir=%s' % gitdir, | 902 | ret = run_git('--git-dir=%s' % gitdir, 'symbolic-ref', 'HEAD') |
| 928 | 'symbolic-ref', | 903 | REPO_REV = ret.stdout.strip() |
| 929 | 'HEAD'], | 904 | except CloneFailure: |
| 930 | stdout=subprocess.PIPE, | ||
| 931 | stderr=subprocess.PIPE) | ||
| 932 | REPO_REV = proc.stdout.read().strip().decode('utf-8') | ||
| 933 | proc.stdout.close() | ||
| 934 | |||
| 935 | proc.stderr.read() | ||
| 936 | proc.stderr.close() | ||
| 937 | |||
| 938 | if proc.wait() != 0: | ||
| 939 | print('fatal: %s has no current branch' % gitdir, file=sys.stderr) | 905 | print('fatal: %s has no current branch' % gitdir, file=sys.stderr) |
| 940 | sys.exit(1) | 906 | sys.exit(1) |
| 941 | 907 | ||
