Alert

์ด ๊ธ€์€ Claude Code์˜ ๋„์›€์„ ๋ฐ›์•„ ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค

TL;DR

Git Hook์€ ์ปค๋ฐ‹, ํ‘ธ์‹œ, ๋จธ์ง€ ๋“ฑ Git ๋™์ž‘์˜ ํŠน์ • ์‹œ์ ์— ์ž๋™์œผ๋กœ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ์ด๋‹ค. ๋ฆฐํŠธ ๊ฒ€์‚ฌ, ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ๊ทœ์น™ ๊ฐ•์ œ, ๋ฏผ๊ฐ ์ •๋ณด ์œ ์ถœ ๋ฐฉ์ง€, ๋ฐฐํฌ ์ž๋™ํ™” ๋“ฑ ๋‹ค์–‘ํ•œ ์šฉ๋„๋กœ ํ™œ์šฉํ•˜๋ฉฐ, Huskyยทpre-commitยทLefthook ๊ฐ™์€ ๋„๊ตฌ๋กœ ํŒ€ ์ „์ฒด๊ฐ€ ๋™์ผํ•œ Hook์„ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋‹ค.


1. Git Hook์ด๋ž€

ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ Hook์€ ํŠน์ • ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ ์ž๋™์œผ๋กœ ์‹คํ–‰๋˜๋Š” ์ฝ”๋“œ๋ฅผ ์˜๋ฏธํ•œ๋‹ค. Git Hook๋„ ๊ฐ™์€ ์›๋ฆฌ๋‹ค. git commit, git push ๊ฐ™์€ Git ๋™์ž‘์ด ์ผ์–ด๋‚  ๋•Œ, ์‚ฌ์ „์— ๋“ฑ๋กํ•ด๋‘” ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์ž๋™์œผ๋กœ ์‹คํ–‰๋œ๋‹ค.

1-1. .git/hooks/ ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ

git init์œผ๋กœ ์ €์žฅ์†Œ๋ฅผ ๋งŒ๋“ค๋ฉด .git/hooks/ ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ์ž๋™ ์ƒ์„ฑ๋˜๊ณ , ์ƒ˜ํ”Œ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๋“ค์–ด์žˆ๋‹ค.

ls .git/hooks/
# applypatch-msg.sample  pre-commit.sample
# commit-msg.sample      pre-push.sample
# post-update.sample     pre-rebase.sample
# pre-applypatch.sample  prepare-commit-msg.sample
# ...

.sample ํ™•์žฅ์ž๋ฅผ ์ œ๊ฑฐํ•˜๋ฉด ํ•ด๋‹น Hook์ด ํ™œ์„ฑํ™”๋œ๋‹ค. ์Šคํฌ๋ฆฝํŠธ๋Š” Bash, Python, Node.js ๋“ฑ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์–ธ์–ด๋ผ๋ฉด ๋ฌด์—‡์ด๋“  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

# pre-commit Hook ํ™œ์„ฑํ™”
mv .git/hooks/pre-commit.sample .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

1-2. ๋™์ž‘ ์›๋ฆฌ

Git Hook์˜ ํ•ต์‹ฌ์€ ์ข…๋ฃŒ ์ฝ”๋“œ(exit code) ๊ธฐ๋ฐ˜ ์ œ์–ด๋‹ค.

  • ์ข…๋ฃŒ ์ฝ”๋“œ 0 โ†’ ๋™์ž‘ ๊ณ„์† ์ง„ํ–‰
  • ์ข…๋ฃŒ ์ฝ”๋“œ 0 ์ด์™ธ โ†’ ๋™์ž‘ ์ค‘๋‹จ

์ด ๋‹จ์ˆœํ•œ ๊ทœ์น™์œผ๋กœ ์ปค๋ฐ‹ ์ฐจ๋‹จ, ํ‘ธ์‹œ ๊ฑฐ๋ถ€ ๊ฐ™์€ ์ œ์–ด๊ฐ€ ๊ฐ€๋Šฅํ•ด์ง„๋‹ค. ๋‹จ, ๋ชจ๋“  Hook์ด ๋™์ž‘์„ ์ฐจ๋‹จํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๊ณ , post-* ๊ณ„์—ด Hook์€ ๋™์ž‘์ด ์ด๋ฏธ ์™„๋ฃŒ๋œ ๋’ค์— ์‹คํ–‰๋˜๋ฏ€๋กœ ์•Œ๋ฆผ์ด๋‚˜ ํ›„์ฒ˜๋ฆฌ ์šฉ๋„๋กœ๋งŒ ์“ธ ์ˆ˜ ์žˆ๋‹ค.


2. Client-side Hook

๊ฐœ๋ฐœ์ž์˜ ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰๋˜๋Š” Hook์ด๋‹ค. ๊ฐ€์žฅ ์ž์ฃผ ์“ฐ์ด๋Š” Hook๋“ค์ด ์—ฌ๊ธฐ์— ์†ํ•œ๋‹ค.

2-1. ์ปค๋ฐ‹ ๊ด€๋ จ Hook

์ปค๋ฐ‹ ๊ณผ์ •์€ ๋‚ด๋ถ€์ ์œผ๋กœ ์—ฌ๋Ÿฌ ๋‹จ๊ณ„๋กœ ๋‚˜๋‰˜๋ฉฐ, ๊ฐ ๋‹จ๊ณ„๋งˆ๋‹ค Hook ์ง€์ ์ด ์žˆ๋‹ค.

pre-commit
git commit ์‹คํ–‰ โ†’ [pre-commit] โ†’ ์Šคํ…Œ์ด์ง• ์˜์—ญ ์Šค๋ƒ…์ƒท ์ƒ์„ฑ โ†’ ...

์ปค๋ฐ‹์ด ๋งŒ๋“ค์–ด์ง€๊ธฐ ์ง์ „์— ์‹คํ–‰๋œ๋‹ค. ๊ฐ€์žฅ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” Hook์ด๋‹ค.

  • ๋ฆฐํŠธ ๊ฒ€์‚ฌ (eslint, flake8 ๋“ฑ)
  • ์ฝ”๋“œ ํฌ๋งคํŒ… (prettier, black ๋“ฑ)
  • ๋ฏผ๊ฐ ์ •๋ณด(API ํ‚ค, ๋น„๋ฐ€๋ฒˆํ˜ธ) ์œ ์ถœ ๊ฐ์ง€
  • ํƒ€์ž… ์ฒดํฌ
#!/bin/sh
# ์Šคํ…Œ์ด์ง•๋œ ํŒŒ์ผ์—์„œ console.log ๊ฒ€์ถœ ์‹œ ์ปค๋ฐ‹ ์ฐจ๋‹จ
if git diff --cached --name-only | xargs grep -l 'console.log' 2>/dev/null; then
  echo "console.log๊ฐ€ ๋‚จ์•„์žˆ์Šต๋‹ˆ๋‹ค."
  exit 1
fi
prepare-commit-msg

์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ํŽธ์ง‘๊ธฐ๊ฐ€ ์—ด๋ฆฌ๊ธฐ ์ „์— ์‹คํ–‰๋œ๋‹ค. ๊ธฐ๋ณธ ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€๋ฅผ ์ž๋™์œผ๋กœ ์ฑ„์šธ ๋•Œ ์œ ์šฉํ•˜๋‹ค.

  • ๋ธŒ๋žœ์น˜ ์ด๋ฆ„์—์„œ ์ด์Šˆ ๋ฒˆํ˜ธ๋ฅผ ์ถ”์ถœํ•ด ๋ฉ”์‹œ์ง€์— ์‚ฝ์ž…
  • ๋จธ์ง€ ์ปค๋ฐ‹, ์Šค์ฟผ์‹œ ์ปค๋ฐ‹์˜ ๊ธฐ๋ณธ ๋ฉ”์‹œ์ง€ ์ˆ˜์ •
#!/bin/sh
# ๋ธŒ๋žœ์น˜ ์ด๋ฆ„์ด feat/ISSUE-123 ํ˜•ํƒœ๋ฉด ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€์— [ISSUE-123] ์ž๋™ ์ถ”๊ฐ€
BRANCH=$(git symbolic-ref --short HEAD)
ISSUE=$(echo "$BRANCH" | grep -oE '[A-Z]+-[0-9]+')
if [ -n "$ISSUE" ]; then
  sed -i.bak -e "1s/^/[$ISSUE] /" "$1"
fi
commit-msg

