Skip to main content

walk

Function walk 

Source
pub fn walk(
    roots: &[PathBuf],
    registry: &EcosystemRegistry,
    gitignore_policy: GitignorePolicy,
    symlink_policy: SymlinkPolicy,
) -> WalkOutcome
Expand description

Walks every path in roots.

Uses the ignore crate, routing every regular file through registry.for_uri (FR-002) unchanged from the LSP’s own routing.

A root that is itself a single file (not a directory) is checked directly against the registry, bypassing the directory walk — this is what lets deps-cli check Cargo.toml work without needing .gitignore semantics at all.

Every directory root is walked twice (spec 062 review, background code-review fix 1): once with ignore’s default hidden-file filtering intact (so .git — a potentially huge tree in a real checkout, and never a source of manifests — is never even descended into, not merely filtered from the results), and once more per registered ecosystem’s own dot-prefixed deps_core::Ecosystem::manifest_directory_patterns entry (.github, .gitlab, …) with hidden-ness lifted for that one subtree specifically. This is derived from the live registry rather than a hardcoded [".github", ".gitlab"] list, so a future ecosystem introducing a new dot-directory pattern is picked up automatically.

gitignore_policy (issue #1109): when GitignorePolicy::Ignore (the check subcommand’s default — see crate::cli::CheckArgs::gitignore_policy), .gitignore and .ignore files are never consulted, because in a CI security-gate invocation (git checkout && deps-cli check . against an untrusted fork PR) both are attacker-controlled input: a one-line addition anywhere in the tree would otherwise silently remove a manifest from the scan. .git itself is still never descended into (that is hidden-filtering, an unrelated concern — see above), and .git/info/exclude / the user’s global gitignore are always honored regardless of this policy, since neither travels with a cloned/fetched PR and both are operator-, not attacker-, controlled. When GitignorePolicy::Respect, standard .gitignore/.ignore awareness is restored (matching git’s own behavior), and any manifest-shaped file that awareness excludes is additionally reported via WalkOutcome::ignored_manifests. The internal PRUNED_DIRECTORIES denylist is applied regardless of gitignore_policy — it is compiled into the binary, not attacker-controlled input, so pruning node_modules/target/vendor/… does not reopen the fail-open gap this policy closes. A manifest sitting directly at the root of a pruned directory (an unusual but real monorepo layout, e.g. a genuine subproject named vendor) is still reported via WalkOutcome::ignored_manifests in every mode, so pruning stays visible rather than a second, narrower silent-omission bug.

One asymmetry is deliberate rather than accidental: in the default (GitignorePolicy::Ignore) mode, a manifest excluded only by the always-on .git/info/exclude or global gitignore is never diffed against (that costly double-walk only runs under GitignorePolicy::Respect), so such a suppression is silent beyond WalkOutcome::manifests coming back emptier than expected — acceptable because both sources are operator-, not attacker-, controlled (see above).

symlink_policy (issue #1112): a directory entry that is itself a symlink to a manifest-shaped file is always detected, regardless of this policy — its path is reported via WalkOutcome::ignored_manifests, the same sink a pruned or .gitignore-excluded manifest already uses, so a symlinked manifest can never silently vanish from the report. Detection alone never reads the target’s content. When symlink_policy is SymlinkPolicy::Follow, such a symlink is additionally resolved and routed like any other manifest (appearing in WalkOutcome::manifests instead, with DiscoveredManifest::path set to the resolved real path used for reading and DiscoveredManifest::display_path kept as the symlink’s own encountered path). Every routed entry under SymlinkPolicy::Follow — not only ones where the leaf itself is a symlink, since an entry reached by descending into a followed symlinked directory is otherwise indistinguishable from an ordinary one — is canonicalized and checked against the walked root’s own canonicalized absolute path; an entry that resolves outside the root is never routed, and is reported via ignored_manifests only when it is itself manifest-shaped (an arbitrary out-of-root symlink to a non-manifest file is silently skipped, matching detection’s own never-warn-on-non-manifests invariant). This containment check applies to every registered ecosystem’s own dot-directory sub-root (e.g. .github) too, and — because ignore/walkdir always follows a walk’s own root symlink regardless of follow_links — that sub-root containment check runs in every mode, not only under SymlinkPolicy::Follow. A symlink loop is detected by the underlying ignore crate and surfaced via WalkOutcome::walk_errors.

Broken symlinks (issue #1124): a symlink whose target is not a manifest (unresolvable, or a non-regular-file such as a directory) is classified by its own filename, not its target, and reported via WalkOutcome::broken_manifest_symlinks instead of ignored_manifests — unconditional across every mode, same as the resolvable case, except a structural ancestor loop under --follow-symlinks, which reports via walk_errors instead.