2
0
Эх сурвалжийг харах

Allows delete to be used with archive filters

Frank Sachsenheim 9 жил өмнө
parent
commit
f2d4d36cea

+ 60 - 31
src/borg/archiver.py

@@ -770,11 +770,31 @@ class Archiver:
 
     @with_repository(exclusive=True, manifest=False)
     def do_delete(self, args, repository):
-        """Delete an existing repository or archive"""
+        """Delete an existing repository or archives"""
+        if any((args.location.archive, args.first, args.last, args.prefix)):
+            return self._delete_archives(args, repository)
+        else:
+            return self._delete_repository(args, repository)
+
+    def _delete_archives(self, args, repository):
+        """Delete archives"""
+        manifest, key = Manifest.load(repository)
+
         if args.location.archive:
-            manifest, key = Manifest.load(repository)
-            with Cache(repository, key, manifest, lock_wait=self.lock_wait) as cache:
-                archive = Archive(repository, key, manifest, args.location.archive, cache=cache)
+            archive_names = (args.location.archive,)
+        else:
+            archive_names = tuple(x.name for x in self._get_filtered_archives(args, manifest))
+            if not archive_names:
+                return self.exit_code
+
+        stats_logger = logging.getLogger('borg.output.stats')
+        if args.stats:
+            log_multi(DASHES, STATS_HEADER, logger=stats_logger)
+
+        with Cache(repository, key, manifest, lock_wait=self.lock_wait) as cache:
+            for i, archive_name in enumerate(archive_names, 1):
+                logger.info('Deleting {} ({}/{}):'.format(archive_name, i, len(archive_names)))
+                archive = Archive(repository, key, manifest, archive_name, cache=cache)
                 stats = Statistics()
                 archive.delete(stats, progress=args.progress, forced=args.forced)
                 manifest.write()
@@ -782,33 +802,41 @@ class Archiver:
                 cache.commit()
                 logger.info("Archive deleted.")
                 if args.stats:
-                    log_multi(DASHES,
-                              STATS_HEADER,
-                              stats.summary.format(label='Deleted data:', stats=stats),
-                              str(cache),
-                              DASHES, logger=logging.getLogger('borg.output.stats'))
-        else:
-            if not args.cache_only:
-                msg = []
-                try:
-                    manifest, key = Manifest.load(repository)
-                except NoManifestError:
-                    msg.append("You requested to completely DELETE the repository *including* all archives it may contain.")
-                    msg.append("This repository seems to have no manifest, so we can't tell anything about its contents.")
-                else:
-                    msg.append("You requested to completely DELETE the repository *including* all archives it contains:")
-                    for archive_info in manifest.archives.list(sort_by='ts'):
-                        msg.append(format_archive(archive_info))
-                msg.append("Type 'YES' if you understand this and want to continue: ")
-                msg = '\n'.join(msg)
-                if not yes(msg, false_msg="Aborting.", invalid_msg='Invalid answer, aborting.', truish=('YES', ),
-                           retry=False, env_var_override='BORG_DELETE_I_KNOW_WHAT_I_AM_DOING'):
-                    self.exit_code = EXIT_ERROR
-                    return self.exit_code
-                repository.destroy()
-                logger.info("Repository deleted.")
-            Cache.destroy(repository)
-            logger.info("Cache deleted.")
+                    log_multi(stats.summary.format(label='Deleted data:', stats=stats),
+                              DASHES, logger=stats_logger)
+                if not args.forced and self.exit_code:
+                    break
+            if args.stats:
+                stats_logger.info(str(cache))
+
+        return self.exit_code
+
+    def _delete_repository(self, args, repository):
+        """Delete a repository"""
+        if not args.cache_only:
+            msg = []
+            try:
+                manifest, key = Manifest.load(repository)
+            except NoManifestError:
+                msg.append("You requested to completely DELETE the repository *including* all archives it may "
+                           "contain.")
+                msg.append("This repository seems to have no manifest, so we can't tell anything about its "
+                           "contents.")
+            else:
+                msg.append("You requested to completely DELETE the repository *including* all archives it "
+                           "contains:")
+                for archive_info in manifest.archives.list(sort_by='ts'):
+                    msg.append(format_archive(archive_info))
+            msg.append("Type 'YES' if you understand this and want to continue: ")
+            msg = '\n'.join(msg)
+            if not yes(msg, false_msg="Aborting.", invalid_msg='Invalid answer, aborting.', truish=('YES',),
+                       retry=False, env_var_override='BORG_DELETE_I_KNOW_WHAT_I_AM_DOING'):
+                self.exit_code = EXIT_ERROR
+                return self.exit_code
+            repository.destroy()
+            logger.info("Repository deleted.")
+        Cache.destroy(repository)
+        logger.info("Cache deleted.")
         return self.exit_code
 
     @with_repository()
@@ -1969,6 +1997,7 @@ class Archiver:
         subparser.add_argument('location', metavar='TARGET', nargs='?', default='',
                                type=location_validator(),
                                help='archive or repository to delete')
+        self.add_archives_filters_args(subparser)
 
         list_epilog = textwrap.dedent("""
         This command lists the contents of a repository or an archive.

+ 14 - 0
src/borg/testsuite/archiver.py

@@ -59,6 +59,9 @@ def exec_cmd(*args, archiver=None, fork=False, exe=None, **kw):
         except subprocess.CalledProcessError as e:
             output = e.output
             ret = e.returncode
+        except SystemExit as e:  # possibly raised by argparse
+            output = ''
+            ret = e.code
         return ret, os.fsdecode(output)
     else:
         stdin, stdout, stderr = sys.stdin, sys.stdout, sys.stderr
@@ -987,8 +990,13 @@ class ArchiverTestCase(ArchiverTestCaseBase):
         self.cmd('init', self.repository_location)
         self.cmd('create', self.repository_location + '::test', 'input')
         self.cmd('create', self.repository_location + '::test.2', 'input')
+        self.cmd('create', self.repository_location + '::test.3', 'input')
+        self.cmd('create', self.repository_location + '::another_test.1', 'input')
+        self.cmd('create', self.repository_location + '::another_test.2', 'input')
         self.cmd('extract', '--dry-run', self.repository_location + '::test')
         self.cmd('extract', '--dry-run', self.repository_location + '::test.2')
+        self.cmd('delete', '--prefix', 'another_', self.repository_location)
+        self.cmd('delete', '--last', '1', self.repository_location)
         self.cmd('delete', self.repository_location + '::test')
         self.cmd('extract', '--dry-run', self.repository_location + '::test.2')
         output = self.cmd('delete', '--stats', self.repository_location + '::test.2')
@@ -1811,6 +1819,12 @@ class ArchiverTestCase(ArchiverTestCaseBase):
         self.assert_not_in("input/file1", output)
         self.assert_not_in("x input/file5", output)
 
+    def test_bad_filters(self):
+        self.cmd('init', self.repository_location)
+        self.cmd('create', self.repository_location + '::test', 'input')
+        self.cmd('delete', '--first', '1', '--last', '1', self.repository_location, fork=True, exit_code=2)
+
+
     def test_key_export_keyfile(self):
         export_file = self.output_path + '/exported'
         self.cmd('init', self.repository_location, '--encryption', 'keyfile')