Define the Bug class for representing bugs.
"""
+import copy
import os
import os.path
import errno
if v is not None:
lines.append(' <%s>%s</%s>' % (k,xml.sax.saxutils.escape(v),k))
for estr in self.extra_strings:
- lines.append(' <extra-string>%s</extra-string>\n' % estr)
+ 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,
>>> bugB.uuid = bugB.alt_id
>>> bugB.xml(shortname="bug-1") == xml
True
+ >>> bugB.explicit_attrs # doctest: +NORMALIZE_WHITESPACE
+ ['uuid', 'severity', 'status', 'creator', 'created', 'summary']
"""
if type(xml_string) == types.UnicodeType:
xml_string = xml_string.strip().encode('unicode_escape')
raise utility.InvalidXML( \
'bug', bug, 'root element must be <comment>')
tags=['uuid','short-name','severity','status','assigned','target',
- 'reporter', 'creator', 'created', 'summary', 'extra-string',
+ 'reporter', 'creator','created','summary','extra-string',
'comment']
+ self.explicit_attrs = []
uuid = None
estrs = []
for child in bug.getchildren():
else:
text = xml.sax.saxutils.unescape(child.text)
text = text.decode('unicode_escape').strip()
- if child.tag == "uuid":
+ if child.tag == 'uuid':
uuid = text
continue # don't set the bug's uuid tag.
- if child.tag == 'extra-string':
+ elif child.tag == 'extra-string':
estrs.append(text)
continue # don't set the bug's extra_string yet.
- else:
- attr_name = child.tag.replace('-','_')
+ attr_name = child.tag.replace('-','_')
+ self.explicit_attrs.append(attr_name)
setattr(self, attr_name, text)
elif verbose == True:
print >> sys.stderr, "Ignoring unknown tag %s in %s" \
% (child.tag, comment.tag)
- if uuid not in [None, self.uuid]:
+ if uuid != self.uuid:
if not hasattr(self, 'alt_id') or self.alt_id == None:
self.alt_id = uuid
self.extra_strings = estrs
+ def merge(self, other, allow_changes=True, allow_new_comments=True):
+ """
+ Merge info from other into this bug. Overrides any attributes
+ in self that are listed in other.explicit_attrs.
+ >>> bugA = Bug(uuid='0123', summary='Need to test Bug.merge()')
+ >>> bugA.date = 'Thu, 01 Jan 1970 00:00:00 +0000'
+ >>> bugA.creator = 'Frank'
+ >>> bugA.extra_strings += ['TAG: very helpful']
+ >>> bugA.extra_strings += ['TAG: favorite']
+ >>> commA = bugA.comment_root.new_reply(body='comment A')
+ >>> commA.uuid = 'uuid-commA'
+ >>> bugB = Bug(uuid='3210', summary='More tests for Bug.merge()')
+ >>> bugB.date = 'Fri, 02 Jan 1970 00:00:00 +0000'
+ >>> bugB.creator = 'John'
+ >>> bugB.explicit_attrs = ['creator', 'summary']
+ >>> bugB.extra_strings += ['TAG: very helpful']
+ >>> bugB.extra_strings += ['TAG: useful']
+ >>> commB = bugB.comment_root.new_reply(body='comment B')
+ >>> commB.uuid = 'uuid-commB'
+ >>> bugA.merge(bugB, allow_changes=False)
+ Traceback (most recent call last):
+ ...
+ ValueError: Merge would change creator "Frank"->"John" for bug 0123
+ >>> bugA.merge(bugB, allow_new_comments=False)
+ Traceback (most recent call last):
+ ...
+ ValueError: Merge would add comment uuid-commB (alt: None) to bug 0123
+ >>> bugA.merge(bugB)
+ >>> print bugA.xml(show_comments=True) # doctest: +ELLIPSIS
+ <bug>
+ <uuid>0123</uuid>
+ <short-name>0123</short-name>
+ <severity>minor</severity>
+ <status>open</status>
+ <creator>John</creator>
+ <created>...</created>
+ <summary>More tests for Bug.merge()</summary>
+ <extra-string>TAG: favorite</extra-string>
+ <extra-string>TAG: useful</extra-string>
+ <extra-string>TAG: very helpful</extra-string>
+ <comment>
+ <uuid>uuid-commA</uuid>
+ <short-name>0123:1</short-name>
+ <author></author>
+ <date>...</date>
+ <content-type>text/plain</content-type>
+ <body>comment A</body>
+ </comment>
+ <comment>
+ <uuid>uuid-commB</uuid>
+ <short-name>0123:2</short-name>
+ <author></author>
+ <date>...</date>
+ <content-type>text/plain</content-type>
+ <body>comment B</body>
+ </comment>
+ </bug>
+ """
+ for attr in other.explicit_attrs:
+ old = getattr(self, attr)
+ new = getattr(other, attr)
+ if old != new:
+ if allow_changes == True:
+ setattr(self, attr, new)
+ else:
+ raise ValueError, \
+ 'Merge would change %s "%s"->"%s" for bug %s' \
+ % (attr, old, new, self.uuid)
+ if allow_changes == False and len(other.extra_strings) > 0:
+ raise ValueError, \
+ 'Merge would change extra_strings for bug %s' % self.uuid
+ for estr in other.extra_strings:
+ if not estr in self.extra_strings:
+ self.extra_strings.append(estr)
+ import sys
+ for o_comm in other.comments():
+ s_comm = None
+ try:
+ s_comm = self.comment_root.comment_from_uuid(o_comm.uuid)
+ except KeyError, e:
+ try:
+ s_comm = self.comment_root.comment_from_uuid(o_comm.alt_id)
+ except KeyError, e:
+ pass
+ if s_comm == None:
+ if allow_new_comments == False:
+ raise ValueError, \
+ 'Merge would add comment %s (alt: %s) to bug %s' \
+ % (o_comm.uuid, o_comm.alt_id, self.uuid)
+ o_comm_copy = copy.copy(o_comm)
+ o_comm_copy.bug = self
+ print >> sys.stderr, "add comment %s" % o_comm.uuid
+ self.comment_root.add_reply(o_comm_copy)
+ else:
+ print >> sys.stderr, "merge comment %s into %s" % (o_comm.uuid, s_comm.uuid)
+ s_comm.merge(o_comm, allow_changes=allow_changes)
+
def string(self, shortlist=False, show_comments=False):
if self.bugdir == None:
shortname = self.uuid
return self.comment_root.comment_from_shortname(shortname,
*args, **kwargs)
- def comment_from_uuid(self, uuid):
- return self.comment_root.comment_from_uuid(uuid)
+ def comment_from_uuid(self, uuid, *args, **kwargs):
+ return self.comment_root.comment_from_uuid(uuid, *args, **kwargs)
def comment_shortnames(self, shortname=None):
"""
if v != None:
lines.append(' <%s>%s</%s>' % (k,xml.sax.saxutils.escape(v),k))
for estr in self.extra_strings:
- lines.append(' <extra-string>%s</extra-string>\n' % estr)
+ lines.append(' <extra-string>%s</extra-string>' % estr)
lines.append('</comment>')
istring = ' '*indent
sep = '\n' + istring
>>> xml = commA.xml(shortname="com-1")
>>> commB = Comment()
>>> commB.from_xml(xml, verbose=True)
+ >>> commB.explicit_attrs
+ ['author', 'date', 'content_type', 'body', 'alt_id']
>>> commB.xml(shortname="com-1") == xml
False
>>> commB.uuid = commB.alt_id
'comment', comment, 'root element must be <comment>')
tags=['uuid','alt-id','in-reply-to','author','date','content-type',
'body','extra-string']
+ self.explicit_attrs = []
uuid = None
body = None
estrs = []
if child.tag == 'uuid':
uuid = text
continue # don't set the comment's uuid tag.
- if child.tag == 'body':
+ elif child.tag == 'body':
body = text
+ self.explicit_attrs.append(child.tag)
continue # don't set the comment's body yet.
- if child.tag == 'extra-string':
+ elif child.tag == 'extra-string':
estrs.append(text)
continue # don't set the comment's extra_string yet.
- else:
- attr_name = child.tag.replace('-','_')
+ attr_name = child.tag.replace('-','_')
+ self.explicit_attrs.append(attr_name)
setattr(self, attr_name, text)
elif verbose == True:
print >> sys.stderr, 'Ignoring unknown tag %s in %s' \
% (child.tag, comment.tag)
- if self.alt_id == None and uuid not in [None, self.uuid]:
+ if self.alt_id == None:
+ self.explicit_attrs.append('alt_id')
self.alt_id = uuid
if body != None:
if self.content_type.startswith('text/'):
self.body = base64.decodestring(body)
self.extra_strings = estrs
+ def merge(self, other, allow_changes=True):
+ """
+ Merge info from other into this comment. Overrides any
+ attributes in self that are listed in other.explicit_attrs.
+ >>> commA = Comment(bug=None, body='Some insightful remarks')
+ >>> commA.uuid = '0123'
+ >>> commA.date = 'Thu, 01 Jan 1970 00:00:00 +0000'
+ >>> commA.author = 'Frank'
+ >>> commA.extra_strings += ['TAG: very helpful']
+ >>> commA.extra_strings += ['TAG: favorite']
+ >>> commB = Comment(bug=None, body='More insightful remarks')
+ >>> commB.uuid = '3210'
+ >>> commB.date = 'Fri, 02 Jan 1970 00:00:00 +0000'
+ >>> commB.author = 'John'
+ >>> commB.explicit_attrs = ['author', 'body']
+ >>> commB.extra_strings += ['TAG: very helpful']
+ >>> commB.extra_strings += ['TAG: useful']
+ >>> commA.merge(commB, allow_changes=False)
+ Traceback (most recent call last):
+ ...
+ ValueError: Merge would change author "Frank"->"John" for comment 0123
+ >>> commA.merge(commB)
+ >>> print commA.xml()
+ <comment>
+ <uuid>0123</uuid>
+ <short-name>0123</short-name>
+ <author>John</author>
+ <date>Thu, 01 Jan 1970 00:00:00 +0000</date>
+ <content-type>text/plain</content-type>
+ <body>More insightful remarks</body>
+ <extra-string>TAG: favorite</extra-string>
+ <extra-string>TAG: useful</extra-string>
+ <extra-string>TAG: very helpful</extra-string>
+ </comment>
+ """
+ for attr in other.explicit_attrs:
+ old = getattr(self, attr)
+ new = getattr(other, attr)
+ if old != new:
+ if allow_changes == True:
+ setattr(self, attr, new)
+ else:
+ raise ValueError, \
+ 'Merge would change %s "%s"->"%s" for comment %s' \
+ % (attr, old, new, self.uuid)
+ if allow_changes == False and len(other.extra_strings) > 0:
+ raise ValueError, \
+ 'Merge would change extra_strings for comment %s' % self.uuid
+ for estr in other.extra_strings:
+ if not estr in self.extra_strings:
+ self.extra_strings.append(estr)
+
def string(self, indent=0, shortname=None):
"""
>>> comm = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n")
raise InvalidShortname(comment_shortname,
list(self.comment_shortnames(*args, **kwargs)))
- def comment_from_uuid(self, uuid):
+ def comment_from_uuid(self, uuid, match_alt_id=True):
"""
Use a comment shortname to look up a comment.
>>> a = Comment(bug=None, uuid="a")
>>> c.uuid = "c"
>>> d = a.new_reply()
>>> d.uuid = "d"
+ >>> d.alt_id = "d-alt"
>>> comm = a.comment_from_uuid("d")
>>> id(comm) == id(d)
True
+ >>> comm = a.comment_from_uuid("d-alt")
+ >>> id(comm) == id(d)
+ True
+ >>> comm = a.comment_from_uuid(None, match_alt_id=False)
+ Traceback (most recent call last):
+ ...
+ KeyError: None
"""
for comment in self.traverse():
if comment.uuid == uuid:
return comment
+ if match_alt_id == True and uuid != None \
+ and comment.alt_id == uuid:
+ return comment
raise KeyError(uuid)
def cmp_attr(comment_1, comment_2, attr, invert=False):