Git Hook 自动化 GitNexus 索引更新指南

目录

  1. Git Hook 基础
  2. 推荐方案
  3. 完整实现
  4. 高级配置
  5. 测试与调试
  6. 最佳实践

Git Hook 基础

什么是 Git Hook

Git Hook 是在特定 Git 事件发生时自动执行的脚本,存放在 .git/hooks/ 目录。

常用 Hook 类型

Hook 名称触发时机适用场景
post-commit提交完成后每次提交后更新索引
post-merge合并完成后拉取代码后更新索引
post-checkout切换分支后切换分支时更新索引
post-rewriterebase/amend 后重写历史后更新索引

推荐方案

方案对比

方案优点缺点推荐度
post-commit实时更新频繁触发⭐⭐⭐⭐
post-merge拉取后更新本地修改不触发⭐⭐⭐
组合方案覆盖全面配置复杂⭐⭐⭐⭐⭐
手动触发完全可控容易忘记⭐⭐

推荐:组合方案(post-commit + post-merge + post-checkout)


完整实现

方案 1:基础 post-commit Hook

适用场景:单人开发,每次提交后自动更新

创建 Hook 脚本

# 创建 post-commit hook
cat > .git/hooks/post-commit << 'EOF'
#!/bin/bash
 
# GitNexus 自动索引更新
# 在后台运行,不阻塞 git 操作
 
echo "🔄 GitNexus: Updating index in background..."
 
# 后台运行,跳过向量嵌入以加速
npx gitnexus analyze --skip-embeddings > /tmp/gitnexus-update.log 2>&1 &
 
# 保存进程 ID
echo $! > /tmp/gitnexus-update.pid
 
echo "✅ GitNexus: Index update started (PID: $!)"
EOF
 
# 添加执行权限
chmod +x .git/hooks/post-commit

测试

# 创建一个测试提交
echo "test" >> test.txt
git add test.txt
git commit -m "test: trigger gitnexus hook"
 
# 查看日志
tail -f /tmp/gitnexus-update.log

方案 2:智能 post-commit Hook(推荐)

特性

  • 检测是否有代码文件变更
  • 避免文档/配置变更时触发
  • 显示更新进度
  • 错误处理
cat > .git/hooks/post-commit << 'EOF'
#!/bin/bash
 
# GitNexus 智能索引更新
# 只在代码文件变更时触发
 
# 配置
LOG_FILE="/tmp/gitnexus-update.log"
PID_FILE="/tmp/gitnexus-update.pid"
 
# 支持的代码文件扩展名
CODE_EXTENSIONS="ts|js|tsx|jsx|py|java|c|cpp|cc|h|hpp|cs|go|rs|php"
 
# 检查最近一次提交是否包含代码文件
has_code_changes() {
    git diff-tree --no-commit-id --name-only -r HEAD | \
        grep -E "\.(${CODE_EXTENSIONS})$" > /dev/null
    return $?
}
 
# 检查是否有正在运行的更新
if [ -f "$PID_FILE" ]; then
    OLD_PID=$(cat "$PID_FILE")
    if ps -p "$OLD_PID" > /dev/null 2>&1; then
        echo "⏳ GitNexus: Previous update still running (PID: $OLD_PID)"
        exit 0
    fi
fi
 
# 检查是否有代码变更
if ! has_code_changes; then
    echo "⏭️  GitNexus: No code changes detected, skipping index update"
    exit 0
fi
 
echo "🔄 GitNexus: Code changes detected, updating index..."
 
