diff --git a/.github/scripts/get_previous_version.sh b/.github/scripts/get_previous_version.sh index d7a6c9f..7bdcd7b 100755 --- a/.github/scripts/get_previous_version.sh +++ b/.github/scripts/get_previous_version.sh @@ -1,74 +1,170 @@ #!/bin/bash -# get_previous_version.sh - Extract the previous version from git history -# Usage: get_previous_version.sh +# get_previous_version.sh - Extract versions from git history with chronological filtering +# Usage: get_previous_version.sh [version] # # Arguments: -# version: The current semver version (e.g., 1.2.3 or 2.0.0-rc.1) +# version: (Optional) The current semver version (e.g., 1.2.3 or 2.0.0-rc.1) # # Output: -# Prints the previous version to stdout -# Returns exit code 0 on success, non-zero on failure +# - No argument: Lists all versions in chronological order (newest first) +# - With argument: Prints the previous version before the specified version # # This script: -# 1. Finds the commit that introduced the specified version -# 2. Gets the parent commit of that version change -# 3. Extracts the version from the parent commit -# 4. Outputs the previous version +# 1. Extracts all versions from git history of src/version.py +# 2. Validates each version using validate_semver.sh +# 3. Filters out duplicates (keeps newest occurrence) +# 4. Filters out reverts (versions that break chronological order) +# 5. Returns a clean, monotonically decreasing version list set -euo pipefail +# Path to validate_semver.sh +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +VALIDATE_SEMVER="$SCRIPT_DIR/validate_semver.sh" + # Display usage information usage() { - echo "Usage: $0 " >&2 + echo "Usage: $0 [version]" >&2 echo "" >&2 - echo "Extracts the previous version from git history before the specified version." >&2 + echo "Extracts and filters versions from git history of src/version.py." >&2 + echo "Removes duplicates and reverts to provide a clean chronological list." >&2 echo "" >&2 echo "Arguments:" >&2 - echo " version: The current semver version (e.g., 1.2.3 or 2.0.0-rc.1)" >&2 + echo " version: (Optional) The current semver version (e.g., 1.2.3 or 2.0.0-rc.1)" >&2 echo "" >&2 echo "Examples:" >&2 + echo " $0 # List all versions in chronological order" >&2 echo " $0 1.2.3 # Get version before 1.2.3" >&2 echo " $0 2.0.0-rc.1 # Get version before 2.0.0-rc.1" >&2 exit 1 } -# Check if version argument is provided -if [ "$#" -ne 1 ]; then - echo "Error: Version argument required" >&2 +# Extract and filter versions from git history +# Returns filtered versions and dates via global arrays +get_filtered_versions() { + # Arrays to store all versions + local -a all_versions=() + local -a all_dates=() + local -a all_timestamps=() + + # Extract all versions from git history + while IFS='|' read -r commit_hash timestamp date_human; do + # Extract version from this commit + local version + version=$(git show "$commit_hash:src/version.py" 2>/dev/null | grep -oP '__version__ = "\K[^"]+' || echo "") + + # Skip if version extraction failed + if [ -z "$version" ]; then + continue + fi + + # Validate version using validate_semver.sh + if ! bash "$VALIDATE_SEMVER" "$version" >/dev/null 2>&1; then + continue + fi + + # Store this version + all_versions+=("$version") + all_dates+=("$date_human") + all_timestamps+=("$timestamp") + done < <(git log --all --format="%H|%ct|%ci" --follow -- src/version.py) + + # Sort arrays by timestamp (newest first) + # Create indexed array for sorting + local -a indices=() + for i in "${!all_timestamps[@]}"; do + indices+=("$i") + done + + # Bubble sort by timestamp (descending) + local n=${#indices[@]} + for ((i = 0; i < n-1; i++)); do + for ((j = 0; j < n-i-1; j++)); do + local idx1=${indices[$j]} + local idx2=${indices[$((j+1))]} + if [ "${all_timestamps[$idx1]}" -lt "${all_timestamps[$idx2]}" ]; then + # Swap + local temp=${indices[$j]} + indices[$j]=${indices[$((j+1))]} + indices[$((j+1))]=$temp + fi + done + done + + # Filter duplicates and reverts + declare -A seen_versions + filtered_versions=() + filtered_dates=() + local max_version="" + + for idx in "${indices[@]}"; do + local version="${all_versions[$idx]}" + local date="${all_dates[$idx]}" + + # Skip duplicates (keep only first/newest occurrence) + if [[ -n "${seen_versions[$version]:-}" ]]; then + continue + fi + + # Skip reverts (versions greater than what we've already seen) + if [[ -n "$max_version" ]]; then + if bash "$VALIDATE_SEMVER" "$version" ">$max_version" >/dev/null 2>&1; then + # This version is greater than max_version = revert detected, skip + continue + fi + fi + + # Keep this version + filtered_versions+=("$version") + filtered_dates+=("$date") + seen_versions["$version"]=1 + max_version="$version" + done +} + +# Check arguments +if [ "$#" -gt 1 ]; then + echo "Error: Too many arguments" >&2 usage fi -VERSION="$1" +# Get filtered versions +declare -a filtered_versions +declare -a filtered_dates +get_filtered_versions -# Get the git history of version.py, find the commit that introduced $VERSION -CURRENT_VERSION_COMMIT=$(git log --all --format="%H" --follow -S "__version__ = \"$VERSION\"" -- src/version.py | head -1) +# Handle based on number of arguments +if [ "$#" -eq 0 ]; then + # No argument: list all versions + echo "Recent versions:" >&2 + echo "" >&2 -if [ -z "$CURRENT_VERSION_COMMIT" ]; then - echo "Error: Could not find commit that set version to $VERSION" >&2 + for i in "${!filtered_versions[@]}"; do + echo "${filtered_versions[$i]} | ${filtered_dates[$i]}" >&2 + done + + exit 0 +else + # One argument: find previous version + VERSION="$1" + + # Find the version in the list + for i in "${!filtered_versions[@]}"; do + if [ "${filtered_versions[$i]}" = "$VERSION" ]; then + # Found it, return the next version in the list + next_idx=$((i + 1)) + if [ "$next_idx" -lt "${#filtered_versions[@]}" ]; then + echo "${filtered_versions[$next_idx]}" + exit 0 + else + echo "Error: No previous version found (this is the oldest version)" >&2 + exit 1 + fi + fi + done + + # Version not found in list + echo "Error: Version $VERSION not found in git history" >&2 exit 1 fi - -# Get the previous commit for version.py -PREVIOUS_VERSION_COMMIT=$(git log --all --format="%H" --follow "$CURRENT_VERSION_COMMIT^..HEAD" -- src/version.py | grep -A1 "$CURRENT_VERSION_COMMIT" | tail -1 || true) - -# If we can't find a previous commit in that way, try getting the parent commit and extracting version from there -if [ -z "$PREVIOUS_VERSION_COMMIT" ] || [ "$PREVIOUS_VERSION_COMMIT" = "$CURRENT_VERSION_COMMIT" ]; then - PREVIOUS_VERSION_COMMIT=$(git rev-parse "$CURRENT_VERSION_COMMIT^" 2>/dev/null || true) -fi - -if [ -z "$PREVIOUS_VERSION_COMMIT" ]; then - echo "Error: Could not find previous commit for version.py" >&2 - exit 1 -fi - -# Extract version from that commit -PREVIOUS_VERSION=$(git show "$PREVIOUS_VERSION_COMMIT:src/version.py" | grep -oP '__version__ = "\K[^"]+' || true) - -if [ -z "$PREVIOUS_VERSION" ]; then - echo "Error: Could not extract previous version from commit ${PREVIOUS_VERSION_COMMIT:0:7}" >&2 - exit 1 -fi - -# Output the previous version to stdout -echo "$PREVIOUS_VERSION"