Skip to main content

gitkit_cli/git/
status.rs

1use std::fmt;
2
3use git2::StatusOptions;
4use ratatui::{
5    style::{
6        Color, Stylize,
7        palette::material::WHITE,
8    },
9    text::Line,
10};
11
12use crate::git::kit::KitRepo;
13
14#[derive(Debug)]
15pub struct KitStatus {
16    pub files: Vec<(String, git2::Status)>,
17    pub pretty: Vec<(String, String)>,
18}
19
20impl KitStatus {
21    pub fn new(repo: &KitRepo) -> Self {
22        let mut opts = StatusOptions::new();
23        opts.include_untracked(true)
24            .recurse_untracked_dirs(true)
25            .include_ignored(false);
26
27        let empty = Self {
28            files: vec![],
29            pretty: vec![("".to_owned(), "no files changes".to_owned())],
30        };
31
32        if let Ok(statuses) = repo.inner.statuses(Some(&mut opts)) {
33            if statuses.is_empty() {
34                return empty;
35            }
36
37            let files: Vec<(String, git2::Status)> = statuses
38                .iter()
39                .map(|entry| {
40                    let path = entry.path().unwrap_or("(no path)").to_string();
41                    let status = entry.status();
42                    (path, status)
43                })
44                .collect();
45            let pretty = KitStatus::pretty(&files);
46            Self { files, pretty }
47        } else {
48            // omit the error silently and return empty vecs
49            empty
50        }
51    }
52
53    // (path, status)
54    fn pretty(files: &Vec<(String, git2::Status)>) -> Vec<(String, String)> {
55        let mut path_status: Vec<(String, String)> = Vec::new();
56
57        for (path, status) in files {
58            let code = match status {
59                s if s.contains(git2::Status::WT_NEW) => "??",
60                s if s.contains(git2::Status::INDEX_NEW) => "A ",
61                s if s.contains(git2::Status::WT_MODIFIED) => " M",
62                s if s.contains(git2::Status::INDEX_MODIFIED) => "M ",
63                s if s.contains(git2::Status::WT_DELETED) => " D",
64                s if s.contains(git2::Status::INDEX_DELETED) => "D ",
65                _ => " ",
66            };
67            path_status.push((path.to_string(), code.to_string()));
68        }
69        path_status
70    }
71
72    // todo add pretty print for empty vec
73    pub fn tui_print(&self) -> Vec<Line<'_>> {
74        let lines: Vec<Line<'_>> = self
75            .pretty
76            .iter()
77            .map(|(path, code)| {
78                let line = Line::from(vec![
79                    format!("{}", code).fg(Color::Green),
80                    " ".into(),
81                    format!(" @ {}", path).fg(WHITE),
82                ]);
83                line
84            })
85            .collect();
86        lines
87    }
88}
89
90impl fmt::Display for KitStatus {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        if self.files.is_empty() {
93            return write!(f, "Your branch is clean.");
94        }
95
96        for (path, status) in &self.files {
97            // Format the status flags into human-readable shorthand
98            let code = match *status {
99                s if s.contains(git2::Status::WT_NEW) => "??",
100                s if s.contains(git2::Status::INDEX_NEW) => "A ",
101                s if s.contains(git2::Status::WT_MODIFIED) => " M",
102                s if s.contains(git2::Status::INDEX_MODIFIED) => "M ",
103                s if s.contains(git2::Status::WT_DELETED) => " D",
104                s if s.contains(git2::Status::INDEX_DELETED) => "D ",
105                _ => "  ",
106            };
107            writeln!(f, "{} {}", code, path)?;
108        }
109        Ok(())
110    }
111}