gix_refspec/match_group/
mod.rs

1use std::collections::BTreeSet;
2
3use crate::{parse::Operation, types::Mode, MatchGroup, RefSpecRef};
4
5pub(crate) mod types;
6pub use types::{match_lhs, match_rhs, Item, Mapping, Source, SourceRef};
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 all *fetch* specs present in this group, returning deduplicated mappings from destination to source.
118    /// `items` are expected to be tracking references in the local clone, which will be matched and reverse-mapped to obtain their remote counterparts,
119    /// i.e. *right side of refspecs is mapped to their left side*.
120    /// *Note that this method is correct only for fetch-specs*, even though it also *works for push-specs*.
121    ///
122    /// Note that negative matches are not part of the return value, so they are not observable but will be used to remove mappings.
123    // Reverse-mapping is implemented here: https://github.com/git/git/blob/76cf4f61c87855ebf0784b88aaf737d6b09f504b/branch.c#L252
124    pub fn match_rhs<'item>(
125        self,
126        mut items: impl Iterator<Item = Item<'item>> + Clone,
127    ) -> match_rhs::Outcome<'spec, 'item> {
128        let mut out = Vec::<Mapping<'spec, 'item>>::new();
129        let mut seen = BTreeSet::default();
130        let mut push_unique = |mapping| {
131            if seen.insert(calculate_hash(&mapping)) {
132                out.push(mapping);
133            }
134        };
135        let mut matchers: Vec<Matcher<'_>> = self.specs.iter().copied().map(Matcher::from).collect();
136
137        let mut has_negation = false;
138        for (spec_index, (spec, matcher)) in self.specs.iter().zip(matchers.iter_mut()).enumerate() {
139            if spec.mode == Mode::Negative {
140                has_negation = true;
141                continue;
142            }
143            for (item_index, item) in items.clone().enumerate() {
144                let (matched, lhs) = matcher.matches_rhs(item);
145                if let Some(lhs) = lhs.filter(|_| matched) {
146                    push_unique(Mapping {
147                        item_index: Some(item_index),
148                        lhs: SourceRef::FullName(lhs),
149                        rhs: Some(item.full_ref_name.into()),
150                        spec_index,
151                    });
152                }
153            }
154        }
155
156        if let Some(hash_kind) = has_negation.then(|| items.next().map(|i| i.target.kind())).flatten() {
157            let null_id = hash_kind.null();
158            for matcher in matchers
159                .into_iter()
160                .zip(self.specs.iter())
161                .filter_map(|(m, spec)| (spec.mode == Mode::Negative).then_some(m))
162            {
163                out.retain(|m| match &m.lhs {
164                    SourceRef::ObjectId(_) => true,
165                    SourceRef::FullName(name) => {
166                        !matcher
167                            .matches_rhs(Item {
168                                full_ref_name: name.as_ref(),
169                                target: &null_id,
170                                object: None,
171                            })
172                            .0
173                    }
174                });
175            }
176        }
177        match_rhs::Outcome {
178            group: self,
179            mappings: out,
180        }
181    }
182}
183
184fn calculate_hash<T: std::hash::Hash>(t: &T) -> u64 {
185    use std::hash::Hasher;
186    let mut s = std::collections::hash_map::DefaultHasher::new();
187    t.hash(&mut s);
188    s.finish()
189}
190
191mod util;
192use util::{Matcher, Needle};