Skip to main content

gix_refspec/match_group/
mod.rs

1use std::collections::BTreeSet;
2
3use crate::{MatchGroup, RefSpecRef, parse::Operation, types::Mode};
4
5pub(crate) mod types;
6pub use types::{Item, Mapping, Source, SourceRef, match_lhs, match_rhs};
7
8///
9pub mod validate;
10
11/// Initialization
12impl<'a> MatchGroup<'a> {
13    /// Take all the fetch ref specs from `specs` get a match group ready.
14    pub fn from_fetch_specs(specs: impl IntoIterator<Item = RefSpecRef<'a>>) -> Self {
15        MatchGroup {
16            specs: specs.into_iter().filter(|s| s.op == Operation::Fetch).collect(),
17        }
18    }
19
20    /// Take all the push ref specs from `specs` get a match group ready.
21    pub fn from_push_specs(specs: impl IntoIterator<Item = RefSpecRef<'a>>) -> Self {
22        MatchGroup {
23            specs: specs.into_iter().filter(|s| s.op == Operation::Push).collect(),
24        }
25    }
26}
27
28/// Matching
29impl<'spec> MatchGroup<'spec> {
30    /// Match all `items` against all *fetch* specs present in this group, returning deduplicated mappings from source to destination.
31    /// `items` are expected to be references on the remote, which will be matched and mapped to obtain their local counterparts,
32    /// i.e. *left side of refspecs is mapped to their right side*.
33    /// *Note that this method is correct only for fetch-specs*, even though it also *works for push-specs*.
34    ///
35    /// Object names are never mapped and always returned as match.
36    ///
37    /// Note that negative matches are not part of the return value, so they are not observable but will be used to remove mappings.
38    // TODO: figure out how to deal with push-specs, probably when push is being implemented.
39    pub fn match_lhs<'item>(
40        self,
41        mut items: impl Iterator<Item = Item<'item>> + Clone,
42    ) -> match_lhs::Outcome<'spec, 'item> {
43        let mut out = Vec::new();
44        let mut seen = BTreeSet::default();
45        let mut push_unique = |mapping| {
46            if seen.insert(calculate_hash(&mapping)) {
47                out.push(mapping);
48            }
49        };
50        let mut matchers: Vec<Option<Matcher<'_>>> = self
51            .specs
52            .iter()
53            .copied()
54            .map(Matcher::from)
55            .enumerate()
56            .map(|(idx, m)| match m.lhs {
57                Some(Needle::Object(id)) => {
58                    push_unique(Mapping {
59                        item_index: None,
60                        lhs: SourceRef::ObjectId(id),
61                        rhs: m.rhs.map(Needle::to_bstr),
62                        spec_index: idx,
63                    });
64                    None
65                }
66                _ => Some(m),
67            })
68            .collect();
69
70        let mut has_negation = false;
71        for (spec_index, (spec, matcher)) in self.specs.iter().zip(matchers.iter_mut()).enumerate() {
72            if spec.mode == Mode::Negative {
73                has_negation = true;
74                continue;
75            }
76            for (item_index, item) in items.clone().enumerate() {
77                let Some(matcher) = matcher else { continue };
78                let (matched, rhs) = matcher.matches_lhs(item);
79                if matched {
80                    push_unique(Mapping {
81                        item_index: Some(item_index),
82                        lhs: SourceRef::FullName(item.full_ref_name.into()),
83                        rhs,
84                        spec_index,
85                    });
86                }
87            }
88        }
89
90        if let Some(hash_kind) = has_negation.then(|| items.next().map(|i| i.target.kind())).flatten() {
91            let null_id = hash_kind.null();
92            for matcher in matchers
93                .into_iter()
94                .zip(self.specs.iter())
95                .filter_map(|(m, spec)| m.and_then(|m| (spec.mode == Mode::Negative).then_some(m)))
96            {
97                out.retain(|m| match &m.lhs {
98                    SourceRef::ObjectId(_) => true,
99                    SourceRef::FullName(name) => {
100                        !matcher
101                            .matches_lhs(Item {
102                                full_ref_name: name.as_ref(),
103                                target: &null_id,
104                                object: None,
105                            })
106                            .0
107                    }
108                });
109            }
110        }
111        match_lhs::Outcome {
112            group: self,
113            mappings: out,
114        }
115    }
116
117    /// Match all `items` against the right-hand side of all *fetch* specs present in this group,
118    /// returning deduplicated mappings from destination to source.
119    /// `items` are expected to be tracking references in the local clone, which will be matched and reverse-mapped to obtain their remote counterparts,
120    /// i.e. *right side of refspecs is mapped to their left side*.
121    ///
122    /// Even though this method starts by matching `items` against the right-hand side of positive refspecs,
123    /// negative fetch refspecs still exclude refs by their left-hand/source side. For that reason, once a
124    /// mapping has been reverse-mapped to its source ref, negative refspecs are applied to the mapping's `lhs`.
125    /// *Note that this method is correct only for fetch-specs*, even though it also *works for push-specs*.
126    ///
127    /// Note that negative matches are not part of the return value, so they are not observable but will be used to remove mappings.
128    // Reverse-mapping is implemented here: https://github.com/git/git/blob/76cf4f61c87855ebf0784b88aaf737d6b09f504b/branch.c#L252
129    pub fn match_rhs<'item>(
130        self,
131        mut items: impl Iterator<Item = Item<'item>> + Clone,
132    ) -> match_rhs::Outcome<'spec, 'item> {
133        let mut out = Vec::<Mapping<'spec, 'item>>::new();
134        let mut seen = BTreeSet::default();
135        let mut push_unique = |mapping| {
136            if seen.insert(calculate_hash(&mapping)) {
137                out.push(mapping);
138            }
139        };
140        let mut matchers: Vec<Matcher<'_>> = self.specs.iter().copied().map(Matcher::from).collect();
141
142        let mut has_negation = false;
143        for (spec_index, (spec, matcher)) in self.specs.iter().zip(matchers.iter_mut()).enumerate() {
144            if spec.mode == Mode::Negative {
145                has_negation = true;
146                continue;
147            }
148            for (item_index, item) in items.clone().enumerate() {
149                let (matched, lhs) = matcher.matches_rhs(item);
150                if let Some(lhs) = lhs.filter(|_| matched) {
151                    push_unique(Mapping {
152                        item_index: Some(item_index),
153                        lhs: SourceRef::FullName(lhs),
154                        rhs: Some(item.full_ref_name.into()),
155                        spec_index,
156                    });
157                }
158            }
159        }
160
161        if let Some(hash_kind) = has_negation.then(|| items.next().map(|i| i.target.kind())).flatten() {
162            let null_id = hash_kind.null();
163            for matcher in matchers
164                .into_iter()
165                .zip(self.specs.iter())
166                .filter_map(|(m, spec)| (spec.mode == Mode::Negative).then_some(m))
167            {
168                out.retain(|m| match &m.lhs {
169                    SourceRef::ObjectId(_) => true,
170                    SourceRef::FullName(name) => {
171                        !matcher
172                            .matches_lhs(Item {
173                                full_ref_name: name.as_ref(),
174                                target: &null_id,
175                                object: None,
176                            })
177                            .0
178                    }
179                });
180            }
181        }
182        match_rhs::Outcome {
183            group: self,
184            mappings: out,
185        }
186    }
187}
188
189fn calculate_hash<T: std::hash::Hash>(t: &T) -> u64 {
190    use std::hash::Hasher;
191    let mut s = std::collections::hash_map::DefaultHasher::new();
192    t.hash(&mut s);
193    s.finish()
194}
195
196mod util;
197use util::{Matcher, Needle};