Merge lp:~cjwatson/launchpad/archive-copy-packages-source-series into lp:launchpad

Proposed by Colin Watson on 2012-01-09
Status: Merged
Approved by: Steve Kowalik on 2012-01-18
Approved revision: no longer in the source branch.
Merged at revision: 14695
Proposed branch: lp:~cjwatson/launchpad/archive-copy-packages-source-series
Merge into: lp:launchpad
Diff against target: 338 lines (+131/-26)
5 files modified
lib/lp/soyuz/browser/tests/test_archive_webservice.py (+11/-8)
lib/lp/soyuz/doc/archive.txt (+19/-0)
lib/lp/soyuz/interfaces/archive.py (+21/-5)
lib/lp/soyuz/model/archive.py (+16/-10)
lib/lp/soyuz/tests/test_archive.py (+64/-3)
To merge this branch: bzr merge lp:~cjwatson/launchpad/archive-copy-packages-source-series
Reviewer Review Type Date Requested Status
Steve Kowalik (community) code 2012-01-09 Approve on 2012-01-18
Review via email: mp+87942@code.launchpad.net

Commit Message

[r=stevenk][bug=913722] Add an optional from_series parameter to Archive.copyPackages and Archive.syncSources.

Description of the Change

== Summary ==

Archive.copyPackages doesn't allow specifying a source distroseries. This means that autosyncs from Debian using this method copy the newest version available no matter what - up to and including versions in experimental!

  https://lists.ubuntu.com/archives/ubuntu-release/2012-January/000672.html

== Proposed fix ==

Add an optional from_series parameter.

== Implementation details ==

I made from_series be a distroseries name rather than an object, matching to_series. This necessitated some mild tedium in Archive._text_to_series to fetch series from the source distribution.

== Tests ==

bin/test -vvct test_archive_webservice -t soyuz/doc/archive.txt -t soyuz.tests.test_archive

== Demo and Q/A ==

Find a package in dogfood that has dogfood/Ubuntu/oneiric (or precise) < dogfood/Debian/wheezy < dogfood/Debian/sid; hopefully there's at least one. Use Archive.copyPackages(from_series='wheezy') and make sure that the wheezy version gets copied and not the sid version.

== lint ==

./lib/lp/soyuz/model/archive.py
     348: redefinition of function 'private' from line 344

Just lint being confused about setter properties.

To post a comment you must log in.
Steve Kowalik (stevenk) wrote :

