Codeowners is a fast, Rust-based CLI for generating and validating GitHub CODEOWNERS files in large repositories.
Note: For Ruby applications, it's usually easier to use codeowners-rs via the code_ownership gem.
The most common workflow is to generate and validate in one step:
codeowners gv- Generates a fresh
CODEOWNERSfile (default:.github/CODEOWNERS) - Validates ownership and that the file is up to date
- Exits non-zero and prints detailed errors if validation fails
- Quick Start: Generate & Validate
- Installation
- Getting Started
- Declaring Ownership
- CLI Reference
- Configuration
- Cache
- Validation
- Library Usage
- Development
You can run codeowners without installing a platform-specific binary by using DotSlash, or install from source with Cargo.
- Install DotSlash: see https://dotslash-cli.com/docs/installation/
- Download the latest DotSlash text file from a release, for example https://github.com/rubyatscale/codeowners-rs/releases.
- Execute the downloaded file with DotSlash; it will fetch and run the correct binary.
Requires Rust toolchain.
cargo install --git https://github.com/rubyatscale/codeowners-rs codeowners-
Configure Ownership
Create aconfig/code_ownership.ymlfile. Example:owned_globs: - '{app,components,config,frontend,lib,packs,spec}/**/*.{rb,rake,js,jsx,ts,tsx}' js_package_paths: [] unowned_globs: - db/**/* - app/services/some_file1.rb - frontend/javascripts/**/__generated__/**/*
-
Declare Teams
Example:config/teams/operations.ymlname: Operations github: team: '@my-org/operations-team'
-
Run the Main Workflow
codeowners gv
You can declare code ownership in several ways:
Add a .codeowner file to a directory with the team name as its contents:
TeamName
Add an annotation at the top of a file:
# @team MyTeam// @team MyTeam<!-- @team MyTeam --><%# @team: Foo %>In package.yml (for Ruby Packwerk):
owner: TeamNameIn your team's YML:
owned_globs:
- app/services/my_team/**/*
unowned_globs:
- app/services/my_team/legacy/*unowned_globs "subtracts" from owned_globs
In package.json:
{
"metadata": {
"owner": "My Team"
}
}Configure search paths in code_ownership.yml:
js_package_paths:
- frontend/javascripts/packages/*--codeowners-file-path <path>: Path for the CODEOWNERS file. Default:./.github/CODEOWNERS--config-path <path>: Path tocode_ownership.yml. Default:./config/code_ownership.yml--project-root <path>: Project root. Default:.--no-cache: Disable on-disk caching (useful in CI)-V, --version,-h, --help
generate(g): Generate the CODEOWNERS file and write it to--codeowners-file-path.- Flags:
--skip-stage, -sto avoidgit addafter writing
- Flags:
validate(v): Validate the CODEOWNERS file and configuration.generate-and-validate(gv): Rungeneratethenvalidate.- Flags:
--skip-stage, -s
- Flags:
for-file <path>(f): Print the owner of a file.- Flags:
--from-codeownersto resolve using only the CODEOWNERS rules
- Flags:
for-team <name>(t): Print ownership report for a team.delete-cache(d): Delete the persisted cache.
codeowners for-file path/to/file.rbcodeowners for-team Payrollcodeowners generate --skip-stagecodeowners gv --no-cacheconfig/code_ownership.yml keys and defaults:
owned_globs(required): Glob patterns that must be owned.ruby_package_paths(default:['packs/**/*', 'components/**'])js_package_paths/javascript_package_paths(default:['frontend/**/*'])team_file_glob(default:['config/teams/**/*.yml'])unowned_globs(default:['frontend/**/node_modules/**/*', 'frontend/**/__generated__/**/*'])vendored_gems_path(default:'vendored/')cache_directory(default:'tmp/cache/codeowners')ignore_dirs(default includes:.git,node_modules,tmp, etc.)
See examples in tests/fixtures/**/config/ for reference setups.
By default, cache is stored under tmp/cache/codeowners relative to the project root. This speeds up repeated runs.
- Disable cache for a run: add the global flag
--no-cache - Clear all cache:
codeowners delete-cache
codeowners validate (or codeowners gv) ensures:
- Only one mechanism defines ownership for any file.
- All referenced teams are valid.
- All files in
owned_globsare owned, unless matched byunowned_globs. - The generated
CODEOWNERSfile is up to date.
Exit status is non-zero on errors.
Import public APIs from codeowners::runner::*.
use codeowners::runner::{RunConfig, for_file, teams_for_files_from_codeowners};
fn main() {
let run_config = RunConfig {
project_root: std::path::PathBuf::from("."),
codeowners_file_path: std::path::PathBuf::from(".github/CODEOWNERS"),
config_path: std::path::PathBuf::from("config/code_ownership.yml"),
no_cache: true, // set false to enable on-disk caching
};
// Find owner for a single file using the optimized path (not just CODEOWNERS)
let result = for_file(&run_config, "app/models/user.rb", false);
for msg in result.info_messages { println!("{}", msg); }
for err in result.io_errors { eprintln!("io: {}", err); }
for err in result.validation_errors { eprintln!("validation: {}", err); }
// Map multiple files to teams using CODEOWNERS rules only
let files = vec![
"app/models/user.rb".to_string(),
"config/teams/payroll.yml".to_string(),
];
match teams_for_files_from_codeowners(&run_config, &files) {
Ok(map) => println!("{:?}", map),
Err(e) => eprintln!("error: {}", e),
}
}-
Written in Rust for speed and reliability.
-
To build locally, install Rust:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
-
Please update
CHANGELOG.mdand thisREADME.mdwhen making changes.
src/runner.rs: public façade re-exporting the API and types.src/runner/api.rs: externally available functions used by the CLI and other crates.src/runner/types.rs:RunConfig,RunResult, and runnerError.src/ownership/: all ownership logic (parsing, mapping, validation, generation).src/ownership/codeowners_query.rs: CODEOWNERS-only queries consumed by the façade.