@@ -85,18 +85,17 @@ pub(crate) fn get_forget_snapshots<P: ProgressBars, S: Open>(
85
85
filter : impl FnMut ( & SnapshotFile ) -> bool ,
86
86
) -> RusticResult < ForgetGroups > {
87
87
let now = Local :: now ( ) ;
88
- if !keep. is_valid ( ) {
89
- return Err ( CommandErrorKind :: NoKeepOption . into ( ) ) ;
90
- }
91
88
92
89
let groups = repo
93
90
. get_snapshot_group ( & [ ] , group_by, filter) ?
94
91
. into_iter ( )
95
- . map ( |( group, snapshots) | ForgetGroup {
96
- group,
97
- snapshots : keep. apply ( snapshots, now) ,
92
+ . map ( |( group, snapshots) | -> RusticResult < _ > {
93
+ Ok ( ForgetGroup {
94
+ group,
95
+ snapshots : keep. apply ( snapshots, now) ?,
96
+ } )
98
97
} )
99
- . collect ( ) ;
98
+ . collect :: < RusticResult < _ > > ( ) ? ;
100
99
101
100
Ok ( ForgetGroups ( groups) )
102
101
}
@@ -113,14 +112,16 @@ pub(crate) fn get_forget_snapshots<P: ProgressBars, S: Open>(
113
112
pub struct KeepOptions {
114
113
/// Keep snapshots with this taglist (can be specified multiple times)
115
114
#[ cfg_attr( feature = "clap" , clap( long, value_name = "TAG[,TAG,..]" ) ) ]
116
- #[ serde_as( as = "OneOrMany<DisplayFromStr>" ) ]
117
115
#[ cfg_attr( feature = "merge" , merge( strategy=merge:: vec:: overwrite_empty) ) ]
116
+ #[ serde_as( as = "OneOrMany<DisplayFromStr>" ) ]
117
+ #[ serde( skip_serializing_if = "Vec::is_empty" ) ]
118
118
pub keep_tags : Vec < StringList > ,
119
119
120
120
/// Keep snapshots ids that start with ID (can be specified multiple times)
121
121
#[ cfg_attr( feature = "clap" , clap( long = "keep-id" , value_name = "ID" ) ) ]
122
122
#[ cfg_attr( feature = "merge" , merge( strategy=merge:: vec:: overwrite_empty) ) ]
123
123
#[ serde_as( as = "OneOrMany<_>" ) ]
124
+ #[ serde( skip_serializing_if = "Vec::is_empty" ) ]
124
125
pub keep_ids : Vec < String > ,
125
126
126
127
/// Keep the last N snapshots (N == -1: keep all snapshots)
@@ -222,6 +223,7 @@ pub struct KeepOptions {
222
223
/// Allow to keep no snapshot
223
224
#[ cfg_attr( feature = "clap" , clap( long) ) ]
224
225
#[ cfg_attr( feature = "merge" , merge( strategy=merge:: bool :: overwrite_false) ) ]
226
+ #[ serde( skip_serializing_if = "std::ops::Not::not" ) ]
225
227
pub keep_none : bool ,
226
228
}
227
229
@@ -352,6 +354,7 @@ impl KeepOptions {
352
354
|| self . keep_monthly . is_some ( )
353
355
|| self . keep_quarter_yearly . is_some ( )
354
356
|| self . keep_half_yearly . is_some ( )
357
+ || self . keep_within . is_some ( )
355
358
|| self . keep_yearly . is_some ( )
356
359
|| self . keep_within_hourly . is_some ( )
357
360
|| self . keep_within_daily . is_some ( )
@@ -500,11 +503,15 @@ impl KeepOptions {
500
503
& self ,
501
504
mut snapshots : Vec < SnapshotFile > ,
502
505
now : DateTime < Local > ,
503
- ) -> Vec < ForgetSnapshot > {
506
+ ) -> RusticResult < Vec < ForgetSnapshot > > {
507
+ if !self . is_valid ( ) {
508
+ return Err ( CommandErrorKind :: NoKeepOption . into ( ) ) ;
509
+ }
510
+
504
511
let mut group_keep = self . clone ( ) ;
505
512
let mut snaps = Vec :: new ( ) ;
506
513
if snapshots. is_empty ( ) {
507
- return snaps;
514
+ return Ok ( snaps) ;
508
515
}
509
516
510
517
snapshots. sort_unstable_by ( |sn1, sn2| sn1. cmp ( sn2) . reverse ( ) ) ;
@@ -534,6 +541,216 @@ impl KeepOptions {
534
541
reasons : reasons. iter ( ) . map ( ToString :: to_string) . collect ( ) ,
535
542
} ) ;
536
543
}
537
- snaps
544
+ Ok ( snaps)
545
+ }
546
+ }
547
+
548
+ #[ cfg( test) ]
549
+ mod tests {
550
+ use std:: str:: FromStr ;
551
+
552
+ use super :: * ;
553
+ use anyhow:: Result ;
554
+ use chrono:: { Local , NaiveDateTime , TimeZone } ;
555
+ use humantime:: Duration ;
556
+ use insta:: { assert_ron_snapshot, Settings } ;
557
+ use rstest:: { fixture, rstest} ;
558
+ use serde_json;
559
+
560
+ #[ fixture]
561
+ fn test_snapshots ( ) -> Result < Vec < SnapshotFile > > {
562
+ let snaps = [
563
+ ( "2014-09-01 10:20:30" , "" ) ,
564
+ ( "2014-09-02 10:20:30" , "" ) ,
565
+ ( "2014-09-05 10:20:30" , "" ) ,
566
+ ( "2014-09-06 10:20:30" , "" ) ,
567
+ ( "2014-09-08 10:20:30" , "" ) ,
568
+ ( "2014-09-09 10:20:30" , "" ) ,
569
+ ( "2014-09-10 10:20:30" , "" ) ,
570
+ ( "2014-09-11 10:20:30" , "" ) ,
571
+ ( "2014-09-20 10:20:30" , "" ) ,
572
+ ( "2014-09-22 10:20:30" , "" ) ,
573
+ ( "2014-08-08 10:20:30" , "" ) ,
574
+ ( "2014-08-10 10:20:30" , "" ) ,
575
+ ( "2014-08-12 10:20:30" , "" ) ,
576
+ ( "2014-08-13 10:20:30" , "" ) ,
577
+ ( "2014-08-15 10:20:30" , "" ) ,
578
+ ( "2014-08-18 10:20:30" , "" ) ,
579
+ ( "2014-08-20 10:20:30" , "" ) ,
580
+ ( "2014-08-21 10:20:30" , "" ) ,
581
+ ( "2014-08-22 10:20:30" , "" ) ,
582
+ ( "2014-10-01 10:20:30" , "foo" ) ,
583
+ ( "2014-10-02 10:20:30" , "foo" ) ,
584
+ ( "2014-10-05 10:20:30" , "foo" ) ,
585
+ ( "2014-10-06 10:20:30" , "foo" ) ,
586
+ ( "2014-10-08 10:20:30" , "foo" ) ,
587
+ ( "2014-10-09 10:20:30" , "foo" ) ,
588
+ ( "2014-10-10 10:20:30" , "foo" ) ,
589
+ ( "2014-10-11 10:20:30" , "foo" ) ,
590
+ ( "2014-10-20 10:20:30" , "foo" ) ,
591
+ ( "2014-10-22 10:20:30" , "foo" ) ,
592
+ ( "2014-11-08 10:20:30" , "foo" ) ,
593
+ ( "2014-11-10 10:20:30" , "foo" ) ,
594
+ ( "2014-11-12 10:20:30" , "foo" ) ,
595
+ ( "2014-11-13 10:20:30" , "foo" ) ,
596
+ ( "2014-11-15 10:20:30" , "foo,bar" ) ,
597
+ ( "2014-11-18 10:20:30" , "" ) ,
598
+ ( "2014-11-20 10:20:30" , "" ) ,
599
+ ( "2014-11-21 10:20:30" , "" ) ,
600
+ ( "2014-11-22 10:20:30" , "" ) ,
601
+ ( "2015-09-01 10:20:30" , "" ) ,
602
+ ( "2015-09-02 10:20:30" , "" ) ,
603
+ ( "2015-09-05 10:20:30" , "" ) ,
604
+ ( "2015-09-06 10:20:30" , "" ) ,
605
+ ( "2015-09-08 10:20:30" , "" ) ,
606
+ ( "2015-09-09 10:20:30" , "" ) ,
607
+ ( "2015-09-10 10:20:30" , "" ) ,
608
+ ( "2015-09-11 10:20:30" , "" ) ,
609
+ ( "2015-09-20 10:20:30" , "" ) ,
610
+ ( "2015-09-22 10:20:30" , "" ) ,
611
+ ( "2015-08-08 10:20:30" , "" ) ,
612
+ ( "2015-08-10 10:20:30" , "" ) ,
613
+ ( "2015-08-12 10:20:30" , "" ) ,
614
+ ( "2015-08-13 10:20:30" , "" ) ,
615
+ ( "2015-08-15 10:20:30" , "" ) ,
616
+ ( "2015-08-18 10:20:30" , "" ) ,
617
+ ( "2015-08-20 10:20:30" , "" ) ,
618
+ ( "2015-08-21 10:20:30" , "" ) ,
619
+ ( "2015-08-22 10:20:30" , "" ) ,
620
+ ( "2015-10-01 10:20:30" , "" ) ,
621
+ ( "2015-10-02 10:20:30" , "" ) ,
622
+ ( "2015-10-05 10:20:30" , "" ) ,
623
+ ( "2015-10-06 10:20:30" , "" ) ,
624
+ ( "2015-10-08 10:20:30" , "" ) ,
625
+ ( "2015-10-09 10:20:30" , "" ) ,
626
+ ( "2015-10-10 10:20:30" , "" ) ,
627
+ ( "2015-10-11 10:20:30" , "" ) ,
628
+ ( "2015-10-20 10:20:30" , "" ) ,
629
+ ( "2015-10-22 10:20:30" , "" ) ,
630
+ ( "2015-10-22 10:20:30" , "" ) ,
631
+ ( "2015-10-22 10:20:30" , "foo,bar" ) ,
632
+ ( "2015-10-22 10:20:30" , "foo,bar" ) ,
633
+ ( "2015-11-08 10:20:30" , "" ) ,
634
+ ( "2015-11-10 10:20:30" , "" ) ,
635
+ ( "2015-11-12 10:20:30" , "" ) ,
636
+ ( "2015-11-13 10:20:30" , "" ) ,
637
+ ( "2015-11-15 10:20:30" , "" ) ,
638
+ ( "2015-11-18 10:20:30" , "" ) ,
639
+ ( "2015-11-20 10:20:30" , "" ) ,
640
+ ( "2015-11-21 10:20:30" , "" ) ,
641
+ ( "2015-11-22 10:20:30" , "" ) ,
642
+ ( "2016-01-01 01:02:03" , "" ) ,
643
+ ( "2016-01-01 01:03:03" , "" ) ,
644
+ ( "2016-01-01 07:08:03" , "" ) ,
645
+ ( "2016-01-03 07:02:03" , "" ) ,
646
+ ( "2016-01-04 10:23:03" , "" ) ,
647
+ ( "2016-01-04 11:23:03" , "" ) ,
648
+ ( "2016-01-04 12:23:03" , "" ) ,
649
+ ( "2016-01-04 12:24:03" , "" ) ,
650
+ ( "2016-01-04 12:28:03" , "" ) ,
651
+ ( "2016-01-04 12:30:03" , "" ) ,
652
+ ( "2016-01-04 16:23:03" , "" ) ,
653
+ ( "2016-01-05 09:02:03" , "" ) ,
654
+ ( "2016-01-06 08:02:03" , "" ) ,
655
+ ( "2016-01-07 10:02:03" , "" ) ,
656
+ ( "2016-01-08 20:02:03" , "" ) ,
657
+ ( "2016-01-09 21:02:03" , "" ) ,
658
+ ( "2016-01-12 21:02:03" , "" ) ,
659
+ ( "2016-01-12 21:08:03" , "" ) ,
660
+ ( "2016-01-18 12:02:03" , "" ) ,
661
+ ] ;
662
+
663
+ let mut snaps: Vec < _ > = snaps
664
+ . into_iter ( )
665
+ . map ( |( time, tags) | -> Result < _ > {
666
+ let time = NaiveDateTime :: parse_from_str ( time, "%Y-%m-%d %H:%M:%S" ) ?;
667
+ let opts = & crate :: SnapshotOptions :: default ( )
668
+ . time ( Local :: from_utc_datetime ( & Local , & time) )
669
+ . tag ( vec ! [ StringList :: from_str( tags) ?] ) ;
670
+ Ok ( SnapshotFile :: from_options ( opts) ?)
671
+ } )
672
+ . collect :: < Result < _ > > ( ) ?;
673
+
674
+ snaps. sort_unstable_by ( |sn1, sn2| sn1. cmp ( sn2) . reverse ( ) ) ;
675
+ Ok ( snaps)
676
+ }
677
+
678
+ #[ fixture]
679
+ fn insta_forget_snapshots_redaction ( ) -> Settings {
680
+ let mut settings = Settings :: clone_current ( ) ;
681
+ settings. add_redaction ( ".**.snapshot" , "[snapshot]" ) ;
682
+ settings
683
+ }
684
+
685
+ #[ rstest]
686
+ #[ case( KeepOptions :: default ( ) ) ]
687
+ fn test_apply_fails (
688
+ #[ case] options : KeepOptions ,
689
+ test_snapshots : Result < Vec < SnapshotFile > > ,
690
+ ) -> Result < ( ) > {
691
+ let now = Local :: now ( ) ;
692
+ let result = options. apply ( test_snapshots?, now) ;
693
+ assert ! ( result. is_err( ) ) ;
694
+ Ok ( ( ) )
695
+ }
696
+
697
+ #[ rstest]
698
+ #[ case( KeepOptions :: default ( ) . keep_last( 10 ) ) ]
699
+ #[ case( KeepOptions :: default ( ) . keep_last( 15 ) ) ]
700
+ #[ case( KeepOptions :: default ( ) . keep_last( 99 ) ) ]
701
+ #[ case( KeepOptions :: default ( ) . keep_last( 200 ) ) ]
702
+ #[ case( KeepOptions :: default ( ) . keep_hourly( 20 ) ) ]
703
+ #[ case( KeepOptions :: default ( ) . keep_daily( 3 ) ) ]
704
+ #[ case( KeepOptions :: default ( ) . keep_daily( 10 ) ) ]
705
+ #[ case( KeepOptions :: default ( ) . keep_daily( 30 ) ) ]
706
+ #[ case( KeepOptions :: default ( ) . keep_last( 5 ) . keep_daily( 5 ) ) ]
707
+ #[ case( KeepOptions :: default ( ) . keep_last( 2 ) . keep_daily( 10 ) ) ]
708
+ #[ case( KeepOptions :: default ( ) . keep_weekly( 2 ) ) ]
709
+ #[ case( KeepOptions :: default ( ) . keep_weekly( 4 ) ) ]
710
+ #[ case( KeepOptions :: default ( ) . keep_daily( 3 ) . keep_weekly( 4 ) ) ]
711
+ #[ case( KeepOptions :: default ( ) . keep_monthly( 6 ) ) ]
712
+ #[ case( KeepOptions :: default ( ) . keep_daily( 2 ) . keep_weekly( 2 ) . keep_monthly( 6 ) ) ]
713
+ #[ case( KeepOptions :: default ( ) . keep_yearly( 10 ) ) ]
714
+ #[ case( KeepOptions :: default ( ) . keep_quarter_yearly( 10 ) ) ]
715
+ #[ case( KeepOptions :: default ( ) . keep_half_yearly( 10 ) ) ]
716
+ #[ case( KeepOptions :: default ( ) . keep_daily( 7 ) . keep_weekly( 2 ) . keep_monthly( 3 ) . keep_yearly( 10 ) ) ]
717
+ /*
718
+ #[case(KeepOptions::default().keep_tags("foo"))]
719
+ #[case(KeepOptions::default().keep_tags("foo,bar"))]
720
+ */
721
+ #[ case( KeepOptions :: default ( ) . keep_within( Duration :: from_str( "1d" ) . unwrap( ) ) ) ]
722
+ #[ case( KeepOptions :: default ( ) . keep_within( Duration :: from_str( "2d" ) . unwrap( ) ) ) ]
723
+ #[ case( KeepOptions :: default ( ) . keep_within( Duration :: from_str( "7d" ) . unwrap( ) ) ) ]
724
+ #[ case( KeepOptions :: default ( ) . keep_within( Duration :: from_str( "1m" ) . unwrap( ) ) ) ]
725
+ #[ case( KeepOptions :: default ( ) . keep_within( Duration :: from_str( "1M14d" ) . unwrap( ) ) ) ]
726
+ #[ case( KeepOptions :: default ( ) . keep_within( Duration :: from_str( "1y1d1M" ) . unwrap( ) ) ) ]
727
+ #[ case( KeepOptions :: default ( ) . keep_within( Duration :: from_str( "13d23h" ) . unwrap( ) ) ) ]
728
+ #[ case( KeepOptions :: default ( ) . keep_within( Duration :: from_str( "2M2h" ) . unwrap( ) ) ) ]
729
+ #[ case( KeepOptions :: default ( ) . keep_within( Duration :: from_str( "1y2M3d3h" ) . unwrap( ) ) ) ]
730
+ #[ case( KeepOptions :: default ( ) . keep_within_hourly( Duration :: from_str( "1y2M3d3h" ) . unwrap( ) ) ) ]
731
+ #[ case( KeepOptions :: default ( ) . keep_within_daily( Duration :: from_str( "1y2M3d3h" ) . unwrap( ) ) ) ]
732
+ #[ case( KeepOptions :: default ( ) . keep_within_weekly( Duration :: from_str( "1y2M3d3h" ) . unwrap( ) ) ) ]
733
+ #[ case( KeepOptions :: default ( ) . keep_within_monthly( Duration :: from_str( "1y2M3d3h" ) . unwrap( ) ) ) ]
734
+ #[ case( KeepOptions :: default ( ) . keep_within_quarter_yearly( Duration :: from_str( "1y2M3d3h" ) . unwrap( ) ) ) ]
735
+ #[ case( KeepOptions :: default ( ) . keep_within_half_yearly( Duration :: from_str( "1y2M3d3h" ) . unwrap( ) ) ) ]
736
+ #[ case( KeepOptions :: default ( ) . keep_within_yearly( Duration :: from_str( "1y2M3d3h" ) . unwrap( ) ) ) ]
737
+ #[ case( KeepOptions :: default ( ) . keep_within( Duration :: from_str( "1h" ) . unwrap( ) ) . keep_within_hourly( Duration :: from_str( "1d" ) . unwrap( ) ) . keep_within_daily( Duration :: from_str( "1w" ) . unwrap( ) ) . keep_within_weekly( Duration :: from_str( "1M" ) . unwrap( ) ) . keep_within_monthly( Duration :: from_str( "1y" ) . unwrap( ) ) . keep_within_yearly( Duration :: from_str( "9999y" ) . unwrap( ) ) ) ]
738
+ #[ case( KeepOptions :: default ( ) . keep_last( -1 ) ) ]
739
+ #[ case( KeepOptions :: default ( ) . keep_last( -1 ) . keep_hourly( -1 ) ) ]
740
+ #[ case( KeepOptions :: default ( ) . keep_hourly( -1 ) ) ]
741
+ #[ case( KeepOptions :: default ( ) . keep_daily( 3 ) . keep_weekly( 2 ) . keep_monthly( -1 ) . keep_yearly( -1 ) ) ]
742
+ #[ case( KeepOptions :: default ( ) . keep_none( true ) ) ]
743
+ fn test_apply (
744
+ #[ case] options : KeepOptions ,
745
+ test_snapshots : Result < Vec < SnapshotFile > > ,
746
+ insta_forget_snapshots_redaction : Settings ,
747
+ ) -> Result < ( ) > {
748
+ let now = Local :: now ( ) ;
749
+ let result = options. apply ( test_snapshots?, now) ?;
750
+ let options = serde_json:: to_string ( & options) ?;
751
+ insta_forget_snapshots_redaction. bind ( || {
752
+ assert_ron_snapshot ! ( options, result) ;
753
+ } ) ;
754
+ Ok ( ( ) )
538
755
}
539
756
}
0 commit comments