浏览代码

implement /./relpath hack, fixes #1655

Thomas Waldmann 8 年之前
父节点
当前提交
e829e8372d
共有 3 个文件被更改,包括 23 次插入7 次删除
  1. 11 5
      borg/helpers.py
  2. 3 1
      borg/remote.py
  3. 9 1
      borg/testsuite/helpers.py

+ 11 - 5
borg/helpers.py

@@ -835,26 +835,32 @@ class Location:
         return True
 
     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 = os.path.normpath(m.group('path'))
+            self.path = normpath_special(m.group('path'))
             self.archive = m.group('archive')
             return True
         m = self.file_re.match(text)
         if m:
             self.proto = m.group('proto')
-            self.path = os.path.normpath(m.group('path'))
+            self.path = normpath_special(m.group('path'))
             self.archive = m.group('archive')
             return True
         m = self.scp_re.match(text)
         if m:
             self.user = m.group('user')
             self.host = m.group('host')
-            self.path = os.path.normpath(m.group('path'))
+            self.path = normpath_special(m.group('path'))
             self.archive = m.group('archive')
             self.proto = self.host and 'ssh' or 'file'
             return True
@@ -885,9 +891,9 @@ class Location:
             return self.path
         else:
             if self.path and self.path.startswith('~'):
-                path = '/' + self.path
+                path = '/' + self.path  # /~/x = path x relative to home dir
             elif self.path and not self.path.startswith('/'):
-                path = '/~/' + self.path
+                path = '/./' + self.path  # /./x = path x relative to cwd
             else:
                 path = self.path
             return 'ssh://{}{}{}{}'.format('{}@'.format(self.user) if self.user else '',

+ 3 - 1
borg/remote.py

@@ -129,8 +129,10 @@ class RepositoryServer:  # pragma: no cover
 
     def open(self, path, create=False, lock_wait=None, lock=True, exclusive=None, append_only=False):
         path = os.fsdecode(path)
-        if path.startswith('/~'):
+        if path.startswith('/~'):  # /~/x = path x relative to home dir, /~username/x = relative to "user" home dir
             path = path[1:]
+        elif path.startswith('/./'):  # /./x = path x relative to cwd
+            path = path[3:]
         path = os.path.realpath(os.path.expanduser(path))
         if self.restrict_to_paths:
             # if --restrict-to-path P is given, we make sure that we only operate in/below path P.

+ 9 - 1
borg/testsuite/helpers.py

@@ -69,6 +69,8 @@ class TestLocationWithoutEnv:
             "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path', archive='archive')"
         assert repr(Location('/some/absolute/path')) == \
             "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path', archive=None)"
+        assert repr(Location('ssh://user@host/some/path')) == \
+               "Location(proto='ssh', user='user', host='host', port=None, path='/some/path', archive=None)"
 
     def test_relpath(self, monkeypatch):
         monkeypatch.delenv('BORG_REPO', raising=False)
@@ -76,6 +78,12 @@ class TestLocationWithoutEnv:
             "Location(proto='file', user=None, host=None, port=None, path='some/relative/path', archive='archive')"
         assert repr(Location('some/relative/path')) == \
             "Location(proto='file', user=None, host=None, port=None, path='some/relative/path', archive=None)"
+        assert repr(Location('ssh://user@host/./some/path')) == \
+               "Location(proto='ssh', user='user', host='host', port=None, path='/./some/path', archive=None)"
+        assert repr(Location('ssh://user@host/~/some/path')) == \
+               "Location(proto='ssh', user='user', host='host', port=None, path='/~/some/path', archive=None)"
+        assert repr(Location('ssh://user@host/~user/some/path')) == \
+               "Location(proto='ssh', user='user', host='host', port=None, path='/~user/some/path', archive=None)"
 
     def test_with_colons(self, monkeypatch):
         monkeypatch.delenv('BORG_REPO', raising=False)
@@ -107,7 +115,7 @@ class TestLocationWithoutEnv:
                      'ssh://user@host:1234/some/path::archive']
         for location in locations:
             assert Location(location).canonical_path() == \
-                Location(Location(location).canonical_path()).canonical_path()
+                Location(Location(location).canonical_path()).canonical_path(), "failed: %s" % location
 
     def test_format_path(self, monkeypatch):
         monkeypatch.delenv('BORG_REPO', raising=False)