Skip to content

Commit 0322948

Browse files
committed
adding git stage
1 parent 595b86d commit 0322948

File tree

3 files changed

+138
-12
lines changed

3 files changed

+138
-12
lines changed

src/cli.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ pub fn cli() -> Result<RunResult, RunnerError> {
101101

102102
let runner_result = match args.command {
103103
Command::Validate => runner::validate(&run_config, vec![]),
104-
Command::Generate => runner::generate(&run_config),
105-
Command::GenerateAndValidate => runner::generate_and_validate(&run_config, vec![]),
104+
Command::Generate => runner::generate(&run_config, false),
105+
Command::GenerateAndValidate => runner::generate_and_validate(&run_config, vec![], false),
106106
Command::ForFile { name, fast } => runner::for_file(&run_config, &name, fast),
107107
Command::ForTeam { name } => runner::for_team(&run_config, &name),
108108
Command::DeleteCache => runner::delete_cache(&run_config),

src/runner.rs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use core::fmt;
22
use std::{
33
fs::File,
44
path::{Path, PathBuf},
5+
process::Command,
56
};
67

78
use error_stack::{Context, Result, ResultExt};
@@ -144,12 +145,12 @@ pub fn validate(run_config: &RunConfig, _file_paths: Vec<String>) -> RunResult {
144145
run_with_runner(run_config, |runner| runner.validate())
145146
}
146147

147-
pub fn generate(run_config: &RunConfig) -> RunResult {
148-
run_with_runner(run_config, |runner| runner.generate())
148+
pub fn generate(run_config: &RunConfig, git_stage: bool) -> RunResult {
149+
run_with_runner(run_config, |runner| runner.generate(git_stage))
149150
}
150151

151-
pub fn generate_and_validate(run_config: &RunConfig, _file_paths: Vec<String>) -> RunResult {
152-
run_with_runner(run_config, |runner| runner.generate_and_validate())
152+
pub fn generate_and_validate(run_config: &RunConfig, _file_paths: Vec<String>, git_stage: bool) -> RunResult {
153+
run_with_runner(run_config, |runner| runner.generate_and_validate(git_stage))
153154
}
154155

155156
pub fn delete_cache(run_config: &RunConfig) -> RunResult {
@@ -243,38 +244,53 @@ impl Runner {
243244
})
244245
}
245246

246-
pub fn validate(&self) -> RunResult {
247+
pub fn validate(&self ) -> RunResult {
247248
match self.ownership.validate() {
248-
Ok(_) => RunResult::default(),
249+
Ok(_) => {
250+
RunResult::default()
251+
},
249252
Err(err) => RunResult {
250253
validation_errors: vec![format!("{}", err)],
251254
..Default::default()
252255
},
253256
}
254257
}
255258

256-
pub fn generate(&self) -> RunResult {
259+
pub fn generate(&self, git_stage: bool) -> RunResult {
257260
let content = self.ownership.generate_file();
258261
if let Some(parent) = &self.run_config.codeowners_file_path.parent() {
259262
let _ = std::fs::create_dir_all(parent);
260263
}
261264
match std::fs::write(&self.run_config.codeowners_file_path, content) {
262-
Ok(_) => RunResult::default(),
265+
Ok(_) => {
266+
if git_stage {
267+
self.git_stage();
268+
}
269+
RunResult::default()
270+
},
263271
Err(err) => RunResult {
264272
io_errors: vec![err.to_string()],
265273
..Default::default()
266274
},
267275
}
268276
}
269277

270-
pub fn generate_and_validate(&self) -> RunResult {
271-
let run_result = self.generate();
278+
pub fn generate_and_validate(&self, git_stage: bool) -> RunResult {
279+
let run_result = self.generate(git_stage);
272280
if run_result.has_errors() {
273281
return run_result;
274282
}
275283
self.validate()
276284
}
277285

286+
fn git_stage(&self) {
287+
let _ = Command::new("git")
288+
.arg("add")
289+
.arg(&self.run_config.codeowners_file_path)
290+
.current_dir(&self.run_config.project_root)
291+
.output();
292+
}
293+
278294
// TODO: remove this once we've verified the fast path is working
279295
#[allow(dead_code)]
280296
pub fn for_file(&self, file_path: &str) -> RunResult {

tests/git_stage_test.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
use std::fs;
2+
use std::path::{Path, PathBuf};
3+
use std::process::Command;
4+
5+
use codeowners::runner::{self, RunConfig};
6+
7+
fn copy_dir_recursive(from: &Path, to: &Path) {
8+
fs::create_dir_all(to).expect("failed to create destination root");
9+
for entry in fs::read_dir(from).expect("failed to read source dir") {
10+
let entry = entry.expect("failed to read dir entry");
11+
let file_type = entry.file_type().expect("failed to read file type");
12+
let src_path = entry.path();
13+
let dest_path = to.join(entry.file_name());
14+
if file_type.is_dir() {
15+
copy_dir_recursive(&src_path, &dest_path);
16+
} else if file_type.is_file() {
17+
// Ensure parent exists then copy
18+
if let Some(parent) = dest_path.parent() {
19+
fs::create_dir_all(parent).expect("failed to create parent dir");
20+
}
21+
fs::copy(&src_path, &dest_path).expect("failed to copy file");
22+
}
23+
}
24+
}
25+
26+
fn init_git_repo(path: &Path) {
27+
// Initialize a new git repository in the temp project
28+
let status = Command::new("git")
29+
.arg("init")
30+
.current_dir(path)
31+
.output()
32+
.expect("failed to run git init");
33+
assert!(status.status.success(), "git init failed: {}", String::from_utf8_lossy(&status.stderr));
34+
35+
// Configure a dummy identity to appease git if commits ever happen; not strictly needed for staging
36+
let _ = Command::new("git").arg("config").arg("user.email").arg("[email protected]").current_dir(path).output();
37+
let _ = Command::new("git").arg("config").arg("user.name").arg("Test User").current_dir(path).output();
38+
}
39+
40+
fn is_file_staged(repo_root: &Path, rel_path: &str) -> bool {
41+
// Use git diff --name-only --cached to list staged files
42+
let output = Command::new("git")
43+
.arg("diff")
44+
.arg("--name-only")
45+
.arg("--cached")
46+
.current_dir(repo_root)
47+
.output()
48+
.expect("failed to run git diff --cached");
49+
assert!(output.status.success(), "git diff failed: {}", String::from_utf8_lossy(&output.stderr));
50+
let stdout = String::from_utf8_lossy(&output.stdout);
51+
stdout.lines().any(|line| line.trim() == rel_path)
52+
}
53+
54+
fn build_run_config(project_root: &Path, codeowners_rel_path: &str) -> RunConfig {
55+
let project_root = project_root.canonicalize().expect("failed to canonicalize project root");
56+
let codeowners_file_path = project_root.join(codeowners_rel_path);
57+
let config_path = project_root.join("config/code_ownership.yml");
58+
RunConfig {
59+
project_root,
60+
codeowners_file_path,
61+
config_path,
62+
no_cache: true,
63+
}
64+
}
65+
66+
#[test]
67+
fn test_generate_stages_codeowners() {
68+
let temp_dir = tempfile::tempdir().expect("failed to create tempdir");
69+
let temp_root = temp_dir.path().to_path_buf();
70+
71+
// Copy the valid project fixture into a temporary git repo
72+
let fixture_root = PathBuf::from("tests/fixtures/valid_project");
73+
copy_dir_recursive(&fixture_root, &temp_root);
74+
init_git_repo(&temp_root);
75+
76+
// Run generate with staging enabled, targeting the standard CODEOWNERS path
77+
let run_config = build_run_config(&temp_root, ".github/CODEOWNERS");
78+
let result = runner::generate(&run_config, true);
79+
assert!(result.io_errors.is_empty(), "io_errors: {:?}", result.io_errors);
80+
assert!(result.validation_errors.is_empty(), "validation_errors: {:?}", result.validation_errors);
81+
82+
// Assert CODEOWNERS file exists and is staged
83+
let rel_path = ".github/CODEOWNERS";
84+
assert!(run_config.codeowners_file_path.exists(), "CODEOWNERS file was not created");
85+
assert!(is_file_staged(&run_config.project_root, rel_path), "CODEOWNERS file was not staged");
86+
}
87+
88+
#[test]
89+
fn test_generate_and_validate_stages_codeowners() {
90+
let temp_dir = tempfile::tempdir().expect("failed to create tempdir");
91+
let temp_root = temp_dir.path().to_path_buf();
92+
93+
// Copy the valid project fixture into a temporary git repo
94+
let fixture_root = PathBuf::from("tests/fixtures/valid_project");
95+
copy_dir_recursive(&fixture_root, &temp_root);
96+
init_git_repo(&temp_root);
97+
98+
// Run generate_and_validate with staging enabled
99+
let run_config = build_run_config(&temp_root, ".github/CODEOWNERS");
100+
let result = runner::generate_and_validate(&run_config, vec![], true);
101+
assert!(result.io_errors.is_empty(), "io_errors: {:?}", result.io_errors);
102+
assert!(result.validation_errors.is_empty(), "validation_errors: {:?}", result.validation_errors);
103+
104+
// Assert CODEOWNERS file exists and is staged
105+
let rel_path = ".github/CODEOWNERS";
106+
assert!(run_config.codeowners_file_path.exists(), "CODEOWNERS file was not created");
107+
assert!(is_file_staged(&run_config.project_root, rel_path), "CODEOWNERS file was not staged");
108+
}
109+
110+

0 commit comments

Comments
 (0)