mirror of https://github.com/borgbackup/borg.git
Merge pull request #4316 from ThomasWaldmann/init-make-parentdirs
borg init --make-parent-dirs parent1/parent2/repo_dir, fixes #4235
This commit is contained in:
commit
9a3fbdd751
|
@ -124,15 +124,17 @@ def with_repository(fake=False, invert_fake=False, create=False, lock=True,
|
||||||
location = args.location # note: 'location' must be always present in args
|
location = args.location # note: 'location' must be always present in args
|
||||||
append_only = getattr(args, 'append_only', False)
|
append_only = getattr(args, 'append_only', False)
|
||||||
storage_quota = getattr(args, 'storage_quota', None)
|
storage_quota = getattr(args, 'storage_quota', None)
|
||||||
|
make_parent_dirs = getattr(args, 'make_parent_dirs', False)
|
||||||
if argument(args, fake) ^ invert_fake:
|
if argument(args, fake) ^ invert_fake:
|
||||||
return method(self, args, repository=None, **kwargs)
|
return method(self, args, repository=None, **kwargs)
|
||||||
elif location.proto == 'ssh':
|
elif location.proto == 'ssh':
|
||||||
repository = RemoteRepository(location, create=create, exclusive=argument(args, exclusive),
|
repository = RemoteRepository(location, create=create, exclusive=argument(args, exclusive),
|
||||||
lock_wait=self.lock_wait, lock=lock, append_only=append_only, args=args)
|
lock_wait=self.lock_wait, lock=lock, append_only=append_only,
|
||||||
|
make_parent_dirs=make_parent_dirs, args=args)
|
||||||
else:
|
else:
|
||||||
repository = Repository(location.path, create=create, exclusive=argument(args, exclusive),
|
repository = Repository(location.path, create=create, exclusive=argument(args, exclusive),
|
||||||
lock_wait=self.lock_wait, lock=lock, append_only=append_only,
|
lock_wait=self.lock_wait, lock=lock, append_only=append_only,
|
||||||
storage_quota=storage_quota)
|
storage_quota=storage_quota, make_parent_dirs=make_parent_dirs)
|
||||||
with repository:
|
with repository:
|
||||||
if manifest or cache:
|
if manifest or cache:
|
||||||
kwargs['manifest'], kwargs['key'] = Manifest.load(repository, compatibility)
|
kwargs['manifest'], kwargs['key'] = Manifest.load(repository, compatibility)
|
||||||
|
@ -2773,6 +2775,8 @@ class Archiver:
|
||||||
subparser.add_argument('--storage-quota', metavar='QUOTA', dest='storage_quota', default=None,
|
subparser.add_argument('--storage-quota', metavar='QUOTA', dest='storage_quota', default=None,
|
||||||
type=parse_storage_quota,
|
type=parse_storage_quota,
|
||||||
help='Set storage quota of the new repository (e.g. 5G, 1.5T). Default: no quota.')
|
help='Set storage quota of the new repository (e.g. 5G, 1.5T). Default: no quota.')
|
||||||
|
subparser.add_argument('--make-parent-dirs', dest='make_parent_dirs', action='store_true',
|
||||||
|
help='create the parent directories of the repository directory, if they are missing.')
|
||||||
|
|
||||||
check_epilog = process_epilog("""
|
check_epilog = process_epilog("""
|
||||||
The check command verifies the consistency of a repository and the corresponding archives.
|
The check command verifies the consistency of a repository and the corresponding archives.
|
||||||
|
|
|
@ -333,7 +333,8 @@ class RepositoryServer: # pragma: no cover
|
||||||
path = path[3:]
|
path = path[3:]
|
||||||
return os.path.realpath(path)
|
return os.path.realpath(path)
|
||||||
|
|
||||||
def open(self, path, create=False, lock_wait=None, lock=True, exclusive=None, append_only=False):
|
def open(self, path, create=False, lock_wait=None, lock=True, exclusive=None, append_only=False,
|
||||||
|
make_parent_dirs=False):
|
||||||
logging.debug('Resolving repository path %r', path)
|
logging.debug('Resolving repository path %r', path)
|
||||||
path = self._resolve_path(path)
|
path = self._resolve_path(path)
|
||||||
logging.debug('Resolved repository path to %r', path)
|
logging.debug('Resolved repository path to %r', path)
|
||||||
|
@ -362,7 +363,8 @@ class RepositoryServer: # pragma: no cover
|
||||||
self.repository = Repository(path, create, lock_wait=lock_wait, lock=lock,
|
self.repository = Repository(path, create, lock_wait=lock_wait, lock=lock,
|
||||||
append_only=append_only,
|
append_only=append_only,
|
||||||
storage_quota=self.storage_quota,
|
storage_quota=self.storage_quota,
|
||||||
exclusive=exclusive)
|
exclusive=exclusive,
|
||||||
|
make_parent_dirs=make_parent_dirs)
|
||||||
self.repository.__enter__() # clean exit handled by serve() method
|
self.repository.__enter__() # clean exit handled by serve() method
|
||||||
return self.repository.id
|
return self.repository.id
|
||||||
|
|
||||||
|
@ -523,7 +525,8 @@ class RemoteRepository:
|
||||||
# If compatibility with 1.0.x is not longer needed, replace all checks of this with True and simplify the code
|
# If compatibility with 1.0.x is not longer needed, replace all checks of this with True and simplify the code
|
||||||
dictFormat = False # outside of __init__ for testing of legacy free protocol
|
dictFormat = False # outside of __init__ for testing of legacy free protocol
|
||||||
|
|
||||||
def __init__(self, location, create=False, exclusive=False, lock_wait=None, lock=True, append_only=False, args=None):
|
def __init__(self, location, create=False, exclusive=False, lock_wait=None, lock=True, append_only=False,
|
||||||
|
make_parent_dirs=False, args=None):
|
||||||
self.location = self._location = location
|
self.location = self._location = location
|
||||||
self.preload_ids = []
|
self.preload_ids = []
|
||||||
self.msgid = 0
|
self.msgid = 0
|
||||||
|
@ -576,7 +579,8 @@ class RemoteRepository:
|
||||||
|
|
||||||
def do_open():
|
def do_open():
|
||||||
self.id = self.open(path=self.location.path, create=create, lock_wait=lock_wait,
|
self.id = self.open(path=self.location.path, create=create, lock_wait=lock_wait,
|
||||||
lock=lock, exclusive=exclusive, append_only=append_only)
|
lock=lock, exclusive=exclusive, append_only=append_only,
|
||||||
|
make_parent_dirs=make_parent_dirs)
|
||||||
|
|
||||||
if self.dictFormat:
|
if self.dictFormat:
|
||||||
do_open()
|
do_open()
|
||||||
|
@ -739,6 +743,8 @@ This problem will go away as soon as the server has been upgraded to 1.0.7+.
|
||||||
raise PathNotAllowed('(unknown)')
|
raise PathNotAllowed('(unknown)')
|
||||||
else:
|
else:
|
||||||
raise PathNotAllowed(args[0].decode())
|
raise PathNotAllowed(args[0].decode())
|
||||||
|
elif error == 'ParentPathDoesNotExist':
|
||||||
|
raise Repository.ParentPathDoesNotExist(args[0].decode())
|
||||||
elif error == 'ObjectNotFound':
|
elif error == 'ObjectNotFound':
|
||||||
if old_server:
|
if old_server:
|
||||||
raise Repository.ObjectNotFound('(not available)', self.location.orig)
|
raise Repository.ObjectNotFound('(not available)', self.location.orig)
|
||||||
|
@ -884,8 +890,10 @@ This problem will go away as soon as the server has been upgraded to 1.0.7+.
|
||||||
self.ignore_responses |= set(waiting_for) # we lose order here
|
self.ignore_responses |= set(waiting_for) # we lose order here
|
||||||
|
|
||||||
@api(since=parse_version('1.0.0'),
|
@api(since=parse_version('1.0.0'),
|
||||||
append_only={'since': parse_version('1.0.7'), 'previously': False})
|
append_only={'since': parse_version('1.0.7'), 'previously': False},
|
||||||
def open(self, path, create=False, lock_wait=None, lock=True, exclusive=False, append_only=False):
|
make_parent_dirs={'since': parse_version('1.1.9'), 'previously': False})
|
||||||
|
def open(self, path, create=False, lock_wait=None, lock=True, exclusive=False, append_only=False,
|
||||||
|
make_parent_dirs=False):
|
||||||
"""actual remoting is done via self.call in the @api decorator"""
|
"""actual remoting is done via self.call in the @api decorator"""
|
||||||
|
|
||||||
@api(since=parse_version('1.0.0'))
|
@api(since=parse_version('1.0.0'))
|
||||||
|
|
|
@ -120,6 +120,9 @@ class Repository:
|
||||||
class PathAlreadyExists(Error):
|
class PathAlreadyExists(Error):
|
||||||
"""There is already something at {}."""
|
"""There is already something at {}."""
|
||||||
|
|
||||||
|
class ParentPathDoesNotExist(Error):
|
||||||
|
"""The parent path of the repo directory [{}] does not exist."""
|
||||||
|
|
||||||
class InvalidRepository(Error):
|
class InvalidRepository(Error):
|
||||||
"""{} is not a valid repository. Check repo config."""
|
"""{} is not a valid repository. Check repo config."""
|
||||||
|
|
||||||
|
@ -147,7 +150,8 @@ class Repository:
|
||||||
"""The storage quota ({}) has been exceeded ({}). Try deleting some archives."""
|
"""The storage quota ({}) has been exceeded ({}). Try deleting some archives."""
|
||||||
|
|
||||||
def __init__(self, path, create=False, exclusive=False, lock_wait=None, lock=True,
|
def __init__(self, path, create=False, exclusive=False, lock_wait=None, lock=True,
|
||||||
append_only=False, storage_quota=None, check_segment_magic=True):
|
append_only=False, storage_quota=None, check_segment_magic=True,
|
||||||
|
make_parent_dirs=False):
|
||||||
self.path = os.path.abspath(path)
|
self.path = os.path.abspath(path)
|
||||||
self._location = Location('file://%s' % self.path)
|
self._location = Location('file://%s' % self.path)
|
||||||
self.io = None # type: LoggedIO
|
self.io = None # type: LoggedIO
|
||||||
|
@ -168,6 +172,7 @@ class Repository:
|
||||||
self.storage_quota_use = 0
|
self.storage_quota_use = 0
|
||||||
self.transaction_doomed = None
|
self.transaction_doomed = None
|
||||||
self.check_segment_magic = check_segment_magic
|
self.check_segment_magic = check_segment_magic
|
||||||
|
self.make_parent_dirs = make_parent_dirs
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
if self.lock:
|
if self.lock:
|
||||||
|
@ -250,8 +255,14 @@ class Repository:
|
||||||
"""Create a new empty repository at `path`
|
"""Create a new empty repository at `path`
|
||||||
"""
|
"""
|
||||||
self.check_can_create_repository(path)
|
self.check_can_create_repository(path)
|
||||||
|
if self.make_parent_dirs:
|
||||||
|
parent_path = os.path.join(path, os.pardir)
|
||||||
|
os.makedirs(parent_path, exist_ok=True)
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
|
try:
|
||||||
os.mkdir(path)
|
os.mkdir(path)
|
||||||
|
except FileNotFoundError as err:
|
||||||
|
raise self.ParentPathDoesNotExist(path) from err
|
||||||
with open(os.path.join(path, 'README'), 'w') as fd:
|
with open(os.path.join(path, 'README'), 'w') as fd:
|
||||||
fd.write(REPOSITORY_README)
|
fd.write(REPOSITORY_README)
|
||||||
os.mkdir(os.path.join(path, 'data'))
|
os.mkdir(os.path.join(path, 'data'))
|
||||||
|
|
|
@ -438,6 +438,17 @@ class ArchiverTestCase(ArchiverTestCaseBase):
|
||||||
# the interesting parts of info_output2 and info_output should be same
|
# the interesting parts of info_output2 and info_output should be same
|
||||||
self.assert_equal(filter(info_output), filter(info_output2))
|
self.assert_equal(filter(info_output), filter(info_output2))
|
||||||
|
|
||||||
|
def test_init_parent_dirs(self):
|
||||||
|
parent_path = os.path.join(self.tmpdir, 'parent1', 'parent2')
|
||||||
|
repository_path = os.path.join(parent_path, 'repository')
|
||||||
|
repository_location = self.prefix + repository_path
|
||||||
|
with pytest.raises(Repository.ParentPathDoesNotExist):
|
||||||
|
# normal borg init does NOT create missing parent dirs
|
||||||
|
self.cmd('init', '--encryption=none', repository_location)
|
||||||
|
# but if told so, it does:
|
||||||
|
self.cmd('init', '--encryption=none', '--make-parent-dirs', repository_location)
|
||||||
|
assert os.path.exists(parent_path)
|
||||||
|
|
||||||
def test_unix_socket(self):
|
def test_unix_socket(self):
|
||||||
self.cmd('init', '--encryption=repokey', self.repository_location)
|
self.cmd('init', '--encryption=repokey', self.repository_location)
|
||||||
try:
|
try:
|
||||||
|
@ -2893,6 +2904,10 @@ class ArchiverTestCaseBinary(ArchiverTestCase):
|
||||||
EXE = 'borg.exe'
|
EXE = 'borg.exe'
|
||||||
FORK_DEFAULT = True
|
FORK_DEFAULT = True
|
||||||
|
|
||||||
|
@unittest.skip('does not raise Exception, but sets rc==2')
|
||||||
|
def test_init_parent_dirs(self):
|
||||||
|
pass
|
||||||
|
|
||||||
@unittest.skip('patches objects')
|
@unittest.skip('patches objects')
|
||||||
def test_init_interrupt(self):
|
def test_init_interrupt(self):
|
||||||
pass
|
pass
|
||||||
|
|
Loading…
Reference in New Issue