Merge lp:~jml/launchpad/free-code-imports into lp:launchpad

Proposed by Jonathan Lange
Status: Merged
Approved by: Michael Hudson-Doyle
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~jml/launchpad/free-code-imports
Merge into: lp:launchpad
Diff against target: 1017 lines (+432/-243)
13 files modified
lib/canonical/launchpad/security.py (+19/-3)
lib/lp/code/browser/branch.py (+4/-2)
lib/lp/code/doc/codeimport.txt (+30/-7)
lib/lp/code/mail/codeimport.py (+3/-2)
lib/lp/code/model/branch.py (+6/-2)
lib/lp/code/model/codeimport.py (+2/-4)
lib/lp/code/stories/codeimport/xx-admin-codeimport.txt (+277/-0)
lib/lp/code/stories/codeimport/xx-codeimport-machines.txt (+7/-7)
lib/lp/code/stories/codeimport/xx-create-codeimport.txt (+2/-2)
lib/lp/code/stories/codeimport/xx-edit-codeimport.txt (+42/-208)
lib/lp/code/stories/codeimport/xx-failing-codeimport.txt (+4/-3)
lib/lp/code/tests/test_branch.py (+33/-1)
lib/lp/testing/factory.py (+3/-2)
To merge this branch: bzr merge lp:~jml/launchpad/free-code-imports
Reviewer Review Type Date Requested Status
Michael Hudson-Doyle Approve
Review via email: mp+12832@code.launchpad.net

Commit message

New code imports will be owned by the person who registered them, rather than the vcs-imports special team.

To post a comment you must log in.
Revision history for this message
Jonathan Lange (jml) wrote :

Hi Michael,

In this branch, I have tried to fix bug 387110 by setting the owner of a code import branch to be the registrant of the branch, rather than ~vcs-imports.

Doing this changed the email that gets sent. I'm not entirely sure what's going on here, but hope I've done the right thing.

jml

Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

Hi, this is simple enough and a good idea. I think you'll need to run the page tests and at least look at the browser code for creating code imports before this can land though.

review: Needs Fixing
Revision history for this message
Jonathan Lange (jml) wrote :

> Hi, this is simple enough and a good idea. I think you'll need to run the
> page tests and at least look at the browser code for creating code imports
> before this can land though.

For those following at home, here's what's happening.

I've changed my branch so that it passes _almost_ all the tests. The only one it doesn't pass is the "edit" page test. The reason is that the way that code import permissions work now is a little bit confusing.

I'll address these as soon as I have a spare moment.

Revision history for this message
Jonathan Lange (jml) wrote :

> > Hi, this is simple enough and a good idea. I think you'll need to run the
> > page tests and at least look at the browser code for creating code imports
> > before this can land though.
>
> For those following at home, here's what's happening.
>
> I've changed my branch so that it passes _almost_ all the tests. The only one
> it doesn't pass is the "edit" page test. The reason is that the way that code
> import permissions work now is a little bit confusing.
>
> I'll address these as soon as I have a spare moment.

Addressed!

Now, if you register the code import, you are the owner of the branch, rather than vcs-imports. I've also tweaked things so that even if the branch is owned by vcs-imports, you can edit the branch.

We actually *don't* want to give people edit permissions on the CodeImport object itself, since we use 'launchpad.Edit' permissions to mean "you are allowed to administer code imports".

jml

Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :
Download full text (31.0 KiB)

I have a couple of questions I'd like you to answer. The page tests are somewhat icky and you've added a little duplication. Remove that and I reckon you should land this.

> === modified file 'lib/lp/code/browser/branch.py'
> --- lib/lp/code/browser/branch.py 2009-10-29 16:54:10 +0000
> +++ lib/lp/code/browser/branch.py 2009-11-10 04:22:15 +0000
> @@ -177,6 +177,7 @@
> if proposal.id == id:
> return proposal
>
> +
> class BranchEditMenu(NavigationMenu):
> """Edit menu for IBranch."""
>
> @@ -206,10 +207,14 @@
> return Link(
> '+whiteboard', text, icon='edit', enabled=enabled)
>
> - @enabled_with_permission('launchpad.Edit')
> + # XXX: Rewrite the page test to be smarter about who is actually logged
> + # in, handling cases of branch owner and vcs-imports member separately.

This is fair enough but I don't see why the comment is here!

> def edit_import(self):
> text = 'Edit import source or review import'
> - enabled = self.branch_is_import()
> + enabled = (
> + self.branch_is_import() and
> + check_permission('launchpad.Edit', self.context.code_import))
> return Link(
> '+edit-import', text, icon='edit', enabled=enabled)
>

> === modified file 'lib/lp/code/doc/codeimport.txt'
> --- lib/lp/code/doc/codeimport.txt 2009-07-01 13:16:44 +0000
> +++ lib/lp/code/doc/codeimport.txt 2009-11-10 04:22:15 +0000
> @@ -94,7 +94,7 @@
> Operator @vcs-imports
> >>> print message.get_payload()
> A new code import has been requested by No Privileges Person:
> - http://code.launchpad.dev/~vcs-imports/firefox/trunk-cvs
> + http://code.launchpad.dev/~no-priv/firefox/trunk-cvs
> -- =
> You are getting this email because you are a member of the vcs-imports team.
>
> @@ -207,6 +207,24 @@
> ... nopriv)
> >>> print_emails(group_similar=True)
> From: No Privileges Person <email address hidden>
> + To: <email address hidden>, ...
> + Subject: Code import product.../name... status: Reviewed
> + <BLANKLINE>
> + The import has been approved and an import will start shortly.
> + <BLANKLINE>
> + ... is now being imported from:
> + http://svn.example.com/project/trunk
> + instead of:
> + http://svn.example.com/project
> + <BLANKLINE>
> + -- =
> + <BLANKLINE>
> + http://code.launchpad.dev/~person.../product.../name...
> + You are getting this email because you are a member of the vcs-imports
> + team.
> + <BLANKLINE>
> + ----------------------------------------

This is a bit bizarre. Oh, I think the rules for subscriptions to
code import branches are a bit unusual, maybe? I guess it would be
good to understand why this is happening before landing.

