#!/usr/bin/python3 # Copyright © 2009 James Westby , # 2010, 2011 Stefano Rivera # # ################################################################## # # This program 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. # # This program 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. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ################################################################## # pylint: disable=invalid-name # pylint: enable=invalid-name import argparse import logging import re import sys import webbrowser import debianbts from launchpadlib.launchpad import Launchpad from ubuntutools import getLogger from ubuntutools.config import UDTConfig Logger = getLogger() ATTACHMENT_MAX_SIZE = 2000 def parse_args(): parser = argparse.ArgumentParser() parser.add_argument( "-b", "--browserless", action="store_true", help="Don't open the bug in the browser at the end", ) parser.add_argument( "-l", "--lpinstance", metavar="INSTANCE", help="LP instance to connect to (default: production)", ) parser.add_argument( "-v", "--verbose", action="store_true", help="Print info about the bug being imported" ) parser.add_argument( "-n", "--dry-run", action="store_true", help="Don't actually open a bug (also sets verbose)", ) parser.add_argument( "-p", "--package", help="Launchpad package to file bug against (default: Same as Debian)" ) parser.add_argument( "--no-conf", action="store_true", help="Don't read config files or environment variables." ) parser.add_argument("bugs", nargs="+", help="Bug number(s) or URL(s)") return parser.parse_args() def get_bug_numbers(bug_list): bug_re = re.compile(r"bug=(\d+)") bug_nums = [] for bug_num in bug_list: if bug_num.startswith("http"): # bug URL match = bug_re.search(bug_num) if match is None: Logger.error("Can't determine bug number from %s", bug_num) sys.exit(1) bug_num = match.groups()[0] bug_num = bug_num.lstrip("#") bug_num = int(bug_num) bug_nums.append(bug_num) return bug_nums def walk_multipart_message(message): summary = "" attachments = [] i = 1 for part in message.walk(): content_type = part.get_content_type() if content_type.startswith("multipart/"): # we're already iterating on multipart items # let's just skip the multipart extra metadata continue if content_type == "application/pgp-signature": # we're not interested in importing pgp signatures continue if part.is_attachment(): attachments.append((i, part)) elif content_type.startswith("image/"): # images here are not attachment, they are inline, but Launchpad can't handle that, # so let's add them as attachments summary += f"Message part #{i}\n" summary += f"[inline image '{part.get_filename()}']\n\n" attachments.append((i, part)) elif content_type.startswith("text/html"): summary += f"Message part #{i}\n" summary += "[inline html]\n\n" attachments.append((i, part)) elif content_type == "text/plain": summary += f"Message part #{i}\n" summary += part.get_content() + "\n" else: raise RuntimeError( f"""Unknown message part Your Debian bug is too weird to be imported in Launchpad, sorry. You can fix that by patching this script in ubuntu-dev-tools. Faulty message part: {part}""" ) i += 1 return summary, attachments def process_bugs(bugs, launchpad, package, dry_run=True, browserless=False): debian = launchpad.distributions["debian"] ubuntu = launchpad.distributions["ubuntu"] lp_debbugs = launchpad.bug_trackers.getByName(name="debbugs") err = False for bug in bugs: ubupackage = package = bug.source if package: ubupackage = package bug_num = bug.bug_num subject = bug.subject log = debianbts.get_bug_log(bug_num) message = log[0]["message"] attachments = [] if message.is_multipart(): summary, attachments = walk_multipart_message(message) else: summary = message.get_payload() target = ubuntu.getSourcePackage(name=ubupackage) if target is None: Logger.error( "Source package '%s' is not in Ubuntu. Please specify " "the destination source package with --package", ubupackage, ) err = True continue description = f"Imported from Debian bug http://bugs.debian.org/{bug_num}:\n\n{summary}" # LP limits descriptions to 50K chars description = (description[:49994] + " [...]") if len(description) > 50000 else description Logger.debug("Target: %s", target) Logger.debug("Subject: %s", subject) Logger.debug("Description: ") Logger.debug(description) for i, attachment in attachments: Logger.debug("Attachment #%s (%s)", i, attachment.get_filename() or "inline") Logger.debug("Content:") if attachment.get_content_type() == "text/plain": content = attachment.get_content() if len(content) > ATTACHMENT_MAX_SIZE: content = ( content[:ATTACHMENT_MAX_SIZE] + f" [attachment cropped after {ATTACHMENT_MAX_SIZE} characters...]" ) Logger.debug(content) else: Logger.debug("[data]") if dry_run: Logger.info("Dry-Run: not creating Ubuntu bug.") continue u_bug = launchpad.bugs.createBug(target=target, title=subject, description=description) for i, attachment in attachments: name = f"#{i}-{attachment.get_filename() or "inline"}" content = attachment.get_content() if isinstance(content, str): # Launchpad only wants bytes content = content.encode() u_bug.addAttachment( filename=name, data=content, comment=f"Imported from Debian bug http://bugs.debian.org/{bug_num}", ) d_sp = debian.getSourcePackage(name=package) if d_sp is None and package: d_sp = debian.getSourcePackage(name=package) d_task = u_bug.addTask(target=d_sp) d_watch = u_bug.addWatch(remote_bug=bug_num, bug_tracker=lp_debbugs) d_task.bug_watch = d_watch d_task.lp_save() Logger.info("Opened %s", u_bug.web_link) if not browserless: webbrowser.open(u_bug.web_link) return err def main(): options = parse_args() config = UDTConfig(options.no_conf) if options.lpinstance is None: options.lpinstance = config.get_value("LPINSTANCE") if options.dry_run: launchpad = Launchpad.login_anonymously("ubuntu-dev-tools") options.verbose = True else: launchpad = Launchpad.login_with("ubuntu-dev-tools", options.lpinstance) if options.verbose: Logger.setLevel(logging.DEBUG) bugs = debianbts.get_status(get_bug_numbers(options.bugs)) if not bugs: Logger.error("Cannot find any of the listed bugs") sys.exit(1) if process_bugs(bugs, launchpad, options.package, options.dry_run, options.browserless): sys.exit(1) if __name__ == "__main__": main()