#!/bin/bash # Generate CHANGELOG.md from git commit history # Follows Conventional Commits format set -e CHANGELOG_FILE="CHANGELOG.md" TEMP_FILE="${CHANGELOG_FILE}.tmp" # Function to generate changelog section for a range of commits function generate_section_for_commits() { local from_ref="$1" local to_ref="$2" # Build git log range if [ -z "$to_ref" ]; then local range="$from_ref" else local range="${to_ref}..${from_ref}" fi # Get commits and categorize them local features="" local fixes="" local breaking="" local others="" # Process commits while IFS= read -r commit; do if [ -z "$commit" ]; then continue fi local message=$(echo "$commit" | cut -d' ' -f2-) local commit_hash=$(echo "$commit" | cut -d' ' -f1) # Get repository URL from git remote local repo_url=$(git remote get-url origin | sed 's/\.git$//' | sed 's/git@github\.com:/https:\/\/github\.com\//') # Categorize commit based on conventional commit format case "$message" in feat*|feature*) features="${features}- ${message} ([${commit_hash:0:7}](${repo_url}/commit/${commit_hash}))\n" ;; fix*) fixes="${fixes}- ${message} ([${commit_hash:0:7}](${repo_url}/commit/${commit_hash}))\n" ;; *"BREAKING CHANGE"*|*"!"*) breaking="${breaking}- ${message} ([${commit_hash:0:7}](${repo_url}/commit/${commit_hash}))\n" ;; chore*|docs*|style*|refactor*|test*|build*|ci*) others="${others}- ${message} ([${commit_hash:0:7}](${repo_url}/commit/${commit_hash}))\n" ;; "Merge pull request"*) # Extract PR info local pr_info=$(echo "$message" | sed 's/Merge pull request #\([0-9]*\) from .*/PR #\1/') others="${others}- ${pr_info}: ${message} ([${commit_hash:0:7}](${repo_url}/commit/${commit_hash}))\n" ;; *) others="${others}- ${message} ([${commit_hash:0:7}](${repo_url}/commit/${commit_hash}))\n" ;; esac done < <(git log --oneline --no-merges "$range" 2>/dev/null || true) # Output categorized changes local has_content=false if [ -n "$breaking" ]; then echo "### 💥 BREAKING CHANGES" echo "" echo -e "$breaking" has_content=true fi if [ -n "$features" ]; then echo "### ✨ Features" echo "" echo -e "$features" has_content=true fi if [ -n "$fixes" ]; then echo "### 🐛 Bug Fixes" echo "" echo -e "$fixes" has_content=true fi if [ -n "$others" ]; then echo "### 📝 Other Changes" echo "" echo -e "$others" has_content=true fi if [ "$has_content" = false ]; then echo "- No notable changes" echo "" fi echo "" } # Get the current version from Cargo.toml CURRENT_VERSION=$(grep "^version" Cargo.toml | sed 's/version = "\(.*\)"/\1/') # Create header cat > "$TEMP_FILE" << EOF # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). EOF # Get all tags sorted by version (newest first) TAGS=$(git tag -l --sort=-version:refname | grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+' | head -20) # If no tags exist, show unreleased changes if [ -z "$TAGS" ]; then echo "## [Unreleased]" >> "$TEMP_FILE" echo "" >> "$TEMP_FILE" generate_section_for_commits "HEAD" "" >> "$TEMP_FILE" else # Get latest tag LATEST_TAG=$(echo "$TAGS" | head -1) # Check if there are unreleased changes UNRELEASED_COMMITS=$(git log --oneline "${LATEST_TAG}..HEAD" --pretty=format:"%s" | wc -l) if [ "$UNRELEASED_COMMITS" -gt 0 ]; then echo "## [Unreleased]" >> "$TEMP_FILE" echo "" >> "$TEMP_FILE" generate_section_for_commits "HEAD" "$LATEST_TAG" >> "$TEMP_FILE" fi # Generate sections for each tag PREV_TAG="" TAGS_ARRAY=($TAGS) for i in "${!TAGS_ARRAY[@]}"; do TAG="${TAGS_ARRAY[$i]}" TAG_DATE=$(git log -1 --format="%ai" "$TAG" | cut -d' ' -f1) CLEAN_VERSION=$(echo "$TAG" | sed 's/^v//') echo "## [$CLEAN_VERSION] - $TAG_DATE" >> "$TEMP_FILE" echo "" >> "$TEMP_FILE" # Get the next (older) tag for the range if [ $((i + 1)) -lt ${#TAGS_ARRAY[@]} ]; then PREV_TAG="${TAGS_ARRAY[$((i + 1))]}" else PREV_TAG="" fi generate_section_for_commits "$TAG" "$PREV_TAG" >> "$TEMP_FILE" done fi # Replace the old changelog with the new one mv "$TEMP_FILE" "$CHANGELOG_FILE" echo "✅ CHANGELOG.md has been generated successfully!" echo "📄 Preview:" echo "----------------------------------------" head -20 "$CHANGELOG_FILE" echo "----------------------------------------"