Repository: borgbackup/borg

Issue:
Don't print a stack trace when missing file with exclude patterns
Today I copied a shell script to create (and name appropriately, following my standards) a backup using borg. When I tried using that script on the host I had copied it to, borg said:

```
Traceback (most recent call last):
  File "/usr/bin/borg", line 11, in <module>
    load_entry_point('borgbackup==1.1.9', 'console_scripts', 'borg')()
  File "/usr/lib/python3/dist-packages/borg/archiver.py", line 4444, in main
    args = archiver.get_args(sys.argv, os.environ.get('SSH_ORIGINAL_COMMAND'))
  File "/usr/lib/python3/dist-packages/borg/archiver.py", line 4279, in get_args
    result = self.parse_args(argv[1:])
  File "/usr/lib/python3/dist-packages/borg/archiver.py", line 4302, in parse_args
    args = parser.parse_args(args or ['-h'])
  File "/usr/lib/python3.7/argparse.py", line 1758, in parse_args
    args, argv = self.parse_known_args(args, namespace)
  File "/usr/lib/python3.7/argparse.py", line 1790, in parse_known_args
    namespace, args = self._parse_known_args(args, namespace)
  File "/usr/lib/python3.7/argparse.py", line 1978, in _parse_known_args
    positionals_end_index = consume_positionals(start_index)
  File "/usr/lib/python3.7/argparse.py", line 1955, in consume_positionals
    take_action(action, args)
  File "/usr/lib/python3.7/argparse.py", line 1864, in take_action
    action(self, namespace, argument_values, option_string)
  File "/usr/lib/python3.7/argparse.py", line 1161, in __call__
    subnamespace, arg_strings = parser.parse_known_args(arg_strings, None)
  File "/usr/lib/python3.7/argparse.py", line 1790, in parse_known_args
    namespace, args = self._parse_known_args(args, namespace)
  File "/usr/lib/python3.7/argparse.py", line 1996, in _parse_known_args
    start_index = consume_optional(start_index)
  File "/usr/lib/python3.7/argparse.py", line 1936, in consume_optional
    take_action(action, args, option_string)
  File "/usr/lib/python3.7/argparse.py", line 1864, in take_action
    action(self, namespace, argument_values, option_string)
  File "/usr/lib/python3/dist-packages/borg/patterns.py", line 56, in __call__
    with open(filename) as f:
FileNotFoundError: [Errno 2] No such file or directory: '.borgbackup-exclude'
```

There's no value in anything but the last line, and before I saw that, seeing a stack trace made me wonder what kind of mess I had gotten myself into.

I'm not good with python, but it is probably a simple change (it would be in perl), not to output the stack trace.


Discussion:
@hcgrove do you use borg 1.1.9? If yes, I strongly recommend update borg to the latest version. Also try to reproduce error with the latest version.
I must admit that I didn't even think about versions earlier when I experienced this. I was just using the version packaged in Debian Buster, which was 1.1.9.
Now I've tried upgrading to the version in Buster-backports which claims to be 1.1.15, that GitHub also says is the latest.
And I get the same behaviour with that, the stack trace looks like:
```
Traceback (most recent call last):
  File "/usr/bin/borg", line 11, in <module>
    load_entry_point('borgbackup==1.1.15', 'console_scripts', 'borg')()
  File "/usr/lib/python3/dist-packages/borg/archiver.py", line 4610, in main
    args = archiver.get_args(sys.argv, os.environ.get('SSH_ORIGINAL_COMMAND'))
  File "/usr/lib/python3/dist-packages/borg/archiver.py", line 4407, in get_args
    result = self.parse_args(argv[1:])
  File "/usr/lib/python3/dist-packages/borg/archiver.py", line 4453, in parse_args
    args = parser.parse_args(args or ['-h'])
  File "/usr/lib/python3.7/argparse.py", line 1758, in parse_args
    args, argv = self.parse_known_args(args, namespace)
  File "/usr/lib/python3.7/argparse.py", line 1790, in parse_known_args
    namespace, args = self._parse_known_args(args, namespace)
  File "/usr/lib/python3.7/argparse.py", line 1978, in _parse_known_args
    positionals_end_index = consume_positionals(start_index)
  File "/usr/lib/python3.7/argparse.py", line 1955, in consume_positionals
    take_action(action, args)
  File "/usr/lib/python3.7/argparse.py", line 1864, in take_action
    action(self, namespace, argument_values, option_string)
  File "/usr/lib/python3.7/argparse.py", line 1161, in __call__
    subnamespace, arg_strings = parser.parse_known_args(arg_strings, None)
  File "/usr/lib/python3.7/argparse.py", line 1790, in parse_known_args
    namespace, args = self._parse_known_args(args, namespace)
  File "/usr/lib/python3.7/argparse.py", line 1996, in _parse_known_args
    start_index = consume_optional(start_index)
  File "/usr/lib/python3.7/argparse.py", line 1936, in consume_optional
    take_action(action, args, option_string)
  File "/usr/lib/python3.7/argparse.py", line 1864, in take_action
    action(self, namespace, argument_values, option_string)
  File "/usr/lib/python3/dist-packages/borg/patterns.py", line 56, in __call__
    with open(filename) as f:
FileNotFoundError: [Errno 2] No such file or directory: '.borgbackup-exclude'
```
 
Check if we can catch `FileNotFoundError` and `raise Error(str(e))`.
Ok I'll have a look at it.

Resolving patch:
diff --git a/src/borg/patterns.py b/src/borg/patterns.py
--- a/src/borg/patterns.py
+++ b/src/borg/patterns.py
@@ -9,6 +9,7 @@
 
 from . import shellpattern
 from .helpers import clean_lines
+from .helpers.errors import Error
 
 
 def parse_patternfile_line(line, roots, ie_commands, fallback):
@@ -53,8 +54,11 @@ def __call__(self, parser, args, values, option_string=None):
         Lines empty or starting with '#' after stripping whitespace on both line ends are ignored.
         """
         filename = values[0]
-        with open(filename) as f:
-            self.parse(f, args)
+        try:
+            with open(filename) as f:
+                self.parse(f, args)
+        except FileNotFoundError as e:
+            raise Error(str(e))
 
     def parse(self, fobj, args):
         load_pattern_file(fobj, args.paths, args.patterns)


Test patch: