Skip to content

Add --exclude-archives, the inverse of --match-archives - #10305

Open
m-altaifi wants to merge 2 commits into
borgbackup:masterfrom
m-altaifi:feat/exclude-archives
Open

Add --exclude-archives, the inverse of --match-archives#10305
m-altaifi wants to merge 2 commits into
borgbackup:masterfrom
m-altaifi:feat/exclude-archives

Conversation

@m-altaifi

Copy link
Copy Markdown

Implements #9936.

--match-archives can only narrow a selection — all given patterns must match, so there is no way to express "all archives except these" without a shell loop around borg repo-list.

--exclude-archives takes the same patterns and the same selector prefixes, and skips any archive matching one of them. An archive is considered if it matches all --match-archives patterns and none of the --exclude-archives patterns, i.e. the new option ORs where the existing one ANDs, exactly as --exclude does for paths:

borg repo-list \
    --match-archives 'sh:my-machine-name-*' \
    --exclude-archives 'sh:*-scratch' \
    --exclude-archives tags:@PROT

Implementation

The per-pattern matching was inline in _matching_info_tuples, so I pulled it out into _matching_by_pattern and now call it from both directions. That is what makes every prefix — aid:, tags:, user:, host:, date:, name: — work for exclusion as well, rather than exclusion supporting names only.

Exclusion patterns are matched against all archives rather than the already-narrowed list, so an exclusion pattern means the same thing regardless of which inclusion patterns were given. It also avoids aid: spuriously raising "needs to match precisely one archive ID" when the referenced archive had already been filtered out by an inclusion pattern.

The option is defined in define_archive_filters_group, so every command that already has -a gets it, and it is passed through the single list_considering funnel. prune reaches list() directly rather than through that funnel, so it is wired explicitly.

LegacyArchives gets the same treatment, otherwise borg transfer --exclude-archives … would silently ignore the option when the source is a borg 1.x repository.

borg help match-archives now documents both directions.

Tests

Four cases in repo_list_cmd_test.py: exclusion on its own, several exclusion patterns ORing together, exclusion combined with --match-archives, and exclusion via a non-name selector (tags:) so the shared matcher path is covered rather than just name globs.

What I could not run

Borg does not run on native Windows and I could not build the Cython extensions here, so I have not executed the test suite locally — I am relying on CI and will follow up on whatever it reports. What I did verify locally: ruff check passes on all six changed files, and black --check (24.8.0, as pinned in .pre-commit-config.yaml) reports them unchanged.

I left CHANGES.rst and the generated usage docs alone, following #10291 and the note in the contributor docs that usage docs are regenerated at release time. Happy to add a changelog entry if you would rather have it in the PR.

@ThomasWaldmann

Copy link
Copy Markdown
Member

Claude? :-)

@codecov

codecov Bot commented Sep 2, 2026

Copy link
Copy Markdown

❌ 10 Tests Failed:

