sourcehut.rs

  1use std::str::FromStr;
  2
  3use url::Url;
  4
  5use git::{
  6    BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote,
  7    RemoteUrl,
  8};
  9
 10pub struct Sourcehut;
 11
 12impl GitHostingProvider for Sourcehut {
 13    fn name(&self) -> String {
 14        "SourceHut".to_string()
 15    }
 16
 17    fn base_url(&self) -> Url {
 18        Url::parse("https://git.sr.ht").unwrap()
 19    }
 20
 21    fn supports_avatars(&self) -> bool {
 22        false
 23    }
 24
 25    fn format_line_number(&self, line: u32) -> String {
 26        format!("L{line}")
 27    }
 28
 29    fn format_line_numbers(&self, start_line: u32, end_line: u32) -> String {
 30        format!("L{start_line}-{end_line}")
 31    }
 32
 33    fn parse_remote_url(&self, url: &str) -> Option<ParsedGitRemote> {
 34        let url = RemoteUrl::from_str(url).ok()?;
 35
 36        let host = url.host_str()?;
 37        if host != "git.sr.ht" {
 38            return None;
 39        }
 40
 41        let mut path_segments = url.path_segments()?;
 42        let owner = path_segments.next()?.trim_start_matches('~');
 43        // We don't trim the `.git` suffix here like we do elsewhere, as
 44        // sourcehut treats a repo with `.git` suffix as a separate repo.
 45        //
 46        // For example, `git@git.sr.ht:~username/repo` and `git@git.sr.ht:~username/repo.git`
 47        // are two distinct repositories.
 48        let repo = path_segments.next()?;
 49
 50        Some(ParsedGitRemote {
 51            owner: owner.into(),
 52            repo: repo.into(),
 53        })
 54    }
 55
 56    fn build_commit_permalink(
 57        &self,
 58        remote: &ParsedGitRemote,
 59        params: BuildCommitPermalinkParams,
 60    ) -> Url {
 61        let BuildCommitPermalinkParams { sha } = params;
 62        let ParsedGitRemote { owner, repo } = remote;
 63
 64        self.base_url()
 65            .join(&format!("~{owner}/{repo}/commit/{sha}"))
 66            .unwrap()
 67    }
 68
 69    fn build_permalink(&self, remote: ParsedGitRemote, params: BuildPermalinkParams) -> Url {
 70        let ParsedGitRemote { owner, repo } = remote;
 71        let BuildPermalinkParams {
 72            sha,
 73            path,
 74            selection,
 75        } = params;
 76
 77        let mut permalink = self
 78            .base_url()
 79            .join(&format!("~{owner}/{repo}/tree/{sha}/item/{path}"))
 80            .unwrap();
 81        permalink.set_fragment(
 82            selection
 83                .map(|selection| self.line_fragment(&selection))
 84                .as_deref(),
 85        );
 86        permalink
 87    }
 88}
 89
 90#[cfg(test)]
 91mod tests {
 92    use git::repository::repo_path;
 93    use pretty_assertions::assert_eq;
 94
 95    use super::*;
 96
 97    #[test]
 98    fn test_parse_remote_url_given_ssh_url() {
 99        let parsed_remote = Sourcehut
100            .parse_remote_url("git@git.sr.ht:~zed-industries/zed")
101            .unwrap();
102
103        assert_eq!(
104            parsed_remote,
105            ParsedGitRemote {
106                owner: "zed-industries".into(),
107                repo: "zed".into(),
108            }
109        );
110    }
111
112    #[test]
113    fn test_parse_remote_url_given_ssh_url_with_git_suffix() {
114        let parsed_remote = Sourcehut
115            .parse_remote_url("git@git.sr.ht:~zed-industries/zed.git")
116            .unwrap();
117
118        assert_eq!(
119            parsed_remote,
120            ParsedGitRemote {
121                owner: "zed-industries".into(),
122                repo: "zed.git".into(),
123            }
124        );
125    }
126
127    #[test]
128    fn test_parse_remote_url_given_https_url() {
129        let parsed_remote = Sourcehut
130            .parse_remote_url("https://git.sr.ht/~zed-industries/zed")
131            .unwrap();
132
133        assert_eq!(
134            parsed_remote,
135            ParsedGitRemote {
136                owner: "zed-industries".into(),
137                repo: "zed".into(),
138            }
139        );
140    }
141
142    #[test]
143    fn test_build_sourcehut_permalink() {
144        let permalink = Sourcehut.build_permalink(
145            ParsedGitRemote {
146                owner: "zed-industries".into(),
147                repo: "zed".into(),
148            },
149            BuildPermalinkParams::new(
150                "faa6f979be417239b2e070dbbf6392b909224e0b",
151                &repo_path("crates/editor/src/git/permalink.rs"),
152                None,
153            ),
154        );
155
156        let expected_url = "https://git.sr.ht/~zed-industries/zed/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/editor/src/git/permalink.rs";
157        assert_eq!(permalink.to_string(), expected_url.to_string())
158    }
159
160    #[test]
161    fn test_build_sourcehut_permalink_with_git_suffix() {
162        let permalink = Sourcehut.build_permalink(
163            ParsedGitRemote {
164                owner: "zed-industries".into(),
165                repo: "zed.git".into(),
166            },
167            BuildPermalinkParams::new(
168                "faa6f979be417239b2e070dbbf6392b909224e0b",
169                &repo_path("crates/editor/src/git/permalink.rs"),
170                None,
171            ),
172        );
173
174        let expected_url = "https://git.sr.ht/~zed-industries/zed.git/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/editor/src/git/permalink.rs";
175        assert_eq!(permalink.to_string(), expected_url.to_string())
176    }
177
178    #[test]
179    fn test_build_sourcehut_permalink_with_single_line_selection() {
180        let permalink = Sourcehut.build_permalink(
181            ParsedGitRemote {
182                owner: "zed-industries".into(),
183                repo: "zed".into(),
184            },
185            BuildPermalinkParams::new(
186                "faa6f979be417239b2e070dbbf6392b909224e0b",
187                &repo_path("crates/editor/src/git/permalink.rs"),
188                Some(6..6),
189            ),
190        );
191
192        let expected_url = "https://git.sr.ht/~zed-industries/zed/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/editor/src/git/permalink.rs#L7";
193        assert_eq!(permalink.to_string(), expected_url.to_string())
194    }
195
196    #[test]
197    fn test_build_sourcehut_permalink_with_multi_line_selection() {
198        let permalink = Sourcehut.build_permalink(
199            ParsedGitRemote {
200                owner: "zed-industries".into(),
201                repo: "zed".into(),
202            },
203            BuildPermalinkParams::new(
204                "faa6f979be417239b2e070dbbf6392b909224e0b",
205                &repo_path("crates/editor/src/git/permalink.rs"),
206                Some(23..47),
207            ),
208        );
209
210        let expected_url = "https://git.sr.ht/~zed-industries/zed/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/editor/src/git/permalink.rs#L24-48";
211        assert_eq!(permalink.to_string(), expected_url.to_string())
212    }
213}