cargo-delta 0.4.0

Detects which crates in a Cargo workspace are affected by changes in a Git feature branch.
cargo-delta-0.4.0 is not a library.

cargo-delta

crate.io CI Coverage License

cargo-delta detects which packages in a Cargo workspace are impacted by changes in a Git feature branch. Build, test, and benchmark only the packages you need.

Installation

cargo install cargo-delta

Usage

Quick Start

Run impact analysis directly from the feature branch:

cargo delta impact --base-ref origin/main

Cargo-delta resolves the merge base, snapshots both it and the current working tree, and caches the snapshots under target/cargo-delta/. Later invocations reuse each snapshot while its embedded state key still matches. The comparison includes committed, staged, unstaged, deleted, and non-ignored untracked files.

By default the command prints the full Impact JSON with all three tiers. Use the tier toggles (--modified, --affected, --required) to filter. To plug the result straight into cargo, change the format:

# One package per line — good for xargs / shell loops.
cargo delta impact --base-ref origin/main -f names --affected

# `-p NAME` pairs — drop into any cargo invocation via $(...).
cargo build $(cargo delta impact --base-ref origin/main -f cargo-args --affected)

# `--exclude NAME` for the workspace complement of the selected tier.
cargo build --workspace $(cargo delta impact --base-ref origin/main -f cargo-excludes --affected)

# JSON, but only the keys you care about:
cargo delta impact --base-ref origin/main --required

Combining tier toggles for non-JSON formats emits the union of the selected tiers (deduplicated, sorted). The human-readable summary is written to stderr, so $(...) capture stays clean.

Explicit snapshot generation

Use cargo delta snapshot when snapshots must be generated or transferred separately:

git checkout main
cargo delta snapshot --output main.json

git checkout feature-branch
cargo delta snapshot --output feature.json

cargo delta impact --baseline main.json --current feature.json

These are the same cache-keyed snapshot artifacts used by managed mode. They can also prepopulate target/cargo-delta/baseline.json and target/cargo-delta/current.json; a baseline cache entry must describe the exact merge-base checkout. Prefer --output for arbitrary destinations so the output file is excluded from its own working-tree digest.

The legacy subcommand names analyze (= snapshot) and run (= impact) continue to work as hidden aliases for back-compat.

CI/CD Integration

cargo-delta is designed to speed up PR builds by building and testing only impacted packages. Since detection is best-effort, a backstop build must run separately to catch anything delta missed or was misconfigured for.

PR pipeline — compare the PR working tree directly with the target branch. Different cargo commands need different tiers:

Command Tier Reasoning
cargo fmt --check, cargo clippy --modified Lints and formatting only matter for code the PR actually touched. Untouched code already passed on main.
cargo build, cargo test, cargo bench --affected A modified package can break a dependent's compile or behavior, so downstream needs to be built and tested too.
cargo doc, vendor verification --required Needs transitive dependencies in scope.
- name: Build, test, lint impacted packages
  run: |
    MODIFIED=$(cargo delta impact --base-ref origin/main -f cargo-args --modified)
    AFFECTED=$(cargo delta impact --base-ref origin/main -f cargo-args --affected)
    echo "Modified: $MODIFIED"
    echo "Affected: $AFFECTED"

    # Lint only what changed.
    cargo fmt --check $MODIFIED
    cargo clippy $MODIFIED -- -D warnings

    # Build & test what could be impacted by the change.
    cargo build $AFFECTED
    cargo test  $AFFECTED

Notes:

  • The variables are unquoted on purpose — that's what lets the shell split -p foo -p bar into separate cargo arguments.
  • If a tier ends up empty, the corresponding cargo command falls back to its workspace default. Add [ -z "$AFFECTED" ] && exit 0 if you'd rather skip.
  • The first call generates any missing or stale snapshot. Later calls reuse the matching cache entries and only repeat the inexpensive impact calculation.

Backstop pipeline — full build without delta, runs post-merge and/or on a nightly schedule:

# Full workspace build and test, no delta
- run: cargo build --workspace
- run: cargo test --workspace

The backstop ensures correctness. If it fails on code that passed the delta-optimized PR build, it indicates a gap in detection or a misconfigured delta — adjust the configuration accordingly.

Configuration

You can customize cargo-delta by providing a -c config.toml argument to the command.

cargo delta snapshot -c config.toml # ...
cargo delta impact -c config.toml # ...

Configuration options can be set globally and overridden per package. For example:

[parser]
foo = true
foo_patterns = ["*.foo", "*.bar"]

