Skip to main content

gitoxide_core/repository/attributes/
query.rs

1use gix::worktree::IndexPersistedOrInMemory;
2
3use crate::OutputFormat;
4
5pub struct Options {
6    pub format: OutputFormat,
7    pub statistics: bool,
8}
9
10pub(crate) mod function {
11    use std::{borrow::Cow, io, path::Path};
12
13    use anyhow::bail;
14    use gix::bstr::BStr;
15
16    use crate::{
17        OutputFormat, is_dir_to_mode,
18        repository::{
19            PathsOrPatterns,
20            attributes::query::{Options, attributes_cache},
21        },
22    };
23
24    pub fn query(
25        repo: gix::Repository,
26        input: PathsOrPatterns,
27        mut out: impl io::Write,
28        mut err: impl io::Write,
29        Options { format, statistics }: Options,
30    ) -> anyhow::Result<()> {
31        if format != OutputFormat::Human {
32            bail!("JSON output isn't implemented yet");
33        }
34
35        let (mut cache, index) = attributes_cache(&repo)?;
36        let mut matches = cache.attribute_matches();
37
38        match input {
39            PathsOrPatterns::Paths(paths) => {
40                for path in paths {
41                    let mode = gix::path::from_bstr(Cow::Borrowed(path.as_ref()))
42                        .metadata()
43                        .ok()
44                        .map(|m| is_dir_to_mode(m.is_dir()));
45
46                    let entry = cache.at_entry(&path, mode)?;
47                    if !entry.matching_attributes(&mut matches) {
48                        continue;
49                    }
50                    print_match(&matches, path.as_ref(), &mut out)?;
51                }
52            }
53            PathsOrPatterns::Patterns(patterns) => {
54                let mut pathspec = repo.pathspec(
55                    true,
56                    patterns.iter(),
57                    true,
58                    &index,
59                    gix::worktree::stack::state::attributes::Source::WorktreeThenIdMapping
60                        .adjust_for_bare(repo.is_bare()),
61                )?;
62                let mut pathspec_matched_entry = false;
63                if let Some(it) = pathspec.index_entries_with_paths(&index) {
64                    for (path, entry) in it {
65                        pathspec_matched_entry = true;
66                        let entry = cache.at_entry(path, entry.mode.into())?;
67                        if !entry.matching_attributes(&mut matches) {
68                            continue;
69                        }
70                        print_match(&matches, path, &mut out)?;
71                    }
72                }
73
74                if !pathspec_matched_entry {
75                    // TODO(borrowchk): this shouldn't be necessary at all, but `pathspec` stays borrowed mutably for some reason.
76                    //                  It's probably due to the strange lifetimes of `index_entries_with_paths()`.
77                    let pathspec = repo.pathspec(
78                        true,
79                        patterns.iter(),
80                        true,
81                        &index,
82                        gix::worktree::stack::state::attributes::Source::WorktreeThenIdMapping
83                            .adjust_for_bare(repo.is_bare()),
84                    )?;
85                    let workdir = repo.workdir();
86                    for pattern in pathspec.search().patterns() {
87                        let path = pattern.path();
88                        let entry = cache.at_entry(
89                            path,
90                            Some(is_dir_to_mode(
91                                workdir.is_some_and(|wd| wd.join(gix::path::from_bstr(path)).is_dir())
92                                    || pattern.signature.contains(gix::pathspec::MagicSignature::MUST_BE_DIR),
93                            )),
94                        )?;
95                        if !entry.matching_attributes(&mut matches) {
96                            continue;
97                        }
98                        print_match(&matches, path, &mut out)?;
99                    }
100                }
101            }
102        }
103
104        if let Some(stats) = statistics.then(|| cache.take_statistics()) {
105            out.flush()?;
106            writeln!(err, "{stats:#?}").ok();
107        }
108        Ok(())
109    }
110
111    fn print_match(
112        matches: &gix::attrs::search::Outcome,
113        path: &BStr,
114        mut out: impl std::io::Write,
115    ) -> std::io::Result<()> {
116        for m in matches.iter() {
117            writeln!(
118                out,
119                "{}:{}:{}\t{}\t{}",
120                m.location.source.map(Path::to_string_lossy).unwrap_or_default(),
121                m.location.sequence_number,
122                m.pattern,
123                path,
124                m.assignment
125            )?;
126        }
127        Ok(())
128    }
129}
130
131pub(crate) fn attributes_cache(
132    repo: &gix::Repository,
133) -> anyhow::Result<(gix::AttributeStack<'_>, IndexPersistedOrInMemory)> {
134    let index = repo.index_or_load_from_head()?;
135    let cache = repo.attributes(
136        &index,
137        if repo.is_bare() {
138            gix::worktree::stack::state::attributes::Source::IdMapping
139        } else {
140            gix::worktree::stack::state::attributes::Source::WorktreeThenIdMapping
141        },
142        gix::worktree::stack::state::ignore::Source::IdMapping,
143        None,
144    )?;
145    Ok((cache, index))
146}