Add ls-tree

While we’re at it, let’s add the ls-tree command. It’s so easy there’s no reason not to. git ls-tree [-r] TREE simply prints the contents of a tree, recursively with the -r flag. In recursive:

Update cli

    LsTree {
        recursive: bool,
        tree: Sha1,
    },
        .subcommand(
            ClapCommand::new("ls-tree")
                .about("Pretty-print a tree object.")
                .arg(
                    Arg::new("recursive")
                        .short('r')
                        .long("recursive")
                        .help("Recurse into sub-trees")
                        .action(ArgAction::SetTrue),
                )
                .arg(
                    Arg::new("tree")
                        .value_name("TREE")
                        .help("A tree-ish object"),
                ),
        )
    } else if let Some(subcommand) = matches.subcommand_matches("ls-tree") {
        let tree: String = subcommand.get_one::<String>("tree").unwrap().clone();
        let recursive = subcommand.get_flag("recursive");
        Ok(Command::LsTree { tree, recursive })

Implement ls-tree

src/main.rs

fn cmd_ls_tree(tree: &str, recursive: bool, base_url: PathBuf) -> Result<()> {
    let current_directory = std::env::current_dir()?;
    let repo = GitRepository::find(&current_directory)?;
    if let git_object::GitObject::Tree(tree) = repo.read_object(tree)? {
        for leaf in tree.iter() {
            if recursive {
                match leaf.get_type() {
                    git_object::mode::Type::Tree => {
                        cmd_ls_tree(&leaf.hash, true, base_url.join(&leaf.path))?;
                    }
                    _ => {
                        println!(
                            "{} {}\t{}",
                            leaf.mode,
                            leaf.hash,
                            base_url.join(&leaf.path).display()
                        );
                    }
                };
            } else {
                println!("{}", leaf);
            }
        }
    } else {
        return Err(anyhow::anyhow!("Provided object is not a tree"));
    };

    Ok(())
}

Add get_type and Display to Leaf

src/git_object/tree/leaf.rs
impl Leaf {
    pub fn new(mode: &[u8], path: &[u8], hash: String) -> Result<Self, TreeLeafParseError> {
        let mode = Mode::new(String::from_utf8(mode.to_vec())?)?;
        let path = String::from_utf8(path.to_vec())?;

        Ok(Self { mode, path, hash })
    }

    pub fn parse(buf_reader: &mut impl std::io::BufRead) -> Result<Self, TreeLeafParseError> {
        let mut mode = vec![];
        let mode_size = buf_reader
            .read_until(b' ', &mut mode)
            .context("Failed to read mode")?;

        let mut path = vec![];
        let path_size = buf_reader
            .read_until(b'\x00', &mut path)
            .context("Failed to read path")?;

        let mut hash = [0_u8; 20];
        buf_reader
            .read_exact(&mut hash)
            .context("Failed to read sha1 hash")?;

        Self::new(
            &mode[..mode_size - 1],
            &path[..path_size - 1],
            hex::encode(hash),
        )
    }

    pub fn get_type(&self) -> Type {
        self.mode.type_
    }
}

impl Display for Leaf {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{} {}\t{}", self.mode, self.hash, self.path)
    }
}

Implement Display for Mode and Type

src/git_object/tree/mode.rs

impl Display for Mode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{} {}", self.type_, self.file_permissions)
    }
}
src/git_object/tree/mode.rs
impl Display for Type {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let str = match self {
            Type::Tree => "tree",
            Type::RegularFile => "blob",
            Type::SymbolicLink => "blob",
            Type::Submodule => "commit",
        };

        write!(f, "{}", str)
    }
}

Implement Deref for Tree

To be able to call .iter() on a Tree


impl Deref for Tree {
    type Target = Vec<Leaf>;

    fn deref(&self) -> &Self::Target {
        &self.leaves
    }
}

Last updated