浏览代码

Merge pull request #941 from ThomasWaldmann/keep-last-n

implement --keep-last N via --keep-secondly N, also --keep-minutely, fixes #537
TW 9 年之前
父节点
当前提交
7ffbd4a200
共有 1 个文件被更改,包括 37 次插入24 次删除
  1. 37 24
      borg/archiver.py

+ 37 - 24
borg/archiver.py

@@ -775,10 +775,10 @@ class Archiver:
     @with_repository()
     @with_repository()
     def do_prune(self, args, repository, manifest, key):
     def do_prune(self, args, repository, manifest, key):
         """Prune repository archives according to specified rules"""
         """Prune repository archives according to specified rules"""
-        if not any((args.hourly, args.daily,
+        if not any((args.secondly, args.minutely, args.hourly, args.daily,
                     args.weekly, args.monthly, args.yearly, args.within)):
                     args.weekly, args.monthly, args.yearly, args.within)):
             self.print_error('At least one of the "keep-within", "keep-last", '
             self.print_error('At least one of the "keep-within", "keep-last", '
-                             '"keep-hourly", "keep-daily", '
+                             '"keep-secondly", "keep-minutely", "keep-hourly", "keep-daily", '
                              '"keep-weekly", "keep-monthly" or "keep-yearly" settings must be specified.')
                              '"keep-weekly", "keep-monthly" or "keep-yearly" settings must be specified.')
             return self.exit_code
             return self.exit_code
         archives = manifest.list_archive_infos(sort_by='ts', reverse=True)  # just a ArchiveInfo list
         archives = manifest.list_archive_infos(sort_by='ts', reverse=True)  # just a ArchiveInfo list
@@ -787,6 +787,10 @@ class Archiver:
         keep = []
         keep = []
         if args.within:
         if args.within:
             keep += prune_within(archives, args.within)
             keep += prune_within(archives, args.within)
+        if args.secondly:
+            keep += prune_split(archives, '%Y-%m-%d %H:%M:%S', args.secondly, keep)
+        if args.minutely:
+            keep += prune_split(archives, '%Y-%m-%d %H:%M', args.minutely, keep)
         if args.hourly:
         if args.hourly:
             keep += prune_split(archives, '%Y-%m-%d %H', args.hourly, keep)
             keep += prune_split(archives, '%Y-%m-%d %H', args.hourly, keep)
         if args.daily:
         if args.daily:
@@ -798,21 +802,21 @@ class Archiver:
         if args.yearly:
         if args.yearly:
             keep += prune_split(archives, '%Y', args.yearly, keep)
             keep += prune_split(archives, '%Y', args.yearly, keep)
 
 
-        keep.sort(key=attrgetter('ts'), reverse=True)
-        to_delete = [a for a in archives if a not in keep]
+        to_delete = set(archives) - set(keep)
         stats = Statistics()
         stats = Statistics()
         with Cache(repository, key, manifest, do_files=args.cache_files, lock_wait=self.lock_wait) as cache:
         with Cache(repository, key, manifest, do_files=args.cache_files, lock_wait=self.lock_wait) as cache:
-            for archive in keep:
-                if args.output_list:
-                    logger.info('Keeping archive: %s' % format_archive(archive))
-            for archive in to_delete:
-                if args.dry_run:
-                    if args.output_list:
-                        logger.info('Would prune:     %s' % format_archive(archive))
+            for archive in archives:
+                if archive in to_delete:
+                    if args.dry_run:
+                        if args.output_list:
+                            logger.info('Would prune:     %s' % format_archive(archive))
+                    else:
+                        if args.output_list:
+                            logger.info('Pruning archive: %s' % format_archive(archive))
+                        Archive(repository, key, manifest, archive.name, cache).delete(stats)
                 else:
                 else:
                     if args.output_list:
                     if args.output_list:
-                        logger.info('Pruning archive: %s' % format_archive(archive))
-                    Archive(repository, key, manifest, archive.name, cache).delete(stats)
+                        logger.info('Keeping archive: %s' % format_archive(archive))
             if to_delete and not args.dry_run:
             if to_delete and not args.dry_run:
                 manifest.write()
                 manifest.write()
                 repository.commit(save_space=args.save_space)
                 repository.commit(save_space=args.save_space)
