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 .unwrap();
38
39 let http_url: reqwest::Url = Into::<String>::into(url.clone()).parse().unwrap();
40
41 let request = client.get(http_url).build().unwrap();
42
43 let response = client.execute(request).unwrap();
44
45 match response.status().as_u16() {
46 401 => {
47 if let Ok(data) = response.json::<serde_json::Value>() {
48 if let Some(message) = data["message"].as_str() {
49 if message == "401 Unauthorized" {
50 true
51 } else {
52 debug!("failed to parse JSON response: {:?}", data);
53 false
54 }
55 } else {
56 debug!("failed to parse JSON response: {:?}", data);
57 false
58 }
59 } else {
60 debug!("failed to parse JSON response");
61 false
62 }
63 }
64 200 => true,
65 _ => {
66 debug!("unexpected HTTP status code: {:?}", response.status());
67 false
68 }
69 }
70}
71
72pub fn determine_gitlab_browser_url(url: &str) -> Url {
74 let parsed_vcs: ParsedVcs = url.trim_end_matches('/').parse().unwrap();
75
76 let parsed_url = Url::parse(&parsed_vcs.repo_url).unwrap();
78
79 let path = parsed_url
80 .path()
81 .trim_end_matches('/')
82 .trim_end_matches(".git");
83
84 let branch = if let Some(branch) = parsed_vcs.branch {
85 Some(branch)
86 } else if parsed_vcs.subpath.is_some() {
87 Some("HEAD".to_string())
88 } else {
89 None
90 };
91
92 let mut path = if let Some(branch) = branch {
93 format!("{}/-/tree/{}", path, branch)
94 } else {
95 path.to_string()
96 };
97
98 if let Some(subpath) = parsed_vcs.subpath {
99 path.push_str(&format!("/{}", subpath));
100 }
101
102 let url = format!(
103 "https://{}/{}",
104 parsed_url.host_str().unwrap(),
105 path.trim_start_matches('/')
106 );
107
108 Url::parse(&url).unwrap()
109}
110
111pub fn determine_browser_url(
113 _vcs_type: &str,
114 vcs_url: &str,
115 net_access: Option<bool>,
116) -> Option<Url> {
117 let parsed_vcs: ParsedVcs = vcs_url.parse().unwrap();
118
119 let parsed_url: Url = parsed_vcs.repo_url.parse().unwrap();
120
121 match parsed_url.host_str().unwrap() {
122 host if is_gitlab_site(host, net_access) => Some(determine_gitlab_browser_url(vcs_url)),
123
124 "github.com" => {
125 let path = parsed_url.path().trim_end_matches(".git");
126
127 let branch = if let Some(branch) = parsed_vcs.branch {
128 Some(branch)
129 } else if parsed_vcs.subpath.is_some() {
130 Some("HEAD".to_string())
131 } else {
132 None
133 };
134
135 let mut path = if let Some(branch) = branch {
136 format!("{}/tree/{}", path, branch)
137 } else {
138 path.to_string()
139 };
140
141 if let Some(subpath) = parsed_vcs.subpath {
142 path.push_str(&format!("/{}", subpath));
143 }
144
145 let url = format!(
146 "https://{}/{}",
147 parsed_url.host_str().unwrap(),
148 path.trim_start_matches('/')
149 );
150
151 Some(Url::parse(&url).unwrap())
152 }
153 host if (host == "code.launchpad.net" || host == "launchpad.net")
154 && parsed_vcs.branch.is_none()
155 && parsed_vcs.subpath.is_none() =>
156 {
157 let url = format!(
158 "https://code.launchpad.net/{}",
159 parsed_url.path().trim_start_matches('/')
160 );
161
162 Some(Url::parse(&url).unwrap())
163 }
164 "git.savannah.gnu.org" | "git.sv.gnu.org" => {
165 let mut path_elements = parsed_url.path_segments().unwrap().collect::<Vec<_>>();
166 if parsed_url.scheme() == "https" && path_elements.first() == Some(&"git") {
167 path_elements.remove(0);
168 }
169 path_elements.insert(0, "cgit");
171 Some(
172 Url::parse(&format!(
173 "https://{}/{}",
174 parsed_url.host_str().unwrap(),
175 path_elements.join("/")
176 ))
177 .unwrap(),
178 )
179 }
180 "git.code.sf.net" | "git.code.sourceforge.net" => {
181 let path_elements = parsed_url.path_segments().unwrap().collect::<Vec<_>>();
182 if path_elements.first() != Some(&"p") {
183 return None;
184 }
185 let project = path_elements[1];
186 let repository = path_elements[2];
187 let mut path_elements = vec!["p", project, repository];
188 let branch = if let Some(branch) = parsed_vcs.branch {
189 Some(branch)
190 } else if parsed_vcs.subpath.is_some() {
191 Some("HEAD".to_string())
192 } else {
193 None
194 };
195
196 if let Some(branch) = branch.as_deref() {
197 path_elements.extend(["ci", branch, "tree"]);
198 }
199
200 if let Some(subpath) = parsed_vcs.subpath.as_ref() {
201 path_elements.push(subpath);
202 }
203
204 let url = format!("https://sourceforge.net/{}", path_elements.join("/"));
205 Some(Url::parse(&url).unwrap())
206 }
207 _ => None,
208 }
209}
210
211pub fn canonicalize_vcs_browser_url(url: &str) -> String {
213 let url = url.replace(
214 "https://svn.debian.org/wsvn/",
215 "https://anonscm.debian.org/viewvc/",
216 );
217 let url = url.replace(
218 "http://svn.debian.org/wsvn/",
219 "https://anonscm.debian.org/viewvc/",
220 );
221 let url = url.replace(
222 "https://git.debian.org/?p=",
223 "https://anonscm.debian.org/git/",
224 );
225 let url = url.replace(
226 "http://git.debian.org/?p=",
227 "https://anonscm.debian.org/git/",
228 );
229 let url = url.replace(
230 "https://bzr.debian.org/loggerhead/",
231 "https://anonscm.debian.org/loggerhead/",
232 );
233 let url = url.replace(
234 "http://bzr.debian.org/loggerhead/",
235 "https://anonscm.debian.org/loggerhead/",
236 );
237
238 lazy_regex::regex_replace!(
239 r"^https?://salsa.debian.org/([^/]+/[^/]+)\.git/?$",
240 &url,
241 |_, x| "https://salsa.debian.org/".to_string() + x
242 )
243 .into_owned()
244}
245
246#[derive(Debug, PartialEq, Eq, Clone)]
248pub enum PackageVcs {
249 Git {
251 url: Url,
253
254 branch: Option<String>,
256
257 subpath: Option<std::path::PathBuf>,
259 },
260 Svn(Url),
262
263 Bzr(Url),
265
266 Hg {
268 url: Url,
270
271 branch: Option<String>,
273
274 subpath: Option<std::path::PathBuf>,
276 },
277
278 Mtn(Url),
280
281 Cvs(String),
283
284 Darcs(Url),
286
287 Arch(Url),
289
290 Svk(Url),
292}
293
294impl PackageVcs {
295 pub fn type_str(&self) -> &str {
297 match self {
298 PackageVcs::Git { .. } => "Git",
299 PackageVcs::Svn(_) => "Svn",
300 PackageVcs::Bzr(_) => "Bzr",
301 PackageVcs::Hg { .. } => "Hg",
302 PackageVcs::Mtn(_) => "Mtn",
303 PackageVcs::Cvs(_) => "Cvs",
304 PackageVcs::Darcs(_) => "Darcs",
305 PackageVcs::Arch(_) => "Arch",
306 PackageVcs::Svk(_) => "Svk",
307 }
308 }
309
310 pub fn url(&self) -> Option<&url::Url> {
312 match self {
313 PackageVcs::Git { url, .. } => Some(url),
314 PackageVcs::Svn(url) => Some(url),
315 PackageVcs::Bzr(url) => Some(url),
316 PackageVcs::Hg { url, .. } => Some(url),
317 PackageVcs::Mtn(url) => Some(url),
318 PackageVcs::Darcs(url) => Some(url),
319 PackageVcs::Arch(url) => Some(url),
320 PackageVcs::Svk(url) => Some(url),
321 PackageVcs::Cvs(_) => None,
322 }
323 }
324
325 pub fn branch(&self) -> Option<&str> {
327 match self {
328 PackageVcs::Git { branch, .. } => branch.as_deref(),
329 PackageVcs::Hg { branch, .. } => branch.as_deref(),
330 _ => None,
331 }
332 }
333
334 pub fn subpath(&self) -> Option<&std::path::Path> {
336 match self {
337 PackageVcs::Git { subpath, .. } => subpath.as_deref(),
338 PackageVcs::Hg { subpath, .. } => subpath.as_deref(),
339 _ => None,
340 }
341 }
342
343 pub fn location(&self) -> String {
345 match self {
346 PackageVcs::Git {
347 url,
348 branch,
349 subpath,
350 } => {
351 let mut result = url.to_string();
352 if let Some(branch) = branch {
353 result.push_str(&format!(" -b {}", branch));
354 }
355 if let Some(subpath) = subpath {
356 result.push_str(&format!(" [{}]", subpath.display()));
357 }
358 result
359 }
360 PackageVcs::Svn(url) => url.to_string(),
361 PackageVcs::Bzr(url) => url.to_string(),
362 PackageVcs::Hg {
363 url,
364 branch,
365 subpath,
366 } => {
367 let mut result = url.to_string();
368 if let Some(branch) = branch {
369 result.push_str(&format!(" -b {}", branch));
370 }
371 if let Some(subpath) = subpath {
372 result.push_str(&format!(" [{}]", subpath.display()));
373 }
374 result
375 }
376 PackageVcs::Mtn(url) => url.to_string(),
377 PackageVcs::Cvs(s) => s.clone(),
378 PackageVcs::Darcs(url) => url.to_string(),
379 PackageVcs::Arch(url) => url.to_string(),
380 PackageVcs::Svk(url) => url.to_string(),
381 }
382 }
383}
384
385impl From<PackageVcs> for ParsedVcs {
386 fn from(vcs: PackageVcs) -> Self {
387 match vcs {
388 PackageVcs::Git {
389 url,
390 branch,
391 subpath,
392 } => ParsedVcs {
393 repo_url: url.to_string(),
394 branch,
395 subpath: subpath.map(|x| x.to_string_lossy().to_string()),
396 },
397 PackageVcs::Svn(url) => ParsedVcs {
398 repo_url: url.to_string(),
399 branch: None,
400 subpath: None,
401 },
402 PackageVcs::Bzr(url) => ParsedVcs {
403 repo_url: url.to_string(),
404 branch: None,
405 subpath: None,
406 },
407 PackageVcs::Hg {
408 url,
409 branch,
410 subpath,
411 } => ParsedVcs {
412 repo_url: url.to_string(),
413 branch,
414 subpath: subpath.map(|x| x.to_string_lossy().to_string()),
415 },
416 PackageVcs::Mtn(url) => ParsedVcs {
417 repo_url: url.to_string(),
418 branch: None,
419 subpath: None,
420 },
421 PackageVcs::Cvs(s) => ParsedVcs {
422 repo_url: s,
423 branch: None,
424 subpath: None,
425 },
426 PackageVcs::Darcs(url) => ParsedVcs {
427 repo_url: url.to_string(),
428 branch: None,
429 subpath: None,
430 },
431 PackageVcs::Arch(url) => ParsedVcs {
432 repo_url: url.to_string(),
433 branch: None,
434 subpath: None,
435 },
436 PackageVcs::Svk(url) => ParsedVcs {
437 repo_url: url.to_string(),
438 branch: None,
439 subpath: None,
440 },
441 }
442 }
443}
444
445pub trait VcsSource {
447 fn vcs_git(&self) -> Option<String>;
449
450 fn vcs_svn(&self) -> Option<String>;
452
453 fn vcs_bzr(&self) -> Option<String>;
455
456 fn vcs_hg(&self) -> Option<String>;
458
459 fn vcs_mtn(&self) -> Option<String>;
461
462 fn vcs_cvs(&self) -> Option<String>;
464
465 fn vcs_darcs(&self) -> Option<String>;
467
468 fn vcs_arch(&self) -> Option<String>;
470
471 fn vcs_svk(&self) -> Option<String>;
473}
474
475impl VcsSource for debian_control::Source {
476 fn vcs_git(&self) -> Option<String> {
477 self.vcs_git()
478 }
479
480 fn vcs_svn(&self) -> Option<String> {
481 self.vcs_svn()
482 }
483
484 fn vcs_bzr(&self) -> Option<String> {
485 self.vcs_bzr()
486 }
487
488 fn vcs_hg(&self) -> Option<String> {
489 self.vcs_hg()
490 }
491
492 fn vcs_mtn(&self) -> Option<String> {
493 self.vcs_mtn()
494 }
495
496 fn vcs_cvs(&self) -> Option<String> {
497 self.vcs_cvs()
498 }
499
500 fn vcs_darcs(&self) -> Option<String> {
501 self.vcs_darcs()
502 }
503
504 fn vcs_arch(&self) -> Option<String> {
505 self.vcs_arch()
506 }
507
508 fn vcs_svk(&self) -> Option<String> {
509 self.vcs_svk()
510 }
511}
512
513impl VcsSource for debian_control::apt::Source {
514 fn vcs_git(&self) -> Option<String> {
515 self.vcs_git()
516 }
517
518 fn vcs_svn(&self) -> Option<String> {
519 self.vcs_svn()
520 }
521
522 fn vcs_bzr(&self) -> Option<String> {
523 self.vcs_bzr()
524 }
525
526 fn vcs_hg(&self) -> Option<String> {
527 self.vcs_hg()
528 }
529
530 fn vcs_mtn(&self) -> Option<String> {
531 self.vcs_mtn()
532 }
533
534 fn vcs_cvs(&self) -> Option<String> {
535 self.vcs_cvs()
536 }
537
538 fn vcs_darcs(&self) -> Option<String> {
539 self.vcs_darcs()
540 }
541
542 fn vcs_arch(&self) -> Option<String> {
543 self.vcs_arch()
544 }
545
546 fn vcs_svk(&self) -> Option<String> {
547 self.vcs_svk()
548 }
549}
550
551pub fn vcs_field(source_package: &impl VcsSource) -> Option<(String, String)> {
553 if let Some(value) = source_package.vcs_git() {
554 return Some(("Git".to_string(), value));
555 }
556 if let Some(value) = source_package.vcs_svn() {
557 return Some(("Svn".to_string(), value));
558 }
559 if let Some(value) = source_package.vcs_bzr() {
560 return Some(("Bzr".to_string(), value));
561 }
562 if let Some(value) = source_package.vcs_hg() {
563 return Some(("Hg".to_string(), value));
564 }
565 if let Some(value) = source_package.vcs_mtn() {
566 return Some(("Mtn".to_string(), value));
567 }
568 if let Some(value) = source_package.vcs_cvs() {
569 return Some(("Cvs".to_string(), value));
570 }
571 if let Some(value) = source_package.vcs_darcs() {
572 return Some(("Darcs".to_string(), value));
573 }
574 if let Some(value) = source_package.vcs_arch() {
575 return Some(("Arch".to_string(), value));
576 }
577 if let Some(value) = source_package.vcs_svk() {
578 return Some(("Svk".to_string(), value));
579 }
580 None
581}
582
583pub fn source_package_vcs(source_package: &impl VcsSource) -> Option<PackageVcs> {
585 if let Some(value) = source_package.vcs_git() {
586 let parsed_vcs: ParsedVcs = value.parse().unwrap();
587 let url = parsed_vcs.repo_url.parse().unwrap();
588 return Some(PackageVcs::Git {
589 url,
590 branch: parsed_vcs.branch,
591 subpath: parsed_vcs.subpath.map(std::path::PathBuf::from),
592 });
593 }
594 if let Some(value) = source_package.vcs_svn() {
595 let url = value.parse().unwrap();
596 return Some(PackageVcs::Svn(url));
597 }
598 if let Some(value) = source_package.vcs_bzr() {
599 let url = value.parse().unwrap();
600 return Some(PackageVcs::Bzr(url));
601 }
602 if let Some(value) = source_package.vcs_hg() {
603 let parsed_vcs: ParsedVcs = value.parse().unwrap();
604 let url = parsed_vcs.repo_url.parse().unwrap();
605 return Some(PackageVcs::Hg {
606 url,
607 branch: parsed_vcs.branch,
608 subpath: parsed_vcs.subpath.map(std::path::PathBuf::from),
609 });
610 }
611 if let Some(value) = source_package.vcs_mtn() {
612 let url = value.parse().unwrap();
613 return Some(PackageVcs::Mtn(url));
614 }
615 if let Some(value) = source_package.vcs_cvs() {
616 return Some(PackageVcs::Cvs(value.clone()));
617 }
618 if let Some(value) = source_package.vcs_darcs() {
619 let url = value.parse().unwrap();
620 return Some(PackageVcs::Darcs(url));
621 }
622 if let Some(value) = source_package.vcs_arch() {
623 let url = value.parse().unwrap();
624 return Some(PackageVcs::Arch(url));
625 }
626 if let Some(value) = source_package.vcs_svk() {
627 let url = value.parse().unwrap();
628 return Some(PackageVcs::Svk(url));
629 }
630 None
631}
632
633#[cfg(test)]
634mod tests {
635 #[test]
636 fn test_source_package_vcs() {
637 use super::PackageVcs;
638 use debian_control::Control;
639
640 let control: Control = r#"Source: foo
641Vcs-Git: https://salsa.debian.org/foo/bar.git
642"#
643 .parse()
644 .unwrap();
645 assert_eq!(
646 super::source_package_vcs(&control.source().unwrap()),
647 Some(PackageVcs::Git {
648 url: "https://salsa.debian.org/foo/bar.git".parse().unwrap(),
649 branch: None,
650 subpath: None
651 })
652 );
653
654 let control: Control = r#"Source: foo
655Vcs-Svn: https://svn.debian.org/svn/foo/bar
656"#
657 .parse()
658 .unwrap();
659 assert_eq!(
660 super::source_package_vcs(&control.source().unwrap()),
661 Some(PackageVcs::Svn(
662 "https://svn.debian.org/svn/foo/bar".parse().unwrap()
663 ))
664 );
665 }
666
667 #[test]
668 fn test_determine_gitlab_browser_url() {
669 use super::determine_gitlab_browser_url;
670
671 assert_eq!(
672 determine_gitlab_browser_url("https://salsa.debian.org/foo/bar"),
673 "https://salsa.debian.org/foo/bar".parse().unwrap()
674 );
675
676 assert_eq!(
677 determine_gitlab_browser_url("https://salsa.debian.org/foo/bar.git"),
678 "https://salsa.debian.org/foo/bar".parse().unwrap()
679 );
680
681 assert_eq!(
682 determine_gitlab_browser_url("https://salsa.debian.org/foo/bar/"),
683 "https://salsa.debian.org/foo/bar".parse().unwrap()
684 );
685
686 assert_eq!(
687 determine_gitlab_browser_url("https://salsa.debian.org/foo/bar/.git"),
688 "https://salsa.debian.org/foo/bar/".parse().unwrap()
689 );
690
691 assert_eq!(
692 determine_gitlab_browser_url("https://salsa.debian.org/foo/bar.git -b baz"),
693 "https://salsa.debian.org/foo/bar/-/tree/baz"
694 .parse()
695 .unwrap()
696 );
697
698 assert_eq!(
699 determine_gitlab_browser_url(
700 "https://salsa.debian.org/foo/bar.git/ -b baz [otherpath]"
701 ),
702 "https://salsa.debian.org/foo/bar/-/tree/baz/otherpath"
703 .parse()
704 .unwrap()
705 );
706 }
707
708 #[test]
709 fn test_determine_browser_url() {
710 use super::determine_browser_url;
711 use url::Url;
712
713 assert_eq!(
714 determine_browser_url("git", "https://salsa.debian.org/foo/bar", Some(false)),
715 Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap())
716 );
717 assert_eq!(
718 determine_browser_url("git", "https://salsa.debian.org/foo/bar.git", Some(false)),
719 Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap())
720 );
721 assert_eq!(
722 determine_browser_url("git", "https://salsa.debian.org/foo/bar/", Some(false)),
723 Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap())
724 );
725 assert_eq!(
726 determine_browser_url("git", "https://salsa.debian.org/foo/bar/.git", Some(false)),
727 Some(Url::parse("https://salsa.debian.org/foo/bar/").unwrap())
728 );
729 assert_eq!(
730 determine_browser_url("git", "https://salsa.debian.org/foo/bar.git/", Some(false)),
731 Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap())
732 );
733 assert_eq!(
734 determine_browser_url(
735 "git",
736 "https://salsa.debian.org/foo/bar.git/.git",
737 Some(false)
738 ),
739 Some(Url::parse("https://salsa.debian.org/foo/bar.git/").unwrap())
740 );
741 assert_eq!(
742 determine_browser_url(
743 "git",
744 "https://salsa.debian.org/foo/bar.git.git",
745 Some(false)
746 ),
747 Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap())
748 );
749 assert_eq!(
750 determine_browser_url(
751 "git",
752 "https://salsa.debian.org/foo/bar.git.git/",
753 Some(false)
754 ),
755 Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap())
756 );
757
758 assert_eq!(
759 Some(Url::parse("https://salsa.debian.org/jelmer/dulwich").unwrap()),
760 determine_browser_url(
761 "git",
762 "https://salsa.debian.org/jelmer/dulwich.git",
763 Some(false)
764 )
765 );
766
767 assert_eq!(
768 Some(Url::parse("https://github.com/jelmer/dulwich").unwrap()),
769 determine_browser_url("git", "https://github.com/jelmer/dulwich.git", Some(false))
770 );
771 assert_eq!(
772 Some(Url::parse("https://github.com/jelmer/dulwich/tree/master").unwrap()),
773 determine_browser_url(
774 "git",
775 "https://github.com/jelmer/dulwich.git -b master",
776 Some(false)
777 )
778 );
779 assert_eq!(
780 Some(Url::parse("https://github.com/jelmer/dulwich/tree/master").unwrap()),
781 determine_browser_url(
782 "git",
783 "git://github.com/jelmer/dulwich -b master",
784 Some(false)
785 ),
786 );
787 assert_eq!(
788 Some(Url::parse("https://github.com/jelmer/dulwich/tree/master/blah").unwrap()),
789 determine_browser_url(
790 "git",
791 "git://github.com/jelmer/dulwich -b master [blah]",
792 Some(false)
793 ),
794 );
795 assert_eq!(
796 Some(Url::parse("https://github.com/jelmer/dulwich/tree/HEAD/blah").unwrap()),
797 determine_browser_url("git", "git://github.com/jelmer/dulwich [blah]", Some(false)),
798 );
799 assert_eq!(
800 Some(Url::parse("https://git.sv.gnu.org/cgit/rcs.git").unwrap()),
801 determine_browser_url("git", "https://git.sv.gnu.org/git/rcs.git", Some(false)),
802 );
803 assert_eq!(
804 Some(Url::parse("https://git.savannah.gnu.org/cgit/rcs.git").unwrap()),
805 determine_browser_url("git", "git://git.savannah.gnu.org/rcs.git", Some(false)),
806 );
807 assert_eq!(
808 Some(Url::parse("https://sourceforge.net/p/shorewall/debian").unwrap()),
809 determine_browser_url(
810 "git",
811 "git://git.code.sf.net/p/shorewall/debian",
812 Some(false)
813 ),
814 );
815 assert_eq!(
816 Some(Url::parse("https://sourceforge.net/p/shorewall/debian/ci/foo/tree").unwrap()),
817 determine_browser_url(
818 "git",
819 "git://git.code.sf.net/p/shorewall/debian -b foo",
820 Some(false)
821 ),
822 );
823 assert_eq!(
824 Some(Url::parse("https://sourceforge.net/p/shorewall/debian/ci/HEAD/tree/sp").unwrap()),
825 determine_browser_url(
826 "git",
827 "git://git.code.sf.net/p/shorewall/debian [sp]",
828 Some(false)
829 ),
830 );
831 assert_eq!(
832 Some(Url::parse("https://sourceforge.net/p/shorewall/debian/ci/foo/tree/sp").unwrap()),
833 determine_browser_url(
834 "git",
835 "git://git.code.sf.net/p/shorewall/debian -b foo [sp]",
836 Some(false)
837 ),
838 );
839 }
840
841 #[test]
842 fn test_vcs_field() {
843 use debian_control::Control;
844
845 let control: Control = r#"Source: foo
846Vcs-Git: https://salsa.debian.org/foo/bar.git
847"#
848 .parse()
849 .unwrap();
850 assert_eq!(
851 super::vcs_field(&control.source().unwrap()),
852 Some((
853 "Git".to_string(),
854 "https://salsa.debian.org/foo/bar.git".to_string()
855 ))
856 );
857 }
858}