~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to scripts/sync-source.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-03-17 20:41:13 UTC
  • mfrom: (3277.1.4 launchpad-foobar2)
  • Revision ID: pqm@pqm.ubuntu.com-20060317204113-9841a4470db3611b
[r=jamesh] Mainline soyuz

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# -*- coding: utf-8 -*-
 
3
 
 
4
# "Sync" a source package by generating an upload
 
5
# Copyright (C) 2005, 2006  Canonical Software Ltd. <james.troup@canonical.com>
 
6
 
 
7
################################################################################
 
8
 
 
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
 
13
# trick can go away.
 
14
 
 
15
################################################################################
 
16
 
 
17
import commands
 
18
import errno
 
19
import optparse
 
20
import os
 
21
import re
 
22
import shutil
 
23
import stat
 
24
import string
 
25
import sys
 
26
import tempfile
 
27
import time
 
28
import urllib
 
29
 
 
30
import apt_pkg
 
31
 
 
32
import dak_utils
 
33
 
 
34
import _pythonpath
 
35
 
 
36
from zope.component import getUtility
 
37
 
 
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)
 
46
 
 
47
from contrib.glock import GlobalLock
 
48
 
 
49
################################################################################
 
50
 
 
51
reject_message = ""
 
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+)")
 
56
 
 
57
################################################################################
 
58
 
 
59
Blacklisted = None
 
60
Library = None
 
61
Lock = None
 
62
Log = None
 
63
Options = None
 
64
 
 
65
################################################################################
 
66
 
 
67
origins = {
 
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"
 
73
              },
 
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"
 
79
              },
 
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"
 
85
                   },
 
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"
 
91
                },
 
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"
 
97
                },
 
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"
 
103
              },
 
104
 
 
105
####################################
 
106
 
 
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"
 
112
},
 
113
 
 
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"
 
119
    },
 
120
 
 
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"
 
126
    },
 
127
 
 
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"
 
133
    },
 
134
 
 
135
    "arda.lt-p.net-debian": { "name": "arda.LT-P.net-debian",
 
136
        "url": "http://arda.LT-P.net/debian/",
 
137
        "default suite": "",
 
138
        "default component": "",
 
139
        "dsc": "can be unsigned"
 
140
    },
 
141
 
 
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"
 
147
    },
 
148
 
 
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"
 
154
    },
 
155
 
 
156
    "debian.speedblue.org": { "name": "debian.speedblue.org",
 
157
        "url": "http://debian.speedblue.org/",
 
158
        "default suite": "",
 
159
        "default component": "",
 
160
        "dsc": "can be unsigned"
 
161
    },
 
162
 
 
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"
 
168
    },
 
169
 
 
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"
 
175
    },
 
176
 
 
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"
 
182
    },
 
183
 
 
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"
 
189
    },
 
190
 
 
191
    "elonen.iki.fi-code-unofficial-debs": { "name": "elonen.iki.fi-code-unofficial-debs",
 
192
        "url": "http://elonen.iki.fi/code/unofficial-debs/",
 
193
        "default suite": "",
 
194
        "default component": "",
 
195
        "dsc": "can be unsigned"
 
196
    },
 
197
 
 
198
    "erlug.linux.it-%7eda-deb": { "name": "erlug.linux.it-%7Eda-deb",
 
199
        "url": "http://erlug.linux.it/~da/deb/",
 
200
        "default suite": "",
 
201
        "default component": "",
 
202
        "dsc": "can be unsigned"
 
203
    },
 
204
 
 
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"
 
210
    },
 
211
 
 
212
    "instantafs.cbs.mpg.de-instantafs-sid": { "name": "instantafs.cbs.mpg.de-instantafs-sid",
 
213
        "url": "ftp://instantafs.cbs.mpg.de/instantafs/sid/",
 
214
        "default suite": "",
 
215
        "default component": "",
 
216
        "dsc": "can be unsigned"
 
217
    },
 
218
 
 
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"
 
224
    },
 
225
 
 
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"
 
231
    },
 
232
 
 
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"
 
238
    },
 
239
 
 
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"
 
245
    },
 
246
 
 
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"
 
252
    },
 
253
 
 
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"
 
259
    },
 
260
 
 
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"
 
266
    },
 
267
 
 
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"
 
