1use flate2::read::GzDecoder;
2use std::{
3 fs,
4 fs::File,
5 io,
6 io::{Read, Write},
7 path::Path,
8 time::{SystemTime, UNIX_EPOCH},
9};
10
11use chrono::prelude::*;
12use sha2::{Digest, Sha256};
13
14use crate::{error, get_current_branch, in_repo, ok};
15
16pub fn pull(commit: &str) -> io::Result<()> {
17 if !in_repo() {
18 return Err(error!("Current directory is not a rvcs repository."));
19 }
20
21 let commits = fs::read_dir(&format!(".rvcs/commits/{}/", get_current_branch()?))?
22 .collect::<Vec<_>>()
23 .into_iter()
24 .rev()
25 .map(|e| e.unwrap().path().to_str().unwrap().to_owned())
26 .collect::<Vec<_>>();
27 let mut to_pull_path = "".to_owned();
28
29 for i in 0..commits.len() {
30 if &commits[i] == &format!(".rvcs/commits/{}/HEAD", get_current_branch()?) {
31 continue;
32 }
33 let msg_path = &format!("{}/MESSAGE", commits[i]);
34 let f_content = fs::read_to_string(msg_path)?;
35 let first_line = f_content.split("\r\n").collect::<Vec<&str>>();
36 let hash = first_line[0].split(" - ").collect::<Vec<&str>>()[0];
37
38
39 if commit == hash || &hash[..8] == commit {
40 to_pull_path = commits[i].clone();
41 break;
42 }
43 }
44
45 if &to_pull_path == "" {
46 return Err(error!("Invalid commit hash."));
47 }
48
49 let _ = fs::read_dir(".")?
50 .map(|e| -> io::Result<String> {
51 let epath = e?.path();
52 let path = &epath.to_str().unwrap();
53
54 if path != &"./.rvcs" {
55 if epath.is_dir() {
56 fs::remove_dir_all(&epath)?;
57 } else {
58 fs::remove_file(&epath)?;
59 }
60 }
61 Ok(path.to_string())
62 }).collect::<Vec<_>>();
63
64 let links_path = &format!("{}/links", to_pull_path);
65 let links = fs::read_to_string(links_path)?;
66 let mut total = 0;
67 for _ in (&links).lines() {
68 total += 1;
69 }
70 for (i, line) in links.lines().enumerate() {
71 let splited = line.split('|').collect::<Vec<_>>();
72
73 if splited.len() != 2 {
74 continue;
75 }
76 let f_path = &format!("{}/{}", to_pull_path, splited[1]);
77 let mut buffer = vec![];
78 File::open(f_path)?.read_to_end(&mut buffer)?;
79 let mut to_write = vec![];
80 GzDecoder::new(&*buffer).read_to_end(&mut to_write)?;
81 File::create(splited[0])?.write_all(&to_write)?;
82
83 print!("\r");
84 for _ in 0..((i+1)/total*50) {
85 print!("█");
86 }
87
88 for _ in 0..((total - (i+1))/total*50) {
89 print!("░");
90 }
91
92 print!("\t{}/{}", i+1, total);
93 }
94 println!();
95 println!("{} Sucessfully pulled commit files.", ok());
96
97 Ok(())
98
99}
100
101pub fn reset(commit: &str, hard: bool) -> io::Result<()> {
102 if !in_repo() {
103 return Err(error!("Current directory is not a rvcs repository."));
104 }
105
106 let ancient = fs::read_to_string(&format!(".rvcs/commits/{}/HEAD", get_current_branch()?))?;
107
108 let commits = fs::read_dir(&format!(".rvcs/commits/{}/", get_current_branch()?))?
109 .collect::<Vec<_>>()
110 .into_iter()
111 .rev()
112 .map(|e| e.unwrap().path().to_str().unwrap().to_owned())
113 .collect::<Vec<_>>();
114 let mut got_it = false;
115
116 for i in 0..commits.len() {
117 if &commits[i] == &format!(".rvcs/commits/{}/HEAD", get_current_branch()?) {
118 continue;
119 }
120
121 let msg_path = &format!("{}/MESSAGE", commits[i]);
122
123 let f_content = fs::read_to_string(msg_path)?;
124 let first_line = f_content.split("\r\n").collect::<Vec<&str>>();
125 let hash = first_line[0].split(" - ").collect::<Vec<&str>>()[0];
126
127
128 if !got_it && (commit == hash || &hash[..8] == commit) {
129 got_it = true;
130 File::create(&format!(".rvcs/commits/{}/HEAD", get_current_branch()?))?.write_all(hash.as_bytes())?;
131 if hard {
132 continue;
133 } else {
134 break;
135 }
136 }
137 if got_it && hard {
138 fs::remove_dir_all(&commits[i])?;
139 }
140 }
141 println!("{} Sucessfully reset from commit `{}` to `{}`.", ok(), &ancient[..8], &commit[..8]);
142 Ok(())
143}
144
145pub fn log(details: bool) -> io::Result<()> {
146 if !in_repo() {
147 return Err(error!("Current folder is not a rvcs repository"));
148 }
149 let head = fs::read_to_string(&format!(".rvcs/commits/{}/HEAD", get_current_branch()?))?;
150 let content = fs::read_dir(format!(".rvcs/commits/{}", get_current_branch()?))?;
151 let elements = content
152 .collect::<Vec<_>>()
153 .into_iter()
154 .rev()
155 .collect::<Vec<_>>();
156 for element in elements {
157 let epath = element?.path();
158 if !epath.is_dir() {
159 continue;
160 }
161 let path = epath.to_str().unwrap();
162
163 let m_content = fs::read_to_string(&format!("{}/MESSAGE", path))?;
164 let splited = m_content.split("\r\n").collect::<Vec<_>>();
165
166 if splited.len() != 3 {
167 continue;
168 }
169
170 let first_ln = splited[0].split(" - ").collect::<Vec<_>>();
171 if first_ln.len() != 2 {
172 continue;
173 }
174
175 let hash = first_ln[0];
176 let message = first_ln[1];
177 let body = splited[1];
178 let date = splited[2];
179 if details {
180 println!(
181 "{}{} - \x1b[0;35m{}\x1b[0m",
182 if hash == head {
183 "(\x1b[0;33mHEAD\x1b[0m) "
184 } else {
185 " "
186 },
187 message,
188 hash,
189 );
190 println!("{}", body);
191 println!("Date: {}", date);
192 } else {
193 println!(
194 "{}\x1b[0;35m{}\x1b[0m - {}",
195 if hash == head {
196 "(\x1b[0;33mHEAD\x1b[0m) "
197 } else {
198 " "
199 },
200 &hash[..8],
201 message
202 );
203 }
204 }
205
206 Ok(())
207}
208
209pub fn commit(message: &str, body: &str) -> io::Result<()> {
210 if !in_repo() {
211 return Err(error!("Current folder is not a rvcs repository."));
212 }
213 let branch_folder = &format!(".rvcs/commits/{}", get_current_branch()?);
214
215 if !Path::new(branch_folder).exists() {
216 return Err(error!(
217 (&format!(
218 "No `{}` branch in the current repository.",
219 get_current_branch()?
220 ))
221 ));
222 }
223
224 let files = fs::read_dir(".rvcs/objects/")?.collect::<Vec<_>>();
225
226 if files.len() < 2
227 {
229 return Err(error!(
230 "No changes since last commit. Consider running `rvcs add <files>` before commiting."
231 ));
232 }
233
234 let date = SystemTime::now()
235 .duration_since(UNIX_EPOCH)
236 .unwrap()
237 .as_secs();
238 let mut hasher = Sha256::new();
239 hasher.update(format!("{}{}{}", date, message, body).as_bytes());
240 let hash = &format!("{:x}", hasher.finalize());
241 let folder_name = format!("{}/{}{}", branch_folder, date, hash);
242
243 fs::create_dir(&folder_name)?;
244
245 for entry in fs::read_dir(".rvcs/objects/")? {
246 let epath = entry?.path();
247 let path = epath.to_str().unwrap();
248
249 let mut buffer = vec![];
250 File::open(path)?.read_to_end(&mut buffer)?;
251 File::create(&path.replace(".rvcs/objects", &folder_name))?.write_all(&buffer)?;
252 }
253 File::create(&format!("{}/MESSAGE", folder_name))?.write_all(
255 format!(
256 "{} - {}\r\n{}\r\n{}",
257 hash,
258 message,
259 body,
260 Local::now().format("%a %b %d %Y %H:%M:%S")
261 )
262 .as_bytes(),
263 )?;
264
265 fs::remove_dir_all(".rvcs/objects/")?;
266 fs::create_dir(".rvcs/objects/")?;
267
268 File::create(&format!(".rvcs/commits/{}/HEAD", get_current_branch()?))?
269 .write_all(hash.as_bytes())?;
270
271 println!("{} Sucessfully commited changes into `{}`.", ok(), &hash[..8]);
272
273 Ok(())
274}