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

Proposed by Jelmer Vernooij
Status: Merged
Approved by: Jelmer Vernooij
Approved revision: no longer in the source branch.
Merged at revision: 14112
Proposed branch: lp:~jelmer/launchpad/bzr-code-imports-ui
Merge into: lp:launchpad
Prerequisite: lp:~jelmer/launchpad/bzr-code-imports
Diff against target: 1310 lines (+227/-479)
17 files modified
lib/lp/app/widgets/doc/launchpad-radio-widget.txt (+3/-3)
lib/lp/code/browser/branch.py (+8/-50)
lib/lp/code/browser/codeimport.py (+30/-10)
lib/lp/code/browser/tests/test_branch.py (+3/-13)
lib/lp/code/enums.py (+2/-2)
lib/lp/code/stories/branches/xx-branch-deletion.txt (+0/-2)
lib/lp/code/stories/branches/xx-branch-url-validation.txt (+0/-72)
lib/lp/code/stories/branches/xx-creating-branches.txt (+0/-188)
lib/lp/code/stories/branches/xx-junk-branches.txt (+2/-4)
lib/lp/code/stories/codeimport/xx-create-codeimport.txt (+62/-16)
lib/lp/code/templates/branch-form-macros.pt (+0/-42)
lib/lp/code/templates/branch-import-details.pt (+28/-4)
lib/lp/code/templates/codeimport-list.pt (+1/-0)
lib/lp/code/templates/codeimport-new.pt (+16/-0)
lib/lp/registry/browser/productseries.py (+35/-51)
lib/lp/registry/browser/tests/productseries-setbranch-view.txt (+36/-21)
lib/lp/registry/templates/productseries-codesummary.pt (+1/-1)
To merge this branch: bzr merge lp:~jelmer/launchpad/bzr-code-imports-ui
Reviewer Review Type Date Requested Status
Benji York (community) code Approve
Michael Hudson-Doyle code Pending
Brad Crittenden Pending
Ian Booth code* Pending
Review via email: mp+77961@code.launchpad.net

This proposal supersedes a proposal from 2011-08-27.

Commit message

[r=benji][bug=352124,352131,419524,611837] Make it possible to create new Bazaar code imports in the code import UI, disable the ability to create mirror branches.

Description of the change

Make it possible to create new Bazaar code imports in the code import UI, disable the ability to create mirror branches.

This removes the user-visible distinction in the web UI between code imports (syncing from remote non-bzr branches) and mirrors (syncing from remote bzr branches), which should make the UI a bit easier to comprehend.

This doesn't migrate any of the existing mirror branches to be code imports, but that would be the next logical step.

It also doesn't remove the ability to create old-style mirrors via the XML/RPC API used by the "bzr register-branch" command in bzr.

To post a comment you must log in.
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote : Posted in a previous version of this proposal

+ bzr_branch_url = URIField(
...
+ allow_query=False, # Query makes no sense in Mercurial

:-)

Otherwise, well it looks fine. It's great to see so much code being deleted. I don't have the time to do a line by line review, and I don't have the Launchpad dev environment set up on my new laptop so I can't play around with it. I worry that there are probably more places that implicitly assume import branches are foreign in some way, but maybe not (also approximately noone uses mirror branches any more aiui).

It would be SO GREAT if we could get rid of the puller after this. I think this requires having some kind of token mechanism that grants the holder of the token the ability to read and write to a particular branch (and read any stacked on branches I guess) via the usual codehosting access. But that can wait :)

Revision history for this message
Brad Crittenden (bac) wrote : Posted in a previous version of this proposal

Hi Jelmer,

Thanks for trying to clean up this UI. It has always been a bit of a mess. I think your simplifications are good.

Twice in browser/codeimport.py we have a list of valid RCS types. It would be nice to define those one place rather than using the same set of literals twice such as:

@@ -576,7 +592,8 @@
151 elif self.code_import.rcs_type in (RevisionControlSystems.SVN,
152 RevisionControlSystems.BZR_SVN,
153 RevisionControlSystems.GIT,
154 - RevisionControlSystems.HG):
155 + RevisionControlSystems.HG,
156 + RevisionControlSystems.BZR):

As with all UI changes, screenshot of before and after as well as clear instructions on how to exercise the changes makes for speedier review.

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

Another direction the UI could go of course would be to not overly
distinguish between the various import types and just let the foreign
branch plugins figure it all out. This feels like it would make for a
simpler user experience, but it would also certainly be a big change. I
wonder if it might seem a bit too magical?

Cheers,
mwh

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

On 26/06/11 23:26, Michael Hudson-Doyle wrote:
> Another direction the UI could go of course would be to not overly
> distinguish between the various import types and just let the foreign
> branch plugins figure it all out. This feels like it would make for a
> simpler user experience, but it would also certainly be a big change. I
> wonder if it might seem a bit too magical?
That's an interesting idea. "bzr branch" has that magic to figure out
what format the source branch is in too, and it seems to work fine
there. One way to do this would be to add a "AUTO" value to
RevisionControlSystems that simply tries the probers registered for each
of the CodeImportWorkers. That would (I think) be a minimal amount of
work, though it would of course only work for those importers that
actually have probers (BZR, BZR_SVN, GIT, HG).

CVS would still need to be special, but I guess there's no way around
that anyway, as it requires a root and a module to be specified.

Cheers,

Jelmer

Revision history for this message
Robert Collins (lifeless) wrote : Posted in a previous version of this proposal

On Mon, Jun 27, 2011 at 11:04 AM, Jelmer Vernooij <email address hidden> wrote:
> CVS would still need to be special, but I guess there's no way around
> that anyway, as it requires a root and a module to be specified.

see config-manager for a url format for cvs that includes the root and module.

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

On Mon, 27 Jun 2011 00:01:14 +0200, Jelmer Vernooij <email address hidden> wrote:
> On 26/06/11 23:26, Michael Hudson-Doyle wrote:
> > Another direction the UI could go of course would be to not overly
> > distinguish between the various import types and just let the foreign
> > branch plugins figure it all out. This feels like it would make for a
> > simpler user experience, but it would also certainly be a big change. I
> > wonder if it might seem a bit too magical?
> That's an interesting idea. "bzr branch" has that magic to figure out
> what format the source branch is in too, and it seems to work fine
> there. One way to do this would be to add a "AUTO" value to
> RevisionControlSystems that simply tries the probers registered for each
> of the CodeImportWorkers. That would (I think) be a minimal amount of
> work, though it would of course only work for those importers that
> actually have probers (BZR, BZR_SVN, GIT, HG).

Yeah, that would be pretty neat, although presenting bzr/svn/git/hg/auto
to the user wouldn't really be progress :)

> CVS would still need to be special, but I guess there's no way around
> that anyway, as it requires a root and a module to be specified.

Apparently config-manager defines a URL format for CVS we could borrow.
But that would be more useful for cleaning up the internals, I don't
think we could get away with expecting the user to know about it.

Cheers,
mwh

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

Hi Brad,

On 24/06/11 20:15, Brad Crittenden wrote:
> Thanks for trying to clean up this UI. It has always been a bit of a mess. I think your simplifications are good.
>
> Twice in browser/codeimport.py we have a list of valid RCS types. It would be nice to define those one place rather than using the same set of literals twice such as:
>
> @@ -576,7 +592,8 @@
> 151 elif self.code_import.rcs_type in (RevisionControlSystems.SVN,
> 152 RevisionControlSystems.BZR_SVN,
> 153 RevisionControlSystems.GIT,
> 154 - RevisionControlSystems.HG):
> 155 + RevisionControlSystems.HG,
> 156 + RevisionControlSystems.BZR):
Updated to use a single list.
>
>
> As with all UI changes, screenshot of before and after as well as clear instructions on how to exercise the changes makes for speedier review.
Thanks for the review! I'll make sure to include screenshots next time.

Cheers,

Jelmer

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

On Sun, 2011-06-26 at 23:19 +0000, Robert Collins wrote:
> On Mon, Jun 27, 2011 at 11:04 AM, Jelmer Vernooij <email address hidden> wrote:
> > CVS would still need to be special, but I guess there's no way around
> > that anyway, as it requires a root and a module to be specified.
> see config-manager for a url format for cvs that includes the root and module.
Even if we do that, do we want users to have to know about that
particular format?

Cheers,

Jelmer

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

On Mon, 25 Jul 2011 13:21:44 -0000, Jelmer Vernooij <email address hidden> wrote:
> On Sun, 2011-06-26 at 23:19 +0000, Robert Collins wrote:
> > On Mon, Jun 27, 2011 at 11:04 AM, Jelmer Vernooij <email address hidden> wrote:
> > > CVS would still need to be special, but I guess there's no way around
> > > that anyway, as it requires a root and a module to be specified.
> > see config-manager for a url format for cvs that includes the root and module.
> Even if we do that, do we want users to have to know about that
> particular format?