์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ์ž‘์„ฑ์ด ๋๋‚œ ๋’ค, ์ปค๋ฐ‹์ด ํ™•์ •๋˜๊ธฐ ์ „์— ์‹คํ–‰๋œ๋‹ค. ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ์„ ๊ฒ€์ฆํ•˜๋Š” ์šฉ๋„๋‹ค.

  • Conventional Commits ํ˜•์‹ ๊ฐ•์ œ (feat:, fix:, docs: ๋“ฑ)
  • ๋ฉ”์‹œ์ง€ ๊ธธ์ด ์ œํ•œ
  • ์ด์Šˆ ๋ฒˆํ˜ธ ํฌํ•จ ์—ฌ๋ถ€ ํ™•์ธ
#!/bin/sh
# Conventional Commits ํ˜•์‹์ด ์•„๋‹ˆ๋ฉด ์ฐจ๋‹จ
if ! head -1 "$1" | grep -qE '^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .+'; then
  echo "์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€๊ฐ€ Conventional Commits ํ˜•์‹์ด ์•„๋‹™๋‹ˆ๋‹ค."
  echo "์˜ˆ: feat: ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ์ถ”๊ฐ€"
  exit 1
fi
post-commit

์ปค๋ฐ‹์ด ์™„๋ฃŒ๋œ ํ›„ ์‹คํ–‰๋œ๋‹ค. ์ข…๋ฃŒ ์ฝ”๋“œ์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ์ปค๋ฐ‹์€ ์ด๋ฏธ ์™„๋ฃŒ๋œ ์ƒํƒœ์ด๋ฏ€๋กœ, ์•Œ๋ฆผ ์ „์†ก์ด๋‚˜ ๋กœ๊ทธ ๊ธฐ๋ก ๊ฐ™์€ ํ›„์ฒ˜๋ฆฌ์— ์‚ฌ์šฉํ•œ๋‹ค.

2-2. ์ด๋ฉ”์ผ ๊ด€๋ จ Hook

git am ๋ช…๋ น์œผ๋กœ ์ด๋ฉ”์ผ ํŒจ์น˜๋ฅผ ์ ์šฉํ•  ๋•Œ ์‚ฌ์šฉ๋˜๋Š” Hook์ด๋‹ค. ๋ฉ”์ผ๋ง ๋ฆฌ์ŠคํŠธ ๊ธฐ๋ฐ˜ ์›Œํฌํ”Œ๋กœ์—์„œ ์ฃผ๋กœ ์“ฐ์ธ๋‹ค.

Hook์‹œ์ ์šฉ๋„
applypatch-msgํŒจ์น˜ ์ ์šฉ ์ „์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ๊ฒ€์ฆ
pre-applypatchํŒจ์น˜ ์ ์šฉ ํ›„, ์ปค๋ฐ‹ ์ „ํŒจ์น˜ ๊ฒฐ๊ณผ ๊ฒ€์ฆ (ํ…Œ์ŠคํŠธ ์‹คํ–‰ ๋“ฑ)
post-applypatch์ปค๋ฐ‹ ์™„๋ฃŒ ํ›„์•Œ๋ฆผ ์ „์†ก

์ฐธ๊ณ 

์ด๋ฉ”์ผ ํŒจ์น˜ ์›Œํฌํ”Œ๋กœ๋Š” Linux ์ปค๋„ ๊ฐ™์€ ๋Œ€๊ทœ๋ชจ ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ์—์„œ ์ฃผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค. GitHub/GitLab ๊ธฐ๋ฐ˜ PR ์›Œํฌํ”Œ๋กœ์—์„œ๋Š” ๊ฑฐ์˜ ์“ฐ์ด์ง€ ์•Š๋Š”๋‹ค.

2-3. ๊ธฐํƒ€ Client-side Hook

pre-rebase

git rebase ์‹คํ–‰ ์ „์— ๋™์ž‘ํ•œ๋‹ค. ์ด๋ฏธ ํ‘ธ์‹œ๋œ ์ปค๋ฐ‹์˜ rebase๋ฅผ ๋ฐฉ์ง€ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

#!/bin/sh
# main ๋ธŒ๋žœ์น˜์—์„œ์˜ rebase ์ฐจ๋‹จ
if [ "$(git symbolic-ref --short HEAD)" = "main" ]; then
  echo "main ๋ธŒ๋žœ์น˜์—์„œ rebaseํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."
  exit 1
fi
post-rewrite

git commit --amend๋‚˜ git rebase์ฒ˜๋Ÿผ ๊ธฐ์กด ์ปค๋ฐ‹์„ ๋‹ค์‹œ ์“ฐ๋Š” ๋ช…๋ น ์ดํ›„์— ์‹คํ–‰๋œ๋‹ค.

