#!/usr/bin/env python3 """Do a sanity check that you're at something that's ready for you to cut an RC from; that you're in the right repo, that you're on the right branch, that all of the subtrees are up-to-date... """ from typing import Generator import sys import time import os.path from os import getenv from contextlib import contextmanager from lib import assert_eq, git_check_clean, parse_bool, vX, vY, re_ga, re_ea from lib.uiutil import Checker, CheckResult, run from lib.uiutil import run_txtcapture as run_capture DEFAULT_REPO = 'git@github.com:emissary-ingress/emissary' def main(next_ver: str, quiet: bool = False, allow_untracked: bool = False) -> int: print(f'Starting work on "v{next_ver}"...') print() remote_repo = getenv('AMBASSADOR_RELEASE_REPO_OVERRIDE') if remote_repo is None or remote_repo == '': remote_repo = DEFAULT_REPO checker = Checker() @contextmanager def check(name: str) -> Generator[CheckResult, None, None]: with checker.check(name) as subcheck: # time.sleep(1) # it's stupid, but honestly the delay makes the output more readable yield subcheck is_private = False with check(f"You're in a clone of {remote_repo}"): url = run_capture(['git', 'remote', 'get-url', '--push', 'origin']) if url.endswith("/"): url = url[:-len("/")] if url.endswith(".git"): url = url[:-len(".git")] if url.endswith("-private"): is_private = True url = url[:-len("-private")] assert_eq(url, remote_repo) with check("You're in the toplevel of the clone"): toplevel = run_capture(['git', 'rev-parse', '--show-toplevel']) if not os.path.samefile(toplevel, '.'): raise Exception(f"Not in {toplevel}") with check("You're in a clean checkout"): git_check_clean(allow_untracked=allow_untracked) # Cache the name of our remote... remote_name = f'{remote_repo}.git' if is_private else 'origin' # ...make sure the passed-in version is OK... m = re_ga.match(next_ver) if not m: m = re_ea.match(next_ver) assert m # ...and figure out some branch names. release_branch = os.environ.get("RELEASE_BRANCH") or f"release/v{m[vX]}.{m[vY]}" cur_branch = "" with check(f"You're on master or {release_branch}"): cur_branch = run_capture(['git', 'rev-parse', '--abbrev-ref', 'HEAD']) if (cur_branch != "master") and (cur_branch != release_branch): raise AssertionError(f"You can't start a release from {cur_branch}") if checker.ok: with check("You're up-to-date with ambassador.git"): remote_name = f'{remote_repo}.git' if is_private else 'origin' branch_up_to_date( remote=remote_name, branch=cur_branch, update_cmd=f'git pull {remote_name} {cur_branch}', ) if is_private: with check("You're up-to-date with ambassador-private.git"): branch_up_to_date( remote='f{remote_repo}-private.git', branch=cur_branch, update_cmd=f'git pull {remote_repo}-private.git {cur_branch}', ) if checker.ok: if not quiet: print() print("Yep, looks like you're good to proceed to running `start-update-version`.") return 0 else: print() print("Looks like there's something wrong with your tree that you need to address before continuing.") return 1 def branch_exists(remote: str, branch: str) -> None: # Allow exceptions to propagate upward run(['git', 'fetch', remote, f'refs/heads/{branch}']) def branch_up_to_date(remote: str, branch: str, update_cmd: str) -> None: run(['git', 'fetch', remote, f'refs/heads/{branch}']) try: run(['git', 'merge-base', '--is-ancestor', 'FETCH_HEAD', 'HEAD']) except Exception as err: print(f"HEAD is not up-to-date with '{remote}' '{branch}':") print("You need to update it with:") print() print(f" $ {update_cmd}") print() raise subtree_up_to_date = branch_up_to_date if __name__ == '__main__': args = sys.argv[1:] quiet = False allow_untracked = parse_bool(getenv("ALLOW_UNTRACKED", "false")) if args and (args[0] == '--quiet'): quiet = True args.pop(0) if len(args) != 1 or (not re_ga.match(args[0]) and not re_ea.match(args[0])): sys.stderr.write(f"{os.path.basename(sys.argv[0])} Version must match X.Y.Z(-ea)\n") sys.exit(2) sys.exit(main(next_ver=args[0], quiet=quiet, allow_untracked=allow_untracked))