//! libgit2 log parsing use crate::git::{tree::FileNode, Repo}; use git2::Oid; use std::collections::{BTreeMap, BTreeSet}; /// A file-commit referenced graph thing /// /// git is _weird_! It's essentially just a glorified key-value store /// and it shows. There's no utilities to figure out how thing are /// related, and all the actual graph things in git are sugar on top /// of this store. /// /// In order to make sense of anything in a repo we need to quite /// heavily parse the log. This type here is the result of this /// parsing: you can ask it smart questions like "when did this file /// change" and it will tell you (sort of). #[derive(Debug, Default)] pub(crate) struct CommitGraph { /// The correct order of commits in the log order: Vec, /// List of all files, and the commits in which they were touched file_refs: BTreeMap>, /// Map of commit IDs to metadata commit_refs: BTreeMap, } #[derive(Debug)] pub(crate) struct CommitNode { id: String, author: String, message: String, touches: BTreeSet, time: i64, } fn build_diff_log(repo: &Repo, log: Vec<(String, Vec)>) -> Vec { todo!() } /// Walk through all commits from a given ref and build a commit graph pub(crate) fn create_commit_log(rev: String, repo: &Repo) -> CommitGraph { let mut walker = repo.get_inner().revwalk().unwrap(); walker.push(Oid::from_str(rev.as_str()).unwrap()).unwrap(); let mut commits = walker .into_iter() .map(|oid| { let oid = oid.unwrap(); repo.get_inner().find_commit(oid).unwrap() }) .collect::>(); commits.reverse(); let mut initial: Vec<(_, _)> = commits .into_iter() .map(|commit| { let id = format!("{}", commit.id()); (id.clone(), repo.get_tree(id.as_str())) }) .collect(); // split off rest of the diffs and dissolve the len(1) vec let log = initial.split_off(1); let previous = initial.remove(0).1; let mut order = vec![]; let (commit_refs, file_refs) = log.into_iter().fold( (BTreeMap::new(), BTreeMap::new()), |(mut cm, mut fm), (cid, current)| { let commit_id = format!("{}", cid); let d = repo .get_inner() .diff_tree_to_tree(Some(&previous), Some(¤t), None) .unwrap(); // Store the commit to preserve order order.push(commit_id.clone()); // For each file, store this commit as one that touched it let touches = d.deltas().fold(BTreeSet::new(), |mut set, delta| { let file_id = format!("{}", delta.new_file().id()); fm.entry(file_id.clone()) .or_insert(vec![]) .push(commit_id.clone()); set.insert(file_id); set }); // From the commit, build a metadata object let commit_u = repo .get_inner() .find_commit(Oid::from_str(cid.as_str()).unwrap()) .unwrap(); let author_u = commit_u.author(); let commit = CommitNode { id: commit_id, message: commit_u.message().unwrap().to_owned(), author: format!("{} {}", author_u.name().unwrap(), author_u.email().unwrap()), touches, time: author_u.when().seconds(), }; // Insert the metadata object cm.insert(cid.clone(), commit); // We pass both the modified maps into the next commit (cm, fm) }, ); CommitGraph { order, file_refs, commit_refs, } }