]> arthur.barton.de Git - bup.git/commitdiff
vfs: change /save/latest back to a symlink to the latest save
authorRob Browning <rlb@defaultvalue.org>
Sun, 9 Dec 2018 18:40:26 +0000 (12:40 -0600)
committerRob Browning <rlb@defaultvalue.org>
Sat, 12 Jan 2019 17:30:59 +0000 (11:30 -0600)
The current, reworked vfs presents /somesave/latest as if it actually
is the latest commit.  Change it back to a symlink to the latest save
to roughly match the previous behavior -- though now it's a link to
the save name, not to the (removed) /.commit/ subtree.

To restore the link, reintroduce the concept of a fake
symlink (i.e. one that has no corresponding blob in the repository).

Signed-off-by: Rob Browning <rlb@defaultvalue.org>
Tested-by: Rob Browning <rlb@defaultvalue.org>
Documentation/bup-restore.md
cmd/restore-cmd.py
lib/bup/t/tvfs.py
lib/bup/vfs.py
t/test-ftp
t/test-ls

index 92643d9cd90a8d0893ea05dfccc7db878130026c..a7c246eacb08491a1a0b0a35df28363d345c125a 100644 (file)
@@ -48,6 +48,11 @@ path/to/dir/.), `bup restore` will do exactly what it would have done
 for path/to/dir, and then restore _dir_'s metadata to the current
 directory (or the `--outdir`).  See the EXAMPLES section.
 
+As a special case, if _some/where_ names the "latest" symlink,
+e.g. `bup restore /foo/latest`, then bup will act exactly as if the
+save that "latest" points to had been specified, and restore that,
+rather than the "latest" symlink itself.
+
 Whenever path metadata is available, `bup restore` will attempt to
 restore it.  When restoring ownership, bup implements tar/rsync-like
 semantics.  It will normally prefer user and group names to uids and
index bb37811b447518a7a1edb09b36fc32d28c4bca8a..6544a24b908e966fd4796a784958957fa13ff79f 100755 (executable)
@@ -250,6 +250,17 @@ def main():
         except vfs.IOError as e:
             add_error(e)
             continue
+        if len(resolved) == 3 and resolved[2][0] == 'latest':
+            # Follow latest symlink to the actual save
+            try:
+                resolved = vfs.resolve(repo, '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 ('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:
index 34a6fa24f58bb0103572aea4c7cafb9ee7b043e0..7cfe2f0330ae124ccaba3f01349b4c864cfbe8a7 100644 (file)
@@ -5,7 +5,7 @@ from errno import ELOOP, ENOTDIR
 from io import BytesIO
 from os import environ, symlink
 from random import Random, randint
-from stat import S_IFDIR, S_IFREG, S_ISDIR, S_ISREG
+from stat import S_IFDIR, S_IFLNK, S_IFREG, S_ISDIR, S_ISREG
 from sys import stderr
 from time import localtime, strftime
 
@@ -28,6 +28,12 @@ def ex(cmd, **kwargs):
     print(shstr(cmd), file=stderr)
     return exc(cmd, **kwargs)
 
+@wvtest
+def test_default_modes():
+    wvpasseq(S_IFREG | 0o644, vfs.default_file_mode)
+    wvpasseq(S_IFDIR | 0o755, vfs.default_dir_mode)
+    wvpasseq(S_IFLNK | 0o755, vfs.default_symlink_mode)
+
 @wvtest
 def test_cache_behavior():
     orig_max = vfs._cache_max_items
@@ -284,6 +290,8 @@ def test_resolve():
             expected_latest_item_w_meta = vfs.Commit(meta=tip_tree['.'].meta,
                                                      oid=tip_tree_oid,
                                                      coid=tip_oid)
+            expected_latest_link = vfs.FakeLink(meta=vfs.default_symlink_mode,
+                                                target=save_time_str)
             expected_test_tag_item = expected_latest_item
 
             wvstart('resolve: /')
@@ -335,7 +343,7 @@ def test_resolve():
             # latest has metadata here due to caching
             wvpasseq(frozenset([('.', test_revlist_w_meta),
                                 (save_time_str, expected_latest_item_w_meta),
-                                ('latest', expected_latest_item_w_meta)]),
+                                ('latest', expected_latest_link)]),
                      test_content)
 
             wvstart('resolve: /test/latest')
@@ -347,7 +355,7 @@ def test_resolve():
                                                      coid=tip_oid)
             expected = (('', vfs._root),
                         ('test', test_revlist_w_meta),
-                        ('latest', expected_latest_item_w_meta))
+                        (save_time_str, expected_latest_item_w_meta))
             wvpasseq(expected, res)
             ignore, latest_item = res[2]
             latest_content = frozenset(vfs.contents(repo, latest_item))
@@ -369,7 +377,7 @@ def test_resolve():
                                                  oid=tip_tree['file'].oid)
             expected = (('', vfs._root),
                         ('test', test_revlist_w_meta),
-                        ('latest', expected_latest_item_w_meta),
+                        (save_time_str, expected_latest_item_w_meta),
                         ('file', expected_file_item_w_meta))
             wvpasseq(expected, res)
 
@@ -379,7 +387,7 @@ def test_resolve():
             wvpasseq(4, len(res))
             expected = (('', vfs._root),
                         ('test', test_revlist_w_meta),
-                        ('latest', expected_latest_item_w_meta),
+                        (save_time_str, expected_latest_item_w_meta),
                         ('not-there', None))
             wvpasseq(expected, res)
 
@@ -392,7 +400,7 @@ def test_resolve():
                                                         oid=bad_symlink_value.oid)
             expected = (('', vfs._root),
                         ('test', test_revlist_w_meta),
-                        ('latest', expected_latest_item_w_meta),
+                        (save_time_str, expected_latest_item_w_meta),
                         ('bad-symlink', expected_bad_symlink_item_w_meta))
             wvpasseq(expected, res)
 
@@ -402,7 +410,7 @@ def test_resolve():
             wvpasseq(4, len(res))
             expected = (('', vfs._root),
                         ('test', test_revlist_w_meta),
-                        ('latest', expected_latest_item_w_meta),
+                        (save_time_str, expected_latest_item_w_meta),
                         ('file', expected_file_item_w_meta))
             wvpasseq(expected, res)
 
@@ -415,7 +423,7 @@ def test_resolve():
                                                          oid=file_symlink_value.oid)
             expected = (('', vfs._root),
                         ('test', test_revlist_w_meta),
-                        ('latest', expected_latest_item_w_meta),
+                        (save_time_str, expected_latest_item_w_meta),
                         ('file-symlink', expected_file_symlink_item_w_meta))
             wvpasseq(expected, res)
 
@@ -440,7 +448,7 @@ def test_resolve():
                     resolve(repo, path)
                 except vfs.IOError as res_ex:
                     wvpasseq(ENOTDIR, res_ex.errno)
-                    wvpasseq(['', 'test', 'latest', 'file'],
+                    wvpasseq(['', 'test', save_time_str, 'file'],
                              [name for name, item in res_ex.terminus])
 
             for path in ('/test/latest/file-symlink/',
@@ -455,7 +463,7 @@ def test_resolve():
                     lresolve(repo, path)
                 except vfs.IOError as res_ex:
                     wvpasseq(ENOTDIR, res_ex.errno)
-                    wvpasseq(['', 'test', 'latest', 'file'],
+                    wvpasseq(['', 'test', save_time_str, 'file'],
                              [name for name, item in res_ex.terminus])
 
             wvstart('resolve: non-directory parent')
@@ -476,7 +484,7 @@ def test_resolve():
                                                          oid=dir_symlink_value.oid)
             expected = (('', vfs._root),
                         ('test', test_revlist_w_meta),
-                        ('latest', expected_latest_item_w_meta),
+                        (save_time_str, expected_latest_item_w_meta),
                         ('dir-symlink', expected_dir_symlink_item_w_meta))
             wvpasseq(expected, res)
 
@@ -485,7 +493,7 @@ def test_resolve():
                                          meta=tree_dict(repo, dir_value.oid)['.'].meta)
             expected = (('', vfs._root),
                         ('test', test_revlist_w_meta),
-                        ('latest', expected_latest_item_w_meta),
+                        (save_time_str, expected_latest_item_w_meta),
                         ('dir', expected_dir_item))
             for resname, resolver in (('resolve', resolve),
                                       ('lresolve', lresolve)):
@@ -614,10 +622,12 @@ def test_resolve_loop():
             symlink('loop', data_path + '/loop')
             ex((bup_path, 'init'))
             ex((bup_path, 'index', '-v', data_path))
-            ex((bup_path, 'save', '-d', '100000', '-tvvn', 'test', '--strip',
+            save_utc = 100000
+            ex((bup_path, 'save', '-d', str(save_utc), '-tvvn', 'test', '--strip',
                 data_path))
+            save_name = strftime('%Y-%m-%d-%H%M%S', localtime(save_utc))
             try:
-                resolve(repo, '/test/latest/loop')
+                resolve(repo, '/test/%s/loop' % save_utc)
             except vfs.IOError as res_ex:
                 wvpasseq(ELOOP, res_ex.errno)
                 wvpasseq(['', 'test', 'latest', 'loop'],
@@ -638,8 +648,10 @@ def test_contents_with_mismatched_bupm_git_ordering():
                 tmpfile.write(b'canary\n')
             ex((bup_path, 'init'))
             ex((bup_path, 'index', '-v', data_path))
-            ex((bup_path, 'save', '-tvvn', 'test', '--strip',
-                data_path))
+            save_utc = 100000
+            save_name = strftime('%Y-%m-%d-%H%M%S', localtime(save_utc))
+            ex((bup_path, 'save', '-tvvn', 'test', '-d', str(save_utc),
+                '--strip', data_path))
             repo = LocalRepo()
             tip_sref = exo(('git', 'show-ref', 'refs/heads/test'))[0]
             tip_oidx = tip_sref.strip().split()[0]
@@ -649,7 +661,7 @@ def test_contents_with_mismatched_bupm_git_ordering():
             tip_tree = tree_dict(repo, tip_tree_oid)
 
             name, item = vfs.resolve(repo, '/test/latest')[2]
-            wvpasseq('latest', name)
+            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 ('.', 'foo', 'foo.')))
index 5bca295b89cff9d9396d651482c34ee9d16f1a92..e36df64d936347eb288f01a67ee1aba49abd7049 100644 (file)
@@ -236,6 +236,7 @@ def _decompose_path(path):
 
 Item = namedtuple('Item', ('meta', 'oid'))
 Chunky = namedtuple('Chunky', ('meta', 'oid'))
+FakeLink = namedtuple('FakeLink', ('meta', 'target'))
 Root = namedtuple('Root', ('meta'))
 Tags = namedtuple('Tags', ('meta'))
 RevList = namedtuple('RevList', ('meta', 'oid'))
@@ -398,6 +399,8 @@ def readlink(repo, item):
         target = item.meta.symlink_target
         if target:
             return target
+    elif isinstance(item, FakeLink):
+        return item.target
     return _readlink(repo, item.oid)
 
 def _compute_item_size(repo, item):
@@ -636,13 +639,13 @@ def cache_commit(repo, oid):
     revs = None  # Don't disturb the tees
     rev_names = _reverse_suffix_duplicates(_name_for_rev(x) for x in rev_names)
     rev_items = (_item_for_rev(x) for x in rev_items)
-    latest = None
+    tip = None
     for item in rev_items:
-        latest = latest or item
         name = next(rev_names)
+        tip = tip or (name, item)
         entries[name] = item
-    entries['latest'] = latest
-    revlist_key = b'rvl:' + latest.coid
+    entries['latest'] = FakeLink(meta=default_symlink_mode, target=tip[0])
+    revlist_key = b'rvl:' + tip[1].coid
     cache_notice(revlist_key, entries)
     return entries
 
@@ -990,7 +993,10 @@ def augment_item_meta(repo, item, include_size=False):
     meta.mode = m
     meta.uid = meta.gid = meta.atime = meta.mtime = meta.ctime = 0
     if S_ISLNK(m):
-        target = _readlink(repo, item.oid)
+        if isinstance(item, FakeLink):
+            target = item.target
+        else:
+            target = _readlink(repo, item.oid)
         meta.symlink_target = target
         meta.size = len(target)
     elif include_size:
index c71bc54be79f512560b1a6d416bfc2d145e3e8b1..3a8e5db38073199f887b735f1ea5bbc11c52eafc 100755 (executable)
@@ -70,9 +70,9 @@ with test_tempdir('ftp-') as tmpdir:
                                               'cd ..', 'pwd')).out)
     wvpasseq('/src\n/\n', bup('ftp', input=jl('cd src', 'pwd',
                                               'cd ..', 'cd ..', 'pwd')).out)
-    wvpasseq('/src/latest/dir\n',
+    wvpasseq('/src/%s/dir\n' % save_name,
              bup('ftp', input=jl('cd src/latest/dir-symlink', 'pwd')).out)
-    wvpasseq('/src/latest/dir\n',
+    wvpasseq('/src/%s/dir\n' % save_name,
              bup('ftp', input=jl('cd src latest dir-symlink', 'pwd')).out)
     wvpassne(0, bup('ftp',
                     input=jl('cd src/latest/bad-symlink', 'pwd'),
index 7fb05cf29346257c17c5082c8d4188a8e528deb8..850b48e04141c1a5c50693a3db7acbf111bad34b 100755 (executable)
--- a/t/test-ls
+++ b/t/test-ls
@@ -249,19 +249,19 @@ $socket_mode $uid/$gid 0 2009-10-03 23:48 socket
 $symlink_mode $uid/$gid $symlink_size $symlink_date symlink -> file"
 
 WVPASSEQ "$(bup-ls -ld "src/latest" | tr -s ' ' ' ')" \
-"drwx------ $user/$group 0 2009-10-03 23:48 src/latest"
+"lrwxr-xr-x 0/0 17 1970-01-01 00:00 src/latest -> 1977-09-05-125600"
 
 
 WVSTART "$ls_cmd_desc (backup set - long)"
 WVPASSEQ "$(bup-ls -l --numeric-ids src | cut -d' ' -f 1-2)" \
 "drwx------ $uid/$gid
-drwx------ $uid/$gid"
+lrwxr-xr-x 0/0"
 
-WVPASSEQ "$(bup-ls -ds "src/latest" | tr -s ' ' ' ')" \
-"$src_tree_hash src/latest"
+WVPASSEQ "$(bup-ls -ds "src/1977-09-05-125600" | tr -s ' ' ' ')" \
+"$src_tree_hash src/1977-09-05-125600"
 
-WVPASSEQ "$(bup-ls -ds --commit-hash "src/latest" | tr -s ' ' ' ')" \
-"$src_commit_hash src/latest"
+WVPASSEQ "$(bup-ls -ds --commit-hash "src/1977-09-05-125600" | tr -s ' ' ' ')" \
+"$src_commit_hash src/1977-09-05-125600"
 
 
 WVSTART "$ls_cmd_desc (dates TZ != UTC)"