Files
gh-dhofheinz-open-plugins-p…/commands/commit-best-practices/.scripts/revert-helper.sh
2025-11-29 18:20:25 +08:00

260 lines
7.7 KiB
Bash
Executable File

#!/usr/bin/env bash
################################################################################
# Revert Helper Script
#
# Purpose: Generate proper revert commit message and analyze safety
# Version: 1.0.0
# Usage: ./revert-helper.sh <commit-sha>
# Returns: JSON with revert information
# Exit Codes:
# 0 = Success
# 1 = Commit not found
# 2 = Script execution error
################################################################################
set -euo pipefail
################################################################################
# Parse conventional commit message
################################################################################
parse_commit_message() {
local subject="$1"
local type=""
local scope=""
local description=""
# Try to match conventional format: type(scope): description
if [[ "$subject" =~ ^([a-z]+)(\([a-z0-9\-]+\)):[[:space:]](.+)$ ]]; then
type="${BASH_REMATCH[1]}"
scope="${BASH_REMATCH[2]}" # includes parentheses
scope="${scope#(}" # remove leading (
scope="${scope%)}" # remove trailing )
description="${BASH_REMATCH[3]}"
# Try to match without scope: type: description
elif [[ "$subject" =~ ^([a-z]+):[[:space:]](.+)$ ]]; then
type="${BASH_REMATCH[1]}"
scope=""
description="${BASH_REMATCH[2]}"
else
# Non-conventional format
type=""
scope=""
description="$subject"
fi
echo "$type|$scope|$description"
}
################################################################################
# Generate revert commit message
################################################################################
generate_revert_message() {
local commit_sha="$1"
local original_subject="$2"
# Parse original message
local parsed
parsed=$(parse_commit_message "$original_subject")
local type
local scope
local description
IFS='|' read -r type scope description <<< "$parsed"
# Build revert message
local revert_subject
if [[ -n "$type" ]]; then
# Conventional format
if [[ -n "$scope" ]]; then
revert_subject="revert: $type($scope): $description"
else
revert_subject="revert: $type: $description"
fi
else
# Non-conventional format
revert_subject="revert: $original_subject"
fi
# Build full message (subject + body + footer)
local revert_message
revert_message=$(cat <<EOF
$revert_subject
This reverts commit $commit_sha.
Reason: [Provide reason for revert here]
EOF
)
echo "$revert_message"
}
################################################################################
# Analyze revert safety
################################################################################
analyze_revert_safety() {
local commit_sha="$1"
local safe_to_revert="true"
local warnings=()
# Check for dependent commits (commits that touch same files after this one)
local files_changed
files_changed=$(git show --name-only --format= "$commit_sha")
local dependent_count=0
local dependent_commits=()
if [[ -n "$files_changed" ]]; then
# Get commits after this one
local later_commits
later_commits=$(git log "$commit_sha"..HEAD --oneline --format='%h %s' || echo "")
if [[ -n "$later_commits" ]]; then
while IFS= read -r commit_line; do
local later_sha
later_sha=$(echo "$commit_line" | awk '{print $1}')
# Check if any files overlap
local later_files
later_files=$(git show --name-only --format= "$later_sha" 2>/dev/null || echo "")
# Check for file overlap
while IFS= read -r file; do
if [[ -n "$file" ]] && echo "$files_changed" | grep -qxF "$file"; then
dependent_commits+=("$commit_line")
((dependent_count++))
break
fi
done <<< "$later_files"
done <<< "$later_commits"
if [[ $dependent_count -gt 0 ]]; then
safe_to_revert="false"
warnings+=("\"$dependent_count commit(s) depend on this change\"")
fi
fi
fi
# Check if files still exist (if deleted, might be harder to revert)
local deleted_files=0
while IFS= read -r file; do
if [[ -n "$file" ]] && [[ ! -f "$file" ]]; then
((deleted_files++))
fi
done <<< "$files_changed"
if [[ $deleted_files -gt 0 ]]; then
warnings+=("\"$deleted_files file(s) from commit no longer exist\"")
fi
# Check for potential merge conflicts (files modified since commit)
local modified_files=0
while IFS= read -r file; do
if [[ -n "$file" ]] && [[ -f "$file" ]]; then
# Check if file has been modified since this commit
local file_changed
file_changed=$(git log "$commit_sha"..HEAD --oneline -- "$file" | wc -l)
if [[ $file_changed -gt 0 ]]; then
((modified_files++))
fi
fi
done <<< "$files_changed"
if [[ $modified_files -gt 0 ]]; then
warnings+=("\"$modified_files file(s) modified since commit - potential conflicts\"")
fi
# Format warnings array
local warnings_json="[]"
if [[ ${#warnings[@]} -gt 0 ]]; then
warnings_json="[$(IFS=,; echo "${warnings[*]}")]"
fi
echo "{\"safe_to_revert\": $safe_to_revert, \"warnings\": $warnings_json, \"dependent_count\": $dependent_count}"
}
################################################################################
# Main execution
################################################################################
main() {
# Check arguments
if [[ $# -lt 1 ]]; then
echo "{\"error\": \"Usage: revert-helper.sh <commit-sha>\"}"
exit 2
fi
local commit_sha="$1"
# Verify we're in a git repository
if ! git rev-parse --git-dir &>/dev/null; then
echo "{\"error\": \"Not a git repository\"}"
exit 2
fi
# Verify commit exists
if ! git rev-parse --verify "$commit_sha" &>/dev/null 2>&1; then
echo "{\"error\": \"Commit not found: $commit_sha\"}"
exit 1
fi
# Get full commit SHA
local full_sha
full_sha=$(git rev-parse "$commit_sha")
local short_sha
short_sha=$(git rev-parse --short "$commit_sha")
# Get commit information
local original_subject
local original_author
local commit_date
local files_affected
original_subject=$(git log -1 --format='%s' "$commit_sha")
original_author=$(git log -1 --format='%an <%ae>' "$commit_sha")
commit_date=$(git log -1 --format='%ad' --date=short "$commit_sha")
files_affected=$(git show --name-only --format= "$commit_sha" | wc -l)
# Parse commit type and scope
local parsed
parsed=$(parse_commit_message "$original_subject")
local type
local scope
local description
IFS='|' read -r type scope description <<< "$parsed"
# Generate revert message
local revert_message
revert_message=$(generate_revert_message "$short_sha" "$original_subject")
# Analyze safety
local safety_analysis
safety_analysis=$(analyze_revert_safety "$commit_sha")
# Build JSON output
cat <<EOF
{
"commit": "$short_sha",
"full_sha": "$full_sha",
"original_message": "$original_subject",
"original_author": "$original_author",
"commit_date": "$commit_date",
"type": ${type:+\"$type\"},
"scope": ${scope:+\"$scope\"},
"files_affected": $files_affected,
"revert_message": $(echo "$revert_message" | jq -Rs .),
"safety": $safety_analysis
}
EOF
exit 0
}
# Execute main function
main "$@"