Repository: borgbackup/borg

Issue:
borg delete --force should kill repo without passphrase
There should be a way to cleanly remove a repo even if the passphrase was lost.

`borg delete REPO` wants to know the passphrase (and if given, it will list all archives in the repo and ask "are you sure?").
to list the archives, the key is needed, so it asks for the passphrase.

But if the user is really sure anyway, we could offer a non-default less safe way with `borg delete --force REPO` and just kill the repo, no questions asked.

Of course, this will **not** delete an append-only mode repository.


Workaround until this is implemented:
```
borg config REPO id     # gives id for manual clean up
rm -rf REPO
rm -rf ~/.cache/borg/THATID
rm -rf ~/.config/borg/security/THATID
```



Discussion:
From what I tell, there doesn't seem to be an existing test case for deleting an append only repo in `src/borg/testsuite/archiver.py`. I have something like this:

```python
def test_delete_force_repo_append_only(self):
        os.environ['BORG_PASSPHRASE'] = 'passphrase'
        self.cmd('init', '--encryption=repokey', '--append-only', self.repository_location)
        self.cmd('create', self.repository_location + '::test', 'input')
        # Should not delete an append-only repository
        os.environ['BORG_DELETE_I_KNOW_WHAT_I_AM_DOING'] = 'no'
        os.environ['BORG_PASSPHRASE'] = ''
        with self.assert_raises(ValueError):
            output = self.cmd('delete', '--force', self.repository_location, exit_code=EXIT_ERROR)
            assert os.path.exists(self.repository_path)
            self.assert_not_in('request', output)
```

It fails with an RPCError when the test suite passes it a `RemoteRepository`. How do you think I should handle this?
@remyabel please move that to a new ticket.
Sorry if I wasn't clear. It's relevant because after adding the functionality, I also wanted to test this part:

> Of course, this will not delete an append-only mode repository.

I will open a new issue.

Resolving patch:
diff --git a/src/borg/archiver.py b/src/borg/archiver.py
--- a/src/borg/archiver.py
+++ b/src/borg/archiver.py
@@ -1241,29 +1241,30 @@ def _delete_repository(self, args, repository):
         keep_security_info = args.keep_security_info
 
         if not args.cache_only:
-            msg = []
-            try:
-                manifest, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK)
-            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:
-                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))
+            if args.forced == 0:  # without --force, we let the user see the archives list and confirm.
+                msg = []
+                try:
+                    manifest, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK)
+                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* %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',),
-                       retry=False, env_var_override='BORG_DELETE_I_KNOW_WHAT_I_AM_DOING'):
-                self.exit_code = EXIT_ERROR
-                return self.exit_code
+                    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',),
+                           retry=False, env_var_override='BORG_DELETE_I_KNOW_WHAT_I_AM_DOING'):
+                    self.exit_code = EXIT_ERROR
+                    return self.exit_code
             if not dry_run:
                 repository.destroy()
                 logger.info("Repository deleted.")


Test patch: