Skip to content

Commit 7efd685

Browse files
committed
feat: add Git CLI commands and refactor stack preset handler
1 parent 7e563de commit 7efd685

File tree

2 files changed

+183
-63
lines changed

2 files changed

+183
-63
lines changed

crates/cli/src/main.rs

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ use std::path::PathBuf;
88
mod advanced_handlers;
99
mod benchmark;
1010
mod comparison_handlers;
11+
mod git_integration;
1112
mod production_handlers;
1213
mod report_handlers;
1314
mod scan_handlers;
1415
mod utils;
1516

1617
use advanced_handlers::*;
18+
use git_integration::GitIntegration;
1719
use production_handlers::*;
1820

1921
#[derive(Parser)]
@@ -243,6 +245,11 @@ enum Commands {
243245
#[arg(long, default_value = "500")]
244246
delay: u64,
245247
},
248+
/// Git integration and hook management
249+
Git {
250+
#[command(subcommand)]
251+
action: GitAction,
252+
},
246253
}
247254

248255
#[derive(Subcommand)]
@@ -349,6 +356,28 @@ enum StackPreset {
349356
},
350357
}
351358

359+
#[derive(Subcommand)]
360+
enum GitAction {
361+
/// Install pre-commit hook
362+
InstallHook {
363+
/// Path to git repository (default: current directory)
364+
#[arg(default_value = ".")]
365+
path: PathBuf,
366+
},
367+
/// Uninstall pre-commit hook
368+
UninstallHook {
369+
/// Path to git repository (default: current directory)
370+
#[arg(default_value = ".")]
371+
path: PathBuf,
372+
},
373+
/// List staged files that would be scanned
374+
Staged {
375+
/// Path to git repository (default: current directory)
376+
#[arg(default_value = ".")]
377+
path: PathBuf,
378+
},
379+
}
380+
352381
fn main() -> Result<()> {
353382
let cli = Cli::parse();
354383

@@ -435,13 +464,14 @@ fn main() -> Result<()> {
435464
format,
436465
production,
437466
} => handle_lang_scan(languages, path, format, production),
438-
Commands::Stack { preset } => handle_stack_preset(preset),
467+
Commands::Stack { preset } => handle_stack_preset_main(preset),
439468
Commands::Watch {
440469
path,
441470
include,
442471
exclude,
443472
delay,
444473
} => handle_watch(path, include, exclude, delay),
474+
Commands::Git { action } => handle_git(action),
445475
}
446476
}
447477

@@ -484,6 +514,116 @@ fn handle_benchmark(path: Option<PathBuf>, quick: bool) -> Result<()> {
484514
}
485515
}
486516

517+
fn handle_stack_preset_main(preset: StackPreset) -> Result<()> {
518+
match preset {
519+
StackPreset::Web { path, production } => {
520+
let languages = vec![
521+
"js".to_string(),
522+
"ts".to_string(),
523+
"jsx".to_string(),
524+
"tsx".to_string(),
525+
"vue".to_string(),
526+
"svelte".to_string(),
527+
];
528+
handle_lang_scan(languages, path, "text".to_string(), production)
529+
}
530+
StackPreset::Backend { path, production } => {
531+
let languages = vec![
532+
"py".to_string(),
533+
"java".to_string(),
534+
"go".to_string(),
535+
"cs".to_string(),
536+
"php".to_string(),
537+
"rb".to_string(),
538+
];
539+
handle_lang_scan(languages, path, "text".to_string(), production)
540+
}
541+
StackPreset::Fullstack { path, production } => {
542+
let languages = vec![
543+
"js".to_string(),
544+
"ts".to_string(),
545+
"py".to_string(),
546+
"java".to_string(),
547+
"go".to_string(),
548+
"rs".to_string(),
549+
];
550+
handle_lang_scan(languages, path, "text".to_string(), production)
551+
}
552+
StackPreset::Mobile { path, production } => {
553+
let languages = vec![
554+
"js".to_string(),
555+
"ts".to_string(),
556+
"swift".to_string(),
557+
"kt".to_string(),
558+
"dart".to_string(),
559+
];
560+
handle_lang_scan(languages, path, "text".to_string(), production)
561+
}
562+
StackPreset::Systems { path, production } => {
563+
let languages = vec![
564+
"rs".to_string(),
565+
"cpp".to_string(),
566+
"c".to_string(),
567+
"go".to_string(),
568+
];
569+
handle_lang_scan(languages, path, "text".to_string(), production)
570+
}
571+
}
572+
}
573+
574+
fn handle_git(action: GitAction) -> Result<()> {
575+
match action {
576+
GitAction::InstallHook { path } => {
577+
println!("🔧 Installing Code-Guardian pre-commit hook...");
578+
579+
if !GitIntegration::is_git_repo(&path) {
580+
eprintln!("❌ Error: {} is not a git repository", path.display());
581+
std::process::exit(1);
582+
}
583+
584+
let repo_root = GitIntegration::get_repo_root(&path)?;
585+
GitIntegration::install_pre_commit_hook(&repo_root)?;
586+
587+
println!("💡 Usage: The hook will automatically run on 'git commit'");
588+
println!("💡 Manual run: code-guardian pre-commit --staged-only --fast");
589+
Ok(())
590+
}
591+
GitAction::UninstallHook { path } => {
592+
println!("🗑️ Uninstalling Code-Guardian pre-commit hook...");
593+
594+
if !GitIntegration::is_git_repo(&path) {
595+
eprintln!("❌ Error: {} is not a git repository", path.display());
596+
std::process::exit(1);
597+
}
598+
599+
let repo_root = GitIntegration::get_repo_root(&path)?;
600+
GitIntegration::uninstall_pre_commit_hook(&repo_root)?;
601+
Ok(())
602+
}
603+
GitAction::Staged { path } => {
604+
println!("📋 Listing staged files...");
605+
606+
if !GitIntegration::is_git_repo(&path) {
607+
eprintln!("❌ Error: {} is not a git repository", path.display());
608+
std::process::exit(1);
609+
}
610+
611+
let repo_root = GitIntegration::get_repo_root(&path)?;
612+
let staged_files = GitIntegration::get_staged_files(&repo_root)?;
613+
614+
if staged_files.is_empty() {
615+
println!("ℹ️ No staged files found.");
616+
} else {
617+
println!("🔍 Found {} staged file(s):", staged_files.len());
618+
for (i, file) in staged_files.iter().enumerate() {
619+
println!(" {}. {}", i + 1, file.display());
620+
}
621+
}
622+
Ok(())
623+
}
624+
}
625+
}
626+
487627
#[cfg(test)]
488628
mod tests {
489629
use super::*;

crates/cli/src/production_handlers.rs

Lines changed: 42 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::git_integration::GitIntegration;
12
use anyhow::Result;
23
use code_guardian_core::{AlertDetector, ConsoleLogDetector, DebuggerDetector};
34
use code_guardian_core::{DetectorFactory, Match, PatternDetector, Scanner};
@@ -102,9 +103,48 @@ pub fn handle_pre_commit(path: PathBuf, staged_only: bool, fast: bool) -> Result
102103
};
103104

