2019-09-04 18:27:03 -03:00
|
|
|
#!/usr/bin/python3
|
2010-07-11 18:43:19 +02:00
|
|
|
|
2010-12-21 23:41:55 +02:00
|
|
|
# Copyright © 2009 James Westby <james.westby@ubuntu.com>,
|
2011-09-08 23:47:38 +02:00
|
|
|
# 2010, 2011 Stefano Rivera <stefanor@ubuntu.com>
|
2010-07-11 20:30:08 +02:00
|
|
|
#
|
|
|
|
# ##################################################################
|
|
|
|
#
|
|
|
|
# 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.
|
2010-12-03 00:06:43 +01:00
|
|
|
#
|
2010-07-11 20:30:08 +02:00
|
|
|
# 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.
|
|
|
|
#
|
|
|
|
# ##################################################################
|
|
|
|
|
2023-01-30 23:10:31 +01:00
|
|
|
# pylint: disable=invalid-name
|
|
|
|
# pylint: enable=invalid-name
|
|
|
|
|
2019-02-04 15:00:50 -05:00
|
|
|
import argparse
|
|
|
|
import logging
|
2010-07-11 18:43:19 +02:00
|
|
|
import re
|
|
|
|
import sys
|
2011-09-08 23:47:38 +02:00
|
|
|
import webbrowser
|
2024-11-02 15:34:59 +01:00
|
|
|
from collections.abc import Iterable
|
|
|
|
from email.message import EmailMessage
|
2010-12-21 23:41:55 +02:00
|
|
|
|
2023-01-30 21:28:47 +01:00
|
|
|
import debianbts
|
2013-03-19 00:43:31 +01:00
|
|
|
from launchpadlib.launchpad import Launchpad
|
|
|
|
|
2018-10-12 18:54:07 -04:00
|
|
|
from ubuntutools import getLogger
|
2023-01-30 21:28:47 +01:00
|
|
|
from ubuntutools.config import UDTConfig
|
2023-01-30 19:45:36 +01:00
|
|
|
|
2021-02-01 18:26:13 -05:00
|
|
|
Logger = getLogger()
|
2024-06-24 18:48:04 +02:00
|
|
|
ATTACHMENT_MAX_SIZE = 2000
|
2013-03-19 00:43:31 +01:00
|
|
|
|
2017-05-01 00:20:03 +02:00
|
|
|
|
2024-11-02 15:34:59 +01:00
|
|
|
def parse_args() -> argparse.Namespace:
|
2019-02-04 15:00:50 -05:00
|
|
|
parser = argparse.ArgumentParser()
|
2023-01-30 19:45:36 +01:00
|
|
|
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."
|
|
|
|
)
|
2019-02-04 15:00:50 -05:00
|
|
|
parser.add_argument("bugs", nargs="+", help="Bug number(s) or URL(s)")
|
2024-07-26 14:59:11 +02:00
|
|
|
return parser.parse_args()
|
2010-12-27 21:54:31 +01:00
|
|
|
|
|
|
|
|
2024-11-02 15:34:59 +01:00
|
|
|
def get_bug_numbers(bug_list: Iterable[str]) -> list[int]:
|
2024-07-26 14:59:11 +02:00
|
|
|
bug_re = re.compile(r"bug=(\d+)")
|
2010-12-27 21:54:31 +01:00
|
|
|
|
|
|
|
bug_nums = []
|
|
|
|
|
2024-07-26 14:59:11 +02:00
|
|
|
for bug_num in bug_list:
|
2010-12-27 21:54:31 +01:00
|
|
|
if bug_num.startswith("http"):
|
|
|
|
# bug URL
|
|
|
|
match = bug_re.search(bug_num)
|
|
|
|
if match is None:
|
2011-09-08 23:47:38 +02:00
|
|
|
Logger.error("Can't determine bug number from %s", bug_num)
|
|
|
|
sys.exit(1)
|
2010-12-27 21:54:31 +01:00
|
|
|
bug_num = match.groups()[0]
|
|
|
|
bug_num = bug_num.lstrip("#")
|
2024-11-02 15:33:15 +01:00
|
|
|
bug_nums.append(int(bug_num))
|
2010-12-27 21:54:31 +01:00
|
|
|
|
2024-07-26 14:59:11 +02:00
|
|
|
return bug_nums
|
2010-12-27 21:54:31 +01:00
|
|
|
|
2024-07-26 14:59:11 +02:00
|
|
|
|
2024-11-02 15:34:59 +01:00
|
|
|
def walk_multipart_message(message: EmailMessage) -> tuple[str, list[tuple[int, EmailMessage]]]:
|
2024-07-26 14:59:11 +02:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2024-11-02 15:34:59 +01:00
|
|
|
def process_bugs(
|
|
|
|
bugs: Iterable[debianbts.Bugreport],
|
|
|
|
launchpad: Launchpad,
|
|
|
|
package: str,
|
|
|
|
dry_run: bool = True,
|
|
|
|
browserless: bool = False,
|
|
|
|
) -> bool:
|
2024-07-26 14:59:11 +02:00
|
|
|
debian = launchpad.distributions["debian"]
|
|
|
|
ubuntu = launchpad.distributions["ubuntu"]
|
|
|
|
lp_debbugs = launchpad.bug_trackers.getByName(name="debbugs")
|
2011-09-08 23:47:38 +02:00
|
|
|
|
2019-02-04 15:00:50 -05:00
|
|
|
err = False
|
2010-12-27 21:54:31 +01:00
|
|
|
for bug in bugs:
|
2025-02-26 14:14:45 +08:00
|
|
|
ubupackage = bug.source
|
2024-07-26 14:59:11 +02:00
|
|
|
if package:
|
|
|
|
ubupackage = package
|
2010-12-27 21:54:31 +01:00
|
|
|
bug_num = bug.bug_num
|
|
|
|
subject = bug.subject
|
2019-09-04 18:27:03 -03:00
|
|
|
log = debianbts.get_bug_log(bug_num)
|
2024-06-24 18:48:04 +02:00
|
|
|
message = log[0]["message"]
|
2024-11-02 15:34:59 +01:00
|
|
|
assert isinstance(message, EmailMessage)
|
|
|
|
attachments: list[tuple[int, EmailMessage]] = []
|
2024-06-24 18:48:04 +02:00
|
|
|
if message.is_multipart():
|
2024-07-26 14:59:11 +02:00
|
|
|
summary, attachments = walk_multipart_message(message)
|
2024-06-24 18:48:04 +02:00
|
|
|
else:
|
2024-11-02 15:34:59 +01:00
|
|
|
summary = str(message.get_payload())
|
2024-06-24 18:48:04 +02:00
|
|
|
|
2011-01-04 22:24:06 +02:00
|
|
|
target = ubuntu.getSourcePackage(name=ubupackage)
|
2011-09-08 23:47:38 +02:00
|
|
|
if target is None:
|
2023-01-30 19:45:36 +01:00
|
|
|
Logger.error(
|
|
|
|
"Source package '%s' is not in Ubuntu. Please specify "
|
|
|
|
"the destination source package with --package",
|
|
|
|
ubupackage,
|
|
|
|
)
|
2019-02-04 15:00:50 -05:00
|
|
|
err = True
|
|
|
|
continue
|
|
|
|
|
2023-01-31 19:32:58 +01:00
|
|
|
description = f"Imported from Debian bug http://bugs.debian.org/{bug_num}:\n\n{summary}"
|
2020-12-26 01:49:43 -05:00
|
|
|
# LP limits descriptions to 50K chars
|
2023-01-30 19:45:36 +01:00
|
|
|
description = (description[:49994] + " [...]") if len(description) > 50000 else description
|
2011-09-08 23:47:38 +02:00
|
|
|
|
2023-01-31 11:13:07 +01:00
|
|
|
Logger.debug("Target: %s", target)
|
|
|
|
Logger.debug("Subject: %s", subject)
|
2023-01-30 19:45:36 +01:00
|
|
|
Logger.debug("Description: ")
|
2019-02-04 15:00:50 -05:00
|
|
|
Logger.debug(description)
|
2024-06-24 18:48:04 +02:00
|
|
|
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]")
|
2019-02-04 15:00:50 -05:00
|
|
|
|
2024-07-26 14:59:11 +02:00
|
|
|
if dry_run:
|
2023-01-30 19:45:36 +01:00
|
|
|
Logger.info("Dry-Run: not creating Ubuntu bug.")
|
2019-02-04 15:00:50 -05:00
|
|
|
continue
|
|
|
|
|
2023-01-30 19:45:36 +01:00
|
|
|
u_bug = launchpad.bugs.createBug(target=target, title=subject, description=description)
|
2024-06-24 18:48:04 +02:00
|
|
|
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}",
|
|
|
|
)
|
2011-01-04 22:24:06 +02:00
|
|
|
d_sp = debian.getSourcePackage(name=package)
|
2024-07-26 14:59:11 +02:00
|
|
|
if d_sp is None and package:
|
|
|
|
d_sp = debian.getSourcePackage(name=package)
|
2011-01-04 22:24:06 +02:00
|
|
|
d_task = u_bug.addTask(target=d_sp)
|
2010-12-27 21:54:31 +01:00
|
|
|
d_watch = u_bug.addWatch(remote_bug=bug_num, bug_tracker=lp_debbugs)
|
|
|
|
d_task.bug_watch = d_watch
|
|
|
|
d_task.lp_save()
|
2018-10-12 18:54:07 -04:00
|
|
|
Logger.info("Opened %s", u_bug.web_link)
|
2024-07-26 14:59:11 +02:00
|
|
|
if not browserless:
|
2011-09-08 23:47:38 +02:00
|
|
|
webbrowser.open(u_bug.web_link)
|
2010-12-27 21:54:31 +01:00
|
|
|
|
2024-07-26 14:59:11 +02:00
|
|
|
return err
|
|
|
|
|
|
|
|
|
2024-11-02 15:34:59 +01:00
|
|
|
def main() -> None:
|
2024-07-26 14:59:11 +02:00
|
|
|
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):
|
2019-02-04 15:00:50 -05:00
|
|
|
sys.exit(1)
|
|
|
|
|
2017-05-01 00:20:03 +02:00
|
|
|
|
2023-01-30 19:45:36 +01:00
|
|
|
if __name__ == "__main__":
|
2010-12-27 21:54:31 +01:00
|
|
|
main()
|