bitbucket.rs

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