aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.rs183
1 files changed, 183 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..0b1ec1b
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,183 @@
1use std::{env, fmt, path::Path};
2
3use git2::{Oid, Repository, Status};
4use tico::tico;
5
6fn main() {
7 let args = env::args().collect::<Vec<_>>();
8 match args
9 .iter()
10 .map(String::as_str)
11 .collect::<Vec<&str>>()
12 .as_slice()
13 {
14 [_, "cwd", target] => print!("{}", cwd(target)),
15 [_, "vcs", target] => {
16 if let Some(status) = vcs(target) {
17 print!("{}", status)
18 }
19 }
20 _ => (),
21 }
22}
23
24fn cwd(target: &str) -> String {
25 let home = env::var("HOME").unwrap();
26
27 let home_dir_ext = format!("{}{}", home, "/");
28 if target == home.as_str() || target.starts_with(&home_dir_ext) {
29 let replaced = target.replacen(home.as_str(), "~", 1);
30 tico(&replaced)
31 } else {
32 tico(&target)
33 }
34}
35
36struct VcsStatus {
37 branch: Branch,
38 dist: Dist,
39 status: StatusSummary,
40}
41
42impl fmt::Display for VcsStatus {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 write!(
45 f,
46 "{}{}{}#[fg=colour7]",
47 self.branch, self.dist, self.status
48 )
49 }
50}
51
52enum Branch {
53 Id(Oid),
54 Ref(String),
55 Unknown,
56}
57
58impl fmt::Display for Branch {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 match self {
61 Branch::Id(id) => write!(f, "#[fg=colour3]{:.7} ", id),
62 Branch::Ref(s) => write!(f, "#[fg=colour8]{} ", s),
63 Branch::Unknown => write!(f, ""),
64 }
65 }
66}
67
68enum Dist {
69 Ahead,
70 Behind,
71 Both,
72 Neither,
73}
74
75impl fmt::Display for Dist {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 write!(
78 f,
79 "#[fg=colour8]{}",
80 match self {
81 Self::Ahead => "↑ ",
82 Self::Behind => "↓ ",
83 Self::Both => "↑↓ ",
84 Self::Neither => "",
85 }
86 )
87 }
88}
89
90enum StatusSummary {
91 WtModified,
92 IdxModified,
93 Conflict,
94 Clean,
95}
96
97impl fmt::Display for StatusSummary {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 write!(
100 f,
101 "{}",
102 match self {
103 Self::WtModified => "#[fg=colour1]×",
104 Self::IdxModified => "#[fg=colour3]±",
105 Self::Conflict => "#[fg=colour5]!",
106 Self::Clean => "#[fg=colour2]·",
107 }
108 )
109 }
110}
111
112fn vcs(target: &str) -> Option<VcsStatus> {
113 let repo = match Path::new(target)
114 .ancestors()
115 .map(Repository::open)
116 .find_map(|r| r.ok())
117 {
118 Some(r) => r,
119 None => return None,
120 };
121
122 let dist = match get_ahead_behind(&repo) {
123 Some((ahead, behind)) if ahead > 0 && behind > 0 => Dist::Both,
124 Some((ahead, _)) if ahead > 0 => Dist::Ahead,
125 Some((_, behind)) if behind > 0 => Dist::Behind,
126 _ => Dist::Neither,
127 };
128
129 let branch = match repo.head() {
130 Ok(reference) if reference.is_branch() => {
131 Branch::Ref(reference.shorthand().unwrap().to_string())
132 }
133 Ok(reference) => Branch::Id(reference.peel_to_commit().unwrap().id()),
134 _ => Branch::Unknown,
135 };
136
137 let status = repo_status(&repo);
138
139 Some(VcsStatus {
140 branch,
141 dist,
142 status,
143 })
144}
145
146fn repo_status(repo: &Repository) -> StatusSummary {
147 for file in repo.statuses(None).unwrap().iter() {
148 match file.status() {
149 // STATE: conflicted
150 Status::CONFLICTED => return StatusSummary::Conflict,
151 // STATE: unstaged (working tree modified)
152 Status::WT_NEW
153 | Status::WT_MODIFIED
154 | Status::WT_DELETED
155 | Status::WT_TYPECHANGE
156 | Status::WT_RENAMED => return StatusSummary::WtModified,
157 // STATE: staged (changes added to index)
158 Status::INDEX_NEW
159 | Status::INDEX_MODIFIED
160 | Status::INDEX_DELETED
161 | Status::INDEX_TYPECHANGE
162 | Status::INDEX_RENAMED => return StatusSummary::IdxModified,
163 // STATE: committed (changes have been saved in the repo)
164 _ => return StatusSummary::Clean,
165 }
166 }
167 StatusSummary::Clean
168}
169
170fn get_ahead_behind(r: &Repository) -> Option<(usize, usize)> {
171 let head = (r.head().ok())?;
172 if !head.is_branch() {
173 return None;
174 }
175
176 let head_name = head.shorthand()?;
177 let head_branch = r.find_branch(head_name, git2::BranchType::Local).ok()?;
178 let upstream = head_branch.upstream().ok()?;
179 let head_oid = head.target()?;
180 let upstream_oid = upstream.get().target()?;
181
182 r.graph_ahead_behind(head_oid, upstream_oid).ok()
183}