rpo 0.1.0-beta.5

Git contribution analysis: commits, file changes, and per-line authorship over time as polars DataFrames
docs.rs failed to build rpo-0.1.0-beta.5
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: rpo-0.1.0-beta.3

rpo

Git-contribution analysis. Walks a repository and produces tabular data (via polars) about commits, file changes, and per-line authorship over time — who wrote what, when, and how much of it survives today.

For the command-line tool and local web UI built on this crate, see rpo-cli. For Python bindings, see rpo on PyPI.

use rpo::RepoAnalyzer;

let analysis = RepoAnalyzer::open(".")?
    .with_exclude_globs(vec!["**/*.lock".to_string()])
    .all()?;

println!("{} commits", analysis.commits.height());
println!("{} file changes", analysis.file_changes.height());
# Ok::<(), rpo::RpoError>(())

How it works

[RepoAnalyzer::open] returns a [Builder]. Configure the walk with the with_* methods, then call a terminal to get DataFrames back:

Terminal Returns
[commits] one row per commit
[file_changes] one row per (commit, file) touched
[blame] per-line authorship at HEAD
[blame_over_time] per-line authorship at each snapshot
[all] every frame, from a single walk

Prefer all() when you need more than one frame — it walks once.

use rpo::{RepoAnalyzer, SnapshotSelector};

// Only the source tree, first-parent history, monthly blame snapshots.
let timeline = RepoAnalyzer::open(".")?
    .with_include_globs(vec!["src/**".to_string()])
    .with_blame_snapshots(SnapshotSelector::Monthly)
    .blame_over_time()?;
# Ok::<(), rpo::RpoError>(())

Reports

[rpo::reports] has ready-made transforms over those frames — summary, per-author and per-file activity, and blame timelines. [rpo::bus_factor] computes knowledge concentration three ways (threshold, ABF, and JBF).

use rpo::{RepoAnalyzer, options::Identity};

let analysis = RepoAnalyzer::open(".")?.all()?;
let report = rpo::reports::author_report(
    &analysis.commits,
    &analysis.file_changes,
    Identity::default(),
    Default::default(),
    Default::default(),
)?;
# Ok::<(), rpo::RpoError>(())

They are ordinary polars, so writing your own is expected — the frames are the real API.

Conventions

Two things to know before reading a frame:

Identities are canonicalized. .mailmap is applied automatically, and the result lands in canonical_author_name, canonical_committer_email, and so on. Group on those, not the raw author_name — otherwise one person committing under several addresses counts as several people. [IdentityMap] adds your own aliases on top.

Datetimes are UTC milliseconds. Every time column is Datetime(TimeUnit::Milliseconds) with no timezone.

Columns, by frame:

  • commitssha, short_sha, author_name, author_email, committer_name, committer_email, canonical_*, author_time, commit_time, parent_count, is_merge, message_subject, files_changed, insertions, deletions
  • file_changessha, commit_time, canonical_*, path, old_path, change_kind, insertions, deletions, extension, is_generated, is_vendored
  • blamesnapshot_sha, snapshot_time, snapshot_label, path, start_line, line_count, commit_sha, commit_time, canonical_*, extension, is_generated, is_vendored

Path filters ([with_include_globs], [with_exclude_globs]) are applied during the walk, so excluded paths never enter any frame and cost nothing downstream. Patterns are globset syntax matched against the full repo-relative path; separators are not special, so *.rs matches src/web/pages.rs. Exclusion wins over inclusion.

Streaming

Repositories large enough that the blame timeline will not fit in memory can stream it instead, one snapshot at a time, via [blame_over_time_streaming] and a [FrameSink] — [ParquetSink], [ParquetDirSink], or DuckDbSink.

Features

Feature Default Description
backend-gix on Git access via gix
backend-git2 off git2 backend — a compile-time seam only; every method is todo!()
sink-duckdb off DuckDbSink, for streaming blame into DuckDB

Exactly one backend must be enabled, or the crate fails to compile.

sink-duckdb is off by default because duckdb is statically bundled and compiles a large C++ amalgamation, roughly doubling a clean build.

# with the DuckDB sink
rpo = { version = "0.1", features = ["sink-duckdb"] }

Compatibility

Pre-1.0: minor releases may break the API. 0.1.0-beta.5 renamed the options types for clarity — Aggregate is now Role, Identify is IdentityField, Aggregation is Identity (with fields role and field), and ActivityOptions is Activity.

Minimum supported Rust version

1.95.