This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new df39c5f17d Add --docs-only mode and Apache identity gate to
scripts/push.py
df39c5f17d is described below
commit df39c5f17d5ab14b8b0a5955e8cb7974b52790e8
Author: James Bognar <[email protected]>
AuthorDate: Wed Aug 12 10:23:26 2026 -0400
Add --docs-only mode and Apache identity gate to scripts/push.py
--docs-only lets a juneau-docs-only change be committed/pushed without
running the full Java build/test gate: it enforces the Apache committer
identity, runs the existing docs verification gate (build-docs.py
--skip-maven, which runs verify-docs.py), and no-ops cleanly when
juneau-docs has no changes. The same identity gate is now also enforced
early in the default (full) flow on both juneau and juneau-docs, right
before each repo's respective commit/push.
---
scripts/push.py | 208 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 207 insertions(+), 1 deletion(-)
diff --git a/scripts/push.py b/scripts/push.py
index 95597c1f0f..c2ce2249b1 100755
--- a/scripts/push.py
+++ b/scripts/push.py
@@ -27,6 +27,7 @@ This script automates the build, test, and deployment
workflow:
Usage: python3 push.py "commit message"
python3 push.py "commit message" --skip-tests
python3 push.py "commit message" --sonarqube
+ python3 push.py "commit message" --docs-only
"""
# Sound file paths
@@ -269,6 +270,39 @@ def run_sonarqube_gate(juneau_root, step_num):
return "error"
+# Identity guard for --docs-only pushes to the juneau-docs ASF repo. Mirrors
the
+# same-named constant/function in juneau-docs/scripts/release-docs.py (and
+# release-docs-stage.py) so the two repos enforce this check identically.
+REQUIRED_GIT_EMAIL = "[email protected]"
+
+
+def verify_apache_identity(repo_dir):
+ """Refuse to proceed unless git is configured with the ASF committer
identity."""
+ try:
+ result = subprocess.run(
+ ["git", "config", "--get", "user.email"],
+ cwd=repo_dir,
+ capture_output=True,
+ text=True,
+ check=False,
+ )
+ email = result.stdout.strip()
+ except Exception as e:
+ print(f"ā ERROR: Could not read git user.email: {e}")
+ return False
+
+ if email != REQUIRED_GIT_EMAIL:
+ print("ā ERROR: Git identity is not the ASF committer identity.")
+ print(f" Found: user.email = '{email or '(unset)'}'")
+ print(f" Required: user.email = '{REQUIRED_GIT_EMAIL}'")
+ print("")
+ print(" Fix (this script cannot mutate git config):")
+ print(f" git config user.email {REQUIRED_GIT_EMAIL}")
+ print(' git config user.name "James Bognar"')
+ return False
+ return True
+
+
def check_git_status(repo_dir):
"""Check if there are any changes to commit."""
try:
@@ -453,6 +487,142 @@ def verify_starter_repos(step_num):
return True
+def run_docs_only(args, juneau_root): # NOSONAR python:S3776 -- Cognitive
complexity is acceptable for this function
+ """
+ --docs-only mode: operate ONLY on the sibling juneau-docs repo.
+
+ Skips the entire juneau code path (no container-tags/BOM checks, no test
run, no
+ mvn build/install, no starter-repo verification, no juneau commit/push).
Runs the
+ same Apache-identity gate and docs-verification gate (Docusaurus smoke
build via
+ build-docs.py --skip-maven, which runs verify-docs.py internally) that the
default
+ flow's Step 6 juneau-docs follow-up already runs, then commits and pushes
+ juneau-docs ā as the ONLY step, rather than a follow-up to a juneau push.
+
+ Args:
+ args: Parsed CLI arguments (message, dry_run, etc).
+ juneau_root: The juneau repo root (used only to locate the juneau-docs
sibling).
+
+ Returns:
+ Process exit code (0 success, 1 failure).
+ """
+ docs_root = juneau_root.parent / "juneau-docs"
+
+ print("=" * 70)
+ print("š Juneau Docs-Only Push Script")
+ print("=" * 70)
+ print(f"Docs directory: {docs_root}")
+ print(f"Commit message: '{args.message}'")
+ print("š DOCS-ONLY MODE (--docs-only) ā skipping juneau Java
build/test/push entirely.")
+ if args.skip_tests or args.sonarqube:
+ print("ā Note: --skip-tests/--sonarqube only apply to the juneau code
path, which "
+ "--docs-only skips entirely; ignoring them.")
+ if args.dry_run:
+ print("š DRY RUN MODE - No actual changes will be made")
+ print("=" * 70)
+
+ if not docs_root.exists():
+ print(f"\nā ERROR: juneau-docs repo not found at {docs_root}")
+ print(" Expected it as a sibling of the juneau checkout.")
+ play_sound(success=False)
+ return 1
+
+ if args.dry_run:
+ print("\nSteps that would be executed:")
+ print(f" 1. Verify Apache git identity on juneau-docs (user.email ==
{REQUIRED_GIT_EMAIL})")
+ print(" 2. Check for upstream changes on juneau-docs (git fetch +
compare to upstream)")
+ print(" 3. Check juneau-docs git status (exit 0 with a no-changes
message if clean)")
+ print(" 4. Run docs verification gate: python3 scripts/build-docs.py
--skip-maven (runs verify-docs.py)")
+ print(f" 5. Commit changes: git add . && git commit -m
\"{args.message}\" (in juneau-docs)")
+ print(" 6. Push to remote: git push (in juneau-docs)")
+ print("\nDry run complete. Use without --dry-run to execute.")
+ return 0
+
+ # Step 1: Apache identity gate ā must hold before any work begins.
+ print("\nš Step 1: Verifying git identity (apache.org email) on
juneau-docs...")
+ if not verify_apache_identity(docs_root):
+ play_sound(success=False)
+ return 1
+ print("ā
Step 1: Git identity verified")
+
+ # Step 2: Check if local juneau-docs branch is behind upstream.
+ print("\nš Checking for upstream changes on juneau-docs...")
+ is_behind, error_msg = check_upstream_changes(docs_root)
+ if error_msg:
+ print(f"\nā Warning: Could not check upstream changes: {error_msg}")
+ print("Continuing anyway...")
+ elif is_behind:
+ print("\nā ERROR: juneau-docs local branch is behind upstream/remote
branch.")
+ print("Please pull/merge upstream changes before pushing.")
+ print("Run: git -C ../juneau-docs pull")
+ play_sound(success=False)
+ return 1
+
+ # Step 3: No-op check ā mirror the default flow's no-changes messaging
style.
+ if not check_git_status(docs_root):
+ print("\nā Warning: No docs changes detected. Skipping commit and
push.")
+ print("š Docs-only push completed successfully (nothing to commit)!")
+ play_sound(success=True)
+ return 0
+
+ # Step 4: Docs verification gate (Docusaurus smoke build; runs
verify-docs.py internally).
+ print("\nš Step 4: juneau-docs has changes ā running Docusaurus smoke
check first...")
+ docs_build_script = docs_root / "scripts" / "build-docs.py"
+ try:
+ result = subprocess.run(
+ [sys.executable, str(docs_build_script), "--skip-maven"],
+ cwd=docs_root,
+ check=False
+ )
+ if result.returncode != 0:
+ print("\nā Docs smoke check failed ā fix the Docusaurus build
before pushing juneau-docs.")
+ play_sound(success=False)
+ return 1
+ print("ā
Step 4: Docs smoke check passed")
+ except Exception as e:
+ print(f"\nā Docs smoke check failed: {e}")
+ play_sound(success=False)
+ return 1
+
+ # Step 5: Git add and commit
+ print("\nš Step 5: Committing changes to Git (juneau-docs)...")
+ if not run_command(
+ ["git", "add", "."],
+ " 5.1: Staging juneau-docs changes...",
+ docs_root
+ ):
+ print("\nā Docs-only push aborted due to juneau-docs git add failure.")
+ play_sound(success=False)
+ return 1
+
+ if not run_command(
+ ["git", "commit", "-m", args.message],
+ " 5.2: Creating commit...",
+ docs_root
+ ):
+ print("\nā Docs-only push aborted due to juneau-docs git commit
failure.")
+ play_sound(success=False)
+ return 1
+ print("ā
Step 5: Git commit completed.")
+
+ # Step 6: Push to remote
+ if not run_command(
+ ["git", "push"],
+ "š Step 6: Pushing juneau-docs changes to remote repository...",
+ docs_root
+ ):
+ print("\nā Docs-only push aborted due to git push failure.")
+ print("ā Your juneau-docs changes have been committed locally but not
pushed.")
+ play_sound(success=False)
+ return 1
+
+ print("\n" + "=" * 70)
+ print("š Docs-only push completed successfully!")
+ print(f"š¦ Commit message: '{args.message}'")
+ print("=" * 70)
+ play_sound(success=True)
+ return 0
+
+
def main(): # NOSONAR python:S3776 -- Cognitive complexity is acceptable for
this main function
parser = argparse.ArgumentParser(
description="Build, test, and push Juneau project to Git repository",
@@ -463,6 +633,7 @@ Examples:
python3 push.py "Updated documentation" --skip-tests
python3 push.py "Quick fix" --skip-tests
python3 push.py "Fixed bug in RestClient" --sonarqube
+ python3 push.py "Updated topic page" --docs-only
"""
)
@@ -483,6 +654,20 @@ Examples:
help="Show what would be done without actually doing it"
)
+ parser.add_argument(
+ "--docs-only",
+ action="store_true",
+ help=(
+ "Operate ONLY on the sibling juneau-docs repo: skip the entire
juneau code "
+ "path (no container-tags/BOM checks, no tests, no mvn
build/install, no "
+ "starter-repo verification, no juneau commit/push). Still enforces
the "
+ "Apache identity gate and the docs verification gate (Docusaurus
smoke "
+ "build via build-docs.py --skip-maven, which runs verify-docs.py)
against "
+ "juneau-docs before committing/pushing it. Exits 0 with a no-op
message if "
+ "juneau-docs has no changes."
+ )
+ )
+
parser.add_argument(
"--sonarqube", "--sonar",
action="store_true",
@@ -499,7 +684,12 @@ Examples:
# Get the Juneau root directory
script_dir = Path(__file__).parent
juneau_root = script_dir.parent
-
+
+ # --docs-only short-circuits into its own self-contained flow (juneau-docs
only);
+ # everything below this is the unchanged default (full juneau build+push)
flow.
+ if args.docs_only:
+ return run_docs_only(args, juneau_root)
+
print("=" * 70)
print("š Juneau Build and Push Script")
print("=" * 70)
@@ -544,6 +734,16 @@ Examples:
step_num = 1
+ # Identity gate ā must hold before the expensive build/test gate and any
commit/push.
+ # Extended (maintainer-approved) from the --docs-only path to the default
flow too;
+ # juneau-docs gets its own check further down, right before its Step 6
commit/push,
+ # since the two repos can have independent git config user.email.
+ print("\nš Verifying git identity (apache.org email) on juneau...")
+ if not verify_apache_identity(juneau_root):
+ play_sound(success=False)
+ return 1
+ print("ā
Git identity verified")
+
# Step 0 (opt-in, --sonarqube/--sonar): SonarQube report gate. Runs first
so it
# aborts cheaply, before the container-tags/BOM checks, tests, and build.
if args.sonarqube:
@@ -728,6 +928,12 @@ Examples:
# Step 6 (optional): juneau-docs follow-up ā smoke check + commit + push
docs_root = juneau_root.parent / "juneau-docs"
if docs_root.exists() and check_git_status(docs_root):
+ print("\nš Verifying git identity (apache.org email) on
juneau-docs...")
+ if not verify_apache_identity(docs_root):
+ play_sound(success=False)
+ return 1
+ print("ā
Git identity verified")
+
print(f"\nš Step {step_num}: juneau-docs has changes ā running
Docusaurus smoke check first...")
docs_build_script = docs_root / "scripts" / "build-docs.py"