post-checkout

git checkout์ด๋‚˜ git switch๋กœ ๋ธŒ๋žœ์น˜๋ฅผ ์ „ํ™˜ํ•œ ํ›„ ์‹คํ–‰๋œ๋‹ค.

  • ๋ธŒ๋žœ์น˜๋ณ„๋กœ ๋‹ค๋ฅธ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •
  • ์˜์กด์„ฑ ์ž๋™ ์„ค์น˜ (npm install ๋“ฑ)
  • ๋นŒ๋“œ ์บ์‹œ ์ •๋ฆฌ
post-merge

git merge ์™„๋ฃŒ ํ›„ ์‹คํ–‰๋œ๋‹ค. ๋จธ์ง€ ํ›„ ์˜์กด์„ฑ์„ ๋‹ค์‹œ ์„ค์น˜ํ•˜๊ฑฐ๋‚˜, ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๋ฐ ํ™œ์šฉํ•œ๋‹ค.

pre-push

git push ์‹คํ–‰ ์ „, ๋ฆฌ๋ชจํŠธ์— ๋ฐ์ดํ„ฐ๊ฐ€ ์ „์†ก๋˜๊ธฐ ์ง์ „์— ์‹คํ–‰๋œ๋‹ค.

  • ์ „์ฒด ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ ์‹คํ–‰
  • ๋ณดํ˜ธ ๋ธŒ๋žœ์น˜(main, production)๋กœ์˜ ์ง์ ‘ ํ‘ธ์‹œ ์ฐจ๋‹จ
  • ๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ ํ‘ธ์‹œ ๋ฐฉ์ง€
#!/bin/sh
# main ๋ธŒ๋žœ์น˜๋กœ ์ง์ ‘ push ์ฐจ๋‹จ
BRANCH=$(git symbolic-ref --short HEAD)
if [ "$BRANCH" = "main" ]; then
  echo "main ๋ธŒ๋žœ์น˜๋กœ ์ง์ ‘ pushํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. PR์„ ์‚ฌ์šฉํ•˜์„ธ์š”."
  exit 1
fi

3. Server-side Hook

Git ์„œ๋ฒ„(์›๊ฒฉ ์ €์žฅ์†Œ)์—์„œ ์‹คํ–‰๋˜๋Š” Hook์ด๋‹ค. ํŒ€ ์ „์ฒด์— ์ผ๊ด„ ์ ์šฉ๋˜๋ฉฐ, ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์šฐํšŒํ•  ์ˆ˜ ์—†๋‹ค๋Š” ์ ์ด Client-side Hook๊ณผ์˜ ํ•ต์‹ฌ ์ฐจ์ด๋‹ค.

pre-receive

ํด๋ผ์ด์–ธํŠธ๊ฐ€ git push๋ฅผ ๋ณด๋‚ด๋ฉด, ์„œ๋ฒ„๊ฐ€ ref๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ ์ „์— ์‹คํ–‰๋œ๋‹ค. ํ‘ธ์‹œ ์ „์ฒด๋ฅผ ํ•œ ๋ฒˆ์— ์ˆ˜๋ฝํ•˜๊ฑฐ๋‚˜ ๊ฑฐ๋ถ€ํ•œ๋‹ค.

  • ๋ธŒ๋žœ์น˜ ๋ณดํ˜ธ ์ •์ฑ… ๊ฐ•์ œ
  • ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ๊ทœ์น™ ์„œ๋ฒ„ ์ธก ๊ฒ€์ฆ
  • ํŠน์ • ํŒŒ์ผ ํŒจํ„ด ํ‘ธ์‹œ ๊ฑฐ๋ถ€ (๋ฐ”์ด๋„ˆ๋ฆฌ, ๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ ๋“ฑ)
update

pre-receive์™€ ๋น„์Šทํ•˜์ง€๋งŒ, ์—…๋ฐ์ดํŠธ๋˜๋Š” ๊ฐ ๋ธŒ๋žœ์น˜๋งˆ๋‹ค ๊ฐœ๋ณ„์ ์œผ๋กœ ์‹คํ–‰๋œ๋‹ค. ํŠน์ • ๋ธŒ๋žœ์น˜๋งŒ ์„ ํƒ์ ์œผ๋กœ ๊ฑฐ๋ถ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

