]> arthur.barton.de Git - bup.git/commitdiff
Replace LocalRepo/RemoteRepo __del__ with context management
authorRob Browning <rlb@defaultvalue.org>
Tue, 28 Sep 2021 00:58:06 +0000 (19:58 -0500)
committerRob Browning <rlb@defaultvalue.org>
Mon, 22 Nov 2021 16:35:28 +0000 (10:35 -0600)
Signed-off-by: Rob Browning <rlb@defaultvalue.org>
Tested-by: Rob Browning <rlb@defaultvalue.org>
13 files changed:
lib/bup/cmd/cat_file.py
lib/bup/cmd/ftp.py
lib/bup/cmd/fuse.py
lib/bup/cmd/join.py
lib/bup/cmd/prune_older.py
lib/bup/cmd/restore.py
lib/bup/cmd/rm.py
lib/bup/cmd/server.py
lib/bup/cmd/web.py
lib/bup/ls.py
lib/bup/repo.py
test/int/test_metadata.py
test/int/test_vfs.py

index 302d8e3d8200627bd49c0d613ed717caeb3727fb..e878e7ea8e57928ed3a8fdc15155dc6319decaf7 100755 (executable)
@@ -34,36 +34,36 @@ def main(argv):
     if not re.match(br'/*[^/]+/[^/]+', target):
         o.fatal("path %r doesn't include a branch and revision" % target)
 
-    repo = LocalRepo()
-    resolved = vfs.resolve(repo, target, follow=False)
-    leaf_name, leaf_item = resolved[-1]
-    if not leaf_item:
-        log('error: cannot access %r in %r\n'
-            % (b'/'.join(name for name, item in resolved), target))
-        sys.exit(1)
+    with LocalRepo() as repo:
+        resolved = vfs.resolve(repo, target, follow=False)
+        leaf_name, leaf_item = resolved[-1]
+        if not leaf_item:
+            log('error: cannot access %r in %r\n'
+                % (b'/'.join(name for name, item in resolved), target))
+            sys.exit(1)
 
-    mode = vfs.item_mode(leaf_item)
+        mode = vfs.item_mode(leaf_item)
 
-    sys.stdout.flush()
-    out = byte_stream(sys.stdout)
+        sys.stdout.flush()
+        out = byte_stream(sys.stdout)
 
-    if opt.bupm:
-        if not stat.S_ISDIR(mode):
-            o.fatal('%r is not a directory' % target)
-        _, bupm_oid = vfs.tree_data_and_bupm(repo, leaf_item.oid)
-        if bupm_oid:
-            with vfs.tree_data_reader(repo, bupm_oid) as meta_stream:
-                out.write(meta_stream.read())
-    elif opt.meta:
-        augmented = vfs.augment_item_meta(repo, leaf_item, include_size=True)
-        out.write(augmented.meta.encode())
-    else:
-        if stat.S_ISREG(mode):
-            with vfs.fopen(repo, leaf_item) as f:
-                for b in chunkyreader(f):
-                    out.write(b)
+        if opt.bupm:
+            if not stat.S_ISDIR(mode):
+                o.fatal('%r is not a directory' % target)
+            _, bupm_oid = vfs.tree_data_and_bupm(repo, leaf_item.oid)
+            if bupm_oid:
+                with vfs.tree_data_reader(repo, bupm_oid) as meta_stream:
+                    out.write(meta_stream.read())
+        elif opt.meta:
+            augmented = vfs.augment_item_meta(repo, leaf_item, include_size=True)
+            out.write(augmented.meta.encode())
         else:
-            o.fatal('%r is not a plain file' % target)
+            if stat.S_ISREG(mode):
+                with vfs.fopen(repo, leaf_item) as f:
+                    for b in chunkyreader(f):
+                        out.write(b)
+            else:
+                o.fatal('%r is not a plain file' % target)
 
     if saved_errors:
         log('warning: %d errors encountered\n' % len(saved_errors))
index 86392301016a798b4c4238ba92c1f78db9d2e173..9a33754c039bac2b3ef656698f00c082367e8414 100755 (executable)
@@ -9,7 +9,7 @@ import os, fnmatch, stat, sys, traceback
 
 from bup import _helpers, options, git, shquote, ls, vfs
 from bup.compat import argv_bytes
-from bup.helpers import chunkyreader, log
+from bup.helpers import chunkyreader, log, saved_errors
 from bup.io import byte_stream, path_msg
 from bup.repo import LocalRepo
 
@@ -94,43 +94,32 @@ optspec = """
 bup ftp [commands...]
 """
 
-def main(argv):
-    o = options.Options(optspec)
-    opt, flags, extra = o.parse_bytes(argv[1:])
 
