1use crate::repository::{GitFileStatus, RepoPath};
2use anyhow::{anyhow, Result};
3use std::{
4 path::{Path, PathBuf},
5 process::{Command, Stdio},
6 sync::Arc,
7};
8
9#[derive(Clone)]
10pub struct GitStatus {
11 pub entries: Arc<[(RepoPath, GitFileStatus)]>,
12}
13
14impl GitStatus {
15 pub(crate) fn new(
16 git_binary: &Path,
17 working_directory: &Path,
18 mut path_prefix: &Path,
19 ) -> Result<Self> {
20 let mut child = Command::new(git_binary);
21
22 if path_prefix == Path::new("") {
23 path_prefix = Path::new(".");
24 }
25
26 child
27 .current_dir(working_directory)
28 .args([
29 "--no-optional-locks",
30 "status",
31 "--porcelain=v1",
32 "--untracked-files=all",
33 "-z",
34 ])
35 .arg(path_prefix)
36 .stdin(Stdio::null())
37 .stdout(Stdio::piped())
38 .stderr(Stdio::piped());
39
40 #[cfg(windows)]
41 {
42 use std::os::windows::process::CommandExt;
43 child.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0);
44 }
45
46 let child = child
47 .spawn()
48 .map_err(|e| anyhow!("Failed to start git status process: {}", e))?;
49
50 let output = child
51 .wait_with_output()
52 .map_err(|e| anyhow!("Failed to read git blame output: {}", e))?;
53
54 if !output.status.success() {
55 let stderr = String::from_utf8_lossy(&output.stderr);
56 return Err(anyhow!("git status process failed: {}", stderr));
57 }
58
59 let stdout = String::from_utf8_lossy(&output.stdout);
60 let mut entries = stdout
61 .split('\0')
62 .filter_map(|entry| {
63 if entry.is_char_boundary(3) {
64 let (status, path) = entry.split_at(3);
65 let status = status.trim();
66 Some((
67 RepoPath(PathBuf::from(path)),
68 match status {
69 "A" | "??" => GitFileStatus::Added,
70 "M" => GitFileStatus::Modified,
71 _ => return None,
72 },
73 ))
74 } else {
75 None
76 }
77 })
78 .collect::<Vec<_>>();
79 entries.sort_unstable_by(|a, b| a.0.cmp(&b.0));
80 Ok(Self {
81 entries: entries.into(),
82 })
83 }
84
85 pub fn get(&self, path: &Path) -> Option<GitFileStatus> {
86 self.entries
87 .binary_search_by(|(repo_path, _)| repo_path.0.as_path().cmp(path))
88 .ok()
89 .map(|index| self.entries[index].1)
90 }
91}
92
93impl Default for GitStatus {
94 fn default() -> Self {
95 Self {
96 entries: Arc::new([]),
97 }
98 }
99}