273
    },
 
274
 
 
275
    "opensource.polytechnique.org-debian": { "name": "opensource.polytechnique.org-debian",
 
276
        "url": "http://opensource.polytechnique.org/debian/",
 
277
        "default suite": "",
 
278
        "default component": "",
 
279
        "dsc": "can be unsigned"
 
280
    },
 
281
 
 
282
    "people.debian.org-%7eamaya-debian": { "name": "people.debian.org-%7Eamaya-debian",
 
283
        "url": "http://people.debian.org/~amaya/debian/",
 
284
        "default suite": "",
 
285
        "default component": "",
 
286
        "dsc": "can be unsigned"
 
287
    },
 
288
 
 
289
    "people.debian.org-%7ecostela-debian": { "name": "people.debian.org-%7Ecostela-debian",
 
290
        "url": "http://people.debian.org/~costela/debian/",
 
291
        "default suite": "",
 
292
        "default component": "",
 
293
        "dsc": "can be unsigned"
 
294
    },
 
295
 
 
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"
 
301
    },
 
302
 
 
303
    "people.debian.org-%7etora-deb": { "name": "people.debian.org-%7Etora-deb",
 
304
        "url": "http://people.debian.org/~tora/deb/",
 
305
        "default suite": "",
 
306
        "default component": "",
 
307
        "dsc": "can be unsigned"
 
308
    },
 
309
 
 
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"
 
315
    },
 
316
 
 
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"
 
322
    },
 
323
 
 
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"
 
329
    },
 
330
 
 
331
    "sadleder.de-debian": { "name": "sadleder.de-debian",
 
332
        "url": "http://sadleder.de/debian/",
 
333
        "default suite": "",
 
334
        "default component": "",
 
335
        "dsc": "can be unsigned"
 
336
    },
 
337
 
 
338
    "security.dsi.unimi.it-%7elorenzo-debian": { "name": "security.dsi.unimi.it-%7Elorenzo-debian",
 
339
        "url": "http://security.dsi.unimi.it/~lorenzo/debian/",
 
340
        "default suite": "",
 
341
        "default component": "",
 
342
        "dsc": "can be unsigned"
 
343
    },
 
344
 
 
345
    "silcnet.org-download-client-deb": { "name": "silcnet.org-download-client-deb",
 
346
        "url": "http://silcnet.org/download/client/deb/",
 
347
        "default suite": "",
 
348
        "default component": "",
 
349
        "dsc": "can be unsigned"
 
350
    },
 
351
 
 
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"
 
357
    },
 
358
 
 
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"
 
364
    },
 
365
 
 
366
    "www.cps-project.org-debian-unstable": { "name": "www.cps-project.org-debian-unstable",
 
367
        "url": "http://www.cps-project.org/debian/unstable/",
 
368
        "default suite": "",
 
369
        "default component": "",
 
370
        "dsc": "can be unsigned"
 
371
    },
 
372
 
 
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"
 
378
    },
 
379
 
 
380
    "www.knizefamily.net-russ-software-debian": { "name": "www.knizefamily.net-russ-software-debian",
 
381
        "url": "http://www.knizefamily.net/russ/software/debian/",
 
382
        "default suite": "",
 
383
        "default component": "",
 
384
        "dsc": "can be unsigned"
 
385
    },
 
386
 
 
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"
 
392
    },
 
393
 
 
394
    "www.steve.org.uk-apt": { "name": "www.steve.org.uk-apt",
 
395
        "url": "http://www.steve.org.uk/apt/",
 
396
        "default suite": "",
 
397
        "default component": "",
 
398
        "dsc": "can be unsigned"
 
399
    },
 
400
 
 
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"
 
406
    },
 
407
 
 
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/",
 
410
        "default suite": "",
 
411
        "default component": "",
 
412
        "dsc": "can be unsigned"
 
413
    },
 
414
 
 
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"
 
420
    },
 
421
 
 
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"
 
427
    },
 
428
 
 
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"
 
434
    },
 
435
 
 
436
####################################
 
437
 
 
438
    }
 
439
 
 
440
whoami = "Ubuntu Archive Auto-Sync <katie@jackass.ubuntu.com>"
 
441
 
 
442
uid_mappings = {
 
443
    "auto": whoami,
 
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>",
 
484
    }
 
