aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--NEWS3
-rw-r--r--libbe/bug.py8
-rw-r--r--libbe/bugdir.py8
-rw-r--r--libbe/command/import_xml.py2
-rw-r--r--libbe/command/init.py9
-rw-r--r--libbe/command/list.py22
-rw-r--r--libbe/comment.py8
-rw-r--r--libbe/storage/base.py55
-rw-r--r--libbe/storage/util/properties.py19
-rw-r--r--libbe/storage/util/settings_object.py388
-rw-r--r--libbe/storage/vcs/base.py62
-rw-r--r--libbe/storage/vcs/bzr.py9
-rwxr-xr-xtest_usage.sh77
13 files changed, 441 insertions, 229 deletions
diff --git a/NEWS b/NEWS
index 17d5aee..e0dc81c 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,6 @@
+January 23, 2010
+ * Added `be list --mine`, listing bugs belonging to you.
+
January 20, 2010
* Renamed 'be-mbox-to-xml' -> 'be-mail-to-xml' and added support for
several mailbox formats.
diff --git a/libbe/bug.py b/libbe/bug.py
index 7e9999e..0b40921 100644
--- a/libbe/bug.py
+++ b/libbe/bug.py
@@ -121,7 +121,7 @@ def load_status(active_status_def, inactive_status_def):
load_status(active_status_def, inactive_status_def)
-class Bug(settings_object.SavedSettingsObject):
+class Bug (settings_object.SavedSettingsObject):
"""
>>> b = Bug()
>>> print b.status
@@ -242,8 +242,6 @@ class Bug(settings_object.SavedSettingsObject):
if from_storage == False:
if uuid == None:
self.uuid = libbe.util.id.uuid_gen()
- self.settings = {}
- self._setup_saved_settings()
self.time = int(time.time()) # only save to second precision
self.summary = summary
dummy = self.comment_root
@@ -630,11 +628,11 @@ class Bug(settings_object.SavedSettingsObject):
settings_mapfile = \
self.storage.get(self.id.storage('values'), default='\n')
try:
- self.settings = mapfile.parse(settings_mapfile)
+ settings = mapfile.parse(settings_mapfile)
except mapfile.InvalidMapfileContents, e:
raise Exception('Invalid settings file for bug %s\n'
'(BE version missmatch?)' % self.id.user())
- self._setup_saved_settings()
+ self._setup_saved_settings(settings)
def save_settings(self):
mf = mapfile.generate(self._get_saved_settings())
diff --git a/libbe/bugdir.py b/libbe/bugdir.py
index 8389716..dd467bf 100644
--- a/libbe/bugdir.py
+++ b/libbe/bugdir.py
@@ -178,12 +178,9 @@ class BugDir (list, settings_object.SavedSettingsObject):
if self.uuid == None:
self.uuid = [c for c in self.storage.children()
if c != 'version'][0]
- self.load_settings()
else:
if self.uuid == None:
self.uuid = libbe.util.id.uuid_gen()
- self.settings = {}
- self._setup_saved_settings()
if self.storage != None and self.storage.is_writeable():
self.save()
@@ -194,12 +191,11 @@ class BugDir (list, settings_object.SavedSettingsObject):
settings_mapfile = \
self.storage.get(self.id.storage('settings'), default='\n')
try:
- self.settings = mapfile.parse(settings_mapfile)
+ settings = mapfile.parse(settings_mapfile)
except mapfile.InvalidMapfileContents, e:
raise Exception('Invalid settings file for bugdir %s\n'
'(BE version missmatch?)' % self.id.user())
- self._setup_saved_settings()
- #self._setup_user_id(self.user_id)
+ self._setup_saved_settings(settings)
self._setup_severities(self.severities)
self._setup_status(self.active_status, self.inactive_status)
diff --git a/libbe/command/import_xml.py b/libbe/command/import_xml.py
index 598ecb8..287d8b7 100644
--- a/libbe/command/import_xml.py
+++ b/libbe/command/import_xml.py
@@ -115,7 +115,7 @@ class Import_XML (libbe.command.Command):
if params['xml-file'] == '-':
xml = self.stdin.read().encode(self.stdin.encoding)
else:
- self._check_restricted_access(storage, params['xml-file'])
+ self._check_restricted_access(bugdir.storage, params['xml-file'])
xml = libbe.util.encoding.get_file_contents(
params['xml-file'])
diff --git a/libbe/command/init.py b/libbe/command/init.py
index 7fdbdae..7b83645 100644
--- a/libbe/command/init.py
+++ b/libbe/command/init.py
@@ -49,6 +49,9 @@ class Init (libbe.command.Command):
BE repository initialized.
>>> bd = libbe.bugdir.BugDir(vcs)
>>> vcs.disconnect()
+ >>> vcs.connect()
+ >>> bugdir = libbe.bugdir.BugDir(vcs, from_storage=True)
+ >>> vcs.disconnect()
>>> vcs.destroy()
>>> dir.cleanup()
@@ -66,6 +69,9 @@ class Init (libbe.command.Command):
Using ... for revision control.
BE repository initialized.
>>> vcs.disconnect()
+ >>> vcs.connect()
+ >>> bugdir = libbe.bugdir.BugDir(vcs, from_storage=True)
+ >>> vcs.disconnect()
>>> vcs.destroy()
>>> dir.cleanup()
"""
@@ -87,8 +93,9 @@ class Init (libbe.command.Command):
pass
storage.init()
storage.connect()
+ self.ui.storage_callbacks.set_storage(storage)
bd = libbe.bugdir.BugDir(storage, from_storage=False)
- bd.save()
+ self.ui.storage_callbacks.set_bugdir(bd)
if bd.storage.name is not 'None':
print >> self.stdout, \
'Using %s for revision control.' % storage.name
diff --git a/libbe/command/list.py b/libbe/command/list.py
index 44be71b..73c60a9 100644
--- a/libbe/command/list.py
+++ b/libbe/command/list.py
@@ -107,8 +107,10 @@ class List (libbe.command.Command):
libbe.command.Option(name='assigned', short_name='a',
help='Only show bugs matching ASSIGNED',
arg=libbe.command.Argument(
- name='assigned', metavar='ASSIGNED', default='all',
+ name='assigned', metavar='ASSIGNED', default=None,
completion_callback=libbe.command.util.complete_assigned)),
+ libbe.command.Option(name='mine', short_name='m',
+ help='List bugs assigned to you'),
libbe.command.Option(name='extra-strings', short_name='e',
help='Only show bugs matching STRINGS, e.g. --extra-strings'
' TAG:working,TAG:xml',
@@ -136,7 +138,6 @@ class List (libbe.command.Command):
# ("U", "unconfirmed", "List unconfirmed bugs"),
# ("o", "open", "List open bugs"),
# ("T", "test", "List bugs in testing"),
-# ("m", "mine", "List bugs assigned to you"))
# for s in bools:
# attr = s[1].replace('-','_')
# short = "-%c" % s[0]
@@ -153,14 +154,14 @@ class List (libbe.command.Command):
writeable = bugdir.storage.writeable
bugdir.storage.writeable = False
cmp_list, status, severity, assigned, extra_strings_regexps = \
- self._parse_params(params)
+ self._parse_params(bugdir, params)
filter = Filter(status, severity, assigned,
extra_strings_regexps=extra_strings_regexps)
bugs = [bugdir.bug_from_uuid(uuid) for uuid in bugdir.uuids()]
bugs = [b for b in bugs if filter(bugdir, b) == True]
self.result = bugs
if len(bugs) == 0 and params['xml'] == False:
- print >> self.stdout, "No matching bugs found"
+ print >> self.stdout, 'No matching bugs found'
# sort bugs
bugs = self._sort_bugs(bugs, cmp_list)
@@ -174,13 +175,13 @@ class List (libbe.command.Command):
bugdir.storage.writeable = writeable
return 0
- def _parse_params(self, params):
+ def _parse_params(self, bugdir, params):
cmp_list = []
if params['sort'] != None:
for cmp in params['sort'].sort_by.split(','):
if cmp not in AVAILABLE_CMPS:
raise libbe.command.UserError(
- "Invalid sort on '%s'.\nValid sorts:\n %s"
+ 'Invalid sort on "%s".\nValid sorts:\n %s'
% (cmp, '\n '.join(AVAILABLE_CMPS)))
cmp_list.append(eval('libbe.bug.cmp_%s' % cmp))
# select status
@@ -203,11 +204,14 @@ class List (libbe.command.Command):
severity = libbe.command.util.select_values(
params['severity'], bug.severity_values)
# select assigned
- if params['assigned'] == "all":
- assigned = "all"
+ if params['assigned'] == None:
+ if params['mine'] == True:
+ assigned = [self._get_user_id()]
+ else:
+ assigned = 'all'
else:
assigned = libbe.command.util.select_values(
- params['assigned'], libbe.command.util.assignees())
+ params['assigned'], libbe.command.util.assignees(bugdir))
for i in range(len(assigned)):
if assigned[i] == '-':
assigned[i] = params['user-id']
diff --git a/libbe/comment.py b/libbe/comment.py
index f0cc45c..accd4df 100644
--- a/libbe/comment.py
+++ b/libbe/comment.py
@@ -93,7 +93,7 @@ def save_comments(bug):
comment.save()
-class Comment(Tree, settings_object.SavedSettingsObject):
+class Comment (Tree, settings_object.SavedSettingsObject):
"""
>>> c = Comment()
>>> c.uuid != None
@@ -206,8 +206,6 @@ class Comment(Tree, settings_object.SavedSettingsObject):
if from_storage == False:
if uuid == None:
self.uuid = libbe.util.id.uuid_gen()
- self.settings = {}
- self._setup_saved_settings()
self.time = int(time.time()) # only save to second precision
self.in_reply_to = in_reply_to
self.body = body
@@ -589,11 +587,11 @@ class Comment(Tree, settings_object.SavedSettingsObject):
settings_mapfile = \
self.storage.get(self.id.storage("values"), default="\n")
try:
- self.settings = mapfile.parse(settings_mapfile)
+ settings = mapfile.parse(settings_mapfile)
except mapfile.InvalidMapfileContents, e:
raise Exception('Invalid settings file for comment %s\n'
'(BE version missmatch?)' % self.id.user())
- self._setup_saved_settings()
+ self._setup_saved_settings(settings)
def save_settings(self):
mf = mapfile.generate(self._get_saved_settings())
diff --git a/libbe/storage/base.py b/libbe/storage/base.py
index 202305b..64ae3e7 100644
--- a/libbe/storage/base.py
+++ b/libbe/storage/base.py
@@ -84,9 +84,12 @@ class EmptyCommit(Exception):
def __init__(self):
Exception.__init__(self, 'No changes to commit')
+class _EMPTY (object):
+ """Entry has been added but has no user-set value."""
+ pass
class Entry (Tree):
- def __init__(self, id, value=None, parent=None, directory=False,
+ def __init__(self, id, value=_EMPTY, parent=None, directory=False,
children=None):
if children == None:
Tree.__init__(self)
@@ -241,10 +244,7 @@ class Storage (object):
"""Add an entry"""
if self.is_writeable() == False:
raise NotWriteable('Cannot add entry to unwriteable storage.')
- try: # Maybe we've already added that id?
- self.get(id)
- pass # yup, no need to add another
- except InvalidID:
+ if not self.exists(id):
self._add(id, *args, **kwargs)
def _add(self, id, parent=None, directory=False):
@@ -253,6 +253,15 @@ class Storage (object):
p = self._data[parent]
self._data[id] = Entry(id, parent=p, directory=directory)
+ def exists(self, *args, **kwargs):
+ """Check an entry's existence"""
+ if self.is_readable() == False:
+ raise NotReadable('Cannot check entry existence in unreadable storage.')
+ return self._exists(*args, **kwargs)
+
+ def _exists(self, id, revision=None):
+ return id in self._data
+
def remove(self, *args, **kwargs):
"""Remove an entry."""
if self.is_writeable() == False:
@@ -332,7 +341,7 @@ class Storage (object):
return value
def _get(self, id, default=InvalidObject, revision=None):
- if id in self._data:
+ if id in self._data and self._data[id].value != _EMPTY:
return self._data[id].value
elif default == InvalidObject:
raise InvalidID(id)
@@ -402,6 +411,13 @@ class VersionedStorage (Storage):
p = self._data[-1][parent]
self._data[-1][id] = Entry(id, parent=p, directory=directory)
+ def _exists(self, id, revision=None):
+ if revision == None:
+ revision = -1
+ else:
+ revision = int(revision)
+ return id in self._data[revision]
+
def _remove(self, id):
if self._data[-1][id].directory == True \
and len(self.children(id)) > 0:
@@ -446,7 +462,8 @@ class VersionedStorage (Storage):
revision = -1
else:
revision = int(revision)
- if id in self._data[revision]:
+ if id in self._data[revision] \
+ and self._data[revision][id].value != _EMPTY:
return self._data[revision][id].value
elif default == InvalidObject:
raise InvalidID(id)
@@ -760,13 +777,14 @@ if TESTING == True:
pass
def test_get_initial_value(self):
- """Data value should be None before any value has been set.
+ """Data value should be default before any value has been set.
"""
self.s.add(self.id, directory=False)
- ret = self.s.get(self.id)
- self.failUnless(ret == None,
- "%s.get() returned %s not None"
- % (vars(self.Class)['name'], ret))
+ val = 'UNLIKELY DEFAULT'
+ ret = self.s.get(self.id, default=val)
+ self.failUnless(ret == val,
+ "%s.get() returned %s not %s"
+ % (vars(self.Class)['name'], ret, val))
def test_set_exception(self):
"""Set should raise exception if id not in Storage.
@@ -830,6 +848,19 @@ if TESTING == True:
"%s.get() returned %s not %s"
% (vars(self.Class)['name'], ret, self.val))
+ def test_empty_get_set_persistence(self):
+ """After empty set, get may return either an empty string or default.
+ """
+ self.s.add(self.id, directory=False)
+ self.s.set(self.id, '')
+ self.s.disconnect()
+ self.s.connect()
+ default = 'UNLIKELY DEFAULT'
+ ret = self.s.get(self.id, default=default)
+ self.failUnless(ret in ['', default],
+ "%s.get() returned %s not in %s"
+ % (vars(self.Class)['name'], ret, ['', default]))
+
def test_add_nonrooted_persistence(self):
"""Adding entries should increase the number of children after reconnect.
"""
diff --git a/libbe/storage/util/properties.py b/libbe/storage/util/properties.py
index f48cfa0..55bac85 100644
--- a/libbe/storage/util/properties.py
+++ b/libbe/storage/util/properties.py
@@ -303,12 +303,14 @@ def cached_property(generator, initVal=None, mutable=False):
return funcs
return decorator
-def primed_property(primer, initVal=None):
+def primed_property(primer, initVal=None, unprimeableVal=None):
"""
Just like a cached_property, except that instead of returning a
- new value and running fset to cache it, the primer performs some
+ new value and running fset to cache it, the primer attempts some
background manipulation (e.g. loads data into instance.settings)
- such that a _second_ pass through fget succeeds.
+ such that a _second_ pass through fget succeeds. If the second
+ pass doesn't succeed (e.g. no readable storage), we give up and
+ return unprimeableVal.
The 'cache' flag becomes a 'prime' flag, with priming taking place
whenever ._<name>_prime is True, or is False or missing and
@@ -326,6 +328,8 @@ def primed_property(primer, initVal=None):
if prime == True or (prime == False and value == initVal):
primer(self)
value = fget(self)
+ if prime == False and value == initVal:
+ return unprimeableVal
return value
funcs["fget"] = _fget
return funcs
@@ -543,13 +547,14 @@ if libbe.TESTING == True:
def testPrimedLocalProperty(self):
class Test(object):
def prime(self):
- self.settings["PRIMED"] = "initialized"
+ self.settings["PRIMED"] = self.primeVal
@Property
- @primed_property(primer=prime, initVal=None)
+ @primed_property(primer=prime, initVal=None, unprimeableVal=2)
@settings_property(name="PRIMED")
def x(): return {}
def __init__(self):
self.settings={}
+ self.primeVal = "initialized"
t = Test()
self.failIf("_PRIMED_prime" in dir(t),
getattr(t, "_PRIMED_prime", None))
@@ -564,6 +569,10 @@ if libbe.TESTING == True:
t._PRIMED_prime = False
t.x = 3
self.failUnless(t.x == 3, t.x)
+ # test unprimableVal
+ t.x = None
+ t.primeVal = None
+ self.failUnless(t.x == 2, t.x)
def testChangeHookLocalProperty(self):
class Test(object):
def _hook(self, old, new):
diff --git a/libbe/storage/util/settings_object.py b/libbe/storage/util/settings_object.py
index ca94f23..8434952 100644
--- a/libbe/storage/util/settings_object.py
+++ b/libbe/storage/util/settings_object.py
@@ -40,7 +40,7 @@ class _Token (object):
pass
class UNPRIMED (_Token):
- "Property has not been primed."
+ "Property has not been primed (loaded)."
pass
class EMPTY (_Token):
@@ -60,13 +60,13 @@ def prop_save_settings(self, old, new):
def prop_load_settings(self):
"""
- The default action undertaken when an UNPRIMED property is accessed.
+ The default action undertaken when an UNPRIMED property is
+ accessed. Attempt to run .load_settings(), which calls
+ ._setup_saved_settings() internally. If .storage is inaccessible,
+ don't do anything.
"""
- if self.storage != None and self.storage.is_readable() \
- and self._settings_loaded==False:
+ if self.storage != None and self.storage.is_readable():
self.load_settings()
- else:
- self._setup_saved_settings(flag_as_loaded=False)
# Some name-mangling routines for pretty printing setting names
def setting_name_to_attr_name(self, name):
@@ -129,6 +129,12 @@ def versioned_property(name, doc,
* you set change_hook and might have mutable property values
See the docstrings in libbe.properties for details on how each of
these cases are handled.
+
+ The value stored in .settings[name] will be
+ * no value (or UNPRIMED) if the property has been neither set,
+ nor loaded as blank.
+ * EMPTY if the value has been loaded as blank.
+ * some value if the property has been either loaded or set.
"""
settings_properties.append(name)
if require_save == True:
@@ -152,7 +158,8 @@ def versioned_property(name, doc,
% (', '.join(allowed))
hooked = change_hook_property(hook=change_hook, mutable=mutable,
default=EMPTY)
- primed = primed_property(primer=primer, initVal=UNPRIMED)
+ primed = primed_property(primer=primer, initVal=UNPRIMED,
+ unprimeableVal=EMPTY)
settings = settings_property(name=name, null=UNPRIMED)
docp = doc_property(doc=fulldoc)
deco = hooked(primed(settings(docp(funcs))))
@@ -181,27 +188,29 @@ class SavedSettingsObject(object):
_attr_name_to_setting_name = attr_name_to_setting_name
def __init__(self):
- self._settings_loaded = False
self.storage = None
self.settings = {}
def load_settings(self):
"""Load the settings from disk."""
- # Override. Must call ._setup_saved_settings() after loading.
- self.settings = {}
- self._setup_saved_settings()
+ # Override. Must call ._setup_saved_settings({}) with
+ # from-storage settings.
+ self._setup_saved_settings({})
- def _setup_saved_settings(self, flag_as_loaded=True):
+ def _setup_saved_settings(self, settings=None):
"""
- To be run after setting self.settings up from disk. Marks all
- settings as primed.
+ Sets up a settings dict loaded from storage. Fills in
+ all missing settings entries with EMPTY.
"""
+ if settings == None:
+ settings = {}
for property in self.settings_properties:
if property not in self.settings \
or self.settings[property] == UNPRIMED:
- self.settings[property] = EMPTY
- if flag_as_loaded == True:
- self._settings_loaded = True
+ if property in settings:
+ self.settings[property] = settings[property]
+ else:
+ self.settings[property] = EMPTY
def save_settings(self):
"""Save the settings to disk."""
@@ -220,15 +229,16 @@ class SavedSettingsObject(object):
load already.
"""
settings = {}
+ for k in self.settings_properties: # force full load
+ if not k in self.settings or self.settings[k] == UNPRIMED:
+ value = getattr(
+ self, self._setting_name_to_attr_name(k))
for k in self.settings_properties:
- if k in self.settings and \
- not self.settings[k] in [None, EMPTY]:
+ if k in self.settings and self.settings[k] != EMPTY:
settings[k] = self.settings[k]
- else:
- value = getattr(
+ elif k in self.required_saved_properties:
+ settings[k] = getattr(
self, self._setting_name_to_attr_name(k))
- if value not in [None, EMPTY, []]:
- settings[k] = value
return settings
def clear_cached_setting(self, setting=None):
@@ -242,134 +252,275 @@ class SavedSettingsObject(object):
if libbe.TESTING == True:
+ import copy
+
+ class TestStorage (list):
+ def __init__(self):
+ list.__init__(self)
+ self.readable = True
+ self.writeable = True
+ def is_readable(self):
+ return self.readable
+ def is_writeable(self):
+ return self.writeable
+
+ class TestObject (SavedSettingsObject):
+ def load_settings(self):
+ self.load_count += 1
+ if len(self.storage) == 0:
+ settings = {}
+ else:
+ settings = copy.deepcopy(self.storage[-1])
+ self._setup_saved_settings(settings)
+ def save_settings(self):
+ settings = self._get_saved_settings()
+ self.storage.append(copy.deepcopy(settings))
+ def __init__(self):
+ SavedSettingsObject.__init__(self)
+ self.load_count = 0
+ self.storage = TestStorage()
+
class SavedSettingsObjectTests(unittest.TestCase):
- def testSimpleProperty(self):
- """Testing a minimal versioned property"""
- class Test(SavedSettingsObject):
+ def testSimplePropertyDoc(self):
+ """Testing a minimal versioned property docstring"""
+ class Test (TestObject):
+ settings_properties = []
+ required_saved_properties = []
+ @versioned_property(
+ name="Content-type",
+ doc="A test property",
+ settings_properties=settings_properties,
+ required_saved_properties=required_saved_properties)
+ def content_type(): return {}
+ expected = "A test property\n\nThis property defaults to None."
+ self.failUnless(Test.content_type.__doc__ == expected,
+ Test.content_type.__doc__)
+ def testSimplePropertyFromMemory(self):
+ """Testing a minimal versioned property from memory"""
+ class Test (TestObject):
settings_properties = []
required_saved_properties = []
- @versioned_property(name="Content-type",
- doc="A test property",
- settings_properties=settings_properties,
- required_saved_properties= \
- required_saved_properties)
+ @versioned_property(
+ name="Content-type",
+ doc="A test property",
+ settings_properties=settings_properties,
+ required_saved_properties=required_saved_properties)
def content_type(): return {}
- def __init__(self):
- SavedSettingsObject.__init__(self)
t = Test()
- # access missing setting
- self.failUnless(t._settings_loaded == False, t._settings_loaded)
self.failUnless(len(t.settings) == 0, len(t.settings))
+ # accessing t.content_type triggers the priming, but
+ # t.storage.is_readable() == False, so nothing happens.
+ t.storage.readable = False
+ self.failUnless(t.content_type == None, t.content_type)
+ self.failUnless(t.settings == {}, t.settings)
+ self.failUnless(len(t.settings) == 0, len(t.settings))
+ self.failUnless(t.content_type == None, t.content_type)
+ # accessing t.content_type triggers the priming again, and
+ # now that t.storage.is_readable() == True, this fills out
+ # t.settings with EMPTY data. At this point there should
+ # be one load and no saves.
+ t.storage.readable = True
self.failUnless(t.content_type == None, t.content_type)
- # accessing t.content_type triggers the priming, which runs
- # t._setup_saved_settings, which fills out t.settings with
- # EMPTY data. t._settings_loaded is still false though, since
- # the default priming does not do any of the `official' loading
- # that occurs in t.load_settings.
self.failUnless(len(t.settings) == 1, len(t.settings))
self.failUnless(t.settings["Content-type"] == EMPTY,
t.settings["Content-type"])
- self.failUnless(t._settings_loaded == False, t._settings_loaded)
- # load settings creates an EMPTY value in the settings array
- t.load_settings()
- self.failUnless(t._settings_loaded == True, t._settings_loaded)
- self.failUnless(t.settings["Content-type"] == EMPTY,
- t.settings["Content-type"])
self.failUnless(t.content_type == None, t.content_type)
+ self.failUnless(t.load_count == 1, t.load_count)
+ self.failUnless(len(t.storage) == 0, len(t.storage))
+ # an explicit call to load settings forces a reload,
+ # but nothing else changes.
+ t.load_settings()
self.failUnless(len(t.settings) == 1, len(t.settings))
self.failUnless(t.settings["Content-type"] == EMPTY,
t.settings["Content-type"])
+ self.failUnless(t.content_type == None, t.content_type)
+ self.failUnless(t.load_count == 2, t.load_count)
+ self.failUnless(len(t.storage) == 0, len(t.storage))
# now we set a value
t.content_type = 5
self.failUnless(t.settings["Content-type"] == 5,
t.settings["Content-type"])
+ self.failUnless(t.load_count == 2, t.load_count)
+ self.failUnless(len(t.storage) == 1, len(t.storage))
+ self.failUnless(t.storage == [{'Content-type':5}], t.storage)
+ # getting its value changes nothing
self.failUnless(t.content_type == 5, t.content_type)
self.failUnless(t.settings["Content-type"] == 5,
t.settings["Content-type"])
+ self.failUnless(t.load_count == 2, t.load_count)
+ self.failUnless(len(t.storage) == 1, len(t.storage))
+ self.failUnless(t.storage == [{'Content-type':5}], t.storage)
# now we set another value
t.content_type = "text/plain"
self.failUnless(t.content_type == "text/plain", t.content_type)
self.failUnless(t.settings["Content-type"] == "text/plain",
t.settings["Content-type"])
+ self.failUnless(t.load_count == 2, t.load_count)
+ self.failUnless(len(t.storage) == 2, len(t.storage))
+ self.failUnless(t.storage == [{'Content-type':5},
+ {'Content-type':'text/plain'}],
+ t.storage)
+ # t._get_saved_settings() returns a dict of required or
+ # non-default values.
self.failUnless(t._get_saved_settings() == \
{"Content-type":"text/plain"},
t._get_saved_settings())
# now we clear to the post-primed value
t.content_type = EMPTY
- self.failUnless(t._settings_loaded == True, t._settings_loaded)
self.failUnless(t.settings["Content-type"] == EMPTY,
t.settings["Content-type"])
self.failUnless(t.content_type == None, t.content_type)
self.failUnless(len(t.settings) == 1, len(t.settings))
self.failUnless(t.settings["Content-type"] == EMPTY,
t.settings["Content-type"])
+ self.failUnless(t._get_saved_settings() == {},
+ t._get_saved_settings())
+ self.failUnless(t.storage == [{'Content-type':5},
+ {'Content-type':'text/plain'},
+ {}],
+ t.storage)
+ def testSimplePropertyFromStorage(self):
+ """Testing a minimal versioned property from storage"""
+ class Test (TestObject):
+ settings_properties = []
+ required_saved_properties = []
+ @versioned_property(
+ name="prop-a",
+ doc="A test property",
+ settings_properties=settings_properties,
+ required_saved_properties=required_saved_properties)
+ def prop_a(): return {}
+ @versioned_property(
+ name="prop-b",
+ doc="Another test property",
+ settings_properties=settings_properties,
+ required_saved_properties=required_saved_properties)
+ def prop_b(): return {}
+ t = Test()
+ t.storage.append({'prop-a':'saved'})
+ # setting prop-b forces a load (to check for changes),
+ # which also pulls in prop-a.
+ t.prop_b = 'new-b'
+ settings = {'prop-b':'new-b', 'prop-a':'saved'}
+ self.failUnless(t.settings == settings, t.settings)
+ self.failUnless(t._get_saved_settings() == settings,
+ t._get_saved_settings())
+ # test that _get_saved_settings() works even when settings
+ # were _not_ loaded beforehand
+ t = Test()
+ t.storage.append({'prop-a':'saved'})
+ settings ={'prop-a':'saved'}
+ self.failUnless(t.settings == {}, t.settings)
+ self.failUnless(t._get_saved_settings() == settings,
+ t._get_saved_settings())
+ def testSimplePropertySetStorageSave(self):
+ """Set a property, then attach storage and save"""
+ class Test (TestObject):
+ settings_properties = []
+ required_saved_properties = []
+ @versioned_property(
+ name="prop-a",
+ doc="A test property",
+ settings_properties=settings_properties,
+ required_saved_properties=required_saved_properties)
+ def prop_a(): return {}
+ @versioned_property(
+ name="prop-b",
+ doc="Another test property",
+ settings_properties=settings_properties,
+ required_saved_properties=required_saved_properties)
+ def prop_b(): return {}
+ t = Test()
+ storage = t.storage
+ t.storage = None
+ t.prop_a = 'text/html'
+ t.storage = storage
+ t.save_settings()
+ self.failUnless(t.prop_a == 'text/html', t.prop_a)
+ self.failUnless(t.settings == {'prop-a':'text/html',
+ 'prop-b':EMPTY},
+ t.settings)
+ self.failUnless(t.load_count == 1, t.load_count)
+ self.failUnless(len(t.storage) == 1, len(t.storage))
+ self.failUnless(t.storage == [{'prop-a':'text/html'}],
+ t.storage)
def testDefaultingProperty(self):
"""Testing a defaulting versioned property"""
- class Test(SavedSettingsObject):
+ class Test (TestObject):
settings_properties = []
required_saved_properties = []
- @versioned_property(name="Content-type",
- doc="A test property",
- default="text/plain",
- settings_properties=settings_properties,
- required_saved_properties= \
- required_saved_properties)
+ @versioned_property(
+ name="Content-type",
+ doc="A test property",
+ default="text/plain",
+ settings_properties=settings_properties,
+ required_saved_properties=required_saved_properties)
def content_type(): return {}
- def __init__(self):
- SavedSettingsObject.__init__(self)
t = Test()
- self.failUnless(t._settings_loaded == False, t._settings_loaded)
+ self.failUnless(t.settings == {}, t.settings)
self.failUnless(t.content_type == "text/plain", t.content_type)
- self.failUnless(t._settings_loaded == False, t._settings_loaded)
- t.load_settings()
- self.failUnless(t._settings_loaded == True, t._settings_loaded)
- self.failUnless(t.content_type == "text/plain", t.content_type)
- self.failUnless(t.settings["Content-type"] == EMPTY,
- t.settings["Content-type"])
- self.failUnless(t._get_saved_settings() ==
- {"Content-type":"text/plain"},
+ self.failUnless(t.settings == {"Content-type":EMPTY},
+ t.settings)
+ self.failUnless(t.load_count == 1, t.load_count)
+ self.failUnless(len(t.storage) == 0, len(t.storage))
+ self.failUnless(t._get_saved_settings() == {},
t._get_saved_settings())
t.content_type = "text/html"
self.failUnless(t.content_type == "text/html",
t.content_type)
- self.failUnless(t.settings["Content-type"] == "text/html",
- t.settings["Content-type"])
+ self.failUnless(t.settings == {"Content-type":"text/html"},
+ t.settings)
+ self.failUnless(t.load_count == 1, t.load_count)
+ self.failUnless(len(t.storage) == 1, len(t.storage))
+ self.failUnless(t.storage == [{'Content-type':'text/html'}],
+ t.storage)
self.failUnless(t._get_saved_settings() == \
{"Content-type":"text/html"},
t._get_saved_settings())
def testRequiredDefaultingProperty(self):
"""Testing a required defaulting versioned property"""
- class Test(SavedSettingsObject):
+ class Test (TestObject):
settings_properties = []
required_saved_properties = []
- @versioned_property(name="Content-type",
- doc="A test property",
- default="text/plain",
- settings_properties=settings_properties,
- required_saved_properties= \
- required_saved_properties,
- require_save=True)
+ @versioned_property(
+ name="Content-type",
+ doc="A test property",
+ default="text/plain",
+ settings_properties=settings_properties,
+ required_saved_properties=required_saved_properties,
+ require_save=True)
def content_type(): return {}
- def __init__(self):
- SavedSettingsObject.__init__(self)
t = Test()
+ self.failUnless(t.settings == {}, t.settings)
+ self.failUnless(t.content_type == "text/plain", t.content_type)
+ self.failUnless(t.settings == {"Content-type":EMPTY},
+ t.settings)
+ self.failUnless(t.load_count == 1, t.load_count)
+ self.failUnless(len(t.storage) == 0, len(t.storage))
self.failUnless(t._get_saved_settings() == \
{"Content-type":"text/plain"},
t._get_saved_settings())
t.content_type = "text/html"
+ self.failUnless(t.content_type == "text/html",
+ t.content_type)
+ self.failUnless(t.settings == {"Content-type":"text/html"},
+ t.settings)
+ self.failUnless(t.load_count == 1, t.load_count)
+ self.failUnless(len(t.storage) == 1, len(t.storage))
+ self.failUnless(t.storage == [{'Content-type':'text/html'}],
+ t.storage)
self.failUnless(t._get_saved_settings() == \
{"Content-type":"text/html"},
t._get_saved_settings())
def testClassVersionedPropertyDefinition(self):
"""Testing a class-specific _versioned property decorator"""
- class Test(SavedSettingsObject):
+ class Test (TestObject):
settings_properties = []
required_saved_properties = []
- def _versioned_property(settings_properties= \
- settings_properties,
- required_saved_properties= \
- required_saved_properties,
- **kwargs):
+ def _versioned_property(
+ settings_properties=settings_properties,
+ required_saved_properties=required_saved_properties,
+ **kwargs):
if "settings_properties" not in kwargs:
kwargs["settings_properties"] = settings_properties
if "required_saved_properties" not in kwargs:
@@ -377,69 +528,60 @@ if libbe.TESTING == True:
required_saved_properties
return versioned_property(**kwargs)
@_versioned_property(name="Content-type",
- doc="A test property",
- default="text/plain",
- require_save=True)
+ doc="A test property",
+ default="text/plain",
+ require_save=True)
def content_type(): return {}
- def __init__(self):
- SavedSettingsObject.__init__(self)
t = Test()
self.failUnless(t._get_saved_settings() == \
{"Content-type":"text/plain"},
t._get_saved_settings())
+ self.failUnless(t.load_count == 1, t.load_count)
+ self.failUnless(len(t.storage) == 0, len(t.storage))
t.content_type = "text/html"
self.failUnless(t._get_saved_settings() == \
{"Content-type":"text/html"},
t._get_saved_settings())
+ self.failUnless(t.load_count == 1, t.load_count)
+ self.failUnless(len(t.storage) == 1, len(t.storage))
+ self.failUnless(t.storage == [{'Content-type':'text/html'}],
+ t.storage)
def testMutableChangeHookedProperty(self):
"""Testing a mutable change-hooked property"""
- SAVES = []
- def prop_log_save_settings(self, old, new, saves=SAVES):
- saves.append("'%s' -> '%s'" % (str(old), str(new)))
- prop_save_settings(self, old, new)
- class Test(SavedSettingsObject):
+ class Test (TestObject):
settings_properties = []
required_saved_properties = []
- @versioned_property(name="List-type",
- doc="A test property",
- mutable=True,
- change_hook=prop_log_save_settings,
- settings_properties=settings_properties,
- required_saved_properties= \
- required_saved_properties)
+ @versioned_property(
+ name="List-type",
+ doc="A test property",
+ mutable=True,
+ change_hook=prop_save_settings,
+ settings_properties=settings_properties,
+ required_saved_properties=required_saved_properties)
def list_type(): return {}
- def __init__(self):
- SavedSettingsObject.__init__(self)
t = Test()
- self.failUnless(t._settings_loaded == False, t._settings_loaded)
- t.load_settings()
- self.failUnless(SAVES == [], SAVES)
- self.failUnless(t._settings_loaded == True, t._settings_loaded)
+ self.failUnless(len(t.storage) == 0, len(t.storage))
self.failUnless(t.list_type == None, t.list_type)
- self.failUnless(SAVES == [], SAVES)
+ self.failUnless(len(t.storage) == 0, len(t.storage))
self.failUnless(t.settings["List-type"]==EMPTY,
t.settings["List-type"])
t.list_type = []
self.failUnless(t.settings["List-type"] == [],
t.settings["List-type"])
- self.failUnless(SAVES == [
- "'<class 'libbe.storage.util.settings_object.EMPTY'>' -> '[]'"
- ], SAVES)
- t.list_type.append(5)
- self.failUnless(SAVES == [
- "'<class 'libbe.storage.util.settings_object.EMPTY'>' -> '[]'",
- ], SAVES)
+ self.failUnless(len(t.storage) == 1, len(t.storage))
+ self.failUnless(t.storage == [{'List-type':[]}],
+ t.storage)
+ t.list_type.append(5) # external modification not detected yet
+ self.failUnless(len(t.storage) == 1, len(t.storage))
+ self.failUnless(t.storage == [{'List-type':[]}],
+ t.storage)
self.failUnless(t.settings["List-type"] == [5],
t.settings["List-type"])
- self.failUnless(SAVES == [ # the append(5) has not yet been saved
- "'<class 'libbe.storage.util.settings_object.EMPTY'>' -> '[]'",
- ], SAVES)
- self.failUnless(t.list_type == [5], t.list_type)#get triggers saved
-
- self.failUnless(SAVES == [ # now the append(5) has been saved.
- "'<class 'libbe.storage.util.settings_object.EMPTY'>' -> '[]'",
- "'[]' -> '[5]'"
- ], SAVES)
+ self.failUnless(t.list_type == [5], t.list_type)# get triggers save
+ self.failUnless(len(t.storage) == 2, len(t.storage))
+ self.failUnless(t.storage == [{'List-type':[]},
+ {'List-type':[5]}],
+ t.storage)
unitsuite = unittest.TestLoader().loadTestsFromTestCase( \
SavedSettingsObjectTests)
diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py
index 9fc43c1..7d4383f 100644
--- a/libbe/storage/vcs/base.py
+++ b/libbe/storage/vcs/base.py
@@ -504,6 +504,12 @@ os.listdir(self.get_path("bugs")):
"""
pass
+ def _vcs_exists(self, path, revision=None):
+ """
+ Does the path exist in a given revision? (True/False)
+ """
+ raise NotImplementedError
+
def _vcs_remove(self, path):
"""
Remove the file at path from version control. Optionally
@@ -550,7 +556,7 @@ os.listdir(self.get_path("bugs")):
def _vcs_path(self, id, revision):
"""
- Return the path to object id as of revision.
+ Return the relative path to object id as of revision.
Revision will not be None.
"""
@@ -694,6 +700,17 @@ os.listdir(self.get_path("bugs")):
def _disconnect(self):
self._cached_path_id.disconnect()
+ def path(self, id, revision=None, relpath=True):
+ if revision == None:
+ path = self._cached_path_id.path(id)
+ if relpath == True:
+ return self._u_rel_path(path)
+ return path
+ path = self._vcs_path(id, revision)
+ if relpath == True:
+ return path
+ return os.path.join(self.repo, path)
+
def _add_path(self, path, directory=False):
relpath = self._u_rel_path(path)
reldirs = relpath.split(os.path.sep)
@@ -716,6 +733,16 @@ os.listdir(self.get_path("bugs")):
path = self._cached_path_id.add_id(id, parent)
self._add_path(path, **kwargs)
+ def _exists(self, id, revision=None):
+ if revision == None:
+ try:
+ path = self.path(id, revision, relpath=False)
+ except InvalidID, e:
+ return False
+ return os.path.exists(path)
+ path = self.path(id, revision, relpath=True)
+ return self._vcs_exists(relpath, revision)
+
def _remove(self, id):
path = self._cached_path_id.path(id)
if os.path.exists(path):
@@ -746,15 +773,10 @@ os.listdir(self.get_path("bugs")):
self._cached_path_id.remove_id(id)
def _ancestors(self, id=None, revision=None):
- if revision == None:
- id_to_path = self._cached_path_id.path
- else:
- id_to_path = lambda id : os.path.join(
- self.repo, self._vcs_path(id, revision))
if id==None:
path = self.be_dir
else:
- path = id_to_path(id)
+ path = self.path(id, revision, relpath=False)
ancestors = []
while True:
if not path.startswith(self.repo + os.path.sep):
@@ -769,12 +791,9 @@ os.listdir(self.get_path("bugs")):
def _children(self, id=None, revision=None):
if revision == None:
- id_to_path = self._cached_path_id.path
isdir = os.path.isdir
listdir = os.listdir
else:
- id_to_path = lambda id : os.path.join(
- self.repo, self._vcs_path(id, revision))
isdir = lambda path : self._vcs_isdir(
self._u_rel_path(path), revision)
listdir = lambda path : self._vcs_listdir(
@@ -782,7 +801,7 @@ os.listdir(self.get_path("bugs")):
if id==None:
path = self.be_dir
else:
- path = id_to_path(id)
+ path = self.path(id, revision, relpath=False)
if isdir(path) == False:
return []
children = listdir(path)
@@ -810,25 +829,18 @@ os.listdir(self.get_path("bugs")):
def _get(self, id, default=libbe.util.InvalidObject, revision=None):
try:
- path = self._cached_path_id.path(id)
+ relpath = self.path(id, revision, relpath=True)
+ contents = self._vcs_get_file_contents(relpath, revision)
except InvalidID, e:
if default == libbe.util.InvalidObject:
raise e
return default
- relpath = self._u_rel_path(path)
- try:
- contents = self._vcs_get_file_contents(relpath, revision)
- except InvalidID, e:
- if e.id == None:
- e.id = id
- if e.revision == None:
- e.revision = revision
- raise
if contents in [libbe.storage.base.InvalidDirectory,
- libbe.util.InvalidObject]:
- raise InvalidID(id, revision)
- elif len(contents) == 0:
- return None
+ libbe.util.InvalidObject] \
+ or len(contents) == 0:
+ if default == libbe.util.InvalidObject:
+ raise InvalidID(id, revision)
+ return default
return contents
def _set(self, id, value):
diff --git a/libbe/storage/vcs/bzr.py b/libbe/storage/vcs/bzr.py
index e1cd2e5..1db50f8 100644
--- a/libbe/storage/vcs/bzr.py
+++ b/libbe/storage/vcs/bzr.py
@@ -98,6 +98,13 @@ class Bzr(base.VCS):
cmd.outf = StringIO.StringIO()
cmd.run(file_list=[path], file_ids_from=self.repo)
+ def _vcs_exists(self, path, revision=None):
+ manifest = self._vcs_listdir(
+ self.repo, revision=revision, recursive=True)
+ if path in manifest:
+ return True
+ return False
+
def _vcs_remove(self, path):
# --force to also remove unversioned files.
path = os.path.join(self.repo, path)
@@ -131,7 +138,7 @@ class Bzr(base.VCS):
if 'not present in revision' in str(e):
raise base.InvalidPath(path, root=self.repo, revision=revision)
raise
- return cmd.outf.getvalue()
+ return cmd.outf.getvalue()
def _vcs_path(self, id, revision):
manifest = self._vcs_listdir(
diff --git a/test_usage.sh b/test_usage.sh
index 13be2ff..9b7dafe 100755
--- a/test_usage.sh
+++ b/test_usage.sh
@@ -4,8 +4,8 @@
# features work, and gives an example of suggested usage to get people
# started.
#
-# usage: test_usage.sh RCS
-# where RCS is one of:
+# usage: test_usage.sh VCS
+# where VCS is one of:
# bzr, git, hg, arch, none
#
# Note that this script uses the *installed* version of be, not the
@@ -18,34 +18,33 @@ set -v # verbose, echo commands to stdout
exec 6>&2 # save stderr to file descriptor 6
exec 2>&1 # fd 2 now writes to stdout
-ONLY_TEST_COMMIT="true"
-
if [ $# -gt 1 ]
then
- echo "usage: test_usage.sh [RCS]"
+ echo "usage: test_usage.sh [VCS]"
echo ""
- echo "where RCS is one of"
- for RCS in arch bzr darcs git hg none
+ echo "where VCS is one of"
+ for VCS in arch bzr darcs git hg none
do
- echo " $RCS"
+ echo " $VCS"
done
exit 1
elif [ $# -eq 0 ]
then
- for RCS in arch bzr darcs git hg none
+ for VCS in arch bzr darcs git hg none
do
- echo -e "\n\nTesting $RCS\n\n"
- $0 "$RCS" || exit 1
+ echo -e "\n\nTesting $VCS\n\n"
+ $0 "$VCS" || exit 1
done
exit 0
fi
-RCS="$1"
+VCS="$1"
TESTDIR=`mktemp -d /tmp/BEtest.XXXXXXXXXX`
cd $TESTDIR
-if [ "$RCS" == "arch" ]
+# Initialize the VCS repository
+if [ "$VCS" == "arch" ]
then
ID=`tla my-id`
ARCH_PARAM_DIR="$HOME/.arch-params"
@@ -64,73 +63,79 @@ then
sed -i 's/^source .*/source ^[._=a-zA-X0-9].*$/' '{arch}/=tagging-method'
echo "tla import -A $ARCH_ARCHIVE --summary 'Began versioning'"
tla import -A $ARCH_ARCHIVE --summary 'Began versioning'
-elif [ "$RCS" == "bzr" ]
+elif [ "$VCS" == "bzr" ]
then
ID=`bzr whoami`
bzr init
-elif [ "$RCS" == "darcs" ]
+elif [ "$VCS" == "darcs" ]
then
if [ -z "$DARCS_EMAIL" ]; then
export DARCS_EMAIL="J. Doe <jdoe@example.com>"
fi
ID="$DARCS_EMAIL"
darcs init
-elif [ "$RCS" == "git" ]
+elif [ "$VCS" == "git" ]
then
NAME=`git config user.name`
EMAIL=`git config user.email`
ID="$NAME <$EMAIL>"
git init
-elif [ "$RCS" == "hg" ]
+elif [ "$VCS" == "hg" ]
then
ID=`hg showconfig ui.username`
hg init
-elif [ "$RCS" == "none" ]
+elif [ "$VCS" == "none" ]
then
ID=`id -nu`
else
- echo "Unrecognized RCS '$RCS'"
+ echo "Unrecognized VCS '$VCS'"
exit 1
fi
+
if [ -z "$ID" ]
-then # set a default ID
+then # set a default ID for VCSs that aren't tracking one yet.
ID="John Doe <jdoe@example.com>"
fi
echo "I am '$ID'"
-be init
-OUT=`be new 'having too much fun'`
+be init # initialize the Bugs Everywhere repository
+OUT=`be new 'having too much fun'` # create a new bug
echo "$OUT"
BUG=`echo "$OUT" | sed -n 's/Created bug with ID //p'`
echo "Working with bug: $BUG"
be comment $BUG "This is an argument"
-be set user_id "$ID" # get tired of guessing user id for none RCS
+#be set user_id "$ID" # get tired of guessing user id for none VCS
be set # show settings
-be comment $BUG:1 "No it isn't" # comment on the first comment
+be comment $BUG/ "No it isn't" # comment on the first comment
be show $BUG # show details on a given bug
-be close $BUG # set bug status to 'closed'
+be status closed $BUG # set bug status to 'closed'
be comment $BUG "It's closed, but I can still comment."
-be open $BUG # set bug status to 'open'
+if [ "$VCS" != 'none' ]; then
+ be commit 'Initial commit'
+fi
+be status open $BUG # set bug status to 'open'
be comment $BUG "Reopend, comment again"
-be status $BUG fixed # set bug status to 'fixed'
+be status fixed $BUG # set bug status to 'fixed'
be list # list all open bugs
be list --status fixed # list all fixed bugs
-be assign $BUG # assign the bug to yourself
-be list -m -s fixed # see fixed bugs assigned to you
-be assign $BUG 'Joe' # assign the bug to Joe
-be list -a Joe -s fixed # list the fixed bugs assigned to Joe
-be assign $BUG none # assign the bug to noone
-be diff # see what has changed
+be assign - $BUG # assign the bug to yourself
+be list -m --status fixed # see fixed bugs assigned to you
+be assign 'Joe' $BUG # assign the bug to Joe
+be list -a Joe --status fixed # list the fixed bugs assigned to Joe
+be assign none $BUG # un-assign the bug
+if [ "$VCS" != 'none' ]; then
+ be diff # see what has changed
+fi
OUT=`be new 'also having too much fun'`
BUGB=`echo "$OUT" | sed -n 's/Created bug with ID //p'`
be comment $BUGB "Blissfully unaware of a similar bug"
be merge $BUG $BUGB # join BUGB to BUG
-be show $BUG # show bug details & comments
+be --no-pager show $BUG # show bug details & comments
# you can also export/import XML bugs/comments
OUT=`be new 'yet more fun'`
BUGC=`echo "$OUT" | sed -n 's/Created bug with ID //p'`
be comment $BUGC "The ants go marching..."
-be show --xml $BUGC | be comment --xml ${BUG}:2 -
+be show --xml $BUGC/ | be import-xml --add-only --comment-root $BUG -
be remove $BUG # decide that you don't like that bug after all
be commit "You can even commit using BE"
be commit --allow-empty "And you can add empty commits if you like"
@@ -139,7 +144,7 @@ be commit "But this will fail" || echo "Failed"
cd /
rm -rf $TESTDIR
-if [ "$RCS" == "arch" ]
+if [ "$VCS" == "arch" ]
then
# Cleanup everything outside of TESTDIR
rm -rf "$ARCH_ARCHIVE_ROOT"