auto-commit.sh 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. #!/usr/bin/env bash
  2. # Git extension: auto-commit.sh
  3. # Automatically commit changes after a Spec Kit command completes.
  4. # Checks per-command config keys in git-config.yml before committing.
  5. #
  6. # Usage: auto-commit.sh <event_name>
  7. # e.g.: auto-commit.sh after_specify
  8. set -e
  9. EVENT_NAME="${1:-}"
  10. if [ -z "$EVENT_NAME" ]; then
  11. echo "Usage: $0 <event_name>" >&2
  12. exit 1
  13. fi
  14. SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  15. _find_project_root() {
  16. local dir="$1"
  17. while [ "$dir" != "/" ]; do
  18. if [ -d "$dir/.specify" ] || [ -d "$dir/.git" ]; then
  19. echo "$dir"
  20. return 0
  21. fi
  22. dir="$(dirname "$dir")"
  23. done
  24. return 1
  25. }
  26. REPO_ROOT=$(_find_project_root "$SCRIPT_DIR") || REPO_ROOT="$(pwd)"
  27. cd "$REPO_ROOT"
  28. # Check if git is available
  29. if ! command -v git >/dev/null 2>&1; then
  30. echo "[specify] Warning: Git not found; skipped auto-commit" >&2
  31. exit 0
  32. fi
  33. if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
  34. echo "[specify] Warning: Not a Git repository; skipped auto-commit" >&2
  35. exit 0
  36. fi
  37. # Read per-command config from git-config.yml
  38. _config_file="$REPO_ROOT/.specify/extensions/git/git-config.yml"
  39. _enabled=false
  40. _commit_msg=""
  41. if [ -f "$_config_file" ]; then
  42. # Parse the auto_commit section for this event.
  43. # Look for auto_commit.<event_name>.enabled and .message
  44. # Also check auto_commit.default as fallback.
  45. _in_auto_commit=false
  46. _in_event=false
  47. _default_enabled=false
  48. while IFS= read -r _line; do
  49. # Detect auto_commit: section
  50. if echo "$_line" | grep -q '^auto_commit:'; then
  51. _in_auto_commit=true
  52. _in_event=false
  53. continue
  54. fi
  55. # Exit auto_commit section on next top-level key
  56. if $_in_auto_commit && echo "$_line" | grep -Eq '^[a-z]'; then
  57. break
  58. fi
  59. if $_in_auto_commit; then
  60. # Check default key
  61. if echo "$_line" | grep -Eq "^[[:space:]]+default:[[:space:]]"; then
  62. _val=$(echo "$_line" | sed 's/^[^:]*:[[:space:]]*//' | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]')
  63. [ "$_val" = "true" ] && _default_enabled=true
  64. fi
  65. # Detect our event subsection
  66. if echo "$_line" | grep -Eq "^[[:space:]]+${EVENT_NAME}:"; then
  67. _in_event=true
  68. continue
  69. fi
  70. # Inside our event subsection
  71. if $_in_event; then
  72. # Exit on next sibling key (same indent level as event name)
  73. if echo "$_line" | grep -Eq '^[[:space:]]{2}[a-z]' && ! echo "$_line" | grep -Eq '^[[:space:]]{4}'; then
  74. _in_event=false
  75. continue
  76. fi
  77. if echo "$_line" | grep -Eq '[[:space:]]+enabled:'; then
  78. _val=$(echo "$_line" | sed 's/^[^:]*:[[:space:]]*//' | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]')
  79. [ "$_val" = "true" ] && _enabled=true
  80. [ "$_val" = "false" ] && _enabled=false
  81. fi
  82. if echo "$_line" | grep -Eq '[[:space:]]+message:'; then
  83. _commit_msg=$(echo "$_line" | sed 's/^[^:]*:[[:space:]]*//' | sed 's/^["'\'']//' | sed 's/["'\'']*$//')
  84. fi
  85. fi
  86. fi
  87. done < "$_config_file"
  88. # If event-specific key not found, use default
  89. if [ "$_enabled" = "false" ] && [ "$_default_enabled" = "true" ]; then
  90. # Only use default if the event wasn't explicitly set to false
  91. # Check if event section existed at all
  92. if ! grep -q "^[[:space:]]*${EVENT_NAME}:" "$_config_file" 2>/dev/null; then
  93. _enabled=true
  94. fi
  95. fi
  96. else
  97. # No config file — auto-commit disabled by default
  98. exit 0
  99. fi
  100. if [ "$_enabled" != "true" ]; then
  101. exit 0
  102. fi
  103. # Check if there are changes to commit
  104. if git diff --quiet HEAD 2>/dev/null && git diff --cached --quiet 2>/dev/null && [ -z "$(git ls-files --others --exclude-standard 2>/dev/null)" ]; then
  105. echo "[specify] No changes to commit after $EVENT_NAME" >&2
  106. exit 0
  107. fi
  108. # Derive a human-readable command name from the event
  109. # e.g., after_specify -> specify, before_plan -> plan
  110. _command_name=$(echo "$EVENT_NAME" | sed 's/^after_//' | sed 's/^before_//')
  111. _phase=$(echo "$EVENT_NAME" | grep -q '^before_' && echo 'before' || echo 'after')
  112. # Use custom message if configured, otherwise default
  113. if [ -z "$_commit_msg" ]; then
  114. _commit_msg="[Spec Kit] Auto-commit ${_phase} ${_command_name}"
  115. fi
  116. # Stage and commit
  117. _git_out=$(git add . 2>&1) || { echo "[specify] Error: git add failed: $_git_out" >&2; exit 1; }
  118. _git_out=$(git commit -q -m "$_commit_msg" 2>&1) || { echo "[specify] Error: git commit failed: $_git_out" >&2; exit 1; }
  119. echo "[OK] Changes committed ${_phase} ${_command_name}" >&2