You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
117 lines
3.7 KiB
117 lines
3.7 KiB
//! 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<String>,
|
|
/// List of all files, and the commits in which they were touched
|
|
file_refs: BTreeMap<String, Vec<String>>,
|
|
/// Map of commit IDs to metadata
|
|
commit_refs: BTreeMap<String, CommitNode>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) struct CommitNode {
|
|
id: String,
|
|
author: String,
|
|
message: String,
|
|
touches: BTreeSet<String>,
|
|
time: i64,
|
|
}
|
|
|
|
fn build_diff_log(repo: &Repo, log: Vec<(String, Vec<FileNode>)>) -> Vec<CommitNode> {
|
|
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::<Vec<_>>();
|
|
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,
|
|
}
|
|
}
|
|
|