From: Rob Browning Date: Sun, 9 Dec 2018 18:40:26 +0000 (-0600) Subject: vfs: change /save/latest back to a symlink to the latest save X-Git-Tag: 0.30~41 X-Git-Url: https://arthur.barton.de/cgi-bin/gitweb.cgi?p=bup.git;a=commitdiff_plain;h=7dfb95be5b950e6b1fb47088ee26dded75a4700d vfs: change /save/latest back to a symlink to the latest save 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 Tested-by: Rob Browning --- diff --git a/Documentation/bup-restore.md b/Documentation/bup-restore.md index 92643d9..a7c246e 100644 --- a/Documentation/bup-restore.md +++ b/Documentation/bup-restore.md @@ -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 diff --git a/cmd/restore-cmd.py b/cmd/restore-cmd.py index bb37811..6544a24 100755 --- a/cmd/restore-cmd.py +++ b/cmd/restore-cmd.py @@ -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: diff --git a/lib/bup/t/tvfs.py b/lib/bup/t/tvfs.py index 34a6fa2..7cfe2f0 100644 --- a/lib/bup/t/tvfs.py +++ b/lib/bup/t/tvfs.py @@ -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.'))) diff --git a/lib/bup/vfs.py b/lib/bup/vfs.py index 5bca295..e36df64 100644 --- a/lib/bup/vfs.py +++ b/lib/bup/vfs.py @@ -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: diff --git a/t/test-ftp b/t/test-ftp index c71bc54..3a8e5db 100755 --- a/t/test-ftp +++ b/t/test-ftp @@ -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'), diff --git a/t/test-ls b/t/test-ls index 7fb05cf..850b48e 100755 --- 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)"