# 后台运行索引更新
(
    # 记录开始时间
    START_TIME=$(date +%s)
 
    # 运行索引更新
    npx gitnexus analyze --skip-embeddings > "$LOG_FILE" 2>&1
    EXIT_CODE=$?
 
    # 记录结束时间
    END_TIME=$(date +%s)
    DURATION=$((END_TIME - START_TIME))
 
    # 输出结果
    if [ $EXIT_CODE -eq 0 ]; then
        echo "✅ GitNexus: Index updated successfully in ${DURATION}s" >> "$LOG_FILE"
        # 可选:发送桌面通知
        if command -v notify-send > /dev/null 2>&1; then
            notify-send "GitNexus" "Index updated successfully (${DURATION}s)"
        fi
    else
        echo "❌ GitNexus: Index update failed (exit code: $EXIT_CODE)" >> "$LOG_FILE"
        if command -v notify-send > /dev/null 2>&1; then
            notify-send -u critical "GitNexus" "Index update failed"
        fi
    fi
 
    # 清理 PID 文件
    rm -f "$PID_FILE"
) &
 
# 保存进程 ID
echo $! > "$PID_FILE"
 
echo "✅ GitNexus: Index update started (PID: $!, log: $LOG_FILE)"
EOF
 
chmod +x .git/hooks/post-commit

方案 3:组合 Hook(最完整)

覆盖场景

  • 本地提交(post-commit)
  • 拉取代码(post-merge)
  • 切换分支(post-checkout)
  • Rebase/Amend(post-rewrite)

创建共享脚本

# 创建共享的更新脚本
cat > .git/hooks/gitnexus-update.sh << 'EOF'
#!/bin/bash
 
# GitNexus 索引更新共享脚本
# 被多个 hook 调用
 
LOG_FILE="/tmp/gitnexus-update.log"
PID_FILE="/tmp/gitnexus-update.pid"
CODE_EXTENSIONS="ts|js|tsx|jsx|py|java|c|cpp|cc|h|hpp|cs|go|rs|php"
 
# 参数:触发来源
TRIGGER_SOURCE="${1:-unknown}"
 
# 检查是否有正在运行的更新
if [ -f "$PID_FILE" ]; then
    OLD_PID=$(cat "$PID_FILE")
    if ps -p "$OLD_PID" > /dev/null 2>&1; then
        echo "⏳ GitNexus: Previous update still running (PID: $OLD_PID)"
        exit 0
    fi
fi
 
# 检查是否有代码变更(根据触发源不同,检查方式不同)
has_code_changes() {
    case "$TRIGGER_SOURCE" in
        post-commit)
            git diff-tree --no-commit-id --name-only -r HEAD | \
                grep -E "\.(${CODE_EXTENSIONS})$" > /dev/null
            ;;
        post-merge)
            git diff-tree --no-commit-id --name-only -r ORIG_HEAD HEAD | \
                grep -E "\.(${CODE_EXTENSIONS})$" > /dev/null
            ;;
        post-checkout)
            # 切换分支时总是更新
            return 0
            ;;
        *)
            return 0
            ;;
    esac
}
 
if ! has_code_changes; then
    echo "⏭️  GitNexus: No code changes detected, skipping index update"
    exit 0
fi
 
echo "🔄 GitNexus: Updating index (triggered by: $TRIGGER_SOURCE)..."
 
# 后台运行索引更新
(
    START_TIME=$(date +%s)
 
    # 根据触发源决定是否跳过向量嵌入
    if [ "$TRIGGER_SOURCE" = "post-commit" ]; then
        # 提交时快速更新,跳过向量嵌入
        npx gitnexus analyze --skip-embeddings > "$LOG_FILE" 2>&1
    else
        # 其他情况完整更新
        npx gitnexus analyze > "$LOG_FILE" 2>&1
    fi
 
    EXIT_CODE=$?
    END_TIME=$(date +%s)
    DURATION=$((END_TIME - START_TIME))
 
    if [ $EXIT_CODE -eq 0 ]; then
        echo "✅ GitNexus: Index updated successfully in ${DURATION}s (trigger: $TRIGGER_SOURCE)" >> "$LOG_FILE"
        if command -v notify-send > /dev/null 2>&1; then
            notify-send "GitNexus" "Index updated (${DURATION}s)"
        fi
    else
        echo "❌ GitNexus: Index update failed (exit code: $EXIT_CODE, trigger: $TRIGGER_SOURCE)" >> "$LOG_FILE"
        if command -v notify-send > /dev/null 2>&1; then
            notify-send -u critical "GitNexus" "Index update failed"
        fi
    fi
 
    rm -f "$PID_FILE"
) &
 
