mirror of
https://github.com/borgbackup/borg.git
synced 2026-06-10 17:32:13 -04:00
archiver: warn about MSYS2 path translation, fixes #9339
This adds a runtime warning when running under MSYS2/Git Bash without the necessary environment variables to disable automatic path translation. The documentation is also updated to explain this behavior and how to mitigate it.
This commit is contained in:
parent
1706e0d36b
commit
d7bbcfb98b
5 changed files with 80 additions and 1 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -626,6 +626,8 @@ jobs:
|
|||
|
||||
env:
|
||||
PY_COLORS: 1
|
||||
MSYS2_ARG_CONV_EXCL: "*"
|
||||
MSYS2_ENV_CONV_EXCL: "*"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
|||
|
|
@ -326,6 +326,29 @@ Install the dependencies with the provided script::
|
|||
|
||||
./scripts/msys2-install-deps
|
||||
|
||||
.. _msys2_path_translation:
|
||||
|
||||
MSYS2 Path Translation
|
||||
++++++++++++++++++++++
|
||||
|
||||
When running Borg within an MSYS2 environment, the shell
|
||||
automatically translates POSIX-style paths (like ``/tmp`` or ``/C/Users``) to
|
||||
Windows paths (like ``C:\msys64\tmp`` or ``C:\Users``) before they reach the
|
||||
Borg process.
|
||||
|
||||
This behavior can result in absolute Windows paths being stored in your backups,
|
||||
which may not be what you intended if you use POSIX paths for portability.
|
||||
|
||||
To disable this automatic translation for Borg, you can use environment variables
|
||||
to exclude everything from conversion. Similarly, MSYS2 also translates
|
||||
environment variables that look like paths. To disable this generally for Borg,
|
||||
set both variables::
|
||||
|
||||
export MSYS2_ARG_CONV_EXCL="*"
|
||||
export MSYS2_ENV_CONV_EXCL="*"
|
||||
|
||||
For more details, see the `MSYS2 documentation on filesystem paths <https://www.msys2.org/docs/filesystem-paths/>`__.
|
||||
|
||||
Windows 10's Linux Subsystem
|
||||
++++++++++++++++++++++++++++
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ try:
|
|||
from ..helpers import msgpack
|
||||
from ..helpers import sig_int
|
||||
from ..helpers import get_config_dir
|
||||
from ..platformflags import is_msystem
|
||||
from ..remote import RemoteRepository
|
||||
from ..selftest import selftest
|
||||
except BaseException:
|
||||
|
|
@ -397,7 +398,16 @@ class Archiver(
|
|||
return functools.partial(self.do_maincommand_help, parser)
|
||||
|
||||
def prerun_checks(self, logger, is_serve):
|
||||
|
||||
if (
|
||||
not is_serve
|
||||
and is_msystem
|
||||
and ("MSYS2_ARG_CONV_EXCL" not in os.environ or "MSYS2_ENV_CONV_EXCL" not in os.environ)
|
||||
):
|
||||
logger.warning(
|
||||
"MSYS2 path translation is active. This can cause POSIX paths to be mangled into "
|
||||
"Windows paths in archives. Consider setting MSYS2_ARG_CONV_EXCL='*' and "
|
||||
"MSYS2_ENV_CONV_EXCL='*'. See https://www.msys2.org/docs/filesystem-paths/ for details."
|
||||
)
|
||||
selftest(logger)
|
||||
|
||||
def _setup_implied_logging(self, args):
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ Flags for platform-specific APIs.
|
|||
Use these flags instead of sys.platform.startswith('<os>') or try/except.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
is_win32 = sys.platform.startswith("win32")
|
||||
|
|
@ -15,3 +16,6 @@ is_netbsd = sys.platform.startswith("netbsd")
|
|||
is_openbsd = sys.platform.startswith("openbsd")
|
||||
is_darwin = sys.platform.startswith("darwin")
|
||||
is_haiku = sys.platform.startswith("haiku")
|
||||
|
||||
# MSYS2 (on Windows)
|
||||
is_msystem = is_win32 and "MSYSTEM" in os.environ
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from ...constants import * # NOQA
|
|||
from ...constants import zeros
|
||||
from ...manifest import Manifest
|
||||
from ...platform import is_win32
|
||||
from ...platformflags import is_msystem
|
||||
from ...repository import Repository
|
||||
from ...helpers import CommandError, BackupPermissionError
|
||||
from .. import has_lchflags, has_mknod
|
||||
|
|
@ -150,6 +151,45 @@ def test_archived_paths(archivers, request):
|
|||
assert expected_paths == sorted([json.loads(line)["path"] for line in archive_list.splitlines() if line])
|
||||
|
||||
|
||||
@pytest.mark.skipif(not is_msystem, reason="only for msystem")
|
||||
def test_create_msys2_path_translation_warning(archivers, request, monkeypatch):
|
||||
archiver = request.getfixturevalue(archivers)
|
||||
cmd(archiver, "repo-create", RK_ENCRYPTION)
|
||||
create_regular_file(archiver.input_path, "test")
|
||||
|
||||
# When MSYS2 path translation is active (variables NOT set), a warning should be emitted.
|
||||
monkeypatch.delenv("MSYS2_ARG_CONV_EXCL", raising=False)
|
||||
monkeypatch.delenv("MSYS2_ENV_CONV_EXCL", raising=False)
|
||||
output = cmd(archiver, "create", "test1", "input", fork=True)
|
||||
assert "MSYS2 path translation is active." in output
|
||||
|
||||
# When the variables ARE set, the warning should not be emitted,
|
||||
# and /tmp should be archived properly without being translated to msys64/tmp.
|
||||
monkeypatch.setenv("MSYS2_ARG_CONV_EXCL", "*")
|
||||
monkeypatch.setenv("MSYS2_ENV_CONV_EXCL", "*")
|
||||
|
||||
# We must create a real /tmp directory to avoid file not found errors,
|
||||
# since we will pass '/tmp' directly to Borg
|
||||
tmp_path = os.path.abspath("/tmp")
|
||||
os.makedirs(tmp_path, exist_ok=True)
|
||||
test_filepath = os.path.join(tmp_path, "borg_msys2_test_file")
|
||||
with open(test_filepath, "w") as f:
|
||||
f.write("test")
|
||||
|
||||
try:
|
||||
output2 = cmd(archiver, "create", "test2", "/tmp", fork=True)
|
||||
assert "MSYS2 path translation is active." not in output2
|
||||
|
||||
archive_list = cmd(archiver, "list", "test2", "--json-lines")
|
||||
paths = [json.loads(line)["path"] for line in archive_list.splitlines() if line]
|
||||
|
||||
# Verify that msys64 is not present and paths start with tmp/
|
||||
assert not any("msys64" in p for p in paths)
|
||||
assert any(p.startswith("tmp/borg_msys2_test_file") for p in paths)
|
||||
finally:
|
||||
os.unlink(test_filepath)
|
||||
|
||||
|
||||
@requires_hardlinks
|
||||
def test_create_duplicate_root(archivers, request):
|
||||
archiver = request.getfixturevalue(archivers)
|
||||
|
|
|
|||
Loading…
Reference in a new issue