1use dialoguer::MultiSelect;
2use git2::{Repository, Status};
3use std::{path::Path, process};
4
5#[derive(Clone, Debug)]
6pub struct PathItems {
7 path: String,
8 is_staged: bool,
9 is_selected: bool,
10}
11
12pub fn get_paths(repo: &Repository) -> Result<Vec<PathItems>, git2::Error> {
15 let statuses = repo.statuses(None)?;
16
17 if statuses.is_empty() {
18 println!("{}", console::style("✔ working tree clean ✔").green());
19 process::exit(1)
20 }
21
22 let mut items: Vec<PathItems> = vec![];
23
24 for diff_entry in statuses.iter() {
25 if diff_entry.status() == Status::IGNORED {
26 continue;
27 }
28
29 let path_items = diff_entry
30 .head_to_index()
32 .and_then(|d| {
34 Some(PathItems {
35 path: String::from(d.new_file().path()?.display().to_string()),
36 is_staged: true,
37 is_selected: false,
38 })
39 })
40 .or_else(|| {
42 Option::from(
43 diff_entry
44 .index_to_workdir()
45 .and_then(|d| {
46 Some(PathItems {
47 path: String::from(d.new_file().path()?.display().to_string()),
48 is_staged: false,
49 is_selected: false,
50 })
51 })
52 .or_else(|| {
55 diff_entry.index_to_workdir().and_then(|d| {
56 Some(PathItems {
57 path: String::from(d.old_file().path()?.display().to_string()),
58 is_staged: false,
59 is_selected: false,
60 })
61 })
62 })
63 .unwrap_or_else(|| PathItems {
65 path: String::from("<unknown>"),
66 is_staged: false,
67 is_selected: false,
68 }),
69 )
70 })
71 .unwrap();
72
73 items.push(path_items);
74 }
75
76 if items.is_empty() {
78 println!("{}", console::style("✔ working tree clean ✔").green());
79 process::exit(1)
80 }
81
82 Ok(items)
83}
84
85pub fn choose_files(path_items: Vec<PathItems>) -> Vec<PathItems> {
93 let list_of_paths: Vec<String> = path_items.iter().map(|p| p.path.clone()).collect();
95 let list_of_preselected: Vec<bool> = path_items.iter().map(|p| p.is_staged).collect();
96
97 let selections = MultiSelect::new()
98 .with_prompt("Choose files to stage")
99 .items(list_of_paths)
100 .defaults(&list_of_preselected)
101 .interact()
102 .unwrap_or_else(|_| {
103 eprintln!("{}", console::style("Error selecting files").red());
104 process::exit(1)
105 });
106
107 let mut paths: Vec<PathItems> = path_items.clone();
108
109 for index in selections {
110 paths[index].is_selected = true;
111 }
112
113 paths
114}
115
116pub fn git_add_selected(repo: &Repository, paths: &Vec<PathItems>) -> Result<(), git2::Error> {
123 let mut index = repo.index()?;
124
125 println!("{}", console::style("Changes Made:").bold());
126
127 let mut logs = vec![];
128
129 for item in paths {
130 if item.is_staged && !item.is_selected {
132 let target = repo.head()?.peel(git2::ObjectType::Commit)?;
133 repo.reset_default(Some(&target), &[&item.path])?;
134
135 logs.push(format!(
136 " - {} {}",
137 console::style("Unstaged:").yellow(),
138 item.path.clone()
139 ));
140 } else if !item.is_staged && item.is_selected {
141 let p = Path::new(&item.path);
142
143 index.add_path(p).unwrap_or_else(|e| {
144 eprintln!("{}", e);
145 process::exit(1)
146 });
147
148 logs.push(format!(
149 " - {} {}",
150 console::style("Staged:").green(),
151 item.path
152 ));
153
154 index.write().unwrap_or_else(|_| {
155 println!("{}", console::style("Failed to write index").red());
156 process::exit(1)
157 });
158 } else {
159 if item.is_staged {
160 logs.push(format!(
161 " - {} {}",
162 console::style("Staged:").green(),
163 item.path.clone()
164 ));
165 } else {
166 logs.push(format!(
167 " - {} {}",
168 console::style("Unstaged:").yellow(),
169 item.path.clone()
170 ));
171 }
172 }
173 }
174
175 println!("{}", logs.join("\n"));
176
177 Ok(())
178}
179
180#[cfg(test)]
182mod tests {}