#!/bin/sh
# release/* ๋ธŒ๋žœ์น˜๋Š” ํŠน์ • ์‚ฌ์šฉ์ž๋งŒ push ํ—ˆ์šฉ
REF=$1
if echo "$REF" | grep -q "refs/heads/release/"; then
  if [ "$USER" != "release-manager" ]; then
    echo "release ๋ธŒ๋žœ์น˜๋Š” release-manager๋งŒ pushํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."
    exit 1
  fi
fi
post-receive

ํ‘ธ์‹œ๊ฐ€ ์™„๋ฃŒ๋œ ํ›„ ์‹คํ–‰๋œ๋‹ค. ์„œ๋ฒ„ ์ธก ํ›„์ฒ˜๋ฆฌ์— ํ™œ์šฉํ•œ๋‹ค.

  • CI/CD ํŒŒ์ดํ”„๋ผ์ธ ํŠธ๋ฆฌ๊ฑฐ
  • ๋ฐฐํฌ ์ž๋™ํ™”
  • ์ฑ„ํŒ…/์ด๋ฉ”์ผ ์•Œ๋ฆผ ์ „์†ก
  • ์ด์Šˆ ํŠธ๋ž˜์ปค ์ž๋™ ์—…๋ฐ์ดํŠธ

Server-side Hook์ด ์ค‘์š”ํ•œ ์ด์œ 

Client-side Hook์€ --no-verify ์˜ต์…˜์œผ๋กœ ์šฐํšŒํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, Server-side Hook์€ ์šฐํšŒ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค. ํŒ€ ์ „์ฒด์— ๋ฐ˜๋“œ์‹œ ์ ์šฉํ•ด์•ผ ํ•˜๋Š” ์ •์ฑ…์€ Server-side Hook์œผ๋กœ ๊ฑธ์–ด์•ผ ํ•œ๋‹ค.


4. Hook ์‹ค์ „ ํ™œ์šฉ ์˜ˆ์‹œ

4-1. ๋ฆฐํŠธ/ํฌ๋งคํ„ฐ ์ž๋™ ์‹คํ–‰ (pre-commit)

์ปค๋ฐ‹ ์ „์— ์Šคํ…Œ์ด์ง•๋œ ํŒŒ์ผ๋งŒ ๋Œ€์ƒ์œผ๋กœ ๋ฆฐํŠธ์™€ ํฌ๋งคํŒ…์„ ์‹คํ–‰ํ•œ๋‹ค. ์ „์ฒด ํ”„๋กœ์ ํŠธ๋ฅผ ๊ฒ€์‚ฌํ•˜๋ฉด ๋А๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์— ๋ณ€๊ฒฝ๋œ ํŒŒ์ผ๋งŒ ๊ฒ€์‚ฌํ•˜๋Š” ๊ฒƒ์ด ํ•ต์‹ฌ์ด๋‹ค.

#!/bin/sh
# ์Šคํ…Œ์ด์ง•๋œ JS/TS ํŒŒ์ผ๋งŒ eslint ์‹คํ–‰
FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts|tsx)$')
if [ -n "$FILES" ]; then
  npx eslint $FILES || exit 1
fi

4-2. ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ์ปจ๋ฒค์…˜ ๊ฐ•์ œ (commit-msg)

ํŒ€์—์„œ Conventional Commits ๊ฐ™์€ ๋ฉ”์‹œ์ง€ ๊ทœ์น™์„ ์ •ํ•˜๋”๋ผ๋„, ์‚ฌ๋žŒ์ด ๋งค๋ฒˆ ๊ธฐ์–ตํ•˜๊ธฐ๋Š” ์–ด๋ ต๋‹ค. commit-msg Hook์œผ๋กœ ์ž๋™ ๊ฒ€์ฆํ•˜๋ฉด ๊ทœ์น™์ด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ •์ฐฉ๋œ๋‹ค.

4-3. ๋ฏผ๊ฐ ์ •๋ณด ์œ ์ถœ ๋ฐฉ์ง€ (pre-commit)

.env ํŒŒ์ผ์ด .gitignore์— ์žˆ๋”๋ผ๋„, ์ฝ”๋“œ ์•ˆ์— ํ•˜๋“œ์ฝ”๋”ฉ๋œ API ํ‚ค๋‚˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์žก์•„๋‚ด์ง€ ๋ชปํ•œ๋‹ค.

