1use breezyshim::delta::filter_excluded;
3use breezyshim::error::Error as BrzError;
4use breezyshim::patches::AppliedPatches;
5use breezyshim::prelude::*;
6use breezyshim::tree::{PyTree, Tree};
7use breezyshim::workingtree::{PyWorkingTree, WorkingTree};
8use breezyshim::workspace::reset_tree_with_dirty_tracker;
9use breezyshim::RevisionId;
10use debian_changelog::ChangeLog;
11use patchkit::quilt::QuiltPatch;
12use patchkit::unified::UnifiedPatch;
13use std::io::Write;
14use std::path::{Path, PathBuf};
15
16pub const DEFAULT_DEBIAN_PATCHES_DIR: &str = "debian/patches";
19
20pub fn tree_patches_directory(tree: &dyn PyTree, subpath: &Path) -> PathBuf {
33 find_patches_directory(tree, subpath).unwrap_or(DEFAULT_DEBIAN_PATCHES_DIR.into())
34}
35
36#[cfg(test)]
37mod tree_patches_directory_tests {
38 use super::*;
39
40 #[test]
41 fn test_simple() {
42 let td = tempfile::tempdir().unwrap();
43 let local_tree = breezyshim::controldir::create_standalone_workingtree(
44 td.path(),
45 &breezyshim::controldir::ControlDirFormat::default(),
46 )
47 .unwrap();
48 assert_eq!(
49 super::tree_patches_directory(&local_tree, std::path::Path::new("")),
50 std::path::Path::new("debian/patches")
51 );
52 }
53
54 #[test]
55 fn test_default() {
56 let td = tempfile::tempdir().unwrap();
57 let local_tree = breezyshim::controldir::create_standalone_workingtree(
58 td.path(),
59 &breezyshim::controldir::ControlDirFormat::default(),
60 )
61 .unwrap();
62 local_tree.mkdir(std::path::Path::new("debian")).unwrap();
63 local_tree
64 .mkdir(std::path::Path::new("debian/patches"))
65 .unwrap();
66 assert_eq!(
67 super::tree_patches_directory(&local_tree, std::path::Path::new("")),
68 std::path::Path::new("debian/patches")
69 );
70 }
71
72 #[test]
73 fn test_custom() {
74 let td = tempfile::tempdir().unwrap();
75 let local_tree = breezyshim::controldir::create_standalone_workingtree(
76 td.path(),
77 &breezyshim::controldir::ControlDirFormat::default(),
78 )
79 .unwrap();
80 local_tree.mkdir(std::path::Path::new("debian")).unwrap();
81 local_tree
82 .mkdir(std::path::Path::new("debian/patches"))
83 .unwrap();
84 local_tree
85 .put_file_bytes_non_atomic(
86 std::path::Path::new("debian/rules"),
87 br#"
88QUILT_PATCH_DIR := debian/patches-applied
89
90all:
91
92blah: bloe
93 foo
94
95"#,
96 )
97 .unwrap();
98 assert_eq!(
99 super::tree_patches_directory(&local_tree, std::path::Path::new("")),
100 std::path::Path::new("debian/patches-applied")
101 );
102 }
103}
104
105pub fn rules_find_patches_directory(mf: &makefile_lossless::Makefile) -> Option<PathBuf> {
107 mf.variable_definitions()
108 .find(|v| v.name().as_deref() == Some("QUILT_PATCH_DIR"))?
109 .raw_value()
110 .map(PathBuf::from)
111}
112
113#[test]
114fn test_rules_find_patches_directory() {
115 let mf = makefile_lossless::Makefile::read_relaxed(
116 &br#"QUILT_PATCH_DIR := debian/patches-applied
117"#[..],
118 )
119 .unwrap();
120 assert_eq!(
121 rules_find_patches_directory(&mf),
122 Some(PathBuf::from("debian/patches-applied"))
123 );
124}
125
126pub fn find_patches_directory(tree: &dyn PyTree, subpath: &Path) -> Option<PathBuf> {
128 let rules_path = subpath.join("debian/rules");
129
130 let rules_file = match tree.get_file(&rules_path) {
131 Ok(f) => Some(f),
132 Err(BrzError::NoSuchFile(_)) => None,
133 Err(e) => {
134 log::warn!("Failed to read {}: {}", rules_path.display(), e);
135 None
136 }
137 };
138
139 if let Some(rules_file) = rules_file {
140 let mf_patch_dir = match makefile_lossless::Makefile::read_relaxed(rules_file) {
141 Ok(mf) => rules_find_patches_directory(&mf).or_else(|| {
142 log::debug!("No QUILT_PATCH_DIR in {}", rules_path.display());
143 None
144 }),
145 Err(e) => {
146 log::warn!("Failed to parse {}: {}", rules_path.display(), e);
147 None
148 }
149 };
150
151 if let Some(mf_patch_dir) = mf_patch_dir {
152 return Some(mf_patch_dir);
153 }
154 }
155
156 if tree.has_filename(Path::new(DEFAULT_DEBIAN_PATCHES_DIR)) {
157 return Some(DEFAULT_DEBIAN_PATCHES_DIR.into());
158 }
159
160 None
161}
162
163pub fn find_patch_base(tree: &dyn WorkingTree) -> Option<RevisionId> {
167 let f = match tree.get_file(std::path::Path::new("debian/changelog")) {
168 Ok(f) => f,
169 Err(BrzError::NoSuchFile(_)) => return None,
170 Err(e) => {
171 log::warn!("Failed to read debian/changelog: {}", e);
172 return None;
173 }
174 };
175 let cl = match ChangeLog::read(f) {
176 Ok(cl) => cl,
177 Err(e) => {
178 log::warn!("Failed to parse debian/changelog: {}", e);
179 return None;
180 }
181 };
182 let entry = cl.iter().next()?;
183 let package = entry.package().unwrap();
184 let upstream_version = entry.version().unwrap().upstream_version;
185 let possible_tags = [
186 format!("upstream-{}", upstream_version),
187 format!("upstream/{}", upstream_version),
188 upstream_version.to_string(),
189 format!("v{}", upstream_version),
190 format!("{}-{}", package, upstream_version),
191 ];
192 let tags = tree.branch().tags().unwrap().get_tag_dict().unwrap();
193 possible_tags.iter().find_map(|tag| tags.get(tag).cloned())
194}
195
196#[cfg(test)]
197mod find_patch_base_tests {
198 const COMMITTER: &str = "Test Suite <test@suite.example.com>";
199 use super::*;
200 use breezyshim::tree::{MutableTree, WorkingTree};
201 use breezyshim::workingtree::GenericWorkingTree;
202 use breezyshim::RevisionId;
203
204 fn setup() -> (tempfile::TempDir, GenericWorkingTree, RevisionId) {
205 let td = tempfile::tempdir().unwrap();
206 let tree = breezyshim::controldir::create_standalone_workingtree(
207 td.path(),
208 &breezyshim::controldir::ControlDirFormat::default(),
209 )
210 .unwrap();
211 let upstream_revid = tree
212 .build_commit()
213 .message("upstream")
214 .committer(COMMITTER)
215 .commit()
216 .unwrap();
217 tree.mkdir(std::path::Path::new("debian")).unwrap();
218 std::fs::write(
219 td.path().join("debian/changelog"),
220 r#"blah (0.38) unstable; urgency=medium
221
222 * Fix something
223
224 -- Jelmer Vernooij <jelmer@debian.org> Sat, 19 Oct 2019 15:21:53 +0000
225"#,
226 )
227 .unwrap();
228 tree.add(&[std::path::Path::new("debian/changelog")])
229 .unwrap();
230 (td, tree, upstream_revid)
231 }
232
233 #[test]
234 fn test_none() {
235 let (td, tree, _upstream_revid) = setup();
236 assert_eq!(None, super::find_patch_base(&tree));
237 std::mem::drop(td);
238 }
239
240 #[test]
241 fn test_upstream_dash() {
242 let (td, tree, upstream_revid) = setup();
243 tree.branch()
244 .tags()
245 .unwrap()
246 .set_tag("upstream-0.38", &upstream_revid)
247 .unwrap();
248 let tags = tree.branch().tags().unwrap().get_tag_dict().unwrap();
249 assert_eq!(Some(&upstream_revid), tags.get("upstream-0.38"));
250 assert_eq!(Some(upstream_revid), super::find_patch_base(&tree));
251 std::mem::drop(td);
252 }
253}
254
255pub fn find_patches_branch(tree: &dyn WorkingTree) -> Option<Box<dyn Branch>> {
262 let local_branch_name = tree.branch().name()?;
263 let branch_name = format!("patch-queue/{}", local_branch_name);
264 match tree
265 .branch()
266 .controldir()
267 .open_branch(Some(branch_name.as_str()))
268 {
269 Ok(b) => return Some(b),
270 Err(BrzError::NotBranchError(..)) => {}
271 Err(e) => {
272 log::warn!("Failed to open branch {}: {}", branch_name, e);
273 }
274 }
275 let branch_name = if local_branch_name == "master" {
276 "patched".to_string()
277 } else {
278 format!("patched-{}", local_branch_name)
279 };
280 match tree
281 .branch()
282 .controldir()
283 .open_branch(Some(branch_name.as_str()))
284 {
285 Ok(b) => return Some(b),
286 Err(BrzError::NotBranchError(..)) => {}
287 Err(e) => {
288 log::warn!("Failed to open branch {}: {}", branch_name, e);
289 }
290 }
291 None
292}
293
294#[cfg(test)]
295mod find_patches_branch_tests {
296 use super::*;
297 use breezyshim::workingtree::{GenericWorkingTree, WorkingTree};
298
299 fn make_named_branch_and_tree(name: &str) -> (tempfile::TempDir, GenericWorkingTree) {
300 let td = tempfile::tempdir().unwrap();
301 let dir = breezyshim::controldir::create(
302 &url::Url::from_directory_path(td.path()).unwrap(),
303 &breezyshim::controldir::ControlDirFormat::default(),
304 None,
305 )
306 .unwrap();
307 dir.create_repository(None).unwrap();
308 let branch = dir.create_branch(Some(name)).unwrap();
309 dir.set_branch_reference(branch.as_ref(), None).unwrap();
310 let wt = dir.create_workingtree().unwrap();
311 (td, wt)
312 }
313
314 #[test]
315 fn test_none() {
316 let td = tempfile::tempdir().unwrap();
317 let local_tree = breezyshim::controldir::create_standalone_workingtree(
318 td.path(),
319 &breezyshim::controldir::ControlDirFormat::default(),
320 )
321 .unwrap();
322 assert!(super::find_patches_branch(&local_tree).is_none());
323 }
324
325 #[test]
326 fn test_patch_queue() {
327 let (td, master) = make_named_branch_and_tree("master");
328 master
329 .branch()
330 .controldir()
331 .create_branch(Some("patch-queue/master"))
332 .unwrap();
333
334 assert_eq!(
335 "patch-queue/master",
336 super::find_patches_branch(&master)
337 .unwrap()
338 .name()
339 .unwrap()
340 .as_str()
341 );
342
343 std::mem::drop(td);
344 }
345
346 #[test]
347 fn test_patched_master() {
348 let (td, master) = make_named_branch_and_tree("master");
349 master
350 .branch()
351 .controldir()
352 .create_branch(Some("patched"))
353 .unwrap();
354 assert_eq!(
355 "patched",
356 super::find_patches_branch(&master).unwrap().name().unwrap()
357 );
358 std::mem::drop(td);
359 }
360
361 #[test]
362 fn test_patched_other() {
363 let (td, other) = make_named_branch_and_tree("other");
364 other
365 .branch()
366 .controldir()
367 .create_branch(Some("patched-other"))
368 .unwrap();
369 assert_eq!(
370 "patched-other",
371 super::find_patches_branch(&other).unwrap().name().unwrap()
372 );
373 std::mem::drop(td);
374 }
375}
376
377pub fn add_patch(
389 tree: &dyn PyWorkingTree,
390 patches_directory: &Path,
391 name: &str,
392 contents: &[u8],
393 header: Option<dep3::lossless::PatchHeader>,
394) -> Result<(Vec<std::path::PathBuf>, String), String> {
395 if !tree.has_filename(patches_directory) {
396 let parent = patches_directory.parent().unwrap();
397 if !tree.has_filename(parent) {
398 tree.mkdir(parent)
399 .expect("Failed to create parent directory");
400 }
401 tree.mkdir(patches_directory).unwrap();
402 }
403 let series_path = patches_directory.join("series");
404 let mut series = match tree.get_file(&series_path) {
405 Ok(f) => patchkit::quilt::Series::read(f).unwrap(),
406 Err(BrzError::NoSuchFile(_)) => patchkit::quilt::Series::new(),
407 Err(e) => {
408 return Err(format!("Failed to read {}: {}", series_path.display(), e));
409 }
410 };
411
412 let patch_suffix =
413 patchkit::quilt::find_common_patch_suffix(series.patches()).unwrap_or(".patch");
414 let patchname = format!("{}{}", name, patch_suffix);
415 let path = patches_directory.join(patchname.as_str());
416 if tree.has_filename(path.as_path()) {
417 return Err(format!("Patch {} already exists", patchname));
418 }
419
420 let mut patch_contents = Vec::new();
421 if let Some(header) = header {
422 header.write(&mut patch_contents).unwrap();
423 }
424 patch_contents.write_all(b"---\n").unwrap();
425 patch_contents.write_all(contents).unwrap();
426 tree.put_file_bytes_non_atomic(&path, patch_contents.as_slice())
427 .map_err(|e| format!("Failed to write patch: {}", e))?;
428
429 series.append(patchname.as_str(), None);
432 let mut series_bytes = Vec::new();
433 series
434 .write(&mut series_bytes)
435 .map_err(|e| format!("Failed to write series: {}", e))?;
436 tree.put_file_bytes_non_atomic(&series_path, series_bytes.as_slice())
437 .map_err(|e| format!("Failed to write series: {}", e))?;
438 tree.add(&[series_path.as_path(), path.as_path()])
439 .map_err(|e| format!("Failed to add patch: {}", e))?;
440
441 let specific_files = vec![series_path, path];
442
443 Ok((specific_files, patchname))
444}
445
446pub fn move_upstream_changes_to_patch<T, U>(
456 local_tree: &T,
457 basis_tree: &U,
458 subpath: &std::path::Path,
459 patch_name: &str,
460 description: &str,
461 dirty_tracker: Option<&mut breezyshim::dirty_tracker::DirtyTreeTracker>,
462 timestamp: Option<chrono::NaiveDate>,
463) -> Result<(Vec<std::path::PathBuf>, String), String>
464where
465 T: PyWorkingTree,
466 U: PyTree,
467{
468 let timestamp = if let Some(timestamp) = timestamp {
469 timestamp
470 } else {
471 chrono::Utc::now().naive_utc().date()
472 };
473 let mut diff = Vec::new();
474 breezyshim::diff::show_diff_trees(basis_tree, local_tree, &mut diff, None, None)
475 .map_err(|e| format!("Failed to generate diff: {}", e))?;
476 reset_tree_with_dirty_tracker(local_tree, Some(basis_tree), Some(subpath), dirty_tracker)
477 .map_err(|e| format!("Failed to reset tree: {}", e))?;
478 let mut dep3_header = dep3::lossless::PatchHeader::new();
480 dep3_header.set_description(description);
481 dep3_header.set_origin(None, dep3::Origin::Other("other".into()));
482 dep3_header.set_last_update(timestamp);
483 let patches_directory = subpath.join(tree_patches_directory(local_tree, subpath));
484 let (specific_files, patchname) = add_patch(
485 local_tree,
486 &patches_directory,
487 patch_name,
488 diff.as_slice(),
489 Some(dep3_header),
490 )?;
491 Ok((specific_files, patchname))
492}
493
494#[cfg(test)]
495mod move_upstream_changes_to_patch_tests {
496 use super::*;
497 use breezyshim::controldir::ControlDirFormat;
498 use breezyshim::tree::MutableTree;
499
500 #[test]
501 fn test_simple() {
502 breezyshim::init();
503 let td = tempfile::tempdir().unwrap();
504 let local_tree = breezyshim::controldir::create_standalone_workingtree(
505 td.path(),
506 &ControlDirFormat::default(),
507 )
508 .unwrap();
509
510 std::fs::write(td.path().join("foo"), b"foo\n").unwrap();
511 local_tree.mkdir(std::path::Path::new("debian")).unwrap();
512 local_tree.add(&[std::path::Path::new("foo")]).unwrap();
513
514 super::move_upstream_changes_to_patch(
515 &local_tree,
516 &local_tree.basis_tree().unwrap(),
517 std::path::Path::new(""),
518 "patch",
519 "This is a description",
520 None,
521 Some(chrono::NaiveDate::from_ymd_opt(2020, 1, 1).unwrap()),
522 )
523 .unwrap();
524
525 let path = td.path();
526
527 assert!(!path.join("foo").exists());
528 assert!(path.join("debian/patches").exists());
529 assert!(path.join("debian/patches/series").exists());
530 assert!(path.join("debian/patches/patch.patch").exists());
531
532 let series = std::fs::read_to_string(path.join("debian/patches/series")).unwrap();
533 assert_eq!(series, "patch.patch\n");
534
535 let patch = std::fs::read_to_string(path.join("debian/patches/patch.patch")).unwrap();
536 assert!(
537 patch.starts_with(
538 r#"Description: This is a description
539Origin: other
540Last-Update: 2020-01-01
541---
542"#
543 ),
544 "{:?}",
545 patch
546 );
547
548 assert!(
549 patch.ends_with(
550 r#"@@ -0,0 +1,1 @@
551+foo
552
553"#
554 ),
555 "{:?}",
556 patch
557 );
558 }
559}
560
561pub fn read_quilt_patches<'a>(
563 tree: &'a dyn Tree,
564 directory: &'a std::path::Path,
565) -> impl Iterator<Item = UnifiedPatch> + 'a {
566 let series_path = directory.join("series");
567 let series = match tree.get_file(series_path.as_path()) {
568 Ok(series) => patchkit::quilt::Series::read(series).unwrap(),
569 Err(BrzError::NoSuchFile(..)) => patchkit::quilt::Series::new(),
570 Err(e) => panic!("error reading series: {:?}", e),
571 };
572
573 let mut ret = vec![];
574 for entry in series.iter() {
575 let (patch, options) = match entry {
576 patchkit::quilt::SeriesEntry::Patch {
577 name: patch,
578 options,
579 } => (patch, options),
580 patchkit::quilt::SeriesEntry::Comment(_) => continue,
581 };
582 let p = directory.join(patch);
583 let lines = tree.get_file_text(p.as_path()).unwrap();
584 let patch = QuiltPatch {
585 name: patch.to_string(),
586 patch: lines,
587 options: options.to_vec(),
588 };
589 ret.push(patch);
590 }
591 ret.into_iter().flat_map(|p| p.parse().unwrap())
592}
593
594#[cfg(test)]
595mod read_quilt_patches_tests {
596 const COMMITTER: &str = "Test Suite <test@suite.example.com>";
597 use super::*;
598 use breezyshim::controldir::ControlDirFormat;
599 use breezyshim::tree::MutableTree;
600
601 #[test]
602 fn test_read_patches() {
603 let patch = "\
604--- a/a
605+++ b/a
606@@ -1,5 +1,5 @@
607 line 1
608 line 2
609-line 3
610+new line 3
611 line 4
612 line 5
613";
614 breezyshim::init();
615 let td = tempfile::tempdir().unwrap();
616 let tree = breezyshim::controldir::create_standalone_workingtree(
617 td.path(),
618 &ControlDirFormat::default(),
619 )
620 .unwrap();
621 tree.mkdir(std::path::Path::new("debian")).unwrap();
622 tree.mkdir(std::path::Path::new("debian/patches")).unwrap();
623 std::fs::write(td.path().join("debian/patches/series"), "foo\n").unwrap();
624 std::fs::write(td.path().join("debian/patches/foo"), patch).unwrap();
625 tree.add(
626 [
627 "debian",
628 "debian/patches",
629 "debian/patches/series",
630 "debian/patches/foo",
631 ]
632 .into_iter()
633 .map(std::path::Path::new)
634 .collect::<Vec<_>>()
635 .as_slice(),
636 )
637 .unwrap();
638 tree.build_commit()
639 .message("add patch")
640 .committer(COMMITTER)
641 .commit()
642 .unwrap();
643 let patches = super::read_quilt_patches(&tree, std::path::Path::new("debian/patches"))
644 .collect::<Vec<_>>();
645 assert_eq!(1, patches.len());
646 assert_eq!(patch, std::str::from_utf8(&patches[0].as_bytes()).unwrap());
647 }
648
649 #[test]
650 fn test_no_series_file() {
651 breezyshim::init();
652 let td = tempfile::tempdir().unwrap();
653 let tree = breezyshim::controldir::create_standalone_workingtree(
654 td.path(),
655 &ControlDirFormat::default(),
656 )
657 .unwrap();
658 let patches = super::read_quilt_patches(&tree, std::path::Path::new("debian/patches"))
659 .collect::<Vec<_>>();
660 assert_eq!(0, patches.len());
661 }
662
663 #[test]
664 fn test_comments() {
665 let td = tempfile::tempdir().unwrap();
666 let tree = breezyshim::controldir::create_standalone_workingtree(
667 td.path(),
668 &ControlDirFormat::default(),
669 )
670 .unwrap();
671 tree.mkdir(std::path::Path::new("debian")).unwrap();
672 tree.mkdir(std::path::Path::new("debian/patches")).unwrap();
673 tree.put_file_bytes_non_atomic(
674 std::path::Path::new("debian/patches/series"),
675 b"# This file intentionally left blank.\n",
676 )
677 .unwrap();
678 tree.add(&[std::path::Path::new("debian/patches/series")])
679 .unwrap();
680 tree.build_commit()
681 .message("add series")
682 .committer(COMMITTER)
683 .commit()
684 .unwrap();
685 let patches = super::read_quilt_patches(&tree, std::path::Path::new("debian/patches"))
686 .collect::<Vec<_>>();
687 assert_eq!(0, patches.len());
688 }
689}
690
691pub fn upstream_with_applied_patches(
693 tree: breezyshim::workingtree::GenericWorkingTree,
694 patches: Vec<UnifiedPatch>,
695) -> breezyshim::Result<Box<dyn PyTree>> {
696 if let Some(patches_branch) = find_patches_branch(&tree) {
697 Ok(Box::new(patches_branch.basis_tree()?) as Box<dyn PyTree>)
698 } else {
699 let upstream_revision = find_patch_base(&tree).unwrap(); if patches.is_empty() {
701 Ok(Box::new(Clone::clone(&tree)) as Box<dyn PyTree>)
702 } else {
703 let upstream_tree = tree
704 .branch()
705 .repository()
706 .revision_tree(&upstream_revision)?;
707 Ok(Box::new(AppliedPatches::new(&upstream_tree, patches, None)?) as Box<dyn PyTree>)
708 }
709 }
710}
711
712#[cfg(test)]
713mod upstream_with_applied_patches_tests {
714 const COMMITTER: &str = "Test Suite <test@suite.example.com>";
715 use super::*;
716 use breezyshim::tree::{MutableTree, WorkingTree};
717 use breezyshim::workingtree::GenericWorkingTree;
718 use breezyshim::RevisionId;
719
720 fn setup() -> (tempfile::TempDir, GenericWorkingTree, RevisionId) {
721 let td = tempfile::tempdir().unwrap();
722 let tree = breezyshim::controldir::create_standalone_workingtree(
723 td.path(),
724 &breezyshim::controldir::ControlDirFormat::default(),
725 )
726 .unwrap();
727 std::fs::write(td.path().join("afile"), b"some line\n").unwrap();
728 tree.add(&[std::path::Path::new("afile")]).unwrap();
729 let upstream_revid = tree
730 .build_commit()
731 .message("upstream")
732 .committer(COMMITTER)
733 .commit()
734 .unwrap();
735 tree.mkdir(std::path::Path::new("debian")).unwrap();
736 std::fs::write(
737 td.path().join("debian/changelog"),
738 r#"blah (0.38) unstable; urgency=medium
739
740 * Fix something
741
742 -- Jelmer Vernooij <jelmer@debian.org> Sat, 19 Oct 2019 15:21:53 +0000
743"#,
744 )
745 .unwrap();
746 tree.add(&[std::path::Path::new("debian/changelog")])
747 .unwrap();
748 tree.mkdir(std::path::Path::new("debian/patches")).unwrap();
749 std::fs::write(td.path().join("debian/patches/series"), "1.patch\n").unwrap();
750 tree.add(&[std::path::Path::new("debian/patches/series")])
751 .unwrap();
752 std::fs::write(
753 td.path().join("debian/patches/1.patch"),
754 r#"--- a/afile
755+++ b/afile
756@@ -1 +1 @@
757-some line
758+another line
759--- /dev/null
760+++ b/newfile
761@@ -0,0 +1 @@
762+new line
763"#,
764 )
765 .unwrap();
766 tree.add(&[std::path::Path::new("debian/patches/1.patch")])
767 .unwrap();
768 std::fs::write(td.path().join("unchangedfile"), b"unchanged\n").unwrap();
769 tree.add(&[std::path::Path::new("unchangedfile")]).unwrap();
770
771 (td, tree, upstream_revid)
772 }
773
774 #[test]
775 fn test_upstream_branch() {
776 let (td, tree, upstream_revid) = setup();
777 tree.branch()
778 .tags()
779 .unwrap()
780 .set_tag("upstream/0.38", &upstream_revid)
781 .unwrap();
782 let tags = tree.branch().tags().unwrap().get_tag_dict().unwrap();
783 assert_eq!(Some(&upstream_revid), tags.get("upstream/0.38"));
784 let patches = super::read_quilt_patches(&tree, std::path::Path::new("debian/patches"))
785 .collect::<Vec<_>>();
786 let t = super::upstream_with_applied_patches(tree, patches).unwrap();
787 assert_eq!(
788 b"another line\n".to_vec(),
789 t.get_file_text(std::path::Path::new("afile")).unwrap()
790 );
791 assert_eq!(
792 b"new line\n".to_vec(),
793 t.get_file_text(std::path::Path::new("newfile")).unwrap()
794 );
795 std::mem::drop(td);
799 }
800}
801
802pub fn tree_non_patches_changes(
804 tree: breezyshim::workingtree::GenericWorkingTree,
805 patches_directory: Option<&std::path::Path>,
806) -> breezyshim::Result<Vec<breezyshim::tree::TreeChange>> {
807 let patches = if let Some(patches_directory) = patches_directory.as_ref() {
808 read_quilt_patches(&tree, patches_directory).collect::<Vec<_>>()
809 } else {
810 vec![]
811 };
812
813 let patches_tree = if patches.is_empty() {
814 Box::new(Clone::clone(&tree))
815 } else {
816 Box::new(AppliedPatches::new(&tree, patches.clone(), None)?) as Box<dyn Tree>
817 };
818
819 let upstream_patches_tree = upstream_with_applied_patches(tree, patches)?;
820
821 let changes = patches_tree
822 .iter_changes(upstream_patches_tree.as_ref(), None, None, None)?
823 .map(|c| c.unwrap());
824
825 let paths = &[std::path::Path::new("debian")][..];
826
827 Ok(filter_excluded(changes, paths)
828 .filter_map(|change| {
829 if change.path.1.as_deref() == Some(std::path::Path::new("")) {
830 None
831 } else {
832 Some(change)
833 }
834 })
835 .collect())
836}
837
838#[cfg(test)]
839mod tree_non_patches_changes_tests {
840 const COMMITTER: &str = "Test Suite <test@suite.example.com>";
841 use super::*;
842 use breezyshim::tree::{MutableTree, WorkingTree};
843 use breezyshim::workingtree::GenericWorkingTree;
844 use breezyshim::RevisionId;
845
846 fn setup() -> (tempfile::TempDir, GenericWorkingTree, RevisionId) {
847 breezyshim::init();
848
849 let td = tempfile::tempdir().unwrap();
850 let local_tree = breezyshim::controldir::create_standalone_workingtree(
851 td.path(),
852 &breezyshim::controldir::ControlDirFormat::default(),
853 )
854 .unwrap();
855
856 std::fs::write(td.path().join("afile"), b"some line\n").unwrap();
857 local_tree.add(&[std::path::Path::new("afile")]).unwrap();
858 let upstream_revid = local_tree
859 .build_commit()
860 .message("upstream")
861 .committer(COMMITTER)
862 .commit()
863 .unwrap();
864
865 local_tree.mkdir(std::path::Path::new("debian")).unwrap();
866 std::fs::write(
867 td.path().join("debian/changelog"),
868 r#"blah (0.38) unstable; urgency=medium
869
870 * Fix something
871
872 -- Jelmer Vernooij <jelmer@debian.org> Sat, 19 Oct 2019 15:21:53 +0000
873"#,
874 )
875 .unwrap();
876 local_tree
877 .mkdir(std::path::Path::new("debian/patches"))
878 .unwrap();
879 std::fs::write(td.path().join("debian/patches/series"), "1.patch\n").unwrap();
880 std::fs::write(
881 td.path().join("debian/patches/1.patch"),
882 r#"--- a/afile
883+++ b/afile
884@@ -1 +1 @@
885-some line
886+another line
887"#,
888 )
889 .unwrap();
890 local_tree
891 .add(&[
892 std::path::Path::new("debian/changelog"),
893 std::path::Path::new("debian/patches"),
894 std::path::Path::new("debian/patches/series"),
895 std::path::Path::new("debian/patches/1.patch"),
896 ])
897 .unwrap();
898
899 (td, local_tree, upstream_revid)
900 }
901
902 #[test]
903 fn test_no_delta() {
904 let (td, tree, upstream_revid) = setup();
905 tree.branch()
906 .tags()
907 .unwrap()
908 .set_tag("upstream/0.38", &upstream_revid)
909 .unwrap();
910 assert_eq!(
911 Vec::<breezyshim::tree::TreeChange>::new(),
912 super::tree_non_patches_changes(tree, Some(std::path::Path::new("debian/patches")))
913 .unwrap()
914 );
915 std::mem::drop(td);
916 }
917
918 #[test]
919 fn test_delta() {
920 let (td, tree, upstream_revid) = setup();
921 tree.branch()
922 .tags()
923 .unwrap()
924 .set_tag("upstream/0.38", &upstream_revid)
925 .unwrap();
926 let tags = tree.branch().tags().unwrap().get_tag_dict().unwrap();
927 assert_eq!(Some(&upstream_revid), tags.get("upstream/0.38"));
928 std::fs::write(tree.basedir().join("anotherfile"), b"blah\n").unwrap();
929 tree.add(&[std::path::Path::new("anotherfile")]).unwrap();
930 assert_eq!(
931 1,
932 super::tree_non_patches_changes(tree, Some(std::path::Path::new("debian/patches")))
933 .unwrap()
934 .len()
935 );
936 std::mem::drop(td);
937 }
938}