-    git.check_repo_or_die()
+def inputiter(f, pwd, out):
+    if os.isatty(f.fileno()):
+        while 1:
+            prompt = b'bup %s> ' % (b'/'.join(name for name, item in pwd) or b'/', )
+            if hasattr(_helpers, 'readline'):
+                try:
+                    yield _helpers.readline(prompt)
+                except EOFError:
+                    print()  # Clear the line for the terminal's next prompt
+                    break
+            else:
+                out.write(prompt)
+                out.flush()
+                read_line = f.readline()
+                if not read_line:
+                    print('')
+                    break
+                yield read_line
+    else:
+        for line in f:
+            yield line
 
-    global repo
 
-    sys.stdout.flush()
-    out = byte_stream(sys.stdout)
-    stdin = byte_stream(sys.stdin)
-    repo = LocalRepo()
+def present_interface(stdin, out, extra, repo):
     pwd = vfs.resolve(repo, b'/')
-    rv = 0
-
-    def inputiter(f):
-        if os.isatty(f.fileno()):
-            while 1:
-                prompt = b'bup %s> ' % (b'/'.join(name for name, item in pwd) or b'/', )
-                if hasattr(_helpers, 'readline'):
-                    try:
-                        yield _helpers.readline(prompt)
-                    except EOFError:
-                        print()  # Clear the line for the terminal's next prompt
-                        break
-                else:
-                    out.write(prompt)
-                    out.flush()
-                    read_line = f.readline()
-                    if not read_line:
-                        print('')
-                        break
-                    yield read_line
-        else:
-            for line in f:
-                yield line
-
 
     if extra:
         lines = (argv_bytes(arg) for arg in extra)
@@ -143,7 +132,7 @@ def main(argv):
                 # MacOS uses a slightly incompatible clone of libreadline
                 _helpers.parse_and_bind(b'bind ^I rl_complete')
             _helpers.parse_and_bind(b'tab: complete')
-        lines = inputiter(stdin)
+        lines = inputiter(stdin, pwd, out)
 
     for line in lines:
         if not line.strip():
@@ -186,7 +175,6 @@ def main(argv):
                 out.flush()
             elif cmd == b'get':
                 if len(words) not in [2,3]:
-                    rv = 1
                     raise Exception('Usage: get <filename> [localname]')
                 rname = words[1]
                 (dir,base) = os.path.split(rname)
@@ -230,11 +218,23 @@ def main(argv):
             elif cmd in (b'quit', b'exit', b'bye'):
                 break
             else:
-                rv = 1
                 raise Exception('no such command %r' % cmd)
         except Exception as e:
-            rv = 1
             log('error: %s\n' % e)
             raise
 
-    sys.exit(rv)
+def main(argv):
+    global repo
+
+    o = options.Options(optspec)
+    opt, flags, extra = o.parse_bytes(argv[1:])
+
+    git.check_repo_or_die()
+    sys.stdout.flush()
+    out = byte_stream(sys.stdout)
+    stdin = byte_stream(sys.stdin)
+    with LocalRepo() as repo:
+        present_interface(stdin, out, extra, repo)
+    if saved_errors:
+        log('warning: %d errors encountered\n' % len(saved_errors))
+        sys.exit(1)
index 385bea9f3a8d0f2d8865db310afce464f97a8f22..27937123835c43cd2f8498d5d1d5e95a26f73773 100755 (executable)
@@ -147,17 +147,17 @@ def main(argv):
         o.fatal('only one mount point argument expected')
 
     git.check_repo_or_die()
-    repo = LocalRepo()
-    f = BupFs(repo=repo, verbose=opt.verbose, fake_metadata=(not opt.meta))
-
-    # This is likely wrong, but the fuse module doesn't currently accept bytes
-    f.fuse_args.mountpoint = extra[0]
-
-    if opt.debug:
-        f.fuse_args.add('debug')
-    if opt.foreground:
-        f.fuse_args.setmod('foreground')
-    f.multithreaded = False
-    if opt.allow_other:
-        f.fuse_args.add('allow_other')
-    f.main()
+    with LocalRepo() as repo:
+        f = BupFs(repo=repo, verbose=opt.verbose, fake_metadata=(not opt.meta))
+
+        # This is likely wrong, but the fuse module doesn't currently accept bytes
+        f.fuse_args.mountpoint = extra[0]
+
+        if opt.debug:
+            f.fuse_args.add('debug')
+        if opt.foreground:
+            f.fuse_args.setmod('foreground')
+        f.multithreaded = False
+        if opt.allow_other:
+            f.fuse_args.add('allow_other')
+        f.main()
index caf524a6463f3ae86445657e09894c12a632c876..bb66585aadc5676ac8951da8cf8a26293ad92bce 100755 (executable)
@@ -31,21 +31,21 @@ def main(argv):
         extra = linereader(stdin)
 
     ret = 0
