58
65
seed_binary = defaultdict(set)
69
def __init__(self, id, status, title):
75
s = "MIR: #%d (%s)" % (self.id, self.status)
76
# no need to repeat the standard title
77
if not self.title.startswith("[MIR]"):
78
s += " %s" % self.title
82
h = 'MIR: <a href="https://launchpad.net/bugs/%d">#%d</a> (%s)' % (
83
self.id, self.id, escape(self.status))
84
# no need to repeat the standard title
85
if not self.title.startswith("[MIR]"):
86
h += " %s" % escape(self.title)
61
90
def ensure_tempdir():
245
return " [Reverse-%s: %s]\n" % (category, ", ".join(keys))
274
return ["[Reverse-%s: %s]" % (category, ", ".join(keys))]
249
output += do_category(depend, 'Depends')
250
output += do_category(recommend, 'Recommends')
251
output += do_category(build_depend, 'Build-Depends')
278
output.extend(do_category(depend, 'Depends'))
279
output.extend(do_category(recommend, 'Recommends'))
280
output.extend(do_category(build_depend, 'Build-Depends'))
377
405
if b in archive_binary and archive_binary[b][1] == component]
380
def print_section(header, body):
408
def print_section(options, header, body,
409
source_and_binary=False, binary_only=False):
382
411
print(" %s" % header)
383
412
print(" %s" % ("-" * len(header)))
416
if source_and_binary:
419
print(" o %s: %s" % (source, " ".join(binaries)))
421
binaries = " ".join(line[:-1])
423
indent_right = 75 - len(binaries) - len(source) - 2
424
print(" o %s%s{%s}" % (binaries, " " * indent_right, source))
426
print(" o %s" % line)
427
for line in entry[1:]:
431
if len(body[-1]) == 1:
436
if body and options.html_output is not None:
437
def print_html(*args, **kwargs):
438
print(*args, file=options.html_output, **kwargs)
440
def source_link(source):
442
'<a href="https://launchpad.net/ubuntu/+source/%s">%s</a>' % (
443
escape(source, quote=True), escape(source)))
445
print_html("<h2>%s</h2>" % escape(header))
446
print_html("<table>")
449
if source_and_binary:
451
binaries = " ".join(line[1:])
453
'<tr><th colspan="2">%s: %s' % (
454
source_link(source), escape(binaries)))
456
binaries = " ".join(line[:-1])
458
print_html('<tr><th>%s</th>' % escape(binaries), end="")
460
"<th><small>%s</small></th></tr>" % source_link(source))
462
print_html('<tr><th colspan="2">%s</th></tr>' % escape(line))
463
for line in entry[1:]:
464
if isinstance(line, MIRLink):
469
'<tr><td colspan="2"><span class="note">%s'
470
'</span></td></tr>' % line)
471
print_html("</table>")
391
474
def do_output(options,
392
475
orig_source_add, orig_source_remove, binary_add, binary_remove,
477
if options.html_output is not None:
479
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
480
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
481
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
483
<meta http-equiv="Content-Type"
484
content="text/html; charset=utf-8" />
485
<title>Component mismatches for %s</title>
486
<style type="text/css">
487
body { background: #CCCCB0; color: black; }
488
a { text-decoration: none; }
489
table { border-collapse: collapse; border-style: none none;
490
margin-bottom: 3ex; empty-cells: show; }
491
table th { text-align: left;
492
border-style: groove none none none;
493
border-width: 3px; padding-right: 10px;
494
font-weight: normal; }
495
table td { vertical-align: top; text-align: left;
496
border-style: none none;
497
border-width: 1px; padding-right: 10px; }
498
.note { margin-left: 3ex; }
502
<h1>Component mismatches for %s</h1>
503
""") % (escape(options.suite), escape(options.suite)),
504
file=options.html_output)
396
508
binary_only = defaultdict(dict)
420
532
for source in filter_source(counterpart, sorted(both)):
421
533
binaries = sorted(both[source])
422
output += " o %s: %s\n" % (source, " ".join(binaries))
534
entry = [[source] + binaries]
424
536
for (id, status, title) in mir_bugs.get(source, []):
425
if title.startswith('[MIR]'):
426
# no need to repeat the standard title
427
output += ' MIR: #%i (%s)\n' % (id, status)
429
output += ' MIR: #%i (%s) %s\n' % (id, status, title)
431
output += do_reverse(source, binaries, both)
433
print_section("Source and binary movements to %s" % component, output)
537
entry.append(MIRLink(id, status, title))
539
entry.extend(do_reverse(source, binaries, both))
543
options, "Source and binary movements to %s" % component, output,
544
source_and_binary=True)
436
547
for source in sorted(binary_only):
437
548
binaries = filter_binary(counterpart, sorted(binary_only[source]))
440
what = " o %s" % (" ".join(binaries))
441
indent_right = 78 - len(what) - len(source) - 2
442
output += "%s%s{%s}\n" % (what, " " * indent_right, source)
444
output += do_reverse(source, binaries, binary_only)
446
print_section("Binary only movements to %s" % component, output)
551
entry = [binaries + [source]]
552
entry.extend(do_reverse(source, binaries, binary_only))
556
options, "Binary only movements to %s" % component, output,
449
560
for source in filter_source(counterpart, sorted(source_add)):
450
output += " o %s\n" % (source)
561
output.append([source])
452
print_section("Source only movements to %s" % component, output)
564
options, "Source only movements to %s" % component, output)
455
567
with open(options.dot, 'w') as f:
480
592
for source in filter_source(component, sorted(both)):
481
593
binaries = sorted(both[source])
482
output += " o %s: %s\n" % (source, " ".join(binaries))
594
output.append([[source] + binaries])
485
"Source and binary movements to %s" % counterpart, output)
597
options, "Source and binary movements to %s" % counterpart, output,
598
source_and_binary=True)
488
601
for source in sorted(binary_only):
489
602
binaries = filter_binary(component, sorted(binary_only[source]))
492
what = " o %s" % (" ".join(binaries))
493
indent_right = 78 - len(what) - len(source) - 2
494
output += "%s%s{%s}\n" % (what, " " * indent_right, source)
496
print_section("Binary only movements to %s" % counterpart, output)
605
output.append([binaries + [source]])
608
options, "Binary only movements to %s" % counterpart, output,
499
612
for source in filter_source(component, sorted(source_remove)):
500
output += " o %s\n" % (source)
502
print_section("Source only movements to %s" % counterpart, output)
613
output.append([source])
616
options, "Source only movements to %s" % counterpart, output)
618
if options.html_output is not None:
620
"<p><small>Generated: %s</small></p>" % escape(options.timestamp),
621
file=options.html_output)
622
print("</body></html>", file=options.html_output)
505
625
def do_source_diff(options):
552
672
parser.add_option(
553
673
"-l", "--launchpad", dest="launchpad_instance", default="production")
554
674
parser.add_option('-o', '--output-file', help='output to this file')
675
parser.add_option('--html-output-file', help='output HTML to this file')
555
676
parser.add_option('-s', '--suite', help='check this suite')
556
677
parser.add_option('-f', '--flavours', default='ubuntu',
557
678
help='check these flavours (comma-separated)')
597
718
if options.output_file is not None:
598
719
sys.stdout = open('%s.new' % options.output_file, 'w')
720
if options.html_output_file is not None:
721
options.html_output = open('%s.new' % options.html_output_file, 'w')
723
options.html_output = None
600
print('Generated: %s' % time.strftime('%a %b %e %H:%M:%S %Z %Y'))
725
options.timestamp = time.strftime('%a %b %e %H:%M:%S %Z %Y')
726
print('Generated: %s' % options.timestamp)
603
729
read_germinate(options)
610
736
options, source_add, source_remove, binary_add, binary_remove,
739
if options.html_output_file is not None:
740
options.html_output.close()
742
'%s.new' % options.html_output_file, options.html_output_file)
613
743
if options.output_file is not None:
614
744
sys.stdout.close()
615
745
os.rename('%s.new' % options.output_file, options.output_file)