gix_refspec/match_group/mod.rs
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 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
use std::collections::BTreeSet;
use crate::{parse::Operation, types::Mode, MatchGroup, RefSpecRef};
pub(crate) mod types;
pub use types::{match_lhs, match_rhs, Item, Mapping, Source, SourceRef};
///
pub mod validate;
/// Initialization
impl<'a> MatchGroup<'a> {
/// Take all the fetch ref specs from `specs` get a match group ready.
pub fn from_fetch_specs(specs: impl IntoIterator<Item = RefSpecRef<'a>>) -> Self {
MatchGroup {
specs: specs.into_iter().filter(|s| s.op == Operation::Fetch).collect(),
}
}
/// Take all the push ref specs from `specs` get a match group ready.
pub fn from_push_specs(specs: impl IntoIterator<Item = RefSpecRef<'a>>) -> Self {
MatchGroup {
specs: specs.into_iter().filter(|s| s.op == Operation::Push).collect(),
}
}
}
/// Matching
impl<'spec> MatchGroup<'spec> {
/// Match all `items` against all *fetch* specs present in this group, returning deduplicated mappings from source to destination.
/// `items` are expected to be references on the remote, which will be matched and mapped to obtain their local counterparts,
/// i.e. *left side of refspecs is mapped to their right side*.
/// *Note that this method is correct only for fetch-specs*, even though it also *works for push-specs*.
///
/// Object names are never mapped and always returned as match.
///
/// Note that negative matches are not part of the return value, so they are not observable but will be used to remove mappings.
// TODO: figure out how to deal with push-specs, probably when push is being implemented.
pub fn match_lhs<'item>(
self,
mut items: impl Iterator<Item = Item<'item>> + Clone,
) -> match_lhs::Outcome<'spec, 'item> {
let mut out = Vec::new();
let mut seen = BTreeSet::default();
let mut push_unique = |mapping| {
if seen.insert(calculate_hash(&mapping)) {
out.push(mapping);
}
};
let mut matchers: Vec<Option<Matcher<'_>>> = self
.specs
.iter()
.copied()
.map(Matcher::from)
.enumerate()
.map(|(idx, m)| match m.lhs {
Some(Needle::Object(id)) => {
push_unique(Mapping {
item_index: None,
lhs: SourceRef::ObjectId(id),
rhs: m.rhs.map(Needle::to_bstr),
spec_index: idx,
});
None
}
_ => Some(m),
})
.collect();
let mut has_negation = false;
for (spec_index, (spec, matcher)) in self.specs.iter().zip(matchers.iter_mut()).enumerate() {
if spec.mode == Mode::Negative {
has_negation = true;
continue;
}
for (item_index, item) in items.clone().enumerate() {
let Some(matcher) = matcher else { continue };
let (matched, rhs) = matcher.matches_lhs(item);
if matched {
push_unique(Mapping {
item_index: Some(item_index),
lhs: SourceRef::FullName(item.full_ref_name.into()),
rhs,
spec_index,
});
}
}
}
if let Some(hash_kind) = has_negation.then(|| items.next().map(|i| i.target.kind())).flatten() {
let null_id = hash_kind.null();
for matcher in matchers
.into_iter()
.zip(self.specs.iter())
.filter_map(|(m, spec)| m.and_then(|m| (spec.mode == Mode::Negative).then_some(m)))
{
out.retain(|m| match &m.lhs {
SourceRef::ObjectId(_) => true,
SourceRef::FullName(name) => {
!matcher
.matches_lhs(Item {
full_ref_name: name.as_ref(),
target: &null_id,
object: None,
})
.0
}
});
}
}
match_lhs::Outcome {
group: self,
mappings: out,
}
}
/// Match all `items` against all *fetch* specs present in this group, returning deduplicated mappings from destination to source.
/// `items` are expected to be tracking references in the local clone, which will be matched and reverse-mapped to obtain their remote counterparts,
/// i.e. *right side of refspecs is mapped to their left side*.
/// *Note that this method is correct only for fetch-specs*, even though it also *works for push-specs*.
///
/// Note that negative matches are not part of the return value, so they are not observable but will be used to remove mappings.
// Reverse-mapping is implemented here: https://github.com/git/git/blob/76cf4f61c87855ebf0784b88aaf737d6b09f504b/branch.c#L252
pub fn match_rhs<'item>(
self,
mut items: impl Iterator<Item = Item<'item>> + Clone,
) -> match_rhs::Outcome<'spec, 'item> {
let mut out = Vec::<Mapping<'spec, 'item>>::new();
let mut seen = BTreeSet::default();
let mut push_unique = |mapping| {
if seen.insert(calculate_hash(&mapping)) {
out.push(mapping);
}
};
let mut matchers: Vec<Matcher<'_>> = self.specs.iter().copied().map(Matcher::from).collect();
let mut has_negation = false;
for (spec_index, (spec, matcher)) in self.specs.iter().zip(matchers.iter_mut()).enumerate() {
if spec.mode == Mode::Negative {
has_negation = true;
continue;
}
for (item_index, item) in items.clone().enumerate() {
let (matched, lhs) = matcher.matches_rhs(item);
if let Some(lhs) = lhs.filter(|_| matched) {
push_unique(Mapping {
item_index: Some(item_index),
lhs: SourceRef::FullName(lhs),
rhs: Some(item.full_ref_name.into()),
spec_index,
});
}
}
}
if let Some(hash_kind) = has_negation.then(|| items.next().map(|i| i.target.kind())).flatten() {
let null_id = hash_kind.null();
for matcher in matchers
.into_iter()
.zip(self.specs.iter())
.filter_map(|(m, spec)| (spec.mode == Mode::Negative).then_some(m))
{
out.retain(|m| match &m.lhs {
SourceRef::ObjectId(_) => true,
SourceRef::FullName(name) => {
!matcher
.matches_rhs(Item {
full_ref_name: name.as_ref(),
target: &null_id,
object: None,
})
.0
}
});
}
}
match_rhs::Outcome {
group: self,
mappings: out,
}
}
}
fn calculate_hash<T: std::hash::Hash>(t: &T) -> u64 {
use std::hash::Hasher;
let mut s = std::collections::hash_map::DefaultHasher::new();
t.hash(&mut s);
s.finish()
}
mod util;
use util::{Matcher, Needle};