1use debian_control::vcs::ParsedVcs;
3use log::debug;
4use url::Url;
5
6pub const KNOWN_GITLAB_SITES: &[&str] = &["salsa.debian.org", "invent.kde.org", "0xacab.org"];
8
9pub fn is_gitlab_site(hostname: &str, net_access: Option<bool>) -> bool {
11 if KNOWN_GITLAB_SITES.contains(&hostname) {
12 return true;
13 }
14
15 if hostname.starts_with("gitlab.") {
16 return true;
17 }
18
19 if net_access.unwrap_or(false) {
20 probe_gitlab_host(hostname)
21 } else {
22 false
23 }
24}
25
26pub fn probe_gitlab_host(hostname: &str) -> bool {
28 use reqwest::header::HeaderMap;
29 let url = format!("https://{}/api/v4/version", hostname);
30
31 let mut headers = HeaderMap::new();
32 headers.insert(reqwest::header::ACCEPT, "application/json".parse().unwrap());
33
34 let client = reqwest::blocking::Client::builder()
35 .default_headers(headers)
36 .build()
37 .expect("Failed to build HTTP client");
38
39 let http_url: reqwest::Url = url.parse().expect("Invalid URL format");
40
41 let request = client
42 .get(http_url)
43 .build()
44 .expect("Failed to build request");
45
46 let response = match client.execute(request) {
47 Ok(r) => r,
48 Err(_) => return false,
49 };
50
51 match response.status().as_u16() {
52 401 => {
53 if let Ok(data) = response.json::<serde_json::Value>() {
54 if let Some(message) = data["message"].as_str() {
55 if message == "401 Unauthorized" {
56 true
57 } else {
58 debug!("failed to parse JSON response: {:?}", data);
59 false
60 }
61 } else {
62 debug!("failed to parse JSON response: {:?}", data);
63 false
64 }
65 } else {
66 debug!("failed to parse JSON response");
67 false
68 }
69 }
70 200 => true,
71 _ => {
72 debug!("unexpected HTTP status code: {:?}", response.status());
73 false
74 }
75 }
76}
77
78pub fn determine_gitlab_browser_url(url: &str) -> Url {
80 let parsed_vcs: ParsedVcs = url.trim_end_matches('/').parse().unwrap();
81
82 let parsed_url = Url::parse(&parsed_vcs.repo_url).unwrap();
84
85 let path = parsed_url
86 .path()
87 .trim_end_matches('/')
88 .trim_end_matches(".git");
89
90 let branch = if let Some(branch) = parsed_vcs.branch {
91 Some(branch)
92 } else if parsed_vcs.subpath.is_some() {
93 Some("HEAD".to_string())
94 } else {
95 None
96 };
97
98 let mut path = if let Some(branch) = branch {
99 format!("{}/-/tree/{}", path, branch)
100 } else {
101 path.to_string()
102 };
103
104 if let Some(subpath) = parsed_vcs.subpath {
105 path.push_str(&format!("/{}", subpath));
106 }
107
108 let url = format!(
109 "https://{}/{}",
110 parsed_url.host_str().unwrap(),
111 path.trim_start_matches('/')
112 );
113
114 Url::parse(&url).unwrap()
115}
116
117pub fn determine_browser_url(
119 _vcs_type: &str,
120 vcs_url: &str,
121 net_access: Option<bool>,
122) -> Option<Url> {
123 let parsed_vcs: ParsedVcs = vcs_url.parse().ok()?;
124
125 let parsed_url: Url = parsed_vcs.repo_url.parse().ok()?;
126
127 match parsed_url.host_str()? {
128 host if is_gitlab_site(host, net_access) => Some(determine_gitlab_browser_url(vcs_url)),
129
130 "github.com" => {
131 let path = parsed_url.path().trim_end_matches(".git");
132
133 let branch = if let Some(branch) = parsed_vcs.branch {
134 Some(branch)
135 } else if parsed_vcs.subpath.is_some() {
136 Some("HEAD".to_string())
137 } else {
138 None
139 };
140
141 let mut path = if let Some(branch) = branch {
142 format!("{}/tree/{}", path, branch)
143 } else {
144 path.to_string()
145 };
146
147 if let Some(subpath) = parsed_vcs.subpath {
148 path.push_str(&format!("/{}", subpath));
149 }
150
151 let url = format!(
152 "https://{}/{}",
153 parsed_url.host_str().unwrap(),
154 path.trim_start_matches('/')
155 );
156
157 Some(Url::parse(&url).unwrap())
158 }
159 host if (host == "code.launchpad.net" || host == "launchpad.net")
160 && parsed_vcs.branch.is_none()
161 && parsed_vcs.subpath.is_none() =>
162 {
163 let url = format!(
164 "https://code.launchpad.net/{}",
165 parsed_url.path().trim_start_matches('/')
166 );
167
168 Some(Url::parse(&url).unwrap())
169 }
170 "git.savannah.gnu.org" | "git.sv.gnu.org" => {
171 let mut path_elements = parsed_url.path_segments().unwrap().collect::<Vec<_>>();
172 if parsed_url.scheme() == "https" && path_elements.first() == Some(&"git") {
173 path_elements.remove(0);
174 }
175 path_elements.insert(0, "cgit");
177 Some(
178 Url::parse(&format!(
179 "https://{}/{}",
180 parsed_url.host_str().unwrap(),
181 path_elements.join("/")
182 ))
183 .unwrap(),
184 )
185 }
186 "git.code.sf.net" | "git.code.sourceforge.net" => {
187 let path_elements = parsed_url.path_segments().unwrap().collect::<Vec<_>>();
188 if path_elements.first() != Some(&"p") {
189 return None;
190 }
191 let project = path_elements[1];
192 let repository = path_elements[2];
193 let mut path_elements = vec!["p", project, repository];
194 let branch = if let Some(branch) = parsed_vcs.branch {
195 Some(branch)
196 } else if parsed_vcs.subpath.is_some() {
197 Some("HEAD".to_string())
198 } else {
199 None
200 };
201
202 if let Some(branch) = branch.as_deref() {
203 path_elements.extend(["ci", branch, "tree"]);
204 }
205
206 if let Some(subpath) = parsed_vcs.subpath.as_ref() {
207 path_elements.push(subpath);
208 }
209
210 let url = format!("https://sourceforge.net/{}", path_elements.join("/"));
211 Some(Url::parse(&url).unwrap())
212 }
213 _ => None,
214 }
215}
216
217pub fn canonicalize_vcs_browser_url(url: &str) -> String {
219 let url = url.replace(
220 "https://svn.debian.org/wsvn/",
221 "https://anonscm.debian.org/viewvc/",
222 );
223 let url = url.replace(
224 "http://svn.debian.org/wsvn/",
225 "https://anonscm.debian.org/viewvc/",
226 );
227 let url = url.replace(
228 "https://git.debian.org/?p=",
229 "https://anonscm.debian.org/git/",
230 );
231 let url = url.replace(
232 "http://git.debian.org/?p=",
233 "https://anonscm.debian.org/git/",
234 );
235 let url = url.replace(
236 "https://bzr.debian.org/loggerhead/",
237 "https://anonscm.debian.org/loggerhead/",
238 );
239 let url = url.replace(
240 "http://bzr.debian.org/loggerhead/",
241 "https://anonscm.debian.org/loggerhead/",
242 );
243
244 lazy_regex::regex_replace!(
245 r"^https?://salsa.debian.org/([^/]+/[^/]+)\.git/?$",
246 &url,
247 |_, x| "https://salsa.debian.org/".to_string() + x
248 )
249 .into_owned()
250}
251
252#[derive(Debug, PartialEq, Eq, Clone)]
254pub enum PackageVcs {
255 Git {
257 url: Url,
259
260 branch: Option<String>,
262
263 subpath: Option<std::path::PathBuf>,
265 },
266 Svn(Url),
268
269 Bzr(Url),
271
272 Hg {
274 url: Url,
276
277 branch: Option<String>,
279
280 subpath: Option<std::path::PathBuf>,
282 },
283
284 Mtn(Url),
286
287 Cvs(String),
289
290 Darcs(Url),
292
293 Arch(Url),
295
296 Svk(Url),
298}
299
300impl PackageVcs {
301 pub fn type_str(&self) -> &str {
303 match self {
304 PackageVcs::Git { .. } => "Git",
305 PackageVcs::Svn(_) => "Svn",
306 PackageVcs::Bzr(_) => "Bzr",
307 PackageVcs::Hg { .. } => "Hg",
308 PackageVcs::Mtn(_) => "Mtn",
309 PackageVcs::Cvs(_) => "Cvs",
310 PackageVcs::Darcs(_) => "Darcs",
311 PackageVcs::Arch(_) => "Arch",
312 PackageVcs::Svk(_) => "Svk",
313 }
314 }
315
316 pub fn url(&self) -> Option<&url::Url> {
318 match self {
319 PackageVcs::Git { url, .. } => Some(url),
320 PackageVcs::Svn(url) => Some(url),
321 PackageVcs::Bzr(url) => Some(url),
322 PackageVcs::Hg { url, .. } => Some(url),
323 PackageVcs::Mtn(url) => Some(url),
324 PackageVcs::Darcs(url) => Some(url),
325 PackageVcs::Arch(url) => Some(url),
326 PackageVcs::Svk(url) => Some(url),
327 PackageVcs::Cvs(_) => None,
328 }
329 }
330
331 pub fn branch(&self) -> Option<&str> {
333 match self {
334 PackageVcs::Git { branch, .. } => branch.as_deref(),
335 PackageVcs::Hg { branch, .. } => branch.as_deref(),
336 _ => None,
337 }
338 }
339
340 pub fn subpath(&self) -> Option<&std::path::Path> {
342 match self {
343 PackageVcs::Git { subpath, .. } => subpath.as_deref(),
344 PackageVcs::Hg { subpath, .. } => subpath.as_deref(),
345 _ => None,
346 }
347 }
348
349 pub fn location(&self) -> String {
351 match self {
352 PackageVcs::Git {
353 url,
354 branch,
355 subpath,
356 } => {
357 let mut result = url.to_string();
358 if let Some(branch) = branch {
359 result.push_str(&format!(" -b {}", branch));
360 }
361 if let Some(subpath) = subpath {
362 result.push_str(&format!(" [{}]", subpath.display()));
363 }
364 result
365 }
366 PackageVcs::Svn(url) => url.to_string(),
367 PackageVcs::Bzr(url) => url.to_string(),
368 PackageVcs::Hg {
369 url,
370 branch,
371 subpath,
372 } => {
373 let mut result = url.to_string();
374 if let Some(branch) = branch {
375 result.push_str(&format!(" -b {}", branch));
376 }
377 if let Some(subpath) = subpath {
378 result.push_str(&format!(" [{}]", subpath.display()));
379 }
380 result
381 }
382 PackageVcs::Mtn(url) => url.to_string(),
383 PackageVcs::Cvs(s) => s.clone(),
384 PackageVcs::Darcs(url) => url.to_string(),
385 PackageVcs::Arch(url) => url.to_string(),
386 PackageVcs::Svk(url) => url.to_string(),
387 }
388 }
389}
390
391impl From<PackageVcs> for ParsedVcs {
392 fn from(vcs: PackageVcs) -> Self {
393 match vcs {
394 PackageVcs::Git {
395 url,
396 branch,
397 subpath,
398 } => ParsedVcs {
399 repo_url: url.to_string(),
400 branch,
401 subpath: subpath.map(|x| x.to_string_lossy().to_string()),
402 },
403 PackageVcs::Svn(url) => ParsedVcs {
404 repo_url: url.to_string(),
405 branch: None,
406 subpath: None,
407 },
408 PackageVcs::Bzr(url) => ParsedVcs {
409 repo_url: url.to_string(),
410 branch: None,
411 subpath: None,
412 },
413 PackageVcs::Hg {
414 url,
415 branch,
416 subpath,
417 } => ParsedVcs {
418 repo_url: url.to_string(),
419 branch,
420 subpath: subpath.map(|x| x.to_string_lossy().to_string()),
421 },
422 PackageVcs::Mtn(url) => ParsedVcs {
423 repo_url: url.to_string(),
424 branch: None,
425 subpath: None,
426 },
427 PackageVcs::Cvs(s) => ParsedVcs {
428 repo_url: s,
429 branch: None,
430 subpath: None,
431 },
432 PackageVcs::Darcs(url) => ParsedVcs {
433 repo_url: url.to_string(),
434 branch: None,
435 subpath: None,
436 },
437 PackageVcs::Arch(url) => ParsedVcs {
438 repo_url: url.to_string(),
439 branch: None,
440 subpath: None,
441 },
442 PackageVcs::Svk(url) => ParsedVcs {
443 repo_url: url.to_string(),
444 branch: None,
445 subpath: None,
446 },
447 }
448 }
449}
450
451pub trait VcsSource {
453 fn vcs_git(&self) -> Option<String>;
455
456 fn vcs_svn(&self) -> Option<String>;
458
459 fn vcs_bzr(&self) -> Option<String>;
461
462 fn vcs_hg(&self) -> Option<String>;
464
465 fn vcs_mtn(&self) -> Option<String>;
467
468 fn vcs_cvs(&self) -> Option<String>;
470
471 fn vcs_darcs(&self) -> Option<String>;
473
474 fn vcs_arch(&self) -> Option<String>;
476
477 fn vcs_svk(&self) -> Option<String>;
479}
480
481impl VcsSource for debian_control::Source {
482 fn vcs_git(&self) -> Option<String> {
483 self.vcs_git()
484 }
485
486 fn vcs_svn(&self) -> Option<String> {
487 self.vcs_svn()
488 }
489
490 fn vcs_bzr(&self) -> Option<String> {
491 self.vcs_bzr()
492 }
493
494 fn vcs_hg(&self) -> Option<String> {
495 self.vcs_hg()
496 }
497
498 fn vcs_mtn(&self) -> Option<String> {
499 self.vcs_mtn()
500 }
501
502 fn vcs_cvs(&self) -> Option<String> {
503 self.vcs_cvs()
504 }
505
506 fn vcs_darcs(&self) -> Option<String> {
507 self.vcs_darcs()
508 }
509
510 fn vcs_arch(&self) -> Option<String> {
511 self.vcs_arch()
512 }
513
514 fn vcs_svk(&self) -> Option<String> {
515 self.vcs_svk()
516 }
517}
518
519impl VcsSource for debian_control::apt::Source {
520 fn vcs_git(&self) -> Option<String> {
521 self.vcs_git()
522 }
523
524 fn vcs_svn(&self) -> Option<String> {
525 self.vcs_svn()
526 }
527
528 fn vcs_bzr(&self) -> Option<String> {
529 self.vcs_bzr()
530 }
531
532 fn vcs_hg(&self) -> Option<String> {
533 self.vcs_hg()
534 }
535
536 fn vcs_mtn(&self) -> Option<String> {
537 self.vcs_mtn()
538 }
539
540 fn vcs_cvs(&self) -> Option<String> {
541 self.vcs_cvs()
542 }
543
544 fn vcs_darcs(&self) -> Option<String> {
545 self.vcs_darcs()
546 }
547
548 fn vcs_arch(&self) -> Option<String> {
549 self.vcs_arch()
550 }
551
552 fn vcs_svk(&self) -> Option<String> {
553 self.vcs_svk()
554 }
555}
556
557pub fn vcs_field(source_package: &impl VcsSource) -> Option<(String, String)> {
559 if let Some(value) = source_package.vcs_git() {
560 return Some(("Git".to_string(), value));
561 }
562 if let Some(value) = source_package.vcs_svn() {
563 return Some(("Svn".to_string(), value));
564 }
565 if let Some(value) = source_package.vcs_bzr() {
566 return Some(("Bzr".to_string(), value));
567 }
568 if let Some(value) = source_package.vcs_hg() {
569 return Some(("Hg".to_string(), value));
570 }
571 if let Some(value) = source_package.vcs_mtn() {
572 return Some(("Mtn".to_string(), value));
573 }
574 if let Some(value) = source_package.vcs_cvs() {
575 return Some(("Cvs".to_string(), value));
576 }
577 if let Some(value) = source_package.vcs_darcs() {
578 return Some(("Darcs".to_string(), value));
579 }
580 if let Some(value) = source_package.vcs_arch() {
581 return Some(("Arch".to_string(), value));
582 }
583 if let Some(value) = source_package.vcs_svk() {
584 return Some(("Svk".to_string(), value));
585 }
586 None
587}
588
589pub fn source_package_vcs(source_package: &impl VcsSource) -> Option<PackageVcs> {
591 if let Some(value) = source_package.vcs_git() {
592 let parsed_vcs: ParsedVcs = value.parse().unwrap();
593 let url = parsed_vcs.repo_url.parse().unwrap();
594 return Some(PackageVcs::Git {
595 url,
596 branch: parsed_vcs.branch,
597 subpath: parsed_vcs.subpath.map(std::path::PathBuf::from),
598 });
599 }
600 if let Some(value) = source_package.vcs_svn() {
601 let url = value.parse().unwrap();
602 return Some(PackageVcs::Svn(url));
603 }
604 if let Some(value) = source_package.vcs_bzr() {
605 let url = value.parse().unwrap();
606 return Some(PackageVcs::Bzr(url));
607 }
608 if let Some(value) = source_package.vcs_hg() {
609 let parsed_vcs: ParsedVcs = value.parse().unwrap();
610 let url = parsed_vcs.repo_url.parse().unwrap();
611 return Some(PackageVcs::Hg {
612 url,
613 branch: parsed_vcs.branch,
614 subpath: parsed_vcs.subpath.map(std::path::PathBuf::from),
615 });
616 }
617 if let Some(value) = source_package.vcs_mtn() {
618 let url = value.parse().unwrap();
619 return Some(PackageVcs::Mtn(url));
620 }
621 if let Some(value) = source_package.vcs_cvs() {
622 return Some(PackageVcs::Cvs(value.clone()));
623 }
624 if let Some(value) = source_package.vcs_darcs() {
625 let url = value.parse().unwrap();
626 return Some(PackageVcs::Darcs(url));
627 }
628 if let Some(value) = source_package.vcs_arch() {
629 let url = value.parse().unwrap();
630 return Some(PackageVcs::Arch(url));
631 }
632 if let Some(value) = source_package.vcs_svk() {
633 let url = value.parse().unwrap();
634 return Some(PackageVcs::Svk(url));
635 }
636 None
637}
638
639#[derive(Debug, Clone, PartialEq, Eq)]
641pub struct GbpTagFormatError {
642 pub tag_name: String,
644 pub variable: String,
646}
647
648impl std::fmt::Display for GbpTagFormatError {
649 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
650 write!(
651 f,
652 "Unknown variable '{}' in gbp tag name '{}'",
653 self.variable, self.tag_name
654 )
655 }
656}
657
658impl std::error::Error for GbpTagFormatError {}
659
660pub fn gbp_expand_tag_name(tag_format: &str, version: &str) -> Result<String, GbpTagFormatError> {
693 let version_mangle_re: &'static lazy_regex::Lazy<lazy_regex::Regex> =
696 lazy_regex::regex!(r"%\(version%(?P<M>.)%(?P<R>([^%]|\\%)+)\)s");
697
698 let (ret, mangled_version) = if let Some(captures) = version_mangle_re.captures(tag_format) {
699 let match_char = captures.name("M").unwrap().as_str();
700 let replacement = captures.name("R").unwrap().as_str().replace(r"\%", "%");
701 let mangled = version.replace(match_char, &replacement);
702 let simplified = version_mangle_re.replace(tag_format, "%(version)s");
703 (simplified.to_string(), mangled)
704 } else {
705 (tag_format.to_string(), version.to_string())
706 };
707
708 let hversion = mangled_version.replace('.', "-");
710
711 let result = ret
712 .replace("%(version)s", &mangled_version)
713 .replace("%(hversion)s", &hversion);
714
715 let unknown_var_re: &'static lazy_regex::Lazy<lazy_regex::Regex> =
717 lazy_regex::regex!(r"%\((\w+)\)s");
718 if let Some(captures) = unknown_var_re.captures(&result) {
719 return Err(GbpTagFormatError {
720 tag_name: tag_format.to_string(),
721 variable: captures.get(1).unwrap().as_str().to_string(),
722 });
723 }
724
725 Ok(result)
726}
727
728#[cfg(test)]
729mod tests {
730 #[test]
731 fn test_source_package_vcs() {
732 use super::PackageVcs;
733 use debian_control::Control;
734
735 let control: Control = r#"Source: foo
736Vcs-Git: https://salsa.debian.org/foo/bar.git
737"#
738 .parse()
739 .unwrap();
740 assert_eq!(
741 super::source_package_vcs(&control.source().unwrap()),
742 Some(PackageVcs::Git {
743 url: "https://salsa.debian.org/foo/bar.git".parse().unwrap(),
744 branch: None,
745 subpath: None
746 })
747 );
748
749 let control: Control = r#"Source: foo
750Vcs-Svn: https://svn.debian.org/svn/foo/bar
751"#
752 .parse()
753 .unwrap();
754 assert_eq!(
755 super::source_package_vcs(&control.source().unwrap()),
756 Some(PackageVcs::Svn(
757 "https://svn.debian.org/svn/foo/bar".parse().unwrap()
758 ))
759 );
760 }
761
762 #[test]
763 fn test_determine_gitlab_browser_url() {
764 use super::determine_gitlab_browser_url;
765
766 assert_eq!(
767 determine_gitlab_browser_url("https://salsa.debian.org/foo/bar"),
768 "https://salsa.debian.org/foo/bar".parse().unwrap()
769 );
770
771 assert_eq!(
772 determine_gitlab_browser_url("https://salsa.debian.org/foo/bar.git"),
773 "https://salsa.debian.org/foo/bar".parse().unwrap()
774 );
775
776 assert_eq!(
777 determine_gitlab_browser_url("https://salsa.debian.org/foo/bar/"),
778 "https://salsa.debian.org/foo/bar".parse().unwrap()
779 );
780
781 assert_eq!(
782 determine_gitlab_browser_url("https://salsa.debian.org/foo/bar/.git"),
783 "https://salsa.debian.org/foo/bar/".parse().unwrap()
784 );
785
786 assert_eq!(
787 determine_gitlab_browser_url("https://salsa.debian.org/foo/bar.git -b baz"),
788 "https://salsa.debian.org/foo/bar/-/tree/baz"
789 .parse()
790 .unwrap()
791 );
792
793 assert_eq!(
794 determine_gitlab_browser_url(
795 "https://salsa.debian.org/foo/bar.git/ -b baz [otherpath]"
796 ),
797 "https://salsa.debian.org/foo/bar/-/tree/baz/otherpath"
798 .parse()
799 .unwrap()
800 );
801 }
802
803 #[test]
804 fn test_determine_browser_url() {
805 use super::determine_browser_url;
806 use url::Url;
807
808 assert_eq!(
809 determine_browser_url("git", "https://salsa.debian.org/foo/bar", Some(false)),
810 Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap())
811 );
812 assert_eq!(
813 determine_browser_url("git", "https://salsa.debian.org/foo/bar.git", Some(false)),
814 Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap())
815 );
816 assert_eq!(
817 determine_browser_url("git", "https://salsa.debian.org/foo/bar/", Some(false)),
818 Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap())
819 );
820 assert_eq!(
821 determine_browser_url("git", "https://salsa.debian.org/foo/bar/.git", Some(false)),
822 Some(Url::parse("https://salsa.debian.org/foo/bar/").unwrap())
823 );
824 assert_eq!(
825 determine_browser_url("git", "https://salsa.debian.org/foo/bar.git/", Some(false)),
826 Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap())
827 );
828 assert_eq!(
829 determine_browser_url(
830 "git",
831 "https://salsa.debian.org/foo/bar.git/.git",
832 Some(false)
833 ),
834 Some(Url::parse("https://salsa.debian.org/foo/bar.git/").unwrap())
835 );
836 assert_eq!(
837 determine_browser_url(
838 "git",
839 "https://salsa.debian.org/foo/bar.git.git",
840 Some(false)
841 ),
842 Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap())
843 );
844 assert_eq!(
845 determine_browser_url(
846 "git",
847 "https://salsa.debian.org/foo/bar.git.git/",
848 Some(false)
849 ),
850 Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap())
851 );
852
853 assert_eq!(
854 Some(Url::parse("https://salsa.debian.org/jelmer/dulwich").unwrap()),
855 determine_browser_url(
856 "git",
857 "https://salsa.debian.org/jelmer/dulwich.git",
858 Some(false)
859 )
860 );
861
862 assert_eq!(
863 Some(Url::parse("https://github.com/jelmer/dulwich").unwrap()),
864 determine_browser_url("git", "https://github.com/jelmer/dulwich.git", Some(false))
865 );
866 assert_eq!(
867 Some(Url::parse("https://github.com/jelmer/dulwich/tree/master").unwrap()),
868 determine_browser_url(
869 "git",
870 "https://github.com/jelmer/dulwich.git -b master",
871 Some(false)
872 )
873 );
874 assert_eq!(
875 Some(Url::parse("https://github.com/jelmer/dulwich/tree/master").unwrap()),
876 determine_browser_url(
877 "git",
878 "git://github.com/jelmer/dulwich -b master",
879 Some(false)
880 ),
881 );
882 assert_eq!(
883 Some(Url::parse("https://github.com/jelmer/dulwich/tree/master/blah").unwrap()),
884 determine_browser_url(
885 "git",
886 "git://github.com/jelmer/dulwich -b master [blah]",
887 Some(false)
888 ),
889 );
890 assert_eq!(
891 Some(Url::parse("https://github.com/jelmer/dulwich/tree/HEAD/blah").unwrap()),
892 determine_browser_url("git", "git://github.com/jelmer/dulwich [blah]", Some(false)),
893 );
894 assert_eq!(
895 Some(Url::parse("https://git.sv.gnu.org/cgit/rcs.git").unwrap()),
896 determine_browser_url("git", "https://git.sv.gnu.org/git/rcs.git", Some(false)),
897 );
898 assert_eq!(
899 Some(Url::parse("https://git.savannah.gnu.org/cgit/rcs.git").unwrap()),
900 determine_browser_url("git", "git://git.savannah.gnu.org/rcs.git", Some(false)),
901 );
902 assert_eq!(
903 Some(Url::parse("https://sourceforge.net/p/shorewall/debian").unwrap()),
904 determine_browser_url(
905 "git",
906 "git://git.code.sf.net/p/shorewall/debian",
907 Some(false)
908 ),
909 );
910 assert_eq!(
911 Some(Url::parse("https://sourceforge.net/p/shorewall/debian/ci/foo/tree").unwrap()),
912 determine_browser_url(
913 "git",
914 "git://git.code.sf.net/p/shorewall/debian -b foo",
915 Some(false)
916 ),
917 );
918 assert_eq!(
919 Some(Url::parse("https://sourceforge.net/p/shorewall/debian/ci/HEAD/tree/sp").unwrap()),
920 determine_browser_url(
921 "git",
922 "git://git.code.sf.net/p/shorewall/debian [sp]",
923 Some(false)
924 ),
925 );
926 assert_eq!(
927 Some(Url::parse("https://sourceforge.net/p/shorewall/debian/ci/foo/tree/sp").unwrap()),
928 determine_browser_url(
929 "git",
930 "git://git.code.sf.net/p/shorewall/debian -b foo [sp]",
931 Some(false)
932 ),
933 );
934 }
935
936 #[test]
937 fn test_vcs_field() {
938 use debian_control::Control;
939
940 let control: Control = r#"Source: foo
941Vcs-Git: https://salsa.debian.org/foo/bar.git
942"#
943 .parse()
944 .unwrap();
945 assert_eq!(
946 super::vcs_field(&control.source().unwrap()),
947 Some((
948 "Git".to_string(),
949 "https://salsa.debian.org/foo/bar.git".to_string()
950 ))
951 );
952 }
953
954 #[test]
955 fn test_determine_browser_url_invalid_inputs() {
956 use super::determine_browser_url;
957
958 assert_eq!(
960 determine_browser_url("git", "not a valid vcs url", Some(false)),
961 None
962 );
963
964 assert_eq!(determine_browser_url("git", "", Some(false)), None);
966
967 assert_eq!(
969 determine_browser_url("git", "file:///path/to/repo", Some(false)),
970 None
971 );
972
973 assert_eq!(
975 determine_browser_url("git", "://missing-scheme", Some(false)),
976 None
977 );
978
979 assert_eq!(
981 determine_browser_url("git", "http://[invalid brackets", Some(false)),
982 None
983 );
984
985 assert_eq!(
987 determine_browser_url("git", "http://example.com/repo with spaces", Some(false)),
988 None
989 );
990 }
991
992 #[test]
993 fn test_determine_browser_url_edge_cases() {
994 use super::determine_browser_url;
995 use url::Url;
996
997 assert_eq!(
999 determine_browser_url("git", "http://localhost/repo.git", Some(false)),
1000 None
1001 );
1002
1003 assert_eq!(
1005 determine_browser_url("git", "http://192.168.1.1/repo.git", Some(false)),
1006 None
1007 );
1008
1009 assert_eq!(
1011 determine_browser_url("git", "https://github.com:443/user/repo.git", Some(false)),
1012 Some(Url::parse("https://github.com:443/user/repo").unwrap())
1013 );
1014 }
1015
1016 #[test]
1017 fn test_gbp_expand_tag_name() {
1018 use super::gbp_expand_tag_name;
1019
1020 assert_eq!(
1022 gbp_expand_tag_name("debian/%(version)s", "1.0-1").unwrap(),
1023 "debian/1.0-1"
1024 );
1025
1026 assert_eq!(
1028 gbp_expand_tag_name("v%(hversion)s", "1.0-1").unwrap(),
1029 "v1-0-1"
1030 );
1031
1032 assert_eq!(
1034 gbp_expand_tag_name("%(version%~%_)s", "1.0~rc1").unwrap(),
1035 "1.0_rc1"
1036 );
1037
1038 assert_eq!(
1040 gbp_expand_tag_name("%(version%:%-)s", "1:2.0-1").unwrap(),
1041 "1-2.0-1"
1042 );
1043
1044 assert_eq!(
1046 gbp_expand_tag_name("%(version)s-%(hversion)s", "1.0").unwrap(),
1047 "1.0-1-0"
1048 );
1049
1050 assert_eq!(
1052 gbp_expand_tag_name("upstream/1.0", "1.0").unwrap(),
1053 "upstream/1.0"
1054 );
1055
1056 let result = gbp_expand_tag_name("%(unknown)s", "1.0");
1058 assert!(result.is_err());
1059 assert_eq!(result.unwrap_err().variable, "unknown");
1060 }
1061}