summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Frysinger <vapier@google.com>2020-02-12 08:01:38 -0500
committerMike Frysinger <vapier@google.com>2020-02-13 06:57:35 +0000
commit62285d22c106b0e96cacf21845fb77cabb361812 (patch)
treeb8d8f8dc9b02c5a6e98225769f4a25b3e088bc08
parent3cda50a41b1a950d70be8887c5efe3735586d1bb (diff)
downloadgit-repo-62285d22c106b0e96cacf21845fb77cabb361812.tar.gz
repo: add some helpers akin to subprocess.run
We can't rely on subprocess.run yet as that requires Python 3.6, but we can clean up the code we have with some ad-hoc replacement. This unifies all the inconsistent subprocess.Popen usage we have. Change-Id: I56af40a3df988ee47b299105d692ff419d07ad6b Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254754 Reviewed-by: David Pursehouse <dpursehouse@collab.net> Tested-by: Mike Frysinger <vapier@google.com>
-rwxr-xr-xrepo242
1 files changed, 104 insertions, 138 deletions
diff --git a/repo b/repo
index d1c6c6de..bbcf0d50 100755
--- a/repo
+++ b/repo
@@ -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+.
289RunResult = collections.namedtuple(
290 'RunResult', ('returncode', 'stdout', 'stderr'))
291
292
293class RunError(Exception):
294 """Error when running a command failed."""
295
296
297def 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
466def 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.
425GitVersion = collections.namedtuple( 486GitVersion = collections.namedtuple(
@@ -429,7 +490,7 @@ GitVersion = collections.namedtuple(
429def ParseGitVersion(ver_str=None): 490def 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
449def _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
468def _CheckGitVersion(): 510def _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
580def _SetConfig(local, name, value): 613def _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
588def _InitHttp(): 619def _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
613def _Fetch(url, local, src, quiet): 644def _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
635def _DownloadBundle(url, local, quiet): 660def _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
687def _ImportBundle(local): 707def _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
695def _Clone(url, local, quiet, clone_bundle): 715def _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
730def _Verify(cwd, branch, quiet): 737def _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
782def _Checkout(cwd, branch, rev, quiet): 762def _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
804def _FindRepo(): 779def _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