104105
let scanner = Scanner::new(detectors);
106+
105107
let matches = if staged_only {
106-
// TODO: Implement git diff --cached integration
107-
scanner.scan(&path)?
108+
// Check if we're in a git repository
109+
if !GitIntegration::is_git_repo(&path) {
110+
println!("⚠️ Not in a git repository. Scanning entire directory instead.");
111+
scanner.scan(&path)?
112+
} else {
113+
// Get repo root and staged files
114+
let repo_root = GitIntegration::get_repo_root(&path)?;
115+
let staged_files = GitIntegration::get_staged_files(&repo_root)?;
116+
117+
if staged_files.is_empty() {
118+
println!("ℹ️ No staged files found. Nothing to scan.");
119+
return Ok(());
120+
}
121+
122+
println!("🔍 Scanning {} staged file(s)...", staged_files.len());
123+
if !fast {
124+
for file in &staged_files {
125+
println!(" 📄 {}", file.display());
126+
}
127+
}
128+
129+
// Scan only staged files
130+
let mut all_matches = Vec::new();
131+
for file_path in staged_files {
132+
if file_path.is_file() {
133+
// For now, use the directory scanner on each file's parent
134+
// This is a workaround until we implement file-specific scanning
135+
if let Some(parent) = file_path.parent() {
136+
let file_matches = scanner.scan(parent)?;
137+
// Filter matches to only include the specific file
138+
let filtered_matches: Vec<_> = file_matches
139+
.into_iter()
140+
.filter(|m| m.file_path == file_path.to_string_lossy())
141+
.collect();
142+
all_matches.extend(filtered_matches);
143+
}
144+
}
145+
}
146+
all_matches
147+
}
108148
} else {
109149
scanner.scan(&path)?
110150
};
@@ -265,66 +305,6 @@ pub fn handle_lang_scan(
265305
Ok(())
266306
}
267307

268-
/// Handle technology stack presets
269-
pub fn handle_stack_preset(preset: crate::StackPreset) -> Result<()> {
270-
use crate::StackPreset;
271-
272-
match preset {
273-
StackPreset::Web { path, production } => {
274-
let languages = vec![
275-
"js".to_string(),
276-
"ts".to_string(),
277-
"jsx".to_string(),
278-
"tsx".to_string(),
279-
"vue".to_string(),
280-
"svelte".to_string(),
281-
];
282-
handle_lang_scan(languages, path, "text".to_string(), production)
283-
}
284-
StackPreset::Backend { path, production } => {
285-
let languages = vec![
286-
"py".to_string(),
287-
"java".to_string(),
288-
"go".to_string(),
289-
"cs".to_string(),
290-
"php".to_string(),
291-
"rb".to_string(),
292-
];
293-
handle_lang_scan(languages, path, "text".to_string(), production)
294-
}
295-
StackPreset::Fullstack { path, production } => {
296-
let languages = vec![
297-
"js".to_string(),
298-
"ts".to_string(),
299-
"py".to_string(),
300-
"java".to_string(),
301-
"go".to_string(),
302-
"rs".to_string(),
303-
];
304-
handle_lang_scan(languages, path, "text".to_string(), production)
305-
}
306-
StackPreset::Mobile { path, production } => {
307-
let languages = vec![
308-
"js".to_string(),
309-
"ts".to_string(),
310-
"swift".to_string(),
311-
"kt".to_string(),
312-
"dart".to_string(),
313-
];
314-
handle_lang_scan(languages, path, "text".to_string(), production)
315-
}
316-
StackPreset::Systems { path, production } => {
317-
let languages = vec![
318-
"rs".to_string(),
319-
"cpp".to_string(),
320-
"c".to_string(),
321-
"go".to_string(),
322-
];
323-
handle_lang_scan(languages, path, "text".to_string(), production)
324-
}
325-
}
326-
}
327-
328308
/// Handle file watching command
329309
pub fn handle_watch(
330310
_path: PathBuf,

0 commit comments

Comments
 (0)