No.

Cheers,
mwh

Revision history for this message
Ian Booth (wallyworld) wrote : Posted in a previous version of this proposal

I like UI simplification so this is a nice change.

A few issues to fix:

1. The conflict in sourcedeps.cache

2. There's an issue on the +new-import page. When first loaded, the bzr branch url field is enabled (and the url fields for the other vcs types are disabled) implying that bzr is the default choice. However, the bzr radio button is not selected by default. Given that this happens, there's a test that should be written. In xx-create-code-import.txt, there's tests for git|mercurial|svn|cvs imports but not bzr. I think a bzr test needs to be added here.

3. Line 120, 121. The comment says "# Query makes no sense in Mercurial" but it should be bzr

4. Did you run lint? There's some lines > 78 characters plus other things like number of blank lines (some of it will be pre-existing but worth fixing as a drive-by). Normally we would like to see the lint output under the ==Lint == heading in the mp description.

5. In ProductSeriesSetBranchView._createBzrBranch, the docstring needs updating to match the new behaviour. Same with testBranchAddRequests too I think? Along similar lines, is the text at the top of xx-creating-branches.txt still 100% correct?

Dumb question 101 - this mp seems to kill the ability to create remote branches. I assume thats intended and such branches are no longer supported?

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

Thanks Ian, much appreciated. I've finally found some time to update this MP and address the issues you raised.

xx-create-codeimport.txt has been updated to also test behaviour with regular bzr branches, and I've fixed the bug you mentioned.

I've fixed sourcedeps.cache and the comments about Mercurial and docstring in ProductSeriesSetBranchView._createBzrBranch. I've also enabled authenticated code imports, which are supported by bzr-{svn,hg,git} and bzr.

Removing remote branches was indeed intentional. There was an open bug about removing them, which I have linked to the branch.

"make lint" doesn't display any errors for me - how are you using it, does it need additional configuration?

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

FWIW Bazaar code imports already work - they can be created through the API. There is a list of existing imports here:

https://code.launchpad.net/+code-imports?field.review_status=&field.review_status-empty-marker=1&field.rcs_type=BZR&field.rcs_type-empty-marker=1&submit=Submit

Revision history for this message
Benji York (benji) wrote : Posted in a previous version of this proposal

This branch looks good to me.

Regarding the lint that Ian mentioned, here's a lint report that shows the problems:

./lib/lp/app/widgets/doc/launchpad-radio-widget.txt
       1: narrative uses a moin header.
      38: narrative uses a moin header.
./lib/lp/code/browser/codeimport.py
     361: Line exceeds 78 characters.
     270: E261 at least two spaces before inline comment
     280: E261 at least two spaces before inline comment
     361: E501 line too long (81 characters)
./lib/lp/code/stories/branches/xx-junk-branches.txt
      33: narrative exceeds 78 characters.
./lib/lp/code/stories/codeimport/xx-create-codeimport.txt
      43: source exceeds 78 characters.
./lib/lp/registry/browser/tests/productseries-setbranch-view.txt
      98: source exceeds 78 characters.
     115: source exceeds 78 characters.
     137: source exceeds 78 characters.
     144: want exceeds 78 characters.
     164: source exceeds 78 characters.
     184: source exceeds 78 characters.
     189: want exceeds 78 characters.
     203: source exceeds 78 characters.
     223: source exceeds 78 characters.
     243: source exceeds 78 characters.
     263: source exceeds 78 characters.
     283: source exceeds 78 characters.
     304: source exceeds 78 characters.
     370: want exceeds 78 characters.
     388: want exceeds 78 characters.
     408: source exceeds 78 characters.

review: Approve (code)
Revision history for this message
Benji York (benji) wrote :

This branch looks good to me.

Regarding the lint that Ian mentioned, here's a lint report that shows
the problems:

./lib/lp/app/widgets/doc/launchpad-radio-widget.txt
       1: narrative uses a moin header.
      38: narrative uses a moin header.
./lib/lp/code/browser/codeimport.py
     361: Line exceeds 78 characters.
     270: E261 at least two spaces before inline comment
     280: E261 at least two spaces before inline comment
     361: E501 line too long (81 characters)
./lib/lp/code/stories/branches/xx-junk-branches.txt
      33: narrative exceeds 78 characters.
./lib/lp/code/stories/codeimport/xx-create-codeimport.txt
      43: source exceeds 78 characters.
./lib/lp/registry/browser/tests/productseries-setbranch-view.txt
      98: source exceeds 78 characters.
     115: source exceeds 78 characters.
     137: source exceeds 78 characters.
     144: want exceeds 78 characters.
     164: source exceeds 78 characters.
     184: source exceeds 78 characters.
     189: want exceeds 78 characters.
     203: source exceeds 78 characters.
     223: source exceeds 78 characters.
     243: source exceeds 78 characters.
     263: source exceeds 78 characters.
     283: source exceeds 78 characters.
     304: source exceeds 78 characters.
     370: want exceeds 78 characters.
     388: want exceeds 78 characters.
     408: source exceeds 78 characters.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/app/widgets/doc/launchpad-radio-widget.txt'
