From 91b284cde8c4443cf0997798f4f847ce95409cd3 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 14 Nov 2008 00:32:38 -0500 Subject: Changed __get/setattribute__ calls to get/setattr() calls. See http://www.python.org/doc/2.5.2/lib/built-in-funcs.html#l2h-33 http://www.python.org/doc/2.5.2/lib/built-in-funcs.html#l2h-66 --- libbe/bugdir.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'libbe/bugdir.py') diff --git a/libbe/bugdir.py b/libbe/bugdir.py index 427ed38..bcc163c 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -198,7 +198,7 @@ class InvalidValue(Exception): def checked_property(name, valid): def getter(self): - value = self.__getattribute__("_"+name) + value = getattr(self, "_"+name) if value not in valid: raise InvalidValue(name, value) return value @@ -206,7 +206,7 @@ def checked_property(name, valid): def setter(self, value): if value not in valid: raise InvalidValue(name, value) - return self.__setattr__("_"+name, value) + return setattr(self, "_"+name, value) return property(getter, setter) severity_levels = ("wishlist", "minor", "serious", "critical", "fatal") @@ -254,7 +254,7 @@ class Bug(object): active = property(_get_active) def add_attr(self, map, name): - value = self.__getattribute__(name) + value = getattr(self, name) if value is not None: map[name] = value @@ -336,7 +336,7 @@ def add_attrs(obj, map, names, map_names=None): map_names[name] = name for name in names: - value = obj.__getattribute__(name) + value = getattr(obj, name) if value is not None: map[map_names[name]] = value -- cgit From 87e356c9208e955fcf6c20c0b271db87bdd48014 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 14 Nov 2008 19:25:44 -0500 Subject: Split Bug and Comment class out to bug.py from bugdir.py Comment should probably have it's own file too... I also tried to clean up the interface for setting status and severity. Both attributes involve selecting strings from predefined lists. The lists of valid strings (and descriptions of each string) are now defined in bug.py. The bug.py lists are then used to generate appropriate help strings in becommands/status.py and severity.py. This should make it easier to keep the help strings in synch with the validation information. The original status strings weren't documented, and I didn't know what they all ment, so I elimanted some of them. 'in-progress' and 'disabled' are no longer with us. Of course, it would be simple to add them back in if people don't agree with me on that. Due to the loss of 'disabled' I had to change the status of two bugs (11e and 597) to 'closed'. I removed becommands/inprogress.py as well. It's functionality was replaced by the more general status.py command, which mimics the severity.py command. --- libbe/bugdir.py | 262 ++------------------------------------------------------ 1 file changed, 8 insertions(+), 254 deletions(-) (limited to 'libbe/bugdir.py') diff --git a/libbe/bugdir.py b/libbe/bugdir.py index bcc163c..7570bb3 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -23,6 +23,7 @@ import mapfile import time import utility from rcs import rcs_by_name +from bug import Bug class NoBugDir(Exception): def __init__(self, path): @@ -108,7 +109,8 @@ def create_bug_dir(path, rcs): raise rcs.mkdir(os.path.join(root, "bugs")) set_version(root, rcs) - map_save(rcs, os.path.join(root, "settings"), {"rcs_name": rcs.name}) + mapfile.map_save(rcs, + os.path.join(root, "settings"), {"rcs_name": rcs.name}) return BugDir(os.path.join(path, ".be")) @@ -137,8 +139,8 @@ class BugDir: self.dir = dir self.bugs_path = os.path.join(self.dir, "bugs") try: - self.settings = map_load(os.path.join(self.dir, "settings")) - except NoSuchFile: + self.settings = mapfile.map_load(os.path.join(self.dir, "settings")) + except mapfile.NoSuchFile: self.settings = {"rcs_name": "None"} rcs_name = setting_property("rcs_name", ("None", "bzr", "git", "Arch", "hg")) @@ -147,7 +149,8 @@ class BugDir: target = setting_property("target") def save_settings(self): - map_save(self.rcs, os.path.join(self.dir, "settings"), self.settings) + mapfile.map_save(self.rcs, + os.path.join(self.dir, "settings"), self.settings) def get_rcs(self): if self._rcs is not None and self.rcs_name == self._rcs.name: @@ -188,258 +191,9 @@ class BugDir: bug.uuid = uuid return bug -class InvalidValue(Exception): +class InvalidValue(ValueError): def __init__(self, name, value): msg = "Cannot assign value %s to %s" % (value, name) Exception.__init__(self, msg) self.name = name self.value = value - - -def checked_property(name, valid): - def getter(self): - value = getattr(self, "_"+name) - if value not in valid: - raise InvalidValue(name, value) - return value - - def setter(self, value): - if value not in valid: - raise InvalidValue(name, value) - return setattr(self, "_"+name, value) - return property(getter, setter) - -severity_levels = ("wishlist", "minor", "serious", "critical", "fatal") -active_status = ("open", "in-progress", "waiting", "new", "verified") -inactive_status = ("closed", "disabled", "fixed", "wontfix", "waiting") - -severity_value = {} -for i in range(len(severity_levels)): - severity_value[severity_levels[i]] = i - -class Bug(object): - status = checked_property("status", (None,)+active_status+inactive_status) - severity = checked_property("severity", (None, "wishlist", "minor", - "serious", "critical", "fatal")) - - def __init__(self, path, uuid, rcs_name): - self.path = path - self.uuid = uuid - if uuid is not None: - dict = map_load(self.get_path("values")) - else: - dict = {} - - self.rcs_name = rcs_name - - self.summary = dict.get("summary") - self.creator = dict.get("creator") - self.target = dict.get("target") - self.status = dict.get("status") - self.severity = dict.get("severity") - self.assigned = dict.get("assigned") - self.time = dict.get("time") - if self.time is not None: - self.time = utility.str_to_time(self.time) - - def __repr__(self): - return "Bug(uuid=%r)" % self.uuid - - def get_path(self, file): - return os.path.join(self.path, self.uuid, file) - - def _get_active(self): - return self.status in active_status - - active = property(_get_active) - - def add_attr(self, map, name): - value = getattr(self, name) - if value is not None: - map[name] = value - - def save(self): - map = {} - self.add_attr(map, "assigned") - self.add_attr(map, "summary") - self.add_attr(map, "creator") - self.add_attr(map, "target") - self.add_attr(map, "status") - self.add_attr(map, "severity") - if self.time is not None: - map["time"] = utility.time_to_str(self.time) - path = self.get_path("values") - map_save(rcs_by_name(self.rcs_name), path, map) - - def _get_rcs(self): - return rcs_by_name(self.rcs_name) - - rcs = property(_get_rcs) - - def new_comment(self): - if not os.path.exists(self.get_path("comments")): - self.rcs.mkdir(self.get_path("comments")) - comm = Comment(None, self) - comm.uuid = names.uuid() - return comm - - def get_comment(self, uuid): - return Comment(uuid, self) - - def iter_comment_ids(self): - path = self.get_path("comments") - if not os.path.isdir(path): - return - try: - for uuid in os.listdir(path): - if (uuid.startswith('.')): - continue - yield uuid - except OSError, e: - if e.errno != errno.ENOENT: - raise - return - - def list_comments(self): - comments = [Comment(id, self) for id in self.iter_comment_ids()] - comments.sort(cmp_date) - return comments - -def cmp_date(comm1, comm2): - return cmp(comm1.date, comm2.date) - -def new_bug(dir, uuid=None): - bug = dir.new_bug(uuid) - bug.creator = names.creator() - bug.severity = "minor" - bug.status = "open" - bug.time = time.time() - return bug - -def new_comment(bug, body=None): - comm = bug.new_comment() - comm.From = names.creator() - comm.date = time.time() - comm.body = body - return comm - -def add_headers(obj, map, names): - map_names = {} - for name in names: - map_names[name] = pyname_to_header(name) - add_attrs(obj, map, names, map_names) - -def add_attrs(obj, map, names, map_names=None): - if map_names is None: - map_names = {} - for name in names: - map_names[name] = name - - for name in names: - value = getattr(obj, name) - if value is not None: - map[map_names[name]] = value - - -class Comment(object): - def __init__(self, uuid, bug): - object.__init__(self) - self.uuid = uuid - self.bug = bug - if self.uuid is not None and self.bug is not None: - mapfile = map_load(self.get_path("values")) - self.date = utility.str_to_time(mapfile["Date"]) - self.From = mapfile["From"] - self.in_reply_to = mapfile.get("In-reply-to") - self.content_type = mapfile.get("Content-type", "text/plain") - self.body = file(self.get_path("body")).read().decode("utf-8") - else: - self.date = None - self.From = None - self.in_reply_to = None - self.content_type = "text/plain" - self.body = None - - def save(self): - map_file = {"Date": utility.time_to_str(self.date)} - add_headers(self, map_file, ("From", "in_reply_to", "content_type")) - if not os.path.exists(self.get_path(None)): - self.bug.rcs.mkdir(self.get_path(None)) - map_save(self.bug.rcs, self.get_path("values"), map_file) - self.bug.rcs.set_file_contents(self.get_path("body"), - self.body.encode('utf-8')) - - - def get_path(self, name): - my_dir = os.path.join(self.bug.get_path("comments"), self.uuid) - if name is None: - return my_dir - return os.path.join(my_dir, name) - - -def thread_comments(comments): - child_map = {} - top_comments = [] - for comment in comments: - child_map[comment.uuid] = [] - for comment in comments: - if comment.in_reply_to is None or comment.in_reply_to not in child_map: - top_comments.append(comment) - continue - child_map[comment.in_reply_to].append(comment) - - def recurse_children(comment): - child_list = [] - for child in child_map[comment.uuid]: - child_list.append(recurse_children(child)) - return (comment, child_list) - return [recurse_children(c) for c in top_comments] - - -def pyname_to_header(name): - return name.capitalize().replace('_', '-') - - -def map_save(rcs, path, map): - """Save the map as a mapfile to the specified path""" - add = not os.path.exists(path) - output = file(path, "wb") - mapfile.generate(output, map) - if add: - rcs.add_id(path) - -class NoSuchFile(Exception): - def __init__(self, pathname): - Exception.__init__(self, "No such file: %s" % pathname) - - -def map_load(path): - try: - return mapfile.parse(file(path, "rb")) - except IOError, e: - if e.errno != errno.ENOENT: - raise e - raise NoSuchFile(path) - - -class MockBug: - def __init__(self, severity): - self.severity = severity - -def cmp_severity(bug_1, bug_2): - """ - Compare the severity levels of two bugs, with more sever bugs comparing - as less. - - >>> cmp_severity(MockBug(None), MockBug(None)) - 0 - >>> cmp_severity(MockBug("wishlist"), MockBug(None)) < 0 - True - >>> cmp_severity(MockBug(None), MockBug("wishlist")) > 0 - True - >>> cmp_severity(MockBug("critical"), MockBug("wishlist")) < 0 - True - """ - val_1 = severity_value.get(bug_1.severity) - val_2 = severity_value.get(bug_2.severity) - return -cmp(val_1, val_2) -- cgit From 03011f286420d8e091052019ee41eba021041e61 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 15 Nov 2008 18:35:41 -0500 Subject: Moved libbe.cmdutil.bug_summary() to libbe.bug.Bug.string(). This seems like a natual place for a function that only operates on Bugs. --- libbe/bugdir.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'libbe/bugdir.py') diff --git a/libbe/bugdir.py b/libbe/bugdir.py index 7570bb3..f8f45b8 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -174,7 +174,7 @@ class BugDir: return bugs def get_bug(self, uuid): - return Bug(self.bugs_path, uuid, self.rcs_name) + return Bug(self.bugs_path, uuid, self.rcs_name, self) def list_uuids(self): for uuid in os.listdir(self.bugs_path): @@ -187,7 +187,7 @@ class BugDir: uuid = names.uuid() path = os.path.join(self.bugs_path, uuid) self.rcs.mkdir(path) - bug = Bug(self.bugs_path, None, self.rcs_name) + bug = Bug(self.bugs_path, None, self.rcs_name, self) bug.uuid = uuid return bug -- cgit From 19b153b9a86377a2b30cc80fa3f475fed892e2fe Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 18 Nov 2008 20:42:50 -0500 Subject: Major rewrite of RCS backends. RCS now represented as a class. Lots of changes and just one commit. This started with bug dac91856-cb6a-4f69-8c03-38ff0b29aab2, when I noticed that new bugs were not being added appropriately with the Git backend. I'd been working with Git trouble before with bug 0cad2ac6-76ef-4a88-abdf-b2e02de76f5c, and decided things would be better off if I just scrapped the current RCS architecture and went to a more object oriented setup. So I did. It's not clear how to add support for an RCS backend: * Create a new module that - defines an inheritor of rsc.RCS, overriding the _rcs_*() methods - provide a new() function for instantizating the new class - defines an inheritor of rcs.RCStestCase, overiding the Class attribute - defines 'suite' a unittest.TestSuite testing the module * Add your new module to the rest in rcs._get_matching_rcs() * Add your new module to the rest in libbe/tests.py Although I'm not sure libbe/tests.py is still usefull. The new framework clears out a bunch of hackery that used to be involved with supporting becommands/diff.py. There's still room for progress though. While implementing the new verision, I moved the testing framework over from doctest to a doctest/unittest combination. Longer tests that don't demonstrate a function's usage should be moved to unittests at the end of the module, since unittest has better support for setup/teardown, etc. The new framework also revealed some underimplented backends, most notably arch. These backends have now been fixed. I also tweaked the test_usage.sh script to run through all the backends if it is called with no arguments. The fix for the dac bug turned out to be an unflushed file write :p. --- libbe/bugdir.py | 97 ++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 76 insertions(+), 21 deletions(-) (limited to 'libbe/bugdir.py') diff --git a/libbe/bugdir.py b/libbe/bugdir.py index f8f45b8..cf8cba5 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -18,11 +18,13 @@ import os import os.path import cmdutil import errno +import unittest +import doctest import names import mapfile import time import utility -from rcs import rcs_by_name +from rcs import rcs_by_name, installed_rcs from bug import Bug class NoBugDir(Exception): @@ -84,22 +86,20 @@ class AlreadyInitialized(Exception): Exception.__init__(self, "Specified root is already initialized: %s" % path) +def bugdir_root(versioning_root): + return os.path.join(versioning_root, ".be") + def create_bug_dir(path, rcs): """ - >>> import no_rcs, tests - >>> create_bug_dir('/highly-unlikely-to-exist', no_rcs) + >>> import tests + >>> rcs = rcs_by_name("None") + >>> create_bug_dir('/highly-unlikely-to-exist', rcs) Traceback (most recent call last): NoRootEntry: Specified root does not exist: /highly-unlikely-to-exist - >>> test_dir = os.path.dirname(tests.bug_arch_dir().dir) - >>> try: - ... create_bug_dir(test_dir, no_rcs) - ... except AlreadyInitialized, e: - ... print "Already Initialized" - Already Initialized """ root = os.path.join(path, ".be") try: - rcs.mkdir(root, paranoid=True) + rcs.mkdir(root) except OSError, e: if e.errno == errno.ENOENT: raise NoRootEntry(path) @@ -111,7 +111,7 @@ def create_bug_dir(path, rcs): set_version(root, rcs) mapfile.map_save(rcs, os.path.join(root, "settings"), {"rcs_name": rcs.name}) - return BugDir(os.path.join(path, ".be")) + return BugDir(bugdir_root(path)) def setting_property(name, valid=None): @@ -139,11 +139,12 @@ class BugDir: self.dir = dir self.bugs_path = os.path.join(self.dir, "bugs") try: - self.settings = mapfile.map_load(os.path.join(self.dir, "settings")) + self.settings = mapfile.map_load(os.path.join(self.dir,"settings")) except mapfile.NoSuchFile: self.settings = {"rcs_name": "None"} - rcs_name = setting_property("rcs_name", ("None", "bzr", "git", "Arch", "hg")) + rcs_name = setting_property("rcs_name", + ("None", "bzr", "git", "Arch", "hg")) _rcs = None target = setting_property("target") @@ -152,16 +153,21 @@ class BugDir: mapfile.map_save(self.rcs, os.path.join(self.dir, "settings"), self.settings) - def get_rcs(self): - if self._rcs is not None and self.rcs_name == self._rcs.name: - return self._rcs + def _get_rcs(self): + if self._rcs is not None: + if self.rcs_name == self._rcs.name: + return self._rcs self._rcs = rcs_by_name(self.rcs_name) + self._rcs.root(self.dir) return self._rcs - rcs = property(get_rcs) + rcs = property(_get_rcs) + + def duplicate_bugdir(self, revision): + return BugDir(bugdir_root(self.rcs.duplicate_repo(revision))) - def get_reference_bugdir(self, spec): - return BugDir(self.rcs.path_in_reference(self.dir, spec)) + def remove_duplicate_bugdir(self): + self.rcs.remove_duplicate_repo() def list(self): for uuid in self.list_uuids(): @@ -174,7 +180,7 @@ class BugDir: return bugs def get_bug(self, uuid): - return Bug(self.bugs_path, uuid, self.rcs_name, self) + return Bug(self.bugs_path, uuid, self.rcs, self) def list_uuids(self): for uuid in os.listdir(self.bugs_path): @@ -187,7 +193,7 @@ class BugDir: uuid = names.uuid() path = os.path.join(self.bugs_path, uuid) self.rcs.mkdir(path) - bug = Bug(self.bugs_path, None, self.rcs_name, self) + bug = Bug(self.bugs_path, None, self.rcs, self) bug.uuid = uuid return bug @@ -197,3 +203,52 @@ class InvalidValue(ValueError): Exception.__init__(self, msg) self.name = name self.value = value + +def simple_bug_dir(): + """ + For testing + >>> bugdir = simple_bug_dir() + >>> ls = list(bugdir.list_uuids()) + >>> ls.sort() + >>> print ls + ['a', 'b'] + """ + dir = utility.Dir() + rcs = installed_rcs() + rcs.init(dir.path) + assert os.path.exists(dir.path) + bugdir = create_bug_dir(dir.path, rcs) + bugdir._dir_ref = dir # postpone cleanup since dir.__del__() removes dir. + bug_a = bugdir.new_bug("a") + bug_a.summary = "Bug A" + bug_a.save() + bug_b = bugdir.new_bug("b") + bug_b.status = "closed" + bug_b.summary = "Bug B" + bug_b.save() + return bugdir + + +class BugDirTestCase(unittest.TestCase): + def __init__(self, *args, **kwargs): + unittest.TestCase.__init__(self, *args, **kwargs) + def setUp(self): + self.dir = utility.Dir() + self.rcs = installed_rcs() + self.rcs.init(self.dir.path) + self.bugdir = create_bug_dir(self.dir.path, self.rcs) + def tearDown(self): + del(self.rcs) + del(self.dir) + def fullPath(self, path): + return os.path.join(self.dir.path, path) + def assertPathExists(self, path): + fullpath = self.fullPath(path) + self.failUnless(os.path.exists(fullpath)==True, + "path %s does not exist" % fullpath) + def testBugDirDuplicate(self): + self.assertRaises(AlreadyInitialized, create_bug_dir, + self.dir.path, self.rcs) + +unitsuite = unittest.TestLoader().loadTestsFromTestCase(BugDirTestCase) +suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) -- cgit From 35b7aaa33c1826d7b39dc8a0f32100f5c0c5788d Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 19 Nov 2008 07:57:57 -0500 Subject: Moved bug.new_bug code into bugdir.BugDir.new_bug. Also removed explicit comparisons from beweb/controllers.py, since they are now built into the Bug.__cmp__ method. --- libbe/bugdir.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'libbe/bugdir.py') diff --git a/libbe/bugdir.py b/libbe/bugdir.py index cf8cba5..41f0fec 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -195,6 +195,10 @@ class BugDir: self.rcs.mkdir(path) bug = Bug(self.bugs_path, None, self.rcs, self) bug.uuid = uuid + bug.creator = self.rcs.get_user_id() + bug.severity = "minor" + bug.status = "open" + bug.time = time.time() return bug class InvalidValue(ValueError): -- cgit From 23179f50092d91dbeab97ad2b88cdaadb79b615f Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 21 Nov 2008 14:56:05 -0500 Subject: Another major rewrite. Now BugDir, Bug, and Comment are more distinct. I pushed a lot of the little helper functions into the main classes, which makes it easier for me to keep track of what's going on. I'm now at the point where I can run through `python test.py` with each of the backends (by changing the search order in rcs.py _get_matching_rcs) without any unexpected errors for each backend (except Arch). I can also run `test_usage.sh` without non-Arch errors either. However, don't consider this a stable commit yet. The bzr backend is *really*slow*, and the other's aren't blazingly fast either. I think I'm rewriting the entire database every time I save it :p. Still, it passes the checks. and I don't like it when zounds of changes build up. --- libbe/bugdir.py | 356 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 222 insertions(+), 134 deletions(-) (limited to 'libbe/bugdir.py') diff --git a/libbe/bugdir.py b/libbe/bugdir.py index 41f0fec..6152e3f 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -16,16 +16,17 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os import os.path -import cmdutil import errno +import time import unittest import doctest -import names + +from beuuid import uuid_gen import mapfile -import time +import bug +import cmdutil import utility -from rcs import rcs_by_name, installed_rcs -from bug import Bug +from rcs import rcs_by_name, detect_rcs, installed_rcs, PathNotInRoot class NoBugDir(Exception): def __init__(self, path): @@ -33,48 +34,6 @@ class NoBugDir(Exception): Exception.__init__(self, msg) self.path = path - -def iter_parent_dirs(cur_dir): - cur_dir = os.path.realpath(cur_dir) - old_dir = None - while True: - yield cur_dir - old_dir = cur_dir - cur_dir = os.path.normpath(os.path.join(cur_dir, '..')) - if old_dir == cur_dir: - break; - - -def tree_root(dir, old_version=False): - for rootdir in iter_parent_dirs(dir): - versionfile=os.path.join(rootdir, ".be", "version") - if os.path.exists(versionfile): - if not old_version: - test_version(versionfile) - return BugDir(os.path.join(rootdir, ".be")) - elif not os.path.exists(rootdir): - raise NoRootEntry(rootdir) - old_rootdir = rootdir - rootdir=os.path.join('..', rootdir) - - raise NoBugDir(dir) - -class BadTreeVersion(Exception): - def __init__(self, version): - Exception.__init__(self, "Unsupported tree version: %s" % version) - self.version = version - -def test_version(path): - tree_version = file(path, "rb").read() - if tree_version != TREE_VERSION_STRING: - raise BadTreeVersion(tree_version) - -def set_version(path, rcs): - rcs.set_file_contents(os.path.join(path, "version"), TREE_VERSION_STRING) - - -TREE_VERSION_STRING = "Bugs Everywhere Tree 1 0\n" - class NoRootEntry(Exception): def __init__(self, path): self.path = path @@ -86,32 +45,15 @@ class AlreadyInitialized(Exception): Exception.__init__(self, "Specified root is already initialized: %s" % path) -def bugdir_root(versioning_root): - return os.path.join(versioning_root, ".be") +class InvalidValue(ValueError): + def __init__(self, name, value): + msg = "Cannot assign value %s to %s" % (value, name) + Exception.__init__(self, msg) + self.name = name + self.value = value + -def create_bug_dir(path, rcs): - """ - >>> import tests - >>> rcs = rcs_by_name("None") - >>> create_bug_dir('/highly-unlikely-to-exist', rcs) - Traceback (most recent call last): - NoRootEntry: Specified root does not exist: /highly-unlikely-to-exist - """ - root = os.path.join(path, ".be") - try: - rcs.mkdir(root) - except OSError, e: - if e.errno == errno.ENOENT: - raise NoRootEntry(path) - elif e.errno == errno.EEXIST: - raise AlreadyInitialized(path) - else: - raise - rcs.mkdir(os.path.join(root, "bugs")) - set_version(root, rcs) - mapfile.map_save(rcs, - os.path.join(root, "settings"), {"rcs_name": rcs.name}) - return BugDir(bugdir_root(path)) +TREE_VERSION_STRING = "Bugs Everywhere Tree 1 0\n" def setting_property(name, valid=None): @@ -130,83 +72,232 @@ def setting_property(name, valid=None): del self.settings[name] else: self.settings[name] = value - self.save_settings() + self.save() return property(getter, setter) -class BugDir: - def __init__(self, dir): - self.dir = dir - self.bugs_path = os.path.join(self.dir, "bugs") +class BugDir (list): + def __init__(self, root=None, sink_to_existing_root=True, + assert_new_BugDir=False, allow_rcs_init=False, + loadNow=False, rcs=None): + list.__init__(self) + if root == None: + root = os.getcwd() + if sink_to_existing_root == True: + self.root = self.find_root(root) + else: + if not os.path.exists(root): + raise NoRootEntry(root) + self.root = root + if loadNow == True: + self.load() + else: + if assert_new_BugDir: + if os.path.exists(self.get_path()): + raise AlreadyInitialized, self.get_path() + if rcs == None: + rcs = self.guess_rcs(allow_rcs_init) + self.settings = {"rcs_name": self.rcs_name} + self.rcs_name = rcs.name + + def find_root(self, path): + """ + Search for an existing bug database dir and it's ancestors and + return a BugDir rooted there. + """ + if not os.path.exists(path): + raise NoRootEntry(path) + versionfile = utility.search_parent_directories(path, os.path.join(".be", "version")) + if versionfile != None: + beroot = os.path.dirname(versionfile) + root = os.path.dirname(beroot) + return root + else: + beroot = utility.search_parent_directories(path, ".be") + if beroot == None: + raise NoBugDir(path) + return beroot + + def get_version(self, path=None): + if path == None: + path = self.get_path("version") try: - self.settings = mapfile.map_load(os.path.join(self.dir,"settings")) - except mapfile.NoSuchFile: - self.settings = {"rcs_name": "None"} + tree_version = self.rcs.get_file_contents(path) + except AttributeError, e: + # haven't initialized rcs yet + tree_version = file(path, "rb").read().decode("utf-8") + return tree_version + + def set_version(self): + self.rcs.set_file_contents(self.get_path("version"), TREE_VERSION_STRING) rcs_name = setting_property("rcs_name", ("None", "bzr", "git", "Arch", "hg")) - _rcs = None - target = setting_property("target") - - def save_settings(self): - mapfile.map_save(self.rcs, - os.path.join(self.dir, "settings"), self.settings) + _rcs = None def _get_rcs(self): if self._rcs is not None: if self.rcs_name == self._rcs.name: return self._rcs self._rcs = rcs_by_name(self.rcs_name) - self._rcs.root(self.dir) + self._rcs.root(self.root) return self._rcs rcs = property(_get_rcs) + target = setting_property("target") + + def get_path(self, *args): + my_dir = os.path.join(self.root, ".be") + if len(args) == 0: + return my_dir + assert args[0] in ["version", "settings", "bugs"], str(args) + return os.path.join(my_dir, *args) + + def guess_rcs(self, allow_rcs_init=False): + deepdir = self.get_path() + if not os.path.exists(deepdir): + deepdir = os.path.dirname(deepdir) + rcs = detect_rcs(deepdir) + if rcs.name == "None": + if allow_rcs_init == True: + rcs = installed_rcs() + rcs.init(self.root) + self.settings = {"rcs_name": rcs.name} + self.rcs_name = rcs.name + return rcs + + def load(self): + version = self.get_version() + if version != TREE_VERSION_STRING: + raise NotImplementedError, "BugDir cannot handle version '%s' yet." % version + else: + if not os.path.exists(self.get_path()): + raise NoBugDir(self.get_path()) + self.settings = self._get_settings(self.get_path("settings")) + self._clear_bugs() + for uuid in self.list_uuids(): + self._load_bug(uuid) + + self._bug_map_gen() + + def save(self): + self.rcs.mkdir(self.get_path()) + self.set_version() + self._save_settings(self.get_path("settings"), self.settings) + self.rcs.mkdir(self.get_path("bugs")) + for bug in self: + bug.save() + + def _get_settings(self, settings_path): + try: + settings = mapfile.map_load(settings_path) + except mapfile.NoSuchFile: + settings = {"rcs_name": "None"} + return settings + + def _save_settings(self, settings_path, settings): + try: + mapfile.map_save(self.rcs, settings_path, settings) + except PathNotInRoot, e: + # Handling duplicate bugdir settings, special case + none_rcs = rcs_by_name("None") + none_rcs.root(settings_path) + mapfile.map_save(none_rcs, settings_path, settings) + def duplicate_bugdir(self, revision): - return BugDir(bugdir_root(self.rcs.duplicate_repo(revision))) + duplicate_path = self.rcs.duplicate_repo(revision) - def remove_duplicate_bugdir(self): - self.rcs.remove_duplicate_repo() + # setup revision RCS as None, since the duplicate may not be versioned + duplicate_settings_path = os.path.join(duplicate_path, ".be", "settings") + duplicate_settings = self._get_settings(duplicate_settings_path) + if "rcs_name" in duplicate_settings: + duplicate_settings["rcs_name"] = "None" + self._save_settings(duplicate_settings_path, duplicate_settings) - def list(self): - for uuid in self.list_uuids(): - yield self.get_bug(uuid) + return BugDir(duplicate_path, loadNow=True) - def bug_map(self): - bugs = {} - for bug in self.list(): - bugs[bug.uuid] = bug - return bugs + def remove_duplicate_bugdir(self): + self.rcs.remove_duplicate_repo() - def get_bug(self, uuid): - return Bug(self.bugs_path, uuid, self.rcs, self) + def _bug_map_gen(self): + map = {} + for bug in self: + map[bug.uuid] = bug + self.bug_map = map def list_uuids(self): - for uuid in os.listdir(self.bugs_path): + for uuid in os.listdir(self.get_path("bugs")): if (uuid.startswith('.')): continue yield uuid - def new_bug(self, uuid=None): - if uuid is None: - uuid = names.uuid() - path = os.path.join(self.bugs_path, uuid) - self.rcs.mkdir(path) - bug = Bug(self.bugs_path, None, self.rcs, self) - bug.uuid = uuid - bug.creator = self.rcs.get_user_id() - bug.severity = "minor" - bug.status = "open" - bug.time = time.time() - return bug + def _clear_bugs(self): + while len(self) > 0: + self.pop() + + def _load_bug(self, uuid): + bg = bug.Bug(bugdir=self, uuid=uuid, loadNow=True) + self.append(bg) + self._bug_map_gen() + return bg + + def new_bug(self, uuid=None, summary=None): + bg = bug.Bug(bugdir=self, uuid=uuid, summary=summary) + self.append(bg) + self._bug_map_gen() + return bg + + def remove_bug(self, bug): + self.remove(bug) + bug.remove() + + def bug_shortname(self, bug): + """ + Generate short names from uuids. Picks the minimum number of + characters (>=3) from the beginning of the uuid such that the + short names are unique. + + Obviously, as the number of bugs in the database grows, these + short names will cease to be unique. The complete uuid should be + used for long term reference. + """ + chars = 3 + for uuid in self.bug_map.keys(): + if bug.uuid == uuid: + continue + while (bug.uuid[:chars] == uuid[:chars]): + chars+=1 + return bug.uuid[:chars] + + def bug_from_shortname(self, shortname): + """ + >>> bd = simple_bug_dir() + >>> bug_a = bd.bug_from_shortname('a') + >>> print type(bug_a) + + >>> print bug_a + a:om: Bug A + """ + matches = [] + for bug in self: + if bug.uuid.startswith(shortname): + matches.append(bug) + if len(matches) > 1: + raise cmdutil.UserError("More than one bug matches %s. Please be more" + " specific." % shortname) + if len(matches) == 1: + return matches[0] + raise KeyError("No bug matches %s" % shortname) + + def bug_from_uuid(self, uuid): + if uuid not in self.bug_map: + self._bug_map_gen() + if uuid not in self.bug_map: + raise KeyError("No bug matches %s" % uuid +str(self.bug_map)+str(self)) + return self.bug_map[uuid] -class InvalidValue(ValueError): - def __init__(self, name, value): - msg = "Cannot assign value %s to %s" % (value, name) - Exception.__init__(self, msg) - self.name = name - self.value = value def simple_bug_dir(): """ @@ -218,18 +309,17 @@ def simple_bug_dir(): ['a', 'b'] """ dir = utility.Dir() - rcs = installed_rcs() - rcs.init(dir.path) assert os.path.exists(dir.path) - bugdir = create_bug_dir(dir.path, rcs) + bugdir = BugDir(dir.path, sink_to_existing_root=False, allow_rcs_init=True) bugdir._dir_ref = dir # postpone cleanup since dir.__del__() removes dir. - bug_a = bugdir.new_bug("a") - bug_a.summary = "Bug A" - bug_a.save() - bug_b = bugdir.new_bug("b") + bug_a = bugdir.new_bug("a", summary="Bug A") + bug_a.creator = "John Doe " + bug_a.time = 0 + bug_b = bugdir.new_bug("b", summary="Bug B") + bug_b.creator = "Jane Doe " + bug_b.time = 0 bug_b.status = "closed" - bug_b.summary = "Bug B" - bug_b.save() + bugdir.save() return bugdir @@ -238,9 +328,8 @@ class BugDirTestCase(unittest.TestCase): unittest.TestCase.__init__(self, *args, **kwargs) def setUp(self): self.dir = utility.Dir() - self.rcs = installed_rcs() - self.rcs.init(self.dir.path) - self.bugdir = create_bug_dir(self.dir.path, self.rcs) + self.bugdir = BugDir(self.dir.path, sink_to_existing_root=False, allow_rcs_init=True) + self.rcs = self.bugdir.rcs def tearDown(self): del(self.rcs) del(self.dir) @@ -250,9 +339,8 @@ class BugDirTestCase(unittest.TestCase): fullpath = self.fullPath(path) self.failUnless(os.path.exists(fullpath)==True, "path %s does not exist" % fullpath) - def testBugDirDuplicate(self): - self.assertRaises(AlreadyInitialized, create_bug_dir, - self.dir.path, self.rcs) + self.assertRaises(AlreadyInitialized, BugDir, + self.dir.path, assertNewBugDir=True) unitsuite = unittest.TestLoader().loadTestsFromTestCase(BugDirTestCase) suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) -- cgit From 6d4785e75e1552b3f04b1499fede6fdef2732c39 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 22 Nov 2008 16:15:16 -0500 Subject: Created and fixed bug 496edad5-1484-413a-bc68-4b01274a65eb. I figured out why Arch was complaining. For non-Arch users, file system access has been tweaked a bit see the BugDir doc string for details. Also, you should now set BugDir.rcs instead of .rcs_name. .rcs_name automatically tracks changes in .rcs (the reverse of the previous situation), so read from whichever you like. --- libbe/bugdir.py | 113 ++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 90 insertions(+), 23 deletions(-) (limited to 'libbe/bugdir.py') diff --git a/libbe/bugdir.py b/libbe/bugdir.py index 6152e3f..a552b0f 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -56,14 +56,14 @@ class InvalidValue(ValueError): TREE_VERSION_STRING = "Bugs Everywhere Tree 1 0\n" -def setting_property(name, valid=None): +def setting_property(name, valid=None, doc=None): def getter(self): value = self.settings.get(name) if valid is not None: if value not in valid: raise InvalidValue(name, value) return value - + def setter(self, value): if valid is not None: if value not in valid and value is not None: @@ -72,15 +72,29 @@ def setting_property(name, valid=None): del self.settings[name] else: self.settings[name] = value - self.save() - return property(getter, setter) + self._save_settings(self.get_path("settings"), self.settings) + + return property(getter, setter, doc=doc) class BugDir (list): + """ + File-system access: + When rooted in non-bugdir directory, BugDirs live completely in + memory until the first call to .save(). This creates a '.be' + sub-directory containing configurations options, bugs, comments, + etc. Once this sub-directory has been created (possibly by + another BugDir instance) any changes to the BugDir in memory will + be flushed to the file system automatically. However, the BugDir + will only load information from the file system when it loads new + bugs/comments that it doesn't already have in memory, or when it + explicitly asked to do so (e.g. .load() or __init__(loadNow=True)). + """ def __init__(self, root=None, sink_to_existing_root=True, assert_new_BugDir=False, allow_rcs_init=False, loadNow=False, rcs=None): list.__init__(self) + self.settings = {} if root == None: root = os.getcwd() if sink_to_existing_root == True: @@ -92,13 +106,12 @@ class BugDir (list): if loadNow == True: self.load() else: - if assert_new_BugDir: + if assert_new_BugDir == True: if os.path.exists(self.get_path()): raise AlreadyInitialized, self.get_path() if rcs == None: rcs = self.guess_rcs(allow_rcs_init) - self.settings = {"rcs_name": self.rcs_name} - self.rcs_name = rcs.name + self.rcs = rcs def find_root(self, path): """ @@ -132,21 +145,24 @@ class BugDir (list): self.rcs.set_file_contents(self.get_path("version"), TREE_VERSION_STRING) rcs_name = setting_property("rcs_name", - ("None", "bzr", "git", "Arch", "hg")) + ("None", "bzr", "git", "Arch", "hg"), + doc="The name of the current RCS. Kept seperate to make saving/loading settings easy. Don't set this attribute. Set .rcs instead, and .rcs_name will be automatically adjusted.") _rcs = None def _get_rcs(self): - if self._rcs is not None: - if self.rcs_name == self._rcs.name: - return self._rcs - self._rcs = rcs_by_name(self.rcs_name) - self._rcs.root(self.root) return self._rcs - rcs = property(_get_rcs) + def _set_rcs(self, rcs): + if rcs == None: + rcs = rcs_by_name("None") + self._rcs = rcs + rcs.root(self.root) + self.rcs_name = rcs.name - target = setting_property("target") + rcs = property(_get_rcs, _set_rcs, doc="A revision control system (RCS) instance") + + target = setting_property("target", doc="The current project development target") def get_path(self, *args): my_dir = os.path.join(self.root, ".be") @@ -160,12 +176,12 @@ class BugDir (list): if not os.path.exists(deepdir): deepdir = os.path.dirname(deepdir) rcs = detect_rcs(deepdir) + install = False if rcs.name == "None": if allow_rcs_init == True: rcs = installed_rcs() rcs.init(self.root) - self.settings = {"rcs_name": rcs.name} - self.rcs_name = rcs.name + self.rcs = rcs return rcs def load(self): @@ -176,6 +192,7 @@ class BugDir (list): if not os.path.exists(self.get_path()): raise NoBugDir(self.get_path()) self.settings = self._get_settings(self.get_path("settings")) + self.rcs = rcs_by_name(self.rcs_name) self._clear_bugs() for uuid in self.list_uuids(): self._load_bug(uuid) @@ -198,10 +215,17 @@ class BugDir (list): return settings def _save_settings(self, settings_path, settings): + if not os.path.exists(self.get_path()): + # don't save settings until the bug directory has been + # initialized. this initialization happens the first time + # a bug directory is saved (BugDir.save()). If the user + # is just working with a BugDir in memory, we don't want + # to go cluttering up his file system with settings files. + return try: mapfile.map_save(self.rcs, settings_path, settings) except PathNotInRoot, e: - # Handling duplicate bugdir settings, special case + # Special case for configuring duplicate bugdir settings none_rcs = rcs_by_name("None") none_rcs.root(settings_path) mapfile.map_save(none_rcs, settings_path, settings) @@ -209,7 +233,7 @@ class BugDir (list): def duplicate_bugdir(self, revision): duplicate_path = self.rcs.duplicate_repo(revision) - # setup revision RCS as None, since the duplicate may not be versioned + # setup revision RCS as None, since the duplicate may not be initialized for versioning duplicate_settings_path = os.path.join(duplicate_path, ".be", "settings") duplicate_settings = self._get_settings(duplicate_settings_path) if "rcs_name" in duplicate_settings: @@ -228,10 +252,18 @@ class BugDir (list): self.bug_map = map def list_uuids(self): - for uuid in os.listdir(self.get_path("bugs")): - if (uuid.startswith('.')): - continue - yield uuid + uuids = [] + if os.path.exists(self.get_path()): + # list the uuids on disk + for uuid in os.listdir(self.get_path("bugs")): + if not (uuid.startswith('.')): + uuids.append(uuid) + yield uuid + # and the ones that are still just in memory + for bug in self: + if bug.uuid not in uuids: + uuids.append(bug.uuid) + yield bug.uuid def _clear_bugs(self): while len(self) > 0: @@ -341,6 +373,41 @@ class BugDirTestCase(unittest.TestCase): "path %s does not exist" % fullpath) self.assertRaises(AlreadyInitialized, BugDir, self.dir.path, assertNewBugDir=True) + def versionTest(self): + if self.rcs.versioned == False: + return + original = self.bugdir.rcs.commit("Began versioning") + bugA = self.bugdir.bug_from_uuid("a") + bugA.status = "fixed" + self.bugdir.save() + new = self.rcs.commit("Fixed bug a") + dupdir = self.bugdir.duplicate_bugdir(original) + self.failUnless(dupdir.root != self.bugdir.root, "%s, %s" % (dupdir.root, self.bugdir.root)) + bugAorig = dupdir.bug_from_uuid("a") + self.failUnless(bugA != bugAorig, "\n%s\n%s" % (bugA.string(), bugAorig.string())) + bugAorig.status = "fixed" + self.failUnless(bug.cmp_status(bugA, bugAorig)==0, "%s, %s" % (bugA.status, bugAorig.status)) + self.failUnless(bug.cmp_severity(bugA, bugAorig)==0, "%s, %s" % (bugA.severity, bugAorig.severity)) + self.failUnless(bug.cmp_assigned(bugA, bugAorig)==0, "%s, %s" % (bugA.assigned, bugAorig.assigned)) + self.failUnless(bug.cmp_time(bugA, bugAorig)==0, "%s, %s" % (bugA.time, bugAorig.time)) + self.failUnless(bug.cmp_creator(bugA, bugAorig)==0, "%s, %s" % (bugA.creator, bugAorig.creator)) + self.failUnless(bugA == bugAorig, "\n%s\n%s" % (bugA.string(), bugAorig.string())) + self.bugdir.remove_duplicate_bugdir() + self.failUnless(os.path.exists(dupdir.root)==False, str(dupdir.root)) + def testRun(self): + self.bugdir.new_bug(uuid="a", summary="Ant") + self.bugdir.new_bug(uuid="b", summary="Cockroach") + self.bugdir.new_bug(uuid="c", summary="Praying mantis") + length = len(self.bugdir) + self.failUnless(length == 3, "%d != 3 bugs" % length) + uuids = list(self.bugdir.list_uuids()) + self.failUnless(len(uuids) == 3, "%d != 3 uuids" % len(uuids)) + self.failUnless(uuids == ["a","b","c"], str(uuids)) + bugA = self.bugdir.bug_from_uuid("a") + bugAprime = self.bugdir.bug_from_shortname("a") + self.failUnless(bugA == bugAprime, "%s != %s" % (bugA, bugAprime)) + self.bugdir.save() + self.versionTest() unitsuite = unittest.TestLoader().loadTestsFromTestCase(BugDirTestCase) suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) -- cgit From 4a626e67b3f401b8e242a55571a802147123a196 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 22 Nov 2008 19:45:37 -0500 Subject: Explicit rcs.cleanup() in bugdir test. Don't use del(rcs), because if there was an error, there is still a reference to rcs in the traceback, so it is never cleaned up. This can leave the external archive cluttering up your Arch install if you're using the Arch backend. See the __del__ documentation http://python.active-venture.com/ref/customization.html#l2h-175 for details. Also fixed some out-of-date method names in libbe.diff --- libbe/bugdir.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'libbe/bugdir.py') diff --git a/libbe/bugdir.py b/libbe/bugdir.py index a552b0f..cc21878 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -363,8 +363,8 @@ class BugDirTestCase(unittest.TestCase): self.bugdir = BugDir(self.dir.path, sink_to_existing_root=False, allow_rcs_init=True) self.rcs = self.bugdir.rcs def tearDown(self): - del(self.rcs) - del(self.dir) + self.rcs.cleanup() + self.dir.cleanup() def fullPath(self, path): return os.path.join(self.dir.path, path) def assertPathExists(self, path): @@ -410,4 +410,4 @@ class BugDirTestCase(unittest.TestCase): self.versionTest() unitsuite = unittest.TestLoader().loadTestsFromTestCase(BugDirTestCase) -suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) +suite = unittest.TestSuite([unitsuite])#, doctest.DocTestSuite()]) -- cgit From 1864e9d3736125ae80c7be33dd181d7636d912e7 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 23 Nov 2008 08:16:19 -0500 Subject: Added bugdir user-id caching and save/load from settings file. --- libbe/bugdir.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'libbe/bugdir.py') diff --git a/libbe/bugdir.py b/libbe/bugdir.py index cc21878..8c96cc6 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -18,6 +18,7 @@ import os import os.path import errno import time +import copy import unittest import doctest @@ -94,6 +95,7 @@ class BugDir (list): assert_new_BugDir=False, allow_rcs_init=False, loadNow=False, rcs=None): list.__init__(self) + self._save_user_id = False self.settings = {} if root == None: root = os.getcwd() @@ -112,6 +114,7 @@ class BugDir (list): if rcs == None: rcs = self.guess_rcs(allow_rcs_init) self.rcs = rcs + user_id = self.rcs.get_user_id() def find_root(self, path): """ @@ -162,8 +165,26 @@ class BugDir (list): rcs = property(_get_rcs, _set_rcs, doc="A revision control system (RCS) instance") + _user_id = setting_property("user-id", doc="The user's prefered name. Kept seperate to make saving/loading settings easy. Don't set this attribute. Set .user_id instead, and ._user_id will be automatically adjusted. This setting is only saved if ._save_user_id == True") + + def _get_user_id(self): + return self._user_id + + def _set_user_id(self, user_id): + if self.rcs != None: + self.rcs.user_id = user_id + self._user_id = user_id + + user_id = property(_get_user_id, _set_user_id, doc="The user's prefered name, e.g 'John Doe '. Not that the Arch RCS backend *enforces* ids with this format.") + target = setting_property("target", doc="The current project development target") + def save_user_id(self, user_id=None): + if user_id == None: + user_id = self.rcs.get_user_id() + self._save_user_id = True + self.user_id = user_id + def get_path(self, *args): my_dir = os.path.join(self.root, ".be") if len(args) == 0: @@ -192,7 +213,13 @@ class BugDir (list): if not os.path.exists(self.get_path()): raise NoBugDir(self.get_path()) self.settings = self._get_settings(self.get_path("settings")) + self.rcs = rcs_by_name(self.rcs_name) + if self.user_id != None: # there was a user name in the settings file + self._save_user_id = True + else: + self.user_id = self.rcs.get_user_id() + self._clear_bugs() for uuid in self.list_uuids(): self._load_bug(uuid) @@ -222,6 +249,10 @@ class BugDir (list): # is just working with a BugDir in memory, we don't want # to go cluttering up his file system with settings files. return + if self._save_user_id == False and settings == self.settings: + if "user-id" in settings: + settings = copy.copy(settings) + del settings["user-id"] try: mapfile.map_save(self.rcs, settings_path, settings) except PathNotInRoot, e: -- cgit From 333fc7968794deff9aa7a7a91d72cf17763df855 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 23 Nov 2008 08:43:40 -0500 Subject: Improved user-id saving/loading/caching & save user-id into duplicate bugdirs. Fixes the duplicate bugs a403de79-8f39-41f2-b9ec-15053b175ee2 c894f10f-197d-4b22-9c5b-19f394df40d4 --- libbe/bugdir.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) (limited to 'libbe/bugdir.py') diff --git a/libbe/bugdir.py b/libbe/bugdir.py index 8c96cc6..2501a27 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -168,6 +168,8 @@ class BugDir (list): _user_id = setting_property("user-id", doc="The user's prefered name. Kept seperate to make saving/loading settings easy. Don't set this attribute. Set .user_id instead, and ._user_id will be automatically adjusted. This setting is only saved if ._save_user_id == True") def _get_user_id(self): + if self._user_id == None and self.rcs != None: + self._user_id = self.rcs.get_user_id() return self._user_id def _set_user_id(self, user_id): @@ -181,7 +183,7 @@ class BugDir (list): def save_user_id(self, user_id=None): if user_id == None: - user_id = self.rcs.get_user_id() + user_id = self.user_id self._save_user_id = True self.user_id = user_id @@ -215,10 +217,8 @@ class BugDir (list): self.settings = self._get_settings(self.get_path("settings")) self.rcs = rcs_by_name(self.rcs_name) - if self.user_id != None: # there was a user name in the settings file - self._save_user_id = True - else: - self.user_id = self.rcs.get_user_id() + if self._user_id != None: # there was a user name in the settings file + self.save_user_id() self._clear_bugs() for uuid in self.list_uuids(): @@ -242,17 +242,19 @@ class BugDir (list): return settings def _save_settings(self, settings_path, settings): - if not os.path.exists(self.get_path()): - # don't save settings until the bug directory has been - # initialized. this initialization happens the first time - # a bug directory is saved (BugDir.save()). If the user - # is just working with a BugDir in memory, we don't want - # to go cluttering up his file system with settings files. - return - if self._save_user_id == False and settings == self.settings: - if "user-id" in settings: - settings = copy.copy(settings) - del settings["user-id"] + this_dir_path = os.path.realpath(self.get_path("settings")) + if os.path.realpath(settings_path) == this_dir_path: + if not os.path.exists(self.get_path()): + # don't save settings until the bug directory has been + # initialized. this initialization happens the first time + # a bug directory is saved (BugDir.save()). If the user + # is just working with a BugDir in memory, we don't want + # to go cluttering up his file system with settings files. + return + if self._save_user_id == False: + if "user-id" in settings: + settings = copy.copy(settings) + del settings["user-id"] try: mapfile.map_save(self.rcs, settings_path, settings) except PathNotInRoot, e: @@ -269,6 +271,7 @@ class BugDir (list): duplicate_settings = self._get_settings(duplicate_settings_path) if "rcs_name" in duplicate_settings: duplicate_settings["rcs_name"] = "None" + duplicate_settings["user-id"] = self.user_id self._save_settings(duplicate_settings_path, duplicate_settings) return BugDir(duplicate_path, loadNow=True) -- cgit From 510c9f33393c1f222ee56732c026f229ed8ae49d Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 23 Nov 2008 09:50:56 -0500 Subject: Go back to lazy bug loading to get execution speed back up. Fixes bug b3c6da51-3a30-42c9-8c75-587c7a1705c5 --- libbe/bugdir.py | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) (limited to 'libbe/bugdir.py') diff --git a/libbe/bugdir.py b/libbe/bugdir.py index 2501a27..779de6b 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -89,11 +89,11 @@ class BugDir (list): be flushed to the file system automatically. However, the BugDir will only load information from the file system when it loads new bugs/comments that it doesn't already have in memory, or when it - explicitly asked to do so (e.g. .load() or __init__(loadNow=True)). + explicitly asked to do so (e.g. .load() or __init__(from_disk=True)). """ def __init__(self, root=None, sink_to_existing_root=True, assert_new_BugDir=False, allow_rcs_init=False, - loadNow=False, rcs=None): + from_disk=False, rcs=None): list.__init__(self) self._save_user_id = False self.settings = {} @@ -105,7 +105,7 @@ class BugDir (list): if not os.path.exists(root): raise NoRootEntry(root) self.root = root - if loadNow == True: + if from_disk == True: self.load() else: if assert_new_BugDir == True: @@ -217,15 +217,17 @@ class BugDir (list): self.settings = self._get_settings(self.get_path("settings")) self.rcs = rcs_by_name(self.rcs_name) - if self._user_id != None: # there was a user name in the settings file - self.save_user_id() - - self._clear_bugs() - for uuid in self.list_uuids(): - self._load_bug(uuid) + if self._user_id != None: # was a user name in the settings file + self.save_user_id() self._bug_map_gen() + def load_all_bugs(self): + "Warning: this could take a while." + self._clear_bugs() + for uuid in self.list_uuids(): + self._load_bug(uuid) + def save(self): self.rcs.mkdir(self.get_path()) self.set_version() @@ -274,7 +276,7 @@ class BugDir (list): duplicate_settings["user-id"] = self.user_id self._save_settings(duplicate_settings_path, duplicate_settings) - return BugDir(duplicate_path, loadNow=True) + return BugDir(duplicate_path, from_disk=True) def remove_duplicate_bugdir(self): self.rcs.remove_duplicate_repo() @@ -283,6 +285,9 @@ class BugDir (list): map = {} for bug in self: map[bug.uuid] = bug + for uuid in self.list_uuids(): + if uuid not in map: + map[uuid] = None self.bug_map = map def list_uuids(self): @@ -304,7 +309,7 @@ class BugDir (list): self.pop() def _load_bug(self, uuid): - bg = bug.Bug(bugdir=self, uuid=uuid, loadNow=True) + bg = bug.Bug(bugdir=self, uuid=uuid, from_disk=True) self.append(bg) self._bug_map_gen() return bg @@ -347,14 +352,15 @@ class BugDir (list): a:om: Bug A """ matches = [] - for bug in self: - if bug.uuid.startswith(shortname): - matches.append(bug) + self._bug_map_gen() + for uuid in self.bug_map.keys(): + if uuid.startswith(shortname): + matches.append(uuid) if len(matches) > 1: - raise cmdutil.UserError("More than one bug matches %s. Please be more" - " specific." % shortname) + raise cmdutil.UserError("More than one bug matches %s. " + "Please be more specific." % shortname) if len(matches) == 1: - return matches[0] + return self.bug_from_uuid(matches[0]) raise KeyError("No bug matches %s" % shortname) def bug_from_uuid(self, uuid): @@ -362,6 +368,8 @@ class BugDir (list): self._bug_map_gen() if uuid not in self.bug_map: raise KeyError("No bug matches %s" % uuid +str(self.bug_map)+str(self)) + if self.bug_map[uuid] == None: + self._load_bug(uuid) return self.bug_map[uuid] -- cgit From 8517f6856983b81363155e4b8ce58deb549e333b Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 23 Nov 2008 10:58:40 -0500 Subject: Removed outdated beuuid import from libbe/bugdir.py --- libbe/bugdir.py | 1 - 1 file changed, 1 deletion(-) (limited to 'libbe/bugdir.py') diff --git a/libbe/bugdir.py b/libbe/bugdir.py index 779de6b..0a3e164 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -22,7 +22,6 @@ import copy import unittest import doctest -from beuuid import uuid_gen import mapfile import bug import cmdutil -- cgit From d80720fcad22215cdbd1f2b39434945364ba11d5 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 23 Nov 2008 14:46:51 -0500 Subject: Created bugdir.MultipleBugMatches so bugdir no longer imports cmdutil. --- libbe/bugdir.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'libbe/bugdir.py') diff --git a/libbe/bugdir.py b/libbe/bugdir.py index 0a3e164..e134a2c 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -24,7 +24,6 @@ import doctest import mapfile import bug -import cmdutil import utility from rcs import rcs_by_name, detect_rcs, installed_rcs, PathNotInRoot @@ -52,6 +51,14 @@ class InvalidValue(ValueError): self.name = name self.value = value +class MultipleBugMatches(ValueError): + def __init__(self, shortname, matches): + msg = ("More than one bug matches %s. " + "Please be more specific.\n%s" % shortname, matches) + ValueError.__init__(self, msg) + self.shortname = shortnamename + self.matches = matches + TREE_VERSION_STRING = "Bugs Everywhere Tree 1 0\n" @@ -356,8 +363,7 @@ class BugDir (list): if uuid.startswith(shortname): matches.append(uuid) if len(matches) > 1: - raise cmdutil.UserError("More than one bug matches %s. " - "Please be more specific." % shortname) + raise MultipleBugMatches(shortname, matches) if len(matches) == 1: return self.bug_from_uuid(matches[0]) raise KeyError("No bug matches %s" % shortname) -- cgit From 63a7726eba738fe2ed340027039ba655ff91898a Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 24 Nov 2008 07:09:03 -0500 Subject: Added 'allow_no_rcs' flag to RCS file system access methods. Now mapfile access has fewer special cases, and there is less redundant rcs.add/update code. --- libbe/bugdir.py | 132 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 82 insertions(+), 50 deletions(-) (limited to 'libbe/bugdir.py') diff --git a/libbe/bugdir.py b/libbe/bugdir.py index e134a2c..175f518 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -25,7 +25,8 @@ import doctest import mapfile import bug import utility -from rcs import rcs_by_name, detect_rcs, installed_rcs, PathNotInRoot +import rcs + class NoBugDir(Exception): def __init__(self, path): @@ -67,18 +68,16 @@ def setting_property(name, valid=None, doc=None): def getter(self): value = self.settings.get(name) if valid is not None: - if value not in valid: + if value not in valid and value != None: raise InvalidValue(name, value) return value def setter(self, value): - if valid is not None: - if value not in valid and value is not None: - raise InvalidValue(name, value) - if value is None: - del self.settings[name] - else: - self.settings[name] = value + if value != getter(self): + if value is None: + del self.settings[name] + else: + self.settings[name] = value self._save_settings(self.get_path("settings"), self.settings) return property(getter, setter, doc=doc) @@ -151,27 +150,36 @@ class BugDir (list): return tree_version def set_version(self): - self.rcs.set_file_contents(self.get_path("version"), TREE_VERSION_STRING) + self.rcs.set_file_contents(self.get_path("version"), + TREE_VERSION_STRING) rcs_name = setting_property("rcs_name", ("None", "bzr", "git", "Arch", "hg"), - doc="The name of the current RCS. Kept seperate to make saving/loading settings easy. Don't set this attribute. Set .rcs instead, and .rcs_name will be automatically adjusted.") + doc= +"""The name of the current RCS. Kept seperate to make saving/loading +settings easy. Don't set this attribute. Set .rcs instead, and +.rcs_name will be automatically adjusted.""") _rcs = None def _get_rcs(self): return self._rcs - def _set_rcs(self, rcs): - if rcs == None: - rcs = rcs_by_name("None") - self._rcs = rcs - rcs.root(self.root) - self.rcs_name = rcs.name + def _set_rcs(self, new_rcs): + if new_rcs == None: + new_rcs = rcs.rcs_by_name("None") + self._rcs = new_rcs + new_rcs.root(self.root) + self.rcs_name = new_rcs.name - rcs = property(_get_rcs, _set_rcs, doc="A revision control system (RCS) instance") + rcs = property(_get_rcs, _set_rcs, + doc="A revision control system (RCS) instance") - _user_id = setting_property("user-id", doc="The user's prefered name. Kept seperate to make saving/loading settings easy. Don't set this attribute. Set .user_id instead, and ._user_id will be automatically adjusted. This setting is only saved if ._save_user_id == True") + _user_id = setting_property("user-id", doc= +"""The user's prefered name. Kept seperate to make saving/loading +settings easy. Don't set this attribute. Set .user_id instead, +and ._user_id will be automatically adjusted. This setting is +only saved if ._save_user_id == True""") def _get_user_id(self): if self._user_id == None and self.rcs != None: @@ -183,9 +191,12 @@ class BugDir (list): self.rcs.user_id = user_id self._user_id = user_id - user_id = property(_get_user_id, _set_user_id, doc="The user's prefered name, e.g 'John Doe '. Not that the Arch RCS backend *enforces* ids with this format.") + user_id = property(_get_user_id, _set_user_id, doc= +"""The user's prefered name, e.g 'John Doe '. Note +that the Arch RCS backend *enforces* ids with this format.""") - target = setting_property("target", doc="The current project development target") + target = setting_property("target", + doc="The current project development target") def save_user_id(self, user_id=None): if user_id == None: @@ -204,25 +215,26 @@ class BugDir (list): deepdir = self.get_path() if not os.path.exists(deepdir): deepdir = os.path.dirname(deepdir) - rcs = detect_rcs(deepdir) + new_rcs = rcs.detect_rcs(deepdir) install = False - if rcs.name == "None": + if new_rcs.name == "None": if allow_rcs_init == True: - rcs = installed_rcs() - rcs.init(self.root) - self.rcs = rcs - return rcs + new_rcs = rcs.installed_rcs() + new_rcs.init(self.root) + self.rcs = new_rcs + return new_rcs def load(self): version = self.get_version() if version != TREE_VERSION_STRING: - raise NotImplementedError, "BugDir cannot handle version '%s' yet." % version + raise NotImplementedError, \ + "BugDir cannot handle version '%s' yet." % version else: if not os.path.exists(self.get_path()): raise NoBugDir(self.get_path()) self.settings = self._get_settings(self.get_path("settings")) - self.rcs = rcs_by_name(self.rcs_name) + self.rcs = rcs.rcs_by_name(self.rcs_name) # set real RCS if self._user_id != None: # was a user name in the settings file self.save_user_id() @@ -243,9 +255,20 @@ class BugDir (list): bug.save() def _get_settings(self, settings_path): + if self.rcs_name == None: + # Use a temporary RCS to loading settings the first time + RCS = rcs.rcs_by_name("None") + RCS.root(self.root) + else: + RCS = self.rcs + + allow_no_rcs = not RCS.path_in_root(settings_path) + # allow_no_rcs=True should only be for the special case of + # configuring duplicate bugdir settings + try: - settings = mapfile.map_load(settings_path) - except mapfile.NoSuchFile: + settings = mapfile.map_load(RCS, settings_path, allow_no_rcs) + except rcs.NoSuchFile: settings = {"rcs_name": "None"} return settings @@ -263,19 +286,18 @@ class BugDir (list): if "user-id" in settings: settings = copy.copy(settings) del settings["user-id"] - try: - mapfile.map_save(self.rcs, settings_path, settings) - except PathNotInRoot, e: - # Special case for configuring duplicate bugdir settings - none_rcs = rcs_by_name("None") - none_rcs.root(settings_path) - mapfile.map_save(none_rcs, settings_path, settings) + allow_no_rcs = not self.rcs.path_in_root(settings_path) + # allow_no_rcs=True should only be for the special case of + # configuring duplicate bugdir settings + mapfile.map_save(self.rcs, settings_path, settings, allow_no_rcs) def duplicate_bugdir(self, revision): duplicate_path = self.rcs.duplicate_repo(revision) - # setup revision RCS as None, since the duplicate may not be initialized for versioning - duplicate_settings_path = os.path.join(duplicate_path, ".be", "settings") + # setup revision RCS as None, since the duplicate may not be + # initialized for versioning + duplicate_settings_path = os.path.join(duplicate_path, + ".be", "settings") duplicate_settings = self._get_settings(duplicate_settings_path) if "rcs_name" in duplicate_settings: duplicate_settings["rcs_name"] = "None" @@ -372,7 +394,8 @@ class BugDir (list): if uuid not in self.bug_map: self._bug_map_gen() if uuid not in self.bug_map: - raise KeyError("No bug matches %s" % uuid +str(self.bug_map)+str(self)) + raise KeyError("No bug matches %s\n bug map: %s\n root: %s" \ + % (uuid, self.bug_map, self.root)) if self.bug_map[uuid] == None: self._load_bug(uuid) return self.bug_map[uuid] @@ -407,7 +430,8 @@ class BugDirTestCase(unittest.TestCase): unittest.TestCase.__init__(self, *args, **kwargs) def setUp(self): self.dir = utility.Dir() - self.bugdir = BugDir(self.dir.path, sink_to_existing_root=False, allow_rcs_init=True) + self.bugdir = BugDir(self.dir.path, sink_to_existing_root=False, + allow_rcs_init=True) self.rcs = self.bugdir.rcs def tearDown(self): self.rcs.cleanup() @@ -429,16 +453,24 @@ class BugDirTestCase(unittest.TestCase): self.bugdir.save() new = self.rcs.commit("Fixed bug a") dupdir = self.bugdir.duplicate_bugdir(original) - self.failUnless(dupdir.root != self.bugdir.root, "%s, %s" % (dupdir.root, self.bugdir.root)) + self.failUnless(dupdir.root != self.bugdir.root, + "%s, %s" % (dupdir.root, self.bugdir.root)) bugAorig = dupdir.bug_from_uuid("a") - self.failUnless(bugA != bugAorig, "\n%s\n%s" % (bugA.string(), bugAorig.string())) + self.failUnless(bugA != bugAorig, + "\n%s\n%s" % (bugA.string(), bugAorig.string())) bugAorig.status = "fixed" - self.failUnless(bug.cmp_status(bugA, bugAorig)==0, "%s, %s" % (bugA.status, bugAorig.status)) - self.failUnless(bug.cmp_severity(bugA, bugAorig)==0, "%s, %s" % (bugA.severity, bugAorig.severity)) - self.failUnless(bug.cmp_assigned(bugA, bugAorig)==0, "%s, %s" % (bugA.assigned, bugAorig.assigned)) - self.failUnless(bug.cmp_time(bugA, bugAorig)==0, "%s, %s" % (bugA.time, bugAorig.time)) - self.failUnless(bug.cmp_creator(bugA, bugAorig)==0, "%s, %s" % (bugA.creator, bugAorig.creator)) - self.failUnless(bugA == bugAorig, "\n%s\n%s" % (bugA.string(), bugAorig.string())) + self.failUnless(bug.cmp_status(bugA, bugAorig)==0, + "%s, %s" % (bugA.status, bugAorig.status)) + self.failUnless(bug.cmp_severity(bugA, bugAorig)==0, + "%s, %s" % (bugA.severity, bugAorig.severity)) + self.failUnless(bug.cmp_assigned(bugA, bugAorig)==0, + "%s, %s" % (bugA.assigned, bugAorig.assigned)) + self.failUnless(bug.cmp_time(bugA, bugAorig)==0, + "%s, %s" % (bugA.time, bugAorig.time)) + self.failUnless(bug.cmp_creator(bugA, bugAorig)==0, + "%s, %s" % (bugA.creator, bugAorig.creator)) + self.failUnless(bugA == bugAorig, + "\n%s\n%s" % (bugA.string(), bugAorig.string())) self.bugdir.remove_duplicate_bugdir() self.failUnless(os.path.exists(dupdir.root)==False, str(dupdir.root)) def testRun(self): -- cgit From d248dbca39e1e8a26a5aa9d39b28038690a406a0 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 24 Nov 2008 07:31:51 -0500 Subject: Replaced direct filesystem read from bugdir.py with RCS mediated read. Also replaced utility.FileString with StringIO() in cmdutil.py, which allowed the removal of utility.FileString and utility.get_file. The only remaining file().read() outside the RCS framework is the read in utility.editor_string(), but should probably not go through the RCS. --- libbe/bugdir.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'libbe/bugdir.py') diff --git a/libbe/bugdir.py b/libbe/bugdir.py index 175f518..d3b7e61 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -140,13 +140,16 @@ class BugDir (list): return beroot def get_version(self, path=None): + if self.rcs_name == None: + # Use a temporary RCS to check the version for the first time + RCS = rcs.rcs_by_name("None") + RCS.root(self.root) + else: + RCS = self.rcs + if path == None: path = self.get_path("version") - try: - tree_version = self.rcs.get_file_contents(path) - except AttributeError, e: - # haven't initialized rcs yet - tree_version = file(path, "rb").read().decode("utf-8") + tree_version = RCS.get_file_contents(path) return tree_version def set_version(self): @@ -234,7 +237,7 @@ that the Arch RCS backend *enforces* ids with this format.""") raise NoBugDir(self.get_path()) self.settings = self._get_settings(self.get_path("settings")) - self.rcs = rcs.rcs_by_name(self.rcs_name) # set real RCS + self.rcs = rcs.rcs_by_name(self.rcs_name) if self._user_id != None: # was a user name in the settings file self.save_user_id() -- cgit From 5fd5bc89a2ec270d3e0b01f583012eaaf7750693 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 24 Nov 2008 07:47:06 -0500 Subject: Added Bug.comments(), BugDir.has_bug() & cleaned up diff.diff(). + some other minor fixes and cleanups. --- libbe/bugdir.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) (limited to 'libbe/bugdir.py') diff --git a/libbe/bugdir.py b/libbe/bugdir.py index d3b7e61..7e4cf3e 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -105,7 +105,7 @@ class BugDir (list): if root == None: root = os.getcwd() if sink_to_existing_root == True: - self.root = self.find_root(root) + self.root = self._find_root(root) else: if not os.path.exists(root): raise NoRootEntry(root) @@ -117,11 +117,11 @@ class BugDir (list): if os.path.exists(self.get_path()): raise AlreadyInitialized, self.get_path() if rcs == None: - rcs = self.guess_rcs(allow_rcs_init) + rcs = self._guess_rcs(allow_rcs_init) self.rcs = rcs user_id = self.rcs.get_user_id() - def find_root(self, path): + def _find_root(self, path): """ Search for an existing bug database dir and it's ancestors and return a BugDir rooted there. @@ -214,7 +214,7 @@ that the Arch RCS backend *enforces* ids with this format.""") assert args[0] in ["version", "settings", "bugs"], str(args) return os.path.join(my_dir, *args) - def guess_rcs(self, allow_rcs_init=False): + def _guess_rcs(self, allow_rcs_init=False): deepdir = self.get_path() if not os.path.exists(deepdir): deepdir = os.path.dirname(deepdir) @@ -319,7 +319,7 @@ that the Arch RCS backend *enforces* ids with this format.""") for uuid in self.list_uuids(): if uuid not in map: map[uuid] = None - self.bug_map = map + self._bug_map = map def list_uuids(self): uuids = [] @@ -366,7 +366,7 @@ that the Arch RCS backend *enforces* ids with this format.""") used for long term reference. """ chars = 3 - for uuid in self.bug_map.keys(): + for uuid in self._bug_map.keys(): if bug.uuid == uuid: continue while (bug.uuid[:chars] == uuid[:chars]): @@ -384,7 +384,7 @@ that the Arch RCS backend *enforces* ids with this format.""") """ matches = [] self._bug_map_gen() - for uuid in self.bug_map.keys(): + for uuid in self._bug_map.keys(): if uuid.startswith(shortname): matches.append(uuid) if len(matches) > 1: @@ -394,15 +394,20 @@ that the Arch RCS backend *enforces* ids with this format.""") raise KeyError("No bug matches %s" % shortname) def bug_from_uuid(self, uuid): - if uuid not in self.bug_map: - self._bug_map_gen() - if uuid not in self.bug_map: - raise KeyError("No bug matches %s\n bug map: %s\n root: %s" \ - % (uuid, self.bug_map, self.root)) - if self.bug_map[uuid] == None: + if not self.has_bug(uuid): + raise KeyError("No bug matches %s\n bug map: %s\n root: %s" \ + % (uuid, self._bug_map, self.root)) + if self._bug_map[uuid] == None: self._load_bug(uuid) - return self.bug_map[uuid] + return self._bug_map[uuid] + def has_bug(self, bug_uuid): + if bug_uuid not in self._bug_map: + self._bug_map_gen() + if bug_uuid not in self._bug_map: + return False + return True + def simple_bug_dir(): """ -- cgit