Skip to main content

cargo_util_schemas/core/
source_kind.rs

1use std::cmp::Ordering;
2
3/// The possible kinds of code source.
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum SourceKind {
6    /// A git repository.
7    Git(GitReference),
8    /// A local path.
9    Path,
10    /// A remote registry.
11    Registry,
12    /// A sparse registry.
13    SparseRegistry,
14    /// A local filesystem-based registry.
15    LocalRegistry,
16    /// A directory-based registry.
17    Directory,
18    /// Package sources distributed with the rust toolchain
19    Builtin,
20}
21
22// The hash here is important for what folder packages get downloaded into.
23// Changes trigger all users to download another copy of their crates.
24// So the `stable_hash` test checks that we only change it intentionally.
25// We implement hash manually to callout the stability impact.
26impl std::hash::Hash for SourceKind {
27    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
28        core::mem::discriminant(self).hash(state);
29        if let SourceKind::Git(git) = self {
30            git.hash(state);
31        }
32    }
33}
34
35impl SourceKind {
36    pub fn protocol(&self) -> Option<&str> {
37        match self {
38            SourceKind::Path => Some("path"),
39            SourceKind::Git(_) => Some("git"),
40            SourceKind::Registry => Some("registry"),
41            // Sparse registry URL already includes the `sparse+` prefix, see `SourceId::new`
42            SourceKind::SparseRegistry => None,
43            SourceKind::LocalRegistry => Some("local-registry"),
44            SourceKind::Directory => Some("directory"),
45            // The protocol is not required for builtins so we don't emit it
46            SourceKind::Builtin => None,
47        }
48    }
49}
50
51// The ordering here is important for how packages are serialized into lock files.
52// We implement it manually to callout the stability guarantee.
53// See https://github.com/rust-lang/cargo/pull/9397 for the history.
54impl Ord for SourceKind {
55    fn cmp(&self, other: &SourceKind) -> Ordering {
56        match (self, other) {
57            (SourceKind::Path, SourceKind::Path) => Ordering::Equal,
58            (SourceKind::Path, _) => Ordering::Less,
59            (_, SourceKind::Path) => Ordering::Greater,
60
61            (SourceKind::Registry, SourceKind::Registry) => Ordering::Equal,
62            (SourceKind::Registry, _) => Ordering::Less,
63            (_, SourceKind::Registry) => Ordering::Greater,
64
65            (SourceKind::SparseRegistry, SourceKind::SparseRegistry) => Ordering::Equal,
66            (SourceKind::SparseRegistry, _) => Ordering::Less,
67            (_, SourceKind::SparseRegistry) => Ordering::Greater,
68
69            (SourceKind::LocalRegistry, SourceKind::LocalRegistry) => Ordering::Equal,
70            (SourceKind::LocalRegistry, _) => Ordering::Less,
71            (_, SourceKind::LocalRegistry) => Ordering::Greater,
72
73            (SourceKind::Directory, SourceKind::Directory) => Ordering::Equal,
74            (SourceKind::Directory, _) => Ordering::Less,
75            (_, SourceKind::Directory) => Ordering::Greater,
76
77            (SourceKind::Git(a), SourceKind::Git(b)) => a.cmp(b),
78            (SourceKind::Git(_), _) => Ordering::Less,
79            (_, SourceKind::Git(_)) => Ordering::Greater,
80
81            (SourceKind::Builtin, SourceKind::Builtin) => Ordering::Equal,
82        }
83    }
84}
85
86/// Forwards to `Ord`
87impl PartialOrd for SourceKind {
88    fn partial_cmp(&self, other: &SourceKind) -> Option<Ordering> {
89        Some(self.cmp(other))
90    }
91}
92
93/// Information to find a specific commit in a Git repository.
94#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
95pub enum GitReference {
96    /// From a tag.
97    Tag(String),
98    /// From a branch.
99    Branch(String),
100    /// From a specific revision. Can be a commit hash (either short or full),
101    /// or a named reference like `refs/pull/493/head`.
102    Rev(String),
103    /// The default branch of the repository, the reference named `HEAD`.
104    DefaultBranch,
105}
106
107impl GitReference {
108    pub fn from_query(
109        query_pairs: impl Iterator<Item = (impl AsRef<str>, impl AsRef<str>)>,
110    ) -> Self {
111        let mut reference = GitReference::DefaultBranch;
112        for (k, v) in query_pairs {
113            let v = v.as_ref();
114            match k.as_ref() {
115                // Map older 'ref' to branch.
116                "branch" | "ref" => reference = GitReference::Branch(v.to_owned()),
117
118                "rev" => reference = GitReference::Rev(v.to_owned()),
119                "tag" => reference = GitReference::Tag(v.to_owned()),
120                _ => {}
121            }
122        }
123        reference
124    }
125
126    /// Returns a `Display`able view of this git reference, or None if using
127    /// the head of the default branch
128    pub fn pretty_ref(&self, url_encoded: bool) -> Option<PrettyRef<'_>> {
129        match self {
130            GitReference::DefaultBranch => None,
131            _ => Some(PrettyRef {
132                inner: self,
133                url_encoded,
134            }),
135        }
136    }
137}
138
139/// A git reference that can be `Display`ed
140pub struct PrettyRef<'a> {
141    inner: &'a GitReference,
142    url_encoded: bool,
143}
144
145impl<'a> std::fmt::Display for PrettyRef<'a> {
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        let value: &str;
148        match self.inner {
149            GitReference::Branch(s) => {
150                write!(f, "branch=")?;
151                value = s;
152            }
153            GitReference::Tag(s) => {
154                write!(f, "tag=")?;
155                value = s;
156            }
157            GitReference::Rev(s) => {
158                write!(f, "rev=")?;
159                value = s;
160            }
161            GitReference::DefaultBranch => unreachable!(),
162        }
163        if self.url_encoded {
164            for value in url::form_urlencoded::byte_serialize(value.as_bytes()) {
165                write!(f, "{value}")?;
166            }
167        } else {
168            write!(f, "{value}")?;
169        }
170        Ok(())
171    }
172}