485
 
 
486
################################################################################
 
487
 
 
488
def md5sum_file(filename):
 
489
    file_handle = open(filename)
 
490
    md5sum = apt_pkg.md5sum(file_handle)
 
491
    file_handle.close()
 
492
    return md5sum
 
493
 
 
494
################################################################################
 
495
 
 
496
def usage (exit_code=0):
 
497
    print """Usage: josie [OPTIONS] [PACKAGE...]
 
498
Sync source from one suite to another.
 
499
 
 
500
General options:
 
501
 
 
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
 
509
 
 
510
Options controlling where to sync packages to:
 
511
 
 
512
  -c, --in-component=CMPNT   limit syncs to packages in COMPONENT
 
513
  -s, --to-suite=SUITE       sync to SUITE
 
514
 
 
515
Options controlling where to sync packages from:
 
516
 
 
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
 
520
"""
 
521
    sys.exit(exit_code)
 
522
 
 
523
################################################################################
 
524
 
 
525
def reject (str, prefix="Rejected: "):
 
526
    global reject_message
 
527
    if str:
 
528
        reject_message += prefix + str + "\n"
 
529
 
 
530
################################################################################
 
531
 
 
532
def sign_changes(changes, dsc):
 
533
    temp_filename = "unsigned-changes"
 
534
    keyid = "0C12BDD7"
 
535
    secret_keyring = "/srv/launchpad.net/dot-gnupg/secring.gpg"
 
536
    pub_keyring = "/srv/launchpad.net/dot-gnupg/pubring.gpg"
 
537
 
 
538
    filehandle = open(temp_filename, 'w')
 
539
    filehandle.write(changes)
 
540
    filehandle.close()
 
541
 
 
542
    output_filename = "%s_%s_source.changes" % (dsc["source"],
 
543
                                                dak_utils.re_no_epoch.sub('', dsc["version"]))
 
544
 
 
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)
 
547
    if (result != 0):
 
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))
 
551
 
 
552
    os.unlink(temp_filename)
 
553
 
 
554
################################################################################
 
555
 
 
556
def generate_changes(dsc, dsc_files, suite, changelog, urgency, closes, section,
 
557
                     priority, description, have_orig_tar_gz, requested_by,
 
558
                     origin):
 
559
    """Generate a .changes as a string"""
 
560
 
 
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?
 
563
 
 
564
    changes = ""
 
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)
 
576
    if description:
 
577
        changes += "Description: \n"
 
578
        changes += " %s\n" % (description)
 
579
    if closes:
 
580
        changes += "Closes: %s\n" % (" ".join(closes))
 
581
    changes += "Changes: \n"
 
582
    changes += changelog
 
583
    changes += "Files: \n"
 
584
    for filename in dsc_files:
 
585
        if filename.endswith(".orig.tar.gz") and have_orig_tar_gz:
 
586
            continue
 
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]
 
592
 
 
593
    return changes
 
594
 
 
595
################################################################################
 
596
 
 
597
# Following two functions are borrowed and (modified) from apt-listchanges
 
598
 
 
599
def urgency_to_numeric(u):
 
600
    urgency_map = { 'low' : 1,
 
601
                    'medium' : 2,
 
602
                    'high' : 3,
 
603
                    'emergency' : 4,
 
604
                    'critical' : 4 }
 
605
 
 
606
    return urgency_map.get(u.lower(), 1)
 
607
 
 
608
def urgency_from_numeric(n):
 
609
    urgency_map = { 1: 'low',
 
610
                    2: 'medium',
 
611
                    3: 'high',
 
612
                    4: 'critical' }
 
613
 
 
614
    return urgency_map.get(n, 'low')
 
615
 
 
616
################################################################################
 
617
 
 
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')
 
622
    changes = ""
 
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)
 
627
        if match:
 
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)
 
633
            else:
 
634
                break
 
635
        changes += line
 
636
 
 
637
    if not is_debian_changelog:
 
638
        dak_utils.fubar("header not found in debian/changelog")
 
639
 
 
640
    closes = []
 
641
    for match in re_closes.finditer(changes):
 
642
        bug_match = re_bug_numbers.findall(match.group(0))
 
643
        closes += map(int, bug_match)
 
644
 
 
645
    l = map(int, closes)
 
646
    l.sort()
 
647
    closes = map(str, l)
 
648
 
 
649
    return (changes, urgency_from_numeric(urgency), closes)
 
650
 
 
651
################################################################################
 
652
 
 
653
def fix_changelog(changelog):
 
654
    """Fix debian/changelog entry or entries to be in .changes compatible format."""
 
655
    fixed = []
 
656
    fixed_idx = -1
 
657
    for line in changelog.split("\n"):
 
658
        if line == "":
 
659
            fixed += [" ."]
 
660
            fixed_idx += 1
 
661
        elif line.startswith(" --"):
 
662
            # Strip any 'blank' lines preceeding the footer
 
663
            while fixed[fixed_idx] == " .":
 
664
                fixed.pop()
 
665
                fixed_idx -= 1
 
666
        else:
 
667
            fixed += [" %s" % (line)]
 
668
            fixed_idx += 1
 
669
    # Strip trailing 'blank' lines
 
670
    while fixed[fixed_idx] == " .":
 
671
        fixed.pop()
 
672
        fixed_idx -= 1
 
673
    fixed_changelog = "\n".join(fixed)
 
674
    fixed_changelog += "\n"
 
675
    return fixed_changelog
 
676
 
 
677
################################################################################
 
678
 
 
679
def parse_control(control_filename):
 
680
    """Parse a debian/control file to extract section, priority and
 
681
description if possible."""
 
682
 
 
683
    source_name = ""
 
684
    source_section = "-"
 
685
    source_priority = "-"
 
686
    source_description = ""
 
687
 
 
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")
 
698
        if source:
 
699
            source_section = section
 
700
            source_priority = priority
 
701
            source_name = source
 
702
        if package and package == source_name:
 
703
            source_description = "%-10s - %-.65s" % (package,
 
704
                                                     description.split("\n")[0])
 
705
    control_filehandle.close()
 
706
 
 
707
    return (source_section, source_priority, source_description)
 
708
 
 
709
################################################################################
 
710
 
 
711
def extract_source(dsc_filename):
 
712
    # Create and move into a temporary directory
 
713
    tmpdir = tempfile.mktemp()
 
714
    os.mkdir(tmpdir)
 
715
    old_cwd = os.getcwd()
 
716
    os.chdir(tmpdir)
 
717
 
 
718
    # Extract the source package
 
719
    cmd = "dpkg-source -sn -x %s" % (dsc_filename)
 
720
    (result, output) = commands.getstatusoutput(cmd)
 
721
    if (result != 0):
 
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))
 
725
 
 
726
    return (old_cwd, tmpdir)
 
727
 
 
728
################################################################################
 
729
 
 
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))
 
735
 
 
736
    # Move back and cleanup the temporary tree
 
737
    os.chdir(old_cwd)
 
738
    try:
 
739
        shutil.rmtree(tmpdir)
 
740
    except OSError, e:
 
741
        if errno.errorcode[e.errno] != 'EACCES':
 
742
            dak_utils.fubar("%s: couldn't remove tmp dir for source tree." % (dsc["source"]))
 
743
 
 
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
 
746
        # and try again.
 
747
        cmd = "chmod -R u+rwx %s" % (tmpdir)
 
748
        result = os.system(cmd)
 
749
        if result != 0:
 
750
            dak_utils.fubar("'%s' failed with result %s." % (cmd, result))
 
751
        shutil.rmtree(tmpdir)
 
752
    except:
 
753
        dak_utils.fubar("%s: couldn't remove tmp dir for source tree." % (dsc["source"]))
 
754
 
 
755
################################################################################
 
756
 
 
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]
 
761
    else:
 
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]
 
766
 
 
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))
 
771
 
 
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))
 
776
 
 
777
            
 
778
            print "I: %s [%s] -> %s_%s [%s]." % (source, source_component,
 
779
                                                 binary, current_version,
 
780
                                                 current_component)
 
781
 
 
782
########################################
 
783
 
 
784
def import_dsc(dsc_filename, suite, previous_version, signing_rules,
 
785
               have_orig_tar_gz, requested_by, origin, current_sources,
 
786
               current_binaries):
 
787
    dsc = dak_utils.parse_changes(dsc_filename, signing_rules)
 
