mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-05 16:02:48 +00:00
149 lines
4.4 KiB
Bash
Executable File
149 lines
4.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Renders the "What's Changed" section for a release tag as Markdown on stdout.
|
|
# Usage: release-notes.sh <tag> [previous-tag]
|
|
set -euo pipefail
|
|
|
|
TAG="${1:?usage: release-notes.sh <tag> [previous-tag]}"
|
|
PREV="${2-}"
|
|
|
|
MAX_ENTRIES="${MAX_ENTRIES:-80}"
|
|
MAX_LEN="${MAX_LEN:-140}"
|
|
|
|
REPO_URL="${GITHUB_SERVER_URL:-https://github.com}/${GITHUB_REPOSITORY:-}"
|
|
if [[ -z "${GITHUB_REPOSITORY:-}" ]]; then
|
|
origin=$(git config --get remote.origin.url || echo "")
|
|
origin="${origin%.git}"
|
|
REPO_URL="${origin:-https://github.com}"
|
|
fi
|
|
|
|
# Previous tag: newest release tag that is an ancestor of this one. A stable tag
|
|
# compares against the last stable tag, so a v1.2.0 release still lists
|
|
# everything its rc tags already covered.
|
|
if [[ -z "$PREV" ]]; then
|
|
tag_sha=$(git rev-parse "${TAG}^{commit}")
|
|
while read -r candidate; do
|
|
[[ -n "$candidate" ]] || continue
|
|
[[ "$TAG" == *-* ]] || [[ "$candidate" != *-* ]] || continue
|
|
[[ "$(git rev-parse "${candidate}^{commit}")" != "$tag_sha" ]] || continue
|
|
PREV="$candidate"
|
|
break
|
|
done < <(git tag -l 'v*' --sort=-v:refname --merged "$TAG")
|
|
fi
|
|
|
|
if [[ -n "$PREV" ]]; then
|
|
RANGE="${PREV}..${TAG}"
|
|
else
|
|
RANGE="$TAG"
|
|
fi
|
|
|
|
# Trim a subject to something readable, preferring a clause boundary over a
|
|
# hard cut. Commit subjects in this repo are deliberately long and specific;
|
|
# the full text is one click away behind the PR link.
|
|
shorten() {
|
|
local s="$1" cut best candidate sep
|
|
s="${s//$'\n'/ }"
|
|
s="$(printf '%s' "$s" | tr -s '[:space:]' ' ')"
|
|
s="${s#"${s%%[![:space:]]*}"}"
|
|
s="${s%"${s##*[![:space:]]}"}"
|
|
if (( ${#s} <= MAX_LEN )); then
|
|
printf '%s' "$s"
|
|
return
|
|
fi
|
|
cut="${s:0:MAX_LEN}"
|
|
best=""
|
|
for sep in ', ' '; ' ': ' ' ('; do
|
|
candidate="${cut%"$sep"*}"
|
|
[[ "$candidate" != "$cut" ]] || continue
|
|
(( ${#candidate} >= 60 )) || continue
|
|
(( ${#candidate} > ${#best} )) && best="$candidate"
|
|
done
|
|
if [[ -z "$best" ]]; then
|
|
best="${cut% *}"
|
|
[[ "$best" != "$cut" ]] || best="$cut"
|
|
fi
|
|
best="${best%[,;:.\ ]}"
|
|
printf '%s…' "$best"
|
|
}
|
|
|
|
declare -A SECTION_LINES=()
|
|
SECTION_ORDER=(Features Fixes Performance Security Documentation Maintenance Other)
|
|
total=0
|
|
shown=0
|
|
|
|
while IFS=$'\t' read -r hash subject; do
|
|
[[ -n "$hash" ]] || continue
|
|
# Branch-sync merges carry no release-visible change of their own.
|
|
[[ "$subject" != Merge\ branch\ * ]] || continue
|
|
[[ "$subject" != Merge\ remote-tracking\ * ]] || continue
|
|
|
|
pr=""
|
|
title="$subject"
|
|
if [[ "$subject" =~ ^Merge\ pull\ request\ \#([0-9]+) ]]; then
|
|
pr="${BASH_REMATCH[1]}"
|
|
body_first=$(git log -1 --format=%b "$hash" | grep -m1 -v '^[[:space:]]*$' || true)
|
|
if [[ -n "$body_first" ]]; then
|
|
title="$body_first"
|
|
else
|
|
# No PR title in the merge body: fall back to the branch name.
|
|
title="${subject#*from }"
|
|
title="${title#*/}"
|
|
title="${title//-/ }"
|
|
fi
|
|
elif [[ "$subject" =~ \(#([0-9]+)\)$ ]]; then
|
|
pr="${BASH_REMATCH[1]}"
|
|
title="${subject% (#"${pr}")}"
|
|
fi
|
|
|
|
type="other"
|
|
if [[ "$title" =~ ^([a-zA-Z]+)(\([^\)]*\))?(!)?:[[:space:]]*(.*)$ ]]; then
|
|
type="${BASH_REMATCH[1],,}"
|
|
title="${BASH_REMATCH[4]}"
|
|
fi
|
|
|
|
case "$type" in
|
|
feat|feature) section="Features" ;;
|
|
fix|bugfix|hotfix) section="Fixes" ;;
|
|
perf) section="Performance" ;;
|
|
sec|security) section="Security" ;;
|
|
docs|doc) section="Documentation" ;;
|
|
refactor|chore|build|ci|test|tests|style|deps) section="Maintenance" ;;
|
|
*) section="Other" ;;
|
|
esac
|
|
|
|
total=$((total + 1))
|
|
(( shown < MAX_ENTRIES )) || continue
|
|
shown=$((shown + 1))
|
|
|
|
title="$(shorten "$title")"
|
|
[[ -z "$title" ]] && title="$subject"
|
|
title="${title^}"
|
|
|
|
if [[ -n "$pr" ]]; then
|
|
ref="([#${pr}](${REPO_URL}/pull/${pr}))"
|
|
else
|
|
ref="([\`$(git rev-parse --short "$hash")\`](${REPO_URL}/commit/${hash}))"
|
|
fi
|
|
|
|
SECTION_LINES[$section]+="- ${title} ${ref}"$'\n'
|
|
done < <(git log --first-parent --format='%H%x09%s' "$RANGE")
|
|
|
|
printf '## What'\''s Changed\n\n'
|
|
|
|
if (( total == 0 )); then
|
|
printf 'No changes recorded for this release.\n'
|
|
else
|
|
for section in "${SECTION_ORDER[@]}"; do
|
|
[[ -n "${SECTION_LINES[$section]:-}" ]] || continue
|
|
printf '### %s\n\n%s\n' "$section" "${SECTION_LINES[$section]}"
|
|
done
|
|
if (( total > shown )); then
|
|
printf '_...and %d more changes. See the full changelog below._\n\n' "$((total - shown))"
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "$PREV" ]]; then
|
|
printf '**Full Changelog**: %s/compare/%s...%s\n' "$REPO_URL" "$PREV" "$TAG"
|
|
else
|
|
printf '**Full Changelog**: %s/commits/%s\n' "$REPO_URL" "$TAG"
|
|
fi
|