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 | ||