gobbledygit/
lib.rs

1use core::fmt;
2use git2::{ErrorCode, Repository, RepositoryOpenFlags, Status, Statuses};
3use std::fmt::{Error, Formatter};
4use std::path::Path;
5
6struct GitStatus {
7    new: i32,
8    modified: i32,
9    deleted: i32,
10    renamed: i32,
11    type_changed: i32,
12    conflicted: i32,
13}
14
15impl fmt::Display for GitStatus {
16    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
17        let mut fmt_string = String::new();
18        // TODO: Too much duplication, refactor this into a impl fn?
19        if self.new != 0 {
20            fmt_string.push_str(format!("{}A", self.new).as_str())
21        }
22
23        if self.modified != 0 {
24            fmt_string.push_str(format!("{}M", self.modified).as_str())
25        }
26
27        if self.deleted != 0 {
28            fmt_string.push_str(format!("{}D", self.deleted).as_str())
29        }
30
31        if self.renamed != 0 {
32            fmt_string.push_str(format!("{}R", self.renamed).as_str())
33        }
34
35        if self.type_changed != 0 {
36            fmt_string.push_str(format!("{}T", self.type_changed).as_str())
37        }
38        if self.conflicted != 0 {
39            fmt_string.push_str(format!("{}C", self.conflicted).as_str())
40        }
41        write!(f, "{}", fmt_string)
42    }
43}
44
45pub fn repo() -> Option<Repository> {
46    let open_flags = RepositoryOpenFlags::all();
47    let paths: [&Path; 0] = []; //Empty path that doesn't need to be
48    match Repository::open_ext(".", open_flags, paths.iter()) {
49        Ok(repo) => Some(repo),
50        Err(_e) => None,
51    }
52}
53
54pub fn head_status(repo: &Repository) -> String {
55    let head = match repo.head() {
56        Ok(head) => Some(head),
57        Err(ref e) if e.code() == ErrorCode::UnbornBranch || e.code() == ErrorCode::NotFound => {
58            None
59        }
60        Err(_e) => None,
61    };
62
63    head.as_ref()
64        .and_then(|r| r.shorthand())
65        .unwrap_or("HEAD")
66        .to_string()
67}
68
69pub fn status(repo: &Repository) -> String {
70    let result = repo.statuses(None);
71
72    match result.as_ref() {
73        Ok(statuses) => format!("{}", git_status(statuses)),
74        Err(_e) => format!(""),
75    }
76}
77
78fn git_status(statuses: &Statuses) -> GitStatus {
79    //TODO: Currently combines both index and working tree status. Separate them
80    let mut new = 0;
81    let mut modified = 0;
82    let mut renamed = 0;
83    let mut deleted = 0;
84    let mut type_changed = 0;
85    let mut conflicted = 0;
86
87    for entry in statuses.iter().filter(|e| e.status() != Status::CURRENT) {
88        match entry.status() {
89            s if s.contains(Status::INDEX_NEW) => new += 1,
90            s if s.contains(Status::INDEX_MODIFIED) => modified += 1,
91            s if s.contains(Status::INDEX_DELETED) => deleted += 1,
92            s if s.contains(Status::INDEX_RENAMED) => renamed += 1,
93            s if s.contains(Status::INDEX_TYPECHANGE) => type_changed += 1,
94            s if s.contains(Status::CONFLICTED) => conflicted += 1,
95            _ => (),
96        };
97
98        match entry.status() {
99            s if s.contains(git2::Status::WT_MODIFIED) => modified += 1,
100            s if s.contains(git2::Status::WT_DELETED) => deleted += 1,
101            s if s.contains(git2::Status::WT_RENAMED) => renamed += 1,
102            s if s.contains(git2::Status::WT_TYPECHANGE) => type_changed += 1,
103            _ => (),
104        };
105    }
106
107    GitStatus {
108        new,
109        modified,
110        renamed,
111        deleted,
112        type_changed,
113        conflicted,
114    }
115}