# 代码变更分析

本文档详细说明如何使用 gitlink-cli 分析 PR 的代码变更。

## 📋 概述

代码变更分析是智能代码审查的第一步，通过获取 PR 的文件列表和 diff 内容，为后续的 AI 分析提供数据基础。

## 🎯 分析流程

```
开始
  ↓
1. 获取 PR 基本信息
  ├─ 使用 pr +view 获取 PR 详情
  └─ 确认 PR 存在且可访问
  ↓
2. 获取变更文件列表
  ├─ 使用 pr +files 获取文件列表
  └─ 识别新增/修改/删除的文件
  ↓
3. 获取 diff 内容
  ├─ 使用 pr +diff 获取完整 diff
  └─ 解析代码变更详情
  ↓
4. 数据预处理
  ├─ 过滤无关文件（如二进制文件）
  ├─ 提取代码片段
  └─ 组织分析数据
  ↓
完成
```

## 🔧 步骤详解

### 步骤 1：获取 PR 基本信息

**目的**: 确认 PR 存在且可访问，获取 PR 的元数据信息。

**命令**:
```bash
gitlink-cli pr +view --id <pr_id> --format json
```

**示例**:
```bash
# 获取 PR #123 的基本信息
gitlink-cli pr +view --id 123 --format json
```

**返回结果**:
```json
{
  "ok": true,
  "data": {
    "id": 123,
    "project_issues_index": 123,
    "title": "Feature: Add user authentication",
    "body": "This PR adds user authentication...",
    "author": {
      "login": "developer",
      "user_id": 456
    },
    "status": "open",
    "pull_request_status": 0,
    "head": "feature/auth",
    "base": "main",
    "created_at": "2026-06-12T10:00:00Z",
    "updated_at": "2026-06-12T10:30:00Z"
  }
}
```

**关键信息提取**:
- `id`: PR 数据库 ID（用于后续 API 调用）
- `project_issues_index`: PR 编号（网页显示）
- `title`: PR 标题
- `author`: 作者信息
- `status`: PR 状态（open/closed/merged）
- `head` / `base`: 分支信息

### 步骤 2：获取变更文件列表

**目的**: 获取 PR 中所有变更的文件列表，了解代码变更的范围。

**命令**:
```bash
gitlink-cli pr +files --id <pr_id> --format json
```

**示例**:
```bash
# 获取 PR #123 的变更文件列表
gitlink-cli pr +files --id 123 --format json
```

**返回结果**:
```json
{
  "ok": true,
  "data": {
    "files": [
      {
        "filename": "src/auth/login.go",
        "status": "modified",
        "additions": 50,
        "deletions": 20,
        "changes": 70,
        "patch": "@@ -1,10 +1,15 @@\n+func login() {"
      },
      {
        "filename": "src/auth/user.go",
        "status": "added",
        "additions": 80,
        "deletions": 0,
        "changes": 80,
        "patch": "+package auth\n+\n+func User() {"
      },
      {
        "filename": "README.md",
        "status": "modified",
        "additions": 5,
        "deletions": 2,
        "changes": 7,
        "patch": "@@ -1,5 +1,7 @@\n+## Usage\n ..."
      }
    ],
    "total_files": 3,
    "total_additions": 135,
    "total_deletions": 22,
    "total_changes": 157
  }
```

**文件状态说明**:
- `added`: 新增文件
- `modified`: 修改文件
- `deleted`: 删除文件
- `renamed`: 重命名文件

**统计信息**:
- `total_files`: 变更文件总数
- `total_additions`: 新增行数
- `total_deletions`: 删除行数
- `total_changes`: 总变更行数

### 步骤 3：获取 diff 内容

**目的**: 获取 PR 的完整 diff 内容，用于 AI 代码分析。

**命令**:
```bash
gitlink-cli pr +diff --id <pr_id> --format json
```

**示例**:
```bash
# 获取 PR #123 的 diff 内容
gitlink-cli pr +diff --id 123 --format json
```

**返回结果**:
```json
{
  "ok": true,
  "data": {
    "diff": "diff --git a/src/auth/login.go b/src/auth/login.go\nindex 1234567..abcdefg 100644\n--- a/src/auth/login.go\n+++ b/src/auth/login.go\n@@ -1,10 +1,15 @@\n package auth\n\n+func login(username, password string) error {\n+\tdb, _ := sql.Open(\"mysql\", dsn)\n+\tquery := \"SELECT * FROM users WHERE username = '\" + username + \"'\"\n+\t...\n+}\n",
    "files_count": 3,
    "additions": 135,
    "deletions": 22
  }
}
```

**diff 格式说明**:
- 标准 unified diff 格式
- 包含文件头、变更块、代码行
- `+` 表示新增行
- `-` 表示删除行

### 步骤 4：数据预处理

**目的**: 清理和组织数据，为 AI 分析做准备。

#### 4.1 过滤无关文件

