may be either a Metadata object, or an integer mode. Functions like
item_mode() and item_size() will return the mode and size in either
case. Any item.meta Metadata instances must not be modified directly.
-Make a copy to modify via item.meta.copy() if needed.
+Make a copy to modify via item.meta.copy() if needed, or call
+copy_item().
The want_meta argument is advisory for calls that accept it, and it
may not be honored. Callers must be able to handle an item.meta value
from collections import namedtuple
from errno import ELOOP, ENOENT, ENOTDIR
from itertools import chain, dropwhile, groupby, izip, tee
+from random import randrange
from stat import S_IFDIR, S_IFLNK, S_IFREG, S_ISDIR, S_ISLNK, S_ISREG
from time import localtime, strftime
import exceptions, re, sys
from bup import client, git, metadata
+from bup.compat import range
from bup.git import BUP_CHUNKED, cp, get_commit_items, parse_commit, tree_decode
from bup.helpers import debug2, last
from bup.metadata import Metadata
### vfs cache
### A general purpose shared cache with (currently) cheap random
-### eviction. There is currently no weighting so a single commit item
-### is just as likely to be evicted as an entire "rev-list". See
+### eviction. At the moment there is no weighting so a single commit
+### item is just as likely to be evicted as an entire "rev-list". See
### is_valid_cache_key for a description of the expected content.
_cache = {}
def is_valid_cache_key(x):
"""Return logically true if x looks like it could be a valid cache key
(with respect to structure). Current valid cache entries:
+ (path, parent, want_meta, dref) -> resolution
commit_oid -> commit
commit_oid + ':r' -> rev-list
i.e. rev-list -> {'.', commit, '2012...', next_commit, ...}
"""
# Suspect we may eventually add "(container_oid, name) -> ...", and others.
x_t = type(x)
+ if x_t is tuple:
+ return len(x) == 4
if x_t is bytes:
if len(x) == 20:
return True
assert is_valid_cache_key(key)
if key in _cache:
return
- _cache[key] = value
if len(_cache) < _cache_max_items:
+ _cache_keys.append(key)
+ _cache[key] = value
return
- victim_i = random.randrange(0, len(_cache_keys))
+ victim_i = randrange(0, len(_cache_keys))
victim = _cache_keys[victim_i]
+ del _cache[victim]
_cache_keys[victim_i] = key
- _cache.pop(victim)
-
+ _cache[key] = value
def cache_get_commit_item(oid, need_meta=True):
"""Return the requested tree item if it can be found in the cache.
if commit:
return RevList(oid=oid, meta=commit.meta)
-
def copy_item(item):
"""Return a completely independent copy of item, such that
modifications will not affect the original.
"""
meta = getattr(item, 'meta', None)
- if not meta:
- return item
- return(item._replace(meta=meta.copy()))
+ if isinstance(meta, Metadata):
+ return(item._replace(meta=meta.copy()))
+ return item
def item_mode(item):
"""Return the integer mode (stat st_mode) for item."""
else:
ndig = len(str(ndup - 1))
fmt = '%s-' + '%0' + str(ndig) + 'd'
- for i in xrange(ndup - 1, -1, -1):
+ for i in range(ndup - 1, -1, -1):
yield fmt % (name, i)
def parse_rev(f):
assert repo
assert S_ISDIR(item_mode(item))
item_t = type(item)
-
if item_t in real_tree_types:
it = repo.cat(item.oid.encode('hex'))
- _, obj_type, size = next(it)
+ _, obj_t, size = next(it)
data = ''.join(it)
- if obj_type == 'tree':
- if want_meta:
- item_gen = tree_items_with_meta(repo, item.oid, data, names)
- else:
- item_gen = tree_items(item.oid, data, names)
- elif obj_type == 'commit':
- if want_meta:
- item_gen = tree_items_with_meta(repo, item.oid, tree_data, names)
- else:
- item_gen = tree_items(item.oid, tree_data, names)
- else:
+ if obj_t != 'tree':
for _ in it: pass
- raise Exception('unexpected git ' + obj_type)
+ # Note: it shouldn't be possible to see an Item with type
+ # 'commit' since a 'commit' should always produce a Commit.
+ raise Exception('unexpected git ' + obj_t)
+ if want_meta:
+ item_gen = tree_items_with_meta(repo, item.oid, data, names)
+ else:
+ item_gen = tree_items(item.oid, data, names)
elif item_t == RevList:
item_gen = revlist_items(repo, item.oid, names)
elif item_t == Root:
yield x
def _resolve_path(repo, path, parent=None, want_meta=True, deref=False):
+ cache_key = (tuple(path), parent, not not want_meta, not not deref)
+ resolution = cache_get(cache_key)
+ if resolution:
+ return resolution
+
+ def notice_resolution(r):
+ cache_notice(cache_key, r)
+ return r
+
def raise_dir_required_but_not_dir(path, parent, past):
raise IOError(ENOTDIR,
"path %r%s resolves to non-directory %r"
deref = True
if not future: # path was effectively '.' or '/'
if is_absolute:
- return (('', _root),)
+ return notice_resolution((('', _root),))
if parent:
- return tuple(parent)
- return [('', _root)]
+ return notice_resolution(tuple(parent))
+ return notice_resolution((('', _root),))
if is_absolute:
past = [('', _root)]
else:
if not future:
if must_be_dir and not S_ISDIR(item_mode(past[-1][1])):
raise_dir_required_but_not_dir(path, parent, past)
- return tuple(past)
+ return notice_resolution(tuple(past))
segment = future.pop()
if segment == '..':
assert len(past) > 0
past[-1] = parent_name, parent_item
if not item:
past.append((segment, None),)
- return tuple(past)
+ return notice_resolution(tuple(past))
mode = item_mode(item)
if not S_ISLNK(mode):
if not S_ISDIR(mode):
terminus=past)
if must_be_dir:
raise_dir_required_but_not_dir(path, parent, past)
- return tuple(past)
+ return notice_resolution(tuple(past))
# It's treeish
if want_meta and type(item) in real_tree_types:
dir_meta = _find_treeish_oid_metadata(repo, item.oid)
is_absolute, _, target_future = _decompose_path(target)
if is_absolute:
if not target_future: # path was effectively '/'
- return (('', _root),)
+ return notice_resolution((('', _root),))
past = [('', _root)]
future = target_future
else: