diff options
| author | Bernhard Guillon <Bernhard.Guillon@begu.org> | 2025-09-17 19:42:01 +0200 |
|---|---|---|
| committer | Bernhard Guillon <Bernhard.Guillon@begu.org> | 2025-09-17 19:42:01 +0200 |
| commit | 5c9246f5331fe579cd686a987ae83e46f0cc868b (patch) | |
| tree | 05e6cd42fc8bed15bcfde53497a4e066987d2c0c /src/main.rs | |
| parent | 99f8dcce0276dbc7df2689e8f8b101ab31b4746d (diff) | |
| download | future-me-5c9246f5331fe579cd686a987ae83e46f0cc868b.tar.gz future-me-5c9246f5331fe579cd686a987ae83e46f0cc868b.zip | |
future-me: Traverse all logs to be able to print the full log
This is how it looks now :)
-------------------------------------------------------------------------
65e8eaa new Bernhard Guillon Hello second bug 2025-09-16 19:44:28
This is the second bug
yey - looks nice
-------------------------------------------------------------------------
79708a9 new Bernhard Guillon Hello world 2025-09-16 19:43:17
this is my first bug report
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 184 |
1 files changed, 140 insertions, 44 deletions
diff --git a/src/main.rs b/src/main.rs index a25a5f7..e070af3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,28 @@ +// 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 serde_json::Result; 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::{NaiveDateTime, Utc, TimeZone}; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Default, Clone)] struct BugReport { timestamp: String, status: String, @@ -56,57 +69,140 @@ fn new_bug() { print!("{}", j); } -fn show() { - let reports = get_reports(); - for report in reports { - //println!("{:?}", report); - println!("================================================================="); - println!("{} | {}", report.status, report.title); - println!("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - for l in report.description { - println!("{}", l); +#[derive(Debug)] +enum GitError { + GitLog(String), + Parse(ParseIntError) +} +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), } } } -fn get_reports() -> Vec<BugReport> { - let mut ret: Vec<BugReport> = Vec::new(); - let output = Command::new("git") - .arg("ls-tree") - .arg("--full-tree") - .arg("-r") +impl From<ParseIntError> 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<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 ls-tree"); - //println!("{}", output.status); - if output.status.success() { - let lines = String::from_utf8_lossy(&output.stdout); - for line in lines.lines() { - if let Some(after_blob) = line.split_once("blob ") { - if let Some((hash, _)) = after_blob.1.split_once('\t') { - let blob = Command::new("git") - .arg("cat-file") - .arg("-p") - .arg(hash) - .output() - .expect("Error with git cat-file"); - if blob.status.success() { - let lines = String::from_utf8_lossy(&blob.stdout); - // TODO: error handling - let bug_report: BugReport = serde_json::from_str(&lines).unwrap(); - ret.push(bug_report); - } - else { - println!("{}", String::from_utf8_lossy(&blob.stderr)); - } - } - } + .expect("Error with git log"); + if !logs.status.success() { + 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(); + 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() { + match i%3 { + 0 => hash = object.split(" ").next().unwrap().to_string(), + 1 => bug_report = serde_json::from_str(&object).unwrap(), + 2 => (), + _ => todo!(), + } + if i>=1 && i%3 ==1 { + map.insert(hash.clone(), bug_report.clone()); } } - else { - println!("{}", String::from_utf8_lossy(&output.stderr)); + 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); + } } - ret } fn main() { |
