From 2d381449ece1326b25c5fbbbf3ee7987f03d1ee2 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 23 Jul 2009 09:20:52 -0400 Subject: libbe/tree.Tree.traverse(depthFirst)->depth_first & stripped trailing spaces. --- libbe/tree.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'libbe') diff --git a/libbe/tree.py b/libbe/tree.py index fe791a5..0256407 100644 --- a/libbe/tree.py +++ b/libbe/tree.py @@ -35,7 +35,7 @@ class Tree(list): >>> a = Tree(); a.n = "a" >>> a.append(c) >>> a.append(b) - + >>> a.branch_len() 5 >>> a.sort(key=lambda node : -node.branch_len()) @@ -44,7 +44,7 @@ class Tree(list): >>> a.sort(key=lambda node : node.branch_len()) >>> "".join([node.n for node in a.traverse()]) 'abdgcefhi' - >>> "".join([node.n for node in a.traverse(depthFirst=False)]) + >>> "".join([node.n for node in a.traverse(depth_first=False)]) 'abcdefghi' >>> for depth,node in a.thread(): ... print "%*s" % (2*depth+1, node.n) @@ -97,11 +97,11 @@ class Tree(list): for child in self: child.sort(*args, **kwargs) - def traverse(self, depthFirst=True): + def traverse(self, depth_first=True): """ Note: you might want to sort() your tree first. """ - if depthFirst == True: + if depth_first == True: yield self for child in self: for descendant in child.traverse(): @@ -119,7 +119,7 @@ class Tree(list): When flatten==False, the depth of any node is one greater than the depth of its parent. That way the inheritance is explicit, but you can end up with highly indented threads. - + When flatten==True, the depth of any node is only greater than the depth of its parent when there is a branch, and the node is not the last child. This can lead to ancestry ambiguity, @@ -138,8 +138,8 @@ class Tree(list): stack = [] # ancestry of the current node if flatten == True: depthDict = {} - - for node in self.traverse(depthFirst=True): + + for node in self.traverse(depth_first=True): while len(stack) > 0 \ and id(node) not in [id(c) for c in stack[-1]]: stack.pop(-1) -- cgit From 678fccaf505eca6d816b859e6d90d728d6426a02 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 23 Jul 2009 09:25:34 -0400 Subject: Added libbe.tree.Tree.has_descendant(). Tree equality is now based on instance id. It had previously used the default list "equal if all elements are equal", which meant that all the leaves matched each other. --- libbe/tree.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'libbe') diff --git a/libbe/tree.py b/libbe/tree.py index 0256407..45ae085 100644 --- a/libbe/tree.py +++ b/libbe/tree.py @@ -68,7 +68,18 @@ class Tree(list): f h i + >>> a.has_descendant(g) + True + >>> c.has_descendant(g) + False + >>> a.has_descendant(a) + False + >>> a.has_descendant(a, match_self=True) + True """ + def __eq__(self, other): + return id(self) == id(other) + def branch_len(self): """ Exhaustive search every time == SLOW. @@ -157,4 +168,12 @@ class Tree(list): yield (depth,node) stack.append(node) + def has_descendant(self, descendant, depth_first=True, match_self=False): + if descendant == self: + return match_self + for d in self.traverse(depth_first): + if descendant == d: + return True + return False + suite = doctest.DocTestSuite() -- cgit From 0cdbb498a29d32b5d2d0a182079ed6cd5ddb7641 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 23 Jul 2009 09:59:14 -0400 Subject: Added libbe.bug.cmp_comments(), and added that to default bug comparison. --- libbe/bug.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'libbe') diff --git a/libbe/bug.py b/libbe/bug.py index f3448e2..c1e5481 100644 --- a/libbe/bug.py +++ b/libbe/bug.py @@ -494,8 +494,25 @@ cmp_assigned = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "assigned") # chronological rankings (newer < older) cmp_time = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "time", invert=True) +def cmp_comments(bug_1, bug_2): + """ + Compare two bugs' comments lists. Doesn't load any new comments, + so you should call each bug's .load_comments() first if you want a + full comparison. + """ + comms_1 = sorted(bug_1.comments(), key = lambda comm : comm.uuid) + comms_2 = sorted(bug_2.comments(), key = lambda comm : comm.uuid) + result = cmp(len(comms_1), len(comms_2)) + if result != 0: + return result + for c_1,c_2 in zip(comms_1, comms_2): + result = cmp(c_1, c_2) + if result != 0: + return result + return 0 + DEFAULT_CMP_FULL_CMP_LIST = \ - (cmp_status,cmp_severity,cmp_assigned,cmp_time,cmp_creator) + (cmp_status,cmp_severity,cmp_assigned,cmp_time,cmp_creator,cmp_comments) class BugCompoundComparator (object): def __init__(self, cmp_list=DEFAULT_CMP_FULL_CMP_LIST): -- cgit From 678d074ba2d20c22255abdcc8c41f3c0e2ec2c2a Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 23 Jul 2009 10:08:04 -0400 Subject: Added bugdir setting comparision to libbe.diff. Renamed libbe.diff.diff -> bug_diffs, since it doesn't compare bugdirs. Load comments before bug comparision so cmp_comments will see them. Use .settings_properties rather than static lists to create attribute lists for change_lines(). Removed trailing endline from becommands/diff.py output. --- libbe/diff.py | 49 +++++++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 20 deletions(-) (limited to 'libbe') diff --git a/libbe/diff.py b/libbe/diff.py index 963d692..ba48efc 100644 --- a/libbe/diff.py +++ b/libbe/diff.py @@ -19,7 +19,7 @@ from libbe import cmdutil, bugdir, bug from libbe.utility import time_to_str import doctest -def diff(old_bugdir, new_bugdir): +def bug_diffs(old_bugdir, new_bugdir): added = [] removed = [] modified = [] @@ -27,6 +27,8 @@ def diff(old_bugdir, new_bugdir): old_bug = old_bugdir.bug_from_uuid(uuid) try: new_bug = new_bugdir.bug_from_uuid(uuid) + old_bug.load_comments() + new_bug.load_comments() if old_bug != new_bug: modified.append((old_bug, new_bug)) except KeyError: @@ -37,26 +39,34 @@ def diff(old_bugdir, new_bugdir): added.append(new_bug) return (removed, modified, added) -def diff_report(diff_data, bug_dir): - (removed, modified, added) = diff_data +def diff_report(bug_diffs_data, old_bugdir, new_bugdir): + bugs_removed,bugs_modified,bugs_added = bug_diffs_data def modified_cmp(left, right): return bug.cmp_severity(left[1], right[1]) - added.sort(bug.cmp_severity) - removed.sort(bug.cmp_severity) - modified.sort(modified_cmp) + bugs_added.sort(bug.cmp_severity) + bugs_removed.sort(bug.cmp_severity) + bugs_modified.sort(modified_cmp) lines = [] - if len(added) > 0: + if old_bugdir.settings != new_bugdir.settings: + bugdir_settings = sorted(new_bugdir.settings_properties) + bugdir_settings.remove("rcs_name") # tweaked by bugdir.duplicate_bugdir + change_list = change_lines(old_bugdir, new_bugdir, bugdir_settings) + if len(change_list) > 0: + lines.append("Modified bug directory:") + change_strings = ["%s: %s -> %s" % f for f in change_list] + lines.extend(change_strings) + lines.append("") + if len(bugs_added) > 0: lines.append("New bug reports:") - for bg in added: + for bg in bugs_added: lines.extend(bg.string(shortlist=True).splitlines()) lines.append("") - - if len(modified) > 0: + if len(bugs_modified) > 0: printed = False - for old_bug, new_bug in modified: - change_str = bug_changes(old_bug, new_bug, bug_dir) + for old_bug, new_bug in bugs_modified: + change_str = bug_changes(old_bug, new_bug) if change_str is None: continue if not printed: @@ -65,14 +75,13 @@ def diff_report(diff_data, bug_dir): lines.extend(change_str.splitlines()) if printed == True: lines.append("") - - if len(removed) > 0: + if len(bugs_removed) > 0: lines.append("Removed bug reports:") - for bg in removed: + for bg in bugs_removed: lines.extend(bg.string(shortlist=True).splitlines()) lines.append("") - return '\n'.join(lines) + return "\n".join(lines).rstrip("\n") def change_lines(old, new, attributes): change_list = [] @@ -86,13 +95,13 @@ def change_lines(old, new, attributes): else: return None -def bug_changes(old, new, bugs): - change_list = change_lines(old, new, ("time", "creator", "severity", - "target", "summary", "status", "assigned")) +def bug_changes(old, new): + bug_settings = sorted(new.settings_properties) + change_list = change_lines(old, new, bug_settings) + change_strings = ["%s: %s -> %s" % f for f in change_list] old_comment_ids = [c.uuid for c in old.comments()] new_comment_ids = [c.uuid for c in new.comments()] - change_strings = ["%s: %s -> %s" % f for f in change_list] for comment_id in new_comment_ids: if comment_id not in old_comment_ids: summary = comment_summary(new.comment_from_uuid(comment_id), "new") -- cgit From 45cc50d7ce0b5c32a2936d6eb87a3002670924bc Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 23 Jul 2009 14:19:15 -0400 Subject: Added .revision_id() to all the VCSs. This makes it easier to compare recent revisions without a human around to give you revision numbers. --- libbe/arch.py | 12 ++++++++++-- libbe/bzr.py | 8 ++++++++ libbe/darcs.py | 43 ++++++++++++++++++++++++++++++------------- libbe/git.py | 13 ++++++++++++- libbe/hg.py | 16 ++++++++-------- libbe/rcs.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 114 insertions(+), 24 deletions(-) (limited to 'libbe') diff --git a/libbe/arch.py b/libbe/arch.py index 2f45aa9..1bdc8ae 100644 --- a/libbe/arch.py +++ b/libbe/arch.py @@ -176,7 +176,6 @@ class Arch(RCS): self._get_archive_project_name(root) return root - def _get_archive_name(self, root): status,output,error = self._u_invoke_client("archives") lines = output.split('\n') @@ -188,7 +187,6 @@ class Arch(RCS): if os.path.realpath(location) == os.path.realpath(root): self._archive_name = archive assert self._archive_name != None - def _get_archive_project_name(self, root): # get project names status,output,error = self._u_invoke_client("tree-version", directory=root) @@ -281,6 +279,16 @@ class Arch(RCS): assert revpath.startswith(self._archive_project_name()+'--') revision = revpath[len(self._archive_project_name()+'--'):] return revpath + def _rcs_revision_id(self, index): + status,output,error = self._u_invoke_client("logs") + logs = output.splitlines() + first_log = logs.pop(0) + assert first_log == "base-0", first_log + try: + log = logs[index] + except IndexError: + return None + return "%s--%s" % (self._archive_project_name(), log) class CantAddFile(Exception): def __init__(self, file): diff --git a/libbe/bzr.py b/libbe/bzr.py index b33292c..7457815 100644 --- a/libbe/bzr.py +++ b/libbe/bzr.py @@ -93,6 +93,14 @@ class Bzr(RCS): assert len(match.groups()) == 1 revision = match.groups()[0] return revision + def _rcs_revision_id(self, index): + status,output,error = self._u_invoke_client("revno") + current_revision = int(output) + if index >= current_revision or index < -current_revision: + return None + if index >= 0: + return str(index+1) # bzr commit 0 is the empty tree. + return str(current_revision+index+1) def postcommit(self): try: self._u_invoke_client('merge') diff --git a/libbe/darcs.py b/libbe/darcs.py index e7132c0..0720ed9 100644 --- a/libbe/darcs.py +++ b/libbe/darcs.py @@ -18,8 +18,13 @@ import codecs import os import re import sys -import unittest +try: # import core module, Python >= 2.5 + from xml.etree import ElementTree +except ImportError: # look for non-core module + from elementtree import ElementTree +from xml.sax.saxutils import unescape import doctest +import unittest import rcs from rcs import RCS @@ -138,24 +143,36 @@ class Darcs(RCS): args = ['record', '--all', '--author', id, '--logfile', commitfile] status,output,error = self._u_invoke_client(*args) empty_strings = ["No changes!"] - revision = None if self._u_any_in_string(empty_strings, output) == True: if allow_empty == False: raise rcs.EmptyCommit() - else: # we need a extra call to get the current revision - args = ["changes", "--last=1", "--xml"] - status,output,error = self._u_invoke_client(*args) - revline = re.compile("[ \t]*(.*)") - # note that darcs does _not_ make an empty revision. - # this returns the last non-empty revision id... + # note that darcs does _not_ make an empty revision. + # this returns the last non-empty revision id... + revision = self._rcs_revision_id(-1) else: revline = re.compile("Finished recording patch '(.*)'") - match = revline.search(output) - assert match != None, output+error - assert len(match.groups()) == 1 - revision = match.groups()[0] + match = revline.search(output) + assert match != None, output+error + assert len(match.groups()) == 1 + revision = match.groups()[0] return revision - + def _rcs_revision_id(self, index): + status,output,error = self._u_invoke_client("changes", "--xml") + revisions = [] + xml_str = output.encode("unicode_escape").replace(r"\n", "\n") + element = ElementTree.XML(xml_str) + assert element.tag == "changelog", element.tag + for patch in element.getchildren(): + assert patch.tag == "patch", patch.tag + for child in patch.getchildren(): + if child.tag == "name": + text = unescape(unicode(child.text).decode("unicode_escape").strip()) + revisions.append(text) + revisions.reverse() + try: + return revisions[index] + except IndexError: + return None rcs.make_rcs_testcase_subclasses(Darcs, sys.modules[__name__]) diff --git a/libbe/git.py b/libbe/git.py index 2f9ffa9..2b45679 100644 --- a/libbe/git.py +++ b/libbe/git.py @@ -111,7 +111,18 @@ class Git(RCS): assert match != None, output+error assert len(match.groups()) == 3 revision = match.groups()[1] - return revision + full_revision = self._rcs_revision_id(-1) + assert full_revision.startswith(revision), \ + "Mismatched revisions:\n%s\n%s" % (revision, full_revision) + return full_revision + def _rcs_revision_id(self, index): + args = ["rev-list", "--first-parent", "--reverse", "HEAD"] + status,output,error = self._u_invoke_client(*args) + commits = output.splitlines() + try: + return commits[index] + except IndexError: + return None rcs.make_rcs_testcase_subclasses(Git, sys.modules[__name__]) diff --git a/libbe/hg.py b/libbe/hg.py index a20eeb5..fcda829 100644 --- a/libbe/hg.py +++ b/libbe/hg.py @@ -80,14 +80,14 @@ class Hg(RCS): strings = ["nothing changed"] if self._u_any_in_string(strings, output) == True: raise rcs.EmptyCommit() - status,output,error = self._u_invoke_client('identify') - revision = None - revline = re.compile("(.*) tip") - match = revline.search(output) - assert match != None, output+error - assert len(match.groups()) == 1 - revision = match.groups()[0] - return revision + return self._rcs_revision_id(-1) + def _rcs_revision_id(self, index, style="id"): + args = ["identify", "--rev", str(int(index)), "--%s" % style] + kwargs = {"expect": (0,255)} + status,output,error = self._u_invoke_client(*args, **kwargs) + if status == 0: + return output.strip() + return None rcs.make_rcs_testcase_subclasses(Hg, sys.modules[__name__]) diff --git a/libbe/rcs.py b/libbe/rcs.py index 1e1cfa7..d979df0 100644 --- a/libbe/rcs.py +++ b/libbe/rcs.py @@ -214,6 +214,16 @@ class RCS(object): changes to commit. """ return None + def _rcs_revision_id(self, index): + """ + Return the name of the th revision. Index will be an + integer (possibly <= 0). The choice of which branch to follow + when crossing branches/merges is not defined. + + Return None if revision IDs are not supported, or if the + specified revision does not exist. + """ + return None def installed(self): try: self._rcs_help() @@ -407,6 +417,18 @@ class RCS(object): pass def postcommit(self, directory): pass + def revision_id(self, index=None): + """ + Return the name of the th revision. The choice of + which branch to follow when crossing branches/merges is not + defined. + + Return None if index==None, revision IDs are not supported, or + if the specified revision does not exist. + """ + if index == None: + return None + return self._rcs_revision_id(index) def _u_any_in_string(self, list, string): """ Return True if any of the strings in list are in string. @@ -814,6 +836,30 @@ class RCS_commit_TestCase(RCSTestCase): self.failUnlessEqual( self.test_contents['rev_1'], committed_contents) + def test_revision_id_as_committed(self): + """Check for compatibility between .commit() and .revision_id()""" + if not self.rcs.versioned: + self.failUnlessEqual(self.rcs.revision_id(5), None) + return + committed_revisions = [] + for path in self.test_files: + full_path = self.full_path(path) + self.rcs.set_file_contents( + full_path, self.test_contents['rev_1']) + revision = self.rcs.commit("Initial %s contents." % path) + committed_revisions.append(revision) + self.rcs.set_file_contents( + full_path, self.test_contents['uncommitted']) + revision = self.rcs.commit("Altered %s contents." % path) + committed_revisions.append(revision) + for i,revision in enumerate(committed_revisions): + self.failUnlessEqual(self.rcs.revision_id(i), revision) + i += -len(committed_revisions) # check negative indices + self.failUnlessEqual(self.rcs.revision_id(i), revision) + i = len(committed_revisions) + self.failUnlessEqual(self.rcs.revision_id(i), None) + self.failUnlessEqual(self.rcs.revision_id(-i-1), None) + class RCS_duplicate_repo_TestCase(RCSTestCase): """Test cases for RCS.duplicate_repo method.""" -- cgit From 9afbe4e78f7a332401ec03008ff66faa5c11e297 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 25 Jul 2009 07:26:19 -0400 Subject: Renamed Comment.From and .time_string to .author and .date respectively. Now they conform to the libbe.settings_object.setting_name_to_attr_name() standard. I fixed the references I found in becommands/comment.py interfaces/xml/be-mbox-to-xml interfaces/xml/be-xml-to-mbox but there may have been some references or files that slipped through. --- libbe/comment.py | 50 +++++++++++++++++++++++--------------------------- 1 file changed, 23 insertions(+), 27 deletions(-) (limited to 'libbe') diff --git a/libbe/comment.py b/libbe/comment.py index 3249e8b..0d2fb57 100644 --- a/libbe/comment.py +++ b/libbe/comment.py @@ -162,9 +162,9 @@ class Comment(Tree, settings_object.SavedSettingsObject): doc="Alternate ID for linking imported comments. Internally comments are linked (via In-reply-to) to the parent's UUID. However, these UUIDs are generated internally, so Alt-id is provided as a user-controlled linking target.") def alt_id(): return {} - @_versioned_property(name="From", + @_versioned_property(name="Author", doc="The author of the comment") - def From(): return {} + def author(): return {} @_versioned_property(name="In-reply-to", doc="UUID for parent comment or bug") @@ -178,17 +178,17 @@ class Comment(Tree, settings_object.SavedSettingsObject): @_versioned_property(name="Date", doc="An RFC 2822 timestamp for comment creation") - def time_string(): return {} + def date(): return {} def _get_time(self): - if self.time_string == None: + if self.date == None: return None - return utility.str_to_time(self.time_string) + return utility.str_to_time(self.date) def _set_time(self, value): - self.time_string = utility.time_to_str(value) + self.date = utility.time_to_str(value) time = property(fget=_get_time, fset=_set_time, - doc="An integer version of .time_string") + doc="An integer version of .date") def _get_comment_body(self): if self.rcs != None and self.sync_with_disk == True: @@ -258,7 +258,7 @@ class Comment(Tree, settings_object.SavedSettingsObject): self.uuid = uuid_gen() self.time = int(time.time()) # only save to second precision if self.rcs != None: - self.From = self.rcs.get_user_id() + self.author = self.rcs.get_user_id() self.in_reply_to = in_reply_to self.body = body @@ -282,12 +282,12 @@ class Comment(Tree, settings_object.SavedSettingsObject): """ >>> comm = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n") >>> comm.uuid = "0123" - >>> comm.time_string = "Thu, 01 Jan 1970 00:00:00 +0000" + >>> comm.date = "Thu, 01 Jan 1970 00:00:00 +0000" >>> print comm.xml(indent=2, shortname="com-1") 0123 com-1 - + Thu, 01 Jan 1970 00:00:00 +0000 text/plain Some @@ -309,8 +309,8 @@ class Comment(Tree, settings_object.SavedSettingsObject): ("alt-id", self.alt_id), ("short-name", shortname), ("in-reply-to", self.in_reply_to), - ("from", self._setting_attr_string("From")), - ("date", self.time_string), + ("author", self._setting_attr_string("author")), + ("date", self.date), ("content-type", self.content_type), ("body", body)] lines = [""] @@ -328,11 +328,11 @@ class Comment(Tree, settings_object.SavedSettingsObject): fields. >>> commA = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n") >>> commA.uuid = "0123" - >>> commA.time_string = "Thu, 01 Jan 1970 00:00:00 +0000" + >>> commA.date = "Thu, 01 Jan 1970 00:00:00 +0000" >>> xml = commA.xml(shortname="com-1") >>> commB = Comment() >>> commB.from_xml(xml) - >>> attrs=['uuid','alt_id','in_reply_to','From','time_string','content_type','body'] + >>> attrs=['uuid','alt_id','in_reply_to','author','date','content_type','body'] >>> for attr in attrs: # doctest: +ELLIPSIS ... if getattr(commB, attr) != getattr(commA, attr): ... estr = "Mismatch on %s: '%s' should be '%s'" @@ -342,15 +342,15 @@ class Comment(Tree, settings_object.SavedSettingsObject): Mismatch on alt_id: '0123' should be 'None' >>> print commB.alt_id 0123 - >>> commA.From - >>> commB.From + >>> commA.author + >>> commB.author """ if type(xml_string) == types.UnicodeType: xml_string = xml_string.strip().encode("unicode_escape") comment = ElementTree.XML(xml_string) if comment.tag != "comment": raise InvalidXML(comment, "root element must be ") - tags=['uuid','alt-id','in-reply-to','from','date','content-type','body'] + tags=['uuid','alt-id','in-reply-to','author','date','content-type','body'] uuid = None body = None for child in comment.getchildren(): @@ -368,10 +368,6 @@ class Comment(Tree, settings_object.SavedSettingsObject): if child.tag == "body": body = text continue # don't set the bug's body yet. - elif child.tag == 'from': - attr_name = "From" - elif child.tag == 'date': - attr_name = 'time_string' else: attr_name = child.tag.replace('-','_') setattr(self, attr_name, text) @@ -389,7 +385,7 @@ class Comment(Tree, settings_object.SavedSettingsObject): def string(self, indent=0, shortname=None): """ >>> comm = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n") - >>> comm.time_string = "Thu, 01 Jan 1970 00:00:00 +0000" + >>> comm.date = "Thu, 01 Jan 1970 00:00:00 +0000" >>> print comm.string(indent=2, shortname="com-1") --------- Comment --------- Name: com-1 @@ -405,8 +401,8 @@ class Comment(Tree, settings_object.SavedSettingsObject): lines = [] lines.append("--------- Comment ---------") lines.append("Name: %s" % shortname) - lines.append("From: %s" % (self._setting_attr_string("From"))) - lines.append("Date: %s" % self.time_string) + lines.append("From: %s" % (self._setting_attr_string("author"))) + lines.append("Date: %s" % self.date) lines.append("") if self.content_type.startswith("text/"): lines.extend((self.body or "").splitlines()) @@ -421,8 +417,8 @@ class Comment(Tree, settings_object.SavedSettingsObject): """ >>> comm = Comment(bug=None, body="Some insightful remarks") >>> comm.uuid = "com-1" - >>> comm.time_string = "Thu, 20 Nov 2008 15:55:11 +0000" - >>> comm.From = "Jane Doe " + >>> comm.date = "Thu, 20 Nov 2008 15:55:11 +0000" + >>> comm.author = "Jane Doe " >>> print comm --------- Comment --------- Name: com-1 @@ -506,7 +502,7 @@ class Comment(Tree, settings_object.SavedSettingsObject): name_map = {} for shortname,comment in comm.comment_shortnames(bug_shortname): name_map[comment.uuid] = shortname - comm.sort(key=lambda c : c.From) # your sort + comm.sort(key=lambda c : c.author) # your sort comm.string_thread(name_map=name_map) >>> a = Comment(bug=None, uuid="a", body="Insightful remarks") -- cgit From a3d5316acfe99f835404d4f8728e5f8acaad4eff Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 25 Jul 2009 07:32:12 -0400 Subject: Added on_disk option to libbe.bugdir.simple_bug_dir(). Now you can easily generate simple_bug_dirs that live only in memory. --- libbe/bugdir.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'libbe') diff --git a/libbe/bugdir.py b/libbe/bugdir.py index 6e020ee..f98186e 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -548,7 +548,7 @@ settings easy. Don't set this attribute. Set .rcs instead, and return True -def simple_bug_dir(): +def simple_bug_dir(on_disk=True): """ For testing >>> bugdir = simple_bug_dir() @@ -557,11 +557,18 @@ def simple_bug_dir(): >>> print ls ['a', 'b'] """ - dir = utility.Dir() - assert os.path.exists(dir.path) - bugdir = BugDir(dir.path, sink_to_existing_root=False, allow_rcs_init=True, + if on_disk == True: + dir = utility.Dir() + assert os.path.exists(dir.path) + root = dir.path + rcs_init = True + else: + root = None + rcs_init = False + bugdir = BugDir(root, sink_to_existing_root=False, allow_rcs_init=rcs_init, manipulate_encodings=False) - bugdir._dir_ref = dir # postpone cleanup since dir.__del__() removes dir. + if on_disk == True: # postpone cleanup since dir.__del__() removes dir. + bugdir._dir_ref = dir bug_a = bugdir.new_bug("a", summary="Bug A") bug_a.creator = "John Doe " bug_a.time = 0 @@ -569,7 +576,8 @@ def simple_bug_dir(): bug_b.creator = "Jane Doe " bug_b.time = 0 bug_b.status = "closed" - bugdir.save() + if on_disk == True: + bugdir.save() return bugdir -- cgit From fa7dd91cd88377e8c41967ef00a9d31e44cb697b Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 25 Jul 2009 08:59:38 -0400 Subject: Added in_memory to BugDir.__init__ to disable saving/loading completely. The previous simple_bug_dir(on_disk==False) supprised me by loading my BE bugdir when called from the BE directory. This functionality could probably move out to Bug and Comment as well, but I have avoided that for now. --- libbe/bugdir.py | 94 ++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 17 deletions(-) (limited to 'libbe') diff --git a/libbe/bugdir.py b/libbe/bugdir.py index f98186e..232ad47 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -17,11 +17,12 @@ # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +import copy +import errno import os import os.path -import errno +import sys import time -import copy import unittest import doctest @@ -99,7 +100,7 @@ class BugDir (list, settings_object.SavedSettingsObject): all bugs/comments/etc. that have been loaded into memory. If you've been living in memory and want to move to .sync_with_disk==True, but you're not sure if anything has been - changed in memoryy, a call to save() is a safe move. + changed in memory, a call to save() is a safe move. Regardless of .sync_with_disk, a call to .save() will write out all the contents that the BugDir instance has loaded into memory. @@ -109,7 +110,10 @@ class BugDir (list, settings_object.SavedSettingsObject): 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__(from_disk=True)). + __init__(from_disk=True)). If you want BugDir to live entirely in + memory (ignoring even explicit calls to .load(), .save(), etc.), + set in_memory = True. The in_memory option is ignored if + from_disk==True. Allow RCS initialization ======================== @@ -281,10 +285,11 @@ settings easy. Don't set this attribute. Set .rcs instead, and def __init__(self, root=None, sink_to_existing_root=True, assert_new_BugDir=False, allow_rcs_init=False, manipulate_encodings=True, - from_disk=False, rcs=None): + from_disk=False, in_memory=False, rcs=None): list.__init__(self) settings_object.SavedSettingsObject.__init__(self) self._manipulate_encodings = manipulate_encodings + self._in_memory = in_memory if root == None: root = os.getcwd() if sink_to_existing_root == True: @@ -299,6 +304,7 @@ settings easy. Don't set this attribute. Set .rcs instead, and if from_disk == True: self.sync_with_disk = True + self._in_memory = False self.load() else: self.sync_with_disk = False @@ -335,6 +341,8 @@ settings easy. Don't set this attribute. Set .rcs instead, and return beroot def get_version(self, path=None, use_none_rcs=False): + if self._in_memory == True: + return TREE_VERSION_STRING if use_none_rcs == True: RCS = rcs.rcs_by_name("None") RCS.root(self.root) @@ -348,6 +356,8 @@ settings easy. Don't set this attribute. Set .rcs instead, and return tree_version def set_version(self): + if self._in_memory == True: + return self.rcs.mkdir(self.get_path()) self.rcs.set_file_contents(self.get_path("version"), TREE_VERSION_STRING) @@ -377,7 +387,7 @@ settings easy. Don't set this attribute. Set .rcs instead, and raise NotImplementedError, \ "BugDir cannot handle version '%s' yet." % version else: - if not os.path.exists(self.get_path()): + if not os.path.exists(self.get_path()) and self._in_memory == False: raise NoBugDir(self.get_path()) self.load_settings() @@ -386,6 +396,8 @@ settings easy. Don't set this attribute. Set .rcs instead, and def load_all_bugs(self): "Warning: this could take a while." + if self._in_memory == True: + return self._clear_bugs() for uuid in self.list_uuids(): self._load_bug(uuid) @@ -403,6 +415,8 @@ settings easy. Don't set this attribute. Set .rcs instead, and self.set_version() self.save_settings() for bug in self: + if self._in_memory == True: + return bug.save() def load_settings(self): @@ -414,6 +428,8 @@ settings easy. Don't set this attribute. Set .rcs instead, and self._setup_status(self.active_status, self.inactive_status) def _get_settings(self, settings_path): + if self._in_memory == True: + return {} 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 @@ -429,6 +445,8 @@ settings easy. Don't set this attribute. Set .rcs instead, and self._save_settings(self.get_path("settings"), settings) def _save_settings(self, settings_path, settings): + if self._in_memory == True: + return 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 @@ -458,7 +476,7 @@ settings easy. Don't set this attribute. Set .rcs instead, and def list_uuids(self): uuids = [] - if os.path.exists(self.get_path()): + if self._in_memory == False and os.path.exists(self.get_path()): # list the uuids on disk for uuid in os.listdir(self.get_path("bugs")): if not (uuid.startswith('.')): @@ -550,22 +568,26 @@ settings easy. Don't set this attribute. Set .rcs instead, and def simple_bug_dir(on_disk=True): """ - For testing + For testing. Set on_disk==False for a memory-only bugdir. >>> bugdir = simple_bug_dir() - >>> ls = list(bugdir.list_uuids()) - >>> ls.sort() - >>> print ls + >>> uuids = list(bugdir.list_uuids()) + >>> uuids.sort() + >>> print uuids ['a', 'b'] """ if on_disk == True: dir = utility.Dir() assert os.path.exists(dir.path) root = dir.path + in_memory = False rcs_init = True else: - root = None + root = "/" + in_memory = True rcs_init = False - bugdir = BugDir(root, sink_to_existing_root=False, allow_rcs_init=rcs_init, + bugdir = BugDir(root, sink_to_existing_root=False, + in_memory=in_memory, + allow_rcs_init=rcs_init, manipulate_encodings=False) if on_disk == True: # postpone cleanup since dir.__del__() removes dir. bugdir._dir_ref = dir @@ -582,8 +604,6 @@ def simple_bug_dir(on_disk=True): class BugDirTestCase(unittest.TestCase): - def __init__(self, *args, **kwargs): - unittest.TestCase.__init__(self, *args, **kwargs) def setUp(self): self.dir = utility.Dir() self.bugdir = BugDir(self.dir.path, sink_to_existing_root=False, @@ -680,5 +700,45 @@ class BugDirTestCase(unittest.TestCase): def testSyncedComments(self): self.testComments(sync_with_disk=True) -unitsuite = unittest.TestLoader().loadTestsFromTestCase(BugDirTestCase) -suite = unittest.TestSuite([unitsuite])#, doctest.DocTestSuite()]) +class SimpleBugDirTestCase (unittest.TestCase): + def setUp(self): + # create a pre-existing bugdir in a temporary directory + self.dir = utility.Dir() + os.chdir(self.dir.path) + self.bugdir = BugDir(self.dir.path, sink_to_existing_root=False, + allow_rcs_init=True) + self.bugdir.new_bug("preexisting", summary="Hopefully not imported") + self.bugdir.save() + def tearDown(self): + self.dir.cleanup() + def testOnDiskCleanLoad(self): + """simple_bug_dir(on_disk==True) should not import preexisting bugs.""" + bugdir = simple_bug_dir(on_disk=True) + self.failUnless(bugdir._in_memory == False, bugdir._in_memory) + uuids = sorted([bug.uuid for bug in bugdir]) + self.failUnless(uuids == ['a', 'b'], uuids) + bugdir._clear_bugs() + uuids = sorted([bug.uuid for bug in bugdir]) + self.failUnless(uuids == [], uuids) + bugdir.load_all_bugs() + uuids = sorted([bug.uuid for bug in bugdir]) + self.failUnless(uuids == ['a', 'b'], uuids) + def testInMemoryCleanLoad(self): + """simple_bug_dir(on_disk==False) should not import preexisting bugs.""" + bugdir = simple_bug_dir(on_disk=False) + self.failUnless(bugdir._in_memory == True, bugdir._in_memory) + uuids = sorted([bug.uuid for bug in bugdir]) + self.failUnless(uuids == ['a', 'b'], uuids) + bugdir.load_all_bugs() + uuids = sorted([bug.uuid for bug in bugdir]) + self.failUnless(uuids == ['a', 'b'], uuids) + bugdir._clear_bugs() + uuids = sorted([bug.uuid for bug in bugdir]) + self.failUnless(uuids == [], uuids) + bugdir.load_all_bugs() + uuids = sorted([bug.uuid for bug in bugdir]) + self.failUnless(uuids == [], uuids) + + +unitsuite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) +suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()]) -- cgit From 058dbd6da7c085674680254b68bdba8a6acda117 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 26 Jul 2009 20:04:32 -0400 Subject: BugDir._in_memory was a stupid idea. Took it back out. It was too confusing having three memory access levels: 1) syncronized 2) explicit 3) memory-only with .sync_with_disk selecting between 1 and 2/3 and ._in_memory selecting between 2/3. Now there are only two: 1) syncronized 2) memory-only excepting explicit BugDir.save() calls. I avoid the problem of non-syncronized loading of on-disk bugs in simple_bug_dir by restricting .list_uuids() to in-memory bugs when .sync_with_disk==True. Beyond that, I shifted the order of the BugDir methods around a bit so that they were better grouped according to general idea. Note that the DiskAccessRequired exceptions on filesystem access when .sync_with_disk==False should be propogated to the Bug and Comment methods, but I haven't done that yet. --- libbe/bugdir.py | 250 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 147 insertions(+), 103 deletions(-) (limited to 'libbe') diff --git a/libbe/bugdir.py b/libbe/bugdir.py index 232ad47..7380172 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -63,6 +63,11 @@ class MultipleBugMatches(ValueError): self.shortname = shortname self.matches = matches +class DiskAccessRequired (Exception): + def __init__(self, goal): + msg = "Cannot %s without accessing the disk" % goal + Exception.__init__(self, msg) + TREE_VERSION_STRING = "Bugs Everywhere Tree 1 0\n" @@ -100,7 +105,8 @@ class BugDir (list, settings_object.SavedSettingsObject): all bugs/comments/etc. that have been loaded into memory. If you've been living in memory and want to move to .sync_with_disk==True, but you're not sure if anything has been - changed in memory, a call to save() is a safe move. + changed in memory, a call to save() immediately before the + .set_sync_with_disk(True) call is a safe move. Regardless of .sync_with_disk, a call to .save() will write out all the contents that the BugDir instance has loaded into memory. @@ -108,12 +114,8 @@ class BugDir (list, settings_object.SavedSettingsObject): changes, this .save() call will be a waste of time. 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__(from_disk=True)). If you want BugDir to live entirely in - memory (ignoring even explicit calls to .load(), .save(), etc.), - set in_memory = True. The in_memory option is ignored if - from_disk==True. + loads new settings/bugs/comments that it doesn't already have in + memory and .sync_with_disk == True. Allow RCS initialization ======================== @@ -284,12 +286,10 @@ settings easy. Don't set this attribute. Set .rcs instead, and def __init__(self, root=None, sink_to_existing_root=True, assert_new_BugDir=False, allow_rcs_init=False, - manipulate_encodings=True, - from_disk=False, in_memory=False, rcs=None): + manipulate_encodings=True, from_disk=False, rcs=None): list.__init__(self) settings_object.SavedSettingsObject.__init__(self) self._manipulate_encodings = manipulate_encodings - self._in_memory = in_memory if root == None: root = os.getcwd() if sink_to_existing_root == True: @@ -304,7 +304,6 @@ settings easy. Don't set this attribute. Set .rcs instead, and if from_disk == True: self.sync_with_disk = True - self._in_memory = False self.load() else: self.sync_with_disk = False @@ -316,15 +315,13 @@ settings easy. Don't set this attribute. Set .rcs instead, and self.rcs = rcs self._setup_user_id(self.user_id) - def set_sync_with_disk(self, value): - self.sync_with_disk = value - for bug in self: - bug.set_sync_with_disk(value) + # methods for getting the BugDir situated in the filesystem def _find_root(self, path): """ Search for an existing bug database dir and it's ancestors and - return a BugDir rooted there. + return a BugDir rooted there. Only called by __init__, and + then only if sink_to_existing_root == True. """ if not os.path.exists(path): raise NoRootEntry(path) @@ -340,9 +337,77 @@ settings easy. Don't set this attribute. Set .rcs instead, and raise NoBugDir(path) return beroot + def _guess_rcs(self, allow_rcs_init=False): + """ + Only called by __init__. + """ + deepdir = self.get_path() + if not os.path.exists(deepdir): + deepdir = os.path.dirname(deepdir) + new_rcs = rcs.detect_rcs(deepdir) + install = False + if new_rcs.name == "None": + if allow_rcs_init == True: + new_rcs = rcs.installed_rcs() + new_rcs.init(self.root) + return new_rcs + + def get_path(self, *args): + """ + Return a path relative to .root. + """ + 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) + + # methods for saving/loading/accessing settings and properties. + + def _get_settings(self, settings_path, for_duplicate_bugdir=False): + allow_no_rcs = not self.rcs.path_in_root(settings_path) + if allow_no_rcs == True: + assert for_duplicate_bugdir == True + if self.sync_with_disk == False and for_duplicate_bugdir == False: + # duplicates can ignore this bugdir's .sync_with_disk status + raise DiskAccessRequired("_get settings") + try: + settings = mapfile.map_load(self.rcs, settings_path, allow_no_rcs) + except rcs.NoSuchFile: + settings = {"rcs_name": "None"} + return settings + + def _save_settings(self, settings_path, settings, + for_duplicate_bugdir=False): + allow_no_rcs = not self.rcs.path_in_root(settings_path) + if allow_no_rcs == True: + assert for_duplicate_bugdir == True + if self.sync_with_disk == False and for_duplicate_bugdir == False: + # duplicates can ignore this bugdir's .sync_with_disk status + raise DiskAccessRequired("_save settings") + self.rcs.mkdir(self.get_path(), allow_no_rcs) + mapfile.map_save(self.rcs, settings_path, settings, allow_no_rcs) + + def load_settings(self): + self.settings = self._get_settings(self.get_path("settings")) + self._setup_saved_settings() + self._setup_user_id(self.user_id) + self._setup_encoding(self.encoding) + self._setup_severities(self.severities) + self._setup_status(self.active_status, self.inactive_status) + self.rcs = rcs.rcs_by_name(self.rcs_name) + self._setup_user_id(self.user_id) + + def save_settings(self): + settings = self._get_saved_settings() + self._save_settings(self.get_path("settings"), settings) + def get_version(self, path=None, use_none_rcs=False): - if self._in_memory == True: - return TREE_VERSION_STRING + """ + Requires disk access. + """ + if self.sync_with_disk == False: + raise DiskAccessRequired("get version") if use_none_rcs == True: RCS = rcs.rcs_by_name("None") RCS.root(self.root) @@ -356,102 +421,77 @@ settings easy. Don't set this attribute. Set .rcs instead, and return tree_version def set_version(self): - if self._in_memory == True: - return + """ + Requires disk access. + """ + if self.sync_with_disk == False: + raise DiskAccessRequired("set version") self.rcs.mkdir(self.get_path()) self.rcs.set_file_contents(self.get_path("version"), TREE_VERSION_STRING) - 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) + # methods controlling disk access - def _guess_rcs(self, allow_rcs_init=False): - deepdir = self.get_path() - if not os.path.exists(deepdir): - deepdir = os.path.dirname(deepdir) - new_rcs = rcs.detect_rcs(deepdir) - install = False - if new_rcs.name == "None": - if allow_rcs_init == True: - new_rcs = rcs.installed_rcs() - new_rcs.init(self.root) - return new_rcs + def set_sync_with_disk(self, value): + """ + Adjust .sync_with_disk for the BugDir and all it's children. + See the BugDir docstring for a description of the role of + .sync_with_disk. + """ + self.sync_with_disk = value + for bug in self: + bug.set_sync_with_disk(value) def load(self): + """ + Reqires disk access + """ version = self.get_version(use_none_rcs=True) if version != TREE_VERSION_STRING: raise NotImplementedError, \ "BugDir cannot handle version '%s' yet." % version else: - if not os.path.exists(self.get_path()) and self._in_memory == False: + if not os.path.exists(self.get_path()): raise NoBugDir(self.get_path()) self.load_settings() - self.rcs = rcs.rcs_by_name(self.rcs_name) - self._setup_user_id(self.user_id) - def load_all_bugs(self): - "Warning: this could take a while." - if self._in_memory == True: - return + """ + Requires disk access. + Warning: this could take a while. + """ + if self.sync_with_disk == False: + raise DiskAccessRequired("load all bugs") self._clear_bugs() for uuid in self.list_uuids(): self._load_bug(uuid) def save(self): """ + Note that this command writes to disk _regardless_ of the + status of .sync_with_disk. + Save any loaded contents to disk. Because of lazy loading of bugs and comments, this is actually not too inefficient. - However, if self.sync_with_disk = True, then any changes are + However, if .sync_with_disk = True, then any changes are automatically written to disk as soon as they happen, so calling this method will just waste time (unless something else has been messing with your on-disk files). + + Requires disk access. """ + sync_with_disk = self.sync_with_disk + if sync_with_disk == False: + self.set_sync_with_disk(True) self.set_version() self.save_settings() for bug in self: - if self._in_memory == True: - return bug.save() + if sync_with_disk == False: + self.set_sync_with_disk(sync_with_disk) - def load_settings(self): - self.settings = self._get_settings(self.get_path("settings")) - self._setup_saved_settings() - self._setup_user_id(self.user_id) - self._setup_encoding(self.encoding) - self._setup_severities(self.severities) - self._setup_status(self.active_status, self.inactive_status) - - def _get_settings(self, settings_path): - if self._in_memory == True: - return {} - 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 - - try: - settings = mapfile.map_load(self.rcs, settings_path, allow_no_rcs) - except rcs.NoSuchFile: - settings = {"rcs_name": "None"} - return settings - - def save_settings(self): - settings = self._get_saved_settings() - self._save_settings(self.get_path("settings"), settings) - - def _save_settings(self, settings_path, settings): - if self._in_memory == True: - return - 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 - self.rcs.mkdir(self.get_path(), allow_no_rcs) - mapfile.map_save(self.rcs, settings_path, settings, allow_no_rcs) + # methods for managing duplicate BugDirs def duplicate_bugdir(self, revision): duplicate_path = self.rcs.duplicate_repo(revision) @@ -460,23 +500,27 @@ settings easy. Don't set this attribute. Set .rcs instead, and # initialized for versioning duplicate_settings_path = os.path.join(duplicate_path, ".be", "settings") - duplicate_settings = self._get_settings(duplicate_settings_path) + duplicate_settings = self._get_settings(duplicate_settings_path, + for_duplicate_bugdir=True) if "rcs_name" in duplicate_settings: duplicate_settings["rcs_name"] = "None" duplicate_settings["user_id"] = self.user_id if "disabled" in bug.status_values: # Hack to support old versions of BE bugs duplicate_settings["inactive_status"] = self.inactive_status - self._save_settings(duplicate_settings_path, duplicate_settings) + self._save_settings(duplicate_settings_path, duplicate_settings, + for_duplicate_bugdir=True) return BugDir(duplicate_path, from_disk=True, manipulate_encodings=self._manipulate_encodings) def remove_duplicate_bugdir(self): self.rcs.remove_duplicate_repo() + # methods for managing bugs + def list_uuids(self): uuids = [] - if self._in_memory == False and os.path.exists(self.get_path()): + if self.sync_with_disk == True and os.path.exists(self.get_path()): # list the uuids on disk for uuid in os.listdir(self.get_path("bugs")): if not (uuid.startswith('.')): @@ -494,6 +538,8 @@ settings easy. Don't set this attribute. Set .rcs instead, and self._bug_map_gen() def _load_bug(self, uuid): + if self.sync_with_disk == False: + raise DiskAccessRequired("_load bug") bg = bug.Bug(bugdir=self, uuid=uuid, from_disk=True) self.append(bg) self._bug_map_gen() @@ -532,7 +578,7 @@ settings easy. Don't set this attribute. Set .rcs instead, and def bug_from_shortname(self, shortname): """ - >>> bd = simple_bug_dir() + >>> bd = simple_bug_dir(sync_with_disk=False) >>> bug_a = bd.bug_from_shortname('a') >>> print type(bug_a) @@ -566,30 +612,30 @@ settings easy. Don't set this attribute. Set .rcs instead, and return True -def simple_bug_dir(on_disk=True): +def simple_bug_dir(sync_with_disk=True): """ - For testing. Set on_disk==False for a memory-only bugdir. + For testing. Set sync_with_disk==False for a memory-only bugdir. >>> bugdir = simple_bug_dir() >>> uuids = list(bugdir.list_uuids()) >>> uuids.sort() >>> print uuids ['a', 'b'] """ - if on_disk == True: + if sync_with_disk == True: dir = utility.Dir() assert os.path.exists(dir.path) root = dir.path - in_memory = False + assert_new_BugDir = True rcs_init = True else: root = "/" - in_memory = True + assert_new_BugDir = False rcs_init = False bugdir = BugDir(root, sink_to_existing_root=False, - in_memory=in_memory, + assert_new_BugDir=assert_new_BugDir, allow_rcs_init=rcs_init, manipulate_encodings=False) - if on_disk == True: # postpone cleanup since dir.__del__() removes dir. + if sync_with_disk == True: # postpone cleanup since dir.__del__() removes dir. bugdir._dir_ref = dir bug_a = bugdir.new_bug("a", summary="Bug A") bug_a.creator = "John Doe " @@ -598,8 +644,9 @@ def simple_bug_dir(on_disk=True): bug_b.creator = "Jane Doe " bug_b.time = 0 bug_b.status = "closed" - if on_disk == True: + if sync_with_disk == True: bugdir.save() + bugdir.set_sync_with_disk(True) return bugdir @@ -673,9 +720,12 @@ class BugDirTestCase(unittest.TestCase): rep.new_reply("And they have six legs.") if sync_with_disk == False: self.bugdir.save() + self.bugdir.set_sync_with_disk(True) self.bugdir._clear_bugs() bug = self.bugdir.bug_from_uuid("a") bug.load_comments() + if sync_with_disk == False: + self.bugdir.set_sync_with_disk(False) self.failUnless(len(bug.comment_root)==1, len(bug.comment_root)) for index,comment in enumerate(bug.comments()): if index == 0: @@ -683,7 +733,6 @@ class BugDirTestCase(unittest.TestCase): self.failUnless(repLoaded.uuid == rep.uuid, repLoaded.uuid) self.failUnless(comment.sync_with_disk == True, comment.sync_with_disk) - #load_settings() self.failUnless(comment.content_type == "text/plain", comment.content_type) self.failUnless(repLoaded.settings["Content-type"]=="text/plain", @@ -712,9 +761,8 @@ class SimpleBugDirTestCase (unittest.TestCase): def tearDown(self): self.dir.cleanup() def testOnDiskCleanLoad(self): - """simple_bug_dir(on_disk==True) should not import preexisting bugs.""" - bugdir = simple_bug_dir(on_disk=True) - self.failUnless(bugdir._in_memory == False, bugdir._in_memory) + """simple_bug_dir(sync_with_disk==True) should not import preexisting bugs.""" + bugdir = simple_bug_dir(sync_with_disk=True) uuids = sorted([bug.uuid for bug in bugdir]) self.failUnless(uuids == ['a', 'b'], uuids) bugdir._clear_bugs() @@ -724,20 +772,16 @@ class SimpleBugDirTestCase (unittest.TestCase): uuids = sorted([bug.uuid for bug in bugdir]) self.failUnless(uuids == ['a', 'b'], uuids) def testInMemoryCleanLoad(self): - """simple_bug_dir(on_disk==False) should not import preexisting bugs.""" - bugdir = simple_bug_dir(on_disk=False) - self.failUnless(bugdir._in_memory == True, bugdir._in_memory) + """simple_bug_dir(sync_with_disk==False) should not import preexisting bugs.""" + bugdir = simple_bug_dir(sync_with_disk=False) uuids = sorted([bug.uuid for bug in bugdir]) self.failUnless(uuids == ['a', 'b'], uuids) - bugdir.load_all_bugs() + self.failUnlessRaises(DiskAccessRequired, bugdir.load_all_bugs) uuids = sorted([bug.uuid for bug in bugdir]) self.failUnless(uuids == ['a', 'b'], uuids) bugdir._clear_bugs() uuids = sorted([bug.uuid for bug in bugdir]) self.failUnless(uuids == [], uuids) - bugdir.load_all_bugs() - uuids = sorted([bug.uuid for bug in bugdir]) - self.failUnless(uuids == [], uuids) unitsuite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) -- cgit From 7c5b0f7abd46b2a6e093e47f7429e5f6c3eadab0 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 26 Jul 2009 20:40:08 -0400 Subject: Added DiskAccessRequired errors to libbe.bug.Bug and .comment.Comment. --- libbe/bug.py | 71 +++++++++++++++-------- libbe/comment.py | 171 +++++++++++++++++++++++++++++++------------------------ 2 files changed, 144 insertions(+), 98 deletions(-) (limited to 'libbe') diff --git a/libbe/bug.py b/libbe/bug.py index c1e5481..3d9cc08 100644 --- a/libbe/bug.py +++ b/libbe/bug.py @@ -33,6 +33,11 @@ import comment import utility +class DiskAccessRequired (Exception): + def __init__(self, goal): + msg = "Cannot %s without accessing the disk" % goal + Exception.__init__(self, msg) + ### Define and describe valid bug categories # Use a tuple of (category, description) tuples since we don't have # ordered dicts in Python yet http://www.python.org/dev/peps/pep-0372/ @@ -245,10 +250,13 @@ class Bug(settings_object.SavedSettingsObject): def __repr__(self): return "Bug(uuid=%r)" % self.uuid - def set_sync_with_disk(self, value): - self.sync_with_disk = value - for comment in self.comments(): - comment.set_sync_with_disk(value) + def __str__(self): + return self.string(shortlist=True) + + def __cmp__(self, other): + return cmp_full(self, other) + + # serializing methods def _setting_attr_string(self, setting): value = getattr(self, setting) @@ -331,11 +339,7 @@ class Bug(settings_object.SavedSettingsObject): output = bugout return output - def __str__(self): - return self.string(shortlist=True) - - def __cmp__(self, other): - return cmp_full(self, other) + # methods for saving/loading/acessing settings and properties. def get_path(self, name=None): my_dir = os.path.join(self.bugdir.get_path("bugs"), self.uuid) @@ -344,30 +348,25 @@ class Bug(settings_object.SavedSettingsObject): assert name in ["values", "comments"] return os.path.join(my_dir, name) + def set_sync_with_disk(self, value): + self.sync_with_disk = value + for comment in self.comments(): + comment.set_sync_with_disk(value) + def load_settings(self): + if self.sync_with_disk == False: + raise DiskAccessRequired("load settings") self.settings = mapfile.map_load(self.rcs, self.get_path("values")) self._setup_saved_settings() - def load_comments(self, load_full=True): - if load_full == True: - # Force a complete load of the whole comment tree - self.comment_root = self._get_comment_root(load_full=True) - else: - # Setup for fresh lazy-loading. Clear _comment_root, so - # _get_comment_root returns a fresh version. Turn of - # syncing temporarily so we don't write our blank comment - # tree to disk. - self.sync_with_disk = False - self.comment_root = None - self.sync_with_disk = True - def save_settings(self): + if self.sync_with_disk == False: + raise DiskAccessRequired("save settings") assert self.summary != None, "Can't save blank bug" - self.rcs.mkdir(self.get_path()) path = self.get_path("values") mapfile.map_save(self.rcs, path, self._get_saved_settings()) - + def save(self): """ Save any loaded contents to disk. Because of lazy loading of @@ -378,15 +377,39 @@ class Bug(settings_object.SavedSettingsObject): calling this method will just waste time (unless something else has been messing with your on-disk files). """ + sync_with_disk = self.sync_with_disk + if sync_with_disk == False: + self.set_sync_with_disk(True) self.save_settings() if len(self.comment_root) > 0: comment.saveComments(self) + if sync_with_disk == False: + self.set_sync_with_disk(False) + + def load_comments(self, load_full=True): + if self.sync_with_disk == False: + raise DiskAccessRequired("load comments") + if load_full == True: + # Force a complete load of the whole comment tree + self.comment_root = self._get_comment_root(load_full=True) + else: + # Setup for fresh lazy-loading. Clear _comment_root, so + # _get_comment_root returns a fresh version. Turn of + # syncing temporarily so we don't write our blank comment + # tree to disk. + self.sync_with_disk = False + self.comment_root = None + self.sync_with_disk = True def remove(self): + if self.sync_with_disk == False: + raise DiskAccessRequired("remove") self.comment_root.remove() path = self.get_path() self.rcs.recursive_remove(path) + # methods for managing comments + def comments(self): for comment in self.comment_root.traverse(): yield comment diff --git a/libbe/comment.py b/libbe/comment.py index 0d2fb57..20dab7e 100644 --- a/libbe/comment.py +++ b/libbe/comment.py @@ -61,6 +61,11 @@ class MissingReference(ValueError): self.reference = comment.in_reply_to self.comment = comment +class DiskAccessRequired (Exception): + def __init__(self, goal): + msg = "Cannot %s without accessing the disk" % goal + Exception.__init__(self, msg) + INVALID_UUID = "!!~~\n INVALID-UUID \n~~!!" def list_to_root(comments, bug, root=None, @@ -115,6 +120,8 @@ def loadComments(bug, load_full=False): Set load_full=True when you want to load the comment completely from disk *now*, rather than waiting and lazy loading as required. """ + if bug.sync_with_disk == False: + raise DiskAccessRequired("load comments") path = bug.get_path("comments") if not os.path.isdir(path): return Comment(bug, uuid=INVALID_UUID) @@ -131,6 +138,8 @@ def loadComments(bug, load_full=False): return list_to_root(comments, bug) def saveComments(bug): + if bug.sync_with_disk == False: + raise DiskAccessRequired("save comments") for comment in bug.comment_root.traverse(): comment.save() @@ -262,8 +271,21 @@ class Comment(Tree, settings_object.SavedSettingsObject): self.in_reply_to = in_reply_to self.body = body - def set_sync_with_disk(self, value): - self.sync_with_disk = True + def __str__(self): + """ + >>> comm = Comment(bug=None, body="Some insightful remarks") + >>> comm.uuid = "com-1" + >>> comm.date = "Thu, 20 Nov 2008 15:55:11 +0000" + >>> comm.author = "Jane Doe " + >>> print comm + --------- Comment --------- + Name: com-1 + From: Jane Doe + Date: Thu, 20 Nov 2008 15:55:11 +0000 + + Some insightful remarks + """ + return self.string() def traverse(self, *args, **kwargs): """Avoid working with the possible dummy root comment""" @@ -272,6 +294,8 @@ class Comment(Tree, settings_object.SavedSettingsObject): continue yield comment + # serializing methods + def _setting_attr_string(self, setting): value = getattr(self, setting) if value == None: @@ -413,78 +437,6 @@ class Comment(Tree, settings_object.SavedSettingsObject): sep = '\n' + istring return istring + sep.join(lines).rstrip('\n') - def __str__(self): - """ - >>> comm = Comment(bug=None, body="Some insightful remarks") - >>> comm.uuid = "com-1" - >>> comm.date = "Thu, 20 Nov 2008 15:55:11 +0000" - >>> comm.author = "Jane Doe " - >>> print comm - --------- Comment --------- - Name: com-1 - From: Jane Doe - Date: Thu, 20 Nov 2008 15:55:11 +0000 - - Some insightful remarks - """ - return self.string() - - def get_path(self, name=None): - my_dir = os.path.join(self.bug.get_path("comments"), self.uuid) - if name is None: - return my_dir - assert name in ["values", "body"] - return os.path.join(my_dir, name) - - def load_settings(self): - self.settings = mapfile.map_load(self.rcs, self.get_path("values")) - self._setup_saved_settings() - - def save_settings(self): - self.rcs.mkdir(self.get_path()) - path = self.get_path("values") - mapfile.map_save(self.rcs, path, self._get_saved_settings()) - - def save(self): - """ - Save any loaded contents to disk. - - However, if self.sync_with_disk = True, then any changes are - automatically written to disk as soon as they happen, so - calling this method will just waste time (unless something - else has been messing with your on-disk files). - """ - assert self.body != None, "Can't save blank comment" - self.save_settings() - self._set_comment_body(new=self.body, force=True) - - def remove(self): - for comment in self.traverse(): - path = comment.get_path() - self.rcs.recursive_remove(path) - - def add_reply(self, reply, allow_time_inversion=False): - if self.uuid != INVALID_UUID: - reply.in_reply_to = self.uuid - self.append(reply) - #raise Exception, "adding reply \n%s\n%s" % (self, reply) - - def new_reply(self, body=None): - """ - >>> comm = Comment(bug=None, body="Some insightful remarks") - >>> repA = comm.new_reply("Critique original comment") - >>> repB = repA.new_reply("Begin flamewar :p") - >>> repB.in_reply_to == repA.uuid - True - """ - reply = Comment(self.bug, body=body) - if self.bug != None: - reply.set_sync_with_disk(self.bug.sync_with_disk) - if reply.sync_with_disk == True: - reply.save() - self.add_reply(reply) - return reply - def string_thread(self, string_method_name="string", name_map={}, indent=0, flatten=True, auto_name_map=False, bug_shortname=None): @@ -589,6 +541,77 @@ class Comment(Tree, settings_object.SavedSettingsObject): indent=indent, auto_name_map=auto_name_map, bug_shortname=bug_shortname) + # methods for saving/loading/acessing settings and properties. + + def get_path(self, name=None): + my_dir = os.path.join(self.bug.get_path("comments"), self.uuid) + if name is None: + return my_dir + assert name in ["values", "body"] + return os.path.join(my_dir, name) + + def set_sync_with_disk(self, value): + self.sync_with_disk = True + + def load_settings(self): + if self.sync_with_disk == False: + raise DiskAccessRequired("load settings") + self.settings = mapfile.map_load(self.rcs, self.get_path("values")) + self._setup_saved_settings() + + def save_settings(self): + if self.sync_with_disk == False: + raise DiskAccessRequired("save settings") + self.rcs.mkdir(self.get_path()) + path = self.get_path("values") + mapfile.map_save(self.rcs, path, self._get_saved_settings()) + + def save(self): + """ + Save any loaded contents to disk. + + However, if self.sync_with_disk = True, then any changes are + automatically written to disk as soon as they happen, so + calling this method will just waste time (unless something + else has been messing with your on-disk files). + """ + sync_with_disk = self.sync_with_disk + if sync_with_disk == False: + self.set_sync_with_disk(True) + assert self.body != None, "Can't save blank comment" + self.save_settings() + self._set_comment_body(new=self.body, force=True) + if sync_with_disk == False: + self.set_sync_with_disk(False) + + def remove(self): + if self.sync_with_disk == False: + raise DiskAccessRequired("remove") + for comment in self.traverse(): + path = comment.get_path() + self.rcs.recursive_remove(path) + + def add_reply(self, reply, allow_time_inversion=False): + if self.uuid != INVALID_UUID: + reply.in_reply_to = self.uuid + self.append(reply) + + def new_reply(self, body=None): + """ + >>> comm = Comment(bug=None, body="Some insightful remarks") + >>> repA = comm.new_reply("Critique original comment") + >>> repB = repA.new_reply("Begin flamewar :p") + >>> repB.in_reply_to == repA.uuid + True + """ + reply = Comment(self.bug, body=body) + if self.bug != None: + reply.set_sync_with_disk(self.bug.sync_with_disk) + if reply.sync_with_disk == True: + reply.save() + self.add_reply(reply) + return reply + def comment_shortnames(self, bug_shortname=None): """ Iterate through (id, comment) pairs, in time order. -- cgit From 58e2b9fe7a13e7cd99e085059867453496712593 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 27 Jul 2009 05:11:33 -0400 Subject: .sync_with_disk fixes for libbe.bugdir and .comment. In BugDir, only call bug.remove if bug.sync_with_disk==True. If it's just in memory, automatic garbage collection is sufficient cleanup. Comment.set_sync_with_disk() had been setting .sync_with_disk=True regardless of the value passed in. Fixed now. Also some minor textual adjustments. --- libbe/bugdir.py | 9 ++++++--- libbe/comment.py | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 'libbe') diff --git a/libbe/bugdir.py b/libbe/bugdir.py index 7380172..e9854c9 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -352,6 +352,8 @@ settings easy. Don't set this attribute. Set .rcs instead, and new_rcs.init(self.root) return new_rcs + # methods for saving/loading/accessing settings and properties. + def get_path(self, *args): """ Return a path relative to .root. @@ -362,8 +364,6 @@ settings easy. Don't set this attribute. Set .rcs instead, and assert args[0] in ["version", "settings", "bugs"], str(args) return os.path.join(my_dir, *args) - # methods for saving/loading/accessing settings and properties. - def _get_settings(self, settings_path, for_duplicate_bugdir=False): allow_no_rcs = not self.rcs.path_in_root(settings_path) if allow_no_rcs == True: @@ -556,7 +556,8 @@ settings easy. Don't set this attribute. Set .rcs instead, and def remove_bug(self, bug): self.remove(bug) - bug.remove() + if bug.sync_with_disk == True: + bug.remove() def bug_shortname(self, bug): """ @@ -763,6 +764,7 @@ class SimpleBugDirTestCase (unittest.TestCase): def testOnDiskCleanLoad(self): """simple_bug_dir(sync_with_disk==True) should not import preexisting bugs.""" bugdir = simple_bug_dir(sync_with_disk=True) + self.failUnless(bugdir.sync_with_disk==True, bugdir.sync_with_disk) uuids = sorted([bug.uuid for bug in bugdir]) self.failUnless(uuids == ['a', 'b'], uuids) bugdir._clear_bugs() @@ -774,6 +776,7 @@ class SimpleBugDirTestCase (unittest.TestCase): def testInMemoryCleanLoad(self): """simple_bug_dir(sync_with_disk==False) should not import preexisting bugs.""" bugdir = simple_bug_dir(sync_with_disk=False) + self.failUnless(bugdir.sync_with_disk==False, bugdir.sync_with_disk) uuids = sorted([bug.uuid for bug in bugdir]) self.failUnless(uuids == ['a', 'b'], uuids) self.failUnlessRaises(DiskAccessRequired, bugdir.load_all_bugs) diff --git a/libbe/comment.py b/libbe/comment.py index 20dab7e..7b43c08 100644 --- a/libbe/comment.py +++ b/libbe/comment.py @@ -551,7 +551,7 @@ class Comment(Tree, settings_object.SavedSettingsObject): return os.path.join(my_dir, name) def set_sync_with_disk(self, value): - self.sync_with_disk = True + self.sync_with_disk = value def load_settings(self): if self.sync_with_disk == False: @@ -608,6 +608,7 @@ class Comment(Tree, settings_object.SavedSettingsObject): if self.bug != None: reply.set_sync_with_disk(self.bug.sync_with_disk) if reply.sync_with_disk == True: + raise Exception, self.bug.sync_with_disk reply.save() self.add_reply(reply) return reply -- cgit From e21a427afbc7369cfa3a3f786e51aaa1e3e01999 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 27 Jul 2009 05:14:49 -0400 Subject: Major rewrite of libbe.diff introduces DiffTree and Diff classes. To make the interface proposed by becommands/subscribers.py easier to implement, I've moved the libbe.diff functionality into classes. Now it should be easy two tweak the output as desired by subclassing these classes. The basic idea is that Diff.report_tree() generates a diff_tree tree of changes between two bugdirs, where diff_tree is some subclass of DiffTree. Each type of change has a default .*_string() method producing a string summary of the change. DiffTree.report() moves through and generates a report by joining all those summary strings to a single root, and DiffTree.report_string() serialized the report to produce e.g. the output of becommands/diff.py. --- libbe/diff.py | 462 ++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 368 insertions(+), 94 deletions(-) (limited to 'libbe') diff --git a/libbe/diff.py b/libbe/diff.py index ba48efc..59e7c66 100644 --- a/libbe/diff.py +++ b/libbe/diff.py @@ -15,111 +15,385 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """Compare two bug trees""" -from libbe import cmdutil, bugdir, bug +from libbe import bugdir, bug, settings_object, tree from libbe.utility import time_to_str +import difflib import doctest -def bug_diffs(old_bugdir, new_bugdir): - added = [] - removed = [] - modified = [] - for uuid in old_bugdir.list_uuids(): - old_bug = old_bugdir.bug_from_uuid(uuid) - try: - new_bug = new_bugdir.bug_from_uuid(uuid) - old_bug.load_comments() - new_bug.load_comments() - if old_bug != new_bug: - modified.append((old_bug, new_bug)) - except KeyError: - removed.append(old_bug) - for uuid in new_bugdir.list_uuids(): - if not old_bugdir.has_bug(uuid): - new_bug = new_bugdir.bug_from_uuid(uuid) - added.append(new_bug) - return (removed, modified, added) +class DiffTree (tree.Tree): + """ + A tree holding difference data for easy report generation. + >>> all = DiffTree("all") + >>> bugdir = DiffTree("bugdir", data="target: None -> 1.0") + >>> all.append(bugdir) + >>> bugs = DiffTree("bugs", "bug-count: 5 -> 6") + >>> all.append(bugs) + >>> new = DiffTree("new", "new bugs: ABC, DEF") + >>> bugs.append(new) + >>> rem = DiffTree("rem", "removed bugs: RST, UVW") + >>> bugs.append(rem) + >>> print all.report_string() + target: None -> 1.0 + bug-count: 5 -> 6 + new bugs: ABC, DEF + removed bugs: RST, UVW + >>> print "\\n".join(all.paths()) + all + all/bugdir + all/bugs + all/bugs/new + all/bugs/rem + >>> all.child_by_path("/") == all + True + >>> all.child_by_path("/bugs") == bugs + True + >>> all.child_by_path("/bugs/rem") == rem + True + >>> all.child_by_path("all") == all + True + >>> all.child_by_path("all/") == all + True + >>> all.child_by_path("all/bugs") == bugs + True + >>> all.child_by_path("/bugs").masked = True + >>> print all.report_string() + target: None -> 1.0 + """ + def __init__(self, name, data=None, data_string_fn=str, + requires_children=False, masked=False): + tree.Tree.__init__(self) + self.name = name + self.data = data + self.data_string_fn = data_string_fn + self.requires_children = requires_children + self.masked = masked + def paths(self, parent_path=None): + paths = [] + if parent_path == None: + path = self.name + else: + path = "%s/%s" % (parent_path, self.name) + paths.append(path) + for child in self: + paths.extend(child.paths(path)) + return paths + def child_by_path(self, path): + if hasattr(path, "split"): # convert string path to a list of names + names = path.split("/") + if names[0] == "": + names[0] = self.name # replace root with self + if len(names) > 1 and names[-1] == "": + names = names[:-1] # strip empty tail + else: # it was already an array + names = path + assert len(names) > 0, path + if names[0] == self.name: + if len(names) == 1: + return self + for child in self: + if names[1] == child.name: + return child.child_by_path(names[1:]) + if len(names) == 1: + raise KeyError, "%s doesn't match '%s'" % (names, self.name) + raise KeyError, "%s points to child not in %s" % (names, [c.name for c in self]) + def report_string(self): + return "\n".join(self.report()) + def report(self, root=None, depth=0): + if root == None: + root = self.make_root() + if self.masked == True: + return None + data_string = self.data_string(depth) + if self.data == None: + pass + elif self.requires_children == True and len(self) == 0: + pass + else: + self.join(root, data_string) + depth += 1 + for child in self: + child.report(root, depth) + return root + def make_root(self): + return [] + def join(self, root, part): + if part != None: + root.append(part) + def data_string(self, depth, indent=True): + data_string = self.data_string_fn(self.data) + if indent == True: + data_string_lines = data_string.splitlines() + indent = " "*(depth) + line_sep = "\n"+indent + data_string = indent+line_sep.join(data_string_lines) + return data_string -def diff_report(bug_diffs_data, old_bugdir, new_bugdir): - bugs_removed,bugs_modified,bugs_added = bug_diffs_data - def modified_cmp(left, right): - return bug.cmp_severity(left[1], right[1]) +class Diff (object): + """ + Difference tree generator for BugDirs. + >>> import copy + >>> bd = bugdir.simple_bug_dir(sync_with_disk=False) + >>> bd.user_id = "John Doe " + >>> bd_new = copy.deepcopy(bd) + >>> bd_new.target = "1.0" + >>> a = bd_new.bug_from_uuid("a") + >>> rep = a.comment_root.new_reply("I'm closing this bug") + >>> rep.uuid = "acom" + >>> rep.date = "Thu, 01 Jan 1970 00:00:00 +0000" + >>> a.status = "closed" + >>> b = bd_new.bug_from_uuid("b") + >>> bd_new.remove_bug(b) + >>> c = bd_new.new_bug("c", "Bug C") + >>> d = Diff(bd, bd_new) + >>> r = d.report_tree() + >>> print "\\n".join(r.paths()) + bugdir + bugdir/settings + bugdir/bugs + bugdir/bugs/new + bugdir/bugs/new/c + bugdir/bugs/rem + bugdir/bugs/rem/b + bugdir/bugs/mod + bugdir/bugs/mod/a + bugdir/bugs/mod/a/settings + bugdir/bugs/mod/a/comments + bugdir/bugs/mod/a/comments/new + bugdir/bugs/mod/a/comments/new/acom + bugdir/bugs/mod/a/comments/rem + bugdir/bugs/mod/a/comments/mod + >>> print r.report_string() + Changed bug directory settings: + target: None -> 1.0 + New bugs: + c:om: Bug C + Removed bugs: + b:cm: Bug B + Modified bugs: + a:cm: Bug A + Changed bug settings: + status: open -> closed + New comments: + from John Doe on Thu, 01 Jan 1970 00:00:00 +0000 + """ + def __init__(self, old_bugdir, new_bugdir): + self.old_bugdir = old_bugdir + self.new_bugdir = new_bugdir - bugs_added.sort(bug.cmp_severity) - bugs_removed.sort(bug.cmp_severity) - bugs_modified.sort(modified_cmp) - lines = [] - - if old_bugdir.settings != new_bugdir.settings: - bugdir_settings = sorted(new_bugdir.settings_properties) - bugdir_settings.remove("rcs_name") # tweaked by bugdir.duplicate_bugdir - change_list = change_lines(old_bugdir, new_bugdir, bugdir_settings) - if len(change_list) > 0: - lines.append("Modified bug directory:") - change_strings = ["%s: %s -> %s" % f for f in change_list] - lines.extend(change_strings) - lines.append("") - if len(bugs_added) > 0: - lines.append("New bug reports:") - for bg in bugs_added: - lines.extend(bg.string(shortlist=True).splitlines()) - lines.append("") - if len(bugs_modified) > 0: - printed = False - for old_bug, new_bug in bugs_modified: - change_str = bug_changes(old_bug, new_bug) - if change_str is None: - continue - if not printed: - printed = True - lines.append("Modified bug reports:") - lines.extend(change_str.splitlines()) - if printed == True: - lines.append("") - if len(bugs_removed) > 0: - lines.append("Removed bug reports:") - for bg in bugs_removed: - lines.extend(bg.string(shortlist=True).splitlines()) - lines.append("") - - return "\n".join(lines).rstrip("\n") + # data assembly methods -def change_lines(old, new, attributes): - change_list = [] - for attr in attributes: - old_attr = getattr(old, attr) - new_attr = getattr(new, attr) - if old_attr != new_attr: - change_list.append((attr, old_attr, new_attr)) - if len(change_list) >= 0: - return change_list - else: + def _changed_bugs(self): + """ + Search for differences in all bugs between .old_bugdir and + .new_bugdir. Returns + (added_bugs, modified_bugs, removed_bugs) + where added_bugs and removed_bugs are lists of added and + removed bugs respectively. modified_bugs is a list of + (old_bug,new_bug) pairs. + """ + if hasattr(self, "__changed_bugs"): + return self.__changed_bugs + added = [] + removed = [] + modified = [] + for uuid in self.new_bugdir.list_uuids(): + new_bug = self.new_bugdir.bug_from_uuid(uuid) + try: + old_bug = self.old_bugdir.bug_from_uuid(uuid) + except KeyError: + added.append(new_bug) + else: + if old_bug.sync_with_disk == True: + old_bug.load_comments() + if new_bug.sync_with_disk == True: + new_bug.load_comments() + if old_bug != new_bug: + modified.append((old_bug, new_bug)) + for uuid in self.old_bugdir.list_uuids(): + if not self.new_bugdir.has_bug(uuid): + old_bug = self.old_bugdir.bug_from_uuid(uuid) + removed.append(old_bug) + added.sort(bug.cmp_severity) + removed.sort(bug.cmp_severity) + modified.sort(self._bug_modified_cmp) + self.__changed_bugs = (added, modified, removed) + return self.__changed_bugs + def _bug_modified_cmp(self, left, right): + return bug.cmp_severity(left[1], right[1]) + def _changed_comments(self, old, new): + """ + Search for differences in all loaded comments between the bugs + old and new. Returns + (added_comments, modified_comments, removed_comments) + analogous to ._changed_bugs. + """ + if hasattr(self, "__changed_comments"): + if new.uuid in self.__changed_comments: + return self.__changed_comments[new.uuid] + else: + self.__changed_comments = {} + added = [] + removed = [] + modified = [] + old.comment_root.sort(key=lambda comm : comm.time) + new.comment_root.sort(key=lambda comm : comm.time) + old_comment_ids = [c.uuid for c in old.comments()] + new_comment_ids = [c.uuid for c in new.comments()] + for uuid in new_comment_ids: + new_comment = new.comment_from_uuid(uuid) + try: + old_comment = old.comment_from_uuid(uuid) + except KeyError: + added.append(new_comment) + else: + if old_comment != new_comment: + modified.append((old_comment, new_comment)) + for uuid in old_comment_ids: + if uuid not in new_comment_ids: + new_comment = new.comment_from_uuid(uuid) + removed.append(new_comment) + self.__changed_comments[new.uuid] = (added, modified, removed) + return self.__changed_comments[new.uuid] + def _attribute_changes(self, old, new, attributes): + """ + Take two objects old and new, and compare the value of *.attr + for attr in the list attribute names. Returns a list of + (attr_name, old_value, new_value) + tuples. + """ + change_list = [] + for attr in attributes: + old_value = getattr(old, attr) + new_value = getattr(new, attr) + if old_value != new_value: + change_list.append((attr, old_value, new_value)) + if len(change_list) >= 0: + return change_list return None + def _settings_properties_attribute_changes(self, old, new, + hidden_properties=[]): + properties = sorted(new.settings_properties) + for p in hidden_properties: + properties.remove(p) + attributes = [settings_object.setting_name_to_attr_name(None, p) + for p in properties] + return self._attribute_changes(old, new, attributes) + def _bugdir_attribute_changes(self): + return self._settings_properties_attribute_changes( \ + self.old_bugdir, self.new_bugdir, + ["rcs_name"]) # tweaked by bugdir.duplicate_bugdir + def _bug_attribute_changes(self, old, new): + return self._settings_properties_attribute_changes(old, new) + def _comment_attribute_changes(self, old, new): + return self._settings_properties_attribute_changes(old, new) -def bug_changes(old, new): - bug_settings = sorted(new.settings_properties) - change_list = change_lines(old, new, bug_settings) - change_strings = ["%s: %s -> %s" % f for f in change_list] + # report generation methods - old_comment_ids = [c.uuid for c in old.comments()] - new_comment_ids = [c.uuid for c in new.comments()] - for comment_id in new_comment_ids: - if comment_id not in old_comment_ids: - summary = comment_summary(new.comment_from_uuid(comment_id), "new") - change_strings.append(summary) - for comment_id in old_comment_ids: - if comment_id not in new_comment_ids: - summary = comment_summary(new.comment_from_uuid(comment_id), - "removed") - change_strings.append(summary) + def report_tree(self, diff_tree=DiffTree): + """ + Pretty bare to make it easy to adjust to specific cases. You + can pass in a DiffTree subclass via diff_tree to override the + default report assembly process. + """ + if hasattr(self, "__report_tree"): + return self.__report_tree + bugdir_settings = sorted(self.new_bugdir.settings_properties) + bugdir_settings.remove("rcs_name") # tweaked by bugdir.duplicate_bugdir + root = diff_tree("bugdir") + bugdir_attribute_changes = self._bugdir_attribute_changes() + if len(bugdir_attribute_changes) > 0: + bugdir = diff_tree("settings", bugdir_attribute_changes, + self.bugdir_attribute_change_string) + root.append(bugdir) + bug_root = diff_tree("bugs") + root.append(bug_root) + add,mod,rem = self._changed_bugs() + bnew = diff_tree("new", "New bugs:", requires_children=True) + bug_root.append(bnew) + for bug in add: + b = diff_tree(bug.uuid, bug, self.bug_add_string) + bnew.append(b) + brem = diff_tree("rem", "Removed bugs:", requires_children=True) + bug_root.append(brem) + for bug in rem: + b = diff_tree(bug.uuid, bug, self.bug_rem_string) + brem.append(b) + bmod = diff_tree("mod", "Modified bugs:", requires_children=True) + bug_root.append(bmod) + for old,new in mod: + b = diff_tree(new.uuid, (old,new), self.bug_mod_string) + bmod.append(b) + bug_attribute_changes = self._bug_attribute_changes(old, new) + if len(bug_attribute_changes) > 0: + bset = diff_tree("settings", bug_attribute_changes, + self.bug_attribute_change_string) + b.append(bset) + if old.summary != new.summary: + data = (old.summary, new.summary) + bsum = diff_tree("summary", data, self.bug_summary_change_string) + b.append(bsum) + cr = diff_tree("comments") + b.append(cr) + a,m,d = self._changed_comments(old, new) + cnew = diff_tree("new", "New comments:", requires_children=True) + for comment in a: + c = diff_tree(comment.uuid, comment, self.comment_add_string) + cnew.append(c) + crem = diff_tree("rem", "Removed comments:",requires_children=True) + for comment in d: + c = diff_tree(comment.uuid, comment, self.comment_rem_string) + crem.append(c) + cmod = diff_tree("mod","Modified comments:",requires_children=True) + for o,n in m: + c = diff_tree(n.uuid, self._comment_attribute_changes(o, n), + self.comment_attribute_change_string) + cmod.append(c) + if o.body != n.body: + data = (o.body, n.body) + cbody = diff_tree("cbody", data, + self.comment_body_change_string) + c.append(cbody) + cr.extend([cnew, crem, cmod]) + self.__report_tree = root + return self.__report_tree - if len(change_strings) == 0: - return None - return "%s\n %s" % (new.string(shortlist=True), - " \n".join(change_strings)) + # change data -> string methods. + # Feel free to play with these in subclasses. + def attribute_change_string(self, attribute_changes, indent=0): + indent_string = " "*indent + change_strings = [u"%s: %s -> %s" % f for f in attribute_changes] + for i,change_string in enumerate(change_strings): + change_strings[i] = indent_string+change_string + return u"\n".join(change_strings) + def bugdir_attribute_change_string(self, attribute_changes): + return "Changed bug directory settings:\n%s" % \ + self.attribute_change_string(attribute_changes, indent=1) + def bug_attribute_change_string(self, attribute_changes): + return "Changed bug settings:\n%s" % \ + self.attribute_change_string(attribute_changes, indent=1) + def comment_attribute_change_string(self, attribute_changes): + return "Changed comment settings:\n%s" % \ + self.attribute_change_string(attribute_changes, indent=1) + def bug_add_string(self, bug): + return bug.string(shortlist=True) + def bug_rem_string(self, bug): + return bug.string(shortlist=True) + def bug_mod_string(self, bugs): + old_bug,new_bug = bugs + return new_bug.string(shortlist=True) + def bug_summary_change_string(self, summaries): + old_summary,new_summary = summaries + return "summary changed:\n %s\n %s" % (old_summary, new_summary) + def _comment_summary_string(self, comment): + return "from %s on %s" % (comment.author, time_to_str(comment.time)) + def comment_add_string(self, comment): + return self._comment_summary_string(comment) + def comment_rem_string(self, comment): + return self._comment_summary_string(comment) + def comment_body_change_string(self, bodies): + old_body,new_body = bodies + return difflib.unified_diff(old_body, new_body) -def comment_summary(comment, status): - return "%8s comment from %s on %s" % (status, comment.From, - time_to_str(comment.time)) suite = doctest.DocTestSuite() -- cgit From 5ce4bd3f4ce955577c46fe8e3c5a8f54b60ce3df Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 27 Jul 2009 05:57:40 -0400 Subject: Cache data strings in libbe.diff.DiffTree. This makes repeated .report() generation from the same tree more efficient. --- libbe/diff.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'libbe') diff --git a/libbe/diff.py b/libbe/diff.py index 59e7c66..decbcf5 100644 --- a/libbe/diff.py +++ b/libbe/diff.py @@ -120,12 +120,15 @@ class DiffTree (tree.Tree): if part != None: root.append(part) def data_string(self, depth, indent=True): + if hasattr(self, "_cached_data_string"): + return self._cached_data_string data_string = self.data_string_fn(self.data) if indent == True: data_string_lines = data_string.splitlines() indent = " "*(depth) line_sep = "\n"+indent data_string = indent+line_sep.join(data_string_lines) + self._cached_data_string = data_string return data_string class Diff (object): -- cgit From 3711c5080619b0decf0ae040a9d244bf3b902c41 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 27 Jul 2009 06:23:20 -0400 Subject: Hack Comment.load_settings() to work around From->Author change. "Author" -> comment.author obeys settings_object.setting_name_to_attr_name(), but all the current on-disk mapfiles talk about "From". Add a hack to accept both forms of on-disk comment files. --- libbe/comment.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'libbe') diff --git a/libbe/comment.py b/libbe/comment.py index 7b43c08..53519bf 100644 --- a/libbe/comment.py +++ b/libbe/comment.py @@ -557,6 +557,9 @@ class Comment(Tree, settings_object.SavedSettingsObject): if self.sync_with_disk == False: raise DiskAccessRequired("load settings") self.settings = mapfile.map_load(self.rcs, self.get_path("values")) + # hack to deal with old BE comments: + if "From" in self.settings: + self.settings["Author"] = self.settings.pop("From") self._setup_saved_settings() def save_settings(self): -- cgit From 159dc9303e10cef81388fb686f79312cc1018c65 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 27 Jul 2009 07:18:13 -0400 Subject: Added cmp functions to libbe.comment, and fleshed them out in libbe.bug. Previous comment comparison had just been the default Tree.__cmp__. Fleshed out so A == B ensures no meaningful differences between A and B. Also added first line of comments to new comment output in libbe.diff, and added a comment/"settings" node and .comment_mod_string() (to mirror bugdir and bug). --- libbe/bug.py | 7 ++++++- libbe/comment.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ libbe/diff.py | 20 +++++++++++++------ 3 files changed, 78 insertions(+), 7 deletions(-) (limited to 'libbe') diff --git a/libbe/bug.py b/libbe/bug.py index 3d9cc08..7554318 100644 --- a/libbe/bug.py +++ b/libbe/bug.py @@ -512,8 +512,12 @@ def cmp_attr(bug_1, bug_2, attr, invert=False): return cmp(val_1, val_2) # alphabetical rankings (a < z) +cmp_uuid = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "uuid") cmp_creator = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "creator") cmp_assigned = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "assigned") +cmp_target = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "target") +cmp_reporter = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "reporter") +cmp_summary = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "summary") # chronological rankings (newer < older) cmp_time = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "time", invert=True) @@ -535,7 +539,8 @@ def cmp_comments(bug_1, bug_2): return 0 DEFAULT_CMP_FULL_CMP_LIST = \ - (cmp_status,cmp_severity,cmp_assigned,cmp_time,cmp_creator,cmp_comments) + (cmp_status, cmp_severity, cmp_assigned, cmp_time, cmp_creator, + cmp_reporter, cmp_target, cmp_comments, cmp_summary, cmp_uuid) class BugCompoundComparator (object): def __init__(self, cmp_list=DEFAULT_CMP_FULL_CMP_LIST): diff --git a/libbe/comment.py b/libbe/comment.py index 53519bf..bf14920 100644 --- a/libbe/comment.py +++ b/libbe/comment.py @@ -271,6 +271,9 @@ class Comment(Tree, settings_object.SavedSettingsObject): self.in_reply_to = in_reply_to self.body = body + def __cmp__(self, other): + return cmp_full(self, other) + def __str__(self): """ >>> comm = Comment(bug=None, body="Some insightful remarks") @@ -682,4 +685,59 @@ class Comment(Tree, settings_object.SavedSettingsObject): return comment raise KeyError(uuid) +def cmp_attr(comment_1, comment_2, attr, invert=False): + """ + Compare a general attribute between two comments using the conventional + comparison rule for that attribute type. If invert == True, sort + *against* that convention. + >>> attr="author" + >>> commentA = Comment() + >>> commentB = Comment() + >>> commentA.author = "John Doe" + >>> commentB.author = "Jane Doe" + >>> cmp_attr(commentA, commentB, attr) < 0 + True + >>> cmp_attr(commentA, commentB, attr, invert=True) > 0 + True + >>> commentB.author = "John Doe" + >>> cmp_attr(commentA, commentB, attr) == 0 + True + """ + if not hasattr(comment_2, attr) : + return 1 + val_1 = getattr(comment_1, attr) + val_2 = getattr(comment_2, attr) + if val_1 == None: val_1 = None + if val_2 == None: val_2 = None + + if invert == True : + return -cmp(val_1, val_2) + else : + return cmp(val_1, val_2) + +# alphabetical rankings (a < z) +cmp_uuid = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "uuid") +cmp_author = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "author") +cmp_in_reply_to = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "in_reply_to") +cmp_content_type = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "content_type") +cmp_body = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "body") +# chronological rankings (newer < older) +cmp_time = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "time", invert=True) + +DEFAULT_CMP_FULL_CMP_LIST = \ + (cmp_time, cmp_author, cmp_content_type, cmp_body, cmp_in_reply_to, + cmp_uuid) + +class CommentCompoundComparator (object): + def __init__(self, cmp_list=DEFAULT_CMP_FULL_CMP_LIST): + self.cmp_list = cmp_list + def __call__(self, comment_1, comment_2): + for comparison in self.cmp_list : + val = comparison(comment_1, comment_2) + if val != 0 : + return val + return 0 + +cmp_full = CommentCompoundComparator() + suite = doctest.DocTestSuite() diff --git a/libbe/diff.py b/libbe/diff.py index decbcf5..4164e94 100644 --- a/libbe/diff.py +++ b/libbe/diff.py @@ -216,13 +216,13 @@ class Diff (object): if not self.new_bugdir.has_bug(uuid): old_bug = self.old_bugdir.bug_from_uuid(uuid) removed.append(old_bug) - added.sort(bug.cmp_severity) - removed.sort(bug.cmp_severity) + added.sort() + removed.sort() modified.sort(self._bug_modified_cmp) self.__changed_bugs = (added, modified, removed) return self.__changed_bugs def _bug_modified_cmp(self, left, right): - return bug.cmp_severity(left[1], right[1]) + return cmp(left[1], right[1]) def _changed_comments(self, old, new): """ Search for differences in all loaded comments between the bugs @@ -348,9 +348,12 @@ class Diff (object): crem.append(c) cmod = diff_tree("mod","Modified comments:",requires_children=True) for o,n in m: - c = diff_tree(n.uuid, self._comment_attribute_changes(o, n), - self.comment_attribute_change_string) + c = diff_tree(n.uuid, (o,n), self.comment_mod_string) cmod.append(c) + comm_attribute_changes = self._comment_attribute_changes(o, n) + if len(comm_attribute_changes) > 0: + cset = diff_tree("settings", comm_attribute_changes, + self.comment_attribute_change_string) if o.body != n.body: data = (o.body, n.body) cbody = diff_tree("cbody", data, @@ -391,9 +394,14 @@ class Diff (object): def _comment_summary_string(self, comment): return "from %s on %s" % (comment.author, time_to_str(comment.time)) def comment_add_string(self, comment): - return self._comment_summary_string(comment) + summary = self._comment_summary_string(comment) + first_line = comment.body.splitlines()[0] + return "%s\n %s..." % (summary, first_line) def comment_rem_string(self, comment): return self._comment_summary_string(comment) + def comment_mod_string(self, comments): + old_comment,new_comment = comments + return self._comment_summary_string(new_comment) def comment_body_change_string(self, bodies): old_body,new_body = bodies return difflib.unified_diff(old_body, new_body) -- cgit From 0cacaf8809ade66e595361b30290234be3a6d8b0 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 27 Jul 2009 07:47:35 -0400 Subject: Added default to settings_object.versioned_property's change_hook_property. Now change_hook properties handle defaults, which allows them to avoid an initial None -> default save hook trigger. Removed the now-redundant read-only mode business in becommands/diff.py. --- libbe/properties.py | 10 +++++----- libbe/settings_object.py | 11 +++-------- 2 files changed, 8 insertions(+), 13 deletions(-) (limited to 'libbe') diff --git a/libbe/properties.py b/libbe/properties.py index 144220b..09dd20e 100644 --- a/libbe/properties.py +++ b/libbe/properties.py @@ -160,10 +160,10 @@ def _get_cached_mutable_property(self, cacher_name, property_name, default=None) if (cacher_name, property_name) not in self._mutable_property_cache_copy: return default return self._mutable_property_cache_copy[(cacher_name, property_name)] -def _cmp_cached_mutable_property(self, cacher_name, property_name, value): +def _cmp_cached_mutable_property(self, cacher_name, property_name, value, default=None): _init_mutable_property_cache(self) if (cacher_name, property_name) not in self._mutable_property_cache_hash: - return 1 # any value > non-existant old hash + _set_cached_mutable_property(self, cacher_name, property_name, default) old_hash = self._mutable_property_cache_hash[(cacher_name, property_name)] return cmp(_hash_mutable_value(value), old_hash) @@ -327,7 +327,7 @@ def primed_property(primer, initVal=None): return funcs return decorator -def change_hook_property(hook, mutable=False): +def change_hook_property(hook, mutable=False, default=None): """ Call the function hook(instance, old_value, new_value) whenever a value different from the current value is set (instance is a a @@ -359,9 +359,9 @@ def change_hook_property(hook, mutable=False): value = new_value # compare new value with cached else: value = fget(self) # compare current value with cached - if _cmp_cached_mutable_property(self, "change hook property", name, value) != 0: + if _cmp_cached_mutable_property(self, "change hook property", name, value, default) != 0: # there has been a change, cache new value - old_value = _get_cached_mutable_property(self, "change hook property", name) + old_value = _get_cached_mutable_property(self, "change hook property", name, default) _set_cached_mutable_property(self, "change hook property", name, value) if from_fset == True: # return previously cached value value = old_value diff --git a/libbe/settings_object.py b/libbe/settings_object.py index dde247f..ceea9d5 100644 --- a/libbe/settings_object.py +++ b/libbe/settings_object.py @@ -148,7 +148,8 @@ def versioned_property(name, doc, checked = checked_property(allowed=allowed) fulldoc += "\n\nThe allowed values for this property are: %s." \ % (', '.join(allowed)) - hooked = change_hook_property(hook=change_hook, mutable=mutable) + hooked = change_hook_property(hook=change_hook, mutable=mutable, + default=EMPTY) primed = primed_property(primer=primer, initVal=UNPRIMED) settings = settings_property(name=name, null=UNPRIMED) docp = doc_property(doc=fulldoc) @@ -385,30 +386,24 @@ class SavedSettingsObjectTests(unittest.TestCase): self.failUnless(SAVES == [], SAVES) self.failUnless(t._settings_loaded == True, t._settings_loaded) self.failUnless(t.list_type == None, t.list_type) - self.failUnless(SAVES == [ - "'None' -> ''" - ], SAVES) + self.failUnless(SAVES == [], SAVES) 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 == [ - "'None' -> ''", "'' -> '[]'" ], SAVES) t.list_type.append(5) self.failUnless(SAVES == [ - "'None' -> ''", "'' -> '[]'", ], SAVES) self.failUnless(t.settings["List-type"] == [5],t.settings["List-type"]) self.failUnless(SAVES == [ # the append(5) has not yet been saved - "'None' -> ''", "'' -> '[]'", ], SAVES) self.failUnless(t.list_type == [5], t.list_type) # <-get triggers saved self.failUnless(SAVES == [ # now the append(5) has been saved. - "'None' -> ''", "'' -> '[]'", "'[]' -> '[5]'" ], SAVES) -- cgit From 8ac7a799ad7917324edbdb15ccbbd3bfcd529896 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 27 Jul 2009 07:56:17 -0400 Subject: Comment.remove() now ignores .sync_with_disk when removing the root comment. --- libbe/comment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe') diff --git a/libbe/comment.py b/libbe/comment.py index bf14920..1e2ef1a 100644 --- a/libbe/comment.py +++ b/libbe/comment.py @@ -591,7 +591,7 @@ class Comment(Tree, settings_object.SavedSettingsObject): self.set_sync_with_disk(False) def remove(self): - if self.sync_with_disk == False: + if self.sync_with_disk == False and self.uuid != INVALID_UUID: raise DiskAccessRequired("remove") for comment in self.traverse(): path = comment.get_path() -- cgit From 0f42025f8f41ea6d354b68e9ba86f2e966cea4b5 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 27 Jul 2009 07:59:38 -0400 Subject: Removed debugging exception from libbe/comment.py. --- libbe/comment.py | 1 - 1 file changed, 1 deletion(-) (limited to 'libbe') diff --git a/libbe/comment.py b/libbe/comment.py index 1e2ef1a..c5bec43 100644 --- a/libbe/comment.py +++ b/libbe/comment.py @@ -614,7 +614,6 @@ class Comment(Tree, settings_object.SavedSettingsObject): if self.bug != None: reply.set_sync_with_disk(self.bug.sync_with_disk) if reply.sync_with_disk == True: - raise Exception, self.bug.sync_with_disk reply.save() self.add_reply(reply) return reply -- cgit