rvcs_lib/
commit.rs

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        /* link file and at least one file */
228    {
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    // <nb_secs_since_1970><hash_date+message+body>
254    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}