aboutsummaryrefslogtreecommitdiffstats
path: root/src/bin
diff options
context:
space:
mode:
authorBernhard Guillon <Bernhard.Guillon@begu.org>2025-09-22 16:00:28 +0200
committerBernhard Guillon <Bernhard.Guillon@begu.org>2025-09-22 16:00:28 +0200
commitfce9ead9c50e00fb1315cd2c1df79a9fe79ddf13 (patch)
tree1b0c924da8f216722c3cd2e31054b68e72816aed /src/bin
parent1bbbc6659120f79468ac0d60df42aae633926c3c (diff)
downloadfuture-me-fce9ead9c50e00fb1315cd2c1df79a9fe79ddf13.tar.gz
future-me-fce9ead9c50e00fb1315cd2c1df79a9fe79ddf13.zip
future-me: move git stuff into it's own library
Diffstat (limited to 'src/bin')
-rw-r--r--src/bin/main.rs303
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);
+ }
+ }
+}