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 # Colors for output
CYAN='\033[0;36m' CYAN='\033[0;36m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m' GREEN='\033[0;32m'
RED='\033[0;31m' RED='\033[0;31m'
BLUE='\033[0;34m'
NOCOLOR='\033[0m' NOCOLOR='\033[0m'
# Get the directory where this hook is located # Run tests before push
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
echo -e "\n${CYAN}Running tests...${NOCOLOR}" echo -e "\n${CYAN}Running tests...${NOCOLOR}"
go test ./... # Runs all tests in your repo go test ./... # Runs all tests in your repo
status=$? status=$?

View File

@ -13,6 +13,21 @@ The format is based on [Keep a Changelog][keepachangelog] and adheres to [Semant
### Deprecated ### Deprecated
### Fixed ### 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 ## [0.53.16] - 2025-11-03
### Added ### 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 .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) COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ) DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)' LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)'

View File

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