Merge pull request #9348 from trxvorr/fix-9339-msys2-paths
Some checks failed
Lint / lint (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / security (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
CI / asan_ubsan (push) Has been cancelled
CI / native_tests (push) Has been cancelled
CI / vm_tests (Haiku, false, haiku, r1beta5) (push) Has been cancelled
CI / vm_tests (NetBSD, false, netbsd, 10.1) (push) Has been cancelled
CI / vm_tests (OmniOS, false, omnios, r151056) (push) Has been cancelled
CI / vm_tests (OpenBSD, false, openbsd, 7.8) (push) Has been cancelled
CI / vm_tests (borg-freebsd-14-x86_64-gh, FreeBSD, true, freebsd, 14.3) (push) Has been cancelled
CI / windows_tests (push) Has been cancelled

archiver: warn about MSYS2 path translation, fixes #9339
This commit is contained in:
TW 2026-03-17 15:37:13 +01:00 committed by GitHub
commit be640e982f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 80 additions and 1 deletions

View file

@ -626,6 +626,8 @@ jobs:
env:
PY_COLORS: 1
MSYS2_ARG_CONV_EXCL: "*"
MSYS2_ENV_CONV_EXCL: "*"
defaults:
run:

View file

@ -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
++++++++++++++++++++++++++++

View file

@ -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):

View file

@ -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

View file

@ -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)