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