+ def xml(self, indent=0, show_bugs=False, show_comments=False):
+ """
+ >>> bug.load_severities(bug.severity_def)
+ >>> bug.load_status(
+ ... active_status_def=bug.active_status_def,
+ ... inactive_status_def=bug.inactive_status_def)
+ >>> bugdirA = SimpleBugDir(memory=True)
+ >>> bugdirA.severities
+ >>> bugdirA.severities = (('minor', 'The standard bug level.'),)
+ >>> bugdirA.inactive_status = (
+ ... ('closed', 'The bug is no longer relevant.'),)
+ >>> bugA = bugdirA.bug_from_uuid('a')
+ >>> commA = bugA.comment_root.new_reply(body='comment A')
+ >>> commA.uuid = 'commA'
+ >>> commA.date = 'Thu, 01 Jan 1970 00:03:00 +0000'
+ >>> print(bugdirA.xml(show_bugs=True, show_comments=True))
+ ... # doctest: +REPORT_UDIFF
+ <bugdir>
+ <uuid>abc123</uuid>
+ <short-name>abc</short-name>
+ <severities>
+ <entry>
+ <key>minor</key>
+ <value>The standard bug level.</value>
+ </entry>
+ </severities>
+ <inactive-status>
+ <entry>
+ <key>closed</key>
+ <value>The bug is no longer relevant.</value>
+ </entry>
+ </inactive-status>
+ <bug>
+ <uuid>a</uuid>
+ <short-name>abc/a</short-name>
+ <severity>minor</severity>
+ <status>open</status>
+ <creator>John Doe <jdoe@example.com></creator>
+ <created>Thu, 01 Jan 1970 00:00:00 +0000</created>
+ <summary>Bug A</summary>
+ <comment>
+ <uuid>commA</uuid>
+ <short-name>abc/a/com</short-name>
+ <author></author>
+ <date>Thu, 01 Jan 1970 00:03:00 +0000</date>
+ <content-type>text/plain</content-type>
+ <body>comment A</body>
+ </comment>
+ </bug>
+ <bug>
+ <uuid>b</uuid>
+ <short-name>abc/b</short-name>
+ <severity>minor</severity>
+ <status>closed</status>
+ <creator>Jane Doe <jdoe@example.com></creator>
+ <created>Thu, 01 Jan 1970 00:00:00 +0000</created>
+ <summary>Bug B</summary>
+ </bug>
+ </bugdir>
+ >>> bug.load_severities(bug.severity_def)
+ >>> bug.load_status(
+ ... active_status_def=bug.active_status_def,
+ ... inactive_status_def=bug.inactive_status_def)
+ >>> bugdirA.cleanup()
+ """
+ info = [('uuid', self.uuid),
+ ('short-name', self.id.user()),
+ ('target', self.target),
+ ('severities', self.severities),
+ ('active-status', self.active_status),
+ ('inactive-status', self.inactive_status),
+ ]
+ lines = ['<bugdir>']
+ for (k,v) in info:
+ if v is not None:
+ if k in ['severities', 'active-status', 'inactive-status']:
+ lines.append(' <{}>'.format(k))
+ for vk,vv in v:
+ lines.extend([
+ ' <entry>',
+ ' <key>{}</key>'.format(
+ xml.sax.saxutils.escape(vk)),
+ ' <value>{}</value>'.format(
+ xml.sax.saxutils.escape(vv)),
+ ' </entry>',
+ ])
+ lines.append(' </{}>'.format(k))
+ else:
+ v = xml.sax.saxutils.escape(v)
+ lines.append(' <{0}>{1}</{0}>'.format(k, v))
+ for estr in self.extra_strings:
+ lines.append(' <extra-string>{}</extra-string>'.format(estr))
+ if show_bugs:
+ for bug in self:
+ bug_xml = bug.xml(indent=indent+2, show_comments=show_comments)
+ if bug_xml:
+ bug_xml = bug_xml[indent:] # strip leading indent spaces
+ lines.append(bug_xml)
+ lines.append('</bugdir>')
+ istring = ' '*indent
+ sep = '\n' + istring
+ return istring + sep.join(lines).rstrip('\n')
+
+ def from_xml(self, xml_string, preserve_uuids=False):
+ """
+ Note: If a bugdir uuid is given, set .alt_id to it's value.
+ >>> bug.load_severities(bug.severity_def)
+ >>> bug.load_status(
+ ... active_status_def=bug.active_status_def,
+ ... inactive_status_def=bug.inactive_status_def)
+ >>> bugdirA = SimpleBugDir(memory=True)
+ >>> bugdirA.severities = (('minor', 'The standard bug level.'),)
+ >>> bugdirA.inactive_status = (
+ ... ('closed', 'The bug is no longer relevant.'),)
+ >>> bugA = bugdirA.bug_from_uuid('a')
+ >>> commA = bugA.comment_root.new_reply(body='comment A')
+ >>> commA.uuid = 'commA'
+ >>> xml = bugdirA.xml(show_bugs=True, show_comments=True)
+ >>> bugdirB = BugDir(storage=None)
+ >>> bugdirB.from_xml(xml)
+ >>> bugdirB.xml(show_bugs=True, show_comments=True) == xml
+ False
+ >>> bugdirB.uuid = bugdirB.alt_id
+ >>> for bug_ in bugdirB:
+ ... bug_.uuid = bug_.alt_id
+ ... bug_.alt_id = None
+ ... for comm in bug_.comments():
+ ... comm.uuid = comm.alt_id
+ ... comm.alt_id = None
+ >>> bugdirB.xml(show_bugs=True, show_comments=True) == xml
+ True
+ >>> bugdirB.explicit_attrs # doctest: +NORMALIZE_WHITESPACE
+ ['severities', 'inactive_status']
+ >>> bugdirC = BugDir(storage=None)
+ >>> bugdirC.from_xml(xml, preserve_uuids=True)
+ >>> bugdirC.uuid == bugdirA.uuid
+ True
+ >>> bugdirC.xml(show_bugs=True, show_comments=True) == xml
+ True
+ >>> bug.load_severities(bug.severity_def)
+ >>> bug.load_status(
+ ... active_status_def=bug.active_status_def,
+ ... inactive_status_def=bug.inactive_status_def)
+ >>> bugdirA.cleanup()
+ """
+ if type(xml_string) == types.UnicodeType:
+ xml_string = xml_string.strip().encode('unicode_escape')
+ if hasattr(xml_string, 'getchildren'): # already an ElementTree Element
+ bugdir = xml_string
+ else:
+ bugdir = ElementTree.XML(xml_string)
+ if bugdir.tag != 'bugdir':
+ raise utility.InvalidXML(
+ 'bugdir', bugdir, 'root element must be <bugdir>')
+ tags = ['uuid', 'short-name', 'target', 'severities', 'active-status',
+ 'inactive-status', 'extra-string']
+ self.explicit_attrs = []
+ uuid = None
+ estrs = []
+ for child in bugdir.getchildren():
+ if child.tag == 'short-name':
+ pass
+ elif child.tag == 'bug':
+ bg = bug.Bug(bugdir=self)
+ bg.from_xml(child, preserve_uuids=preserve_uuids)
+ self.append(bg, update=True)
+ continue
+ elif child.tag in tags:
+ if child.text == None or len(child.text) == 0:
+ text = settings_object.EMPTY
+ elif child.tag in ['severities', 'active-status',
+ 'inactive-status']:
+ entries = []
+ for entry in child.getchildren():
+ if entry.tag != 'entry':
+ raise utility.InvalidXML(
+ '{} child element {} must be <entry>'.format(
+ child.tag, entry))
+ key = value = None
+ for kv in entry.getchildren():
+ if kv.tag == 'key':
+ if key is not None:
+ raise utility.InvalidXML(
+ ('duplicate keys ({} and {}) in {}'
+ ).format(key, kv.text, child.tag))
+ key = xml.sax.saxutils.unescape(kv.text)
+ elif kv.tag == 'value':
+ if value is not None:
+ raise utility.InvalidXML(
+ ('duplicate values ({} and {}) in {}'
+ ).format(
+ value, kv.text, child.tag))
+ value = xml.sax.saxutils.unescape(kv.text)
+ else:
+ raise utility.InvalidXML(
+ ('{} child element {} must be <key> or '
+ '<value>').format(child.tag, kv))
+ if key is None:
+ raise utility.InvalidXML(
+ 'no key for {}'.format(child.tag))
+ if value is None:
+ raise utility.InvalidXML(
+ 'no key for {}'.format(child.tag))
+ entries.append((key, value))
+ text = entries
+ else:
+ text = xml.sax.saxutils.unescape(child.text)
+ 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 == 'extra-string':
+ estrs.append(text)
+ continue # don't set the bug's extra_string yet.
+ attr_name = child.tag.replace('-','_')
+ self.explicit_attrs.append(attr_name)
+ setattr(self, attr_name, text)
+ else:
+ libbe.LOG.warning(
+ 'ignoring unknown tag {0} in {1}'.format(
+ child.tag, bugdir.tag))
+ 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, accept_changes=True,
+ accept_extra_strings=True, accept_bugs=True,
+ accept_comments=True, change_exception=False):
+ """Merge info from other into this bugdir.
+
+ Overrides any attributes in self that are listed in
+ other.explicit_attrs.
+
+ >>> bugdirA = SimpleBugDir()
+ >>> bugdirA.extra_strings += ['TAG: favorite']
+ >>> bugdirB = SimpleBugDir()
+ >>> bugdirB.explicit_attrs = ['target']
+ >>> bugdirB.target = '1234'
+ >>> bugdirB.extra_strings += ['TAG: very helpful']
+ >>> bugdirB.extra_strings += ['TAG: useful']
+ >>> bugA = bugdirB.bug_from_uuid('a')
+ >>> commA = bugA.comment_root.new_reply(body='comment A')
+ >>> commA.uuid = 'uuid-commA'
+ >>> commA.date = 'Thu, 01 Jan 1970 00:01:00 +0000'
+ >>> bugC = bugdirB.new_bug(summary='bug C', _uuid='c')
+ >>> bugC.alt_id = 'alt-c'
+ >>> bugC.time_string = 'Thu, 01 Jan 1970 00:02:00 +0000'
+ >>> bugdirA.merge(
+ ... bugdirB, accept_changes=False, accept_extra_strings=False,
+ ... accept_bugs=False, change_exception=False)
+ >>> print(bugdirA.target)
+ None
+ >>> bugdirA.merge(
+ ... bugdirB, accept_changes=False, accept_extra_strings=False,
+ ... accept_bugs=False, change_exception=True)
+ Traceback (most recent call last):
+ ...
+ ValueError: Merge would change target "None"->"1234" for bugdir abc123
+ >>> print(bugdirA.target)
+ None
+ >>> bugdirA.merge(
+ ... bugdirB, accept_changes=True, accept_extra_strings=False,
+ ... accept_bugs=False, change_exception=True)
+ Traceback (most recent call last):
+ ...
+ ValueError: Merge would add extra string "TAG: useful" for bugdir abc123
+ >>> print(bugdirA.target)
+ 1234
+ >>> print(bugdirA.extra_strings)
+ ['TAG: favorite']
+ >>> bugdirA.merge(
+ ... bugdirB, accept_changes=True, accept_extra_strings=True,
+ ... accept_bugs=False, change_exception=True)
+ Traceback (most recent call last):
+ ...
+ ValueError: Merge would add bug c (alt: alt-c) to bugdir abc123
+ >>> print(bugdirA.extra_strings)
+ ['TAG: favorite', 'TAG: useful', 'TAG: very helpful']
+ >>> bugdirA.merge(
+ ... bugdirB, accept_changes=True, accept_extra_strings=True,
+ ... accept_bugs=True, change_exception=True)
+ >>> print(bugdirA.xml(show_bugs=True, show_comments=True))
+ ... # doctest: +ELLIPSIS, +REPORT_UDIFF
+ <bugdir>
+ <uuid>abc123</uuid>
+ <short-name>abc</short-name>
+ <target>1234</target>
+ <extra-string>TAG: favorite</extra-string>
+ <extra-string>TAG: useful</extra-string>
+ <extra-string>TAG: very helpful</extra-string>
+ <bug>
+ <uuid>a</uuid>
+ <short-name>abc/a</short-name>
+ <severity>minor</severity>
+ <status>open</status>
+ <creator>John Doe <jdoe@example.com></creator>
+ <created>Thu, 01 Jan 1970 00:00:00 +0000</created>
+ <summary>Bug A</summary>
+ <comment>
+ <uuid>uuid-commA</uuid>
+ <short-name>abc/a/uui</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>
+ </bug>
+ <bug>
+ <uuid>b</uuid>
+ <short-name>abc/b</short-name>
+ <severity>minor</severity>
+ <status>closed</status>
+ <creator>Jane Doe <jdoe@example.com></creator>
+ <created>Thu, 01 Jan 1970 00:00:00 +0000</created>
+ <summary>Bug B</summary>
+ </bug>
+ <bug>
+ <uuid>c</uuid>
+ <short-name>abc/c</short-name>
+ <severity>minor</severity>
+ <status>open</status>
+ <created>Thu, 01 Jan 1970 00:02:00 +0000</created>
+ <summary>bug C</summary>
+ </bug>
+ </bugdir>
+ >>> bugdirA.cleanup()
+ >>> bugdirB.cleanup()
+ """
+ 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 bugdir {}'
+ ).format(attr, old, new, self.uuid))
+ for estr in other.extra_strings:
+ if not estr in self.extra_strings:
+ if accept_extra_strings:
+ self.extra_strings += [estr]
+ elif change_exception:
+ raise ValueError(
+ ('Merge would add extra string "{}" for bugdir {}'
+ ).format(estr, self.uuid))
+ for o_bug in other:
+ try:
+ s_bug = self.bug_from_uuid(o_bug.uuid)
+ except KeyError as e:
+ try:
+ s_bug = self.bug_from_uuid(o_bug.alt_id)
+ except KeyError as e:
+ s_bug = None
+ if s_bug is None:
+ if accept_bugs:
+ o_bug_copy = copy.copy(o_bug)
+ o_bug_copy.bugdir = self
+ o_bug_copy.id = libbe.util.id.ID(o_bug_copy, 'bug')
+ self.append(o_bug_copy)
+ elif change_exception:
+ raise ValueError(
+ ('Merge would add bug {} (alt: {}) to bugdir {}'
+ ).format(o_bug.uuid, o_bug.alt_id, self.uuid))
+ else:
+ s_bug.merge(o_bug, accept_changes=accept_changes,
+ accept_extra_strings=accept_extra_strings,
+ change_exception=change_exception)
+