788
    dsc_files = dak_utils.build_file_list(dsc, is_a_dsc=1)\
 
789
    
 
790
    check_dsc(dsc, current_sources, current_binaries)
 
791
 
 
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]
 
797
 
 
798
    (old_cwd, tmpdir) = extract_source(dsc_filename)
 
799
    
 
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)
 
804
 
 
805
    # Ensure the changelog file exists
 
806
    changelog_filename = "%s-%s/debian/changelog" % (dsc["source"], upstr_version)
 
807
 
 
808
    # Parse it and then adapt it for .changes
 
809
    (changelog, urgency, closes) = parse_changelog(changelog_filename, previous_version)
 
810
    changelog = fix_changelog(changelog)
 
811
 
 
812
    # Parse the control file
 
813
    control_filename = "%s-%s/debian/control" % (dsc["source"], upstr_version)
 
814
    (section, priority, description) = parse_control(control_filename)
 
815
 
 
816
    cleanup_source(tmpdir, old_cwd, dsc)
 
817
 
 
818
    changes = generate_changes(dsc, dsc_files, suite, changelog, urgency, closes,
 
819
                               section, priority, description, have_orig_tar_gz,
 
820
                               requested_by, origin)
 
821
 
 
822
    sign_changes(changes, dsc)
 
823
 
 
824
################################################################################
 
825
 
 
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.
 
830
"""
 
831
 
 
832
    S = {}
 
833
    valid_components = dak_utils.split_args(valid_components)
 
834
 
 
835
    spp = distrorelease.getAllSourceReleasesByStatus(
 
836
        dbschema.PackagePublishingStatus.PUBLISHED)
 
837
 
 
838
    for sp in spp:
 
839
        component = sp.component.name
 
840
        version = sp.sourcepackagerelease.version
 
841
        pkg = sp.sourcepackagerelease.sourcepackagename.name
 
842
 
 
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,
 
845
                                                                        component,
 
846
                                                                        valid_components))
 
847
            continue
 
848
        
 
849
        if not S.has_key(pkg):
 
850
            S[pkg] = [version, component]
 
851
        else:
 
852
            if apt_pkg.VersionCompare(S[pkg][0], version) < 0:
 
853
                dak_utils.warn("%s: skipping because %s is < %s" % (pkg, version,
 
854
                                                                S[pkg][0]))
 
855
                S[pkg] = [version, component]
 
856
 
 
857
    return S
 
858
 
 
859
################################################################################
 
860
 
 
861
def read_current_binaries(distrorelease):
 
862
    """Returns a dictionary of binaries packages in 'distrorelease' with their
 
863
       version and component as the attributes.
 
