Repository: borgbackup/borg

Issue:
borg delete: implement --dry-run
So one can first see what it would do, before actually deleting stuff.


Discussion:
Do you think that there is a need for more information than just listing all archives which are to be deleted (with the information of the creation date and the id).

For example, the output I'm thinking about would be this one (close to the prune `--dry-run` stuff).
```
$ borg delete --dry-run ~/tmpfs::archive1
would delete archive1                             Wed, 2018-05-16 19:02:12 [36c4b33fd3ba3972d7b7c94fb40dded88e05651537a7fb20e66de687112e42f5]

$ borg delete --dry-run ~/tmpfs

would delete archive1                             Wed, 2018-05-16 19:02:12 [36c4b33fd3ba3972d7b7c94fb40dded88e05651537a7fb20e66de687112e42f5]
would delete repository ~/tmpfs
```
Looks fine. Try to be close to borg prune, for consistency.

Resolving patch:
diff --git a/src/borg/archiver.py b/src/borg/archiver.py
--- a/src/borg/archiver.py
+++ b/src/borg/archiver.py
@@ -1230,6 +1230,8 @@ def do_delete(self, args, repository):
 
     def _delete_archives(self, args, repository):
         """Delete archives"""
+        dry_run = args.dry_run
+
         manifest, key = Manifest.load(repository, (Manifest.Operation.DELETE,))
 
         if args.location.archive or args.archives:
@@ -1252,8 +1254,11 @@ def _delete_archives(self, args, repository):
                     logger.warning('Archive {} not found ({}/{}).'.format(archive_name, i, len(archive_names)))
                 else:
                     deleted = True
-                    logger.info('Deleted {} ({}/{}).'.format(archive_name, i, len(archive_names)))
-            if deleted:
+                    msg = 'Would delete: {} ({}/{})' if dry_run else 'Deleted archive: {} ({}/{})'
+                    logger.info(msg.format(archive_name, i, len(archive_names)))
+            if dry_run:
+                logger.info('Finished dry-run.')
+            elif deleted:
                 manifest.write()
                 # note: might crash in compact() after committing the repo
                 repository.commit()
@@ -1262,32 +1267,31 @@ def _delete_archives(self, args, repository):
                 logger.warning('Aborted.')
             return self.exit_code
 
-        stats_logger = logging.getLogger('borg.output.stats')
-        if args.stats:
-            log_multi(DASHES, STATS_HEADER, logger=stats_logger)
-
+        stats = Statistics()
         with Cache(repository, key, manifest, progress=args.progress, 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)
+                msg = 'Would delete archive: {} ({}/{})' if dry_run else 'Deleting archive: {} ({}/{})'
+                logger.info(msg.format(archive_name, i, len(archive_names)))
+                if not dry_run:
+                    Archive(repository, key, manifest, archive_name, cache=cache).delete(
+                        stats, progress=args.progress, forced=args.forced)
+            if not dry_run:
                 manifest.write()
                 repository.commit(save_space=args.save_space)
                 cache.commit()
-                logger.info("Archive deleted.")
-                if args.stats:
-                    log_multi(stats.summary.format(label='Deleted data:', stats=stats),
-                              DASHES, logger=stats_logger)
-                if args.forced == 0 and self.exit_code:
-                    break
             if args.stats:
-                stats_logger.info(str(cache))
+                log_multi(DASHES,
+                          STATS_HEADER,
+                          stats.summary.format(label='Deleted data:', stats=stats),
+                          str(cache),
+                          DASHES, logger=logging.getLogger('borg.output.stats'))
 
         return self.exit_code
 
     def _delete_repository(self, args, repository):
         """Delete a repository"""
+        dry_run = args.dry_run
+
         if not args.cache_only:
             msg = []
             try:
@@ -1308,11 +1312,17 @@ def _delete_repository(self, args, repository):
                        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.")
-            SecurityManager.destroy(repository)
-        Cache.destroy(repository)
-        logger.info("Cache deleted.")
+            if not dry_run:
+                repository.destroy()
+                logger.info("Repository deleted.")
+                SecurityManager.destroy(repository)
+            else:
+                logger.info("Would delete repository.")
+        if not dry_run:
+            Cache.destroy(repository)
+            logger.info("Cache deleted.")
+        else:
+            logger.info("Would delete cache.")
         return self.exit_code
 
     def do_mount(self, args):
@@ -3380,6 +3390,8 @@ def define_archive_filters_group(subparser, *, sort_by=True, first_last=True):
                                           formatter_class=argparse.RawDescriptionHelpFormatter,
                                           help='delete archive')
         subparser.set_defaults(func=self.do_delete)
+        subparser.add_argument('-n', '--dry-run', dest='dry_run', action='store_true',
+                               help='do not change repository')
         subparser.add_argument('-s', '--stats', dest='stats', action='store_true',
                                help='print statistics for the deleted archive')
         subparser.add_argument('--cache-only', dest='cache_only', action='store_true',


Test patch: