chat

背景

我想在想设计一个 git history 方案。 

通过代码仓库的 git log 提交历史,然后在编程的时候,提供一个 skills(可以理解每一个文件和变更之间的隐形的知识关系,先让 LLM 理解)。

我大概的思路是定时扫描仓库近一年的 git history,初次全量,后续增量。 

然后构建git histiry 的提交与文件的图数据关系,额外加上 mivlus 向量库对提交的语义检索。 

使用时,可以通过语义检索---》拿到提交--》拿到文件? 

然后让 LLM 分析这些文件?

你作为一名资深架构师,评价下这个流程,然后给出更好更加可行的方案。

回答

一、对你当前方案的评价

你的核心思路——定时拉取 Git 历史 → 构建提交‑文件关系图 + 向量库 → 语义检索后喂给 LLM——方向是对的,利用了版本控制中隐含的“变更共现”“意图描述”等宝贵信息。但作为一个可落地的工程方案,存在以下几个关键缺陷:

  1. 粒度太粗
    Commit → File 只能知道“某次提交改了某文件”,但不知道文件内部的函数/类/代码块层面的关联。很多隐形知识(比如“这个 bug 修复只改了函数 A,却导致函数 B 出问题”)需要看到具体 diff hunk 甚至调用关系。

  2. 上下文爆炸
    一次语义检索可能返回多个提交,每个提交又关联多个文件。直接把这些文件完整内容丢给 LLM,token 消耗巨大,且大量无关代码会稀释关键信息。

  3. 忽略分支与合并语义
    近一年的历史包含大量 feature 分支、merge commit。如果不区分主干和分支的演化路径,单纯的“提交‑文件”图会丢失代码的“真实意图”和“稳定版本”边界。

  4. 缺少代码结构的静态关系
    单纯依赖提交历史无法捕捉函数调用、类继承、依赖注入等静态结构。而这些正是 LLM 理解“修改影响范围”所必需的上下文。

  5. 增量更新不完整
    定时扫描简单粗暴,但无法感知 git rebaseamendforce push 等改写操作,会导致图数据与真实历史不一致。


二、更可行的整体架构(增强版)

2.1 核心目标

建立一个 “代码时空知识图谱”,让 LLM 能够:

  • 给定当前文件/光标位置,检索历史上相关的变更意图(为什么这么写)
  • 找到可能受影响的远端代码(跨文件/跨函数)
  • 控制上下文大小,只给 LLM 最精华的代码片段 + 变更说明

2.2 数据模型(节点与边)

节点类型 说明 示例
Commit 提交节点,含 hash、message、时间、作者 commit#abc123
File 文件路径(跟踪重命名) src/auth/login.py
Function / Method 函数/方法签名 + 起始行号 UserService.authenticate()
Class 类名 + 文件范围 LoginHandler
DiffHunk 一次提交中一个文件的一个变更块(±行号,原代码+新代码) @@ -10,7 +10,8 @@
Symbol 变量/常量/宏定义等 MAX_RETRY

关键边关系

  • CommitMODIFIES–> File
  • CommitCONTAINS–> DiffHunk
  • DiffHunkCHANGES–> Function / Class / Symbol
    (通过解析 diff 前后代码,识别受影响的代码元素)
  • FunctionCALLS–> Function (静态分析获得)
  • ClassINHERITS–> Class
  • FileIMPORTS–> File
  • CommitFIXES–> Issue(可选,连接 issue tracker)

2.3 数据采集与增量更新

  • 全量初始化
    克隆仓库,遍历近一年所有提交(git rev-list --since="1 year ago")。按拓扑顺序处理(先父后子),构建图。

  • 增量策略
    使用 Git Hookpost-receive 或客户端 post-commit)触发更新,而不是盲目定时扫描。
    对于 rebase 等操作,利用 git reflog 检测已处理 commit 的消失,进行图清理。

  • 跨分支处理
    只对默认分支(main/master)最近活跃的 release 分支建立全量关系。对其他特性分支,仅在需要时按 commit 范围动态加载。

2.4 语义向量化(Milvus 使用)

对以下三类内容分别嵌入,存入不同 collection:

