210 lines · 6.2 KB · Rust
RawDownload
use std::{borrow::Cow, io, io::BufWriter}; use anyhow::bail;use gix::Tree; use crate::OutputFormat; mod entries {    use std::collections::VecDeque;     use gix::{        bstr::{BStr, BString, ByteSlice, ByteVec},        objs::tree::EntryRef,        traverse::tree::visit::Action,    };     use crate::repository::tree::format_entry;     #[cfg_attr(feature = "serde", 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 = "serde", serde(skip_serializing_if = "Option::is_none"))]        #[cfg_attr(not(feature = "serde"), allow(dead_code))]        pub bytes: Option<u64>,        #[cfg_attr(feature = "serde", serde(skip))]        pub num_bytes: u64,    }     pub struct Traverse<'repo, 'a> {        pub stats: Statistics,        repo: Option<&'repo gix::Repository>,        out: Option<&'a mut dyn std::io::Write>,        path: BString,        path_deque: VecDeque<BString>,    }     impl<'repo, 'a> Traverse<'repo, 'a> {        pub fn new(repo: Option<&'repo gix::Repository>, out: Option<&'a mut dyn std::io::Write>) -> Self {            Traverse {                stats: Default::default(),                repo,                out,                path: BString::default(),                path_deque: VecDeque::new(),            }        }         fn pop_element(&mut self) {            if let Some(pos) = self.path.rfind_byte(b'/') {                self.path.resize(pos, 0);            } else {                self.path.clear();            }        }         fn push_element(&mut self, name: &BStr) {            if name.is_empty() {                return;            }            if !self.path.is_empty() {                self.path.push(b'/');            }            self.path.push_str(name);        }    }     impl gix::traverse::tree::Visit for Traverse<'_, '_> {        fn pop_back_tracked_path_and_set_current(&mut self) {            self.path = self.path_deque.pop_back().unwrap_or_default();        }         fn pop_front_tracked_path_and_set_current(&mut self) {            self.path = self.path_deque.pop_front().expect("every parent is set only once");        }         fn push_back_tracked_path_component(&mut self, component: &BStr) {            self.push_element(component);            self.path_deque.push_back(self.path.clone());        }         fn push_path_component(&mut self, component: &BStr) {            self.push_element(component);        }         fn pop_path_component(&mut self) {            self.pop_element();        }         fn visit_tree(&mut self, _entry: &EntryRef<'_>) -> Action {            self.stats.num_trees += 1;            std::ops::ControlFlow::Continue(true)        }         fn visit_nontree(&mut self, entry: &EntryRef<'_>) -> Action {            let size = self                .repo                .and_then(|repo| repo.find_header(entry.oid).map(|h| h.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.stats.num_bytes += size;            }             use gix::object::tree::EntryKind::*;            match entry.mode.kind() {                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"),            }            std::ops::ControlFlow::Continue(true)        }    }} #[cfg_attr(not(feature = "serde"), allow(unused_variables))]pub fn info(    repo: gix::Repository,    treeish: Option<&str>,    extended: bool,    format: OutputFormat,    out: impl io::Write,    mut err: impl io::Write,) -> anyhow::Result<()> {    if format == OutputFormat::Human {        writeln!(err, "Only JSON is implemented - using that instead")?;    }     let tree = treeish_to_tree(treeish, &repo)?;     let mut delegate = entries::Traverse::new(extended.then_some(&repo), None);    tree.traverse().breadthfirst(&mut delegate)?;     #[cfg(feature = "serde")]    {        delegate.stats.bytes = extended.then_some(delegate.stats.num_bytes);        serde_json::to_writer_pretty(out, &delegate.stats)?;    }     Ok(())} pub fn entries(    repo: gix::Repository,    treeish: Option<&str>,    recursive: bool,    extended: bool,    format: OutputFormat,    mut out: impl io::Write,) -> anyhow::Result<()> {    if format != OutputFormat::Human {        bail!("Only human output format is supported at the moment");    }     let tree = treeish_to_tree(treeish, &repo)?;     if recursive {        let mut write = BufWriter::new(out);        let mut delegate = entries::Traverse::new(extended.then_some(&repo), Some(&mut write));        tree.traverse().depthfirst(&mut delegate)?;    } else {        for entry in tree.iter() {            let entry = entry?;            format_entry(                &mut out,                &entry.inner,                entry.inner.filename,                extended.then(|| entry.id().header().map(|o| o.size())).transpose()?,            )?;        }    }     Ok(())} fn treeish_to_tree<'repo>(treeish: Option<&str>, repo: &'repo gix::Repository) -> anyhow::Result<Tree<'repo>> {    let spec = treeish.map_or_else(|| "@^{tree}".into(), |spec| format!("{spec}^{{tree}}"));    Ok(repo.rev_parse_single(spec.as_str())?.object()?.into_tree())} fn format_entry(    mut out: impl io::Write,    entry: &gix::objs::tree::EntryRef<'_>,    filename: &gix::bstr::BStr,    size: Option<u64>,) -> std::io::Result<()> {    use gix::objs::tree::EntryKind::*;    write!(        out,        "{} {}{} ",        match entry.mode.kind() {            Tree => "TREE",            Blob => "BLOB",            BlobExecutable => " EXE",            Link => "LINK",            Commit => "SUBM",        },        entry.oid,        size.map_or_else(|| "".into(), |s| Cow::Owned(format!(" {s}")))    )?;    out.write_all(filename)?;    out.write_all(b"\n")}