#!/bin/sh
# AWS ํ‚ค ํŒจํ„ด ๊ฐ์ง€
if git diff --cached | grep -qE 'AKIA[0-9A-Z]{16}'; then
  echo "AWS Access Key๊ฐ€ ๊ฐ์ง€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ปค๋ฐ‹์„ ์ค‘๋‹จํ•ฉ๋‹ˆ๋‹ค."
  exit 1
fi

์ „๋ฌธ ๋„๊ตฌ ํ™œ์šฉ

๋‹จ์ˆœ ์ •๊ทœ์‹๋ณด๋‹ค๋Š” gitleaks๋‚˜ detect-secrets ๊ฐ™์€ ์ „๋ฌธ ๋„๊ตฌ๋ฅผ pre-commit Hook์— ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒƒ์ด ๋” ์•ˆ์ •์ ์ด๋‹ค.

4-4. ๋ฐฐํฌ ์ž๋™ํ™” (post-receive)

์„œ๋ฒ„์˜ post-receive Hook์œผ๋กœ ํŠน์ • ๋ธŒ๋žœ์น˜์— ํ‘ธ์‹œ๊ฐ€ ์˜ค๋ฉด ์ž๋™ ๋ฐฐํฌ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

#!/bin/sh
while read oldrev newrev refname; do
  if [ "$refname" = "refs/heads/main" ]; then
    echo "main ๋ธŒ๋žœ์น˜ ๋ฐฐํฌ ์‹œ์ž‘..."
    GIT_WORK_TREE=/var/www/app git checkout -f main
    cd /var/www/app && npm install && npm run build
  fi
done

5. Hook ๊ด€๋ฆฌ ๋„๊ตฌ

.git/hooks/ ๋””๋ ‰ํ† ๋ฆฌ๋Š” Git์ด ์ถ”์ ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, ํŒ€์›๋“ค๊ณผ Hook์„ ๊ณต์œ ํ•˜๋ ค๋ฉด ๋ณ„๋„ ๋„๊ตฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ํ”„๋กœ์ ํŠธ์˜ ๊ธฐ์ˆ  ์Šคํƒ์— ๋”ฐ๋ผ ์ ํ•ฉํ•œ ๋„๊ตฌ๋ฅผ ์„ ํƒํ•˜๋ฉด ๋œ๋‹ค.

5-1. Husky (Node.js)

Node.js ํ”„๋กœ์ ํŠธ์—์„œ ๊ฐ€์žฅ ๋„๋ฆฌ ์“ฐ์ด๋Š” Git Hook ๊ด€๋ฆฌ ๋„๊ตฌ๋‹ค. package.json๊ณผ ํ•จ๊ป˜ ๋ฒ„์ „ ๊ด€๋ฆฌ๋˜๋ฏ€๋กœ ํŒ€ ์ „์ฒด๊ฐ€ ๋™์ผํ•œ Hook์„ ๊ณต์œ ํ•œ๋‹ค.

# ์„ค์น˜
npm install --save-dev husky
 
# ์ดˆ๊ธฐํ™” (.husky/ ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ)
npx husky init

.husky/pre-commit ํŒŒ์ผ์„ ์ˆ˜์ •ํ•ด Hook์„ ๋“ฑ๋กํ•œ๋‹ค.

# .husky/pre-commit
npx lint-staged

lint-staged์™€์˜ ์กฐํ•ฉ

Husky๋Š” ๋ณดํ†ต lint-staged์™€ ํ•จ๊ป˜ ์“ด๋‹ค. lint-staged๋Š” ์Šคํ…Œ์ด์ง•๋œ ํŒŒ์ผ๋งŒ ๋Œ€์ƒ์œผ๋กœ ๋ฆฐํŠธ/ํฌ๋งคํŒ…์„ ์‹คํ–‰ํ•ด ์†๋„๋ฅผ ๋†’์—ฌ์ค€๋‹ค.

5-2. pre-commit framework (Python)

Python ์ƒํƒœ๊ณ„์—์„œ ํ‘œ์ค€์ฒ˜๋Ÿผ ์“ฐ์ด๋Š” ๋„๊ตฌ๋‹ค. YAML ์„ค์ • ํŒŒ์ผ ํ•˜๋‚˜๋กœ ๋‹ค์–‘ํ•œ Hook์„ ์„ ์–ธ์ ์œผ๋กœ ๊ด€๋ฆฌํ•œ๋‹ค. Python ํ”„๋กœ์ ํŠธ๊ฐ€ ์•„๋‹ˆ์–ด๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

# ์„ค์น˜
pip install pre-commit

