use async_trait::async_trait;
use clap::{Arg, Command};
use liboxen::core::v_latest::index::CommitMerkleTree;
use liboxen::error::OxenError;
use liboxen::model::{Commit, LocalRepository};
use liboxen::repositories;
use std::time::Instant;
use crate::cmd::RunCmd;
pub const NAME: &str = "tree";
pub struct TreeCmd;
#[async_trait]
impl RunCmd for TreeCmd {
fn name(&self) -> &str {
NAME
}
fn args(&self) -> Command {
Command::new(NAME)
.about("Print the merkle tree 🌲 of a commit.")
.arg(
Arg::new("commit")
.long("commit")
.short('c')
.help("The commit to print the tree of.")
.default_value("HEAD")
.action(clap::ArgAction::Set),
)
.arg(
Arg::new("node")
.long("node")
.short('n')
.help("The node to print the tree of.")
.action(clap::ArgAction::Set),
)
.arg(
Arg::new("path")
.long("path")
.short('p')
.help("The path to print the tree of.")
.action(clap::ArgAction::Set),
)
.arg(
Arg::new("depth")
.long("depth")
.short('d')
.help("How many levels deep to traverse the tree. -1 for all.")
.default_value("-1")
.action(clap::ArgAction::Set),
)
.arg(
Arg::new("legacy")
.long("legacy")
.help("To use the legacy lookup method")
.action(clap::ArgAction::SetTrue),
)
}
async fn run(&self, args: &clap::ArgMatches) -> Result<(), OxenError> {
let depth = args
.get_one::<String>("depth")
.expect("depth has a default value")
.parse::<i32>()
.map_err(OxenError::ParseIntError)?;
let commit_id = args
.get_one::<String>("commit")
.expect("commit has a default value");
let repo = LocalRepository::from_current_dir()?;
let commit = if commit_id == "HEAD" {
repositories::commits::head_commit(&repo)?
} else {
let Some(commit) = repositories::commits::get_by_id(&repo, commit_id)? else {
return Err(OxenError::commit_id_does_not_exist(commit_id));
};
commit
};
let path = args.get_one::<String>("path");
if let Some(node) = args.get_one::<String>("node") {
self.print_node(&repo, node, depth)?;
} else {
self.print_tree(&repo, &commit, path, depth)?;
}
Ok(())
}
}
impl TreeCmd {
fn print_node(&self, repo: &LocalRepository, node: &str, depth: i32) -> Result<(), OxenError> {
let node_hash = node.parse()?;
let tree = CommitMerkleTree::read_node(repo, &node_hash, true)?
.ok_or_else(|| OxenError::resource_not_found(format!("Node {node_hash} not found")))?;
CommitMerkleTree::print_node_depth(&tree, depth);
Ok(())
}
fn print_tree(
&self,
repo: &LocalRepository,
commit: &Commit,
path: Option<&String>,
depth: i32,
) -> Result<(), OxenError> {
let load_start = Instant::now(); match (repo.subtree_paths(), repo.depth()) {
(Some(subtrees), Some(depth)) => {
println!("Working with subtrees: {subtrees:?}");
println!("Depth: {depth}");
println!("Loading first tree...");
let first = subtrees
.first()
.ok_or_else(|| OxenError::basic_str("No subtree paths configured"))?;
repositories::tree::print_tree_depth_subtree(repo, commit, depth, first)?;
}
(_, _) => {
if let Some(path) = path {
repositories::tree::print_tree_path(repo, commit, path)?;
} else {
repositories::tree::print_tree_depth(repo, commit, depth)?;
}
}
};
let load_duration = load_start.elapsed(); println!("Time to load tree: {load_duration:?}");
Ok(())
}
}