代码评审自动化工具

这是一个基于 Gemini API 的自动化代码评审工具,可以自动分析 Git 仓库中的代码变更并生成评审报告。

功能特点

使用前提

系统要求

API 配置

在使用前,需要配置以下参数:

API_URL="https://gemini.tangrenbin.us.kg"    # API服务器地址
API_KEY="your_api_key_here"                  # 你的API密钥

安装和配置

  1. 下载脚本到本地
  2. 添加执行权限:
    chmod +x code_review.sh
  3. 修改 API 配置(如果需要)

使用方法

  1. 进入 Git 仓库目录
  2. 运行脚本:
    ./code_review.sh

脚本会自动:

  1. 检查必要的依赖
  2. 验证 Git 仓库
  3. 获取所有变更的文件
  4. 对每个文件进行 AI 评审
  5. 生成评审报告

评审报告

评审报告将保存在 review_reports 目录下,文件名格式为:

review_report_YYYYMMDD_HHMMSS.md

报告内容包括:

错误处理

脚本内置了多种错误处理机制:

调试模式

如果需要查看详细的调试信息,可以取消注释以下行:

# echo "DEBUG: 请求数据: $json_data" >&2
# echo "DEBUG: API响应: $response" >&2

注意事项

  1. 确保在运行脚本前已经正确配置 API 密钥
  2. 脚本需要在 Git 仓库目录下运行
  3. 评审报告目录会自动创建
  4. 大文件评审可能需要较长时间
  5. 如果遇到 API 限制,请适当调整重试间隔

常见问题

  1. 权限问题

    chmod +x code_review.sh
  2. 依赖安装

    # Ubuntu/Debian
    sudo apt-get install curl jq
    
    # CentOS/RHEL
    sudo yum install curl jq
  3. 如果报告为空,请检查:

    • Git 仓库是否有变更
    • API 密钥是否正确
    • 网络连接是否正常
#!/bin/bash

# Configuration
API_URL="https://gemini.tangrenbin.us.kg"
API_KEY="AIzaSyDOLKXvMTqw0EgLHVOA13fgBZHbS8NEvBs"
REPORT_DIR="review_reports"

# Check dependencies
check_dependencies() {
    local missing_deps=()
    
    if ! command -v jq &> /dev/null; then
        missing_deps+=("jq")
    fi
    
    if ! command -v python3 &> /dev/null; then
        missing_deps+=("python3")
    fi

    if ! command -v curl &> /dev/null; then
        missing_deps+=("curl")
    fi

    if ! command -v git &> /dev/null; then
        missing_deps+=("git")
    fi
    
    if [ ${#missing_deps[@]} -ne 0 ]; then
        echo "错误: 缺少必要的依赖:"
        printf '%s\n' "${missing_deps[@]}"
        echo "请安装所需依赖后重试"
        exit 1
    fi
}

# Check if in git repository
check_git_repo() {
    if ! git rev-parse --is-inside-work-tree &> /dev/null; then
        echo "错误: 当前目录不是 git 仓库"
        exit 1
    fi
}

# Function to get available model
get_model() {
    local response
    response=$(curl -s -f -X GET "$API_URL/v1/models" \
        -H "Content-Type: application/json" \
        -H "Authorization: Bearer $API_KEY") || {
        echo "错误: API 请求失败" >&2
        return 1
    }
    
    local model
    model=$(echo "$response" | jq -r '.data[] | select(.id | contains("gemini")) | .id' 2>/dev/null | tail -n 1)
    
    if [ -z "$model" ]; then
        echo "错误: 无法获取可用的模型" >&2
        echo "API返回: $response" >&2
        return 1
    fi
    
    echo "$model"
}

# Function to get the full content of a file
get_file_content() {
    local file="$1"
    if [ ! -f "$file" ]; then
        echo "错误: 文件不存在: $file" >&2
        return 1
    fi
    
    # 使用 base64 编码处理特殊字符
    base64 "$file" | base64 --decode 2>/dev/null || {
        echo "错误: 文件内容读取失败: $file" >&2
        return 1
    }
}

# Function to call Gemini API
call_gemini_api() {
    local prompt="$1"
    local max_retries=3
    local retry_count=0
    
    # 获取可用模型
    local model
    if ! model=$(get_model); then
        echo "错误: 无法获取可用模型" >&2
        return 1
    fi
    
    while [ $retry_count -lt $max_retries ]; do
        # 使用jq构建JSON请求
        local json_data
        json_data=$(jq -n \
            --arg msg "$prompt" \
            --arg model "$model" \
            '{
                model: $model,
                messages: [
                    {
                        role: "user",
                        content: $msg
                    }
                ],
                stream: false,
                temperature: 0.7,
                max_tokens: 2048
            }') || {
            echo "错误: JSON 数据准备失败" >&2
            return 1
        }
        
        echo "DEBUG: 使用模型: $model" >&2
        echo "DEBUG: 发送请求到 $API_URL/v1/chat/completions" >&2
        # echo "DEBUG: 请求数据: $json_data" >&2
        
        local response
        response=$(curl -s -X POST "$API_URL/v1/chat/completions" \
            -H "Content-Type: application/json" \
            -H "Authorization: Bearer $API_KEY" \
            -d "$json_data")
        
        local curl_exit_code=$?
        echo "DEBUG: curl退出码: $curl_exit_code" >&2
        # echo "DEBUG: API响应: $response" >&2
        
        if [ $curl_exit_code -ne 0 ]; then
            retry_count=$((retry_count + 1))
            if [ $retry_count -eq $max_retries ]; then
                echo "错误: API 调用失败,已重试 $max_retries 次" >&2
                echo "错误详情: $response" >&2
                return 1
            fi
            echo "警告: API 调用失败,正在重试 ($retry_count/$max_retries)..." >&2
            sleep 2
            continue
        fi
        
        if [ -n "$response" ]; then
            # 检查响应是否包含错误信息
            if echo "$response" | jq -e 'has("error")' > /dev/null; then
                local error_msg=$(echo "$response" | jq -r '.error.message // .error')
                echo "错误: API返回错误: $error_msg" >&2
                retry_count=$((retry_count + 1))
                [ $retry_count -lt $max_retries ] && sleep 2
                continue
            fi
            
            # 从响应中提取内容
            local content
            content=$(echo "$response" | jq -r '.choices[0].message.content' 2>/dev/null)
            if [ -n "$content" ] && [ "$content" != "null" ]; then
                echo "$content"
                return 0
            fi
            
            echo "$response"
            return 0
        fi
        
        retry_count=$((retry_count + 1))
        [ $retry_count -lt $max_retries ] && sleep 2
    done
    
    echo "错误: API 响应为空" >&2
    return 1
}

main() {
    # Run dependency check
    check_dependencies
    
    # Check if in git repository
    check_git_repo
    
    # Create reports directory
    mkdir -p "$REPORT_DIR" || {
        echo "错误: 无法创建报告目录" >&2
        exit 1
    }
    
    # Generate report file path
    local timestamp=$(date +"%Y%m%d_%H%M%S")
    local report_file="$REPORT_DIR/review_report_${timestamp}.md"
    
    # Initialize report file
    {
        echo "# 代码评审报告 - $(date '+%Y-%m-%d %H:%M:%S')"
        echo -e "\n## 变更文件列表\n"
    } > "$report_file" || {
        echo "错误: 无法创建报告文件" >&2
        exit 1
    }
    
    # Get list of changed files
    local changed_files
    changed_files=$(git diff --name-only) || {
        echo "错误: 无法获取变更文件列表" >&2
        exit 1
    }
    
    if [ -z "$changed_files" ]; then
        echo "没有检测到文件变更。"
        exit 0
    fi
    
    echo "开始代码评审..."
    
    # Process each changed file
    local success_count=0
    local total_files=0
    
    while IFS= read -r file; do
        [ -z "$file" ] && continue
        ((total_files++))
        
        if [ -f "$file" ]; then
            echo "正在评审: $file"
            echo -e "\n### $file\n" >> "$report_file"
            
            # Get file content
            local file_content
            if ! file_content=$(get_file_content "$file"); then
                echo "警告: 跳过文件 $file" >&2
                continue
            fi
            
            # Prepare prompt
            local prompt="请对以下代码进行代码评审,重点关注:
1. 代码质量和可维护性
2. 潜在的bug和安全问题
3. 性能优化建议
4. 最佳实践遵循情况

代码文件:$file

\`\`\`
$file_content
\`\`\`"
            
            # Get review comments
            local review_comments
            if review_comments=$(call_gemini_api "$prompt"); then
                echo -e "$review_comments\n" >> "$report_file"
                echo "✓ 完成评审: $file"
                ((success_count++))
            else
                echo "✗ 评审失败: $file"
            fi
        fi
    done <<< "$changed_files"
    
    echo -e "\n评审完成: 成功评审 $success_count/$total_files 个文件"
    echo "评审报告已保存至: $report_file"
}

# Run main function
main