mirror of
https://git.launchpad.net/ubuntu-dev-tools
synced 2025-04-27 02:01:09 +00:00
- Fix --lp for Firefox 3: it now tries ~/.lpcookie.txt, ~/.mozilla/*/*/cookies.sqlite and ~/.mozilla/*/*/cookies.txt to find a Launchpad cookie file - Added confirm loops, which displays the message to be send/posted and either allows to edit (or forces to, in case of Ubuntu changes). (LP: #194613, #194615) This adds a convient edit_report method, which gets used both from the Launchpad and mail code path. - Do not fallback to submitting by email, if posting to Launchpad failed. This hasn't been requested and therefore should not get done.
429 lines
14 KiB
Python
Executable File
429 lines
14 KiB
Python
Executable File
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# (C) 2007 Canonical Ltd., Steve Kowalik
|
|
# Authors:
|
|
# Martin Pitt <martin.pitt@ubuntu.com>
|
|
# Steve Kowalik <stevenk@ubuntu.com>
|
|
# Michael Bienia <geser@ubuntu.com> (python-launchpad-bugs support)
|
|
#
|
|
# License: GPLv2, see /usr/share/common-licenses/GPL
|
|
|
|
import os, sys, urllib, subprocess, getopt
|
|
from debian_bundle.changelog import Version
|
|
|
|
# Set this to the path of your Launchpad cookie file, when using
|
|
# python-launchpad-bugs support (--lp).
|
|
# The following will be tried automatically, if unset (first match gets used):
|
|
# 1. ~/.lpcookie.txt
|
|
# 2. ~/.mozilla/*/*/cookies.sqlite
|
|
# 3. ~/.mozilla/*/*/cookies.txt
|
|
launchpad_cookiefile = None
|
|
|
|
|
|
def cur_version_component(sourcepkg, release):
|
|
'''Determine current package version in ubuntu.'''
|
|
madison = subprocess.Popen(['rmadison', '-u', 'ubuntu', '-a', 'source', \
|
|
'-s', release, sourcepkg], stdout=subprocess.PIPE)
|
|
out = madison.communicate()[0]
|
|
assert (madison.returncode == 0)
|
|
|
|
for l in out.splitlines():
|
|
(pkg, version, rel, builds) = l.split('|')
|
|
component = 'main'
|
|
if rel.find('/') != -1:
|
|
component = rel.split('/')[1]
|
|
return (version.strip(), component.strip())
|
|
|
|
print "%s doesn't appear to exist in %s, specify -n for a package not in Ubuntu." % (sourcepkg, release)
|
|
sys.exit(1)
|
|
|
|
def cur_deb_version(sourcepkg):
|
|
'''Return the current debian version of a package in unstable.'''
|
|
madison = subprocess.Popen(['rmadison', '-u', 'debian', '-a', 'source', \
|
|
'-s', 'unstable', sourcepkg], \
|
|
stdout=subprocess.PIPE)
|
|
out = madison.communicate()[0]
|
|
assert (madison.returncode == 0)
|
|
|
|
try:
|
|
assert out
|
|
except AssertionError:
|
|
print "%s doesn't appear to exist in Debian." % sourcepkg
|
|
sys.exit(1)
|
|
|
|
# Work-around for a bug in Debians madison.php script not returning
|
|
# only the source line
|
|
for line in out.splitlines():
|
|
if line.find('source') > 0:
|
|
out = line
|
|
|
|
return out.split('|')[1].rstrip('[]''').strip()
|
|
|
|
def debian_changelog(sourcepkg, component, version):
|
|
'''Return the Debian changelog from the latest up to the given version
|
|
(exclusive).'''
|
|
|
|
base_version = Version(version)
|
|
|
|
ch = ''
|
|
subdir = sourcepkg[0]
|
|
if sourcepkg.startswith('lib'):
|
|
subdir = 'lib%s' % sourcepkg[3]
|
|
for l in urllib.urlopen('http://packages.debian.org/changelogs/pool/%s/%s/%s/current/changelog.txt' % (component, subdir, sourcepkg)):
|
|
if l.startswith(sourcepkg):
|
|
ch_version = l[ l.find("(")+1 : l.find(")") ]
|
|
if Version(ch_version) <= base_version:
|
|
break
|
|
ch += l
|
|
|
|
return ch
|
|
|
|
def debian_component(sourcepkg):
|
|
'''Return the Debian component for the source package.'''
|
|
madison = subprocess.Popen(['rmadison', '-u', 'debian', '-a', 'source', '-s', 'unstable', \
|
|
sourcepkg], stdout=subprocess.PIPE)
|
|
out = madison.communicate()[0]
|
|
assert (madison.returncode == 0)
|
|
|
|
try:
|
|
assert out
|
|
except AssertionError:
|
|
print "%s doesn't appear to exist in Debian." % sourcepkg
|
|
sys.exit(1)
|
|
raw_comp = out.split('|')[2].split('/')
|
|
component = 'main'
|
|
if len(raw_comp) == 2:
|
|
component = raw_comp[1].strip()
|
|
return component
|
|
|
|
def raw_input_exit_on_ctrlc(*args, **kwargs):
|
|
"""A wrapper around raw_input() to exit with a normalized message on Control-C"""
|
|
try:
|
|
return raw_input(*args, **kwargs)
|
|
except KeyboardInterrupt:
|
|
print 'Abort requested. No sync request filed.'
|
|
sys.exit(1)
|
|
|
|
def usage():
|
|
print '''Usage: requestsync [-h|-n|-s|-k <keyid>|--lp] <source package> <target release> [basever]
|
|
|
|
In some cases, the base version (fork point from Debian) cannot be determined
|
|
automatically, and you'll get a complete Debian changelog. Specify the correct
|
|
base version of the package in Ubuntu.
|
|
|
|
Options:
|
|
-h print this help
|
|
-n new source package (doesn't exist yet in Ubuntu)
|
|
-s request sponsoring (through ubuntu-{main,universe}-sponsors)
|
|
-k <keyid> sign email with <keyid> (only used when submitting per email)
|
|
--lp use python-launchpad-bugs instead of email for bug submitting
|
|
'''
|
|
sys.exit(1)
|
|
|
|
def mail_bug(source_package, subscribe, status, bugtitle, bugtext, keyid = None):
|
|
'''Submit the sync request per email.
|
|
Return True if email successfully send, otherwise False.'''
|
|
|
|
import smtplib
|
|
|
|
to = 'new@bugs.launchpad.net'
|
|
|
|
myemailaddr = os.getenv('DEBEMAIL')
|
|
if not myemailaddr:
|
|
print >> sys.stderr, 'The environment variable DEBEMAIL needs to be set to make use of this script.'
|
|
return False
|
|
|
|
# generate initial mailbody:
|
|
mailbody = ''
|
|
if source_package:
|
|
mailbody += ' affects ubuntu/%s\n' % source_package
|
|
else:
|
|
mailbody += ' affects ubuntu\n'
|
|
mailbody = mailbody + ' status %s\n importance wishlist\n subscribe %s\n\n%s' % (status, subscribe, bugtext)
|
|
|
|
# prepare sign_command:
|
|
sign_command = 'gpg'
|
|
for cmd in ('gpg2', 'gnome-gpg'):
|
|
if os.access('/usr/bin/%s' % cmd, os.X_OK):
|
|
sign_command = cmd
|
|
|
|
gpg_command = [sign_command, '--clearsign']
|
|
if keyid:
|
|
gpg_command.extend(('-u', keyid))
|
|
|
|
in_confirm_loop = True
|
|
while in_confirm_loop:
|
|
# sign it
|
|
gpg = subprocess.Popen(gpg_command, stdin = subprocess.PIPE, stdout = subprocess.PIPE)
|
|
signed_report = gpg.communicate(mailbody)[0]
|
|
assert gpg.returncode == 0
|
|
|
|
# generate email
|
|
mail = 'From: %s\nTo: %s\nSubject: %s\n\n%s' % (myemailaddr, to, bugtitle, signed_report)
|
|
|
|
# ask for confirmation and allow to edit:
|
|
print mail
|
|
print 'Do you want to edit the report before sending [y/N]? Press Control-C to abort.'
|
|
while 1:
|
|
val = raw_input_exit_on_ctrlc()
|
|
if val.lower() in ('y', 'yes'):
|
|
(bugtitle, mailbody) = edit_report(bugtitle, mailbody)
|
|
break
|
|
elif val.lower() in ('n', 'no', ''):
|
|
in_confirm_loop = False
|
|
break
|
|
else:
|
|
print "Invalid answer"
|
|
|
|
# get server address
|
|
mailserver = os.getenv('DEBSMTP')
|
|
if mailserver:
|
|
print 'Using custom SMTP server:', mailserver
|
|
else:
|
|
mailserver = 'fiordland.ubuntu.com'
|
|
|
|
# get server port
|
|
mailserver_port = os.getenv('DEBSMTP_PORT')
|
|
if mailserver_port:
|
|
print 'Using custom SMTP port:', mailserver_port
|
|
else:
|
|
mailserver_port = 25
|
|
|
|
# connect to the server
|
|
s = smtplib.SMTP(mailserver, mailserver_port)
|
|
|
|
# authenticate to the server
|
|
mailserver_user = os.getenv('DEBSMTP_USER')
|
|
mailserver_pass = os.getenv('DEBSMTP_PASS')
|
|
if mailserver_user and mailserver_pass:
|
|
try:
|
|
s.login(mailserver_user, mailserver_pass)
|
|
except smtplib.SMTPAuthenticationError:
|
|
print 'Error authenticating to the server: invalid username and password.'
|
|
s.quit()
|
|
return False
|
|
except:
|
|
print 'Unknown SMTP error.'
|
|
s.quit()
|
|
return False
|
|
|
|
s.sendmail(myemailaddr, to, mail)
|
|
s.quit()
|
|
print 'Sync request mailed.'
|
|
|
|
return True
|
|
|
|
def post_bug(source_package, subscribe, status, bugtitle, bugtext):
|
|
'''Use python-launchpad-bugs to submit the sync request.
|
|
Return True if successfully posted, otherwise False.'''
|
|
|
|
import glob, os.path
|
|
global launchpad_cookiefile
|
|
|
|
try:
|
|
import launchpadbugs.connector
|
|
except ImportError:
|
|
print >> sys.stderr, 'Importing launchpadbugs failed. Is python-launchpad-bugs installed?'
|
|
return False
|
|
|
|
# Search cookiefile (for authentication to lp)
|
|
if launchpad_cookiefile == None:
|
|
try_globs = ('~/.lpcookie.txt', '~/.mozilla/*/*/cookies.sqlite', '~/.mozilla/*/*/cookies.txt')
|
|
for try_glob in try_globs:
|
|
try:
|
|
cookiefile = glob.glob(os.path.expanduser(try_glob))[0]
|
|
except IndexError:
|
|
continue
|
|
# Found:
|
|
launchpad_cookiefile = cookiefile
|
|
print "Using cookie file at %s" % launchpad_cookiefile
|
|
break
|
|
|
|
if launchpad_cookiefile == None:
|
|
print >> sys.stderr, 'Could not find cookie file for Launchpad (looked in %s)' % ", ".join(try_globs)
|
|
return False
|
|
|
|
if source_package:
|
|
product = {'name': source_package, 'target': 'ubuntu'}
|
|
else:
|
|
# new source package
|
|
product = {'name': 'ubuntu'}
|
|
|
|
in_confirm_loop = True
|
|
while in_confirm_loop:
|
|
print 'Summary:\n%s\n\nDescription:\n%s' % (bugtitle, bugtext)
|
|
|
|
# ask for confirmation and allow to edit:
|
|
print 'Do you want to edit the report before sending [y/N]? Press Control-C to abort.'
|
|
while 1:
|
|
val = raw_input_exit_on_ctrlc()
|
|
if val.lower() in ('y', 'yes'):
|
|
(bugtitle, bugtext) = edit_report(bugtitle, bugtext)
|
|
break
|
|
elif val.lower() in ('n', 'no', ''):
|
|
in_confirm_loop = False
|
|
break
|
|
else:
|
|
print "Invalid answer"
|
|
|
|
# Create bug
|
|
Bug = launchpadbugs.connector.ConnectBug()
|
|
Bug.authentication = launchpad_cookiefile
|
|
|
|
bug = Bug.New(product = product, summary = bugtitle, description = bugtext)
|
|
bug.importance = 'Wishlist'
|
|
bug.status = status
|
|
bug.subscriptions.add(subscribe)
|
|
bug.commit()
|
|
|
|
print 'Sync request filed as bug #%i: https://launchpad.net/bugs/%i' % (bug.bugnumber, bug.bugnumber)
|
|
|
|
return True
|
|
|
|
def edit_report(subject, body, changes_required=False):
|
|
"""Edit a report (consisting of subject and body) in sensible-editor.
|
|
|
|
subject and body get decorated, before they are written to the temporary
|
|
file and undecorated after editing again.
|
|
If changes_required is True and the file has not been edited
|
|
(according to its mtime), an error is written to STDERR and the
|
|
program exits.
|
|
Returns (new_subject, new_body).
|
|
"""
|
|
import re, string
|
|
|
|
report = "Summary (one line):\n%s\n\nDescription:\n%s" % (subject, body)
|
|
|
|
# Create tempfile and remember mtime
|
|
import tempfile
|
|
report_file = tempfile.NamedTemporaryFile( prefix='requestsync_' )
|
|
report_file.file.write(report)
|
|
report_file.file.flush()
|
|
mtime_before = os.stat( report_file.name ).st_mtime
|
|
|
|
# Launch editor
|
|
try:
|
|
editor = subprocess.check_call( ['sensible-editor', report_file.name] )
|
|
except subprocess.CalledProcessError, e:
|
|
print >> sys.stderr, 'Error calling sensible-editor: %s\nAborting.' % (e,)
|
|
sys.exit(1)
|
|
|
|
# Check if the tempfile has been changed
|
|
if changes_required:
|
|
report_file_info = os.stat( report_file.name )
|
|
if mtime_before == os.stat( report_file.name ).st_mtime:
|
|
print >> sys.stderr, 'The temporary file %s has not been changed, but you have\nto explain why the Ubuntu changes can be dropped. Aborting. [Press ENTER]' % (report_file.name,)
|
|
raw_input()
|
|
sys.exit(1)
|
|
|
|
report_file.file.seek(0)
|
|
report = report_file.file.read()
|
|
report_file.file.close()
|
|
|
|
# Undecorate report again:
|
|
(new_subject, new_body) = report.split("\nDescription:\n", 1)
|
|
# Remove prefix and whitespace for subject:
|
|
new_subject = string.rstrip( re.sub("\n", " ", re.sub("^Summary \(one line\):\s*", "", new_subject, 1)) )
|
|
|
|
return (new_subject, new_body)
|
|
|
|
|
|
#
|
|
# entry point
|
|
#
|
|
|
|
if __name__ == '__main__':
|
|
newsource = False
|
|
sponsorship = False
|
|
keyid = None
|
|
use_lp_bugs = False
|
|
need_interaction = False
|
|
|
|
try:
|
|
opts, args = getopt.gnu_getopt(sys.argv[1:], 'hnsk:', ('lp'))
|
|
except getopt.GetoptError:
|
|
usage()
|
|
for o, a in opts:
|
|
if o == '-h': usage()
|
|
if o == '-n': newsource = True
|
|
if o == '-s': sponsorship = True
|
|
if o == '-k': keyid = a
|
|
if o == '--lp': use_lp_bugs = True
|
|
|
|
if len(args) not in (2, 3):
|
|
usage()
|
|
|
|
(srcpkg, release) = args[:2]
|
|
force_base_ver = None
|
|
if len(args) == 3:
|
|
force_base_ver = args[2]
|
|
(cur_ver, component) = ('0', 'universe') # Let's assume universe
|
|
if not newsource:
|
|
(cur_ver, component) = cur_version_component(srcpkg, release)
|
|
|
|
debiancomponent = debian_component(srcpkg)
|
|
deb_version = cur_deb_version(srcpkg)
|
|
|
|
if deb_version == cur_ver:
|
|
print 'The versions in Debian and Ubuntu are the same already (%s). Aborting.' % (deb_version,)
|
|
sys.exit(1)
|
|
|
|
# generate bug report
|
|
subscribe = 'ubuntu-archive'
|
|
status = 'confirmed'
|
|
if sponsorship:
|
|
status = 'new'
|
|
if component in ['main', 'restricted']:
|
|
subscribe = 'ubuntu-main-sponsors'
|
|
else:
|
|
subscribe = 'ubuntu-universe-sponsors'
|
|
|
|
report = 'Please sync %s %s (%s) from Debian unstable (%s).\n\n' % (srcpkg, deb_version, component, debiancomponent)
|
|
title = report[:-2]
|
|
|
|
base_ver = cur_ver
|
|
uidx = base_ver.find('ubuntu')
|
|
if uidx > 0:
|
|
base_ver = base_ver[:uidx]
|
|
need_interaction = True
|
|
|
|
print 'Changes have been made to the package in Ubuntu.'
|
|
print 'Please edit the report and give an explanation.'
|
|
print 'Press ENTER to start your editor. Press Control-C to abort now.'
|
|
print 'Not saving the report file will abort the request, too.'
|
|
raw_input_exit_on_ctrlc()
|
|
report += 'Explanation of the Ubuntu delta and why it can be dropped:\n' + \
|
|
'>>> ENTER_EXPLANATION_HERE <<<\n\n'
|
|
|
|
uidx = base_ver.find('build')
|
|
if uidx > 0:
|
|
base_ver = base_ver[:uidx]
|
|
|
|
if force_base_ver:
|
|
base_ver = force_base_ver
|
|
|
|
report += 'Changelog since current %s version %s:\n\n' % (release, cur_ver)
|
|
report += debian_changelog(srcpkg, debiancomponent, base_ver) + '\n'
|
|
|
|
if need_interaction:
|
|
report = edit_report(title, report, changes_required=True)
|
|
|
|
# Post sync request using Launchpad interface:
|
|
srcpkg = not newsource and srcpkg or None
|
|
if use_lp_bugs:
|
|
# Map status to the values expected by lp-bugs
|
|
mapping = {'new': 'New', 'confirmed': 'Confirmed'}
|
|
if post_bug(srcpkg, subscribe, mapping[status], title, report):
|
|
sys.exit(0)
|
|
# Abort on error:
|
|
print 'Something went wrong. No sync request filed.'
|
|
sys.exit(1)
|
|
|
|
# Mail sync request:
|
|
if mail_bug(srcpkg, subscribe, status, title, report, keyid):
|
|
sys.exit(0)
|
|
|
|
print 'Something went wrong. No sync request filed.'
|
|
sys.exit(1)
|