Duplication Trend over Git History
A single scan tells you how much duplication a codebase has today. --history tells you where it is heading: it scans every commit in a git range with the run's own configuration and prints the series as a chart and a table, without a hosted dashboard.
git on the PATH and the commits present locally.Running it
jscpd src --history v5.0.0..HEAD # every commit in the range
jscpd src --history-since 2026-01-01 # every commit since a date, up to HEAD
jscpd src --history main --history-since 2026-06-01 --history-every 5 --history-limit 12
For each selected commit jscpd checks the tree out into a temporary detached worktree, the same mechanism --baseline-from-ref uses, scans it, and keeps the totals. The current run is the last point of the series, labelled working. Scans run one after another; each scan is already parallel across files.
| Option | Effect | Default |
|---|---|---|
--history <range> | git log revision range to walk, e.g. v5.0.0..HEAD or main | — |
--history-since <date> | Select commits since a date; alone it walks HEAD, with --history it bounds the range | — |
--history-every <N> | Keep every Nth commit, counted from the newest so the latest is always in | 1 |
--history-limit <N> | Cap the series by sampling it evenly, keeping both ends | 30 |
The same keys work in .jscpd.json: history, historySince, historyEvery, historyLimit.
Reading the output
The console and console-full reporters append a block after the normal report:
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 0a3e3d5 2026-08-01 1 11 0 0 0.0% initial helpers
2 6728f91 2026-08-08 2 21 1 9 42.9% +42.9 copy total() into b.js
3 aff51e5 2026-08-15 3 31 2 18 58.1% +15.2 and again into c.js
4 817b39d 2026-08-22 2 21 1 9 42.9% -15.2 b.js imports total() instead
5 working 2026-09-12 2 21 1 9 42.9% = (uncommitted changes)
Trend: +42.9 points since 0a3e3d5 (2026-08-01)
Threshold 50.0% has 7.1 points of headroom: the series never needed it, tighten it with --threshold 42.9
- The chart plots the duplicated-lines percentage, one column per point. Its y-axis spans the series' own minimum and maximum, so a drift from 2.0% to 2.4% is visible instead of a flat line on a 0-based axis. The numbers under it are the
#column of the table. - CHANGE is the difference to the previous point in percentage points, red when duplication rose and green when it fell.
- Trend is the change from the first point to the last.
- The threshold line appears when
--thresholdis set and the latest value sits below it. It is the manual form of a ratchet: jscpd reports how far the threshold could be tightened and leaves the change to you. When the latest value is over the threshold, the usual threshold error fires instead and the run exits 1.
Other reporters: json adds a history object (range, threshold, points[] with commit, short, date, subject, sources, lines, tokens, clones, duplicatedLines, percentage), ai prints one compact line per point. The rest are unchanged.
In CI
The GitHub Action exposes the range as the history input. The range must be present in the checkout, so disable the shallow clone:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: kucherenko/jscpd@v5
with:
history: v5.0.0..HEAD
threshold: 3
The chart and table land in the job log. For a scheduled job on a long-lived repository, --history-every and --history-limit keep the run short: 30 points is a readable series, and each point costs one scan.
Good to know
- Paths that did not exist at a commit count as zero files for that point, so a series can start before the scanned directory was created.
- The range is passed to
git logas is: tags, branches,A..B,A...Band--history-sincedates all work as they do on the command line. - Uncommitted changes are included through the
workingpoint only; every other point is a clean checkout of its commit. - To see what one of the earlier points contained, scan the commit directly:
git worktree add /tmp/old <sha> && jscpd /tmp/old/src.
See Types of Code Clones for what the percentage counts, and the configuration reference for the full option list.