Merge lp:~jelmer/launchpad/bzr-code-imports into lp:launchpad/db-devel

Proposed by Jelmer Vernooij
Status: Superseded
Proposed branch: lp:~jelmer/launchpad/bzr-code-imports
Merge into: lp:launchpad/db-devel
Prerequisite: lp:~jelmer/launchpad/bzr-2.4b4
Diff against target: 2037 lines (+1137/-48) (has conflicts)
35 files modified
database/schema/patch-2208-74-0.sql (+20/-0)
lib/canonical/config/schema-lazr.conf (+5/-0)
lib/canonical/launchpad/daemons/tachandler.py (+9/-0)
lib/canonical/launchpad/doc/vocabulary-json.txt (+10/-0)
lib/lp/app/javascript/picker/person_picker.js (+71/-0)
lib/lp/app/javascript/picker/picker_patcher.js (+4/-0)
lib/lp/app/javascript/picker/tests/test_personpicker.js (+73/-0)
lib/lp/app/javascript/picker/tests/test_picker_patcher.js (+179/-0)
lib/lp/code/errors.py (+3/-0)
lib/lp/code/mail/codeimport.py (+3/-1)
lib/lp/code/model/branchjob.py (+16/-0)
lib/lp/code/model/codeimport.py (+8/-3)
lib/lp/code/model/codeimportevent.py (+2/-1)
lib/lp/code/model/tests/test_codeimport.py (+44/-0)
lib/lp/codehosting/codeimport/tests/servers.py (+21/-0)
lib/lp/codehosting/codeimport/tests/test_worker.py (+53/-4)
lib/lp/codehosting/codeimport/tests/test_workermonitor.py (+20/-0)
lib/lp/codehosting/codeimport/worker.py (+35/-4)
lib/lp/registry/browser/productseries.py (+9/-17)
lib/lp/registry/browser/tests/test_distroseries.py (+59/-0)
lib/lp/registry/interfaces/distroseries.py (+5/-0)
lib/lp/registry/model/distribution.py (+34/-0)
lib/lp/registry/model/product.py (+12/-0)
lib/lp/soyuz/browser/queue.py (+105/-0)
lib/lp/soyuz/browser/tests/test_queue.py (+166/-0)
lib/lp/soyuz/interfaces/archive.py (+10/-3)
lib/lp/soyuz/interfaces/files.py (+4/-0)
lib/lp/soyuz/model/packagesetsources.py (+44/-0)
lib/lp/soyuz/scripts/tests/test_queue.py (+43/-0)
lib/lp/soyuz/stories/webservice/xx-archive.txt (+5/-0)
lib/lp/soyuz/tests/test_packageupload.py (+3/-0)
lib/lp/testing/factory.py (+30/-3)
scripts/code-import-worker.py (+14/-4)
utilities/sourcedeps.cache (+14/-8)
versions.cfg (+4/-0)
Text conflict in database/schema/patch-2208-74-0.sql
Text conflict in lib/canonical/launchpad/daemons/tachandler.py
Text conflict in lib/canonical/launchpad/doc/vocabulary-json.txt
Text conflict in lib/lp/app/javascript/picker/person_picker.js
Text conflict in lib/lp/app/javascript/picker/picker_patcher.js
Text conflict in lib/lp/app/javascript/picker/tests/test_personpicker.js
Text conflict in lib/lp/app/javascript/picker/tests/test_picker_patcher.js
Text conflict in lib/lp/code/errors.py
Text conflict in lib/lp/code/model/branchjob.py
Text conflict in lib/lp/codehosting/codeimport/worker.py
Text conflict in lib/lp/registry/browser/tests/test_distroseries.py
Text conflict in lib/lp/registry/interfaces/distroseries.py
Text conflict in lib/lp/registry/model/distribution.py
Text conflict in lib/lp/registry/model/product.py
Text conflict in lib/lp/soyuz/browser/queue.py
Text conflict in lib/lp/soyuz/browser/tests/test_queue.py
Text conflict in lib/lp/soyuz/interfaces/archive.py
Text conflict in lib/lp/soyuz/interfaces/files.py
Text conflict in lib/lp/soyuz/model/packagesetsources.py
Text conflict in lib/lp/soyuz/scripts/tests/test_queue.py
Text conflict in lib/lp/soyuz/stories/webservice/xx-archive.txt
Text conflict in lib/lp/soyuz/tests/test_packageupload.py
Text conflict in lib/lp/testing/factory.py
Text conflict in scripts/code-import-worker.py
Text conflict in utilities/sourcedeps.cache
Text conflict in versions.cfg
To merge this branch: bzr merge lp:~jelmer/launchpad/bzr-code-imports
Reviewer Review Type Date Requested Status
Robert Collins code Pending
Gavin Panella Pending
Michael Hudson-Doyle Pending
Review via email: mp+68231@code.launchpad.net

This proposal supersedes a proposal from 2011-06-23.

This proposal has been superseded by a proposal from 2011-07-18.

Description of the change

Add support for importing code from Bazaar branches.

At the moment mirrors of remote Bazaar branches are created with completely different infrastructure as the code imports. This is confusing for users (bug 611837) and duplicates a lot of code. Several features are only available for code imports (bug 362622, bug 193607, bug 193607, bug 371484) and vice versa. Having shared infrastructure would also make it easier to fix several open bugs that affect both code imports and code mirrors (bug 519159, bug 136939)

Code imports are a bit heavier than mirrors at the moment, as they run on a separate machine and require an extra copy of the branch that is imported.

This branch only adds backend support for Bazaar branches, it does not yet add a UI which can add code imports of this kind nor does it migrate any of the existing code mirrors.

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote : Posted in a previous version of this proposal

I don't know the ins and outs of bzrlib or codehosting - for that I
guess that's why you've asked Michael to review - but the rest looks
good.

[1]

+ def test_partial(self):
+ # Skip tests of partial tests, as they are disabled for native imports
+ # at the moment.
+ return

You could use TestCase.skip() here:

    def test_partial(self):
        self.skip("Disabled for native imports at the moment.")

review: Approve
Revision history for this message
Jelmer Vernooij (jelmer) wrote : Posted in a previous version of this proposal

> I don't know the ins and outs of bzrlib or codehosting - for that I
> guess that's why you've asked Michael to review - but the rest looks
> good.
Thanks :)

I'm pretty confident about this code change (especially since nothing actually triggers the new code yet), but I'd like to get some feedback from more people (Michael, Jono?, Rob?) on to confirm this is the right direction to go in.

> + def test_partial(self):
> + # Skip tests of partial tests, as they are disabled for native
> imports
> + # at the moment.
> + return
>
> You could use TestCase.skip() here:
>
> def test_partial(self):
> self.skip("Disabled for native imports at the moment.")
I looked for things that raised SkipTest or TestSkipped and couldn't find any. I didn't know about TestCase.skip - thanks, fixed.

Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote : Posted in a previous version of this proposal

Some random comments: it would have been nice to see the things you had to do wrt the bzr upgrade

The fact that you put this line in suggests that the tests aren't very well isolated:

        branch.get_config().set_user_option("create_signatures", "never")

Does something need to inherit from bzrlib's TestCase? It's all a complete tangle though.

Is simply prohibiting branch references the right thing to do? The branch puller goes to some lengths to support them safely -- being able to dump all that code would surely be very nice. Relatedly, and on thinking about it I think this is a bit more serious, I think you might need to be careful about stacking -- it's contrived but you might be able to construct a branch that was stacked on some DC-internal branch and have that be imported so you can grab it.

In terms of overall direction, I'm all for removing duplication. I think the UI will require some effort (e.g. do we want to count bzr mirrors as "imported branches" on the code.launchpad.net frontpage?) but engineering wise, this looks fine, modulo the above comments.

review: Approve
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote : Posted in a previous version of this proposal

"Some random comments: it would have been nice to see the things you had to do wrt the bzr upgrade " ... in a separate branch, I meant to say.

Revision history for this message
Jelmer Vernooij (jelmer) wrote : Posted in a previous version of this proposal

On 06/24/2011 05:35 AM, Michael Hudson-Doyle wrote:
> Review: Approve
> Some random comments: it would have been nice to see the things you had to do wrt the bzr upgrade
This branch has two prerequisite branches, but I can only set one in
Launchpad. Hopefully the diff will get smaller when the update to the
newer bzr lands on lp:launchpad...
>
> The fact that you put this line in suggests that the tests aren't very well isolated:
>
> branch.get_config().set_user_option("create_signatures", "never")
>
> Does something need to inherit from bzrlib's TestCase? It's all a complete tangle though.
Perhaps; bzr's TestCase does a lot though, and I'm kindof worried that
mixing it in will break other things. It should do more than just this
ad-hoc override though. Perhaps reset the global bzr configuration in setUp?

>
> Is simply prohibiting branch references the right thing to do? The branch puller goes to some lengths to support them safely -- being able to dump all that code would surely be very nice. Relatedly, and on thinking about it I think this is a bit more serious, I think you might need to be careful about stacking -- it's contrived but you might be able to construct a branch that was stacked on some DC-internal branch and have that be imported so you can grab it.
Stacking is a very good point, and one that I had not considered -
thanks. I should probably also have another, closer, look at the branch
puller to see what it does and why.

For branch references, the easiest thing to do for the moment seemed to
be to just refuse to mirror them. If code mirrors support branch
references at the moment, we should keep that support.

>
> In terms of overall direction, I'm all for removing duplication. I think the UI will require some effort (e.g. do we want to count bzr mirrors as "imported branches" on the code.launchpad.net frontpage?) but engineering wise, this looks fine, modulo the above comments.
Thanks for having a look at this. I'm only just getting into this code
and am not very familiar with it yet.

Cheers,

Jelmer

Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote : Posted in a previous version of this proposal

On Fri, 24 Jun 2011 19:55:45 +0200, Jelmer Vernooij <email address hidden> wrote:
> On 06/24/2011 05:35 AM, Michael Hudson-Doyle wrote:
> > Review: Approve
> > Some random comments: it would have been nice to see the things you had to do wrt the bzr upgrade
> This branch has two prerequisite branches, but I can only set one in
> Launchpad. Hopefully the diff will get smaller when the update to the
> newer bzr lands on lp:launchpad...

Ah! I guess you could have created a branch with both of the
prerequisites merged in, but that's getting pretty tedious...

> >
> > The fact that you put this line in suggests that the tests aren't very well isolated:
> >
> > branch.get_config().set_user_option("create_signatures", "never")
> >
> > Does something need to inherit from bzrlib's TestCase? It's all a complete tangle though.
> Perhaps; bzr's TestCase does a lot though, and I'm kindof worried that
> mixing it in will break other things.

I'd be surprised if it broke other stuff on past experience, but you
might be right.

> It should do more than just this ad-hoc override though. Perhaps reset
> the global bzr configuration in setUp?

I guess what you want is a test fixture that just does the environment
isolation in bzrlib you can reuse here... but yes, doing something more
generic than just clearing create_signatures would be better, I think.

> >
> > Is simply prohibiting branch references the right thing to do? The branch puller goes to some lengths to support them safely -- being able to dump all that code would surely be very nice. Relatedly, and on thinking about it I think this is a bit more serious, I think you might need to be careful about stacking -- it's contrived but you might be able to construct a branch that was stacked on some DC-internal branch and have that be imported so you can grab it.
> Stacking is a very good point, and one that I had not considered -
> thanks. I should probably also have another, closer, look at the branch
> puller to see what it does and why.

For reasons I should probably be ashamed of, there seem to be two
implementations of 'safe opening' in Launchpad; one is around
lp.codehosting.bzrutils.safe_open and the other around
lp.codehosting.puller.worker.BranchMirrorer.

(looking at worker.py makes me realize how much code we can delete once
this branch is done, never mind how much can be deleted when the puller
is gone completely).

> For branch references, the easiest thing to do for the moment seemed to
> be to just refuse to mirror them. If code mirrors support branch
> references at the moment, we should keep that support.

Yeah, they do.

> >
> > In terms of overall direction, I'm all for removing duplication. I think the UI will require some effort (e.g. do we want to count bzr mirrors as "imported branches" on the code.launchpad.net frontpage?) but engineering wise, this looks fine, modulo the above comments.
> Thanks for having a look at this. I'm only just getting into this code
> and am not very familiar with it yet.

You seem to be doing fine :)

Cheers,
mwh

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'database/schema/patch-2208-74-0.sql'
2--- database/schema/patch-2208-74-0.sql 2011-06-27 10:38:18 +0000
3+++ database/schema/patch-2208-74-0.sql 2011-07-18 11:13:26 +0000
4@@ -1,3 +1,4 @@
5+<<<<<<< TREE
6 -- Copyright 2011 Canonical Ltd. This software is licensed under the
7 -- GNU Affero General Public License version 3 (see the file LICENSE).
8
9@@ -15,3 +16,22 @@
10
11 INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 74, 0);
12
13+=======
14+-- Copyright 2011 Canonical Ltd. This software is licensed under the
15+-- GNU Affero General Public License version 3 (see the file LICENSE).
16+
17+SET client_min_messages=ERROR;
18+
19+ALTER TABLE CodeImport DROP CONSTRAINT valid_vcs_details;
20+ALTER TABLE CodeImport ADD CONSTRAINT "valid_vcs_details" CHECK (
21+CASE
22+ WHEN rcs_type = 1 THEN cvs_root IS NOT NULL AND cvs_root <> ''::text AND cvs_module IS NOT NULL AND cvs_module <> ''::text AND url IS NULL
23+ WHEN rcs_type = ANY (ARRAY[2, 3]) THEN cvs_root IS NULL AND cvs_module IS NULL AND url IS NOT NULL AND valid_absolute_url(url)
24+ WHEN rcs_type = ANY (ARRAY[4, 5, 6]) THEN cvs_root IS NULL AND cvs_module IS NULL AND url IS NOT NULL
25+ ELSE false
26+END);
27+
28+
29+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 74, 0);
30+
31+>>>>>>> MERGE-SOURCE
32
33=== modified file 'lib/canonical/config/schema-lazr.conf'
34--- lib/canonical/config/schema-lazr.conf 2011-07-15 15:46:51 +0000
35+++ lib/canonical/config/schema-lazr.conf 2011-07-18 11:13:26 +0000
36@@ -504,6 +504,11 @@
37 # datatype: integer
38 default_interval_cvs: 43200
39
40+# The default value of the update interval of a code import from
41+# Bazaar, in seconds.
42+# datatype: integer
43+default_interval_bzr: 21600
44+
45 # Where the tarballs of foreign branches are uploaded for storage.
46 # datatype: string
47 foreign_tree_store: sftp://hoover@escudero/srv/importd/sources/
48
49=== modified file 'lib/canonical/launchpad/daemons/tachandler.py'
50--- lib/canonical/launchpad/daemons/tachandler.py 2011-06-21 17:13:47 +0000
51+++ lib/canonical/launchpad/daemons/tachandler.py 2011-07-18 11:13:26 +0000
52@@ -20,6 +20,7 @@
53 from fixtures import Fixture
54
55 from canonical.launchpad.daemons import readyservice
56+<<<<<<< TREE
57 from lp.services.osutils import (
58 get_pid_from_file,
59 kill_by_pidfile,
60@@ -27,6 +28,14 @@
61 two_stage_kill,
62 until_no_eintr,
63 )
64+=======
65+from lp.services.osutils import (
66+ get_pid_from_file,
67+ kill_by_pidfile,
68+ remove_if_exists,
69+ two_stage_kill,
70+ )
71+>>>>>>> MERGE-SOURCE
72
73
74 twistd_script = os.path.abspath(os.path.join(
75
76=== modified file 'lib/canonical/launchpad/doc/vocabulary-json.txt'
77--- lib/canonical/launchpad/doc/vocabulary-json.txt 2011-07-18 00:30:18 +0000
78+++ lib/canonical/launchpad/doc/vocabulary-json.txt 2011-07-18 11:13:26 +0000
79@@ -93,9 +93,14 @@
80 "api_uri": "/~commercial-admins",
81 "css": "sprite team",
82 "link_css": "js-action",
83+<<<<<<< TREE
84 "metadata": "team",
85 "title": "Commercial Subscription Admins",
86 "value": "commercial-admins"
87+=======
88+ "title": "Commercial Subscription Admins",
89+ "value": "commercial-admins"
90+>>>>>>> MERGE-SOURCE
91 }
92 ],
93 "total_size": 6
94@@ -114,9 +119,14 @@
95 "api_uri": "/~launchpad-buildd-admins",
96 "css": "sprite team",
97 "link_css": "js-action",
98+<<<<<<< TREE
99 "metadata": "team",
100 "title": "Launchpad Buildd Admins",
101 "value": "launchpad-buildd-admins"
102+=======
103+ "title": "Launchpad Buildd Admins",
104+ "value": "launchpad-buildd-admins"
105+>>>>>>> MERGE-SOURCE
106 }
107 ],
108 "total_size": 6
109
110=== modified file 'lib/lp/app/javascript/picker/person_picker.js'
111--- lib/lp/app/javascript/picker/person_picker.js 2011-07-14 12:46:31 +0000
112+++ lib/lp/app/javascript/picker/person_picker.js 2011-07-18 11:13:26 +0000
113@@ -1,10 +1,81 @@
114 /* Copyright 2011 Canonical Ltd. This software is licensed under the
115 * GNU Affero General Public License version 3 (see the file LICENSE).
116 *
117+<<<<<<< TREE
118 * @namespace Y.lazr.person-picker
119 * @requires lazr.picker
120 */
121 YUI.add('lazr.person-picker', function(Y) {
122+=======
123+ * @namespace Y.lp.app.widgets
124+ * @requires Y.lazr.picker
125+ */
126+YUI.add('lp.app.widgets', function(Y) {
127+var namespace = Y.namespace('lp.app.widgets');
128+
129+/**
130+ * Extend the lazr-js Picker.
131+ */
132+var Picker = function() {
133+ Picker.superclass.constructor.apply(this, arguments);
134+};
135+
136+Y.extend(Picker, Y.lazr.Picker, {
137+ // We want to render alt title slightly differently.
138+ _renderTitleUI: function(data) {
139+ var li_title = Y.Node.create(
140+ '<span></span>').addClass(Y.lazr.Picker.C_RESULT_TITLE);
141+ if (data.title === undefined) {
142+ // Display an empty element if data is empty.
143+ return li_title;
144+ }
145+ var title = this._text_or_link(
146+ data.title, data.title_link, data.link_css);
147+ li_title.appendChild(title);
148+ if (data.alt_title) {
149+ var alt_link = null;
150+ if (data.alt_title_link) {
151+ alt_link =Y.Node.create('<a></a>')
152+ .addClass(data.link_css)
153+ .addClass('discreet');
154+ alt_link.set('text', " Details...")
155+ .set('href', data.alt_title_link);
156+ Y.on('click', function(e) {
157+ e.halt();
158+ window.open(data.alt_title_link);
159+ }, alt_link);
160+ }
161+ li_title.appendChild('&nbsp;(');
162+ li_title.appendChild(document.createTextNode(data.alt_title));
163+ li_title.appendChild(')');
164+ if (alt_link !== null) {
165+ li_title.appendChild(alt_link);
166+ }
167+ }
168+ return li_title;
169+ },
170+
171+ /**
172+ * Create the widget's HTML components.
173+ * <p>
174+ * This method is invoked after renderUI is invoked for the Widget class
175+ * using YUI's aop infrastructure.
176+ * </p>
177+ *
178+ * @method _renderUIPicker
179+ * @protected
180+ */
181+ _renderUIPicker: function() {
182+ Picker.superclass._renderUIPicker.apply(this, arguments);
183+ var body = this._batches_box.get('parentNode');
184+ body.removeChild(this._batches_box);
185+ this._results_box.insert(this._batches_box, 'after');
186+ }
187+});
188+
189+Picker.NAME = 'picker';
190+namespace.Picker = Picker;
191+>>>>>>> MERGE-SOURCE
192
193 /*
194 * Extend the picker into the PersonPicker
195
196=== modified file 'lib/lp/app/javascript/picker/picker_patcher.js'
197--- lib/lp/app/javascript/picker/picker_patcher.js 2011-07-18 05:32:40 +0000
198+++ lib/lp/app/javascript/picker/picker_patcher.js 2011-07-18 11:13:26 +0000
199@@ -93,8 +93,12 @@
200 if (link === null || !show_remove_button) {
201 remove_button.addClass('yui3-picker-hidden');
202 } else {
203+<<<<<<< TREE
204 remove_button.removeClass('yui3-picker-hidden');
205 update_button_text();
206+=======
207+ remove_button.removeClass('yui3-picker-hidden');
208+>>>>>>> MERGE-SOURCE
209 }
210 }
211
212
213=== modified file 'lib/lp/app/javascript/picker/tests/test_personpicker.js'
214--- lib/lp/app/javascript/picker/tests/test_personpicker.js 2011-07-15 02:20:36 +0000
215+++ lib/lp/app/javascript/picker/tests/test_personpicker.js 2011-07-18 11:13:26 +0000
216@@ -6,6 +6,7 @@
217 'lazr.picker', 'lazr.person-picker', 'lp.app.picker',
218 'node-event-simulate', function(Y) {
219
220+<<<<<<< TREE
221 var Assert = Y.Assert;
222
223 /* Helper function to clean up a dynamically added widget instance. */
224@@ -144,6 +145,78 @@
225 picker.set('results', []);
226 } else {
227 picker.set('results', this.vocabulary);
228+=======
229+ var suite = new Y.Test.Suite("lp.app.widgets.PersonPicker Tests");
230+
231+ suite.add(new Y.Test.Case({
232+ name: 'personpicker',
233+
234+ setUp: function() {
235+ window.LP = {
236+ links: {me: '/~no-one'},
237+ cache: {}
238+ };
239+ },
240+
241+ test_render: function () {
242+ var personpicker = new Y.lp.app.widgets.PersonPicker();
243+ personpicker.render();
244+ personpicker.show();
245+
246+ // The extra buttons section exists
247+ Y.Assert.isNotNull(Y.one('.extra-form-buttons'));
248+ Y.Assert.isNotUndefined(personpicker.assign_me_button);
249+ Y.Assert.isNotUndefined(personpicker.remove_button);
250+ },
251+
252+ test_search_field_focus: function () {
253+ var personpicker = new Y.lp.app.widgets.PersonPicker();
254+ personpicker.render();
255+ personpicker.hide();
256+
257+ var got_focus = false;
258+ personpicker._search_input.on('focus', function(e) {
259+ got_focus = true;
260+ });
261+ personpicker.show();
262+ Y.Assert.isTrue(got_focus, "search input did not get focus.");
263+ },
264+
265+ test_buttons: function () {
266+ var personpicker = new Y.lp.app.widgets.PersonPicker();
267+ personpicker.render();
268+ personpicker.show();
269+
270+ // Patch the picker so the assign_me and remove methods can be
271+ // tested.
272+ var data = null;
273+ personpicker.on('save', function (result) {
274+ data = result.value;
275+ });
276+ var remove = Y.one('.yui-picker-remove-button');
277+ remove.simulate('click');
278+ Y.Assert.areEqual('', data);
279+
280+ var assign_me = Y.one('.yui-picker-assign-me-button');
281+ assign_me.simulate('click');
282+ Y.Assert.areEqual('no-one', data);
283+ },
284+
285+ test_buttons_config: function () {
286+ cfg = {
287+ show_assign_me_button: false,
288+ show_remove_button: false
289+ };
290+
291+ personpicker = new Y.lp.app.widgets.PersonPicker(cfg);
292+ personpicker.render();
293+ personpicker.show();
294+
295+ Y.Assert.isNotNull(Y.one('.extra-form-buttons'));
296+ Y.Assert.isUndefined(personpicker.remove_button);
297+ Y.Assert.isUndefined(personpicker.assign_me_button);
298+
299+>>>>>>> MERGE-SOURCE
300 }
301 },
302
303
304=== modified file 'lib/lp/app/javascript/picker/tests/test_picker_patcher.js'
305--- lib/lp/app/javascript/picker/tests/test_picker_patcher.js 2011-07-18 05:32:40 +0000
306+++ lib/lp/app/javascript/picker/tests/test_picker_patcher.js 2011-07-18 11:13:26 +0000
307@@ -231,6 +231,7 @@
308 }));
309
310 /*
311+<<<<<<< TREE
312 * Test cases for assign and remove buttons.
313 */
314 suite.add(new Y.Test.Case({
315@@ -471,6 +472,184 @@
316 }));
317
318 /*
319+=======
320+ * Test cases for assign and remove buttons.
321+ */
322+suite.add(new Y.Test.Case({
323+
324+ name: 'picker_assign_remove_button',
325+
326+ setUp: function() {
327+ var i;
328+ this.vocabulary = new Array(121);
329+ for (i = 0; i <5; i++) {
330+ this.vocabulary[i] = {
331+ "value": "value-" + i,
332+ "title": "title-" + i,
333+ "css": "sprite-person",
334+ "description": "description-" + i,
335+ "api_uri": "~/fred-" + i};
336+ }
337+ this.ME = '/~me';
338+ window.LP = {
339+ links: {
340+ me: this.ME
341+ },
342+ cache: {}
343+ };
344+
345+ // We patch Launchpad client to return some fake data for the patch
346+ // operation.
347+ Y.lp.client.Launchpad = function() {};
348+ Y.lp.client.Launchpad.prototype.patch =
349+ function(uri, representation, config, headers) {
350+ // our setup assumes success, so we just do the success
351+ // callback.
352+ var entry_repr = {
353+ 'lp_html': {'test_link': '<a href="/~me">Content</a>'}
354+ };
355+ var result = new Y.lp.client.Entry(
356+ null, entry_repr, "a_self_link");
357+ config.on.success(result);
358+ };
359+
360+ },
361+
362+ tearDown: function() {
363+ cleanup_widget(this.picker);
364+ delete window.LP;
365+ },
366+
367+ create_picker: function(show_assign_me, show_remove, field_value) {
368+ if (field_value !== undefined) {
369+ var data_box = Y.one('#picker_id .yui3-activator-data-box');
370+ data_box.appendChild(Y.Node.create('<a>Content</a>'));
371+ data_box.one('a').set('href', field_value);
372+ }
373+
374+ var config = {
375+ "step_title": "Choose someone",
376+ "header": "Pick Someone",
377+ "validate_callback": null,
378+ "show_search_box": true,
379+ "show_assign_me_button": show_assign_me,
380+ "show_remove_button": show_remove
381+ };
382+ this.picker = Y.lp.app.picker.addPickerPatcher(
383+ this.vocabulary,
384+ "foo/bar",
385+ "test_link",
386+ "picker_id",
387+ config);
388+ },
389+
390+ _check_button_state: function(btn_class, is_visible) {
391+ var assign_me_button = Y.one(btn_class);
392+ Assert.isNotNull(assign_me_button);
393+ if (is_visible) {
394+ Assert.isFalse(
395+ assign_me_button.hasClass('yui3-picker-hidden'),
396+ btn_class + " should be visible but is hidden");
397+ } else {
398+ Assert.isTrue(
399+ assign_me_button.hasClass('yui3-picker-hidden'),
400+ btn_class + " should be hidden but is visible");
401+ }
402+ },
403+
404+ _check_assign_me_button_state: function(is_visible) {
405+ this._check_button_state('.yui-picker-assign-me-button', is_visible);
406+ },
407+
408+ _check_remove_button_state: function(is_visible) {
409+ this._check_button_state('.yui-picker-remove-button', is_visible);
410+ },
411+
412+ test_picker_has_assign_me_button: function() {
413+ // The assign me button is shown.
414+ this.create_picker(true, true);
415+ this.picker.render();
416+ this._check_assign_me_button_state(true);
417+ },
418+
419+ test_picker_no_assign_me_button_unless_configured: function() {
420+ // The assign me button is only rendered if show_assign_me_button
421+ // config setting is true.
422+ this.create_picker(false, true);
423+ this.picker.render();
424+ Assert.isNull(Y.one('.yui-picker-assign-me-button'));
425+ },
426+
427+ test_picker_no_assign_me_button_if_value_is_me: function() {
428+ // The assign me button is not shown if the picker is created for a
429+ // field where the value is "me".
430+ this.create_picker(true, true, this.ME);
431+ this.picker.render();
432+ this._check_assign_me_button_state(false);
433+ },
434+
435+ test_picker_assign_me_button_hide_on_save: function() {
436+ // The assign me button is shown initially but hidden if the picker
437+ // saves a value equal to 'me'.
438+ this.create_picker(true, true);
439+ this._check_assign_me_button_state(true);
440+ this.picker.set('results', this.vocabulary);
441+ this.picker.render();
442+ simulate(
443+ this.picker.get('boundingBox').one('.yui3-picker-results'),
444+ 'li:nth-child(1)', 'click');
445+ this._check_assign_me_button_state(false);
446+ },
447+
448+ test_picker_no_remove_button_if_null_value: function() {
449+ // The remove button is not shown if the picker is created for a field
450+ // which has a null value.
451+ this.create_picker(true, true);
452+ this.picker.render();
453+ this._check_remove_button_state(false);
454+ },
455+
456+ test_picker_has_remove_button_if_value: function() {
457+ // The remove button is shown if the picker is created for a field
458+ // which has a value.
459+ this.create_picker(true, true, this.ME);
460+ this.picker.render();
461+ this._check_remove_button_state(true);
462+ },
463+
464+ test_picker_no_remove_button_unless_configured: function() {
465+ // The remove button is only rendered if show_remove_button setting is
466+ // true.
467+ this.create_picker(true, false, this.ME);
468+ this.picker.render();
469+ Assert.isNull(Y.one('.yui-picker-remove-button'));
470+ },
471+
472+ test_picker_remove_button_clicked: function() {
473+ // The remove button is hidden once a picker value has been removed.
474+ // And the assign me button is shown.
475+ this.create_picker(true, true, this.ME);
476+ this.picker.render();
477+ var remove = Y.one('.yui-picker-remove-button');
478+ remove.simulate('click');
479+ this._check_remove_button_state(false);
480+ this._check_assign_me_button_state(true);
481+ },
482+
483+ test_picker_assign_me_button_clicked: function() {
484+ // The assign me button is hidden once it is clicked.
485+ // And the remove button is shown.
486+ this.create_picker(true, true);
487+ this.picker.render();
488+ var remove = Y.one('.yui-picker-assign-me-button');
489+ remove.simulate('click');
490+ this._check_remove_button_state(true);
491+ this._check_assign_me_button_state(false);
492+ }
493+}));
494+
495+/*
496+>>>>>>> MERGE-SOURCE
497 * Test cases for a picker with a large vocabulary.
498 */
499 suite.add(new Y.Test.Case({
500
501=== modified file 'lib/lp/code/errors.py'
502--- lib/lp/code/errors.py 2011-06-23 21:09:20 +0000
503+++ lib/lp/code/errors.py 2011-07-18 11:13:26 +0000
504@@ -182,6 +182,7 @@
505 class BranchMergeProposalExists(InvalidBranchMergeProposal):
506 """Raised if there is already a matching BranchMergeProposal."""
507
508+<<<<<<< TREE
509 def __init__(self, existing_proposal):
510 super(BranchMergeProposalExists, self).__init__(
511 'There is already a branch merge proposal registered for '
512@@ -190,6 +191,8 @@
513 existing_proposal.target_branch.displayname))
514 self.existing_proposal = existing_proposal
515
516+=======
517+>>>>>>> MERGE-SOURCE
518
519 class InvalidNamespace(Exception):
520 """Raised when someone tries to lookup a namespace with a bad name.
521
522=== modified file 'lib/lp/code/mail/codeimport.py'
523--- lib/lp/code/mail/codeimport.py 2011-05-27 21:12:25 +0000
524+++ lib/lp/code/mail/codeimport.py 2011-07-18 11:13:26 +0000
525@@ -51,6 +51,7 @@
526 RevisionControlSystems.BZR_SVN: 'subversion',
527 RevisionControlSystems.GIT: 'git',
528 RevisionControlSystems.HG: 'mercurial',
529+ RevisionControlSystems.BZR: 'bazaar',
530 }
531 body = get_email_template('new-code-import.txt') % {
532 'person': code_import.registrant.displayname,
533@@ -123,7 +124,8 @@
534 elif code_import.rcs_type in (RevisionControlSystems.SVN,
535 RevisionControlSystems.BZR_SVN,
536 RevisionControlSystems.GIT,
537- RevisionControlSystems.HG):
538+ RevisionControlSystems.HG,
539+ RevisionControlSystems.BZR):
540 if CodeImportEventDataType.OLD_URL in event_data:
541 old_url = event_data[CodeImportEventDataType.OLD_URL]
542 body.append(
543
544=== modified file 'lib/lp/code/model/branchjob.py'
545--- lib/lp/code/model/branchjob.py 2011-07-11 17:48:50 +0000
546+++ lib/lp/code/model/branchjob.py 2011-07-18 11:13:26 +0000
547@@ -107,11 +107,15 @@
548 from lp.scripts.helpers import TransactionFreeOperation
549 from lp.services.job.interfaces.job import JobStatus
550 from lp.services.job.model.job import Job
551+<<<<<<< TREE
552 from lp.services.job.runner import (
553 BaseRunnableJob,
554 BaseRunnableJobSource,
555 )
556 from lp.services.utils import RegisteredSubclass
557+=======
558+from lp.services.job.runner import BaseRunnableJob
559+>>>>>>> MERGE-SOURCE
560 from lp.translations.interfaces.translationimportqueue import (
561 ITranslationImportQueue,
562 )
563@@ -294,6 +298,18 @@
564 raise SQLObjectNotFound(
565 'No occurrence of %s has key %s' % (cls.__name__, key))
566
567+ @classmethod
568+ def get(cls, key):
569+ """Return the instance of this class whose key is supplied.
570+
571+ :raises: SQLObjectNotFound
572+ """
573+ instance = IStore(BranchJob).get(BranchJob, key)
574+ if instance is None or instance.job_type != cls.class_job_type:
575+ raise SQLObjectNotFound(
576+ 'No occurrence of %s has key %s' % (cls.__name__, key))
577+ return cls(instance)
578+
579 def getOopsVars(self):
580 """See `IRunnableJob`."""
581 vars = BaseRunnableJob.getOopsVars(self)
582
583=== modified file 'lib/lp/code/model/codeimport.py'
584--- lib/lp/code/model/codeimport.py 2011-04-27 01:42:46 +0000
585+++ lib/lp/code/model/codeimport.py 2011-07-18 11:13:26 +0000
586@@ -116,6 +116,8 @@
587 config.codeimport.default_interval_git,
588 RevisionControlSystems.HG:
589 config.codeimport.default_interval_hg,
590+ RevisionControlSystems.BZR:
591+ config.codeimport.default_interval_bzr,
592 }
593 seconds = default_interval_dict[self.rcs_type]
594 return timedelta(seconds=seconds)
595@@ -133,7 +135,8 @@
596 RevisionControlSystems.SVN,
597 RevisionControlSystems.GIT,
598 RevisionControlSystems.BZR_SVN,
599- RevisionControlSystems.HG):
600+ RevisionControlSystems.HG,
601+ RevisionControlSystems.BZR):
602 return self.url
603 else:
604 raise AssertionError(
605@@ -252,7 +255,8 @@
606 elif rcs_type in (RevisionControlSystems.SVN,
607 RevisionControlSystems.BZR_SVN,
608 RevisionControlSystems.GIT,
609- RevisionControlSystems.HG):
610+ RevisionControlSystems.HG,
611+ RevisionControlSystems.BZR):
612 assert cvs_root is None and cvs_module is None
613 assert url is not None
614 else:
615@@ -262,7 +266,8 @@
616 if review_status is None:
617 # Auto approve git and hg imports.
618 if rcs_type in (
619- RevisionControlSystems.GIT, RevisionControlSystems.HG):
620+ RevisionControlSystems.GIT, RevisionControlSystems.HG,
621+ RevisionControlSystems.BZR):
622 review_status = CodeImportReviewStatus.REVIEWED
623 else:
624 review_status = CodeImportReviewStatus.NEW
625
626=== modified file 'lib/lp/code/model/codeimportevent.py'
627--- lib/lp/code/model/codeimportevent.py 2010-10-17 22:51:50 +0000
628+++ lib/lp/code/model/codeimportevent.py 2011-07-18 11:13:26 +0000
629@@ -269,7 +269,8 @@
630 if code_import.rcs_type in (RevisionControlSystems.SVN,
631 RevisionControlSystems.BZR_SVN,
632 RevisionControlSystems.GIT,
633- RevisionControlSystems.HG):
634+ RevisionControlSystems.HG,
635+ RevisionControlSystems.BZR):
636 yield 'URL', code_import.url
637 elif code_import.rcs_type == RevisionControlSystems.CVS:
638 yield 'CVS_ROOT', code_import.cvs_root
639
640=== modified file 'lib/lp/code/model/tests/test_codeimport.py'
641--- lib/lp/code/model/tests/test_codeimport.py 2011-05-27 21:12:25 +0000
642+++ lib/lp/code/model/tests/test_codeimport.py 2011-07-18 11:13:26 +0000
643@@ -71,6 +71,20 @@
644 # No job is created for the import.
645 self.assertIs(None, code_import.import_job)
646
647+ def test_new_svn_import_svn_scheme(self):
648+ """A subversion import can use the svn:// scheme."""
649+ code_import = CodeImportSet().new(
650+ registrant=self.factory.makePerson(),
651+ target=IBranchTarget(self.factory.makeProduct()),
652+ branch_name='imported',
653+ rcs_type=RevisionControlSystems.SVN,
654+ url=self.factory.getUniqueURL(scheme="svn"))
655+ self.assertEqual(
656+ CodeImportReviewStatus.NEW,
657+ code_import.review_status)
658+ # No job is created for the import.
659+ self.assertIs(None, code_import.import_job)
660+
661 def test_reviewed_svn_import(self):
662 """A specific review status can be set for a new import."""
663 code_import = CodeImportSet().new(
664@@ -117,6 +131,21 @@
665 # A job is created for the import.
666 self.assertIsNot(None, code_import.import_job)
667
668+ def test_git_import_git_scheme(self):
669+ """A git import can have a git:// style URL."""
670+ code_import = CodeImportSet().new(
671+ registrant=self.factory.makePerson(),
672+ target=IBranchTarget(self.factory.makeProduct()),
673+ branch_name='imported',
674+ rcs_type=RevisionControlSystems.GIT,
675+ url=self.factory.getUniqueURL(scheme="git"),
676+ review_status=None)
677+ self.assertEqual(
678+ CodeImportReviewStatus.REVIEWED,
679+ code_import.review_status)
680+ # A job is created for the import.
681+ self.assertIsNot(None, code_import.import_job)
682+
683 def test_git_import_reviewed(self):
684 """A new git import is always reviewed by default."""
685 code_import = CodeImportSet().new(
686@@ -147,6 +176,21 @@
687 # A job is created for the import.
688 self.assertIsNot(None, code_import.import_job)
689
690+ def test_bzr_import_reviewed(self):
691+ """A new bzr import is always reviewed by default."""
692+ code_import = CodeImportSet().new(
693+ registrant=self.factory.makePerson(),
694+ target=IBranchTarget(self.factory.makeProduct()),
695+ branch_name='mirrored',
696+ rcs_type=RevisionControlSystems.BZR,
697+ url=self.factory.getUniqueURL(),
698+ review_status=None)
699+ self.assertEqual(
700+ CodeImportReviewStatus.REVIEWED,
701+ code_import.review_status)
702+ # A job is created for the import.
703+ self.assertIsNot(None, code_import.import_job)
704+
705 def test_junk_code_import_rejected(self):
706 """You are not allowed to create code imports targetting +junk."""
707 registrant = self.factory.makePerson()
708
709=== modified file 'lib/lp/codehosting/codeimport/tests/servers.py'
710--- lib/lp/codehosting/codeimport/tests/servers.py 2011-06-02 10:48:54 +0000
711+++ lib/lp/codehosting/codeimport/tests/servers.py 2011-07-18 11:13:26 +0000
712@@ -4,6 +4,7 @@
713 """Server classes that know how to create various kinds of foreign archive."""
714
715 __all__ = [
716+ 'BzrServer',
717 'CVSServer',
718 'GitServer',
719 'MercurialServer',
720@@ -21,6 +22,8 @@
721 import tempfile
722 import time
723
724+from bzrlib.bzrdir import BzrDir
725+from bzrlib.branchbuilder import BranchBuilder
726 from bzrlib.tests.treeshape import build_tree_contents
727 from bzrlib.transport import Server
728 from bzrlib.urlutils import (
729@@ -246,3 +249,21 @@
730 f.close()
731 repo[None].add([filename])
732 repo.commit(text='<The commit message>', user='jane Foo <joe@foo.com>')
733+
734+
735+class BzrServer(Server):
736+
737+ def __init__(self, repo_url):
738+ super(BzrServer, self).__init__()
739+ self.repo_url = repo_url
740+
741+ def makeRepo(self, tree_contents):
742+ branch = BzrDir.create_branch_convenience(self.repo_url)
743+ branch.get_config().set_user_option("create_signatures", "never")
744+ builder = BranchBuilder(branch=branch)
745+ actions = [('add', ('', 'tree-root', 'directory', None))]
746+ actions += [('add', (path, path+'-id', 'file', content)) for (path,
747+ content) in tree_contents]
748+ builder.build_snapshot(None, None,
749+ actions, committer='Joe Foo <joe@foo.com>',
750+ message=u'<The commit message>')
751
752=== modified file 'lib/lp/codehosting/codeimport/tests/test_worker.py'
753--- lib/lp/codehosting/codeimport/tests/test_worker.py 2011-06-21 05:38:15 +0000
754+++ lib/lp/codehosting/codeimport/tests/test_worker.py 2011-07-18 11:13:26 +0000
755@@ -17,6 +17,9 @@
756 Branch,
757 BranchReferenceFormat,
758 )
759+from bzrlib.branchbuilder import (
760+ BranchBuilder,
761+ )
762 from bzrlib.bzrdir import (
763 BzrDir,
764 BzrDirFormat,
765@@ -24,12 +27,10 @@
766 )
767 from bzrlib.errors import (
768 NoSuchFile,
769- NotBranchError,
770 )
771 from bzrlib.tests import TestCaseWithTransport
772 from bzrlib import trace
773 from bzrlib.transport import get_transport
774-from bzrlib.upgrade import upgrade
775 from bzrlib.urlutils import (
776 join as urljoin,
777 local_path_from_url,
778@@ -49,6 +50,7 @@
779 extract_tarball,
780 )
781 from lp.codehosting.codeimport.tests.servers import (
782+ BzrServer,
783 CVSServer,
784 GitServer,
785 MercurialServer,
786@@ -56,6 +58,7 @@
787 )
788 from lp.codehosting.codeimport.worker import (
789 BazaarBranchStore,
790+ BzrImportWorker,
791 BzrSvnImportWorker,
792 CodeImportWorkerExitCode,
793 CSCVSImportWorker,
794@@ -1001,10 +1004,10 @@
795 # import should be rejected.
796 args = {'rcstype': self.rcstype}
797 reference_url = self.createBranchReference()
798- if self.rcstype in ('git', 'bzr-svn', 'hg'):
799+ if self.rcstype in ('git', 'bzr-svn', 'hg', 'bzr'):
800 args['url'] = reference_url
801 else:
802- raise AssertionError("unexpected rcs_type %r" % self.rcs_type)
803+ raise AssertionError("unexpected rcs_type %r" % self.rcstype)
804 source_details = self.factory.makeCodeImportSourceDetails(**args)
805 worker = self.makeImportWorker(source_details)
806 self.assertEqual(
807@@ -1162,5 +1165,51 @@
808 self.bazaar_store, logging.getLogger())
809
810
811+class TestBzrImport(WorkerTest, TestActualImportMixin,
812+ PullingImportWorkerTests):
813+
814+ rcstype = 'bzr'
815+
816+ def setUp(self):
817+ super(TestBzrImport, self).setUp()
818+ self.setUpImport()
819+
820+ def makeImportWorker(self, source_details):
821+ """Make a new `ImportWorker`."""
822+ return BzrImportWorker(
823+ source_details, self.get_transport('import_data'),
824+ self.bazaar_store, logging.getLogger())
825+
826+ def makeForeignCommit(self, source_details):
827+ """Change the foreign tree, generating exactly one commit."""
828+ branch = Branch.open(source_details.url)
829+ builder = BranchBuilder(branch=branch)
830+ builder.build_commit(message=self.factory.getUniqueString(),
831+ committer="Joe Random Hacker <joe@example.com>")
832+ self.foreign_commit_count += 1
833+
834+ def makeSourceDetails(self, branch_name, files):
835+ """Make Bzr `CodeImportSourceDetails` pointing at a real Bzr repo.
836+ """
837+ repository_path = self.makeTemporaryDirectory()
838+ bzr_server = BzrServer(repository_path)
839+ bzr_server.start_server()
840+ self.addCleanup(bzr_server.stop_server)
841+
842+ bzr_server.makeRepo(files)
843+ self.foreign_commit_count = 1
844+
845+ return self.factory.makeCodeImportSourceDetails(
846+ rcstype='bzr', url=repository_path)
847+
848+ def test_partial(self):
849+ self.skip(
850+ "Partial fetching is not supported for native bzr branches "
851+ "at the moment.")
852+
853+ def test_unsupported_feature(self):
854+ self.skip("All Bazaar features are supported by Bazaar.")
855+
856+
857 def test_suite():
858 return unittest.TestLoader().loadTestsFromName(__name__)
859
860=== modified file 'lib/lp/codehosting/codeimport/tests/test_workermonitor.py'
861--- lib/lp/codehosting/codeimport/tests/test_workermonitor.py 2011-06-16 23:43:04 +0000
862+++ lib/lp/codehosting/codeimport/tests/test_workermonitor.py 2011-07-18 11:13:26 +0000
863@@ -50,6 +50,7 @@
864 from lp.code.model.codeimportjob import CodeImportJob
865 from lp.codehosting import load_optional_plugin
866 from lp.codehosting.codeimport.tests.servers import (
867+ BzrServer,
868 CVSServer,
869 GitServer,
870 MercurialServer,
871@@ -682,6 +683,16 @@
872
873 return self.factory.makeCodeImport(hg_repo_url=self.repo_path)
874
875+ def makeBzrCodeImport(self):
876+ """Make a `CodeImport` that points to a real Bazaar branch."""
877+ self.bzr_server = BzrServer(self.repo_path)
878+ self.bzr_server.start_server()
879+ self.addCleanup(self.bzr_server.stop_server)
880+
881+ self.bzr_server.makeRepo([('README', 'contents')])
882+ self.foreign_commit_count = 1
883+ return self.factory.makeCodeImport(bzr_branch_url=self.repo_path)
884+
885 def getStartedJobForImport(self, code_import):
886 """Get a started `CodeImportJob` for `code_import`.
887
888@@ -782,6 +793,15 @@
889 result = self.performImport(job_id)
890 return result.addCallback(self.assertImported, code_import_id)
891
892+ def test_import_bzr(self):
893+ # Create a Bazaar CodeImport and import it.
894+ job = self.getStartedJobForImport(self.makeBzrCodeImport())
895+ code_import_id = job.code_import.id
896+ job_id = job.id
897+ self.layer.txn.commit()
898+ result = self.performImport(job_id)
899+ return result.addCallback(self.assertImported, code_import_id)
900+
901 # XXX 2010-03-24 MichaelHudson, bug=541526: This test fails intermittently
902 # in EC2.
903 def DISABLED_test_import_bzrsvn(self):
904
905=== modified file 'lib/lp/codehosting/codeimport/worker.py'
906--- lib/lp/codehosting/codeimport/worker.py 2011-06-21 05:38:15 +0000
907+++ lib/lp/codehosting/codeimport/worker.py 2011-07-18 11:13:26 +0000
908@@ -6,6 +6,7 @@
909 __metaclass__ = type
910 __all__ = [
911 'BazaarBranchStore',
912+ 'BzrImportWorker',
913 'BzrSvnImportWorker',
914 'CSCVSImportWorker',
915 'CodeImportSourceDetails',
916@@ -190,9 +191,9 @@
917
918 :ivar branch_id: The id of the branch associated to this code import, used
919 for locating the existing import and the foreign tree.
920- :ivar rcstype: 'svn' or 'cvs' as appropriate.
921+ :ivar rcstype: 'svn', 'cvs', 'hg', 'git', 'bzr-svn', 'bzr' as appropriate.
922 :ivar url: The branch URL if rcstype in ['svn', 'bzr-svn',
923- 'git'], None otherwise.
924+ 'git', 'hg', 'bzr'], None otherwise.
925 :ivar cvs_root: The $CVSROOT if rcstype == 'cvs', None otherwise.
926 :ivar cvs_module: The CVS module if rcstype == 'cvs', None otherwise.
927 """
928@@ -210,7 +211,7 @@
929 """Convert command line-style arguments to an instance."""
930 branch_id = int(arguments.pop(0))
931 rcstype = arguments.pop(0)
932- if rcstype in ['svn', 'bzr-svn', 'git', 'hg']:
933+ if rcstype in ['svn', 'bzr-svn', 'git', 'hg', 'bzr']:
934 [url] = arguments
935 cvs_root = cvs_module = None
936 elif rcstype == 'cvs':
937@@ -237,6 +238,8 @@
938 return cls(branch_id, 'git', str(code_import.url))
939 elif code_import.rcs_type == RevisionControlSystems.HG:
940 return cls(branch_id, 'hg', str(code_import.url))
941+ elif code_import.rcs_type == RevisionControlSystems.BZR:
942+ return cls(branch_id, 'bzr', str(code_import.url))
943 else:
944 raise AssertionError("Unknown rcstype %r." % code_import.rcs_type)
945
946@@ -244,7 +247,7 @@
947 """Return a list of arguments suitable for passing to a child process.
948 """
949 result = [str(self.branch_id), self.rcstype]
950- if self.rcstype in ['svn', 'bzr-svn', 'git', 'hg']:
951+ if self.rcstype in ['svn', 'bzr-svn', 'git', 'hg', 'bzr']:
952 result.append(self.url)
953 elif self.rcstype == 'cvs':
954 result.append(self.cvs_root)
955@@ -619,9 +622,19 @@
956 except NotBranchError:
957 pass
958 else:
959+<<<<<<< TREE
960 self._logger.info("No branch found at remote location.")
961 return CodeImportWorkerExitCode.FAILURE_INVALID
962 remote_branch = format.open(transport).open_branch()
963+=======
964+ self._logger.info("No branch found at remote location.")
965+ return CodeImportWorkerExitCode.FAILURE_INVALID
966+ remote_dir = format.open(transport)
967+ if remote_dir.get_branch_reference() is not None:
968+ self._logger.info("Remote branch is a branch reference.")
969+ return CodeImportWorkerExitCode.FAILURE_INVALID
970+ remote_branch = remote_dir.open_branch()
971+>>>>>>> MERGE-SOURCE
972 remote_branch_tip = remote_branch.last_revision()
973 inter_branch = InterBranch.get(remote_branch, bazaar_branch)
974 self._logger.info("Importing branch.")
975@@ -822,3 +835,21 @@
976 """See `PullingImportWorker.probers`."""
977 from bzrlib.plugins.svn import SvnRemoteProber
978 return [SvnRemoteProber]
979+
980+
981+class BzrImportWorker(PullingImportWorker):
982+ """An import worker for importing Bazaar branches."""
983+
984+ invalid_branch_exceptions = []
985+ unsupported_feature_exceptions = []
986+
987+ def getRevisionLimit(self):
988+ """See `PullingImportWorker.getRevisionLimit`."""
989+ # For now, just grab the whole branch at once
990+ return None
991+
992+ @property
993+ def probers(self):
994+ """See `PullingImportWorker.probers`."""
995+ from bzrlib.bzrdir import BzrProber, RemoteBzrProber
996+ return [BzrProber, RemoteBzrProber]
997
998=== modified file 'lib/lp/registry/browser/productseries.py'
999--- lib/lp/registry/browser/productseries.py 2011-05-27 21:12:25 +0000
1000+++ lib/lp/registry/browser/productseries.py 2011-07-18 11:13:26 +0000
1001@@ -827,15 +827,6 @@
1002 ))
1003
1004
1005-class RevisionControlSystemsExtended(RevisionControlSystems):
1006- """External RCS plus Bazaar."""
1007- BZR = DBItem(99, """
1008- Bazaar
1009-
1010- External Bazaar branch.
1011- """)
1012-
1013-
1014 class SetBranchForm(Interface):
1015 """The fields presented on the form for setting a branch."""
1016
1017@@ -844,7 +835,7 @@
1018 ['cvs_module'])
1019
1020 rcs_type = Choice(title=_("Type of RCS"),
1021- required=False, vocabulary=RevisionControlSystemsExtended,
1022+ required=False, vocabulary=RevisionControlSystems,
1023 description=_(
1024 "The version control system to import from. "))
1025
1026@@ -908,7 +899,7 @@
1027 @property
1028 def initial_values(self):
1029 return dict(
1030- rcs_type=RevisionControlSystemsExtended.BZR,
1031+ rcs_type=RevisionControlSystems.BZR,
1032 branch_type=LINK_LP_BZR,
1033 branch_location=self.context.branch)
1034
1035@@ -989,7 +980,7 @@
1036 self.setFieldError(
1037 'rcs_type',
1038 'You must specify the type of RCS for the remote host.')
1039- elif rcs_type == RevisionControlSystemsExtended.CVS:
1040+ elif rcs_type == RevisionControlSystems.CVS:
1041 if 'cvs_module' not in data:
1042 self.setFieldError(
1043 'cvs_module',
1044@@ -1022,8 +1013,9 @@
1045 # Extend the allowed schemes for the repository URL based on
1046 # rcs_type.
1047 extra_schemes = {
1048- RevisionControlSystemsExtended.BZR_SVN: ['svn'],
1049- RevisionControlSystemsExtended.GIT: ['git'],
1050+ RevisionControlSystems.BZR_SVN: ['svn'],
1051+ RevisionControlSystems.GIT: ['git'],
1052+ RevisionControlSystems.BZR: ['bzr'],
1053 }
1054 schemes.update(extra_schemes.get(rcs_type, []))
1055 return schemes
1056@@ -1050,7 +1042,7 @@
1057 # The branch location is not required for validation.
1058 self._setRequired(['branch_location'], False)
1059 # The cvs_module is required if it is a CVS import.
1060- if rcs_type == RevisionControlSystemsExtended.CVS:
1061+ if rcs_type == RevisionControlSystems.CVS:
1062 self._setRequired(['cvs_module'], True)
1063 else:
1064 raise AssertionError("Unknown branch type %s" % branch_type)
1065@@ -1110,7 +1102,7 @@
1066 # Either create an externally hosted bzr branch
1067 # (a.k.a. 'mirrored') or create a new code import.
1068 rcs_type = data.get('rcs_type')
1069- if rcs_type == RevisionControlSystemsExtended.BZR:
1070+ if rcs_type == RevisionControlSystems.BZR:
1071 branch = self._createBzrBranch(
1072 BranchType.MIRRORED, branch_name, branch_owner,
1073 data['repo_url'])
1074@@ -1123,7 +1115,7 @@
1075 'the series.')
1076 else:
1077 # We need to create an import request.
1078- if rcs_type == RevisionControlSystemsExtended.CVS:
1079+ if rcs_type == RevisionControlSystems.CVS:
1080 cvs_root = data.get('repo_url')
1081 cvs_module = data.get('cvs_module')
1082 url = None
1083
1084=== modified file 'lib/lp/registry/browser/tests/test_distroseries.py'
1085--- lib/lp/registry/browser/tests/test_distroseries.py 2011-07-07 20:07:51 +0000
1086+++ lib/lp/registry/browser/tests/test_distroseries.py 2011-07-18 11:13:26 +0000
1087@@ -32,11 +32,16 @@
1088 from canonical.config import config
1089 from canonical.database.constants import UTC_NOW
1090 from canonical.database.sqlbase import flush_database_caches
1091+<<<<<<< TREE
1092 from canonical.launchpad.testing.pages import (
1093 extract_text,
1094 find_tag_by_id,
1095 )
1096 from canonical.launchpad.webapp.authorization import check_permission
1097+=======
1098+from canonical.launchpad.testing.pages import find_tag_by_id
1099+from canonical.launchpad.webapp.authorization import check_permission
1100+>>>>>>> MERGE-SOURCE
1101 from canonical.launchpad.webapp.batching import BatchNavigator
1102 from canonical.launchpad.webapp.interaction import get_current_principal
1103 from canonical.launchpad.webapp.interfaces import BrowserNotificationLevel
1104@@ -829,6 +834,60 @@
1105 check_permission('launchpad.Edit', view))
1106
1107
1108+class TestDistroSeriesInitializeViewAccess(TestCaseWithFactory):
1109+ """Test access to IDS.+initseries."""
1110+
1111+ layer = LaunchpadFunctionalLayer
1112+
1113+ def setUp(self):
1114+ super(TestDistroSeriesInitializeViewAccess,
1115+ self).setUp('foo.bar@canonical.com')
1116+ set_derived_series_ui_feature_flag(self)
1117+
1118+ def test_initseries_access_anon(self):
1119+ # Anonymous users cannot access +initseries.
1120+ distroseries = self.factory.makeDistroSeries()
1121+ view = create_initialized_view(distroseries, "+initseries")
1122+ login(ANONYMOUS)
1123+
1124+ self.assertEqual(
1125+ False,
1126+ check_permission('launchpad.Edit', view))
1127+
1128+ def test_initseries_access_simpleuser(self):
1129+ # Unprivileged users cannot access +initseries.
1130+ distroseries = self.factory.makeDistroSeries()
1131+ view = create_initialized_view(distroseries, "+initseries")
1132+ login_person(self.factory.makePerson())
1133+
1134+ self.assertEqual(
1135+ False,
1136+ check_permission('launchpad.Edit', view))
1137+
1138+ def test_initseries_access_admin(self):
1139+ # Users with lp.Admin can access +initseries.
1140+ distroseries = self.factory.makeDistroSeries()
1141+ view = create_initialized_view(distroseries, "+initseries")
1142+ login_celebrity('admin')
1143+
1144+ self.assertEqual(
1145+ True,
1146+ check_permission('launchpad.Edit', view))
1147+
1148+ def test_initseries_access_driver(self):
1149+ # Distroseries drivers can access +initseries.
1150+ distroseries = self.factory.makeDistroSeries()
1151+ view = create_initialized_view(distroseries, "+initseries")
1152+ driver = self.factory.makePerson()
1153+ with celebrity_logged_in('admin'):
1154+ distroseries.driver = driver
1155+ login_person(driver)
1156+
1157+ self.assertEqual(
1158+ True,
1159+ check_permission('launchpad.Edit', view))
1160+
1161+
1162 class DistroSeriesDifferenceMixin:
1163 """A helper class for testing differences pages"""
1164
1165
1166=== modified file 'lib/lp/registry/interfaces/distroseries.py'
1167--- lib/lp/registry/interfaces/distroseries.py 2011-07-07 20:07:51 +0000
1168+++ lib/lp/registry/interfaces/distroseries.py 2011-07-18 11:13:26 +0000
1169@@ -1075,6 +1075,7 @@
1170 released == None will do no filtering on status.
1171 """
1172
1173+<<<<<<< TREE
1174 def priorReleasedSeries(self, distribution, prior_to_date):
1175 """Find distroseries for the supplied distro released before a
1176 certain date.
1177@@ -1089,6 +1090,10 @@
1178
1179
1180 @error_status(httplib.BAD_REQUEST)
1181+=======
1182+
1183+@error_status(httplib.BAD_REQUEST)
1184+>>>>>>> MERGE-SOURCE
1185 class DerivationError(Exception):
1186 """Raised when there is a problem deriving a distroseries."""
1187 _message_prefix = "Error deriving distro series"
1188
1189=== modified file 'lib/lp/registry/model/distribution.py'
1190--- lib/lp/registry/model/distribution.py 2011-07-14 14:08:04 +0000
1191+++ lib/lp/registry/model/distribution.py 2011-07-18 11:13:26 +0000
1192@@ -655,6 +655,7 @@
1193 """See `IBugTarget`."""
1194 return get_bug_tags("BugTask.distribution = %s" % sqlvalues(self))
1195
1196+<<<<<<< TREE
1197 def getBranchTips(self, since=None):
1198 """See `IDistribution`."""
1199 query = """
1200@@ -684,6 +685,17 @@
1201 # to the end of the current record, removing Nones from the list.
1202 result[-1].append(filter(None, map(itemgetter(-1), group)))
1203 return result
1204+=======
1205+ def getUsedBugTagsWithOpenCounts(self, user, tag_limit=0,
1206+ include_tags=None):
1207+ """See IBugTarget."""
1208+ # Circular fail.
1209+ from lp.bugs.model.bugsummary import BugSummary
1210+ return get_bug_tags_open_count(
1211+ And(BugSummary.distribution_id == self.id,
1212+ BugSummary.sourcepackagename_id == None),
1213+ user, tag_limit=tag_limit, include_tags=include_tags)
1214+>>>>>>> MERGE-SOURCE
1215
1216 def getMirrorByName(self, name):
1217 """See `IDistribution`."""
1218@@ -1414,6 +1426,7 @@
1219 # Note that in the source package case, we don't restrict
1220 # the search to the distribution release, making a best
1221 # effort to find a package.
1222+<<<<<<< TREE
1223 publishing = IStore(SourcePackagePublishingHistory).find(
1224 SourcePackagePublishingHistory,
1225 # We use an extra query to get the IDs instead of an
1226@@ -1431,6 +1444,20 @@
1227 PackagePublishingStatus.PENDING)
1228 )).order_by(
1229 Desc(SourcePackagePublishingHistory.id)).first()
1230+=======
1231+ publishing = IStore(SourcePackagePublishingHistory).find(
1232+ SourcePackagePublishingHistory,
1233+ SourcePackagePublishingHistory.archiveID.is_in(
1234+ self.all_distro_archive_ids),
1235+ SourcePackagePublishingHistory.sourcepackagereleaseID ==
1236+ SourcePackageRelease.id,
1237+ SourcePackageRelease.sourcepackagename == sourcepackagename,
1238+ SourcePackagePublishingHistory.status.is_in(
1239+ (PackagePublishingStatus.PUBLISHED,
1240+ PackagePublishingStatus.PENDING)
1241+ )).order_by(
1242+ Desc(SourcePackagePublishingHistory.id)).first()
1243+>>>>>>> MERGE-SOURCE
1244 if publishing is not None:
1245 return sourcepackagename
1246
1247@@ -1453,6 +1480,7 @@
1248 # the sourcepackagename from that.
1249 bpph = IStore(BinaryPackagePublishingHistory).find(
1250 BinaryPackagePublishingHistory,
1251+<<<<<<< TREE
1252 # See comment above for rationale for using an extra query
1253 # instead of an inner join. (Bottom line, it would time out
1254 # otherwise.)
1255@@ -1460,6 +1488,12 @@
1256 self.all_distro_archive_ids),
1257 BinaryPackagePublishingHistory.binarypackagereleaseID ==
1258 BinaryPackageRelease.id,
1259+=======
1260+ BinaryPackagePublishingHistory.archiveID.is_in(
1261+ self.all_distro_archive_ids),
1262+ BinaryPackagePublishingHistory.binarypackagereleaseID ==
1263+ BinaryPackageRelease.id,
1264+>>>>>>> MERGE-SOURCE
1265 BinaryPackageRelease.binarypackagename == binarypackagename,
1266 BinaryPackagePublishingHistory.dateremoved == None,
1267 ).order_by(
1268
1269=== modified file 'lib/lp/registry/model/product.py'
1270--- lib/lp/registry/model/product.py 2011-06-21 01:34:08 +0000
1271+++ lib/lp/registry/model/product.py 2011-07-18 11:13:26 +0000
1272@@ -800,6 +800,18 @@
1273 """See `IBugTarget`."""
1274 return get_bug_tags("BugTask.product = %s" % sqlvalues(self))
1275
1276+<<<<<<< TREE
1277+=======
1278+ def getUsedBugTagsWithOpenCounts(self, user, tag_limit=0,
1279+ include_tags=None):
1280+ """See IBugTarget."""
1281+ # Circular fail.
1282+ from lp.bugs.model.bugsummary import BugSummary
1283+ return get_bug_tags_open_count(
1284+ BugSummary.product_id == self.id,
1285+ user, tag_limit=tag_limit, include_tags=include_tags)
1286+
1287+>>>>>>> MERGE-SOURCE
1288 series = SQLMultipleJoin('ProductSeries', joinColumn='product',
1289 orderBy='name')
1290
1291
1292=== modified file 'lib/lp/soyuz/browser/queue.py'
1293--- lib/lp/soyuz/browser/queue.py 2011-06-29 07:25:18 +0000
1294+++ lib/lp/soyuz/browser/queue.py 2011-07-18 11:13:26 +0000
1295@@ -46,6 +46,7 @@
1296 QueueInconsistentStateError,
1297 )
1298 from lp.soyuz.interfaces.section import ISectionSet
1299+from lp.soyuz.model.queue import PackageUploadSource
1300
1301
1302 QUEUE_SIZE = 30
1303@@ -197,10 +198,16 @@
1304 CompletePackageUpload. This avoids many additional SQL queries
1305 in the +queue template.
1306 """
1307+<<<<<<< TREE
1308 # Avoid circular imports.
1309 from lp.soyuz.model.queue import PackageUploadSource
1310 from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
1311
1312+=======
1313+ # Avoid circular imports.
1314+ from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
1315+
1316+>>>>>>> MERGE-SOURCE
1317 uploads = list(self.batchnav.currentBatch())
1318
1319 if len(uploads) == 0:
1320@@ -214,9 +221,14 @@
1321 upload.status != PackageUploadStatus.DONE)]
1322 binary_file_set = getUtility(IBinaryPackageFileSet)
1323 binary_files = binary_file_set.getByPackageUploadIDs(upload_ids)
1324+<<<<<<< TREE
1325 binary_file_set.loadLibraryFiles(binary_files)
1326 packageuploadsources = load_referencing(
1327 PackageUploadSource, uploads, ['packageuploadID'])
1328+=======
1329+ packageuploadsources = load_referencing(
1330+ PackageUploadSource, uploads, ['packageuploadID'])
1331+>>>>>>> MERGE-SOURCE
1332 source_file_set = getUtility(ISourcePackageReleaseFileSet)
1333 source_files = source_file_set.getByPackageUploadIDs(upload_ids)
1334
1335@@ -490,6 +502,12 @@
1336 else:
1337 self.package_sets = []
1338
1339+ if self.contains_source:
1340+ self.package_sets = package_sets.get(
1341+ self.sourcepackagerelease.sourcepackagenameID, [])
1342+ else:
1343+ self.package_sets = []
1344+
1345 @property
1346 def pending_delayed_copy(self):
1347 """Whether the context is a delayed-copy pending processing."""
1348@@ -509,6 +527,7 @@
1349 return self.context.changesfile
1350
1351 @property
1352+<<<<<<< TREE
1353 def display_package_sets(self):
1354 """Package sets, if any, for display on the +queue page."""
1355 return ' '.join(sorted(
1356@@ -592,3 +611,89 @@
1357 (%s)
1358 </div>
1359 """ % (iconlist_id, '\n'.join(icons), link, self.displayarchs)
1360+=======
1361+ def display_package_sets(self):
1362+ """Package sets, if any, for display on the +queue page."""
1363+ if self.contains_source:
1364+ return ' '.join(sorted(
1365+ packageset.name for packageset in self.package_sets))
1366+ else:
1367+ return ""
1368+
1369+ @property
1370+ def display_component(self):
1371+ """Component name, if any, for display on the +queue page."""
1372+ if self.contains_source:
1373+ return self.component_name.lower()
1374+ else:
1375+ return ""
1376+
1377+ @property
1378+ def display_section(self):
1379+ """Section name, if any, for display on the +queue page."""
1380+ if self.contains_source:
1381+ return self.section_name.lower()
1382+ else:
1383+ return ""
1384+
1385+ @property
1386+ def display_priority(self):
1387+ """Priority name, if any, for display on the +queue page."""
1388+ if self.contains_source:
1389+ return self.sourcepackagerelease.urgency.name.lower()
1390+ else:
1391+ return ""
1392+
1393+ def composeIcon(self, alt, icon, title=None):
1394+ """Compose an icon for the package's icon list."""
1395+ # These should really be sprites!
1396+ if title is None:
1397+ title = alt
1398+ return '<img alt="[%s]" src="/@@/%s" title="%s" />' % (
1399+ cgi.escape(alt, quote=True),
1400+ icon,
1401+ cgi.escape(title, quote=True),
1402+ )
1403+
1404+ def composeIconList(self):
1405+ """List icons that should be shown for this upload."""
1406+ ddtp = "Debian Description Translation Project Indexes"
1407+ potential_icons = [
1408+ (self.contains_source, ("Source", 'package-source')),
1409+ (self.contains_build, ("Build", 'package-binary', "Binary")),
1410+ (self.contains_translation, ("Translation", 'translation-file')),
1411+ (self.contains_installer, ("Installer", 'ubuntu-icon')),
1412+ (self.contains_upgrader, ("Upgrader", 'ubuntu-icon')),
1413+ (self.contains_ddtp, (ddtp, 'ubuntu-icon')),
1414+ ]
1415+ return [
1416+ self.composeIcon(*details)
1417+ for condition, details in potential_icons
1418+ if condition]
1419+
1420+ def composeNameAndChangesLink(self):
1421+ """Compose HTML: upload name and link to changes file."""
1422+ raw_displayname = self.displayname
1423+ displayname = cgi.escape(raw_displayname)
1424+ if self.pending_delayed_copy or self.changesfile is None:
1425+ return displayname
1426+ else:
1427+ return '<a href="%s" title="Changes file for %s">%s</a>' % (
1428+ self.changesfile.http_url,
1429+ cgi.escape(self.displayname, quote=True),
1430+ displayname)
1431+
1432+ @property
1433+ def icons_and_name(self):
1434+ """Icon list and name, linked to changes file if appropriate."""
1435+ iconlist_id = "queue%d-iconlist" % self.id
1436+ icons = self.composeIconList()
1437+ link = self.composeNameAndChangesLink()
1438+ return """
1439+ <div id="%s">
1440+ %s
1441+ %s
1442+ (%s)
1443+ </div>
1444+ """ % (iconlist_id, '\n'.join(icons), link, self.displayarchs)
1445+>>>>>>> MERGE-SOURCE
1446
1447=== modified file 'lib/lp/soyuz/browser/tests/test_queue.py'
1448--- lib/lp/soyuz/browser/tests/test_queue.py 2011-06-23 13:05:52 +0000
1449+++ lib/lp/soyuz/browser/tests/test_queue.py 2011-07-18 11:13:26 +0000
1450@@ -234,6 +234,7 @@
1451 upload.distroseries.main_archive)
1452 with person_logged_in(queue_admin):
1453 view = self.makeView(upload.distroseries, queue_admin)
1454+<<<<<<< TREE
1455 html_text = view()
1456 self.assertIn(upload.package_name, html_text)
1457
1458@@ -414,3 +415,168 @@
1459 self.assertNotEqual(None, icons_and_name.find("a"))
1460 self.assertIn(
1461 complete_upload.displayarchs, ' '.join(icons_and_name.itertext()))
1462+=======
1463+ html_text = view()
1464+ self.assertIn(upload.package_name, html_text)
1465+
1466+
1467+class TestCompletePackageUpload(TestCaseWithFactory):
1468+
1469+ layer = LaunchpadZopelessLayer
1470+
1471+ def makeCompletePackageUpload(self, upload=None, build_upload_files=None,
1472+ source_upload_files=None,
1473+ package_sets=None):
1474+ if upload is None:
1475+ upload = self.factory.makeSourcePackageUpload()
1476+ if build_upload_files is None:
1477+ build_upload_files = {}
1478+ if source_upload_files is None:
1479+ source_upload_files = {}
1480+ if package_sets is None:
1481+ package_sets = {}
1482+ return CompletePackageUpload(
1483+ upload, build_upload_files, source_upload_files, package_sets)
1484+
1485+ def mapPackageSets(self, upload, package_sets=None):
1486+ if package_sets is None:
1487+ package_sets = [self.factory.makePackageset(
1488+ distroseries=upload.distroseries)]
1489+ spn = upload.sourcepackagerelease.sourcepackagename
1490+ return {spn.id: package_sets}
1491+
1492+ def test_display_package_sets_returns_source_upload_packagesets(self):
1493+ upload = self.factory.makeSourcePackageUpload()
1494+ package_sets = self.mapPackageSets(upload)
1495+ complete_upload = self.makeCompletePackageUpload(
1496+ upload, package_sets=package_sets)
1497+ self.assertEqual(
1498+ package_sets.values()[0][0].name,
1499+ complete_upload.display_package_sets)
1500+
1501+ def test_display_package_sets_returns_empty_for_non_source_upload(self):
1502+ upload = self.factory.makeBuildPackageUpload()
1503+ complete_upload = self.makeCompletePackageUpload(
1504+ upload, package_sets=self.mapPackageSets(upload))
1505+ self.assertEqual("", complete_upload.display_package_sets)
1506+
1507+ def test_display_package_sets_sorts_by_name(self):
1508+ complete_upload = self.makeCompletePackageUpload()
1509+ distroseries = complete_upload.distroseries
1510+ complete_upload.package_sets = [
1511+ self.factory.makePackageset(distroseries=distroseries, name=name)
1512+ for name in [u'ccc', u'aaa', u'bbb']]
1513+ self.assertEqual("aaa bbb ccc", complete_upload.display_package_sets)
1514+
1515+ def test_display_component_returns_source_upload_component_name(self):
1516+ complete_upload = self.makeCompletePackageUpload()
1517+ self.assertEqual(
1518+ complete_upload.sourcepackagerelease.component.name.lower(),
1519+ complete_upload.display_component)
1520+
1521+ def test_display_component_returns_empty_for_non_source_upload(self):
1522+ complete_upload = self.makeCompletePackageUpload(
1523+ self.factory.makeBuildPackageUpload())
1524+ self.assertEqual('', complete_upload.display_component)
1525+
1526+ def test_display_section_returns_source_upload_section_name(self):
1527+ complete_upload = self.makeCompletePackageUpload()
1528+ self.assertEqual(
1529+ complete_upload.sourcepackagerelease.section.name.lower(),
1530+ complete_upload.display_section)
1531+
1532+ def test_display_section_returns_empty_for_non_source_upload(self):
1533+ complete_upload = self.makeCompletePackageUpload(
1534+ self.factory.makeBuildPackageUpload())
1535+ self.assertEqual('', complete_upload.display_section)
1536+
1537+ def test_display_priority_returns_source_upload_priority(self):
1538+ complete_upload = self.makeCompletePackageUpload()
1539+ self.assertEqual(
1540+ complete_upload.sourcepackagerelease.urgency.name.lower(),
1541+ complete_upload.display_priority)
1542+
1543+ def test_display_priority_returns_empty_for_non_source_upload(self):
1544+ complete_upload = self.makeCompletePackageUpload(
1545+ self.factory.makeBuildPackageUpload())
1546+ self.assertEqual('', complete_upload.display_priority)
1547+
1548+ def test_composeIcon_produces_image_tag(self):
1549+ alt = self.factory.getUniqueString()
1550+ icon = self.factory.getUniqueString() + ".png"
1551+ title = self.factory.getUniqueString()
1552+ html_text = self.makeCompletePackageUpload().composeIcon(
1553+ alt, icon, title)
1554+ img = html.fromstring(html_text)
1555+ self.assertEqual("img", img.tag)
1556+ self.assertEqual("[%s]" % alt, img.get("alt"))
1557+ self.assertEqual("/@@/" + icon, img.get("src"))
1558+ self.assertEqual(title, img.get("title"))
1559+
1560+ def test_composeIcon_title_defaults_to_alt_text(self):
1561+ alt = self.factory.getUniqueString()
1562+ icon = self.factory.getUniqueString() + ".png"
1563+ html_text = self.makeCompletePackageUpload().composeIcon(alt, icon)
1564+ img = html.fromstring(html_text)
1565+ self.assertEqual(alt, img.get("title"))
1566+
1567+ def test_composeIcon_escapes_alt_and_title(self):
1568+ alt = 'alt"&'
1569+ icon = self.factory.getUniqueString() + ".png"
1570+ title = 'title"&'
1571+ html_text = self.makeCompletePackageUpload().composeIcon(
1572+ alt, icon, title)
1573+ img = html.fromstring(html_text)
1574+ self.assertEqual("[%s]" % alt, img.get("alt"))
1575+ self.assertEqual(title, img.get("title"))
1576+
1577+ def test_composeIconList_produces_icons(self):
1578+ icons = self.makeCompletePackageUpload().composeIconList()
1579+ self.assertNotEqual([], icons)
1580+ self.assertEqual('img', html.fromstring(icons[0]).tag)
1581+
1582+ def test_composeIconList_produces_icons_conditionally(self):
1583+ complete_upload = self.makeCompletePackageUpload()
1584+ base_count = len(complete_upload.composeIconList())
1585+ complete_upload.contains_build = True
1586+ new_count = len(complete_upload.composeIconList())
1587+ self.assertEqual(base_count + 1, new_count)
1588+
1589+ def test_composeNameAndChangesLink_does_not_link_if_no_changes_file(self):
1590+ upload = self.factory.makeCopyJobPackageUpload()
1591+ complete_upload = self.makeCompletePackageUpload(upload)
1592+ self.assertEqual(
1593+ complete_upload.displayname,
1594+ complete_upload.composeNameAndChangesLink())
1595+
1596+ def test_composeNameAndChangesLink_links_to_changes_file(self):
1597+ complete_upload = self.makeCompletePackageUpload()
1598+ link = html.fromstring(complete_upload.composeNameAndChangesLink())
1599+ self.assertEqual(
1600+ complete_upload.changesfile.http_url, link.get("href"))
1601+
1602+ def test_composeNameAndChangesLink_escapes_nonlinked_display_name(self):
1603+ filename = 'name"&name'
1604+ upload = self.factory.makeCustomPackageUpload(filename=filename)
1605+ # Stop nameAndChangesLink from producing a link.
1606+ upload.changesfile = None
1607+ complete_upload = self.makeCompletePackageUpload(upload)
1608+ self.assertIn(
1609+ cgi.escape(filename), complete_upload.composeNameAndChangesLink())
1610+
1611+ def test_composeNameAndChangesLink_escapes_name_in_link(self):
1612+ filename = 'name"&name'
1613+ upload = self.factory.makeCustomPackageUpload(filename=filename)
1614+ complete_upload = self.makeCompletePackageUpload(upload)
1615+ link = html.fromstring(complete_upload.composeNameAndChangesLink())
1616+ self.assertIn(filename, link.get("title"))
1617+ self.assertEqual(filename, link.text)
1618+
1619+ def test_icons_and_name_composes_icons_and_link_and_archs(self):
1620+ complete_upload = self.makeCompletePackageUpload()
1621+ icons_and_name = html.fromstring(complete_upload.icons_and_name)
1622+ self.assertNotEqual(None, icons_and_name.find("img"))
1623+ self.assertNotEqual(None, icons_and_name.find("a"))
1624+ self.assertIn(
1625+ complete_upload.displayarchs, ' '.join(icons_and_name.itertext()))
1626+>>>>>>> MERGE-SOURCE
1627
1628=== modified file 'lib/lp/soyuz/interfaces/archive.py'
1629--- lib/lp/soyuz/interfaces/archive.py 2011-06-24 21:21:36 +0000
1630+++ lib/lp/soyuz/interfaces/archive.py 2011-07-18 11:13:26 +0000
1631@@ -101,9 +101,16 @@
1632 from lp.soyuz.enums import ArchivePurpose
1633 from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
1634 from lp.soyuz.interfaces.component import IComponent
1635-
1636-
1637-@error_status(httplib.BAD_REQUEST)
1638+<<<<<<< TREE
1639+
1640+
1641+@error_status(httplib.BAD_REQUEST)
1642+=======
1643+from lp.soyuz.interfaces.processor import IProcessorFamily
1644+
1645+
1646+@error_status(httplib.BAD_REQUEST)
1647+>>>>>>> MERGE-SOURCE
1648 class ArchiveDependencyError(Exception):
1649 """Raised when an `IArchiveDependency` does not fit the context archive.
1650
1651
1652=== modified file 'lib/lp/soyuz/interfaces/files.py'
1653--- lib/lp/soyuz/interfaces/files.py 2011-06-29 11:40:03 +0000
1654+++ lib/lp/soyuz/interfaces/files.py 2011-07-18 11:13:26 +0000
1655@@ -22,8 +22,12 @@
1656 )
1657
1658 from canonical.launchpad import _
1659+<<<<<<< TREE
1660 from canonical.launchpad.interfaces.librarian import ILibraryFileAlias
1661 from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease
1662+=======
1663+from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease
1664+>>>>>>> MERGE-SOURCE
1665
1666
1667 class IBinaryPackageFile(Interface):
1668
1669=== modified file 'lib/lp/soyuz/model/packagesetsources.py'
1670--- lib/lp/soyuz/model/packagesetsources.py 2011-07-01 15:53:38 +0000
1671+++ lib/lp/soyuz/model/packagesetsources.py 2011-07-18 11:13:26 +0000
1672@@ -1,3 +1,4 @@
1673+<<<<<<< TREE
1674 # Copyright 2011 Canonical Ltd. This software is licensed under the
1675 # GNU Affero General Public License version 3 (see the file LICENSE).
1676
1677@@ -43,3 +44,46 @@
1678 def __init__(self, packageset, sourcepackagename):
1679 self.packageset = packageset
1680 self.sourcepackagename = sourcepackagename
1681+=======
1682+# Copyright 2011 Canonical Ltd. This software is licensed under the
1683+# GNU Affero General Public License version 3 (see the file LICENSE).
1684+
1685+"""The `PackagesetSources` linking table.
1686+
1687+This table associates `Packageset`s with `SourcePackageName`s.
1688+"""
1689+
1690+__metaclass__ = type
1691+__all__ = [
1692+ 'PackagesetSources',
1693+ ]
1694+
1695+from storm.locals import (
1696+ Int,
1697+ Reference,
1698+ Storm,
1699+ )
1700+
1701+
1702+class PackagesetSources(Storm):
1703+ """Linking table: which packages are in a package set?"""
1704+ # This table is largely managed from Packageset, but also directly
1705+ # accessed from other places.
1706+
1707+ __storm_table__ = 'PackagesetSources'
1708+
1709+ # There's a vestigial id as well, a holdover from the SQLObject
1710+ # days. Nobody seems to use it. The only key that matters is
1711+ # (packageset, sourcepackagename).
1712+ # XXX JeroenVermeulen 2011-06-22, bug=800677: Drop the id column.
1713+ __storm_primary__ = (
1714+ 'packageset_id',
1715+ 'sourcepackagename_id',
1716+ )
1717+
1718+ packageset_id = Int(name='packageset')
1719+ packageset = Reference(packageset_id, 'Packageset.id')
1720+ sourcepackagename_id = Int(name='sourcepackagename')
1721+ sourcepackagename = Reference(
1722+ sourcepackagename_id, 'SourcePackageName.id')
1723+>>>>>>> MERGE-SOURCE
1724
1725=== modified file 'lib/lp/soyuz/scripts/tests/test_queue.py'
1726--- lib/lp/soyuz/scripts/tests/test_queue.py 2011-07-07 16:05:03 +0000
1727+++ lib/lp/soyuz/scripts/tests/test_queue.py 2011-07-18 11:13:26 +0000
1728@@ -966,6 +966,7 @@
1729 distro.name, distroseries.name, queue, terms, component.name,
1730 section.name, priority_name, display)
1731
1732+<<<<<<< TREE
1733 def makeQueueActionOverride(self, package_upload, component, section,
1734 distroseries=None):
1735 return self.makeQueueAction(
1736@@ -980,6 +981,16 @@
1737 """
1738 return tuple(item.strip() for item in output_line.split('|'))
1739
1740+=======
1741+ def parseUploadSummaryLine(self, output_line):
1742+ """Parse an output line from `QueueAction.displayItem`.
1743+
1744+ :param output_line: A line of output text from `displayItem`.
1745+ :return: A tuple of displayed items: (id, tag, name, version, age).
1746+ """
1747+ return tuple(item.strip() for item in output_line.split('|'))
1748+
1749+>>>>>>> MERGE-SOURCE
1750 def test_display_actions_have_privileges_for_PackageCopyJob(self):
1751 # The methods that display uploads have privileges to work with
1752 # a PackageUpload that has a copy job.
1753@@ -1031,6 +1042,7 @@
1754 action = self.makeQueueAction(upload)
1755
1756 action.displayItem(upload)
1757+<<<<<<< TREE
1758
1759 ((output, ), kwargs) = action.display.calls[0]
1760 (upload_id, tag, name, version, age) = self.parseUploadSummaryLine(
1761@@ -1085,6 +1097,37 @@
1762 def test_makeTag_returns_dashes_for_custom_upload(self):
1763 upload = self.factory.makeCustomPackageUpload()
1764 self.assertEqual('--', self.makeQueueAction(upload)._makeTag(upload))
1765+=======
1766+
1767+ ((output, ), kwargs) = action.display.calls[0]
1768+ (upload_id, tag, name, version, age) = self.parseUploadSummaryLine(
1769+ output)
1770+ self.assertEqual(str(upload.id), upload_id)
1771+ self.assertEqual("X-", tag)
1772+ self.assertThat(upload.displayname, StartsWith(name))
1773+ self.assertThat(upload.package_version, StartsWith(version))
1774+
1775+ def test_makeTag_returns_S_for_source_upload(self):
1776+ upload = self.factory.makeSourcePackageUpload()
1777+ self.assertEqual('S-', self.makeQueueAction(upload)._makeTag(upload))
1778+
1779+ def test_makeTag_returns_B_for_binary_upload(self):
1780+ upload = self.factory.makeBuildPackageUpload()
1781+ self.assertEqual('-B', self.makeQueueAction(upload)._makeTag(upload))
1782+
1783+ def test_makeTag_returns_SB_for_mixed_upload(self):
1784+ upload = self.factory.makeSourcePackageUpload()
1785+ upload.addBuild(self.factory.makeBinaryPackageBuild())
1786+ self.assertEqual('SB', self.makeQueueAction(upload)._makeTag(upload))
1787+
1788+ def test_makeTag_returns_X_for_copy_job_upload(self):
1789+ upload = self.factory.makeCopyJobPackageUpload()
1790+ self.assertEqual('X-', self.makeQueueAction(upload)._makeTag(upload))
1791+
1792+ def test_makeTag_returns_dashes_for_custom_upload(self):
1793+ upload = self.factory.makeCustomPackageUpload()
1794+ self.assertEqual('--', self.makeQueueAction(upload)._makeTag(upload))
1795+>>>>>>> MERGE-SOURCE
1796
1797 def test_displayInfo_displays_PackageUpload_with_source(self):
1798 # displayInfo can display a source package upload.
1799
1800=== modified file 'lib/lp/soyuz/stories/webservice/xx-archive.txt'
1801--- lib/lp/soyuz/stories/webservice/xx-archive.txt 2011-06-23 16:05:30 +0000
1802+++ lib/lp/soyuz/stories/webservice/xx-archive.txt 2011-07-18 11:13:26 +0000
1803@@ -17,6 +17,7 @@
1804 >>> from lazr.restful.testing.webservice import pprint_entry
1805 >>> pprint_entry(cprov_archive)
1806 commercial: False
1807+<<<<<<< TREE
1808 dependencies_collection_link:
1809 u'http://.../~cprov/+archive/ppa/dependencies'
1810 description: u'packages to help my friends.'
1811@@ -38,6 +39,10 @@
1812 >>> pprint_entry(cprov_archive_devel)
1813 commercial: False
1814 dependencies_collection_link: u'http://.../~cprov/+archive/ppa/dependencies'
1815+=======
1816+ dependencies_collection_link:
1817+ u'http://.../~cprov/+archive/ppa/dependencies'
1818+>>>>>>> MERGE-SOURCE
1819 description: u'packages to help my friends.'
1820 displayname: u'PPA for Celso Providelo'
1821 distribution_link: u'http://.../ubuntu'
1822
1823=== modified file 'lib/lp/soyuz/tests/test_packageupload.py'
1824--- lib/lp/soyuz/tests/test_packageupload.py 2011-07-11 08:31:46 +0000
1825+++ lib/lp/soyuz/tests/test_packageupload.py 2011-07-18 11:13:26 +0000
1826@@ -816,6 +816,7 @@
1827 [upload],
1828 upload_set.getAll(
1829 distroseries, name=spn.name, version=upload.displayversion))
1830+<<<<<<< TREE
1831
1832 def test_getAll_orders_in_reverse_historical_order(self):
1833 # The results from getAll are returned in order of creation,
1834@@ -838,3 +839,5 @@
1835 self.assertEqual(
1836 list(reversed(ordered_uploads)),
1837 list(getUtility(IPackageUploadSet).getAll(series)))
1838+=======
1839+>>>>>>> MERGE-SOURCE
1840
1841=== modified file 'lib/lp/testing/factory.py'
1842--- lib/lp/testing/factory.py 2011-07-13 20:55:34 +0000
1843+++ lib/lp/testing/factory.py 2011-07-18 11:13:26 +0000
1844@@ -474,7 +474,7 @@
1845 branch_id = self.getUniqueInteger()
1846 if rcstype is None:
1847 rcstype = 'svn'
1848- if rcstype in ['svn', 'bzr-svn', 'hg']:
1849+ if rcstype in ['svn', 'bzr-svn', 'hg', 'bzr']:
1850 assert cvs_root is cvs_module is None
1851 if url is None:
1852 url = self.getUniqueURL()
1853@@ -2100,7 +2100,8 @@
1854
1855 def makeCodeImport(self, svn_branch_url=None, cvs_root=None,
1856 cvs_module=None, target=None, branch_name=None,
1857- git_repo_url=None, hg_repo_url=None, registrant=None,
1858+ git_repo_url=None, hg_repo_url=None,
1859+ bzr_branch_url=None, registrant=None,
1860 rcs_type=None, review_status=None):
1861 """Create and return a new, arbitrary code import.
1862
1863@@ -2109,7 +2110,7 @@
1864 unique URL.
1865 """
1866 if (svn_branch_url is cvs_root is cvs_module is git_repo_url is
1867- hg_repo_url is None):
1868+ hg_repo_url is bzr_branch_url is None):
1869 svn_branch_url = self.getUniqueURL()
1870
1871 if target is None:
1872@@ -2140,6 +2141,11 @@
1873 registrant, target, branch_name,
1874 rcs_type=RevisionControlSystems.HG,
1875 url=hg_repo_url)
1876+ elif bzr_branch_url is not None:
1877+ code_import = code_import_set.new(
1878+ registrant, target, branch_name,
1879+ rcs_type=RevisionControlSystems.BZR,
1880+ url=bzr_branch_url)
1881 else:
1882 assert rcs_type in (None, RevisionControlSystems.CVS)
1883 code_import = code_import_set.new(
1884@@ -3485,6 +3491,7 @@
1885 distribution=distribution)
1886
1887 if archive is None:
1888+<<<<<<< TREE
1889 archive = distroseries.main_archive
1890
1891 if (sourcepackagename is None or
1892@@ -3494,6 +3501,19 @@
1893
1894 if (component is None or isinstance(component, basestring)):
1895 component = self.makeComponent(component)
1896+=======
1897+ archive = self.makeArchive(
1898+ distribution=distroseries.distribution,
1899+ purpose=ArchivePurpose.PRIMARY)
1900+
1901+ if (sourcepackagename is None or
1902+ isinstance(sourcepackagename, basestring)):
1903+ sourcepackagename = self.getOrMakeSourcePackageName(
1904+ sourcepackagename)
1905+
1906+ if component is None:
1907+ component = self.makeComponent()
1908+>>>>>>> MERGE-SOURCE
1909
1910 if urgency is None:
1911 urgency = self.getAnySourcePackageUrgency()
1912@@ -3567,6 +3587,7 @@
1913 to determine archive.
1914 :param source_package_release: The SourcePackageRelease this binary
1915 build uses as its source.
1916+<<<<<<< TREE
1917 :param sourcepackagename: when source_package_release is None, the
1918 sourcepackagename from which the build will come.
1919 :param distroarchseries: The DistroArchSeries to use. Defaults to the
1920@@ -3574,6 +3595,12 @@
1921 :param archive: The Archive to use. Defaults to the one from the
1922 source_package_release, or the distro arch series main archive
1923 otherwise.
1924+=======
1925+ :param sourcepackagename: when source_package_release is None, the
1926+ sourcepackagename from which the build will come.
1927+ :param distroarchseries: The DistroArchSeries to use.
1928+ :param archive: The Archive to use.
1929+>>>>>>> MERGE-SOURCE
1930 :param builder: An optional builder to assign.
1931 :param status: The BuildStatus for the build.
1932 """
1933
1934=== modified file 'scripts/code-import-worker.py'
1935--- scripts/code-import-worker.py 2011-06-16 23:43:04 +0000
1936+++ scripts/code-import-worker.py 2011-07-18 11:13:26 +0000
1937@@ -26,8 +26,9 @@
1938 from canonical.config import config
1939 from lp.codehosting import load_optional_plugin
1940 from lp.codehosting.codeimport.worker import (
1941- BzrSvnImportWorker, CSCVSImportWorker, CodeImportSourceDetails,
1942- GitImportWorker, HgImportWorker, get_default_bazaar_branch_store)
1943+ BzrImportWorker, BzrSvnImportWorker, CSCVSImportWorker,
1944+ CodeImportSourceDetails, GitImportWorker, HgImportWorker,
1945+ get_default_bazaar_branch_store)
1946 from canonical.launchpad import scripts
1947
1948
1949@@ -65,8 +66,17 @@
1950 elif source_details.rcstype == 'hg':
1951 load_optional_plugin('hg')
1952 import_worker_cls = HgImportWorker
1953- elif source_details.rcstype in ['cvs', 'svn']:
1954- import_worker_cls = CSCVSImportWorker
1955+<<<<<<< TREE
1956+ elif source_details.rcstype in ['cvs', 'svn']:
1957+ import_worker_cls = CSCVSImportWorker
1958+=======
1959+ elif source_details.rcstype == 'bzr':
1960+ load_optional_plugin('loom')
1961+ load_optional_plugin('weave_fmt')
1962+ import_worker_cls = BzrImportWorker
1963+ elif source_details.rcstype in ['cvs', 'svn']:
1964+ import_worker_cls = CSCVSImportWorker
1965+>>>>>>> MERGE-SOURCE
1966 else:
1967 raise AssertionError(
1968 'unknown rcstype %r' % source_details.rcstype)
1969
1970=== modified file 'utilities/sourcedeps.cache'
1971--- utilities/sourcedeps.cache 2011-06-29 14:14:20 +0000
1972+++ utilities/sourcedeps.cache 2011-07-18 11:13:26 +0000
1973@@ -1,8 +1,4 @@
1974 {
1975- "bzr-builder": [
1976- 68,
1977- "launchpad@pqm.canonical.com-20101123183213-777lz46xgagn1deg"
1978- ],
1979 "testresources": [
1980 16,
1981 "robertc@robertcollins.net-20050911111209-ee5da49011cf936a"
1982@@ -31,14 +27,18 @@
1983 24,
1984 "launchpad@pqm.canonical.com-20100601182722-wo7h2fh0fvyw3aaq"
1985 ],
1986- "lpreview": [
1987- 23,
1988- "launchpad@pqm.canonical.com-20090720061538-euyh68ifavhy0pi8"
1989- ],
1990 "bzr-git": [
1991 259,
1992 "launchpad@pqm.canonical.com-20110601140035-gl5merbechngjw5s"
1993 ],
1994+ "loggerhead": [
1995+ 445,
1996+ "john@arbash-meinel.com-20110325141442-536j4be3x0c464zy"
1997+ ],
1998+ "bzr-builder": [
1999+ 68,
2000+ "launchpad@pqm.canonical.com-20101123183213-777lz46xgagn1deg"
2001+ ],
2002 "bzr-loom": [
2003 49,
2004 "launchpad@pqm.canonical.com-20110601122412-54vo3k8yae9i2zve"
2005@@ -47,9 +47,15 @@
2006 4,
2007 "sinzui-20090526164636-1swugzupwvjgomo4"
2008 ],
2009+<<<<<<< TREE
2010 "loggerhead": [
2011 452,
2012 "andrew.bennetts@canonical.com-20110628173100-owrifrnckvoi60af"
2013+=======
2014+ "lpreview": [
2015+ 23,
2016+ "launchpad@pqm.canonical.com-20090720061538-euyh68ifavhy0pi8"
2017+>>>>>>> MERGE-SOURCE
2018 ],
2019 "difftacular": [
2020 6,
2021
2022=== modified file 'versions.cfg'
2023--- versions.cfg 2011-07-18 11:13:00 +0000
2024+++ versions.cfg 2011-07-18 11:13:26 +0000
2025@@ -78,8 +78,12 @@
2026 soupmatchers = 0.1r53
2027 sourcecodegen = 0.6.9
2028 storm = 0.18.0.99-lpwithnodatetime-r393
2029+<<<<<<< TREE
2030 testresources = 0.2.4-r58
2031 testtools = 0.9.12-r194
2032+=======
2033+testtools = 0.9.11
2034+>>>>>>> MERGE-SOURCE
2035 transaction = 1.0.0
2036 txamqp = 0.4
2037 Twisted = 11.0.0

Subscribers

People subscribed via source and target branches

to status/vote changes: