From fce9ead9c50e00fb1315cd2c1df79a9fe79ddf13 Mon Sep 17 00:00:00 2001 From: Bernhard Guillon Date: Mon, 22 Sep 2025 16:00:28 +0200 Subject: future-me: move git stuff into it's own library --- src/bin/main.rs | 303 ++++++++++++++++++++++++++++++ src/git.rs | 231 +++++++++++++++++++++++ src/main.rs | 561 -------------------------------------------------------- 3 files changed, 534 insertions(+), 561 deletions(-) create mode 100644 src/bin/main.rs create mode 100644 src/git.rs delete mode 100644 src/main.rs (limited to 'src') diff --git a/src/bin/main.rs b/src/bin/main.rs new file mode 100644 index 0000000..0812f49 --- /dev/null +++ b/src/bin/main.rs @@ -0,0 +1,303 @@ +// SPDX MIT Bernhard Guillon 2025 +// +// As this project is currently in research please just ignore +// all clones and memory copies ^^ As on all my rust projects +// I follow the copy all and fix it later approach. To make +// rust a nice prototyping languague. If you don't like that +// approach you do yours and I do mine ^^ +use git::GitError; +use serde::{Deserialize, Serialize}; +use std::time::{SystemTime, UNIX_EPOCH}; +use std::{ + env::{temp_dir, var}, + fs::File, + io::Read, + process::Command, + process::Stdio, +}; +use std::fs; +use std::io::Write; +use std::collections::HashMap; +use chrono::{Utc, TimeZone}; +use std::env::args; + +// TODO +use git::*; + +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +struct BugReport { + timestamp: String, + status: String, + title: String, + description: Vec, + tags: Option, + version: String +} + +fn new_bug() -> String { + let editor = var("EDITOR").unwrap(); + let mut file_path = temp_dir(); // TODO: figgure out how to get .git dir in a save manner + file_path.push("NEW_BUG_REPORT"); + File::create(&file_path).expect("Could not create file"); + + Command::new(editor) + .arg(&file_path) + .status() + .expect("Something went wrong"); + + let mut new_bug_report = String::new(); + let _ = File::open(&file_path) + .expect("Could not open file") + .read_to_string(&mut new_bug_report); + let _ = fs::remove_file(&file_path); + let mut header = ""; + let mut description: Vec:: = Vec::new(); + for (i, line) in new_bug_report.lines().enumerate() { + match i { + 0 => header = line, + 1 => (), + _ => description.push(line.to_string()), + } + } + let report = BugReport { + timestamp: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs().to_string(), + title: header.to_owned(), + description: description.to_owned(), + status: "new".to_owned(), + tags: None, + version: "v1".to_owned(), + }; + serde_json::to_string(&report).unwrap() +} + + + +#[derive(Debug, Default, Clone)] +struct GitLog{ + timestamp: u64, + hash: String, + author_name: String, + author_email: String, + blob_object: String, +} + +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() { + GitError::GitLog(String::from_utf8_lossy(&logs.stderr).to_string()); + //return 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)); +} + +fn show() { + // TODO: split this function to collect the reports and to show them + // in the desired format. + // For a short log we don't need to do all this extra work ^^ + let (logs, blobs) = collect_reachable_objects().unwrap(); + //println!("logs {:?}", logs); + //println!("blobs {:?}", blobs); + 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); + let mut map: HashMap = HashMap::new(); + let mut bug_report: BugReport = BugReport::default(); + let mut hash: String = String::default(); + for (i, object) in objects.lines().enumerate() { + //println!("object |{}|", object); + match i%2 { + 0 => hash = object.split(" ").next().unwrap().to_string(), + 1 => { + //println!("|{:?}|", object); + bug_report = serde_json::from_str(&object).unwrap(); map.insert(hash.clone(), bug_report.clone()); + //println!("INSERT") + }, + //2 => (), + _ => todo!(), + } +// if i>=1 && i%3 ==1 { +// println!("insert bug report"); +// map.insert(hash.clone(), bug_report.clone()); +// } + } + for log in logs { + //println!("{:?}", log); + let entry = map[&log.blob_object].clone(); + let datetime = Utc.timestamp_opt(log.timestamp as i64, 0).unwrap(); + // TODO: do we really need to be able to convert times? Or should we just + // collect the git time with the possibilites git gives us? + // I don't like the chrono dependency :/ + println!("-------------------------------------------------------------------------"); + println!{"{} {} {} {}\t{}", &log.hash[0..7], entry.status, log.author_name, entry.title, datetime.format("%Y-%m-%d %H:%M:%S")}; + for line in entry.description { + println!("{}", line); + } + } +} + +fn hash_to_path(hash: &str) -> String { + format!("{}/{}", &hash[..2], &hash[2..]) +} + +fn create_new_bug() { + // first of all check if there is anything staged as we mess with the trees and staging area + // use check_status for that + // after that get the current tree get_current_tree and save it. We need to guarantee that + // we switch back to that as best as we can if anything went bad. + // + // get_last_ref (show-ref ref/notes/devtools/future-me) + // if empty + // call init future-me which creates a new tree + // with git read-tree --empty + // otherwise read the current tree with git read-tree future-me-hash + // + // let the user enter the bug report and creat the json string + // then + // create an git object with git hash-object -w --stdin + // collect the output as object hash + // create a file path string with dd/fffff + // update the index + // with git update-index --add --cacheinfo 100644 hash dd/fffff + // write a new tree object and collect the hash from the output + // git write-tree + // reset our changes to the staging area with + // git update-index --remove + // files_to_unstage=$(git update-index --refresh | cut -d ' ' -f 1 | cut -d ':' -f 1) + // git update-index --remove $files_to_unstage // TODO: check if we can just unstage the + // file as we know the path? + // If we are the first and there is no parent aka future-me ref was empty + // commit_id=$(echo 'future-me: created a new bug for you' | git commit-tree $tree_id) + // else add the parent + // commit_id=$(echo 'future-me: created a new bug for you' | git commit-tree $tree_id -p $add_parent) + // cool now update the ref + // + // git update-ref refs/notes/devtools/future-me $commit_id + // + // switch back to what ever was the working tree we got from get_current_tree + + + check_status(); + let save_current_tree = get_current_tree().unwrap(); + let future_me_ref = match get_last_ref() { + Ok(hash) => {read_tree(&hash); Some(hash)}, + Err(GitError::UnknownRef) => {create_new_tree(); None}, + Err(_) => panic!("fixme"), + }; + let bug_report = new_bug(); + let bug_object = create_object(bug_report.clone()).unwrap(); + let path = hash_to_path(&bug_object); + stage_object(&bug_object, &path); + let tree_object = write_tree(); + let commit_object = match future_me_ref { + Some(parent) => commit(tree_object, Some(parent)).unwrap(), + None => commit(tree_object, None).unwrap(), + }; + let files_to_unstage = get_files_to_unstage(); + unstage_object(&files_to_unstage); + update_ref(&commit_object); + read_tree(&save_current_tree); + println!("hello new bug"); +} + +fn print_usage() { + println!("usage: future-me \n"); + println!("commands:"); + println!("show - shows the log"); + println!("new - enter new bug"); + println!("help - get this help"); +} + +enum Cmd{ + Show, + New, + Check, +} + +fn process_args() -> Cmd { + let myargs: Vec = args().collect(); + println!("{:?}", myargs); + if myargs.len() != 2 { + print_usage(); + } + let unwraped = myargs.get(1).unwrap(); + match unwraped.as_str() { + "show" => Cmd::Show, + "new" => Cmd::New, + "check" => Cmd::Check, + _ => {print_usage(); todo!()}, + } +} + +fn main() { + match process_args() { + Cmd::Show => show(), + Cmd::New => create_new_bug(), + Cmd::Check=> { + println!("{}", get_files_to_unstage()); + //let tree = get_current_tree().unwrap(); + //println!("{}", get_last_ref().unwrap()); + //read_tree(&tree); + } + } +} diff --git a/src/git.rs b/src/git.rs new file mode 100644 index 0000000..728b247 --- /dev/null +++ b/src/git.rs @@ -0,0 +1,231 @@ +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 {} + +////// TODO +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 GitError::GitLog(String::from_utf8_lossy(&cmd.stderr).to_string()); + 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() { + 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() { + 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"); + } + } +} + +pub fn hello() { + println!("hello from git"); +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index c64f6fe..0000000 --- a/src/main.rs +++ /dev/null @@ -1,561 +0,0 @@ -// SPDX MIT Bernhard Guillon 2025 -// -// As this project is currently in research please just ignore -// all clones and memory copies ^^ As on all my rust projects -// I follow the copy all and fix it later approach. To make -// rust a nice prototyping languague. If you don't like that -// approach you do yours and I do mine ^^ - -use serde::{Deserialize, Serialize}; -use std::time::{SystemTime, UNIX_EPOCH}; -use std::{ - env::{temp_dir, var}, - fs::File, - io::Read, - process::Command, - process::Stdio, -}; -use std::fs; -use std::fmt; -use std::num::ParseIntError; -use std::io::Write; -use std::collections::HashMap; -use chrono::{Utc, TimeZone}; -use std::env::args; - -#[derive(Serialize, Deserialize, Debug, Default, Clone)] -struct BugReport { - timestamp: String, - status: String, - title: String, - description: Vec, - tags: Option, - version: String -} - -fn new_bug() -> String { - let editor = var("EDITOR").unwrap(); - let mut file_path = temp_dir(); // TODO: figgure out how to get .git dir in a save manner - file_path.push("NEW_BUG_REPORT"); - File::create(&file_path).expect("Could not create file"); - - Command::new(editor) - .arg(&file_path) - .status() - .expect("Something went wrong"); - - let mut new_bug_report = String::new(); - let _ = File::open(&file_path) - .expect("Could not open file") - .read_to_string(&mut new_bug_report); - let _ = fs::remove_file(&file_path); - let mut header = ""; - let mut description: Vec:: = Vec::new(); - for (i, line) in new_bug_report.lines().enumerate() { - match i { - 0 => header = line, - 1 => (), - _ => description.push(line.to_string()), - } - } - let report = BugReport { - timestamp: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs().to_string(), - title: header.to_owned(), - description: description.to_owned(), - status: "new".to_owned(), - tags: None, - version: "v1".to_owned(), - }; - serde_json::to_string(&report).unwrap() -} - -#[derive(Debug)] -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 {} - -#[derive(Debug, Default, Clone)] -struct GitLog{ - timestamp: u64, - hash: String, - author_name: String, - author_email: String, - blob_object: String, -} - -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() { - GitError::GitLog(String::from_utf8_lossy(&logs.stderr).to_string()); - //return 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)); -} - -fn show() { - // TODO: split this function to collect the reports and to show them - // in the desired format. - // For a short log we don't need to do all this extra work ^^ - let (logs, blobs) = collect_reachable_objects().unwrap(); - //println!("logs {:?}", logs); - //println!("blobs {:?}", blobs); - 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); - let mut map: HashMap = HashMap::new(); - let mut bug_report: BugReport = BugReport::default(); - let mut hash: String = String::default(); - for (i, object) in objects.lines().enumerate() { - //println!("object |{}|", object); - match i%2 { - 0 => hash = object.split(" ").next().unwrap().to_string(), - 1 => { - //println!("|{:?}|", object); - bug_report = serde_json::from_str(&object).unwrap(); map.insert(hash.clone(), bug_report.clone()); - //println!("INSERT") - }, - //2 => (), - _ => todo!(), - } -// if i>=1 && i%3 ==1 { -// println!("insert bug report"); -// map.insert(hash.clone(), bug_report.clone()); -// } - } - for log in logs { - //println!("{:?}", log); - let entry = map[&log.blob_object].clone(); - let datetime = Utc.timestamp_opt(log.timestamp as i64, 0).unwrap(); - // TODO: do we really need to be able to convert times? Or should we just - // collect the git time with the possibilites git gives us? - // I don't like the chrono dependency :/ - println!("-------------------------------------------------------------------------"); - println!{"{} {} {} {}\t{}", &log.hash[0..7], entry.status, log.author_name, entry.title, datetime.format("%Y-%m-%d %H:%M:%S")}; - for line in entry.description { - println!("{}", line); - } - } -} - - -//create-new-bug() { -// readonly bug="$1" -// last_ref=$(git show-ref refs/notes/devtools/future-me | cut -d ' ' -f 1) -// echo $last_ref -// if [ -n "$last_ref" ] -// then -// git read-tree $last_ref -// else -// git read-tree --empty -// fi -// -// object_id=$(echo "$1" | git hash-object -w --stdin) -// echo $object_id -// file_path=${object_id:0:2}/${object_id:2} -// git update-index --add --cacheinfo 100644 $object_id $file_path -// tree_id=$(git write-tree) -// files_to_unstage=$(git update-index --refresh | cut -d ' ' -f 1 | cut -d ':' -f 1) -// git update-index --remove $files_to_unstage -// if [ -n "$last_ref" ] -// then -// add_parent="$last_ref" -// commit_id=$(echo 'future-me: created a new bug for you' | git commit-tree $tree_id -p $add_parent) -// else -// commit_id=$(echo 'future-me: created a new bug for you' | git commit-tree $tree_id) -// fi -// git update-ref refs/notes/devtools/future-me $commit_id -//} -// -// new) -// echo create new bug "$payload" #"${*:2}" -// check-git-status -// last_tree="$(git --no-pager log -1 --format="%H" | tr --delete '\n')" -// echo $last_tree -// create-new-bug "$payload" -// git read-tree "$last_tree" -// ;; - -fn hash_to_path(hash: &str) -> String { - format!("{}/{}", &hash[..2], &hash[2..]) -} - -fn create_new_bug() { - // first of all check if there is anything staged as we mess with the trees and staging area - // use check_status for that - // after that get the current tree get_current_tree and save it. We need to guarantee that - // we switch back to that as best as we can if anything went bad. - // - // get_last_ref (show-ref ref/notes/devtools/future-me) - // if empty - // call init future-me which creates a new tree - // with git read-tree --empty - // otherwise read the current tree with git read-tree future-me-hash - // - // let the user enter the bug report and creat the json string - // then - // create an git object with git hash-object -w --stdin - // collect the output as object hash - // create a file path string with dd/fffff - // update the index - // with git update-index --add --cacheinfo 100644 hash dd/fffff - // write a new tree object and collect the hash from the output - // git write-tree - // reset our changes to the staging area with - // git update-index --remove - // files_to_unstage=$(git update-index --refresh | cut -d ' ' -f 1 | cut -d ':' -f 1) - // git update-index --remove $files_to_unstage // TODO: check if we can just unstage the - // file as we know the path? - // If we are the first and there is no parent aka future-me ref was empty - // commit_id=$(echo 'future-me: created a new bug for you' | git commit-tree $tree_id) - // else add the parent - // commit_id=$(echo 'future-me: created a new bug for you' | git commit-tree $tree_id -p $add_parent) - // cool now update the ref - // - // git update-ref refs/notes/devtools/future-me $commit_id - // - // switch back to what ever was the working tree we got from get_current_tree - - - check_status(); - let save_current_tree = get_current_tree().unwrap(); - let future_me_ref = match get_last_ref() { - Ok(hash) => {read_tree(&hash); Some(hash)}, - Err(GitError::UnknownRef) => {create_new_tree(); None}, - Err(_) => panic!("fixme"), - }; - let bug_report = new_bug(); - let bug_object = create_object(bug_report.clone()).unwrap(); - let path = hash_to_path(&bug_object); - stage_object(&bug_object, &path); - let tree_object = write_tree(); - let commit_object = match future_me_ref { - Some(parent) => commit(tree_object, Some(parent)).unwrap(), - None => commit(tree_object, None).unwrap(), - }; - let files_to_unstage = get_files_to_unstage(); - unstage_object(&files_to_unstage); - update_ref(&commit_object); - read_tree(&save_current_tree); - println!("hello new bug"); -} - -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() -} - -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"); - } -} - -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"); - } -} - -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"); - } -} - -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()) -} - -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()) -} - -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 GitError::GitLog(String::from_utf8_lossy(&cmd.stderr).to_string()); - 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), - } -} - -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() { - GitError::GitLog(String::from_utf8_lossy(&cmd.stderr).to_string()); - } - let lines = String::from_utf8_lossy(&cmd.stdout); - Ok(lines.trim().to_string()) -} - -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() -} - -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)); - } -} - -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)); - } -} - -fn check_status() { - let logs = Command::new("git") - .arg("status") - .arg("--porcelain") - .output() - .expect("Error with git status"); - if !logs.status.success() { - 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"); - } - } -} - -fn print_usage() { - println!("usage: future-me \n"); - println!("commands:"); - println!("show - shows the log"); - println!("new - enter new bug"); - println!("help - get this help"); -} - -enum Cmd{ - Show, - New, - Check, -} - -fn process_args() -> Cmd { - let myargs: Vec = args().collect(); - println!("{:?}", myargs); - if myargs.len() != 2 { - print_usage(); - } - let unwraped = myargs.get(1).unwrap(); - match unwraped.as_str() { - "show" => Cmd::Show, - "new" => Cmd::New, - "check" => Cmd::Check, - _ => {print_usage(); todo!()}, - } -} - -fn main() { - match process_args() { - Cmd::Show => show(), - Cmd::New => create_new_bug(), - Cmd::Check=> { - println!("{}", get_files_to_unstage()); - //let tree = get_current_tree().unwrap(); - //println!("{}", get_last_ref().unwrap()); - //read_tree(&tree); - } - } -} -- cgit v1.2.3