api: Emit an error if ./compile_build_api_proto is run on stale protos.
Currently if chromite/infra/proto is behind the remote, it's easy to
generate and commit generated bindings in chromite that delete protos or
fields that were added in prior CLs.
This attempts to mitigate that possibility by checking whether the git
checkout that ./compile_build_api_proto consumes has all the changes
from the remote, before generating bindings.
BUG=b:302068615
TEST=Tested various git workflows
Change-Id: I793a7a8d2858342c144625645c6349343299ae76
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/4891533
Reviewed-by: Mike Frysinger <vapier@chromium.org>
Tested-by: Trent Apted <tapted@chromium.org>
Commit-Queue: Trent Apted <tapted@chromium.org>
diff --git a/api/compile_build_api_proto.py b/api/compile_build_api_proto.py
index 7e684a6..f46e864 100644
--- a/api/compile_build_api_proto.py
+++ b/api/compile_build_api_proto.py
@@ -106,6 +106,47 @@
return subdirs
+def check_upstream_changes(repo: Path) -> None:
+ """Ensures the checked-out branch at `repo` includes the upstream HEAD."""
+ branch = git.GetCurrentBranch(repo)
+ if branch:
+ upstream = git.GetTrackingBranchViaGitConfig(
+ repo, branch, for_checkout=False
+ )
+ if not upstream:
+ cros_build_lib.Die("Failed to get upstream for %s.", repo)
+ else:
+ # Detached head.
+ branch = "HEAD"
+ upstream = git.RemoteRef("cros", "refs/heads/main")
+ remote_head = git.RunGit(
+ repo,
+ ["ls-remote", upstream.remote, upstream.ref],
+ ).stdout.split(maxsplit=1)[0]
+ logging.notice(
+ "Ensuring proto dir contains %s from %s",
+ upstream.ref,
+ upstream.remote,
+ )
+ branches = git.RunGit(
+ repo,
+ ["branch", "--contains", remote_head, branch],
+ print_cmd=True,
+ check=False,
+ )
+ # Git emits the branch name if the commit is in the history. Otherwise, the
+ # commit is either not found (git exits with an error - 129), or missing
+ # from the branch. In either case, there is no output to stdout.
+ if not branches.stdout:
+ cros_build_lib.Die(
+ "The checked-out branch (%s) at %s is missing the remote head.\n"
+ "Please repo sync (and rebase), or skip this check by passing"
+ " --no-check-upstream-proto-changes-included.",
+ branch,
+ repo,
+ )
+
+
def InstallProtoc(protoc_version: ProtocVersion) -> Path:
"""Install protoc from CIPD."""
if protoc_version is ProtocVersion.SDK:
@@ -311,6 +352,14 @@
def GetParser():
"""Build the argument parser."""
parser = commandline.ArgumentParser(description=__doc__)
+ parser.add_bool_argument(
+ "--check-upstream-proto-changes-included",
+ True,
+ "Check whether the checked-out proto branch includes changes"
+ " from refs/heads/main on the remote.",
+ "Skip the check that validates whether upstream proto changes"
+ " are included in the current branch.",
+ )
standard_group = parser.add_argument_group(
"Committed Bindings",
description="Options for generating the bindings in chromite/api/.",
@@ -418,6 +467,10 @@
return 0
if ProtocVersion.CHROMITE in opts.protoc_version:
+ # Validate here to avoid checking again inside the chroot.
+ if opts.check_upstream_proto_changes_included:
+ check_upstream_changes(ProtocVersion.CHROMITE.get_proto_dir())
+
# Compile the chromite bindings.
try:
CompileProto(protoc_version=ProtocVersion.CHROMITE)