2--- lib/lp/app/widgets/doc/launchpad-radio-widget.txt 2011-04-28 02:59:45 +0000
3+++ lib/lp/app/widgets/doc/launchpad-radio-widget.txt 2011-10-04 14:34:53 +0000
4@@ -75,9 +75,9 @@
5 <td><label for="field.branch_type.2">Imported</label></td>
6 </tr>
7 <tr>
8- <td class="formHelp">Branches that have been converted from some
9- other revision control system into bzr and are made available through
10- Launchpad. </td>
11+ <td class="formHelp">Branches that have been imported from an
12+ externally hosted branch in bzr or another VCS and are made available
13+ through Launchpad. </td>
14 </tr>
15 <tr>
16 <td rowspan="2"><input class="radioType" id="field.branch_type.3"
17
18=== modified file 'lib/lp/code/browser/branch.py'
19--- lib/lp/code/browser/branch.py 2011-09-13 05:23:16 +0000
20+++ lib/lp/code/browser/branch.py 2011-10-04 14:34:53 +0000
21@@ -641,9 +641,10 @@
22 (RevisionControlSystems.SVN, RevisionControlSystems.BZR_SVN)
23
24 @property
25- def svn_url_is_web(self):
26- """True if an imported branch's SVN URL is HTTP or HTTPS."""
27- # You should only be calling this if it's an SVN code import
28+ def url_is_web(self):
29+ """True if an imported branch's URL is HTTP or HTTPS."""
30+ # You should only be calling this if it's an SVN, BZR, GIT or HG code
31+ # import
32 assert self.context.code_import
33 url = self.context.code_import.url
34 assert url
35@@ -1146,15 +1147,12 @@
36
37 class schema(Interface):
38 use_template(
39- IBranch, include=['owner', 'name', 'url', 'lifecycle_status'])
40- branch_type = copy_field(
41- IBranch['branch_type'], vocabulary=UICreatableBranchType)
42+ IBranch, include=['owner', 'name', 'lifecycle_status'])
43
44 for_input = True
45- field_names = ['owner', 'name', 'branch_type', 'url', 'lifecycle_status']
46+ field_names = ['owner', 'name', 'lifecycle_status']
47
48 branch = None
49- custom_widget('branch_type', LaunchpadRadioWidgetWithDescription)
50 custom_widget('lifecycle_status', LaunchpadRadioWidgetWithDescription)
51
52 initial_focus_widget = 'name'
53@@ -1183,27 +1181,17 @@
54 """
55 return IPerson(self.context, self.user)
56
57- def showOptionalMarker(self, field_name):
58- """Don't show the optional marker for url."""
59- if field_name == 'url':
60- return False
61- else:
62- return LaunchpadFormView.showOptionalMarker(self, field_name)
63-
64 @action('Register Branch', name='add')
65 def add_action(self, action, data):
66 """Handle a request to create a new branch for this product."""
67 try:
68- ui_branch_type = data['branch_type']
69 namespace = self.target.getNamespace(data['owner'])
70 self.branch = namespace.createBranch(
71- branch_type=BranchType.items[ui_branch_type.name],
72+ branch_type=BranchType.HOSTED,
73 name=data['name'],
74 registrant=self.user,
75- url=data.get('url'),
76+ url=None,
77 lifecycle_status=data['lifecycle_status'])
78- if self.branch.branch_type == BranchType.MIRRORED:
79- self.branch.requestMirror()
80 except BranchCreationForbidden:
81 self.addError(
82 "You are not allowed to create branches in %s." %
83@@ -1221,36 +1209,6 @@
84 'owner',
85 'You are not a member of %s' % owner.displayname)
86
87- branch_type = data.get('branch_type')
88- # If branch_type failed to validate, then the rest of the method
89- # doesn't make any sense.
90- if branch_type is None:
91- return
92-
93- # If the branch is a MIRRORED branch, then the url
94- # must be supplied, and if HOSTED the url must *not*
95- # be supplied.
96- url = data.get('url')
97- if branch_type == UICreatableBranchType.MIRRORED:
98- if url is None:
99- # If the url is not set due to url validation errors,
100- # there will be an error set for it.
101- error = self.getFieldError('url')
102- if not error:
103- self.setFieldError(
104- 'url',
105- 'Branch URLs are required for Mirrored branches.')
106- elif branch_type == UICreatableBranchType.HOSTED:
107- if url is not None:
108- self.setFieldError(
109- 'url',
110- 'Branch URLs cannot be set for Hosted branches.')
111- elif branch_type == UICreatableBranchType.REMOTE:
112- # A remote location can, but doesn't have to be set.
113- pass
114- else:
115- raise AssertionError('Unknown branch type')
116-
117 @property
118 def cancel_url(self):
119 return canonical_url(self.context)
120
121=== modified file 'lib/lp/code/browser/codeimport.py'
122--- lib/lp/code/browser/codeimport.py 2011-09-26 06:30:07 +0000
123+++ lib/lp/code/browser/codeimport.py 2011-10-04 14:34:53 +0000
124@@ -252,7 +252,7 @@
125 "The URL of the git repository. The HEAD branch will be "
126 "imported."),
127 allowed_schemes=["git", "http", "https"],
128- allow_userinfo=False, # Only anonymous access is supported.
129+ allow_userinfo=True,
130 allow_port=True,
131 allow_query=False,
132 allow_fragment=False,
133@@ -264,12 +264,22 @@
134 "The URL of the Mercurial repository. The tip branch will be "
135 "imported."),
136 allowed_schemes=["http", "https"],
137- allow_userinfo=False, # Only anonymous access is supported.
138+ allow_userinfo=True,
139 allow_port=True,
140- allow_query=False, # Query makes no sense in Mercurial.
141- allow_fragment=False, # Fragment makes no sense in Mercurial.
142+ allow_query=False, # Query makes no sense in Bazaar.
143+ allow_fragment=False, # Fragment makes no sense in Bazaar.
144 trailing_slash=False) # See http://launchpad.net/bugs/56357.
145
146+ bzr_branch_url = URIField(
147+ title=_("Branch URL"), required=False,
148+ description=_("The URL of the Bazaar branch."),
149+ allowed_schemes=["http", "https", "bzr"],
150+ allow_userinfo=True,
151+ allow_port=True,
152+ allow_query=False, # Query makes no sense in Bazaar
153+ allow_fragment=False, # Fragment makes no sense in Bazaar
154+ trailing_slash=False)
155+
156 branch_name = copy_field(
157 IBranch['name'],
158 __name__='branch_name',
159@@ -298,7 +308,7 @@
160 def initial_values(self):
161 return {
162 'owner': self.user,
163- 'rcs_type': RevisionControlSystems.BZR_SVN,
164+ 'rcs_type': RevisionControlSystems.BZR,
165 'branch_name': 'trunk',
166 }
167
168@@ -346,9 +356,11 @@
169 # display them separately in the form.
170 soup = BeautifulSoup(self.widgets['rcs_type']())
171 fields = soup.findAll('input')
172- [cvs_button, svn_button, git_button, hg_button, empty_marker] = [
173- field for field in fields
174- if field.get('value') in ['CVS', 'BZR_SVN', 'GIT', 'HG', '1']]
175+ [cvs_button, svn_button, git_button, hg_button, bzr_button,
176+ empty_marker] = [
177+ field for field in fields
178+ if field.get('value') in [
179+ 'CVS', 'BZR_SVN', 'GIT', 'HG', 'BZR', '1']]
180 cvs_button['onclick'] = 'updateWidgets()'
181 svn_button['onclick'] = 'updateWidgets()'
182 git_button['onclick'] = 'updateWidgets()'
183@@ -358,6 +370,7 @@
184 self.rcs_type_svn = str(svn_button)
185 self.rcs_type_git = str(git_button)
186 self.rcs_type_hg = str(hg_button)
187+ self.rcs_type_bzr = str(bzr_button)
188 self.rcs_type_emptymarker = str(empty_marker)
189
190 def _getImportLocation(self, data):
191@@ -371,6 +384,8 @@
192 return None, None, data.get('git_repo_url')
193 elif rcs_type == RevisionControlSystems.HG:
194 return None, None, data.get('hg_repo_url')
195+ elif rcs_type == RevisionControlSystems.BZR:
196+ return None, None, data.get('bzr_branch_url')
197 else:
198 raise AssertionError(
199 'Unexpected revision control type %r.' % rcs_type)
200@@ -463,6 +478,9 @@
201 elif rcs_type == RevisionControlSystems.HG:
202 self._validateURL(
203 data.get('hg_repo_url'), field_name='hg_repo_url')
204+ elif rcs_type == RevisionControlSystems.BZR:
205+ self._validateURL(
206+ data.get('bzr_branch_url'), field_name='bzr_branch_url')
207 else:
208 raise AssertionError(
209 'Unexpected revision control type %r.' % rcs_type)
210@@ -555,7 +573,8 @@
211 elif self.code_import.rcs_type in (RevisionControlSystems.SVN,
212 RevisionControlSystems.BZR_SVN,
213 RevisionControlSystems.GIT,
214- RevisionControlSystems.HG):
215+ RevisionControlSystems.HG,
216+ RevisionControlSystems.BZR):
217 self.form_fields = self.form_fields.omit(
218 'cvs_root', 'cvs_module')
219 else:
220@@ -590,7 +609,8 @@
221 elif self.code_import.rcs_type in (RevisionControlSystems.SVN,
222 RevisionControlSystems.BZR_SVN,
223 RevisionControlSystems.GIT,
224- RevisionControlSystems.HG):
225+ RevisionControlSystems.HG,
226+ RevisionControlSystems.BZR):
227 self._validateURL(data.get('url'), self.code_import)
228 else:
229 raise AssertionError('Unknown rcs_type for code import.')
230
231=== modified file 'lib/lp/code/browser/tests/test_branch.py'
232--- lib/lp/code/browser/tests/test_branch.py 2011-08-29 07:55:48 +0000
233+++ lib/lp/code/browser/tests/test_branch.py 2011-10-04 14:34:53 +0000
234@@ -191,8 +191,8 @@
235 "This is a short error message.",
236 branch_view.mirror_status_message)
237
238- def testBranchAddRequestsMirror(self):
239- """Registering a mirrored branch requests a mirror."""
240+ def testBranchAddRequests(self):
241+ """Registering a branch that requests a mirror."""
242 arbitrary_person = self.factory.makePerson()
243 arbitrary_product = self.factory.makeProduct()
244 login_person(arbitrary_person)
245@@ -200,9 +200,8 @@
246 add_view = BranchAddView(arbitrary_person, self.request)
247 add_view.initialize()
248 data = {
249- 'branch_type': BranchType.MIRRORED,
250+ 'branch_type': BranchType.HOSTED,
251 'name': 'some-branch',
252- 'url': 'http://example.com',
253 'title': 'Branch Title',
254 'summary': '',
255 'lifecycle_status': BranchLifecycleStatus.DEVELOPMENT,
256@@ -212,15 +211,6 @@
257 'product': arbitrary_product,
258 }
259 add_view.add_action.success(data)
260- # Make sure that next_mirror_time is a datetime, not an sqlbuilder
261- # expression.
262- removeSecurityProxy(add_view.branch).sync()
263- now = datetime.now(pytz.timezone('UTC'))
264- self.assertNotEqual(None, add_view.branch.next_mirror_time)
265- self.assertTrue(
266- add_view.branch.next_mirror_time < now,
267- "next_mirror_time not set to UTC_NOW: %s < %s"
268- % (add_view.branch.next_mirror_time, now))
269 finally:
270 logout()
271
272
273=== modified file 'lib/lp/code/enums.py'
274--- lib/lp/code/enums.py 2011-08-28 07:29:11 +0000
275+++ lib/lp/code/enums.py 2011-10-04 14:34:53 +0000
276@@ -99,8 +99,8 @@
277 IMPORTED = DBItem(3, """
278 Imported
279
280- Branches that have been converted from some other revision
281- control system into bzr and are made available through Launchpad.
282+ Branches that have been imported from an externally hosted
283+ branch in bzr or another VCS and are made available through Launchpad.
284 """)
285
286 REMOTE = DBItem(4, """
287
288=== modified file 'lib/lp/code/stories/branches/xx-branch-deletion.txt'
289--- lib/lp/code/stories/branches/xx-branch-deletion.txt 2011-06-06 19:39:45 +0000
290+++ lib/lp/code/stories/branches/xx-branch-deletion.txt 2011-10-04 14:34:53 +0000
291@@ -22,7 +22,6 @@
292 >>> browser = setupBrowser(auth="Basic alice@example.com:test")
293 >>> browser.open('http://code.launchpad.dev/earthlynx')
294 >>> browser.getLink("Register a branch").click()
295- >>> browser.getControl('Branch URL').value = 'http://foo.bar.com/oops'
296 >>> browser.getControl('Name').value = 'to-delete'
297 >>> browser.getControl('Register Branch').click()
298 >>> print browser.title
299@@ -60,7 +59,6 @@
300
301 >>> browser.open('http://code.launchpad.dev/~alice')
302 >>> browser.getLink("Register a branch").click()
303- >>> browser.getControl('Hosted').click()
304 >>> browser.getControl('Name').value = 'to-delete'
305 >>> browser.getControl('Register Branch').click()
306 >>> browser.getLink('Delete branch').click()
307
308=== removed file 'lib/lp/code/stories/branches/xx-branch-url-validation.txt'
309--- lib/lp/code/stories/branches/xx-branch-url-validation.txt 2010-08-23 05:06:49 +0000
310+++ lib/lp/code/stories/branches/xx-branch-url-validation.txt 1970-01-01 00:00:00 +0000
311@@ -1,72 +0,0 @@
312-= Validating of branch URLs =
313-
314-First, let's define a helper to post to the Person +addbranch form.
315-
316- >>> def add_branch(url):
317- ... user_browser.open(
318- ... 'http://code.launchpad.dev/~lifeless/+addbranch')
319- ... user_browser.getControl('Name').value = 'pyresources'
320- ... user_browser.getControl('Branch URL').value = url
321- ... user_browser.getControl('Register Branch').click()
322-
323-Try to create a branch with an invalid URL.
324-
325- >>> add_branch('example.com/~lifeless/pyresources/')
326- >>> for message in get_feedback_messages(user_browser.contents):
327- ... print message
328- There is 1 error.
329- "example.com/~lifeless/pyresources/" is not a valid URI
330-
331-Try to create branches with URLs in bazaar.launchpad.dev.
332-
333- >>> add_branch('http://bazaar.launchpad.dev')
334- >>> for message in get_feedback_messages(user_browser.contents):
335- ... print message
336- There is 1 error.
337- For Launchpad to mirror a branch, the original branch cannot be on
338- launchpad.dev.
339-
340- >>> add_branch('http://bazaar.launchpad.dev/~me/that/this')
341- >>> for message in get_feedback_messages(user_browser.contents):
342- ... print message
343- There is 1 error.
344- For Launchpad to mirror a branch, the original branch cannot be on
345- launchpad.dev.
346-
347- >>> add_branch('sftp://bazaar.launchpad.dev/~me/that/this')
348- >>> for message in get_feedback_messages(user_browser.contents):
349- ... print message
350- There is 1 error.
351- For Launchpad to mirror a branch, the original branch cannot be on
352- launchpad.dev.
353-
354-Try to create a branch with a URL that is already registered.
355-
356- >>> add_branch('http://example.com/gnome-terminal/main/')
357- >>> for message in get_feedback_messages(user_browser.contents):
358- ... print message
359- There is 1 error.
360- The bzr branch lp://dev/~name12/gnome-terminal/main is already registered
361- with this URL.
362-
363-
364-Try to create a branch without URL
365-
366- >>> add_branch('')
367- >>> for message in get_feedback_messages(user_browser.contents):
368- ... print message
369- There is 1 error.
370- Branch URLs are required for Mirrored branches.
371-
372-Try to create a branch using the bzr+ssh URL scheme.
373-
374- >>> add_branch('bzr+ssh://example.com/code/branch')
375- >>> print user_browser.url
376- http://code.launchpad.dev/~no-priv/+junk/pyresources
377-
378-
379-= The other forms =
380-
381-The +addbranch form of Product, and the +edit form of a branch all use the same
382-validation logic that the previous form, since it is defined in the IBranch
383-interface. No point in repeating the tests here.
384
385=== modified file 'lib/lp/code/stories/branches/xx-creating-branches.txt'
386--- lib/lp/code/stories/branches/xx-creating-branches.txt 2010-10-18 22:24:59 +0000
387+++ lib/lp/code/stories/branches/xx-creating-branches.txt 2011-10-04 14:34:53 +0000
388@@ -32,94 +32,8 @@
389 >>> browser.open('http://code.launchpad.dev/redfish')
390 >>> browser.getLink("Register a branch").click()
391
392- >>> browser.getControl('Hosted').click()
393 >>> browser.getControl('Name').value = 'hosted-branch'
394
395-Branch URLs are not valid for hosted branches.
396-
397- >>> browser.getControl('Branch URL').value = (
398- ... 'http://example.com/hosted-branch')
399- >>> browser.getControl('Register Branch').click()
400- >>> for message in get_feedback_messages(browser.contents):
401- ... print message
402- There is 1 error.
403- Branch URLs cannot be set for Hosted branches.
404-
405- >>> browser.getControl('Branch URL').value = ''
406- >>> browser.getControl('Register Branch').click()
407-
408- >>> def print_branch_info(browser):
409- ... branch_info = find_tag_by_id(browser.contents, 'branch-info')
410- ... print extract_text(branch_info)
411- >>> print_branch_info(browser)
412- Branch information
413- Owner: Sample Person
414- Project: Redfish
415- Status: Development
416-
417-
418-Creating a mirrored branch
419---------------------------
420-
421-Mirrored branches are primarily hosted outside of Launchpad, and
422-Launchpad mirrors the branch.
423-
424- >>> browser.open('http://code.launchpad.dev/redfish')
425- >>> browser.getLink("Register a branch").click()
426-
427- >>> print_radio_button_field(browser.contents, 'branch_type')
428- ( ) Hosted
429- (*) Mirrored
430- ( ) Remote
431-
432- >>> browser.getControl('Name').value = 'mirrored-branch'
433-
434-Branch URLs are required for mirrored branches.
435-
436- >>> browser.getControl('Register Branch').click()
437- >>> for message in get_feedback_messages(browser.contents):
438- ... print message
439- There is 1 error.
440- Branch URLs are required for Mirrored branches.
441-
442- >>> browser.getControl('Branch URL').value = (
443- ... 'http://example.com/mirrored-branch')
444- >>> browser.getControl('Register Branch').click()
445-
446- >>> print_branch_info(browser)
447- Branch information
448- Owner: Sample Person
449- Project: Redfish
450- Status: Development
451- Location: http://example.com/mirrored-branch
452- Last mirrored: Not mirrored yet
453- Next mirror: As soon as possible
454-
455-
456-Creating a remote branch
457-------------------------
458-
459-The contents of Remote branches are not accessible through Launchpad.
460-
461- >>> browser.open('http://code.launchpad.dev/redfish')
462- >>> browser.getLink("Register a branch").click()
463-
464- >>> browser.getControl('Remote').click()
465- >>> browser.getControl('Name').value = 'remote-branch'
466-
467-A remote branch may have a URL, but doesn't have to.
468-
469- >>> browser.getControl('Branch URL').value = (
470- ... 'http://example.com/remote-branch')
471- >>> browser.getControl('Register Branch').click()
472- >>> print_branch_info(browser)
473- Branch information
474- Owner: Sample Person
475- Project: Redfish
476- Status: Development
477- Location: http://example.com/remote-branch
478-
479-
480 Finding product/+addbranch
481 --------------------------
482
483@@ -172,9 +86,6 @@
484 The specified URL has a trailing slash. In the next test, we will check
485 that it has been stripped.
486
487- >>> user_browser.getControl('Branch URL').value = (
488- ... 'http://example.com/applets/main/')
489-
490 >>> user_browser.getControl('Name').value = 'main'
491 >>> user_browser.getControl('Experimental').click()
492 >>> user_browser.getControl('Register Branch').click()
493@@ -193,12 +104,6 @@
494 >>> print extract_text(find_tag_by_id(content, 'privacy'))
495 This branch is public
496
497- >>> print extract_text(content.h1)
498- lp://dev/~no-priv/applets/main
499-
500- >>> print extract_text(find_tag_by_id(content, 'mirror-location'))
501- Location: http://example.com/applets/main
502-
503 This page includes a link to other branches associated with that
504 product, and a link to other branches maintained by that person.
505
506@@ -209,34 +114,6 @@
507 Other branches owned by No Privileges Person
508
509
510-Adding a branch via a product series
511-------------------------------------
512-
513-A user can register a branch from the project series page using the
514-'Submit code' link, or in the case of a series driver, by using the
515-'registering a mirrored branch' link.
516-
517- >>> browser.open('http://launchpad.dev/gnome-terminal/trunk')
518- >>> browser.getLink('registering a mirrored branch').click()
519- >>> print browser.title
520- Register a branch : Code : GNOME Terminal
521-
522-The user sees that he is registering a branch for the series' project.
523-
524- >>> print extract_text(find_main_content(browser.contents).h1)
525- Register a branch on GNOME Terminal
526-
527-After posting the form, he sees the registered branch page.
528-
529- >>> browser.getControl('Branch URL').value = (
530- ... 'http://example.com/applets/master')
531- >>> browser.getControl('Name').value = 'master'
532- >>> browser.getControl('Experimental').click()
533- >>> browser.getControl('Register Branch').click()
534- >>> print browser.title
535- master : Code : GNOME Terminal
536-
537-
538 Finding person/+addbranch
539 -------------------------
540
541@@ -271,7 +148,6 @@
542 Ubuntu Gnome Team (name18)
543 Warty Security Team (name20)
544
545-
546 Conflict on unique name
547 -----------------------
548
549@@ -288,8 +164,6 @@
550 Try a adding a conflicting branch from the product/+addbranch form.
551
552 >>> browser.open('http://code.launchpad.dev/gnome-terminal/+addbranch')
553- >>> browser.getControl('Branch URL').value = (
554- ... 'http://example.com/gnome-terminal/main-dup')
555
556 Trying to post the form without filling a name at all should not cause
557 an oops!
558@@ -321,7 +195,6 @@
559 >>> browser.getControl('Owner').displayValue = [
560 ... 'Landscape Developers']
561 >>> browser.getControl('Name').value = 'main'
562- >>> browser.getControl('Hosted').click()
563 >>> browser.getControl('Register Branch').click()
564 >>> print browser.url
565 http://code.launchpad.dev/~landscape-developers/gnome-terminal/main
566@@ -335,7 +208,6 @@
567 >>> browser.getControl('Owner').displayValue = [
568 ... 'Landscape Developers']
569 >>> browser.getControl('Name').value = 'main'
570- >>> browser.getControl('Hosted').click()
571 >>> browser.getControl('Register Branch').click()
572 >>> for message in get_feedback_messages(browser.contents):
573 ... print extract_text(message)
574@@ -343,65 +215,6 @@
575 Landscape Developers already has a branch for GNOME Terminal called main.
576
577
578-Checking URLs
579--------------
580-
581-URL validation should check that the entered URL is not the root of a
582-site.
583-
584- >>> user_browser.open('http://code.launchpad.dev/applets')
585- >>> user_browser.getLink("Register a branch").click()
586- >>> user_browser.getControl('Branch URL').value = 'http://example.com'
587- >>> user_browser.getControl('Name').value = 'unique-name'
588- >>> user_browser.getControl('Register Branch').click()
589- >>> messages = find_tags_by_class(user_browser.contents, 'message')
590- >>> for element in messages:
591- ... print element.renderContents()
592- There is 1 error.
593- URLs for branches cannot point to the root of a site.
594-
595-URL validation should check that the entered URL is not from Launchpad.
596-
597- >>> user_browser.open('http://code.launchpad.dev/applets')
598- >>> user_browser.getLink("Register a branch").click()
599- >>> user_browser.getControl('Branch URL').value = (
600- ... 'http://code.launchpad.dev/~testuser/')
601- >>> user_browser.getControl('Name').value = 'unique-name'
602- >>> user_browser.getControl('Register Branch').click()
603- >>> for message in get_feedback_messages(user_browser.contents):
604- ... print message
605- There is 1 error.
606- For Launchpad to mirror a branch, the original branch cannot be on
607- launchpad.dev.
608-
609-As well as checking against the root site set in the config, a check is
610-also done against the value stored as a database constraint.
611-
612- >>> user_browser.open('http://code.launchpad.dev/applets')
613- >>> user_browser.getLink("Register a branch").click()
614- >>> user_browser.getControl('Branch URL').value = (
615- ... 'http://bazaar.launchpad.net/foo/bar/')
616- >>> user_browser.getControl('Name').value = 'unique-name'
617- >>> user_browser.getControl('Register Branch').click()
618- >>> for message in get_feedback_messages(user_browser.contents):
619- ... print message
620- There is 1 error.
621- For Launchpad to mirror a branch, the original branch cannot be on
622- http://bazaar.launchpad.net.
623-
624-Trailing slashes on URLs are removed.
625-
626- >>> user_browser.getControl('Branch URL').value = (
627- ... 'sftp://example.com/~lifeless/pyresources/')
628- >>> user_browser.getControl('Register Branch').click()
629- >>> print_tag_with_id(user_browser.contents, 'branch-info')
630- Branch information
631- Owner: ...
632- ...
633- Location: sftp://example.com/~lifeless/pyresources
634- ...
635-
636-
637 Attempting to create a branch in a forbidden project
638 ----------------------------------------------------
639
640@@ -423,7 +236,6 @@
641
642 >>> user_browser.open('http://code.launchpad.dev/landscape')
643 >>> user_browser.getLink("Register a branch").click()
644- >>> user_browser.getControl('Branch URL').value = 'http://foo.com/bar'
645 >>> user_browser.getControl('Name').value = 'landscape1'
646 >>> user_browser.getControl('Register Branch').click()
647 >>> messages = find_tags_by_class(user_browser.contents, 'message')
648
649=== modified file 'lib/lp/code/stories/branches/xx-junk-branches.txt'
650--- lib/lp/code/stories/branches/xx-junk-branches.txt 2010-01-21 03:02:09 +0000
651+++ lib/lp/code/stories/branches/xx-junk-branches.txt 2011-10-04 14:34:53 +0000
652@@ -17,7 +17,6 @@
653 >>> browser.open('http://code.launchpad.dev/~eric')
654 >>> browser.getLink("Register a branch").click()
655 >>> browser.getControl('Name').value = 'personal-junk'
656- >>> browser.getControl('Hosted').click()
657 >>> browser.getControl('Register Branch').click()
658
659 Posting the form should succeed and redirect to the page of the newly created
660@@ -31,8 +30,8 @@
661 Nearby
662 Other branches owned by Eric
663
664-Junk branches do not support merge proposals, so the "Branch Merges" section of
665-the page should not be shown.
666+Junk branches do not support merge proposals, so the "Branch Merges" section
667+of the page should not be shown.
668
669 >>> print find_tag_by_id(browser.contents, 'branch-merges')
670 None
671@@ -46,7 +45,6 @@
672 >>> browser.open('http://code.launchpad.dev/~vikings')
673 >>> browser.getLink("Register a branch").click()
674 >>> browser.getControl('Name').value = 'team-junk'
675- >>> browser.getControl('Hosted').click()
676 >>> browser.getControl('Register Branch').click()
677
678 >>> print browser.url
679
680=== modified file 'lib/lp/code/stories/codeimport/xx-create-codeimport.txt'
681--- lib/lp/code/stories/codeimport/xx-create-codeimport.txt 2011-08-15 11:21:26 +0000
682+++ lib/lp/code/stories/codeimport/xx-create-codeimport.txt 2011-10-04 14:34:53 +0000
683@@ -40,7 +40,8 @@
684 >>> owner_browser.getLink('Configure code hosting').click()
685 >>> owner_browser.getControl(
686 ... 'Import a branch').click()
687- >>> owner_browser.getControl('Branch URL').value = 'git://example.com/firefox'
688+ >>> owner_browser.getControl('Branch URL').value = (
689+ ... 'git://example.com/firefox')
690 >>> owner_browser.getControl('Git').click()
691 >>> owner_browser.getControl('Branch name').value = 'trunk'
692 >>> owner_browser.getControl('Update').click()
693@@ -50,22 +51,65 @@
694 >>> browser.open('http://code.launchpad.dev/firefox')
695 >>> browser.getLink('Import a branch').click()
696
697-Requesting a Subversion import
698-==============================
699+Requesting a Bazaar import
700+==========================
701
702-The default foreign VCS type is Subversion.
703+The default VCS type is Bazaar.
704
705 >>> print_radio_button_field(browser.contents, "rcs_type")
706+ (*) Bazaar
707 ( ) Git
708 ( ) Mercurial
709- (*) Subversion
710+ ( ) Subversion
711 ( ) CVS
712
713 The user is required to enter a project that the import is for, a name
714-for the import branch, and a subversion branch location.
715-
716+for the import branch, and a Bazaar branch location.
717+
718+ >>> browser.getControl('Branch Name').value = "mirrored"
719+ >>> browser.getControl('Branch URL', index=0).value = (
720+ ... "http://bzr.example.com/firefox/trunk")
721+ >>> browser.getControl('Request Import').click()
722+
723+When the user clicks continue, the import branch is created
724+
725+ >>> print extract_text(find_tag_by_id(browser.contents, "import-details"))
726+ Import Status: Reviewed
727+ This branch is an import of the Bazaar branch
728+ at http://bzr.example.com/firefox/trunk.
729+ The next import is scheduled to run
730+ as soon as possible.
731+ >>> browser.getLink("http://bzr.example.com/firefox/trunk")
732+ <Link text='http://bzr.example.com/firefox/trunk'
733+ url='http://bzr.example.com/firefox/trunk'>
734+
735+If the user wants, they can include a username and password in the
736+URL.
737+
738+ >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
739+ >>> browser.getControl('Branch Name').value = "with-password"
740+ >>> browser.getControl('Branch URL', index=0).value = (
741+ ... "http://user:password@bzr.example.com/firefox/trunk")
742+ >>> browser.getControl('Project').value = "firefox"
743+ >>> browser.getControl('Request Import').click()
744+ >>> print extract_text(find_tag_by_id(browser.contents, "import-details"))
745+ Import Status: Reviewed
746+ This branch is an import of the Bazaar branch
747+ at http://user:password@bzr.example.com/firefox/trunk.
748+ The next import is scheduled to run
749+ as soon as possible.
750+
751+Requesting a Subversion import
752+==============================
753+
754+The user is required to enter a project that the import is for,
755+a name for the import branch, and a subversion branch location.
756+
757+ >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
758+ >>> browser.getControl('Subversion').click()
759+ >>> browser.getControl('Project').value = "firefox"
760 >>> browser.getControl('Branch Name').value = "imported"
761- >>> browser.getControl('Branch URL').value = (
762+ >>> browser.getControl('Branch URL', index=1).value = (
763 ... "http://svn.example.com/firefox/trunk")
764 >>> browser.getControl('Request Import').click()
765
766@@ -94,8 +138,9 @@
767 URL.
768
769 >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
770- >>> browser.getControl('Branch Name').value = "with-password"
771- >>> browser.getControl('Branch URL').value = (
772+ >>> browser.getControl('Subversion').click()
773+ >>> browser.getControl('Branch Name').value = "svn-with-password"
774+ >>> browser.getControl('Branch URL', index=1).value = (
775 ... "http://user:password@svn.example.com/firefox/trunk")
776 >>> browser.getControl('Project').value = "firefox"
777 >>> browser.getControl('Request Import').click()
778@@ -221,7 +266,7 @@
779 the imported branch ~no-priv/firefox/import2.
780
781 >>> browser.getControl('Subversion').click()
782- >>> browser.getControl('Branch URL').value = (
783+ >>> browser.getControl('Branch URL', index=1).value = (
784 ... "http://svn.example.com/firefox/trunk")
785 >>> browser.getControl('Request Import').click()
786
787@@ -241,9 +286,10 @@
788 one.
789
790 >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
791+ >>> browser.getControl('Subversion').click()
792 >>> browser.getControl('Project').value = "firefox"
793 >>> browser.getControl('Branch Name').value = "imported"
794- >>> browser.getControl('Branch URL').value = (
795+ >>> browser.getControl('Branch URL', index=1).value = (
796 ... "http://svn.example.com/firefox/other")
797 >>> browser.getControl('Request Import').click()
798 >>> for message in get_feedback_messages(browser.contents):
799@@ -261,7 +307,7 @@
800 >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
801 >>> browser.getControl('Project').value = "launchpad"
802 >>> browser.getControl('Branch Name').value = "imported"
803- >>> browser.getControl('Branch URL').value = (
804+ >>> browser.getControl('Branch URL', index=0).value = (
805 ... "http://svn.example.com/launchpage/fake")
806 >>> browser.getControl('Request Import').click()
807 >>> for message in get_feedback_messages(browser.contents):
808@@ -279,7 +325,7 @@
809 >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
810 >>> browser.getControl('Project').value = "no-such-product"
811 >>> browser.getControl('Branch Name').value = "imported"
812- >>> browser.getControl('Branch URL').value = (
813+ >>> browser.getControl('Branch URL', index=0).value = (
814 ... "http://svn.example.com/launchpage/fake")
815 >>> browser.getControl('Request Import').click()
816 >>> for message in get_feedback_messages(browser.contents):
817@@ -306,7 +352,7 @@
818 >>> sample_browser.getControl('Owner').value = ['landscape-developers']
819 >>> sample_browser.getControl('Owner').displayValue
820 ['Landscape Developers (landscape-developers)']
821- >>> sample_browser.getControl('Branch URL').value = (
822+ >>> sample_browser.getControl('Branch URL', index=0).value = (
823 ... "http://svn.example.com/firefox-beta/trunk")
824 >>> sample_browser.getControl('Request Import').click()
825
826@@ -320,7 +366,7 @@
827 >>> admin_browser = setupBrowser(auth='Basic admin@canonical.com:test')
828 >>> admin_browser.open("http://code.launchpad.dev/firefox/+new-import")
829 >>> admin_browser.getControl('Owner').value = 'mark'
830- >>> admin_browser.getControl('Branch URL').value = (
831+ >>> admin_browser.getControl('Branch URL', index=0).value = (
832 ... "http://svn.example.com/firefox-theta/trunk")
833 >>> admin_browser.getControl('Request Import').click()
834
835
836=== modified file 'lib/lp/code/templates/branch-form-macros.pt'
837--- lib/lp/code/templates/branch-form-macros.pt 2009-10-02 15:02:23 +0000
838+++ lib/lp/code/templates/branch-form-macros.pt 2011-10-04 14:34:53 +0000
839@@ -17,20 +17,6 @@
840
841 <script type="text/javascript">
842 //<![CDATA[
843-function update_branch_url()
844-{
845- var branch_type_field = document.getElementsByName('field.branch_type')
846- var form = branch_type_field[0].form;
847- var branch_type = 'None';
848- for (var i = 0; i < branch_type_field.length; i++) {
849- if (branch_type_field[i].checked) {
850- branch_type = branch_type_field[i].value;
851- break;
852- }
853- }
854- updateField(form['field.url'], branch_type != 'HOSTED');
855-}
856-
857 function update_branch_unique_name()
858 {
859 var unique_name = document.getElementById("branch-unique-name")
860@@ -43,44 +29,16 @@
861 unique_name.innerHTML = branch_name
862 }
863
864-function populate_branch_name_from_url()
865-{
866- url_field = document.getElementById('field.url');
867- var url_text = trim(url_field.value);
868- // strip of any trailing slashes
869- url_text = url_text.replace(/\/+$/, '')
870- if (url_text != url_field.value) {
871- url_field.value = url_text;
872- }
873- var name_field = document.getElementById('field.name');
874- if (name_field.value == '')
875- {
876- // parse the value of the url field
877- url_bits = url_text.split('/');
878- if (url_bits.length > 2) {
879- // attempt at not barfing on something completely invalid
880- last_bit = url_bits[url_bits.length - 1];
881- name_field.value = last_bit;
882- }
883- }
884-}
885-
886 function hookUpBranchFieldFunctions()
887 {
888 connect('field.owner', 'onkeyup', update_branch_unique_name);
889 connect('field.owner', 'onchange', update_branch_unique_name);
890 connect('field.name', 'onkeyup', update_branch_unique_name);
891- connect('field.branch_type.0', 'onclick', update_branch_url);
892- connect('field.branch_type.1', 'onclick', update_branch_url);
893- connect('field.branch_type.2', 'onclick', update_branch_url);
894 update_branch_unique_name();
895- connect('field.url', 'onchange', populate_branch_name_from_url);
896- connect('field.url', 'onblur', populate_branch_name_from_url);
897 document.getElementById("branch-unique-name-div").style.display = "block";
898 }
899
900 registerLaunchpadFunction(hookUpBranchFieldFunctions);
901-registerLaunchpadFunction(update_branch_url);
902
903 //]]>
904 </script>
905
906=== modified file 'lib/lp/code/templates/branch-import-details.pt'
907--- lib/lp/code/templates/branch-import-details.pt 2010-06-10 07:54:59 +0000
908+++ lib/lp/code/templates/branch-import-details.pt 2011-10-04 14:34:53 +0000
909@@ -41,26 +41,50 @@
910
911 <tal:git-import condition="code_import/rcs_type/enumvalue:GIT">
912 <p>This branch is an import of the HEAD branch of the Git repository at
913- <span tal:replace="code_import/url" />.
914+ <tal:is-web-url condition="view/url_is_web">
915+ <a tal:attributes="href code_import/url"
916+ tal:content="code_import/url" />.
917+ </tal:is-web-url>
918+ <tal:not-web-url condition="not: view/url_is_web">
919+ <span tal:replace="code_import/url" />.
920+ </tal:not-web-url>
921 </p>
922 </tal:git-import>
923
924 <tal:hg-import condition="code_import/rcs_type/enumvalue:HG">
925 <p>This branch is an import of the tip branch of the Mercurial repository at
926- <span tal:replace="code_import/url" />.
927+ <tal:is-web-url condition="view/url_is_web">
928+ <a tal:attributes="href code_import/url"
929+ tal:content="code_import/url" />.
930+ </tal:is-web-url>
931+ <tal:not-web-url condition="not: view/url_is_web">
932+ <span tal:replace="code_import/url" />.
933+ </tal:not-web-url>
934 </p>
935 </tal:hg-import>
936
937+ <tal:bzr-import condition="code_import/rcs_type/enumvalue:BZR">
938+ <p>This branch is an import of the Bazaar branch at
939+ <tal:is-web-url condition="view/url_is_web">
940+ <a tal:attributes="href code_import/url"
941+ tal:content="code_import/url" />.
942+ </tal:is-web-url>
943+ <tal:not-web-url condition="not: view/url_is_web">
944+ <span tal:replace="code_import/url" />.
945+ </tal:not-web-url>
946+ </p>
947+ </tal:bzr-import>
948+
949 <tal:svn-import condition="view/is_svn_import">
950 <p id="svn-import-details">
951 This branch is an import of the
952 <span tal:attributes="title code_import/rcs_type/title">Subversion</span>
953 branch from
954- <tal:is-web-url condition="view/svn_url_is_web">
955+ <tal:is-web-url condition="view/url_is_web">
956 <a tal:attributes="href code_import/url"
957 tal:content="code_import/url" />.
958 </tal:is-web-url>
959- <tal:not-web-url condition="not: view/svn_url_is_web">
960+ <tal:not-web-url condition="not: view/url_is_web">
961 <span tal:replace="code_import/url" />.
962 </tal:not-web-url>
963 </p>
964
965=== modified file 'lib/lp/code/templates/codeimport-list.pt'
966--- lib/lp/code/templates/codeimport-list.pt 2010-03-18 22:39:15 +0000
967+++ lib/lp/code/templates/codeimport-list.pt 2011-10-04 14:34:53 +0000
968@@ -21,6 +21,7 @@
969 </select>
970 <select name="rcs_type" tal:replace="structure view/rcs_type_widget">
971 <option label="">Any</option>
972+ <option label="BZR">Bazaar</option>
973 <option label="GIT">Git</option>
974 <option label="BZR_SVN">Subversion</option>
975 <option label="SVN">Subversion (legacy)</option>
976
977=== modified file 'lib/lp/code/templates/codeimport-new.pt'
978--- lib/lp/code/templates/codeimport-new.pt 2010-04-22 23:50:51 +0000
979+++ lib/lp/code/templates/codeimport-new.pt 2011-10-04 14:34:53 +0000
980@@ -53,6 +53,20 @@
981 <tr>
982 <td>
983 <label>
984+ <input tal:replace="structure view/rcs_type_bzr" />
985+ Bazaar
986+ </label>
987+ <table class="importdetails">
988+ <tal:widget define="widget nocall:view/widgets/bzr_branch_url">
989+ <metal:block use-macro="context/@@launchpad_form/widget_row" />
990+ </tal:widget>
991+ </table>
992+ </td>
993+ </tr>
994+
995+ <tr>
996+ <td>
997+ <label>
998 <input tal:replace="structure view/rcs_type_git" />
999 Git
1000 </label>
1001@@ -134,6 +148,8 @@
1002 updateField(form['field.cvs_module'], rcs_type == 'CVS');
1003 // SVN
1004 updateField(form['field.svn_branch_url'], rcs_type == 'BZR_SVN');
1005+ // BZR
1006+ updateField(form['field.bzr_branch_url'], rcs_type == 'BZR');
1007 }
1008 registerLaunchpadFunction(updateWidgets);
1009 //]]>
1010
1011=== modified file 'lib/lp/registry/browser/productseries.py'
1012--- lib/lp/registry/browser/productseries.py 2011-08-28 08:36:14 +0000
1013+++ lib/lp/registry/browser/productseries.py 2011-10-04 14:34:53 +0000
1014@@ -1089,8 +1089,7 @@
1015
1016 # Create a new branch.
1017 if branch_type == CREATE_NEW:
1018- branch = self._createBzrBranch(
1019- BranchType.HOSTED, branch_name, branch_owner)
1020+ branch = self._createBzrBranch(branch_name, branch_owner)
1021 if branch is not None:
1022 self.context.branch = branch
1023 self.request.response.addInfoNotification(
1024@@ -1101,68 +1100,53 @@
1025 # Either create an externally hosted bzr branch
1026 # (a.k.a. 'mirrored') or create a new code import.
1027 rcs_type = data.get('rcs_type')
1028- if rcs_type == RevisionControlSystems.BZR:
1029- branch = self._createBzrBranch(
1030- BranchType.MIRRORED, branch_name, branch_owner,
1031- data['repo_url'])
1032- if branch is None:
1033- return
1034-
1035- self.context.branch = branch
1036- self.request.response.addInfoNotification(
1037- 'Mirrored branch created and linked to '
1038- 'the series.')
1039+ # We need to create an import request.
1040+ if rcs_type == RevisionControlSystems.CVS:
1041+ cvs_root = data.get('repo_url')
1042+ cvs_module = data.get('cvs_module')
1043+ url = None
1044 else:
1045- # We need to create an import request.
1046- if rcs_type == RevisionControlSystems.CVS:
1047- cvs_root = data.get('repo_url')
1048- cvs_module = data.get('cvs_module')
1049- url = None
1050- else:
1051- cvs_root = None
1052- cvs_module = None
1053- url = data.get('repo_url')
1054- rcs_item = RevisionControlSystems.items[rcs_type.name]
1055- try:
1056- code_import = getUtility(ICodeImportSet).new(
1057- registrant=branch_owner,
1058- target=IBranchTarget(self.context.product),
1059- branch_name=branch_name,
1060- rcs_type=rcs_item,
1061- url=url,
1062- cvs_root=cvs_root,
1063- cvs_module=cvs_module)
1064- except BranchExists, e:
1065- self._setBranchExists(e.existing_branch,
1066- 'branch_name')
1067- self.errors_in_action = True
1068- # Abort transaction. This is normally handled
1069- # by LaunchpadFormView, but we are already in
1070- # the success handler.
1071- self._abort()
1072- return
1073- self.context.branch = code_import.branch
1074- self.request.response.addInfoNotification(
1075- 'Code import created and branch linked to the '
1076- 'series.')
1077+ cvs_root = None
1078+ cvs_module = None
1079+ url = data.get('repo_url')
1080+ rcs_item = RevisionControlSystems.items[rcs_type.name]
1081+ try:
1082+ code_import = getUtility(ICodeImportSet).new(
1083+ registrant=branch_owner,
1084+ target=IBranchTarget(self.context.product),
1085+ branch_name=branch_name,
1086+ rcs_type=rcs_item,
1087+ url=url,
1088+ cvs_root=cvs_root,
1089+ cvs_module=cvs_module)
1090+ except BranchExists, e:
1091+ self._setBranchExists(e.existing_branch,
1092+ 'branch_name')
1093+ self.errors_in_action = True
1094+ # Abort transaction. This is normally handled
1095+ # by LaunchpadFormView, but we are already in
1096+ # the success handler.
1097+ self._abort()
1098+ return
1099+ self.context.branch = code_import.branch
1100+ self.request.response.addInfoNotification(
1101+ 'Code import created and branch linked to the '
1102+ 'series.')
1103 else:
1104 raise UnexpectedFormData(branch_type)
1105
1106- def _createBzrBranch(self, branch_type, branch_name,
1107- branch_owner, repo_url=None):
1108- """Create a new Bazaar branch. It may be hosted or mirrored.
1109+ def _createBzrBranch(self, branch_name, branch_owner, repo_url=None):
1110+ """Create a new hosted Bazaar branch.
1111
1112 Return the branch on success or None.
1113 """
1114 branch = None
1115 try:
1116 namespace = self.target.getNamespace(branch_owner)
1117- branch = namespace.createBranch(branch_type=branch_type,
1118+ branch = namespace.createBranch(branch_type=BranchType.HOSTED,
1119 name=branch_name,
1120 registrant=self.user,
1121 url=repo_url)
1122- if branch_type == BranchType.MIRRORED:
1123- branch.requestMirror()
1124 except BranchCreationForbidden:
1125 self.addError(
1126 "You are not allowed to create branches in %s." %
1127
1128=== modified file 'lib/lp/registry/browser/tests/productseries-setbranch-view.txt'
1129--- lib/lp/registry/browser/tests/productseries-setbranch-view.txt 2011-08-27 23:24:05 +0000
1130+++ lib/lp/registry/browser/tests/productseries-setbranch-view.txt 2011-10-04 14:34:53 +0000
1131@@ -95,7 +95,8 @@
1132 ... 'field.branch_type': 'create-new',
1133 ... 'field.actions.update': 'Update',
1134 ... }
1135- >>> view = create_initialized_view(series, name='+setbranch', principal=product.owner, form=form)
1136+ >>> view = create_initialized_view(
1137+ ... series, name='+setbranch', principal=product.owner, form=form)
1138 >>> for notification in view.request.response.notifications:
1139 ... print notification.message
1140 >>> for error in view.errors:
1141@@ -112,7 +113,8 @@
1142 ... 'field.actions.update': 'Update',
1143 ... }
1144
1145- >>> view = create_initialized_view(series, name='+setbranch', principal=product.owner, form=form)
1146+ >>> view = create_initialized_view(
1147+ ... series, name='+setbranch', principal=product.owner, form=form)
1148 >>> print view.errors_in_action
1149 False
1150 >>> print view.next_url
1151@@ -134,7 +136,8 @@
1152 ... 'field.actions.update': 'Update',
1153 ... }
1154
1155- >>> view = create_initialized_view(series, name='+setbranch', principal=product.owner, form=form)
1156+ >>> view = create_initialized_view(
1157+ ... series, name='+setbranch', principal=product.owner, form=form)
1158 >>> print view.errors_in_action
1159 True
1160 >>> print view.next_url
1161@@ -161,7 +164,8 @@
1162 ... 'field.branch_type': 'import-external',
1163 ... 'field.actions.update': 'Update',
1164 ... }
1165- >>> view = create_initialized_view(series, name='+setbranch', principal=product.owner, form=form)
1166+ >>> view = create_initialized_view(
1167+ ... series, name='+setbranch', principal=product.owner, form=form)
1168 >>> for notification in view.request.response.notifications:
1169 ... print notification.message
1170 >>> for error in view.errors:
1171@@ -181,13 +185,14 @@
1172 ... 'field.repo_url': 'bzr+ssh://bzr.com/foo',
1173 ... 'field.actions.update': 'Update',
1174 ... }
1175- >>> view = create_initialized_view(series, name='+setbranch', principal=product.owner, form=form)
1176+ >>> view = create_initialized_view(
1177+ ... series, name='+setbranch', principal=product.owner, form=form)
1178 >>> for notification in view.request.response.notifications:
1179 ... print notification.message
1180 >>> for error in view.errors:
1181 ... print error
1182- ('repo_url'...The URI scheme "bzr+ssh" is not allowed. Only URIs with the following schemes may be
1183- used: bzr, http, https'))
1184+ ('repo_url'...The URI scheme "bzr+ssh" is not allowed. Only URIs with
1185+ the following schemes may be used: bzr, http, https'))
1186
1187
1188 A correct URL is accepted.
1189@@ -200,13 +205,14 @@
1190 ... 'field.repo_url': 'http://bzr.com/foo',
1191 ... 'field.actions.update': 'Update',
1192 ... }
1193- >>> view = create_initialized_view(series, name='+setbranch', principal=product.owner, form=form)
1194+ >>> view = create_initialized_view(
1195+ ... series, name='+setbranch', principal=product.owner, form=form)
1196 >>> transaction.commit()
1197 >>> for error in view.errors:
1198 ... print error
1199 >>> for notification in view.request.response.notifications:
1200 ... print notification.message
1201- Mirrored branch created and linked to the series.
1202+ Code import created and branch linked to the series.
1203 >>> print series.branch.name
1204 blazer-branch
1205
1206@@ -220,7 +226,8 @@
1207 ... 'field.repo_url': 'svn://svn.com/chevette',
1208 ... 'field.actions.update': 'Update',
1209 ... }
1210- >>> view = create_initialized_view(series, name='+setbranch', principal=product.owner, form=form)
1211+ >>> view = create_initialized_view(
1212+ ... series, name='+setbranch', principal=product.owner, form=form)
1213 >>> for notification in view.request.response.notifications:
1214 ... print notification.message
1215 >>> for error in view.errors:
1216@@ -240,7 +247,8 @@
1217 ... 'field.repo_url': 'git://github.com/chevette',
1218 ... 'field.actions.update': 'Update',
1219 ... }
1220- >>> view = create_initialized_view(series, name='+setbranch', principal=product.owner, form=form)
1221+ >>> view = create_initialized_view(
1222+ ... series, name='+setbranch', principal=product.owner, form=form)
1223 >>> transaction.commit()
1224 >>> for error in view.errors:
1225 ... print error
1226@@ -260,7 +268,8 @@
1227 ... 'field.repo_url': 'git://github.com/suburban',
1228 ... 'field.actions.update': 'Update',
1229 ... }
1230- >>> view = create_initialized_view(series, name='+setbranch', principal=product.owner, form=form)
1231+ >>> view = create_initialized_view(
1232+ ... series, name='+setbranch', principal=product.owner, form=form)
1233 >>> for notification in view.request.response.notifications:
1234 ... print notification.message
1235 >>> for error in view.errors:
1236@@ -280,7 +289,8 @@
1237 ... 'field.repo_url': 'svn://svn.com/suburban',
1238 ... 'field.actions.update': 'Update',
1239 ... }
1240- >>> view = create_initialized_view(series, name='+setbranch', principal=product.owner, form=form)
1241+ >>> view = create_initialized_view(
1242+ ... series, name='+setbranch', principal=product.owner, form=form)
1243 >>> for error in view.errors:
1244 ... print error
1245 >>> for notification in view.request.response.notifications:
1246@@ -301,7 +311,8 @@
1247 ... 'field.repo_url': 'https://mercurial.com/branch',
1248 ... 'field.actions.update': 'Update',
1249 ... }
1250- >>> view = create_initialized_view(series, name='+setbranch', principal=product.owner, form=form)
1251+ >>> view = create_initialized_view(
1252+ ... series, name='+setbranch', principal=product.owner, form=form)
1253 >>> for error in view.errors:
1254 ... print error
1255 >>> for notification in view.request.response.notifications:
1256@@ -367,8 +378,8 @@
1257 ... print error
1258 <BLANKLINE>
1259 This foreign branch URL is already specified for
1260- the imported branch <a href="http://code.launchpad.dev/~.../chevy/chevette-branch">~.../chevy/chevette-branch</a>.
1261-
1262+ the imported branch
1263+ <a href="http://.../chevy/chevette-branch">~.../chevy/chevette-branch</a>.
1264 >>> for notification in view.request.response.notifications:
1265 ... print notification.message
1266
1267@@ -386,7 +397,8 @@
1268 ... principal=product.owner, form=form)
1269 >>> for error in view.errors:
1270 ... print error
1271- You already have a branch for <em>Chevy</em> called <em>chevette-branch</em>.
1272+ You already have a branch for
1273+ <em>Chevy</em> called <em>chevette-branch</em>.
1274 >>> print view.errors_in_action
1275 True
1276 >>> print view.next_url
1277@@ -406,13 +418,16 @@
1278 ... 'field.repo_url': 'http://bzr.com/foo',
1279 ... 'field.actions.update': 'Update',
1280 ... }
1281- >>> view = create_initialized_view(series, name='+setbranch', principal=product.owner, form=form)
1282+ >>> view = create_initialized_view(
1283+ ... series, name='+setbranch', principal=product.owner, form=form)
1284 >>> for error in view.errors:
1285 ... print error
1286- You already have a branch for <em>Chevy</em> called <em>blazer-branch</em>.
1287+ <BLANKLINE>
1288+ This foreign branch URL is already specified for the imported branch
1289+ <a href="http://.../blazer-branch">...</a>.
1290 >>> print view.errors_in_action
1291- True
1292+ False
1293 >>> print view.next_url
1294- None
1295+ http://launchpad.dev/chevy/corvair
1296 >>> for notification in view.request.response.notifications:
1297 ... print notification.message
1298
1299=== modified file 'lib/lp/registry/templates/productseries-codesummary.pt'
1300--- lib/lp/registry/templates/productseries-codesummary.pt 2011-05-16 20:55:08 +0000
1301+++ lib/lp/registry/templates/productseries-codesummary.pt 2011-10-04 14:34:53 +0000
1302@@ -54,7 +54,7 @@
1303 </li>
1304
1305 <li>
1306- If the code is in Git, Mercurial, CVS or Subversion you can
1307+ If the code is in Git, Mercurial, CVS, Subversion or an external Bazaar branch you can
1308 <a tal:attributes="href view/request_import_link">request that the branch be imported to Bazaar</a>.
1309 </li>
1310 </ul>