TechLead
Lección 6 de 9
5 min de lectura
Git Avanzado

Hooks de Git

Automatiza flujos con hooks del cliente y del servidor para validación, tests y despliegues.

Resumen de Git Hooks

Los hooks de Git son scripts que se ejecutan automáticamente en puntos específicos del flujo de trabajo. Son poderosos para imponer estándares y automatizar tareas.

Tipos de hooks

Client-side Hooks:
├── pre-commit      # Before commit is created
├── prepare-commit-msg  # Before commit message editor
├── commit-msg      # Validate commit message
├── post-commit     # After commit is created
├── pre-rebase      # Before rebase starts
├── post-rewrite    # After commit is rewritten
├── post-checkout   # After checkout
├── post-merge      # After merge
└── pre-push        # Before push

Server-side Hooks:
├── pre-receive     # Before accepting push
├── update          # Per-branch before update
└── post-receive    # After push is accepted

Hook pre-commit

#!/bin/bash
# .git/hooks/pre-commit

# Run linter
npm run lint
if [ $? -ne 0 ]; then
  echo "Linting failed. Fix errors before committing."
  exit 1
fi

# Run tests
npm test
if [ $? -ne 0 ]; then
  echo "Tests failed. Fix tests before committing."
  exit 1
fi

# Check for console.log
if git diff --cached | grep -E "console\.log" > /dev/null; then
  echo "Warning: console.log found in staged changes"
  # exit 1  # Uncomment to block commit
fi

# Check for large files
for file in $(git diff --cached --name-only); do
  size=$(wc -c < "$file" 2>/dev/null || echo 0)
  if [ $size -gt 1000000 ]; then
    echo "Error: $file is larger than 1MB"
    exit 1
  fi
done

exit 0

Hook commit-msg

#!/bin/bash
# .git/hooks/commit-msg

commit_msg_file=$1
commit_msg=$(cat "$commit_msg_file")

# Conventional commits pattern
pattern="^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .{1,50}"

if ! echo "$commit_msg" | grep -qE "$pattern"; then
  echo "Error: Commit message doesn't follow conventional commits"
  echo "Format: type(scope): description"
  echo "Types: feat, fix, docs, style, refactor, test, chore"
  exit 1
fi

# Check minimum length
msg_length=${#commit_msg}
if [ $msg_length -lt 10 ]; then
  echo "Error: Commit message too short (min 10 chars)"
  exit 1
fi

exit 0

Hook pre-push

#!/bin/bash
# .git/hooks/pre-push

remote="$1"
url="$2"

# Prevent push to main
current_branch=$(git branch --show-current)
if [ "$current_branch" = "main" ]; then
  echo "Error: Direct push to main is not allowed"
  echo "Please create a pull request instead"
  exit 1
fi

# Run full test suite before push
npm run test:all
if [ $? -ne 0 ]; then
  echo "Tests failed. Fix before pushing."
  exit 1
fi

# Check for WIP commits
if git log @{u}.. --oneline | grep -i "wip" > /dev/null; then
  echo "Warning: WIP commits detected. Continue? (y/n)"
  read -r response
  if [ "$response" != "y" ]; then
    exit 1
  fi
fi

exit 0

Usar Husky

# Install Husky
npm install husky -D

# Initialize
npx husky init

# Add hook
echo "npm test" > .husky/pre-commit

# Add commit-msg hook
cat > .husky/commit-msg << 'EOF'
#!/bin/sh
npx --no -- commitlint --edit $1
EOF

Husky con lint-staged

// package.json
{
  "scripts": {
    "prepare": "husky"
  },
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{css,scss}": [
      "stylelint --fix"
    ],
    "*.{json,md}": [
      "prettier --write"
    ]
  }
}
# .husky/pre-commit
#!/bin/sh
npx lint-staged

Configuración de commitlint

// commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      [
        'feat',
        'fix',
        'docs',
        'style',
        'refactor',
        'perf',
        'test',
        'build',
        'ci',
        'chore',
        'revert',
      ],
    ],
    'subject-min-length': [2, 'always', 10],
    'subject-max-length': [2, 'always', 72],
    'body-max-line-length': [2, 'always', 100],
  },
};

Ejemplo de hook server-side

#!/bin/bash
# hooks/pre-receive (server-side)

while read oldrev newrev refname; do
  # Block force push
  if [ "$oldrev" != "0000000000000000000000000000000000000000" ]; then
    if ! git merge-base --is-ancestor "$oldrev" "$newrev"; then
      echo "Error: Force push detected and blocked"
      exit 1
    fi
  fi

  # Block push to protected branches
  branch=$(echo "$refname" | sed 's/refs\/heads\///')
  if [[ "$branch" =~ ^(main|production)$ ]]; then
    echo "Error: Direct push to $branch is blocked"
    exit 1
  fi
done

exit 0

Compartir hooks

# Store hooks in repository
mkdir -p .githooks

# Configure Git to use custom hooks directory
git config core.hooksPath .githooks

# Add to project setup
# In package.json or Makefile:
# "prepare": "git config core.hooksPath .githooks"

Patrones comunes de hooks

  • Pre-commit: lint, format, tests unitarios
  • Commit-msg: validar formato de mensaje
  • Pre-push: suite completa, evitar WIP
  • Post-checkout: instalar dependencias
  • Post-merge: avisar cambios de dependencias

Continuar aprendiendo