Repository: borgbackup/borg

Issue:
1.1.1 gives a BrokenPipeError when sending output to | less
With 1.1.1 when I run `borg list | less` I get this exception. It works fine on 1.0.11.

> Local Exception
> Traceback (most recent call last):
>   File "borg/archiver.py", line 4049, in main
>   File "borg/archiver.py", line 3977, in run
>   File "borg/archiver.py", line 148, in wrapper
>   File "borg/archiver.py", line 1356, in do_list
>   File "borg/archiver.py", line 1399, in _list_repository
> BrokenPipeError: [Errno 32] Broken pipe
> 
> Platform: Linux borg 4.9.0-4-amd64 #1 SMP Debian 4.9.51-1 (2017-09-28) x86_64
> Linux: debian 9.2
> Borg: 1.1.1  Python: CPython 3.5.4
> PID: 1956  CWD: /root/borg-script/1.1
> sys.argv: ['./borg-1.1.1', 'list']
> SSH_ORIGINAL_COMMAND: None
> 
> Exception ignored in: <_io.TextIOWrapper name='<stdout>' encoding='UTF-8'>
> BrokenPipeError: [Errno 32] Broken pipe



Discussion:
check: #790 #1151 #1379
in archiver.main, we have these to address `BrokenPipeError`:
```
    sys.stdout = ErrorIgnoringTextIOWrapper(sys.stdout.buffer, sys.stdout.encoding, 'replace', line_buffering=True)
    sys.stderr = ErrorIgnoringTextIOWrapper(sys.stderr.buffer, sys.stderr.encoding, 'replace', line_buffering=True)
```

and do_list does not use that by doing:
```
        if not hasattr(sys.stdout, 'buffer'):
            # This is a shim for supporting unit tests replacing sys.stdout with e.g. StringIO,
            # which doesn't have an underlying buffer (= lower file object).
            def write(bytestring):
                sys.stdout.write(bytestring.decode('utf-8', errors='replace'))
        else:
            write = sys.stdout.buffer.write
```

Just use that write function in any case?

Resolving patch:
diff --git a/src/borg/archiver.py b/src/borg/archiver.py
--- a/src/borg/archiver.py
+++ b/src/borg/archiver.py
@@ -1343,26 +1343,18 @@ def do_umount(self, args):
     @with_repository(compatibility=(Manifest.Operation.READ,))
     def do_list(self, args, repository, manifest, key):
         """List archive or repository contents"""
-        if not hasattr(sys.stdout, 'buffer'):
-            # This is a shim for supporting unit tests replacing sys.stdout with e.g. StringIO,
-            # which doesn't have an underlying buffer (= lower file object).
-            def write(bytestring):
-                sys.stdout.write(bytestring.decode('utf-8', errors='replace'))
-        else:
-            write = sys.stdout.buffer.write
-
         if args.location.archive:
             if args.json:
                 self.print_error('The --json option is only valid for listing archives, not archive contents.')
                 return self.exit_code
-            return self._list_archive(args, repository, manifest, key, write)
+            return self._list_archive(args, repository, manifest, key)
         else:
             if args.json_lines:
                 self.print_error('The --json-lines option is only valid for listing archive contents, not archives.')
                 return self.exit_code
-            return self._list_repository(args, repository, manifest, key, write)
+            return self._list_repository(args, repository, manifest, key)
 
-    def _list_archive(self, args, repository, manifest, key, write):
+    def _list_archive(self, args, repository, manifest, key):
         matcher = self.build_matcher(args.patterns, args.paths)
         if args.format is not None:
             format = args.format
@@ -1377,7 +1369,7 @@ def _list_inner(cache):
 
             formatter = ItemFormatter(archive, format, json_lines=args.json_lines)
             for item in archive.iter_items(lambda item: matcher.match(item.path)):
-                write(safe_encode(formatter.format_item(item)))
+                sys.stdout.write(formatter.format_item(item))
 
         # Only load the cache if it will be used
         if ItemFormatter.format_needs_cache(format):
@@ -1388,7 +1380,7 @@ def _list_inner(cache):
 
         return self.exit_code
 
-    def _list_repository(self, args, repository, manifest, key, write):
+    def _list_repository(self, args, repository, manifest, key):
         if args.format is not None:
             format = args.format
         elif args.short:
@@ -1403,7 +1395,7 @@ def _list_repository(self, args, repository, manifest, key, write):
             if args.json:
                 output_data.append(formatter.get_item_data(archive_info))
             else:
-                write(safe_encode(formatter.format_item(archive_info)))
+                sys.stdout.write(formatter.format_item(archive_info))
 
         if args.json:
             json_print(basic_json_data(manifest, extra={


Test patch: