remote.rs

 1use derive_more::Deref;
 2use url::Url;
 3
 4/// The URL to a Git remote.
 5#[derive(Debug, PartialEq, Eq, Clone, Deref)]
 6pub struct RemoteUrl(Url);
 7
 8impl std::str::FromStr for RemoteUrl {
 9    type Err = url::ParseError;
10
11    fn from_str(input: &str) -> Result<Self, Self::Err> {
12        if input.starts_with("git@") {
13            // Rewrite remote URLs like `git@github.com:user/repo.git` to `ssh://git@github.com/user/repo.git`
14            let ssh_url = input.replacen(':', "/", 1).replace("git@", "ssh://git@");
15            Ok(RemoteUrl(Url::parse(&ssh_url)?))
16        } else {
17            Ok(RemoteUrl(Url::parse(input)?))
18        }
19    }
20}
21
22#[cfg(test)]
23mod tests {
24    use pretty_assertions::assert_eq;
25
26    use super::*;
27
28    #[test]
29    fn test_parsing_valid_remote_urls() {
30        let valid_urls = vec![
31            (
32                "https://github.com/octocat/zed.git",
33                "https",
34                "github.com",
35                "/octocat/zed.git",
36            ),
37            (
38                "git@github.com:octocat/zed.git",
39                "ssh",
40                "github.com",
41                "/octocat/zed.git",
42            ),
43            (
44                "ssh://git@github.com/octocat/zed.git",
45                "ssh",
46                "github.com",
47                "/octocat/zed.git",
48            ),
49            (
50                "file:///path/to/local/zed",
51                "file",
52                "",
53                "/path/to/local/zed",
54            ),
55        ];
56
57        for (input, expected_scheme, expected_host, expected_path) in valid_urls {
58            let parsed = input.parse::<RemoteUrl>().expect("failed to parse URL");
59            let url = parsed.0;
60            assert_eq!(
61                url.scheme(),
62                expected_scheme,
63                "unexpected scheme for {input:?}",
64            );
65            assert_eq!(
66                url.host_str().unwrap_or(""),
67                expected_host,
68                "unexpected host for {input:?}",
69            );
70            assert_eq!(url.path(), expected_path, "unexpected path for {input:?}");
71        }
72    }
73
74    #[test]
75    fn test_parsing_invalid_remote_urls() {
76        let invalid_urls = vec!["not_a_url", "http://"];
77
78        for url in invalid_urls {
79            assert!(
80                url.parse::<RemoteUrl>().is_err(),
81                "expected \"{url}\" to not parse as a Git remote URL",
82            );
83        }
84    }
85}