资讯动态

在 Qwen Code Web Shell 中构建 Git 提交历史浏览器:从 `git log` 管线到 `/log` 斜杠命令的完整实现

发布时间:2026/9/13 2:27:05 来源:尧图企业网站定制
在 Qwen Code Web Shell 中构建 Git 提交历史浏览器从git log管线到/log斜杠命令的完整实现【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code本文围绕设计文档 docs/design/web-shell/2026-07-19-web-shell-git-log.md 展开讲解 Qwen Code 如何为 Web Shell 增加一个只读的 Git 提交历史浏览器/log斜杠命令 GitDialog 的 History 视图覆盖从 core 层fetchGitLog/fetchGitCommitDetail、daemon 层 REST 路由、SDK 层 client 方法到前端GitLogDialog组件的完整调用链并给出分页、SHA 校验、refs 解析、merge 识别等关键细节与对应源码路径。读完本文你将掌握该功能的架构分层、wire format、参数约束与实现取舍可在自己的集成或二次开发中直接复用这套模式。背景为什么 Web Shell 需要独立的提交历史视图Qwen Code 的 Web Shell 此前已通过2026-07-16-webshell-git-status-diff.md的设计落地了 branch chip 增强dirty / ahead-behind / stash / detached / operation 等状态感知与可视化 diff 查看器GitDiffDialog解决了工作区当前状态的感知问题。但用户仍有另一个高频需求查看提交历史。在设计文档出现之前想了解最近做了什么只能在 chat 里让 agent 执行git log --oneline再读一段纯文本。对图形界面而言这是明显的体验缺口——提交历史是代码审查、回溯变更、理解项目演进的基础视图。本期设计目标见 设计文档提供/log斜杠命令与 UI 入口打开提交历史浏览器紧凑列表短 SHA、subject、作者、相对时间、ref 标签branch/tag点击展开完整 commit message body 文件变更统计numstat分页加载Load more不一次性拉取全部历史复用现有架构模式core git 工具 → daemon 路由 → SDK → Web Shell 组件全程只读不引入任何写操作。明确的非目标设计文档同时划定了边界避免范围膨胀不做提交图谱graph / DAG 可视化不做分支筛选 / 搜索留作后续增量不做任意 commit 间的 diff 查看留作后续增量可复用 diff 基础设施不做 commit 详情中的行级 diff文件统计足够行级 diff 是后续增量不做 blame / annotate不改变 agent 侧的 git 行为。方案概述一条贯穿四层的只读调用链整个功能沿用 Qwen Code 既有的分层架构core (gitDiff.ts 扩展) ├─ fetchGitLog(cwd, { limit, skip }) [新增] 提交列表 └─ fetchGitCommitDetail(cwd, sha) [新增] 单 commit 详情message numstat │ ▼ daemon (serve) ├─ GET /workspace/git/log?limitskip [新增] ├─ GET /workspace/git/log/commit?sha [新增] └─ qualified 版本 /workspaces/:workspace/git/log[/commit] │ ▼ SDK (DaemonClient types) ├─ DaemonGitLogEntry / DaemonGitLog [新增] ├─ DaemonGitCommitDetail [新增] ├─ workspaceGitLog(limit?, skip?) [新增] └─ workspaceGitCommitDetail(sha) [新增] │ ▼ Web Shell (client) ├─ GitDialog.tsx [新增] Changes / History 统一容器 ├─ GitLogDialog.tsx ( .module.css) [新增] 提交列表 展开详情 ├─ App.tsx [扩展] /log 命令拦截 dialog view 状态 └─ i18n.tsx [扩展] gitLog.* 文案这套模式的关键收益git 命令执行统一收敛在 core 层便于复用runGit、parseGitNumstat、上限常量并做单元测试daemon 层只做参数解析与 wire format 映射SDK 层为前端提供类型安全的 client 方法Web Shell 层专注 UI 与交互。Core 层fetchGitLog与fetchGitCommitDetail两个新函数都位于 packages/core/src/utils/gitDiff.ts与既有 diff 能力共享runGit内部函数、MAX_FILES50等常量。文件末尾专门划分了 Git log 段落约从 L1425 开始并新增两个常量/** Maximum entries per fetchGitLog page. */ export const MAX_LOG_LIMIT 200; /** Default page size for fetchGitLog. */ export const DEFAULT_LOG_LIMIT 50;数据结构Core 层定义gitDiff.ts#L1434-L1475export interface GitLogEntry { sha: string; // 完整 40 字符 SHA shortSha: string; // 短 SHAgit 默认缩写 authorName: string; authorEmail: string; authorDate: number; // unix timestamp秒 subject: string; refs: string; // %D 输出如 HEAD - main, origin/main, v1.2.0 parents: string[]; // parent SHA 列表length 1 表示 merge commit } export interface GitLogResult { entries: GitLogEntry[]; hasMore: boolean; // 是否还有更多提交 } export interface GitCommitFileStat { path: string; added: number; // 二进制文件为 0 removed: number; isBinary: boolean; } export interface GitCommitDetail { sha: string; shortSha: string; authorName: string; authorEmail: string; authorDate: number; subject: string; body: string; // 完整 message body可能为空 refs: string; parents: string[]; files: GitCommitFileStat[]; filesCount: number; linesAdded: number; linesRemoved: number; hiddenCount: number; // 超出 MAX_FILES 的文件数 }fetchGitLogNUL 分隔协议 一页多取一条判断 hasMoreexport async function fetchGitLog( cwd: string, options?: { limit?: number; skip?: number; range?: string }, ): PromiseGitLogResult | null {实现要点gitDiff.ts#L1501-L1552git 命令git --no-optional-locks log -z --format%H%x00%h%x00%an%x00%ae%x00%at%x00%s%x00%D%x00%P -n limit1 --skipskip\x00NUL分隔 8 个字段%H完整 SHA、%h短 SHA、%an作者名、%ae作者邮箱、%at作者时间戳、%ssubject、%Drefs、%Pparents-z用 NUL 终止每条记录利用Git commit message 不允许 NUL的特性subject 中的其他控制字符不会与协议冲突请求limit 1条判断hasMore返回时截断到limit--no-optional-locks避免写锁只读语义。limit/skip 归一化limit钳制在[1, 200]MAX_LOG_LIMITskip下限为 0。空仓库区分git log在空仓库无 commit会失败此时通过git rev-parse --verify HEAD探测HEAD 也无法解析则判定为空仓库返回{ entries: [], hasMore: false }否则视为真实失败返回null。解析stdout.split(\0)后每 8 个字段构成一条记录parseLogFields见 gitDiff.ts#L1481-L1493parents 按空格拆分。另外实现还支持可选的range参数带白名单正则校验拒绝以-或..开头的注入输入daemon 层会将其作为查询参数透传。fetchGitCommitDetail元数据 numstat 两次 git 调用export async function fetchGitCommitDetail( cwd: string, sha: string, ): PromiseGitCommitDetail | null {实现要点gitDiff.ts#L1561-L1664SHA 校验/^[0-9a-f]{7,40}$/i非法的 sha 直接返回null防止注入core 层与 daemon 层双重校验。第一次调用元数据git --no-optional-locks log -1 -z --format%H%x00%h%x00%an%x00%ae%x00%at%x00%s%x00%D%x00%P%x00%b sha比列表多一个%b完整 body共 9 个字段。第二次调用文件统计git --no-optional-locks diff-tree --no-commit-id --numstat -r -z sharoot commit无 parent使用--rootmerge commit 特殊处理纯diff-tree对 merge 输出为空因此 parents 数大于 1 时改为对第一个 parent 做 diffsha^1 sha显式加-M检测重命名git mv计为一个文件、按新路径展示——diff-tree是 plumbing 命令不遵循diff.renames配置不加-M时重命名会拆成 delete add 两条。统计聚合forEachNumstatEntry遍历累计filesCount/linesAdded/linesRemoved逐文件数组受MAX_FILES50上限约束超出部分计入hiddenCount保证峰值堆占用可控。非仓库 / sha 不存在 / git 失败返回nullbody 会去掉末尾换行。Daemon 层workspace-git-log.ts路由与双注册模式新增 packages/cli/src/serve/routes/workspace-git-log.ts完全遵循workspace-git-diff.ts的双注册模式export function registerWorkspaceGitLogRoutes(app, deps: { boundWorkspace: string; sendBridgeError: SendBridgeError; }): void { app.get(/workspace/git/log, ...); app.get(/workspace/git/log/commit, ...); } export function registerWorkspaceQualifiedGitLogRoutes(app, deps: { workspaceRegistry: WorkspaceRegistry; sendBridgeError: SendBridgeError; }): void { app.get(/workspaces/:workspace/git/log, ...); app.get(/workspaces/:workspace/git/log/commit, ...); }GET /workspace/git/log?limit50skip0parsePaginationworkspace-git-log.ts#L88-L100解析limit非法值回退默认 50、上限 200与skip非法值回退 0调用fetchGitLog(workspaceCwd, { limit, skip, range })buildLogList映射为DaemonGitLogv: 1、available标记refs为空时不序列化该字段applyReadHeaders(res)设置只读响应头错误统一走sendBridgeError。GET /workspace/git/log/commit?shashadaemon 层再次校验sha格式/^[0-9a-f]{7,40}$/i不合法直接返回 400parse_error合法则调用fetchGitCommitDetail并映射为DaemonGitCommitDetail。qualified 路由复用resolveTrustedRuntimeworkspaceRegistry trusted 校验resolveContainedCwd确保只对受信任的工作区运行时提供服务。SDK 层类型定义与 client 方法Wire format 类型新增类型位于 packages/sdk-typescript/src/daemon/types.ts从src/index.ts与src/daemon/index.ts统一导出export interface DaemonGitLogEntry { sha: string; shortSha: string; authorName: string; authorEmail: string; authorDate: number; subject: string; refs?: string; // 可选为空时不输出 parents: string[]; } export interface DaemonGitLog { v: 1; workspaceCwd: string; available: boolean; entries: DaemonGitLogEntry[]; hasMore: boolean; } export interface DaemonGitCommitFileStat { path: string; added: number; removed: number; isBinary: boolean; } export interface DaemonGitCommitDetail { v: 1; workspaceCwd: string; available: boolean; sha: string; shortSha: string; authorName: string; authorEmail: string; authorDate: number; subject: string; body: string; refs?: string; parents: string[]; files: DaemonGitCommitFileStat[]; filesCount: number; linesAdded: number; linesRemoved: number; hiddenCount: number; }Client 方法DaemonClient主 client 与 workspace-qualified client 均有新增两个方法packages/sdk-typescript/src/daemon/DaemonClient.ts#L1424-L1447async workspaceGitLog( limit?: number, skip?: number, range?: string, ): PromiseDaemonGitLog { const params new URLSearchParams(); if (limit ! null) params.set(limit, String(limit)); if (skip ! null) params.set(skip, String(skip)); if (range) params.set(range, range); const qs params.toString(); return await this.jsonRequestDaemonGitLog( /workspace/git/log${qs ? ?${qs} : }, GET /workspace/git/log, { mode: rest }, ); } async workspaceGitCommitDetail(sha: string): PromiseDaemonGitCommitDetail { return await this.jsonRequestDaemonGitCommitDetail( /workspace/git/log/commit?sha${urlEncode(sha)}, GET /workspace/git/log/commit, { mode: rest }, ); }细节参数为空时不拼接 query stringsha 经urlEncode编码后再拼入 URLjsonRequest走rest模式。Web Shell 层GitLogDialog组件与交互组件骨架新增 packages/web-shell/client/components/dialogs/GitLogDialog.tsx 与配套 CSS ModuleProps 与GitDiffDialog对齐export function GitLogDialog({ workspaceCwd, onClose, }: { workspaceCwd: string; onClose: () void; });数据获取策略打开时调用client.workspaceByCwd(workspaceCwd).workspaceGitLog()拉首页PAGE_SIZE 50Load more 按钮使用独立的服务端 offset 调用workspaceGitLog(50, nextSkip)追加时按 SHA 去重展开单条时调用workspaceGitCommitDetail(sha)按需拉详情取消模式与GitDiffDialog一致effect 用let cancelledclick 用useRef避免竞态导致的状态泄漏见 GitLogDialog.tsx#L75-L104。渲染细节DialogShell title{t(gitLog.title)} sizexl allowFullscreensubtitle 显示已加载条数body 状态机覆盖 loading / error / unavailable / empty / data。提交行短 SHAmonospace、muted 色、subject、作者名、相对时间另提供 copy SHA 按钮writeClipboardTextuseCopiedFlash闪烁反馈。refs 解析GitLogDialog.tsx#L33-L45按逗号拆分、trim、过滤空值只取前 3 个对应设计文档中大量 tag/branch 时 refs 字符串很长的风险控制HEAD - branch提取分支名并标记isHead渲染为小标签head ref 有独立样式。merge commitentry.parents.length 1时显示⎇图标。展开详情完整 body 用pre渲染保留换行文件统计列表复用 GitDiffDialog 的语义样式——N −M path二进制文件显示~超过MAX_FILES时展示hiddenCount提示还有 N 个文件未显示。相对时间使用 packages/web-shell/client/utils/timeAgo.ts 的timeAgo(timestamp, now, language)——基于Intl.RelativeTimeFormat实现秒/分/时/天/周/月/年分级格式化周 5用周、月 12用月、其余用年不引入外部库支持当前语言环境。GitDialog 统一容器与 App 集成GitDialog.tsx 作为 Changes / History 的统一容器内部持有view状态log/diff/commitview log时渲染GitLogContent来自 GitLogDialog通过 tabs 在视图间切换而不关闭、不重新打开 Radix dialog。App.tsx 中新增统一的gitDialog状态{ workspaceCwd, gitCwd, view: diff | log } | undefinedApp.tsx#L8069 附近/log斜杠命令按/diff同模式做本地拦截并将view设为logGit chip 默认打开diffviewdialogOpen判断纳入gitDialog ! undefinedgetLocalCommands补充log补全项渲染时条件渲染单个GitDialogkey 含 workspaceCwd / gitCwd / view见 App.tsx#L16901-L16906。i18n 文案新增gitLog.*命名空间en zh-CN覆盖加载、空态、错误、分页、统计等全部状态KeyENzh-CNgitLog.titleHistory提交历史gitLog.subtitle(v) \${v?.count} commits|(v) ${v?.count} 条提交gitLog.loadingLoading history…加载历史中…gitLog.emptyNo commits yet暂无提交gitLog.unavailableGit is not available for this workspace此工作区不可用 GitgitLog.errorFailed to load history加载历史失败gitLog.loadMoreLoad more加载更多gitLog.loadingMoreLoading…加载中…gitLog.files(v) \${v?.count} files · ${v?.added} −${v?.removed}|(v) ${v?.count} 个文件 · ${v?.added} −${v?.removed}gitLog.detailErrorFailed to load commit details加载提交详情失败gitLog.hidden(v) \${v?.count} more file(s) not shown|(v) 还有 ${v?.count} 个文件未显示gitLog.copySha(v) \Copy commit ${v?.sha}|(v) 复制提交 ${v?.sha}localCommand.logNoWorkspaceNo workspace is available yet to show history for.当前还没有可用于查看历史的工作区。兼容性与风险控制兼容性设计新路由、新 SDK 方法、新组件全部是增量不修改任何现有接口旧 daemon 没有/workspace/git/log路由SDK 调用会 404前端显示Failed to load history错误占位不会崩溃旧 client 不受影响不请求新路由非 git 仓库 / 空仓库fetchGitLog返回null或空列表前端显示对应占位/log仅在 Web Shell 客户端存在不影响 CLI / ACP 等其他客户端形态。风险与对策摘录自设计文档风险对策超大仓库100k commits上--skip是 O(skip) 的深翻页性能退化每页 50 条、用户手动翻页场景下 skip 通常不大后续如需深分页可改用--beforetimestamp游标本期不做%Drefs在大量 tag/branch 时字符串很长UI 只显示前 2-3 个 ref其余折叠sha 查询参数注入core 层正则校验/^[0-9a-f]{7,40}$/i daemon 层二次校验跨包新增类型扩大 PR 面积类型最小化不引入额外依赖测试计划与验证单元测试corefetchGitLog见 packages/core/src/utils/gitDiff.test.ts正常仓库的字段解析SHA/作者/时间/subject/refs/parents分页hasMore判断与skip偏移空仓库返回空列表非仓库返回nulllimit超 200 截断。corefetchGitCommitDetailbody numstat 正确root commit 的--root生效merge commit 的 parents 列表非法 sha注入尝试被拒绝不存在的 sha 返回null。daemon 路由见 packages/cli/src/serve/routes/workspace-git-log.test.tscore 结果到 wire format 的映射commit 路由的 sha 校验qualified 路由的 trusted 校验limit/skip 非法值处理。SDK见 packages/sdk-typescript/test/unit/DaemonClient.test.tsworkspaceGitLog有/无参数时的 URL 拼接workspaceGitCommitDetail的 URL 拼接与 sha 编码。Web ShellGitLogDialog见 packages/web-shell/client/components/dialogs/GitLogDialog.test.tsx与GitDialog见 GitDialog.test.tsx列表渲染SHA/subject/作者/时间/refs/merge 图标展开按需拉详情与折叠Load more 追加loading / error / unavailable / empty 占位相对时间格式化/log本地拦截有/无 workspace。集成 / 浏览器验证正常仓库打开/log列表、展开详情、Load more 均正确空仓库 / 非 git 目录显示占位文案大仓库200 commits分页正常、性能可接受。实施路线设计文档给出的落地顺序对应四个包各自的职责边界步骤内容涉及包1corefetchGitLogfetchGitCommitDetail 单测core2daemonworkspace-git-log.ts路由 注册 单测cli3SDK类型 client 方法 导出sdk-typescript4Web ShellGitLogDialog CSS i18n App 集成 单测web-shell5build typecheck lint 全量单测验证all这套core 命令封装 → daemon REST 路由 → SDK client → Web Shell 组件的四层增量模式与既有的GitDiffDialog完全同构。对于需要为 Web Shell 增加其他只读 Git 能力如后续的分支筛选、提交搜索、任意 commit 间 diff的场景直接复用parsePagination、applyReadHeaders、sendBridgeError、resolveTrustedRuntime、DialogShell与 CSS Module 语义变量这套基础设施即可这也是本文所述设计最具复用价值的骨架。【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价