.pre-commit-config.yaml ํŒŒ์ผ๋กœ Hook์„ ์„ค์ •ํ•œ๋‹ค.

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.6.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-added-large-files
  - repo: https://github.com/psf/black
    rev: 24.4.2
    hooks:
      - id: black
# Git Hook์œผ๋กœ ๋“ฑ๋ก
pre-commit install
 
# ์ „์ฒด ํŒŒ์ผ์— ์ˆ˜๋™ ์‹คํ–‰
pre-commit run --all-files

์ปค๋ฎค๋‹ˆํ‹ฐ์—์„œ ์ œ๊ณตํ•˜๋Š” ์ˆ˜๋ฐฑ ๊ฐœ์˜ Hook์„ ๊ฐ€์ ธ๋‹ค ์“ธ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด ํฐ ์žฅ์ ์ด๋‹ค.

5-3. Lefthook (Go)

Go๋กœ ์ž‘์„ฑ๋œ ๋„๊ตฌ๋กœ, ๋ณ„๋„ ๋Ÿฐํƒ€์ž„ ์˜์กด์„ฑ ์—†์ด ๋ฐ”์ด๋„ˆ๋ฆฌ ํ•˜๋‚˜๋กœ ๋™์ž‘ํ•œ๋‹ค. ์„ค์ •์ด ๊ฐ„๊ฒฐํ•˜๊ณ  ๋ณ‘๋ ฌ ์‹คํ–‰์„ ์ง€์›ํ•ด ์†๋„๊ฐ€ ๋น ๋ฅด๋‹ค.

# ์„ค์น˜
brew install lefthook

lefthook.yml ํŒŒ์ผ๋กœ ์„ค์ •ํ•œ๋‹ค.

pre-commit:
  parallel: true
  commands:
    lint:
      glob: "*.{js,ts,tsx}"
      run: npx eslint {staged_files}
    format:
      glob: "*.{js,ts,tsx}"
      run: npx prettier --check {staged_files}
# Git Hook์œผ๋กœ ๋“ฑ๋ก
lefthook install

5-4. simple-git-hooks (Node.js)

Husky๋ณด๋‹ค ๊ฐ€๋ฒผ์šด ๋Œ€์•ˆ์ด๋‹ค. ๋ณ„๋„ ๋””๋ ‰ํ† ๋ฆฌ ์—†์ด package.json ์•ˆ์— Hook์„ ์ง์ ‘ ์„ ์–ธํ•œ๋‹ค. ์„ค์ • ํŒŒ์ผ์ด ๋”ฐ๋กœ ์—†๊ณ , Hook ํ•˜๋‚˜๋‹น ๋ช…๋ น์–ด ํ•˜๋‚˜๋ฅผ ๋งคํ•‘ํ•˜๋Š” ๊ตฌ์กฐ๋ผ์„œ ๋‹จ์ˆœํ•œ ํ”„๋กœ์ ํŠธ์— ์ ํ•ฉํ•˜๋‹ค.

# ์„ค์น˜
npm install --save-dev simple-git-hooks

package.json์— Hook์„ ์„ ์–ธํ•œ๋‹ค.

{
  "simple-git-hooks": {
    "pre-commit": "npx lint-staged",
    "commit-msg": "npx commitlint --edit $1"
  }
}
# Git Hook์œผ๋กœ ๋“ฑ๋ก (package.json์˜ "prepare" ์Šคํฌ๋ฆฝํŠธ์— ๋„ฃ์–ด๋‘๋ฉด npm install ์‹œ ์ž๋™ ์‹คํ–‰)
npx simple-git-hooks

Husky์™€์˜ ์ฐจ์ด

Husky๋Š” .husky/ ๋””๋ ‰ํ† ๋ฆฌ์— Hook๋ณ„ ์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ์„ ๋‘๋Š” ๋ฐ˜๋ฉด, simple-git-hooks๋Š” package.json์— ์ธ๋ผ์ธ์œผ๋กœ ์„ ์–ธํ•œ๋‹ค. Hook ์ˆ˜๊ฐ€ ์ ๊ณ  ๋‹จ์ˆœํ•œ ๋ช…๋ น๋งŒ ์‹คํ–‰ํ•˜๋ฉด simple-git-hooks๊ฐ€ ๋” ๊ฐ„๊ฒฐํ•˜๋‹ค.

5-5. ๋„๊ตฌ ๋น„๊ต

