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
CODEOWNERS
file (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.yml
file. 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.yml
name: 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: TeamName
In 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, -s
to avoidgit add
after writing
- Flags:
validate
(v
): Validate the CODEOWNERS file and configuration.generate-and-validate
(gv
): Rungenerate
thenvalidate
.- Flags:
--skip-stage, -s
- Flags:
for-file <path>
(f
): Print the owner of a file.- Flags:
--from-codeowners
to 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.rb
codeowners for-team Payroll
codeowners generate --skip-stage
codeowners gv --no-cache
config/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_globs
are owned, unless matched byunowned_globs
. - The generated
CODEOWNERS
file 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.md
and thisREADME.md
when 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.