Repository: borgbackup/borg

Issue:
Inconsistent switch to display changes during 'dry-run' operation
There are two operations I use, so far, to destructively modify a repository:
_delete_ and _recreate_

They allow wildcards, patterns, etc.  Obviously this is a dangerous operation and therefore the '--dry-run' switch exists for both to check your work before execution.

I have found an inconsistency in the method of displaying the proposed (dry-run) changes in order to review them however.

When I use _recreate_, the switches are "--dry-run --list" which will output what is going to happen.

However when using _delete_  the same process of "--dry-run --list" results in error:
_borg: error: unrecognized arguments: --list_

When using _delete_ , instead (through trial and error) I found out that instead I have to use:
"--dry-run --verbose"

My issues are:
1) this is not consistent switch usage for the listing of changes among operations that are relatively similar/destructive/deleting/changing; I think '--list' should function for both; (addendum: '--list' is also used with _create_ to likewise show added files and directories)

2) '--verbose' is generally viewed in a debugging light and should not be used in this context to display the desired output of a '--dry-run'; (It can still do that, but I think the 'what will change with this command' should be primarily on the '--list' command. verbose might include other info as well);

3) if '--verbose' is kept as the command to review results of a _delete_ dry-run it is not listed on the documentation page, (whereas the _recreate_ documentation page has '--list' right next to the '--dry-run' switch so a user can immediately grasp what to do.)  I suggest adding it under --dry-run switch to clarify how to see proposed deletions:
"_--verbose_      output list of items deleted (or to delete with --dry-run)"


Discussion:
There may be other operations that have --dry-run options.  I would suggest (asserting that nobody does a --dry-run without wanting to see what will happen) that '--list' is available consistently in all such circumstances. 
I'm using 1.1.11
maybe one should do a global review about consistency relating to this.
Also worth noting that https://borgbackup.readthedocs.io/en/stable/usage/delete.html makes no mention of `--verbose` or `-v` and without those options `borg delete --dry-run` produces no output whatsoever, which probably makes it rather useless to the average user who has not searched the GitHub issues.
`-v` or `--info` sets the log level to `INFO` and is a global borg option.

By default, the level is `WARNING` and you won't see info-level output.

So guess the global log level stuff is only explained at some other place in the docs, but maybe we could at least add an example showing correct usage of `--dry-run` with `-v` or `--info`.
Is there *any* situation where a person does a --dry-run but does not want to see the output?
Unlikely. :-) But the point is that borg needs to behave consistently and not do too much magic.

So, if you want to investigate, look at how similar use cases work (if their behaviour differs and is better).
Or, what else all needs to change IF we need a new mechanism for this and want consistent behaviour.

I think that:
 * It is necessary to add the --list switch to _delete_
 * Update the documentation

This way we have a consistent way of listing files in all commands.
I can work on this one.
"update docs" likely means to just update `archiver.py` strings, the docs are auto-generated from that.

except the examples, they need to be edited in the respective rst files.

Resolving patch:
diff --git a/src/borg/archiver.py b/src/borg/archiver.py
--- a/src/borg/archiver.py
+++ b/src/borg/archiver.py
@@ -1144,6 +1144,7 @@ def do_delete(self, args, repository):
         """Delete an existing repository or archives"""
         archive_filter_specified = any((args.first, args.last, args.prefix is not None, args.glob_archives))
         explicit_archives_specified = args.location.archive or args.archives
+        self.output_list = args.output_list
         if archive_filter_specified and explicit_archives_specified:
             self.print_error('Mixing archive filters and explicitly named archives is not supported.')
             return self.exit_code
@@ -1171,6 +1172,7 @@ def _delete_archives(self, args, repository):
 
         if args.forced == 2:
             deleted = False
+            logger_list = logging.getLogger('borg.output.list')
             for i, archive_name in enumerate(archive_names, 1):
                 try:
                     current_archive = manifest.archives.pop(archive_name)
@@ -1179,8 +1181,10 @@ def _delete_archives(self, args, repository):
                     logger.warning('Archive {} not found ({}/{}).'.format(archive_name, i, len(archive_names)))
                 else:
                     deleted = True
-                    msg = 'Would delete: {} ({}/{})' if dry_run else 'Deleted archive: {} ({}/{})'
-                    logger.info(msg.format(format_archive(current_archive), i, len(archive_names)))
+                    if self.output_list:
+                        msg = 'Would delete: {} ({}/{})' if dry_run else 'Deleted archive: {} ({}/{})'
+                        logger_list.info(msg.format(format_archive(current_archive),
+                                                    i, len(archive_names)))
             if dry_run:
                 logger.info('Finished dry-run.')
             elif deleted:
@@ -1196,13 +1200,16 @@ def _delete_archives(self, args, repository):
         with Cache(repository, key, manifest, progress=args.progress, lock_wait=self.lock_wait) as cache:
             msg_delete = 'Would delete archive: {} ({}/{})' if dry_run else 'Deleting archive: {} ({}/{})'
             msg_not_found = 'Archive {} not found ({}/{}).'
+            logger_list = logging.getLogger('borg.output.list')
             for i, archive_name in enumerate(archive_names, 1):
                 try:
                     archive_info = manifest.archives[archive_name]
                 except KeyError:
                     logger.warning(msg_not_found.format(archive_name, i, len(archive_names)))
                 else:
-                    logger.info(msg_delete.format(format_archive(archive_info), i, len(archive_names)))
+                    if self.output_list:
+                        logger_list.info(msg_delete.format(format_archive(archive_info), i, len(archive_names)))
+
                     if not dry_run:
                         archive = Archive(repository, key, manifest, archive_name, cache=cache,
                                           consider_part_files=args.consider_part_files)
@@ -1236,10 +1243,14 @@ def _delete_repository(self, args, repository):
                 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))
+                if self.output_list:
+                    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))
+                else:
+                    msg.append("You requested to completely DELETE the repository *including* %d archives it contains."
+                               % len(manifest.archives))
             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',),
@@ -3486,6 +3497,8 @@ def define_borg_mount(parser):
         with the ``--cache-only`` option, or keep the security info with the
         ``--keep-security-info`` option.
 
+        When in doubt, use ``--dry-run --list`` to see what would be deleted.
+
         When using ``--stats``, you will get some statistics about how much data was
         deleted - the "Deleted data" deduplicated size there is most interesting as
         that is how much your repository will shrink.
@@ -3509,6 +3522,8 @@ def define_borg_mount(parser):
         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('--list', dest='output_list', action='store_true',
+                               help='output verbose list of archives')
         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: