Skip to content

Instantly share code, notes, and snippets.

@UncleBill
Created April 25, 2025 01:34
Show Gist options
  • Save UncleBill/2ae97ee0ccebe868ca397a3723a1e930 to your computer and use it in GitHub Desktop.
Save UncleBill/2ae97ee0ccebe868ca397a3723a1e930 to your computer and use it in GitHub Desktop.
Function to auto-generate commit messages with issue tag
#!/bin/bash
# Function to auto-generate commit messages with issue tag
autocommit() {
local ISSUE_TAG="" # Initialize variable
if [ -n "$1" ]; then
# Tag was provided as an argument
ISSUE_TAG="$1"
echo "Info: Using provided issue tag '$ISSUE_TAG'."
else
# Tag not provided, try to extract from branch name
local current_branch=$(git branch --show-current 2>/dev/null) # Get current branch, suppress stderr if not in a repo/branch
if [ -z "$current_branch" ]; then
echo "Error: Could not determine current git branch." >&2
echo "Please provide the issue tag manually or ensure you are on a branch." >&2
echo "Usage: autocommit [issue-tag]" >&2
return 1
fi
echo "Info: No issue tag provided, attempting to extract from branch '$current_branch'."
# Regex to find pattern like 'abc-123' after the first '/'
# - sed -n: suppress default output
# - s|.*/\([a-zA-Z]\+-[0-9]\+\).*|\1|p:
# - .*/ : Match anything up to the last '/' (greedy)
# - \([a-zA-Z]\+-[0-9]\+\) : Capture group 1: one or more letters, '-', one or more digits
# - .* : Match the rest of the string
# - \1 : Replace the whole line with the captured group
# - p : Print the result if substitution occurred
ISSUE_TAG=$(echo "$current_branch" | sed -n 's|.*/\([a-zA-Z]\+-[0-9]\+\).*|\1|p')
fi
# Store the issue tag
local ISSUE_TAG="$1"
# Get the git diff, both staged and unstaged
local GIT_DIFF=$(git diff head)
# Check if there are staged changes
if [ -z "$GIT_DIFF" ]; then
echo "Error: No staged changes found. Please stage your changes with 'git add' first."
return 1
fi
# The prompt template for generating commit messages
# Added a hint to keep the type lowercase as requested by the format
local PROMPT="Based on the following git diff, generate a concise and descriptive commit message.
Start the message with a conventional commit type (fix, feat, chore, refactor, docs, style, test, build, ci, perf, revert - keep it lowercase) followed by the issue tag.
The format should be: '<type>: ${ISSUE_TAG} <summary line based on git diff, the first word of the summary should be lowercase>'
Focus on WHAT changes were made and WHY, not HOW they were implemented.
Keep the summary line under 72 characters if possible, but prioritize clarity.
Do not include a body UNLESS ABSOLUTELY NECESSARY for clarity.
Here's the git diff:
${GIT_DIFF}"
echo "Generating..."
# Use smartcat to get AI-generated commit message
local COMMIT_MSG=$(echo "$PROMPT" | sc commit)
# Check if smartcat produced output
if [ -z "$COMMIT_MSG" ]; then
echo "Error: smartcat failed to generate a commit message."
return 1
fi
# Show the generated commit message to the user and ask for confirmation
echo -e "\nGenerated commit message:\n${COMMIT_MSG}\n"
echo -n "Do you want to use this commit message? \n(Enter: accept, Esc: cancel, y: accept, n: cancel, e: edit): "
# --- Single-Key Input Handling using stty and dd ---
local confirmation_char
local confirmation_result="" # Will store 'y', 'n', or 'e'
# Check if running in a terminal
if [ -t 0 ]; then
# Save current terminal settings
local old_settings=$(stty -g)
# Ensure settings are restored on exit, interrupt, or terminate
trap 'stty "$old_settings"; echo "";' EXIT INT TERM
# Set terminal to not echo input (-echo), non-canonical mode (-icanon)
# and return after reading 1 character (min 1)
stty -echo -icanon min 1
# Read a single byte from standard input using dd
# Redirect stderr to suppress dd's output like "1+0 records in"
confirmation_char=$(dd bs=1 count=1 2>/dev/null)
# Restore terminal settings immediately after reading the character
stty "$old_settings"
# Remove the trap now that settings are restored
trap - EXIT INT TERM
else
# Not running in a terminal, this input method won't work well.
# Default to cancel or handle as you see fit for non-interactive
echo "Warning: Not running in a terminal. Skipping interactive confirmation." >&2
confirmation_result="n" # Default to cancel in non-interactive
confirmation_char="" # Ensure case doesn't match unexpected input
fi
# Process the input character to determine the result ('y', 'n', or 'e')
if [ -n "$confirmation_char" ]; then # Only process if a character was read
case "$confirmation_char" in
$'\r'|$'\n'|"") # Enter key (CR or LF) or sometimes empty string
confirmation_result="y"
;;
$'\033') # Escape key
confirmation_result="n"
;;
[Yy]) # 'y' key
confirmation_result="y"
;;
[Nn]) # 'n' key
confirmation_result="n"
;;
[Ee]) # 'e' key
confirmation_result="e"
;;
*) # Any other key - treat as cancel for safety
confirmation_result="n"
;;
esac
else
confirmation_result="y"
fi
# Note: if not in a terminal, confirmation_result is already 'n'
# Provide feedback by printing the action result and a newline
echo "$confirmation_result"
# A second newline here ensures the next prompt/output is clearly separated
echo ""
# --- Act on the confirmation_result ---
if [[ "$confirmation_result" == "y" ]]; then
git commit -m "$COMMIT_MSG"
echo "Changes committed successfully!"
elif [[ "$confirmation_result" == "e" ]]; then
# Create a temporary file with the commit message
# Use mktemp with a template for better security and uniqueness
local TEMP_FILE=$(mktemp /tmp/autocommit_edit_XXXXXX)
if [ ! -f "$TEMP_FILE" ]; then
echo "Error: Could not create temporary file for editing." >&2
return 1
fi
echo "$COMMIT_MSG" > "$TEMP_FILE"
# Open the file in the default editor ($EDITOR or vi)
local editor="${EDITOR:-vi}"
if ! command -v "$editor" &> /dev/null; then
echo "Error: Editor '$editor' not found. Cannot edit message." >&2
rm "$TEMP_FILE" # Clean up temp file
return 1
fi
echo "Opening editor for commit message..."
# Run the editor
"$editor" "$TEMP_FILE"
# Read the edited message, check if file is empty after editing
local EDITED_MSG
if [ -s "$TEMP_FILE" ]; then # -s checks if file exists and is not empty
EDITED_MSG=$(cat "$TEMP_FILE")
else
EDITED_MSG=""
fi
# Clean up temp file
rm "$TEMP_FILE"
# Commit with the edited message if it's not empty
if [ -n "$EDITED_MSG" ]; then
git commit -m "$EDITED_MSG"
echo "Changes committed successfully with edited message!"
else
echo "Commit canceled - empty commit message after editing."
fi
else # confirmation_result is "n"
echo "Commit canceled."
fi
}
@nicejade
Copy link

NICE WORK.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment