Merge pull request #9464 from ThomasWaldmann/create-set-metadata

borg create: add --hostname/--username/--tags
This commit is contained in:
TW 2026-03-11 21:04:45 +01:00 committed by GitHub
commit 7f7e6061fb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 80 additions and 4 deletions

View file

@ -48,7 +48,8 @@ from .helpers.lrucache import LRUCache
from .manifest import Manifest
from .patterns import PathPrefixPattern, FnmatchPattern, IECommand
from .item import Item, ArchiveItem, ItemDiff
from .platform import acl_get, acl_set, set_flags, get_flags, swidth, hostname
from . import platform
from .platform import acl_get, acl_set, set_flags, get_flags, swidth
from .remote import RemoteRepository, cache_if_remote
from .repository import Repository, NoManifestError
from .repoobj import RepoObj
@ -505,6 +506,8 @@ class Archive:
log_json=False,
iec=False,
deleted=False,
hostname=None,
username=None,
):
name_is_id = isinstance(name, bytes)
if not name_is_id:
@ -523,6 +526,8 @@ class Archive:
self.name_in_manifest = name # can differ from .name later (if borg check fixed duplicate archive names)
self.comment = None
self.tags = None
self.hostname = hostname if hostname is not None else platform.hostname
self.username = username if username is not None else getuser()
self.numeric_ids = numeric_ids
self.noatime = noatime
self.noctime = noctime
@ -693,8 +698,8 @@ Duration: {0.duration}
"item_ptrs": item_ptrs, # see #1473
"command_line": join_cmd(sys.argv),
"cwd": self.cwd,
"hostname": hostname,
"username": getuser(),
"hostname": self.hostname,
"username": self.username,
"time": nominal.isoformat(timespec="microseconds"),
"start": start.isoformat(timespec="microseconds"),
"end": end.isoformat(timespec="microseconds"),

View file

@ -211,6 +211,14 @@ class CreateMixIn:
# do not save the archive if the user ctrl-c-ed.
raise Error("Got Ctrl-C / SIGINT.")
else:
# deal with tags
if args.tags is not None:
tags = {tag for tag in args.tags if tag}
special = {tag for tag in tags if tag.startswith("@")}
if not special.issubset(SPECIAL_TAGS):
raise Error("Unknown special tags given.")
archive.tags = tags
archive.save(comment=args.comment, timestamp=args.timestamp)
args.stats |= args.json
if args.stats:
@ -250,6 +258,8 @@ class CreateMixIn:
start=t0,
log_json=args.log_json,
iec=args.iec,
hostname=args.hostname,
username=args.username,
)
metadata_collector = MetadataCollector(
noatime=not args.atime,
@ -593,6 +603,8 @@ class CreateMixIn:
The archive will consume almost no disk space for files or parts of files that
have already been stored in other archives.
The ``--tags`` option can be used to add a list of tags to the new archive.
The archive name does not need to be unique; you can and should use the same
name for a series of archives. The unique archive identifier is its ID (hash),
and you can abbreviate the ID as long as it is unique.
@ -975,6 +987,32 @@ class CreateMixIn:
action=Highlander,
help="select compression algorithm, see the output of the " '"borg help compression" command for details.',
)
archive_group.add_argument(
"--hostname",
metavar="HOSTNAME",
dest="hostname",
type=str,
default=None,
action=Highlander,
help="explicitly set hostname for the archive",
)
archive_group.add_argument(
"--username",
metavar="USERNAME",
dest="username",
type=str,
default=None,
action=Highlander,
help="explicitly set username for the archive",
)
archive_group.add_argument(
"--tags",
metavar="TAG",
dest="tags",
type=helpers.tag_validator,
nargs="+",
help="add tags to archive (comma-separated or multiple arguments)",
)
subparser.add_argument("name", metavar="NAME", type=archivename_validator, help="specify the archive name")
subparser.add_argument(

View file

@ -15,7 +15,7 @@ from ...constants import zeros
from ...manifest import Manifest
from ...platform import is_win32
from ...repository import Repository
from ...helpers import CommandError, BackupPermissionError
from ...helpers import CommandError, BackupPermissionError, Error
from .. import has_lchflags, has_mknod
from .. import changedir
from .. import (
@ -682,6 +682,28 @@ def test_file_status(archivers, request):
assert "A input/file2" in output
def test_create_tags(archivers, request):
archiver = request.getfixturevalue(archivers)
create_test_files(archiver.input_path)
cmd(archiver, "repo-create", RK_ENCRYPTION)
cmd(archiver, "create", "--tags", "foo", "bar", "baz", "--", "test", "input")
info = cmd(archiver, "info", "--json", "test")
info = json.loads(info)
assert sorted(info["archives"][0]["tags"]) == ["bar", "baz", "foo"]
def test_create_invalid_tags(archivers, request):
archiver = request.getfixturevalue(archivers)
create_test_files(archiver.input_path)
cmd(archiver, "repo-create", RK_ENCRYPTION)
if archiver.FORK_DEFAULT:
output = cmd(archiver, "create", "--tags", "@INVALID", "--", "test", "input", exit_code=EXIT_ERROR)
assert "Unknown special tags given" in output
else:
with pytest.raises(Error):
cmd(archiver, "create", "--tags", "@INVALID", "--", "test", "input")
@pytest.mark.skipif(
is_win32, reason="ctime attribute is file creation time on Windows"
) # see https://docs.python.org/3/library/os.html#os.stat_result.st_ctime
@ -829,6 +851,17 @@ def test_create_json(archivers, request):
assert "stats" in archive
def test_explicit_hostname_and_username(archivers, request):
archiver = request.getfixturevalue(archivers)
create_regular_file(archiver.input_path, "file1", size=1024 * 80)
cmd(archiver, "repo-create", RK_ENCRYPTION)
cmd(archiver, "create", "--hostname", "foo_host", "--username", "bar_user", "test", "input")
info = json.loads(cmd(archiver, "info", "--json", "test"))
archive = info["archives"][0]
assert archive["hostname"] == "foo_host"
assert archive["username"] == "bar_user"
def test_create_topical(archivers, request):
archiver = request.getfixturevalue(archivers)
create_regular_file(archiver.input_path, "file1", size=1024 * 80)