Tests completed Failed Passed Skipped
2970 10 2960 777
View the top 3 failed test(s) by shortest run time
src.borg.testsuite.archives_test::test_list_considering_delegates
Stack Traces | 0.002s run time
#x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_list_considering_delegates#x1B[39;49;00m():#x1B[90m#x1B[39;49;00m
        i1 = _archiveinfo(#x1B[33m"#x1B[39;49;00m#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, _id(#x1B[94m2#x1B[39;49;00m), TS2)#x1B[90m#x1B[39;49;00m
        i2 = _archiveinfo(#x1B[33m"#x1B[39;49;00m#x1B[33ma#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, _id(#x1B[94m1#x1B[39;49;00m), TS)#x1B[90m#x1B[39;49;00m
        ar = _stub_matching_info_tuples([i1, i2])#x1B[90m#x1B[39;49;00m
        args = Namespace(#x1B[90m#x1B[39;49;00m
            name=#x1B[94mNone#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            sort_by=#x1B[33m"#x1B[39;49;00m#x1B[33mname#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            match_archives=#x1B[94mNone#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            first=#x1B[94mNone#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            last=#x1B[94mNone#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            older=#x1B[94mNone#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            newer=#x1B[94mNone#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            oldest=#x1B[94mNone#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            newest=#x1B[94mNone#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            deleted=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
>       result = ar.list_considering(args)#x1B[90m#x1B[39;49;00m
                 ^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[.../borg/testsuite/archives_test.py#x1B[0m:569: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31msrc/borg/manifest.py#x1B[0m:477: in list_considering
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.list(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31msrc/borg/manifest.py#x1B[0m:452: in list
    #x1B[0marchive_infos = #x1B[96mself#x1B[39;49;00m._matching_info_tuples(match, match_end, deleted=deleted, exclude_patterns=exclude)#x1B[90m#x1B[39;49;00m
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1176: in __call__
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._mock_call(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1180: in _mock_call
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._execute_mock_call(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock id='131092832396560'>, args = (None, '\\Z')
kwargs = {'deleted': False, 'exclude_patterns': None}

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m_execute_mock_call#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, /, *args, **kwargs):#x1B[90m#x1B[39;49;00m
        #x1B[90m# separate from _increment_mock_call so that awaited functions are#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# executed separately from their call, also AsyncMock overrides this method#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        effect = #x1B[96mself#x1B[39;49;00m.side_effect#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m effect #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m _is_exception(effect):#x1B[90m#x1B[39;49;00m
                #x1B[94mraise#x1B[39;49;00m effect#x1B[90m#x1B[39;49;00m
            #x1B[94melif#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m _callable(effect):#x1B[90m#x1B[39;49;00m
                result = #x1B[96mnext#x1B[39;49;00m(effect)#x1B[90m#x1B[39;49;00m
                #x1B[94mif#x1B[39;49;00m _is_exception(result):#x1B[90m#x1B[39;49;00m
                    #x1B[94mraise#x1B[39;49;00m result#x1B[90m#x1B[39;49;00m
            #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>               result = effect(*args, **kwargs)#x1B[90m#x1B[39;49;00m
                         ^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE               TypeError: _stub_matching_info_tuples.<locals>.<lambda>() got an unexpected keyword argument 'exclude_patterns'#x1B[0m

#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1247: TypeError
src.borg.testsuite.archives_test::test_list_considering_multi_key_sort
Stack Traces | 0.002s run time
#x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_list_considering_multi_key_sort#x1B[39;49;00m():#x1B[90m#x1B[39;49;00m
        i1 = _archiveinfo(#x1B[33m"#x1B[39;49;00m#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, _id(#x1B[94m1#x1B[39;49;00m), TS2)#x1B[90m#x1B[39;49;00m
        i2 = _archiveinfo(#x1B[33m"#x1B[39;49;00m#x1B[33ma#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, _id(#x1B[94m2#x1B[39;49;00m), TS2)#x1B[90m#x1B[39;49;00m
        i3 = _archiveinfo(#x1B[33m"#x1B[39;49;00m#x1B[33mc#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, _id(#x1B[94m3#x1B[39;49;00m), TS)#x1B[90m#x1B[39;49;00m
        ar = _stub_matching_info_tuples([i1, i2, i3])#x1B[90m#x1B[39;49;00m
        args = Namespace(#x1B[90m#x1B[39;49;00m
            name=#x1B[94mNone#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            sort_by=#x1B[33m"#x1B[39;49;00m#x1B[33mts,name#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            match_archives=#x1B[94mNone#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            first=#x1B[94mNone#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            last=#x1B[94mNone#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            older=#x1B[94mNone#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            newer=#x1B[94mNone#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            oldest=#x1B[94mNone#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            newest=#x1B[94mNone#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            deleted=#x1B[94mFalse#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
        )#x1B[90m#x1B[39;49;00m
>       result = ar.list_considering(args)#x1B[90m#x1B[39;49;00m
                 ^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[.../borg/testsuite/archives_test.py#x1B[0m:610: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31msrc/borg/manifest.py#x1B[0m:477: in list_considering
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m.list(#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31msrc/borg/manifest.py#x1B[0m:452: in list
    #x1B[0marchive_infos = #x1B[96mself#x1B[39;49;00m._matching_info_tuples(match, match_end, deleted=deleted, exclude_patterns=exclude)#x1B[90m#x1B[39;49;00m
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1176: in __call__
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._mock_call(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1180: in _mock_call
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._execute_mock_call(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock id='131092873571904'>, args = (None, '\\Z')
kwargs = {'deleted': False, 'exclude_patterns': None}

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m_execute_mock_call#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, /, *args, **kwargs):#x1B[90m#x1B[39;49;00m
        #x1B[90m# separate from _increment_mock_call so that awaited functions are#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# executed separately from their call, also AsyncMock overrides this method#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        effect = #x1B[96mself#x1B[39;49;00m.side_effect#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m effect #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m _is_exception(effect):#x1B[90m#x1B[39;49;00m
                #x1B[94mraise#x1B[39;49;00m effect#x1B[90m#x1B[39;49;00m
            #x1B[94melif#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m _callable(effect):#x1B[90m#x1B[39;49;00m
                result = #x1B[96mnext#x1B[39;49;00m(effect)#x1B[90m#x1B[39;49;00m
                #x1B[94mif#x1B[39;49;00m _is_exception(result):#x1B[90m#x1B[39;49;00m
                    #x1B[94mraise#x1B[39;49;00m result#x1B[90m#x1B[39;49;00m
            #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>               result = effect(*args, **kwargs)#x1B[90m#x1B[39;49;00m
                         ^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE               TypeError: _stub_matching_info_tuples.<locals>.<lambda>() got an unexpected keyword argument 'exclude_patterns'#x1B[0m

#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1247: TypeError
src.borg.testsuite.archives_test::test_list_date_filter
Stack Traces | 0.002s run time
#x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_list_date_filter#x1B[39;49;00m():#x1B[90m#x1B[39;49;00m
        i1 = _archiveinfo(#x1B[33m"#x1B[39;49;00m#x1B[33ma#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, _id(#x1B[94m1#x1B[39;49;00m))#x1B[90m#x1B[39;49;00m
        ar = _stub_matching_info_tuples([i1])#x1B[90m#x1B[39;49;00m
        #x1B[94mwith#x1B[39;49;00m patch(#x1B[33m"#x1B[39;49;00m#x1B[33mborg.manifest.filter_archives_by_date#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, return_value=[i1]) #x1B[94mas#x1B[39;49;00m mock_filter:#x1B[90m#x1B[39;49;00m
>           result = ar.list(older=#x1B[33m"#x1B[39;49;00m#x1B[33m1d#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
                     ^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[.../borg/testsuite/archives_test.py#x1B[0m:434: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31msrc/borg/manifest.py#x1B[0m:452: in list
    #x1B[0marchive_infos = #x1B[96mself#x1B[39;49;00m._matching_info_tuples(match, match_end, deleted=deleted, exclude_patterns=exclude)#x1B[90m#x1B[39;49;00m
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1176: in __call__
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._mock_call(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1180: in _mock_call
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._execute_mock_call(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock id='131092873573920'>, args = (None, '\\Z')
kwargs = {'deleted': False, 'exclude_patterns': None}

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m_execute_mock_call#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, /, *args, **kwargs):#x1B[90m#x1B[39;49;00m
        #x1B[90m# separate from _increment_mock_call so that awaited functions are#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# executed separately from their call, also AsyncMock overrides this method#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        effect = #x1B[96mself#x1B[39;49;00m.side_effect#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m effect #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m _is_exception(effect):#x1B[90m#x1B[39;49;00m
                #x1B[94mraise#x1B[39;49;00m effect#x1B[90m#x1B[39;49;00m
            #x1B[94melif#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m _callable(effect):#x1B[90m#x1B[39;49;00m
                result = #x1B[96mnext#x1B[39;49;00m(effect)#x1B[90m#x1B[39;49;00m
                #x1B[94mif#x1B[39;49;00m _is_exception(result):#x1B[90m#x1B[39;49;00m
                    #x1B[94mraise#x1B[39;49;00m result#x1B[90m#x1B[39;49;00m
            #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>               result = effect(*args, **kwargs)#x1B[90m#x1B[39;49;00m
                         ^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE               TypeError: _stub_matching_info_tuples.<locals>.<lambda>() got an unexpected keyword argument 'exclude_patterns'#x1B[0m

#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1247: TypeError
src.borg.testsuite.archives_test::test_list_first
Stack Traces | 0.002s run time
#x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_list_first#x1B[39;49;00m():#x1B[90m#x1B[39;49;00m
        infos = [_archiveinfo(#x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33ma#x1B[39;49;00m#x1B[33m{#x1B[39;49;00mi#x1B[33m}#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, _id(i + #x1B[94m1#x1B[39;49;00m)) #x1B[94mfor#x1B[39;49;00m i #x1B[95min#x1B[39;49;00m #x1B[96mrange#x1B[39;49;00m(#x1B[94m5#x1B[39;49;00m)]#x1B[90m#x1B[39;49;00m
        ar = _stub_matching_info_tuples(infos)#x1B[90m#x1B[39;49;00m
>       #x1B[94massert#x1B[39;49;00m ar.list(first=#x1B[94m3#x1B[39;49;00m) == infos[:#x1B[94m3#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
               ^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[.../borg/testsuite/archives_test.py#x1B[0m:409: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31msrc/borg/manifest.py#x1B[0m:452: in list
    #x1B[0marchive_infos = #x1B[96mself#x1B[39;49;00m._matching_info_tuples(match, match_end, deleted=deleted, exclude_patterns=exclude)#x1B[90m#x1B[39;49;00m
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1176: in __call__
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._mock_call(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1180: in _mock_call
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._execute_mock_call(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock id='131092873572240'>, args = (None, '\\Z')
kwargs = {'deleted': False, 'exclude_patterns': None}

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m_execute_mock_call#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, /, *args, **kwargs):#x1B[90m#x1B[39;49;00m
        #x1B[90m# separate from _increment_mock_call so that awaited functions are#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# executed separately from their call, also AsyncMock overrides this method#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        effect = #x1B[96mself#x1B[39;49;00m.side_effect#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m effect #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m _is_exception(effect):#x1B[90m#x1B[39;49;00m
                #x1B[94mraise#x1B[39;49;00m effect#x1B[90m#x1B[39;49;00m
            #x1B[94melif#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m _callable(effect):#x1B[90m#x1B[39;49;00m
                result = #x1B[96mnext#x1B[39;49;00m(effect)#x1B[90m#x1B[39;49;00m
                #x1B[94mif#x1B[39;49;00m _is_exception(result):#x1B[90m#x1B[39;49;00m
                    #x1B[94mraise#x1B[39;49;00m result#x1B[90m#x1B[39;49;00m
            #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>               result = effect(*args, **kwargs)#x1B[90m#x1B[39;49;00m
                         ^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE               TypeError: _stub_matching_info_tuples.<locals>.<lambda>() got an unexpected keyword argument 'exclude_patterns'#x1B[0m

#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1247: TypeError
src.borg.testsuite.archives_test::test_list_first_zero
Stack Traces | 0.002s run time
#x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_list_first_zero#x1B[39;49;00m():#x1B[90m#x1B[39;49;00m
        infos = [_archiveinfo(#x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33ma#x1B[39;49;00m#x1B[33m{#x1B[39;49;00mi#x1B[33m}#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, _id(i + #x1B[94m1#x1B[39;49;00m)) #x1B[94mfor#x1B[39;49;00m i #x1B[95min#x1B[39;49;00m #x1B[96mrange#x1B[39;49;00m(#x1B[94m3#x1B[39;49;00m)]#x1B[90m#x1B[39;49;00m
        ar = _stub_matching_info_tuples(infos)#x1B[90m#x1B[39;49;00m
>       #x1B[94massert#x1B[39;49;00m ar.list(first=#x1B[94m0#x1B[39;49;00m) == infos#x1B[90m#x1B[39;49;00m
               ^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[.../borg/testsuite/archives_test.py#x1B[0m:421: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31msrc/borg/manifest.py#x1B[0m:452: in list
    #x1B[0marchive_infos = #x1B[96mself#x1B[39;49;00m._matching_info_tuples(match, match_end, deleted=deleted, exclude_patterns=exclude)#x1B[90m#x1B[39;49;00m
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1176: in __call__
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._mock_call(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1180: in _mock_call
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._execute_mock_call(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock id='131092873575600'>, args = (None, '\\Z')
kwargs = {'deleted': False, 'exclude_patterns': None}

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m_execute_mock_call#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, /, *args, **kwargs):#x1B[90m#x1B[39;49;00m
        #x1B[90m# separate from _increment_mock_call so that awaited functions are#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# executed separately from their call, also AsyncMock overrides this method#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        effect = #x1B[96mself#x1B[39;49;00m.side_effect#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m effect #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m _is_exception(effect):#x1B[90m#x1B[39;49;00m
                #x1B[94mraise#x1B[39;49;00m effect#x1B[90m#x1B[39;49;00m
            #x1B[94melif#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m _callable(effect):#x1B[90m#x1B[39;49;00m
                result = #x1B[96mnext#x1B[39;49;00m(effect)#x1B[90m#x1B[39;49;00m
                #x1B[94mif#x1B[39;49;00m _is_exception(result):#x1B[90m#x1B[39;49;00m
                    #x1B[94mraise#x1B[39;49;00m result#x1B[90m#x1B[39;49;00m
            #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>               result = effect(*args, **kwargs)#x1B[90m#x1B[39;49;00m
                         ^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE               TypeError: _stub_matching_info_tuples.<locals>.<lambda>() got an unexpected keyword argument 'exclude_patterns'#x1B[0m

#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1247: TypeError
src.borg.testsuite.archives_test::test_list_last
Stack Traces | 0.002s run time
#x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_list_last#x1B[39;49;00m():#x1B[90m#x1B[39;49;00m
        infos = [_archiveinfo(#x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33ma#x1B[39;49;00m#x1B[33m{#x1B[39;49;00mi#x1B[33m}#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, _id(i + #x1B[94m1#x1B[39;49;00m)) #x1B[94mfor#x1B[39;49;00m i #x1B[95min#x1B[39;49;00m #x1B[96mrange#x1B[39;49;00m(#x1B[94m5#x1B[39;49;00m)]#x1B[90m#x1B[39;49;00m
        ar = _stub_matching_info_tuples(infos)#x1B[90m#x1B[39;49;00m
>       #x1B[94massert#x1B[39;49;00m ar.list(last=#x1B[94m2#x1B[39;49;00m) == infos[-#x1B[94m2#x1B[39;49;00m:]#x1B[90m#x1B[39;49;00m
               ^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[.../borg/testsuite/archives_test.py#x1B[0m:415: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31msrc/borg/manifest.py#x1B[0m:452: in list
    #x1B[0marchive_infos = #x1B[96mself#x1B[39;49;00m._matching_info_tuples(match, match_end, deleted=deleted, exclude_patterns=exclude)#x1B[90m#x1B[39;49;00m
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1176: in __call__
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._mock_call(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1180: in _mock_call
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._execute_mock_call(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock id='131092873574928'>, args = (None, '\\Z')
kwargs = {'deleted': False, 'exclude_patterns': None}

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m_execute_mock_call#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, /, *args, **kwargs):#x1B[90m#x1B[39;49;00m
        #x1B[90m# separate from _increment_mock_call so that awaited functions are#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# executed separately from their call, also AsyncMock overrides this method#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        effect = #x1B[96mself#x1B[39;49;00m.side_effect#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m effect #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m _is_exception(effect):#x1B[90m#x1B[39;49;00m
                #x1B[94mraise#x1B[39;49;00m effect#x1B[90m#x1B[39;49;00m
            #x1B[94melif#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m _callable(effect):#x1B[90m#x1B[39;49;00m
                result = #x1B[96mnext#x1B[39;49;00m(effect)#x1B[90m#x1B[39;49;00m
                #x1B[94mif#x1B[39;49;00m _is_exception(result):#x1B[90m#x1B[39;49;00m
                    #x1B[94mraise#x1B[39;49;00m result#x1B[90m#x1B[39;49;00m
            #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>               result = effect(*args, **kwargs)#x1B[90m#x1B[39;49;00m
                         ^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE               TypeError: _stub_matching_info_tuples.<locals>.<lambda>() got an unexpected keyword argument 'exclude_patterns'#x1B[0m

#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1247: TypeError
src.borg.testsuite.archives_test::test_list_no_filters
Stack Traces | 0.002s run time
#x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_list_no_filters#x1B[39;49;00m():#x1B[90m#x1B[39;49;00m
        info = _archiveinfo(#x1B[33m"#x1B[39;49;00m#x1B[33ma#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, _id(#x1B[94m1#x1B[39;49;00m))#x1B[90m#x1B[39;49;00m
        ar = _stub_matching_info_tuples([info])#x1B[90m#x1B[39;49;00m
>       #x1B[94massert#x1B[39;49;00m ar.list() == [info]#x1B[90m#x1B[39;49;00m
               ^^^^^^^^^#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[.../borg/testsuite/archives_test.py#x1B[0m:375: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31msrc/borg/manifest.py#x1B[0m:452: in list
    #x1B[0marchive_infos = #x1B[96mself#x1B[39;49;00m._matching_info_tuples(match, match_end, deleted=deleted, exclude_patterns=exclude)#x1B[90m#x1B[39;49;00m
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1176: in __call__
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._mock_call(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1180: in _mock_call
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._execute_mock_call(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock id='131092832392864'>, args = (None, '\\Z')
kwargs = {'deleted': False, 'exclude_patterns': None}

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m_execute_mock_call#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, /, *args, **kwargs):#x1B[90m#x1B[39;49;00m
        #x1B[90m# separate from _increment_mock_call so that awaited functions are#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# executed separately from their call, also AsyncMock overrides this method#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        effect = #x1B[96mself#x1B[39;49;00m.side_effect#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m effect #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m _is_exception(effect):#x1B[90m#x1B[39;49;00m
                #x1B[94mraise#x1B[39;49;00m effect#x1B[90m#x1B[39;49;00m
            #x1B[94melif#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m _callable(effect):#x1B[90m#x1B[39;49;00m
                result = #x1B[96mnext#x1B[39;49;00m(effect)#x1B[90m#x1B[39;49;00m
                #x1B[94mif#x1B[39;49;00m _is_exception(result):#x1B[90m#x1B[39;49;00m
                    #x1B[94mraise#x1B[39;49;00m result#x1B[90m#x1B[39;49;00m
            #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>               result = effect(*args, **kwargs)#x1B[90m#x1B[39;49;00m
                         ^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE               TypeError: _stub_matching_info_tuples.<locals>.<lambda>() got an unexpected keyword argument 'exclude_patterns'#x1B[0m

#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1247: TypeError
src.borg.testsuite.archives_test::test_list_reverse
Stack Traces | 0.002s run time
#x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_list_reverse#x1B[39;49;00m():#x1B[90m#x1B[39;49;00m
        i1 = _archiveinfo(#x1B[33m"#x1B[39;49;00m#x1B[33ma#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, _id(#x1B[94m1#x1B[39;49;00m))#x1B[90m#x1B[39;49;00m
        i2 = _archiveinfo(#x1B[33m"#x1B[39;49;00m#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, _id(#x1B[94m2#x1B[39;49;00m))#x1B[90m#x1B[39;49;00m
        ar = _stub_matching_info_tuples([i1, i2])#x1B[90m#x1B[39;49;00m
>       #x1B[94massert#x1B[39;49;00m ar.list(reverse=#x1B[94mTrue#x1B[39;49;00m) == [i2, i1]#x1B[90m#x1B[39;49;00m
               ^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[.../borg/testsuite/archives_test.py#x1B[0m:403: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31msrc/borg/manifest.py#x1B[0m:452: in list
    #x1B[0marchive_infos = #x1B[96mself#x1B[39;49;00m._matching_info_tuples(match, match_end, deleted=deleted, exclude_patterns=exclude)#x1B[90m#x1B[39;49;00m
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1176: in __call__
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._mock_call(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1180: in _mock_call
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._execute_mock_call(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock id='131092873565184'>, args = (None, '\\Z')
kwargs = {'deleted': False, 'exclude_patterns': None}

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m_execute_mock_call#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, /, *args, **kwargs):#x1B[90m#x1B[39;49;00m
        #x1B[90m# separate from _increment_mock_call so that awaited functions are#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# executed separately from their call, also AsyncMock overrides this method#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        effect = #x1B[96mself#x1B[39;49;00m.side_effect#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m effect #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m _is_exception(effect):#x1B[90m#x1B[39;49;00m
                #x1B[94mraise#x1B[39;49;00m effect#x1B[90m#x1B[39;49;00m
            #x1B[94melif#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m _callable(effect):#x1B[90m#x1B[39;49;00m
                result = #x1B[96mnext#x1B[39;49;00m(effect)#x1B[90m#x1B[39;49;00m
                #x1B[94mif#x1B[39;49;00m _is_exception(result):#x1B[90m#x1B[39;49;00m
                    #x1B[94mraise#x1B[39;49;00m result#x1B[90m#x1B[39;49;00m
            #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>               result = effect(*args, **kwargs)#x1B[90m#x1B[39;49;00m
                         ^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE               TypeError: _stub_matching_info_tuples.<locals>.<lambda>() got an unexpected keyword argument 'exclude_patterns'#x1B[0m

#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1247: TypeError
src.borg.testsuite.archives_test::test_list_sort_by
Stack Traces | 0.002s run time
#x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_list_sort_by#x1B[39;49;00m():#x1B[90m#x1B[39;49;00m
        i1 = _archiveinfo(#x1B[33m"#x1B[39;49;00m#x1B[33mb#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, _id(#x1B[94m2#x1B[39;49;00m), TS2)#x1B[90m#x1B[39;49;00m
        i2 = _archiveinfo(#x1B[33m"#x1B[39;49;00m#x1B[33ma#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, _id(#x1B[94m1#x1B[39;49;00m), TS)#x1B[90m#x1B[39;49;00m
        ar = _stub_matching_info_tuples([i1, i2])#x1B[90m#x1B[39;49;00m
>       result = ar.list(sort_by=[#x1B[33m"#x1B[39;49;00m#x1B[33mname#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m])#x1B[90m#x1B[39;49;00m
                 ^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[.../borg/testsuite/archives_test.py#x1B[0m:395: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31msrc/borg/manifest.py#x1B[0m:452: in list
    #x1B[0marchive_infos = #x1B[96mself#x1B[39;49;00m._matching_info_tuples(match, match_end, deleted=deleted, exclude_patterns=exclude)#x1B[90m#x1B[39;49;00m
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1176: in __call__
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._mock_call(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1180: in _mock_call
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._execute_mock_call(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock id='131092873572576'>, args = (None, '\\Z')
kwargs = {'deleted': False, 'exclude_patterns': None}

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m_execute_mock_call#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, /, *args, **kwargs):#x1B[90m#x1B[39;49;00m
        #x1B[90m# separate from _increment_mock_call so that awaited functions are#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# executed separately from their call, also AsyncMock overrides this method#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        effect = #x1B[96mself#x1B[39;49;00m.side_effect#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m effect #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m _is_exception(effect):#x1B[90m#x1B[39;49;00m
                #x1B[94mraise#x1B[39;49;00m effect#x1B[90m#x1B[39;49;00m
            #x1B[94melif#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m _callable(effect):#x1B[90m#x1B[39;49;00m
                result = #x1B[96mnext#x1B[39;49;00m(effect)#x1B[90m#x1B[39;49;00m
                #x1B[94mif#x1B[39;49;00m _is_exception(result):#x1B[90m#x1B[39;49;00m
                    #x1B[94mraise#x1B[39;49;00m result#x1B[90m#x1B[39;49;00m
            #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>               result = effect(*args, **kwargs)#x1B[90m#x1B[39;49;00m
                         ^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE               TypeError: _stub_matching_info_tuples.<locals>.<lambda>() got an unexpected keyword argument 'exclude_patterns'#x1B[0m

#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1247: TypeError
src.borg.testsuite.archives_test::test_list_last_zero
Stack Traces | 0.003s run time
#x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_list_last_zero#x1B[39;49;00m():#x1B[90m#x1B[39;49;00m
        infos = [_archiveinfo(#x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33ma#x1B[39;49;00m#x1B[33m{#x1B[39;49;00mi#x1B[33m}#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, _id(i + #x1B[94m1#x1B[39;49;00m)) #x1B[94mfor#x1B[39;49;00m i #x1B[95min#x1B[39;49;00m #x1B[96mrange#x1B[39;49;00m(#x1B[94m3#x1B[39;49;00m)]#x1B[90m#x1B[39;49;00m
        ar = _stub_matching_info_tuples(infos)#x1B[90m#x1B[39;49;00m
>       #x1B[94massert#x1B[39;49;00m ar.list(last=#x1B[94m0#x1B[39;49;00m) == infos#x1B[90m#x1B[39;49;00m
               ^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m

#x1B[1m#x1B[.../borg/testsuite/archives_test.py#x1B[0m:427: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31msrc/borg/manifest.py#x1B[0m:452: in list
    #x1B[0marchive_infos = #x1B[96mself#x1B[39;49;00m._matching_info_tuples(match, match_end, deleted=deleted, exclude_patterns=exclude)#x1B[90m#x1B[39;49;00m
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1176: in __call__
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._mock_call(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1180: in _mock_call
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._execute_mock_call(*args, **kwargs)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock id='131092832387488'>, args = (None, '\\Z')
kwargs = {'deleted': False, 'exclude_patterns': None}

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m_execute_mock_call#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, /, *args, **kwargs):#x1B[90m#x1B[39;49;00m
        #x1B[90m# separate from _increment_mock_call so that awaited functions are#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# executed separately from their call, also AsyncMock overrides this method#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        effect = #x1B[96mself#x1B[39;49;00m.side_effect#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m effect #x1B[95mis#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mif#x1B[39;49;00m _is_exception(effect):#x1B[90m#x1B[39;49;00m
                #x1B[94mraise#x1B[39;49;00m effect#x1B[90m#x1B[39;49;00m
            #x1B[94melif#x1B[39;49;00m #x1B[95mnot#x1B[39;49;00m _callable(effect):#x1B[90m#x1B[39;49;00m
                result = #x1B[96mnext#x1B[39;49;00m(effect)#x1B[90m#x1B[39;49;00m
                #x1B[94mif#x1B[39;49;00m _is_exception(result):#x1B[90m#x1B[39;49;00m
                    #x1B[94mraise#x1B[39;49;00m result#x1B[90m#x1B[39;49;00m
            #x1B[94melse#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>               result = effect(*args, **kwargs)#x1B[90m#x1B[39;49;00m
                         ^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE               TypeError: _stub_matching_info_tuples.<locals>.<lambda>() got an unexpected keyword argument 'exclude_patterns'#x1B[0m

#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.7........./x64/lib/python3.14/unittest/mock.py#x1B[0m:1247: TypeError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@ThomasWaldmann

Copy link
Copy Markdown
Member

@m-altaifi The CI is quite red.

Maybe rebase your change onto current master branch and try to fix the failures.

m-altaifi and others added 2 commits September 3, 2026 19:49
--match-archives can only narrow a selection: every pattern given must
match, so there is no way to say "all archives except these". Doing that
needed a shell loop around borg repo-list.

Add --exclude-archives, taking the same patterns and selector prefixes.
An archive is considered if it matches all --match-archives patterns and
none of the --exclude-archives ones, so the new option ORs while the
existing one ANDs.

The per-pattern matching is factored out of _matching_info_tuples into
_matching_by_pattern so both directions share one matcher and all
prefixes (aid:, tags:, user:, host:, date:, name:) work for exclusion
too. Exclusion patterns are evaluated against all archives rather than
the already narrowed list, so an exclusion does not depend on what the
inclusion patterns happened to keep.

Wired at the shared archive filters group, so every command using
define_archive_filters_group gets it. LegacyArchives is updated the same
way so borg transfer does not silently ignore the option when reading a
borg 1.x repository.
@m-altaifi
m-altaifi force-pushed the feat/exclude-archives branch from ebdb1a6 to 6117a54 Compare September 3, 2026 20:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants