Git Hook 自动化 GitNexus 索引更新指南
目录
Git Hook 基础
什么是 Git Hook
Git Hook 是在特定 Git 事件发生时自动执行的脚本,存放在 .git/hooks/ 目录。
常用 Hook 类型
| Hook 名称 | 触发时机 | 适用场景 |
|---|---|---|
post-commit | 提交完成后 | 每次提交后更新索引 |
post-merge | 合并完成后 | 拉取代码后更新索引 |
post-checkout | 切换分支后 | 切换分支时更新索引 |
post-rewrite | rebase/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 analyze2. 团队协作
方案 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"
fiSlack 通知:
# 需要 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总结
推荐配置:
- 个人开发:使用方案 2(智能 post-commit Hook)
- 团队协作:使用方案 3(组合 Hook)+ 提交到仓库
- 大型项目:使用生产级 Hook + 时间窗口限制
关键要点:
- ✅ 后台运行,不阻塞 Git 操作
- ✅ 智能检测代码变更,避免无效触发
- ✅ 错误处理和日志记录
- ✅ 可配置、可禁用、可调试
- ✅ 团队共享,统一体验
现在你的 GitNexus 索引会自动保持最新状态!