[parser.my-crate]
foo_patterns = ["*.baz"] # Override for a specific package

Default settings are provided in config.toml.example.

Detection Methods

Module Traversal

Follows mod declarations and #[path] attributes to discover all Rust modules in the workspace.

Mod Macros

Discovers modules declared via custom macros (e.g., my_mod!), assuming first argument is the name of the module.

Config default:

[parser]
mod_macros = []

Config example:

[parser]
mod_macros = ["my_mod"]  # my_mod!(foo)

Include Macros

Detects files included via macros such as include_str! and include_bytes!, assuming the first argument is the name of the file.

Config default:

[parser]
includes = true
include_macros = [
    "include_str",   # include_str!("file.txt")
    "include_bytes"  # include_bytes!("file.bin")
]

Pattern-based Assumptions

Assumes certain files are dependencies based on glob patterns (e.g., *.proto, *.snap).

Config default:

[parser]
assume = false
assume_patterns = []

Config example:

[parser.grpc_crate]
assume = true
assume_patterns = [".proto"]

File Method Matching

Detects files loaded at runtime by matching method names (e.g., from_file, load, open), assuming the first argument is the name of the file.

Config default:

[parser]
file_refs = true
file_methods = [
    "file",       # ::file(path, ...)
    "from_file",  # ::from_file(path, ...)
    "load",       # ::load(path, ...)
    "open",       # ::open(path, ...)
    "read",       # ::read(path, ...)
    "load_from"   # ::load_from(path, ...)
]

File Control

File Exclusion

Exclude files and folders from analysis using glob patterns.

Config default:

file_exclude_patterns = ["target/**", "*.tmp"]

Trip Wire

If any changed or deleted file matches a trip wire pattern, all packages are considered impacted.

Config default:

trip_wire_patterns = []

Config example:

trip_wire_patterns = [
    "Cargo.toml",       # top-level Cargo.toml
    "delta.toml"   # Delta config file
]

Output

Snapshot

cargo delta snapshot writes the same cache-keyed JSON artifact that cached impact generation stores under target/cargo-delta/. It describes the workspace at the current checkout and can be used as an explicit impact input or placed directly at a cache path.

Snapshot generation requires the Cargo workspace to be inside a Git worktree. The snapshot records the resolved HEAD commit and uses Git-root-relative paths; outside Git, the command exits with an error and does not write a snapshot.

  • files is the nested tree of detected inputs. Each package manifest node records its name@version package ID.
  • packages maps each workspace package ID to its direct workspace dependencies, transitive external dependencies up to the next workspace package, and effective direct dependency declarations.

For example:

{
  "files": {
    "path": "Cargo.toml",
    "kind": "Workspace",
    "children": [
      {
        "path": "crates/app/Cargo.toml",
        "kind": "Package",
        "package": "app@1.0.0",
        "children": []
      }
    ]
  },
  "packages": {
    "packages": {
      "app@1.0.0": {
        "workspace_dependencies": ["core@1.0.0"],
        "external_dependencies": [],
        "dependency_declarations": []
      },
      "core@1.0.0": {
        "workspace_dependencies": [],
        "external_dependencies": [],
        "dependency_declarations": []
      }
    },
    "resolution_complete": true
  }
}

Write the JSON to stdout or directly to a file:

cargo delta snapshot > snapshot.json
cargo delta snapshot --output snapshot.json

# Prepopulate the managed current cache entry, creating its directory if needed.
cargo delta snapshot --output target/cargo-delta/current.json

# Shell redirection produces the same entry when the directory already exists.
cargo delta snapshot > target/cargo-delta/current.json

The cache directory is always excluded from the snapshot's working-tree digest, so both current.json commands produce a directly reusable cache entry. For other destinations, prefer --output so cargo-delta can create parent directories and exclude the output file from the digest.

Impact

cargo delta impact combines the Git change set with the baseline and current snapshots. The snapshots map changed, deleted, and newly discovered inputs to packages and provide the dependency graph used to calculate impact.

How impact is calculated

Each invocation performs these steps:

  1. Determine the base commit. --base-ref uses its merge base with HEAD; a cache-keyed explicit baseline uses the exact HEAD commit recorded when it was generated.
  2. Ask Git for tracked files changed or deleted between that commit and the current working tree. This includes committed, staged, and unstaged changes.
  3. Query non-ignored untracked files separately, because git diff does not report them, and treat those paths as changed.
  4. Use the current snapshot to map changed and newly discovered paths to packages. Use the baseline snapshot for deleted paths, which no longer exist in the current workspace.
  5. Expand the modified packages through the current dependency graph to produce the affected and required tiers.

