Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/borg/archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
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
Expand Down Expand Up @@ -505,6 +506,8 @@ def __init__(
log_json=False,
iec=False,
deleted=False,
hostname=None,
username=None,
):
name_is_id = isinstance(name, bytes)
if not name_is_id:
Expand All @@ -523,6 +526,8 @@ def __init__(
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
Expand Down Expand Up @@ -693,8 +698,8 @@ def save(self, name=None, comment=None, timestamp=None, stats=None, additional_m
"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"),
Expand Down
38 changes: 38 additions & 0 deletions src/borg/archiver/create_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,14 @@ def create_inner(archive, cache, fso):
# 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:
Expand Down Expand Up @@ -250,6 +258,8 @@ def create_inner(archive, cache, fso):
start=t0,
log_json=args.log_json,
iec=args.iec,
hostname=args.hostname,
username=args.username,
)
metadata_collector = MetadataCollector(
noatime=not args.atime,
Expand Down Expand Up @@ -593,6 +603,8 @@ def build_parser_create(self, subparsers, common_parser, mid_common_parser):
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.
Expand Down Expand Up @@ -975,6 +987,32 @@ def build_parser_create(self, subparsers, common_parser, mid_common_parser):
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(
Expand Down
35 changes: 34 additions & 1 deletion src/borg/testsuite/archiver/create_cmd_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
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 (
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Loading