864
"""
 
865
    B = {}
 
866
 
 
867
    for distroarchrelease in distrorelease.architectures:
 
868
        bpp = distroarchrelease.getAllReleasesByStatus(
 
869
            dbschema.PackagePublishingStatus.PUBLISHED)
 
870
 
 
871
        for bp in bpp:
 
872
            component = bp.component.name
 
873
            version = bp.binarypackagerelease.version
 
874
            pkg = bp.binarypackagerelease.binarypackagename.name
 
875
        
 
876
            if not B.has_key(pkg):
 
877
                B[pkg] = [version, component]
 
878
            else:
 
879
                if apt_pkg.VersionCompare(B[pkg][0], version) < 0:
 
880
                    B[pkg] = [version, component]
 
881
    return B
 
882
 
 
883
################################################################################
 
884
 
 
885
def read_Sources(filename, origin):
 
886
    S = {}
 
887
 
 
888
    suite = origin["suite"]
 
889
    component = origin["component"]
 
890
    if suite:
 
891
        suite = "_%s" % (suite)
 
892
    if component:
 
893
        component = "_%s" % (component)
 
894
 
 
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")
 
901
 
 
902
        if S.has_key(pkg) and apt_pkg.VersionCompare(S[pkg]["version"], version) > 0:
 
903
            continue
 
904
        
 
905
        S[pkg] = {}
 
906
        S[pkg]["version"] = version
 
907
 
 
908
        directory = Sources.Section.Find("Directory", "")
 
909
        files = {}
 
910
        for line in Sources.Section.Find("Files").split('\n'):
 
911
            (md5sum, size, filename) = line.strip().split()
 
912
            files[filename] = {}
 
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()
 
918
    return S
 
919
 
 
920
################################################################################
 
921
 
 
922
def add_source(pkg, Sources, previous_version, suite, requested_by, origin,
 
923
               current_sources, current_binaries):
 
924
    print " * Trying to add %s..." % (pkg)
 
925
 
 
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))
 
929
        
 
930
    have_orig_tar_gz = False
 
931
 
 
932
    # Download the source
 
933
    files = Sources[pkg]["files"]
 
934
    for filename in files:
 
935
        clauseTables = ['SourcePackageFilePublishing']
 
936
        query = "SourcePackageFilePublishing.libraryfilealiasfilename = %s" % \
 
937
                sqlvalues(filename)
 
938
        spfp_l = shortlist(SourcePackageFilePublishing.select(
 
939
            query, clauseTables=clauseTables, distinct=True))
 
940
        if spfp_l:
 
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))
 
943
            if len(spfp_l) != 1:
 
944
                dak_utils.fubar("%s (from %s) returns multiple IDs for orig.tar.gz.  Help?" % (filename, pkg))
 
945
            spfp = spfp_l[0]
 
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())
 
951
            output_file.close()
 
952
            continue
 
953
 
 
954
        # Download the file
 
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"])
 
958
            sys.stdout.flush()
 
959
            urllib.urlretrieve(download_f, filename)
 
960
        else:
 
961
            print "  - <%s: cached>" % (filename)
 
962
 
 
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))
 
974
 
 
975
        # Remember the name of the .dsc file
 
976
        if filename.endswith(".dsc"):
 
977
            dsc_filename = os.path.abspath(filename)
 
978
 
 
979
    if origin["dsc"] == "must be signed and valid":
 
980
        signing_rules = 1
 
981
    elif origin["dsc"] == "must be signed":
 
982
        signing_rules = 0
 
983
    else:
 
984
        signing_rules = -1
 
985
    
 
986
    import_dsc(dsc_filename, suite, previous_version, signing_rules,
 
987
               have_orig_tar_gz, requested_by, origin, current_sources,
 
988
               current_binaries)
 
989
 
 
990
    if have_orig_tar_gz:
 
991
        os.unlink(have_orig_tar_gz)
 
992
 
 
993
################################################################################
 
994
 
 
995
def do_diff(Sources, Suite, origin, arguments, current_binaries):
 
996
    stat_us = 0
 
997
    stat_cant_update = 0
 
998
    stat_updated = 0
 
999
    stat_uptodate_modified = 0
 
1000
    stat_uptodate = 0
 
1001
    stat_count = 0
 
1002
    stat_broken = 0
 
1003
    stat_blacklisted = 0
 
1004
 
 
1005
    if Options.all:
 
1006
        packages = Suite.keys()
 
1007
    else:
 
1008
        packages = arguments
 
1009
    packages.sort()
 
1010
    for pkg in packages:
 
1011
        stat_count += 1
 
1012
        dest_version = Suite.get(pkg, ["0", ""])[0]
 
1013
 
 
1014
        if not Sources.has_key(pkg):
 
1015
            if not Options.all:
 
1016
                dak_utils.fubar("%s: not found" % (pkg))
 
1017
            else:
 
1018
                print "[Ubuntu Specific] %s_%s" % (pkg, dest_version)
 
1019
                stat_us += 1
 
1020
                continue
 
1021
 
 
1022
        if Blacklisted.has_key(pkg):
 
1023
            print "[BLACKLISTED] %s_%s" % (pkg, dest_version)
 
1024
            stat_blacklisted += 1
 
1025
            continue
 
1026
        
 
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)
 
1030
            stat_broken += 1
 
1031
            continue
 
1032
 
 
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)
 
1039
            else:
 
1040
                stat_updated += 1
 
1041
                print "[Updating] %s (%s [Ubuntu] < %s [%s])" \
 
1042
                      % (pkg, dest_version, source_version, origin["name"])
 
1043
                if Options.action:
 
1044
                    add_source(pkg, Sources, Suite.get(pkg, ["0", ""])[0], Options.tosuite.name,
 
1045
                               Options.requestor, origin, Suite, current_binaries)
 
1046
        else:
 
1047
            if dest_version.find("ubuntu") != -1:
 
1048
                stat_uptodate_modified += 1;    
 
1049
                if Options.verbose:
 
1050
                    print "[Nothing to update (Modified)] %s_%s (vs %s)" \
 
1051
                          % (pkg, dest_version, source_version)
 
1052
            else:
 
1053
                stat_uptodate += 1
 
1054
                if Options.verbose:
 
1055
                    print "[Nothing to update] %s (%s [ubuntu] >= %s [debian])" \
 
1056
                          % (pkg, dest_version, source_version)
 
1057
 
 
1058
    if Options.all:
 
1059
        print
 
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)
 
1076
 
 
1077
 
 
1078
################################################################################
 
1079
 
 
1080
def options_setup():
 
1081
    global Log, Options
 
1082
 
 
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")
 
1096
 
 
1097
    # Options controlling where to sync packages to:
 
1098
 
 
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)")
 
1105
 
 
1106
    # Options controlling where to sync packages from:
 
1107
 
 
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)")
 
1114
 
 
1115
 
 
1116
    (Options, arguments) = parser.parse_args()
 
1117
 
 
1118
    # Defaults
 
1119
    if not Options.todistro:
 
1120
        Options.todistro = "ubuntu"
 
1121
 
 
1122
    if not Options.tosuite:
 
1123
        Options.tosuite = "breezy"
 
1124
 
 
1125
    if not Options.fromdistro:
 
1126
        Options.fromdistro = "debian"
 
1127
 
 
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"]
 
1133
 
 
1134
    # Sanity checks on options
 
1135
 
 
1136
    if not Options.all and not arguments:
 
1137
        dak_utils.fubar("Need -a/--all or at least one package name as an argument.")
 
1138
        
 
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.")
 
1142
        else:
 
1143
            Options.requestor = whoami
 
1144
    else:
 
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]
 
1149
 
 
1150
    return arguments
 
1151
 
 
1152
################################################################################
 
1153
 
 
1154
def objectize_options():
 
1155
    # Convert 'todistro', 'tosuite' and 'incomponent' to objects rather than strings
 
1156
 
 
1157
    Options.todistro = getUtility(IDistributionSet)[Options.todistro]
 
1158
 
 
1159
    if not Options.tosuite:
 
1160
        Options.tosuite = Options.todistro.currentrelease.name
 
1161
    Options.tosuite = Options.todistro.getRelease(Options.tosuite)
 
1162
 
 
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]
 
1170
 
 
1171
########################################
 
1172
 
 
1173
def init():
 
1174
    global Blacklisted, Library, Lock, Log
 
1175
 
 
1176
    apt_pkg.init()
 
1177
 
 
1178
    arguments = options_setup()
 
1179
 
 
1180
    Log = logger(Options, "sync-source")
 
1181
 
 
1182
    Log.debug("Acquiring lock")
 
1183
    Lock = GlobalLock('/var/lock/launchpad-sync-source.lock')
 
1184
    Lock.acquire(blocking=True)
 
1185
 
 
1186
    Log.debug("Initialising connection.")
 
1187
    initZopeless(dbuser="ro")
 
1188
 
 
1189
    execute_zcml_for_scripts()
 
1190
 
 
1191
    Library = LibrarianClient()
 
1192
 
 
1193
    objectize_options()
 
1194
 
 
1195
    # Blacklist
 
1196
    Blacklisted = {}
 
1197
    # XXX
 
1198
    blacklist_file = open("/srv/launchpad.net/dak/sync-blacklist.txt")
 
1199
    for line in blacklist_file:
 
1200
        line = line.strip()
 
1201
        if not line or line[0] == "#":
 
1202
            continue
 
1203
        Blacklisted[line] = ""
 
1204
    blacklist_file.close()
 
1205
 
 
1206
 
 
1207
    return arguments
 
1208
 
 
1209
def main():
 
1210
    arguments = init()
 
1211
 
 
1212
    origin = origins[Options.fromdistro]
 
1213
    origin["suite"] = Options.fromsuite
 
1214
    origin["component"] = Options.fromcomponent
 
1215
 
 
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)
 
1220
 
 
1221
################################################################################
 
1222
 
 
1223
if __name__ == '__main__':
 
1224
    main()