Skip to content

What's the true --pattern equivalent of --exclude #9654

@PhrozenByte

Description

@PhrozenByte

I'm currently working on refactoring the borg help patterns docs. I was wondering: What's the true --pattern equivalent of --exclude?

Testing with Borg 1.4.4.

According to the current docs (reinforced multiple times), the --exclude option matches - exclude patterns (fm: style, but let's ignore that for now). However, this doesn't seem to be true: In practice it looks more like --exclude matches ! no-recurse exclude patterns.

The main difference between - and ! is that if a directory is excluded with !, it isn't traversed by Borg, i.e., the directory's contents are effectively also excluded. This means that if I use a pattern style that doesn't implicitly also match a directory's contents, i.e. pf: or re:, Borg should exclude the matched directory, but not its contents. This is exactly what I'm seeing with --pattern and -, but not what I see with --exclude. With --exclude directory contents are always excluded, too.

Take the following test setup:

$ borg create repo::archive data
$ borg list repo::archive
drwxr-xr-x daniel daniel        0 Sun, 2026-05-17 23:30:25 data
drwxr-xr-x daniel daniel        0 Sun, 2026-05-17 23:30:07 data/foo
drwxr-xr-x daniel daniel        0 Sun, 2026-05-17 23:29:48 data/foo/bar
drwxr-xr-x daniel daniel        0 Sun, 2026-05-17 23:29:53 data/foo/bar/baz
-rw-r--r-- daniel daniel        3 Sun, 2026-05-17 23:29:53 data/foo/bar/baz/42
-rw-r--r-- daniel daniel        4 Sun, 2026-05-17 23:30:00 data/foo/bla
drwxr-xr-x daniel daniel        0 Sun, 2026-05-17 23:30:12 data/foo/Hello
-rw-r--r-- daniel daniel        6 Sun, 2026-05-17 23:30:12 data/foo/Hello/World
-rw-r--r-- daniel daniel        6 Sun, 2026-05-17 23:30:25 data/blubb

Let's try a pf: exclusion pattern with --pattern:

$ borg create --pattern='- pf:data/foo/bar' repo::archive1 data
$ borg list repo::archive1
drwxr-xr-x daniel daniel        0 Sun, 2026-05-17 23:30:25 data
drwxr-xr-x daniel daniel        0 Sun, 2026-05-17 23:30:07 data/foo
drwxr-xr-x daniel daniel        0 Sun, 2026-05-17 23:29:53 data/foo/bar/baz
-rw-r--r-- daniel daniel        3 Sun, 2026-05-17 23:29:53 data/foo/bar/baz/42
-rw-r--r-- daniel daniel        4 Sun, 2026-05-17 23:30:00 data/foo/bla
drwxr-xr-x daniel daniel        0 Sun, 2026-05-17 23:30:12 data/foo/Hello
-rw-r--r-- daniel daniel        6 Sun, 2026-05-17 23:30:12 data/foo/Hello/World
-rw-r--r-- daniel daniel        6 Sun, 2026-05-17 23:30:25 data/blubb

As expected, data/foo/bar is excluded, but the directory's contents (data/foo/bar/baz and data/foo/bar/baz/42) are not.

Let's try the supposedly same pf: exclusion pattern with --exclude instead:

$ borg create --exclude='pf:data/foo/bar' repo::archive2 data
$ borg list repo::archive2
drwxr-xr-x daniel daniel        0 Sun, 2026-05-17 23:30:25 data
drwxr-xr-x daniel daniel        0 Sun, 2026-05-17 23:30:07 data/foo
-rw-r--r-- daniel daniel        4 Sun, 2026-05-17 23:30:00 data/foo/bla
drwxr-xr-x daniel daniel        0 Sun, 2026-05-17 23:30:12 data/foo/Hello
-rw-r--r-- daniel daniel        6 Sun, 2026-05-17 23:30:12 data/foo/Hello/World
-rw-r--r-- daniel daniel        6 Sun, 2026-05-17 23:30:25 data/blubb

The directory's contents are also excluded!

This better matches the behaviour of no-recurse exclusion patterns with --pattern:

$ borg create --pattern='! pf:data/foo/bar' repo::archive3 data
$ borg list repo::archive3
drwxr-xr-x daniel daniel        0 Sun, 2026-05-17 23:30:25 data
drwxr-xr-x daniel daniel        0 Sun, 2026-05-17 23:30:07 data/foo
-rw-r--r-- daniel daniel        4 Sun, 2026-05-17 23:30:00 data/foo/bla
drwxr-xr-x daniel daniel        0 Sun, 2026-05-17 23:30:12 data/foo/Hello
-rw-r--r-- daniel daniel        6 Sun, 2026-05-17 23:30:12 data/foo/Hello/World
-rw-r--r-- daniel daniel        6 Sun, 2026-05-17 23:30:25 data/blubb

This has many implications, like that there's no difference between pp: and pf: patterns with --exclude. It also doesn't matter that with fm: and sh: patterns Borg will effectively append a * when the pattern ends with a /. The challenges with re: patterns are also wildly different between --exclude and --pattern then.

What am I missing? Is --exclude truly expected to behave like no-recurse exclusions, meaning that the docs are wrong? Or is this a bug and --exclude should actually behave like plain - exclusions?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions