tag: add --clear to remove all normal tags

Special tags like @PROT are kept. --clear can be combined with --add
to replace the normal tags, it is mutually exclusive with --set.

Previously, a bare "--set" cleared all tags, but that is not possible
anymore since --set now takes exactly one tag.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Thomas Waldmann
2026-09-26 23:19:35 +02:00
co-authored by Claude Opus 5.5
parent 2712d2f3bb
commit 01d42bec43
2 changed files with 35 additions and 1 deletions
+14 -1
View File
@@ -29,6 +29,9 @@ class TagMixIn:
clobber = not existing_special.issubset(new_tags)
if not clobber:
archive.tags = new_tags
if args.clear_tags:
# only remove normal tags, keep special tags.
archive.tags = {tag for tag in archive.tags if tag.startswith("@")}
archive.tags |= set(args.add_tags or [])
archive.tags -= set(args.remove_tags or [])
old_id = archive.id
@@ -61,6 +64,9 @@ class TagMixIn:
``--set``, but you must also give pre-existing special tags (so they won't be
removed).
``--clear`` removes all normal tags, but keeps special tags. Combined with
``--add``, it replaces the normal tags.
Each of ``--set``, ``--add`` and ``--remove`` takes exactly one tag. To give
multiple tags, use the option multiple times.
@@ -75,6 +81,9 @@ class TagMixIn:
# set the tags of the archive with the given ID to exactly "foo" and "bar"
$ borg tag --set foo --set bar aid:1ddaae55
# remove all normal tags (but not special tags like @PROT) from the archive with the given ID
$ borg tag --clear aid:1ddaae55
# protect the archive with the given ID against deletion and pruning
$ borg tag --add @PROT aid:1ddaae55
"""
@@ -83,7 +92,8 @@ class TagMixIn:
subparsers.add_subcommand("tag", subparser, help="tag archives")
# each option takes exactly one tag, so it can not swallow the NAME positional argument.
# note: "extend" with nargs=1 (not "append") gives a flat list of tags that jsonargparse can validate.
subparser.add_argument(
set_clear_group = subparser.add_mutually_exclusive_group()
set_clear_group.add_argument(
"--set",
dest="set_tags",
metavar="TAG",
@@ -92,6 +102,9 @@ class TagMixIn:
nargs=1,
help="set tags (can be given multiple times)",
)
set_clear_group.add_argument(
"--clear", dest="clear_tags", action="store_true", help="remove all normal tags (keep special tags)"
)
subparser.add_argument(
"--add",
dest="add_tags",
@@ -45,6 +45,27 @@ def test_tag_set_noclobber_special(archivers, request):
assert "tags: @PROT,noclobber." in output
def test_tag_clear(archivers, request):
archiver = request.getfixturevalue(archivers)
cmd(archiver, "repo-create", RK_ENCRYPTION)
cmd(archiver, "create", "archive", archiver.input_path)
output = cmd(archiver, "tag", "-a", "archive", "--add", "aa", "--add", "bb")
assert "tags: aa,bb." in output
output = cmd(archiver, "tag", "-a", "archive", "--clear")
assert "tags: ." in output # no tags!
output = cmd(archiver, "tag", "-a", "archive", "--add", "aa", "--add", "@PROT")
assert "tags: @PROT,aa." in output
# --clear must not remove special tags:
output = cmd(archiver, "tag", "-a", "archive", "--clear")
assert "tags: @PROT." in output
# --clear with --add replaces the normal tags:
cmd(archiver, "tag", "-a", "archive", "--add", "aa")
output = cmd(archiver, "tag", "-a", "archive", "--clear", "--add", "bb")
assert "tags: @PROT,bb." in output
# --clear and --set are mutually exclusive:
cmd(archiver, "tag", "-a", "archive", "--clear", "--set", "cc", exit_code=EXIT_ERROR)
def test_tag_only_known_special(archivers, request):
archiver = request.getfixturevalue(archivers)
cmd(archiver, "repo-create", RK_ENCRYPTION)