Skip to content

Instantly share code, notes, and snippets.

@kopach
Last active May 20, 2025 11:20
Show Gist options
  • Save kopach/53168f97cdf8fa91c0998d44f06d15af to your computer and use it in GitHub Desktop.
Save kopach/53168f97cdf8fa91c0998d44f06d15af to your computer and use it in GitHub Desktop.
#!/bin/bash
# This script generates a changelog for a given repository between two versions.
# It uses the GitHub API to fetch all tags and releases, and then generates a changelog
# based on the release notes.
#
# Use it without installation like so:
#
# curl -fsSL https://gist.githubusercontent.com/kopach/53168f97cdf8fa91c0998d44f06d15af/raw/gh-release-changelog.sh | bash -s -- \
# --repo kopach/karma-sabarivka-reporter \
# --from v1.1.1 \
# --to v3.0.2 \
# --output karma-changelog-v1.1.1-v3.0.2.md
#
# Or save it to a file and run it like so:
#
# bash ./gh-release-changelog.sh --repo kopach/karma-sabarivka-reporter --from v1.1.1 --to v3.0.2 --output karma-changelog-v1.1.1-v3.0.2.md
# Initialize
REPO=""
FROM=""
TO=""
OUTPUT=""
SHOW_HELP=0
# Help text
usage() {
echo "Usage: $0 [--repo org/repo] [--from v1.0.0] [--to v2.0.0] [--output file.md]"
echo ""
echo "Options:"
echo " --repo GitHub repository (e.g. kopach/karma-sabarivka-reporter)"
echo " --from Starting version (older, e.g. v1.1.1)"
echo " --to Ending version (newer, e.g. v3.0.2 )"
echo " --output Output file (optional; if omitted, prints to stdout)"
echo " --help Show this help message"
}
# Parse args
while [[ $# -gt 0 ]]; do
case "$1" in
--repo)
REPO="$2"
shift 2
;;
--from)
FROM="$2"
shift 2
;;
--to)
TO="$2"
shift 2
;;
--output)
OUTPUT="$2"
shift 2
;;
--help)
SHOW_HELP=1
shift
;;
*)
echo "❌ Unknown option: $1"
usage
exit 1
;;
esac
done
if [[ $SHOW_HELP -eq 1 ]]; then
usage
exit 0
fi
# Prompt for missing
[[ -z "$REPO" ]] && read -p "Enter repository (e.g. actions/setup-node): " REPO
[[ -z "$FROM" ]] && read -p "Enter FROM version (older, e.g. v1.2.3): " FROM
[[ -z "$TO" ]] && read -p "Enter TO version (newer, e.g. v2.0.0): " TO
echo "πŸ”„ Fetching all git tags from $REPO ..."
ALL_TAGS=($(gh api repos/$REPO/tags --paginate --jq '.[].name'))
# Filter semver tags
SEMVER_TAGS=($(printf '%s\n' "${ALL_TAGS[@]}" | grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+$' | sort -V))
# Validate FROM/TO
if ! printf '%s\n' "${SEMVER_TAGS[@]}" | grep -qx "$FROM"; then
echo "❌ FROM tag '$FROM' not found in semver tag list."
exit 1
fi
if ! printf '%s\n' "${SEMVER_TAGS[@]}" | grep -qx "$TO"; then
echo "❌ TO tag '$TO' not found in semver tag list."
exit 1
fi
# Build range: FROM β†’ TO (semver)
RANGE_STARTED=0
RANGE_TAGS=()
for TAG in "${SEMVER_TAGS[@]}"; do
[[ "$TAG" == "$FROM" ]] && RANGE_STARTED=1
[[ $RANGE_STARTED -eq 1 ]] && RANGE_TAGS+=("$TAG")
[[ "$TAG" == "$TO" ]] && break
done
if [[ ${#RANGE_TAGS[@]} -eq 0 ]]; then
echo "❌ No semver tags found between $FROM and $TO."
exit 1
fi
# Reverse range: TO β†’ FROM
REVERSED_TAGS=($(printf '%s\n' "${RANGE_TAGS[@]}" | tail -r))
# Cache release tags
RELEASE_MAP=$(gh release list --repo "$REPO" --limit 1000 | awk '{print $1}')
# Output setup
print() {
if [[ -n "$OUTPUT" ]]; then
echo -e "$1" >>"$OUTPUT"
else
echo -e "$1"
fi
}
# Begin output
[[ -n "$OUTPUT" ]] && echo "πŸ’Ύ Writing changelog to $OUTPUT..."
print "\nπŸ“˜ Changelog from $TO to $FROM in $REPO:\n"
for TAG in "${REVERSED_TAGS[@]}"; do
if echo "$RELEASE_MAP" | grep -qx "$TAG"; then
print "## $TAG"
BODY=$(gh release view "$TAG" --repo "$REPO" --json body -q '.body')
print "$BODY"
print "\n---\n"
fi
done
[[ -n "$OUTPUT" ]] && echo "βœ… Done: changelog saved to $OUTPUT"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment