2
# -*- coding: utf-8 -*-
4
# "Sync" a source package by generating an upload
5
# Copyright (C) 2005, 2006 Canonical Software Ltd. <james.troup@canonical.com>
7
################################################################################
9
# This is a straight port of the original dak 'josie' tool to soyuz.
10
# Long term once soyuz is monitoring other archives regularly, syncing
11
# will become a matter of simply 'publishing' source from Debian
12
# unstable wherever) into Ubuntu dapper and the whole fake upload
15
################################################################################
36
from zope.component import getUtility
38
from canonical.database.sqlbase import sqlvalues
39
from canonical.launchpad.scripts import (execute_zcml_for_scripts,
40
logger, logger_options)
41
from canonical.launchpad.helpers import shortlist
42
from canonical.launchpad.database.publishing import SourcePackageFilePublishing
43
from canonical.librarian.client import LibrarianClient
44
from canonical.launchpad.interfaces import IDistributionSet
45
from canonical.lp import (dbschema, initZopeless)
47
from contrib.glock import GlobalLock
49
################################################################################
52
re_strip_revision = re.compile(r"-([^-]+)$")
53
re_changelog_header = re.compile(r"^\S+ \((?P<version>.*)\) .*;.*urgency=(?P<urgency>\w+).*")
54
re_closes = re.compile(r"closes:\s*(?:bug)?\#?\s?\d+(?:,\s*(?:bug)?\#?\s?\d+)*", re.I)
55
re_bug_numbers = re.compile(r"\#?\s?(\d+)")
57
################################################################################
65
################################################################################
68
"debian": { "name": "Debian",
69
"url": "http://ftp.debian.org/debian/",
70
"default suite": "unstable",
71
"default component": "main",
72
"dsc": "must be signed and valid"
74
"incoming": { "name": "Debian",
75
"url": "http://incoming.debian.org/",
76
"default suite": "incoming",
77
"default component": "main",
78
"dsc": "must be signed and valid"
80
"blackdown": { "name": "Blackdown",
81
"url": "http://ftp.gwdg.de/pub/languages/java/linux/debian/",
82
"default suite": "unstable",
83
"default component": "non-free",
84
"dsc": "must be signed and valid"
86
"marillat": { "name": "Marillat",
87
"url": "ftp://ftp.nerim.net/debian-marillat/",
88
"default suite": "unstable",
89
"default component": "main",
90
"dsc": "can be unsigned"
92
"mythtv": { "name": "MythTV",
93
"url": "http://dijkstra.csh.rit.edu/~mdz/debian/",
94
"default suite": "unstable",
95
"default component": "mythtv",
96
"dsc": "can be unsigned"
98
"xfce": { "name": "XFCE",
99
"url": "http://www.os-works.com/debian/",
100
"default suite": "testing",
101
"default component": "main",
102
"dsc": "must be signed and valid"
105
####################################
107
"apt.logreport.org-pub-debian": { "name": "apt.logreport.org-pub-debian",
108
"url": "http://apt.logreport.org/pub/debian/",
109
"default suite": "local",
110
"default component": "contrib",
111
"dsc": "can be unsigned"
114
"apt.pgpackages.org-debian": { "name": "apt.pgpackages.org-debian",
115
"url": "http://apt.pgpackages.org/debian/",
116
"default suite": "sid",
117
"default component": "main",
118
"dsc": "can be unsigned"
121
"apt.pgpackages.org-debian": { "name": "apt.pgpackages.org-debian",
122
"url": "http://apt.pgpackages.org/debian/",
123
"default suite": "sid",
124
"default component": "contrib",
125
"dsc": "can be unsigned"
128
"apt.pgpackages.org-debian": { "name": "apt.pgpackages.org-debian",
129
"url": "http://apt.pgpackages.org/debian/",
130
"default suite": "sid",
131
"default component": "non-free",
132
"dsc": "can be unsigned"
135
"arda.lt-p.net-debian": { "name": "arda.LT-P.net-debian",
136
"url": "http://arda.LT-P.net/debian/",
138
"default component": "",
139
"dsc": "can be unsigned"
142
"colo.khms.westfalen.de-pakete": { "name": "colo.khms.westfalen.de-Pakete",
143
"url": "http://colo.khms.westfalen.de/Pakete/",
144
"default suite": "unstable",
145
"default component": "",
146
"dsc": "can be unsigned"
149
"debian.hinterhof.net": { "name": "debian.hinterhof.net",
150
"url": "http://debian.hinterhof.net/",
151
"default suite": "unstable",
152
"default component": "",
153
"dsc": "can be unsigned"
156
"debian.speedblue.org": { "name": "debian.speedblue.org",
157
"url": "http://debian.speedblue.org/",
159
"default component": "",
160
"dsc": "can be unsigned"
163
"debian.wgdd.de-debian": { "name": "debian.wgdd.de-debian",
164
"url": "http://debian.wgdd.de/debian/",
165
"default suite": "unstable",
166
"default component": "main",
167
"dsc": "can be unsigned"
170
"debian.wgdd.de-debian": { "name": "debian.wgdd.de-debian",
171
"url": "http://debian.wgdd.de/debian/",
172
"default suite": "unstable",
173
"default component": "contrib",
174
"dsc": "can be unsigned"
177
"debian.wgdd.de-debian": { "name": "debian.wgdd.de-debian",
178
"url": "http://debian.wgdd.de/debian/",
179
"default suite": "unstable",
180
"default component": "non-free",
181
"dsc": "can be unsigned"
184
"dl.gna.org-kazehakase": { "name": "dl.gna.org-kazehakase",
185
"url": "http://dl.gna.org/kazehakase/",
186
"default suite": "debian",
187
"default component": "",
188
"dsc": "can be unsigned"
191
"elonen.iki.fi-code-unofficial-debs": { "name": "elonen.iki.fi-code-unofficial-debs",
192
"url": "http://elonen.iki.fi/code/unofficial-debs/",
194
"default component": "",
195
"dsc": "can be unsigned"
198
"erlug.linux.it-%7eda-deb": { "name": "erlug.linux.it-%7Eda-deb",
199
"url": "http://erlug.linux.it/~da/deb/",
201
"default component": "",
202
"dsc": "can be unsigned"
205
"ftp.arege.jp-debian-arege": { "name": "ftp.arege.jp-debian-arege",
206
"url": "http://ftp.arege.jp/debian-arege/",
207
"default suite": "sid",
208
"default component": "ALL",
209
"dsc": "can be unsigned"
212
"instantafs.cbs.mpg.de-instantafs-sid": { "name": "instantafs.cbs.mpg.de-instantafs-sid",
213
"url": "ftp://instantafs.cbs.mpg.de/instantafs/sid/",
215
"default component": "",
216
"dsc": "can be unsigned"
219
"jeroen.coekaerts.be-debian": { "name": "jeroen.coekaerts.be-debian",
220
"url": "http://jeroen.coekaerts.be/debian/",
221
"default suite": "unstable",
222
"default component": "main",
223
"dsc": "can be unsigned"
226
"jeroen.coekaerts.be-debian": { "name": "jeroen.coekaerts.be-debian",
227
"url": "http://jeroen.coekaerts.be/debian/",
228
"default suite": "unstable",
229
"default component": "contrib",
230
"dsc": "can be unsigned"
233
"jeroen.coekaerts.be-debian": { "name": "jeroen.coekaerts.be-debian",
234
"url": "http://jeroen.coekaerts.be/debian/",
235
"default suite": "unstable",
236
"default component": "non-free",
237
"dsc": "can be unsigned"
240
"laylward.com-debian": { "name": "laylward.com-debian",
241
"url": "http://laylward.com/debian/",
242
"default suite": "unstable",
243
"default component": "",
244
"dsc": "can be unsigned"
247
"mherrn.de-debian": { "name": "mherrn.de-debian",
248
"url": "http://mherrn.de/debian/",
249
"default suite": "sid",
250
"default component": "hatari",
251
"dsc": "can be unsigned"
254
"mherrn.de-debian": { "name": "mherrn.de-debian",
255
"url": "http://mherrn.de/debian/",
256
"default suite": "sid",
257
"default component": "paranoia",
258
"dsc": "can be unsigned"
261
"mherrn.de-debian": { "name": "mherrn.de-debian",
262
"url": "http://mherrn.de/debian/",
263
"default suite": "sid",
264
"default component": "exim",
265
"dsc": "can be unsigned"
268
"mulk.dyndns.org-apt": { "name": "mulk.dyndns.org-apt",
269
"url": "http://mulk.dyndns.org/apt/",
270
"default suite": "unstable",
271
"default component": "main",
272
"dsc": "can be unsigned"
275
"opensource.polytechnique.org-debian": { "name": "opensource.polytechnique.org-debian",
276
"url": "http://opensource.polytechnique.org/debian/",
278
"default component": "",
279
"dsc": "can be unsigned"
282
"people.debian.org-%7eamaya-debian": { "name": "people.debian.org-%7Eamaya-debian",
283
"url": "http://people.debian.org/~amaya/debian/",
285
"default component": "",
286
"dsc": "can be unsigned"
289
"people.debian.org-%7ecostela-debian": { "name": "people.debian.org-%7Ecostela-debian",
290
"url": "http://people.debian.org/~costela/debian/",
292
"default component": "",
293
"dsc": "can be unsigned"
296
"people.debian.org-%7ercardenes": { "name": "people.debian.org-%7Ercardenes",
297
"url": "http://people.debian.org/~rcardenes/",
298
"default suite": "sid",
299
"default component": "main",
300
"dsc": "can be unsigned"
303
"people.debian.org-%7etora-deb": { "name": "people.debian.org-%7Etora-deb",
304
"url": "http://people.debian.org/~tora/deb/",
306
"default component": "",
307
"dsc": "can be unsigned"
310
"piem.homeip.net-%7epiem-debian": { "name": "piem.homeip.net-%7Epiem-debian",
311
"url": "http://piem.homeip.net/~piem/debian/",
312
"default suite": "source",
313
"default component": "",
314
"dsc": "can be unsigned"
317
"progn.org-debian": { "name": "progn.org-debian",
318
"url": "ftp://progn.org/debian/",
319
"default suite": "unstable",
320
"default component": "main",
321
"dsc": "can be unsigned"
324
"ressukka.net-%7eressu-deb": { "name": "ressukka.net-%7Eressu-deb",
325
"url": "http://ressukka.net/~ressu/deb/",
326
"default suite": "unstable",
327
"default component": "",
328
"dsc": "can be unsigned"
331
"sadleder.de-debian": { "name": "sadleder.de-debian",
332
"url": "http://sadleder.de/debian/",
334
"default component": "",
335
"dsc": "can be unsigned"
338
"security.dsi.unimi.it-%7elorenzo-debian": { "name": "security.dsi.unimi.it-%7Elorenzo-debian",
339
"url": "http://security.dsi.unimi.it/~lorenzo/debian/",
341
"default component": "",
342
"dsc": "can be unsigned"
345
"silcnet.org-download-client-deb": { "name": "silcnet.org-download-client-deb",
346
"url": "http://silcnet.org/download/client/deb/",
348
"default component": "",
349
"dsc": "can be unsigned"
352
"src.braincells.com-debian": { "name": "src.braincells.com-debian",
353
"url": "http://src.braincells.com/debian/",
354
"default suite": "sid",
355
"default component": "",
356
"dsc": "can be unsigned"
359
"themind.altervista.org-debian": { "name": "themind.altervista.org-debian",
360
"url": "http://themind.altervista.org/debian/",
361
"default suite": "unstable",
362
"default component": "main",
363
"dsc": "can be unsigned"
366
"www.cps-project.org-debian-unstable": { "name": "www.cps-project.org-debian-unstable",
367
"url": "http://www.cps-project.org/debian/unstable/",
369
"default component": "",
370
"dsc": "can be unsigned"
373
"www.gwhere.org-download-debian": { "name": "www.gwhere.org-download-debian",
374
"url": "http://www.gwhere.org/download/debian/",
375
"default suite": "unstable",
376
"default component": "main",
377
"dsc": "can be unsigned"
380
"www.knizefamily.net-russ-software-debian": { "name": "www.knizefamily.net-russ-software-debian",
381
"url": "http://www.knizefamily.net/russ/software/debian/",
383
"default component": "",
384
"dsc": "can be unsigned"
387
"www.litux.org-debian": { "name": "www.litux.org-debian",
388
"url": "http://www.litux.org/debian/",
389
"default suite": "unstable",
390
"default component": "",
391
"dsc": "can be unsigned"
394
"www.steve.org.uk-apt": { "name": "www.steve.org.uk-apt",
395
"url": "http://www.steve.org.uk/apt/",
397
"default component": "",
398
"dsc": "can be unsigned"
401
"www.stuff.demon.co.uk-apt": { "name": "www.stuff.demon.co.uk-apt",
402
"url": "http://www.stuff.demon.co.uk/apt/",
403
"default suite": "source",
404
"default component": "",
405
"dsc": "can be unsigned"
408
"www.thomas-alfeld.de-frank-download-debian": { "name": "www.thomas-alfeld.de-frank-download-debian",
409
"url": "http://www.thomas-alfeld.de/frank/download/debian/",
411
"default component": "",
412
"dsc": "can be unsigned"
415
"www.zero-based.org-debian": { "name": "www.zero-based.org-debian",
416
"url": "http://www.zero-based.org/debian/",
417
"default suite": "packagessource",
418
"default component": "",
419
"dsc": "can be unsigned"
422
"kitenet.net-%7ejoey-debian": { "name": "kitenet.net-%7Ejoey-debian",
423
"url": "http://kitenet.net/~joey/debian/",
424
"default suite": "unstable",
425
"default component": "",
426
"dsc": "can be unsigned"
429
"www.roughtrade.net-debian": { "name": "www.roughtrade.net-debian",
430
"url": "http://www.roughtrade.net/debian/",
431
"default suite": "sid",
432
"default component": "main",
433
"dsc": "can be unsigned"
436
####################################
440
whoami = "Ubuntu Archive Auto-Sync <katie@jackass.ubuntu.com>"
444
"lathiat": "Trent Lloyd <lathiat@bur.st>",
445
"nafallo": "Christian Bjälevik <nafallo@magicalforest.se>",
446
"riddell": "Jonathan Riddell <jonathan.riddell@ubuntu.com>",
447
"corey": "Corey Burger <corey.burger@gmail.com>",
448
"droge": "Sebastian Dröge <mail@slomosnail.de>",
449
"mjg59": "Matthew Garrett <mjg59@srcf.ucam.org>",
450
"mbreit": "Moritz Breit <mail@mobr.de>",
451
"sh": "Stephan Hermann <sh@sourcecode.de>",
452
"herve": "Herv� Cauwelier <hcauwelier@oursours.net>",
453
"chmj": "Charles Majola <charles@ubuntu.com>",
454
"siretart": "Reinhard Tartler <siretart@tauware.de>",
455
"daniels": "Daniel Stone <daniel.stone@ubuntu.com>",
456
"fabbione": "Fabio Massimo Di Nitto <fabbione@ubuntu.com>",
457
"infinity": "Adam Conrad <adconrad@0c3.net>",
458
"azeem": "Michael Banck <mbanck@debian.org>",
459
"dag": "Dagfinn Ilmari Mannsaker <ilmari@ilmari.org>",
460
"adam": "Adam Israel <adam@battleaxe.net>",
461
"diamond": "Stephen Shirley <diamond@nonado.net>",
462
"reinhard": "Reinhard Tartler <siretart@tauware.de>",
463
"crimsun": "Daniel T Chen <crimsun@fungus.sh.nu>",
464
"jani": "Jani Monoses <jani@email.ro>",
465
"keybuk": "Scott James Remnant <scott@ubuntu.com>",
466
"mvo": "Michael Vogt <michael.vogt@ubuntu.com>",
467
"thibaut": "Thibaut Varene <varenet@debian.org>",
468
"ajmitch": "Andrew Mitchell <ajmitch@gnu.org>",
469
"amu": "Andreas Mueller <amu@ubuntu.com>",
470
"jdub": "Jeff Waugh <jeff.waugh@ubuntu.com>",
471
"thom": "Thom May <thom@ubuntu.com>",
472
"tseng": "Brandon Hale <brandon@smarterits.com>",
473
"smurfix": "Matthias Urlichs <smurf@debian.org>",
474
"dholbach": "Daniel Holbach <dh@mailempfang.de>",
475
"kamion": "Colin Watson <cjwatson@ubuntu.com>",
476
"tollef": "Tollef Fog Heen <tfheen@canonical.com>",
477
"ogra": "Oliver Grawert <oliver.grawert@ubuntu.com>",
478
"treenaks": "Martijn van de Streek <martijn@foodfight.org>",
479
"pitti": "Martin Pitt <martin.pitt@ubuntu.com>",
480
"seb128": "Sebastien Bacher <seb128@ubuntu.com>",
481
"mdz": "Matt Zimmerman <mdz@ubuntu.com>",
482
"doko": "Matthias Klose <doko@ubuntu.com>",
483
"lamont": "LaMont Jones <lamont@ubuntu.com>",
486
################################################################################
488
def md5sum_file(filename):
489
file_handle = open(filename)
490
md5sum = apt_pkg.md5sum(file_handle)
494
################################################################################
496
def usage (exit_code=0):
497
print """Usage: josie [OPTIONS] [PACKAGE...]
498
Sync source from one suite to another.
502
-a, --all sync all packages
503
-b, --requested-by=UID who the sync was requested by
504
-f, --force force sync over the top of ubuntu changes
505
-h, --help show this help and exit
506
-k, --keybuk write out list of modified+out-of-date pkgs
507
-n, --no-action don't do anything
508
-v, --verbose be more verbose
510
Options controlling where to sync packages to:
512
-c, --in-component=CMPNT limit syncs to packages in COMPONENT
513
-s, --to-suite=SUITE sync to SUITE
515
Options controlling where to sync packages from:
517
-C, --from-component=CMPNT sync from COMPONENT in DISTRO [default: main]
518
-D, --from-distro=DISTRO sync from DISTRO [default: Debian]
519
-S, --from-suite=SUITE sync from SUITE in DISTRO
523
################################################################################
525
def reject (str, prefix="Rejected: "):
526
global reject_message
528
reject_message += prefix + str + "\n"
530
################################################################################
532
def sign_changes(changes, dsc):
533
temp_filename = "unsigned-changes"
535
secret_keyring = "/srv/launchpad.net/dot-gnupg/secring.gpg"
536
pub_keyring = "/srv/launchpad.net/dot-gnupg/pubring.gpg"
538
filehandle = open(temp_filename, 'w')
539
filehandle.write(changes)
542
output_filename = "%s_%s_source.changes" % (dsc["source"],
543
dak_utils.re_no_epoch.sub('', dsc["version"]))
545
cmd = "gpg --no-options --batch --no-tty --secret-keyring=%s --keyring=%s --default-key=0x%s --output=%s --clearsign %s" % (secret_keyring, pub_keyring, keyid, output_filename, temp_filename)
546
(result, output) = commands.getstatusoutput(cmd)
548
print " * command was '%s'" % (cmd)
549
print dak_utils.prefix_multi_line_string(output, " [gpg output:] "), ""
550
dak_utils.fubar("%s: signing .changes failed [return code: %s]." % (output_filename, result))
552
os.unlink(temp_filename)
554
################################################################################
556
def generate_changes(dsc, dsc_files, suite, changelog, urgency, closes, section,
557
priority, description, have_orig_tar_gz, requested_by,
559
"""Generate a .changes as a string"""
561
# [xxx] Changed-By can be extracted from most-recent changelog footer, but do we care?
562
# [xxx] 'Closes' but could be gotten from changelog, but we don't use them?
565
changes += "Origin: %s/%s\n" % (origin["name"], origin["suite"])
566
changes += "Format: 1.7\n"
567
changes += "Date: %s\n" % (time.strftime("%a, %d %b %Y %H:%M:%S %z"))
568
changes += "Source: %s\n" % (dsc["source"])
569
changes += "Binary: %s\n" % (dsc["binary"])
570
changes += "Architecture: source\n"
571
changes += "Version: %s\n"% (dsc["version"])
572
changes += "Distribution: %s\n" % (suite)
573
changes += "Urgency: %s\n" % (urgency)
574
changes += "Maintainer: %s\n" % (dsc["maintainer"])
575
changes += "Changed-By: %s\n" % (requested_by)
577
changes += "Description: \n"
578
changes += " %s\n" % (description)
580
changes += "Closes: %s\n" % (" ".join(closes))
581
changes += "Changes: \n"
583
changes += "Files: \n"
584
for filename in dsc_files:
585
if filename.endswith(".orig.tar.gz") and have_orig_tar_gz:
587
changes += " %s %s %s %s %s\n" % (dsc_files[filename]["md5sum"],
588
dsc_files[filename]["size"],
589
section, priority, filename)
590
# Strip trailing newline
591
changes = changes[:-1]
595
################################################################################
597
# Following two functions are borrowed and (modified) from apt-listchanges
599
def urgency_to_numeric(u):
600
urgency_map = { 'low' : 1,
606
return urgency_map.get(u.lower(), 1)
608
def urgency_from_numeric(n):
609
urgency_map = { 1: 'low',
614
return urgency_map.get(n, 'low')
616
################################################################################
618
def parse_changelog(changelog_filename, previous_version):
619
if not os.path.exists(changelog_filename):
620
dak_utils.fubar("debian/changelog not found in extracted source.")
621
urgency = urgency_to_numeric('low')
623
is_debian_changelog = 0
624
changelog_file = open(changelog_filename)
625
for line in changelog_file.readlines():
626
match = re_changelog_header.match(line)
628
is_debian_changelog = 1
629
if previous_version is None:
630
previous_version = "9999:9999"
631
elif apt_pkg.VersionCompare(match.group('version'), previous_version) > 0:
632
urgency = max(urgency_to_numeric(match.group('urgency')),urgency)
637
if not is_debian_changelog:
638
dak_utils.fubar("header not found in debian/changelog")
641
for match in re_closes.finditer(changes):
642
bug_match = re_bug_numbers.findall(match.group(0))
643
closes += map(int, bug_match)
649
return (changes, urgency_from_numeric(urgency), closes)
651
################################################################################
653
def fix_changelog(changelog):
654
"""Fix debian/changelog entry or entries to be in .changes compatible format."""
657
for line in changelog.split("\n"):
661
elif line.startswith(" --"):
662
# Strip any 'blank' lines preceeding the footer
663
while fixed[fixed_idx] == " .":
667
fixed += [" %s" % (line)]
669
# Strip trailing 'blank' lines
670
while fixed[fixed_idx] == " .":
673
fixed_changelog = "\n".join(fixed)
674
fixed_changelog += "\n"
675
return fixed_changelog
677
################################################################################
679
def parse_control(control_filename):
680
"""Parse a debian/control file to extract section, priority and
681
description if possible."""
685
source_priority = "-"
686
source_description = ""
688
if not os.path.exists(control_filename):
689
dak_utils.fubar("debian/control not found in extracted source.")
690
control_filehandle = open(control_filename)
691
Control = apt_pkg.ParseTagFile(control_filehandle)
692
while Control.Step():
693
source = Control.Section.Find("Source")
694
package = Control.Section.Find("Package")
695
section = Control.Section.Find("Section")
696
priority = Control.Section.Find("Priority")
697
description = Control.Section.Find("Description")
699
source_section = section
700
source_priority = priority
702
if package and package == source_name:
703
source_description = "%-10s - %-.65s" % (package,
704
description.split("\n")[0])
705
control_filehandle.close()
707
return (source_section, source_priority, source_description)
709
################################################################################
711
def extract_source(dsc_filename):
712
# Create and move into a temporary directory
713
tmpdir = tempfile.mktemp()
715
old_cwd = os.getcwd()
718
# Extract the source package
719
cmd = "dpkg-source -sn -x %s" % (dsc_filename)
720
(result, output) = commands.getstatusoutput(cmd)
722
print " * command was '%s'" % (cmd)
723
print dak_utils.prefix_multi_line_string(output, " [dpkg-source output:] "), ""
724
dak_utils.fubar("'dpkg-source -x' failed for %s [return code: %s]." % (dsc_filename, result))
726
return (old_cwd, tmpdir)
728
################################################################################
730
def cleanup_source(tmpdir, old_cwd, dsc):
731
# Sanity check that'll probably break if people set $TMPDIR, but
732
# WTH, shutil.rmtree scares me
733
if not tmpdir.startswith("/tmp/"):
734
dak_utils.fubar("%s: tmpdir doesn't start with /tmp" % (tmpdir))
736
# Move back and cleanup the temporary tree
739
shutil.rmtree(tmpdir)
741
if errno.errorcode[e.errno] != 'EACCES':
742
dak_utils.fubar("%s: couldn't remove tmp dir for source tree." % (dsc["source"]))
744
reject("%s: source tree could not be cleanly removed." % (dsc["source"]))
745
# We probably have u-r or u-w directories so chmod everything
747
cmd = "chmod -R u+rwx %s" % (tmpdir)
748
result = os.system(cmd)
750
dak_utils.fubar("'%s' failed with result %s." % (cmd, result))
751
shutil.rmtree(tmpdir)
753
dak_utils.fubar("%s: couldn't remove tmp dir for source tree." % (dsc["source"]))
755
################################################################################
757
def check_dsc(dsc, current_sources, current_binaries):
758
source = dsc["source"]
759
if current_sources.has_key(source):
760
source_component = current_sources[source][1]
762
source_component = "universe"
763
for binary in map(string.strip, dsc["binary"].split(',')):
764
if current_binaries.has_key(binary):
765
(current_version, current_component) = current_binaries[binary]
767
# Check that a non-main source package is not trying to
768
# override a main binary package
769
if current_component == "main" and source_component != "main":
770
dak_utils.fubar("%s is in main but it's source (%s) is not." % (binary, source))
772
# Check that a source package is not trying to override an
773
# ubuntu-modified binary package
774
if not Options.force and current_binaries[binary][0].find("ubuntu") != -1:
775
dak_utils.fubar("%s is trying to override %s_%s without -f/--force." % (source, binary, current_version))
778
print "I: %s [%s] -> %s_%s [%s]." % (source, source_component,
779
binary, current_version,
782
########################################
784
def import_dsc(dsc_filename, suite, previous_version, signing_rules,
785
have_orig_tar_gz, requested_by, origin, current_sources,
787
dsc = dak_utils.parse_changes(dsc_filename, signing_rules)
788
dsc_files = dak_utils.build_file_list(dsc, is_a_dsc=1)\
790
check_dsc(dsc, current_sources, current_binaries)
792
# Add the .dsc itself to dsc_files so it's listed in the Files: field
793
dsc_base_filename = os.path.basename(dsc_filename)
794
dsc_files.setdefault(dsc_base_filename, {})
795
dsc_files[dsc_base_filename]["md5sum"] = md5sum_file(dsc_filename)
796
dsc_files[dsc_base_filename]["size"] = os.stat(dsc_filename)[stat.ST_SIZE]
798
(old_cwd, tmpdir) = extract_source(dsc_filename)
800
# Get the upstream version
801
upstr_version = dak_utils.re_no_epoch.sub('', dsc["version"])
802
if re_strip_revision.search(upstr_version):
803
upstr_version = re_strip_revision.sub('', upstr_version)
805
# Ensure the changelog file exists
806
changelog_filename = "%s-%s/debian/changelog" % (dsc["source"], upstr_version)
808
# Parse it and then adapt it for .changes
809
(changelog, urgency, closes) = parse_changelog(changelog_filename, previous_version)
810
changelog = fix_changelog(changelog)
812
# Parse the control file
813
control_filename = "%s-%s/debian/control" % (dsc["source"], upstr_version)
814
(section, priority, description) = parse_control(control_filename)
816
cleanup_source(tmpdir, old_cwd, dsc)
818
changes = generate_changes(dsc, dsc_files, suite, changelog, urgency, closes,
819
section, priority, description, have_orig_tar_gz,
820
requested_by, origin)
822
sign_changes(changes, dsc)
824
################################################################################
826
def read_current_source(distrorelease, valid_components=""):
827
"""Returns a dictionary of packages in 'suite' with their version as the
828
attribute. 'component' is an optional list of (comma or whitespace
829
separated) components to restrict the search to.
833
valid_components = dak_utils.split_args(valid_components)
835
spp = distrorelease.getAllSourceReleasesByStatus(
836
dbschema.PackagePublishingStatus.PUBLISHED)
839
component = sp.component.name
840
version = sp.sourcepackagerelease.version
841
pkg = sp.sourcepackagerelease.sourcepackagename.name
843
if valid_components and sp.component.name not in valid_components:
844
dak_utils.warn("%s/%s: skipping because %s is not in %s" % (pkg, version,
849
if not S.has_key(pkg):
850
S[pkg] = [version, component]
852
if apt_pkg.VersionCompare(S[pkg][0], version) < 0:
853
dak_utils.warn("%s: skipping because %s is < %s" % (pkg, version,
855
S[pkg] = [version, component]
859
################################################################################
861
def read_current_binaries(distrorelease):
862
"""Returns a dictionary of binaries packages in 'distrorelease' with their
863
version and component as the attributes.
867
for distroarchrelease in distrorelease.architectures:
868
bpp = distroarchrelease.getAllReleasesByStatus(
869
dbschema.PackagePublishingStatus.PUBLISHED)
872
component = bp.component.name
873
version = bp.binarypackagerelease.version
874
pkg = bp.binarypackagerelease.binarypackagename.name
876
if not B.has_key(pkg):
877
B[pkg] = [version, component]
879
if apt_pkg.VersionCompare(B[pkg][0], version) < 0:
880
B[pkg] = [version, component]
883
################################################################################
885
def read_Sources(filename, origin):
888
suite = origin["suite"]
889
component = origin["component"]
891
suite = "_%s" % (suite)
893
component = "_%s" % (component)
895
filename = "%s%s%s_%s" % (origin["name"], suite, component, filename)
896
sources_filehandle = open(filename)
897
Sources = apt_pkg.ParseTagFile(sources_filehandle)
898
while Sources.Step():
899
pkg = Sources.Section.Find("Package")
900
version = Sources.Section.Find("Version")
902
if S.has_key(pkg) and apt_pkg.VersionCompare(S[pkg]["version"], version) > 0:
906
S[pkg]["version"] = version
908
directory = Sources.Section.Find("Directory", "")
910
for line in Sources.Section.Find("Files").split('\n'):
911
(md5sum, size, filename) = line.strip().split()
913
files[filename]["md5sum"] = md5sum
914
files[filename]["size"] = int(size)
915
files[filename]["remote filename"] = os.path.join(directory, filename)
916
S[pkg]["files"] = files
917
sources_filehandle.close()
920
################################################################################
922
def add_source(pkg, Sources, previous_version, suite, requested_by, origin,
923
current_sources, current_binaries):
924
print " * Trying to add %s..." % (pkg)
926
# Check it's in the Sources file
927
if not Sources.has_key(pkg):
928
dak_utils.fubar("%s doesn't exist in the Sources file." % (pkg))
930
have_orig_tar_gz = False
932
# Download the source
933
files = Sources[pkg]["files"]
934
for filename in files:
935
clauseTables = ['SourcePackageFilePublishing']
936
query = "SourcePackageFilePublishing.libraryfilealiasfilename = %s" % \
938
spfp_l = shortlist(SourcePackageFilePublishing.select(
939
query, clauseTables=clauseTables, distinct=True))
941
if not filename.endswith("orig.tar.gz"):
942
dak_utils.fubar("%s (from %s) is in the DB but isn't an orig.tar.gz. Help?" % (filename, pkg))
944
dak_utils.fubar("%s (from %s) returns multiple IDs for orig.tar.gz. Help?" % (filename, pkg))
946
have_orig_tar_gz = filename
947
print " - <%s: already in distro - downloading from librarian>" % (filename)
948
output_file = open(filename, 'w')
949
librarian_input = Library.getFileByAlias(spfp.libraryfilealias)
950
output_file.write(librarian_input.read())
955
download_f = "%s%s" % (origin["url"], files[filename]["remote filename"])
956
if not os.path.exists(filename):
957
print " - <%s: downloading from %s>" % (filename, origin["url"])
959
urllib.urlretrieve(download_f, filename)
961
print " - <%s: cached>" % (filename)
963
# Check md5sum and size match Source
964
actual_md5sum = md5sum_file(filename)
965
expected_md5sum = files[filename]["md5sum"]
966
if actual_md5sum != expected_md5sum:
967
dak_utils.fubar("%s: md5sum check failed (%s [actual] vs. %s [expected])." \
968
% (filename, actual_md5sum, expected_md5sum))
969
actual_size = os.stat(filename)[stat.ST_SIZE]
970
expected_size = int(files[filename]["size"])
971
if actual_size != expected_size:
972
dak_utils.fubar("%s: size mismatch (%s [actual] vs. %s [expected])." \
973
% (filename, actual_size, expected_size))
975
# Remember the name of the .dsc file
976
if filename.endswith(".dsc"):
977
dsc_filename = os.path.abspath(filename)
979
if origin["dsc"] == "must be signed and valid":
981
elif origin["dsc"] == "must be signed":
986
import_dsc(dsc_filename, suite, previous_version, signing_rules,
987
have_orig_tar_gz, requested_by, origin, current_sources,
991
os.unlink(have_orig_tar_gz)
993
################################################################################
995
def do_diff(Sources, Suite, origin, arguments, current_binaries):
999
stat_uptodate_modified = 0
1003
stat_blacklisted = 0
1006
packages = Suite.keys()
1008
packages = arguments
1010
for pkg in packages:
1012
dest_version = Suite.get(pkg, ["0", ""])[0]
1014
if not Sources.has_key(pkg):
1016
dak_utils.fubar("%s: not found" % (pkg))
1018
print "[Ubuntu Specific] %s_%s" % (pkg, dest_version)
1022
if Blacklisted.has_key(pkg):
1023
print "[BLACKLISTED] %s_%s" % (pkg, dest_version)
1024
stat_blacklisted += 1
1027
if pkg in [ "mozilla-thunderbird", "ncmpc", "ocrad", "gnuradio-core",
1028
"gtk-smooth-engine", "libant1.6-java", "glade", "devilspie" ]:
1029
print "[BROKEN] %s_%s" % (pkg, dest_version)
1033
source_version = Sources[pkg]["version"]
1034
if apt_pkg.VersionCompare(dest_version, source_version) < 0:
1035
if not Options.force and dest_version.find("ubuntu") != -1:
1036
stat_cant_update += 1
1037
print "[NOT Updating - Modified] %s_%s (vs %s)" \
1038
% (pkg, dest_version, source_version)
1041
print "[Updating] %s (%s [Ubuntu] < %s [%s])" \
1042
% (pkg, dest_version, source_version, origin["name"])
1044
add_source(pkg, Sources, Suite.get(pkg, ["0", ""])[0], Options.tosuite.name,
1045
Options.requestor, origin, Suite, current_binaries)
1047
if dest_version.find("ubuntu") != -1:
1048
stat_uptodate_modified += 1;
1050
print "[Nothing to update (Modified)] %s_%s (vs %s)" \
1051
% (pkg, dest_version, source_version)
1055
print "[Nothing to update] %s (%s [ubuntu] >= %s [debian])" \
1056
% (pkg, dest_version, source_version)
1060
print "Out-of-date BUT modified: %3d (%.2f%%)" \
1061
% (stat_cant_update, (float(stat_cant_update)/stat_count)*100)
1062
print "Updated: %3d (%.2f%%)" \
1063
% (stat_updated, (float(stat_updated)/stat_count)*100)
1064
print "Ubuntu Specific: %3d (%.2f%%)" \
1065
% (stat_us, (float(stat_us)/stat_count)*100)
1066
print "Up-to-date [Modified]: %3d (%.2f%%)" \
1067
% (stat_uptodate_modified, (float(stat_uptodate_modified)/stat_count)*100)
1068
print "Up-to-date: %3d (%.2f%%)" \
1069
% (stat_uptodate, (float(stat_uptodate)/stat_count)*100)
1070
print "Blacklisted: %3d (%.2f%%)" \
1071
% (stat_blacklisted, (float(stat_blacklisted)/stat_count)*100)
1072
print "Broken: %3d (%.2f%%)" \
1073
% (stat_broken, (float(stat_broken)/stat_count)*100)
1074
print " -----------"
1075
print "Total: %s" % (stat_count)
1078
################################################################################
1080
def options_setup():
1083
parser = optparse.OptionParser()
1084
logger_options(parser)
1085
parser.add_option("-a", "--all", dest="all",
1086
default=False, action="store_true",
1087
help="sync all packages")
1088
parser.add_option("-b", "--requested-by", dest="requestor",
1089
help="who the sync was requested by")
1090
parser.add_option("-f", "--force", dest="force",
1091
default=False, action="store_true",
1092
help="force sync over the top of Ubuntu changes")
1093
parser.add_option("-n", "--noaction", dest="action",
1094
default=True, action="store_false",
1095
help="don't do anything")
1097
# Options controlling where to sync packages to:
1099
parser.add_option("-c", "--in-component", dest="incomponent",
1100
help="limit syncs to packages in COMPONENT")
1101
parser.add_option("-d", "--to-distro", dest="todistro",
1102
help="sync to DISTRO")
1103
parser.add_option("-s", "--to-suite", dest="tosuite",
1104
help="sync to SUITE (aka distrorelease)")
1106
# Options controlling where to sync packages from:
1108
parser.add_option("-C", "--from-component", dest="fromcomponent",
1109
help="sync from COMPONENT")
1110
parser.add_option("-D", "--from-distro", dest="fromdistro",
1111
help="sync from DISTRO")
1112
parser.add_option("-S", "--from-suite", dest="fromsuite",
1113
help="sync from SUITE (aka distrorelease)")
1116
(Options, arguments) = parser.parse_args()
1119
if not Options.todistro:
1120
Options.todistro = "ubuntu"
1122
if not Options.tosuite:
1123
Options.tosuite = "breezy"
1125
if not Options.fromdistro:
1126
Options.fromdistro = "debian"
1128
distro = Options.fromdistro.lower()
1129
if not Options.fromcomponent:
1130
Options.fromcomponent = origins[distro]["default component"]
1131
if not Options.fromsuite:
1132
Options.fromsuite = origins[distro]["default suite"]
1134
# Sanity checks on options
1136
if not Options.all and not arguments:
1137
dak_utils.fubar("Need -a/--all or at least one package name as an argument.")
1139
if not Options.requestor:
1140
if Options.action and not Options.all:
1141
dak_utils.fubar("Need -a/--all or an argument for -b/--requested-by.")
1143
Options.requestor = whoami
1145
if not uid_mappings.has_key(Options.requestor):
1146
dak_utils.fubar("Unknown uid '%s', please update uid_mappings dictionary."
1147
% (Options.requestor))
1148
Options.requestor = uid_mappings[Options.requestor]
1152
################################################################################
1154
def objectize_options():
1155
# Convert 'todistro', 'tosuite' and 'incomponent' to objects rather than strings
1157
Options.todistro = getUtility(IDistributionSet)[Options.todistro]
1159
if not Options.tosuite:
1160
Options.tosuite = Options.todistro.currentrelease.name
1161
Options.tosuite = Options.todistro.getRelease(Options.tosuite)
1163
valid_components = dict([(c.name,c) for c in Options.tosuite.components])
1164
if Options.incomponent:
1165
if Options.incomponent not in valid_components:
1166
dak_utils.fubar("%s is not a valid component for %s/%s."
1167
% (Options.incomponent, Options.todistro.name,
1168
Options.tosuite.name))
1169
Options.incomponent = valid_components[Options.incomponent]
1171
########################################
1174
global Blacklisted, Library, Lock, Log
1178
arguments = options_setup()
1180
Log = logger(Options, "sync-source")
1182
Log.debug("Acquiring lock")
1183
Lock = GlobalLock('/var/lock/launchpad-sync-source.lock')
1184
Lock.acquire(blocking=True)
1186
Log.debug("Initialising connection.")
1187
initZopeless(dbuser="ro")
1189
execute_zcml_for_scripts()
1191
Library = LibrarianClient()
1198
blacklist_file = open("/srv/launchpad.net/dak/sync-blacklist.txt")
1199
for line in blacklist_file:
1201
if not line or line[0] == "#":
1203
Blacklisted[line] = ""
1204
blacklist_file.close()
1212
origin = origins[Options.fromdistro]
1213
origin["suite"] = Options.fromsuite
1214
origin["component"] = Options.fromcomponent
1216
Sources = read_Sources("Sources", origin)
1217
Suite = read_current_source(Options.tosuite, Options.incomponent)
1218
current_binaries = read_current_binaries(Options.tosuite)
1219
do_diff(Sources, Suite, origin, arguments, current_binaries)
1221
################################################################################
1223
if __name__ == '__main__':