nix/bin/gc-changelog

105 lines
2.5 KiB
Bash
Executable file

#!/usr/bin/env bash
# Generate changelog from conventional commits
# Usage: gc-changelog [from_ref] [to_ref]
# gc-changelog # all commits
# gc-changelog v1.0.0 # from tag to HEAD
# gc-changelog v1.0.0 v2.0.0 # between tags
set -o errexit
set -o nounset
from_ref=${1:-}
to_ref=${2:-HEAD}
if [ -n "$from_ref" ]; then
range="$from_ref..$to_ref"
else
range=""
fi
# Collect commits by type
declare -A titles=(
[feat]="Features"
[fix]="Bug Fixes"
[docs]="Documentation"
[style]="Styles"
[refactor]="Refactoring"
[test]="Tests"
[chore]="Chores"
[perf]="Performance"
[ci]="CI"
[build]="Build"
)
declare -A commits
declare -a breaking_changes
for type in feat fix docs style refactor test chore perf ci build; do
commits[$type]=""
done
# Parse commits
# Full pattern: type(scope)!: description
while IFS= read -r line; do
[ -z "$line" ] && continue
msg=$(echo "$line" | cut -d' ' -f2-)
for type in feat fix docs style refactor test chore perf ci build; do
# Match: type, optional (scope), optional !, colon, space
if echo "$msg" | grep -qE "^$type(\([^)]+\))?!?: "; then
is_breaking=false
if echo "$msg" | grep -qE "^$type(\([^)]+\))?!:"; then
is_breaking=true
fi
# Extract scope and description
if echo "$msg" | grep -qE "^$type\([^)]+\)"; then
scope=$(echo "$msg" | sed -E "s/^$type\(([^)]+)\)!?: .*/\1/")
desc=$(echo "$msg" | sed -E "s/^$type\([^)]+\)!?: //")
else
scope=""
desc=$(echo "$msg" | sed -E "s/^$type!?: //")
fi
# Format the entry
if [ -n "$scope" ]; then
entry="- **$scope**: $desc"
else
entry="- $desc"
fi
if [ "$is_breaking" = true ]; then
breaking_changes+=("$entry")
fi
commits[$type]+="$entry"$'\n'
break
fi
done
done < <(git log --oneline $range)
# Output changelog
echo "# Changelog"
echo ""
# Breaking changes first
if [ ${#breaking_changes[@]} -gt 0 ]; then
echo "## BREAKING CHANGES"
echo ""
for change in "${breaking_changes[@]}"; do
echo "$change"
done
echo ""
fi
# Then by type
for type in feat fix perf refactor docs style test chore ci build; do
if [ -n "${commits[$type]}" ]; then
echo "## ${titles[$type]}"
echo ""
echo -n "${commits[$type]}"
echo ""
fi
done