echo $! > "$PID_FILE"
echo "✅ GitNexus: Index update started (PID: $!, log: $LOG_FILE)"
EOF
 
chmod +x .git/hooks/gitnexus-update.sh

创建各个 Hook

# post-commit
cat > .git/hooks/post-commit << 'EOF'
#!/bin/bash
.git/hooks/gitnexus-update.sh post-commit
EOF
chmod +x .git/hooks/post-commit
 
# post-merge
cat > .git/hooks/post-merge << 'EOF'
#!/bin/bash
.git/hooks/gitnexus-update.sh post-merge
EOF
chmod +x .git/hooks/post-merge
 
# post-checkout
cat > .git/hooks/post-checkout << 'EOF'
#!/bin/bash
# 参数:$1=prev-ref $2=new-ref $3=branch-flag
# 只在切换分支时触发(不在检出文件时触发)
if [ "$3" = "1" ]; then
    .git/hooks/gitnexus-update.sh post-checkout
fi
EOF
chmod +x .git/hooks/post-checkout
 
# post-rewrite
cat > .git/hooks/post-rewrite << 'EOF'
#!/bin/bash
# 参数:$1=rebase 或 amend
.git/hooks/gitnexus-update.sh post-rewrite
EOF
chmod +x .git/hooks/post-rewrite

高级配置

配置 1:条件触发

只在特定分支触发:

# 只在 main/develop 分支触发
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [[ "$CURRENT_BRANCH" != "main" && "$CURRENT_BRANCH" != "develop" ]]; then
    echo "⏭️  GitNexus: Not on main/develop branch, skipping"
    exit 0
fi

配置 2:时间窗口限制

避免频繁触发:

# 距离上次更新不足 5 分钟则跳过
LAST_UPDATE_FILE="/tmp/gitnexus-last-update"
CURRENT_TIME=$(date +%s)
MIN_INTERVAL=300  # 5 分钟
 
if [ -f "$LAST_UPDATE_FILE" ]; then
    LAST_UPDATE=$(cat "$LAST_UPDATE_FILE")
    TIME_DIFF=$((CURRENT_TIME - LAST_UPDATE))
    if [ $TIME_DIFF -lt $MIN_INTERVAL ]; then
        echo "⏭️  GitNexus: Updated ${TIME_DIFF}s ago, skipping (min interval: ${MIN_INTERVAL}s)"
        exit 0
    fi
fi
 
# 记录本次更新时间
echo "$CURRENT_TIME" > "$LAST_UPDATE_FILE"

配置 3:变更文件数量阈值

只在变更文件数超过阈值时触发:

# 变更文件数少于 3 个则跳过
CHANGED_FILES=$(git diff-tree --no-commit-id --name-only -r HEAD | wc -l)
MIN_FILES=3
 
if [ $CHANGED_FILES -lt $MIN_FILES ]; then
    echo "⏭️  GitNexus: Only $CHANGED_FILES files changed (min: $MIN_FILES), skipping"
    exit 0
fi

配置 4:工作时间限制

只在工作时间触发(避免下班后触发):

# 只在工作时间(9:00-18:00)触发
CURRENT_HOUR=$(date +%H)
if [ $CURRENT_HOUR -lt 9 ] || [ $CURRENT_HOUR -ge 18 ]; then
    echo "⏭️  GitNexus: Outside working hours, skipping"
    exit 0
fi

测试与调试

测试 Hook

# 1. 测试 post-commit
echo "test" >> test.txt
git add test.txt
git commit -m "test: trigger hook"
 
# 2. 查看日志
tail -f /tmp/gitnexus-update.log
 
# 3. 检查进程
ps aux | grep gitnexus
 