**需要过滤的文件类型**:
- 二进制文件（图片、字体、压缩包）
- 配置文件（package.json、tsconfig.json）
- 文档文件（README.md、CHANGELOG.md）
- 测试文件（*_test.go、*.spec.js）

**过滤规则**:
```javascript
const shouldSkip = (filename) => {
  // 跳过二进制文件
  const binaryExts = ['.png', '.jpg', '.gif', '.pdf', '.zip', '.exe'];
  if (binaryExts.some(ext => filename.endsWith(ext))) {
    return true;
  }

  // 跳过配置文件
  const configFiles = ['package.json', 'tsconfig.json', '.gitignore'];
  if (configFiles.includes(filename)) {
    return true;
  }

  // 跳过文档文件
  if (filename.match(/^(README|CHANGELOG|CONTRIBUTING)\.md$/i)) {
    return true;
  }

  return false;
};
```

#### 4.2 提取代码片段

**目的**: 从 diff 中提取变更的代码片段，便于 AI 分析。

**示例**:
```javascript
const extractCodeSnippets = (diff) => {
  const lines = diff.split('\n');
  const snippets = [];
  let currentSnippet = [];
  let inHunk = false;

  lines.forEach(line => {
    if (line.startsWith('@@')) {
      // 开始新的代码块
      if (currentSnippet.length > 0) {
        snippets.push(currentSnippet.join('\n'));
      }
      currentSnippet = [line];
      inHunk = true;
    } else if (inHunk && (line.startsWith('+') || line.startsWith('-') || line.startsWith(' '))) {
      // 收集代码行
      currentSnippet.push(line);
    }
  });

  if (currentSnippet.length > 0) {
    snippets.push(currentSnippet.join('\n'));
  }

  return snippets;
};
```

#### 4.3 组织分析数据

**最终数据结构**:
```json
{
  "pr_info": {
    "id": 123,
    "title": "Feature: Add user authentication",
    "author": "developer",
    "branch": "feature/auth → main"
  },
  "files": [
    {
      "filename": "src/auth/login.go",
      "status": "modified",
      "language": "go",
      "code_snippets": [
        {
          "start_line": 10,
          "end_line": 25,
          "code": "+func login(username, password string) error {"
        }
      ]
    }
  ],
  "statistics": {
    "total_files": 3,
    "code_files": 2,
    "total_additions": 135,
    "total_deletions": 22
  }
}
```

## 💡 最佳实践

### 1. 按文件类型分组

将变更文件按语言和类型分组，便于针对性分析：

```javascript
const groupFilesByLanguage = (files) => {
  const groups = {
    go: [],
    javascript: [],
    python: [],
    other: []
  };

  files.forEach(file => {
    const ext = file.filename.split('.').pop();
    const lang = detectLanguage(ext);
    groups[lang].push(file);
  });

  return groups;
};
```

### 2. 优先审查核心文件

优先审查核心业务逻辑文件：

```javascript
const prioritizeFiles = (files) => {
  const priority = {
    'high': [],    // 核心业务逻辑
    'medium': [],  // 工具函数
    'low': []      // 配置、测试
  };

  files.forEach(file => {
    if (file.filename.includes('core') || file.filename.includes('service')) {
      priority.high.push(file);
    } else if (file.filename.includes('util') || file.filename.includes('helper')) {
      priority.medium.push(file);
    } else {
      priority.low.push(file);
    }
  });

  return priority;
};
```

### 3. 限制分析范围

对于大型 PR，限制分析范围：

```javascript
const limitAnalysisScope = (files, maxFiles = 10, maxLines = 1000) => {
  let totalLines = 0;
  const selectedFiles = [];

  for (const file of files) {
    if (selectedFiles.length >= maxFiles) break;
    if (totalLines + file.changes > maxLines) break;

    selectedFiles.push(file);
    totalLines += file.changes;
  }

  return selectedFiles;
};
```

## 🔍 常见问题

### Q: 如何处理大型 PR？

**A**: 大型 PR（>1000 行）建议：
1. 按模块分组分析
2. 优先审查核心文件
3. 分批生成审查报告
4. 建议作者拆分为多个小 PR

### Q: 如何处理重命名文件？

**A**: GitLink 的 PR API 会正确处理重命名：
- `status` 为 `renamed`
- `patch` 包含重命名前后的完整路径
- 分析时使用新文件名

### Q: 如何检测文件语言？

**A**: 使用文件扩展名检测：

```javascript
const detectLanguage = (filename) => {
  const ext = filename.split('.').pop();
  const languageMap = {
    'go': 'go',
    'js': 'javascript',
    'ts': 'typescript',
    'py': 'python',
    'java': 'java',
    'rb': 'ruby',
    'php': 'php'
  };
  return languageMap[ext] || 'other';
};
```

## 📚 相关文档

- [代码质量检查](code-review-quality.md) - 代码质量分析
- [安全性检查](code-review-security.md) - 安全性分析
- [性能检查](code-review-performance.md) - 性能分析
- [完整工作流](../examples/comprehensive-review-workflow.md) - 完整审查流程

---

*最后更新: 2026-06-12*
