Composite action that generates a changelog from git history between two tags, grouped by conventional commit types. Supports customizable categories via JSON input and auto-detects the previous tag.
157 lines
4.7 KiB
Shell
Executable file
157 lines
4.7 KiB
Shell
Executable file
#!/bin/bash
|
|
# Generate changelog from git history for CI releases
|
|
# Groups commits by conventional commit types
|
|
#
|
|
# Expected environment variables:
|
|
# INPUT_TAG - Git tag to generate changelog for (required)
|
|
# INPUT_PREVIOUS_TAG - Previous tag override (optional, auto-detected)
|
|
# INPUT_CATEGORIES - JSON mapping of prefixes to headers (optional)
|
|
# GITHUB_OUTPUT - Output file for action outputs (set by runner)
|
|
# GITEA_SERVER_URL - Forgejo server URL (set by runner)
|
|
# GITEA_REPOSITORY - Repository in owner/repo format (set by runner)
|
|
|
|
set -euo pipefail
|
|
|
|
TAG="${INPUT_TAG:?Error: tag input is required}"
|
|
PREV_TAG="${INPUT_PREVIOUS_TAG:-}"
|
|
CATEGORIES="${INPUT_CATEGORIES:-}"
|
|
|
|
# Default categories if not provided or empty
|
|
if [ -z "$CATEGORIES" ]; then
|
|
CATEGORIES='{"feat": "✨ Features", "fix": "🐛 Bug Fixes", "refactor": "♻️ Refactoring", "chore": "🔧 Chores"}'
|
|
fi
|
|
|
|
echo "Generating changelog for ${TAG}..."
|
|
|
|
# Determine previous tag
|
|
if [ -z "$PREV_TAG" ]; then
|
|
PREV_TAG=$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || echo "")
|
|
fi
|
|
|
|
if [ -z "$PREV_TAG" ]; then
|
|
echo "No previous tag found. This is the first release."
|
|
COMMIT_RANGE="${TAG}"
|
|
else
|
|
echo "Generating changelog from ${PREV_TAG} to ${TAG}"
|
|
COMMIT_RANGE="${PREV_TAG}..${TAG}"
|
|
fi
|
|
|
|
# Parse category prefixes and headers from JSON using jq if available, else fallback
|
|
parse_categories() {
|
|
if command -v jq &>/dev/null; then
|
|
jq -r 'to_entries[] | "\(.key)|\(.value)"' <<< "$CATEGORIES"
|
|
else
|
|
# Pure bash fallback: parse simple flat JSON {"key": "value", ...}
|
|
local json="${CATEGORIES//\{/}"
|
|
json="${json//\}/}"
|
|
local IFS=','
|
|
for pair in $json; do
|
|
local key value
|
|
key=$(echo "$pair" | sed 's/.*"\([^"]*\)"\s*:.*/\1/')
|
|
value=$(echo "$pair" | sed 's/.*:\s*"\([^"]*\)".*/\1/')
|
|
echo "${key}|${value}"
|
|
done
|
|
fi
|
|
}
|
|
|
|
# Read categories into arrays
|
|
CATEGORY_PREFIXES=()
|
|
CATEGORY_HEADERS=()
|
|
while IFS='|' read -r prefix header; do
|
|
[ -z "$prefix" ] && continue
|
|
CATEGORY_PREFIXES+=("$prefix")
|
|
CATEGORY_HEADERS+=("$header")
|
|
done < <(parse_categories)
|
|
|
|
NUM_CATEGORIES=${#CATEGORY_PREFIXES[@]}
|
|
|
|
# Initialize arrays to hold commits per category (+ 1 for "other")
|
|
declare -a CATEGORY_COMMITS
|
|
for ((i = 0; i <= NUM_CATEGORIES; i++)); do
|
|
CATEGORY_COMMITS[$i]=""
|
|
done
|
|
|
|
# Get all commits in range
|
|
COMMITS=$(git log "${COMMIT_RANGE}" --pretty=format:"%H|%s" 2>/dev/null || echo "")
|
|
|
|
if [ -n "$COMMITS" ]; then
|
|
while IFS='|' read -r hash message; do
|
|
[ -z "$hash" ] && continue
|
|
|
|
matched=false
|
|
for ((i = 0; i < NUM_CATEGORIES; i++)); do
|
|
prefix="${CATEGORY_PREFIXES[$i]}"
|
|
# Match "prefix: msg" or "prefix(scope): msg"
|
|
if [[ "$message" == "${prefix}:"* ]] || [[ "$message" == "${prefix}("* ]]; then
|
|
# Strip prefix from message
|
|
clean_msg="${message#"${prefix}"}"
|
|
# Strip optional (scope): prefix
|
|
if [[ "$clean_msg" == "("* ]]; then
|
|
clean_msg="${clean_msg#*): }"
|
|
clean_msg="${clean_msg#*):}"
|
|
else
|
|
clean_msg="${clean_msg#: }"
|
|
clean_msg="${clean_msg#:}"
|
|
fi
|
|
CATEGORY_COMMITS[$i]+="- ${clean_msg} (${hash:0:7})"$'\n'
|
|
matched=true
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ "$matched" = false ]; then
|
|
CATEGORY_COMMITS[$NUM_CATEGORIES]+="- ${message} (${hash:0:7})"$'\n'
|
|
fi
|
|
done <<< "$COMMITS"
|
|
fi
|
|
|
|
# Build changelog
|
|
CHANGELOG="# Release ${TAG}"$'\n'$'\n'
|
|
|
|
if [ -z "$PREV_TAG" ] && [ -z "$INPUT_PREVIOUS_TAG" ]; then
|
|
CHANGELOG+="First release! 🎉"$'\n'$'\n'
|
|
fi
|
|
|
|
if [ -z "$COMMITS" ]; then
|
|
CHANGELOG+="No commits found in range"$'\n'
|
|
else
|
|
# Write categorized sections
|
|
for ((i = 0; i < NUM_CATEGORIES; i++)); do
|
|
if [ -n "${CATEGORY_COMMITS[$i]}" ]; then
|
|
CHANGELOG+="## ${CATEGORY_HEADERS[$i]}"$'\n'$'\n'
|
|
CHANGELOG+="${CATEGORY_COMMITS[$i]}"$'\n'
|
|
fi
|
|
done
|
|
|
|
# Other changes
|
|
if [ -n "${CATEGORY_COMMITS[$NUM_CATEGORIES]}" ]; then
|
|
CHANGELOG+="## 📝 Other Changes"$'\n'$'\n'
|
|
CHANGELOG+="${CATEGORY_COMMITS[$NUM_CATEGORIES]}"$'\n'
|
|
fi
|
|
fi
|
|
|
|
CHANGELOG+="---"$'\n'
|
|
|
|
# Add compare link using Forgejo env vars
|
|
if [ -n "${GITEA_SERVER_URL:-}" ] && [ -n "${GITEA_REPOSITORY:-}" ]; then
|
|
COMPARE_BASE="${PREV_TAG:-$(git rev-list --max-parents=0 HEAD | head -1)}"
|
|
CHANGELOG+="**Full Changelog**: ${GITEA_SERVER_URL}/${GITEA_REPOSITORY}/compare/${COMPARE_BASE}...${TAG}"$'\n'
|
|
elif [ -n "$PREV_TAG" ]; then
|
|
CHANGELOG+="**Commits**: \`git log ${PREV_TAG}..${TAG}\`"$'\n'
|
|
else
|
|
CHANGELOG+="**First Release**"$'\n'
|
|
fi
|
|
|
|
echo ""
|
|
echo "=== Generated Changelog ==="
|
|
echo "$CHANGELOG"
|
|
echo "==========================="
|
|
|
|
# Write to GITHUB_OUTPUT using multiline delimiter
|
|
if [ -n "${GITHUB_OUTPUT:-}" ]; then
|
|
{
|
|
echo "changelog<<CHANGELOG_EOF"
|
|
echo "$CHANGELOG"
|
|
echo "CHANGELOG_EOF"
|
|
} >> "$GITHUB_OUTPUT"
|
|
fi
|