Repository: borgbackup/borg

Issue:
borg recreate and --exclude-if-present flag behavior isn't very clear
## Have you checked borgbackup docs, FAQ, and open Github issues?

Yes

## Is this a BUG / ISSUE report or a QUESTION?

Documentation Bug, IMO

## System information. For client/server mode post info for both machines.
Linux home-desktop 5.3.0-53-generic #47~18.04.1-Ubuntu SMP Thu May 7 13:10:50 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
Distro - KDE Neon

#### Your borg version (borg -V).
borg 1.1.11

#### Hardware / network configuration, and filesystems used.
ext4, local disk

#### How much data is handled by borg?
12Gig repo

#### Full borg commandline that lead to the problem (leave away excludes and passwords)
borg recreate --exclude-if-present .nobackup /data/borg-backup

## Describe the problem you're observing.
So I figured that there was a lot of cruft built up and my overall backup size had grown a lot (12G),
Mounting the backup, it was clear that a lot of thing in my home folder were the problem
for instance: `/home/raghu/.arduino15` isn't a folder I need. Ditto for `/home/raghu/code/go`
For my nightly backups, I have `borg create <archive> --exclude-if-present .nobackup /home /etc` and my preference is to exclude folders with a `touch /path/to/exclude/.nobackup` 

So, before running recreate, I dropped a lot of `.nobackup`  files into folders that I needed to delete. In my mind, I figure that these folders will be excluded by virtue of me dropping `.nobackup` files into the folders. It doesn't work of course. 

Obviously (well, not obvious at all - took me more than a day), the penny finally drops and I realize that recreate ONLY looks at the files/folders already IN the archive - and won't touch the fs at all.

So now, I've reverted to using `--exclude-from-file` - but the behavior of  `--exclude-if-present` should really be highlighted in the documentation on the `recreate` page.

<!--

If this complaint relates to borg performance, please include CRUD benchmark
results and any steps you took to troubleshoot.
How to run benchmark: http://borgbackup.readthedocs.io/en/stable/usage/benchmark.html

*IMPORTANT* - Please mark logs and text output from terminal commands 
or else Github will not display them correctly. 
An example is provided below.

