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