use flate2::read::GzDecoder;
use std::{
fs,
fs::File,
io,
io::{Read, Write},
path::Path,
time::{SystemTime, UNIX_EPOCH},
};
use chrono::prelude::*;
use sha2::{Digest, Sha256};
use crate::{error, get_current_branch, in_repo, ok};
pub fn pull(commit: &str) -> io::Result<()> {
if !in_repo() {
return Err(error!("Current directory is not a rvcs repository."));
}
let commits = fs::read_dir(&format!(".rvcs/commits/{}/", get_current_branch()?))?
.collect::<Vec<_>>()
.into_iter()
.rev()
.map(|e| e.unwrap().path().to_str().unwrap().to_owned())
.collect::<Vec<_>>();
let mut to_pull_path = "".to_owned();
for i in 0..commits.len() {
if &commits[i] == &format!(".rvcs/commits/{}/HEAD", get_current_branch()?) {
continue;
}
let msg_path = &format!("{}/MESSAGE", commits[i]);
let f_content = fs::read_to_string(msg_path)?;
let first_line = f_content.split("\r\n").collect::<Vec<&str>>();
let hash = first_line[0].split(" - ").collect::<Vec<&str>>()[0];
if commit == hash || &hash[..8] == commit {
to_pull_path = commits[i].clone();
break;
}
}
if &to_pull_path == "" {
return Err(error!("Invalid commit hash."));
}
let _ = fs::read_dir(".")?
.map(|e| -> io::Result<String> {
let epath = e?.path();
let path = &epath.to_str().unwrap();
if path != &"./.rvcs" {
if epath.is_dir() {
fs::remove_dir_all(&epath)?;
} else {
fs::remove_file(&epath)?;
}
}
Ok(path.to_string())
}).collect::<Vec<_>>();
let links_path = &format!("{}/links", to_pull_path);
let links = fs::read_to_string(links_path)?;
let mut total = 0;
for _ in (&links).lines() {
total += 1;
}
for (i, line) in links.lines().enumerate() {
let splited = line.split('|').collect::<Vec<_>>();
if splited.len() != 2 {
continue;
}
let f_path = &format!("{}/{}", to_pull_path, splited[1]);
let mut buffer = vec![];
File::open(f_path)?.read_to_end(&mut buffer)?;
let mut to_write = vec![];
GzDecoder::new(&*buffer).read_to_end(&mut to_write)?;
File::create(splited[0])?.write_all(&to_write)?;
print!("\r");
for _ in 0..((i+1)/total*50) {
print!("â–ˆ");
}
for _ in 0..((total - (i+1))/total*50) {
print!("â–‘");
}
print!("\t{}/{}", i+1, total);
}
println!();
println!("{} Sucessfully pulled commit files.", ok());
Ok(())
}
pub fn reset(commit: &str, hard: bool) -> io::Result<()> {
if !in_repo() {
return Err(error!("Current directory is not a rvcs repository."));
}
let ancient = fs::read_to_string(&format!(".rvcs/commits/{}/HEAD", get_current_branch()?))?;
let commits = fs::read_dir(&format!(".rvcs/commits/{}/", get_current_branch()?))?
.collect::<Vec<_>>()
.into_iter()
.rev()
.map(|e| e.unwrap().path().to_str().unwrap().to_owned())
.collect::<Vec<_>>();
let mut got_it = false;
for i in 0..commits.len() {
if &commits[i] == &format!(".rvcs/commits/{}/HEAD", get_current_branch()?) {
continue;
}
let msg_path = &format!("{}/MESSAGE", commits[i]);
let f_content = fs::read_to_string(msg_path)?;
let first_line = f_content.split("\r\n").collect::<Vec<&str>>();
let hash = first_line[0].split(" - ").collect::<Vec<&str>>()[0];
if !got_it && (commit == hash || &hash[..8] == commit) {
got_it = true;
File::create(&format!(".rvcs/commits/{}/HEAD", get_current_branch()?))?.write_all(hash.as_bytes())?;
if hard {
continue;
} else {
break;
}
}
if got_it && hard {
fs::remove_dir_all(&commits[i])?;
}
}
println!("{} Sucessfully reset from commit `{}` to `{}`.", ok(), &ancient[..8], &commit[..8]);
Ok(())
}
pub fn log(details: bool) -> io::Result<()> {
if !in_repo() {
return Err(error!("Current folder is not a rvcs repository"));
}
let head = fs::read_to_string(&format!(".rvcs/commits/{}/HEAD", get_current_branch()?))?;
let content = fs::read_dir(format!(".rvcs/commits/{}", get_current_branch()?))?;
let elements = content
.collect::<Vec<_>>()
.into_iter()
.rev()
.collect::<Vec<_>>();
for element in elements {
let epath = element?.path();
if !epath.is_dir() {
continue;
}
let path = epath.to_str().unwrap();
let m_content = fs::read_to_string(&format!("{}/MESSAGE", path))?;
let splited = m_content.split("\r\n").collect::<Vec<_>>();
if splited.len() != 3 {
continue;
}
let first_ln = splited[0].split(" - ").collect::<Vec<_>>();
if first_ln.len() != 2 {
continue;
}
let hash = first_ln[0];
let message = first_ln[1];
let body = splited[1];
let date = splited[2];
if details {
println!(
"{}{} - \x1b[0;35m{}\x1b[0m",
if hash == head {
"(\x1b[0;33mHEAD\x1b[0m) "
} else {
" "
},
message,
hash,
);
println!("{}", body);
println!("Date: {}", date);
} else {
println!(
"{}\x1b[0;35m{}\x1b[0m - {}",
if hash == head {
"(\x1b[0;33mHEAD\x1b[0m) "
} else {
" "
},
&hash[..8],
message
);
}
}
Ok(())
}
pub fn commit(message: &str, body: &str) -> io::Result<()> {
if !in_repo() {
return Err(error!("Current folder is not a rvcs repository."));
}
let branch_folder = &format!(".rvcs/commits/{}", get_current_branch()?);
if !Path::new(branch_folder).exists() {
return Err(error!(
(&format!(
"No `{}` branch in the current repository.",
get_current_branch()?
))
));
}
let files = fs::read_dir(".rvcs/objects/")?.collect::<Vec<_>>();
if files.len() < 2
{
return Err(error!(
"No changes since last commit. Consider running `rvcs add <files>` before commiting."
));
}
let date = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let mut hasher = Sha256::new();
hasher.update(format!("{}{}{}", date, message, body).as_bytes());
let hash = &format!("{:x}", hasher.finalize());
let folder_name = format!("{}/{}{}", branch_folder, date, hash);
fs::create_dir(&folder_name)?;
for entry in fs::read_dir(".rvcs/objects/")? {
let epath = entry?.path();
let path = epath.to_str().unwrap();
let mut buffer = vec![];
File::open(path)?.read_to_end(&mut buffer)?;
File::create(&path.replace(".rvcs/objects", &folder_name))?.write_all(&buffer)?;
}
File::create(&format!("{}/MESSAGE", folder_name))?.write_all(
format!(
"{} - {}\r\n{}\r\n{}",
hash,
message,
body,
Local::now().format("%a %b %d %Y %H:%M:%S")
)
.as_bytes(),
)?;
fs::remove_dir_all(".rvcs/objects/")?;
fs::create_dir(".rvcs/objects/")?;
File::create(&format!(".rvcs/commits/{}/HEAD", get_current_branch()?))?
.write_all(hash.as_bytes())?;
println!("{} Sucessfully commited changes into `{}`.", ok(), &hash[..8]);
Ok(())
}