105 lines
2.5 KiB
Bash
Executable file
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
|