# 4. 测试 post-merge
git pull origin main
 
# 5. 测试 post-checkout
git checkout -b test-branch
git checkout main

调试技巧

启用详细日志

# 在 hook 脚本开头添加
set -x  # 打印每条命令
exec 2>> /tmp/gitnexus-hook-debug.log  # 重定向错误输出

手动运行 Hook

# 直接运行 hook 脚本测试
.git/hooks/post-commit

查看 Hook 输出

# Git hook 的输出会显示在终端
# 如果没有输出,检查是否被重定向

常见问题排查

问题 1:Hook 不执行

# 检查文件权限
ls -la .git/hooks/post-commit
 
# 应该显示 -rwxr-xr-x(可执行)
# 如果不是,运行:
chmod +x .git/hooks/post-commit

问题 2:npx 命令找不到

# 在 hook 脚本开头添加 PATH
export PATH="/usr/local/bin:/usr/bin:/bin:$HOME/.nvm/versions/node/v20.0.0/bin:$PATH"

问题 3:后台进程被杀死

# 使用 nohup 确保进程不被终止
nohup npx gitnexus analyze --skip-embeddings > "$LOG_FILE" 2>&1 &

最佳实践

1. 性能优化

# 提交时跳过向量嵌入(快速)
npx gitnexus analyze --skip-embeddings
 
# 每天定时完整更新一次(crontab)
0 9 * * * cd /path/to/project && npx gitnexus analyze

2. 团队协作

方案 A:提交 Hook 到仓库

# 创建 hooks 目录
mkdir -p .githooks
 
# 将 hook 脚本放入 .githooks/
cp .git/hooks/post-commit .githooks/
 
# 配置 Git 使用自定义 hooks 目录
git config core.hooksPath .githooks
 
# 提交到仓库
git add .githooks/
git commit -m "chore: add gitnexus git hooks"

团队成员克隆后自动生效。

方案 B:安装脚本

创建 install-hooks.sh