内容类型 嵌入来源 检索用途
Commit commit message + diff 的语义摘要(用一个小 LLM 生成 1‑2 句话) 用户问“之前谁处理过登录超时问题?”
DiffHunk 变更前后的代码片段 + 变更意图(从 message 继承) 检索“类似这个函数的修改历史”
Function / Class 签名 + 注释 + 代码结构(AST 简化) 找功能相似的代码单元

检索增强
用户当前编辑位置 → 提取附近函数/类 → 用其签名向量去 Milvus 检索相似的 FunctionDiffHunk → 返回 top‑K 结果。

2.5 与 LLM 交互的精炼流程

不是“检索提交 → 拿文件 → 全给 LLM”,而是:

  1. 上下文提取:当前文件 + 光标所在函数/类 + 最近编辑行
  2. 图查询
    • 该函数历史上被哪些 Commit 修改过?
    • 这些 Commit 还修改了哪些其他 File / Function(共现变更)?
    • 通过静态调用边,找到该函数调用了哪些外部函数。
  3. 向量检索
    • 用当前函数签名检索相似的 Function 节点(跨文件)。
    • 用当前代码片段检索相关的 DiffHunk(寻找类似变更模式)。
  4. 上下文裁剪
    • 只取出检索到的 DiffHunk 中的新代码片段(几十行),不取整个文件。
    • 对关联函数的代码,只提取其签名 + 前 5 行注释/实现。
    • 将 commit message 作为“为什么改”的说明。
  5. 组装 prompt
    [当前文件 + 光标位置]
    [历史相关变更]:commit #a 改了函数 foo(),因为“修复 NPE”;改动如下:<diff>
    [相似函数参考]:文件 bar.py 中的函数 baz() 实现了类似逻辑:<签名+头几行>
    [可能影响的其他代码]:函数 foo() 调用了 util.check(),该函数近期被 commit #b 修改过...
    

    总 token 控制在 4k 以内(可根据模型调整)。

2.6 存储与查询引擎

  • 图数据库:Neo4j 或 JanusGraph。
    用于快速遍历 “Commit → DiffHunk → Function → Call → Function” 等路径。
  • 向量数据库:Milvus 或 Qdrant。
    存储 embedding 并支持相似检索。
  • 代码解析
    • Python 用 ast,Java 用 javaparser,Go 用 go/parser
    • 解析后建立符号表,识别函数/类/调用关系。
  • 变更识别
    对每个 diff,用 git diff --unified=0 获取精确行号,再用解析器判断该行号属于哪个语法节点(函数/类)。

三、与你的方案对比

维度 你的方案 增强方案
粒度 提交 → 文件 提交 → diff块 → 函数/类/符号
静态关系 调用图、继承图、导入图
检索依据 仅 commit message commit message + diff 语义 + 代码结构
LLM 输入 整个文件 精准的 diff 片段 + 关键函数签名
更新方式 定时扫描全量 Git hook + reflog 增量
分支处理 忽略 默认分支为主,特性分支按需加载

四、落地建议(渐进式)

如果你的资源有限,可以分阶段实现:

Phase 1(快速验证)

  • 只收集默认分支的 commit + file 关系。
  • 对每个 commit,用 LLM 生成一个“一句话摘要”存入 Milvus。
  • 检索时返回相关 commit 的摘要及修改的文件列表,再由 LLM 根据当前文件内容自主决定是否请求 diff。
  • 优点:实现简单,1 周内可跑通。

Phase 2(核心价值)

  • 增加 diff 解析和函数识别(先支持一种语言,如 Python)。
  • 构建简单的调用关系(正则匹配 import函数名()。
  • 用图数据库存储函数级关系。

Phase 3(完整系统)

  • 支持多语言符号解析。
  • 全量图 + 向量库,使用 Git hook 增量更新。
  • 集成到 IDE 插件中,实现实时代码辅助。

五、一句话总结

你的思路是良好的起点,但要真正让 LLM 理解“隐形知识”,必须将粒度细化到函数/代码块级别,并融合静态调用关系。

建议采用 “提交 → diff 块 → 受影响的语法节点” 的图模型,配合向量语义检索,最后对 LLM 做精炼的上下文投喂,才能在不爆炸 token 的前提下提供有价值的编程辅助。

参考资料