diff --git a/scripts/aspell-pws b/scripts/aspell-pws index c5db81fdd..1094a19e9 100644 --- a/scripts/aspell-pws +++ b/scripts/aspell-pws @@ -350,3 +350,7 @@ typedef BitInt noreturn pragma +ollama +qwen +aicommit +LLM diff --git a/scripts/prepare-commit-msg.hook b/scripts/prepare-commit-msg.hook index 207795e40..a29f3e70d 100755 --- a/scripts/prepare-commit-msg.hook +++ b/scripts/prepare-commit-msg.hook @@ -4,7 +4,7 @@ COMMIT_MSG_FILE="$1" # If the commit message file already contains non-comment lines, do nothing. if grep -qE '^[^[:space:]#]' "$COMMIT_MSG_FILE"; then - exit 0 + exit 0 fi # Gather a list of staged (changed) files. @@ -34,6 +34,250 @@ INLINE_MSG=$(cat <<'EOF' EOF ) +# AICommit uses an LLM (via ollama) to generate commit messages that match git +# commit style by learning from previous commits. +# Inspired by https://github.com/acrosa/aicommits. +# +# Configuration options: +# - aicommit.temperature: Controls the randomness of the generated text (default: 0.3) +# Example: git config --global aicommit.temperature 0.5 +# - aicommit.show-prompt: Show the full prompts used for generation (default: false) +# Example: git config --global aicommit.show-prompt true +# - core.aicommit: Control when to use AI commit ('always', 'auto', or 'never', default: 'auto') +# Example: git config --global core.aicommit always + +MODEL="qwen2.5-coder" +TEMPERATURE=$(git config --get aicommit.temperature || echo 0.3) +SHOW_AI_COMMIT_PROMPT=$(git config --get aicommit.show-prompt || echo "false") +SUGGESTED_COMMITMSG= +AICOMMIT=$(git config --get core.aicommit || echo 'auto') +if [[ "$AICOMMIT" == "always" ]] || [[ "$AICOMMIT" == "auto" && -t 1 ]] && \ + git diff --cached --name-only | grep -qiE "\.(c|h|cpp|hpp)$"; then + # Build commit history list from the last non-merge commit messages. + commit_history=$( + { + echo '---' + git log -n 50 --no-merges --pretty=format:'%B%n---' + } | sed -E 's/ \(\#[0-9]+\)//; /^Change-Id:/d' | awk ' +function print_block(block) { + sub(/\n+$/, "", block) + n = split(block, a, "\n") + subject = a[1] + body = "" + for (i = 2; i <= n; i++) { + if (a[i] ~ /[^[:space:]]/) + body = body (body ? "\n" : "") a[i] + } + print "" subject "" + print "" body "" + print "---" +} +{ + if ($0=="---") { + if (block != "") { print_block(block); block = "" } + } else { + block = block $0 "\n" + } +} +END { + if (block != "") print_block(block) +}' + ) + + # Capture the staged diff. + staged_diff=$(git diff --cached) + + # Create a style prompt from commit history. + style_prompt=" +You are a specialized system for generating high-quality Git commit messages based on 'git diff --cached' output and optional developer descriptions. +# Task: +Analyze the following commit messages and produce **accurate, concise, and meaningful commit messages** that clearly describe the changes: (Format: '---' separates messages, ... and ... tags) +$commit_history + +# Output: +Provide a concise description of the style without quoting commit content. +" + echo "Running ollama to set temperature to $TEMPERATURE (You can set aicommit.temperature git config to change it)" + echo "/set parameter temperature $TEMPERATURE" + echo "Running ollama for style analysis... (You can set aicommit.show-prompt git config to see full prompt)" + style_description=$(echo "$style_prompt" | ollama run "$MODEL") + + # Build the commit message prompt. + prompt=" +# Context: +Style: $style_description + +# Instructions: Follow these carefully to generate a high-quality commit message. MUST be concise, style-consistent, and under length limits. + +- Generate a commit message that consists of a Subject Line and a Body, separated by a blank line. +- Analyze the diff below and generate a commit message based solely on its content. +- **Crucially, mimic the style** described above (tone, length, structure) without copying previous messages. **Pay close attention to maintaining consistency with the established style.**- Use clear action verbs and be concise. +- Output ONLY the commit message (subject, a blank line, then body). +- Separate the subject from the body with a blank line. +- Remove triple backticks; replace backticks with single quotes. +- Keep the first line (subject) under 50 characters +- Ensure no line exceeds 72 characters. +- Avoid vague messages like 'Updates' or 'Fixed bug' +- Respond in **plain text only**. No markdown, HTML, JSON, code blocks, or special formatting characters. +- Use only standard punctuation and paragraph breaks. +- No concluding remarks. +- Do NOT use conventional commit prefixes (like 'feat:', 'fix:', 'docs:') +- Avoid the redundant message like 'Updated commit messages' +- Directly output your commit message, without additional explanations. +- Avoid using ### or other markdown formatting. + +# Diff: +$staged_diff + +Commit message:" + # Show prompts if aicommit.show-prompt is set to true + if [ "$SHOW_AI_COMMIT_PROMPT" = "true" ]; then + echo "Full style prompt:" + echo "$style_prompt" + echo "Extracted style:" + echo "$style_description" + echo "Full commit prompt:" + echo "$prompt" + fi + + # Generate commit message using ollama. + echo "Running ollama for commit message... " + SUGGESTED_COMMITMSG=$(echo "$prompt" | ollama run "$MODEL") + + # Post-process the commit message. + # - Trim whitespace. + # - Remove triple backticks. + # - Replace backticks with single quotes. + SUGGESTED_COMMITMSG=$(echo "$SUGGESTED_COMMITMSG" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') + SUGGESTED_COMMITMSG=$(echo "$SUGGESTED_COMMITMSG" | sed -E '/^(Author:|Date:|Commit message:|commit )/d') + SUGGESTED_COMMITMSG=$(echo "$SUGGESTED_COMMITMSG" | sed -E '/^```(markdown|diff|text|plaintext)?$/d; s/\*\*([^*]+)\*\*/\1/g; s/`([^`]+)`/'\''\1'\''/g') + + # Extract the subject line (first line) and body + subject_line=$(echo "$SUGGESTED_COMMITMSG" | head -n 1) + body=$(echo "$SUGGESTED_COMMITMSG" | tail -n +3) # Skip the first line and the blank line + + # Check if the subject line is too long + if [ ${#subject_line} -gt 50 ]; then + echo "Subject line too long (${#subject_line} chars), will attempt to regenerate up to 10 times..." + + # Try up to 10 times to generate a subject line under 50 characters + max_attempts=10 + attempt=1 + original_subject="$subject_line" + + while [ $attempt -le $max_attempts ] && [ ${#subject_line} -gt 50 ]; do + echo "Attempt $attempt of $max_attempts to generate a shorter subject line..." + + # Generate a new subject line based on the body and previous attempts + subject_prompt=" +# Context +Original subject (${#original_subject} chars): +$original_subject + +Previous attempt (${#subject_line} chars): +$subject_line + +Body: +$body + +# Instructions: +- The previous commit message's subject line was too long, exceeding 50 characters. Your task is to shorten it to 50 characters or less. +- Based on this commit message, create ONLY a subject line for the commit message +- The subject line MUST be under 50 characters (this is attempt $attempt of $max_attempts) +- Use imperative mood (e.g., 'Add' not 'Added') +- Capitalize the first word +- Do not end with a period +- Be specific but concise +- Use clear action verbs +- Avoid vague terms like 'Update' or 'Fix' without context +- Output ONLY the subject line, nothing else +- Do NOT use conventional commit prefixes (like 'feat:', 'fix:', 'docs:') +- Use plain text without markdown or HTML + +# Output:" + + echo "Running ollama for new subject line (attempt $attempt)... " + new_subject_line=$(echo "$subject_prompt" | ollama run "$MODEL") + + # Clean up the new subject line + new_subject_line=$(echo "$new_subject_line" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') + new_subject_line=$(echo "$new_subject_line" | sed -E '/^(Author:|Date:|Subject line:)/d') + new_subject_line=$(echo "$new_subject_line" | sed -E '/^```(markdown|diff|text|plaintext)?$/d; s/\*\*([^*]+)\*\*/\1/g; s/`([^`]+)`/'\''\1'\''/g') + + # Update the subject line + subject_line="$new_subject_line" + + # Check if the new subject line is under 50 characters + if [ ${#subject_line} -le 50 ]; then + echo "Success! Generated a subject line under 50 characters (${#subject_line} chars)." + break + else + echo "Attempt $attempt failed: Subject line still too long (${#subject_line} chars)." + fi + + attempt=$((attempt + 1)) + done + + # If we've tried 3 times and still have a long subject line, inform the user + if [ ${#subject_line} -gt 50 ]; then + echo "Warning: After $max_attempts attempts, the subject line is still too long (${#subject_line} chars)." + echo "You may want to edit it manually to comply with the 50-character limit." + fi + fi + + # Combine the (possibly new) subject line with the original body + SUGGESTED_COMMITMSG="$subject_line + +$body" + + # Show final subject line if aicommit.show-prompt is set to true + if [ "$SHOW_AI_COMMIT_PROMPT" = "true" ]; then + echo "Final subject line (${#subject_line} chars):" + echo "$subject_line" + fi + + # Wrap lines at 72 characters + SUGGESTED_COMMITMSG="$( + echo "$SUGGESTED_COMMITMSG" \ + | sed -E '/^```(markdown|diff|text|plaintext)?$/d; s/\*\*([^*]+)\*\*/\1/g; s/`([^`]+)`/'\''\1'\''/g' \ + | awk -v w=72 ' +function sp(n){ return sprintf("%" n "s", "") } +function wrap(bp, txt){ + gsub(/^ +| +$/,"",txt) + if(!length(txt)){ print bp; return } + n = split(txt, a, /[ \t]+/) + l = bp; len = length(bp) + for(i = 1; i <= n; i++){ + wl = length(a[i]) + if((len > length(bp) ? len + 1 : len) + wl > w){ + print l; l = sp(length(bp)) a[i]; len = length(bp) + wl + } else if(len == length(bp)){ + l = bp a[i]; len = length(bp) + wl + } else { + l = l " " a[i]; len++; len += wl + } + } + if(len > length(bp)) print l +} +BEGIN { paragraph = ""; bullet = "" } +{ + line = $0; gsub(/^ +| +$/, "", line) + if(!length(line)){ + if(length(paragraph)){ wrap(bullet, paragraph); paragraph = ""; bullet = "" } + print ""; next + } + if(match(line, /^( *[0-9]+\.[ \t]+| *-[ \t]+| *\*[ \t]+)/)){ + if(length(paragraph)){ wrap(bullet, paragraph); paragraph = ""; bullet = "" } + bp = substr(line, RSTART, RLENGTH); rest = substr(line, RSTART + RLENGTH) + gsub(/^[ \t]+/, "", rest); wrap(bp, rest) + } else { + if(!length(paragraph)) paragraph = line; else paragraph = paragraph " " line + } +} +END { if(length(paragraph)) wrap(bullet, paragraph) } +')" +fi + # Write an empty line, the guidelines, and the changed files into the commit message. { echo @@ -44,6 +288,11 @@ EOF else echo "# (No staged files detected.)" fi + if [ -n "$SUGGESTED_COMMITMSG" ]; then + echo "#" + echo "# âś…Suggested commit messages:" + echo "$SUGGESTED_COMMITMSG" | sed 's/^/# /' + fi } > "$COMMIT_MSG_FILE" # Prompt the user about aborting the commit.