Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "codeowners"
version = "0.2.5"
version = "0.2.6"
edition = "2024"

[profile.release]
Expand Down
15 changes: 15 additions & 0 deletions dev/run_benchmarks_for_file.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

mkdir -p tmp

# Check if the file exists before removing it
if [ -f "tmp/codeowners_for_file_benchmarks.md" ]; then
rm tmp/codeowners_for_file_benchmarks.md
fi

echo "To run these benchmarks on your application, you can place this repo next to your rails application and run /usr/bin/env bash ../rubyatscale/codeowners-rs/dev/run_benchmarks_for_file.sh <path/to/file>" >> tmp/codeowners_for_file_benchmarks.md

hyperfine --warmup=2 --runs=3 --export-markdown tmp/codeowners_for_file_benchmarks.md \
"../rubyatscale/codeowners-rs/target/release/codeowners for-file \"$1\"" \
"bin/codeowners for_file \"$1\"" \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"bin/codeowners for_file \"$1\"" \
"bin/codeowners-rs for-file \"$1\"" \

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Command Mean [ms] Min [ms] Max [ms] Relative
../rubyatscale/codeowners-rs/target/release/codeowners for-file "packs/technical_services/addresses/addresses/app/public/addresses.rb" 23.1 ± 9.7 14.8 33.7 1.00
bin/codeowners-rs for-file "packs/technical_services/addresses/addresses/app/public/addresses.rb" 4031.4 ± 32.1 4007.7 4067.9 174.56 ± 73.17
bin/codeownership for_file "packs/technical_services/addresses/addresses/app/public/addresses.rb" 812.9 ± 18.0 799.1 833.2 35.20 ± 14.77

"bin/codeownership for_file \"$1\""
9 changes: 5 additions & 4 deletions dev/run_benchmarks.sh → dev/run_benchmarks_for_gv.sh
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

# Check if the file exists before removing it
if [ -f "tmp/codeowners_benchmarks.md" ]; then
rm tmp/codeowners_benchmarks.md
rm tmp/codeowners_benchmarks_gv.md
fi

echo "To run these benchmarks on your application, you can place this repo next to your rails application and run /usr/bin/env bash ../rubyatscale/codeowners-rs/dev/run_benchmarks.sh from the root of your application" >> tmp/codeowners_benchmarks.md
echo "To run these benchmarks on your application, you can place this repo next to your rails application and run /usr/bin/env bash ../rubyatscale/codeowners-rs/dev/run_benchmarks_for_gv.sh from the root of your application" >> tmp/codeowners_benchmarks_gv.md

hyperfine --warmup=2 --runs=3 --export-markdown tmp/codeowners_benchmarks.md \
hyperfine --warmup=2 --runs=3 --export-markdown tmp/codeowners_benchmarks_gv.md \
'../rubyatscale/codeowners-rs/target/release/codeowners gv' \
'bin/codeowners-rs gv'
'bin/codeowners validate' \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'bin/codeowners validate' \
'bin/codeowners-rs validate' \

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Command Mean [s] Min [s] Max [s] Relative
../rubyatscale/codeowners-rs/target/release/codeowners gv 3.492 ± 0.040 3.447 3.522 1.00
bin/codeowners-rs validate 4.857 ± 0.107 4.772 4.976 1.39 ± 0.03
bin/codeownership validate 60.224 ± 0.473 59.868 60.761 17.25 ± 0.24

🔥

'bin/codeownership validate'
179 changes: 179 additions & 0 deletions src/bin/compare_for_file.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// This is a tool to compare the output of the original codeowners CLI with the optimized version.
// It's useful for verifying that the optimized version is correct.
//
// It's not used in CI, but it's useful for debugging.
//
// To run it, use `cargo run --bin compare_for_file <absolute_project_root>`
//
// It will compare the output of the original codeowners CLI with the optimized version for all files in the project.

use std::{
fs::File,
io::{self, Write},
path::{Path, PathBuf},
process::Command,
};

use codeowners::config::Config as OwnershipConfig;
use codeowners::ownership::{FileOwner, for_file_fast};
use codeowners::runner::{RunConfig, Runner};
use ignore::WalkBuilder;

