Merge pull request #8449 from ThomasWaldmann/tag-command

tag: set, add, remove tags
This commit is contained in:
TW 2024-10-03 11:53:13 +02:00 committed by GitHub
commit c308f76ddc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 336 additions and 5 deletions

98
docs/man/borg-tag.1 Normal file
View File

@ -0,0 +1,98 @@
.\" Man page generated from reStructuredText.
.
.
.nr rst2man-indent-level 0
.
.de1 rstReportMargin
\\$1 \\n[an-margin]
level \\n[rst2man-indent-level]
level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
-
\\n[rst2man-indent0]
\\n[rst2man-indent1]
\\n[rst2man-indent2]
..
.de1 INDENT
.\" .rstReportMargin pre:
. RS \\$1
. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
. nr rst2man-indent-level +1
.\" .rstReportMargin post:
..
.de UNINDENT
. RE
.\" indent \\n[an-margin]
.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
.nr rst2man-indent-level -1
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "BORG-TAG" 1 "2024-10-02" "" "borg backup tool"
.SH NAME
borg-tag \- Manage tags
.SH SYNOPSIS
.sp
borg [common options] tag [options] [NAME]
.SH DESCRIPTION
.sp
Manage archive tags.
.sp
Borg archives can have a set of tags which can be used for matching archives.
.sp
You can set the tags to a specific set of tags or you can add or remove
tags from the current set of tags.
.SH OPTIONS
.sp
See \fIborg\-common(1)\fP for common options of Borg commands.
.SS arguments
.INDENT 0.0
.TP
.B NAME
specify the archive name
.UNINDENT
.SS optional arguments
.INDENT 0.0
.TP
.BI \-\-set \ TAG
set tags (can be given multiple times)
.TP
.BI \-\-add \ TAG
add tags (can be given multiple times)
.TP
.BI \-\-remove \ TAG
remove tags (can be given multiple times)
.UNINDENT
.SS Archive filters
.INDENT 0.0
.TP
.BI \-a \ PATTERN\fR,\fB \ \-\-match\-archives \ PATTERN
only consider archives matching all patterns. see \(dqborg help match\-archives\(dq.
.TP
.BI \-\-sort\-by \ KEYS
Comma\-separated list of sorting keys; valid keys are: timestamp, archive, name, id, tags, host, user; default is: timestamp
.TP
.BI \-\-first \ N
consider first N archives after other filters were applied
.TP
.BI \-\-last \ N
consider last N archives after other filters were applied
.TP
.BI \-\-oldest \ TIMESPAN
consider archives between the oldest archive\(aqs timestamp and (oldest + TIMESPAN), e.g. 7d or 12m.
.TP
.BI \-\-newest \ TIMESPAN
consider archives between the newest archive\(aqs timestamp and (newest \- TIMESPAN), e.g. 7d or 12m.
.TP
.BI \-\-older \ TIMESPAN
consider archives older than (now \- TIMESPAN), e.g. 7d or 12m.
.TP
.BI \-\-newer \ TIMESPAN
consider archives newer than (now \- TIMESPAN), e.g. 7d or 12m.
.UNINDENT
.SH SEE ALSO
.sp
\fIborg\-common(1)\fP
.SH AUTHOR
The Borg Collective
.\" Generated by docutils manpage writer.
.

View File

@ -51,8 +51,9 @@ Usage
usage/create
usage/extract
usage/check
usage/rename
usage/list
usage/tag
usage/rename
usage/diff
usage/delete
usage/prune

1
docs/usage/tag.rst Normal file
View File

@ -0,0 +1 @@
.. include:: tag.rst.inc

93
docs/usage/tag.rst.inc Normal file
View File

@ -0,0 +1,93 @@
.. IMPORTANT: this file is auto-generated from borg's built-in help, do not edit!
.. _borg_tag:
borg tag
--------
.. code-block:: none
borg [common options] tag [options] [NAME]
.. only:: html
.. class:: borg-options-table
+-----------------------------------------------------------------------------+----------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
| **positional arguments** |
+-----------------------------------------------------------------------------+----------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
| | ``NAME`` | specify the archive name |
+-----------------------------------------------------------------------------+----------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
| **optional arguments** |
+-----------------------------------------------------------------------------+----------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
| | ``--set TAG`` | set tags (can be given multiple times) |
+-----------------------------------------------------------------------------+----------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
| | ``--add TAG`` | add tags (can be given multiple times) |
+-----------------------------------------------------------------------------+----------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
| | ``--remove TAG`` | remove tags (can be given multiple times) |
+-----------------------------------------------------------------------------+----------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
| .. class:: borg-common-opt-ref |
| |
| :ref:`common_options` |
+-----------------------------------------------------------------------------+----------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
| **Archive filters** Archive filters can be applied to repository targets. |
+-----------------------------------------------------------------------------+----------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
| | ``-a PATTERN``, ``--match-archives PATTERN`` | only consider archives matching all patterns. see "borg help match-archives". |
+-----------------------------------------------------------------------------+----------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
| | ``--sort-by KEYS`` | Comma-separated list of sorting keys; valid keys are: timestamp, archive, name, id, tags, host, user; default is: timestamp |
+-----------------------------------------------------------------------------+----------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
| | ``--first N`` | consider first N archives after other filters were applied |
+-----------------------------------------------------------------------------+----------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
| | ``--last N`` | consider last N archives after other filters were applied |
+-----------------------------------------------------------------------------+----------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
| | ``--oldest TIMESPAN`` | consider archives between the oldest archive's timestamp and (oldest + TIMESPAN), e.g. 7d or 12m. |
+-----------------------------------------------------------------------------+----------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
| | ``--newest TIMESPAN`` | consider archives between the newest archive's timestamp and (newest - TIMESPAN), e.g. 7d or 12m. |
+-----------------------------------------------------------------------------+----------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
| | ``--older TIMESPAN`` | consider archives older than (now - TIMESPAN), e.g. 7d or 12m. |
+-----------------------------------------------------------------------------+----------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
| | ``--newer TIMESPAN`` | consider archives newer than (now - TIMESPAN), e.g. 7d or 12m. |
+-----------------------------------------------------------------------------+----------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
.. raw:: html
<script type='text/javascript'>
$(document).ready(function () {
$('.borg-options-table colgroup').remove();
})
</script>
.. only:: latex
NAME
specify the archive name
optional arguments
--set TAG set tags (can be given multiple times)
--add TAG add tags (can be given multiple times)
--remove TAG remove tags (can be given multiple times)
:ref:`common_options`
|
Archive filters
-a PATTERN, --match-archives PATTERN only consider archives matching all patterns. see "borg help match-archives".
--sort-by KEYS Comma-separated list of sorting keys; valid keys are: timestamp, archive, name, id, tags, host, user; default is: timestamp
--first N consider first N archives after other filters were applied
--last N consider last N archives after other filters were applied
--oldest TIMESPAN consider archives between the oldest archive's timestamp and (oldest + TIMESPAN), e.g. 7d or 12m.
--newest TIMESPAN consider archives between the newest archive's timestamp and (newest - TIMESPAN), e.g. 7d or 12m.
--older TIMESPAN consider archives older than (now - TIMESPAN), e.g. 7d or 12m.
--newer TIMESPAN consider archives newer than (now - TIMESPAN), e.g. 7d or 12m.
Description
~~~~~~~~~~~
Manage archive tags.
Borg archives can have a set of tags which can be used for matching archives.
You can set the tags to a specific set of tags or you can add or remove
tags from the current set of tags.

View File

@ -89,6 +89,7 @@ def get_func(args):
from .repo_list_cmd import RepoListMixIn
from .repo_space_cmd import RepoSpaceMixIn
from .serve_cmd import ServeMixIn
from .tag_cmd import TagMixIn
from .tar_cmds import TarMixIn
from .transfer_cmd import TransferMixIn
from .version_cmd import VersionMixIn
@ -120,6 +121,7 @@ class Archiver(
RepoListMixIn,
RepoSpaceMixIn,
ServeMixIn,
TagMixIn,
TarMixIn,
TransferMixIn,
VersionMixIn,
@ -359,6 +361,7 @@ def build_parser(self):
self.build_parser_rename(subparsers, common_parser, mid_common_parser)
self.build_parser_repo_space(subparsers, common_parser, mid_common_parser)
self.build_parser_serve(subparsers, common_parser, mid_common_parser)
self.build_parser_tag(subparsers, common_parser, mid_common_parser)
self.build_parser_tar(subparsers, common_parser, mid_common_parser)
self.build_parser_transfer(subparsers, common_parser, mid_common_parser)
self.build_parser_version(subparsers, common_parser, mid_common_parser)

View File

@ -23,7 +23,8 @@ def do_repo_list(self, args, repository, manifest):
format = "{id}{NL}"
else:
format = os.environ.get(
"BORG_RLIST_FORMAT", "{id:.8} {time} {archive:<15} {username:<10} {hostname:<10} {comment:.40}{NL}"
"BORG_RLIST_FORMAT",
"{id:.8} {time} {archive:<15} {tags:<10} {username:<10} {hostname:<10} {comment:.40}{NL}",
)
formatter = ArchiveFormatter(format, repository, manifest, manifest.key, iec=args.iec)

View File

@ -0,0 +1,95 @@
import argparse
from ._common import with_repository, define_archive_filters_group
from ..archive import Archive
from ..constants import * # NOQA
from ..helpers import bin_to_hex, archivename_validator, tag_validator
from ..manifest import Manifest
from ..logger import create_logger
logger = create_logger()
class TagMixIn:
@with_repository(cache=True, compatibility=(Manifest.Operation.WRITE,))
def do_tag(self, args, repository, manifest, cache):
"""Manage tags"""
def tags_set(tags):
"""return a set of tags, removing empty tags"""
return set(tag for tag in tags if tag)
if args.name:
archive_infos = [manifest.archives.get_one([args.name])]
else:
archive_infos = manifest.archives.list_considering(args)
for archive_info in archive_infos:
archive = Archive(manifest, archive_info.id, cache=cache)
if args.set_tags:
archive.tags = tags_set(args.set_tags)
if args.add_tags:
archive.tags |= tags_set(args.add_tags)
if args.remove_tags:
archive.tags -= tags_set(args.remove_tags)
old_id = archive.id
archive.set_meta("tags", list(sorted(archive.tags)))
if old_id != archive.id:
manifest.archives.delete_by_id(old_id)
print(
f"id: {bin_to_hex(old_id):.8} -> {bin_to_hex(archive.id):.8}, "
f"tags: {','.join(sorted(archive.tags))}."
)
def build_parser_tag(self, subparsers, common_parser, mid_common_parser):
from ._common import process_epilog
tag_epilog = process_epilog(
"""
Manage archive tags.
Borg archives can have a set of tags which can be used for matching archives.
You can set the tags to a specific set of tags or you can add or remove
tags from the current set of tags.
"""
)
subparser = subparsers.add_parser(
"tag",
parents=[common_parser],
add_help=False,
description=self.do_tag.__doc__,
epilog=tag_epilog,
formatter_class=argparse.RawDescriptionHelpFormatter,
help="tag archives",
)
subparser.set_defaults(func=self.do_tag)
subparser.add_argument(
"--set",
dest="set_tags",
metavar="TAG",
type=tag_validator,
action="append",
help="set tags (can be given multiple times)",
)
subparser.add_argument(
"--add",
dest="add_tags",
metavar="TAG",
type=tag_validator,
action="append",
help="add tags (can be given multiple times)",
)
subparser.add_argument(
"--remove",
dest="remove_tags",
metavar="TAG",
type=tag_validator,
action="append",
help="remove tags (can be given multiple times)",
)
define_archive_filters_group(subparser)
subparser.add_argument(
"name", metavar="NAME", nargs="?", type=archivename_validator, help="specify the archive name"
)

View File

@ -34,7 +34,7 @@
from .parseformat import sizeof_fmt, sizeof_fmt_iec, sizeof_fmt_decimal, Location, text_validator
from .parseformat import format_line, replace_placeholders, PlaceholderError, relative_time_marker_validator
from .parseformat import format_archive, parse_stringified_list, clean_lines
from .parseformat import location_validator, archivename_validator, comment_validator
from .parseformat import location_validator, archivename_validator, comment_validator, tag_validator
from .parseformat import BaseFormatter, ArchiveFormatter, ItemFormatter, DiffFormatter, file_status
from .parseformat import swidth_slice, ellipsis_truncate
from .parseformat import BorgJsonEncoder, basic_json_data, json_print, json_dump, prepare_dump_dict

View File

@ -686,6 +686,7 @@ def validator(text):
comment_validator = text_validator(name="comment", max_length=10000)
tag_validator = text_validator(name="tag", min_length=0, max_length=10, invalid_chars=" ,$")
def archivename_validator(text):
@ -771,6 +772,7 @@ class ArchiveFormatter(BaseFormatter):
"archive": "archive name",
"name": 'alias of "archive"',
"comment": "archive comment",
"tags": "archive tags",
# *start* is the key used by borg-info for this timestamp, this makes the formats more compatible
"start": "time (start) of creation of the archive",
"time": 'alias of "start"',
@ -783,7 +785,7 @@ class ArchiveFormatter(BaseFormatter):
"nfiles": "count of files in this archive",
}
KEY_GROUPS = (
("archive", "name", "comment", "id"),
("archive", "name", "comment", "id", "tags"),
("start", "time", "end", "command_line"),
("hostname", "username"),
("size", "nfiles"),
@ -809,6 +811,7 @@ def __init__(self, format, repository, manifest, key, *, iec=False):
"size": partial(self.get_meta, "size", 0),
"nfiles": partial(self.get_meta, "nfiles", 0),
"end": self.get_ts_end,
"tags": self.get_tags,
}
self.used_call_keys = set(self.call_keys) & self.format_keys
@ -854,6 +857,9 @@ def get_ts_end(self):
def format_time(self, ts):
return OutputTimestamp(ts)
def get_tags(self):
return ",".join(sorted(self.archive.tags))
class ItemFormatter(BaseFormatter):
# we provide the hash algos from python stdlib (except shake_*) and additionally xxh64.

View File

@ -29,7 +29,7 @@ def test_archives_format(archivers, request):
archiver,
"repo-list",
"--format",
"{id:.8} {time} {archive:<15} {username:<10} {hostname:<10} {comment:.40}{NL}",
"{id:.8} {time} {archive:<15} {tags:<10} {username:<10} {hostname:<10} {comment:.40}{NL}",
)
assert output_1 == output_2
output = cmd(archiver, "repo-list", "--short")

View File

@ -0,0 +1,32 @@
from ...constants import * # NOQA
from . import cmd, generate_archiver_tests, RK_ENCRYPTION
pytest_generate_tests = lambda metafunc: generate_archiver_tests(metafunc, kinds="local") # NOQA
def test_tag_set(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", "--set", "aa")
assert "tags: aa." in output
output = cmd(archiver, "tag", "-a", "archive", "--set", "bb")
assert "tags: bb." in output
output = cmd(archiver, "tag", "-a", "archive", "--set", "bb", "--set", "aa")
assert "tags: aa,bb." in output # sorted!
output = cmd(archiver, "tag", "-a", "archive", "--set", "")
assert "tags: ." in output # no tags!
def test_tag_add_remove(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")
assert "tags: aa." in output
output = cmd(archiver, "tag", "-a", "archive", "--add", "bb")
assert "tags: aa,bb." in output
output = cmd(archiver, "tag", "-a", "archive", "--remove", "aa")
assert "tags: bb." in output
output = cmd(archiver, "tag", "-a", "archive", "--remove", "bb")
assert "tags: ." in output

View File

@ -96,6 +96,7 @@ def convert_tz(local_naive, tzoffset, tzinfo):
del got_archive["username"] # we didn't have this in the 1.x default format
del got_archive["hostname"] # we didn't have this in the 1.x default format
del got_archive["comment"] # we didn't have this in the 1.x default format
del got_archive["tags"] # we didn't have this in the 1.x default format
del expected_archive["id"]
del expected_archive["barchive"]
# timestamps: