Source capability tiers
Not every source can answer every question, and the honest version of that is worth reading before a proof of concept rather than during one.
If you can put it on a disk, we'll index it. Search, symbols, navigation, the dependency index, findings and complexity work over a plain directory with no version control in it at all. What a directory cannot give you is anything computed from commits — and that is stated up front, reported as a gap at query time, and never dressed up as an empty result.
The table
| Capability | What it answers | Native git | Git bridge | fullHistory: false |
Directory |
|---|---|---|---|---|---|
search |
Search, symbols, code navigation | Yes | Yes | Yes | Yes |
dependencies |
Dependency index, resolved versions, "who uses this" | Yes | Yes | Yes | Yes |
findings |
Security findings, code quality, complexity | Yes | Yes | Yes | Yes |
history |
Commit log for a file or a repository | Yes | Yes | No | No |
blame |
Who wrote this line, and when | Yes | Yes | No | No |
coupling |
Hotspots and change coupling | Yes | Yes | No | No |
commit_sha |
Which exact commit an answer came from | Yes | Yes | Yes | No |
The column names are the four things you can actually configure. Git bridge
is not a separate code path — git-p4, git-svn and hg-git produce a git
repository, and the product cannot tell it apart from any other. That is the
point: run a bridge and you get the full feature set.
Every repository reports its own row of this table on /api/v1/repos, keyed by
these exact names. Read it before asking a history question rather than
inferring it from the source type.
Why a gap is not an empty result
Standing rule: coverage gaps are reported as gaps, never as empty results.
"No coupling data available: source type directory has no history" is a
different sentence from "this file has no coupling", and an agent that cannot
tell them apart will draw the false conclusion and state it confidently — and so
will the human reading its output.
So every response that can be partially answered carries a gaps array. It is
never null: an empty results with an empty gaps means nothing matched;
with a populated one it means we could not answer. Both halves matter, and the
distinction is what the array exists for.
Native git
The default and the full set. Clone URL, optional credential, and the refresh
loop is git fetch on an interval.
repositories:
- url: https://github.example.com/platform/api
groups: [platform]
credential: github-ssh
Git bridge — Perforce, Subversion, Mercurial
This product does not natively speak Perforce, Subversion or Mercurial, and this page will not imply that it does. Advertising the product as Perforce-native would imply changelist awareness, and somebody would be disappointed in a proof of concept.
What is true is better than it sounds: the bridges are mature, widely deployed, and a customer running one gets every capability in the table above, including blame and change coupling.
| Your system | Bridge |
|---|---|
| Perforce Helix | git-p4 |
| Subversion | git-svn |
| Mercurial | hg-git |
Run the bridge on a schedule into a git repository, point searchcode at that, and nothing downstream knows the difference. If you already mirror to git for CI — most Perforce shops do — you are done.
If you would rather not run a bridge, a directory source over a periodic export works and costs you the bottom three rows of the table.
fullHistory: false
The escape hatch for a repository whose history is the expensive part — fifteen years of binary assets, say.
repositories:
- url: https://github.example.com/platform/monolith
groups: [platform]
fullHistory: false
depth: 10
It is a per repository setting, so the deep-history repository that is
costing you the disk can be shallow while everything else stays full. What it
costs is exactly the history, blame and coupling rows, for that repository
only — reported as gaps.
The commit SHA survives, so provenance is intact: an answer still names the commit it came from.
See Sizing for what full history actually costs, measured.
Directory sources
A path this process can read. A dropped tarball, a Perforce export, an NFS mount populated by your own tooling, a mainframe extract.
directorySources:
allowedRoots: ["/mnt/depot"]
denyPaths: ["/mnt/depot/.creds"]
sources:
- path: /mnt/depot/cobol
groups: [mainframe]
unitDetection: marker
changeDetection: mtime
allowedRoots is a hard allowlist: every source path must resolve inside one of
its entries, symlinks included. instance.dataDir, the configuration file's own
directory and /run/secrets are denied implicitly whether or not you list them.
A relative path is refused rather than resolved against whatever directory the
process happened to start in.
unitDetection decides how many repositories you have, which makes it a
metering input as well as a display one:
| Mode | One drop becomes |
|---|---|
single |
One unit |
subdirectories |
One unit per immediate subdirectory |
marker (default) |
One unit per directory containing .git, go.mod, pom.xml, package.json, Cargo.toml or a marker you name |
A Perforce-style depot drop of 2,000 projects under one path is 2,000
repositories under subdirectories and one under single. Choose deliberately.
changeDetection is mtime by default. Use hash when your export tooling
rewrites every modification time on every run — mainframe and some Perforce
export scripts do — at the cost of a full content read per scan.