-# Copyright (C) 2008-2011 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2008-2012 Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
+# Robert Lehmann <mail@robertlehmann.de>
# Thomas Habets <thomas@habets.pp.se>
+# Valtteri Kokkoniemi <rvk@iki.fi>
# W. Trevor King <wking@drexel.edu>
#
# This file is part of Bugs Everywhere.
#
-# Bugs Everywhere is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by the
-# Free Software Foundation, either version 2 of the License, or (at your
-# option) any later version.
+# Bugs Everywhere is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 2 of the License, or (at your option) any
+# later version.
#
# Bugs Everywhere is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
#
-# You should have received a copy of the GNU General Public License
-# along with Bugs Everywhere. If not, see <http://www.gnu.org/licenses/>.
+# You should have received a copy of the GNU General Public License along with
+# Bugs Everywhere. If not, see <http://www.gnu.org/licenses/>.
"""Define the :class:`Bug` class for representing bugs.
"""
import doctest
-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/
def _get_time(self):
if self.time_string == None:
+ self._cached_time_string = None
+ self._cached_time = None
return None
- return utility.str_to_time(self.time_string)
+ if (not hasattr(self, '_cached_time_string')
+ or self.time_string != self._cached_time_string):
+ self._cached_time_string = self.time_string
+ self._cached_time = utility.str_to_time(self.time_string)
+ return self._cached_time
def _set_time(self, value):
- self.time_string = utility.time_to_str(value)
+ if not hasattr(self, '_cached_time') or value != self._cached_time:
+ self.time_string = utility.time_to_str(value)
+ self._cached_time_string = self.time_string
+ self._cached_time = value
time = property(fget=_get_time,
fset=_set_time,
doc="An integer version of .time_string")
("Reporter", self._setting_attr_string("reporter")),
("Creator", self._setting_attr_string("creator")),
("Created", timestring)]
+ for estr in self.extra_strings:
+ info.append(('Extra string', estr))
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')
return output
def xml(self, indent=0, show_comments=False):
+ """
+ >>> bugA = Bug(uuid='0123', summary='Need to test Bug.xml()')
+ >>> bugA.uuid = 'bugA'
+ >>> bugA.time_string = 'Thu, 01 Jan 1970 00:00:00 +0000'
+ >>> bugA.creator = u'Frank'
+ >>> bugA.extra_strings += ['TAG: very helpful']
+ >>> commA = bugA.comment_root.new_reply(body='comment A')
+ >>> commA.uuid = 'commA'
+ >>> commA.date = 'Thu, 01 Jan 1970 00:01:00 +0000'
+ >>> commB = commA.new_reply(body='comment B')
+ >>> commB.uuid = 'commB'
+ >>> commB.date = 'Thu, 01 Jan 1970 00:02:00 +0000'
+ >>> commC = commB.new_reply(body='comment C')
+ >>> commC.uuid = 'commC'
+ >>> commC.date = 'Thu, 01 Jan 1970 00:03:00 +0000'
+ >>> print(bugA.xml(show_comments=True)) # doctest: +REPORT_UDIFF
+ <bug>
+ <uuid>bugA</uuid>
+ <short-name>/bug</short-name>
+ <severity>minor</severity>
+ <status>open</status>
+ <creator>Frank</creator>
+ <created>Thu, 01 Jan 1970 00:00:00 +0000</created>
+ <summary>Need to test Bug.xml()</summary>
+ <extra-string>TAG: very helpful</extra-string>
+ <comment>
+ <uuid>commA</uuid>
+ <short-name>/bug/commA</short-name>
+ <author></author>
+ <date>Thu, 01 Jan 1970 00:01:00 +0000</date>
+ <content-type>text/plain</content-type>
+ <body>comment A</body>
+ </comment>
+ <comment>
+ <uuid>commB</uuid>
+ <short-name>/bug/commB</short-name>
+ <in-reply-to>commA</in-reply-to>
+ <author></author>
+ <date>Thu, 01 Jan 1970 00:02:00 +0000</date>
+ <content-type>text/plain</content-type>
+ <body>comment B</body>
+ </comment>
+ <comment>
+ <uuid>commC</uuid>
+ <short-name>/bug/commC</short-name>
+ <in-reply-to>commB</in-reply-to>
+ <author></author>
+ <date>Thu, 01 Jan 1970 00:03:00 +0000</date>
+ <content-type>text/plain</content-type>
+ <body>comment C</body>
+ </comment>
+ </bug>
+ >>> print(bugA.xml(show_comments=True, indent=2))
+ ... # doctest: +REPORT_UDIFF
+ <bug>
+ <uuid>bugA</uuid>
+ <short-name>/bug</short-name>
+ <severity>minor</severity>
+ <status>open</status>
+ <creator>Frank</creator>
+ <created>Thu, 01 Jan 1970 00:00:00 +0000</created>
+ <summary>Need to test Bug.xml()</summary>
+ <extra-string>TAG: very helpful</extra-string>
+ <comment>
+ <uuid>commA</uuid>
+ <short-name>/bug/commA</short-name>
+ <author></author>
+ <date>Thu, 01 Jan 1970 00:01:00 +0000</date>
+ <content-type>text/plain</content-type>
+ <body>comment A</body>
+ </comment>
+ <comment>
+ <uuid>commB</uuid>
+ <short-name>/bug/commB</short-name>
+ <in-reply-to>commA</in-reply-to>
+ <author></author>
+ <date>Thu, 01 Jan 1970 00:02:00 +0000</date>
+ <content-type>text/plain</content-type>
+ <body>comment B</body>
+ </comment>
+ <comment>
+ <uuid>commC</uuid>
+ <short-name>/bug/commC</short-name>
+ <in-reply-to>commB</in-reply-to>
+ <author></author>
+ <date>Thu, 01 Jan 1970 00:03:00 +0000</date>
+ <content-type>text/plain</content-type>
+ <body>comment C</body>
+ </comment>
+ </bug>
+ """
if self.time == None:
timestring = ""
else:
lines.append(' <extra-string>%s</extra-string>' % estr)
if show_comments == True:
comout = self.comment_root.xml_thread(indent=indent+2)
- if len(comout) > 0:
+ if comout:
+ comout = comout[indent:] # strip leading indent spaces
lines.append(comout)
lines.append('</bug>')
istring = ' '*indent
sep = '\n' + istring
return istring + sep.join(lines).rstrip('\n')
- def from_xml(self, xml_string, verbose=True):
+ def from_xml(self, xml_string, preserve_uuids=False, verbose=True):
u"""
Note: If a bug uuid is given, set .alt_id to it's value.
>>> bugA = Bug(uuid="0123", summary="Need to test Bug.from_xml()")
>>> bugB.xml(show_comments=True) == xml
True
>>> bugB.explicit_attrs # doctest: +NORMALIZE_WHITESPACE
- ['severity', 'status', 'creator', 'created', 'summary']
+ ['severity', 'status', 'creator', 'time', 'summary']
>>> len(list(bugB.comments()))
3
+ >>> bugC = Bug()
+ >>> bugC.from_xml(xml, preserve_uuids=True)
+ >>> bugC.uuid == bugA.uuid
+ True
"""
if type(xml_string) == types.UnicodeType:
xml_string = xml_string.strip().encode('unicode_escape')
bug = ElementTree.XML(xml_string)
if bug.tag != 'bug':
raise utility.InvalidXML( \
- 'bug', bug, 'root element must be <comment>')
+ 'bug', bug, 'root element must be <bug>')
tags=['uuid','short-name','severity','status','assigned',
'reporter', 'creator','created','summary','extra-string']
self.explicit_attrs = []
pass
elif child.tag == 'comment':
comm = comment.Comment(bug=self)
- comm.from_xml(child)
+ comm.from_xml(
+ child, preserve_uuids=preserve_uuids, verbose=verbose)
comments.append(comm)
continue
elif child.tag in tags:
text = settings_object.EMPTY
else:
text = xml.sax.saxutils.unescape(child.text)
- text = text.decode('unicode_escape').strip()
- if child.tag == 'uuid':
+ if not isinstance(text, unicode):
+ text = text.decode('unicode_escape')
+ text = text.strip()
+ if child.tag == 'uuid' and not preserve_uuids:
uuid = text
continue # don't set the bug's uuid tag.
+ elif child.tag == 'created':
+ if text is not settings_object.EMPTY:
+ self.time = utility.str_to_time(text)
+ self.explicit_attrs.append('time')
+ continue
elif child.tag == 'extra-string':
estrs.append(text)
continue # don't set the bug's extra_string yet.
</comment>
</bug>
"""
- for attr in other.explicit_attrs:
- old = getattr(self, attr)
- new = getattr(other, attr)
- if old != new:
- if accept_changes == True:
- setattr(self, attr, new)
- elif change_exception == True:
- raise ValueError, \
- 'Merge would change %s "%s"->"%s" for bug %s' \
- % (attr, old, new, self.uuid)
+ if hasattr(other, 'explicit_attrs'):
+ for attr in other.explicit_attrs:
+ old = getattr(self, attr)
+ new = getattr(other, attr)
+ if old != new:
+ if accept_changes:
+ setattr(self, attr, new)
+ elif change_exception:
+ raise ValueError(
+ ('Merge would change {} "{}"->"{}" for bug {}'
+ ).format(attr, old, new, self.uuid))
for estr in other.extra_strings:
if not estr in self.extra_strings:
if accept_extra_strings == True:
- self.extra_strings.append(estr)
+ self.extra_strings += [estr]
elif change_exception == True:
raise ValueError, \
'Merge would add extra string "%s" for bug %s' \
def load_settings(self, settings_mapfile=None):
if settings_mapfile == None:
- settings_mapfile = \
- self.storage.get(self.id.storage('values'), default='\n')
+ settings_mapfile = self.storage.get(
+ self.id.storage('values'), '\n')
try:
settings = mapfile.parse(settings_mapfile)
except mapfile.InvalidMapfileContents, e:
# chronological rankings (newer < older)
cmp_time = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "time", invert=True)
+def cmp_mine(bug_1, bug_2):
+ user_id = libbe.ui.util.user.get_user_id(bug_1.storage)
+ mine_1 = bug_1.assigned != user_id
+ mine_2 = bug_2.assigned != user_id
+ return cmp(mine_1, mine_2)
+
def cmp_comments(bug_1, bug_2):
"""
Compare two bugs' comments lists. Doesn't load any new comments,