fn main() {
let project_root = std::env::args().nth(1).expect("usage: compare_for_file <absolute_project_root>");
let project_root = PathBuf::from(project_root);
if !project_root.is_absolute() {
eprintln!("Project root must be absolute");
std::process::exit(2);
}

let codeowners_file_path = project_root.join(".github/CODEOWNERS");
let config_path = project_root.join("config/code_ownership.yml");

let run_config = RunConfig {
project_root: project_root.clone(),
codeowners_file_path,
config_path: config_path.clone(),
no_cache: false,
};

// Build the original, accurate-but-slower runner once
let runner = match Runner::new(&run_config) {
Ok(r) => r,
Err(e) => {
eprintln!("Failed to initialize Runner: {}", e);
std::process::exit(1);
}
};

// Load config once for the optimized path
let config_file = match File::open(&config_path) {
Ok(f) => f,
Err(e) => {
eprintln!("Can't open config file {}: {}", config_path.display(), e);
std::process::exit(1);
}
};
let optimized_config: OwnershipConfig = match serde_yaml::from_reader(config_file) {
Ok(c) => c,
Err(e) => {
eprintln!("Can't parse config file {}: {}", config_path.display(), e);
std::process::exit(1);
}
};

let mut total_files: usize = 0;
let mut diff_count: usize = 0;

// Prefer tracked files from git; fall back to walking the FS if git is unavailable
let tracked_files_output = Command::new("git").arg("-C").arg(&project_root).arg("ls-files").arg("-z").output();

match tracked_files_output {
Ok(output) if output.status.success() => {
let bytes = output.stdout;
for rel in bytes.split(|b| *b == 0u8) {
if rel.is_empty() {
continue;
}
let rel_str = match std::str::from_utf8(rel) {
Ok(s) => s,
Err(_) => continue,
};
let abs_path = project_root.join(rel_str);
// Only process regular files that currently exist
if !abs_path.is_file() {
continue;
}

total_files += 1;
let original = run_original(&runner, &abs_path);
let optimized = run_optimized(&project_root, &optimized_config, &abs_path);

if original != optimized {
diff_count += 1;
println!("\n==== {} ====", abs_path.display());
println!("ORIGINAL:\n{}", original);
println!("OPTIMIZED:\n{}", optimized);
let _ = io::stdout().flush();
}

if total_files % 1000 == 0 {
eprintln!("Processed {} files... diffs so far: {}", total_files, diff_count);
}
}
}
_ => {
eprintln!("git ls-files failed; falling back to filesystem walk (untracked files may be included)");
let walker = WalkBuilder::new(&project_root)
.hidden(false)
.git_ignore(true)
.git_exclude(true)
.follow_links(false)
.build();

for result in walker {
let entry = match result {
Ok(e) => e,
Err(err) => {
eprintln!("walk error: {}", err);
continue;
}
};
if !entry.file_type().map(|t| t.is_file()).unwrap_or(false) {
continue;
}
let path = entry.path();
total_files += 1;

let original = run_original(&runner, path);
let optimized = run_optimized(&project_root, &optimized_config, path);

if original != optimized {
diff_count += 1;
println!("\n==== {} ====", path.display());
println!("ORIGINAL:\n{}", original);
println!("OPTIMIZED:\n{}", optimized);
let _ = io::stdout().flush();
}

if total_files % 1000 == 0 {
eprintln!("Processed {} files... diffs so far: {}", total_files, diff_count);
}
}
}
}

println!("Checked {} files. Diffs: {}", total_files, diff_count);
if diff_count > 0 {
std::process::exit(3);
}
}

fn run_original(runner: &Runner, file_path: &Path) -> String {
let result = runner.for_file(&file_path.to_string_lossy());
if !result.validation_errors.is_empty() {
return result.validation_errors.join("\n");
}
if !result.io_errors.is_empty() {
return format!("IO_ERROR: {}", result.io_errors.join(" | "));
}
result.info_messages.join("\n")
}

fn run_optimized(project_root: &Path, config: &OwnershipConfig, file_path: &Path) -> String {
let owners: Vec<FileOwner> = match for_file_fast::find_file_owners(project_root, config, file_path) {
Ok(v) => v,
Err(e) => return format!("IO_ERROR: {}", e),
};
match owners.len() {
0 => format!("{}", FileOwner::default()),
1 => format!("{}", owners[0]),
_ => {
let mut lines = vec!["Error: file is owned by multiple teams!".to_string()];
for owner in owners {
lines.push(format!("\n{}", owner));
}
lines.join("\n")
}
}
}
3 changes: 2 additions & 1 deletion src/ownership.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use tracing::{info, instrument};

mod file_generator;
mod file_owner_finder;
pub mod for_file_fast;
pub(crate) mod mapper;
pub(crate) mod parser;
mod validator;
Expand All @@ -32,7 +33,7 @@ use self::{
pub struct Ownership {
project: Arc<Project>,
}

#[derive(Debug)]
pub struct FileOwner {
pub team: Team,
pub team_config_file_path: String,
Expand Down
Loading