This looks excellent, thank you.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/soyuz/browser/tests/test_archive_webservice.py'
2--- lib/lp/soyuz/browser/tests/test_archive_webservice.py 2012-01-01 02:58:52 +0000
3+++ lib/lp/soyuz/browser/tests/test_archive_webservice.py 2012-01-09 14:58:27 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2010-2011 Canonical Ltd. This software is licensed under the
6+# Copyright 2010-2012 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 __metaclass__ = type
10@@ -338,13 +338,14 @@
11 with person_logged_in(target_archive.owner):
12 target_archive.newComponentUploader(uploader_dude, "universe")
13 transaction.commit()
14- return (source_archive, source_name, target_archive, to_pocket,
15- to_series, uploader_dude, sponsored_dude, version)
16+ return (source, source_archive, source_name, target_archive,
17+ to_pocket, to_series, uploader_dude, sponsored_dude, version)
18
19 def test_copyPackage(self):
20 """Basic smoke test"""
21- (source_archive, source_name, target_archive, to_pocket, to_series,
22- uploader_dude, sponsored_dude, version) = self.setup_data()
23+ (source, source_archive, source_name, target_archive, to_pocket,
24+ to_series, uploader_dude, sponsored_dude,
25+ version) = self.setup_data()
26
27 ws_target_archive = self.wsObject(target_archive, user=uploader_dude)
28 ws_source_archive = self.wsObject(source_archive)
29@@ -363,8 +364,9 @@
30
31 def test_copyPackages(self):
32 """Basic smoke test"""
33- (source_archive, source_name, target_archive, to_pocket, to_series,
34- uploader_dude, sponsored_dude, version) = self.setup_data()
35+ (source, source_archive, source_name, target_archive, to_pocket,
36+ to_series, uploader_dude, sponsored_dude,
37+ version) = self.setup_data()
38
39 ws_target_archive = self.wsObject(target_archive, user=uploader_dude)
40 ws_source_archive = self.wsObject(source_archive)
41@@ -373,7 +375,8 @@
42 ws_target_archive.copyPackages(
43 source_names=[source_name], from_archive=ws_source_archive,
44 to_pocket=to_pocket.name, to_series=to_series.name,
45- include_binaries=False, sponsored=ws_sponsored_dude)
46+ from_series=source.distroseries.name, include_binaries=False,
47+ sponsored=ws_sponsored_dude)
48 transaction.commit()
49
50 job_source = getUtility(IPlainPackageCopyJobSource)
51
52=== modified file 'lib/lp/soyuz/doc/archive.txt'
53--- lib/lp/soyuz/doc/archive.txt 2011-12-30 06:14:56 +0000
54+++ lib/lp/soyuz/doc/archive.txt 2012-01-09 14:58:27 +0000
55@@ -2342,6 +2342,25 @@
56 ...
57 NoSuchDistroSeries: No such distribution series: 'badseries'.
58
59+If a package exists in multiple distroseries, we can use the `from_series`
60+parameter to select the distroseries to synchronise from:
61+
62+ >>> test_publisher.addFakeChroots(breezy_autotest)
63+ >>> discard = test_publisher.getPubSource(
64+ ... sourcename="package-multiseries", version="1.0",
65+ ... archive=cprov.archive, status=PackagePublishingStatus.PUBLISHED)
66+ >>> discard = test_publisher.getPubSource(
67+ ... sourcename="package-multiseries", version="1.1",
68+ ... distroseries=breezy_autotest, archive=cprov.archive,
69+ ... status=PackagePublishingStatus.PUBLISHED)
70+ >>> mark.archive.syncSources(
71+ ... ["package-multiseries"], cprov.archive, "release",
72+ ... from_series="hoary", person=mark)
73+ >>> mark_multiseries = mark.archive.getPublishedSources(
74+ ... name="package-multiseries").one()
75+ >>> print mark_multiseries.sourcepackagerelease.version
76+ 1.0
77+
78 We can also specify a single source to be copied with the `syncSource`
79 call. This allows a version to be specified so older versions can be
80 pulled.
81
82=== modified file 'lib/lp/soyuz/interfaces/archive.py'
83--- lib/lp/soyuz/interfaces/archive.py 2012-01-01 02:58:52 +0000
84+++ lib/lp/soyuz/interfaces/archive.py 2012-01-09 14:58:27 +0000
85@@ -1,4 +1,4 @@
86-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
87+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
88 # GNU Affero General Public License version 3 (see the file LICENSE).
89
90 # pylint: disable-msg=E0211,E0213
91@@ -1268,7 +1268,14 @@
92 from_archive=Reference(schema=Interface),
93 #Really IArchive, see below
94 to_pocket=TextLine(title=_("Pocket name")),
95- to_series=TextLine(title=_("Distroseries name"), required=False),
96+ to_series=TextLine(
97+ title=_("Distroseries name"),
98+ description=_("The distro series to copy packages into."),
99+ required=False),
100+ from_series=TextLine(
101+ title=_("Distroseries name"),
102+ description=_("The distro series to copy packages from."),
103+ required=False),
104 include_binaries=Bool(
105 title=_("Include Binaries"),
106 description=_("Whether or not to copy binaries already built for"
107@@ -1282,7 +1289,7 @@
108 @export_write_operation()
109 @operation_for_version('devel')
110 def copyPackages(source_names, from_archive, to_pocket, person,
111- to_series=None, include_binaries=False,
112+ to_series=None, from_series=None, include_binaries=False,
113 sponsored=None):
114 """Copy multiple named sources into this archive from another.
115
116@@ -1298,6 +1305,7 @@
117 :param from_archive: the source archive from which to copy.
118 :param to_pocket: the target pocket (as a string).
119 :param to_series: the target distroseries (as a string).
120+ :param from_series: the source distroseries (as a string).
121 :param include_binaries: optional boolean, controls whether or not
122 the published binaries for each given source should also be
123 copied along with the source.
124@@ -1325,7 +1333,14 @@
125 from_archive=Reference(schema=Interface),
126 #Really IArchive, see below
127 to_pocket=TextLine(title=_("Pocket name")),
128- to_series=TextLine(title=_("Distroseries name"), required=False),
129+ to_series=TextLine(
130+ title=_("Distroseries name"),
131+ description=_("The distro series to copy packages into."),
132+ required=False),
133+ from_series=TextLine(
134+ title=_("Distroseries name"),
135+ description=_("The distro series to copy packages from."),
136+ required=False),
137 include_binaries=Bool(
138 title=_("Include Binaries"),
139 description=_("Whether or not to copy binaries already built for"
140@@ -1335,7 +1350,7 @@
141 # Source_names is a string because exporting a SourcePackageName is
142 # rather nonsensical as it only has id and name columns.
143 def syncSources(source_names, from_archive, to_pocket, to_series=None,
144- include_binaries=False, person=None):
145+ from_series=None, include_binaries=False, person=None):
146 """Synchronise (copy) named sources into this archive from another.
147
148 It will copy the most recent PUBLISHED versions of the named
149@@ -1350,6 +1365,7 @@
150 :param from_archive: the source archive from which to copy.
151 :param to_pocket: the target pocket (as a string).
152 :param to_series: the target distroseries (as a string).
153+ :param from_series: the source distroseries (as a string).
154 :param include_binaries: optional boolean, controls whether or not
155 the published binaries for each given source should also be
156 copied along with the source.
157
158=== modified file 'lib/lp/soyuz/model/archive.py'
159--- lib/lp/soyuz/model/archive.py 2012-01-06 11:08:30 +0000
160+++ lib/lp/soyuz/model/archive.py 2012-01-09 14:58:27 +0000
161@@ -1,4 +1,4 @@
162-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
163+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
164 # GNU Affero General Public License version 3 (see the file LICENSE).
165
166 # pylint: disable-msg=E0611,W0212
167@@ -1526,11 +1526,12 @@
168 reason)
169
170 def syncSources(self, source_names, from_archive, to_pocket,
171- to_series=None, include_binaries=False, person=None):
172+ to_series=None, from_series=None, include_binaries=False,
173+ person=None):
174 """See `IArchive`."""
175 # Find and validate the source package names in source_names.
176 sources = self._collectLatestPublishedSources(
177- from_archive, source_names)
178+ from_archive, from_series, source_names)
179 self._copySources(
180 sources, to_pocket, to_series, include_binaries,
181 person=person)
182@@ -1591,13 +1592,13 @@
183 sponsored=sponsored)
184
185 def copyPackages(self, source_names, from_archive, to_pocket,
186- person, to_series=None, include_binaries=None,
187- sponsored=None):
188+ person, to_series=None, from_series=None,
189+ include_binaries=None, sponsored=None):
190 """See `IArchive`."""
191 self._checkCopyPackageFeatureFlags()
192
193 sources = self._collectLatestPublishedSources(
194- from_archive, source_names)
195+ from_archive, from_series, source_names)
196 if not sources:
197 raise CannotCopy(
198 "None of the supplied package names are published")
199@@ -1635,13 +1636,16 @@
200 copy_policy=PackageCopyPolicy.MASS_SYNC,
201 include_binaries=include_binaries, sponsored=sponsored)
202
203- def _collectLatestPublishedSources(self, from_archive, source_names):
204+ def _collectLatestPublishedSources(self, from_archive, from_series,
205+ source_names):
206 """Private helper to collect the latest published sources for an
207 archive.
208
209 :raises NoSuchSourcePackageName: If any of the source_names do not
210 exist.
211 """
212+ from_series_obj = self._text_to_series(
213+ from_series, distribution=from_archive.distribution)
214 # XXX bigjools bug=810421
215 # This code is inefficient. It should try to bulk load all the
216 # sourcepackagenames and publications instead of iterating.
217@@ -1654,7 +1658,7 @@
218 # Grabbing the item at index 0 ensures it's the most recent
219 # publication.
220 published_sources = from_archive.getPublishedSources(
221- name=name, exact_match=True,
222+ name=name, distroseries=from_series_obj, exact_match=True,
223 status=(PackagePublishingStatus.PUBLISHED,
224 PackagePublishingStatus.PENDING))
225 first_source = published_sources.first()
226@@ -1662,10 +1666,12 @@
227 sources.append(first_source)
228 return sources
229
230- def _text_to_series(self, to_series):
231+ def _text_to_series(self, to_series, distribution=None):
232+ if distribution is None:
233+ distribution = self.distribution
234 if to_series is not None:
235 result = getUtility(IDistroSeriesSet).queryByName(
236- self.distribution, to_series)
237+ distribution, to_series)
238 if result is None:
239 raise NoSuchDistroSeries(to_series)
240 series = result
241
242=== modified file 'lib/lp/soyuz/tests/test_archive.py'
243--- lib/lp/soyuz/tests/test_archive.py 2011-12-30 06:14:56 +0000
244+++ lib/lp/soyuz/tests/test_archive.py 2012-01-09 14:58:27 +0000
245@@ -1,4 +1,4 @@
246-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
247+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
248 # GNU Affero General Public License version 3 (see the file LICENSE).
249
250 """Test Archive features."""
251@@ -443,7 +443,7 @@
252 ["1.0", "1.1", "2.0"],
253 [sourcepackagename, sourcepackagename, other_spn])
254 pubs = removeSecurityProxy(archive)._collectLatestPublishedSources(
255- archive, ["foo"])
256+ archive, None, ["foo"])
257 self.assertEqual(1, len(pubs))
258 self.assertEqual('1.1', pubs[0].source_package_version)
259
260@@ -460,10 +460,32 @@
261 ["1.0", "1.1", "2.0"],
262 [sourcepackagename, sourcepackagename, other_spn])
263 pubs = removeSecurityProxy(archive)._collectLatestPublishedSources(
264- archive, ["foo"])
265+ archive, None, ["foo"])
266 self.assertEqual(1, len(pubs))
267 self.assertEqual('1.0', pubs[0].source_package_version)
268
269+ def test_collectLatestPublishedSources_multiple_distroseries(self):
270+ # The helper method selects the correct publication from multiple
271+ # distroseries.
272+ sourcepackagename = self.factory.makeSourcePackageName(name="foo")
273+ archive = self.factory.makeArchive()
274+ distroseries_one = self.factory.makeDistroSeries(
275+ distribution=archive.distribution)
276+ distroseries_two = self.factory.makeDistroSeries(
277+ distribution=archive.distribution)
278+ self.factory.makeSourcePackagePublishingHistory(
279+ sourcepackagename=sourcepackagename, archive=archive,
280+ distroseries=distroseries_one, version="1.0",
281+ status=PackagePublishingStatus.PUBLISHED)
282+ self.factory.makeSourcePackagePublishingHistory(
283+ sourcepackagename=sourcepackagename, archive=archive,
284+ distroseries=distroseries_two, version="1.1",
285+ status=PackagePublishingStatus.PUBLISHED)
286+ pubs = removeSecurityProxy(archive)._collectLatestPublishedSources(
287+ archive, distroseries_one.name, ["foo"])
288+ self.assertEqual(1, len(pubs))
289+ self.assertEqual("1.0", pubs[0].source_package_version)
290+
291
292 class TestArchiveCanUpload(TestCaseWithFactory):
293 """Test the various methods that verify whether uploads are allowed to
294@@ -2302,6 +2324,45 @@
295 to_pocket.name, to_series=to_series.name, include_binaries=False,
296 person=person)
297
298+ def test_copyPackages_with_multiple_distroseries(self):
299+ # The from_series parameter selects a source distroseries.
300+ (source, source_archive, source_name, target_archive, to_pocket,
301+ to_series, version) = self._setup_copy_data()
302+ new_distroseries = self.factory.makeDistroSeries(
303+ distribution=source_archive.distribution)
304+ new_version = "%s.1" % version
305+ new_spr = self.factory.makeSourcePackageRelease(
306+ archive=source_archive, distroseries=new_distroseries,
307+ sourcepackagename=source_name, version=new_version)
308+ self.factory.makeSourcePackagePublishingHistory(
309+ archive=source_archive, distroseries=new_distroseries,
310+ sourcepackagerelease=new_spr)
311+
312+ with person_logged_in(target_archive.owner):
313+ target_archive.copyPackages(
314+ [source_name], source_archive, to_pocket.name,
315+ to_series=to_series.name,
316+ from_series=source.distroseries.name, include_binaries=False,
317+ person=target_archive.owner)
318+
319+ # There should be one copy job with the correct version.
320+ job_source = getUtility(IPlainPackageCopyJobSource)
321+ copy_job = job_source.getActiveJobs(target_archive).one()
322+ self.assertEqual(version, copy_job.package_version)
323+
324+ # If we now do another copy without the from_series parameter, it
325+ # selects the newest version in the source archive.
326+ with person_logged_in(target_archive.owner):
327+ target_archive.copyPackages(
328+ [source_name], source_archive, to_pocket.name,
329+ to_series=to_series.name, include_binaries=False,
330+ person=target_archive.owner)
331+
332+ copy_jobs = job_source.getActiveJobs(target_archive)
333+ self.assertEqual(2, copy_jobs.count())
334+ self.assertEqual(copy_job, copy_jobs[0])
335+ self.assertEqual(new_version, copy_jobs[1].package_version)
336+
337
338 class TestgetAllPublishedBinaries(TestCaseWithFactory):
339