-    repo = RemoteRepo(opt.remote) if opt.remote else LocalRepo()
-
-    if opt.o:
-        outfile = open(opt.o, 'wb')
-    else:
-        sys.stdout.flush()
-        outfile = byte_stream(sys.stdout)
-
-    for ref in [argv_bytes(x) for x in extra]:
-        try:
-            for blob in repo.join(ref):
-                outfile.write(blob)
-        except KeyError as e:
-            outfile.flush()
-            log('error: %s\n' % e)
-            ret = 1
+    with RemoteRepo(opt.remote) if opt.remote else LocalRepo() as repo:
+
+        if opt.o:
+            outfile = open(opt.o, 'wb')
+        else:
+            sys.stdout.flush()
+            outfile = byte_stream(sys.stdout)
+
+        for ref in [argv_bytes(x) for x in extra]:
+            try:
+                for blob in repo.join(ref):
+                    outfile.write(blob)
+            except KeyError as e:
+                outfile.flush()
+                log('error: %s\n' % e)
+                ret = 1
 
     sys.exit(ret)
index e0f8545f5eaad8e4b9b0a30e0d3169637fde09d5..d5a8f1cf767690969e66f7d6228b295d4b869734 100755 (executable)
@@ -153,8 +153,9 @@ def main(argv):
 
     if not opt.pretend:
         die_if_errors()
-        repo = LocalRepo()
-        bup_rm(repo, removals, compression=opt.compress, verbosity=opt.verbose)
+        with LocalRepo() as repo:
+            bup_rm(repo, removals, compression=opt.compress,
+                   verbosity=opt.verbose)
         if opt.gc:
             die_if_errors()
             bup_gc(threshold=opt.gc_threshold,
index ee403b6c20d199fdf9c023a1c9e81665c7349d0a..0dcee2649b3f240024460da9ee1f7b2a61db13c7 100755 (executable)
@@ -242,61 +242,61 @@ def main(argv):
         mkdirp(opt.outdir)
         os.chdir(opt.outdir)
 
-    repo = RemoteRepo(opt.remote) if opt.remote else LocalRepo()
-    top = fsencode(os.getcwd())
-    hardlinks = {}
-    for path in [argv_bytes(x) for x in extra]:
-        if not valid_restore_path(path):
-            add_error("path %r doesn't include a branch and revision" % path)
-            continue
-        try:
-            resolved = vfs.resolve(repo, path, want_meta=True, follow=False)
-        except vfs.IOError as e:
-            add_error(e)
-            continue
-        if len(resolved) == 3 and resolved[2][0] == b'latest':
-            # Follow latest symlink to the actual save
+    with RemoteRepo(opt.remote) if opt.remote else LocalRepo() as repo:
+        top = fsencode(os.getcwd())
+        hardlinks = {}
+        for path in [argv_bytes(x) for x in extra]:
+            if not valid_restore_path(path):
+                add_error("path %r doesn't include a branch and revision" % path)
+                continue
             try:
-                resolved = vfs.resolve(repo, b'latest', parent=resolved[:-1],
-                                       want_meta=True)
+                resolved = vfs.resolve(repo, path, want_meta=True, follow=False)
             except vfs.IOError as e:
                 add_error(e)
                 continue
-            # Rename it back to 'latest'
-            resolved = tuple(elt if i != 2 else (b'latest',) + elt[1:]
-                             for i, elt in enumerate(resolved))
-        path_parent, path_name = os.path.split(path)
-        leaf_name, leaf_item = resolved[-1]
-        if not leaf_item:
-            add_error('error: cannot access %r in %r'
-                      % (b'/'.join(name for name, item in resolved),
-                         path))
-            continue
-        if not path_name or path_name == b'.':
-            # Source is /foo/what/ever/ or /foo/what/ever/. -- extract
-            # what/ever/* to the current directory, and if name == '.'
-            # (i.e. /foo/what/ever/.), then also restore what/ever's
-            # metadata to the current directory.
-            treeish = vfs.item_mode(leaf_item)
-            if not treeish:
-                add_error('%r cannot be restored as a directory' % path)
+            if len(resolved) == 3 and resolved[2][0] == b'latest':
+                # Follow latest symlink to the actual save
+                try:
+                    resolved = vfs.resolve(repo, b'latest', parent=resolved[:-1],
+                                           want_meta=True)
+                except vfs.IOError as e:
+                    add_error(e)
+                    continue
+                # Rename it back to 'latest'
+                resolved = tuple(elt if i != 2 else (b'latest',) + elt[1:]
+                                 for i, elt in enumerate(resolved))
+            path_parent, path_name = os.path.split(path)
+            leaf_name, leaf_item = resolved[-1]
+            if not leaf_item:
+                add_error('error: cannot access %r in %r'
+                          % (b'/'.join(name for name, item in resolved),
+                             path))
+                continue
+            if not path_name or path_name == b'.':
+                # Source is /foo/what/ever/ or /foo/what/ever/. -- extract
+                # what/ever/* to the current directory, and if name == '.'
+                # (i.e. /foo/what/ever/.), then also restore what/ever's
+                # metadata to the current directory.
+                treeish = vfs.item_mode(leaf_item)
+                if not treeish:
+                    add_error('%r cannot be restored as a directory' % path)
+                else:
+                    items = vfs.contents(repo, leaf_item, want_meta=True)
+                    dot, leaf_item = next(items, None)
+                    assert dot == b'.'
+                    for sub_name, sub_item in items:
+                        restore(repo, b'', sub_name, sub_item, top,
+                                opt.sparse, opt.numeric_ids, owner_map,
+                                exclude_rxs, verbosity, hardlinks)
+                    if path_name == b'.':
+                        leaf_item = vfs.augment_item_meta(repo, leaf_item,
+                                                          include_size=True)
+                        apply_metadata(leaf_item.meta, b'.',
+                                       opt.numeric_ids, owner_map)
             else:
-                items = vfs.contents(repo, leaf_item, want_meta=True)
-                dot, leaf_item = next(items, None)
-                assert dot == b'.'
-                for sub_name, sub_item in items:
-                    restore(repo, b'', sub_name, sub_item, top,
-                            opt.sparse, opt.numeric_ids, owner_map,
-                            exclude_rxs, verbosity, hardlinks)
-                if path_name == b'.':
-                    leaf_item = vfs.augment_item_meta(repo, leaf_item,
-                                                      include_size=True)
-                    apply_metadata(leaf_item.meta, b'.',
-                                   opt.numeric_ids, owner_map)
-        else:
-            restore(repo, b'', leaf_name, leaf_item, top,
-                    opt.sparse, opt.numeric_ids, owner_map,
-                    exclude_rxs, verbosity, hardlinks)
+                restore(repo, b'', leaf_name, leaf_item, top,
+                        opt.sparse, opt.numeric_ids, owner_map,
+                        exclude_rxs, verbosity, hardlinks)
 
     if verbosity >= 0:
         progress('Restoring: %d, done.\n' % total_restored)
index 2867657f87fa6a41b74cf57bf0a76bdd683c523b..4c4a0f03e01f1bd6115f75b1a917e4a94f408e9f 100755 (executable)
@@ -27,7 +27,7 @@ def main(argv):
         o.fatal('no paths specified')
 
     check_repo_or_die()
-    repo = LocalRepo()
-    bup_rm(repo, [argv_bytes(x) for x in extra],
-           compression=opt.compress, verbosity=opt.verbose)
+    with LocalRepo() as repo:
+        bup_rm(repo, [argv_bytes(x) for x in extra],
+               compression=opt.compress, verbosity=opt.verbose)
     die_if_errors()
index 56b6d4450a9c63983c9e04343408c9feb49be138..5e2b6af680bf4ef0928cc40a44b899e41371c9b1 100755 (executable)
@@ -289,7 +289,7 @@ commands = {
 }
 
 def main(argv):
-    global suspended_w
+    global repo, suspended_w
 
     o = options.Options(optspec)
     opt, flags, extra = o.parse_bytes(argv[1:])
@@ -303,7 +303,8 @@ def main(argv):
     sys.stdout.flush()
     conn = Conn(byte_stream(sys.stdin), byte_stream(sys.stdout))
     lr = linereader(conn)
-    with finalized(None, lambda _: suspended_w and suspended_w.close()):
+    with finalized(None, lambda _: repo and repo.close()), \
+         finalized(None, lambda _: suspended_w and suspended_w.close()):
         for _line in lr:
             line = _line.strip()
             if not line:
index 20333cbbbb47f11b9685ef7186a029ade7c75ed8..c609d872e72456ff1ea38fbe8e32045f8432795f 100755 (executable)
@@ -290,30 +290,30 @@ def main(argv):
     except AttributeError:
         sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
 
-    application = tornado.web.Application([
-        (r"(?P<path>/.*)", BupRequestHandler, dict(repo=LocalRepo())),
-    ], **settings)
-
-    http_server = HTTPServer(application)
-    io_loop_pending = IOLoop.instance()
-
-    if isinstance(address, InetAddress):
-        sockets = tornado.netutil.bind_sockets(address.port, address.host)
-        http_server.add_sockets(sockets)
-        print('Serving HTTP on %s:%d...' % sockets[0].getsockname()[0:2])
-        if opt.browser:
-            browser_addr = 'http://' + address[0] + ':' + str(address[1])
-            io_loop_pending.add_callback(lambda : webbrowser.open(browser_addr))
-    elif isinstance(address, UnixAddress):
-        unix_socket = bind_unix_socket(address.path)
-        http_server.add_socket(unix_socket)
-        print('Serving HTTP on filesystem socket %r' % address.path)
-    else:
-        log('error: unexpected address %r', address)
-        sys.exit(1)
+    with LocalRepo() as repo:
+        handlers = [ (r"(?P<path>/.*)", BupRequestHandler, dict(repo=repo))]
+        application = tornado.web.Application(handlers, **settings)
+
+        http_server = HTTPServer(application)
+        io_loop_pending = IOLoop.instance()
+
+        if isinstance(address, InetAddress):
+            sockets = tornado.netutil.bind_sockets(address.port, address.host)
+            http_server.add_sockets(sockets)
+            print('Serving HTTP on %s:%d...' % sockets[0].getsockname()[0:2])
+            if opt.browser:
+                browser_addr = 'http://' + address[0] + ':' + str(address[1])
+                io_loop_pending.add_callback(lambda : webbrowser.open(browser_addr))
+        elif isinstance(address, UnixAddress):
+            unix_socket = bind_unix_socket(address.path)
+            http_server.add_socket(unix_socket)
+            print('Serving HTTP on filesystem socket %r' % address.path)
+        else:
+            log('error: unexpected address %r', address)
+            sys.exit(1)
 
-    io_loop = io_loop_pending
-    io_loop.start()
+        io_loop = io_loop_pending
+        io_loop.start()
 
     if saved_errors:
         log('WARNING: %d errors encountered while saving.\n' % len(saved_errors))
index 755c9981878385a2222b2223c8d53d9006ff2a83..c70fa4e76ed24b2e449d7897d714fe0be8487d5d 100644 (file)
@@ -193,5 +193,6 @@ def via_cmdline(args, out=None, onabort=None):
     """
     assert out
     opt = opts_from_cmdline(args, onabort=onabort)
-    repo = RemoteRepo(argv_bytes(opt.remote)) if opt.remote else LocalRepo()
-    return within_repo(repo, opt, out)
+    with RemoteRepo(argv_bytes(opt.remote)) if opt.remote \
+         else LocalRepo() as repo:
+        return within_repo(repo, opt, out)
index d52aa0bdc3734d32ab31ff00d3ba307759e6d2fb..64feeb70f509031d1292c4fe379ac4a47e97eefd 100644 (file)
@@ -6,6 +6,7 @@ from functools import partial
 from bup import client, git, vfs
 from bup.compat import pending_raise
 
+
 _next_repo_id = 0
 _repo_ids = {}
 
@@ -29,9 +30,6 @@ class LocalRepo:
     def close(self):
         pass
 
-    def __del__(self):
-        self.close()
-
     def __enter__(self):
         return self
 
@@ -100,9 +98,6 @@ class RemoteRepo:
             self.client.close()
             self.client = None
 
-    def __del__(self):
-        self.close()
-
     def __enter__(self):
         return self
 
index fd3a00c54650c97383513a204303b7a7994ff00d..b3ff3a5eff764303c24ca98f044a30f4f16586bd 100644 (file)
@@ -138,27 +138,27 @@ def test_metadata_method(tmpdir):
     ex(bup_path, b'-d', bup_dir, b'index', b'-v', data_path)
     ex(bup_path, b'-d', bup_dir, b'save', b'-tvvn', b'test', data_path)
     git.check_repo_or_die(bup_dir)
-    repo = LocalRepo()
-    resolved = vfs.resolve(repo,
-                           b'/test/latest' + resolve_parent(data_path),
-                           follow=False)
-    leaf_name, leaf_item = resolved[-1]
-    m = leaf_item.meta
-    WVPASS(m.mtime == test_time2)
-    WVPASS(leaf_name == b'foo')
-    contents = tuple(vfs.contents(repo, leaf_item))
-    WVPASS(len(contents) == 3)
-    WVPASSEQ(frozenset(name for name, item in contents),
-             frozenset((b'.', b'file', b'symlink')))
-    for name, item in contents:
-        if name == b'file':
-            m = item.meta
-            WVPASS(m.mtime == test_time1)
-        elif name == b'symlink':
-            m = item.meta
-            WVPASSEQ(m.symlink_target, b'file')
-            WVPASSEQ(m.size, 4)
-            WVPASSEQ(m.mtime, 0)
+    with  LocalRepo() as repo:
+        resolved = vfs.resolve(repo,
+                               b'/test/latest' + resolve_parent(data_path),
+                               follow=False)
+        leaf_name, leaf_item = resolved[-1]
+        m = leaf_item.meta
+        WVPASS(m.mtime == test_time2)
+        WVPASS(leaf_name == b'foo')
+        contents = tuple(vfs.contents(repo, leaf_item))
+        WVPASS(len(contents) == 3)
+        WVPASSEQ(frozenset(name for name, item in contents),
+                 frozenset((b'.', b'file', b'symlink')))
+        for name, item in contents:
+            if name == b'file':
+                m = item.meta
+                WVPASS(m.mtime == test_time1)
+            elif name == b'symlink':
+                m = item.meta
+                WVPASSEQ(m.symlink_target, b'file')
+                WVPASSEQ(m.size, 4)
+                WVPASSEQ(m.mtime, 0)
 
 
 def _first_err():
index f9b3f65ccd441d0f606860af365dcd61170b99b7..010b39ec994658dd0f16af9c3bbfb2d690cc35f8 100644 (file)
@@ -161,45 +161,45 @@ def test_misc(tmpdir):
     ex((bup_path, b'index', b'-v', data_path))
     ex((bup_path, b'save', b'-d', b'100000', b'-tvvn', b'test',
         b'--strip', data_path))
-    repo = LocalRepo()
-
-    ls_tree = exo((b'git', b'ls-tree', b'test', b'symlink')).out
-    mode, typ, oidx, name = ls_tree.strip().split(None, 3)
-    assert name == b'symlink'
-    link_item = vfs.Item(oid=unhexlify(oidx), meta=int(mode, 8))
-    wvpasseq(b'file', vfs.readlink(repo, link_item))
-
-    ls_tree = exo((b'git', b'ls-tree', b'test', b'file')).out
-    mode, typ, oidx, name = ls_tree.strip().split(None, 3)
-    assert name == b'file'
-    file_item = vfs.Item(oid=unhexlify(oidx), meta=int(mode, 8))
-    wvexcept(Exception, vfs.readlink, repo, file_item)
-
-    wvpasseq(4, vfs.item_size(repo, link_item))
-    wvpasseq(7, vfs.item_size(repo, file_item))
-    meta = metadata.from_path(fsencode(__file__))
-    meta.size = 42
-    fake_item = file_item._replace(meta=meta)
-    wvpasseq(42, vfs.item_size(repo, fake_item))
-
-    _, fakelink_item = vfs.resolve(repo, b'/test/latest', follow=False)[-1]
-    wvpasseq(17, vfs.item_size(repo, fakelink_item))
-
-    run_augment_item_meta_tests(repo,
-                                b'/test/latest/file', 7,
-                                b'/test/latest/symlink', b'file')
-
-    # FIXME: this caused StopIteration
-    #_, file_item = vfs.resolve(repo, '/file')[-1]
-    _, file_item = vfs.resolve(repo, b'/test/latest/file')[-1]
-    file_copy = vfs.copy_item(file_item)
-    wvpass(file_copy is not file_item)
-    wvpass(file_copy.meta is not file_item.meta)
-    wvpass(isinstance(file_copy, tuple))
-    wvpass(file_item.meta.user)
-    wvpass(file_copy.meta.user)
-    file_copy.meta.user = None
-    wvpass(file_item.meta.user)
+
+    with LocalRepo() as repo:
+        ls_tree = exo((b'git', b'ls-tree', b'test', b'symlink')).out
+        mode, typ, oidx, name = ls_tree.strip().split(None, 3)
+        assert name == b'symlink'
+        link_item = vfs.Item(oid=unhexlify(oidx), meta=int(mode, 8))
+        wvpasseq(b'file', vfs.readlink(repo, link_item))
+
+        ls_tree = exo((b'git', b'ls-tree', b'test', b'file')).out
+        mode, typ, oidx, name = ls_tree.strip().split(None, 3)
+        assert name == b'file'
+        file_item = vfs.Item(oid=unhexlify(oidx), meta=int(mode, 8))
+        wvexcept(Exception, vfs.readlink, repo, file_item)
+
+        wvpasseq(4, vfs.item_size(repo, link_item))
+        wvpasseq(7, vfs.item_size(repo, file_item))
+        meta = metadata.from_path(fsencode(__file__))
+        meta.size = 42
+        fake_item = file_item._replace(meta=meta)
+        wvpasseq(42, vfs.item_size(repo, fake_item))
+
+        _, fakelink_item = vfs.resolve(repo, b'/test/latest', follow=False)[-1]
+        wvpasseq(17, vfs.item_size(repo, fakelink_item))
+
+        run_augment_item_meta_tests(repo,
+                                    b'/test/latest/file', 7,
+                                    b'/test/latest/symlink', b'file')
+
+        # FIXME: this caused StopIteration
+        #_, file_item = vfs.resolve(repo, '/file')[-1]
+        _, file_item = vfs.resolve(repo, b'/test/latest/file')[-1]
+        file_copy = vfs.copy_item(file_item)
+        wvpass(file_copy is not file_item)
+        wvpass(file_copy.meta is not file_item.meta)
+        wvpass(isinstance(file_copy, tuple))
+        wvpass(file_item.meta.user)
+        wvpass(file_copy.meta.user)
+        file_copy.meta.user = None
+        wvpass(file_item.meta.user)
 
 def write_sized_random_content(parent_dir, size, seed):
     verbose = 0
@@ -261,38 +261,38 @@ def test_read_and_seek(tmpdir):
     environ[b'GIT_DIR'] = bup_dir
     environ[b'BUP_DIR'] = bup_dir
     git.repodir = bup_dir
-    repo = LocalRepo()
-    data_path = tmpdir + b'/src'
-    os.mkdir(data_path)
-    seed = randint(-(1 << 31), (1 << 31) - 1)
-    rand = Random()
-    rand.seed(seed)
-    print('test_read seed:', seed, file=sys.stderr)
-    max_size = 2 * 1024 * 1024
-    sizes = set((rand.randint(1, max_size) for _ in range(5)))
-    sizes.add(1)
-    sizes.add(max_size)
-    for size in sizes:
-        write_sized_random_content(data_path, size, seed)
-    ex((bup_path, b'init'))
-    ex((bup_path, b'index', b'-v', data_path))
-    ex((bup_path, b'save', b'-d', b'100000', b'-tvvn', b'test',
-        b'--strip', data_path))
-    read_sizes = set((rand.randint(1, max_size) for _ in range(10)))
-    sizes.add(1)
-    sizes.add(max_size)
-    print('test_read src sizes:', sizes, file=sys.stderr)
-    print('test_read read sizes:', read_sizes, file=sys.stderr)
-    for size in sizes:
-        res = resolve(repo, b'/test/latest/' + str(size).encode('ascii'))
-        _, item = res[-1]
-        wvpasseq(size, vfs.item_size(repo, res[-1][1]))
-        validate_vfs_streaming_read(repo, item,
-                                    b'%s/%d' % (data_path, size),
-                                    read_sizes)
-        validate_vfs_seeking_read(repo, item,
-                                  b'%s/%d' % (data_path, size),
-                                  read_sizes)
+    with LocalRepo() as repo:
+        data_path = tmpdir + b'/src'
+        os.mkdir(data_path)
+        seed = randint(-(1 << 31), (1 << 31) - 1)
+        rand = Random()
+        rand.seed(seed)
+        print('test_read seed:', seed, file=sys.stderr)
+        max_size = 2 * 1024 * 1024
+        sizes = set((rand.randint(1, max_size) for _ in range(5)))
+        sizes.add(1)
+        sizes.add(max_size)
+        for size in sizes:
+            write_sized_random_content(data_path, size, seed)
+        ex((bup_path, b'init'))
+        ex((bup_path, b'index', b'-v', data_path))
+        ex((bup_path, b'save', b'-d', b'100000', b'-tvvn', b'test',
+            b'--strip', data_path))
+        read_sizes = set((rand.randint(1, max_size) for _ in range(10)))
+        sizes.add(1)
+        sizes.add(max_size)
+        print('test_read src sizes:', sizes, file=sys.stderr)
+        print('test_read read sizes:', read_sizes, file=sys.stderr)
+        for size in sizes:
+            res = resolve(repo, b'/test/latest/' + str(size).encode('ascii'))
+            _, item = res[-1]
+            wvpasseq(size, vfs.item_size(repo, res[-1][1]))
+            validate_vfs_streaming_read(repo, item,
+                                        b'%s/%d' % (data_path, size),
+                                        read_sizes)
+            validate_vfs_seeking_read(repo, item,
+                                      b'%s/%d' % (data_path, size),
+                                      read_sizes)
 
 def test_contents_with_mismatched_bupm_git_ordering(tmpdir):
     bup_dir = tmpdir + b'/bup'
@@ -310,26 +310,26 @@ def test_contents_with_mismatched_bupm_git_ordering(tmpdir):
     save_name = strftime('%Y-%m-%d-%H%M%S', localtime(save_utc)).encode('ascii')
     ex((bup_path, b'save', b'-tvvn', b'test', b'-d', b'%d' % save_utc,
         b'--strip', data_path))
-    repo = LocalRepo()
-    tip_sref = exo((b'git', b'show-ref', b'refs/heads/test')).out
-    tip_oidx = tip_sref.strip().split()[0]
-    tip_tree_oidx = exo((b'git', b'log', b'--pretty=%T', b'-n1',
-                         tip_oidx)).out.strip()
-    tip_tree_oid = unhexlify(tip_tree_oidx)
-    tip_tree = tree_dict(repo, tip_tree_oid)
-
-    name, item = vfs.resolve(repo, b'/test/latest')[2]
-    wvpasseq(save_name, name)
-    expected = frozenset((x.name, vfs.Item(oid=x.oid, meta=x.meta))
-                         for x in (tip_tree[name]
-                                   for name in (b'.', b'foo', b'foo.')))
-    contents = tuple(vfs.contents(repo, item))
-    wvpasseq(expected, frozenset(contents))
-    # Spot check, in case tree_dict shares too much code with the vfs
-    name, item = next(((n, i) for n, i in contents if n == b'foo'))
-    wvpass(S_ISDIR(item.meta))
-    name, item = next(((n, i) for n, i in contents if n == b'foo.'))
-    wvpass(S_ISREG(item.meta.mode))
+    with LocalRepo() as repo:
+        tip_sref = exo((b'git', b'show-ref', b'refs/heads/test')).out
+        tip_oidx = tip_sref.strip().split()[0]
+        tip_tree_oidx = exo((b'git', b'log', b'--pretty=%T', b'-n1',
+                             tip_oidx)).out.strip()
+        tip_tree_oid = unhexlify(tip_tree_oidx)
+        tip_tree = tree_dict(repo, tip_tree_oid)
+
+        name, item = vfs.resolve(repo, b'/test/latest')[2]
+        wvpasseq(save_name, name)
+        expected = frozenset((x.name, vfs.Item(oid=x.oid, meta=x.meta))
+                             for x in (tip_tree[name]
+                                       for name in (b'.', b'foo', b'foo.')))
+        contents = tuple(vfs.contents(repo, item))
+        wvpasseq(expected, frozenset(contents))
+        # Spot check, in case tree_dict shares too much code with the vfs
+        name, item = next(((n, i) for n, i in contents if n == b'foo'))
+        wvpass(S_ISDIR(item.meta))
+        name, item = next(((n, i) for n, i in contents if n == b'foo.'))
+        wvpass(S_ISREG(item.meta.mode))
 
 def test_duplicate_save_dates(tmpdir):
     bup_dir = tmpdir + b'/bup'
@@ -348,25 +348,25 @@ def test_duplicate_save_dates(tmpdir):
     for i in range(11):
         ex((bup_path, b'save', b'-d', b'100000', b'-n', b'test',
             data_path))
-    repo = LocalRepo()
-    res = vfs.resolve(repo, b'/test')
-    wvpasseq(2, len(res))
-    name, revlist = res[-1]
-    wvpasseq(b'test', name)
-    wvpasseq((b'.',
-              b'1970-01-02-034640-00',
-              b'1970-01-02-034640-01',
-              b'1970-01-02-034640-02',
-              b'1970-01-02-034640-03',
-              b'1970-01-02-034640-04',
-              b'1970-01-02-034640-05',
-              b'1970-01-02-034640-06',
-              b'1970-01-02-034640-07',
-              b'1970-01-02-034640-08',
-              b'1970-01-02-034640-09',
-              b'1970-01-02-034640-10',
-              b'latest'),
-             tuple(sorted(x[0] for x in vfs.contents(repo, revlist))))
+    with LocalRepo() as repo:
+        res = vfs.resolve(repo, b'/test')
+        wvpasseq(2, len(res))
+        name, revlist = res[-1]
+        wvpasseq(b'test', name)
+        wvpasseq((b'.',
+                  b'1970-01-02-034640-00',
+                  b'1970-01-02-034640-01',
+                  b'1970-01-02-034640-02',
+                  b'1970-01-02-034640-03',
+                  b'1970-01-02-034640-04',
+                  b'1970-01-02-034640-05',
+                  b'1970-01-02-034640-06',
+                  b'1970-01-02-034640-07',
+                  b'1970-01-02-034640-08',
+                  b'1970-01-02-034640-09',
+                  b'1970-01-02-034640-10',
+                  b'latest'),
+                 tuple(sorted(x[0] for x in vfs.contents(repo, revlist))))
 
 def test_item_read_write():
     x = vfs.Root(meta=13)