aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/dot_config/git/hooks/executable_commit-msg
blob: e484ccbd1cc70de2518515abc017c053cce3c18f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#!/bin/sh
# Strip Co-authored-by trailers whose identity looks like a coding agent
# (Copilot, Claude, Codex, ChatGPT, Cursor, Aider, Devin, ...). Various
# tools — GitHub Copilot CLI, VS Code chat, etc. — append these
# automatically and they then trip the pre-push hook's agent check.
# Easier to scrub at commit time than to amend later.
#
# Activated via core.hooksPath in ~/.config/git/config so it applies to
# every repo unless that repo overrides hooksPath itself.
#
# Bypass: git commit --no-verify

set -eu

# shellcheck source=./_dispatch.sh
. "${0%/*}/_dispatch.sh"
dispatch_repo_hook commit-msg "$@"

msg_file=$1

# Keep this list in sync with executable_pre-push.
agent_subs='copilot claude codex chatgpt cursor aider devin [bot] @openai. @anthropic.'

# Rewrite the message file, dropping any Co-authored-by line whose
# lowercased content contains an agent substring. We deliberately
# operate line-by-line rather than on trailer blocks because the file
# already includes git's auto-generated comment block (`# ...`) and we
# don't want to touch any of that.
tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT INT TERM

awk -v agent_subs="$agent_subs" '
  BEGIN { n = split(agent_subs, subs, " ") }
  function is_agent(s,    i) {
    for (i = 1; i <= n; i++) if (index(s, subs[i])) return 1
    return 0
  }
  {
    # Match the trailer case-insensitively but keep the original
    # casing for everything we pass through.
    lower = tolower($0)
    if (lower ~ /^co-authored-by:[[:space:]]/ && is_agent(lower)) {
      stripped = 1
      next
    }
    print
  }
  END { exit stripped ? 10 : 0 }
' "$msg_file" >"$tmp" && rc=0 || rc=$?

# awk exits 10 only when we actually dropped a line; anything else is
# treated as a real error.
case $rc in
  0) exit 0 ;;
  10) ;;
  *) printf 'commit-msg: awk failed (rc=%d); leaving message untouched.\n' "$rc" >&2; exit 0 ;;
esac

# Collapse any trailing blank lines produced by the removal so we don't
# leave a dangling blank trailer block.
sed -e :a -e '/^$/{$d;N;ba' -e '}' "$tmp" >"$msg_file"

printf 'commit-msg: stripped AI Co-authored-by trailer(s).\n' >&2
exit 0