Skip to content

Issue 186

clean edited this page Oct 6, 2026 · 1 revision

Issue #186:提交历史支持搜索筛选与文件历史

背景与问题

提交历史支持滚动加载,但无法按关键字、作者、时间或文件筛选。查找某次改动、某人的提交或某个文件的演变时,只能一直滚动或切到终端。

目标与范围

  • 搜索提交信息与提交哈希;按作者、时间范围、分支范围(全部分支 / 当前分支)、文件或目录路径筛选,条件可组合。
  • “文件”与“改动”视图右键提供“查看文件历史”,跟踪重命名(--follow),选中提交时显示该文件在此提交中的 Diff。
  • 筛选结果保留滚动加载;无结果时显示空状态;清除筛选恢复完整提交图与原有选择。
  • 本地、Worktree、SSH 仓库一致。

本次不处理 Issue 中的扩展候选:按代码内容搜索(git log -S/-G)和自然语言搜索。

方案与取舍

查询(packages/ssh-client/src/git-log.ts)

条件 Git 参数 说明
搜索 -i -F --grep=<文本> 匹配完整提交信息,不区分大小写、按字面匹配,避免用户输入被当作正则。
哈希 rev-parse --verify <前缀>^{commit} + log --no-walk 搜索词为 4–40 位十六进制且能唯一解析为以该前缀开头的提交时,若该提交也满足其他筛选与分支范围,则排在首页首位,并从信息匹配结果中去重。
作者 -i -F --author=<文本> 与 --grep 同时使用时为“且”关系(Git 行为)。
时间 --since=@<秒> --until=@<秒> 前端按本机整天换算为 Unix 秒,避免远端时区差异;与 Git 一致按提交时间筛选。@0 在 Git 中表示“无日期”,因此下限为 1。
分支范围 --all HEAD / HEAD “当前分支”传 branch=HEAD,沿用既有的单起点逻辑。
路径 --parents -- <路径> 仅按路径筛选时,父提交改写为仍显示的提交,提交图保留真实拓扑。
跟踪重命名 --follow --name-status -z 每个提交返回该文件当时的 path 与重命名前的 oldPath。
  • 分页沿用 skip + revision 快照;快照哈希加入新参数,引用变化时提示刷新。哈希命中占首页一个位置,首页至少保留一条信息匹配,确保 nextSkip 前进。
  • --follow 的两个 Git 限制:
    1. --skip 跳过的提交不会计算 Diff,重命名不会被识别,第二页会退回到新路径并重复结果。跟踪模式改为 --max-count=skip+count+1 从头遍历再在本地切片。
    2. --follow 与 --grep、--author、--since 同用时,若重命名提交本身被筛掉,Git 会停止跟踪(如按作者筛选无法看到改名前的提交)。跟踪模式下若有其他条件,先读取文件完整历史(附带 %B 完整信息与 %cI 提交时间),再按 Git 相同语义在服务端筛选;哈希前缀在同一列表中匹配,因此改名前的提交也可按哈希找到。
  • 输入参数沿用既有校验:拒绝 NUL、非整数或越界时间、开始晚于结束、非布尔 follow、无路径的 follow,统一返回 400。

界面

  • 筛选状态 logFilter 保存在仓库 store,刷新、创建标签、切换分支后保留;切换仓库清空;逐行追溯跳转到某个提交时清除筛选。
  • 筛选栏(HistoryFilterBar):文本输入停顿 400ms 后应用,Enter 立即应用;日期、范围、跟踪开关立即应用。外部设置的筛选(文件历史入口)会同步到输入框,比较时不受字段顺序影响,避免重复请求。日期非法区间就地提示且不请求。
  • 提交图:仅路径筛选时使用原有增量布局;信息、作者、时间或跟踪重命名时改为单列虚线串联(linearGraph),避免永远不会出现的父提交占用泳道,并在结果摘要中说明“虚线连接的提交之间可能还有其他提交”。
  • 文件历史:右键菜单对文件显示“查看文件历史”(开启跟踪),对目录/子模块显示“查看目录历史”(仅路径)。选中提交时自动选中该文件,使用该提交中的路径读取 Diff。
  • 进入筛选时记录当前选中的提交与文件;清除全部筛选后恢复选择并滚动到该行。原选择在首页之外时仍恢复详情区,但不强制加载到该行。
  • 空结果显示“没有符合筛选条件的提交”与“清除筛选”;交互式变基按钮移入筛选栏末尾,不再单独占一行。

