]> 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.
 
 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
 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
         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:
         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 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
 
 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)
 
     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
 @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_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: /')
             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 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')
                      test_content)
 
             wvstart('resolve: /test/latest')
@@ -347,7 +355,7 @@ def test_resolve():
                                                      coid=tip_oid)
             expected = (('', vfs._root),
                         ('test', test_revlist_w_meta),
                                                      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))
             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),
                                                  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)
 
                         ('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),
             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)
 
                         ('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),
                                                         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)
 
                         ('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),
             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)
 
                         ('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),
                                                          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)
 
                         ('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)
                     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/',
                              [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)
                     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')
                              [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),
                                                          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)
 
                         ('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),
                                          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)):
                         ('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))
             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))
                 data_path))
+            save_name = strftime('%Y-%m-%d-%H%M%S', localtime(save_utc))
             try:
             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'],
             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))
                 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]
             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]
             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.')))
             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'))
 
 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'))
 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
         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):
     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)
     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:
     for item in rev_items:
-        latest = latest or item
         name = next(rev_names)
         name = next(rev_names)
+        tip = tip or (name, item)
         entries[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
 
     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):
     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:
         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)
                                               '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)
              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'),
              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 ' ' ' ')" \
 $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
 
 
 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)"
 
 
 WVSTART "$ls_cmd_desc (dates TZ != UTC)"