summaryrefslogtreecommitdiffstats
path: root/vendor/gix-refspec/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
commit10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch)
treebdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/gix-refspec/src
parentReleasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff)
downloadrustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.tar.xz
rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.zip
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gix-refspec/src')
-rw-r--r--vendor/gix-refspec/src/instruction.rs68
-rw-r--r--vendor/gix-refspec/src/lib.rs39
-rw-r--r--vendor/gix-refspec/src/match_group/mod.rs112
-rw-r--r--vendor/gix-refspec/src/match_group/types.rs104
-rw-r--r--vendor/gix-refspec/src/match_group/util.rs162
-rw-r--r--vendor/gix-refspec/src/match_group/validate.rs141
-rw-r--r--vendor/gix-refspec/src/parse.rs255
-rw-r--r--vendor/gix-refspec/src/spec.rs257
-rw-r--r--vendor/gix-refspec/src/types.rs21
-rw-r--r--vendor/gix-refspec/src/write.rs74
10 files changed, 1233 insertions, 0 deletions
diff --git a/vendor/gix-refspec/src/instruction.rs b/vendor/gix-refspec/src/instruction.rs
new file mode 100644
index 000000000..990d0debc
--- /dev/null
+++ b/vendor/gix-refspec/src/instruction.rs
@@ -0,0 +1,68 @@
+use bstr::BStr;
+
+use crate::{parse::Operation, Instruction};
+
+impl Instruction<'_> {
+ /// Derive the mode of operation from this instruction.
+ pub fn operation(&self) -> Operation {
+ match self {
+ Instruction::Push(_) => Operation::Push,
+ Instruction::Fetch(_) => Operation::Fetch,
+ }
+ }
+}
+
+/// Note that all sources can either be a ref-name, partial or full, or a rev-spec, unless specified otherwise, on the local side.
+/// Destinations can only be a partial or full ref names on the remote side.
+#[derive(PartialOrd, Ord, PartialEq, Eq, Copy, Clone, Hash, Debug)]
+pub enum Push<'a> {
+ /// Push all local branches to the matching destination on the remote, which has to exist to be updated.
+ AllMatchingBranches {
+ /// If true, allow non-fast-forward updates of the matched destination branch.
+ allow_non_fast_forward: bool,
+ },
+ /// Delete the destination ref or glob pattern, with only a single `*` allowed.
+ Delete {
+ /// The reference or pattern to delete on the remote.
+ ref_or_pattern: &'a BStr,
+ },
+ /// Push a single ref or refspec to a known destination ref.
+ Matching {
+ /// The source ref or refspec to push. If pattern, it contains a single `*`.
+ /// Examples are refnames like `HEAD` or `refs/heads/main`, or patterns like `refs/heads/*`.
+ src: &'a BStr,
+ /// The ref to update with the object from `src`. If `src` is a pattern, this is a pattern too.
+ /// Examples are refnames like `HEAD` or `refs/heads/main`, or patterns like `refs/heads/*`.
+ dst: &'a BStr,
+ /// If true, allow non-fast-forward updates of `dest`.
+ allow_non_fast_forward: bool,
+ },
+}
+
+/// Any source can either be a ref name (full or partial) or a fully spelled out hex-sha for an object, on the remote side.
+///
+/// Destinations can only be a partial or full ref-names on the local side.
+#[derive(PartialOrd, Ord, PartialEq, Eq, Copy, Clone, Hash, Debug)]
+pub enum Fetch<'a> {
+ /// Fetch a ref or refs, without updating local branches.
+ Only {
+ /// The partial or full ref name to fetch on the remote side or the full object hex-name, without updating the local side.
+ /// Note that this may not be a glob pattern, as those need to be matched by a destination which isn't present here.
+ src: &'a BStr,
+ },
+ /// Exclude a single ref.
+ Exclude {
+ /// A single partial or full ref name to exclude on the remote, or a pattern with a single `*`. It cannot be a spelled out object hash.
+ src: &'a BStr,
+ },
+ /// Fetch from `src` and update the corresponding destination branches in `dst` accordingly.
+ AndUpdate {
+ /// The ref name to fetch on the remote side, or a pattern with a single `*` to match against.
+ src: &'a BStr,
+ /// The local destination to update with what was fetched, or a pattern whose single `*` will be replaced with the matching portion
+ /// of the `*` from `src`.
+ dst: &'a BStr,
+ /// If true, allow non-fast-forward updates of `dest`.
+ allow_non_fast_forward: bool,
+ },
+}
diff --git a/vendor/gix-refspec/src/lib.rs b/vendor/gix-refspec/src/lib.rs
new file mode 100644
index 000000000..54d5f3057
--- /dev/null
+++ b/vendor/gix-refspec/src/lib.rs
@@ -0,0 +1,39 @@
+//! Parse git ref-specs and represent them.
+#![deny(missing_docs, rust_2018_idioms)]
+#![forbid(unsafe_code)]
+
+///
+pub mod parse;
+pub use parse::function::parse;
+
+///
+pub mod instruction;
+
+/// A refspec with references to the memory it was parsed from.
+#[derive(Eq, Copy, Clone, Debug)]
+pub struct RefSpecRef<'a> {
+ mode: types::Mode,
+ op: parse::Operation,
+ src: Option<&'a bstr::BStr>,
+ dst: Option<&'a bstr::BStr>,
+}
+
+/// An owned refspec.
+#[derive(Eq, Clone, Debug)]
+pub struct RefSpec {
+ mode: types::Mode,
+ op: parse::Operation,
+ src: Option<bstr::BString>,
+ dst: Option<bstr::BString>,
+}
+
+mod spec;
+
+mod write;
+
+///
+pub mod match_group;
+pub use match_group::types::MatchGroup;
+
+mod types;
+pub use types::Instruction;
diff --git a/vendor/gix-refspec/src/match_group/mod.rs b/vendor/gix-refspec/src/match_group/mod.rs
new file mode 100644
index 000000000..c53b5b531
--- /dev/null
+++ b/vendor/gix-refspec/src/match_group/mod.rs
@@ -0,0 +1,112 @@
+use std::collections::BTreeSet;
+
+use crate::{parse::Operation, types::Mode, MatchGroup, RefSpecRef};
+
+pub(crate) mod types;
+pub use types::{Item, Mapping, Outcome, 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(),
+ }
+ }
+}
+
+/// Matching
+impl<'a> MatchGroup<'a> {
+ /// Match all `items` against all fetch specs present in this group, returning deduplicated mappings from source to destination.
+ /// Note that this method only makes sense if the specs are indeed fetch specs and may panic otherwise.
+ ///
+ /// Note that negative matches are not part of the return value, so they are not observable but will be used to remove mappings.
+ pub fn match_remotes<'item>(self, mut items: impl Iterator<Item = Item<'item>> + Clone) -> Outcome<'a, '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(|n| n.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() {
+ for (item_index, item) in items.clone().enumerate() {
+ if spec.mode == Mode::Negative {
+ has_negation = true;
+ continue;
+ }
+ if let Some(matcher) = matcher {
+ let (matched, rhs) = matcher.matches_lhs(item);
+ if matched {
+ push_unique(Mapping {
+ item_index: Some(item_index),
+ lhs: SourceRef::FullName(item.full_ref_name),
+ rhs,
+ spec_index,
+ })
+ }
+ }
+ }
+ }
+
+ if let Some(id) = has_negation.then(|| items.next().map(|i| i.target)).flatten() {
+ let null_id = gix_hash::ObjectId::null(id.kind());
+ 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,
+ target: &null_id,
+ object: None,
+ })
+ .0
+ }
+ });
+ }
+ }
+ 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};
diff --git a/vendor/gix-refspec/src/match_group/types.rs b/vendor/gix-refspec/src/match_group/types.rs
new file mode 100644
index 000000000..6be601dd5
--- /dev/null
+++ b/vendor/gix-refspec/src/match_group/types.rs
@@ -0,0 +1,104 @@
+use std::borrow::Cow;
+
+use bstr::{BStr, BString};
+use gix_hash::oid;
+
+use crate::RefSpecRef;
+
+/// A match group is able to match a list of ref specs in order while handling negation, conflicts and one to many mappings.
+#[derive(Default, Debug, Clone)]
+pub struct MatchGroup<'a> {
+ /// The specs that take part in item matching.
+ pub specs: Vec<RefSpecRef<'a>>,
+}
+
+/// The outcome of any matching operation of a [`MatchGroup`].
+///
+/// It's used to validate and process the contained [mappings][Mapping].
+#[derive(Debug, Clone)]
+pub struct Outcome<'spec, 'item> {
+ /// The match group that produced this outcome.
+ pub group: MatchGroup<'spec>,
+ /// The mappings derived from matching [items][Item].
+ pub mappings: Vec<Mapping<'item, 'spec>>,
+}
+
+/// An item to match, input to various matching operations.
+#[derive(Debug, Copy, Clone)]
+pub struct Item<'a> {
+ /// The full name of the references, like `refs/heads/main`
+ pub full_ref_name: &'a BStr,
+ /// The id that `full_ref_name` points to, which typically is a commit, but can also be a tag object (or anything else).
+ pub target: &'a oid,
+ /// The object an annotated tag is pointing to, if `target` is an annotated tag.
+ pub object: Option<&'a oid>,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+/// The source (or left-hand) side of a mapping, which references its name.
+pub enum SourceRef<'a> {
+ /// A full reference name, which is expected to be valid.
+ ///
+ /// Validity, however, is not enforced here.
+ FullName(&'a BStr),
+ /// The name of an object that is expected to exist on the remote side.
+ /// Note that it might not be advertised by the remote but part of the object graph,
+ /// and thus gets sent in the pack. The server is expected to fail unless the desired
+ /// object is present but at some time it is merely a request by the user.
+ ObjectId(gix_hash::ObjectId),
+}
+
+impl SourceRef<'_> {
+ /// Create a fully owned instance from this one.
+ pub fn to_owned(&self) -> Source {
+ match self {
+ SourceRef::ObjectId(id) => Source::ObjectId(*id),
+ SourceRef::FullName(name) => Source::FullName((*name).to_owned()),
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+/// The source (or left-hand) side of a mapping, which owns its name.
+pub enum Source {
+ /// A full reference name, which is expected to be valid.
+ ///
+ /// Validity, however, is not enforced here.
+ FullName(BString),
+ /// The name of an object that is expected to exist on the remote side.
+ /// Note that it might not be advertised by the remote but part of the object graph,
+ /// and thus gets sent in the pack. The server is expected to fail unless the desired
+ /// object is present but at some time it is merely a request by the user.
+ ObjectId(gix_hash::ObjectId),
+}
+
+impl std::fmt::Display for Source {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Source::FullName(name) => name.fmt(f),
+ Source::ObjectId(id) => id.fmt(f),
+ }
+ }
+}
+
+/// A mapping from a remote to a local refs for fetches or local to remote refs for pushes.
+///
+/// Mappings are like edges in a graph, initially without any constraints.
+#[derive(Debug, Clone)]
+pub struct Mapping<'a, 'b> {
+ /// The index into the initial `items` list that matched against a spec.
+ pub item_index: Option<usize>,
+ /// The name of the remote side for fetches or the local one for pushes that matched.
+ pub lhs: SourceRef<'a>,
+ /// The name of the local side for fetches or the remote one for pushes that corresponds to `lhs`, if available.
+ pub rhs: Option<Cow<'b, BStr>>,
+ /// The index of the matched ref-spec as seen from the match group.
+ pub spec_index: usize,
+}
+
+impl std::hash::Hash for Mapping<'_, '_> {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ self.lhs.hash(state);
+ self.rhs.hash(state);
+ }
+}
diff --git a/vendor/gix-refspec/src/match_group/util.rs b/vendor/gix-refspec/src/match_group/util.rs
new file mode 100644
index 000000000..5339aef32
--- /dev/null
+++ b/vendor/gix-refspec/src/match_group/util.rs
@@ -0,0 +1,162 @@
+use std::{borrow::Cow, ops::Range};
+
+use bstr::{BStr, BString, ByteSlice, ByteVec};
+use gix_hash::ObjectId;
+
+use crate::{match_group::Item, RefSpecRef};
+
+/// A type keeping enough information about a ref-spec to be able to efficiently match it against multiple matcher items.
+pub struct Matcher<'a> {
+ pub(crate) lhs: Option<Needle<'a>>,
+ pub(crate) rhs: Option<Needle<'a>>,
+}
+
+impl<'a> Matcher<'a> {
+ /// Match `item` against this spec and return `(true, Some<rhs>)` to gain the other side of the match as configured, or `(true, None)`
+ /// if there was no `rhs`.
+ ///
+ /// This may involve resolving a glob with an allocation, as the destination is built using the matching portion of a glob.
+ pub fn matches_lhs(&self, item: Item<'_>) -> (bool, Option<Cow<'a, BStr>>) {
+ match (self.lhs, self.rhs) {
+ (Some(lhs), None) => (lhs.matches(item).is_match(), None),
+ (Some(lhs), Some(rhs)) => lhs.matches(item).into_match_outcome(rhs, item),
+ (None, None) | (None, Some(_)) => {
+ unreachable!("For all we know, the lefthand side is never empty. Push specs might change that.")
+ }
+ }
+ }
+}
+
+#[derive(Debug, Copy, Clone)]
+pub(crate) enum Needle<'a> {
+ FullName(&'a BStr),
+ PartialName(&'a BStr),
+ Glob { name: &'a BStr, asterisk_pos: usize },
+ Object(ObjectId),
+}
+
+enum Match {
+ /// There was no match.
+ None,
+ /// No additional data is provided as part of the match.
+ Normal,
+ /// The range of text to copy from the originating item name
+ GlobRange(Range<usize>),
+}
+
+impl Match {
+ fn is_match(&self) -> bool {
+ !matches!(self, Match::None)
+ }
+ fn into_match_outcome<'a>(self, destination: Needle<'a>, item: Item<'_>) -> (bool, Option<Cow<'a, BStr>>) {
+ let arg = match self {
+ Match::None => return (false, None),
+ Match::Normal => None,
+ Match::GlobRange(range) => Some((range, item)),
+ };
+ (true, destination.to_bstr_replace(arg).into())
+ }
+}
+
+impl<'a> Needle<'a> {
+ #[inline]
+ fn matches(&self, item: Item<'_>) -> Match {
+ match self {
+ Needle::FullName(name) => {
+ if *name == item.full_ref_name {
+ Match::Normal
+ } else {
+ Match::None
+ }
+ }
+ Needle::PartialName(name) => crate::spec::expand_partial_name(name, |expanded| {
+ (expanded == item.full_ref_name).then_some(Match::Normal)
+ })
+ .unwrap_or(Match::None),
+ Needle::Glob { name, asterisk_pos } => {
+ match item.full_ref_name.get(..*asterisk_pos) {
+ Some(full_name_portion) if full_name_portion != name[..*asterisk_pos] => {
+ return Match::None;
+ }
+ None => return Match::None,
+ _ => {}
+ };
+ let tail = &name[*asterisk_pos + 1..];
+ if !item.full_ref_name.ends_with(tail) {
+ return Match::None;
+ }
+ let end = item.full_ref_name.len() - tail.len();
+ Match::GlobRange(*asterisk_pos..end)
+ }
+ Needle::Object(id) => {
+ if *id == item.target {
+ return Match::Normal;
+ }
+ match item.object {
+ Some(object) if object == *id => Match::Normal,
+ _ => Match::None,
+ }
+ }
+ }
+ }
+
+ fn to_bstr_replace(self, range: Option<(Range<usize>, Item<'_>)>) -> Cow<'a, BStr> {
+ match (self, range) {
+ (Needle::FullName(name), None) => Cow::Borrowed(name),
+ (Needle::PartialName(name), None) => Cow::Owned({
+ let mut base: BString = "refs/".into();
+ if !(name.starts_with(b"tags/") || name.starts_with(b"remotes/")) {
+ base.push_str("heads/");
+ }
+ base.push_str(name);
+ base
+ }),
+ (Needle::Glob { name, asterisk_pos }, Some((range, item))) => {
+ let mut buf = Vec::with_capacity(name.len() + range.len() - 1);
+ buf.push_str(&name[..asterisk_pos]);
+ buf.push_str(&item.full_ref_name[range]);
+ buf.push_str(&name[asterisk_pos + 1..]);
+ Cow::Owned(buf.into())
+ }
+ (Needle::Object(id), None) => {
+ let mut name = id.to_string();
+ name.insert_str(0, "refs/heads/");
+ Cow::Owned(name.into())
+ }
+ (Needle::Glob { .. }, None) => unreachable!("BUG: no range provided for glob pattern"),
+ (_, Some(_)) => {
+ unreachable!("BUG: range provided even though needle wasn't a glob. Globs are symmetric.")
+ }
+ }
+ }
+
+ pub fn to_bstr(self) -> Cow<'a, BStr> {
+ self.to_bstr_replace(None)
+ }
+}
+
+impl<'a> From<&'a BStr> for Needle<'a> {
+ fn from(v: &'a BStr) -> Self {
+ if let Some(pos) = v.find_byte(b'*') {
+ Needle::Glob {
+ name: v,
+ asterisk_pos: pos,
+ }
+ } else if v.starts_with(b"refs/") {
+ Needle::FullName(v)
+ } else if let Ok(id) = gix_hash::ObjectId::from_hex(v) {
+ Needle::Object(id)
+ } else {
+ Needle::PartialName(v)
+ }
+ }
+}
+
+impl<'a> From<RefSpecRef<'a>> for Matcher<'a> {
+ fn from(v: RefSpecRef<'a>) -> Self {
+ Matcher {
+ lhs: v.src.map(Into::into),
+ rhs: v.dst.map(Into::into),
+ }
+ }
+}
diff --git a/vendor/gix-refspec/src/match_group/validate.rs b/vendor/gix-refspec/src/match_group/validate.rs
new file mode 100644
index 000000000..097a64587
--- /dev/null
+++ b/vendor/gix-refspec/src/match_group/validate.rs
@@ -0,0 +1,141 @@
+use std::collections::BTreeMap;
+
+use bstr::BString;
+
+use crate::{
+ match_group::{Outcome, Source},
+ RefSpec,
+};
+
+/// All possible issues found while validating matched mappings.
+#[derive(Debug, PartialEq, Eq)]
+pub enum Issue {
+ /// Multiple sources try to write the same destination.
+ ///
+ /// Note that this issue doesn't take into consideration that these sources might contain the same object behind a reference.
+ Conflict {
+ /// The unenforced full name of the reference to be written.
+ destination_full_ref_name: BString,
+ /// The list of sources that map to this destination.
+ sources: Vec<Source>,
+ /// The list of specs that caused the mapping conflict, each matching the respective one in `sources` to allow both
+ /// `sources` and `specs` to be zipped together.
+ specs: Vec<BString>,
+ },
+}
+
+impl std::fmt::Display for Issue {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Issue::Conflict {
+ destination_full_ref_name,
+ sources,
+ specs,
+ } => {
+ write!(
+ f,
+ "Conflicting destination {destination_full_ref_name:?} would be written by {}",
+ sources
+ .iter()
+ .zip(specs.iter())
+ .map(|(src, spec)| format!("{src} ({spec:?})"))
+ .collect::<Vec<_>>()
+ .join(", ")
+ )
+ }
+ }
+ }
+}
+
+/// All possible fixes corrected while validating matched mappings.
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum Fix {
+ /// Removed a mapping that contained a partial destination entirely.
+ MappingWithPartialDestinationRemoved {
+ /// The destination ref name that was ignored.
+ name: BString,
+ /// The spec that defined the mapping
+ spec: RefSpec,
+ },
+}
+
+/// The error returned [outcome validation][Outcome::validated()].
+#[derive(Debug)]
+pub struct Error {
+ /// All issues discovered during validation.
+ pub issues: Vec<Issue>,
+}
+
+impl std::fmt::Display for Error {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "Found {} {} the refspec mapping to be used: \n\t{}",
+ self.issues.len(),
+ if self.issues.len() == 1 {
+ "issue that prevents"
+ } else {
+ "issues that prevent"
+ },
+ self.issues
+ .iter()
+ .map(|issue| issue.to_string())
+ .collect::<Vec<_>>()
+ .join("\n\t")
+ )
+ }
+}
+
+impl std::error::Error for Error {}
+
+impl<'spec, 'item> Outcome<'spec, 'item> {
+ /// Validate all mappings or dissolve them into an error stating the discovered issues.
+ /// Return `(modified self, issues)` providing a fixed-up set of mappings in `self` with the fixed `issues`
+ /// provided as part of it.
+ /// Terminal issues are communicated using the [`Error`] type accordingly.
+ pub fn validated(mut self) -> Result<(Self, Vec<Fix>), Error> {
+ let mut sources_by_destinations = BTreeMap::new();
+ for (dst, (spec_index, src)) in self
+ .mappings
+ .iter()
+ .filter_map(|m| m.rhs.as_ref().map(|dst| (dst.as_ref(), (m.spec_index, &m.lhs))))
+ {
+ let sources = sources_by_destinations.entry(dst).or_insert_with(Vec::new);
+ if !sources.iter().any(|(_, lhs)| lhs == &src) {
+ sources.push((spec_index, src))
+ }
+ }
+ let mut issues = Vec::new();
+ for (dst, conflicting_sources) in sources_by_destinations.into_iter().filter(|(_, v)| v.len() > 1) {
+ issues.push(Issue::Conflict {
+ destination_full_ref_name: dst.to_owned(),
+ specs: conflicting_sources
+ .iter()
+ .map(|(spec_idx, _)| self.group.specs[*spec_idx].to_bstring())
+ .collect(),
+ sources: conflicting_sources.into_iter().map(|(_, src)| src.to_owned()).collect(),
+ })
+ }
+ if !issues.is_empty() {
+ Err(Error { issues })
+ } else {
+ let mut fixed = Vec::new();
+ let group = &self.group;
+ self.mappings.retain(|m| match m.rhs.as_ref() {
+ Some(dst) => {
+ if dst.starts_with(b"refs/") || dst.as_ref() == "HEAD" {
+ true
+ } else {
+ fixed.push(Fix::MappingWithPartialDestinationRemoved {
+ name: dst.as_ref().to_owned(),
+ spec: group.specs[m.spec_index].to_owned(),
+ });
+ false
+ }
+ }
+ None => true,
+ });
+ Ok((self, fixed))
+ }
+ }
+}
diff --git a/vendor/gix-refspec/src/parse.rs b/vendor/gix-refspec/src/parse.rs
new file mode 100644
index 000000000..390db13fa
--- /dev/null
+++ b/vendor/gix-refspec/src/parse.rs
@@ -0,0 +1,255 @@
+/// The error returned by the [`parse()`][crate::parse()] function.
+#[derive(Debug, thiserror::Error)]
+#[allow(missing_docs)]
+pub enum Error {
+ #[error("Empty refspecs are invalid")]
+ Empty,
+ #[error("Negative refspecs cannot have destinations as they exclude sources")]
+ NegativeWithDestination,
+ #[error("Negative specs must not be empty")]
+ NegativeEmpty,
+ #[error("Negative specs are only supported when fetching")]
+ NegativeUnsupported,
+ #[error("Negative specs must be object hashes")]
+ NegativeObjectHash,
+ #[error("Negative specs must be full ref names, starting with \"refs/\"")]
+ NegativePartialName,
+ #[error("Negative glob patterns are not allowed")]
+ NegativeGlobPattern,
+ #[error("Fetch destinations must be ref-names, like 'HEAD:refs/heads/branch'")]
+ InvalidFetchDestination,
+ #[error("Cannot push into an empty destination")]
+ PushToEmpty,
+ #[error("glob patterns may only involved a single '*' character, found {pattern:?}")]
+ PatternUnsupported { pattern: bstr::BString },
+ #[error("Both sides of the specification need a pattern, like 'a/*:b/*'")]
+ PatternUnbalanced,
+ #[error(transparent)]
+ ReferenceName(#[from] gix_validate::refname::Error),
+ #[error(transparent)]
+ RevSpec(#[from] gix_revision::spec::parse::Error),
+}
+
+/// Define how the parsed refspec should be used.
+#[derive(PartialOrd, Ord, PartialEq, Eq, Copy, Clone, Hash, Debug)]
+pub enum Operation {
+ /// The `src` side is local and the `dst` side is remote.
+ Push,
+ /// The `src` side is remote and the `dst` side is local.
+ Fetch,
+}
+
+pub(crate) mod function {
+ use bstr::{BStr, ByteSlice};
+
+ use crate::{
+ parse::{Error, Operation},
+ types::Mode,
+ RefSpecRef,
+ };
+
+ /// Parse `spec` for use in `operation` and return it if it is valid.
+ pub fn parse(mut spec: &BStr, operation: Operation) -> Result<RefSpecRef<'_>, Error> {
+ fn fetch_head_only(mode: Mode) -> RefSpecRef<'static> {
+ RefSpecRef {
+ mode,
+ op: Operation::Fetch,
+ src: Some("HEAD".into()),
+ dst: None,
+ }
+ }
+
+ let mode = match spec.first() {
+ Some(&b'^') => {
+ spec = &spec[1..];
+ if operation == Operation::Push {
+ return Err(Error::NegativeUnsupported);
+ }
+ Mode::Negative
+ }
+ Some(&b'+') => {
+ spec = &spec[1..];
+ Mode::Force
+ }
+ Some(_) => Mode::Normal,
+ None => {
+ return match operation {
+ Operation::Push => Err(Error::Empty),
+ Operation::Fetch => Ok(fetch_head_only(Mode::Normal)),
+ }
+ }
+ };
+
+ let (mut src, dst) = match spec.find_byte(b':') {
+ Some(pos) => {
+ if mode == Mode::Negative {
+ return Err(Error::NegativeWithDestination);
+ }
+
+ let (src, dst) = spec.split_at(pos);
+ let dst = &dst[1..];
+ let src = (!src.is_empty()).then(|| src.as_bstr());
+ let dst = (!dst.is_empty()).then(|| dst.as_bstr());
+ match (src, dst) {
+ (None, None) => match operation {
+ Operation::Push => (None, None),
+ Operation::Fetch => (Some("HEAD".into()), None),
+ },
+ (None, Some(dst)) => match operation {
+ Operation::Push => (None, Some(dst)),
+ Operation::Fetch => (Some("HEAD".into()), Some(dst)),
+ },
+ (Some(src), None) => match operation {
+ Operation::Push => return Err(Error::PushToEmpty),
+ Operation::Fetch => (Some(src), None),
+ },
+ (Some(src), Some(dst)) => (Some(src), Some(dst)),
+ }
+ }
+ None => {
+ let src = (!spec.is_empty()).then_some(spec);
+ if Operation::Fetch == operation && mode != Mode::Negative && src.is_none() {
+ return Ok(fetch_head_only(mode));
+ } else {
+ (src, None)
+ }
+ }
+ };
+
+ if let Some(spec) = src.as_mut() {
+ if *spec == "@" {
+ *spec = "HEAD".into();
+ }
+ }
+ let (src, src_had_pattern) = validated(src, operation == Operation::Push && dst.is_some())?;
+ let (dst, dst_had_pattern) = validated(dst, false)?;
+ if mode != Mode::Negative && src_had_pattern != dst_had_pattern {
+ return Err(Error::PatternUnbalanced);
+ }
+
+ if mode == Mode::Negative {
+ match src {
+ Some(spec) => {
+ if src_had_pattern {
+ return Err(Error::NegativeGlobPattern);
+ } else if looks_like_object_hash(spec) {
+ return Err(Error::NegativeObjectHash);
+ } else if !spec.starts_with(b"refs/") && spec != "HEAD" {
+ return Err(Error::NegativePartialName);
+ }
+ }
+ None => return Err(Error::NegativeEmpty),
+ }
+ }
+
+ Ok(RefSpecRef {
+ op: operation,
+ mode,
+ src,
+ dst,
+ })
+ }
+
+ fn looks_like_object_hash(spec: &BStr) -> bool {
+ spec.len() >= gix_hash::Kind::shortest().len_in_hex() && spec.iter().all(|b| b.is_ascii_hexdigit())
+ }
+
+ fn validated(spec: Option<&BStr>, allow_revspecs: bool) -> Result<(Option<&BStr>, bool), Error> {
+ match spec {
+ Some(spec) => {
+ let glob_count = spec.iter().filter(|b| **b == b'*').take(2).count();
+ if glob_count > 1 {
+ return Err(Error::PatternUnsupported { pattern: spec.into() });
+ }
+ let has_globs = glob_count == 1;
+ if has_globs {
+ let mut buf = smallvec::SmallVec::<[u8; 256]>::with_capacity(spec.len());
+ buf.extend_from_slice(spec);
+ let glob_pos = buf.find_byte(b'*').expect("glob present");
+ buf[glob_pos] = b'a';
+ gix_validate::reference::name_partial(buf.as_bstr())?;
+ } else {
+ gix_validate::reference::name_partial(spec)
+ .map_err(Error::from)
+ .or_else(|err| {
+ if allow_revspecs {
+ match gix_revision::spec::parse(spec, &mut super::revparse::Noop) {
+ Ok(_) => {
+ if spec.iter().any(|b| b.is_ascii_whitespace()) {
+ Err(err)
+ } else {
+ Ok(spec)
+ }
+ }
+ Err(err) => Err(err.into()),
+ }
+ } else {
+ Err(err)
+ }
+ })?;
+ }
+ Ok((Some(spec), has_globs))
+ }
+ None => Ok((None, false)),
+ }
+ }
+}
+
+mod revparse {
+ use bstr::BStr;
+ use gix_revision::spec::parse::delegate::{
+ Kind, Navigate, PeelTo, PrefixHint, ReflogLookup, Revision, SiblingBranch, Traversal,
+ };
+
+ pub(crate) struct Noop;
+
+ impl Revision for Noop {
+ fn find_ref(&mut self, _name: &BStr) -> Option<()> {
+ Some(())
+ }
+
+ fn disambiguate_prefix(&mut self, _prefix: gix_hash::Prefix, _hint: Option<PrefixHint<'_>>) -> Option<()> {
+ Some(())
+ }
+
+ fn reflog(&mut self, _query: ReflogLookup) -> Option<()> {
+ Some(())
+ }
+
+ fn nth_checked_out_branch(&mut self, _branch_no: usize) -> Option<()> {
+ Some(())
+ }
+
+ fn sibling_branch(&mut self, _kind: SiblingBranch) -> Option<()> {
+ Some(())
+ }
+ }
+
+ impl Navigate for Noop {
+ fn traverse(&mut self, _kind: Traversal) -> Option<()> {
+ Some(())
+ }
+
+ fn peel_until(&mut self, _kind: PeelTo<'_>) -> Option<()> {
+ Some(())
+ }
+
+ fn find(&mut self, _regex: &BStr, _negated: bool) -> Option<()> {
+ Some(())
+ }
+
+ fn index_lookup(&mut self, _path: &BStr, _stage: u8) -> Option<()> {
+ Some(())
+ }
+ }
+
+ impl Kind for Noop {
+ fn kind(&mut self, _kind: gix_revision::spec::Kind) -> Option<()> {
+ Some(())
+ }
+ }
+
+ impl gix_revision::spec::parse::Delegate for Noop {
+ fn done(&mut self) {}
+ }
+}
diff --git a/vendor/gix-refspec/src/spec.rs b/vendor/gix-refspec/src/spec.rs
new file mode 100644
index 000000000..9361ae682
--- /dev/null
+++ b/vendor/gix-refspec/src/spec.rs
@@ -0,0 +1,257 @@
+use bstr::{BStr, BString, ByteSlice};
+
+use crate::{
+ instruction::{Fetch, Push},
+ parse::Operation,
+ types::Mode,
+ Instruction, RefSpec, RefSpecRef,
+};
+
+/// Conversion. Use the [RefSpecRef][RefSpec::to_ref()] type for more usage options.
+impl RefSpec {
+ /// Return ourselves as reference type.
+ pub fn to_ref(&self) -> RefSpecRef<'_> {
+ RefSpecRef {
+ mode: self.mode,
+ op: self.op,
+ src: self.src.as_ref().map(|b| b.as_ref()),
+ dst: self.dst.as_ref().map(|b| b.as_ref()),
+ }
+ }
+
+ /// Return true if the spec stats with a `+` and thus forces setting the reference.
+ pub fn allow_non_fast_forward(&self) -> bool {
+ matches!(self.mode, Mode::Force)
+ }
+}
+
+mod impls {
+ use std::{
+ cmp::Ordering,
+ hash::{Hash, Hasher},
+ };
+
+ use crate::{RefSpec, RefSpecRef};
+
+ impl From<RefSpecRef<'_>> for RefSpec {
+ fn from(v: RefSpecRef<'_>) -> Self {
+ v.to_owned()
+ }
+ }
+
+ impl Hash for RefSpec {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.to_ref().hash(state)
+ }
+ }
+
+ impl Hash for RefSpecRef<'_> {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.instruction().hash(state)
+ }
+ }
+
+ impl PartialEq for RefSpec {
+ fn eq(&self, other: &Self) -> bool {
+ self.to_ref().eq(&other.to_ref())
+ }
+ }
+
+ impl PartialEq for RefSpecRef<'_> {
+ fn eq(&self, other: &Self) -> bool {
+ self.instruction().eq(&other.instruction())
+ }
+ }
+
+ impl PartialOrd for RefSpecRef<'_> {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ self.instruction().partial_cmp(&other.instruction())
+ }
+ }
+
+ impl PartialOrd for RefSpec {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ self.to_ref().partial_cmp(&other.to_ref())
+ }
+ }
+
+ impl Ord for RefSpecRef<'_> {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.instruction().cmp(&other.instruction())
+ }
+ }
+
+ impl Ord for RefSpec {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.to_ref().cmp(&other.to_ref())
+ }
+ }
+}
+
+/// Access
+impl<'a> RefSpecRef<'a> {
+ /// Return the left-hand side of the spec, typically the source.
+ /// It takes many different forms so don't rely on this being a ref name.
+ ///
+ /// It's not present in case of deletions.
+ pub fn source(&self) -> Option<&BStr> {
+ self.src
+ }
+
+ /// Return the right-hand side of the spec, typically the destination.
+ /// It takes many different forms so don't rely on this being a ref name.
+ ///
+ /// It's not present in case of source-only specs.
+ pub fn destination(&self) -> Option<&BStr> {
+ self.dst
+ }
+
+ /// Always returns the remote side, whose actual side in the refspec depends on how it was parsed.
+ pub fn remote(&self) -> Option<&BStr> {
+ match self.op {
+ Operation::Push => self.dst,
+ Operation::Fetch => self.src,
+ }
+ }
+
+ /// Always returns the local side, whose actual side in the refspec depends on how it was parsed.
+ pub fn local(&self) -> Option<&BStr> {
+ match self.op {
+ Operation::Push => self.src,
+ Operation::Fetch => self.dst,
+ }
+ }
+
+ /// Derive the prefix from the [`source`][Self::source()] side of this spec if this is a fetch spec,
+ /// or the [`destination`][Self::destination()] side if it is a push spec, if it is possible to do so without ambiguity.
+ ///
+ /// This means it starts with `refs/`. Note that it won't contain more than two components, like `refs/heads/`
+ pub fn prefix(&self) -> Option<&BStr> {
+ if self.mode == Mode::Negative {
+ return None;
+ }
+ let source = match self.op {
+ Operation::Fetch => self.source(),
+ Operation::Push => self.destination(),
+ }?;
+ if source == "HEAD" {
+ return source.into();
+ }
+ let suffix = source.strip_prefix(b"refs/")?;
+ let slash_pos = suffix.find_byte(b'/')?;
+ let prefix = source[..="refs/".len() + slash_pos].as_bstr();
+ (!prefix.contains(&b'*')).then_some(prefix)
+ }
+
+ /// As opposed to [`prefix()`][Self::prefix], if the latter is `None` it will expand to all possible prefixes and place them in `out`.
+ ///
+ /// Note that only the `source` side is considered.
+ pub fn expand_prefixes(&self, out: &mut Vec<BString>) {
+ match self.prefix() {
+ Some(prefix) => out.push(prefix.into()),
+ None => {
+ let source = match match self.op {
+ Operation::Fetch => self.source(),
+ Operation::Push => self.destination(),
+ } {
+ Some(source) => source,
+ None => return,
+ };
+ if let Some(rest) = source.strip_prefix(b"refs/") {
+ if !rest.contains(&b'/') {
+ out.push(source.into());
+ }
+ return;
+ } else if gix_hash::ObjectId::from_hex(source).is_ok() {
+ return;
+ }
+ expand_partial_name(source, |expanded| {
+ out.push(expanded.into());
+ None::<()>
+ });
+ }
+ }
+ }
+
+ /// Transform the state of the refspec into an instruction making clear what to do with it.
+ pub fn instruction(&self) -> Instruction<'a> {
+ match self.op {
+ Operation::Fetch => match (self.mode, self.src, self.dst) {
+ (Mode::Normal | Mode::Force, Some(src), None) => Instruction::Fetch(Fetch::Only { src }),
+ (Mode::Normal | Mode::Force, Some(src), Some(dst)) => Instruction::Fetch(Fetch::AndUpdate {
+ src,
+ dst,
+ allow_non_fast_forward: matches!(self.mode, Mode::Force),
+ }),
+ (Mode::Negative, Some(src), None) => Instruction::Fetch(Fetch::Exclude { src }),
+ (mode, src, dest) => {
+ unreachable!(
+ "BUG: fetch instructions with {:?} {:?} {:?} are not possible",
+ mode, src, dest
+ )
+ }
+ },
+ Operation::Push => match (self.mode, self.src, self.dst) {
+ (Mode::Normal | Mode::Force, Some(src), None) => Instruction::Push(Push::Matching {
+ src,
+ dst: src,
+ allow_non_fast_forward: matches!(self.mode, Mode::Force),
+ }),
+ (Mode::Normal | Mode::Force, None, Some(dst)) => {
+ Instruction::Push(Push::Delete { ref_or_pattern: dst })
+ }
+ (Mode::Normal | Mode::Force, None, None) => Instruction::Push(Push::AllMatchingBranches {
+ allow_non_fast_forward: matches!(self.mode, Mode::Force),
+ }),
+ (Mode::Normal | Mode::Force, Some(src), Some(dst)) => Instruction::Push(Push::Matching {
+ src,
+ dst,
+ allow_non_fast_forward: matches!(self.mode, Mode::Force),
+ }),
+ (mode, src, dest) => {
+ unreachable!(
+ "BUG: push instructions with {:?} {:?} {:?} are not possible",
+ mode, src, dest
+ )
+ }
+ },
+ }
+ }
+}
+
+/// Conversion
+impl RefSpecRef<'_> {
+ /// Convert this ref into a standalone, owned copy.
+ pub fn to_owned(&self) -> RefSpec {
+ RefSpec {
+ mode: self.mode,
+ op: self.op,
+ src: self.src.map(ToOwned::to_owned),
+ dst: self.dst.map(ToOwned::to_owned),
+ }
+ }
+}
+
+pub(crate) fn expand_partial_name<T>(name: &BStr, mut cb: impl FnMut(&BStr) -> Option<T>) -> Option<T> {
+ use bstr::ByteVec;
+ let mut buf = BString::from(Vec::with_capacity(128));
+ for (base, append_head) in [
+ ("", false),
+ ("refs/", false),
+ ("refs/tags/", false),
+ ("refs/heads/", false),
+ ("refs/remotes/", false),
+ ("refs/remotes/", true),
+ ] {
+ buf.clear();
+ buf.push_str(base);
+ buf.push_str(name);
+ if append_head {
+ buf.push_str("/HEAD");
+ }
+ if let Some(res) = cb(buf.as_ref()) {
+ return Some(res);
+ }
+ }
+ None
+}
diff --git a/vendor/gix-refspec/src/types.rs b/vendor/gix-refspec/src/types.rs
new file mode 100644
index 000000000..0a0e24e36
--- /dev/null
+++ b/vendor/gix-refspec/src/types.rs
@@ -0,0 +1,21 @@
+use crate::instruction;
+
+/// The way to interpret a refspec.
+#[derive(PartialOrd, Ord, PartialEq, Eq, Copy, Clone, Hash, Debug)]
+pub(crate) enum Mode {
+ /// Apply standard rules for refspecs which are including refs with specific rules related to allowing fast forwards of destinations.
+ Normal,
+ /// Even though according to normal rules a non-fastforward would be denied, override this and reset a ref forcefully in the destination.
+ Force,
+ /// Instead of considering matching refs included, we consider them excluded. This applies only to the source side of a refspec.
+ Negative,
+}
+
+/// Tells what to do and is derived from a [`RefSpec`][crate::RefSpecRef].
+#[derive(PartialOrd, Ord, PartialEq, Eq, Copy, Clone, Hash, Debug)]
+pub enum Instruction<'a> {
+ /// An instruction for pushing.
+ Push(instruction::Push<'a>),
+ /// An instruction for fetching.
+ Fetch(instruction::Fetch<'a>),
+}
diff --git a/vendor/gix-refspec/src/write.rs b/vendor/gix-refspec/src/write.rs
new file mode 100644
index 000000000..74c71c6e1
--- /dev/null
+++ b/vendor/gix-refspec/src/write.rs
@@ -0,0 +1,74 @@
+use bstr::BString;
+
+use crate::{
+ instruction::{Fetch, Push},
+ Instruction, RefSpecRef,
+};
+
+impl RefSpecRef<'_> {
+ /// Reproduce ourselves in parseable form.
+ pub fn to_bstring(&self) -> BString {
+ let mut buf = Vec::with_capacity(128);
+ self.write_to(&mut buf).expect("no io error");
+ buf.into()
+ }
+
+ /// Serialize ourselves in a parseable format to `out`.
+ pub fn write_to(&self, out: impl std::io::Write) -> std::io::Result<()> {
+ self.instruction().write_to(out)
+ }
+}
+
+impl Instruction<'_> {
+ /// Reproduce ourselves in parseable form.
+ pub fn to_bstring(&self) -> BString {
+ let mut buf = Vec::with_capacity(128);
+ self.write_to(&mut buf).expect("no io error");
+ buf.into()
+ }
+
+ /// Serialize ourselves in a parseable format to `out`.
+ pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> {
+ match self {
+ Instruction::Push(Push::Matching {
+ src,
+ dst,
+ allow_non_fast_forward,
+ }) => {
+ if *allow_non_fast_forward {
+ out.write_all(&[b'+'])?;
+ }
+ out.write_all(src)?;
+ out.write_all(&[b':'])?;
+ out.write_all(dst)
+ }
+ Instruction::Push(Push::AllMatchingBranches { allow_non_fast_forward }) => {
+ if *allow_non_fast_forward {
+ out.write_all(&[b'+'])?;
+ }
+ out.write_all(&[b':'])
+ }
+ Instruction::Push(Push::Delete { ref_or_pattern }) => {
+ out.write_all(&[b':'])?;
+ out.write_all(ref_or_pattern)
+ }
+ Instruction::Fetch(Fetch::Only { src }) => out.write_all(src),
+ Instruction::Fetch(Fetch::Exclude { src }) => {
+ out.write_all(&[b'^'])?;
+ out.write_all(src)
+ }
+ Instruction::Fetch(Fetch::AndUpdate {
+ src,
+ dst,
+ allow_non_fast_forward,
+ }) => {
+ if *allow_non_fast_forward {
+ out.write_all(&[b'+'])?;
+ }
+ out.write_all(src)?;
+ out.write_all(&[b':'])?;
+ out.write_all(dst)
+ }
+ }
+ }
+}