// 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, }; use std::fs; use std::collections::HashMap; use chrono::{Utc, TimeZone}; use std::env::args; // TODO use git::*; use git::GitError; #[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() } 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(); let objects = cat_files(blobs).unwrap(); 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() { match i%2 { 0 => hash = object.split(" ").next().unwrap().to_string(), 1 => { bug_report = serde_json::from_str(&object).unwrap(); map.insert(hash.clone(), bug_report.clone()); }, _ => todo!(), } } for log in logs { 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); } 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, } 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, _ => {print_usage(); todo!()}, } } fn main() { match process_args() { Cmd::Show => show(), Cmd::New => create_new_bug(), } }