When Git reports the workspace Cargo.lock changed, cargo-delta compares each workspace package's baseline and current external_dependencies graph. External records contain package identity, version, source, and direct resolved dependencies. Packages whose external graph changed are modified; workspace dependents become affected through the existing local graph. Cargo-delta does not parse the lockfile.

Lockfile formatting, unused entries, and checksum-only edits do not change package-build scope when Cargo reports the same resolution. Cargo and unscoped lock-integrity checks remain responsible for validating those changes.

When Git reports the root Cargo.toml changed, cargo-delta compares the manifests semantically. Changes confined to [workspace.dependencies], workspace.members, or workspace.exclude are mapped through effective package declarations and package-set changes. Any other root setting remains a full-workspace trip wire, including profiles, workspace lints, resolver, workspace package defaults, patches, and unknown settings.

Full external resolution is collected with cargo metadata --all-features --locked when a lockfile exists. A workspace without a lockfile remains supported, but lockfile scoping is unavailable and therefore conservative.

Snapshot caching avoids rebuilding file ownership and dependency information, but it does not cache the Git change set. Every impact invocation reruns the Git queries. It also fingerprints changes relative to HEAD—including untracked paths and contents—to verify whether the current snapshot cache entry is still valid. The same untracked-file listing is reused for change detection and the fingerprint.

  • Modified: Packages directly modified by Git changes.
  • Affected: Modified packages plus all their dependents, direct and indirect.
  • Required: Affected packages plus all their dependencies, direct and indirect.

Use --base-ref REF for the primary cached workflow:

cargo delta impact --base-ref origin/main

An explicit baseline generated by the current cargo-delta version supplies the exact Git commit used for the change set:

cargo delta impact --baseline main.json --current feature.json

The baseline must describe a clean checkout because its digest cannot reconstruct uncommitted baseline content. Explicit baseline and current snapshots must both contain a cache_key; older keyless artifacts are rejected as invalid.

-f/--format controls the emitted representation:

Format Output
json Selected impact tiers as package-name arrays.
names One package name per line.
cargo-args One line of -p NAME arguments.
cargo-args-versioned One line of -p NAME@VERSION arguments.
cargo-excludes One line of --exclude NAME arguments for unselected workspace packages.
cargo-excludes-versioned One line of --exclude NAME@VERSION arguments for unselected workspace packages.
gamma-test-packages One line of repeated --test-package NAME arguments for cargo gamma run.
packages One name@version package ID per line.

Use --output PATH to write the same output directly instead of relying on shell redirection:

cargo delta impact --baseline main.json --current feature.json --affected -f packages
cargo delta impact --baseline main.json --current feature.json --affected -f packages --output affected.packages

--current is optional and overrides current-snapshot generation. Supplying an explicit --baseline is mutually exclusive with --base-ref; when --baseline is used alone, the current snapshot is still managed:

cargo delta impact --base-ref origin/main --affected -f cargo-args-versioned
cargo delta impact --baseline main.json --affected -f cargo-args-versioned

Managed snapshots are stored under Cargo's target directory in cargo-delta/baseline.json and cargo-delta/current.json. Their embedded cache_key records the checkout commit and working-tree content digest, workspace path, configuration digest, and cargo-delta version. A matching snapshot is reused regardless of whether cargo-delta generated it implicitly or cargo delta snapshot wrote it explicitly. An explicit stale snapshot is accepted with a warning.

The working-tree comparison includes committed, staged, unstaged, deleted, and non-ignored untracked files. If the merge base predates the Cargo workspace, all current packages are selected.

Limitations

This tool is best-effort and may not detect all dependencies:

  • Dynamic file paths computed at runtime
  • Conditional compilation dependencies
  • Other dependencies not captured by the heuristics

Example

$ cargo delta impact --base-ref origin/main
Computing impact..
Looking up git changes..

Changed file: "src/api/mod.rs"
Changed file: "src/utils.rs"

{
  "Modified": [
    "my-api",
    "my-utils"
  ],
  "Affected": [
    "my-api",
    "my-utils",
    "my-app"
  ],
  "Required": [
    "my-api",
    "my-utils",
    "my-app",
    "common-lib"
  ]
}

Modified      2 (Packages directly modified by Git changes.)
Affected      3 (Modified packages plus all their dependents, direct and indirect.)
Required      4 (Affected packages plus all their dependencies, direct and indirect.)
Total        15 (Total packages in this workspace.)

Contributing

Fork the repository and submit a pull request.

License

MIT