验收标准

  • 按提交信息、哈希、作者、时间、路径筛选的结果与命令行一致,可组合使用。
  • 文件历史可跟踪重命名,选中提交显示该文件的 Diff。
  • 筛选结果分页加载,无结果时有空状态说明。
  • 清除筛选后恢复原有提交图与选择。
  • 本地、Worktree、SSH 仓库均通过验证。
  • 大仓库下响应可接受(未在大仓库实测)。
  • Windows/Linux 原生、外部 SSH 主机和桌面安装包验收(本次未运行)。

验证记录

日期:2026-10-06。代码提交见页首;环境:macOS arm64、Git 2.43。SSH 使用本机隔离 SSH/SFTP 服务和临时 Git 仓库,无用户凭据。验证仓库包含 4 名作者、侧分支与合并、同一文件两次重命名(parser.ts → reader.ts → scanner.ts),以及超过 50 条的后续提交。

命令或场景 结果与证据
npm run build -w @alune/shared、-w @alune/ssh-client、-w server、-w web 通过;Vite 保留大 chunk 提示
node --test packages/ssh-client/tests/log-filters.test.cjs 5 项通过:8 组筛选及组合以每页 2 条分页读取,与 git log 逐条一致;路径筛选父提交全部落在结果内;--follow 逐提交路径;跟踪模式下按信息、时间、哈希跨两次重命名命中;哈希大小写、去重、分支可达性;非法参数
npm test -w @alune/ssh-client 203 项通过,2 项按平台跳过,含既有真实 SSH 分页测试
node --test apps/web/tests/history-filter.integration.cjs 1 项通过:生产 HTTP 对本地、SSH、Worktree 执行 5 组筛选、文件历史、哈希、非法时间区间、空结果,结果一致
npm test -w web 255 项通过:筛选参数贯穿分页、刷新保留、追溯跳转清除、切换仓库清空;虚线图;日期换算与草稿规范化
npm test -w server 309 项通过
浏览器:SSH 仓库“文件”视图右键 src/scanner.ts →“查看文件历史” 切换到提交历史,显示 5 个提交,含两次重命名前的提交
浏览器:选中 fix: 修复 Parser 空行 自动选中 src/parser.ts,显示该提交的 Diff
浏览器:先选中 chore: 后续提交 57,筛选后“清除筛选” 恢复该提交的选中行与详情
浏览器:搜索不存在的文本 显示空状态与“清除筛选”
浏览器:作者 bob + 当前分支;搜索 7be6a0b 前者 3 个 Bob 提交;后者只返回该提交
浏览器:390px 深色 SSH 文件历史 页面宽度 390px,无横向溢出,筛选栏换行
大仓库耗时、Windows/Linux 原生、外部 SSH、桌面安装包 未运行

复验可运行 node apps/web/tests/history-filter-fixture.cjs,输出本地/SSH/Worktree 界面地址,退出时清理临时仓库。

改动前:现有提交历史

以下复用 v0.7.0 README 截图,展示原提交图;不是本次临时仓库的逐像素对照。

Alune 原提交历史

改动后:按提交信息筛选

2026-10-06 在本次 fixture 本地仓库采集,1440px 浅色。搜索 parser 后提交图改为虚线串联,选中提交显示 Diff。

Alune 筛选后的提交历史

改动后:文件历史跟踪重命名

SSH 仓库,1512px 浅色。旧提交打开 src/parser.ts 的 Diff。

Alune 文件历史

改动后:390px 深色

Alune 文件历史窄窗口

风险、未决事项与回退

  • 跟踪重命名并叠加其他筛选时会读取文件完整历史;跟踪模式每页从头遍历。长历史文件在大仓库中会更慢,尚未实测。
  • 时间筛选按提交时间(committer date),与列表显示的作者时间可能不同(如变基后的提交)。
  • 信息、作者、时间筛选下的提交图只表示顺序,不表示父子关系。
  • 无数据库迁移,/log 新参数均可选。回退代码提交即可移除筛选栏与菜单入口;Wiki 有独立历史,需要单独记录回退。

关联资料

Clone this wiki locally