Skip to content

Commit

Permalink
add tree-info subcommand to more easily test actual tree-traversal pe…
Browse files Browse the repository at this point in the history
…rformance (#301)
  • Loading branch information
Byron committed Mar 6, 2022
1 parent 53e79c8 commit 29fb0c8
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 42 deletions.
98 changes: 66 additions & 32 deletions gitoxide-core/src/repository/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::path::PathBuf;
use crate::OutputFormat;
use git_repository as git;
use git_repository::prelude::ObjectIdExt;
use git_repository::Tree;

mod entries {
use git_repository as git;
Expand All @@ -17,28 +18,32 @@ mod entries {
use git::traverse::tree::visit::Action;
use git_repository::bstr::{ByteSlice, ByteVec};

pub struct Traverse<'a> {
#[cfg_attr(feature = "serde1", derive(serde::Serialize))]
#[derive(Default)]
pub struct Statistics {
pub num_trees: usize,
pub num_links: usize,
pub num_blobs: usize,
pub num_blobs_exec: usize,
pub num_submodules: usize,
#[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
pub bytes: Option<u64>,
#[cfg_attr(feature = "serde1", serde(skip))]
pub num_bytes: u64,
repo: Option<git::Repository>,
out: &'a mut dyn std::io::Write,
}

pub struct Traverse<'repo, 'a> {
pub stats: Statistics,
repo: Option<&'repo git::Repository>,
out: Option<&'a mut dyn std::io::Write>,
path: BString,
path_deque: VecDeque<BString>,
}

impl<'a> Traverse<'a> {
pub fn new(repo: Option<git::Repository>, out: &'a mut dyn std::io::Write) -> Self {
impl<'repo, 'a> Traverse<'repo, 'a> {
pub fn new(repo: Option<&'repo git::Repository>, out: Option<&'a mut dyn std::io::Write>) -> Self {
Traverse {
num_trees: 0,
num_links: 0,
num_blobs: 0,
num_blobs_exec: 0,
num_submodules: 0,
num_bytes: 0,
stats: Default::default(),
repo,
out,
path: BString::default(),
Expand All @@ -62,7 +67,7 @@ mod entries {
}
}

impl<'a> git::traverse::tree::Visit for Traverse<'a> {
impl<'repo, 'a> git::traverse::tree::Visit for Traverse<'repo, 'a> {
fn pop_front_tracked_path_and_set_current(&mut self) {
self.path = self.path_deque.pop_front().expect("every parent is set only once");
}
Expand All @@ -81,59 +86,78 @@ mod entries {
}

fn visit_tree(&mut self, _entry: &EntryRef<'_>) -> Action {
self.num_trees += 1;
self.stats.num_trees += 1;
Action::Continue
}

fn visit_nontree(&mut self, entry: &EntryRef<'_>) -> Action {
use git::objs::tree::EntryMode::*;
let size = self
.repo
.as_ref()
.and_then(|repo| repo.find_object(entry.oid).map(|o| o.data.len()).ok());
format_entry(&mut *self.out, entry, self.path.as_bstr(), size).ok();
if let Some(out) = &mut self.out {
format_entry(out, entry, self.path.as_bstr(), size).ok();
}
if let Some(size) = size {
self.num_bytes += size as u64;
self.stats.num_bytes += size as u64;
}

match entry.mode {
Commit => self.num_submodules += 1,
Blob => self.num_blobs += 1,
BlobExecutable => self.num_blobs_exec += 1,
Link => self.num_links += 1,
Commit => self.stats.num_submodules += 1,
Blob => self.stats.num_blobs += 1,
BlobExecutable => self.stats.num_blobs_exec += 1,
Link => self.stats.num_links += 1,
Tree => unreachable!("BUG"),
}
Action::Continue
}
}
}

pub fn info(
repository: PathBuf,
treeish: Option<&str>,
extended: bool,
format: OutputFormat,
out: &mut dyn io::Write,
err: &mut dyn io::Write,
) -> anyhow::Result<()> {
if format == OutputFormat::Human {
writeln!(err, "Only JSON is implemented - using that instead")?;
}

let repo = git::open(repository)?.apply_environment();
let tree = treeish_to_tree(treeish, &repo)?;

let mut delegate = entries::Traverse::new(extended.then(|| &repo), None);
tree.traverse().breadthfirst(&mut delegate)?;

#[cfg(feature = "serde1")]
{
delegate.stats.bytes = extended.then(|| delegate.stats.num_bytes);
serde_json::to_writer_pretty(out, &delegate.stats)?;
}

Ok(())
}

pub fn entries(
repository: PathBuf,
treeish: Option<&str>,
recursive: bool,
extended: bool,
format: OutputFormat,
out: &mut dyn io::Write,
_err: &mut dyn io::Write,
) -> anyhow::Result<()> {
if format == OutputFormat::Json {
bail!("Only human output format is supported at the moment");
}

let tree_repo = git::open(repository)?;
let repo = tree_repo.clone().apply_environment();

let tree = match treeish {
Some(hex) => git::hash::ObjectId::from_hex(hex.as_bytes())
.map(|id| id.attach(&repo))?
.object()?
.try_into_tree()?,
None => repo.head()?.peel_to_commit_in_place()?.tree()?,
};
let repo = git::open(repository)?.apply_environment();
let tree = treeish_to_tree(treeish, &repo)?;

if recursive {
let mut delegate = entries::Traverse::new(extended.then(|| tree_repo), out);
let mut delegate = entries::Traverse::new(extended.then(|| &repo), out.into());
tree.traverse().breadthfirst(&mut delegate)?;
} else {
for entry in tree.iter() {
Expand All @@ -152,6 +176,16 @@ pub fn entries(
Ok(())
}

fn treeish_to_tree<'repo>(treeish: Option<&str>, repo: &'repo git::Repository) -> anyhow::Result<Tree<'repo>> {
Ok(match treeish {
Some(hex) => git::hash::ObjectId::from_hex(hex.as_bytes())
.map(|id| id.attach(&repo))?
.object()?
.try_into_tree()?,
None => repo.head()?.peel_to_commit_in_place()?.tree()?,
})
}

fn format_entry(
mut out: impl io::Write,
entry: &git::objs::tree::EntryRef<'_>,
Expand Down
22 changes: 13 additions & 9 deletions src/plumbing/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,20 @@ pub fn main() -> Result<()> {
progress,
progress_keep_open,
None,
move |_progress, out, _err| {
core::repository::tree::entries(repository, treeish.as_deref(), recursive, extended, format, out)
},
),
repo::Subcommands::Tree {
cmd: repo::tree::Subcommands::Info { treeish, extended },
} => prepare_and_run(
"repository-tree-info",
verbose,
progress,
progress_keep_open,
None,
move |_progress, out, err| {
core::repository::tree::entries(
repository,
treeish.as_deref(),
recursive,
extended,
format,
out,
err,
)
core::repository::tree::info(repository, treeish.as_deref(), extended, format, out, err)
},
),
repo::Subcommands::Verify {
Expand Down
11 changes: 10 additions & 1 deletion src/plumbing/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,18 +351,27 @@ pub mod repo {
pub mod tree {
#[derive(Debug, clap::Subcommand)]
pub enum Subcommands {
/// Print entries in a given tree
Entries {
/// Traverse the entire tree and its subtrees respectively, not only this tree.
#[clap(long, short = 'r')]
recursive: bool,

/// Provide the files size as well. This is expensive as the object is decoded entirely.
/// Provide files size as well. This is expensive as the object is decoded entirely.
#[clap(long, short = 'e')]
extended: bool,

/// The tree to traverse, or the tree at `HEAD` if unspecified.
treeish: Option<String>,
},
/// Provide information about a tree.
Info {
/// Provide files size as well. This is expensive as the object is decoded entirely.
#[clap(long, short = 'e')]
extended: bool,
/// The tree to traverse, or the tree at `HEAD` if unspecified.
treeish: Option<String>,
},
}
}
}
Expand Down

0 comments on commit 29fb0c8

Please sign in to comment.