> + From: No Privileges Person <email address hidden>
> To: <email address hidden>
> Subject: Code import product.../name... status: Reviewed
> <BLANKLINE>
> @@ -218,10 +236,11 @@
> http://svn.example.com/project
> <BLANKLINE>
> -- =
> - http://code.launchpad.dev/~vcs-imports/product.../name...
> + <BLANKLINE>
> + http://co...

review: Needs Information
Revision history for this message
Jonathan Lange (jml) wrote :

I've fixed everything you've pointed out, but haven't done anything about the two open questions:

  1. Why do I need to do the UnauthenticatedPrincipal dance?

  2. What's going on with email?

In answer to the first, I don't know why I had to make the change, other than that the tests made me do it. Perhaps Gary or Bjorn would know why.

As for the second, we should look into it at the sprint tomorrow.

Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

I answered my own questions in the end, I think the behaviour in this branch is better than trunk in fact.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/security.py'
2--- lib/canonical/launchpad/security.py 2009-10-20 13:21:44 +0000
3+++ lib/canonical/launchpad/security.py 2009-11-11 09:00:39 +0000
4@@ -1591,9 +1591,25 @@
5 usedfor = IBranch
6
7 def checkAuthenticated(self, user):
8- return (user.inTeam(self.obj.owner) or
9- user_has_special_branch_access(user) or
10- can_upload_linked_package(user, self.obj))
11+ can_edit = (
12+ user.inTeam(self.obj.owner) or
13+ user_has_special_branch_access(user) or
14+ can_upload_linked_package(user, self.obj))
15+ if can_edit:
16+ return True
17+ # It used to be the case that all import branches were owned by the
18+ # special, restricted team ~vcs-imports. For these legacy code import
19+ # branches, we still want the code import registrant to be able to
20+ # edit them. Similarly, we still want vcs-imports members to be able
21+ # to edit those branches.
22+ code_import = self.obj.code_import
23+ if code_import is None:
24+ return False
25+ vcs_imports = getUtility(ILaunchpadCelebrities).vcs_imports
26+ return (
27+ user.inTeam(vcs_imports)
28+ or (self.obj.owner == vcs_imports
29+ and user.inTeam(code_import.registrant)))
30
31
32 def can_upload_linked_package(person, branch):
33
34=== modified file 'lib/lp/code/browser/branch.py'
35--- lib/lp/code/browser/branch.py 2009-10-29 16:54:10 +0000
36+++ lib/lp/code/browser/branch.py 2009-11-11 09:00:39 +0000
37@@ -177,6 +177,7 @@
38 if proposal.id == id:
39 return proposal
40
41+
42 class BranchEditMenu(NavigationMenu):
43 """Edit menu for IBranch."""
44
45@@ -206,10 +207,11 @@
46 return Link(
47 '+whiteboard', text, icon='edit', enabled=enabled)
48
49- @enabled_with_permission('launchpad.Edit')
50 def edit_import(self):
51 text = 'Edit import source or review import'
52- enabled = self.branch_is_import()
53+ enabled = (
54+ self.branch_is_import() and
55+ check_permission('launchpad.Edit', self.context.code_import))
56 return Link(
57 '+edit-import', text, icon='edit', enabled=enabled)
58
59
60=== modified file 'lib/lp/code/doc/codeimport.txt'
61--- lib/lp/code/doc/codeimport.txt 2009-07-01 13:16:44 +0000
62+++ lib/lp/code/doc/codeimport.txt 2009-11-11 09:00:39 +0000
63@@ -94,7 +94,7 @@
64 Operator @vcs-imports
65 >>> print message.get_payload()
66 A new code import has been requested by No Privileges Person:
67- http://code.launchpad.dev/~vcs-imports/firefox/trunk-cvs
68+ http://code.launchpad.dev/~no-priv/firefox/trunk-cvs
69 -- =
70 You are getting this email because you are a member of the vcs-imports team.
71
72@@ -207,6 +207,24 @@
73 ... nopriv)
74 >>> print_emails(group_similar=True)
75 From: No Privileges Person <no-priv@canonical.com>
76+ To: david.allouche@canonical.com, ...
77+ Subject: Code import product.../name... status: Reviewed
78+ <BLANKLINE>
79+ The import has been approved and an import will start shortly.
80+ <BLANKLINE>
81+ ... is now being imported from:
82+ http://svn.example.com/project/trunk
83+ instead of:
84+ http://svn.example.com/project
85+ <BLANKLINE>
86+ -- =
87+ <BLANKLINE>
88+ http://code.launchpad.dev/~person.../product.../name...
89+ You are getting this email because you are a member of the vcs-imports
90+ team.
91+ <BLANKLINE>
92+ ----------------------------------------
93+ From: No Privileges Person <no-priv@canonical.com>
94 To: no-priv@canonical.com
95 Subject: Code import product.../name... status: Reviewed
96 <BLANKLINE>
97@@ -218,10 +236,11 @@
98 http://svn.example.com/project
99 <BLANKLINE>
100 -- =
101- http://code.launchpad.dev/~vcs-imports/product.../name...
102+ <BLANKLINE>
103+ http://code.launchpad.dev/~person.../product.../name...
104 You are receiving this email as you are subscribed to the branch.
105- To unsubscribe from this branch go to http://code.launchpad.dev/~vcs-import=
106- s/product.../name.../+edit-subscription.
107+ To unsubscribe from this branch go to .../+edit-subscription.
108+ <BLANKLINE>
109 ----------------------------------------
110
111 updateFromData is smart enough to not send an email if no changes were
112@@ -239,6 +258,10 @@
113 ... None)
114 >>> print_emails(group_similar=True)
115 From: noreply@launchpad.net
116+ To: david.allouche@canonical.com, ...
117+ Subject: Code import product.../name... status: Reviewed
118+ ...
119+ From: noreply@launchpad.net
120 To: no-priv@canonical.com
121 Subject: Code import product.../name... status: Reviewed
122 ...
123@@ -351,7 +374,7 @@
124 and are not traversable in themselves.
125
126 >>> print canonical_url(svn_import.branch)
127- http://code.launchpad.dev/~vcs-imports/firefox/trunk-svn
128+ http://code.launchpad.dev/~no-priv/firefox/trunk-svn
129 >>> print canonical_url(svn_import)
130 Traceback (most recent call last):
131 NoCanonicalUrl: ...
132@@ -410,7 +433,7 @@
133 >>> modify_event = cvs_import.updateFromData(data, nopriv)
134 >>> print make_email_body_for_code_import_update(
135 ... cvs_import, modify_event, None)
136- ~vcs-imports/firefox/trunk-cvs is now being imported from:
137+ ~no-priv/firefox/trunk-cvs is now being imported from:
138 hello from :pserver:anoncvs@cvs.example.com:/var/cvsroot
139 instead of:
140 hello from :pserver:anonymous@cvs.example.com:/cvsroot
141@@ -421,7 +444,7 @@
142 >>> modify_event = git_import.updateFromData(data, nopriv)
143 >>> print make_email_body_for_code_import_update(
144 ... git_import, modify_event, None)
145- ~vcs-imports/firefox/trunk-git is now being imported from:
146+ ~no-priv/firefox/trunk-git is now being imported from:
147 git://git.example.com/goodbye.git
148 instead of:
149 git://git.example.com/hello.git
150
151=== modified file 'lib/lp/code/mail/codeimport.py'
152--- lib/lp/code/mail/codeimport.py 2009-07-17 00:26:05 +0000
153+++ lib/lp/code/mail/codeimport.py 2009-11-11 09:00:39 +0000
154@@ -8,6 +8,7 @@
155 import textwrap
156
157 from zope.component import getUtility
158+from zope.app.security.interfaces import IUnauthenticatedPrincipal
159
160 from canonical.config import config
161 from canonical.launchpad.helpers import (
162@@ -23,11 +24,11 @@
163
164 def new_import(code_import, event):
165 """Email the vcs-imports team about a new code import."""
166- if event.user is None:
167+ if (event.user is None
168+ or IUnauthenticatedPrincipal.providedBy(event.user)):
169 # If there is no logged in user, then we are most likely in a
170 # test.
171 return
172-
173 user = IPerson(event.user)
174 subject = 'New code import: %s/%s' % (
175 code_import.product.name, code_import.branch.name)
176
177=== modified file 'lib/lp/code/model/branch.py'
178--- lib/lp/code/model/branch.py 2009-10-30 22:56:45 +0000
179+++ lib/lp/code/model/branch.py 2009-11-11 09:00:39 +0000
180@@ -591,8 +591,6 @@
181 series_set = getUtility(IFindOfficialBranchLinks)
182 alteration_operations.extend(
183 map(ClearOfficialPackageBranch, series_set.findForBranch(self)))
184- if self.code_import is not None:
185- deletion_operations.append(DeleteCodeImport(self.code_import))
186 return (alteration_operations, deletion_operations)
187
188 def deletionRequirements(self):
189@@ -623,6 +621,12 @@
190 operation()
191 for operation in deletion_operations:
192 operation()
193+ # Special-case code import, since users don't have lp.Edit on them,
194+ # since if you can delete a branch you should be able to delete the
195+ # code import and since deleting the code import object itself isn't
196+ # actually a very interesting thing to tell the user about.
197+ if self.code_import is not None:
198+ DeleteCodeImport(self.code_import)()
199
200 def associatedProductSeries(self):
201 """See `IBranch`."""
202
203=== modified file 'lib/lp/code/model/codeimport.py'
204--- lib/lp/code/model/codeimport.py 2009-08-31 09:52:42 +0000
205+++ lib/lp/code/model/codeimport.py 2009-11-11 09:00:39 +0000
206@@ -33,7 +33,6 @@
207 from canonical.database.sqlbase import SQLBase, quote, sqlvalues
208 from lp.code.model.codeimportjob import CodeImportJobWorkflow
209 from lp.registry.model.productseries import ProductSeries
210-from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
211 from canonical.launchpad.webapp.interfaces import NotFoundError
212 from lp.code.enums import (
213 BranchType, CodeImportResultStatus, CodeImportReviewStatus,
214@@ -238,11 +237,10 @@
215 else:
216 review_status = CodeImportReviewStatus.NEW
217 # Create the branch for the CodeImport.
218- vcs_imports = getUtility(ILaunchpadCelebrities).vcs_imports
219- namespace = get_branch_namespace(vcs_imports, product)
220+ namespace = get_branch_namespace(registrant, product)
221 import_branch = namespace.createBranch(
222 branch_type=BranchType.IMPORTED, name=branch_name,
223- registrant=vcs_imports)
224+ registrant=registrant)
225
226 code_import = CodeImport(
227 registrant=registrant, owner=registrant, branch=import_branch,
228
229=== added file 'lib/lp/code/stories/codeimport/xx-admin-codeimport.txt'
230--- lib/lp/code/stories/codeimport/xx-admin-codeimport.txt 1970-01-01 00:00:00 +0000
231+++ lib/lp/code/stories/codeimport/xx-admin-codeimport.txt 2009-11-11 09:00:39 +0000
232@@ -0,0 +1,277 @@
233+= Administrating code imports =
234+
235+The code import details are displayed on the main branch page for
236+imported branches. If the logged in user is an import operator
237+(member of VCS imports or Launchpad admin) then they can see a link
238+to edit the details.
239+
240+ >>> from canonical.launchpad.ftests import ANONYMOUS, login, logout
241+ >>> login('test@canonical.com')
242+
243+ >>> svn_import = factory.makeCodeImport(
244+ ... svn_branch_url='svn://svn.example.com/fooix/trunk')
245+ >>> from canonical.launchpad.webapp import canonical_url
246+ >>> svn_import_location = str(canonical_url(svn_import.branch))
247+ >>> svn_import_branch_unique_name = svn_import.branch.unique_name
248+
249+ >>> cvs_import = factory.makeCodeImport(
250+ ... cvs_root=":pserver:anonymous@cvs.example.com:/fooix",
251+ ... cvs_module="fooix")
252+ >>> cvs_import_location = str(canonical_url(cvs_import.branch))
253+ >>> cvs_import_branch_unique_name = cvs_import.branch.unique_name
254+
255+ >>> git_import = factory.makeCodeImport(
256+ ... git_repo_url="git://git.example.org/fooix")
257+ >>> git_import_location = str(canonical_url(git_import.branch))
258+ >>> git_import_branch_unique_name = git_import.branch.unique_name
259+
260+ >>> import_browser = setupBrowser(
261+ ... auth='Basic david.allouche@canonical.com:test')
262+
263+
264+== Editing the import ==
265+
266+Both VCS Imports members and Launchpad administrators can edit the
267+import:
268+
269+ >>> import_browser.open(svn_import_location)
270+ >>> print_import_details(import_browser)
271+ Import Status: Pending Review
272+ This branch is an import of the Subversion branch
273+ from svn://svn.example.com/fooix/trunk.
274+
275+
276+== Editing details ==
277+
278+There are a number of buttons shown on the editing page for
279+import operators.
280+
281+ >>> import_browser.getLink('Edit import source or review import').click()
282+ >>> print_submit_buttons(import_browser.contents)
283+ Update
284+ Approve
285+ Mark Invalid
286+ Suspend
287+ Mark Failing
288+
289+A cancel link is also shown next to the buttons, that takes the user
290+back to the main branch details page.
291+
292+ >>> import_browser.getLink('Cancel').url == svn_import_location
293+ True
294+
295+Only the fields that are relevant to the type of import are shown
296+in the form.
297+
298+ >>> def print_form_fields(browser):
299+ ... tags = find_tags_by_class(browser.contents, 'textType')
300+ ... for tag in tags:
301+ ... print '%s: %s' % (tag['name'], tag['value'])
302+
303+ >>> print_form_fields(import_browser)
304+ field.svn_branch_url: svn://svn.example.com/fooix/trunk
305+
306+ >>> import_browser.open(cvs_import_location)
307+ >>> import_browser.getLink('Edit import source or review import').click()
308+ >>> print_form_fields(import_browser)
309+ field.cvs_root: :pserver:anonymous@cvs.example.com:/fooix
310+ field.cvs_module: fooix
311+
312+ >>> import_browser.open(git_import_location)
313+ >>> import_browser.getLink('Edit import source or review import').click()
314+ >>> print_form_fields(import_browser)
315+ field.git_repo_url: git://git.example.org/fooix
316+
317+=== Editing the import location ===
318+
319+The +edit-import page allows the import operator to edit the location
320+an import is from.
321+
322+This is true for Subversion imports,
323+
324+ >>> import_browser.open(svn_import_location + '/+edit-import')
325+ >>> import_browser.getControl('Branch URL').value = \
326+ ... 'svn://svn-new.example.com/fooix/trunk'
327+ >>> import_browser.getControl('Update').click()
328+ >>> for message in get_feedback_messages(import_browser.contents):
329+ ... print extract_text(message)
330+ The code import has been updated.
331+
332+CVS imports,
333+
334+ >>> import_browser.open(cvs_import_location + '/+edit-import')
335+ >>> import_browser.getControl('Repository').value = \
336+ ... ':pserver:anonymous@cvs-new.example.com:/fooix'
337+ >>> import_browser.getControl('Module').value = \
338+ ... 'fooix2'
339+ >>> import_browser.getControl('Update').click()
340+ >>> for message in get_feedback_messages(import_browser.contents):
341+ ... print extract_text(message)
342+ The code import has been updated.
343+
344+and Git imports.
345+
346+ >>> import_browser.open(git_import_location + '/+edit-import')
347+ >>> import_browser.getControl('Repo URL').value = \
348+ ... 'git://git-new.example.org/fooix'
349+ >>> import_browser.getControl('Update').click()
350+ >>> for message in get_feedback_messages(import_browser.contents):
351+ ... print extract_text(message)
352+ The code import has been updated.
353+
354+
355+=== Approving an import ===
356+
357+When a code import is approved, a pending job is created for it.
358+
359+ >>> import_browser.open(svn_import_location + '/+edit-import')
360+ >>> import_browser.getControl('Approve').click()
361+ >>> print_import_details(import_browser)
362+ Import Status: Reviewed
363+ ...
364+ The next import is scheduled to run as soon as possible.
365+
366+ >>> for message in get_feedback_messages(import_browser.contents):
367+ ... print extract_text(message)
368+ The code import has been approved.
369+
370+
371+=== Invalidating an import ===
372+
373+ >>> import_browser.getLink('Edit import source or review import').click()
374+ >>> import_browser.getControl('Mark Invalid').click()
375+ >>> print_import_details(import_browser)
376+ Import Status: Invalid
377+ ...
378+ >>> for message in get_feedback_messages(import_browser.contents):
379+ ... print extract_text(message)
380+ The code import has been set as invalid.
381+
382+
383+=== Suspending an import ===
384+
385+ >>> import_browser.getLink('Edit import source or review import').click()
386+ >>> import_browser.getControl('Suspend').click()
387+ >>> print_import_details(import_browser)
388+ Import Status: Suspended
389+ ...
390+ >>> for message in get_feedback_messages(import_browser.contents):
391+ ... print extract_text(message)
392+ The code import has been suspended.
393+
394+
395+=== Marking an import as failing ===
396+
397+ >>> import_browser.getLink('Edit import source or review import').click()
398+ >>> import_browser.getControl('Mark Failing').click()
399+ >>> print_import_details(import_browser)
400+ Import Status: Failed
401+ ...
402+ >>> for message in get_feedback_messages(import_browser.contents):
403+ ... print extract_text(message)
404+ The code import has been marked as failing.
405+
406+
407+== Import details for a running job ==
408+
409+If the job for an approved import is running, then the import details
410+says how long ago since it started.
411+
412+ >>> import_browser.getLink('Edit import source or review import').click()
413+ >>> import_browser.getControl('Approve').click()
414+ >>> print_import_details(import_browser)
415+ Import Status: Reviewed
416+ ...
417+ The next import is scheduled to run as soon as possible.
418+
419+Now set the job as running.
420+
421+ >>> login('david.allouche@canonical.com')
422+ >>> from canonical.launchpad.testing.codeimporthelpers import (
423+ ... get_import_for_branch_name, make_running_import)
424+ >>> code_import = get_import_for_branch_name(
425+ ... svn_import_branch_unique_name)
426+
427+Set the started time to 2h 20m ago, and the approximate datetime
428+should show this as 2 hours.
429+
430+ >>> from datetime import datetime, timedelta
431+ >>> import pytz
432+ >>> date_started = datetime.now(pytz.UTC) - timedelta(hours=2, minutes=20)
433+ >>> code_import = make_running_import(
434+ ... code_import, date_started=date_started, factory=factory,
435+ ... logtail="Changeset 1\nChangeset 2")
436+ >>> logout()
437+
438+ >>> import_browser.open(svn_import_location)
439+ >>> print_import_details(import_browser)
440+ Import Status: Reviewed
441+ ...
442+ An import is currently running on machine-..., and was started 2 hours
443+ ago. The last few lines of the job's output were:
444+ Changeset 1
445+ Changeset 2
446+
447+
448+== Import details for a import that has been imported successfully ==
449+
450+If a branch has been successfully imported in the past, then the date
451+that it was last successful is shown, as well as when the next import
452+will be run -- which is, by default for Subversion, six hours after the
453+last import completed, and so in this case in about three hours.
454+
455+ >>> login('david.allouche@canonical.com')
456+ >>> from canonical.launchpad.testing.codeimporthelpers import (
457+ ... make_finished_import)
458+ >>> date_finished = datetime(2007,9,10,12, tzinfo=pytz.UTC)
459+ >>> code_import = get_import_for_branch_name(
460+ ... svn_import_branch_unique_name)
461+ >>> code_import = make_finished_import(code_import, factory=factory,
462+ ... date_finished=date_finished)
463+ >>> logout()
464+
465+ >>> import_browser.open(svn_import_location)
466+ >>> print_import_details(import_browser)
467+ Import Status: Reviewed
468+ This branch is an import of the Subversion branch from
469+ svn://svn-new.example.com/fooix/trunk.
470+ The next import is scheduled to run in 3 hours.
471+ Last successful import was on 2007-09-10.
472+ ...
473+
474+
475+== Requesting an import ==
476+
477+If an import is waiting for its next update, any logged in user can
478+click a button to request an immediate import.
479+
480+ >>> sample_person_browser = setupBrowser(
481+ ... auth='Basic test@canonical.com:test')
482+ >>> sample_person_browser.open(import_browser.url)
483+ >>> sample_person_browser.getControl('Import Now')
484+ <SubmitControl ...>
485+
486+Anonymous users cannot see this button.
487+
488+ >>> anon_browser.open(import_browser.url)
489+ >>> anon_browser.getControl('Import Now')
490+ Traceback (most recent call last):
491+ ...
492+ LookupError: label 'Import Now'
493+
494+If the logged in user clicks this button, the import will be scheduled
495+to run ASAP and the fact that the import has been requested is
496+displayed.
497+
498+ >>> sample_person_browser.getControl('Import Now').click()
499+ >>> for msg in get_feedback_messages(sample_person_browser.contents):
500+ ... print msg
501+ Import will run as soon as possible.
502+ >>> print_import_details(sample_person_browser)
503+ Import Status: Reviewed
504+ This branch is an import of the Subversion branch from
505+ svn://svn-new.example.com/fooix/trunk.
506+ The next import is scheduled to run as soon as possible (requested
507+ by Sample Person).
508+ Last successful import was on 2007-09-10.
509+ ...
510
511=== modified file 'lib/lp/code/stories/codeimport/xx-codeimport-machines.txt'
512--- lib/lp/code/stories/codeimport/xx-codeimport-machines.txt 2009-06-02 08:20:49 +0000
513+++ lib/lp/code/stories/codeimport/xx-codeimport-machines.txt 2009-11-11 09:00:39 +0000
514@@ -50,13 +50,13 @@
515 >>> print_table(browser)
516 Machine Status
517 apollo (2 jobs running) Online
518- lp://dev/~vcs-imports/.../...
519+ lp://dev/~.../.../...
520 Started: ... ago Last heartbeat: ... ago
521- lp://dev/~vcs-imports/.../...
522+ lp://dev/~.../.../...
523 Started: ... ago Last heartbeat: ... ago
524 bazaar-importer (0 jobs running) Online
525 odin (1 job running) Online
526- lp://dev/~vcs-imports/.../...
527+ lp://dev/~.../.../...
528 Started: ... ago Last heartbeat: ... ago
529 saturn Offline
530
531@@ -79,10 +79,10 @@
532
533 >>> print extract_text(find_tag_by_id(browser.contents, 'current-jobs'))
534 Current jobs
535- lp://dev/~vcs-imports/.../... Started: ... ago Last heartbeat: ... ago
536+ lp://dev/~.../.../... Started: ... ago Last heartbeat: ... ago
537 Creating changeset 1
538 Creating changeset 2
539- lp://dev/~vcs-imports/.../... Started: ... ago Last heartbeat: ... ago
540+ lp://dev/~.../.../... Started: ... ago Last heartbeat: ... ago
541
542 And also shows the most recent ten events for the machine, with the most
543 recent event first.
544@@ -92,8 +92,8 @@
545 ... print extract_text(recent_events)
546 >>> print_events(browser)
547 Related events
548- ...: Job Started lp://dev/~vcs-imports/.../...
549- ...: Job Started lp://dev/~vcs-imports/.../...
550+ ...: Job Started lp://dev/~.../.../...
551+ ...: Job Started lp://dev/~.../.../...
552 ...: Machine Online
553
554
555
556=== modified file 'lib/lp/code/stories/codeimport/xx-create-codeimport.txt'
557--- lib/lp/code/stories/codeimport/xx-create-codeimport.txt 2009-10-19 17:31:15 +0000
558+++ lib/lp/code/stories/codeimport/xx-create-codeimport.txt 2009-11-11 09:00:39 +0000
559@@ -149,7 +149,7 @@
560 ... print extract_text(message)
561 There is 1 error.
562 Those CVS details are already specified for
563- the imported branch ~vcs-imports/firefox/import2.
564+ the imported branch ~no-priv/firefox/import2.
565
566 >>> browser.getControl('Subversion').click()
567 >>> browser.getControl('Branch URL').value = (
568@@ -160,7 +160,7 @@
569 ... print extract_text(message)
570 There is 1 error.
571 This Subversion branch URL is already specified for
572- the imported branch ~vcs-imports/firefox/imported.
573+ the imported branch ~no-priv/firefox/imported.
574
575
576 Requesting an import whose name would clash
577
578=== modified file 'lib/lp/code/stories/codeimport/xx-edit-codeimport.txt'
579--- lib/lp/code/stories/codeimport/xx-edit-codeimport.txt 2009-09-18 05:49:48 +0000
580+++ lib/lp/code/stories/codeimport/xx-edit-codeimport.txt 2009-11-11 09:00:39 +0000
581@@ -1,32 +1,29 @@
582 = Editing code import details =
583
584-The code import details are displayed on the main branch page for
585-imported branches. If the logged in user is an import operator
586-(member of VCS imports or Launchpad admin) then they can see a link
587-to edit the details.
588+Not everyone can edit code import details, these stories show what it's like
589+to edit a code import if you are the owner of the code import branch.
590+
591+The code import details are displayed on the main branch page for imported
592+branches. If the logged-in user is allowed to edit the code import then they
593+can see a link to edit the details.
594
595 >>> from canonical.launchpad.ftests import ANONYMOUS, login, logout
596- >>> login('test@canonical.com')
597+ >>> from canonical.launchpad.webapp import canonical_url
598+
599+ >>> login(ANONYMOUS)
600+ >>> registrant = factory.makePerson(password='test')
601 >>> svn_import = factory.makeCodeImport(
602- ... svn_branch_url='svn://svn.example.com/fooix/trunk')
603- >>> from canonical.launchpad.webapp import canonical_url
604+ ... svn_branch_url='svn://svn.example.com/fooix/trunk',
605+ ... registrant=registrant)
606 >>> svn_import_location = str(canonical_url(svn_import.branch))
607 >>> svn_import_branch_unique_name = svn_import.branch.unique_name
608- >>> cvs_import = factory.makeCodeImport(
609- ... cvs_root=":pserver:anonymous@cvs.example.com:/fooix",
610- ... cvs_module="fooix")
611- >>> cvs_import_location = str(canonical_url(cvs_import.branch))
612- >>> cvs_import_branch_unique_name = cvs_import.branch.unique_name
613- >>> git_import = factory.makeCodeImport(
614- ... git_repo_url="git://git.example.org/fooix")
615- >>> git_import_location = str(canonical_url(git_import.branch))
616- >>> git_import_branch_unique_name = git_import.branch.unique_name
617+
618 >>> hosted_branch = factory.makeAnyBranch()
619 >>> hosted_branch_location = str(canonical_url(hosted_branch))
620 >>> logout()
621
622 >>> import_browser = setupBrowser(
623- ... auth='Basic david.allouche@canonical.com:test')
624+ ... auth='Basic %s:test' % str(registrant.preferredemail.email))
625
626
627 == Import details on the branch page ==
628@@ -42,34 +39,19 @@
629 ... div = find_tag_by_id(
630 ... browser.contents, 'branch-import-details').div.div
631 ... print extract_text(div)
632- >>> browser.open(svn_import_location)
633- >>> print_import_details(browser)
634+ >>> anon_browser.open(svn_import_location)
635+ >>> print_import_details(anon_browser)
636 Import Status: Pending Review
637 This branch is an import of the Subversion branch
638 from svn://svn.example.com/fooix/trunk.
639
640 Because it's an svn:// URL, it doesn't get linkified:
641
642- >>> browser.getLink("svn://svn.example.com/fooix/trunk")
643+ >>> anon_browser.getLink("svn://svn.example.com/fooix/trunk")
644 Traceback (most recent call last):
645 ...
646 LinkNotFoundError
647
648-Both VCS Imports members and Launchpad administrators can edit the
649-import, though:
650-
651- >>> import_browser.open(svn_import_location)
652- >>> print_import_details(import_browser)
653- Import Status: Pending Review
654- This branch is an import of the Subversion branch
655- from svn://svn.example.com/fooix/trunk.
656-
657- >>> admin_browser.open(svn_import_location)
658- >>> print_import_details(admin_browser)
659- Import Status: Pending Review
660- This branch is an import of the Subversion branch
661- from svn://svn.example.com/fooix/trunk.
662-
663
664 == Not authorized versus 404 ==
665
666@@ -77,7 +59,7 @@
667 they will get a not authorised page if the branch has a code import,
668 and a 404 if the branch doesn't have an import.
669
670- >>> browser.open(svn_import_location + '/+edit-import')
671+ >>> anon_browser.open(svn_import_location + '/+edit-import')
672 Traceback (most recent call last):
673 Unauthorized: (... 'launchpad.Edit')
674
675@@ -88,174 +70,10 @@
676
677 == Editing details ==
678
679-There are a number of buttons shown on the editing page for
680-import operators.
681-
682- >>> import_browser.getLink('Edit import source or review import').click()
683- >>> print_submit_buttons(import_browser.contents)
684- Update
685- Approve
686- Mark Invalid
687- Suspend
688- Mark Failing
689-
690-A cancel link is also shown next to the buttons, that takes the user
691-back to the main branch details page.
692-
693- >>> import_browser.getLink('Cancel').url == svn_import_location
694- True
695-
696-Only the fields that are relevant to the type of import are shown
697-in the form.
698-
699- >>> def print_form_fields(browser):
700- ... tags = find_tags_by_class(browser.contents, 'textType')
701- ... for tag in tags:
702- ... print '%s: %s' % (tag['name'], tag['value'])
703-
704- >>> print_form_fields(import_browser)
705- field.svn_branch_url: svn://svn.example.com/fooix/trunk
706-
707- >>> admin_browser.open(cvs_import_location)
708- >>> admin_browser.getLink('Edit import source or review import').click()
709- >>> print_form_fields(admin_browser)
710- field.cvs_root: :pserver:anonymous@cvs.example.com:/fooix
711- field.cvs_module: fooix
712-
713- >>> admin_browser.open(git_import_location)
714- >>> admin_browser.getLink('Edit import source or review import').click()
715- >>> print_form_fields(admin_browser)
716- field.git_repo_url: git://git.example.org/fooix
717-
718-=== Editing the import location ===
719-
720-The +edit-import page allows the import operator to edit the location
721-an import is from.
722-
723-This is true for Subversion imports,
724-
725- >>> import_browser.open(svn_import_location + '/+edit-import')
726- >>> import_browser.getControl('Branch URL').value = \
727- ... 'svn://svn-new.example.com/fooix/trunk'
728- >>> import_browser.getControl('Update').click()
729- >>> for message in get_feedback_messages(import_browser.contents):
730- ... print extract_text(message)
731- The code import has been updated.
732-
733-CVS imports,
734-
735- >>> import_browser.open(cvs_import_location + '/+edit-import')
736- >>> import_browser.getControl('Repository').value = \
737- ... ':pserver:anonymous@cvs-new.example.com:/fooix'
738- >>> import_browser.getControl('Module').value = \
739- ... 'fooix2'
740- >>> import_browser.getControl('Update').click()
741- >>> for message in get_feedback_messages(import_browser.contents):
742- ... print extract_text(message)
743- The code import has been updated.
744-
745-and Git imports.
746-
747- >>> import_browser.open(git_import_location + '/+edit-import')
748- >>> import_browser.getControl('Repo URL').value = \
749- ... 'git://git-new.example.org/fooix'
750- >>> import_browser.getControl('Update').click()
751- >>> for message in get_feedback_messages(import_browser.contents):
752- ... print extract_text(message)
753- The code import has been updated.
754-
755-
756-=== Approving an import ===
757-
758-When a code import is approved, a pending job is created for it.
759-
760- >>> import_browser.open(svn_import_location + '/+edit-import')
761- >>> import_browser.getControl('Approve').click()
762- >>> print_import_details(import_browser)
763- Import Status: Reviewed
764- ...
765- The next import is scheduled to run as soon as possible.
766-
767- >>> for message in get_feedback_messages(import_browser.contents):
768- ... print extract_text(message)
769- The code import has been approved.
770-
771-
772-=== Invalidating an import ===
773-
774- >>> import_browser.getLink('Edit import source or review import').click()
775- >>> import_browser.getControl('Mark Invalid').click()
776- >>> print_import_details(import_browser)
777- Import Status: Invalid
778- ...
779- >>> for message in get_feedback_messages(import_browser.contents):
780- ... print extract_text(message)
781- The code import has been set as invalid.
782-
783-
784-=== Suspending an import ===
785-
786- >>> import_browser.getLink('Edit import source or review import').click()
787- >>> import_browser.getControl('Suspend').click()
788- >>> print_import_details(import_browser)
789- Import Status: Suspended
790- ...
791- >>> for message in get_feedback_messages(import_browser.contents):
792- ... print extract_text(message)
793- The code import has been suspended.
794-
795-
796-=== Marking an import as failing ===
797-
798- >>> import_browser.getLink('Edit import source or review import').click()
799- >>> import_browser.getControl('Mark Failing').click()
800- >>> print_import_details(import_browser)
801- Import Status: Failed
802- ...
803- >>> for message in get_feedback_messages(import_browser.contents):
804- ... print extract_text(message)
805- The code import has been marked as failing.
806-
807-
808-== Import details for a running job ==
809-
810-If the job for an approved import is running, then the import details
811-says how long ago since it started.
812-
813- >>> import_browser.getLink('Edit import source or review import').click()
814- >>> import_browser.getControl('Approve').click()
815- >>> print_import_details(import_browser)
816- Import Status: Reviewed
817- ...
818- The next import is scheduled to run as soon as possible.
819-
820-Now set the job as running.
821-
822- >>> login('david.allouche@canonical.com')
823- >>> from canonical.launchpad.testing.codeimporthelpers import (
824- ... get_import_for_branch_name, make_running_import)
825- >>> code_import = get_import_for_branch_name(
826- ... svn_import_branch_unique_name)
827-
828-Set the started time to 2h 20m ago, and the approximate datetime
829-should show this as 2 hours.
830-
831- >>> from datetime import datetime, timedelta
832- >>> import pytz
833- >>> date_started = datetime.now(pytz.UTC) - timedelta(hours=2, minutes=20)
834- >>> code_import = make_running_import(
835- ... code_import, date_started=date_started, factory=factory,
836- ... logtail="Changeset 1\nChangeset 2")
837- >>> logout()
838-
839 >>> import_browser.open(svn_import_location)
840- >>> print_import_details(import_browser)
841- Import Status: Reviewed
842- ...
843- An import is currently running on machine-..., and was started 2 hours
844- ago. The last few lines of the job's output were:
845- Changeset 1
846- Changeset 2
847+ >>> import_browser.getLink('Edit import source or review import')
848+ Traceback (most recent call last):
849+ LinkNotFoundError
850
851
852 == Import details for a import that has been imported successfully ==
853@@ -265,10 +83,16 @@
854 will be run -- which is, by default for Subversion, six hours after the
855 last import completed, and so in this case in about three hours.
856
857+We log in as "david.allouche" since he has super-user privileges for code
858+imports.
859+
860 >>> login('david.allouche@canonical.com')
861 >>> from canonical.launchpad.testing.codeimporthelpers import (
862- ... make_finished_import)
863- >>> date_finished = datetime(2007,9,10,12, tzinfo=pytz.UTC)
864+ ... make_finished_import, get_import_for_branch_name,
865+ ... make_running_import)
866+ >>> from datetime import datetime
867+ >>> import pytz
868+ >>> date_finished = datetime(2007, 9, 10, 12, tzinfo=pytz.UTC)
869 >>> code_import = get_import_for_branch_name(
870 ... svn_import_branch_unique_name)
871 >>> code_import = make_finished_import(code_import, factory=factory,
872@@ -279,8 +103,8 @@
873 >>> print_import_details(import_browser)
874 Import Status: Reviewed
875 This branch is an import of the Subversion branch from
876- svn://svn-new.example.com/fooix/trunk.
877- The next import is scheduled to run in 3 hours.
878+ svn://svn.example.com/fooix/trunk.
879+ The next import is scheduled to run in 5 hours.
880 Last successful import was on 2007-09-10.
881 ...
882
883@@ -315,8 +139,18 @@
884 >>> print_import_details(sample_person_browser)
885 Import Status: Reviewed
886 This branch is an import of the Subversion branch from
887- svn://svn-new.example.com/fooix/trunk.
888+ svn://svn.example.com/fooix/trunk.
889 The next import is scheduled to run as soon as possible (requested
890 by Sample Person).
891 Last successful import was on 2007-09-10.
892 ...
893+
894+
895+== Deleting an import ==
896+
897+If you own the branch that has the code import, you can delete the branch.
898+
899+ >>> import_browser.getLink('Delete branch').click()
900+ >>> import_browser.getControl('Delete').click()
901+ >>> print_feedback_messages(import_browser.contents)
902+ Branch ... deleted.
903
904=== modified file 'lib/lp/code/stories/codeimport/xx-failing-codeimport.txt'
905--- lib/lp/code/stories/codeimport/xx-failing-codeimport.txt 2009-05-13 03:41:09 +0000
906+++ lib/lp/code/stories/codeimport/xx-failing-codeimport.txt 2009-11-11 09:00:39 +0000
907@@ -9,8 +9,9 @@
908 >>> from lp.code.interfaces.codeimportresult import CodeImportResultStatus
909 >>> from canonical.config import config
910 >>> product = factory.makeProduct(name='imported')
911+ >>> owner = factory.makePerson(name='import-owner')
912 >>> code_import = factory.makeCodeImport(
913- ... product=product, branch_name='trunk')
914+ ... product=product, branch_name='trunk', registrant=owner)
915 >>> for i in range(config.codeimport.consecutive_failure_limit):
916 ... dummy = make_finished_import(
917 ... code_import, CodeImportResultStatus.FAILURE,
918@@ -20,7 +21,7 @@
919 This is shown on the branch index page:
920
921 >>> user_browser.open(
922- ... 'http://code.launchpad.dev/~vcs-imports/imported/trunk')
923+ ... 'http://code.launchpad.dev/~import-owner/imported/trunk')
924 >>> print extract_text(find_tag_by_id(user_browser.contents, 'failing-try-again'))
925 The import has been suspended because it failed 5 or more times in succession.
926
927@@ -33,7 +34,7 @@
928 Anonymous users do not see this button, however.
929
930 >>> anon_browser.open(
931- ... 'http://code.launchpad.dev/~vcs-imports/imported/trunk')
932+ ... 'http://code.launchpad.dev/~import-owner/imported/trunk')
933 >>> anon_browser.getControl('Try Again')
934 Traceback (most recent call last):
935 ...
936
937=== modified file 'lib/lp/code/tests/test_branch.py'
938--- lib/lp/code/tests/test_branch.py 2009-10-20 07:47:54 +0000
939+++ lib/lp/code/tests/test_branch.py 2009-11-11 09:00:39 +0000
940@@ -15,7 +15,7 @@
941 from lp.archiveuploader.permission import verify_upload
942 from lp.code.enums import (
943 BranchSubscriptionDiffSize, BranchSubscriptionNotificationLevel,
944- CodeReviewNotificationLevel)
945+ BranchType, CodeReviewNotificationLevel)
946 from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch
947 from lp.registry.interfaces.distroseries import DistroSeriesStatus
948 from lp.registry.interfaces.pocket import PackagePublishingPocket
949@@ -231,6 +231,16 @@
950 branch = self.factory.makeAnyBranch(owner=team)
951 self.assertCanEdit(person, branch)
952
953+ def test_vcs_imports_members_can_edit_import_branch(self):
954+ # Even if a branch isn't owned by vcs-imports, vcs-imports members can
955+ # edit it if it has a code import associated with it.
956+ person = self.factory.makePerson()
957+ branch = self.factory.makeCodeImport().branch
958+ vcs_imports = getUtility(ILaunchpadCelebrities).vcs_imports
959+ removeSecurityProxy(vcs_imports).addMember(
960+ person, vcs_imports.teamowner)
961+ self.assertCanEdit(person, branch)
962+
963 def makeOfficialPackageBranch(self):
964 """Make a branch linked to the pocket of a source package."""
965 branch = self.factory.makePackageBranch()
966@@ -307,6 +317,28 @@
967 # permissions granted above.
968 self.assertCanEdit(person, branch)
969
970+ def test_arbitrary_person_cannot_edit(self):
971+ # Arbitrary people cannot edit branches, you have to be someone
972+ # special.
973+ branch = self.factory.makeAnyBranch()
974+ person = self.factory.makePerson()
975+ self.assertCannotEdit(person, branch)
976+
977+ def test_code_import_registrant_can_edit(self):
978+ # It used to be the case that all import branches were owned by the
979+ # special, restricted team ~vcs-imports. This made a lot of work for
980+ # the Launchpad development team, since they needed to delete and
981+ # rename import branches whenever people wanted it. To reduce this
982+ # work a little, whoever registered of a code import branch is allowed
983+ # to edit the branch, even if they aren't one of the owners.
984+ registrant = self.factory.makePerson()
985+ code_import = self.factory.makeCodeImport(registrant=registrant)
986+ branch = code_import.branch
987+ removeSecurityProxy(branch).setOwner(
988+ getUtility(ILaunchpadCelebrities).vcs_imports,
989+ getUtility(ILaunchpadCelebrities).vcs_imports)
990+ self.assertCanEdit(registrant, branch)
991+
992
993 def test_suite():
994 return unittest.TestLoader().loadTestsFromName(__name__)
995
996=== modified file 'lib/lp/testing/factory.py'
997--- lib/lp/testing/factory.py 2009-10-30 17:32:39 +0000
998+++ lib/lp/testing/factory.py 2009-11-11 09:00:39 +0000
999@@ -1193,7 +1193,7 @@
1000
1001 def makeCodeImport(self, svn_branch_url=None, cvs_root=None,
1002 cvs_module=None, product=None, branch_name=None,
1003- git_repo_url=None):
1004+ git_repo_url=None, registrant=None):
1005 """Create and return a new, arbitrary code import.
1006
1007 The type of code import will be inferred from the source details
1008@@ -1207,7 +1207,8 @@
1009 product = self.makeProduct()
1010 if branch_name is None:
1011 branch_name = self.getUniqueString('name')
1012- registrant = self.makePerson()
1013+ if registrant is None:
1014+ registrant = self.makePerson()
1015
1016 code_import_set = getUtility(ICodeImportSet)
1017 if svn_branch_url is not None: