mirror of
https://github.com/borgbackup/borg.git
synced 2025-03-07 03:59:19 +00:00
new ssh: URLs, see #8372
ssh://user@host:port/rel/path ssh://user@host:port//abs/path remove the /./ and /~/ hacks.
This commit is contained in:
parent
72fc028142
commit
835b2657cd
6 changed files with 74 additions and 124 deletions
src/borg
|
@ -120,7 +120,7 @@ def archiver(tmp_path, set_env_variables):
|
|||
|
||||
@pytest.fixture()
|
||||
def remote_archiver(archiver):
|
||||
archiver.repository_location = "ssh://__testsuite__" + str(archiver.repository_path)
|
||||
archiver.repository_location = "ssh://__testsuite__/" + str(archiver.repository_path)
|
||||
yield archiver
|
||||
|
||||
|
||||
|
|
|
@ -454,9 +454,9 @@ class Location:
|
|||
+ optional_user_re
|
||||
+ host_re
|
||||
+ r""" # user@ (optional), host name or address
|
||||
(?::(?P<port>\d+))? # :port (optional)
|
||||
(?::(?P<port>\d+))?/ # :port (optional) + "/" as separator
|
||||
"""
|
||||
+ abs_path_re,
|
||||
+ path_re,
|
||||
re.VERBOSE,
|
||||
) # path
|
||||
|
||||
|
@ -538,19 +538,13 @@ class Location:
|
|||
raise ValueError('Invalid location format: "%s"' % self.processed)
|
||||
|
||||
def _parse(self, text):
|
||||
def normpath_special(p):
|
||||
# avoid that normpath strips away our relative path hack and even makes p absolute
|
||||
relative = p.startswith("/./")
|
||||
p = os.path.normpath(p)
|
||||
return ("/." + p) if relative else p
|
||||
|
||||
m = self.ssh_re.match(text)
|
||||
if m:
|
||||
self.proto = m.group("proto")
|
||||
self.user = m.group("user")
|
||||
self._host = m.group("host")
|
||||
self.port = m.group("port") and int(m.group("port")) or None
|
||||
self.path = normpath_special(m.group("path"))
|
||||
self.path = os.path.normpath(m.group("path"))
|
||||
return True
|
||||
m = self.sftp_re.match(text)
|
||||
if m:
|
||||
|
@ -558,7 +552,7 @@ class Location:
|
|||
self.user = m.group("user")
|
||||
self._host = m.group("host")
|
||||
self.port = m.group("port") and int(m.group("port")) or None
|
||||
self.path = normpath_special(m.group("path"))
|
||||
self.path = os.path.normpath(m.group("path"))
|
||||
return True
|
||||
m = self.rclone_re.match(text)
|
||||
if m:
|
||||
|
@ -568,17 +562,17 @@ class Location:
|
|||
m = self.file_re.match(text)
|
||||
if m:
|
||||
self.proto = m.group("proto")
|
||||
self.path = normpath_special(m.group("path"))
|
||||
self.path = os.path.normpath(m.group("path"))
|
||||
return True
|
||||
m = self.socket_re.match(text)
|
||||
if m:
|
||||
self.proto = m.group("proto")
|
||||
self.path = normpath_special(m.group("path"))
|
||||
self.path = os.path.normpath(m.group("path"))
|
||||
return True
|
||||
m = self.local_re.match(text)
|
||||
if m:
|
||||
self.proto = "file"
|
||||
self.path = normpath_special(m.group("path"))
|
||||
self.path = os.path.normpath(m.group("path"))
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -615,31 +609,17 @@ class Location:
|
|||
def canonical_path(self):
|
||||
if self.proto in ("file", "socket"):
|
||||
return self.path
|
||||
else:
|
||||
if self.path and self.path.startswith("~"):
|
||||
path = "/" + self.path # /~/x = path x relative to home dir
|
||||
elif self.path and not self.path.startswith("/"):
|
||||
path = "/./" + self.path # /./x = path x relative to cwd
|
||||
else:
|
||||
path = self.path
|
||||
if self.proto == "rclone":
|
||||
return f"{self.proto}:{self.path}"
|
||||
elif self.proto == "sftp":
|
||||
return (
|
||||
f"{self.proto}://"
|
||||
f"{(self.user + '@') if self.user else ''}"
|
||||
f"{self._host if self._host else ''}"
|
||||
f"{self.port if self.port else ''}/"
|
||||
f"{path}"
|
||||
)
|
||||
else:
|
||||
return "{}://{}{}{}{}".format(
|
||||
self.proto if self.proto else "???",
|
||||
f"{self.user}@" if self.user else "",
|
||||
self._host if self._host else "", # needed for ipv6 addrs
|
||||
f":{self.port}" if self.port else "",
|
||||
path,
|
||||
)
|
||||
if self.proto == "rclone":
|
||||
return f"{self.proto}:{self.path}"
|
||||
if self.proto in ("sftp", "ssh"):
|
||||
return (
|
||||
f"{self.proto}://"
|
||||
f"{(self.user + '@') if self.user else ''}"
|
||||
f"{self._host if self._host else ''}"
|
||||
f"{self.port if self.port else ''}/"
|
||||
f"{self.path}"
|
||||
)
|
||||
raise NotImplementedError(self.proto)
|
||||
|
||||
def with_timestamp(self, timestamp):
|
||||
# note: this only affects the repository URL/path, not the archive name!
|
||||
|
|
|
@ -361,12 +361,8 @@ class RepositoryServer: # pragma: no cover
|
|||
def _resolve_path(self, path):
|
||||
if isinstance(path, bytes):
|
||||
path = os.fsdecode(path)
|
||||
if path.startswith("/~/"): # /~/x = path x relative to own home dir
|
||||
home_dir = os.environ.get("HOME") or os.path.expanduser("~%s" % os.environ.get("USER", ""))
|
||||
path = os.path.join(home_dir, path[3:])
|
||||
elif path.startswith("/./"): # /./x = path x relative to cwd
|
||||
path = path[3:]
|
||||
return os.path.realpath(path)
|
||||
path = os.path.realpath(path)
|
||||
return path
|
||||
|
||||
def open(
|
||||
self,
|
||||
|
|
|
@ -108,83 +108,69 @@ class TestLocationWithoutEnv:
|
|||
def test_ssh(self, monkeypatch, keys_dir):
|
||||
monkeypatch.delenv("BORG_REPO", raising=False)
|
||||
assert (
|
||||
repr(Location("ssh://user@host:1234/some/path"))
|
||||
== "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path')"
|
||||
repr(Location("ssh://user@host:1234//absolute/path"))
|
||||
== "Location(proto='ssh', user='user', host='host', port=1234, path='/absolute/path')"
|
||||
)
|
||||
assert Location("ssh://user@host:1234/some/path").to_key_filename() == keys_dir + "host___some_path"
|
||||
assert Location("ssh://user@host:1234//absolute/path").to_key_filename() == keys_dir + "host___absolute_path"
|
||||
assert (
|
||||
repr(Location("ssh://user@host:1234/some/path"))
|
||||
== "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path')"
|
||||
repr(Location("ssh://user@host:1234/relative/path"))
|
||||
== "Location(proto='ssh', user='user', host='host', port=1234, path='relative/path')"
|
||||
)
|
||||
assert Location("ssh://user@host:1234/relative/path").to_key_filename() == keys_dir + "host__relative_path"
|
||||
assert (
|
||||
repr(Location("ssh://user@host/relative/path"))
|
||||
== "Location(proto='ssh', user='user', host='host', port=None, path='relative/path')"
|
||||
)
|
||||
assert (
|
||||
repr(Location("ssh://user@host/some/path"))
|
||||
== "Location(proto='ssh', user='user', host='host', port=None, path='/some/path')"
|
||||
repr(Location("ssh://user@[::]:1234/relative/path"))
|
||||
== "Location(proto='ssh', user='user', host='::', port=1234, path='relative/path')"
|
||||
)
|
||||
assert Location("ssh://user@[::]:1234/relative/path").to_key_filename() == keys_dir + "____relative_path"
|
||||
assert (
|
||||
repr(Location("ssh://user@[::]/relative/path"))
|
||||
== "Location(proto='ssh', user='user', host='::', port=None, path='relative/path')"
|
||||
)
|
||||
assert (
|
||||
repr(Location("ssh://user@[::]:1234/some/path"))
|
||||
== "Location(proto='ssh', user='user', host='::', port=1234, path='/some/path')"
|
||||
repr(Location("ssh://user@[2001:db8::]:1234/relative/path"))
|
||||
== "Location(proto='ssh', user='user', host='2001:db8::', port=1234, path='relative/path')"
|
||||
)
|
||||
assert (
|
||||
repr(Location("ssh://user@[::]:1234/some/path"))
|
||||
== "Location(proto='ssh', user='user', host='::', port=1234, path='/some/path')"
|
||||
)
|
||||
assert Location("ssh://user@[::]:1234/some/path").to_key_filename() == keys_dir + "_____some_path"
|
||||
assert (
|
||||
repr(Location("ssh://user@[::]/some/path"))
|
||||
== "Location(proto='ssh', user='user', host='::', port=None, path='/some/path')"
|
||||
Location("ssh://user@[2001:db8::]:1234/relative/path").to_key_filename()
|
||||
== keys_dir + "2001_db8____relative_path"
|
||||
)
|
||||
assert (
|
||||
repr(Location("ssh://user@[2001:db8::]:1234/some/path"))
|
||||
== "Location(proto='ssh', user='user', host='2001:db8::', port=1234, path='/some/path')"
|
||||
repr(Location("ssh://user@[2001:db8::]/relative/path"))
|
||||
== "Location(proto='ssh', user='user', host='2001:db8::', port=None, path='relative/path')"
|
||||
)
|
||||
assert (
|
||||
repr(Location("ssh://user@[2001:db8::]:1234/some/path"))
|
||||
== "Location(proto='ssh', user='user', host='2001:db8::', port=1234, path='/some/path')"
|
||||
repr(Location("ssh://user@[2001:db8::c0:ffee]:1234/relative/path"))
|
||||
== "Location(proto='ssh', user='user', host='2001:db8::c0:ffee', port=1234, path='relative/path')"
|
||||
)
|
||||
assert (
|
||||
Location("ssh://user@[2001:db8::]:1234/some/path").to_key_filename() == keys_dir + "2001_db8_____some_path"
|
||||
repr(Location("ssh://user@[2001:db8::c0:ffee]/relative/path"))
|
||||
== "Location(proto='ssh', user='user', host='2001:db8::c0:ffee', port=None, path='relative/path')"
|
||||
)
|
||||
assert (
|
||||
repr(Location("ssh://user@[2001:db8::]/some/path"))
|
||||
== "Location(proto='ssh', user='user', host='2001:db8::', port=None, path='/some/path')"
|
||||
repr(Location("ssh://user@[2001:db8::192.0.2.1]:1234/relative/path"))
|
||||
== "Location(proto='ssh', user='user', host='2001:db8::192.0.2.1', port=1234, path='relative/path')"
|
||||
)
|
||||
assert (
|
||||
repr(Location("ssh://user@[2001:db8::c0:ffee]:1234/some/path"))
|
||||
== "Location(proto='ssh', user='user', host='2001:db8::c0:ffee', port=1234, path='/some/path')"
|
||||
repr(Location("ssh://user@[2001:db8::192.0.2.1]/relative/path"))
|
||||
== "Location(proto='ssh', user='user', host='2001:db8::192.0.2.1', port=None, path='relative/path')"
|
||||
)
|
||||
assert (
|
||||
repr(Location("ssh://user@[2001:db8::c0:ffee]:1234/some/path"))
|
||||
== "Location(proto='ssh', user='user', host='2001:db8::c0:ffee', port=1234, path='/some/path')"
|
||||
Location("ssh://user@[2001:db8::192.0.2.1]/relative/path").to_key_filename()
|
||||
== keys_dir + "2001_db8__192_0_2_1__relative_path"
|
||||
)
|
||||
assert (
|
||||
repr(Location("ssh://user@[2001:db8::c0:ffee]/some/path"))
|
||||
== "Location(proto='ssh', user='user', host='2001:db8::c0:ffee', port=None, path='/some/path')"
|
||||
)
|
||||
assert (
|
||||
repr(Location("ssh://user@[2001:db8::192.0.2.1]:1234/some/path"))
|
||||
== "Location(proto='ssh', user='user', host='2001:db8::192.0.2.1', port=1234, path='/some/path')"
|
||||
)
|
||||
assert (
|
||||
repr(Location("ssh://user@[2001:db8::192.0.2.1]:1234/some/path"))
|
||||
== "Location(proto='ssh', user='user', host='2001:db8::192.0.2.1', port=1234, path='/some/path')"
|
||||
)
|
||||
assert (
|
||||
repr(Location("ssh://user@[2001:db8::192.0.2.1]/some/path"))
|
||||
== "Location(proto='ssh', user='user', host='2001:db8::192.0.2.1', port=None, path='/some/path')"
|
||||
)
|
||||
assert (
|
||||
Location("ssh://user@[2001:db8::192.0.2.1]/some/path").to_key_filename()
|
||||
== keys_dir + "2001_db8__192_0_2_1___some_path"
|
||||
)
|
||||
assert (
|
||||
repr(Location("ssh://user@[2a02:0001:0002:0003:0004:0005:0006:0007]/some/path"))
|
||||
repr(Location("ssh://user@[2a02:0001:0002:0003:0004:0005:0006:0007]/relative/path"))
|
||||
== "Location(proto='ssh', user='user', "
|
||||
"host='2a02:0001:0002:0003:0004:0005:0006:0007', port=None, path='/some/path')"
|
||||
"host='2a02:0001:0002:0003:0004:0005:0006:0007', port=None, path='relative/path')"
|
||||
)
|
||||
assert (
|
||||
repr(Location("ssh://user@[2a02:0001:0002:0003:0004:0005:0006:0007]:1234/some/path"))
|
||||
repr(Location("ssh://user@[2a02:0001:0002:0003:0004:0005:0006:0007]:1234/relative/path"))
|
||||
== "Location(proto='ssh', user='user', "
|
||||
"host='2a02:0001:0002:0003:0004:0005:0006:0007', port=1234, path='/some/path')"
|
||||
"host='2a02:0001:0002:0003:0004:0005:0006:0007', port=1234, path='relative/path')"
|
||||
)
|
||||
|
||||
def test_rclone(self, monkeypatch, keys_dir):
|
||||
|
@ -250,41 +236,28 @@ class TestLocationWithoutEnv:
|
|||
def test_abspath(self, monkeypatch, keys_dir):
|
||||
monkeypatch.delenv("BORG_REPO", raising=False)
|
||||
assert (
|
||||
repr(Location("/some/absolute/path"))
|
||||
== "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path')"
|
||||
repr(Location("/absolute/path"))
|
||||
== "Location(proto='file', user=None, host=None, port=None, path='/absolute/path')"
|
||||
)
|
||||
assert Location("/absolute/path").to_key_filename() == keys_dir + "_absolute_path"
|
||||
assert (
|
||||
repr(Location("/some/absolute/path"))
|
||||
== "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path')"
|
||||
repr(Location("ssh://user@host//absolute/path"))
|
||||
== "Location(proto='ssh', user='user', host='host', port=None, path='/absolute/path')"
|
||||
)
|
||||
assert Location("/some/absolute/path").to_key_filename() == keys_dir + "_some_absolute_path"
|
||||
assert (
|
||||
repr(Location("ssh://user@host/some/path"))
|
||||
== "Location(proto='ssh', user='user', host='host', port=None, path='/some/path')"
|
||||
)
|
||||
assert Location("ssh://user@host/some/path").to_key_filename() == keys_dir + "host___some_path"
|
||||
assert Location("ssh://user@host//absolute/path").to_key_filename() == keys_dir + "host___absolute_path"
|
||||
|
||||
def test_relpath(self, monkeypatch, keys_dir):
|
||||
monkeypatch.delenv("BORG_REPO", raising=False)
|
||||
assert (
|
||||
repr(Location("some/relative/path"))
|
||||
== "Location(proto='file', user=None, host=None, port=None, path='some/relative/path')"
|
||||
repr(Location("relative/path"))
|
||||
== "Location(proto='file', user=None, host=None, port=None, path='relative/path')"
|
||||
)
|
||||
assert Location("relative/path").to_key_filename() == keys_dir + "relative_path"
|
||||
assert (
|
||||
repr(Location("some/relative/path"))
|
||||
== "Location(proto='file', user=None, host=None, port=None, path='some/relative/path')"
|
||||
repr(Location("ssh://user@host/relative/path"))
|
||||
== "Location(proto='ssh', user='user', host='host', port=None, path='relative/path')"
|
||||
)
|
||||
assert Location("some/relative/path").to_key_filename() == keys_dir + "some_relative_path"
|
||||
assert (
|
||||
repr(Location("ssh://user@host/./some/path"))
|
||||
== "Location(proto='ssh', user='user', host='host', port=None, path='/./some/path')"
|
||||
)
|
||||
assert Location("ssh://user@host/./some/path").to_key_filename() == keys_dir + "host_____some_path"
|
||||
assert (
|
||||
repr(Location("ssh://user@host/~/some/path"))
|
||||
== "Location(proto='ssh', user='user', host='host', port=None, path='/~/some/path')"
|
||||
)
|
||||
assert Location("ssh://user@host/~/some/path").to_key_filename() == keys_dir + "host_____some_path"
|
||||
assert Location("ssh://user@host/relative/path").to_key_filename() == keys_dir + "host__relative_path"
|
||||
|
||||
def test_with_colons(self, monkeypatch, keys_dir):
|
||||
monkeypatch.delenv("BORG_REPO", raising=False)
|
||||
|
@ -310,7 +283,8 @@ class TestLocationWithoutEnv:
|
|||
"host:some/path",
|
||||
"host:~user/some/path",
|
||||
"socket:///some/path",
|
||||
"ssh://host/some/path",
|
||||
"ssh://host/relative/path",
|
||||
"ssh://host//absolute/path",
|
||||
"ssh://user@host:1234/some/path",
|
||||
]
|
||||
for location in locations:
|
||||
|
|
|
@ -30,7 +30,7 @@ def repository(tmp_path):
|
|||
def remote_repository(tmp_path):
|
||||
if is_win32:
|
||||
pytest.skip("Remote repository does not yet work on Windows.")
|
||||
repository_location = Location("ssh://__testsuite__" + os.fspath(tmp_path / "repository"))
|
||||
repository_location = Location("ssh://__testsuite__/" + os.fspath(tmp_path / "repository"))
|
||||
yield LegacyRemoteRepository(repository_location, exclusive=True, create=True)
|
||||
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ def repository(tmp_path):
|
|||
def remote_repository(tmp_path):
|
||||
if is_win32:
|
||||
pytest.skip("Remote repository does not yet work on Windows.")
|
||||
repository_location = Location("ssh://__testsuite__" + os.fspath(tmp_path / "repository"))
|
||||
repository_location = Location("ssh://__testsuite__/" + os.fspath(tmp_path / "repository"))
|
||||
yield RemoteRepository(repository_location, exclusive=True, create=True)
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue