Browse Source

new ssh: URLs, see #8372

ssh://user@host:port/rel/path
ssh://user@host:port//abs/path

remove the /./ and /~/ hacks.
Thomas Waldmann 7 months ago
parent
commit
835b2657cd

+ 1 - 1
src/borg/conftest.py

@@ -120,7 +120,7 @@ def archiver(tmp_path, set_env_variables):
 
 
 @pytest.fixture()
 @pytest.fixture()
 def remote_archiver(archiver):
 def remote_archiver(archiver):
-    archiver.repository_location = "ssh://__testsuite__" + str(archiver.repository_path)
+    archiver.repository_location = "ssh://__testsuite__/" + str(archiver.repository_path)
     yield archiver
     yield archiver
 
 
 
 

+ 18 - 38
src/borg/helpers/parseformat.py

@@ -454,9 +454,9 @@ class Location:
         + optional_user_re
         + optional_user_re
         + host_re
         + host_re
         + r"""                 # user@  (optional), host name or address
         + 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,
         re.VERBOSE,
     )  # path
     )  # path
 
 
@@ -538,19 +538,13 @@ class Location:
             raise ValueError('Invalid location format: "%s"' % self.processed)
             raise ValueError('Invalid location format: "%s"' % self.processed)
 
 
     def _parse(self, text):
     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)
         m = self.ssh_re.match(text)
         if m:
         if m:
             self.proto = m.group("proto")
             self.proto = m.group("proto")
             self.user = m.group("user")
             self.user = m.group("user")
             self._host = m.group("host")
             self._host = m.group("host")
             self.port = m.group("port") and int(m.group("port")) or None
             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
             return True
         m = self.sftp_re.match(text)
         m = self.sftp_re.match(text)
         if m:
         if m:
@@ -558,7 +552,7 @@ class Location:
             self.user = m.group("user")
             self.user = m.group("user")
             self._host = m.group("host")
             self._host = m.group("host")
             self.port = m.group("port") and int(m.group("port")) or None
             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
             return True
         m = self.rclone_re.match(text)
         m = self.rclone_re.match(text)
         if m:
         if m:
@@ -568,17 +562,17 @@ class Location:
         m = self.file_re.match(text)
         m = self.file_re.match(text)
         if m:
         if m:
             self.proto = m.group("proto")
             self.proto = m.group("proto")
-            self.path = normpath_special(m.group("path"))
+            self.path = os.path.normpath(m.group("path"))
             return True
             return True
         m = self.socket_re.match(text)
         m = self.socket_re.match(text)
         if m:
         if m:
             self.proto = m.group("proto")
             self.proto = m.group("proto")
-            self.path = normpath_special(m.group("path"))
+            self.path = os.path.normpath(m.group("path"))
             return True
             return True
         m = self.local_re.match(text)
         m = self.local_re.match(text)
         if m:
         if m:
             self.proto = "file"
             self.proto = "file"
-            self.path = normpath_special(m.group("path"))
+            self.path = os.path.normpath(m.group("path"))
             return True
             return True
         return False
         return False
 
 
@@ -615,31 +609,17 @@ class Location:
     def canonical_path(self):
     def canonical_path(self):
         if self.proto in ("file", "socket"):
         if self.proto in ("file", "socket"):
             return self.path
             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):
     def with_timestamp(self, timestamp):
         # note: this only affects the repository URL/path, not the archive name!
         # note: this only affects the repository URL/path, not the archive name!

+ 2 - 6
src/borg/remote.py

@@ -361,12 +361,8 @@ class RepositoryServer:  # pragma: no cover
     def _resolve_path(self, path):
     def _resolve_path(self, path):
         if isinstance(path, bytes):
         if isinstance(path, bytes):
             path = os.fsdecode(path)
             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(
     def open(
         self,
         self,

+ 47 - 73
src/borg/testsuite/helpers_test.py

@@ -108,83 +108,69 @@ class TestLocationWithoutEnv:
     def test_ssh(self, monkeypatch, keys_dir):
     def test_ssh(self, monkeypatch, keys_dir):
         monkeypatch.delenv("BORG_REPO", raising=False)
         monkeypatch.delenv("BORG_REPO", raising=False)
         assert (
         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 (
         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 (
         assert (
-            repr(Location("ssh://user@host/some/path"))
-            == "Location(proto='ssh', user='user', host='host', port=None, path='/some/path')"
+            repr(Location("ssh://user@host/relative/path"))
+            == "Location(proto='ssh', user='user', host='host', port=None, path='relative/path')"
         )
         )
         assert (
         assert (
-            repr(Location("ssh://user@[::]:1234/some/path"))
-            == "Location(proto='ssh', user='user', host='::', port=1234, 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 (
         assert (
-            repr(Location("ssh://user@[::]:1234/some/path"))
-            == "Location(proto='ssh', user='user', host='::', port=1234, path='/some/path')"
+            repr(Location("ssh://user@[::]/relative/path"))
+            == "Location(proto='ssh', user='user', host='::', port=None, path='relative/path')"
         )
         )
-        assert Location("ssh://user@[::]:1234/some/path").to_key_filename() == keys_dir + "_____some_path"
         assert (
         assert (
-            repr(Location("ssh://user@[::]/some/path"))
-            == "Location(proto='ssh', user='user', host='::', port=None, 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 (
         assert (
-            repr(Location("ssh://user@[2001:db8::]:1234/some/path"))
-            == "Location(proto='ssh', user='user', host='2001:db8::', port=1234, path='/some/path')"
+            Location("ssh://user@[2001:db8::]:1234/relative/path").to_key_filename()
+            == keys_dir + "2001_db8____relative_path"
         )
         )
         assert (
         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 (
         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]:1234/relative/path"))
+            == "Location(proto='ssh', user='user', host='2001:db8::c0:ffee', port=1234, path='relative/path')"
         )
         )
         assert (
         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::c0:ffee]/relative/path"))
+            == "Location(proto='ssh', user='user', host='2001:db8::c0:ffee', port=None, path='relative/path')"
         )
         )
         assert (
         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]:1234/relative/path"))
+            == "Location(proto='ssh', user='user', host='2001:db8::192.0.2.1', port=1234, path='relative/path')"
         )
         )
         assert (
         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 (
         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')"
+            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 (
         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', "
             == "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 (
         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', "
             == "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):
     def test_rclone(self, monkeypatch, keys_dir):
@@ -250,41 +236,28 @@ class TestLocationWithoutEnv:
     def test_abspath(self, monkeypatch, keys_dir):
     def test_abspath(self, monkeypatch, keys_dir):
         monkeypatch.delenv("BORG_REPO", raising=False)
         monkeypatch.delenv("BORG_REPO", raising=False)
         assert (
         assert (
-            repr(Location("/some/absolute/path"))
-            == "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path')"
-        )
-        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("/some/absolute/path").to_key_filename() == keys_dir + "_some_absolute_path"
+        assert Location("/absolute/path").to_key_filename() == keys_dir + "_absolute_path"
         assert (
         assert (
-            repr(Location("ssh://user@host/some/path"))
-            == "Location(proto='ssh', user='user', host='host', port=None, path='/some/path')"
+            repr(Location("ssh://user@host//absolute/path"))
+            == "Location(proto='ssh', user='user', host='host', port=None, path='/absolute/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):
     def test_relpath(self, monkeypatch, keys_dir):
         monkeypatch.delenv("BORG_REPO", raising=False)
         monkeypatch.delenv("BORG_REPO", raising=False)
         assert (
         assert (
-            repr(Location("some/relative/path"))
-            == "Location(proto='file', user=None, host=None, port=None, path='some/relative/path')"
-        )
-        assert (
-            repr(Location("some/relative/path"))
-            == "Location(proto='file', user=None, host=None, port=None, path='some/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')"
+            repr(Location("relative/path"))
+            == "Location(proto='file', user=None, host=None, port=None, path='relative/path')"
         )
         )
-        assert Location("ssh://user@host/./some/path").to_key_filename() == keys_dir + "host_____some_path"
+        assert Location("relative/path").to_key_filename() == keys_dir + "relative_path"
         assert (
         assert (
-            repr(Location("ssh://user@host/~/some/path"))
-            == "Location(proto='ssh', user='user', host='host', port=None, path='/~/some/path')"
+            repr(Location("ssh://user@host/relative/path"))
+            == "Location(proto='ssh', user='user', host='host', port=None, path='relative/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):
     def test_with_colons(self, monkeypatch, keys_dir):
         monkeypatch.delenv("BORG_REPO", raising=False)
         monkeypatch.delenv("BORG_REPO", raising=False)
@@ -310,7 +283,8 @@ class TestLocationWithoutEnv:
             "host:some/path",
             "host:some/path",
             "host:~user/some/path",
             "host:~user/some/path",
             "socket:///some/path",
             "socket:///some/path",
-            "ssh://host/some/path",
+            "ssh://host/relative/path",
+            "ssh://host//absolute/path",
             "ssh://user@host:1234/some/path",
             "ssh://user@host:1234/some/path",
         ]
         ]
         for location in locations:
         for location in locations:

+ 1 - 1
src/borg/testsuite/legacyrepository_test.py

@@ -30,7 +30,7 @@ def repository(tmp_path):
 def remote_repository(tmp_path):
 def remote_repository(tmp_path):
     if is_win32:
     if is_win32:
         pytest.skip("Remote repository does not yet work on Windows.")
         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)
     yield LegacyRemoteRepository(repository_location, exclusive=True, create=True)
 
 
 
 

+ 1 - 1
src/borg/testsuite/repository_test.py

@@ -25,7 +25,7 @@ def repository(tmp_path):
 def remote_repository(tmp_path):
 def remote_repository(tmp_path):
     if is_win32:
     if is_win32:
         pytest.skip("Remote repository does not yet work on Windows.")
         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)
     yield RemoteRepository(repository_location, exclusive=True, create=True)