Skip to content

Commit 29fb0c8

Browse files
committed
add tree-info subcommand to more easily test actual tree-traversal performance (#301)
1 parent 53e79c8 commit 29fb0c8

File tree

3 files changed

+89
-42
lines changed

3 files changed

+89
-42
lines changed

Diff for: gitoxide-core/src/repository/tree.rs

+66-32
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::path::PathBuf;
66
use crate::OutputFormat;
77
use git_repository as git;
88
use git_repository::prelude::ObjectIdExt;
9+
use git_repository::Tree;
910

1011
mod entries {
1112
use git_repository as git;
@@ -17,28 +18,32 @@ mod entries {
1718
use git::traverse::tree::visit::Action;
1819
use git_repository::bstr::{ByteSlice, ByteVec};
1920

20-
pub struct Traverse<'a> {
21+
#[cfg_attr(feature = "serde1", derive(serde::Serialize))]
22+
#[derive(Default)]
23+
pub struct Statistics {
2124
pub num_trees: usize,
2225
pub num_links: usize,
2326
pub num_blobs: usize,
2427
pub num_blobs_exec: usize,
2528
pub num_submodules: usize,
29+
#[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
30+
pub bytes: Option<u64>,
31+
#[cfg_attr(feature = "serde1", serde(skip))]
2632
pub num_bytes: u64,
27-
repo: Option<git::Repository>,
28-
out: &'a mut dyn std::io::Write,
33+
}
34+
35+
pub struct Traverse<'repo, 'a> {
36+
pub stats: Statistics,
37+
repo: Option<&'repo git::Repository>,
38+
out: Option<&'a mut dyn std::io::Write>,
2939
path: BString,
3040
path_deque: VecDeque<BString>,
3141
}
3242

33-
impl<'a> Traverse<'a> {
34-
pub fn new(repo: Option<git::Repository>, out: &'a mut dyn std::io::Write) -> Self {
43+
impl<'repo, 'a> Traverse<'repo, 'a> {
44+
pub fn new(repo: Option<&'repo git::Repository>, out: Option<&'a mut dyn std::io::Write>) -> Self {
3545
Traverse {
36-
num_trees: 0,
37-
num_links: 0,
38-
num_blobs: 0,
39-
num_blobs_exec: 0,
40-
num_submodules: 0,
41-
num_bytes: 0,
46+
stats: Default::default(),
4247
repo,
4348
out,
4449
path: BString::default(),
@@ -62,7 +67,7 @@ mod entries {
6267
}
6368
}
6469

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

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

8893
fn visit_nontree(&mut self, entry: &EntryRef<'_>) -> Action {
8994
use git::objs::tree::EntryMode::*;
9095
let size = self
9196
.repo
92-
.as_ref()
9397
.and_then(|repo| repo.find_object(entry.oid).map(|o| o.data.len()).ok());
94-
format_entry(&mut *self.out, entry, self.path.as_bstr(), size).ok();
98+
if let Some(out) = &mut self.out {
99+
format_entry(out, entry, self.path.as_bstr(), size).ok();
100+
}
95101
if let Some(size) = size {
96-
self.num_bytes += size as u64;
102+
self.stats.num_bytes += size as u64;
97103
}
98104

99105
match entry.mode {
100-
Commit => self.num_submodules += 1,
101-
Blob => self.num_blobs += 1,
102-
BlobExecutable => self.num_blobs_exec += 1,
103-
Link => self.num_links += 1,
106+
Commit => self.stats.num_submodules += 1,
107+
Blob => self.stats.num_blobs += 1,
108+
BlobExecutable => self.stats.num_blobs_exec += 1,
109+
Link => self.stats.num_links += 1,
104110
Tree => unreachable!("BUG"),
105111
}
106112
Action::Continue
107113
}
108114
}
109115
}
110116

117+
pub fn info(
118+
repository: PathBuf,
119+
treeish: Option<&str>,
120+
extended: bool,
121+
format: OutputFormat,
122+
out: &mut dyn io::Write,
123+
err: &mut dyn io::Write,
124+
) -> anyhow::Result<()> {
125+
if format == OutputFormat::Human {
126+
writeln!(err, "Only JSON is implemented - using that instead")?;
127+
}
128+
129+
let repo = git::open(repository)?.apply_environment();
130+
let tree = treeish_to_tree(treeish, &repo)?;
131+
132+
let mut delegate = entries::Traverse::new(extended.then(|| &repo), None);
133+
tree.traverse().breadthfirst(&mut delegate)?;
134+
135+
#[cfg(feature = "serde1")]
136+
{
137+
delegate.stats.bytes = extended.then(|| delegate.stats.num_bytes);
138+
serde_json::to_writer_pretty(out, &delegate.stats)?;
139+
}
140+
141+
Ok(())
142+
}
143+
111144
pub fn entries(
112145
repository: PathBuf,
113146
treeish: Option<&str>,
114147
recursive: bool,
115148
extended: bool,
116149
format: OutputFormat,
117150
out: &mut dyn io::Write,
118-
_err: &mut dyn io::Write,
119151
) -> anyhow::Result<()> {
120152
if format == OutputFormat::Json {
121153
bail!("Only human output format is supported at the moment");
122154
}
123155

124-
let tree_repo = git::open(repository)?;
125-
let repo = tree_repo.clone().apply_environment();
126-
127-
let tree = match treeish {
128-
Some(hex) => git::hash::ObjectId::from_hex(hex.as_bytes())
129-
.map(|id| id.attach(&repo))?
130-
.object()?
131-
.try_into_tree()?,
132-
None => repo.head()?.peel_to_commit_in_place()?.tree()?,
133-
};
156+
let repo = git::open(repository)?.apply_environment();
157+
let tree = treeish_to_tree(treeish, &repo)?;
134158

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

179+
fn treeish_to_tree<'repo>(treeish: Option<&str>, repo: &'repo git::Repository) -> anyhow::Result<Tree<'repo>> {
180+
Ok(match treeish {
181+
Some(hex) => git::hash::ObjectId::from_hex(hex.as_bytes())
182+
.map(|id| id.attach(&repo))?
183+
.object()?
184+
.try_into_tree()?,
185+
None => repo.head()?.peel_to_commit_in_place()?.tree()?,
186+
})
187+
}
188+
155189
fn format_entry(
156190
mut out: impl io::Write,
157191
entry: &git::objs::tree::EntryRef<'_>,

Diff for: src/plumbing/main.rs

+13-9
Original file line numberDiff line numberDiff line change
@@ -154,16 +154,20 @@ pub fn main() -> Result<()> {
154154
progress,
155155
progress_keep_open,
156156
None,
157+
move |_progress, out, _err| {
158+
core::repository::tree::entries(repository, treeish.as_deref(), recursive, extended, format, out)
159+
},
160+
),
161+
repo::Subcommands::Tree {
162+
cmd: repo::tree::Subcommands::Info { treeish, extended },
163+
} => prepare_and_run(
164+
"repository-tree-info",
165+
verbose,
166+
progress,
167+
progress_keep_open,
168+
None,
157169
move |_progress, out, err| {
158-
core::repository::tree::entries(
159-
repository,
160-
treeish.as_deref(),
161-
recursive,
162-
extended,
163-
format,
164-
out,
165-
err,
166-
)
170+
core::repository::tree::info(repository, treeish.as_deref(), extended, format, out, err)
167171
},
168172
),
169173
repo::Subcommands::Verify {

Diff for: src/plumbing/options.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -351,18 +351,27 @@ pub mod repo {
351351
pub mod tree {
352352
#[derive(Debug, clap::Subcommand)]
353353
pub enum Subcommands {
354+
/// Print entries in a given tree
354355
Entries {
355356
/// Traverse the entire tree and its subtrees respectively, not only this tree.
356357
#[clap(long, short = 'r')]
357358
recursive: bool,
358359

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

363364
/// The tree to traverse, or the tree at `HEAD` if unspecified.
364365
treeish: Option<String>,
365366
},
367+
/// Provide information about a tree.
368+
Info {
369+
/// Provide files size as well. This is expensive as the object is decoded entirely.
370+
#[clap(long, short = 'e')]
371+
extended: bool,
372+
/// The tree to traverse, or the tree at `HEAD` if unspecified.
373+
treeish: Option<String>,
374+
},
366375
}
367376
}
368377
}

0 commit comments

Comments
 (0)