feat: add pre-commit hook for automatic changelog updates

- Introduced a pre-commit hook that updates the changelog if there are code changes, excluding commits that only modify the changelog or Makefile.
- Added user confirmation for proceeding with the commit after displaying the changelog preview.
- Enhanced the update_changelog.sh script to differentiate between pre-commit and pre-push contexts for better change analysis.
This commit is contained in:
anonpenguin23 2025-11-03 07:34:42 +02:00
parent d3543ac3ab
commit 11ce4f2a53
5 changed files with 186 additions and 113 deletions

89
.githooks/pre-commit Normal file
View File

@ -0,0 +1,89 @@
#!/bin/bash
# Colors for output
CYAN='\033[0;36m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NOCOLOR='\033[0m'
# Get the directory where this hook is located
HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Go up from .git/hooks/ to repo root
REPO_ROOT="$(cd "$HOOK_DIR/../.." && pwd)"
CHANGELOG_SCRIPT="$REPO_ROOT/scripts/update_changelog.sh"
PREVIEW_FILE="$REPO_ROOT/.changelog_preview.tmp"
VERSION_FILE="$REPO_ROOT/.changelog_version.tmp"
# Only run changelog update if there are actual code changes (not just changelog files)
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
if [ -z "$STAGED_FILES" ]; then
# No staged files, exit
exit 0
fi
# Check if only CHANGELOG.md and/or Makefile are being committed
OTHER_FILES=$(echo "$STAGED_FILES" | grep -v "^CHANGELOG.md$" | grep -v "^Makefile$")
if [ -z "$OTHER_FILES" ]; then
# Only changelog files are being committed, skip update
exit 0
fi
# Update changelog before commit
if [ -f "$CHANGELOG_SCRIPT" ]; then
echo -e "\n${CYAN}Updating changelog...${NOCOLOR}"
# Set environment variable to indicate we're running from pre-commit
export CHANGELOG_CONTEXT=pre-commit
bash "$CHANGELOG_SCRIPT"
changelog_status=$?
if [ $changelog_status -ne 0 ]; then
echo -e "${RED}Commit aborted: changelog update failed.${NOCOLOR}"
exit 1
fi
# Show preview if changelog was updated
if [ -f "$PREVIEW_FILE" ] && [ -f "$VERSION_FILE" ]; then
NEW_VERSION=$(cat "$VERSION_FILE")
PREVIEW_CONTENT=$(cat "$PREVIEW_FILE")
echo ""
echo -e "${BLUE}========================================================================${NOCOLOR}"
echo -e "${CYAN} CHANGELOG PREVIEW${NOCOLOR}"
echo -e "${BLUE}========================================================================${NOCOLOR}"
echo ""
echo -e "${GREEN}New Version: ${YELLOW}$NEW_VERSION${NOCOLOR}"
echo ""
echo -e "${CYAN}Changelog Entry:${NOCOLOR}"
echo -e "${BLUE}────────────────────────────────────────────────────────────────────────${NOCOLOR}"
echo -e "$PREVIEW_CONTENT"
echo -e "${BLUE}────────────────────────────────────────────────────────────────────────${NOCOLOR}"
echo ""
echo -e "${YELLOW}Do you want to proceed with the commit? (yes/no):${NOCOLOR} "
# Read from /dev/tty to ensure we can read from terminal even in git hook context
read -r confirmation < /dev/tty
if [ "$confirmation" != "yes" ]; then
echo -e "${RED}Commit aborted by user.${NOCOLOR}"
echo -e "${YELLOW}To revert changes, run:${NOCOLOR}"
echo -e " git checkout CHANGELOG.md Makefile"
# Clean up temp files
rm -f "$PREVIEW_FILE" "$VERSION_FILE"
exit 1
fi
echo -e "${GREEN}Proceeding with commit...${NOCOLOR}"
# Add the updated CHANGELOG.md and Makefile to the current commit
echo -e "${CYAN}Staging CHANGELOG.md and Makefile...${NOCOLOR}"
git add CHANGELOG.md Makefile
# Clean up temp files
rm -f "$PREVIEW_FILE" "$VERSION_FILE"
fi
else
echo -e "${YELLOW}Warning: changelog update script not found at $CHANGELOG_SCRIPT${NOCOLOR}"
fi

View File

@ -2,81 +2,11 @@
# Colors for output
CYAN='\033[0;36m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NOCOLOR='\033[0m'
# Get the directory where this hook is located
HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Go up from .git/hooks/ to repo root
REPO_ROOT="$(cd "$HOOK_DIR/../.." && pwd)"
CHANGELOG_SCRIPT="$REPO_ROOT/scripts/update_changelog.sh"
PREVIEW_FILE="$REPO_ROOT/.changelog_preview.tmp"
VERSION_FILE="$REPO_ROOT/.changelog_version.tmp"
# Update changelog before push
if [ -f "$CHANGELOG_SCRIPT" ]; then
echo -e "\n${CYAN}Updating changelog...${NOCOLOR}"
bash "$CHANGELOG_SCRIPT"
changelog_status=$?
if [ $changelog_status -ne 0 ]; then
echo -e "${RED}Push aborted: changelog update failed.${NOCOLOR}"
exit 1
fi
# Show preview if changelog was updated
if [ -f "$PREVIEW_FILE" ] && [ -f "$VERSION_FILE" ]; then
NEW_VERSION=$(cat "$VERSION_FILE")
PREVIEW_CONTENT=$(cat "$PREVIEW_FILE")
echo ""
echo -e "${BLUE}========================================================================${NOCOLOR}"
echo -e "${CYAN} CHANGELOG PREVIEW${NOCOLOR}"
echo -e "${BLUE}========================================================================${NOCOLOR}"
echo ""
echo -e "${GREEN}New Version: ${YELLOW}$NEW_VERSION${NOCOLOR}"
echo ""
echo -e "${CYAN}Changelog Entry:${NOCOLOR}"
echo -e "${BLUE}────────────────────────────────────────────────────────────────────────${NOCOLOR}"
echo -e "$PREVIEW_CONTENT"
echo -e "${BLUE}────────────────────────────────────────────────────────────────────────${NOCOLOR}"
echo ""
echo -e "${YELLOW}Do you want to proceed with the push? (yes/no):${NOCOLOR} "
# Read from /dev/tty to ensure we can read from terminal even in git hook context
read -r confirmation < /dev/tty
if [ "$confirmation" != "yes" ]; then
echo -e "${RED}Push aborted by user.${NOCOLOR}"
echo -e "${YELLOW}To revert changes, run:${NOCOLOR}"
echo -e " git checkout CHANGELOG.md Makefile"
# Clean up temp files
rm -f "$PREVIEW_FILE" "$VERSION_FILE"
exit 1
fi
echo -e "${GREEN}Proceeding with push...${NOCOLOR}"
# Commit the updated CHANGELOG.md and Makefile
cd "$REPO_ROOT"
echo -e "${CYAN}Staging CHANGELOG.md and Makefile...${NOCOLOR}"
git add CHANGELOG.md Makefile
echo -e "${CYAN}Committing changes...${NOCOLOR}"
NEW_VERSION=$(cat "$VERSION_FILE")
git commit -m "chore: update changelog and version to $NEW_VERSION" || {
# If commit fails (e.g., no changes to commit), continue anyway
echo -e "${YELLOW}Note: Could not commit changes (may already be committed)${NOCOLOR}"
}
# Clean up temp files
rm -f "$PREVIEW_FILE" "$VERSION_FILE"
fi
else
echo -e "${YELLOW}Warning: changelog update script not found at $CHANGELOG_SCRIPT${NOCOLOR}"
fi
# Run tests before push
echo -e "\n${CYAN}Running tests...${NOCOLOR}"
go test ./... # Runs all tests in your repo
status=$?

View File

@ -13,6 +13,21 @@ The format is based on [Keep a Changelog][keepachangelog] and adheres to [Semant
### Deprecated
### Fixed
## [0.53.17] - 2025-11-03
### Added
- Added a new Git `pre-commit` hook to automatically update the changelog and version before committing, ensuring version consistency.
### Changed
- Refactored the `update_changelog.sh` script to support different execution contexts (pre-commit vs. pre-push), allowing it to analyze only staged changes during commit.
- The Git `pre-push` hook was simplified by removing the changelog update logic, which is now handled by the `pre-commit` hook.
### Deprecated
### Removed
### Fixed
\n
## [0.53.16] - 2025-11-03
### Added

View File

@ -21,7 +21,7 @@ test-e2e:
.PHONY: build clean test run-node run-node2 run-node3 run-example deps tidy fmt vet lint clear-ports install-hooks
VERSION := 0.53.16
VERSION := 0.53.17
COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)'

View File

@ -93,47 +93,76 @@ fi
# Gather all git diffs
log "Collecting git diffs..."
# Unstaged changes
UNSTAGED_DIFF=$(git diff 2>/dev/null || echo "")
UNSTAGED_COUNT=$(echo "$UNSTAGED_DIFF" | grep -c "^diff\|^index" 2>/dev/null || echo "0")
[ -z "$UNSTAGED_COUNT" ] && UNSTAGED_COUNT="0"
# Staged changes
STAGED_DIFF=$(git diff --cached 2>/dev/null || echo "")
STAGED_COUNT=$(echo "$STAGED_DIFF" | grep -c "^diff\|^index" 2>/dev/null || echo "0")
[ -z "$STAGED_COUNT" ] && STAGED_COUNT="0"
# Unpushed commits
UNPUSHED_DIFF=$(git diff "$REMOTE_BRANCH"..HEAD 2>/dev/null || echo "")
UNPUSHED_COMMITS=$(git rev-list --count "$REMOTE_BRANCH"..HEAD 2>/dev/null || echo "0")
[ -z "$UNPUSHED_COMMITS" ] && UNPUSHED_COMMITS="0"
# Check if the only unpushed commit is a changelog update commit
# If so, exclude it from the diff to avoid infinite loops
if [ "$UNPUSHED_COMMITS" -gt 0 ]; then
LATEST_COMMIT_MSG=$(git log -1 --pretty=%B HEAD 2>/dev/null || echo "")
if echo "$LATEST_COMMIT_MSG" | grep -q "chore: update changelog and version"; then
# If the latest commit is a changelog commit, check if there are other commits
if [ "$UNPUSHED_COMMITS" -eq 1 ]; then
log "Latest commit is a changelog update. No other changes detected. Skipping changelog update."
# Clean up any old preview files
rm -f "$REPO_ROOT/.changelog_preview.tmp" "$REPO_ROOT/.changelog_version.tmp"
exit 0
else
# Multiple commits, exclude the latest changelog commit from diff
log "Multiple unpushed commits detected. Excluding latest changelog commit from analysis."
# Get all commits except the latest one
UNPUSHED_DIFF=$(git diff "$REMOTE_BRANCH"..HEAD~1 2>/dev/null || echo "")
UNPUSHED_COMMITS=$(git rev-list --count "$REMOTE_BRANCH"..HEAD~1 2>/dev/null || echo "0")
[ -z "$UNPUSHED_COMMITS" ] && UNPUSHED_COMMITS="0"
# Check if running from pre-commit context
if [ "$CHANGELOG_CONTEXT" = "pre-commit" ]; then
log "Running in pre-commit context - analyzing staged changes only"
# Unstaged changes (usually none in pre-commit, but check anyway)
UNSTAGED_DIFF=$(git diff 2>/dev/null || echo "")
UNSTAGED_COUNT=$(echo "$UNSTAGED_DIFF" | grep -c "^diff\|^index" 2>/dev/null || echo "0")
[ -z "$UNSTAGED_COUNT" ] && UNSTAGED_COUNT="0"
# Staged changes (these are what we're committing)
STAGED_DIFF=$(git diff --cached 2>/dev/null || echo "")
STAGED_COUNT=$(echo "$STAGED_DIFF" | grep -c "^diff\|^index" 2>/dev/null || echo "0")
[ -z "$STAGED_COUNT" ] && STAGED_COUNT="0"
# No unpushed commits analysis in pre-commit context
UNPUSHED_DIFF=""
UNPUSHED_COMMITS="0"
log "Found: $UNSTAGED_COUNT unstaged file(s), $STAGED_COUNT staged file(s)"
else
# Pre-push context - analyze everything
# Unstaged changes
UNSTAGED_DIFF=$(git diff 2>/dev/null || echo "")
UNSTAGED_COUNT=$(echo "$UNSTAGED_DIFF" | grep -c "^diff\|^index" 2>/dev/null || echo "0")
[ -z "$UNSTAGED_COUNT" ] && UNSTAGED_COUNT="0"
# Staged changes
STAGED_DIFF=$(git diff --cached 2>/dev/null || echo "")
STAGED_COUNT=$(echo "$STAGED_DIFF" | grep -c "^diff\|^index" 2>/dev/null || echo "0")
[ -z "$STAGED_COUNT" ] && STAGED_COUNT="0"
# Unpushed commits
UNPUSHED_DIFF=$(git diff "$REMOTE_BRANCH"..HEAD 2>/dev/null || echo "")
UNPUSHED_COMMITS=$(git rev-list --count "$REMOTE_BRANCH"..HEAD 2>/dev/null || echo "0")
[ -z "$UNPUSHED_COMMITS" ] && UNPUSHED_COMMITS="0"
# Check if the only unpushed commit is a changelog update commit
# If so, exclude it from the diff to avoid infinite loops
if [ "$UNPUSHED_COMMITS" -gt 0 ]; then
LATEST_COMMIT_MSG=$(git log -1 --pretty=%B HEAD 2>/dev/null || echo "")
if echo "$LATEST_COMMIT_MSG" | grep -q "chore: update changelog and version"; then
# If the latest commit is a changelog commit, check if there are other commits
if [ "$UNPUSHED_COMMITS" -eq 1 ]; then
log "Latest commit is a changelog update. No other changes detected. Skipping changelog update."
# Clean up any old preview files
rm -f "$REPO_ROOT/.changelog_preview.tmp" "$REPO_ROOT/.changelog_version.tmp"
exit 0
else
# Multiple commits, exclude the latest changelog commit from diff
log "Multiple unpushed commits detected. Excluding latest changelog commit from analysis."
# Get all commits except the latest one
UNPUSHED_DIFF=$(git diff "$REMOTE_BRANCH"..HEAD~1 2>/dev/null || echo "")
UNPUSHED_COMMITS=$(git rev-list --count "$REMOTE_BRANCH"..HEAD~1 2>/dev/null || echo "0")
[ -z "$UNPUSHED_COMMITS" ] && UNPUSHED_COMMITS="0"
fi
fi
fi
log "Found: $UNSTAGED_COUNT unstaged file(s), $STAGED_COUNT staged file(s), $UNPUSHED_COMMITS unpushed commit(s)"
fi
log "Found: $UNSTAGED_COUNT unstaged file(s), $STAGED_COUNT staged file(s), $UNPUSHED_COMMITS unpushed commit(s)"
# Combine all diffs
ALL_DIFFS="${UNSTAGED_DIFF}
if [ "$CHANGELOG_CONTEXT" = "pre-commit" ]; then
ALL_DIFFS="${UNSTAGED_DIFF}
---
STAGED CHANGES:
---
${STAGED_DIFF}"
else
ALL_DIFFS="${UNSTAGED_DIFF}
---
STAGED CHANGES:
---
@ -142,13 +171,23 @@ ${STAGED_DIFF}
UNPUSHED COMMITS:
---
${UNPUSHED_DIFF}"
fi
# Check if there are any changes
if [ -z "$(echo "$UNSTAGED_DIFF$STAGED_DIFF$UNPUSHED_DIFF" | tr -d '[:space:]')" ]; then
log "No changes detected (unstaged, staged, or unpushed). Skipping changelog update."
# Clean up any old preview files
rm -f "$REPO_ROOT/.changelog_preview.tmp" "$REPO_ROOT/.changelog_version.tmp"
exit 0
if [ "$CHANGELOG_CONTEXT" = "pre-commit" ]; then
# In pre-commit, only check staged changes
if [ -z "$(echo "$STAGED_DIFF" | tr -d '[:space:]')" ]; then
log "No staged changes detected. Skipping changelog update."
rm -f "$REPO_ROOT/.changelog_preview.tmp" "$REPO_ROOT/.changelog_version.tmp"
exit 0
fi
else
# In pre-push, check all changes
if [ -z "$(echo "$UNSTAGED_DIFF$STAGED_DIFF$UNPUSHED_DIFF" | tr -d '[:space:]')" ]; then
log "No changes detected (unstaged, staged, or unpushed). Skipping changelog update."
rm -f "$REPO_ROOT/.changelog_preview.tmp" "$REPO_ROOT/.changelog_version.tmp"
exit 0
fi
fi
# Get current version from Makefile