diff --git a/debian/NEWS b/debian/NEWS index 1915691..d096a17 100644 --- a/debian/NEWS +++ b/debian/NEWS @@ -1,3 +1,10 @@ +ubuntu-dev-tools (0.135) unstable; urgency=low + + reverse-build-depends was removed from ubuntu-dev-tools. reverse-depends -b + is equivalent. + + -- Stefano Rivera Sat, 12 Nov 2011 13:11:21 +0200 + ubuntu-dev-tools (0.131) unstable; urgency=low get-build-deps was removed from ubuntu-dev-tools. The newer mk-build-deps in diff --git a/debian/changelog b/debian/changelog index a75a9a3..b814834 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -ubuntu-dev-tools (0.136) UNRELEASED; urgency=low +ubuntu-dev-tools (0.135) UNRELEASED; urgency=low * grab-merge: Use wget -nv rather than -q, so that we see error messages (LP: #881967) @@ -10,8 +10,16 @@ ubuntu-dev-tools (0.136) UNRELEASED; urgency=low - Add --eatmydata flag (LP: #888440) * pbuilder-dist: Support using non-master mirrors. Thanks Mathieu Parent. (LP: #824285) + * New scripts: + - reverse-depends: Replaces reverse-build-depends. Uses an UbuntuWire + webservice for determining all reverse(-build)-dependencies for a + package. (LP: #696373) + - requestbackport: Files a backport request bug report, including a full + testing checklist. + * Don't allow boilerplate prompts through in submittodebian and requestsync + (LP: #887336) - -- Stefano Rivera Sat, 12 Nov 2011 23:28:05 +0200 + -- Stefano Rivera Sat, 12 Nov 2011 13:09:05 +0200 ubuntu-dev-tools (0.134) unstable; urgency=low diff --git a/debian/control b/debian/control index bb91a4c..d75670c 100644 --- a/debian/control +++ b/debian/control @@ -93,9 +93,10 @@ Description: useful tools for Ubuntu developers Debian of a package. - pull-lp-source - downloads lastest source package from Launchpad. - pull-revu-source - downloads the latest source package from REVU + - requestbackport - file a backporting request. - requestsync - files a sync request with Debian changelog and rationale. - - reverse-build-depends - find the reverse build dependencies that a package - has. + - reverse-depends - find the reverse dependencies (or build dependencies) of + a package. - setup-packaging-environment - assistant to get an Ubuntu installation ready for packaging work. - sponsor-patch - Downloads a patch from a Launchpad bug, patches the source diff --git a/debian/copyright b/debian/copyright index 15f3b53..55a4885 100644 --- a/debian/copyright +++ b/debian/copyright @@ -47,18 +47,15 @@ Files: 404main doc/import-bug-from-debian.1 doc/pbuilder-dist-simple.1 doc/pbuilder-dist.1 - doc/reverse-build-depends.1 doc/submittodebian.1 import-bug-from-debian pbuilder-dist pbuilder-dist-simple - reverse-build-depends submittodebian Copyright: 2007-2010, Canonical Ltd. 2009, James Westby 2008, Jamin W. Collins 2008, Jordan Mantha - 2008-2009, Patrick Schoenfeld 2006-2007, Pete Savage 2009, Ryan Kavanagh 2007, Siegfried-Angel Gevatter Pujals @@ -152,17 +149,22 @@ License: GPL-3+ Files: doc/pull-debian-debdiff.1 doc/pull-debian-source.1 + doc/requestbackport.1 + doc/reverse-depends.1 doc/sponsor-patch.1 doc/ubuntu-dev-tools.5 doc/update-maintainer.1 pull-debian-debdiff pull-debian-source + requestbackport + reverse-depends sponsor-patch test-data/* ubuntutools/archive.py ubuntutools/builder.py ubuntutools/config.py ubuntutools/question.py + ubuntutools/rdepends.py ubuntutools/sponsor_patch/* ubuntutools/test/* ubuntutools/update_maintainer.py diff --git a/doc/requestbackport.1 b/doc/requestbackport.1 new file mode 100644 index 0000000..5ee9eba --- /dev/null +++ b/doc/requestbackport.1 @@ -0,0 +1,56 @@ +.\" Copyright (C) 2011, Stefano Rivera +.\" +.\" Permission to use, copy, modify, and/or distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +.\" REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +.\" AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +.\" INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +.\" LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +.\" OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +.\" PERFORMANCE OF THIS SOFTWARE. +.TH requestbackport 1 "November 2011" ubuntu\-dev\-tools + +.SH NAME +requestbackport \- File a backport request bug + +.SH SYNOPSIS +.B requestbackport \fR[\fIoptions\fR] \fIpackage\fR + +.SH DESCRIPTION +Determine the intermediate releases that \fIpackage\fR needs to be +backported to, list all reverse\-dependencies, and file the backporting +request. +\fBrequestbackport\fR will include a testing checklist in the bug. + +.SH OPTIONS +.TP +\fB\-d\fR \fIDEST\fR, \fB\-\-destination\fR=\fIDEST\fR +Backport to \fIDEST\fR release and necessary intermediate +releases. Default: current stable release. +.TP +\fB\-s\fR \fISOURCE\fR, \fB\-\-source\fR=\fISOURCE\fR +Backport from \fISOURCE\fR release. +Default: current development release. +.TP +\fB\-l\fR \fIINSTANCE\fR, \fB\-\-lpinstance\fR=\fIINSTANCE\fR +Launchpad instance to connect to. +Default: \fBproduction\fR. +.TP +\fB\-\-no\-conf\fR +Don't read config files or environment variables +.TP +\fB\-h\fR, \fB\-\-help\fR +Display a help message and exit. + +.SH SEE ALSO +.BR backportpackage (1), +.BR reverse\-depends (1). + +.SH AUTHORS +\fBreverse\-depends\fR and this manpage were written by Stefano Rivera +. +.PP +Both are released under the terms of the ISC License. diff --git a/doc/reverse-build-depends.1 b/doc/reverse-build-depends.1 deleted file mode 100644 index 3fcdc0d..0000000 --- a/doc/reverse-build-depends.1 +++ /dev/null @@ -1,172 +0,0 @@ -.\" Automatically generated by Pod::Man 2.1801 (Pod::Simple 3.05) -.\" -.\" Standard preamble: -.\" ======================================================================== -.de Sp \" Vertical space (when we can't use .PP) -.if t .sp .5v -.if n .sp -.. -.de Vb \" Begin verbatim text -.ft CW -.nf -.ne \\$1 -.. -.de Ve \" End verbatim text -.ft R -.fi -.. -.\" Set up some character translations and predefined strings. \*(-- will -.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left -.\" double quote, and \*(R" will give a right double quote. \*(C+ will -.\" give a nicer C++. Capital omega is used to do unbreakable dashes and -.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, -.\" nothing in troff, for use with C<>. -.tr \(*W- -.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' -.ie n \{\ -. ds -- \(*W- -. ds PI pi -. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch -. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch -. ds L" "" -. ds R" "" -. ds C` "" -. ds C' "" -'br\} -.el\{\ -. ds -- \|\(em\| -. ds PI \(*p -. ds L" `` -. ds R" '' -'br\} -.\" -.\" Escape single quotes in literal strings from groff's Unicode transform. -.ie \n(.g .ds Aq \(aq -.el .ds Aq ' -.\" -.\" If the F register is turned on, we'll generate index entries on stderr for -.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index -.\" entries marked with X<> in POD. Of course, you'll have to process the -.\" output yourself in some meaningful fashion. -.ie \nF \{\ -. de IX -. tm Index:\\$1\t\\n%\t"\\$2" -.. -. nr % 0 -. rr F -.\} -.el \{\ -. de IX -.. -.\} -.\" -.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). -.\" Fear. Run. Save yourself. No user-serviceable parts. -. \" fudge factors for nroff and troff -.if n \{\ -. ds #H 0 -. ds #V .8m -. ds #F .3m -. ds #[ \f1 -. ds #] \fP -.\} -.if t \{\ -. ds #H ((1u-(\\\\n(.fu%2u))*.13m) -. ds #V .6m -. ds #F 0 -. ds #[ \& -. ds #] \& -.\} -. \" simple accents for nroff and troff -.if n \{\ -. ds ' \& -. ds ` \& -. ds ^ \& -. ds , \& -. ds ~ ~ -. ds / -.\} -.if t \{\ -. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" -. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' -. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' -. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' -. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' -. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' -.\} -. \" troff and (daisy-wheel) nroff accents -.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' -.ds 8 \h'\*(#H'\(*b\h'-\*(#H' -.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] -.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' -.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' -.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] -.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] -.ds ae a\h'-(\w'a'u*4/10)'e -.ds Ae A\h'-(\w'A'u*4/10)'E -. \" corrections for vroff -.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' -.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' -. \" for low resolution devices (crt and lpr) -.if \n(.H>23 .if \n(.V>19 \ -\{\ -. ds : e -. ds 8 ss -. ds o a -. ds d- d\h'-1'\(ga -. ds D- D\h'-1'\(hy -. ds th \o'bp' -. ds Th \o'LP' -. ds ae ae -. ds Ae AE -.\} -.rm #[ #] #H #V #F C -.\" ======================================================================== -.\" -.IX Title "BUILD-RDEPS 1" -.TH BUILD-RDEPS 1 "2008-08-14" "Debian Utilities" " " -.\" For nroff, turn off justification. Always turn off hyphenation; it makes -.\" way too many mistakes in technical documents. -.if n .ad l -.nh -.SH "NAME" -build\-rdeps \- find packages that depend on a specific package to build (reverse build depends) -.SH "SYNOPSIS" -.IX Header "SYNOPSIS" -\&\fBubuild-rdeps\fR \fIpackage\fR -.SH "DESCRIPTION" -.IX Header "DESCRIPTION" -\&\fBubuild-rdeps\fR searches for all packages that build-depend on the specified package. -.SH "OPTIONS" -.IX Header "OPTIONS" -.IP "\fB\-u\fR \fB\-\-update\fR" 4 -.IX Item "-u --update" -Run apt-get update before searching for build-depends. -.IP "\fB\-s\fR \fB\-\-sudo\fR" 4 -.IX Item "-s --sudo" -Use sudo when running apt-get update. Has no effect if \-u is omitted. -.IP "\fB\-\-distribution\fR" 4 -.IX Item "--distribution" -Select another distribution, which is searched for build-depends. -.IP "\fB\-m\fR \fB\-\-print\-maintainer\fR" 4 -.IX Item "-m --print-maintainer" -Print the value of the maintainer field for each package. -.IP "\fB\-d\fR \fB\-\-debug\fR" 4 -.IX Item "-d --debug" -Run the debug mode -.IP "\fB\-\-help\fR" 4 -.IX Item "--help" -Show the usage information. -.IP "\fB\-\-version\fR" 4 -.IX Item "--version" -Show the version information. -.SH "LICENSE" -.IX Header "LICENSE" -This code is copyright by Patrick Schoenfeld -, all rights reserved. -This program comes with \s-1ABSOLUTELEY\s0 \s-1NO\s0 \s-1WARRANTY\s0. -You are free to redistribute this code under the terms of the -\&\s-1GNU\s0 General Public License, version 2 or later. -.SH "AUTHOR" -.IX Header "AUTHOR" -Patrick Schoenfeld diff --git a/doc/reverse-depends.1 b/doc/reverse-depends.1 new file mode 100644 index 0000000..9b405b1 --- /dev/null +++ b/doc/reverse-depends.1 @@ -0,0 +1,81 @@ +.\" Copyright (C) 2011, Stefano Rivera +.\" +.\" Permission to use, copy, modify, and/or distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +.\" REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +.\" AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +.\" INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +.\" LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +.\" OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +.\" PERFORMANCE OF THIS SOFTWARE. +.TH reverse\-depends 1 "November 2011" ubuntu\-dev\-tools + +.SH NAME +reverse\-depends \- List the reverse\-dependencies (or +build\-dependencies) of a package + +.SH SYNOPSIS +.B reverse\-depends \fR[\fIoptions\fR] \fIpackage + +.SH DESCRIPTION +List reverse\-dependencies (or build\-dependencies) of \fIpackage\fR. +If the package name is prefixed with \fBsrc:\fR then the +reverse\-dependencies of all the binary packages that the specified +source package builds will be listed. + +.SH OPTIONS +.TP +\fB\-r\fR \fIRELEASE\fR, \fB\-\-release\fR=\fIRELEASE\fR +Query dependencies in \fIRELEASE\fR. +Default: current development release. +.TP +\fB\-R\fR, \fB\-\-without\-recommends\fR +Only consider Depends relationships, not Recommends. +.TP +\fB\-s\fR, \fB\-\-with\-suggests\fR +Also consider Suggests relationships. +.TP +\fB\-b\fR, \fB\-\-build\-depends\fR +Query build dependencies. +Synonym for \fB\-\-arch\fR=\fIsource\fR. +.TP +\fB\-a\fR \fIARCH\fR, \fB\-\-arch\fR=\fIARCH\fR +Query dependencies in \fIARCH\fR. +Besides valid architecture names, the special values \fBany\fR and +\fBsource\fR may be used. +\fBany\fR displays all reverse dependencies, the union across all +architecture. +\fBsource\fR displays build dependencies. +Default: \fBany\fR. +.TP +\fB\-c\fR \fICOMPONENT\fR, \fB\-\-component\fR=\fICOMPONENT\fR +Only consider reverse\-dependencies in \fICOMPONENT\fR. Can +be specified multiple times. +Default: all components. +.TP +\fB\-l\fR, \fB\-\-list\fR +Display a simple, machine\-readable list. +.TP +\fB\-u\fR \fIURL\fR, \fB\-\-service\-url\fR=\fIURL\fR +Reverse Dependencies web\-service \fIURL\fR. +Default: UbuntuWire's service at +\fBhttp://qa.ubuntuwire.org/rdepends/\fR. +.TP +\fB\-h\fR, \fB\-\-help\fR +Display a help message and exit + +.SH EXAMPLES +All reverse dependencies of source package bash: +.IP +.nf +.B reverse\-depends src:bash +.fi + +.SH AUTHORS +\fBreverse\-depends\fR and this manpage were written by Stefano Rivera +. +.PP +Both are released under the terms of the ISC License. diff --git a/pbuilder-dist b/pbuilder-dist index a4faad1..3d0e00b 100755 --- a/pbuilder-dist +++ b/pbuilder-dist @@ -38,6 +38,8 @@ from distro_info import DebianDistroInfo import ubuntutools.misc from ubuntutools.config import UDTConfig from ubuntutools import subprocess +from ubuntutools.question import YesNoQuestion + class PbuilderDist: def __init__(self, builder): @@ -136,9 +138,10 @@ class PbuilderDist: # Debian experimental doesn't have a debootstrap file but # should work nevertheless. if distro not in self._debian_distros: - answer = ask(('Warning: Unknown distribution "%s". Do you ' - 'want to continue [y/N]? ') % distro) - if answer not in ('y', 'Y'): + answer = YesNoQuestion().ask( + 'Warning: Unknown distribution "%s". ' + 'Do you want to continue' % distro, 'no') + if answer == 'yes': sys.exit(0) else: Logger.error('Please install package "debootstrap".') @@ -307,22 +310,6 @@ class PbuilderDist: self.builder, ] + arguments - -def ask(question): - """ ask(question) -> string - - Ask the given question and return the answer. Also catch - KeyboardInterrupt (Ctrl+C) and EOFError (Ctrl+D) exceptions and - immediately return None if one of those is found. - """ - try: - answer = raw_input(question) - except (KeyboardInterrupt, EOFError): - print - answer = None - - return answer - def show_help(exit_code = 0): """ help() -> None diff --git a/requestbackport b/requestbackport new file mode 100755 index 0000000..1ad21b0 --- /dev/null +++ b/requestbackport @@ -0,0 +1,258 @@ +#!/usr/bin/python +# +# Copyright (C) 2011, Stefano Rivera +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +from collections import defaultdict +import optparse +import sys + +import apt +from devscripts.logger import Logger +from distro_info import UbuntuDistroInfo + +from ubuntutools.lp.lpapicache import Launchpad, Distribution +from ubuntutools.lp.udtexceptions import PackageNotFoundException +from ubuntutools.config import UDTConfig +from ubuntutools.rdepends import query_rdepends, RDependsException +from ubuntutools.question import YesNoQuestion, EditBugReport + + +class DestinationException(Exception): + pass + + +def determine_destinations(source, destination): + ubuntu_info = UbuntuDistroInfo() + if destination is None: + destination = ubuntu_info.stable() + + if source not in ubuntu_info.all: + raise DestinationException("Source release %s does not exist" % source) + if destination not in ubuntu_info.all: + raise DestinationException("Destination release %s does not exist" + % destination) + if destination not in ubuntu_info.supported(): + raise DestinationException("Destination release %s is not supported" + % destination) + + found = False + destinations = [] + support_gap = False + for release in ubuntu_info.all: + if release == destination: + found = True + if release == source: + break + if found: + if support_gap: + if ubuntu_info.is_lts(release): + support_gap = False + else: + continue + if release not in ubuntu_info.supported(): + support_gap = True + continue + destinations.append(release) + + assert found + assert len(destinations) > 0 + + return destinations + + +def find_rdepends(package, releases, published_binaries): + intermediate = defaultdict(lambda: defaultdict(list)) + + # We want to display every pubilshed binary, even if it has no rdepends + for binpkg in published_binaries: + intermediate[binpkg] + + for arch in ('any', 'source'): + for release in releases: + for binpkg in published_binaries: + try: + raw_rdeps = query_rdepends(binpkg, release, arch) + except RDependsException: + # Not published? TODO: Check + continue + for relationship, rdeps in raw_rdeps.iteritems(): + for rdep in rdeps: + if rdep['Package'] in published_binaries: + continue + intermediate[binpkg][rdep['Package']] \ + .append((release, relationship)) + + output = [] + for binpkg, rdeps in intermediate.iteritems(): + output += ['', binpkg, '-' * len(binpkg)] + for pkg, appearences in rdeps.iteritems(): + output += ['* %s' % pkg] + for release, relationship in appearences: + output += [' [ ] %s (%s)' % (release, relationship)] + + found_any = sum(len(rdeps) for rdeps in intermediate.itervalues()) + if found_any: + output = [ + "Reverse dependencies:", + "=====================", + "The following reverse-dependencies need to be tested against the " + "new version of %(package)s. " + "For reverse-build-dependencies (-Indep), please test that the " + "package still builds against the new %(package)s. " + "For reverse-dependencies, please test that the version of the " + "package currently in the release still works with the new " + "%(package)s installed. " + "Reverse- Recommends, Suggests, and Enhances don't need to be " + "tested, and are listed for completeness-sake." + ] + output + else: + output = ["No reverse dependencies"] + + return output + + +def locate_package(package, distribution): + archive = Distribution('ubuntu').getArchive() + for pass_ in ('source', 'binary'): + try: + package_spph = archive.getSourcePackage(package, distribution) + return package_spph + except PackageNotFoundException, e: + if pass_ == 'binary': + Logger.error(str(e)) + sys.exit(1) + + try: + apt_pkg = apt.Cache()[package] + except KeyError: + continue + package = apt_pkg.candidate.source_name + Logger.normal("Binary package specified, considering its source " + "package instead: %s", package) + + +def request_backport(package_spph, source, destinations): + + published_binaries = set() + for bpph in package_spph._lpobject.getPublishedBinaries(): + published_binaries.add(bpph.binary_package_name) + + testing = [] + testing += ["You can test-build the backport in your PPA with " + "backportpackage:"] + testing += ["$ backportpackage -u ppa:/ " + "-s %s -d %s %s" + % (source, dest, package_spph.getPackageName()) + for dest in destinations] + testing += [""] + for dest in destinations: + testing += ['* %s:' % dest] + testing += ["[ ] Package builds without modification"] + testing += ["[ ] %s installs cleanly and runs" % binary + for binary in published_binaries] + + subst = { + 'package': package_spph.getPackageName(), + 'version': package_spph.getVersion(), + 'component': package_spph.getComponent(), + 'source': source, + 'destinations': ', '.join(destinations), + } + subject = ("Please backport %(package)s %(version)s (%(component)s) " + "from %(source)s" % subst) + body = ('\n'.join( + [ + "Please backport %(package)s %(version)s (%(component)s) " + "from %(source)s to %(destinations)s.", + "", + "Reason for the backport:", + "========================", + "<<< Enter your reasoning here >>>", + "", + "Testing:", + "========", + "Mark off items in the checklist [X] as you test them, " + "but please leave the checklist so that backporters can quickly " + "evaluate the state of testing.", + "" + ] + + testing + + [""] + + find_rdepends(package_spph, destinations, published_binaries) + + [""] + ) % subst) + + editor = EditBugReport(subject, body) + editor.edit() + subject, body = editor.get_report() + + Logger.normal('The final report is:\nSummary: %s\nDescription:\n%s\n', + subject, body) + if YesNoQuestion().ask("Request this backport", "yes") == "no": + sys.exit(1) + + targets = [Launchpad.projects['%s-backports' % destination] + for destination in destinations] + bug = Launchpad.bugs.createBug(title=subject, description=body, + target=targets[0]) + for target in targets[1:]: + bug.addTask(target=target) + + Logger.normal("Backport request filed as %s", bug.web_link) + + +def main(): + parser = optparse.OptionParser('%progname [options] package') + parser.add_option('-d', '--destination', metavar='DEST', + help='Backport to DEST release and necessary ' + 'intermediate releases ' + '(default: current stable release)') + parser.add_option('-s', '--source', metavar='SOURCE', + help='Backport from SOURCE release ' + '(default: current devel release)') + parser.add_option('-l', '--lpinstance', metavar='INSTANCE', default=None, + help='Launchpad instance to connect to ' + '(default: production).') + parser.add_option('--no-conf', action='store_true', + dest='no_conf', default=False, + help="Don't read config files or environment variables") + options, args = parser.parse_args() + + if len(args) != 1: + parser.error("One (and only one) package must be specified") + package = args[0] + + config = UDTConfig(options.no_conf) + + if options.lpinstance is None: + options.lpinstance = config.get_value('LPINSTANCE') + Launchpad.login(options.lpinstance) + + if options.source is None: + options.source = Distribution('ubuntu').getDevelopmentSeries().name + + try: + destinations = determine_destinations(options.source, + options.destination) + except DestinationException, e: + Logger.error(str(e)) + sys.exit(1) + + package_spph = locate_package(package, options.source) + request_backport(package_spph, options.source, destinations) + + +if __name__ == '__main__': + main() diff --git a/requestsync b/requestsync index cf89e5f..66d17dd 100755 --- a/requestsync +++ b/requestsync @@ -36,8 +36,8 @@ from distro_info import UbuntuDistroInfo from ubuntutools.config import UDTConfig, ubu_email from ubuntutools.lp import udtexceptions from ubuntutools.misc import require_utf8 -from ubuntutools.requestsync.common import (edit_report, get_debian_changelog, - raw_input_exit_on_ctrlc) +from ubuntutools.question import confirmation_prompt, EditBugReport +from ubuntutools.requestsync.common import get_debian_changelog # # entry point @@ -212,8 +212,7 @@ def main(): print ("'%s' doesn't exist in 'Ubuntu %s'.\n" "Do you want to sync a new package?" % (srcpkg, release)) - raw_input_exit_on_ctrlc('Press [Enter] to continue ' - 'or [Ctrl-C] to abort. ') + confirmation_prompt() newsource = True # Get the requested Debian source package @@ -290,8 +289,7 @@ def main(): '>>> ENTER_EXPLANATION_HERE <<<\n\n') if need_interaction: - raw_input_exit_on_ctrlc('Press [Enter] to continue.' - 'Press [Ctrl-C] to abort now. ') + confirmation_prompt() base_version = force_base_version or ubuntu_version @@ -313,8 +311,10 @@ def main(): changelog = "XXX FIXME: add changelog here XXX" report += changelog - (title, report) = edit_report(title, report, - changes_required=need_interaction) + editor = EditBugReport(title, report) + editor.edit(optional=not need_interaction) + title, report = editor.get_report() + if 'XXX FIXME' in report: print >> sys.stderr, ("E: changelog boilerplate found in report, " "please manually add changelog when using '-C'") diff --git a/reverse-build-depends b/reverse-build-depends deleted file mode 100755 index 8b66be9..0000000 --- a/reverse-build-depends +++ /dev/null @@ -1,271 +0,0 @@ -#!/usr/bin/perl -# Copyright (C) Patrick Schoenfeld -# Copyright (C) 2009 Ryan Kavanagh -# -# 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. - -=head1 NAME - -build-rdeps - find packages that depend on a specific package to build (reverse build depends) - -=head1 SYNOPSIS - -B I - -=head1 DESCRIPTION - -B searches for all packages that build-depend on the specified package. - -=head1 OPTIONS - -=over 4 - -=item B<-u> B<--update> - -Run apt-get update before searching for build-depends. - -=item B<-s> B<--sudo> - -Use sudo when running apt-get update. Has no effect if -u is omitted. - -=item B<--distribution> - -Select another distribution, which is searched for build-depends. - -=item B<-m> B<--print-maintainer> - -Print the value of the maintainer field for each package. - -=item B<-d> B<--debug> - -Run the debug mode - -=item B<--help> - -Show the usage information. - -=item B<--version> - -Show the version information. - -=back - -=cut - -use warnings; -use strict; -use File::Basename; -use File::Find; -use Getopt::Long; -use Pod::Usage; -use Data::Dumper; -my $progname = basename($0); -my $version = '1.0'; -my $default_distribution = `ubuntu-distro-info --devel`; -chomp($default_distribution); -my $dctrl = "/usr/bin/grep-dctrl"; -my $sources_path = "/var/lib/apt/lists/"; -my $source_pattern = ".*_dists_${default_distribution}_.*Sources\$"; -my @source_files; -my $sources_count=0; -my $opt_debug; -my $opt_update; -my $opt_sudo; -my $opt_maintainer; -my $opt_mainonly; -my $opt_distribution; - -if (!(-x $dctrl)) { - die "$progname: Fatal error. grep-dctrl is not available.\nPlease install the 'dctrl-tools' package.\n"; -} - -sub version { - print <<"EOT"; -This is $progname $version, from the Debian devscripts package, v. ###VERSION### -This code is copyright by Patrick Schoenfeld, all rights reserved. -It comes with ABSOLUTELY NO WARRANTY. You are free to redistribute this code -under the terms of the GNU General Public License, version 2 or later. -EOT -exit (0); -} - -sub usage { - print <<"EOT"; -usage: $progname packagename - $progname --help - $progname --version - -Searches for all packages that build-depend on the specified package. - -Options: - -u, --update Run apt-get update before searching for build-depends. - (needs root privileges) - -s, --sudo Use sudo when running apt-get update - (has no effect when -u is omitted) - -d, --debug Enable the debug mode - -m, --print-maintainer Print the maintainer information (experimental) - --distribution distribution Select a distribution to search for build-depends - (Default: $default_distribution) - --only-main Ignore universe and multiverse - -EOT -version; -} - -sub findsources { - if (/$source_pattern/ and $sources_count <= 3) { - unless ($opt_mainonly and /(universe|multiverse)/) { - push(@source_files, $_); - $sources_count+=1; - print STDERR "DEBUG: Added source file: $_ (#$sources_count)\n" if ($opt_debug); - } - } -} - -sub findreversebuilddeps { - my ($package, $source_file) = @_; - my %packages; - my $depending_package; - my $count=0; - my $maintainer_info=''; - - open(PACKAGES, "$dctrl -F Build-Depends,Build-Depends-Indep $package -s Package,Build-Depends,Build-Depends-Indep,Maintainer $source_file|"); - - while() { - chomp; - print STDERR "$_\n" if ($opt_debug); - if (/Package: (.*)$/) { - $depending_package = $1; - $packages{$depending_package}->{'Build-Depends'} = 0; - } - - if (/Maintainer: (.*)$/) { - if ($depending_package) { - $packages{$depending_package}->{'Maintainer'} = $1; - } - } - - if (/Build-Depends: (.*)$/ or /Build-Depends-Indep: (.*)$/) { - if ($depending_package) { - print STDERR "$1\n" if ($opt_debug); - if ($1 =~ /^(.*\s)?$package([\s,]|$)/) { - $packages{$depending_package}->{'Build-Depends'} = 1; - } - } - - } - } - - while($depending_package = each(%packages)) { - if ($packages{$depending_package}->{'Build-Depends'} != 1) { - print STDERR "Ignoring package $depending_package because its not really build depending on $package.\n" if ($opt_debug); - next; - } - if ($opt_maintainer) { - $maintainer_info = "($packages{$depending_package}->{'Maintainer'})"; - } - - $count+=1; - print "$depending_package $maintainer_info \n"; - - } - - if ($count == 0) { - print "No reverse build-depends found for $package.\n\n" - } - else { - print "\nFound a total of $count reverse build-depend(s) for $package.\n\n"; - } -} - -if ($#ARGV < 0) { usage; exit(0); } - - -Getopt::Long::Configure('bundling'); -GetOptions( - "u|update" => \$opt_update, - "s|sudo" => \$opt_sudo, - "m|print-maintainer" => \$opt_maintainer, - "distribution=s" => \$opt_distribution, - "only-main" => \$opt_mainonly, - "d|debug" => \$opt_debug, - "h|help" => sub { usage; }, - "v|version" => sub { version; } -); - -my $package = shift; - -if (!$package) { - die "$progname: missing argument. expecting packagename\n"; -} - -print STDERR "DEBUG: Package => $package\n" if ($opt_debug); - -if ($opt_update) { - print STDERR "DEBUG: Updating apt-cache before search\n" if ($opt_debug); - my @cmd; - if ($opt_sudo) { - print STDERR "DEBUG: Using sudo to become root\n" if ($opt_debug); - push(@cmd, 'sudo'); - } - push(@cmd, 'apt-get', 'update'); - system @cmd; -} - -if ($opt_distribution) { - print STDERR "DEBUG: Setting distribution to $opt_distribution" if ($opt_debug); - $source_pattern = ".*_dists_" . $opt_distribution . "_.*Sources\$"; -} - -# Find sources files -find(\&findsources, $sources_path); - -if (($#source_files+1) <= 0) { - die "$progname: unable to find sources files.\nDid you forget to run apt-get update (or add --update to this command)?"; -} - -foreach my $source_file (@source_files) { - if ($source_file =~ /main/) { - print "Reverse Build-depends in main:\n"; - print "------------------------------\n\n"; - findreversebuilddeps($package, "$sources_path/$source_file"); - } - - if ($source_file =~ /universe/) { - print "Reverse Build-depends in universe:\n"; - print "---------------------------------\n\n"; - findreversebuilddeps($package, "$sources_path/$source_file"); - } - - if ($source_file =~ /multiverse/) { - print "Reverse Build-depends in multiverse:\n"; - print "----------------------------------\n\n"; - findreversebuilddeps($package, "$sources_path/$source_file"); - } -} - -=head1 LICENSE - -This code is copyright by Patrick Schoenfeld -, all rights reserved. -This program comes with ABSOLUTELEY NO WARRANTY. -You are free to redistribute this code under the terms of the -GNU General Public License, version 2 or later. - -=head1 AUTHOR - -Patrick Schoenfeld - -=cut diff --git a/reverse-depends b/reverse-depends new file mode 100755 index 0000000..983b7f5 --- /dev/null +++ b/reverse-depends @@ -0,0 +1,148 @@ +#!/usr/bin/python +# +# Copyright (C) 2011, Stefano Rivera +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +import optparse +import sys + +from devscripts.logger import Logger +from distro_info import UbuntuDistroInfo + +from ubuntutools.rdepends import query_rdepends, RDependsException + + +def main(): + parser = optparse.OptionParser('%progname [options] package', + description="List reverse-dependencies of package. " + "If the package name is prefixed with src: then the " + "reverse-dependencies of all the binary packages that " + "the specified source package builds will be listed.") + parser.add_option('-r', '--release', metavar='RELEASE', + default=UbuntuDistroInfo().devel(), + help='Query dependencies in RELEASE. Default: devel') + parser.add_option('-R', '--without-recommends', + action='store_false', dest='recommends', default=True, + help='Only consider Depends relationships, ' + 'not Recommends') + parser.add_option('-s', '--with-suggests', + action='store_true', dest='suggests', default=False, + help='Also consider Suggests relationships') + parser.add_option('-b', '--build-depends', + action='store_const', dest='arch', const='source', + help='Query build dependencies (synonym for ' + '--arch=source)') + parser.add_option('-a', '--arch', metavar='ARCH', default='any', + help='Query dependencies in ARCH. ' + 'Default: any') + parser.add_option('-c', '--component', metavar='COMPONENT', + action='append', + help='Only consider reverse-dependencies in COMPONENT. ' + 'Can be specified multiple times. Default: all') + parser.add_option('-l', '--list', + action='store_true', default=False, + help='Display a simple, machine-readable list') + parser.add_option('-u', '--service-url', metavar='URL', + dest='server', default=None, + help='Reverse Dependencies webservice URL. ' + 'Default: UbuntuWire') + + options, args = parser.parse_args() + + if len(args) != 1: + parser.error("One (and only one) package must be specified") + package = args[0] + + opts = {} + if options.server is not None: + opts['server'] = options.server + + try: + data = query_rdepends(package, options.release, options.arch, **opts) + except RDependsException, e: + Logger.error(str(e)) + sys.exit(1) + + if options.arch == 'source': + fields = ['Reverse-Build-Depends', 'Reverse-Build-Depends-Indep'] + else: + fields = ['Reverse-Depends'] + if options.recommends: + fields.append('Reverse-Recommends') + if options.suggests: + fields.append('Reverse-Suggests') + + for field in data.keys(): + if field not in fields: + del data[field] + + if options.component: + for field, rdeps in data.items(): + filtered = [rdep for rdep in rdeps + if rdep['Component'] in options.component] + if not filtered: + del data[field] + else: + data[field] = filtered + + if options.list: + display_consise(data) + else: + display_verbose(data) + + +def display_verbose(data): + if not data: + print "No reverse dependencies found" + return + + all_archs = set() + # This isn't accurate, but we make up for it by displaying what we found + for rdeps in data.itervalues(): + for rdep in rdeps: + if 'Architectures' in rdep: + all_archs.update(rdep['Architectures']) + + for field, rdeps in data.iteritems(): + print field + print '=' * len(field) + rdeps.sort(key=lambda x: x['Package']) + for rdep in rdeps: + line = '* %s' % rdep['Package'] + if all_archs and set(rdep['Architectures']) != all_archs: + line += ' [%s]' % ' '.join(sorted(rdep['Architectures'])) + if 'Dependency' in rdep: + if len(line) < 30: + line += ' ' * (30 - len(line)) + line += ' (for %s)' % rdep['Dependency'] + print line + print + + if all_archs: + print ("Packages without architectures listed are " + "reverse-dependencies in: %s" + % ', '.join(sorted(list(all_archs)))) + + +def display_consise(data): + result = set() + for rdeps in data.itervalues(): + for rdep in rdeps: + result.add(rdep['Package']) + + print u'\n'.join(sorted(list(result))) + + +if __name__ == '__main__': + main() diff --git a/setup.py b/setup.py index 2439c76..f6a83b4 100755 --- a/setup.py +++ b/setup.py @@ -33,8 +33,9 @@ scripts = ['404main', 'pull-debian-source', 'pull-lp-source', 'pull-revu-source', + 'requestbackport', 'requestsync', - 'reverse-build-depends', + 'reverse-depends', 'setup-packaging-environment', 'sponsor-patch', 'submittodebian', diff --git a/submittodebian b/submittodebian index f83f1b6..f7501bb 100755 --- a/submittodebian +++ b/submittodebian @@ -30,7 +30,8 @@ from tempfile import mkdtemp from distro_info import UbuntuDistroInfo from ubuntutools.config import ubu_email -from ubuntutools.question import YesNoQuestion +from ubuntutools.question import YesNoQuestion, EditFile +from ubuntutools.subprocess import call, check_call, Popen, PIPE try: from debian.changelog import Changelog @@ -76,11 +77,10 @@ def gen_debdiff(tmpdir, changelog): debdiff = os.path.join(tmpdir, '%s_%s.debdiff' % (pkg, newver)) - if os.system('bzr diff -r tag:%s > /dev/null 2>&1' % oldver) == 256: + devnull = open('/dev/null', 'w') + diff_cmd = ['bzr', 'diff', '-r', 'tag:' + str(oldver)] + if call(diff_cmd, stdout=devnull, stderr=devnull) == 1: print "Extracting bzr diff between %s and %s" % (oldver, newver) - cmd = 'bzr diff -r tag:%s | filterdiff -x "*changelog*" > %s' % \ - (oldver, debdiff) - run_cmd(cmd) else: if oldver.epoch is not None: oldver = str(oldver)[str(oldver).index(":")+1:] @@ -94,9 +94,16 @@ def gen_debdiff(tmpdir, changelog): check_file(newdsc) print "Generating debdiff between %s and %s" % (oldver, newver) - cmd = 'debdiff %s %s | filterdiff -x "*changelog*" > %s' % \ - (olddsc, newdsc, debdiff) - run_cmd(cmd) + diff_cmd = ['debdiff', olddsc, newdsc] + + diff = Popen(diff_cmd, stdout=PIPE) + debdiff_f = open(debdiff, 'w') + filterdiff = Popen(['filterdiff', '-x', '*changelog*'], + stdin=diff.stdout, stdout=debdiff_f) + diff.stdout.close() + filterdiff.wait() + debdiff_f.close() + devnull.close() return debdiff @@ -109,22 +116,13 @@ def check_file(fname, critical = True): print u"Couldn't find «%s».\n" % fname sys.exit(1) -def edit_debdiff(debdiff): - cmd = 'sensible-editor %s' % (debdiff) - run_cmd(cmd) - def submit_bugreport(body, debdiff, deb_version, changelog): - cmd = ('reportbug -P "User: ubuntu-devel@lists.ubuntu.com" ' - '-P "Usertags: origin-ubuntu %s ubuntu-patch" -T patch -A %s ' - '-B debian -i %s -V %s %s') % \ - (UbuntuDistroInfo().devel(), debdiff, body, deb_version, - changelog.package) - run_cmd(cmd) - -def run_cmd(cmd): - if os.getenv('DEBUG'): - print "%s\n" % cmd - os.system(cmd) + devel = UbuntuDistroInfo().devel() + cmd = ('reportbug', '-P', 'User: ubuntu-devel@lists.ubuntu.com', + '-P', 'Usertags: origin-ubuntu %s ubuntu-patch' % devel, + '-T', 'patch', '-A', debdiff, '-B', 'debian', '-i', body, + '-V', deb_version, changelog.package) + check_call(cmd) def check_reportbug_config(): fn = os.path.expanduser('~/.reportbugrc') @@ -182,7 +180,12 @@ def main(): fp.close() debdiff = gen_debdiff(tmpdir, changelog) - edit_debdiff(debdiff) + + EditFile(debdiff, 'debdiff').edit(optional=True) + EditFile(body, 'bug report', [ + re.compile('.*REPLACE THIS WITH ACTUAL INFORMATION.*') + ]).edit() + submit_bugreport(body, debdiff, deb_version, changelog) os.unlink(body) os.unlink(debdiff) diff --git a/ubuntutools/question.py b/ubuntutools/question.py index 2f547a2..60c8f13 100644 --- a/ubuntutools/question.py +++ b/ubuntutools/question.py @@ -1,7 +1,8 @@ # # question.py - Helper class for asking questions # -# Copyright (C) 2010, Benjamin Drung +# Copyright (C) 2010, Benjamin Drung , +# 2011, Stefano Rivera # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above @@ -15,6 +16,14 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +import tempfile +import os +import re +import sys + +import ubuntutools.subprocess + + class Question(object): def __init__(self, options, show_help=True): assert len(options) >= 2 @@ -85,3 +94,104 @@ def input_number(question, min_number, max_number, default=None): print "Please input a number." assert type(selected) == int return selected + + +def confirmation_prompt(message=None, action=None): + '''Display message, or a stock message including action, and wait for the + user to press Enter + ''' + if message is None: + if action is None: + action = 'continue' + message = 'Press [Enter] to %s. Press [Ctrl-C] to abort now.' % action + try: + raw_input(message) + except KeyboardInterrupt: + print '\nAborting as requested.' + sys.exit(1) + + +class EditFile(object): + def __init__(self, filename, description, placeholders=None): + self.filename = filename + self.description = description + if placeholders is None: + placeholders = (re.compile(r'^<<<.*>>>$', re.UNICODE),) + self.placeholders = placeholders + + def edit(self, optional=False): + if optional: + print "Currently the %s looks like:" % self.description + with open(self.filename, 'r') as f: + print f.read() + if YesNoQuestion().ask("Edit", "no") == "no": + return + + done = False + while not done: + old_mtime = os.stat(self.filename).st_mtime + ubuntutools.subprocess.check_call(['sensible-editor', + self.filename]) + modified = old_mtime != os.stat(self.filename).st_mtime + placeholders_present = False + if self.placeholders: + with open(self.filename, 'r') as f: + for line in f: + for placeholder in self.placeholders: + if placeholder.search(line.strip()): + placeholders_present = True + + if placeholders_present: + print ("Placeholders still present in the %s. " + "Please replace them with useful information." + % self.description) + confirmation_prompt(action='edit again') + elif not modified: + print "The %s was not modified" % self.description + if YesNoQuestion().ask("Edit again", "yes") == "no": + done = True + elif self.check_edit(): + done = True + + def check_edit(self): + '''Override this to implement extra checks on the edited report. + Should return False if another round of editing is needed, + and should prompt the user to confirm that, if necessary. + ''' + return True + + +class EditBugReport(EditFile): + split_re = re.compile(r'^Summary.*?:\s+(.*)\s+' + r'Description:\s+(.*)$', + re.DOTALL | re.UNICODE) + + def __init__(self, subject, body, placeholders=None): + tmpfile = tempfile.NamedTemporaryFile(prefix=sys.argv[0] + '_', + suffix='.txt', + delete=False) + tmpfile.write((u'Summary (one line):\n%s\n\nDescription:\n%s' + % (subject, body)).encode('utf-8')) + tmpfile.close() + super(EditBugReport, self).__init__(tmpfile.name, 'bug report', + placeholders) + + def check_edit(self): + with open(self.filename, 'r') as f: + report = f.read().decode('utf-8') + + if self.split_re.match(report) is None: + print ("The %s doesn't start with 'Summary:' and 'Description:' " + "blocks" % self.description) + confirmation_prompt('edit again') + return False + return True + + def get_report(self): + with open(self.filename, 'r') as f: + report = f.read().decode('utf-8') + + m = self.split_re.match(report) + report = (m.group(1), m.group(2)) + os.unlink(self.filename) + return report diff --git a/ubuntutools/rdepends.py b/ubuntutools/rdepends.py new file mode 100644 index 0000000..f9d72de --- /dev/null +++ b/ubuntutools/rdepends.py @@ -0,0 +1,35 @@ +# Copyright (C) 2011, Stefano Rivera +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +import json +import os +import urllib2 + + +class RDependsException(Exception): + pass + + +def query_rdepends(package, release, arch, + server='http://qa.ubuntuwire.org/rdepends'): + """Look up a packages reverse-dependencies on the Ubuntuwire + Reverse- webservice + """ + + url = os.path.join(server, 'v1', release, arch, package) + + try: + return json.load(urllib2.urlopen(url)) + except urllib2.HTTPError, e: + raise RDependsException(e.read().strip()) diff --git a/ubuntutools/requestsync/common.py b/ubuntutools/requestsync/common.py index 37f90cb..37511f5 100644 --- a/ubuntutools/requestsync/common.py +++ b/ubuntutools/requestsync/common.py @@ -22,22 +22,10 @@ import os import sys import urllib2 -import re -import tempfile from debian.changelog import Changelog from ubuntutools import subprocess -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 '\nAbort requested. No sync request filed.' - sys.exit(1) - def get_changelog(srcpkg, distro): ''' Download and return a parsed changelog for srcpackage, from @@ -83,68 +71,3 @@ def get_debian_changelog(srcpkg, version): break new_entries.append(unicode(block)) return u''.join(new_entries) - -def edit_report(subject, body, changes_required = False): - ''' - Ask if the user wants to edit a report (consisting of subject and body) - in sensible-editor. - - If changes_required is True then the file has to be edited before we - can proceed. - - Returns (new_subject, new_body). - ''' - - editing_finished = False - while not editing_finished: - report = 'Summary (one line):\n%s\n\nDescription:\n%s' % (subject, body) - - if not changes_required: - print 'Currently the report looks as follows:\n%s' % report - while True: - val = raw_input_exit_on_ctrlc('Do you want to edit the report ' - '[y/N]? ') - if val.lower() in ('y', 'yes'): - break - elif val.lower() in ('n', 'no', ''): - editing_finished = True - break - else: - print 'Invalid answer.' - - if not editing_finished: - # Create tempfile and remember mtime - report_file = tempfile.NamedTemporaryFile(prefix='requestsync_') - report_file.write(report.encode('utf-8')) - report_file.flush() - mtime_before = os.stat(report_file.name).st_mtime - - # Launch editor - try: - subprocess.check_call(['sensible-editor', report_file.name]) - except subprocess.CalledProcessError, e: - print >> sys.stderr, ('Error calling sensible-editor: %s\n' - 'Aborting.' % e) - sys.exit(1) - - # Check if the tempfile has been changed - if changes_required: - if mtime_before == os.stat(report_file.name).st_mtime: - print ('The report has not been changed, but you have to ' - 'explain why the Ubuntu changes can be dropped.') - raw_input_exit_on_ctrlc('Press [Enter] to retry or ' - '[Control-C] to abort. ') - else: - changes_required = False - - report_file.seek(0) - report = report_file.read().decode('utf-8') - report_file.close() - - # Undecorate report again - (subject, body) = report.split("\nDescription:\n", 1) - # Remove prefix and whitespace from subject - subject = re.sub('^Summary \(one line\):\s*', '', subject, - 1).strip() - - return (subject, body) diff --git a/ubuntutools/requestsync/lp.py b/ubuntutools/requestsync/lp.py index b6067da..b201ef9 100644 --- a/ubuntutools/requestsync/lp.py +++ b/ubuntutools/requestsync/lp.py @@ -26,9 +26,9 @@ import urllib2 from debian.deb822 import Changes from distro_info import DebianDistroInfo -from ubuntutools.requestsync.common import raw_input_exit_on_ctrlc from ubuntutools.lp.lpapicache import (Launchpad, Distribution, PersonTeam, DistributionSourcePackage) +from ubuntutools.question import confirmation_prompt def get_debian_srcpkg(name, release): debian = Distribution('debian') @@ -60,7 +60,7 @@ Your sync request shall require an approval by a member of the appropriate sponsorship team, who shall be subscribed to this bug report. This must be done before it can be processed by a member of the Ubuntu Archive team.''' - raw_input_exit_on_ctrlc('If the above is correct please press [Enter] ') + confirmation_prompt() return need_sponsor @@ -88,8 +88,7 @@ def check_existing_reports(srcpkg): 'Please check the above URL to verify this before ' 'continuing.' % (bug.title, bug.web_link)) - raw_input_exit_on_ctrlc('Press [Enter] to continue or [Ctrl-C] ' - 'to abort. ') + confirmation_prompt() def get_ubuntu_delta_changelog(srcpkg): ''' @@ -133,7 +132,7 @@ def post_bug(srcpkg, subscribe, status, bugtitle, bugtext): print ('The final report is:\nSummary: %s\nDescription:\n%s\n' % (bugtitle, bugtext)) - raw_input_exit_on_ctrlc('Press [Enter] to continue or [Ctrl-C] to abort. ') + confirmation_prompt() if srcpkg: bug_target = DistributionSourcePackage( diff --git a/ubuntutools/requestsync/mail.py b/ubuntutools/requestsync/mail.py index 862feb7..6335ee2 100644 --- a/ubuntutools/requestsync/mail.py +++ b/ubuntutools/requestsync/mail.py @@ -30,8 +30,8 @@ from devscripts.logger import Logger from distro_info import DebianDistroInfo from ubuntutools.archive import rmadison, FakeSPPH -from ubuntutools.requestsync.common import (get_changelog, - raw_input_exit_on_ctrlc) +from ubuntutools.requestsync.common import get_changelog +from ubuntutools.question import confirmation_prompt, YesNoQuestion from ubuntutools import subprocess from ubuntutools.lp.udtexceptions import PackageNotFoundException @@ -71,17 +71,12 @@ def need_sponsorship(name, component, release): component. ''' - while True: - print ("Do you have upload permissions for the '%s' component " - "or the package '%s' in Ubuntu %s?" - % (component, name, release)) - val = raw_input_exit_on_ctrlc("If in doubt answer 'n'. [y/N]? ") - if val.lower() in ('y', 'yes'): - return False - elif val.lower() in ('n', 'no', ''): - return True - else: - print 'Invalid answer' + val = YesNoQuestion().ask("Do you have upload permissions for the " + "'%s' component or the package '%s' in " + "Ubuntu %s?\n" + "If in doubt answer 'n'." + % (component, name, release), 'no') + return val == 'no' def check_existing_reports(srcpkg): ''' @@ -90,7 +85,7 @@ def check_existing_reports(srcpkg): print ('Please check on ' 'https://bugs.launchpad.net/ubuntu/+source/%s/+bugs\n' 'for duplicate sync requests before continuing.' % srcpkg) - raw_input_exit_on_ctrlc('Press [Enter] to continue or [Ctrl-C] to abort. ') + confirmation_prompt() def get_ubuntu_delta_changelog(srcpkg): ''' @@ -166,7 +161,7 @@ Content-Type: text/plain; charset=UTF-8 %s''' % (myemailaddr, to, bugtitle, signed_report) print 'The final report is:\n%s' % mail - raw_input_exit_on_ctrlc('Press [Enter] to continue or [Ctrl-C] to abort. ') + confirmation_prompt() # connect to the server try: