Hi, I belive that cloning a bug is a useful feature. Below I'm attaching a patch that should do the trick. I attempted to follow the behaviour of Bugzilla's web UI:
----------------8<---------------------------------------------
diff --git a/bugzilla/base.py b/bugzilla/base.py index 7748e2b..7e148c4 100644 --- a/bugzilla/base.py +++ b/bugzilla/base.py @@ -618,6 +618,17 @@ class BugzillaBase(object): You may also add a "nomail":1 item, which will suppress email if set.''' raise NotImplementedError
+ #---- Methods for cloning bugs. + + def _clonebug(self,id,product=None,component=None,version=None): + '''IMPLEMENT ME: Clone the given bug. This is the raw call, and no data checking is + done here. That's up to the clonebug method.''' + raise NotImplementedError + + def clonebug(self,product=None,component=None,version=None): + '''Clone the given bug.''' + return self.bugzilla._clonebug(self.bug_id,product,component,version) + #---- Methods for working with attachments
def _attachment_encode(self,fh): @@ -1305,6 +1316,11 @@ class _Bug(object): '''An alias for refresh()''' self.refresh()
+ def clonebug(self,product=None,component=None,version=None): + '''Clone the given bug. This is the raw call, and no data checking is + done here. That's up to the clonebug method.''' + return self.bugzilla._clonebug(self.bug_id,product,component,version) + def setstatus(self,status,comment='',private=False,private_in_it=False,nomail=False): '''Update the status for this bug report. Valid values for status are listed in querydefaults['bug_status_list'] diff --git a/bugzilla/rhbugzilla.py b/bugzilla/rhbugzilla.py index 4233d32..59fb418 100644 --- a/bugzilla/rhbugzilla.py +++ b/bugzilla/rhbugzilla.py @@ -130,6 +130,17 @@ class RHBugzilla(Bugzilla4): requestee (as in: needinfo from smartguy@answers.com). Alas.''' return self._proxy.bugzilla.updateFlags(id,flags)
+ #---- Methods for cloning bugs. + + def _clonebug(self,id,product=None,component=None,version=None): + '''IMPLEMENT MEClone the given bug. This is the raw call, and no data checking is + done here. That's up to the clonebug method.''' + raise NotImplementedError + + def clonebug(self,product=None,component=None,version=None): + '''Clone the given bug.''' + return self.bugzilla._clonebug(self.bug_id,product,component,version) + #---- Methods for working with attachments
# If your bugzilla wants attachments in something other than base64, you @@ -281,6 +292,94 @@ class RHBugzilla(Bugzilla4): return self._close_bug(id, update) #return self._update_bug(id,update)
+ def _clonebug(self,id,product=None,component=None,version=None): + '''Clone this bug similarly as one can do it in the webui''' + origbug = self.getbug(id) + clone_message = '+++ This bug was initially created as a clone of Bug #%s +++\n\n' % (origbug.id) + isprivate = False + isadditional = False + for rec in origbug.longdescs: + if isadditional: + clone_message += '\n--- Additional comment from ' + clone_message += rec['author']['realname'] + clone_message += ' on ' + clone_message += rec['time'] + clone_message += ' EDT ---\n\n' + if 'extra_data' in rec.keys(): + clone_message += '\n\n*** This bug has been marked as a duplicate of bug %s ***\n' % (rec['extra_data']) + else: + clone_message += rec['body'] + "\n" + isadditional = True + if rec['isprivate'] == 1: + isprivate = True + cc = origbug.cc + cc.append(origbug.reporter) + depends_on = str(origbug.bug_id) + ' ' + depends_on += ' '.join([str(i) for i in origbug.dependson]) + new_product = product + if new_product == None: new_product = origbug.product + new_component = component + if new_component == None: new_component = origbug.component + new_version = version + new_target_release = None + if new_version == None and new_product != origbug.product: + releases = self._proxy.Product.get({'names': new_product, 'include_fields': ['releases']}) + prodinfo = releases['products'][0]['releases'] + versions = [p['name'] for p in prodinfo] + new_version = versions[-1] + elif new_version == None: + new_target_release = origbug.target_release + new_version = origbug.version + kwargs = { + 'product': new_product, + 'component': new_component, + 'version': new_version, + 'op_sys': origbug.op_sys, + 'platform': origbug.platform, + 'summary': origbug.summary, + 'description': clone_message, + 'comment_is_private': isprivate, + 'priority': origbug.priority, + 'bug_severity': origbug.bug_severity, + 'depends_on': depends_on, + 'blocked': origbug.blocked, + 'whiteboard': origbug.whiteboard, + 'keywords': origbug.keywords, + 'cc': cc, + 'estimated_time': origbug.estimated_time, + 'remaining_time': origbug.remaining_time, + 'deadline': origbug.deadline, + 'url': origbug.bug_file_loc, + 'target_release': new_target_release + } + for key in kwargs.keys(): + if kwargs[key] == None: + del kwargs[key] + newbug = self.createbug(**kwargs) + origgroups = [grp['name'] for grp in origbug.groups if grp['ison'] == 1] + update = dict( + cf_clone_of = str(origbug.id), + groups = dict(add = origgroups), + cf_qa_whiteboard = origbug.qa_whiteboard, + cf_cust_facing = origbug.cust_facing, + cf_devel_whiteboard = origbug.devel_whiteboard, + cf_internal_whiteboard = origbug.internal_whiteboard, + cf_build_id = origbug.build_id, + cf_partner = origbug.partner, + cf_verified = ['Any'], + cf_environment = origbug.cf_environment + ) + changes = self._update_bug(newbug.id,update) + # external tracker references + extbugs = [] + for eb in origbug.external_bugs: + neb = {} + neb['ext_bz_bug_id'] = eb['ext_bz_bug_id'] + neb['ext_type_id'] = eb['type']['id'] + extbugs.append(neb) + self._proxy.ExternalBugs.add_external_bug({'bug_ids': [newbug.id], 'external_bugs': extbugs}) + return newbug + def _setassignee(self,id,**data): '''Raw xmlrpc call to set one of the assignee fields on a bug. changeAssignment($id, $data, $username, $password)
----------------8<---------------------------------------------
Martin