use crate::config::hooks::HookCommand;
#[derive(Debug, Clone, PartialEq)]
pub enum FileDiscoveryMethod {
CustomCommand(String),
StagedFiles,
AllFiles,
PushFiles,
NoFiles,
Skip,
}
impl FileDiscoveryMethod {
pub fn from_hook_command(cmd: &HookCommand, hook_name: &str) -> Self {
tracing::trace!("Determining file discovery method for command: {}", cmd.run);
if let Some(files_cmd) = &cmd.files {
tracing::trace!("Using custom files command: {}", files_cmd);
return Self::CustomCommand(files_cmd.clone());
}
if cmd.run.contains("{staged_files}") {
tracing::trace!("Found {{staged_files}} placeholder");
return Self::StagedFiles;
}
if cmd.run.contains("{all_files}") {
tracing::trace!("Found {{all_files}} placeholder");
return Self::AllFiles;
}
if cmd.run.contains("{push_files}") {
tracing::trace!("Found {{push_files}} placeholder");
return Self::PushFiles;
}
if cmd.run.contains("{files}") {
tracing::trace!("Found {{files}} placeholder without files: command - will skip");
return Self::Skip;
}
if !cmd.glob.is_empty() || !cmd.exclude.is_empty() || !cmd.file_types.is_empty() {
tracing::trace!(
"Has filtering patterns but no file placeholders - using staged files for {}",
hook_name
);
return Self::StagedFiles;
}
tracing::trace!("No file operations needed - command will execute without file filtering");
Self::NoFiles
}
pub fn should_skip(&self) -> bool {
matches!(self, Self::Skip)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_custom_files_command_takes_priority() {
let cmd = HookCommand {
run: "echo {files}".to_string(),
files: Some("git diff --name-only".to_string()),
..Default::default()
};
let method = FileDiscoveryMethod::from_hook_command(&cmd, "pre-commit");
assert_eq!(
method,
FileDiscoveryMethod::CustomCommand("git diff --name-only".to_string())
);
}
#[test]
fn test_staged_files_placeholder() {
let cmd = HookCommand {
run: "echo {staged_files}".to_string(),
..Default::default()
};
let method = FileDiscoveryMethod::from_hook_command(&cmd, "pre-commit");
assert_eq!(method, FileDiscoveryMethod::StagedFiles);
}
#[test]
fn test_all_files_placeholder() {
let cmd = HookCommand {
run: "echo {all_files}".to_string(),
..Default::default()
};
let method = FileDiscoveryMethod::from_hook_command(&cmd, "pre-commit");
assert_eq!(method, FileDiscoveryMethod::AllFiles);
}
#[test]
fn test_files_without_files_command_skips() {
let cmd = HookCommand {
run: "echo {files}".to_string(),
files: None,
..Default::default()
};
let method = FileDiscoveryMethod::from_hook_command(&cmd, "pre-commit");
assert_eq!(method, FileDiscoveryMethod::Skip);
}
#[test]
fn test_glob_patterns_use_staged_files() {
let cmd = HookCommand {
run: "echo test".to_string(),
glob: std::sync::Arc::new(vec!["*.rs".to_string()]),
..Default::default()
};
let method = FileDiscoveryMethod::from_hook_command(&cmd, "pre-commit");
assert_eq!(method, FileDiscoveryMethod::StagedFiles);
}
#[test]
fn test_no_file_operations_skips() {
let cmd = HookCommand {
run: "echo no files".to_string(),
..Default::default()
};
let method = FileDiscoveryMethod::from_hook_command(&cmd, "pre-commit");
assert_eq!(method, FileDiscoveryMethod::NoFiles);
}
#[test]
fn test_should_skip() {
assert!(!FileDiscoveryMethod::NoFiles.should_skip());
assert!(FileDiscoveryMethod::Skip.should_skip());
assert!(!FileDiscoveryMethod::StagedFiles.should_skip());
assert!(!FileDiscoveryMethod::AllFiles.should_skip());
assert!(!FileDiscoveryMethod::CustomCommand("test".to_string()).should_skip());
}
}