| #!/usr/bin/env python3 |
| # *************************************************************************************************************************** |
| # * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * |
| # * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * |
| # * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * |
| # * with the License. You may obtain a copy of the License at * |
| # * * |
| # * http://www.apache.org/licenses/LICENSE-2.0 * |
| # * * |
| # * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * |
| # * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * |
| # * specific language governing permissions and limitations under the License. * |
| # *************************************************************************************************************************** |
| """ |
| Promote Documentation from Staging to Production |
| |
| This script promotes the documentation from the asf-staging branch to the asf-site branch. |
| This makes the documentation live on the production website. |
| |
| The script: |
| 1. Fetches the asf-staging branch |
| 2. Switches to a detached HEAD at origin/asf-staging |
| 3. Force pushes to the asf-site branch |
| |
| Usage: |
| python3 scripts/release-docs.py [--no-push] [--commit-message MESSAGE] |
| |
| Options: |
| --no-push Perform all steps except the final git push |
| --commit-message Not used (kept for consistency with release-docs-stage.py) |
| """ |
| |
| import argparse |
| import os |
| import platform |
| import subprocess |
| import sys |
| from pathlib import Path |
| |
| |
| def run_command(cmd, cwd=None, check=True, description=None): |
| """Run a shell command and handle errors.""" |
| if description: |
| print(f"\n{description}") |
| print(f"Running: {' '.join(cmd) if isinstance(cmd, list) else cmd}") |
| |
| try: |
| result = subprocess.run( |
| cmd, |
| cwd=cwd, |
| shell=isinstance(cmd, str), |
| check=check, |
| capture_output=False, |
| text=True |
| ) |
| if description: |
| print(f"✅ {description} - SUCCESS") |
| return True |
| except subprocess.CalledProcessError as e: |
| if description: |
| print(f"❌ {description} - FAILED (exit code: {e.returncode})") |
| return False |
| except Exception as e: |
| if description: |
| print(f"❌ {description} - FAILED: {e}") |
| return False |
| |
| |
| def get_git_remote_url(): |
| """Get the git remote URL.""" |
| try: |
| result = subprocess.run( |
| ["git", "config", "--get", "remote.origin.url"], |
| capture_output=True, |
| text=True, |
| check=True |
| ) |
| return result.stdout.strip() |
| except Exception: |
| return None |
| |
| |
| def play_sound(success=True): |
| """ |
| Play a system sound to indicate success or failure. |
| |
| Args: |
| success: True for success sound, False for failure sound |
| """ |
| try: |
| system = platform.system() |
| if system == "Darwin": # macOS |
| if success: |
| # Success sound |
| sound_path = "/System/Library/Sounds/Glass.aiff" |
| else: |
| # Failure sound |
| sound_path = "/System/Library/Sounds/Basso.aiff" |
| |
| if os.path.exists(sound_path): |
| subprocess.run( |
| ["afplay", sound_path], |
| capture_output=True, |
| timeout=5 |
| ) |
| elif system == "Linux": |
| # Try to use paplay (PulseAudio) or aplay (ALSA) |
| if success: |
| # Try to play a beep or use speaker-test |
| try: |
| subprocess.run( |
| ["paplay", "/usr/share/sounds/freedesktop/stereo/complete.oga"], |
| capture_output=True, |
| timeout=5 |
| ) |
| except: |
| # Fallback to speaker-test |
| subprocess.run( |
| ["speaker-test", "-t", "sine", "-f", "1000", "-l", "1"], |
| capture_output=True, |
| timeout=2 |
| ) |
| else: |
| try: |
| subprocess.run( |
| ["paplay", "/usr/share/sounds/freedesktop/stereo/dialog-error.oga"], |
| capture_output=True, |
| timeout=5 |
| ) |
| except: |
| # Fallback to speaker-test with lower frequency |
| subprocess.run( |
| ["speaker-test", "-t", "sine", "-f", "400", "-l", "1"], |
| capture_output=True, |
| timeout=2 |
| ) |
| elif system == "Windows": |
| # Use winsound module |
| import winsound |
| if success: |
| winsound.MessageBeep(winsound.MB_OK) |
| else: |
| winsound.MessageBeep(winsound.MB_ICONHAND) |
| except Exception: |
| # Silently fail if sound can't be played |
| pass |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser( |
| description="Promote documentation from asf-staging to asf-site branch", |
| formatter_class=argparse.RawDescriptionHelpFormatter, |
| epilog=__doc__ |
| ) |
| parser.add_argument( |
| '--no-push', |
| action='store_true', |
| help='Perform all steps except the final git push' |
| ) |
| parser.add_argument( |
| '--commit-message', |
| type=str, |
| help='Not used (kept for consistency with release-docs-stage.py)' |
| ) |
| |
| args = parser.parse_args() |
| |
| script_dir = Path(__file__).parent |
| docs_dir = script_dir.parent # docs/ |
| |
| print("=" * 79) |
| print("Promote Documentation to Production (asf-site branch)") |
| print("=" * 79) |
| print() |
| print("⚠️ WARNING: This will promote documentation from asf-staging to asf-site,") |
| print(" making it live on the production website.") |
| print() |
| |
| # Get git remote URL |
| remote_url = get_git_remote_url() |
| if not remote_url: |
| print("❌ ERROR: Could not determine git remote URL") |
| sys.exit(1) |
| |
| # Use sibling directory structure: /git/apache/juneau/asf-site |
| parent_dir = docs_dir.parent # /git/apache/juneau/ |
| site_dir = parent_dir / 'asf-site' |
| print(f"Site directory: {site_dir}") |
| |
| # Verify parent directory structure |
| if not parent_dir.exists(): |
| print(f"❌ ERROR: Parent directory not found: {parent_dir}") |
| print("Expected structure:") |
| print(f" {parent_dir}/docs/ (current)") |
| print(f" {parent_dir}/asf-site/ (will be created)") |
| sys.exit(1) |
| |
| try: |
| # Step 1: Setup sibling directory with asf-site branch |
| print("\nStep 1: Setting up sibling directory with asf-site branch...") |
| |
| # Check if directory already exists |
| if site_dir.exists() and (site_dir / '.git').exists(): |
| # Directory exists and is a git repo, update it |
| print("📁 Site directory already exists, updating...") |
| if not run_command( |
| ["git", "fetch", "origin"], |
| cwd=site_dir, |
| description="Fetching latest changes" |
| ): |
| print("\n❌ Failed to fetch latest changes") |
| sys.exit(1) |
| else: |
| # Directory doesn't exist or isn't a git repo, clone it |
| if site_dir.exists(): |
| print(f"⚠️ Directory exists but is not a git repository. Removing: {site_dir}") |
| import shutil |
| shutil.rmtree(site_dir) |
| |
| if not run_command( |
| ["git", "clone", remote_url, str(site_dir)], |
| description="Cloning repository to site directory" |
| ): |
| print("\n❌ Failed to clone repository") |
| sys.exit(1) |
| |
| # Step 2: Fetch asf-staging branch |
| if not run_command( |
| ["git", "fetch", "origin", "asf-staging"], |
| cwd=site_dir, |
| description="Fetching asf-staging branch" |
| ): |
| print("\n❌ Failed to fetch asf-staging branch") |
| sys.exit(1) |
| |
| # Step 3: Switch to detached HEAD at origin/asf-staging |
| if not run_command( |
| ["git", "switch", "--detach", "origin/asf-staging"], |
| cwd=site_dir, |
| description="Switching to detached HEAD at origin/asf-staging" |
| ): |
| print("\n❌ Failed to switch to asf-staging branch") |
| sys.exit(1) |
| |
| # Step 4: Set git user (use current user's git config or defaults) |
| try: |
| result = subprocess.run( |
| ["git", "config", "--get", "user.name"], |
| capture_output=True, |
| text=True |
| ) |
| git_user = result.stdout.strip() if result.returncode == 0 else "Documentation Promoter" |
| |
| result = subprocess.run( |
| ["git", "config", "--get", "user.email"], |
| capture_output=True, |
| text=True |
| ) |
| git_email = result.stdout.strip() if result.returncode == 0 else "docs@juneau.apache.org" |
| except Exception: |
| git_user = "Documentation Promoter" |
| git_email = "docs@juneau.apache.org" |
| |
| run_command( |
| ["git", "config", "user.name", git_user], |
| cwd=site_dir, |
| description="Setting git user name" |
| ) |
| run_command( |
| ["git", "config", "user.email", git_email], |
| cwd=site_dir, |
| description="Setting git user email" |
| ) |
| |
| # Step 5: Push to asf-site branch (if not --no-push) |
| if not args.no_push: |
| print("\nStep 5: Pushing to remote asf-site branch...") |
| if not run_command( |
| ["git", "push", "origin", "HEAD:asf-site", "--force"], |
| cwd=site_dir, |
| description="Pushing to asf-site branch" |
| ): |
| print("\n❌ Failed to push to asf-site branch") |
| sys.exit(1) |
| else: |
| print("\n⏭️ Skipping push (--no-push flag set)") |
| print(f" Changes are ready in: {site_dir}") |
| print(" You can manually push with:") |
| print(f" cd {site_dir}") |
| print(" git push origin HEAD:asf-site --force") |
| |
| print("\n" + "=" * 79) |
| print("✅ Documentation promotion to production complete!") |
| print("=" * 79) |
| print(f"\nSite directory: {site_dir}") |
| print("(This directory is persistent and will be reused for future deployments)") |
| |
| # Play success sound |
| play_sound(success=True) |
| |
| except KeyboardInterrupt: |
| print("\n\n⚠️ Process interrupted by user") |
| sys.exit(1) |
| except Exception as e: |
| print(f"\n❌ ERROR: {e}") |
| import traceback |
| traceback.print_exc() |
| sys.exit(1) |
| |
| |
| if __name__ == '__main__': |
| main() |
| |