CI / Diff-Based Analysis

View Source

MetaCredo supports diff-based analysis for CI/CD pipelines. Only files changed in a pull request are checked, giving fast, focused feedback.

Quick Start

# Analyze only changed files, fail on issues
mix metacredo --diff --strict

# With GitHub Actions inline PR annotations
mix metacredo --diff --format github --strict

Diff Mode

When --diff is given, MetaCredo resolves the list of files changed between two git refs and runs checks only on those files. This is much faster than analyzing the entire project and avoids reporting pre-existing issues that are unrelated to the current PR.

How it works

  1. MetaCredo.Git.changed_files/2 runs git diff --name-only --diff-filter=ACMR <base>...<head>
  2. The result is filtered to supported extensions (.ex, .exs, .erl, .hrl, .py, .rb, .hs)
  3. The file list is passed as :files_included to the execution pipeline
  4. Checks run only on those files

Options

--diff                Enable diff-based mode
--base REF            Base git ref (default: origin/main)
--head REF            Head git ref (default: HEAD)
--format FORMAT       Output format: text (default), json, github
--strict              Only report normal+ priority issues
--only CATEGORIES     Comma-separated categories (security,warning,...)
--ignore CATEGORIES   Comma-separated categories to skip

Examples

# Default: diff against origin/main
mix metacredo --diff

# Custom base branch
mix metacredo --diff --base origin/develop

# Strict mode + GitHub annotations
mix metacredo --diff --strict --format github

# Only security checks on changed files
mix metacredo --diff --only security --format github

# JSON output for downstream tooling
mix metacredo --diff --format json

GitHub Actions Format

Use --format github to produce GitHub Actions workflow commands:

::error file=lib/repo.ex,line=42::HardcodedValue: Hardcoded URL found
::warning file=lib/worker.ex,line=15::MissingErrorHandling: Missing error handling
::notice file=lib/utils.ex,line=3::ModuleDoc: Module missing documentation
metacredo: 3 issue(s) found

GitHub renders these as inline annotations on the PR diff:

  • ::error for severity :error (red)
  • ::warning for severity :warning (yellow)
  • ::notice for severity :info and :refactoring_opportunity (grey)

GitHub Actions Integration

Standalone job

metacredo:
  name: MetaCredo Analysis
  if: github.event_name == 'pull_request'
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 0  # Required for git diff

    - uses: erlef/setup-beam@v1
      with:
        elixir-version: '1.19'
        otp-version: '28'

    - name: Restore deps cache
      uses: actions/cache@v4
      with:
        path: deps
        key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}

    - run: mix deps.get
    - run: mix metacredo --diff --base origin/${{ github.base_ref }} --format github --strict

Notes:

  • fetch-depth: 0 is required so git diff can see the base branch.
  • origin/${{ github.base_ref }} is the PR target (e.g. origin/main).

Combined with Ragex

If your project uses Ragex, a single command runs both tools:

- run: mix ragex.ci --base origin/${{ github.base_ref }} --format github

See the Ragex CI guide for details.

Other CI Systems

GitLab CI

metacredo:
  stage: test
  only:
    - merge_requests
  script:
    - mix deps.get
    - mix metacredo --diff --base origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME --strict

Generic

#!/bin/bash
BASE="${CI_BASE_REF:-origin/main}"
mix metacredo --diff --base "$BASE" --strict

API: MetaCredo.Git

The MetaCredo.Git module provides a lightweight git integration with no external dependencies beyond the git binary:

# Find the repo root
root = MetaCredo.Git.repo_root("/path/inside/repo")
# => "/path/to/repo"

# Get changed files
{:ok, files} = MetaCredo.Git.changed_files(root,
  base: "origin/main",
  head: "HEAD",
  extensions: [".ex", ".exs"]
)
# => {:ok, ["lib/foo.ex", "lib/bar.ex"]}

# Bang version (raises on error)
files = MetaCredo.Git.changed_files!(root)

Options for changed_files/2

  • :base -- base git ref (default: "origin/main")
  • :head -- head git ref (default: "HEAD")
  • :filter -- git diff status filter (default: "ACMR", excludes deleted)
  • :extensions -- list of file extensions to keep (default: all)

Troubleshooting

"fatal: bad revision 'origin/main...HEAD'"

Ensure the CI checkout fetches the full history (fetch-depth: 0) or fetch the base branch explicitly:

git fetch origin main
mix metacredo --diff

"No changed files found in diff"

Expected when only non-code files changed. The task exits with code 0.

"--diff requires a git repository, but none was found"

The working directory is not inside a git repo. In CI, the checkout step should handle this automatically.


Version: MetaCredo 0.2.0 Last Updated: May 2026