Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,11 @@ development header files (sometimes in a separate `-dev` or `-devel` package).
- Additionally, your OS will need to have FUSE support installed
(e.g. a package `fuse` for fuse 2 or a package `fuse3` for fuse 3 support).
* Optionally, if you wish to use S3/B2 Backend:
- borgstore[rest,blake3,s3] ~= 0.6.1 (use `pip install borgbackup[s3]`)
- borgstore[rest,blake3,s3] ~= 0.6.4 (use `pip install borgbackup[s3]`)
* Optionally, if you wish to use SFTP Backend:
- borgstore[rest,blake3,sftp] ~= 0.6.1 (use `pip install borgbackup[sftp]`)
- borgstore[rest,blake3,sftp] ~= 0.6.4 (use `pip install borgbackup[sftp]`)
* Optionally, if you wish to use rclone Backend:
- borgstore[rest,blake3,rclone] ~= 0.6.1 (use `pip install borgbackup[rclone]`)
- borgstore[rest,blake3,rclone] ~= 0.6.4 (use `pip install borgbackup[rclone]`)
* Optionally, if you wish to use the TUI (``borg --cockpit``):
- textual >= 6.8.0 (use `pip install borgbackup[cockpit]`)

Expand Down
2 changes: 2 additions & 0 deletions docs/internals/frontends.rst
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,8 @@ Errors
Repository permission denied: {}
Repository.KeyRequired rc: 28 traceback: yes
Repository {} needs its key to access the store object {}, but no key was set.
Repository.LegacyRepository rc: 29 traceback: no
{} looks like a borg 1.x repository, use --from-borg1 to access it (e.g. with borg transfer).

MandatoryFeatureUnsupported rc: 25 traceback: no
Unsupported repository feature(s) {}. A newer version of Borg is required to access this repository.
Expand Down
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ license = "BSD-3-Clause"
license-files = ["LICENSE", "AUTHORS"]
dependencies = [
"borghash ~= 0.2.0",
"borgstore[rest,blake3] ~= 0.6.1",
"borgstore[rest,blake3] ~= 0.6.4",
"msgpack >=1.0.3, <=1.2.2",
"packaging",
"platformdirs >=3.0.0, <5.0.0; sys_platform == 'darwin'", # for macOS: breaking changes in 3.0.0.
Expand All @@ -57,9 +57,9 @@ mfusepy = ["mfusepy >= 3.1.0, <4.0.0"] # fuse 2+3, high-level
# a pypi release of borgbackup can't contain a dependency on github!
# mfusepym = ["mfusepy @ git+https://github.com/mxmlnkn/mfusepy.git@master"]
nofuse = []
s3 = ["borgstore[rest,blake3,s3] ~= 0.6.1"]
sftp = ["borgstore[rest,blake3,sftp] ~= 0.6.1"]
rclone = ["borgstore[rest,blake3,rclone] ~= 0.6.1"]
s3 = ["borgstore[rest,blake3,s3] ~= 0.6.4"]
sftp = ["borgstore[rest,blake3,sftp] ~= 0.6.4"]
rclone = ["borgstore[rest,blake3,rclone] ~= 0.6.4"]
cockpit = ["textual>=6.8.0"] # might also work with older versions, untested

[project.urls]
Expand Down
20 changes: 20 additions & 0 deletions src/borg/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,11 @@ class KeyRequired(ErrorWithTraceback):

exit_mcode = 28

class LegacyRepository(Error):
"""{} looks like a borg 1.x repository, use --from-borg1 to access it (e.g. with borg transfer)."""

exit_mcode = 29

# Whole packs kept in memory for reads; the least recently used is evicted first.
# Memory use is this count times the pack size.
PACK_READER_CACHE_SIZE = 3
Expand Down Expand Up @@ -1141,6 +1146,8 @@ def _load_config(self):
# only shows up here, when the first request fails with BackendDoesNotExist (#10365).
raise self.DoesNotExist(str(self._location)) from None
except StoreObjectNotFound:
if self._is_legacy_repository():
raise self.LegacyRepository(str(self._location)) from None
# the store exists, but has no repository config: a repository that lost its config, something
# that never was a borg 2 repository, or the leftover of an interrupted repo-create (see create()).
raise _ConfigMissing() from None
Expand All @@ -1159,6 +1166,19 @@ def _load_config(self):
# the crypto suite is recorded by both entries or by none, see save_config().
raise self.InvalidRepository(str(self._location))

def _is_legacy_repository(self):
"""Does the (opened) store look like a borg 1.x repository?

A borg 1.x repository has a config *file* where borg 2 has the config/ namespace, so the store
object config/config is not found. The backend (not the Store, which only knows names within
namespaces) can look at that file-or-namespace directly.
"""
try:
info = self.store.backend.info("config")
except (StoreBackendError, OSError): # e.g. PermissionDenied, connection errors
return False
return info.exists and not info.directory

def looks_like_borg_store(self):
"""Does the (opened, config-less) store have the packs, archives, index and config namespaces?

Expand Down
13 changes: 13 additions & 0 deletions src/borg/testsuite/repository_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,19 @@ def failing_rest_backend(location):
assert "unrecognized arguments: --rest" in str(exc_info.value)


@pytest.mark.parametrize("proto", ["file", "ssh"])
def test_open_legacy_repository(tmp_path, proto):
# A borg 1.x repository (config is a file, not the config/ namespace) raises Repository.LegacyRepository.
path = tmp_path / "v1repo"
(path / "data").mkdir(parents=True)
(path / "config").write_text("[repository]\nversion = 1\nsegments_per_dir = 1000\nid = 00\n")
(path / "README").write_text("This is a Borg Backup repository.\n")
location = Location(os.fspath(path) if proto == "file" else f"ssh://__testsuite__/{os.fspath(path)}")
with pytest.raises(Repository.LegacyRepository):
with Repository(location, exclusive=True):
pass


@pytest.fixture()
def repository(tmp_path):
repository_location = os.fspath(tmp_path / "repository")
Expand Down
Loading