ํ•ญ๋ชฉHuskypre-commitLefthooksimple-git-hooks
์–ธ์–ดNode.jsPythonGoNode.js
๋Ÿฐํƒ€์ž„ ์˜์กด์„ฑNode.js ํ•„์š”Python ํ•„์š”์—†์Œ (๋ฐ”์ด๋„ˆ๋ฆฌ)Node.js ํ•„์š”
์„ค์ • ํŒŒ์ผ.husky/ ๋””๋ ‰ํ† ๋ฆฌ.pre-commit-config.yamllefthook.ymlpackage.json ๋‚ด ์ธ๋ผ์ธ
๋ณ‘๋ ฌ ์‹คํ–‰๋ฏธ์ง€์›๋ฏธ์ง€์›์ง€์›๋ฏธ์ง€์›
์ปค๋ฎค๋‹ˆํ‹ฐ Hooklint-staged ์กฐํ•ฉ์ˆ˜๋ฐฑ ๊ฐœ ๋‚ด์žฅ ์ €์žฅ์†Œ์ง์ ‘ ๋ช…๋ น ์ง€์ •์ง์ ‘ ๋ช…๋ น ์ง€์ •
์ถ”์ฒœ ํ™˜๊ฒฝNode.js ํ”„๋กœ์ ํŠธPython ๋˜๋Š” ๋‹ค๊ตญ์–ด ํ”„๋กœ์ ํŠธ๋Ÿฐํƒ€์ž„ ์˜์กด์„ฑ ์—†์ด ์“ฐ๊ณ  ์‹ถ์„ ๋•ŒHook์ด ์ ๊ณ  ์„ค์ •์„ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์œ ์ง€ํ•˜๊ณ  ์‹ถ์„ ๋•Œ

6. ์ฃผ์˜์‚ฌํ•ญ

  • --no-verify๋กœ ์šฐํšŒ ๊ฐ€๋Šฅ: git commit --no-verify๋ฅผ ์“ฐ๋ฉด Client-side Hook์„ ๊ฑด๋„ˆ๋›ธ ์ˆ˜ ์žˆ๋‹ค. ๋ฐ˜๋“œ์‹œ ์ง€์ผœ์•ผ ํ•˜๋Š” ๊ทœ์น™์€ Server-side Hook์ด๋‚˜ CI์—์„œ ์ด์ค‘์œผ๋กœ ๊ฒ€์ฆํ•ด์•ผ ํ•œ๋‹ค.
  • .git/hooks/๋Š” ๋ฒ„์ „ ๊ด€๋ฆฌ ๋Œ€์ƒ์ด ์•„๋‹˜: Git์€ .git/ ๋‚ด๋ถ€ ํŒŒ์ผ์„ ์ถ”์ ํ•˜์ง€ ์•Š๋Š”๋‹ค. ํŒ€ ๊ณต์œ ๊ฐ€ ํ•„์š”ํ•˜๋ฉด ์œ„์˜ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, ๋ณ„๋„ ๋””๋ ‰ํ† ๋ฆฌ์— ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋‘๊ณ  core.hooksPath ์„ค์ •์œผ๋กœ ์—ฐ๊ฒฐํ•œ๋‹ค.
# ์ปค์Šคํ…€ Hook ๋””๋ ‰ํ† ๋ฆฌ ์ง€์ •
git config core.hooksPath .githooks
  • ์‹คํ–‰ ์‹œ๊ฐ„์— ์ฃผ์˜: pre-commit Hook์ด ๋А๋ฆฌ๋ฉด ๊ฐœ๋ฐœ ํ๋ฆ„์ด ๋Š๊ธด๋‹ค. ์ „์ฒด ํ…Œ์ŠคํŠธ๋ณด๋‹ค๋Š” ๋ณ€๊ฒฝ ํŒŒ์ผ ๋Œ€์ƒ์˜ ๊ฐ€๋ฒผ์šด ๊ฒ€์‚ฌ๋งŒ ๊ฑธ๊ณ , ๋ฌด๊ฑฐ์šด ๊ฒ€์ฆ์€ CI์— ๋งก๊ธฐ๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.
  • ์‹คํ–‰ ๊ถŒํ•œ ํ•„์ˆ˜: Hook ์Šคํฌ๋ฆฝํŠธ์— ์‹คํ–‰ ๊ถŒํ•œ(chmod +x)์ด ์—†์œผ๋ฉด ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค.