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>
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
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:
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
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
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: /')
# 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')
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))
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)
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)
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)
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)
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)
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/',
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')
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)
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)):
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',
+ save_name = strftime('%Y-%m-%d-%H%M%S', localtime(save_utc))
- 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'],
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]
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.')))
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'))
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):
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 = latest or item
+ tip = tip or (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
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:
'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'),
$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
-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)"