use std::io::{self, Write};
use std::process::ExitCode;
use clap_complete::ArgValueCompleter;
use color_eyre::eyre::{Result, WrapErr as _};
use girt::{EntryMode, ObjectId, Objects, ReadLimits, Tree, TreeEntry};
use crate::common::{resolve_tree, stdout, write_path};
use crate::{Context, complete};
#[derive(Debug, clap::Subcommand)]
pub enum Command {
List(ListArgs),
}
#[derive(Debug, clap::Args)]
pub struct ListArgs {
#[arg(add = ArgValueCompleter::new(complete::revisions))]
rev: String,
#[arg(long, short)]
recursive: bool,
#[arg(long)]
name_only: bool,
}
pub fn run(command: Command, context: &Context) -> Result<ExitCode> {
match command {
Command::List(args) => list(args, context),
}
}
fn list(args: ListArgs, context: &Context) -> Result<ExitCode> {
let repository = context.repository()?;
let objects = repository.objects()?;
let id = resolve_tree(&repository, &objects, &args.rev)?;
let mut out = stdout();
walk(&objects, id, &mut Vec::new(), &args, context.git, &mut out)?;
out.flush()?;
Ok(ExitCode::SUCCESS)
}
fn walk(
objects: &Objects,
id: ObjectId,
prefix: &mut Vec<u8>,
args: &ListArgs,
git: bool,
out: &mut impl Write,
) -> Result<()> {
let tree = read_tree(objects, id)?;
for entry in tree.entries() {
let length = prefix.len();
if length > 0 {
prefix.push(b'/');
}
prefix.extend_from_slice(&entry.name);
if args.recursive && entry.mode == EntryMode::Tree {
walk(objects, entry.id, prefix, args, git, out)?;
} else if args.name_only {
write_path(out, prefix, git)?;
} else {
write_entry(out, entry, prefix, git)?;
}
prefix.truncate(length);
}
Ok(())
}
fn read_tree(objects: &Objects, id: ObjectId) -> Result<Tree> {
objects
.read_tree_with(id, ReadLimits::trusted())
.wrap_err_with(|| format!("reading tree {id}"))
}
pub fn write_entry(
out: &mut impl Write,
entry: &TreeEntry,
path: &[u8],
git: bool,
) -> io::Result<()> {
let (mode, kind) = (entry.mode.octal(), entry.mode.object_kind().as_str());
write!(out, "{mode} {kind} {}\t", entry.id)?;
write_path(out, path, git)
}