Skip to main content

git_filter_tree/
exe.rs

1use crate::FilterTree;
2use git2 as git;
3
4/// Arguments for filtering a Git tree by glob patterns.
5///
6/// Defined here so that the library exposes a self-contained type that
7/// downstream code can construct directly without going through the CLI.
8#[derive(clap::Args, Clone)]
9pub struct FilterTreeArgs {
10    /// Tree-ish reference (commit, branch, tag, or tree SHA).
11    pub treeish: String,
12
13    /// Glob patterns for entries to keep in the tree.
14    #[arg(required = true)]
15    pub patterns: Vec<String>,
16
17    /// Output format.
18    #[arg(short, long, value_enum, default_value = "tree-sha")]
19    pub format: OutputFormat,
20}
21
22/// Output format for [`print_tree`].
23#[derive(Clone, Copy, Default, clap::ValueEnum)]
24pub enum OutputFormat {
25    /// Print only the tree SHA (default).
26    #[default]
27    TreeSha,
28    /// Print each entry's type and name.
29    Entries,
30    /// Print mode, type, OID, and name for every entry.
31    Detailed,
32}
33
34/// Core filter-tree operation: resolve the treeish, filter by patterns, and
35/// return the OID of the resulting tree.  This is the reusable building-block
36/// that both the plumbing binary and the porcelain CLI can call.
37pub fn filter_tree(
38    repo: &git::Repository,
39    treeish: &str,
40    patterns: &[String],
41) -> Result<git::Oid, Box<dyn std::error::Error>> {
42    let obj = repo.revparse_single(treeish)?;
43    let tree = obj.peel_to_tree()?;
44
45    let pattern_refs: Vec<&str> = patterns.iter().map(|s| s.as_str()).collect();
46    let filtered = repo.filter_by_patterns(&tree, &pattern_refs)?;
47    Ok(filtered.id())
48}
49
50/// Print a tree according to the requested output format.
51pub fn print_tree(
52    repo: &git::Repository,
53    tree_oid: git::Oid,
54    format: OutputFormat,
55) -> Result<(), Box<dyn std::error::Error>> {
56    let tree = repo.find_tree(tree_oid)?;
57
58    match format {
59        OutputFormat::TreeSha => {
60            println!("{}", tree.id());
61        }
62        OutputFormat::Entries => {
63            for entry in tree.iter() {
64                let name = entry.name().unwrap_or("<invalid-utf8>");
65                let kind = kind_label(entry.kind());
66                println!("{}\t{}", kind, name);
67            }
68        }
69        OutputFormat::Detailed => {
70            println!("Tree: {}", tree.id());
71            println!("Entries: {}", tree.len());
72            println!();
73            for entry in tree.iter() {
74                let name = entry.name().unwrap_or("<invalid-utf8>");
75                let kind = kind_label(entry.kind());
76                let mode = entry.filemode();
77                let id = entry.id();
78                println!("{:06o} {} {}\t{}", mode, kind, id, name);
79            }
80        }
81    }
82
83    Ok(())
84}
85
86/// Run the full plumbing command: filter and print.
87pub fn run(args: &FilterTreeArgs) -> Result<(), Box<dyn std::error::Error>> {
88    let repo = git::Repository::open_from_env()?;
89    let tree_oid = filter_tree(&repo, &args.treeish, &args.patterns)?;
90    print_tree(&repo, tree_oid, args.format)?;
91    Ok(())
92}
93
94fn kind_label(kind: Option<git::ObjectType>) -> &'static str {
95    match kind {
96        Some(git::ObjectType::Blob) => "blob",
97        Some(git::ObjectType::Tree) => "tree",
98        Some(git::ObjectType::Commit) => "commit",
99        _ => "unknown",
100    }
101}