SourceTree Ai Commit message A developer created a SourceTree custom action that uses Anthropic's Claude CLI to automatically generate commit messages from staged diffs. The script enforces Conventional Commits format, truncates large diffs, and presents the message in a dialog for review before committing. | /bin/bash | | | SourceTree Custom Action: generates a commit message with Claude from the | | | staged files and commits directly. | | | Usage from SourceTree: Parameter = $REPO | | | set -euo pipefail | | | GUI apps on macOS SourceTree included start with a minimal PATH, so we | | | need to manually add the paths where the claude binary might live. | | | export PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$PATH" | | | REPO="${1:-}" | | | if -z "$REPO" ; then | | | echo "Error: missing repository path use \$REPO as the Custom Action parameter ." | | | exit 1 | | | fi | | | cd "$REPO" | | | if git diff --cached --quiet; then | | | echo "No staged files. Run 'git add' on your changes before running this action." | | | exit 1 | | | fi | | | CACHE DIR="$HOME/.cache/sourcetree-ai-commit" | | | mkdir -p "$CACHE DIR" | | | CACHE KEY=$ printf '%s' "$REPO" | tr '/' ' ' | | | CACHE FILE="$CACHE DIR/${CACHE KEY}.txt" | | | generate message { | | | if command -v claude /dev/null 2 &1; then | | | echo "Error: 'claude' CLI not found in PATH." | | | exit 1 | | | fi | | | STAT=$ git diff --cached --stat | | | RAW DIFF=$ git diff --cached -- . \ | | | ': exclude .lock' \ | | | ': exclude .resolved' \ | | | ': exclude .xcworkspace/ ' \ | | | ': exclude .pbxproj' | | | Truncate with bash parameter expansion instead of "| head -c": with | | | pipefail enabled, head -c closes the pipe as soon as it has enough | | | bytes, git diff gets SIGPIPE and the whole pipeline "fails" exit 141 , | | | killing the script with no output because of set -e . | | | DIFF="${RAW DIFF:0:12000}" | | | PROMPT="You are a commit message generator. Analyze the staged diff and return ONLY the commit message, with no explanations and no surrounding quotes. | | | Rules: | | | - Conventional Commits format feat, fix, refactor, chore, docs, test, style, perf followed by ':'. | | | - English, imperative mood, subject line max 72 characters. | | | - If the change warrants it, add a short bulleted body after a blank line explaining 'why', not 'what'. | | | - If there are several unrelated changes, prioritize the most relevant one in the subject. | | | - Plain text only: no markdown whatsoever — no backticks, no bold/italic with or , no headers, no code fences. Write identifiers class/function names, etc. as plain words, not wrapped in special quoting. | | | File summary: | | | ${STAT} | | | Diff: | | | ${DIFF}" | | | MESSAGE=$ claude -p "$PROMPT" --output-format text 2 /dev/null | sed -e '/^ :space: $/d' -e '1{/^ /d;}' -e '${/^ $/d;}' -e 's/ //g' | | | if -z "$MESSAGE" ; then | | | echo "Error: Claude did not return a message." | | | exit 1 | | | fi | | | } | | | if -f "$CACHE FILE" ; then | | | MESSAGE=$ <"$CACHE FILE" | | | else | | | generate message | | | fi | | | escape for applescript { | | | local input="$1" | | | input="${input//\\/\\\\}" | | | input="${input//\"/\\\"}" | | | printf '%s' "$input" | | | } | | | while true; do | | | ESCAPED MESSAGE=$ escape for applescript "$MESSAGE" | | | AppleScript doesn't interpret "\n" inside a string: each line break must be | | | concatenated explicitly as the ASCII 10 character between literals. | | | AS DEFAULT ANSWER=$ printf '%s' "$ESCAPED MESSAGE" | awk 'BEGIN{ORS=""} {if NR 1 printf " & ASCII character 10 & "; printf "\"%s\"", $0}' | | | set +e | | | DIALOG RESULT=$ osascript <