1use anyhow::{Context, Result};
2use regex::Regex;
3use std::path::Path;
4use std::process::{Command, Stdio};
5
6use crate::log_debug;
7
8pub fn is_inside_work_tree() -> Result<bool> {
19 let status = Command::new("git")
20 .args(["rev-parse", "--is-inside-work-tree"])
21 .stdout(Stdio::null())
22 .stderr(Stdio::null())
23 .status();
24
25 match status {
26 Ok(exit) => Ok(exit.success()),
27 Err(_) => Ok(false),
28 }
29}
30
31#[must_use]
33pub fn is_binary_diff(diff: &str) -> bool {
34 diff.contains("Binary files")
35 || diff.contains("GIT binary patch")
36 || diff.contains("[Binary file changed]")
37}
38
39pub fn run_git_command(args: &[&str]) -> Result<String> {
53 let output = Command::new("git")
54 .args(args)
55 .output()
56 .context("Failed to execute git command")?;
57
58 if !output.status.success() {
59 return Err(anyhow::anyhow!(
60 "Git command failed: {}",
61 String::from_utf8_lossy(&output.stderr)
62 ));
63 }
64
65 let stdout =
66 String::from_utf8(output.stdout).context("Invalid UTF-8 output from git command")?;
67
68 Ok(stdout.trim().to_string())
69}
70
71#[must_use]
76pub fn should_exclude_file(path: &str) -> bool {
77 log_debug!("Checking if file should be excluded: {}", path);
78 let exclude_patterns = vec![
79 (String::from(r"(^|/)\.git(/|$)"), false), (String::from(r"(^|/)\.svn(/|$)"), false),
81 (String::from(r"(^|/)\.hg(/|$)"), false),
82 (String::from(r"(^|/)\.DS_Store$"), false),
83 (String::from(r"(^|/)node_modules(/|$)"), false),
84 (String::from(r"(^|/)target(/|$)"), false),
85 (String::from(r"(^|/)build(/|$)"), false),
86 (String::from(r"(^|/)dist(/|$)"), false),
87 (String::from(r"(^|/)\.vscode(/|$)"), false),
88 (String::from(r"(^|/)\.idea(/|$)"), false),
89 (String::from(r"(^|/)\.vs(/|$)"), false),
90 (String::from(r"package-lock\.json$"), true),
91 (String::from(r"\.lock$"), true),
92 (String::from(r"\.log$"), true),
93 (String::from(r"\.tmp$"), true),
94 (String::from(r"\.temp$"), true),
95 (String::from(r"\.swp$"), true),
96 (String::from(r"\.min\.js$"), true),
97 ];
98
99 let path = Path::new(path);
100
101 for (pattern, is_extension) in exclude_patterns {
102 let re = match Regex::new(&pattern) {
103 Ok(re) => re,
104 Err(e) => {
105 log_debug!("Failed to compile regex '{}': {}", pattern, e);
106 continue;
107 }
108 };
109
110 if is_extension {
111 if let Some(file_name) = path.file_name()
112 && let Some(file_name_str) = file_name.to_str()
113 && re.is_match(file_name_str)
114 {
115 log_debug!("File excluded: {}", path.display());
116 return true;
117 }
118 } else if let Some(path_str) = path.to_str()
119 && re.is_match(path_str)
120 {
121 log_debug!("File excluded: {}", path.display());
122 return true;
123 }
124 }
125 log_debug!("File not excluded: {}", path.display());
126 false
127}