codediff 0.0.14

Fast, robust, syntax-aware code diffing using tree-sitter ASTs
Documentation
name: CI

on:
  push:
    branches: [main]
  pull_request:

env:
  CARGO_TERM_COLOR: always

jobs:
  fmt:
    name: cargo fmt --check
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7

      - uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt

      - run: cargo fmt --check

  clippy:
    name: cargo clippy (${{ matrix.features == '' && 'default' || matrix.features }})
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        # Same reasoning as build-and-test's matrix below: each feature config is its own
        # compilation unit (see Cargo.toml's [features]), so each needs its own clippy pass, not
        # just a single default-feature run.
        features: ["", "test-fixtures", "stats", "web"]
    steps:
      - uses: actions/checkout@v7

      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy

      - uses: Swatinem/rust-cache@v2
        with:
          key: clippy-${{ matrix.features }}

      - run: cargo clippy --tests --features "${{ matrix.features }}" -- -D warnings

  audit:
    name: cargo audit
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7

      - uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2
        with:
          key: audit

      - run: cargo install cargo-audit --locked

      # No --ignore flags, and it should stay that way. RUSTSEC-2026-0195/-0194 (quick-xml, via
      # syntect -> plist) were ignored here until 2026-09-15 on the stated grounds that syntect
      # pinned a vulnerable plist. It does not - syntect 5.3.0 asks for `plist ^1.3`, which permits
      # 1.10.1, and that depends on quick-xml ^0.42, past the >=0.41 fix. What actually held the
      # resolver back was this crate's own `rust-version`: plist 1.10.1 needs Rust 1.88, so the
      # MSRV-aware resolver would not take it while we claimed 1.85. Raising the MSRV cleared both.
      - run: cargo audit

  mapping-site-js:
    name: JS tests (human_mapping site, web viewer)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7

      - uses: actions/setup-node@v7
        with:
          node-version: 24

      # The Makefile target, not the two `node` lines by hand: it is what `make test` and the
      # pre-push hook run, and the three must not drift apart (see the target's own comment for
      # what the tests do and do not cover).
      - run: make test-mapping-site-js

      # The browser viewer's model (assets/web/), same arrangement - see the target's comment.
      - run: make test-web-js

  # The analysis scripts under research/ compute the numbers that go into the papers, and until
  # 2026-08-27 nothing checked them at all while the Rust half had clippy -D warnings across three
  # feature configs. The ruff version is pinned for the same reason the rule set is pinned in the
  # root ruff.toml: an unpinned linter turns CI red on a day nobody touched the code. The three
  # directories are the same three `make lint-python` covers.
  python-lint:
    name: ruff + pytest (research/, scripts/, assets/)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7

      - uses: astral-sh/ruff-action@v3
        with:
          version: 0.16.4
          args: check
          src: research scripts assets

      - uses: astral-sh/ruff-action@v3
        with:
          version: 0.16.4
          args: format --check
          src: research scripts assets

      # The Gentoo ebuild names all ~293 dependency crates, generated from Cargo.lock. A lock
      # change that does not reach the ebuild produces a package that fails to build for users
      # while looking perfectly fine in review, so it is checked rather than remembered.
      - name: Gentoo CRATES list is in sync with Cargo.lock
        run: python3 scripts/generate_gentoo_crates.py --check

      # The Python unit tests, in research/'s uv environment (pytest is a dev dependency there).
      - uses: astral-sh/setup-uv@v6

      - run: make test-python

  # The release gate, run on every push rather than only at deploy time - a regression is far
  # cheaper to find on the commit that caused it than in the middle of a release. Safe to gate in
  # CI because it is algorithm-only: mismatch counts against the human mapping do not vary by
  # machine (unlike the runtime figure, which `make check-quality` only warns on).
  quality:
    name: quality gate
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7

      - uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2
        with:
          key: quality

      # `test-fixtures`, not the Makefile's default `stats`: the gate needs no git2/rusqlite, and
      # keeping them out of this job's cache is worth the one override.
      - run: make check-quality FEATURES=test-fixtures

  # The painting counterpart of the quality gate, and gated here for the same reason: byte counts
  # against the hand-painted ground truth are algorithm-only and do not vary by machine. It
  # separates what a reader sees from the part of it no matcher improvement can remove - see the
  # target's own comment in the Makefile for why this baseline, unlike the quality one, moves when
  # the ground truth is improved.
  painting:
    name: painting gate
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7

      - uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2
        with:
          key: painting

      - run: make check-painting-attribution

  build-and-test:
    name: build + test (${{ matrix.features == '' && 'default' || matrix.features }})
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        # "" = default features (tui only). test-fixtures, stats and web are each their own
        # compilation unit - see Cargo.toml's [features] - so each needs its own build+test pass,
        # not just a single default-feature run.
        features: ["", "test-fixtures", "stats", "web"]
    steps:
      - uses: actions/checkout@v7

      - uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2
        with:
          key: ${{ matrix.features }}

      - uses: taiki-e/install-action@nextest

      - name: Build
        run: cargo build --release --features "${{ matrix.features }}"

      # cargo-nextest, not `cargo test`: it runs each test in its own process instead of as a
      # thread inside one long-lived binary, so this crate's test fixture caches (never-evicting,
      # process-lifetime - see src/test/helper.rs) get reclaimed by the OS after every test rather
      # than accumulating for the whole suite. Measured locally: same machine, same --release
      # build, peak RSS 10.66GB under `cargo test --release` vs 5.37GB under `cargo nextest run
      # --release`, at comparable wall-clock - the difference between a CI runner OOMing on this
      # suite and not.
      - name: Test
        run: cargo nextest run --release --features "${{ matrix.features }}"