mirror of
https://git.launchpad.net/ubuntu-dev-tools
synced 2025-05-04 05:21:28 +00:00
This allows better understanding of the various parts of the code, by naming important parts and defining boundaries on the used variables.
249 lines
8.2 KiB
Python
Executable File
249 lines
8.2 KiB
Python
Executable File
#!/usr/bin/python3
|
|
|
|
# Copyright © 2009 James Westby <james.westby@ubuntu.com>,
|
|
# 2010, 2011 Stefano Rivera <stefanor@ubuntu.com>
|
|
#
|
|
# ##################################################################
|
|
#
|
|
# 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 = log[0]["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()
|