extract-changelog.sh 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. # Extract cumulative release notes from CHANGELOG.md.
  4. #
  5. # For a given version (e.g. 1.0.5), extracts all entries from the current
  6. # minor series back to x.x.0 (e.g. 1.0.0 through 1.0.5). This means each
  7. # GitHub release restates the full arc of changes for the minor series.
  8. #
  9. # The [Unreleased] section is included — it contains the content that will
  10. # become [X.Y.Z] when the release script runs. If the version is already
  11. # released, [Unreleased] may be empty and is omitted.
  12. #
  13. # Fails if neither [Unreleased] nor [X.Y.Z] has content in the changelog.
  14. #
  15. # Usage: scripts/extract-changelog.sh <version>
  16. # Example: scripts/extract-changelog.sh 1.0.5
  17. # -> extracts [Unreleased] + [1.0.5], [1.0.4], ..., [1.0.0]
  18. VERSION="${1:?Usage: extract-changelog.sh <version>}"
  19. # Parse major.minor.patch from version
  20. IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
  21. if [[ ! -f CHANGELOG.md ]]; then
  22. echo "CHANGELOG.md not found" >&2
  23. exit 1
  24. fi
  25. # Extract [Unreleased] section and all [X.Y.Z] sections matching our minor series.
  26. OUTPUT=""
  27. CAPTURING=false
  28. UNRELEASED_CONTENT=""
  29. IN_UNRELEASED=false
  30. while IFS= read -r line; do
  31. if [[ "$line" =~ ^##\ \[Unreleased\] ]]; then
  32. CAPTURING=true
  33. IN_UNRELEASED=true
  34. elif [[ "$line" =~ ^##\ \[([0-9]+\.[0-9]+\.[0-9]+)\] ]]; then
  35. IN_UNRELEASED=false
  36. ENTRY_VERSION="${BASH_REMATCH[1]}"
  37. IFS='.' read -r E_MAJOR E_MINOR E_PATCH <<< "$ENTRY_VERSION"
  38. if [[ "$E_MAJOR" == "$MAJOR" && "$E_MINOR" == "$MINOR" ]]; then
  39. CAPTURING=true
  40. OUTPUT+="$line"$'\n'
  41. else
  42. CAPTURING=false
  43. fi
  44. elif [[ "$line" =~ ^##\ ]]; then
  45. IN_UNRELEASED=false
  46. CAPTURING=false
  47. elif $CAPTURING; then
  48. if $IN_UNRELEASED; then
  49. UNRELEASED_CONTENT+="$line"$'\n'
  50. else
  51. OUTPUT+="$line"$'\n'
  52. fi
  53. fi
  54. done < CHANGELOG.md
  55. # Only include [Unreleased] if it has non-blank content
  56. TRIMMED=$(echo "$UNRELEASED_CONTENT" | sed '/^[[:space:]]*$/d')
  57. if [[ -n "$TRIMMED" ]]; then
  58. OUTPUT="## [Unreleased]"$'\n'"$UNRELEASED_CONTENT$OUTPUT"
  59. fi
  60. # Fail if we got nothing
  61. TRIMMED_OUTPUT=$(echo "$OUTPUT" | sed '/^[[:space:]]*$/d')
  62. if [[ -z "$TRIMMED_OUTPUT" ]]; then
  63. echo "error: no changelog content found for $VERSION" >&2
  64. echo "Expected either:" >&2
  65. echo " ## [Unreleased] (with content)" >&2
  66. echo " ## [$VERSION] - YYYY-MM-DD" >&2
  67. exit 1
  68. fi
  69. printf '%s' "$OUTPUT"