用 jscpd 的 `--history` 追踪代码重复率演进:Git 提交级别的重复趋势分析实战

📅 发布时间:2026/10/7 2:27:29
用 jscpd 的 `--history` 追踪代码重复率演进:Git 提交级别的重复趋势分析实战
开发工具代码质量静态分析【免费下载链接】jscpdCopy/paste detector for source code. 220 languages, Rust engine, SARIF/HTML/badge reporters, GitHub Action, MCP server for AI agents.项目地址https://gitcode.com/gh_mirrors/js/jscpd点击查看免费下载本指南讲解 jscpd 的--history家族参数如何对 Git 提交历史中的每一个 commit 执行代码重复检测输出条形图、逐提交明细表、整体趋势与阈值余量提示从而把重复代码从一个静态快照变成一条可观察、可设质量门槛的时间曲线。读完本文你将能在自己的仓库中复现完整流程理解底层工作树扫描原理并掌握--history-since、--history-every、--history-limit与--threshold的组合用法把重复率趋势接入 JSON 报告与 CI 流程。为什么需要重复代码历史趋势普通的jscpd运行只回答一个问题此刻我的代码有多少重复它无法告诉你重复是正在蔓延、已经收敛还是某个特定提交引入了整段复制粘贴。--history正是为此设计的它扫描 Git 提交范围内每个 commit 的代码使用与本次运行完全相同的配置并输出重复趋势——一张柱状图、每个 commit 一行明细表、相邻点的变化量以及在给出--threshold时阈值还能收紧多少的余量提示。趋势需要提交记录因此本演示fixtures/history-demo/README.md不在仓库中存放静态文件而是在临时目录里现场构建一个自己的 Git 仓库再从仓库根目录以默认阈值运行。该功能在源码中以 issue #1002 立项实现见 rust/CHANGELOG.md核心实现位于 rust/crates/cpd/src/history.rs控制台渲染位于 rust/crates/cpd-reporter/src/history_render.rs。典型应用场景包括重构前评估复制粘贴正在往哪些模块扩散、为质量门禁选择合理的重复率阈值、以及在 Code Review 中定位重复率从哪次提交开始上涨。命令参数总览--history由四个参数组成全部定义在 rust/crates/cpd/src/cli.rs 的 CLI 结构与.jscpd.json配置文件的ConfigFile结构中命令行参数配置文件字段说明默认值--history RANGEhistory指定 Git revision 范围如v5.0.0..HEAD、HEAD~3..HEAD内的每个 commit 都执行一次扫描无未提供则不启用历史模式--history-since DATEhistorySince仅选择 DATE 之后的提交如2026-01-01可与--history组合来限定范围无--history-every NhistoryEvery每隔 N 个提交保留一个从最新提交往前计数保证最新点永远在列1全保留--history-limit NhistoryLimit序列最多保留 N 个提交超出后按等距采样截断30从 history.rs 中的HistorySpec::from_options可以看到取值逻辑只要--history或--history-since任一给出即启用历史模式range缺省为HEAD即仅按日期过滤every与limit都取max(1)避免传入0时出现异常行为。范围的显示标签HistorySpec::label会根据是否只有日期给出since 2026-01-01、v5.0.0..HEAD since 2026-01-01或HEAD~3..HEAD三种形式。构建一个演示仓库可直接复制运行演示先在临时目录构造一个含 4 次提交的仓库模拟复制粘贴蔓延再收敛的过程。下面这段脚本来自原文档可直接在仓库根目录复制执行demo$(mktemp -d) cd $demo git init -q mkdir src git config user.email demoexample.com git config user.name demo git config commit.gpgsign false fn() { printf export function %s(a, b, c) {\n const first a * b c;\n const second first - a / b;\n const third second c * c;\n const fourth third - first a;\n const fifth fourth * second - b;\n console.log(first, second, third, fourth, fifth);\n return [first, second, third, fourth, fifth];\n}\n $1; } one() { printf export const %s (n) n * %d %d;\n $1 $2 $3; } snap() { git add -A GIT_COMMITTER_DATE$2 git commit -q -m $1 --date$2; } { fn total; one tax 3 1; one fee 5 2; } src/a.js snap initial helpers 2026-08-01T10:00:00 { fn total; one rate 7 3; } src/b.js snap copy total() into b.js 2026-08-08T10:00:00 { fn total; one discount 9 4; } src/c.js snap and again into c.js 2026-08-15T10:00:00 { one rate 7 3; one rate2 11 5; } src/b.js snap b.js imports total() instead 2026-08-22T10:00:00脚本要点fn生成一个结构相同、仅函数名不同的total辅助函数——这是用于制造复制粘贴克隆的最小素材one生成互不重复的小工具函数。snap用固定的GIT_COMMITTER_DATE与--date固定作者时间戳保证 4 个提交的日期完全确定趋势可复现。提交节奏刻意设计成一条清晰的故事线与下表一一对应步骤提交信息文件克隆数1initial helpersa.js02copy total() into b.jsa.js,b.js13and again into c.jsa.js,b.js,c.js24b.js imports total() insteadb.js重写1第 4 次提交把b.js里的total()替换为对已有函数的引用代码上体现为改用rate/rate2模拟重构消除重复。一次完整的趋势运行解读输出对上述仓库执行在仓库根目录jscpd src --history-since 2026-01-01 --no-colors # Found 1 clones. # # History (since 2026-01-01: 4 commits working tree) # duplicated lines, % of all lines: min 0.0% max 58.1% now 42.9% # 58.1% ┤ ██ # │ ██ # │ ▇▇ ██ ▇▇ ▇▇ # 29.0% ┤ ██ ██ ██ ██ # │ ██ ██ ██ ██ # │ ██ ██ ██ ██ # │ ██ ██ ██ ██ # 0.0% ┤ ▁▁ ██ ██ ██ ██ # └──────────────── # 1 2 3 4 5 # # # COMMIT DATE FILES LINES CLONES DUP LINES DUP% CHANGE SUBJECT # 1 sha 2026-08-01 1 11 0 0 0.0% initial helpers # 2 sha 2026-08-08 2 21 1 9 42.9% 42.9 copy total() into b.js # 3 sha 2026-08-15 3 31 2 18 58.1% 15.2 and again into c.js # 4 sha 2026-08-22 2 21 1 9 42.9% -15.2 b.js imports total() instead # 5 working today 2 21 1 9 42.9% (uncommitted changes) # Trend: 42.9 points since sha (2026-08-01)这段输出是理解整个功能的钥匙各部分对应的渲染代码都在 history_render.rs标题行History (since 2026-01-01: 4 commits working tree)显示范围标签、提交数量并额外追加一个工作树点未提交的当前状态。工作树点是--history报告的最后一行标识为working主题列显示(uncommitted changes)。柱状图纵轴刻度只跨越序列自身的最小值与最大值min 0.0%到max 58.1%而非固定的 0–100%因此即使系列在 2.0%–2.4% 之间波动也不会被画成一条平线底部的1 2 3 4 5数字对应明细表的#列。源码中render_chart用 8 行 × 8 级▁▂▃▄▅▆▇█绘制点数不超过 20 时每点占两字符列并留空隙超过 20 点自动切换为每点一字符保证 30 点也能放进 80 列终端。明细表每行一个提交列为#、COMMIT7 位缩写哈希、DATE、FILES、LINES、CLONES、DUP LINES、DUP%、CHANGE、SUBJECT提交主题超长时截断为 48 字符并加省略号。CHANGE是相对上一行的百分比点差绝对值小于 0.05 显示否则显示42.9/-15.2这样的带符号一位小数。趋势行Trend: 42.9 points since sha (2026-08-01)即首点到末点含工作树的总体变化。颜色约定启用颜色时CHANGE为正数标红、负数标绿、近似不变为暗色Trend行同理--no-colors关闭所有 ANSI 颜色。一个值得注意的细节不同机器上输出的 commit 哈希不同。因为演示固定了作者日期但作者身份git config user.name属于你本机而哈希由作者身份参与计算除此之外其余输出完全一致。底层原理每个提交是如何被扫描的--history并不读取 git 对象做离线分析而是复用--baseline-from-ref的同一套临时工作树机制真实地逐提交执行检测。核心流程见 rust/crates/cpd/src/history.rs 的collect_history列出提交list_commits运行git log --reverse --format%H%x1f%cs%x1f%s按时间正序最旧在前输出哈希、日期%cs短日期与主题若指定了--history-since则追加--since参数。序列精简sample()先按--history-every从最新端往前每隔 N 取一个保证最新点保留再用--history-limit做等距采样截断保证首尾两端保留limit1时只保留最新点。这一步在 history.rs 的单元测试 中有完整覆盖。逐提交扫描对每个选中的提交在临时 detached worktree 中物化其快照用当前运行的全部配置格式、忽略规则、min-tokens 等执行一次标准检测随后删除 worktree。扫描是串行的——源码注释明确说明每个提交的扫描本身已在文件间并行再开并行 worktree 只会争抢同一批 CPU 核心。诚实归零scan_commit会把扫描路径映射到 worktree 内如果某次提交当时还不存在这些路径paths为空则直接产生一个全零统计点这个提交里还没有这些文件而不是报错。追加工作树点working_tree_point使用本次运行的实时统计作为最后一个点日期取检测日期主题留空。数据模型本身HistoryPoint/History以及sparkline、change_at、overall_change、threshold_headroom等纯函数独立放在 rust/crates/cpd-core/src/history.rs不接触 git——渲染层与 JSON 序列化共享这一模型。控制台渲染与aireporter 的紧凑单行输出分别由print_history与print_history_compact提供。阈值余量把--threshold当作可收紧空间而非自动棘轮--history与--threshold组合时会产生第三种输出——阈值余量headroomjscpd src --history HEAD~3..HEAD --threshold 50 --no-colors # History (HEAD~3..HEAD: 3 commits working tree) # ... # Threshold 50.0% has 7.1 points of headroom: the series never needed it, tighten it with --threshold 42.9语义是当前序列的最高/最新重复率从未触及 50%意味着 50% 这个门禁过于宽松。jscpd 会计算阈值 − 最新值的百分点余量并直接建议把阈值收紧到当前值42.9。这正是自动棘轮ratchet会做的事——但 jscpd只报告、不自动修改把决策留给使用者cpd-core 的threshold_headroom中余量须大于 0.05 个百分点才会显示。反向情形如果最新值超过阈值则不打印余量提示而是触发常规的阈值错误运行以退出码 1 结束——这与普通--threshold的行为完全一致因此--history--threshold可以直接用作 CI 中的趋势质量门禁趋势越过红线即失败未越线则提示你还可以进一步收紧。长序列精简--history-every与--history-limit真实仓库动辄数百次提交逐点渲染既慢又难读两个参数用于稀释序列jscpd src --history-since 2026-01-01 --history-every 2 --no-colors # 每隔 2 个提交取一个最新者保留 # History (since 2026-01-01: 2 commits working tree) jscpd src --history-since 2026-01-01 --history-limit 2 --no-colors # 只保留首尾两点 # History (since 2026-01-01: 2 commits working tree)两者的行为差异在集成测试 rust/crates/cpd/tests/integration.rs 中被精确验证--history-every 24 次提交中保留第 2、4 次2026-08-08与2026-08-22计数从最新端开始因此最新提交永远在列--history-limit 2保留首尾两点2026-08-01与2026-08-22体现等距采样、两端保留的语义。--history-every与--history-limit可以叠加使用先按步长稀释再按上限截断。默认上限 30 意味着不加参数时长范围仓库也会被自动压到最多约 30 个点保持可读。JSON 输出与 CI/自动化集成趋势数据同样可以落入结构化报告jscpd src --history-since 2026-01-01 --reporters json --output report # report/jscpd-report.json gains history: { range, threshold, points: [...] }JSON 中的history对象由 rust/crates/cpd-reporter/src/json_reporter.rs 写入仅在请求了--history时才出现字段名采用 camelCase包含三部分range范围标签如since 2026-01-01或HEAD~3..HEADthreshold本次生效的阈值未传则为nullpoints逐提交的检测总量数组每点包含commit完整哈希末点为working tree、short7 位缩写、date、subject、sources、lines、tokens、clones、duplicatedLines、percentage等字段最旧在前、工作树收尾。集成测试 history_json_has_one_point_per_commit_plus_working_tree 验证了4 次提交 1 个工作树点 5 个点、克隆数序列为[0, 1, 2, 1, 1]、日期序列确定等行为。这让趋势可以方便地接入自定义脚本、看板或数据仓库据 rust/CHANGELOG.mdGitHub Action 也已提供history输入项console-fullreporter 同样会打印历史块。注意事项与常见错误必须位于 Git 仓库内--history依赖git log与临时 worktree扫描路径不在任何 Git 仓库中会直接报错退出--history: path is not inside a git repository集成测试 history_outside_a_git_repository_is_an_error 覆盖了该场景。范围无匹配提交会报错例如--history nosuchref..HEAD会得到--history: git log nosuchref..HEAD failed并以退出码 1 结束见 history_without_matching_commits_is_an_error。扫描成本与提交数成正比每个点都是一次完整的独立检测串行执行长范围、大仓库的组合可能耗时较长建议配合--history-every/--history-limit控制点数。哈希不可跨机复现如上文所述作者身份参与哈希计算跨机器比较趋势输出时应忽略哈希列。演示仓库为一次性临时目录脚本结束后可执行cd - rm -rf $demo清理不会污染正式仓库。至此你已经掌握 jscpd 历史趋势分析的全部核心从构造演示仓库、读懂柱状图与明细表到用阈值余量收紧质量门禁、用两个参数稀释长序列再到 JSON 输出对接 CI。下一步可以直接把jscpd src --history vX.Y..HEAD --threshold 40加入你的 CI 脚本让重复率趋势成为每次合并前的可见指标。赞分享开发工具代码质量静态分析【免费下载链接】jscpdCopy/paste detector for source code. 220 languages, Rust engine, SARIF/HTML/badge reporters, GitHub Action, MCP server for AI agents.项目地址https://gitcode.com/gh_mirrors/js/jscpd点击查看免费下载相关推荐wiliwili代码复杂度趋势分析改进跟踪wiliwili代码复杂度趋势分析改进跟踪 wiliwili作为专为手柄控制设计的第三方跨平台B站客户端支持PC全平台、PSVita、PS4和Nintend音视频桌面应用裸机系统部署平台使用指南Network-Reinstall-System-Modify高级用户必备裸机系统部署平台使用指南Network Reinstall System Modify高级用户必备 Network Reinstall System ModiHome Assistant 中的 hue.hue_activate_scene 动作按房间与场景名在旧版 Hue 桥激活 Philips Hue 场景Home Assistant 中的 hue.hue_activate_scene 动作按房间与场景名在旧版 Hue 桥激活 Philips Hue 场景 本文开发工具代码质量静态分析上一篇AssetRipper游戏资源的时空解码器下一篇3步快速上手Switch注入终极指南与TegraRcmGUI完全教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考