@@ -1587,13 +1591,10 @@ class Archiver:
         any of the specified retention options. This command is normally used by
         any of the specified retention options. This command is normally used by
         automated backup scripts wanting to keep a certain number of historic backups.
         automated backup scripts wanting to keep a certain number of historic backups.
 
 
-        As an example, "-d 7" means to keep the latest backup on each day, up to 7
-        most recent days with backups (days without backups do not count).
-        The rules are applied from hourly to yearly, and backups selected by previous
-        rules do not count towards those of later rules. The time that each backup
-        completes is used for pruning purposes. Dates and times are interpreted in
-        the local timezone, and weeks go from Monday to Sunday. Specifying a
-        negative number of archives to keep means that there is no limit.
+        If a prefix is set with -P, then only archives that start with the prefix are
+        considered for deletion and only those archives count towards the totals
+        specified by the rules.
+        Otherwise, *all* archives in the repository are candidates for deletion!
 
 
         The "--keep-within" option takes an argument of the form "<int><char>",
         The "--keep-within" option takes an argument of the form "<int><char>",
         where char is "H", "d", "w", "m", "y". For example, "--keep-within 2d" means
         where char is "H", "d", "w", "m", "y". For example, "--keep-within 2d" means
@@ -1601,10 +1602,18 @@ class Archiver:
         "1m" is taken to mean "31d". The archives kept with this option do not
         "1m" is taken to mean "31d". The archives kept with this option do not
         count towards the totals specified by any other options.
         count towards the totals specified by any other options.
 
 
-        If a prefix is set with -P, then only archives that start with the prefix are
-        considered for deletion and only those archives count towards the totals
-        specified by the rules.
-        Otherwise, *all* archives in the repository are candidates for deletion!
+        A good procedure is to thin out more and more the older your backups get.
+        As an example, "--keep-daily 7" means to keep the latest backup on each day,
+        up to 7 most recent days with backups (days without backups do not count).
+        The rules are applied from secondly to yearly, and backups selected by previous
+        rules do not count towards those of later rules. The time that each backup
+        completes is used for pruning purposes. Dates and times are interpreted in
+        the local timezone, and weeks go from Monday to Sunday. Specifying a
+        negative number of archives to keep means that there is no limit.
+
+        The "--keep-last N" option is doing the same as "--keep-secondly N" (and it will
+        keep the last N archives under the assumption that you do not create more than one
+        backup archive in the same second).
         """)
         """)
         subparser = subparsers.add_parser('prune', parents=[common_parser], add_help=False,
         subparser = subparsers.add_parser('prune', parents=[common_parser], add_help=False,
                                           description=self.do_prune.__doc__,
                                           description=self.do_prune.__doc__,
@@ -1623,6 +1632,10 @@ class Archiver:
                                help='output verbose list of archives it keeps/prunes')
                                help='output verbose list of archives it keeps/prunes')
         subparser.add_argument('--keep-within', dest='within', type=str, metavar='WITHIN',
         subparser.add_argument('--keep-within', dest='within', type=str, metavar='WITHIN',
                                help='keep all archives within this time interval')
                                help='keep all archives within this time interval')
+        subparser.add_argument('--keep-last', '--keep-secondly', dest='secondly', type=int, default=0,
+                               help='number of secondly archives to keep')
+        subparser.add_argument('--keep-minutely', dest='minutely', type=int, default=0,
+                               help='number of minutely archives to keep')
         subparser.add_argument('-H', '--keep-hourly', dest='hourly', type=int, default=0,
         subparser.add_argument('-H', '--keep-hourly', dest='hourly', type=int, default=0,
                                help='number of hourly archives to keep')
                                help='number of hourly archives to keep')
         subparser.add_argument('-d', '--keep-daily', dest='daily', type=int, default=0,
         subparser.add_argument('-d', '--keep-daily', dest='daily', type=int, default=0,