// TODO: fix error handling // TODO: fix cloning // TODO: consistant naming // TODO: check matchers use std::num::ParseIntError; use std::fmt; use std::process::Command; use std::io::Write; use std::process::Stdio; #[derive(Debug)] pub enum GitError { GitLog(String), Parse(ParseIntError), UnknownRef, } impl fmt::Display for GitError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { GitError::GitLog(s) => write!(f, "{}", s), GitError::Parse(s) => write!(f, "{}", s), GitError::UnknownRef => write!(f, "Unknown reference"), } } } impl From for GitError { fn from(err: ParseIntError) -> GitError { GitError::Parse(err) } } impl std::error::Error for GitError {} pub fn get_files_to_unstage() -> String { let cmd = Command::new("git") .arg("update-index") .arg("--refresh") .output() .expect("Error with update-index"); match cmd.status.code() { Some(1) => (), Some(0) => (), Some(s) => panic!("Fixme status code: {}",s), None => panic!("Fixme git update-index"), } let lines = String::from_utf8_lossy(&cmd.stdout).to_string(); let mut files = String::default(); for line in lines.lines() { files = files + " " +line.split(":").next().unwrap(); } files[1..].to_string() } pub fn unstage_object(path: &str) { let cmd = Command::new("git") .arg("update-index") .arg("--remove") .arg(path) .output() .expect("Error with update-index"); if !cmd.status.success() { panic!("FIXME unstage_object failed"); } } pub fn update_ref(object: &str) { let cmd = Command::new("git") .arg("update-ref") .arg("refs/notes/devtools/future-me") .arg(object) .output() .expect("Error with update-index"); if !cmd.status.success() { panic!("FIXME: update ref failed"); } } pub fn stage_object(hash: &str, path: &str) { // with git update-index --add --cacheinfo 100644 hash dd/fffff let cmd = Command::new("git") .arg("update-index") .arg("--add") .arg("--cacheinfo") .arg("100644") .arg(hash) .arg(path) .output() .expect("Error with update-index"); if !cmd.status.success() { panic!("FIXME"); } } pub fn create_object(object: String) -> Result { // create an git object with git hash-object -w --stdin let mut files = Command::new("git") .arg("hash-object") .arg("-w") .arg("--stdin") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() .expect("Error with git hash-ojbect"); let mut stdin = files.stdin.take().expect("Failed to open stdin"); std::thread::spawn(move || { stdin.write_all(object.as_bytes()).expect("Failed to write to stdin"); }); let output = files.wait_with_output().expect("Failed to write to stdout"); let lines = String::from_utf8_lossy(&output.stdout).to_string(); Ok(lines.split_whitespace().next().unwrap().to_string()) } pub fn commit(object: String, parent: Option) -> Result { // commit_id=$(echo 'future-me: created a new bug for you' | git commit-tree $tree_id) let mut files = match parent { Some(parent) => Command::new("git") .arg("commit-tree") .arg(object) .arg("-p") .arg(parent) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() .expect("Error with git commit-tree"), None => Command::new("git") .arg("commit-tree") .arg(object) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() .expect("Error with git commit-tree"), }; let mut stdin = files.stdin.take().expect("Failed to open stdin"); std::thread::spawn(move || { stdin.write_all("future-me: created a new bug for you".as_bytes()).expect("Failed to write to stdin"); }); let output = files.wait_with_output().expect("Failed to write to stdout"); let lines = String::from_utf8_lossy(&output.stdout).to_string(); Ok(lines.split_whitespace().next().unwrap().to_string()) } pub fn get_last_ref() -> Result { let cmd = Command::new("git") .arg("show-ref") .arg("refs/notes/devtools/future-me") .output() .expect("Error with git show-ref"); if !cmd.status.success() { return Err(GitError::GitLog(String::from_utf8_lossy(&cmd.stderr).to_string())); } let lines = String::from_utf8_lossy(&cmd.stdout); match lines.split_whitespace().next() { Some(line) => Ok(line.to_string()), None => Err(GitError::UnknownRef), } } pub fn get_current_tree() -> Result{ let cmd = Command::new("git") .arg("log") .arg("-1") .arg("--format=%H") .output() .expect("Error with git log"); if !cmd.status.success() { return Err(GitError::GitLog(String::from_utf8_lossy(&cmd.stderr).to_string())); } let lines = String::from_utf8_lossy(&cmd.stdout); Ok(lines.trim().to_string()) } pub fn short_to_long_hash(short: String) -> Result{ let cmd = Command::new("git") .arg("log") .arg("-1") // TODO: don't limit the output and check if the output is more then one .arg("--format=%H") .arg(short) .output() .expect("Error with git log"); if !cmd.status.success() { return Err(GitError::GitLog(String::from_utf8_lossy(&cmd.stderr).to_string())); } let lines = String::from_utf8_lossy(&cmd.stdout); Ok(lines.trim().to_string()) } pub fn write_tree() -> String { let cmd = Command::new("git") .arg("write-tree") .output() .expect("Error with git write-tree"); if !cmd.status.success() { panic!("{}", String::from_utf8_lossy(&cmd.stderr)); } let lines = String::from_utf8_lossy(&cmd.stdout); lines.trim().to_string() } pub fn create_new_tree() { let cmd = Command::new("git") .arg("read-tree") .arg("--empty") .output() .expect("Error with git read-tree"); if !cmd.status.success() { panic!("{}", String::from_utf8_lossy(&cmd.stderr)); } } pub fn read_tree(tree: &str) { let cmd = Command::new("git") .arg("read-tree") .arg(tree) .output() .expect("Error with git read-tree"); if !cmd.status.success() { panic!("{}", String::from_utf8_lossy(&cmd.stderr)); } } pub fn check_status() { let logs = Command::new("git") .arg("status") .arg("--porcelain") .output() .expect("Error with git status"); if !logs.status.success() { panic!("{}", String::from_utf8_lossy(&logs.stderr).to_string()); //Err(GitError::GitLog(String::from_utf8_lossy(&logs.stderr).to_string())); } let lines = String::from_utf8_lossy(&logs.stdout); for line in lines.lines() { println!("{}", line); if line.starts_with(['M', 'A']) { panic!("You first need to clean you git staging status to use future-me"); } } } #[derive(Debug, Default, Clone)] pub struct GitLog{ pub timestamp: u64, pub hash: String, pub author_name: String, pub author_email: String, pub blob_object: String, } pub fn collect_reachable_objects() -> Result<(Vec, String), GitError> { // TODO: until we have the need for archiving old stuff to archive // we can guarantee that all objects collected exist and are // unchanged. As soon as we support archives we might need to // change this approach a bit. // As we use the first two chars of a git hash as directory name and // the other part as file name we are able to regenerate the hash // from the changed file path. Git log can provide the file path // as well as all other needed information. let mut git_logs: Vec = Vec::new(); let mut blobs = String::default(); let logs = Command::new("git") .arg("log") .arg("--pretty=format:%H#%an#%ae#%at") .arg("--name-only") .arg("refs/notes/devtools/future-me") .output() .expect("Error with git log"); if !logs.status.success() { return Err(GitError::GitLog(String::from_utf8_lossy(&logs.stderr).to_string())); } let lines = String::from_utf8_lossy(&logs.stdout); let mut git_log = GitLog::default(); for (i, line) in lines.lines().enumerate() { match i%3 { 0 => { let parts = line.split("#"); for (i, part) in parts.enumerate() { match i%4 { 0 => git_log.hash=part.to_string(), 1 => git_log.author_name = part.to_string(), 2 => git_log.author_email = part.to_string(), 3 => git_log.timestamp = part.parse::()?, _ => todo!(), // TODO: why we need this? } } }, 1 => { git_log.blob_object = line.replace("/", ""); if blobs.len() > 0 { blobs = blobs + "\n" + &git_log.blob_object; } else { blobs = blobs + &git_log.blob_object; } }, 2 => (), // commit seperator _ => todo!(), // TODO: why we need this? } if i>=1 && i%3 ==1 { git_logs.push(git_log.clone()); } } return Ok((git_logs, blobs)); } pub fn cat_files(blobs: String) -> Result { let mut files = Command::new("git") .arg("cat-file") .arg("--batch") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() .expect("Error with git cat-files --batch"); let mut stdin = files.stdin.take().expect("Failed to open stdin"); std::thread::spawn(move || { stdin.write_all(blobs.as_bytes()).expect("Failed to write to stdin"); }); let output = files.wait_with_output().expect("Failed to write to stdout"); let objects = String::from_utf8_lossy(&output.stdout); Ok(objects.to_string()) }