From 852bc3806a61384b5258dd0727e784e89047e862 Mon Sep 17 00:00:00 2001 From: Raminder Singh Date: Wed, 11 Feb 2026 12:01:37 +0530 Subject: [PATCH] chore: add release scripts --- README.md | 75 +++++++++-- scripts/check-version.sh | 48 +++++++ scripts/push-tag.sh | 165 +++++++++++++++++++++++ scripts/release.sh | 155 +++++++++++++++++++++ scripts/update-version.sh | 275 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 705 insertions(+), 13 deletions(-) create mode 100755 scripts/check-version.sh create mode 100755 scripts/push-tag.sh create mode 100755 scripts/release.sh create mode 100755 scripts/update-version.sh diff --git a/README.md b/README.md index 1e61495..203c2b1 100644 --- a/README.md +++ b/README.md @@ -16,39 +16,49 @@ `pg_jsonschema` is a PostgreSQL extension adding support for [JSON schema](https://json-schema.org/) validation on `json` and `jsonb` data types. - ## API + This extension exposes the following four SQL functions: + - json_matches_schema - jsonb_matches_schema (note the **jsonb** in front) - jsonschema_is_valid - jsonschema_validation_errors With the following signatures + ```sql -- Validates a json *instance* against a *schema* json_matches_schema(schema json, instance json) returns bool ``` + and + ```sql -- Validates a jsonb *instance* against a *schema* jsonb_matches_schema(schema json, instance jsonb) returns bool ``` + and + ```sql -- Validates whether a json *schema* is valid jsonschema_is_valid(schema json) returns bool ``` + and + ```sql -- Returns an array of errors if a *schema* is invalid jsonschema_validation_errors(schema json, instance json) returns text[] ``` ## Usage + Those functions can be used to constrain `json` and `jsonb` columns to conform to a schema. For example: + ```sql create extension pg_jsonschema; @@ -102,19 +112,18 @@ pg_jsonschema is a (very) thin wrapper around the [jsonschema](https://docs.rs/j Spin up Postgres with pg_jsonschema installed in a docker container via `docker-compose up`. The database is available at `postgresql://postgres:password@localhost:5407/app` - ## Installation - Requires: -- [pgrx](https://github.com/tcdi/pgrx) +- [pgrx](https://github.com/tcdi/pgrx) ```shell cargo pgrx run ``` which drops into a psql prompt. + ```psql psql (13.6) Type "help" for help. @@ -123,7 +132,7 @@ pg_jsonschema=# create extension pg_jsonschema; CREATE EXTENSION pg_jsonschema=# select json_matches_schema('{"type": "object"}', '{}'); - json_matches_schema + json_matches_schema --------------------- t (1 row) @@ -131,6 +140,45 @@ pg_jsonschema=# select json_matches_schema('{"type": "object"}', '{}'); for more complete installation guidelines see the [pgrx](https://github.com/tcdi/pgrx) docs. +## Releasing + +Releases are automated via a single command: + +```shell +./scripts/release.sh +``` + +For example: + +```shell +./scripts/release.sh 0.4.0 +``` + +This orchestrates the full release process end-to-end: + +1. **Verifies** that the tag and GitHub release don't already exist +2. **Updates versions** in `Cargo.toml`, `META.json`, and `Cargo.lock`; creates a `release/` branch; commits, pushes, and waits for the PR to be merged into `master` +3. **Verifies** all file versions match before tagging +4. **Creates and pushes** the `v` tag, which triggers the CI workflows (`release.yml` for GitHub release + `.deb` artifacts, `pgxn-release.yml` for PGXN) +5. **Polls** GitHub until the release is published and prints the release URL + +> **Note:** `pg_jsonschema.control` uses `@CARGO_VERSION@` which is substituted by pgrx at build time from `Cargo.toml`, so it doesn't need manual updates. + +### Idempotency + +The script is safe to re-run if interrupted — it detects what has already been completed (branch exists, tag exists, release exists) and picks up where it left off. + +### Individual Scripts + +The release process is composed of smaller scripts that can also be run independently: + +| Script | Purpose | +| -------------------------------------------------- | ----------------------------------------------------------------------- | +| `scripts/check-version.sh ` | Checks if `Cargo.toml` and `META.json` match the given version | +| `scripts/update-version.sh ` | Updates version files, creates a release branch, and waits for PR merge | +| `scripts/update-version.sh --files-only ` | Updates version files without any git operations | +| `scripts/push-tag.sh ` | Creates and pushes the git tag, then monitors for the GitHub release | +| `scripts/push-tag.sh --dry-run ` | Validates versions without creating a tag | ## Prior Art @@ -140,25 +188,25 @@ for more complete installation guidelines see the [pgrx](https://github.com/tcdi [pgx_json_schema](https://github.com/jefbarn/pgx_json_schema) - JSON Schema Postgres extension written with pgrx + jsonschema - ## Benchmark - #### System + - 2021 MacBook Pro M1 Max (32GB) - macOS 14.2 - PostgreSQL 16.2 ### Setup + Validating the following schema on 20k unique inserts ```json { - "type": "object", - "properties": { - "a": {"type": "number"}, - "b": {"type": "string"} - } + "type": "object", + "properties": { + "a": { "type": "number" }, + "b": { "type": "string" } + } } ``` @@ -181,6 +229,7 @@ select ) from generate_series(1, 20000) t(i); --- Query Completed in 351 ms +-- Query Completed in 351 ms ``` + for comparison, the equivalent test using postgres-json-schema's `validate_json_schema` function ran in 5.54 seconds. pg_jsonschema's ~15x speedup on this example JSON schema grows quickly as the schema becomes more complex. diff --git a/scripts/check-version.sh b/scripts/check-version.sh new file mode 100755 index 0000000..4b83539 --- /dev/null +++ b/scripts/check-version.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# This script checks if the provided version matches the versions in Cargo.toml and META.json +# Usage: source ./scripts/check-version.sh [--warn-only] +# After sourcing, the following variables will be available: +# - CARGO_VERSION: Version from Cargo.toml +# - META_VERSION: Version from META.json +# - HAS_MISMATCH: true if versions don't match, false otherwise + +VERSION_TO_CHECK=$1 +WARN_ONLY=false + +# Check for --warn-only flag +if [ "$2" = "--warn-only" ]; then + WARN_ONLY=true +fi + +if [ -z "$VERSION_TO_CHECK" ]; then + echo "Error: Version argument required for check-version.sh" + exit 1 +fi + +# Extract versions from Cargo.toml and META.json +CARGO_VERSION=$(grep -E '^version = ' Cargo.toml | head -n 1 | sed -E 's/version = "(.*)"/\1/') +META_VERSION=$(jq -r '.version' META.json) + +# Check for version mismatches +HAS_MISMATCH=false + +if [ "$VERSION_TO_CHECK" != "$CARGO_VERSION" ]; then + echo "" + if [ "$WARN_ONLY" = true ]; then + echo "⚠️ Warning: Cargo.toml has version $CARGO_VERSION" + else + echo "❌ Error: Cargo.toml has version $CARGO_VERSION" + fi + HAS_MISMATCH=true +fi + +if [ "$VERSION_TO_CHECK" != "$META_VERSION" ]; then + echo "" + if [ "$WARN_ONLY" = true ]; then + echo "⚠️ Warning: META.json has version $META_VERSION" + else + echo "❌ Error: META.json has version $META_VERSION" + fi + HAS_MISMATCH=true +fi diff --git a/scripts/push-tag.sh b/scripts/push-tag.sh new file mode 100755 index 0000000..ecc5fe6 --- /dev/null +++ b/scripts/push-tag.sh @@ -0,0 +1,165 @@ +#!/bin/bash +set -e + +# Parse arguments +DRY_RUN=false +VERSION="" + +while [[ $# -gt 0 ]]; do + case $1 in + --dry-run) + DRY_RUN=true + shift + ;; + *) + VERSION=$1 + shift + ;; + esac +done + +# Check if version argument is provided +if [ -z "$VERSION" ]; then + echo "Error: Version argument required" + echo "Usage: ./scripts/push-tag.sh [--dry-run] " + exit 1 +fi + +# Validate version format +if ! [[ $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: Invalid version format. Expected: major.minor.patch (e.g., 1.2.3)" + exit 1 +fi + +# Check version mismatches using shared script +source "$(dirname "$0")/check-version.sh" "$VERSION" "--warn-only" + +if [ "$HAS_MISMATCH" = true ]; then + echo "" + echo "⚠️ Warning: Version mismatch detected, but continuing..." + echo " Ensure versions are updated before creating a release." +fi + +# Exit early if dry run +if [ "$DRY_RUN" = true ]; then + echo "" + echo "✅ Dry run successful - all version checks passed" + echo " Version: $VERSION" + echo " Tag that would be created: v$VERSION" + exit 0 +fi + +# Create and push tag +TAG="v$VERSION" +echo "" +echo "Creating tag: $TAG" + +# Fetch all tags from remote +echo "Fetching latest tags from remote..." +git fetch --tags + +# Check if tag already exists +TAG_EXISTS=false +if git rev-parse "$TAG" >/dev/null 2>&1; then + TAG_EXISTS=true + TAG_COMMIT=$(git rev-parse "$TAG") + CURRENT_COMMIT=$(git rev-parse HEAD) + + echo "⚠️ Warning: Tag $TAG already exists" + + if [ "$TAG_COMMIT" = "$CURRENT_COMMIT" ]; then + echo "✅ Tag points to current commit, continuing..." + else + echo "⚠️ Warning: Tag points to a different commit" + echo " Tag commit: $TAG_COMMIT" + echo " Current commit: $CURRENT_COMMIT" + echo " Continuing anyway..." + fi +else + # Create the tag + git tag "$TAG" + echo "✅ Tag $TAG created" +fi + +# Push the tag to remote +echo "Pushing tag to remote..." +if git push origin "$TAG" 2>&1 | tee /tmp/git_push_tag_output.txt; then + echo "✅ Tag $TAG pushed successfully" +else + # Check if error is because tag already exists on remote + if grep -q "already exists" /tmp/git_push_tag_output.txt; then + echo "⚠️ Warning: Tag already exists on remote, continuing..." + else + echo "⚠️ Warning: Failed to push tag, but continuing..." + fi +fi +rm -f /tmp/git_push_tag_output.txt + +echo "" + +# Poll for GitHub release +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "⏳ Monitoring GitHub release creation..." +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" + +# Extract repository info from git remote +GIT_REMOTE=$(git remote get-url origin) +if [[ $GIT_REMOTE =~ github\.com[:/]([^/]+)/([^/.]+)(\.git)?$ ]]; then + REPO_OWNER="${BASH_REMATCH[1]}" + REPO_NAME="${BASH_REMATCH[2]}" + + echo "Waiting for release.yml workflow to create the release..." + echo "(This may take a few minutes)" + echo "" + + # Poll for release with timeout (30s interval keeps us well under the 60 req/hr unauthenticated API limit) + POLL_INTERVAL=30 + MAX_ATTEMPTS=40 # 20 minutes (40 * 30 seconds) + ATTEMPT=0 + RELEASE_FOUND=false + RELEASE_URL="" + + while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do + HTTP_STATUS=$(curl -s -o /tmp/release_check.json -w "%{http_code}" "https://api.github.com/repos/$REPO_OWNER/$REPO_NAME/releases/tags/$TAG") + + if [ "$HTTP_STATUS" = "200" ]; then + RELEASE_FOUND=true + RELEASE_URL=$(grep -o '"html_url":"[^"]*"' /tmp/release_check.json | head -1 | cut -d'"' -f4) + rm -f /tmp/release_check.json + break + fi + + ATTEMPT=$((ATTEMPT + 1)) + ELAPSED=$((ATTEMPT * POLL_INTERVAL)) + printf "\r⏳ Waiting... %dm%02ds elapsed" $((ELAPSED / 60)) $((ELAPSED % 60)) + sleep $POLL_INTERVAL + done + + rm -f /tmp/release_check.json + + echo "" + echo "" + + if [ "$RELEASE_FOUND" = true ]; then + echo "✅ GitHub release is now available!" + echo "" + if [ -n "$RELEASE_URL" ]; then + echo "🔗 Release URL: $RELEASE_URL" + else + echo "🔗 Release URL: https://github.com/$REPO_OWNER/$REPO_NAME/releases/tag/$TAG" + fi + else + echo "⚠️ Timeout waiting for GitHub release" + echo " The release workflow may still be running." + echo " Check the Actions tab on GitHub for status." + echo " https://github.com/$REPO_OWNER/$REPO_NAME/actions" + fi +else + echo "⚠️ Could not parse GitHub repository from git remote" + echo " The release workflow has been triggered by pushing the tag." +fi + +echo "" +echo "✅ Successfully released version $VERSION" +echo " Tag: $TAG" diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..82a90e4 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,155 @@ +#!/bin/bash +# Exit on errors — idempotency is handled via skip flags +set -e + +# Color codes for pretty output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Parse arguments +VERSION="" + +if [ $# -eq 0 ]; then + echo -e "${RED}Error: Version argument required${NC}" + echo "Usage: ./scripts/release.sh " + exit 1 +fi + +VERSION=$1 + +# Validate version format +if ! [[ $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo -e "${RED}Error: Invalid version format. Expected: major.minor.patch (e.g., 1.2.3)${NC}" + exit 1 +fi + +echo -e "${BLUE}========================================${NC}" +echo -e "${BLUE}Release Process for version $VERSION${NC}" +echo -e "${BLUE}========================================${NC}" +echo "" + +# Step 1: Verify that release and tag don't already exist +echo -e "${YELLOW}[Step 1/3] Verifying release and tag status...${NC}" +echo "" + +# Fetch latest tags from remote +echo "Fetching latest tags from remote..." +git fetch --tags + +# Check if tag already exists +TAG="v$VERSION" +TAG_EXISTS=false +RELEASE_EXISTS=false +SKIP_VERSION_UPDATE=false +SKIP_TAG_PUSH=false + +if git rev-parse "$TAG" >/dev/null 2>&1; then + TAG_EXISTS=true + echo -e "${YELLOW}⚠️ Warning: Tag $TAG already exists${NC}" + + # Check commit the tag points to + TAG_COMMIT=$(git rev-parse "$TAG") + echo " Tag points to commit: $TAG_COMMIT" + SKIP_TAG_PUSH=true +else + echo -e "${GREEN}✅ Tag $TAG does not exist${NC}" +fi + +# Check if release exists on GitHub +echo "Checking if GitHub release exists..." + +# Extract repository info from git remote +GIT_REMOTE=$(git remote get-url origin) +if [[ $GIT_REMOTE =~ github\.com[:/]([^/]+)/([^/.]+)(\.git)?$ ]]; then + REPO_OWNER="${BASH_REMATCH[1]}" + REPO_NAME="${BASH_REMATCH[2]}" + + # Check if release exists via GitHub API + HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://api.github.com/repos/$REPO_OWNER/$REPO_NAME/releases/tags/$TAG") + + if [ "$HTTP_STATUS" = "200" ]; then + RELEASE_EXISTS=true + echo -e "${YELLOW}⚠️ Warning: GitHub release $TAG already exists${NC}" + echo " You can view it at: https://github.com/$REPO_OWNER/$REPO_NAME/releases/tag/$TAG" + SKIP_VERSION_UPDATE=true + SKIP_TAG_PUSH=true + elif [ "$HTTP_STATUS" = "404" ]; then + echo -e "${GREEN}✅ No existing release found${NC}" + else + echo -e "${RED}❌ Error: Could not verify release status (HTTP $HTTP_STATUS)${NC}" + exit 1 + fi +else + echo -e "${RED}❌ Error: Could not parse GitHub repository from remote${NC}" + exit 1 +fi + +echo "" + +# Step 2: Update version files, create branch, push, and wait for PR merge +echo -e "${YELLOW}[Step 2/3] Updating version and creating PR...${NC}" +echo "" + +if [ "$SKIP_VERSION_UPDATE" = true ]; then + echo -e "${YELLOW}⚠️ Skipping version update (release already exists)${NC}" + echo "" +else + # Call update-version.sh + ./scripts/update-version.sh "$VERSION" + + echo "" + echo -e "${GREEN}✅ Version update complete${NC}" +fi + +echo "" + +# Step 3: Push tag and monitor release +echo -e "${YELLOW}[Step 3/3] Creating tag and monitoring release...${NC}" +echo "" + +# Ensure we are on master before tagging +CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) +if [ "$CURRENT_BRANCH" != "master" ]; then + echo "Switching to master branch..." + git checkout master + git pull origin master + echo -e "${GREEN}✅ On master branch${NC}" + echo "" +fi + +# Final version verification before tagging +if [ "$SKIP_TAG_PUSH" != true ]; then + source "$(dirname "$0")/check-version.sh" "$VERSION" + if [ "$HAS_MISMATCH" = true ]; then + echo -e "${RED}❌ Error: Version mismatch detected before tagging!${NC}" + echo -e "${RED} Files must match version $VERSION before creating a tag.${NC}" + echo -e "${RED} Run: ./scripts/update-version.sh --files-only $VERSION${NC}" + exit 1 + fi + echo -e "${GREEN}✅ All file versions match $VERSION${NC}" + echo "" +fi + +if [ "$SKIP_TAG_PUSH" = true ]; then + echo -e "${YELLOW}⚠️ Skipping tag push (tag already exists)${NC}" + + if [ "$RELEASE_EXISTS" = true ]; then + echo -e "${GREEN}✅ Release already exists and is available!${NC}" + echo " View at: https://github.com/$REPO_OWNER/$REPO_NAME/releases/tag/$TAG" + else + echo "Monitoring for release creation..." + # Run push-tag.sh to monitor the release + ./scripts/push-tag.sh "$VERSION" + fi +else + # Run push-tag.sh to create a new tag and monitor the release + ./scripts/push-tag.sh "$VERSION" +fi + +echo "" +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}✅ Release process complete!${NC}" +echo -e "${GREEN}========================================${NC}" diff --git a/scripts/update-version.sh b/scripts/update-version.sh new file mode 100755 index 0000000..1c9c8f1 --- /dev/null +++ b/scripts/update-version.sh @@ -0,0 +1,275 @@ +#!/bin/bash +set -e + +# Parse arguments +FILES_ONLY=false +VERSION="" + +while [[ $# -gt 0 ]]; do + case $1 in + --files-only) + FILES_ONLY=true + shift + ;; + *) + VERSION=$1 + shift + ;; + esac +done + +# Check if version argument is provided +if [ -z "$VERSION" ]; then + echo "Error: Version argument required" + echo "Usage: ./scripts/update-version.sh [--files-only] " + exit 1 +fi + +# Validate version format +if ! [[ $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: Invalid version format. Expected: major.minor.patch (e.g., 1.2.3)" + exit 1 +fi + +# Check version mismatches using shared script +source "$(dirname "$0")/check-version.sh" "$VERSION" + +if [ "$HAS_MISMATCH" = true ]; then + if [ "$FILES_ONLY" = true ]; then + echo "" + echo "Updating versions to $VERSION (files only mode)..." + + # Update Cargo.toml (using awk for cross-platform compatibility) + if [ "$VERSION" != "$CARGO_VERSION" ]; then + awk -v ver="$VERSION" ' + /^\[package\]/ { in_package=1 } + /^\[/ && !/^\[package\]/ { in_package=0 } + in_package && /^version = / { + print "version = \"" ver "\"" + in_package=0 + next + } + { print } + ' Cargo.toml > Cargo.toml.tmp && mv Cargo.toml.tmp Cargo.toml + echo "✅ Updated Cargo.toml" + fi + + # Update META.json (both root version and provides.pg_jsonschema.version, but not meta-spec) + # Using awk for cross-platform compatibility - updates first 2 version occurrences only + if [ "$VERSION" != "$META_VERSION" ]; then + awk -v ver="$VERSION" ' + /"version":/ { + version_count++ + if (version_count <= 2) { + sub(/"version": "[^"]*"/, "\"version\": \"" ver "\"") + } + } + { print } + ' META.json > META.json.tmp && mv META.json.tmp META.json + echo "✅ Updated META.json" + fi + + echo "" + echo "Updating Cargo.lock..." + cargo build --quiet 2>/dev/null || cargo build + echo "✅ Updated Cargo.lock" + + echo "" + echo "✅ Files updated to version $VERSION" + echo " Note: No git operations performed (--files-only mode)" + else + BRANCH_NAME="release/$VERSION" + + # Fetch latest from remote + echo "" + echo "Fetching latest from remote..." + git fetch + echo "✅ Fetched from remote" + + # Check if branch already exists (locally or remotely) + BRANCH_EXISTS=false + if git rev-parse --verify "$BRANCH_NAME" >/dev/null 2>&1; then + BRANCH_EXISTS=true + echo "" + echo "⚠️ Warning: Branch $BRANCH_NAME already exists locally" + + # Check if we're already on this branch + CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) + if [ "$CURRENT_BRANCH" != "$BRANCH_NAME" ]; then + echo "Switching to existing branch: $BRANCH_NAME" + git checkout "$BRANCH_NAME" + else + echo "Already on branch: $BRANCH_NAME" + fi + elif git rev-parse --verify "origin/$BRANCH_NAME" >/dev/null 2>&1; then + BRANCH_EXISTS=true + echo "" + echo "⚠️ Warning: Branch $BRANCH_NAME already exists on remote" + echo "Checking out existing branch from remote" + git checkout -b "$BRANCH_NAME" "origin/$BRANCH_NAME" + else + echo "" + echo "Creating branch: $BRANCH_NAME" + git checkout -b "$BRANCH_NAME" + echo "✅ Branch created" + fi + + echo "" + echo "Updating versions to $VERSION..." + + # Update Cargo.toml (using awk for cross-platform compatibility) + if [ "$VERSION" != "$CARGO_VERSION" ]; then + awk -v ver="$VERSION" ' + /^\[package\]/ { in_package=1 } + /^\[/ && !/^\[package\]/ { in_package=0 } + in_package && /^version = / { + print "version = \"" ver "\"" + in_package=0 + next + } + { print } + ' Cargo.toml > Cargo.toml.tmp && mv Cargo.toml.tmp Cargo.toml + echo "✅ Updated Cargo.toml" + fi + + # Update META.json (both root version and provides.pg_jsonschema.version, but not meta-spec) + # Using awk for cross-platform compatibility - updates first 2 version occurrences only + if [ "$VERSION" != "$META_VERSION" ]; then + awk -v ver="$VERSION" ' + /"version":/ { + version_count++ + if (version_count <= 2) { + sub(/"version": "[^"]*"/, "\"version\": \"" ver "\"") + } + } + { print } + ' META.json > META.json.tmp && mv META.json.tmp META.json + echo "✅ Updated META.json" + fi + + echo "" + echo "Updating Cargo.lock..." + cargo build --quiet 2>/dev/null || cargo build + echo "✅ Updated Cargo.lock" + + echo "" + echo "Committing changes..." + + # Check if there are changes to commit + if git diff --quiet Cargo.toml META.json Cargo.lock; then + echo "⚠️ Warning: No changes to commit (files already updated)" + COMMIT_HASH=$(git rev-parse HEAD) + else + git add Cargo.toml META.json Cargo.lock + git commit -m "chore: bump version to $VERSION" + echo "✅ Changes committed" + COMMIT_HASH=$(git rev-parse HEAD) + fi + + echo "" + echo "Pushing branch to remote..." + + # Try to push, but continue if already pushed + if git push -u origin "$BRANCH_NAME" 2>&1 | tee /tmp/git_push_output.txt; then + echo "✅ Branch pushed to remote" + else + # Check if error is because branch is already up to date + if grep -q "Everything up-to-date" /tmp/git_push_output.txt || grep -q "already exists" /tmp/git_push_output.txt; then + echo "⚠️ Warning: Branch already pushed to remote" + else + # If it's a real error, still show it but continue + echo "⚠️ Warning: Failed to push branch, but continuing..." + fi + fi + rm -f /tmp/git_push_output.txt + + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "📝 Next Steps:" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + echo "1. Create a Pull Request for branch: $BRANCH_NAME" + echo "2. Get the PR reviewed and merged into master" + echo "" + + # Extract repository info for PR URL + GIT_REMOTE=$(git remote get-url origin) + if [[ $GIT_REMOTE =~ github\.com[:/]([^/]+)/([^/.]+)(\.git)?$ ]]; then + REPO_OWNER="${BASH_REMATCH[1]}" + REPO_NAME="${BASH_REMATCH[2]}" + PR_URL="https://github.com/$REPO_OWNER/$REPO_NAME/compare/master...$BRANCH_NAME?expand=1" + echo "Create a PR at: $PR_URL" + else + echo "Create a PR manually on GitHub." + fi + echo "" + + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "⏳ Waiting for PR to be merged..." + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + + # Fetch latest from remote to check current status + git fetch origin master >/dev/null 2>&1 + + # Check if already merged into master + if git merge-base --is-ancestor "$COMMIT_HASH" origin/master 2>/dev/null; then + echo "✅ Changes already merged into master" + else + # Poll for PR merge status + CHECK_INTERVAL=15 # Check every 15 seconds + MAX_ATTEMPTS=240 # Max 1 hour (240 * 15 seconds) + ATTEMPT=0 + + echo "Checking if PR is merged (polling every ${CHECK_INTERVAL}s)..." + echo "Press Ctrl+C to cancel" + echo "" + + while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do + # Fetch latest from remote + git fetch origin master >/dev/null 2>&1 + + # Check if the commit exists in master + if git merge-base --is-ancestor "$COMMIT_HASH" origin/master 2>/dev/null; then + echo "✅ Changes are merged into master" + break + fi + + ATTEMPT=$((ATTEMPT + 1)) + ELAPSED=$((ATTEMPT * CHECK_INTERVAL)) + echo "⏳ Still waiting... (${ELAPSED}s elapsed, checking again in ${CHECK_INTERVAL}s)" + sleep $CHECK_INTERVAL + done + + # Final check + if ! git merge-base --is-ancestor "$COMMIT_HASH" origin/master 2>/dev/null; then + echo "" + echo "⚠️ Warning: PR not merged after waiting for 1 hour" + echo " Please merge the PR and run the release script again to continue." + echo " Branch: $BRANCH_NAME" + echo " Commit: $COMMIT_HASH" + exit 1 + fi + fi + + echo "" + echo "Verifying PR is merged into master..." + + # Checkout master and pull latest + echo "" + echo "Checking out master branch..." + git checkout master + git pull origin master + echo "✅ Master branch updated" + + echo "" + echo "✅ Release preparation complete" + echo " Branch: $BRANCH_NAME" + echo " Version: $VERSION" + echo " Status: Merged into master" + fi +else + echo "✅ All versions already match: $VERSION" + echo "" + echo "No changes needed. Skipping branch creation and commit." +fi