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