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