From 7f79b65e39c85fc42cba9d9b2383d74c8575f94e Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sun, 13 Oct 2024 21:10:47 +0200 Subject: [PATCH 01/13] require borgstore ~= 0.1.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 62c8e42f2..f515195dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ classifiers = [ ] license = {text="BSD"} dependencies = [ - "borgstore ~= 0.0.4", + "borgstore ~= 0.1.0", "msgpack >=1.0.3, <=1.1.0", "packaging", "platformdirs >=3.0.0, <5.0.0; sys_platform == 'darwin'", # for macOS: breaking changes in 3.0.0, From 4e576d51c79adae719d9522be3de1e0ee18fd69b Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 14 Oct 2024 16:48:10 +0200 Subject: [PATCH 02/13] repo-create: write empty ChunkIndex to repo/cache/, docs --- src/borg/archiver/repo_create_cmd.py | 4 ++++ src/borg/repository.py | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/src/borg/archiver/repo_create_cmd.py b/src/borg/archiver/repo_create_cmd.py index ccb278ab5..ac76a44e7 100644 --- a/src/borg/archiver/repo_create_cmd.py +++ b/src/borg/archiver/repo_create_cmd.py @@ -69,6 +69,10 @@ class RepoCreateMixIn: This command creates a new, empty repository. A repository is a ``borgstore`` store containing the deduplicated data from zero or more archives. + Repository creation can be quite slow for some kinds of stores (e.g. for ``sftp:``) - + this is due to borgstore pre-creating all directories needed, making usage of the + store faster. + Encryption mode TLDR ++++++++++++++++++++ diff --git a/src/borg/repository.py b/src/borg/repository.py index fc671861f..fb0540e3b 100644 --- a/src/borg/repository.py +++ b/src/borg/repository.py @@ -181,6 +181,14 @@ class Repository: self.version = 3 self.store.store("config/version", str(self.version).encode()) self.store.store("config/id", bin_to_hex(os.urandom(32)).encode()) + # we know repo/data/ still does not have any chunks stored in it, + # but for some stores, there might be a lot of empty directories and + # listing them all might be rather slow, so we better cache an empty + # ChunkIndex from here so that the first repo operation does not have + # to build the ChunkIndex the slow way by listing all the directories. + from borg.cache import write_chunkindex_to_repo_cache + + write_chunkindex_to_repo_cache(self, ChunkIndex(), compact=True, clear=True, force_write=True) finally: self.store.close() From 83414b037d48cb1c4a6c1002217db6720ed62147 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sun, 13 Oct 2024 21:40:31 +0200 Subject: [PATCH 03/13] new rclone: URLs, fixes #8446 rclone:remote:path --- src/borg/helpers/parseformat.py | 19 +++++++++++-------- src/borg/testsuite/helpers_test.py | 4 ++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/borg/helpers/parseformat.py b/src/borg/helpers/parseformat.py index 5558d5278..d79629ca1 100644 --- a/src/borg/helpers/parseformat.py +++ b/src/borg/helpers/parseformat.py @@ -469,7 +469,7 @@ class Location: rclone_re = re.compile( r""" - (?Prclone):// # rclone:// + (?Prclone): # rclone: (?P(.*)) """, re.VERBOSE, @@ -616,13 +616,16 @@ class Location: path = "/./" + self.path # /./x = path x relative to cwd else: path = self.path - 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}" + 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, + ) def with_timestamp(self, timestamp): # note: this only affects the repository URL/path, not the archive name! diff --git a/src/borg/testsuite/helpers_test.py b/src/borg/testsuite/helpers_test.py index e7ce9eea5..182b81926 100644 --- a/src/borg/testsuite/helpers_test.py +++ b/src/borg/testsuite/helpers_test.py @@ -190,10 +190,10 @@ class TestLocationWithoutEnv: def test_rclone(self, monkeypatch, keys_dir): monkeypatch.delenv("BORG_REPO", raising=False) assert ( - repr(Location("rclone://remote:path")) + repr(Location("rclone:remote:path")) == "Location(proto='rclone', user=None, host=None, port=None, path='remote:path')" ) - assert Location("rclone://remote:path").to_key_filename() == keys_dir + "remote_path" + assert Location("rclone:remote:path").to_key_filename() == keys_dir + "remote_path" def test_sftp(self, monkeypatch, keys_dir): monkeypatch.delenv("BORG_REPO", raising=False) From d60303eb8c2c96d7015117c3f9f5d300d1bd4916 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 14 Oct 2024 14:36:21 +0200 Subject: [PATCH 04/13] new sftp: URLs, see #8372 sftp://user@host:port/rel/path sftp://user@host:port//abs/path --- src/borg/helpers/parseformat.py | 18 ++++++++++++++++-- src/borg/testsuite/helpers_test.py | 13 ++++++++++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/borg/helpers/parseformat.py b/src/borg/helpers/parseformat.py index d79629ca1..e21cdc207 100644 --- a/src/borg/helpers/parseformat.py +++ b/src/borg/helpers/parseformat.py @@ -431,6 +431,12 @@ class Location: (?P(/([^:]|(:(?!:)))+)) # start with /, then any chars, but no "::" """ + # path must not contain :: (it ends at :: or string end), but may contain single colons. + # it may or may not start with a /. + path_re = r""" + (?P(([^:]|(:(?!:)))+)) # any chars, but no "::" + """ + # host NAME, or host IP ADDRESS (v4 or v6, v6 must be in square brackets) host_re = r""" (?P( @@ -461,9 +467,9 @@ class Location: + optional_user_re + host_re + r""" # user@ (optional), host name or address - (?::(?P\d+))? # :port (optional) + (?::(?P\d+))?/ # :port (optional) + "/" as separator """ - + abs_path_re, + + path_re, re.VERBOSE, ) # path @@ -618,6 +624,14 @@ class Location: 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 "???", diff --git a/src/borg/testsuite/helpers_test.py b/src/borg/testsuite/helpers_test.py index 182b81926..bb6c839e2 100644 --- a/src/borg/testsuite/helpers_test.py +++ b/src/borg/testsuite/helpers_test.py @@ -197,11 +197,18 @@ class TestLocationWithoutEnv: def test_sftp(self, monkeypatch, keys_dir): monkeypatch.delenv("BORG_REPO", raising=False) + # relative path assert ( - repr(Location("sftp://user@host:1234/some/path")) - == "Location(proto='sftp', user='user', host='host', port=1234, path='/some/path')" + repr(Location("sftp://user@host:1234/rel/path")) + == "Location(proto='sftp', user='user', host='host', port=1234, path='rel/path')" ) - assert Location("sftp://user@host:1234/some/path").to_key_filename() == keys_dir + "host__some_path" + assert Location("sftp://user@host:1234/rel/path").to_key_filename() == keys_dir + "host__rel_path" + # absolute path + assert ( + repr(Location("sftp://user@host:1234//abs/path")) + == "Location(proto='sftp', user='user', host='host', port=1234, path='/abs/path')" + ) + assert Location("sftp://user@host:1234//abs/path").to_key_filename() == keys_dir + "host__abs_path" def test_socket(self, monkeypatch, keys_dir): monkeypatch.delenv("BORG_REPO", raising=False) From 72fc02814274519b39c1c069390b501ec045ff95 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 14 Oct 2024 14:56:18 +0200 Subject: [PATCH 05/13] to_key_filename: generate different filenames for rel/abs paths --- src/borg/helpers/parseformat.py | 2 +- src/borg/testsuite/helpers_test.py | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/borg/helpers/parseformat.py b/src/borg/helpers/parseformat.py index e21cdc207..33a73c214 100644 --- a/src/borg/helpers/parseformat.py +++ b/src/borg/helpers/parseformat.py @@ -593,7 +593,7 @@ class Location: return ", ".join(items) def to_key_filename(self): - name = re.sub(r"[^\w]", "_", self.path).strip("_") + name = re.sub(r"[^\w]", "_", self.path.rstrip("/")) if self.proto not in ("file", "socket", "rclone"): name = re.sub(r"[^\w]", "_", self.host) + "__" + name if len(name) > 100: diff --git a/src/borg/testsuite/helpers_test.py b/src/borg/testsuite/helpers_test.py index bb6c839e2..b6041d004 100644 --- a/src/borg/testsuite/helpers_test.py +++ b/src/borg/testsuite/helpers_test.py @@ -111,7 +111,7 @@ class TestLocationWithoutEnv: repr(Location("ssh://user@host:1234/some/path")) == "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path')" ) - assert Location("ssh://user@host:1234/some/path").to_key_filename() == keys_dir + "host__some_path" + assert Location("ssh://user@host:1234/some/path").to_key_filename() == keys_dir + "host___some_path" assert ( repr(Location("ssh://user@host:1234/some/path")) == "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path')" @@ -128,7 +128,7 @@ class TestLocationWithoutEnv: 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 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')" @@ -142,7 +142,7 @@ class TestLocationWithoutEnv: == "Location(proto='ssh', user='user', host='2001:db8::', port=1234, path='/some/path')" ) assert ( - Location("ssh://user@[2001:db8::]:1234/some/path").to_key_filename() == keys_dir + "2001_db8____some_path" + Location("ssh://user@[2001:db8::]:1234/some/path").to_key_filename() == keys_dir + "2001_db8_____some_path" ) assert ( repr(Location("ssh://user@[2001:db8::]/some/path")) @@ -174,7 +174,7 @@ class TestLocationWithoutEnv: ) 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" + == 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")) @@ -208,7 +208,7 @@ class TestLocationWithoutEnv: repr(Location("sftp://user@host:1234//abs/path")) == "Location(proto='sftp', user='user', host='host', port=1234, path='/abs/path')" ) - assert Location("sftp://user@host:1234//abs/path").to_key_filename() == keys_dir + "host__abs_path" + assert Location("sftp://user@host:1234//abs/path").to_key_filename() == keys_dir + "host___abs_path" def test_socket(self, monkeypatch, keys_dir): monkeypatch.delenv("BORG_REPO", raising=False) @@ -216,7 +216,7 @@ class TestLocationWithoutEnv: repr(Location("socket:///repo/path")) == "Location(proto='socket', user=None, host=None, port=None, path='/repo/path')" ) - assert Location("socket:///some/path").to_key_filename() == keys_dir + "some_path" + assert Location("socket:///some/path").to_key_filename() == keys_dir + "_some_path" def test_file(self, monkeypatch, keys_dir): monkeypatch.delenv("BORG_REPO", raising=False) @@ -228,7 +228,7 @@ class TestLocationWithoutEnv: repr(Location("file:///some/path")) == "Location(proto='file', user=None, host=None, port=None, path='/some/path')" ) - assert Location("file:///some/path").to_key_filename() == keys_dir + "some_path" + assert Location("file:///some/path").to_key_filename() == keys_dir + "_some_path" def test_smb(self, monkeypatch, keys_dir): monkeypatch.delenv("BORG_REPO", raising=False) @@ -236,7 +236,7 @@ class TestLocationWithoutEnv: repr(Location("file:////server/share/path")) == "Location(proto='file', user=None, host=None, port=None, path='//server/share/path')" ) - assert Location("file:////server/share/path").to_key_filename() == keys_dir + "server_share_path" + assert Location("file:////server/share/path").to_key_filename() == keys_dir + "__server_share_path" def test_folder(self, monkeypatch, keys_dir): monkeypatch.delenv("BORG_REPO", raising=False) @@ -257,12 +257,12 @@ class TestLocationWithoutEnv: repr(Location("/some/absolute/path")) == "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path')" ) - assert Location("/some/absolute/path").to_key_filename() == keys_dir + "some_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/some/path").to_key_filename() == keys_dir + "host___some_path" def test_relpath(self, monkeypatch, keys_dir): monkeypatch.delenv("BORG_REPO", raising=False) @@ -279,12 +279,12 @@ class TestLocationWithoutEnv: 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/./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/~/some/path").to_key_filename() == keys_dir + "host_____some_path" def test_with_colons(self, monkeypatch, keys_dir): monkeypatch.delenv("BORG_REPO", raising=False) @@ -300,7 +300,7 @@ class TestLocationWithoutEnv: repr(Location("/abs/path:with:colons")) == "Location(proto='file', user=None, host=None, port=None, path='/abs/path:with:colons')" ) - assert Location("/abs/path:with:colons").to_key_filename() == keys_dir + "abs_path_with_colons" + assert Location("/abs/path:with:colons").to_key_filename() == keys_dir + "_abs_path_with_colons" def test_canonical_path(self, monkeypatch): monkeypatch.delenv("BORG_REPO", raising=False) From 835b2657cd0cb634c8bcb310cf7954a009946e54 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 14 Oct 2024 19:51:44 +0200 Subject: [PATCH 06/13] new ssh: URLs, see #8372 ssh://user@host:port/rel/path ssh://user@host:port//abs/path remove the /./ and /~/ hacks. --- src/borg/conftest.py | 2 +- src/borg/helpers/parseformat.py | 56 +++------ src/borg/remote.py | 8 +- src/borg/testsuite/helpers_test.py | 128 ++++++++------------ src/borg/testsuite/legacyrepository_test.py | 2 +- src/borg/testsuite/repository_test.py | 2 +- 6 files changed, 74 insertions(+), 124 deletions(-) diff --git a/src/borg/conftest.py b/src/borg/conftest.py index affd317ce..4478ac025 100644 --- a/src/borg/conftest.py +++ b/src/borg/conftest.py @@ -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 diff --git a/src/borg/helpers/parseformat.py b/src/borg/helpers/parseformat.py index 33a73c214..373fc8c77 100644 --- a/src/borg/helpers/parseformat.py +++ b/src/borg/helpers/parseformat.py @@ -454,9 +454,9 @@ class Location: + optional_user_re + host_re + r""" # user@ (optional), host name or address - (?::(?P\d+))? # :port (optional) + (?::(?P\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! diff --git a/src/borg/remote.py b/src/borg/remote.py index f2e82d0ce..27ec9f68b 100644 --- a/src/borg/remote.py +++ b/src/borg/remote.py @@ -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, diff --git a/src/borg/testsuite/helpers_test.py b/src/borg/testsuite/helpers_test.py index b6041d004..46969a07e 100644 --- a/src/borg/testsuite/helpers_test.py +++ b/src/borg/testsuite/helpers_test.py @@ -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: diff --git a/src/borg/testsuite/legacyrepository_test.py b/src/borg/testsuite/legacyrepository_test.py index df21d7df8..eed96a60e 100644 --- a/src/borg/testsuite/legacyrepository_test.py +++ b/src/borg/testsuite/legacyrepository_test.py @@ -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) diff --git a/src/borg/testsuite/repository_test.py b/src/borg/testsuite/repository_test.py index 60e40dbf4..7a142b26f 100644 --- a/src/borg/testsuite/repository_test.py +++ b/src/borg/testsuite/repository_test.py @@ -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) From 858fbae9a91b64e42a19650b5063c774b121a0df Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 14 Oct 2024 23:16:24 +0200 Subject: [PATCH 07/13] Location: simplify regexes, parsing There were still some relicts from pre-borgstore / borg 1.x in there: - patterns about "::", used to be separator between repository and archive. - patterns for //server/share (not supported by borgstore) Also: unified ssh+sftp and file+socket processing. --- src/borg/helpers/parseformat.py | 132 +++++---------------- src/borg/testsuite/archiver/checks_test.py | 1 - src/borg/testsuite/helpers_test.py | 15 ++- 3 files changed, 40 insertions(+), 108 deletions(-) diff --git a/src/borg/helpers/parseformat.py b/src/borg/helpers/parseformat.py index 373fc8c77..c94052732 100644 --- a/src/borg/helpers/parseformat.py +++ b/src/borg/helpers/parseformat.py @@ -401,6 +401,7 @@ def parse_stringified_list(s): class Location: """Object representing a repository location""" + # user@ (optional) # user must not contain "@", ":" or "/". # Quoting adduser error message: # "To avoid problems, the username should consist only of letters, digits, @@ -408,34 +409,7 @@ class Location: # (as defined by IEEE Std 1003.1-2001)." # We use "@" as separator between username and hostname, so we must # disallow it within the pure username part. - optional_user_re = r""" - (?:(?P[^@:/]+)@)? - """ - - # path must not contain :: (it ends at :: or string end), but may contain single colons. - # to avoid ambiguities with other regexes, it must also not start with ":" nor with "//" nor with "ssh://". - local_path_re = r""" - (?!(:|//|ssh://|socket://)) # not starting with ":" or // or ssh:// or socket:// - (?P([^:]|(:(?!:)))+) # any chars, but no "::" - """ - - # file_path must not contain :: (it ends at :: or string end), but may contain single colons. - # it must start with a / and that slash is part of the path. - file_path_re = r""" - (?P(([^/]*)/([^:]|(:(?!:)))+)) # start opt. servername, then /, then any chars, but no "::" - """ - - # abs_path must not contain :: (it ends at :: or string end), but may contain single colons. - # it must start with a / and that slash is part of the path. - abs_path_re = r""" - (?P(/([^:]|(:(?!:)))+)) # start with /, then any chars, but no "::" - """ - - # path must not contain :: (it ends at :: or string end), but may contain single colons. - # it may or may not start with a /. - path_re = r""" - (?P(([^:]|(:(?!:)))+)) # any chars, but no "::" - """ + optional_user_re = r"(?:(?P[^@:/]+)@)?" # host NAME, or host IP ADDRESS (v4 or v6, v6 must be in square brackets) host_re = r""" @@ -446,70 +420,39 @@ class Location: ) """ + # :port (optional) + optional_port_re = r"(?::(?P\d+))?" + + # path may contain any chars. to avoid ambiguities with other regexes, + # it must not start with "//" nor with "scheme://" nor with "rclone:". + local_path_re = r""" + (?!(//|(ssh|socket|sftp|file)://|rclone:)) + (?P.+) + """ + + # abs_path must start with a slash. + abs_path_re = r"(?P/.+)" + + # path may or may not start with a slash. + abs_or_rel_path_re = r"(?P.+)" + # regexes for misc. kinds of supported location specifiers: - ssh_re = re.compile( - r""" - (?Pssh):// # ssh:// - """ + ssh_or_sftp_re = re.compile( + r"(?P(ssh|sftp))://" + optional_user_re + host_re - + r""" # user@ (optional), host name or address - (?::(?P\d+))?/ # :port (optional) + "/" as separator - """ - + path_re, - re.VERBOSE, - ) # path - - sftp_re = re.compile( - r""" - (?Psftp):// # sftp:// - """ - + optional_user_re - + host_re - + r""" # user@ (optional), host name or address - (?::(?P\d+))?/ # :port (optional) + "/" as separator - """ - + path_re, - re.VERBOSE, - ) # path - - rclone_re = re.compile( - r""" - (?Prclone): # rclone: - (?P(.*)) - """, - re.VERBOSE, - ) # path - - socket_re = re.compile( - r""" - (?Psocket):// # socket:// - """ - + abs_path_re, - re.VERBOSE, - ) # path - - file_re = re.compile( - r""" - (?Pfile):// # file:// - """ - + file_path_re, - re.VERBOSE, - ) # servername/path or path - - local_re = re.compile(local_path_re, re.VERBOSE) # local path - - win_file_re = re.compile( - r""" - (?:file://)? # optional file protocol - (?P - (?:[a-zA-Z]:)? # Drive letter followed by a colon (optional) - (?:[^:]+) # Anything which does not contain a :, at least one char - ) - """, + + optional_port_re + + r"/" # this is the separator, not part of the path! + + abs_or_rel_path_re, re.VERBOSE, ) + rclone_re = re.compile(r"(?Prclone):(?P(.*))", re.VERBOSE) + + file_or_socket_re = re.compile(r"(?P(file|socket))://" + abs_path_re, re.VERBOSE) + + local_re = re.compile(local_path_re, re.VERBOSE) + def __init__(self, text="", overrides={}, other=False): self.repo_env_var = "BORG_OTHER_REPO" if other else "BORG_REPO" self.valid = False @@ -538,15 +481,7 @@ class Location: raise ValueError('Invalid location format: "%s"' % self.processed) def _parse(self, text): - 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")) - return True - m = self.sftp_re.match(text) + m = self.ssh_or_sftp_re.match(text) if m: self.proto = m.group("proto") self.user = m.group("user") @@ -559,12 +494,7 @@ class Location: self.proto = m.group("proto") self.path = m.group("path") return True - m = self.file_re.match(text) - if m: - self.proto = m.group("proto") - self.path = os.path.normpath(m.group("path")) - return True - m = self.socket_re.match(text) + m = self.file_or_socket_re.match(text) if m: self.proto = m.group("proto") self.path = os.path.normpath(m.group("path")) diff --git a/src/borg/testsuite/archiver/checks_test.py b/src/borg/testsuite/archiver/checks_test.py index 761093a0e..eff167027 100644 --- a/src/borg/testsuite/archiver/checks_test.py +++ b/src/borg/testsuite/archiver/checks_test.py @@ -250,7 +250,6 @@ def test_unknown_feature_on_mount(archivers, request): mountpoint = os.path.join(archiver.tmpdir, "mountpoint") os.mkdir(mountpoint) # XXX this might hang if it doesn't raise an error - archiver.repository_location += "::test" cmd_raises_unknown_feature(archiver, ["mount", mountpoint]) diff --git a/src/borg/testsuite/helpers_test.py b/src/borg/testsuite/helpers_test.py index 46969a07e..d0ca464a7 100644 --- a/src/borg/testsuite/helpers_test.py +++ b/src/borg/testsuite/helpers_test.py @@ -278,14 +278,17 @@ class TestLocationWithoutEnv: def test_canonical_path(self, monkeypatch): monkeypatch.delenv("BORG_REPO", raising=False) locations = [ - "some/path", - "file://some/path", - "host:some/path", - "host:~user/some/path", - "socket:///some/path", + "relative/path", + "/absolute/path", + "file:///absolute/path", + "socket:///absolute/path", "ssh://host/relative/path", "ssh://host//absolute/path", - "ssh://user@host:1234/some/path", + "ssh://user@host:1234/relative/path", + "sftp://host/relative/path", + "sftp://host//absolute/path", + "sftp://user@host:1234/relative/path", + "rclone:remote:path", ] for location in locations: assert ( From 6adf18d97c9f3b82dd5aeba17d6a147b1c02cba4 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 14 Oct 2024 23:30:12 +0200 Subject: [PATCH 08/13] update URL docs --- docs/usage/general/repository-urls.rst.inc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/usage/general/repository-urls.rst.inc b/docs/usage/general/repository-urls.rst.inc index 3d7464d48..17f9560b9 100644 --- a/docs/usage/general/repository-urls.rst.inc +++ b/docs/usage/general/repository-urls.rst.inc @@ -14,21 +14,21 @@ Note: you may also prepend a ``file://`` to a filesystem path to get URL style. **Remote repositories** accessed via ssh user@host: -``ssh://user@host:port/path/to/repo`` - absolute path +``ssh://user@host:port//abs/path/to/repo`` - absolute path -``ssh://user@host:port/./path/to/repo`` - path relative to current directory - -``ssh://user@host:port/~/path/to/repo`` - path relative to user's home directory +``ssh://user@host:port/rel/path/to/repo`` - path relative to current directory **Remote repositories** accessed via sftp: -``sftp://user@host:port/path/to/repo`` - absolute path +``sftp://user@host:port//abs/path/to/repo`` - absolute path + +``sftp://user@host:port/rel/path/to/repo`` - path relative to current directory For ssh and sftp URLs, the ``user@`` and ``:port`` parts are optional. **Remote repositories** accessed via rclone: -``rclone://remote:path`` - see the rclone docs for more details. +``rclone:remote:path`` - see the rclone docs for more details about remote:path. If you frequently need the same repo URL, it is a good idea to set the @@ -36,7 +36,7 @@ If you frequently need the same repo URL, it is a good idea to set the :: - export BORG_REPO='ssh://user@host:port/path/to/repo' + export BORG_REPO='ssh://user@host:port/rel/path/to/repo' Then just leave away the ``--repo`` option if you want to use the default - it will be read from BORG_REPO then. From ffdc9581d99b2d456686481c59182857e5e5ac4d Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Tue, 15 Oct 2024 01:21:41 +0200 Subject: [PATCH 09/13] "file:" Location: use absolute path --- src/borg/helpers/parseformat.py | 2 +- src/borg/testsuite/helpers_test.py | 20 +++++++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/borg/helpers/parseformat.py b/src/borg/helpers/parseformat.py index c94052732..f995c8467 100644 --- a/src/borg/helpers/parseformat.py +++ b/src/borg/helpers/parseformat.py @@ -502,7 +502,7 @@ class Location: m = self.local_re.match(text) if m: self.proto = "file" - self.path = os.path.normpath(m.group("path")) + self.path = os.path.abspath(os.path.normpath(m.group("path"))) return True return False diff --git a/src/borg/testsuite/helpers_test.py b/src/borg/testsuite/helpers_test.py index d0ca464a7..102ead54b 100644 --- a/src/borg/testsuite/helpers_test.py +++ b/src/borg/testsuite/helpers_test.py @@ -226,12 +226,10 @@ class TestLocationWithoutEnv: def test_folder(self, monkeypatch, keys_dir): monkeypatch.delenv("BORG_REPO", raising=False) - assert repr(Location("path")) == "Location(proto='file', user=None, host=None, port=None, path='path')" - assert Location("path").to_key_filename() == keys_dir + "path" - - def test_long_path(self, monkeypatch, keys_dir): - monkeypatch.delenv("BORG_REPO", raising=False) - assert Location(os.path.join(*(40 * ["path"]))).to_key_filename() == keys_dir + "_".join(20 * ["path"]) + "_" + rel_path = "path" + abs_path = os.path.abspath(rel_path) + assert repr(Location(rel_path)) == f"Location(proto='file', user=None, host=None, port=None, path='{abs_path}')" + assert Location("path").to_key_filename().endswith(rel_path) def test_abspath(self, monkeypatch, keys_dir): monkeypatch.delenv("BORG_REPO", raising=False) @@ -248,11 +246,11 @@ class TestLocationWithoutEnv: def test_relpath(self, monkeypatch, keys_dir): monkeypatch.delenv("BORG_REPO", raising=False) - assert ( - 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" + # for a local path, borg creates a Location instance with an absolute path + rel_path = "relative/path" + abs_path = os.path.abspath(rel_path) + assert repr(Location(rel_path)) == f"Location(proto='file', user=None, host=None, port=None, path='{abs_path}')" + assert Location(rel_path).to_key_filename().endswith("relative_path") assert ( repr(Location("ssh://user@host/relative/path")) == "Location(proto='ssh', user='user', host='host', port=None, path='relative/path')" From 703c98dbc945132a68302730587bdb02d1ac343d Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Tue, 15 Oct 2024 14:30:11 +0200 Subject: [PATCH 10/13] create: catch StoreBackendAlreadyExists Don't show traceback if a repo at the given location already exists. --- src/borg/repository.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/borg/repository.py b/src/borg/repository.py index fb0540e3b..a107d477e 100644 --- a/src/borg/repository.py +++ b/src/borg/repository.py @@ -5,6 +5,7 @@ from borgstore.store import Store from borgstore.store import ObjectNotFound as StoreObjectNotFound from borgstore.backends.errors import BackendError as StoreBackendError from borgstore.backends.errors import BackendDoesNotExist as StoreBackendDoesNotExist +from borgstore.backends.errors import BackendAlreadyExists as StoreBackendAlreadyExists from .checksums import xxh64 from .constants import * # NOQA @@ -117,6 +118,7 @@ class Repository: url = "file://%s" % os.path.abspath(path_or_location) location = Location(url) self._location = location + self.url = url # lots of stuff in data: use 2 levels by default (data/00/00/ .. data/ff/ff/ dirs)! data_levels = int(os.environ.get("BORG_STORE_DATA_LEVELS", "2")) levels_config = { @@ -174,7 +176,10 @@ class Repository: def create(self): """Create a new empty repository""" - self.store.create() + try: + self.store.create() + except StoreBackendAlreadyExists: + raise self.AlreadyExists(self.url) self.store.open() try: self.store.store("config/readme", REPOSITORY_README.encode()) From e8dbcb3bfae1e0b0c33d9e0ce047925ef9d2dc23 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Tue, 15 Oct 2024 19:05:27 +0200 Subject: [PATCH 11/13] msys2: install rclone --- scripts/msys2-install-deps | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/msys2-install-deps b/scripts/msys2-install-deps index bf83d5510..c2ae1a414 100644 --- a/scripts/msys2-install-deps +++ b/scripts/msys2-install-deps @@ -1,9 +1,9 @@ -#!/bin/bash - -pacman -S --needed --noconfirm git mingw-w64-ucrt-x86_64-{toolchain,pkgconf,zstd,lz4,xxhash,openssl,python-msgpack,python-argon2_cffi,python-platformdirs,python,cython,python-setuptools,python-wheel,python-build,python-pkgconfig,python-packaging,python-pip,python-paramiko} -python -m pip install --upgrade pip -pip install pyinstaller==6.3.0 - -if [ "$1" = "development" ]; then - pacman -S --needed --noconfirm mingw-w64-ucrt-x86_64-python-{pytest,pytest-benchmark,pytest-cov,pytest-forked,pytest-xdist} -fi +#!/bin/bash + +pacman -S --needed --noconfirm git mingw-w64-ucrt-x86_64-{toolchain,pkgconf,zstd,lz4,xxhash,openssl,rclone,python-msgpack,python-argon2_cffi,python-platformdirs,python,cython,python-setuptools,python-wheel,python-build,python-pkgconfig,python-packaging,python-pip,python-paramiko} +python -m pip install --upgrade pip +pip install pyinstaller==6.3.0 + +if [ "$1" = "development" ]; then + pacman -S --needed --noconfirm mingw-w64-ucrt-x86_64-python-{pytest,pytest-benchmark,pytest-cov,pytest-forked,pytest-xdist} +fi From f6f8c678e8277c58541b9144bd4914ebf7e10f0a Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Tue, 15 Oct 2024 19:06:34 +0200 Subject: [PATCH 12/13] msys2: use pyinstaller 6.10.0 --- scripts/msys2-install-deps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/msys2-install-deps b/scripts/msys2-install-deps index c2ae1a414..9f43144c5 100644 --- a/scripts/msys2-install-deps +++ b/scripts/msys2-install-deps @@ -2,7 +2,7 @@ pacman -S --needed --noconfirm git mingw-w64-ucrt-x86_64-{toolchain,pkgconf,zstd,lz4,xxhash,openssl,rclone,python-msgpack,python-argon2_cffi,python-platformdirs,python,cython,python-setuptools,python-wheel,python-build,python-pkgconfig,python-packaging,python-pip,python-paramiko} python -m pip install --upgrade pip -pip install pyinstaller==6.3.0 +pip install pyinstaller==6.10.0 if [ "$1" = "development" ]; then pacman -S --needed --noconfirm mingw-w64-ucrt-x86_64-python-{pytest,pytest-benchmark,pytest-cov,pytest-forked,pytest-xdist} From 7be254efe5468cb905ac2757240775a0b46fc474 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Tue, 15 Oct 2024 20:36:17 +0200 Subject: [PATCH 13/13] github CI: temporarily disabled windows CI, #8474 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 96f1e07a0..b8d20ded4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -196,7 +196,7 @@ jobs: windows: - if: true # build enabled + if: false # build temporary disabled runs-on: windows-latest timeout-minutes: 120 needs: linux