mirror of
https://github.com/borgbackup/borg.git
synced 2024-10-03 23:42:59 +03:00
Merge pull request #8449 from ThomasWaldmann/tag-command
tag: set, add, remove tags
This commit is contained in:
commit
c308f76ddc
98
docs/man/borg-tag.1
Normal file
98
docs/man/borg-tag.1
Normal 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.
|
||||
.
|
@ -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
1
docs/usage/tag.rst
Normal file
@ -0,0 +1 @@
|
||||
.. include:: tag.rst.inc
|
93
docs/usage/tag.rst.inc
Normal file
93
docs/usage/tag.rst.inc
Normal 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.
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
95
src/borg/archiver/tag_cmd.py
Normal file
95
src/borg/archiver/tag_cmd.py
Normal 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"
|
||||
)
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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")
|
||||
|
32
src/borg/testsuite/archiver/tag_cmd.py
Normal file
32
src/borg/testsuite/archiver/tag_cmd.py
Normal 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
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user