1use crate::release_info;
3use breezyshim::error::Error;
4use breezyshim::prelude::*;
5use breezyshim::tree::TreeChange;
6use debian_changelog::ChangeLog;
7
8pub fn only_changes_last_changelog_block<'a>(
15 tree: &dyn WorkingTree,
16 basis_tree: &dyn Tree,
17 changelog_path: &std::path::Path,
18 changes: impl Iterator<Item = &'a TreeChange>,
19) -> Result<bool, debian_changelog::Error> {
20 let read_lock = tree.lock_read();
21 let basis_lock = basis_tree.lock_read();
22 let mut changes_seen = false;
23 for change in changes {
24 if let Some(path) = change.path.1.as_ref() {
25 if path == std::path::Path::new("") {
26 continue;
27 }
28 if path == changelog_path {
29 changes_seen = true;
30 continue;
31 }
32 if !tree.has_versioned_directories() && changelog_path.starts_with(path) {
33 continue;
35 }
36 }
37 return Ok(false);
39 }
40
41 if !changes_seen {
42 return Ok(false);
44 }
45 let mut new_cl = match tree.get_file(changelog_path) {
49 Ok(f) => ChangeLog::read_relaxed(f)?,
50 Err(Error::NoSuchFile(_)) => {
51 return Ok(false);
52 }
53 Err(e) => {
54 panic!("Error reading changelog: {}", e);
55 }
56 };
57 let mut old_cl = match basis_tree.get_file(changelog_path) {
58 Ok(f) => ChangeLog::read_relaxed(f)?,
59 Err(Error::NoSuchFile(_)) => {
60 return Ok(true);
61 }
62 Err(e) => {
63 panic!("Error reading changelog: {}", e);
64 }
65 };
66 let first_entry = if let Some(e) = new_cl.pop_first() {
67 e
68 } else {
69 return Ok(false);
71 };
72 if first_entry.distributions().as_deref() != Some(&["UNRELEASED".into()]) {
73 return Ok(false);
75 }
76 old_cl.pop_first();
77 std::mem::drop(read_lock);
78 std::mem::drop(basis_lock);
79 Ok(new_cl.to_string() == old_cl.to_string())
80}
81
82pub fn find_last_distribution(cl: &ChangeLog) -> Option<String> {
84 for block in cl.iter() {
85 if block.is_unreleased() != Some(true) {
86 if let Some(distributions) = block.distributions() {
87 if distributions.len() == 1 {
88 return Some(distributions[0].to_string());
89 }
90 }
91 }
92 }
93 None
94}
95
96pub fn find_previous_upload(changelog: &ChangeLog) -> Option<debversion::Version> {
112 let current_target = find_last_distribution(changelog)?;
113 let all_debian = crate::release_info::debian_releases()
115 .iter()
116 .flat_map(|r| {
117 release_info::DEBIAN_POCKETS
118 .iter()
119 .map(move |t| format!("{}{}", r, t))
120 })
121 .collect::<Vec<_>>();
122 let all_ubuntu = crate::release_info::ubuntu_releases()
123 .iter()
124 .flat_map(|r| {
125 release_info::UBUNTU_POCKETS
126 .iter()
127 .map(move |t| format!("{}{}", r, t))
128 })
129 .collect::<Vec<_>>();
130 let match_targets = if all_debian.contains(¤t_target) {
131 vec![current_target]
132 } else if all_ubuntu.contains(¤t_target) {
133 let mut match_targets = crate::release_info::ubuntu_releases();
134 if current_target.contains('-') {
135 let distro = current_target.split('-').next().unwrap();
136 match_targets.extend(
137 release_info::DEBIAN_POCKETS
138 .iter()
139 .map(|r| format!("{}{}", r, distro)),
140 );
141 }
142 match_targets
143 } else {
144 vec![current_target]
148 };
149 for block in changelog.iter().skip(1) {
150 if match_targets.contains(&block.distributions().unwrap()[0]) {
151 return block.version().clone();
152 }
153 }
154
155 None
156}
157
158#[derive(Debug)]
159pub enum FindChangelogError {
161 MissingChangelog(Vec<std::path::PathBuf>),
163
164 AddChangelog(std::path::PathBuf),
166
167 ChangelogParseError(String),
169
170 BrzError(breezyshim::error::Error),
172}
173
174impl std::fmt::Display for FindChangelogError {
175 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
176 match self {
177 FindChangelogError::MissingChangelog(files) => {
178 write!(f, "No changelog found in {:?}", files)
179 }
180 FindChangelogError::AddChangelog(file) => {
181 write!(f, "Add a changelog at {:?}", file)
182 }
183 FindChangelogError::ChangelogParseError(e) => write!(f, "{}", e),
184 FindChangelogError::BrzError(e) => write!(f, "{}", e),
185 }
186 }
187}
188
189impl std::error::Error for FindChangelogError {}
190
191impl From<breezyshim::error::Error> for FindChangelogError {
192 fn from(e: breezyshim::error::Error) -> Self {
193 FindChangelogError::BrzError(e)
194 }
195}
196
197pub fn find_changelog(
222 tree: &dyn Tree,
223 subpath: &std::path::Path,
224 merge: Option<bool>,
225) -> Result<(ChangeLog, bool), FindChangelogError> {
226 let mut top_level = false;
227 let lock = tree.lock_read();
228
229 let mut changelog_file = subpath.join("debian/changelog");
230 if !tree.has_filename(&changelog_file) {
231 let mut checked_files = vec![changelog_file.to_path_buf()];
232 let changelog_file = if merge.unwrap_or(false) {
233 let changelog_file = subpath.join("changelog");
235 top_level = true;
236 if !tree.has_filename(&changelog_file) {
237 checked_files.push(changelog_file);
238 None
239 } else {
240 Some(changelog_file)
241 }
242 } else {
243 None
244 };
245 if changelog_file.is_none() {
246 return Err(FindChangelogError::MissingChangelog(checked_files));
247 }
248 } else if merge.unwrap_or(true) && tree.has_filename(&subpath.join("changelog")) {
249 let debian_file = subpath.join("debian");
253 if tree.is_versioned(&debian_file)
254 && tree.kind(&debian_file)? == breezyshim::tree::Kind::Symlink
255 && tree.get_symlink_target(&debian_file)?.as_path() == std::path::Path::new(".")
256 {
257 changelog_file = "changelog".into();
258 top_level = true;
259 }
260 }
261 log::debug!(
262 "Using '{}' to get package information",
263 changelog_file.display()
264 );
265 if !tree.is_versioned(&changelog_file) {
266 return Err(FindChangelogError::AddChangelog(changelog_file));
267 }
268 let contents = tree.get_file_text(&changelog_file)?;
269 std::mem::drop(lock);
270 let changelog = ChangeLog::read_relaxed(contents.as_slice()).unwrap();
271 Ok((changelog, top_level))
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277 use breezyshim::workingtree::GenericWorkingTree;
278 pub const COMMITTER: &str = "Test User <example@example.com>";
279 #[test]
280 fn test_find_previous_upload() {
281 let cl = r#"test (1.0-1) unstable; urgency=medium
282
283 * Initial release.
284
285 -- Test User <test@user.example.com> Fri, 01 Jan 2021 00:00:00 +0000
286"#
287 .parse()
288 .unwrap();
289 assert_eq!(super::find_previous_upload(&cl), None);
290
291 let cl = r#"test (1.0-1) unstable; urgency=medium
292
293 * More change.
294
295 -- Test User <test@user.example.com> Fri, 01 Jan 2021 00:00:00 +0000
296
297test (1.0-0) unstable; urgency=medium
298
299 * Initial release.
300
301 -- Test User <test@example.com> Fri, 01 Jan 2021 00:00:00 +0000
302"#
303 .parse()
304 .unwrap();
305 assert_eq!(
306 super::find_previous_upload(&cl),
307 Some("1.0-0".parse().unwrap())
308 );
309 }
310
311 mod test_only_changes_last_changelog_block {
312 use super::*;
313 use breezyshim::controldir::{create_standalone_workingtree, ControlDirFormat};
314 use breezyshim::tree::Path;
315 fn make_package_tree(p: &std::path::Path) -> GenericWorkingTree {
316 let tree = create_standalone_workingtree(p, &ControlDirFormat::default()).unwrap();
317 std::fs::create_dir_all(p.join("debian")).unwrap();
318
319 std::fs::write(
320 p.join("debian/control"),
321 r###"Source: blah
322Vcs-Git: https://example.com/blah
323Testsuite: autopkgtest
324
325Binary: blah
326Arch: all
327
328"###,
329 )
330 .unwrap();
331 std::fs::write(
332 p.join("debian/changelog"),
333 r###"blah (0.2) UNRELEASED; urgency=medium
334
335 * And a change.
336
337 -- Blah <example@debian.org> Sat, 13 Oct 2018 11:21:39 +0100
338
339blah (0.1) unstable; urgency=medium
340
341 * Initial release. (Closes: #911016)
342
343 -- Blah <example@debian.org> Sat, 13 Oct 2018 11:21:39 +0100
344"###,
345 )
346 .unwrap();
347 tree.add(&[
348 Path::new("debian"),
349 Path::new("debian/changelog"),
350 Path::new("debian/control"),
351 ])
352 .unwrap();
353 tree.build_commit()
354 .message("Initial thingy.")
355 .committer(COMMITTER)
356 .commit()
357 .unwrap();
358 tree
359 }
360
361 #[test]
362 fn test_no_changes() {
363 let td = tempfile::tempdir().unwrap();
364 let tree = make_package_tree(td.path());
365 let basis_tree = tree.basis_tree().unwrap();
366 let lock_read = tree.lock_read();
367 let basis_lock_read = basis_tree.lock_read();
368 let changes = tree
369 .iter_changes(&basis_tree, None, None, None)
370 .unwrap()
371 .collect::<Result<Vec<_>, _>>()
372 .unwrap();
373 assert!(!only_changes_last_changelog_block(
374 &tree,
375 &tree.basis_tree().unwrap(),
376 Path::new("debian/changelog"),
377 changes.iter()
378 )
379 .unwrap());
380 std::mem::drop(basis_lock_read);
381 std::mem::drop(lock_read);
382 }
383
384 #[test]
385 fn test_other_change() {
386 let td = tempfile::tempdir().unwrap();
387 let tree = make_package_tree(td.path());
388 std::fs::write(
389 td.path().join("debian/control"),
390 r###"Source: blah
391Vcs-Bzr: https://example.com/blah
392Testsuite: autopkgtest
393
394Binary: blah
395Arch: all
396"###,
397 )
398 .unwrap();
399 let basis_tree = tree.basis_tree().unwrap();
400 let lock_read = tree.lock_read();
401 let basis_lock_read = basis_tree.lock_read();
402 let changes = tree
403 .iter_changes(&basis_tree, None, None, None)
404 .unwrap()
405 .collect::<Result<Vec<_>, _>>()
406 .unwrap();
407 assert!(!only_changes_last_changelog_block(
408 &tree,
409 &tree.basis_tree().unwrap(),
410 Path::new("debian/changelog"),
411 changes.iter()
412 )
413 .unwrap());
414 std::mem::drop(basis_lock_read);
415 std::mem::drop(lock_read);
416 }
417
418 #[test]
419 fn test_other_changes() {
420 let td = tempfile::tempdir().unwrap();
421 let tree = make_package_tree(td.path());
422 std::fs::write(
423 td.path().join("debian/control"),
424 r###"Source: blah
425Vcs-Bzr: https://example.com/blah
426Testsuite: autopkgtest
427
428Binary: blah
429Arch: all
430
431"###,
432 )
433 .unwrap();
434 std::fs::write(
435 td.path().join("debian/changelog"),
436 r###"blah (0.1) UNRELEASED; urgency=medium
437
438 * Initial release. (Closes: #911016)
439 * Some other change.
440
441 -- Blah <example@debian.org> Sat, 13 Oct 2018 11:21:39 +0100
442"###,
443 )
444 .unwrap();
445 let basis_tree = tree.basis_tree().unwrap();
446 let lock_read = tree.lock_read();
447 let basis_lock_read = basis_tree.lock_read();
448 let changes = tree
449 .iter_changes(&basis_tree, None, None, None)
450 .unwrap()
451 .collect::<Result<Vec<_>, _>>()
452 .unwrap();
453 assert!(!only_changes_last_changelog_block(
454 &tree,
455 &tree.basis_tree().unwrap(),
456 Path::new("debian/changelog"),
457 changes.iter()
458 )
459 .unwrap());
460 std::mem::drop(basis_lock_read);
461 std::mem::drop(lock_read);
462 }
463
464 #[test]
465 fn test_changes_to_other_changelog_entries() {
466 let td = tempfile::tempdir().unwrap();
467 let tree = make_package_tree(td.path());
468 std::fs::write(
469 td.path().join("debian/changelog"),
470 r###"blah (0.2) UNRELEASED; urgency=medium
471
472 * debian/changelog: And a change.
473
474 -- Blah <example@debian.org> Sat, 13 Oct 2018 11:21:39 +0100
475
476blah (0.1) unstable; urgency=medium
477
478 * debian/changelog: Initial release. (Closes: #911016)
479
480 -- Blah <example@debian.org> Sat, 13 Oct 2018 11:21:39 +0100
481"###,
482 )
483 .unwrap();
484 let basis_tree = tree.basis_tree().unwrap();
485 let lock_read = tree.lock_read();
486 let basis_lock_read = basis_tree.lock_read();
487 let changes = tree
488 .iter_changes(&basis_tree, None, None, None)
489 .unwrap()
490 .collect::<Result<Vec<_>, _>>()
491 .unwrap();
492 assert!(!only_changes_last_changelog_block(
493 &tree,
494 &tree.basis_tree().unwrap(),
495 Path::new("debian/changelog"),
496 changes.iter()
497 )
498 .unwrap());
499 std::mem::drop(basis_lock_read);
500 std::mem::drop(lock_read);
501 }
502
503 #[test]
504 fn test_changes_to_last_only() {
505 let td = tempfile::tempdir().unwrap();
506 let tree = make_package_tree(td.path());
507 std::fs::write(
508 td.path().join("debian/changelog"),
509 r###"blah (0.2) UNRELEASED; urgency=medium
510
511 * And a change.
512 * Not a team upload.
513
514 -- Blah <example@debian.org> Sat, 13 Oct 2018 11:21:39 +0100
515
516blah (0.1) unstable; urgency=medium
517
518 * Initial release. (Closes: #911016)
519
520 -- Blah <example@debian.org> Sat, 13 Oct 2018 11:21:39 +0100
521"###,
522 )
523 .unwrap();
524 let basis_tree = tree.basis_tree().unwrap();
525 let lock_read = tree.lock_read();
526 let basis_lock_read = basis_tree.lock_read();
527 let changes = tree
528 .iter_changes(&basis_tree, None, None, None)
529 .unwrap()
530 .collect::<Result<Vec<_>, _>>()
531 .unwrap();
532 assert!(only_changes_last_changelog_block(
533 &tree,
534 &tree.basis_tree().unwrap(),
535 Path::new("debian/changelog"),
536 changes.iter()
537 )
538 .unwrap());
539 std::mem::drop(basis_lock_read);
540 std::mem::drop(lock_read);
541 }
542
543 #[test]
544 fn test_only_new_changelog() {
545 use breezyshim::tree::MutableTree;
546 let td = tempfile::tempdir().unwrap();
547 let tree = create_standalone_workingtree(td.path(), "git").unwrap();
548 let lock_write = tree.lock_write();
549 std::fs::create_dir_all(td.path().join("debian")).unwrap();
550 std::fs::write(
551 td.path().join("debian/changelog"),
552 r###"blah (0.1) unstable; urgency=medium
553
554 * Initial release. (Closes: #911016)
555
556 -- Blah <example@debian.org> Sat, 13 Oct 2018 11:21:39 +0100
557"###,
558 )
559 .unwrap();
560 let basis_tree = tree.basis_tree().unwrap();
561 let lock_read = tree.lock_read();
562 let basis_lock_read = basis_tree.lock_read();
563 tree.add(&[Path::new("debian"), Path::new("debian/changelog")])
564 .unwrap();
565 let changes = tree
566 .iter_changes(&basis_tree, None, None, None)
567 .unwrap()
568 .collect::<Result<Vec<_>, _>>()
569 .unwrap();
570 assert!(only_changes_last_changelog_block(
571 &tree,
572 &tree.basis_tree().unwrap(),
573 Path::new("debian/changelog"),
574 changes.iter()
575 )
576 .unwrap());
577 std::mem::drop(basis_lock_read);
578 std::mem::drop(lock_read);
579 std::mem::drop(lock_write);
580 }
581
582 #[test]
583 fn test_changes_to_last_only_but_released() {
584 let td = tempfile::tempdir().unwrap();
585 let tree = make_package_tree(td.path());
586 std::fs::write(
587 td.path().join("debian/changelog"),
588 r###"blah (0.2) unstable; urgency=medium
589
590 * And a change.
591
592 -- Blah <example@debian.org> Sat, 13 Oct 2018 11:21:39 +0100
593
594blah (0.1) unstable; urgency=medium
595
596 * Initial release. (Closes: #911016)
597
598 -- Blah <example@debian.org> Sat, 13 Oct 2018 11:21:39 +0100
599"###,
600 )
601 .unwrap();
602 tree.build_commit()
603 .message("release")
604 .committer(COMMITTER)
605 .commit()
606 .unwrap();
607 std::fs::write(
608 td.path().join("debian/changelog"),
609 r###"blah (0.2) unstable; urgency=medium
610
611 * And a change.
612 * Team Upload.
613
614 -- Blah <example@debian.org> Sat, 13 Oct 2018 11:21:39 +0100
615
616blah (0.1) unstable; urgency=medium
617
618 * Initial release. (Closes: #911016)
619
620 -- Blah <example@debian.org> Sat, 13 Oct 2018 11:21:39 +0100
621"###,
622 )
623 .unwrap();
624 let basis_tree = tree.basis_tree().unwrap();
625 let lock_read = tree.lock_read();
626 let basis_lock_read = basis_tree.lock_read();
627 let changes = tree
628 .iter_changes(&basis_tree, None, None, None)
629 .unwrap()
630 .collect::<Result<Vec<_>, _>>()
631 .unwrap();
632
633 assert!(!only_changes_last_changelog_block(
634 &tree,
635 &tree.basis_tree().unwrap(),
636 Path::new("debian/changelog"),
637 changes.iter()
638 )
639 .unwrap());
640 std::mem::drop(basis_lock_read);
641 std::mem::drop(lock_read);
642 }
643
644 #[test]
645 fn test_changes_to_last_only_with_old_style_trailing_entries() {
646 let td = tempfile::tempdir().unwrap();
651 let tree =
652 create_standalone_workingtree(td.path(), &ControlDirFormat::default()).unwrap();
653 std::fs::create_dir_all(td.path().join("debian")).unwrap();
654 let changelog = r###"blah (0.2) UNRELEASED; urgency=medium
655
656 * And a change.
657
658 -- Blah <example@debian.org> Sat, 13 Oct 2018 11:21:39 +0100
659
660blah (0.1) unstable; urgency=medium
661
662 * Initial release. (Closes: #911016)
663
664 -- Blah <example@debian.org> Sat, 13 Oct 2018 11:21:39 +0100
665
6660.0.1-1:
667 19940101
668 * Old-style changelog entry.
669"###;
670 std::fs::write(td.path().join("debian/changelog"), changelog).unwrap();
671 tree.add(&[Path::new("debian"), Path::new("debian/changelog")])
672 .unwrap();
673 tree.build_commit()
674 .message("Initial thingy.")
675 .committer(COMMITTER)
676 .commit()
677 .unwrap();
678 std::fs::write(
679 td.path().join("debian/changelog"),
680 changelog.replace(
681 " * And a change.\n",
682 " * And a change.\n * Another change.\n",
683 ),
684 )
685 .unwrap();
686 let basis_tree = tree.basis_tree().unwrap();
687 let lock_read = tree.lock_read();
688 let basis_lock_read = basis_tree.lock_read();
689 let changes = tree
690 .iter_changes(&basis_tree, None, None, None)
691 .unwrap()
692 .collect::<Result<Vec<_>, _>>()
693 .unwrap();
694 assert!(only_changes_last_changelog_block(
695 &tree,
696 &tree.basis_tree().unwrap(),
697 Path::new("debian/changelog"),
698 changes.iter()
699 )
700 .unwrap());
701 std::mem::drop(basis_lock_read);
702 std::mem::drop(lock_read);
703 }
704 }
705}