diff options
Diffstat (limited to 'src/bin')
| -rw-r--r-- | src/bin/main.rs | 303 |
1 files changed, 303 insertions, 0 deletions
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<String>, + tags: Option<String>, + 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::<String> = 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<GitLog>, 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<GitLog> = 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::<u64>()?, + _ => 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<String, BugReport> = 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 <command>\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<String> = 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); + } + } +} |