#!/bin/bash
echo "Installing GitNexus hooks..."
cp .githooks/* .git/hooks/
chmod +x .git/hooks/*
echo "✅ Hooks installed successfully"

团队成员运行 ./install-hooks.sh 安装。

3. 禁用 Hook

临时禁用

# 使用 --no-verify 跳过 hook
git commit --no-verify -m "quick fix"

永久禁用

# 删除或重命名 hook
mv .git/hooks/post-commit .git/hooks/post-commit.disabled

条件禁用

在 hook 脚本中添加:

# 检查环境变量
if [ "$SKIP_GITNEXUS_HOOK" = "1" ]; then
    echo "⏭️  GitNexus: Hook disabled by SKIP_GITNEXUS_HOOK"
    exit 0
fi

使用时:

SKIP_GITNEXUS_HOOK=1 git commit -m "skip hook"

4. 监控与通知

桌面通知(Linux)

if command -v notify-send > /dev/null 2>&1; then
    notify-send "GitNexus" "Index updated successfully"
fi

Slack 通知

# 需要 Slack Webhook URL
SLACK_WEBHOOK="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
 
curl -X POST "$SLACK_WEBHOOK" \
    -H 'Content-Type: application/json' \
    -d "{\"text\":\"GitNexus index updated for $(git rev-parse --abbrev-ref HEAD)\"}"

5. 日志管理

日志轮转

# 限制日志文件大小
LOG_FILE="/tmp/gitnexus-update.log"
MAX_LOG_SIZE=10485760  # 10MB
 
if [ -f "$LOG_FILE" ] && [ $(stat -f%z "$LOG_FILE" 2>/dev/null || stat -c%s "$LOG_FILE") -gt $MAX_LOG_SIZE ]; then
    mv "$LOG_FILE" "$LOG_FILE.old"
fi

结构化日志

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
}
 
log "INFO: Starting index update"
log "ERROR: Update failed with code $EXIT_CODE"

完整示例:生产级 Hook

cat > .git/hooks/post-commit << 'EOF'
#!/bin/bash
 
# GitNexus 生产级自动索引更新
# 版本: 1.0.0
 
set -euo pipefail
 
# ============================================================================
# 配置
# ============================================================================
 
LOG_FILE="/tmp/gitnexus-update.log"
PID_FILE="/tmp/gitnexus-update.pid"
LAST_UPDATE_FILE="/tmp/gitnexus-last-update"
CODE_EXTENSIONS="ts|js|tsx|jsx|py|java|c|cpp|cc|h|hpp|cs|go|rs|php"
MIN_INTERVAL=300  # 5 分钟
MIN_FILES=2       # 最少变更文件数
 
# ============================================================================
# 工具函数
# ============================================================================
 
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
 
# 检查环境变量禁用
if [ "${SKIP_GITNEXUS_HOOK:-0}" = "1" ]; then
    log "INFO: Hook disabled by SKIP_GITNEXUS_HOOK"
    exit 0
fi
 
# 检查是否有正在运行的更新
if [ -f "$PID_FILE" ]; then
    OLD_PID=$(cat "$PID_FILE")
    if ps -p "$OLD_PID" > /dev/null 2>&1; then
        log "INFO: Previous update still running (PID: $OLD_PID)"
        exit 0
    fi
fi
 
# 检查时间间隔
if [ -f "$LAST_UPDATE_FILE" ]; then
    LAST_UPDATE=$(cat "$LAST_UPDATE_FILE")
    CURRENT_TIME=$(date +%s)
    TIME_DIFF=$((CURRENT_TIME - LAST_UPDATE))
    if [ $TIME_DIFF -lt $MIN_INTERVAL ]; then
        log "INFO: Updated ${TIME_DIFF}s ago, skipping (min interval: ${MIN_INTERVAL}s)"
        exit 0
    fi
fi
 
# 检查代码变更
CHANGED_FILES=$(git diff-tree --no-commit-id --name-only -r HEAD | grep -E "\.(${CODE_EXTENSIONS})$" | wc -l)
if [ $CHANGED_FILES -lt $MIN_FILES ]; then
    log "INFO: Only $CHANGED_FILES code files changed (min: $MIN_FILES), skipping"
    exit 0
fi
 
# ============================================================================
# 执行更新
# ============================================================================
 
log "INFO: Starting index update ($CHANGED_FILES files changed)"
 
# 后台运行
(
    START_TIME=$(date +%s)
 
    if npx gitnexus analyze --skip-embeddings >> "$LOG_FILE" 2>&1; then
        END_TIME=$(date +%s)
        DURATION=$((END_TIME - START_TIME))
        log "SUCCESS: Index updated in ${DURATION}s"
 
        # 桌面通知
        if command -v notify-send > /dev/null 2>&1; then
            notify-send "GitNexus" "Index updated (${DURATION}s)"
        fi
    else
        EXIT_CODE=$?
        log "ERROR: Index update failed (exit code: $EXIT_CODE)"
 
        if command -v notify-send > /dev/null 2>&1; then
            notify-send -u critical "GitNexus" "Index update failed"
        fi
    fi
 
    rm -f "$PID_FILE"
) &
 
# 保存状态
echo $! > "$PID_FILE"
date +%s > "$LAST_UPDATE_FILE"
 
log "INFO: Index update started (PID: $!)"
EOF
 
chmod +x .git/hooks/post-commit

总结

推荐配置

  1. 个人开发:使用方案 2(智能 post-commit Hook)
  2. 团队协作:使用方案 3(组合 Hook)+ 提交到仓库
  3. 大型项目:使用生产级 Hook + 时间窗口限制

关键要点

  • ✅ 后台运行,不阻塞 Git 操作
  • ✅ 智能检测代码变更,避免无效触发
  • ✅ 错误处理和日志记录
  • ✅ 可配置、可禁用、可调试
  • ✅ 团队共享,统一体验

现在你的 GitNexus 索引会自动保持最新状态!