aboutsummaryrefslogtreecommitdiffstats
path: root/libbe/bug.py
diff options
context:
space:
mode:
authorW. Trevor King <wking@drexel.edu>2009-12-31 15:54:12 -0500
committerW. Trevor King <wking@drexel.edu>2009-12-31 15:54:12 -0500
commitb0b5341c4045dd27cfbb3e2585cb2614ed9ad903 (patch)
tree37c7c2d011617ccd7a6f28a24ea77bb1b3cddfe7 /libbe/bug.py
parenta06030436d3940dddfba37b344f90651366d67e1 (diff)
parent2d1562d951e763fed71fe60e77cc9921be9abdc9 (diff)
downloadbugseverywhere-b0b5341c4045dd27cfbb3e2585cb2614ed9ad903.tar.gz
Merged be.restructure, major internal reorganization.
Added a bunch of classes to make the commands, user interfaces, and storage backends more abstract and distinct. This should make it much easier to extend and maintain BE. Features: * Directory restructured: becommands/ -> libbe/commands submods sorted by functionality. * Lots of new classes: Option, Argument, Command InputOutput, StorageCallbacks, UserInterface Storage * Consolidated ID handling in libbe.util.id * Transitioned VCS backends for Python-based VCSs from subprocess calss to internal python calls. Plus the user-visible changes: * New bugdir/bug/comment ID format replaces old bug:comment format. * Deprecated support for `be diff` on Arch and Darcs <= 2.3.1. A new backend abstraction (Storage) makes the former implementation ungainly. * Improved command completion. * Removed commands close, open, email_bugs, * Flipped some arguments `be assign BUG-ID [ASSIGNEE]` -> `be status ASSIGNED BUG-ID ...` `be severity BUG-ID SEVERITY` -> `be severity SEVERITY BUG-ID ...` `be status BUG-ID STATUS` -> `be status STATUS BUG-ID ...` In the merge: * Added 'commit' to list of pagerless commands. * Updated doc/README.dev See #bea86499-824e-4e77-b085-2d581fa9ccab/1100c966-9671-4bc6-8b68-6d408a910da1# for a discussion of why the changes were made and some of the difficulties en-route.
Diffstat (limited to 'libbe/bug.py')
-rw-r--r--libbe/bug.py272
1 files changed, 121 insertions, 151 deletions
diff --git a/libbe/bug.py b/libbe/bug.py
index 06c2cc5..66ba579 100644
--- a/libbe/bug.py
+++ b/libbe/bug.py
@@ -24,6 +24,7 @@ import copy
import os
import os.path
import errno
+import sys
import time
import types
try: # import core module, Python >= 2.5
@@ -33,14 +34,15 @@ except ImportError: # look for non-core module
import xml.sax.saxutils
import libbe
-from beuuid import uuid_gen
-from properties import Property, doc_property, local_property, \
- defaulting_property, checked_property, cached_property, \
+import libbe.util.id
+from libbe.storage.util.properties import Property, doc_property, \
+ local_property, defaulting_property, checked_property, cached_property, \
primed_property, change_hook_property, settings_property
-import settings_object
-import mapfile
-import comment
-import utility
+import libbe.storage.util.settings_object as settings_object
+import libbe.storage.util.mapfile as mapfile
+import libbe.comment as comment
+import libbe.util.utility as utility
+
if libbe.TESTING == True:
import doctest
@@ -169,7 +171,7 @@ class Bug(settings_object.SavedSettingsObject):
check_fn=lambda s: s in status_values,
require_save=True)
def status(): return {}
-
+
@property
def active(self):
return self.status in active_status_values
@@ -219,8 +221,8 @@ class Bug(settings_object.SavedSettingsObject):
def summary(): return {}
def _get_comment_root(self, load_full=False):
- if self.sync_with_disk:
- return comment.loadComments(self, load_full=load_full)
+ if self.storage != None and self.storage.is_readable():
+ return comment.load_comments(self, load_full=load_full)
else:
return comment.Comment(self, uuid=comment.INVALID_UUID)
@@ -230,31 +232,26 @@ class Bug(settings_object.SavedSettingsObject):
@doc_property(doc="The trunk of the comment tree. We use a dummy root comment by default, because there can be several comment threads rooted on the same parent bug. To simplify comment interaction, we condense these threads into a single thread with a Comment dummy root.")
def comment_root(): return {}
- def _get_vcs(self):
- if hasattr(self.bugdir, "vcs"):
- return self.bugdir.vcs
-
- @Property
- @cached_property(generator=_get_vcs)
- @local_property("vcs")
- @doc_property(doc="A revision control system instance.")
- def vcs(): return {}
-
- def __init__(self, bugdir=None, uuid=None, from_disk=False,
+ def __init__(self, bugdir=None, uuid=None, from_storage=False,
load_comments=False, summary=None):
settings_object.SavedSettingsObject.__init__(self)
self.bugdir = bugdir
+ self.storage = None
self.uuid = uuid
- if from_disk == True:
- self.sync_with_disk = True
- else:
- self.sync_with_disk = False
+ self.id = libbe.util.id.ID(self, 'bug')
+ if from_storage == False:
if uuid == None:
- self.uuid = uuid_gen()
+ self.uuid = libbe.util.id.uuid_gen()
+ self.settings = {}
+ self._setup_saved_settings()
self.time = int(time.time()) # only save to second precision
- if self.vcs != None:
- self.creator = self.vcs.get_user_id()
self.summary = summary
+ dummy = self.comment_root
+ if self.bugdir != None:
+ self.storage = self.bugdir.storage
+ if from_storage == False:
+ if self.storage != None and self.storage.is_writeable():
+ self.save()
def __repr__(self):
return "Bug(uuid=%r)" % self.uuid
@@ -275,20 +272,46 @@ class Bug(settings_object.SavedSettingsObject):
return str(value)
return value
- def xml(self, indent=0, shortname=None, show_comments=False):
- if shortname == None:
- if self.bugdir == None:
- shortname = self.uuid
+ def string(self, shortlist=False, show_comments=False):
+ if shortlist == False:
+ if self.time == None:
+ timestring = ""
else:
- shortname = self.bugdir.bug_shortname(self)
+ htime = utility.handy_time(self.time)
+ timestring = "%s (%s)" % (htime, self.time_string)
+ info = [("ID", self.uuid),
+ ("Short name", self.id.user()),
+ ("Severity", self.severity),
+ ("Status", self.status),
+ ("Assigned", self._setting_attr_string("assigned")),
+ ("Reporter", self._setting_attr_string("reporter")),
+ ("Creator", self._setting_attr_string("creator")),
+ ("Created", timestring)]
+ longest_key_len = max([len(k) for k,v in info])
+ infolines = [" %*s : %s\n" %(longest_key_len,k,v) for k,v in info]
+ bugout = "".join(infolines) + "%s" % self.summary.rstrip('\n')
+ else:
+ statuschar = self.status[0]
+ severitychar = self.severity[0]
+ chars = "%c%c" % (statuschar, severitychar)
+ bugout = "%s:%s: %s" % (self.id.user(),chars,self.summary.rstrip('\n'))
+ if show_comments == True:
+ self.comment_root.sort(cmp=libbe.comment.cmp_time, reverse=True)
+ comout = self.comment_root.string_thread(flatten=False)
+ output = bugout + '\n' + comout.rstrip('\n')
+ else :
+ output = bugout
+ return output
+
+ def xml(self, indent=0, show_comments=False):
if self.time == None:
timestring = ""
else:
timestring = utility.time_to_str(self.time)
info = [('uuid', self.uuid),
- ('short-name', shortname),
+ ('short-name', self.id.user()),
('severity', self.severity),
('status', self.status),
('assigned', self.assigned),
@@ -303,9 +326,7 @@ class Bug(settings_object.SavedSettingsObject):
for estr in self.extra_strings:
lines.append(' <extra-string>%s</extra-string>' % estr)
if show_comments == True:
- comout = self.comment_root.xml_thread(indent=indent+2,
- auto_name_map=True,
- bug_shortname=shortname)
+ comout = self.comment_root.xml_thread(indent=indent+2)
if len(comout) > 0:
lines.append(comout)
lines.append('</bug>')
@@ -323,16 +344,16 @@ class Bug(settings_object.SavedSettingsObject):
>>> commA = bugA.comment_root.new_reply(body='comment A')
>>> commB = bugA.comment_root.new_reply(body='comment B')
>>> commC = commA.new_reply(body='comment C')
- >>> xml = bugA.xml(shortname="bug-1", show_comments=True)
+ >>> xml = bugA.xml(show_comments=True)
>>> bugB = Bug()
>>> bugB.from_xml(xml, verbose=True)
- >>> bugB.xml(shortname="bug-1", show_comments=True) == xml
+ >>> bugB.xml(show_comments=True) == xml
False
>>> bugB.uuid = bugB.alt_id
>>> for comm in bugB.comments():
... comm.uuid = comm.alt_id
... comm.alt_id = None
- >>> bugB.xml(shortname="bug-1", show_comments=True) == xml
+ >>> bugB.xml(show_comments=True) == xml
True
>>> bugB.explicit_attrs # doctest: +NORMALIZE_WHITESPACE
['severity', 'status', 'creator', 'created', 'summary']
@@ -378,13 +399,13 @@ class Bug(settings_object.SavedSettingsObject):
self.explicit_attrs.append(attr_name)
setattr(self, attr_name, text)
elif verbose == True:
- print >> sys.stderr, "Ignoring unknown tag %s in %s" \
+ print >> sys.stderr, 'Ignoring unknown tag %s in %s' \
% (child.tag, comment.tag)
if uuid != self.uuid:
if not hasattr(self, 'alt_id') or self.alt_id == None:
self.alt_id = uuid
self.extra_strings = estrs
- self.add_comments(comments)
+ self.add_comments(comments, ignore_missing_references=True)
def add_comment(self, comment, *args, **kwargs):
"""
@@ -402,10 +423,10 @@ class Bug(settings_object.SavedSettingsObject):
>>> commC.uuid = 'commC'
>>> commC.in_reply_to = commA.uuid
>>> bugA.add_comment(commC)
- >>> print bugA.xml(shortname="bug-1", show_comments=True) # doctest: +ELLIPSIS
+ >>> print bugA.xml(show_comments=True) # doctest: +ELLIPSIS
<bug>
<uuid>0123</uuid>
- <short-name>bug-1</short-name>
+ <short-name>/012</short-name>
<severity>minor</severity>
<status>open</status>
<creator>Jack</creator>
@@ -413,7 +434,7 @@ class Bug(settings_object.SavedSettingsObject):
<summary>Need to test Bug.add_comment()</summary>
<comment>
<uuid>commA</uuid>
- <short-name>bug-1:1</short-name>
+ <short-name>/012/commA</short-name>
<author></author>
<date>...</date>
<content-type>text/plain</content-type>
@@ -421,7 +442,7 @@ class Bug(settings_object.SavedSettingsObject):
</comment>
<comment>
<uuid>commC</uuid>
- <short-name>bug-1:2</short-name>
+ <short-name>/012/commC</short-name>
<in-reply-to>commA</in-reply-to>
<author></author>
<date>...</date>
@@ -430,7 +451,7 @@ class Bug(settings_object.SavedSettingsObject):
</comment>
<comment>
<uuid>commB</uuid>
- <short-name>bug-1:3</short-name>
+ <short-name>/012/commB</short-name>
<author></author>
<date>...</date>
<content-type>text/plain</content-type>
@@ -458,8 +479,9 @@ class Bug(settings_object.SavedSettingsObject):
if c.alt_id != None:
uuid_map[c.alt_id] = c
uuid_map[None] = self.comment_root
+ uuid_map[comment.INVALID_UUID] = self.comment_root
if default_parent != self.comment_root:
- assert default_parent.uuid in uuid_map, default_parent
+ assert default_parent.uuid in uuid_map, default_parent.uuid
for c in comments:
if c.in_reply_to == None \
and default_parent.uuid != comment.INVALID_UUID:
@@ -471,7 +493,7 @@ class Bug(settings_object.SavedSettingsObject):
except KeyError:
if ignore_missing_references == True:
print >> sys.stderr, \
- "Ignoring missing reference to %s" % c.in_reply_to
+ 'Ignoring missing reference to %s' % c.in_reply_to
parent = default_parent
if parent.uuid != comment.INVALID_UUID:
c.in_reply_to = parent.uuid
@@ -533,7 +555,7 @@ class Bug(settings_object.SavedSettingsObject):
>>> print bugA.xml(show_comments=True) # doctest: +ELLIPSIS
<bug>
<uuid>0123</uuid>
- <short-name>0123</short-name>
+ <short-name>/012</short-name>
<severity>minor</severity>
<status>open</status>
<creator>John</creator>
@@ -544,7 +566,7 @@ class Bug(settings_object.SavedSettingsObject):
<extra-string>TAG: very helpful</extra-string>
<comment>
<uuid>uuid-commA</uuid>
- <short-name>0123:1</short-name>
+ <short-name>/012/uuid-commA</short-name>
<author></author>
<date>...</date>
<content-type>text/plain</content-type>
@@ -552,7 +574,7 @@ class Bug(settings_object.SavedSettingsObject):
</comment>
<comment>
<uuid>uuid-commB</uuid>
- <short-name>0123:2</short-name>
+ <short-name>/012/uuid-commB</short-name>
<author></author>
<date>...</date>
<content-type>text/plain</content-type>
@@ -590,6 +612,7 @@ class Bug(settings_object.SavedSettingsObject):
if accept_comments == True:
o_comm_copy = copy.copy(o_comm)
o_comm_copy.bug = self
+ o_comm_copy.id = libbe.util.id.ID(o_comm_copy, 'comment')
self.comment_root.add_reply(o_comm_copy)
elif change_exception == True:
raise ValueError, \
@@ -600,116 +623,68 @@ class Bug(settings_object.SavedSettingsObject):
accept_extra_strings=accept_extra_strings,
change_exception=change_exception)
- def string(self, shortlist=False, show_comments=False):
- if self.bugdir == None:
- shortname = self.uuid
- else:
- shortname = self.bugdir.bug_shortname(self)
- if shortlist == False:
- if self.time == None:
- timestring = ""
- else:
- htime = utility.handy_time(self.time)
- timestring = "%s (%s)" % (htime, self.time_string)
- info = [("ID", self.uuid),
- ("Short name", shortname),
- ("Severity", self.severity),
- ("Status", self.status),
- ("Assigned", self._setting_attr_string("assigned")),
- ("Reporter", self._setting_attr_string("reporter")),
- ("Creator", self._setting_attr_string("creator")),
- ("Created", timestring)]
- longest_key_len = max([len(k) for k,v in info])
- infolines = [" %*s : %s\n" %(longest_key_len,k,v) for k,v in info]
- bugout = "".join(infolines) + "%s" % self.summary.rstrip('\n')
- else:
- statuschar = self.status[0]
- severitychar = self.severity[0]
- chars = "%c%c" % (statuschar, severitychar)
- bugout = "%s:%s: %s" % (shortname,chars,self.summary.rstrip('\n'))
-
- if show_comments == True:
- # take advantage of the string_thread(auto_name_map=True)
- # SIDE-EFFECT of sorting by comment time.
- comout = self.comment_root.string_thread(flatten=False,
- auto_name_map=True,
- bug_shortname=shortname)
- output = bugout + '\n' + comout.rstrip('\n')
- else :
- output = bugout
- return output
-
# methods for saving/loading/acessing settings and properties.
- def get_path(self, *args):
- dir = os.path.join(self.bugdir.get_path("bugs"), self.uuid)
- if len(args) == 0:
- return dir
- assert args[0] in ["values", "comments"], str(args)
- return os.path.join(dir, *args)
-
- 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.vcs, self.get_path("values"))
+ def load_settings(self, settings_mapfile=None):
+ if settings_mapfile == None:
+ settings_mapfile = \
+ self.storage.get(self.id.storage('values'), default='\n')
+ try:
+ self.settings = mapfile.parse(settings_mapfile)
+ except mapfile.InvalidMapfileContents, e:
+ raise Exception('Invalid settings file for bug %s\n'
+ '(BE version missmatch?)' % self.id.user())
self._setup_saved_settings()
def save_settings(self):
- if self.sync_with_disk == False:
- raise DiskAccessRequired("save settings")
- assert self.summary != None, "Can't save blank bug"
- self.vcs.mkdir(self.get_path())
- path = self.get_path("values")
- mapfile.map_save(self.vcs, path, self._get_saved_settings())
+ mf = mapfile.generate(self._get_saved_settings())
+ self.storage.set(self.id.storage('values'), mf)
def save(self):
"""
- Save any loaded contents to disk. Because of lazy loading of
- comments, this is actually not too inefficient.
-
- 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).
+ Save any loaded contents to storage. Because of lazy loading
+ of comments, this is actually not too inefficient.
+
+ However, if self.storage.is_writeable() == True, then any
+ changes are automatically written to storage as soon as they
+ happen, so calling this method will just waste time (unless
+ something else has been messing with your stored files).
"""
- sync_with_disk = self.sync_with_disk
- if sync_with_disk == False:
- self.set_sync_with_disk(True)
+ assert self.storage != None, "Can't save without storage"
+ if self.bugdir != None:
+ parent = self.bugdir.id.storage()
+ else:
+ parent = None
+ self.storage.add(self.id.storage(), parent=parent, directory=True)
+ self.storage.add(self.id.storage('values'), parent=self.id.storage(),
+ directory=False)
self.save_settings()
if len(self.comment_root) > 0:
- comment.saveComments(self)
- if sync_with_disk == False:
- self.set_sync_with_disk(False)
+ comment.save_comments(self)
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
+ # next _get_comment_root returns a fresh version. Turn of
+ # writing temporarily so we don't write our blank comment
# tree to disk.
- self.sync_with_disk = False
+ w = self.storage.writeable
+ self.storage.writeable = False
self.comment_root = None
- self.sync_with_disk = True
+ self.storage.writeable = w
def remove(self):
- if self.sync_with_disk == False:
- raise DiskAccessRequired("remove")
- self.comment_root.remove()
- path = self.get_path()
- self.vcs.recursive_remove(path)
-
+ self.storage.recursive_remove(self.id.storage())
+
# methods for managing comments
+ def uuids(self):
+ for comment in self.comments():
+ yield comment.uuid
+
def comments(self):
for comment in self.comment_root.traverse():
yield comment
@@ -718,20 +693,15 @@ class Bug(settings_object.SavedSettingsObject):
comm = self.comment_root.new_reply(body=body)
return comm
- def comment_from_shortname(self, shortname, *args, **kwargs):
- return self.comment_root.comment_from_shortname(shortname,
- *args, **kwargs)
-
def comment_from_uuid(self, uuid, *args, **kwargs):
return self.comment_root.comment_from_uuid(uuid, *args, **kwargs)
- def comment_shortnames(self, shortname=None):
- """
- SIDE-EFFECT : Comment.comment_shortnames will sort the comment
- tree by comment.time
- """
- for id, comment in self.comment_root.comment_shortnames(shortname):
- yield (id, comment)
+ # methods for id generation
+
+ def sibling_uuids(self):
+ if self.bugdir != None:
+ return self.bugdir.uuids()
+ return []
# The general rule for bug sorting is that "more important" bugs are
@@ -805,7 +775,7 @@ def cmp_attr(bug_1, bug_2, attr, invert=False):
val_2 = getattr(bug_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 :
@@ -851,7 +821,7 @@ class BugCompoundComparator (object):
if val != 0 :
return val
return 0
-
+
cmp_full = BugCompoundComparator()