Repository: borgbackup/borg

Issue:
check_free_space triggers TypeError in LoggedIO.cleanup
While testing PR #1820 this showed up when testing on tmpfs:
```
  ==> xenial64:     def test_recreate_dry_run(self):
  ==> xenial64:         self.create_regular_file('compressible', size=10000)
> ==> xenial64: >       self.cmd('init', self.repository_location)
  ==> xenial64: 
  ==> xenial64: .tox/py35/lib/python3.5/site-packages/borg/testsuite/archiver.py:1713: 
  ==> xenial64: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
  ==> xenial64: .tox/py35/lib/python3.5/site-packages/borg/testsuite/archiver.py:247: in cmd
  ==> xenial64:     ret, output = exec_cmd(*args, fork=fork, exe=self.EXE, archiver=self.archiver, **kw)
  ==> xenial64: .tox/py35/lib/python3.5/site-packages/borg/testsuite/archiver.py:78: in exec_cmd
  ==> xenial64:     ret = archiver.run(args)
  ==> xenial64: .tox/py35/lib/python3.5/site-packages/borg/archiver.py:2666: in run
  ==> xenial64:     return args.func(args)
  ==> xenial64: .tox/py35/lib/python3.5/site-packages/borg/archiver.py:101: in wrapper
  ==> xenial64:     return method(self, args, repository=repository, **kwargs)
  ==> xenial64: .tox/py35/lib/python3.5/site-packages/borg/archiver.py:193: in do_init
  ==> xenial64:     repository.commit()
  ==> xenial64: .tox/py35/lib/python3.5/site-packages/borg/repository.py:290: in commit
> ==> xenial64:     self.check_free_space()
  ==> xenial64: .tox/py35/lib/python3.5/site-packages/borg/repository.py:440: in check_free_space
  ==> xenial64:     self.rollback(cleanup=True)
  ==> xenial64: .tox/py35/lib/python3.5/site-packages/borg/repository.py:742: in rollback
> ==> xenial64:     self.io.cleanup(self.io.get_segments_transaction_id())
  ==> xenial64: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
  ==> xenial64: 
  ==> xenial64: self = <borg.repository.LoggedIO object at 0x7f0b30b736d8>
  ==> xenial64: transaction_id = None
  ==> xenial64: 
  ==> xenial64:     def cleanup(self, transaction_id):
  ==> xenial64:         """Delete segment files left by aborted transactions
  ==> xenial64:             """
> ==> xenial64: >       self.segment = transaction_id + 1
  ==> xenial64: E       TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
  ==> xenial64: 
  ==> xenial64: .tox/py35/lib/python3.5/site-packages/borg/repository.py:932: TypeError
```

Maybe it is not related to tmpfs, maybe this is just a filesystem where it ran (nearly?) out of space.


Discussion:
Probable cause: When repo is initialized, there is no segments transaction before the initial commit happens. When the c_f_s check fails, it tries to cleanup to `None` transaction ID. Fix should be simple.


Resolving patch:
diff --git a/src/borg/repository.py b/src/borg/repository.py
--- a/src/borg/repository.py
+++ b/src/borg/repository.py
@@ -120,6 +120,7 @@ def __init__(self, path, create=False, exclusive=False, lock_wait=None, lock=Tru
         self.lock_wait = lock_wait
         self.do_lock = lock
         self.do_create = create
+        self.created = False
         self.exclusive = exclusive
         self.append_only = append_only
         self.hostname_is_unique = yes(env_var_override='BORG_HOSTNAME_IS_UNIQUE', env_msg=None, prompt=False)
@@ -138,6 +139,7 @@ def __enter__(self):
         if self.do_create:
             self.do_create = False
             self.create(self.path)
+            self.created = True
         self.open(self.path, bool(self.exclusive), lock_wait=self.lock_wait, lock=self.do_lock)
         return self
 
@@ -437,7 +439,11 @@ def check_free_space(self):
         free_space = st_vfs.f_bavail * st_vfs.f_bsize
         logger.debug('check_free_space: required bytes {}, free bytes {}'.format(required_free_space, free_space))
         if free_space < required_free_space:
-            self._rollback(cleanup=True)
+            if self.created:
+                logger.error('Not enough free space to initialize repository at this location.')
+                self.destroy()
+            else:
+                self._rollback(cleanup=True)
             formatted_required = format_file_size(required_free_space)
             formatted_free = format_file_size(free_space)
             raise self.InsufficientFreeSpaceError(formatted_required, formatted_free)
diff --git a/src/borg/testsuite/repository.py b/src/borg/testsuite/repository.py
--- a/src/borg/testsuite/repository.py
+++ b/src/borg/testsuite/repository.py
@@ -406,6 +406,13 @@ def test_additional_free_space(self):
             self.repository.put(H(0), b'foobar')
             with pytest.raises(Repository.InsufficientFreeSpaceError):
                 self.repository.commit()
+        assert os.path.exists(self.repository.path)
+
+    def test_create_free_space(self):
+        self.repository.additional_free_space = 1e20
+        with pytest.raises(Repository.InsufficientFreeSpaceError):
+            self.add_keys()
+        assert not os.path.exists(self.repository.path)
 
 
 class NonceReservation(RepositoryTestCaseBase):


Test patch: