uv_git_types/
lib.rs

1pub use crate::github::GitHubRepository;
2pub use crate::oid::{GitOid, OidParseError};
3pub use crate::reference::GitReference;
4
5use thiserror::Error;
6use uv_redacted::DisplaySafeUrl;
7
8mod github;
9mod oid;
10mod reference;
11
12#[derive(Debug, Error)]
13pub enum GitUrlParseError {
14    #[error(
15        "Unsupported Git URL scheme `{0}:` in `{1}` (expected one of `https:`, `ssh:`, or `file:`)"
16    )]
17    UnsupportedGitScheme(String, DisplaySafeUrl),
18}
19
20/// A URL reference to a Git repository.
21#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Hash, Ord)]
22pub struct GitUrl {
23    /// The URL of the Git repository, with any query parameters, fragments, and leading `git+`
24    /// removed.
25    repository: DisplaySafeUrl,
26    /// The reference to the commit to use, which could be a branch, tag or revision.
27    reference: GitReference,
28    /// The precise commit to use, if known.
29    precise: Option<GitOid>,
30}
31
32impl GitUrl {
33    /// Create a new [`GitUrl`] from a repository URL and a reference.
34    pub fn from_reference(
35        repository: DisplaySafeUrl,
36        reference: GitReference,
37    ) -> Result<Self, GitUrlParseError> {
38        Self::from_fields(repository, reference, None)
39    }
40
41    /// Create a new [`GitUrl`] from a repository URL and a precise commit.
42    pub fn from_commit(
43        repository: DisplaySafeUrl,
44        reference: GitReference,
45        precise: GitOid,
46    ) -> Result<Self, GitUrlParseError> {
47        Self::from_fields(repository, reference, Some(precise))
48    }
49
50    /// Create a new [`GitUrl`] from a repository URL and a precise commit, if known.
51    pub fn from_fields(
52        repository: DisplaySafeUrl,
53        reference: GitReference,
54        precise: Option<GitOid>,
55    ) -> Result<Self, GitUrlParseError> {
56        match repository.scheme() {
57            "http" | "https" | "ssh" | "file" => {}
58            unsupported => {
59                return Err(GitUrlParseError::UnsupportedGitScheme(
60                    unsupported.to_string(),
61                    repository,
62                ));
63            }
64        }
65        Ok(Self {
66            repository,
67            reference,
68            precise,
69        })
70    }
71
72    /// Set the precise [`GitOid`] to use for this Git URL.
73    #[must_use]
74    pub fn with_precise(mut self, precise: GitOid) -> Self {
75        self.precise = Some(precise);
76        self
77    }
78
79    /// Set the [`GitReference`] to use for this Git URL.
80    #[must_use]
81    pub fn with_reference(mut self, reference: GitReference) -> Self {
82        self.reference = reference;
83        self
84    }
85
86    /// Return the [`Url`] of the Git repository.
87    pub fn repository(&self) -> &DisplaySafeUrl {
88        &self.repository
89    }
90
91    /// Return the reference to the commit to use, which could be a branch, tag or revision.
92    pub fn reference(&self) -> &GitReference {
93        &self.reference
94    }
95
96    /// Return the precise commit, if known.
97    pub fn precise(&self) -> Option<GitOid> {
98        self.precise
99    }
100}
101
102impl TryFrom<DisplaySafeUrl> for GitUrl {
103    type Error = GitUrlParseError;
104
105    /// Initialize a [`GitUrl`] source from a URL.
106    fn try_from(mut url: DisplaySafeUrl) -> Result<Self, Self::Error> {
107        // Remove any query parameters and fragments.
108        url.set_fragment(None);
109        url.set_query(None);
110
111        // If the URL ends with a reference, like `https://git.example.com/MyProject.git@v1.0`,
112        // extract it.
113        let mut reference = GitReference::DefaultBranch;
114        if let Some((prefix, suffix)) = url
115            .path()
116            .rsplit_once('@')
117            .map(|(prefix, suffix)| (prefix.to_string(), suffix.to_string()))
118        {
119            reference = GitReference::from_rev(suffix);
120            url.set_path(&prefix);
121        }
122
123        Self::from_reference(url, reference)
124    }
125}
126
127impl From<GitUrl> for DisplaySafeUrl {
128    fn from(git: GitUrl) -> Self {
129        let mut url = git.repository;
130
131        // If we have a precise commit, add `@` and the commit hash to the URL.
132        if let Some(precise) = git.precise {
133            let path = format!("{}@{}", url.path(), precise);
134            url.set_path(&path);
135        } else {
136            // Otherwise, add the branch or tag name.
137            match git.reference {
138                GitReference::Branch(rev)
139                | GitReference::Tag(rev)
140                | GitReference::BranchOrTag(rev)
141                | GitReference::NamedRef(rev)
142                | GitReference::BranchOrTagOrCommit(rev) => {
143                    let path = format!("{}@{}", url.path(), rev);
144                    url.set_path(&path);
145                }
146                GitReference::DefaultBranch => {}
147            }
148        }
149
150        url
151    }
152}
153
154impl std::fmt::Display for GitUrl {
155    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156        write!(f, "{}", &self.repository)
157    }
158}