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().unwrap();
124
125 let parsed_url: Url = parsed_vcs.repo_url.parse().unwrap();
126
127 match parsed_url.host_str().unwrap() {
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#[cfg(test)]
640mod tests {
641 #[test]
642 fn test_source_package_vcs() {
643 use super::PackageVcs;
644 use debian_control::Control;
645
646 let control: Control = r#"Source: foo
647Vcs-Git: https://salsa.debian.org/foo/bar.git
648"#
649 .parse()
650 .unwrap();
651 assert_eq!(
652 super::source_package_vcs(&control.source().unwrap()),
653 Some(PackageVcs::Git {
654 url: "https://salsa.debian.org/foo/bar.git".parse().unwrap(),
655 branch: None,
656 subpath: None
657 })
658 );
659
660 let control: Control = r#"Source: foo
661Vcs-Svn: https://svn.debian.org/svn/foo/bar
662"#
663 .parse()
664 .unwrap();
665 assert_eq!(
666 super::source_package_vcs(&control.source().unwrap()),
667 Some(PackageVcs::Svn(
668 "https://svn.debian.org/svn/foo/bar".parse().unwrap()
669 ))
670 );
671 }
672
673 #[test]
674 fn test_determine_gitlab_browser_url() {
675 use super::determine_gitlab_browser_url;
676
677 assert_eq!(
678 determine_gitlab_browser_url("https://salsa.debian.org/foo/bar"),
679 "https://salsa.debian.org/foo/bar".parse().unwrap()
680 );
681
682 assert_eq!(
683 determine_gitlab_browser_url("https://salsa.debian.org/foo/bar.git"),
684 "https://salsa.debian.org/foo/bar".parse().unwrap()
685 );
686
687 assert_eq!(
688 determine_gitlab_browser_url("https://salsa.debian.org/foo/bar/"),
689 "https://salsa.debian.org/foo/bar".parse().unwrap()
690 );
691
692 assert_eq!(
693 determine_gitlab_browser_url("https://salsa.debian.org/foo/bar/.git"),
694 "https://salsa.debian.org/foo/bar/".parse().unwrap()
695 );
696
697 assert_eq!(
698 determine_gitlab_browser_url("https://salsa.debian.org/foo/bar.git -b baz"),
699 "https://salsa.debian.org/foo/bar/-/tree/baz"
700 .parse()
701 .unwrap()
702 );
703
704 assert_eq!(
705 determine_gitlab_browser_url(
706 "https://salsa.debian.org/foo/bar.git/ -b baz [otherpath]"
707 ),
708 "https://salsa.debian.org/foo/bar/-/tree/baz/otherpath"
709 .parse()
710 .unwrap()
711 );
712 }
713
714 #[test]
715 fn test_determine_browser_url() {
716 use super::determine_browser_url;
717 use url::Url;
718
719 assert_eq!(
720 determine_browser_url("git", "https://salsa.debian.org/foo/bar", Some(false)),
721 Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap())
722 );
723 assert_eq!(
724 determine_browser_url("git", "https://salsa.debian.org/foo/bar.git", Some(false)),
725 Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap())
726 );
727 assert_eq!(
728 determine_browser_url("git", "https://salsa.debian.org/foo/bar/", Some(false)),
729 Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap())
730 );
731 assert_eq!(
732 determine_browser_url("git", "https://salsa.debian.org/foo/bar/.git", Some(false)),
733 Some(Url::parse("https://salsa.debian.org/foo/bar/").unwrap())
734 );
735 assert_eq!(
736 determine_browser_url("git", "https://salsa.debian.org/foo/bar.git/", Some(false)),
737 Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap())
738 );
739 assert_eq!(
740 determine_browser_url(
741 "git",
742 "https://salsa.debian.org/foo/bar.git/.git",
743 Some(false)
744 ),
745 Some(Url::parse("https://salsa.debian.org/foo/bar.git/").unwrap())
746 );
747 assert_eq!(
748 determine_browser_url(
749 "git",
750 "https://salsa.debian.org/foo/bar.git.git",
751 Some(false)
752 ),
753 Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap())
754 );
755 assert_eq!(
756 determine_browser_url(
757 "git",
758 "https://salsa.debian.org/foo/bar.git.git/",
759 Some(false)
760 ),
761 Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap())
762 );
763
764 assert_eq!(
765 Some(Url::parse("https://salsa.debian.org/jelmer/dulwich").unwrap()),
766 determine_browser_url(
767 "git",
768 "https://salsa.debian.org/jelmer/dulwich.git",
769 Some(false)
770 )
771 );
772
773 assert_eq!(
774 Some(Url::parse("https://github.com/jelmer/dulwich").unwrap()),
775 determine_browser_url("git", "https://github.com/jelmer/dulwich.git", Some(false))
776 );
777 assert_eq!(
778 Some(Url::parse("https://github.com/jelmer/dulwich/tree/master").unwrap()),
779 determine_browser_url(
780 "git",
781 "https://github.com/jelmer/dulwich.git -b master",
782 Some(false)
783 )
784 );
785 assert_eq!(
786 Some(Url::parse("https://github.com/jelmer/dulwich/tree/master").unwrap()),
787 determine_browser_url(
788 "git",
789 "git://github.com/jelmer/dulwich -b master",
790 Some(false)
791 ),
792 );
793 assert_eq!(
794 Some(Url::parse("https://github.com/jelmer/dulwich/tree/master/blah").unwrap()),
795 determine_browser_url(
796 "git",
797 "git://github.com/jelmer/dulwich -b master [blah]",
798 Some(false)
799 ),
800 );
801 assert_eq!(
802 Some(Url::parse("https://github.com/jelmer/dulwich/tree/HEAD/blah").unwrap()),
803 determine_browser_url("git", "git://github.com/jelmer/dulwich [blah]", Some(false)),
804 );
805 assert_eq!(
806 Some(Url::parse("https://git.sv.gnu.org/cgit/rcs.git").unwrap()),
807 determine_browser_url("git", "https://git.sv.gnu.org/git/rcs.git", Some(false)),
808 );
809 assert_eq!(
810 Some(Url::parse("https://git.savannah.gnu.org/cgit/rcs.git").unwrap()),
811 determine_browser_url("git", "git://git.savannah.gnu.org/rcs.git", Some(false)),
812 );
813 assert_eq!(
814 Some(Url::parse("https://sourceforge.net/p/shorewall/debian").unwrap()),
815 determine_browser_url(
816 "git",
817 "git://git.code.sf.net/p/shorewall/debian",
818 Some(false)
819 ),
820 );
821 assert_eq!(
822 Some(Url::parse("https://sourceforge.net/p/shorewall/debian/ci/foo/tree").unwrap()),
823 determine_browser_url(
824 "git",
825 "git://git.code.sf.net/p/shorewall/debian -b foo",
826 Some(false)
827 ),
828 );
829 assert_eq!(
830 Some(Url::parse("https://sourceforge.net/p/shorewall/debian/ci/HEAD/tree/sp").unwrap()),
831 determine_browser_url(
832 "git",
833 "git://git.code.sf.net/p/shorewall/debian [sp]",
834 Some(false)
835 ),
836 );
837 assert_eq!(
838 Some(Url::parse("https://sourceforge.net/p/shorewall/debian/ci/foo/tree/sp").unwrap()),
839 determine_browser_url(
840 "git",
841 "git://git.code.sf.net/p/shorewall/debian -b foo [sp]",
842 Some(false)
843 ),
844 );
845 }
846
847 #[test]
848 fn test_vcs_field() {
849 use debian_control::Control;
850
851 let control: Control = r#"Source: foo
852Vcs-Git: https://salsa.debian.org/foo/bar.git
853"#
854 .parse()
855 .unwrap();
856 assert_eq!(
857 super::vcs_field(&control.source().unwrap()),
858 Some((
859 "Git".to_string(),
860 "https://salsa.debian.org/foo/bar.git".to_string()
861 ))
862 );
863 }
864}