1399.1.27
by Brian Murray
move copy-report to python3 |
1 |
#! /usr/bin/python3
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
2 |
|
464.1.3
by Colin Watson
copy-report: use Python-3-compatible print functions |
3 |
from __future__ import print_function |
4 |
||
464.1.4
by Colin Watson
copy-report: consolidate and sort imports |
5 |
import atexit |
1236
by Colin Watson
copy-report: port to requests |
6 |
import bz2 |
464.1.13
by Colin Watson
copy-report: clarify tagfile section handling with namedtuples |
7 |
from collections import namedtuple |
464.1.4
by Colin Watson
copy-report: consolidate and sort imports |
8 |
import optparse |
464.1.1
by Colin Watson
copy-report: import from cocoplum |
9 |
import os |
10 |
import re |
|
11 |
import shutil |
|
12 |
import subprocess |
|
464.1.4
by Colin Watson
copy-report: consolidate and sort imports |
13 |
import tempfile |
1399.1.27
by Brian Murray
move copy-report to python3 |
14 |
|
15 |
from urllib.parse import unquote |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
16 |
|
17 |
import apt_pkg |
|
464.1.7
by Colin Watson
copy-report: try multiple mirrors, eventually falling back to Archive.getPublishedSources; use Archive.copyPackage directly; suggest copy-package rather than copy-package.py |
18 |
from launchpadlib.launchpad import Launchpad |
1236
by Colin Watson
copy-report: port to requests |
19 |
import lzma |
20 |
import requests |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
21 |
|
464.1.8
by Colin Watson
copy-report: PEP-8 |
22 |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
23 |
# from dak, more or less
|
24 |
re_no_epoch = re.compile(r"^\d+:") |
|
25 |
re_strip_revision = re.compile(r"-[^-]+$") |
|
26 |
re_changelog_versions = re.compile(r"^\w[-+0-9a-z.]+ \(([^\(\) \t]+)\)") |
|
27 |
||
464.1.7
by Colin Watson
copy-report: try multiple mirrors, eventually falling back to Archive.getPublishedSources; use Archive.copyPackage directly; suggest copy-package rather than copy-package.py |
28 |
default_mirrors = ":".join([ |
29 |
'/home/ubuntu-archive/mirror/ubuntu', |
|
30 |
'/srv/archive.ubuntu.com/ubuntu', |
|
677
by Colin Watson
make all scripts pass current stricter pep8(1) in raring |
31 |
])
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
32 |
tempdir = None |
33 |
||
570
by Colin Watson
copy-report: Archive.getPublishedSources takes a series link, not a series name |
34 |
series_by_name = {} |
35 |
||
464.1.8
by Colin Watson
copy-report: PEP-8 |
36 |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
37 |
def ensure_tempdir(): |
38 |
global tempdir |
|
39 |
if not tempdir: |
|
40 |
tempdir = tempfile.mkdtemp(prefix='copy-report') |
|
41 |
atexit.register(shutil.rmtree, tempdir) |
|
42 |
||
464.1.8
by Colin Watson
copy-report: PEP-8 |
43 |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
44 |
def decompress_open(tagfile): |
45 |
if tagfile.startswith('http:') or tagfile.startswith('ftp:'): |
|
46 |
ensure_tempdir() |
|
1245
by Colin Watson
copy-report: suppress automatic decompression by requests |
47 |
response = requests.get(tagfile, stream=True) |
1236
by Colin Watson
copy-report: port to requests |
48 |
if response.status_code == 404: |
1245
by Colin Watson
copy-report: suppress automatic decompression by requests |
49 |
response.close() |
1236
by Colin Watson
copy-report: port to requests |
50 |
tagfile = tagfile.replace('.xz', '.bz2') |
1245
by Colin Watson
copy-report: suppress automatic decompression by requests |
51 |
response = requests.get(tagfile, stream=True) |
1236
by Colin Watson
copy-report: port to requests |
52 |
response.raise_for_status() |
53 |
if '.' in tagfile: |
|
54 |
suffix = '.' + tagfile.rsplit('.', 1)[1] |
|
55 |
else: |
|
56 |
suffix = '' |
|
57 |
fd, tagfile = tempfile.mkstemp(suffix=suffix, dir=tempdir) |
|
58 |
with os.fdopen(fd, 'wb') as f: |
|
1245
by Colin Watson
copy-report: suppress automatic decompression by requests |
59 |
f.write(response.raw.read()) |
60 |
response.close() |
|
1236
by Colin Watson
copy-report: port to requests |
61 |
elif not os.path.exists(tagfile): |
62 |
tagfile = tagfile.replace('.xz', '.bz2') |
|
63 |
||
64 |
if tagfile.endswith('.xz'): |
|
65 |
decompressor = lzma.LZMAFile |
|
66 |
elif tagfile.endswith('.bz2'): |
|
67 |
decompressor = bz2.BZ2File |
|
68 |
else: |
|
69 |
decompressor = None |
|
70 |
||
71 |
if decompressor is not None: |
|
72 |
fd, decompressed = tempfile.mkstemp(dir=tempdir) |
|
73 |
dcf = decompressor(tagfile) |
|
74 |
try: |
|
75 |
with os.fdopen(fd, 'wb') as f: |
|
76 |
f.write(dcf.read()) |
|
77 |
finally: |
|
78 |
dcf.close() |
|
79 |
return open(decompressed, 'rb') |
|
80 |
else: |
|
81 |
return open(tagfile, 'rb') |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
82 |
|
464.1.8
by Colin Watson
copy-report: PEP-8 |
83 |
|
464.1.13
by Colin Watson
copy-report: clarify tagfile section handling with namedtuples |
84 |
Section = namedtuple("Section", ["version", "directory", "files"]) |
85 |
||
86 |
||
464.1.1
by Colin Watson
copy-report: import from cocoplum |
87 |
def tagfiletodict(tagfile): |
88 |
suite = {} |
|
464.1.2
by Colin Watson
copy-report: port to python-apt 0.8 API |
89 |
for section in apt_pkg.TagFile(decompress_open(tagfile)): |
90 |
files = [s.strip().split()[2] for s in section["Files"].split('\n')] |
|
464.1.13
by Colin Watson
copy-report: clarify tagfile section handling with namedtuples |
91 |
suite[section["Package"]] = Section( |
92 |
version=section["Version"], directory=section["Directory"], |
|
93 |
files=files) |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
94 |
return suite |
95 |
||
464.1.8
by Colin Watson
copy-report: PEP-8 |
96 |
|
464.1.13
by Colin Watson
copy-report: clarify tagfile section handling with namedtuples |
97 |
def find_dsc(options, pkg, section): |
98 |
dsc_filename = [s for s in section.files if s.endswith('.dsc')][0] |
|
464.1.7
by Colin Watson
copy-report: try multiple mirrors, eventually falling back to Archive.getPublishedSources; use Archive.copyPackage directly; suggest copy-package rather than copy-package.py |
99 |
for mirror in options.mirrors: |
464.1.13
by Colin Watson
copy-report: clarify tagfile section handling with namedtuples |
100 |
path = '%s/%s/%s' % (mirror, section.directory, dsc_filename) |
464.1.7
by Colin Watson
copy-report: try multiple mirrors, eventually falling back to Archive.getPublishedSources; use Archive.copyPackage directly; suggest copy-package rather than copy-package.py |
101 |
if os.path.exists(path): |
571
by Colin Watson
copy-report: continue to next .dsc if one fails to unpack (e.g. incomplete mirror) |
102 |
yield path |
103 |
ensure_tempdir() |
|
104 |
spph = options.archive.getPublishedSources( |
|
105 |
source_name=pkg, version=section.version, exact_match=True)[0] |
|
106 |
outdir = tempfile.mkdtemp(dir=tempdir) |
|
107 |
filenames = [] |
|
108 |
for url in spph.sourceFileUrls(): |
|
594
by Colin Watson
copy-report: unquote URLs |
109 |
filename = os.path.join(outdir, unquote(os.path.basename(url))) |
1245
by Colin Watson
copy-report: suppress automatic decompression by requests |
110 |
response = requests.get(url, stream=True) |
1236
by Colin Watson
copy-report: port to requests |
111 |
response.raise_for_status() |
112 |
with open(filename, 'wb') as f: |
|
1245
by Colin Watson
copy-report: suppress automatic decompression by requests |
113 |
f.write(response.raw.read()) |
114 |
response.close() |
|
571
by Colin Watson
copy-report: continue to next .dsc if one fails to unpack (e.g. incomplete mirror) |
115 |
filenames.append(filename) |
116 |
yield [s for s in filenames if s.endswith('.dsc')][0] |
|
117 |
||
118 |
||
119 |
class BrokenSourcePackage(Exception): |
|
120 |
pass
|
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
121 |
|
464.1.8
by Colin Watson
copy-report: PEP-8 |
122 |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
123 |
def get_changelog_versions(pkg, dsc, version): |
124 |
ensure_tempdir() |
|
125 |
||
126 |
upstream_version = re_no_epoch.sub('', version) |
|
127 |
upstream_version = re_strip_revision.sub('', upstream_version) |
|
128 |
||
725
by Colin Watson
copy-report: use os.devnull |
129 |
with open(os.devnull, 'w') as devnull: |
464.1.12
by Colin Watson
copy-report: pass -q to dpkg-source to get rid of warnings about -sn and 3.0 (quilt) |
130 |
ret = subprocess.call( |
131 |
['dpkg-source', '-q', '--no-check', '-sn', '-x', dsc], |
|
132 |
stdout=devnull, cwd=tempdir) |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
133 |
|
134 |
# It's in the archive, so these assertions must hold.
|
|
582
by Colin Watson
copy-report: fix reversed test |
135 |
if ret != 0: |
581
by Colin Watson
copy-report: include .dsc path in BrokenSourcePackage exception |
136 |
raise BrokenSourcePackage(dsc) |
571
by Colin Watson
copy-report: continue to next .dsc if one fails to unpack (e.g. incomplete mirror) |
137 |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
138 |
unpacked = '%s/%s-%s' % (tempdir, pkg, upstream_version) |
139 |
assert os.path.isdir(unpacked) |
|
140 |
changelog_path = '%s/debian/changelog' % unpacked |
|
141 |
assert os.path.exists(changelog_path) |
|
142 |
||
464.1.6
by Colin Watson
copy-report: use with statements a bit more |
143 |
with open(changelog_path) as changelog: |
144 |
versions = set() |
|
145 |
for line in changelog: |
|
146 |
m = re_changelog_versions.match(line) |
|
147 |
if m: |
|
148 |
versions.add(m.group(1)) |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
149 |
|
150 |
shutil.rmtree(unpacked) |
|
151 |
||
152 |
return versions |
|
153 |
||
464.1.8
by Colin Watson
copy-report: PEP-8 |
154 |
|
464.1.13
by Colin Watson
copy-report: clarify tagfile section handling with namedtuples |
155 |
def descended_from(options, pkg, section1, section2): |
156 |
if apt_pkg.version_compare(section1.version, section2.version) <= 0: |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
157 |
return False |
571
by Colin Watson
copy-report: continue to next .dsc if one fails to unpack (e.g. incomplete mirror) |
158 |
exception = None |
159 |
for dsc in find_dsc(options, pkg, section1): |
|
160 |
try: |
|
161 |
versions = get_changelog_versions(pkg, dsc, section1.version) |
|
1244
by Colin Watson
copy-report: fix descended_from exception handling to work in Python 3 |
162 |
except BrokenSourcePackage as e: |
163 |
exception = e |
|
571
by Colin Watson
copy-report: continue to next .dsc if one fails to unpack (e.g. incomplete mirror) |
164 |
continue
|
165 |
return section1.version in versions |
|
166 |
raise exception |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
167 |
|
464.1.8
by Colin Watson
copy-report: PEP-8 |
168 |
|
464.1.15
by Colin Watson
copy-report: clarify candidate handling with namedtuples |
169 |
Candidate = namedtuple( |
170 |
"Candidate", ["package", "suite1", "suite2", "version1", "version2"]) |
|
171 |
||
172 |
||
570
by Colin Watson
copy-report: Archive.getPublishedSources takes a series link, not a series name |
173 |
def get_series(options, name): |
174 |
if name not in series_by_name: |
|
175 |
series_by_name[name] = options.distro.getSeries(name_or_version=name) |
|
176 |
return series_by_name[name] |
|
177 |
||
178 |
||
565
by Colin Watson
copy-report: skip copying packages that have already been copied (workaround for bug 1023372, but reasonably sensible anyway) |
179 |
def already_copied(options, candidate): |
180 |
if "-" in candidate.suite2: |
|
181 |
series, pocket = candidate.suite2.split("-", 1) |
|
182 |
pocket = pocket.title() |
|
183 |
else: |
|
184 |
series = candidate.suite2 |
|
185 |
pocket = "Release" |
|
570
by Colin Watson
copy-report: Archive.getPublishedSources takes a series link, not a series name |
186 |
series = get_series(options, series) |
565
by Colin Watson
copy-report: skip copying packages that have already been copied (workaround for bug 1023372, but reasonably sensible anyway) |
187 |
pubs = options.archive.getPublishedSources( |
188 |
source_name=candidate.package, version=candidate.version1, |
|
189 |
exact_match=True, distro_series=series, pocket=pocket) |
|
190 |
for pub in pubs: |
|
191 |
if pub.status in ("Pending", "Published"): |
|
192 |
return True |
|
193 |
return False |
|
194 |
||
195 |
||
464.1.7
by Colin Watson
copy-report: try multiple mirrors, eventually falling back to Archive.getPublishedSources; use Archive.copyPackage directly; suggest copy-package rather than copy-package.py |
196 |
def copy(options, candidate): |
464.1.15
by Colin Watson
copy-report: clarify candidate handling with namedtuples |
197 |
if "-" in candidate.suite2: |
198 |
to_series, to_pocket = candidate.suite2.split("-", 1) |
|
464.1.7
by Colin Watson
copy-report: try multiple mirrors, eventually falling back to Archive.getPublishedSources; use Archive.copyPackage directly; suggest copy-package rather than copy-package.py |
199 |
to_pocket = to_pocket.title() |
200 |
else: |
|
464.1.15
by Colin Watson
copy-report: clarify candidate handling with namedtuples |
201 |
to_series = candidate.suite2 |
464.1.7
by Colin Watson
copy-report: try multiple mirrors, eventually falling back to Archive.getPublishedSources; use Archive.copyPackage directly; suggest copy-package rather than copy-package.py |
202 |
to_pocket = "Release" |
203 |
options.archive.copyPackage( |
|
464.1.15
by Colin Watson
copy-report: clarify candidate handling with namedtuples |
204 |
source_name=candidate.package, version=candidate.version1, |
464.1.7
by Colin Watson
copy-report: try multiple mirrors, eventually falling back to Archive.getPublishedSources; use Archive.copyPackage directly; suggest copy-package rather than copy-package.py |
205 |
from_archive=options.archive, to_pocket=to_pocket, to_series=to_series, |
206 |
include_binaries=True, auto_approve=True) |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
207 |
|
464.1.8
by Colin Watson
copy-report: PEP-8 |
208 |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
209 |
def candidate_string(candidate): |
464.1.7
by Colin Watson
copy-report: try multiple mirrors, eventually falling back to Archive.getPublishedSources; use Archive.copyPackage directly; suggest copy-package rather than copy-package.py |
210 |
string = ('copy-package -y -b -s %s --to-suite %s -e %s %s' % |
464.1.15
by Colin Watson
copy-report: clarify candidate handling with namedtuples |
211 |
(candidate.suite1, candidate.suite2, candidate.version1, |
212 |
candidate.package)) |
|
213 |
if candidate.version2 is not None: |
|
214 |
string += ' # %s: %s' % (candidate.suite2, candidate.version2) |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
215 |
return string |
216 |
||
464.1.8
by Colin Watson
copy-report: PEP-8 |
217 |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
218 |
def main(): |
464.1.2
by Colin Watson
copy-report: port to python-apt 0.8 API |
219 |
apt_pkg.init_system() |
464.1.1
by Colin Watson
copy-report: import from cocoplum |
220 |
|
221 |
parser = optparse.OptionParser(usage="usage: %prog [options] [suites]") |
|
464.1.7
by Colin Watson
copy-report: try multiple mirrors, eventually falling back to Archive.getPublishedSources; use Archive.copyPackage directly; suggest copy-package rather than copy-package.py |
222 |
parser.add_option( |
223 |
"-l", "--launchpad", dest="launchpad_instance", default="production") |
|
224 |
parser.add_option( |
|
225 |
"--quick", action="store_true", help="don't examine changelogs") |
|
226 |
parser.add_option( |
|
595
by Colin Watson
copy-report: rename --safe option to --copy-safe |
227 |
"--copy-safe", action="store_true", |
464.1.7
by Colin Watson
copy-report: try multiple mirrors, eventually falling back to Archive.getPublishedSources; use Archive.copyPackage directly; suggest copy-package rather than copy-package.py |
228 |
help="automatically copy safe candidates") |
229 |
parser.add_option( |
|
230 |
"--mirrors", default=default_mirrors, |
|
231 |
help="colon-separated list of local mirrors") |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
232 |
options, args = parser.parse_args() |
464.1.7
by Colin Watson
copy-report: try multiple mirrors, eventually falling back to Archive.getPublishedSources; use Archive.copyPackage directly; suggest copy-package rather than copy-package.py |
233 |
|
234 |
options.launchpad = Launchpad.login_with( |
|
235 |
"copy-report", options.launchpad_instance, version="devel") |
|
236 |
options.distro = options.launchpad.distributions["ubuntu"] |
|
237 |
options.archive = options.distro.main_archive |
|
238 |
options.mirrors = options.mirrors.split(":") |
|
239 |
||
464.1.1
by Colin Watson
copy-report: import from cocoplum |
240 |
if args: |
241 |
suites = args |
|
242 |
else: |
|
566
by Colin Watson
copy-report: fix reversed suite order (cosmetic) |
243 |
suites = reversed([ |
563
by Colin Watson
copy-report: Distribution.series, not Distribution.suites |
244 |
series.name |
245 |
for series in options.launchpad.distributions["ubuntu"].series |
|
566
by Colin Watson
copy-report: fix reversed suite order (cosmetic) |
246 |
if series.status in ("Supported", "Current Stable Release")]) |
464.1.1
by Colin Watson
copy-report: import from cocoplum |
247 |
|
248 |
yes = [] |
|
249 |
maybe = [] |
|
250 |
no = [] |
|
251 |
||
252 |
for suite in suites: |
|
253 |
for component in 'main', 'restricted', 'universe', 'multiverse': |
|
1236
by Colin Watson
copy-report: port to requests |
254 |
tagfile1 = '%s/dists/%s-security/%s/source/Sources.xz' % ( |
464.1.7
by Colin Watson
copy-report: try multiple mirrors, eventually falling back to Archive.getPublishedSources; use Archive.copyPackage directly; suggest copy-package rather than copy-package.py |
255 |
options.mirrors[0], suite, component) |
1236
by Colin Watson
copy-report: port to requests |
256 |
tagfile2 = '%s/dists/%s-updates/%s/source/Sources.xz' % ( |
464.1.7
by Colin Watson
copy-report: try multiple mirrors, eventually falling back to Archive.getPublishedSources; use Archive.copyPackage directly; suggest copy-package rather than copy-package.py |
257 |
options.mirrors[0], suite, component) |
464.1.1
by Colin Watson
copy-report: import from cocoplum |
258 |
name1 = '%s-security' % suite |
259 |
name2 = '%s-updates' % suite |
|
260 |
||
261 |
suite1 = tagfiletodict(tagfile1) |
|
262 |
suite2 = tagfiletodict(tagfile2) |
|
263 |
||
464.1.11
by Colin Watson
copy-report: rely on default iterator for mappings iterating over keys |
264 |
for package in sorted(suite1): |
464.1.13
by Colin Watson
copy-report: clarify tagfile section handling with namedtuples |
265 |
section1 = suite1[package] |
266 |
section2 = suite2.get(package) |
|
267 |
if (section2 is None or |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
268 |
(not options.quick and |
464.1.13
by Colin Watson
copy-report: clarify tagfile section handling with namedtuples |
269 |
descended_from(options, package, section1, section2))): |
565
by Colin Watson
copy-report: skip copying packages that have already been copied (workaround for bug 1023372, but reasonably sensible anyway) |
270 |
candidate = Candidate( |
464.1.15
by Colin Watson
copy-report: clarify candidate handling with namedtuples |
271 |
package=package, suite1=name1, suite2=name2, |
565
by Colin Watson
copy-report: skip copying packages that have already been copied (workaround for bug 1023372, but reasonably sensible anyway) |
272 |
version1=section1.version, version2=None) |
273 |
if not already_copied(options, candidate): |
|
274 |
yes.append(candidate) |
|
464.1.8
by Colin Watson
copy-report: PEP-8 |
275 |
elif apt_pkg.version_compare( |
677
by Colin Watson
make all scripts pass current stricter pep8(1) in raring |
276 |
section1.version, section2.version) > 0: |
464.1.15
by Colin Watson
copy-report: clarify candidate handling with namedtuples |
277 |
candidate = Candidate( |
278 |
package=package, suite1=name1, suite2=name2, |
|
279 |
version1=section1.version, version2=section2.version) |
|
565
by Colin Watson
copy-report: skip copying packages that have already been copied (workaround for bug 1023372, but reasonably sensible anyway) |
280 |
if already_copied(options, candidate): |
281 |
pass
|
|
282 |
elif not options.quick: |
|
464.1.15
by Colin Watson
copy-report: clarify candidate handling with namedtuples |
283 |
no.append(candidate) |
464.1.1
by Colin Watson
copy-report: import from cocoplum |
284 |
else: |
464.1.15
by Colin Watson
copy-report: clarify candidate handling with namedtuples |
285 |
maybe.append(candidate) |
464.1.1
by Colin Watson
copy-report: import from cocoplum |
286 |
|
287 |
if yes: |
|
464.1.9
by Colin Watson
copy-report: print output even with --safe |
288 |
print("The following packages can be copied safely:") |
289 |
print("--------------------------------------------") |
|
290 |
print() |
|
291 |
for candidate in yes: |
|
292 |
print(candidate_string(candidate)) |
|
293 |
print() |
|
294 |
||
595
by Colin Watson
copy-report: rename --safe option to --copy-safe |
295 |
if options.copy_safe: |
464.1.1
by Colin Watson
copy-report: import from cocoplum |
296 |
for candidate in yes: |
464.1.7
by Colin Watson
copy-report: try multiple mirrors, eventually falling back to Archive.getPublishedSources; use Archive.copyPackage directly; suggest copy-package rather than copy-package.py |
297 |
copy(options, candidate) |
464.1.1
by Colin Watson
copy-report: import from cocoplum |
298 |
|
464.1.9
by Colin Watson
copy-report: print output even with --safe |
299 |
if maybe: |
464.1.3
by Colin Watson
copy-report: use Python-3-compatible print functions |
300 |
print("Check that these packages are descendants before copying:") |
301 |
print("---------------------------------------------------------") |
|
302 |
print() |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
303 |
for candidate in maybe: |
464.1.3
by Colin Watson
copy-report: use Python-3-compatible print functions |
304 |
print('#%s' % candidate_string(candidate)) |
305 |
print() |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
306 |
|
464.1.9
by Colin Watson
copy-report: print output even with --safe |
307 |
if no: |
464.1.3
by Colin Watson
copy-report: use Python-3-compatible print functions |
308 |
print("The following packages need to be merged by hand:") |
309 |
print("-------------------------------------------------") |
|
310 |
print() |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
311 |
for candidate in no: |
464.1.3
by Colin Watson
copy-report: use Python-3-compatible print functions |
312 |
print('#%s' % candidate_string(candidate)) |
313 |
print() |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
314 |
|
464.1.8
by Colin Watson
copy-report: PEP-8 |
315 |
|
464.1.1
by Colin Watson
copy-report: import from cocoplum |
316 |
if __name__ == '__main__': |
317 |
main() |