Example:
```
this is an example how log text should be marked (wrap it with ```)
```
-->


Discussion:

Patch:
diff --git a/docs/usage/notes.rst b/docs/usage/notes.rst
index 59f56c34..0fd58a63 100644
--- a/docs/usage/notes.rst
+++ b/docs/usage/notes.rst
@@ -84,19 +84,12 @@ use this option also for speeding up operations.
 ``--umask``
 ~~~~~~~~~~~
 
-borg uses a safe default umask of 077 (that means the files borg creates have
-only permissions for owner, but no permissions for group and others) - so there
-should rarely be a need to change the default behaviour.
-
-This option only affects the process to which it is given. Thus, when you run
-borg in client/server mode and you want to change the behaviour on the server
-side, you need to use ``borg serve --umask=XXX ...`` as a ssh forced command
-in ``authorized_keys``. The ``--umask`` value given on the client side is
-**not** transferred to the server side.
-
-Also, if you choose to use the ``--umask`` option, always be consistent and use
-the same umask value so you do not create a mixup of permissions in a borg
-repository or with other files borg creates.
+If you use ``--umask``, make sure that all repository-modifying borg commands
+(create, delete, prune) that access the repository in question use the same
+``--umask`` value.
+
+If multiple machines access the same repository, this should hold true for all
+of them.
 
 ``--read-special``
 ~~~~~~~~~~~~~~~~~~
diff --git a/src/borg/archiver.py b/src/borg/archiver.py
index 1b6482b6..8080b509 100644
--- a/src/borg/archiver.py
+++ b/src/borg/archiver.py
@@ -2617,7 +2617,7 @@ def define_common_options(add_common_option):
             add_common_option('--show-rc', dest='show_rc', action='store_true',
                               help='show/log the return code (rc)')
             add_common_option('--umask', metavar='M', dest='umask', type=lambda s: int(s, 8), default=UMASK_DEFAULT,
-                              help='set umask to M (local only, default: %(default)04o)')
+                              help='set umask to M (local and remote, default: %(default)04o)')
             add_common_option('--remote-path', metavar='PATH', dest='remote_path',
                               help='use PATH as borg executable on the remote (default: "borg")')
             add_common_option('--remote-ratelimit', metavar='RATE', dest='remote_ratelimit', type=int,
@@ -4092,9 +4092,10 @@ def define_borg_mount(parser):
 
         Important: Repository disk space is **not** freed until you run ``borg compact``.
 
-        ``--exclude``, ``--exclude-from``, ``--exclude-if-present``, ``--keep-exclude-tags``, and PATH
-        have the exact same semantics as in "borg create". If PATHs are specified the
-        resulting archive will only contain files from these PATHs.
+        ``--exclude``, ``--exclude-from``, ``--exclude-if-present``, ``--keep-exclude-tags``
+        and PATH have the exact same semantics as in "borg create", but they only check
+        for files in the archives and not in the local file system. If PATHs are specified,
+        the resulting archives will only contain files from these PATHs.
 
         Note that all paths in an archive are relative, therefore absolute patterns/paths
         will *not* match (``--exclude``, ``--exclude-from``, PATHs).
@@ -4422,12 +4423,12 @@ def get_args(self, argv, cmd):
                     'restrict_to_repositories',
                     'append_only',
                     'storage_quota',
-                    'umask',
                 }
                 whitelist = {
                     'debug_topics',
                     'lock_wait',
                     'log_level',
+                    'umask',
                 }
                 not_present = object()
                 for attr_name in whitelist:
diff --git a/src/borg/remote.py b/src/borg/remote.py
index fa04c7b2..99ea53d4 100644
--- a/src/borg/remote.py
+++ b/src/borg/remote.py
@@ -647,6 +647,7 @@ def borg_cmd(self, args, testing):
         # give some args/options to 'borg serve' process as they were given to us
         opts = []
         if args is not None:
+            opts.append('--umask=%03o' % args.umask)
             root_logger = logging.getLogger()
             if root_logger.isEnabledFor(logging.DEBUG):
                 opts.append('--debug')


Test patch:
diff --git a/src/borg/testsuite/archiver.py b/src/borg/testsuite/archiver.py
index 3b970071..8383e13f 100644
--- a/src/borg/testsuite/archiver.py
+++ b/src/borg/testsuite/archiver.py
@@ -3833,8 +3833,8 @@ def test_get_args():
     # everything normal:
     # first param is argv as produced by ssh forced command,
     # second param is like from SSH_ORIGINAL_COMMAND env variable
-    args = archiver.get_args(['borg', 'serve', '--umask=0027', '--restrict-to-path=/p1', '--restrict-to-path=/p2', ],
-                             'borg serve --info')
+    args = archiver.get_args(['borg', 'serve', '--restrict-to-path=/p1', '--restrict-to-path=/p2', ],
+                             'borg serve --info --umask=0027')
     assert args.func == archiver.do_serve
     assert args.restrict_to_paths == ['/p1', '/p2']
     assert args.umask == 0o027
diff --git a/src/borg/testsuite/repository.py b/src/borg/testsuite/repository.py
index 9c318355..99939a60 100644
--- a/src/borg/testsuite/repository.py
+++ b/src/borg/testsuite/repository.py
@@ -885,17 +885,17 @@ def test_borg_cmd(self):
         # XXX without next line we get spurious test fails when using pytest-xdist, root cause unknown:
         logging.getLogger().setLevel(logging.INFO)
         # note: test logger is on info log level, so --info gets added automagically
-        assert self.repository.borg_cmd(args, testing=False) == ['borg', 'serve', '--info']
+        assert self.repository.borg_cmd(args, testing=False) == ['borg', 'serve', '--umask=077', '--info']
         args.remote_path = 'borg-0.28.2'
-        assert self.repository.borg_cmd(args, testing=False) == ['borg-0.28.2', 'serve', '--info']
+        assert self.repository.borg_cmd(args, testing=False) == ['borg-0.28.2', 'serve', '--umask=077', '--info']
         args.debug_topics = ['something_client_side', 'repository_compaction']
-        assert self.repository.borg_cmd(args, testing=False) == ['borg-0.28.2', 'serve', '--info',
+        assert self.repository.borg_cmd(args, testing=False) == ['borg-0.28.2', 'serve', '--umask=077', '--info',
                                                                  '--debug-topic=borg.debug.repository_compaction']
         args = self._get_mock_args()
         args.storage_quota = 0
-        assert self.repository.borg_cmd(args, testing=False) == ['borg', 'serve', '--info']
+        assert self.repository.borg_cmd(args, testing=False) == ['borg', 'serve', '--umask=077', '--info']
         args.storage_quota = 314159265
-        assert self.repository.borg_cmd(args, testing=False) == ['borg', 'serve', '--info',
+        assert self.repository.borg_cmd(args, testing=False) == ['borg', 'serve', '--umask=077', '--info',
                                                                  '--storage-quota=314159265']
         args.rsh = 'ssh -i foo'
         self.repository._args = args