This code is work-in-progress, and doesn't work on a repo that has a branched history. The issue here is that after handling a merge commit, keeping track of which commit to look at next is non-trivial. This solution tries to isuse a "skip" command on the walker, but this can accidentally skip commits, when two merges have happened in succession (maybe a bug with the impl, not the concept). But also, the actual merge commit seems to already be part of the norma history? So maybe we can ommit the merge commit explicitly, and simply return a new branch handle instead.wip/yesman
parent
8be6dc679e
commit
70fe187f1e
@ -1,11 +1,57 @@ |
||||
pub type CommitId = usize; |
||||
use crate::HashId; |
||||
use git2::Repository; |
||||
use std::sync::Arc; |
||||
|
||||
/// Represent a commit on a repository
|
||||
///
|
||||
/// This abstraction only contains metadata required to fetch the full
|
||||
/// commit from disk, if it is queried. Any operation on this type
|
||||
/// will block to first load
|
||||
/// When creating a commit object, it is guaranteed that it exists in
|
||||
/// the repository.
|
||||
#[derive(Clone)] |
||||
pub struct Commit { |
||||
pub id: CommitId, |
||||
hash: String, |
||||
pub id: HashId, |
||||
repo: Arc<Repository>, |
||||
} |
||||
|
||||
impl Commit { |
||||
/// Create a commit object and check if it exists in the repo
|
||||
pub fn new(r: &Arc<Repository>, id: HashId) -> Option<Self> { |
||||
r.find_commit(id.to_oid()).ok().map(|_| Self { |
||||
id, |
||||
repo: Arc::clone(r), |
||||
}) |
||||
} |
||||
|
||||
pub fn id_str(&self) -> String { |
||||
self.id.to_string() |
||||
} |
||||
|
||||
pub fn summary(&self) -> String { |
||||
self.find().summary().unwrap().into() |
||||
} |
||||
|
||||
pub fn parent_count(&self) -> usize { |
||||
self.repo |
||||
.find_commit(self.id.to_oid()) |
||||
.unwrap() |
||||
.parent_count() |
||||
} |
||||
|
||||
/// Return the first parent, if it exists
|
||||
pub fn first_parent(&self) -> Option<Self> { |
||||
self.find() |
||||
.parent(0) |
||||
.ok() |
||||
.and_then(|c| Self::new(&self.repo, c.id().into())) |
||||
} |
||||
|
||||
pub fn parent(&self, num: usize) -> Option<Self> { |
||||
self.find() |
||||
.parent(num) |
||||
.ok() |
||||
.and_then(|c| Self::new(&self.repo, c.id().into())) |
||||
} |
||||
|
||||
fn find(&self) -> git2::Commit { |
||||
self.repo.find_commit(self.id.to_oid()).unwrap() |
||||
} |
||||
} |
||||
|
@ -1 +0,0 @@ |
||||
//! Walk the file tree for a particular commit |
@ -1 +0,0 @@ |
||||
spacekookie@qq.53166 |
@ -1,51 +0,0 @@ |
||||
use super::{HashId, RawRepository}; |
||||
use crate::Branch; |
||||
use git2::{Commit, Repository}; |
||||
|
||||
/// Represent some raw branch metadata
|
||||
pub struct RawBranch { |
||||
pub name: String, |
||||
pub head: HashId, |
||||
} |
||||
|
||||
fn print_commit(i: &String, c: &Commit) { |
||||
println!( |
||||
"{}{}: {}", |
||||
i, |
||||
c.id().to_string(), |
||||
c.message().unwrap().trim().split("\n").nth(0).unwrap() |
||||
); |
||||
} |
||||
|
||||
fn print_parent_tree(c: &Commit, indent: String) { |
||||
c.parents().for_each(|c| { |
||||
println!( |
||||
"{}{}: {}", |
||||
indent, |
||||
c.id().to_string(), |
||||
c.message().unwrap().trim().split("\n").nth(0).unwrap() |
||||
); |
||||
|
||||
print_parent_tree(&c, indent.clone()); |
||||
}); |
||||
} |
||||
|
||||
impl RawBranch { |
||||
/// Consume branch reference and enumerate real branch history
|
||||
pub fn into_branch(self, repo: &mut RawRepository) -> Branch { |
||||
todo!() |
||||
} |
||||
|
||||
/// **REMOVE ME** A test function to do some test things
|
||||
pub fn enumerate(&self, indent: String, repo: &Repository) { |
||||
let c = repo.find_commit((&self.head).into()).unwrap(); |
||||
println!( |
||||
"{}{}: {}", |
||||
indent, |
||||
c.id().to_string(), |
||||
c.message().unwrap().trim().split("\n").nth(0).unwrap() |
||||
); |
||||
|
||||
print_parent_tree(&c, indent); |
||||
} |
||||
} |
@ -1,19 +0,0 @@ |
||||
//! Walk along a branch parsing commit metadata
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet}; |
||||
|
||||
pub struct CommitHistory { |
||||
/// The correct order of commit IDs
|
||||
order: Vec<String>, |
||||
/// Map of commit IDs to commit metadata
|
||||
meta: BTreeMap<String, CommitNode>, |
||||
} |
||||
|
||||
pub struct CommitNode { |
||||
id: String, |
||||
author: String, |
||||
commiter: String, |
||||
message: String, |
||||
touches: BTreeSet<String>, |
||||
time: u64, |
||||
} |
@ -1,168 +0,0 @@ |
||||
//! Walk the file tree for a particular commit
|
||||
|
||||
use git2::{self, ObjectType, TreeWalkMode}; |
||||
use std::collections::BTreeMap; |
||||
|
||||
/// A cache of a repository tree
|
||||
#[derive(Default, Debug, Clone)] |
||||
pub(crate) struct Tree { |
||||
inner: BTreeMap<String, TreeNode>, |
||||
} |
||||
|
||||
impl Tree { |
||||
/// Insert a node into a subtree with it's full path
|
||||
fn insert_to_subtree(&mut self, mut path: Vec<String>, name: String, node: TreeNode) { |
||||
// If we are given a path, resolve it first
|
||||
let curr = if path.len() > 0 { |
||||
let rest = path.split_off(1); |
||||
let mut curr = self.inner.get_mut(&path[0]).unwrap(); |
||||
|
||||
for dir in rest { |
||||
match curr { |
||||
TreeNode::Dir(ref mut d) => { |
||||
curr = d.children.inner.get_mut(&dir).unwrap(); |
||||
} |
||||
_ => panic!("Not a tree!"), |
||||
} |
||||
} |
||||
|
||||
match curr { |
||||
TreeNode::Dir(ref mut d) => &mut d.children, |
||||
TreeNode::File(_) => panic!("Not a tree!"), |
||||
} |
||||
} else { |
||||
// If no path was given, we assume the root is meant
|
||||
self |
||||
}; |
||||
|
||||
curr.inner.insert(name, node); |
||||
} |
||||
|
||||
/// Walk through the tree and only return filenode objects
|
||||
pub(crate) fn flatten(&self) -> Vec<FileNode> { |
||||
self.inner.values().fold(vec![], |mut vec, node| { |
||||
match node { |
||||
TreeNode::File(f) => vec.push(f.clone()), |
||||
TreeNode::Dir(d) => vec.append(&mut d.children.flatten()), |
||||
} |
||||
|
||||
vec |
||||
}) |
||||
} |
||||
|
||||
/// Get all the commits that touch a file
|
||||
pub(crate) fn grab_path_history(&self, path: String) -> String { |
||||
let mut path: Vec<String> = path |
||||
.split("/") |
||||
.filter_map(|seg| match seg { |
||||
"" => None, |
||||
val => Some(val.into()), |
||||
}) |
||||
.collect(); |
||||
|
||||
let leaf = if path.len() > 0 { |
||||
let rest = path.split_off(1); |
||||
let mut curr = self.inner.get(&path[0]).unwrap(); |
||||
|
||||
for dir in rest { |
||||
match curr { |
||||
TreeNode::Dir(d) => curr = d.children.inner.get(&dir).unwrap(), |
||||
TreeNode::File(_) => break, // we reached the leaf
|
||||
} |
||||
} |
||||
|
||||
curr |
||||
} else { |
||||
panic!("No valid path!"); |
||||
}; |
||||
|
||||
match leaf { |
||||
TreeNode::File(f) => f.id.clone(), |
||||
_ => panic!("Not a leaf!"), |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[derive(Clone, Debug)] |
||||
pub(crate) enum TreeNode { |
||||
File(FileNode), |
||||
Dir(DirNode), |
||||
} |
||||
|
||||
impl TreeNode { |
||||
fn name(&self) -> String { |
||||
match self { |
||||
Self::File(f) => f.name.clone(), |
||||
Self::Dir(d) => d.name.clone(), |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[derive(Clone, Debug)] |
||||
pub(crate) struct FileNode { |
||||
pub id: String, |
||||
pub path: Vec<String>, |
||||
pub name: String, |
||||
} |
||||
|
||||
#[derive(Clone, Debug)] |
||||
pub(crate) struct DirNode { |
||||
pub path: Vec<String>, |
||||
pub name: String, |
||||
pub children: Tree, |
||||
} |
||||
|
||||
impl DirNode { |
||||
fn append(&mut self, node: TreeNode) { |
||||
self.children.inner.insert(node.name(), node); |
||||
} |
||||
} |
||||
|
||||
/// Take a series of path-segments and render a tree at that location
|
||||
pub(crate) fn parse_tree(tree: git2::Tree) -> Tree { |
||||
let mut root = Tree::default(); |
||||
|
||||
tree.walk(TreeWalkMode::PreOrder, |path, entry| { |
||||
let path: Vec<String> = path |
||||
.split("/") |
||||
.filter_map(|seg| match seg { |
||||
"" => None, |
||||
val => Some(val.into()), |
||||
}) |
||||
.collect(); |
||||
let name = entry.name().unwrap().to_string(); |
||||
|
||||
match entry.kind() { |
||||
// For every tree in the tree we create a new TreeNode with the path we know about
|
||||
Some(ObjectType::Tree) => { |
||||
root.insert_to_subtree( |
||||
path.clone(), |
||||
name.clone(), |
||||
TreeNode::Dir(DirNode { |
||||
path, |
||||
name, |
||||
children: Tree::default(), |
||||
}), |
||||
); |
||||
} |
||||
// If we encounter a blob, this is a file that we can simply insert into the tree
|
||||
Some(ObjectType::Blob) => { |
||||
root.insert_to_subtree( |
||||
path.clone(), |
||||
name.clone(), |
||||
TreeNode::File(FileNode { |
||||
id: format!("{}", entry.id()), |
||||
path, |
||||
name, |
||||
}), |
||||
); |
||||
} |
||||
_ => {} |
||||
} |
||||
|
||||
0 |
||||
}) |
||||
.unwrap(); |
||||
|
||||
root |
||||
} |
Loading…
Reference in new issue