~ubuntu-branches/debian/sid/ubuntu-dev-tools/sid

« back to all changes in this revision

Viewing changes to syncpackage

  • Committer: Package Import Robot
  • Author(s): Benjamin Drung, Colin Watson, Jelmer Vernooij, Stefano Rivera, Julian Taylor, Benjamin Drung
  • Date: 2011-09-06 14:31:31 UTC
  • Revision ID: package-import@ubuntu.com-20110906143131-pukbbowpabmw4w42
Tags: 0.129
[ Colin Watson ]
* syncpackage: Convert to new LP API, with --no-lp available for the old
  style of operation.
* syncpackage: Require -f/--force option to overwrite Ubuntu changes.

[ Jelmer Vernooij ]
* Remove several tools not specific to Ubuntu that have been migrated to
  lptools (LP: #708886):
 - get-branches (renamed to lp-get-branches)
 - grab-attachments (renamed to lp-grab-attachments)
 - lp-project-upload
 - lp-list-bugs
 - lp-set-dup
 - lp-shell

[ Stefano Rivera ]
* syncpackage: Show changes to be synced when performing native syncs.
* syncpackage: Check the sync blacklist.
* syncpackage: Support --bug (extra bugs to be closed by the sync) with
  native syncs. (Bugs are closed one individually, via the API, post-sync)
* dgetlp, submittodebian, 404main: Use unicode strings for literal strings
  containing non-ASCII characters (LP: #836661)
* Recommend pbuilder | aptitude for get-build-deps, and exit with an error
  if neither are installed (LP: #799368)
* get-build-deps: Tell aptitude not to follow Recommends (LP: #817500)
* doc/requestsync.1: Document the -C option (LP: #833408)
* ubuntutools.archive: Don't write .dsc files until we pull the entire
  source package, just hold it in memory. Avoids littering the current
  directory (LP: #838361)
* Run harvest as part of sponsor-patch (LP: #833699)

[ Julian Taylor ]
* requestsync: omit dups when checking for duplicate requests (LP: #842217)

[ Benjamin Drung ]
* sponsor-patch: Default to not upload the package.
* requestsync: Do not crash on user abort (Closes: #637168).

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
#
21
21
# ##################################################################
22
22
 
23
 
import debian.deb822
24
 
import debian.debian_support
 
23
import codecs
25
24
import optparse
26
25
import os
 
26
import re
27
27
import shutil
28
28
import sys
 
29
import textwrap
 
30
import urllib
29
31
 
 
32
import debian.debian_support
30
33
from devscripts.logger import Logger
 
34
from lazr.restfulclient.errors import HTTPError
31
35
 
32
36
from ubuntutools.archive import (DebianSourcePackage, UbuntuSourcePackage,
33
37
                                 DownloadError)
34
38
from ubuntutools.config import UDTConfig, ubu_email
 
39
from ubuntutools.requestsync.common import getDebianChangelog
35
40
from ubuntutools.requestsync.mail import (getDebianSrcPkg
36
41
                                          as requestsync_mail_getDebianSrcPkg)
37
42
from ubuntutools.requestsync.lp import getDebianSrcPkg, getUbuntuSrcPkg
38
43
from ubuntutools.lp import udtexceptions
39
 
from ubuntutools.lp.lpapicache import Launchpad
 
44
from ubuntutools.lp.lpapicache import (Distribution, Launchpad,
 
45
                                       SourcePackagePublishingHistory)
 
46
from ubuntutools.misc import split_release_pocket
 
47
from ubuntutools.question import YesNoQuestion
40
48
from ubuntutools import subprocess
41
49
 
42
50
 
95
103
 
96
104
    changes = [l for l in changes.split("\n") if l.strip() != ""]
97
105
    # Remove duplicates
98
 
    bugs = set(bugs)
 
106
    bugs = set(str(bug) for bug in bugs)
99
107
 
100
108
    for i in xrange(len(changes)):
101
109
        if changes[i].startswith("Launchpad-Bugs-Fixed:"):
110
118
    return "\n".join(changes + [""])
111
119
 
112
120
def sync_dsc(src_pkg, debian_dist, release, name, email, bugs, ubuntu_mirror,
113
 
             keyid=None):
 
121
             keyid=None, simulate=False, force=False):
114
122
    uploader = name + " <" + email + ">"
115
123
 
116
124
    src_pkg.pull_dsc()
128
136
        ubuntu_ver = Version('~')
129
137
        ubu_pkg = None
130
138
        need_orig = True
131
 
        Logger.info('%s does not exist in Ubuntu.', name)
 
139
        Logger.normal('%s does not exist in Ubuntu.', name)
132
140
 
133
141
    Logger.debug('Source %s: current version %s, new version %s',
134
142
                 src_pkg.source, ubuntu_ver, new_ver)
136
144
 
137
145
    cur_ver = ubuntu_ver.get_related_debian_version()
138
146
    if ubuntu_ver.is_modified_in_ubuntu():
 
147
        if not force:
 
148
            Logger.error('--force is required to discard Ubuntu changes.')
 
149
            sys.exit(1)
 
150
 
139
151
        Logger.warn('Overwriting modified Ubuntu version %s, '
140
152
                    'setting current version to %s',
141
153
                    ubuntu_ver.full_version, cur_ver.full_version)
 
154
    if simulate:
 
155
        return
142
156
 
143
157
    try:
144
158
        src_pkg.pull()
248
262
    """Download the specified source package.
249
263
    dist, version, component, mirror can all be None.
250
264
    """
 
265
    if mirror is None:
 
266
        mirrors = []
 
267
    else:
 
268
        mirrors = [mirror]
 
269
 
251
270
    if package.endswith('.dsc'):
252
 
        return DebianSourcePackage(dscfile=package, mirrors=[mirror])
 
271
        return DebianSourcePackage(dscfile=package, mirrors=mirrors)
253
272
 
254
273
    if dist is None:
255
274
        dist = "unstable"
290
309
    assert component in ('main', 'contrib', 'non-free')
291
310
 
292
311
    return DebianSourcePackage(package, version.full_version, component,
293
 
                               mirrors=[mirror])
 
312
                               mirrors=mirrors)
 
313
 
 
314
def copy(src_pkg, release, bugs, simulate=False, force=False):
 
315
    """Copy a source package from Debian to Ubuntu using the Launchpad API."""
 
316
    ubuntu = Distribution('ubuntu')
 
317
    debian_archive = Distribution('debian').getArchive()
 
318
    ubuntu_archive = ubuntu.getArchive()
 
319
    if release is None:
 
320
        ubuntu_series = ubuntu.getDevelopmentSeries().name
 
321
        ubuntu_pocket = 'Release'
 
322
    else:
 
323
        ubuntu_series, ubuntu_pocket = split_release_pocket(release)
 
324
 
 
325
    # Ensure that the provided Debian version actually exists.
 
326
    try:
 
327
        debian_spph = SourcePackagePublishingHistory(
 
328
                debian_archive.getPublishedSources(
 
329
                    source_name=src_pkg.source,
 
330
                    version=src_pkg.version.full_version,
 
331
                    exact_match=True)[0]
 
332
                )
 
333
    except IndexError:
 
334
        Logger.error('Debian version %s does not exist!', src_pkg.version)
 
335
        sys.exit(1)
 
336
 
 
337
    try:
 
338
        ubuntu_spph = getUbuntuSrcPkg(src_pkg.source,
 
339
                                      ubuntu_series, ubuntu_pocket)
 
340
        ubuntu_pkg = UbuntuSourcePackage(src_pkg.source,
 
341
                                         ubuntu_spph.getVersion(),
 
342
                                         ubuntu_spph.getComponent(),
 
343
                                         mirrors=[])
 
344
 
 
345
        Logger.normal('Source %s -> %s/%s: current version %s, new version %s',
 
346
                      src_pkg.source, ubuntu_series, ubuntu_pocket,
 
347
                      ubuntu_pkg.version, src_pkg.version)
 
348
 
 
349
        ubuntu_version = Version(ubuntu_pkg.version.full_version)
 
350
        base_version = ubuntu_version.get_related_debian_version()
 
351
        if not force and ubuntu_version.is_modified_in_ubuntu():
 
352
            Logger.error('--force is required to discard Ubuntu changes.')
 
353
            sys.exit(1)
 
354
 
 
355
        # Check whether a fakesync would be required.
 
356
        src_pkg.pull_dsc()
 
357
        ubuntu_pkg.pull_dsc()
 
358
        if not src_pkg.dsc.compare_dsc(ubuntu_pkg.dsc):
 
359
            Logger.error('The checksums of the Debian and Ubuntu packages '
 
360
                         'mismatch. A fake sync using --no-lp is required.')
 
361
            sys.exit(1)
 
362
    except udtexceptions.PackageNotFoundException:
 
363
        base_version = Version('~')
 
364
        Logger.normal('Source %s -> %s/%s: not in Ubuntu, new version %s',
 
365
                      src_pkg.source, ubuntu_series, ubuntu_pocket,
 
366
                      src_pkg.version)
 
367
 
 
368
    changes = getDebianChangelog(debian_spph, base_version).strip()
 
369
    if changes:
 
370
        Logger.normal("New changes:\n%s", changes)
 
371
 
 
372
    if simulate:
 
373
        return
 
374
 
 
375
    answer = YesNoQuestion().ask("Sync this package", "no")
 
376
    if answer != "yes":
 
377
        return
 
378
 
 
379
    try:
 
380
        ubuntu_archive.copyPackage(
 
381
            source_name=src_pkg.source,
 
382
            version=src_pkg.version.full_version,
 
383
            from_archive=debian_archive,
 
384
            to_series=ubuntu_series,
 
385
            to_pocket=ubuntu_pocket,
 
386
            include_binaries=False)
 
387
    except HTTPError, error:
 
388
        Logger.error("HTTP Error %s: %s", error.response.status,
 
389
                     error.response.reason)
 
390
        Logger.error(error.content)
 
391
        sys.exit(1)
 
392
 
 
393
    Logger.normal('Request succeeded; you should get an e-mail once it is '
 
394
                  'processed.')
 
395
    bugs = sorted(set(bugs))
 
396
    if bugs:
 
397
        Logger.normal("Launchpad bugs to be closed: %s",
 
398
                      ', '.join(str(bug) for bug in bugs))
 
399
        Logger.normal('Please wait for the sync to be successful before '
 
400
                      'closing bugs.')
 
401
        answer = YesNoQuestion().ask("Close bugs", "yes")
 
402
        if answer == "yes":
 
403
            close_bugs(bugs, src_pkg.source, src_pkg.version.full_version,
 
404
                       changes)
 
405
 
 
406
def is_blacklisted(query):
 
407
    """"Determine if package "query" is in the sync blacklist
 
408
    Returns tuple of (blacklisted, comments)
 
409
    blacklisted is one of False, 'CURRENT', 'ALWAYS'
 
410
    """
 
411
    series = Launchpad.distributions['ubuntu'].current_series
 
412
    lp_comments = series.getDifferenceComments(source_package_name=query)
 
413
    blacklisted = False
 
414
    comments = [c.body_text + u' -- ' + c.comment_author.name
 
415
                for c in lp_comments]
 
416
 
 
417
    diff = series.getDifferencesTo(source_package_name_filter=query)[0]
 
418
    if diff.status == 'Blacklisted current version':
 
419
        blacklisted = 'CURRENT'
 
420
    if diff.status == 'Blacklisted always':
 
421
        blacklisted = 'ALWAYS'
 
422
 
 
423
    # Old blacklist:
 
424
    url = 'http://people.canonical.com/~ubuntu-archive/sync-blacklist.txt'
 
425
    with codecs.EncodedFile(urllib.urlopen(url), 'UTF-8') as f:
 
426
        applicable_comments = []
 
427
        for line in f:
 
428
            if not line.strip():
 
429
                applicable_comments = []
 
430
                continue
 
431
            m = re.match(r'^\s*([a-z0-9.+-]+)?\s*(?:#\s*(.+)?)?$', line)
 
432
            source, comment = m.groups()
 
433
            if source and query == source:
 
434
                if comment:
 
435
                    applicable_comments.append(comment)
 
436
                comments += applicable_comments
 
437
                blacklisted = 'ALWAYS'
 
438
                break
 
439
            elif comment:
 
440
                applicable_comments.append(comment)
 
441
 
 
442
    return (blacklisted, comments)
 
443
 
 
444
def close_bugs(bugs, package, version, changes):
 
445
    """Close the correct task on all bugs, with changes"""
 
446
    ubuntu = Launchpad.distributions['ubuntu']
 
447
    message = ("This bug was fixed in the package %s - %s"
 
448
               "\n\n---------------\n%s" % (
 
449
               package, version, changes))
 
450
    for bug in bugs:
 
451
        bug = Launchpad.bugs[bug]
 
452
        if bug.duplicate_of is not None:
 
453
            bug = bug.duplicate_of
 
454
        for task in bug.bug_tasks:
 
455
            target = task.target
 
456
            if target == ubuntu or (target.name == package and
 
457
               getattr(target, 'distribution', None) == ubuntu):
 
458
                if task.status != 'Fix Released':
 
459
                    Logger.normal("Closed bug %s", task.web_link)
 
460
                    task.status = 'Fix Released'
 
461
                    task.lp_save()
 
462
                    bug.newMessage(content=message)
 
463
                break
 
464
        else:
 
465
            Logger.error(u"Cannot find any tasks on LP: #%i to close.", bug.id)
294
466
 
295
467
def main():
296
468
    usage = "%prog [options] <.dsc URL/path or package name>"
312
484
    parser.add_option("-v", "--verbose",
313
485
                      dest="verbose", action="store_true", default=False,
314
486
                      help="Display more progress information.")
 
487
    parser.add_option("--no-lp",
 
488
                      dest="lp", action="store_false", default=True,
 
489
                      help="Construct sync locally rather than letting "
 
490
                           "Launchpad copy the package directly (not "
 
491
                           "recommended).")
 
492
    parser.add_option('-l', '--lpinstance', metavar='INSTANCE',
 
493
                      dest='lpinstance', default=None,
 
494
                      help='Launchpad instance to connect to '
 
495
                           '(default: production).')
315
496
    parser.add_option("-n", "--uploader-name",
316
497
                      dest="uploader_name", default=None,
317
498
                      help="Use UPLOADER_NAME as the name of the maintainer "
330
511
                      dest="bugs", action="append", default=list(),
331
512
                      help="Mark Launchpad bug BUG as being fixed by this "
332
513
                           "upload.")
 
514
    parser.add_option("-f", "--force",
 
515
                      dest="force", action="store_true", default=False,
 
516
                      help="Force sync over the top of Ubuntu changes "
 
517
                           "or a fake-sync when blacklisted.")
333
518
    parser.add_option('-D', '--debian-mirror', metavar='DEBIAN_MIRROR',
334
519
                      dest='debian_mirror',
335
520
                      help='Preferred Debian mirror '
343
528
    parser.add_option('--no-conf',
344
529
                      dest='no_conf', default=False, action='store_true',
345
530
                      help="Don't read config files or environment variables.")
 
531
    parser.add_option('--simulate',
 
532
                      dest='simulate', default=False, action='store_true',
 
533
                      help="Show what would be done, but don't actually do "
 
534
                           "it.")
346
535
 
347
536
    (options, args) = parser.parse_args()
348
537
 
352
541
        parser.error('Multiple .dsc URLs/paths or package names specified: '
353
542
                     + ', '.join(args))
354
543
 
355
 
    invalid_bug_numbers = [bug for bug in options.bugs if not bug.isdigit()]
356
 
    if len(invalid_bug_numbers) > 0:
357
 
        parser.error('Invalid bug number(s) specified: '
358
 
                     + ', '.join(invalid_bug_numbers))
 
544
    try:
 
545
        options.bugs = map(int, options.bugs)
 
546
    except TypeError:
 
547
        parser.error('Invalid bug number(s) specified.')
359
548
 
360
549
    if options.component not in (None, "main", "contrib", "non-free"):
361
550
        parser.error('%s is not a valid Debian component. '
362
551
                     'It should be one of main, contrib, or non-free.'
363
552
                     % options.component)
364
553
 
 
554
    if options.lp and options.uploader_name:
 
555
        parser.error('Uploader name can only be overridden using --no-lp.')
 
556
    if options.lp and options.uploader_email:
 
557
        parser.error('Uploader email address can only be overridden using '
 
558
                     '--no-lp.')
 
559
    # --key, --dont-sign, --debian-mirror, and --ubuntu-mirror are just
 
560
    # ignored with options.lp, and do not require warnings.
 
561
 
365
562
    Logger.verbose = options.verbose
366
563
    config = UDTConfig(options.no_conf)
367
564
    if options.debian_mirror is None:
373
570
    if options.uploader_email is None:
374
571
        options.uploader_email = ubu_email(export=False)[1]
375
572
 
376
 
    Launchpad.login_anonymously()
 
573
    if options.lp:
 
574
        if args[0].endswith('.dsc'):
 
575
            parser.error('.dsc files can only be synced using --no-lp.')
 
576
 
 
577
    if options.lpinstance is None:
 
578
        options.lpinstance = config.get_value('LPINSTANCE')
 
579
    try:
 
580
        Launchpad.login(service=options.lpinstance, api_version='devel')
 
581
    except IOError:
 
582
        sys.exit(1)
 
583
 
377
584
    if options.release is None:
378
 
        options.release = Launchpad.distributions["ubuntu"].current_series.name
379
 
 
380
 
    os.environ['DEB_VENDOR'] = 'Ubuntu'
 
585
        ubuntu = Launchpad.distributions["ubuntu"]
 
586
        options.release = ubuntu.current_series.name
381
587
 
382
588
    src_pkg = fetch_source_pkg(args[0], options.dist, options.debversion,
383
589
                               options.component, options.release,
384
590
                               options.debian_mirror)
385
591
 
386
 
    sync_dsc(src_pkg, options.dist, options.release, options.uploader_name,
387
 
             options.uploader_email, options.bugs, options.ubuntu_mirror,
388
 
             options.keyid)
 
592
    blacklisted, comments = is_blacklisted(src_pkg.source)
 
593
    if blacklisted:
 
594
        fail = False
 
595
        messages = []
 
596
 
 
597
        if blacklisted == 'CURRENT':
 
598
            if options.force:
 
599
                messages += ["Forcing override of temporary blacklising."]
 
600
            else:
 
601
                fail = True
 
602
                messages += ["The blacklisting only applies to the current "
 
603
                             "versions in Debian and Ubuntu.",
 
604
                             "--force is required to override the blacklist."]
 
605
        else:
 
606
            if options.force and not options.lp:
 
607
                messages += ["Forcing fake-sync, overriding blacklist."]
 
608
            else:
 
609
                fail = True
 
610
                messages += ["--force and --no-lp are required to override the "
 
611
                             "blacklist, if this package needs a fakesync."]
 
612
            messages += ["If you think this package shouldn't be blacklisted, "
 
613
                         "please file a bug explaining your reasoning and "
 
614
                         "subscribe ~ubuntu-archive."]
 
615
 
 
616
        if fail:
 
617
            Logger.error(u"Source package %s is blacklisted.", src_pkg.source)
 
618
        else:
 
619
            Logger.normal(u"Source package %s is blacklisted.", src_pkg.source)
 
620
 
 
621
        if comments:
 
622
            Logger.normal("Blacklist Comments:")
 
623
            for comment in comments:
 
624
                for line in textwrap.wrap(comment):
 
625
                    Logger.normal(u"  " + line)
 
626
        if messages:
 
627
            for message in messages:
 
628
                for line in textwrap.wrap(message):
 
629
                    Logger.normal(line)
 
630
        if fail:
 
631
            sys.exit(1)
 
632
 
 
633
    if options.lp:
 
634
        copy(src_pkg, options.release, options.bugs, options.simulate,
 
635
             options.force)
 
636
    else:
 
637
        os.environ['DEB_VENDOR'] = 'Ubuntu'
 
638
        sync_dsc(src_pkg, options.dist, options.release, options.uploader_name,
 
639
                 options.uploader_email, options.bugs, options.ubuntu_mirror,
 
640
                 options.keyid, options.simulate, options.force)
389
641
 
390
642
if __name__ == "__main__":
391
643
    main()