1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
use std::path::Path;

use crate::{search::Spec, MagicSignature, Pattern, Search};

/// Create a new specification to support matches from `pathspec`, [normalizing](Pattern::normalize()) it with `prefix` and `root`.
fn mapping_from_pattern(
    mut pathspec: Pattern,
    prefix: &Path,
    root: &Path,
    sequence_number: usize,
) -> Result<gix_glob::search::pattern::Mapping<Spec>, crate::normalize::Error> {
    pathspec.normalize(prefix, root)?;
    let mut match_all = pathspec.is_nil();
    let glob = {
        let mut g = gix_glob::Pattern::from_bytes_without_negation(&pathspec.path).unwrap_or_else(|| {
            match_all = true;
            // This pattern is setup to match literally as all-whitespace.
            gix_glob::Pattern {
                text: pathspec.path.clone(),
                mode: gix_glob::pattern::Mode::empty(),
                first_wildcard_pos: None,
            }
        });
        g.mode |= gix_glob::pattern::Mode::ABSOLUTE;
        if pathspec.signature.contains(MagicSignature::MUST_BE_DIR) {
            g.mode |= gix_glob::pattern::Mode::MUST_BE_DIR;
        }
        g
    };

    Ok(gix_glob::search::pattern::Mapping {
        pattern: glob,
        value: Spec {
            attrs_match: {
                (!pathspec.attributes.is_empty()).then(|| {
                    let mut out = gix_attributes::search::Outcome::default();
                    out.initialize_with_selection(
                        &Default::default(),
                        pathspec.attributes.iter().map(|a| a.name.as_str()),
                    );
                    out
                })
            },
            pattern: pathspec,
        },
        sequence_number,
    })
}

fn common_prefix_len(patterns: &[gix_glob::search::pattern::Mapping<Spec>]) -> usize {
    let mut count = 0;
    let len = patterns
        .iter()
        .filter(|p| !p.value.pattern.is_excluded())
        .map(|p| {
            count += 1;
            if p.value.pattern.signature.contains(MagicSignature::ICASE) {
                p.value.pattern.prefix_len
            } else {
                p.pattern.first_wildcard_pos.unwrap_or(p.pattern.text.len())
            }
        })
        .min()
        .unwrap_or_default();

    if len == 0 {
        return 0;
    }

    let mut max_len = len;
    if count < 2 {
        return max_len;
    }

    let mut patterns = patterns
        .iter()
        .filter(|p| !p.value.pattern.is_excluded())
        .map(|p| &p.value.pattern.path);
    let base = &patterns.next().expect("at least two patterns");
    for path in patterns {
        for (idx, (a, b)) in base[..max_len].iter().zip(path[..max_len].iter()).enumerate() {
            if *a != *b {
                max_len = idx;
                break;
            }
        }
    }
    max_len
}

/// Lifecycle
impl Search {
    /// Create a search from ready-made `pathspecs`, and [normalize](Pattern::normalize()) them with `prefix` and `root`.
    /// `root` is the absolute path to the worktree root, if available, or the `git_dir` in case of bare repositories.
    /// If `pathspecs` doesn't yield any pattern, we will match everything automatically. If `prefix` is also provided and not empty,
    /// an artificial pattern will be added to yield all.
    pub fn from_specs(
        pathspecs: impl IntoIterator<Item = Pattern>,
        prefix: Option<&std::path::Path>,
        root: &std::path::Path,
    ) -> Result<Self, crate::normalize::Error> {
        fn inner(
            pathspecs: &mut dyn Iterator<Item = Pattern>,
            prefix: Option<&std::path::Path>,
            root: &std::path::Path,
        ) -> Result<Search, crate::normalize::Error> {
            let prefix = prefix.unwrap_or(std::path::Path::new(""));
            let mut patterns = pathspecs
                .enumerate()
                .map(|(idx, pattern)| mapping_from_pattern(pattern, prefix, root, idx))
                .collect::<Result<Vec<_>, _>>()?;

            if patterns.is_empty() && !prefix.as_os_str().is_empty() {
                patterns.push(mapping_from_pattern(
                    Pattern::from_literal(&[], MagicSignature::MUST_BE_DIR),
                    prefix,
                    root,
                    0,
                )?);
            }

            // Excludes should always happen first so we know a match is authoritative (otherwise we could find a non-excluding match first).
            patterns.sort_by(|a, b| {
                a.value
                    .pattern
                    .is_excluded()
                    .cmp(&b.value.pattern.is_excluded())
                    .reverse()
            });

            let common_prefix_len = common_prefix_len(&patterns);
            Ok(Search {
                all_patterns_are_excluded: patterns.iter().all(|s| s.value.pattern.is_excluded()),
                patterns,
                source: None,
                common_prefix_len,
            })
        }
        inner(&mut pathspecs.into_iter(), prefix, root)
    }

    /// Obtain ownership of the normalized pathspec patterns that were used for the search.
    pub fn into_patterns(self) -> impl Iterator<Item = Pattern> {
        self.patterns.into_iter().map(|p| p.value.pattern)
    }
}