1#!/usr/bin/env python3
2"""Do a sanity check that you're at something that's ready for you to
3cut an RC from; that you're in the right repo, that you're on the
4right branch, that all of the subtrees are up-to-date...
5"""
6
7from typing import Generator
8
9import sys
10import time
11
12import os.path
13
14from os import getenv
15from contextlib import contextmanager
16
17from lib import assert_eq, git_check_clean, parse_bool, vX, vY, re_ga, re_ea
18from lib.uiutil import Checker, CheckResult, run
19from lib.uiutil import run_txtcapture as run_capture
20
21
22DEFAULT_REPO = 'git@github.com:emissary-ingress/emissary'
23
24
25def main(next_ver: str, quiet: bool = False, allow_untracked: bool = False) -> int:
26 print(f'Starting work on "v{next_ver}"...')
27 print()
28 remote_repo = getenv('AMBASSADOR_RELEASE_REPO_OVERRIDE')
29 if remote_repo is None or remote_repo == '':
30 remote_repo = DEFAULT_REPO
31
32 checker = Checker()
33
34 @contextmanager
35 def check(name: str) -> Generator[CheckResult, None, None]:
36 with checker.check(name) as subcheck:
37 # time.sleep(1) # it's stupid, but honestly the delay makes the output more readable
38 yield subcheck
39
40 is_private = False
41
42 with check(f"You're in a clone of {remote_repo}"):
43 url = run_capture(['git', 'remote', 'get-url', '--push', 'origin'])
44 if url.endswith("/"):
45 url = url[:-len("/")]
46 if url.endswith(".git"):
47 url = url[:-len(".git")]
48 if url.endswith("-private"):
49 is_private = True
50 url = url[:-len("-private")]
51 assert_eq(url, remote_repo)
52
53 with check("You're in the toplevel of the clone"):
54 toplevel = run_capture(['git', 'rev-parse', '--show-toplevel'])
55 if not os.path.samefile(toplevel, '.'):
56 raise Exception(f"Not in {toplevel}")
57
58 with check("You're in a clean checkout"):
59 git_check_clean(allow_untracked=allow_untracked)
60
61 # Cache the name of our remote...
62 remote_name = f'{remote_repo}.git' if is_private else 'origin'
63
64 # ...make sure the passed-in version is OK...
65 m = re_ga.match(next_ver)
66 if not m:
67 m = re_ea.match(next_ver)
68 assert m
69
70 # ...and figure out some branch names.
71 release_branch = os.environ.get("RELEASE_BRANCH") or f"release/v{m[vX]}.{m[vY]}"
72 cur_branch = ""
73
74 with check(f"You're on master or {release_branch}"):
75 cur_branch = run_capture(['git', 'rev-parse', '--abbrev-ref', 'HEAD'])
76
77 if (cur_branch != "master") and (cur_branch != release_branch):
78 raise AssertionError(f"You can't start a release from {cur_branch}")
79
80 if checker.ok:
81 with check("You're up-to-date with ambassador.git"):
82 remote_name = f'{remote_repo}.git' if is_private else 'origin'
83
84 branch_up_to_date(
85 remote=remote_name,
86 branch=cur_branch,
87 update_cmd=f'git pull {remote_name} {cur_branch}',
88 )
89
90 if is_private:
91 with check("You're up-to-date with ambassador-private.git"):
92 branch_up_to_date(
93 remote='f{remote_repo}-private.git',
94 branch=cur_branch,
95 update_cmd=f'git pull {remote_repo}-private.git {cur_branch}',
96 )
97
98 if checker.ok:
99 if not quiet:
100 print()
101 print("Yep, looks like you're good to proceed to running `start-update-version`.")
102 return 0
103 else:
104 print()
105 print("Looks like there's something wrong with your tree that you need to address before continuing.")
106 return 1
107
108
109def branch_exists(remote: str, branch: str) -> None:
110 # Allow exceptions to propagate upward
111 run(['git', 'fetch', remote, f'refs/heads/{branch}'])
112
113
114def branch_up_to_date(remote: str, branch: str, update_cmd: str) -> None:
115 run(['git', 'fetch', remote, f'refs/heads/{branch}'])
116 try:
117 run(['git', 'merge-base', '--is-ancestor', 'FETCH_HEAD', 'HEAD'])
118 except Exception as err:
119 print(f"HEAD is not up-to-date with '{remote}' '{branch}':")
120 print("You need to update it with:")
121 print()
122 print(f" $ {update_cmd}")
123 print()
124 raise
125
126
127subtree_up_to_date = branch_up_to_date
128
129if __name__ == '__main__':
130 args = sys.argv[1:]
131 quiet = False
132
133 allow_untracked = parse_bool(getenv("ALLOW_UNTRACKED", "false"))
134
135 if args and (args[0] == '--quiet'):
136 quiet = True
137 args.pop(0)
138
139 if len(args) != 1 or (not re_ga.match(args[0]) and not re_ea.match(args[0])):
140 sys.stderr.write(f"{os.path.basename(sys.argv[0])} Version must match X.Y.Z(-ea)\n")
141 sys.exit(2)
142
143 sys.exit(main(next_ver=args[0], quiet=quiet, allow_untracked=allow_untracked))
View as plain text