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

Proposed by Jelmer Vernooij
Status: Merged
Approved by: Jelmer Vernooij
Approved revision: no longer in the source branch.
Merged at revision: 13811
Proposed branch: lp:~jelmer/launchpad/bzr-code-imports
Merge into: lp:launchpad
Prerequisite: lp:~jelmer/launchpad/code-import-use-safe-open
Diff against target: 716 lines (+288/-39)
13 files modified
lib/canonical/config/schema-lazr.conf (+5/-0)
lib/lp/code/mail/codeimport.py (+3/-1)
lib/lp/code/model/codeimport.py (+8/-3)
lib/lp/code/model/codeimportevent.py (+2/-1)
lib/lp/code/model/tests/test_codeimport.py (+44/-0)
lib/lp/codehosting/codeimport/tests/servers.py (+55/-0)
lib/lp/codehosting/codeimport/tests/test_worker.py (+85/-2)
lib/lp/codehosting/codeimport/tests/test_workermonitor.py (+22/-0)
lib/lp/codehosting/codeimport/worker.py (+36/-6)
lib/lp/registry/browser/productseries.py (+9/-17)
lib/lp/registry/browser/tests/productseries-setbranch-view.txt (+4/-3)
lib/lp/testing/factory.py (+9/-3)
scripts/code-import-worker.py (+6/-3)
To merge this branch: bzr merge lp:~jelmer/launchpad/bzr-code-imports
Reviewer Review Type Date Requested Status
Michael Hudson-Doyle Approve
Gavin Panella Pending
Robert Collins code Pending
Review via email: mp+72794@code.launchpad.net

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

Commit message

[r=mwhudson][no-qa] Add support for importing Bazaar branches using the code import mechanism.

Description of the change

Add support for importing code from Bazaar branches.

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

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

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

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

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

[1]

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

You could use TestCase.skip() here:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Cheers,

Jelmer

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

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

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

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

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

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

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

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

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

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

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

Yeah, they do.

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

You seem to be doing fine :)

Cheers,
mwh

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

I don't see anything to do with stacking safety and/or supporting branch references in here yet -- is this really ready for review again?

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

> I don't see anything to do with stacking safety and/or supporting branch
> references in here yet -- is this really ready for review again?
No, it indeed isn't - I was merely trying to generate a proper diff. Sorry.

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

Does this permit imports from lp hosted branches? if so, thats likely to permit bypassing of private branch ACLs - can you check that that isn't the please?

review: Needs Information
Revision history for this message
Gavin Panella (allenap) : Posted in a previous version of this proposal
review: Abstain
Revision history for this message
Jelmer Vernooij (jelmer) wrote : Posted in a previous version of this proposal

On Sun, 2011-08-07 at 22:41 +0000, Robert Collins wrote:
> Review: Needs Information
> Does this permit imports from lp hosted branches? if so, thats likely to permit bypassing of private branch ACLs - can you check that that isn't the please?
It does not allow imports from lp hosted branches, importing of branch
references to launchpad branches or even importing of branches stacked
on Launchpad branches.

It also limits imports to a specific (whitelist) set of schemes, which
excludes bzr+ssh://, sftp:// and file://.

This is the same policy as is currently in place for code mirrors at the
moment.

Cheers,

Jelmer

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

This (finally!) looks fine to me. Thanks for plugging away. Maybe a positive test for a branch reference being imported would be nice?

BzrServer._use_server appears to always be false, so maybe some code can be deleted?

review: Approve
Revision history for this message
Jelmer Vernooij (jelmer) wrote :

> This (finally!) looks fine to me. Thanks for plugging away. Maybe a positive
> test for a branch reference being imported would be nice?
I'll add one.

> BzrServer._use_server appears to always be false, so maybe some code can be
> deleted?
I'd like to keep it around for the moment. Hopefully we can switch to running actual VCS servers at some point, so we can use the proper database constrains for code import URLs.

Thanks for all your reviews :)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/config/schema-lazr.conf'
2--- lib/canonical/config/schema-lazr.conf 2011-08-15 08:47:36 +0000
3+++ lib/canonical/config/schema-lazr.conf 2011-08-27 23:26:24 +0000
4@@ -473,6 +473,11 @@
5 # datatype: integer
6 default_interval_cvs: 43200
7
8+# The default value of the update interval of a code import from
9+# Bazaar, in seconds.
10+# datatype: integer
11+default_interval_bzr: 21600
12+
13 # Where the tarballs of foreign branches are uploaded for storage.
14 # datatype: string
15 foreign_tree_store: sftp://hoover@escudero/srv/importd/sources/
16
17=== modified file 'lib/lp/code/mail/codeimport.py'
18--- lib/lp/code/mail/codeimport.py 2011-08-12 14:39:51 +0000
19+++ lib/lp/code/mail/codeimport.py 2011-08-27 23:26:24 +0000
20@@ -51,6 +51,7 @@
21 RevisionControlSystems.BZR_SVN: 'subversion',
22 RevisionControlSystems.GIT: 'git',
23 RevisionControlSystems.HG: 'mercurial',
24+ RevisionControlSystems.BZR: 'bazaar',
25 }
26 body = get_email_template('new-code-import.txt') % {
27 'person': code_import.registrant.displayname,
28@@ -123,7 +124,8 @@
29 elif code_import.rcs_type in (RevisionControlSystems.SVN,
30 RevisionControlSystems.BZR_SVN,
31 RevisionControlSystems.GIT,
32- RevisionControlSystems.HG):
33+ RevisionControlSystems.HG,
34+ RevisionControlSystems.BZR):
35 if CodeImportEventDataType.OLD_URL in event_data:
36 old_url = event_data[CodeImportEventDataType.OLD_URL]
37 body.append(
38
39=== modified file 'lib/lp/code/model/codeimport.py'
40--- lib/lp/code/model/codeimport.py 2011-04-27 01:42:46 +0000
41+++ lib/lp/code/model/codeimport.py 2011-08-27 23:26:24 +0000
42@@ -116,6 +116,8 @@
43 config.codeimport.default_interval_git,
44 RevisionControlSystems.HG:
45 config.codeimport.default_interval_hg,
46+ RevisionControlSystems.BZR:
47+ config.codeimport.default_interval_bzr,
48 }
49 seconds = default_interval_dict[self.rcs_type]
50 return timedelta(seconds=seconds)
51@@ -133,7 +135,8 @@
52 RevisionControlSystems.SVN,
53 RevisionControlSystems.GIT,
54 RevisionControlSystems.BZR_SVN,
55- RevisionControlSystems.HG):
56+ RevisionControlSystems.HG,
57+ RevisionControlSystems.BZR):
58 return self.url
59 else:
60 raise AssertionError(
61@@ -252,7 +255,8 @@
62 elif rcs_type in (RevisionControlSystems.SVN,
63 RevisionControlSystems.BZR_SVN,
64 RevisionControlSystems.GIT,
65- RevisionControlSystems.HG):
66+ RevisionControlSystems.HG,
67+ RevisionControlSystems.BZR):
68 assert cvs_root is None and cvs_module is None
69 assert url is not None
70 else:
71@@ -262,7 +266,8 @@
72 if review_status is None:
73 # Auto approve git and hg imports.
74 if rcs_type in (
75- RevisionControlSystems.GIT, RevisionControlSystems.HG):
76+ RevisionControlSystems.GIT, RevisionControlSystems.HG,
77+ RevisionControlSystems.BZR):
78 review_status = CodeImportReviewStatus.REVIEWED
79 else:
80 review_status = CodeImportReviewStatus.NEW
81
82=== modified file 'lib/lp/code/model/codeimportevent.py'
83--- lib/lp/code/model/codeimportevent.py 2010-10-17 22:51:50 +0000
84+++ lib/lp/code/model/codeimportevent.py 2011-08-27 23:26:24 +0000
85@@ -269,7 +269,8 @@
86 if code_import.rcs_type in (RevisionControlSystems.SVN,
87 RevisionControlSystems.BZR_SVN,
88 RevisionControlSystems.GIT,
89- RevisionControlSystems.HG):
90+ RevisionControlSystems.HG,
91+ RevisionControlSystems.BZR):
92 yield 'URL', code_import.url
93 elif code_import.rcs_type == RevisionControlSystems.CVS:
94 yield 'CVS_ROOT', code_import.cvs_root
95
96=== modified file 'lib/lp/code/model/tests/test_codeimport.py'
97--- lib/lp/code/model/tests/test_codeimport.py 2011-08-12 11:37:08 +0000
98+++ lib/lp/code/model/tests/test_codeimport.py 2011-08-27 23:26:24 +0000
99@@ -70,6 +70,20 @@
100 # No job is created for the import.
101 self.assertIs(None, code_import.import_job)
102
103+ def test_new_svn_import_svn_scheme(self):
104+ """A subversion import can use the svn:// scheme."""
105+ code_import = CodeImportSet().new(
106+ registrant=self.factory.makePerson(),
107+ target=IBranchTarget(self.factory.makeProduct()),
108+ branch_name='imported',
109+ rcs_type=RevisionControlSystems.SVN,
110+ url=self.factory.getUniqueURL(scheme="svn"))
111+ self.assertEqual(
112+ CodeImportReviewStatus.NEW,
113+ code_import.review_status)
114+ # No job is created for the import.
115+ self.assertIs(None, code_import.import_job)
116+
117 def test_reviewed_svn_import(self):
118 """A specific review status can be set for a new import."""
119 code_import = CodeImportSet().new(
120@@ -116,6 +130,21 @@
121 # A job is created for the import.
122 self.assertIsNot(None, code_import.import_job)
123
124+ def test_git_import_git_scheme(self):
125+ """A git import can have a git:// style URL."""
126+ code_import = CodeImportSet().new(
127+ registrant=self.factory.makePerson(),
128+ target=IBranchTarget(self.factory.makeProduct()),
129+ branch_name='imported',
130+ rcs_type=RevisionControlSystems.GIT,
131+ url=self.factory.getUniqueURL(scheme="git"),
132+ review_status=None)
133+ self.assertEqual(
134+ CodeImportReviewStatus.REVIEWED,
135+ code_import.review_status)
136+ # A job is created for the import.
137+ self.assertIsNot(None, code_import.import_job)
138+
139 def test_git_import_reviewed(self):
140 """A new git import is always reviewed by default."""
141 code_import = CodeImportSet().new(
142@@ -146,6 +175,21 @@
143 # A job is created for the import.
144 self.assertIsNot(None, code_import.import_job)
145
146+ def test_bzr_import_reviewed(self):
147+ """A new bzr import is always reviewed by default."""
148+ code_import = CodeImportSet().new(
149+ registrant=self.factory.makePerson(),
150+ target=IBranchTarget(self.factory.makeProduct()),
151+ branch_name='mirrored',
152+ rcs_type=RevisionControlSystems.BZR,
153+ url=self.factory.getUniqueURL(),
154+ review_status=None)
155+ self.assertEqual(
156+ CodeImportReviewStatus.REVIEWED,
157+ code_import.review_status)
158+ # A job is created for the import.
159+ self.assertIsNot(None, code_import.import_job)
160+
161 def test_junk_code_import_rejected(self):
162 """You are not allowed to create code imports targetting +junk."""
163 registrant = self.factory.makePerson()
164
165=== modified file 'lib/lp/codehosting/codeimport/tests/servers.py'
166--- lib/lp/codehosting/codeimport/tests/servers.py 2011-08-22 11:56:26 +0000
167+++ lib/lp/codehosting/codeimport/tests/servers.py 2011-08-27 23:26:24 +0000
168@@ -4,6 +4,7 @@
169 """Server classes that know how to create various kinds of foreign archive."""
170
171 __all__ = [
172+ 'BzrServer',
173 'CVSServer',
174 'GitServer',
175 'MercurialServer',
176@@ -23,7 +24,14 @@
177 import time
178 import threading
179
180+from bzrlib.branch import Branch
181+from bzrlib.branchbuilder import BranchBuilder
182+from bzrlib.bzrdir import BzrDir
183 from bzrlib.tests.treeshape import build_tree_contents
184+from bzrlib.tests.test_server import (
185+ ReadonlySmartTCPServer_for_testing,
186+ TestServer,
187+ )
188 from bzrlib.transport import Server
189 from bzrlib.urlutils import (
190 escape,
191@@ -348,3 +356,50 @@
192 f.close()
193 repo[None].add([filename])
194 repo.commit(text='<The commit message>', user='jane Foo <joe@foo.com>')
195+
196+
197+class BzrServer(Server):
198+
199+ def __init__(self, repository_path, use_server=False):
200+ super(BzrServer, self).__init__()
201+ self.repository_path = repository_path
202+ self._use_server = use_server
203+
204+ def createRepository(self, path):
205+ BzrDir.create_branch_convenience(path)
206+
207+ def makeRepo(self, tree_contents):
208+ branch = Branch.open(self.repository_path)
209+ branch.get_config().set_user_option("create_signatures", "never")
210+ builder = BranchBuilder(branch=branch)
211+ actions = [('add', ('', 'tree-root', 'directory', None))]
212+ actions += [('add', (path, path+'-id', 'file', content)) for (path,
213+ content) in tree_contents]
214+ builder.build_snapshot(None, None,
215+ actions, committer='Joe Foo <joe@foo.com>',
216+ message=u'<The commit message>')
217+
218+ def get_url(self):
219+ if self._use_server:
220+ return self._bzrserver.get_url()
221+ else:
222+ return local_path_to_url(self.repository_path)
223+
224+ def start_server(self):
225+ super(BzrServer, self).start_server()
226+ self.createRepository(self.repository_path)
227+ class LocalURLServer(TestServer):
228+ def __init__(self, repository_path):
229+ self.repository_path = repository_path
230+ def start_server(self): pass
231+ def get_url(self):
232+ return local_path_to_url(self.repository_path)
233+ if self._use_server:
234+ self._bzrserver = ReadonlySmartTCPServer_for_testing()
235+ self._bzrserver.start_server(
236+ LocalURLServer(self.repository_path))
237+
238+ def stop_server(self):
239+ super(BzrServer, self).stop_server()
240+ if self._use_server:
241+ self._bzrserver.stop_server()
242
243=== modified file 'lib/lp/codehosting/codeimport/tests/test_worker.py'
244--- lib/lp/codehosting/codeimport/tests/test_worker.py 2011-08-24 19:39:15 +0000
245+++ lib/lp/codehosting/codeimport/tests/test_worker.py 2011-08-27 23:26:24 +0000
246@@ -16,6 +16,9 @@
247 Branch,
248 BranchReferenceFormat,
249 )
250+from bzrlib.branchbuilder import (
251+ BranchBuilder,
252+ )
253 from bzrlib.bzrdir import (
254 BzrDir,
255 BzrDirFormat,
256@@ -45,6 +48,7 @@
257 from lp.codehosting import load_optional_plugin
258 from lp.codehosting.safe_open import (
259 AcceptAnythingPolicy,
260+ BlacklistPolicy,
261 BadUrl,
262 SafeBranchOpener,
263 )
264@@ -53,6 +57,7 @@
265 extract_tarball,
266 )
267 from lp.codehosting.codeimport.tests.servers import (
268+ BzrServer,
269 CVSServer,
270 GitServer,
271 MercurialServer,
272@@ -60,6 +65,7 @@
273 )
274 from lp.codehosting.codeimport.worker import (
275 BazaarBranchStore,
276+ BzrImportWorker,
277 BzrSvnImportWorker,
278 CodeImportBranchOpenPolicy,
279 CodeImportWorkerExitCode,
280@@ -1012,13 +1018,13 @@
281 t.mkdir('reference')
282 a_bzrdir = BzrDir.create(self.get_url('reference'))
283 BranchReferenceFormat().initialize(a_bzrdir, target_branch=branch)
284- return a_bzrdir.root_transport.base
285+ return a_bzrdir.root_transport.base, branch.base
286
287 def test_reject_branch_reference(self):
288 # URLs that point to other branch types than that expected by the
289 # import should be rejected.
290 args = {'rcstype': self.rcstype}
291- reference_url = self.createBranchReference()
292+ reference_url, target_url = self.createBranchReference()
293 if self.rcstype in ('git', 'bzr-svn', 'hg'):
294 args['url'] = reference_url
295 else:
296@@ -1195,6 +1201,83 @@
297 opener_policy=opener_policy)
298
299
300+class TestBzrImport(WorkerTest, TestActualImportMixin,
301+ PullingImportWorkerTests):
302+
303+ rcstype = 'bzr'
304+
305+ def setUp(self):
306+ super(TestBzrImport, self).setUp()
307+ self.setUpImport()
308+
309+ def makeImportWorker(self, source_details, opener_policy):
310+ """Make a new `ImportWorker`."""
311+ return BzrImportWorker(
312+ source_details, self.get_transport('import_data'),
313+ self.bazaar_store, logging.getLogger(), opener_policy)
314+
315+ def makeForeignCommit(self, source_details):
316+ """Change the foreign tree, generating exactly one commit."""
317+ branch = Branch.open(source_details.url)
318+ builder = BranchBuilder(branch=branch)
319+ builder.build_commit(message=self.factory.getUniqueString(),
320+ committer="Joe Random Hacker <joe@example.com>")
321+ self.foreign_commit_count += 1
322+
323+ def makeSourceDetails(self, branch_name, files):
324+ """Make Bzr `CodeImportSourceDetails` pointing at a real Bzr repo.
325+ """
326+ repository_path = self.makeTemporaryDirectory()
327+ bzr_server = BzrServer(repository_path)
328+ bzr_server.start_server()
329+ self.addCleanup(bzr_server.stop_server)
330+
331+ bzr_server.makeRepo(files)
332+ self.foreign_commit_count = 1
333+
334+ return self.factory.makeCodeImportSourceDetails(
335+ rcstype='bzr', url=bzr_server.get_url())
336+
337+ def test_partial(self):
338+ self.skip(
339+ "Partial fetching is not supported for native bzr branches "
340+ "at the moment.")
341+
342+ def test_unsupported_feature(self):
343+ self.skip("All Bazaar features are supported by Bazaar.")
344+
345+ def test_reject_branch_reference(self):
346+ # Branch references are allowed in the BzrImporter, but their URL
347+ # should be checked.
348+ reference_url, target_url = self.createBranchReference()
349+ source_details = self.factory.makeCodeImportSourceDetails(
350+ url=reference_url, rcstype='bzr')
351+ policy = BlacklistPolicy(True, [target_url])
352+ worker = self.makeImportWorker(source_details, opener_policy=policy)
353+ self.assertEqual(
354+ CodeImportWorkerExitCode.FAILURE_FORBIDDEN, worker.run())
355+
356+ def test_support_branch_reference(self):
357+ # Branch references are allowed in the BzrImporter.
358+ reference_url, target_url = self.createBranchReference()
359+ target_branch = Branch.open(target_url)
360+ builder = BranchBuilder(branch=target_branch)
361+ builder.build_commit(message=self.factory.getUniqueString(),
362+ committer="Some Random Hacker <jane@example.com>")
363+ source_details = self.factory.makeCodeImportSourceDetails(
364+ url=reference_url, rcstype='bzr')
365+ worker = self.makeImportWorker(source_details,
366+ opener_policy=AcceptAnythingPolicy())
367+ self.assertEqual(
368+ CodeImportWorkerExitCode.SUCCESS, worker.run())
369+ branch = self.getStoredBazaarBranch(worker)
370+ self.assertEqual(
371+ 1, len(branch.revision_history()))
372+ self.assertEqual(
373+ "Some Random Hacker <jane@example.com>",
374+ branch.repository.get_revision(branch.last_revision()).committer)
375+
376+
377 class CodeImportBranchOpenPolicyTests(TestCase):
378
379 def setUp(self):
380
381=== modified file 'lib/lp/codehosting/codeimport/tests/test_workermonitor.py'
382--- lib/lp/codehosting/codeimport/tests/test_workermonitor.py 2011-08-27 03:35:53 +0000
383+++ lib/lp/codehosting/codeimport/tests/test_workermonitor.py 2011-08-27 23:26:24 +0000
384@@ -49,6 +49,7 @@
385 from lp.code.model.codeimportjob import CodeImportJob
386 from lp.codehosting import load_optional_plugin
387 from lp.codehosting.codeimport.tests.servers import (
388+ BzrServer,
389 CVSServer,
390 GitServer,
391 MercurialServer,
392@@ -67,6 +68,7 @@
393 CodeImportWorkerMonitorProtocol,
394 ExitQuietly,
395 )
396+from lp.codehosting.safe_open import AcceptAnythingPolicy
397 from lp.services.log.logger import BufferLogger
398 from lp.services.twistedsupport import suppress_stderr
399 from lp.services.twistedsupport.tests.test_processmonitor import (
400@@ -704,6 +706,17 @@
401 return self.factory.makeCodeImport(
402 hg_repo_url=self.hg_server.get_url())
403
404+ def makeBzrCodeImport(self):
405+ """Make a `CodeImport` that points to a real Bazaar branch."""
406+ self.bzr_server = BzrServer(self.repo_path)
407+ self.bzr_server.start_server()
408+ self.addCleanup(self.bzr_server.stop_server)
409+
410+ self.bzr_server.makeRepo([('README', 'contents')])
411+ self.foreign_commit_count = 1
412+ return self.factory.makeCodeImport(
413+ bzr_branch_url=self.bzr_server.get_url())
414+
415 def getStartedJobForImport(self, code_import):
416 """Get a started `CodeImportJob` for `code_import`.
417
418@@ -806,6 +819,15 @@
419 result = self.performImport(job_id)
420 return result.addCallback(self.assertImported, code_import_id)
421
422+ def test_import_bzr(self):
423+ # Create a Bazaar CodeImport and import it.
424+ job = self.getStartedJobForImport(self.makeBzrCodeImport())
425+ code_import_id = job.code_import.id
426+ job_id = job.id
427+ self.layer.txn.commit()
428+ result = self.performImport(job_id)
429+ return result.addCallback(self.assertImported, code_import_id)
430+
431 def test_import_bzrsvn(self):
432 # Create a Subversion-via-bzr-svn CodeImport and import it.
433 job = self.getStartedJobForImport(self.makeBzrSvnCodeImport())
434
435=== modified file 'lib/lp/codehosting/codeimport/worker.py'
436--- lib/lp/codehosting/codeimport/worker.py 2011-08-26 10:00:49 +0000
437+++ lib/lp/codehosting/codeimport/worker.py 2011-08-27 23:26:24 +0000
438@@ -6,6 +6,7 @@
439 __metaclass__ = type
440 __all__ = [
441 'BazaarBranchStore',
442+ 'BzrImportWorker',
443 'BzrSvnImportWorker',
444 'CSCVSImportWorker',
445 'CodeImportBranchOpenPolicy',
446@@ -33,8 +34,8 @@
447 from bzrlib.errors import (
448 ConnectionError,
449 InvalidEntryName,
450+ NoRepositoryPresent,
451 NoSuchFile,
452- NoRepositoryPresent,
453 NotBranchError,
454 )
455 from bzrlib.transport import get_transport
456@@ -249,9 +250,9 @@
457
458 :ivar branch_id: The id of the branch associated to this code import, used
459 for locating the existing import and the foreign tree.
460- :ivar rcstype: 'svn', 'cvs', 'hg', 'git', 'bzr-svn', as appropriate.
461+ :ivar rcstype: 'svn', 'cvs', 'hg', 'git', 'bzr-svn', 'bzr' as appropriate.
462 :ivar url: The branch URL if rcstype in ['svn', 'bzr-svn',
463- 'git', 'hg'], None otherwise.
464+ 'git', 'hg', 'bzr'], None otherwise.
465 :ivar cvs_root: The $CVSROOT if rcstype == 'cvs', None otherwise.
466 :ivar cvs_module: The CVS module if rcstype == 'cvs', None otherwise.
467 """
468@@ -269,7 +270,7 @@
469 """Convert command line-style arguments to an instance."""
470 branch_id = int(arguments.pop(0))
471 rcstype = arguments.pop(0)
472- if rcstype in ['svn', 'bzr-svn', 'git', 'hg']:
473+ if rcstype in ['svn', 'bzr-svn', 'git', 'hg', 'bzr']:
474 [url] = arguments
475 cvs_root = cvs_module = None
476 elif rcstype == 'cvs':
477@@ -296,6 +297,8 @@
478 return cls(branch_id, 'git', str(code_import.url))
479 elif code_import.rcs_type == RevisionControlSystems.HG:
480 return cls(branch_id, 'hg', str(code_import.url))
481+ elif code_import.rcs_type == RevisionControlSystems.BZR:
482+ return cls(branch_id, 'bzr', str(code_import.url))
483 else:
484 raise AssertionError("Unknown rcstype %r." % code_import.rcs_type)
485
486@@ -303,7 +306,7 @@
487 """Return a list of arguments suitable for passing to a child process.
488 """
489 result = [str(self.branch_id), self.rcstype]
490- if self.rcstype in ['svn', 'bzr-svn', 'git', 'hg']:
491+ if self.rcstype in ['svn', 'bzr-svn', 'git', 'hg', 'bzr']:
492 result.append(self.url)
493 elif self.rcstype == 'cvs':
494 result.append(self.cvs_root)
495@@ -708,7 +711,12 @@
496 remote_branch_tip = remote_branch.last_revision()
497 inter_branch = InterBranch.get(remote_branch, bazaar_branch)
498 self._logger.info("Importing branch.")
499- inter_branch.fetch(limit=self.getRevisionLimit())
500+ revision_limit = self.getRevisionLimit()
501+ if revision_limit is None:
502+ # bzr < 2.4 does not support InterBranch.fetch()
503+ bazaar_branch.fetch(remote_branch)
504+ else:
505+ inter_branch.fetch(limit=revision_limit)
506 if bazaar_branch.repository.has_revision(remote_branch_tip):
507 pull_result = inter_branch.pull(overwrite=True)
508 if pull_result.old_revid != pull_result.new_revid:
509@@ -922,3 +930,25 @@
510 """See `PullingImportWorker.probers`."""
511 from bzrlib.plugins.svn import SvnRemoteProber
512 return [SvnRemoteProber]
513+
514+
515+class BzrImportWorker(PullingImportWorker):
516+ """An import worker for importing Bazaar branches."""
517+
518+ invalid_branch_exceptions = [
519+ NotBranchError,
520+ ConnectionError,
521+ ]
522+ unsupported_feature_exceptions = []
523+
524+ def getRevisionLimit(self):
525+ """See `PullingImportWorker.getRevisionLimit`."""
526+ # For now, just grab the whole branch at once.
527+ # bzr does support fetch(limit=) but it isn't very efficient at the moment.
528+ return None
529+
530+ @property
531+ def probers(self):
532+ """See `PullingImportWorker.probers`."""
533+ from bzrlib.bzrdir import BzrProber, RemoteBzrProber
534+ return [BzrProber, RemoteBzrProber]
535
536=== modified file 'lib/lp/registry/browser/productseries.py'
537--- lib/lp/registry/browser/productseries.py 2011-05-27 21:12:25 +0000
538+++ lib/lp/registry/browser/productseries.py 2011-08-27 23:26:24 +0000
539@@ -827,15 +827,6 @@
540 ))
541
542
543-class RevisionControlSystemsExtended(RevisionControlSystems):
544- """External RCS plus Bazaar."""
545- BZR = DBItem(99, """
546- Bazaar
547-
548- External Bazaar branch.
549- """)
550-
551-
552 class SetBranchForm(Interface):
553 """The fields presented on the form for setting a branch."""
554
555@@ -844,7 +835,7 @@
556 ['cvs_module'])
557
558 rcs_type = Choice(title=_("Type of RCS"),
559- required=False, vocabulary=RevisionControlSystemsExtended,
560+ required=False, vocabulary=RevisionControlSystems,
561 description=_(
562 "The version control system to import from. "))
563
564@@ -908,7 +899,7 @@
565 @property
566 def initial_values(self):
567 return dict(
568- rcs_type=RevisionControlSystemsExtended.BZR,
569+ rcs_type=RevisionControlSystems.BZR,
570 branch_type=LINK_LP_BZR,
571 branch_location=self.context.branch)
572
573@@ -989,7 +980,7 @@
574 self.setFieldError(
575 'rcs_type',
576 'You must specify the type of RCS for the remote host.')
577- elif rcs_type == RevisionControlSystemsExtended.CVS:
578+ elif rcs_type == RevisionControlSystems.CVS:
579 if 'cvs_module' not in data:
580 self.setFieldError(
581 'cvs_module',
582@@ -1022,8 +1013,9 @@
583 # Extend the allowed schemes for the repository URL based on
584 # rcs_type.
585 extra_schemes = {
586- RevisionControlSystemsExtended.BZR_SVN: ['svn'],
587- RevisionControlSystemsExtended.GIT: ['git'],
588+ RevisionControlSystems.BZR_SVN: ['svn'],
589+ RevisionControlSystems.GIT: ['git'],
590+ RevisionControlSystems.BZR: ['bzr'],
591 }
592 schemes.update(extra_schemes.get(rcs_type, []))
593 return schemes
594@@ -1050,7 +1042,7 @@
595 # The branch location is not required for validation.
596 self._setRequired(['branch_location'], False)
597 # The cvs_module is required if it is a CVS import.
598- if rcs_type == RevisionControlSystemsExtended.CVS:
599+ if rcs_type == RevisionControlSystems.CVS:
600 self._setRequired(['cvs_module'], True)
601 else:
602 raise AssertionError("Unknown branch type %s" % branch_type)
603@@ -1110,7 +1102,7 @@
604 # Either create an externally hosted bzr branch
605 # (a.k.a. 'mirrored') or create a new code import.
606 rcs_type = data.get('rcs_type')
607- if rcs_type == RevisionControlSystemsExtended.BZR:
608+ if rcs_type == RevisionControlSystems.BZR:
609 branch = self._createBzrBranch(
610 BranchType.MIRRORED, branch_name, branch_owner,
611 data['repo_url'])
612@@ -1123,7 +1115,7 @@
613 'the series.')
614 else:
615 # We need to create an import request.
616- if rcs_type == RevisionControlSystemsExtended.CVS:
617+ if rcs_type == RevisionControlSystems.CVS:
618 cvs_root = data.get('repo_url')
619 cvs_module = data.get('cvs_module')
620 url = None
621
622=== modified file 'lib/lp/registry/browser/tests/productseries-setbranch-view.txt'
623--- lib/lp/registry/browser/tests/productseries-setbranch-view.txt 2010-04-30 05:21:19 +0000
624+++ lib/lp/registry/browser/tests/productseries-setbranch-view.txt 2011-08-27 23:26:24 +0000
625@@ -178,7 +178,7 @@
626 ... 'field.rcs_type': 'BZR',
627 ... 'field.branch_name': 'blazer-branch',
628 ... 'field.branch_owner': product.owner.name,
629- ... 'field.repo_url': 'bzr://bzr.com/foo',
630+ ... 'field.repo_url': 'bzr+ssh://bzr.com/foo',
631 ... 'field.actions.update': 'Update',
632 ... }
633 >>> view = create_initialized_view(series, name='+setbranch', principal=product.owner, form=form)
634@@ -186,8 +186,9 @@
635 ... print notification.message
636 >>> for error in view.errors:
637 ... print error
638- ('repo_url'...The URI scheme "bzr" is not allowed. Only URIs with the following schemes may be
639- used: http, https'))
640+ ('repo_url'...The URI scheme "bzr+ssh" is not allowed. Only URIs with the following schemes may be
641+ used: bzr, http, https'))
642+
643
644 A correct URL is accepted.
645
646
647=== modified file 'lib/lp/testing/factory.py'
648--- lib/lp/testing/factory.py 2011-08-26 04:15:38 +0000
649+++ lib/lp/testing/factory.py 2011-08-27 23:26:24 +0000
650@@ -474,7 +474,7 @@
651 branch_id = self.getUniqueInteger()
652 if rcstype is None:
653 rcstype = 'svn'
654- if rcstype in ['svn', 'bzr-svn', 'hg']:
655+ if rcstype in ['svn', 'bzr-svn', 'hg', 'bzr']:
656 assert cvs_root is cvs_module is None
657 if url is None:
658 url = self.getUniqueURL()
659@@ -2123,7 +2123,8 @@
660
661 def makeCodeImport(self, svn_branch_url=None, cvs_root=None,
662 cvs_module=None, target=None, branch_name=None,
663- git_repo_url=None, hg_repo_url=None, registrant=None,
664+ git_repo_url=None, hg_repo_url=None,
665+ bzr_branch_url=None, registrant=None,
666 rcs_type=None, review_status=None):
667 """Create and return a new, arbitrary code import.
668
669@@ -2132,7 +2133,7 @@
670 unique URL.
671 """
672 if (svn_branch_url is cvs_root is cvs_module is git_repo_url is
673- hg_repo_url is None):
674+ hg_repo_url is bzr_branch_url is None):
675 svn_branch_url = self.getUniqueURL()
676
677 if target is None:
678@@ -2163,6 +2164,11 @@
679 registrant, target, branch_name,
680 rcs_type=RevisionControlSystems.HG,
681 url=hg_repo_url)
682+ elif bzr_branch_url is not None:
683+ code_import = code_import_set.new(
684+ registrant, target, branch_name,
685+ rcs_type=RevisionControlSystems.BZR,
686+ url=bzr_branch_url)
687 else:
688 assert rcs_type in (None, RevisionControlSystems.CVS)
689 code_import = code_import_set.new(
690
691=== modified file 'scripts/code-import-worker.py'
692--- scripts/code-import-worker.py 2011-08-24 21:36:52 +0000
693+++ scripts/code-import-worker.py 2011-08-27 23:26:24 +0000
694@@ -26,9 +26,9 @@
695 from canonical.config import config
696 from lp.codehosting import load_optional_plugin
697 from lp.codehosting.codeimport.worker import (
698- BzrSvnImportWorker, CSCVSImportWorker, CodeImportBranchOpenPolicy,
699- CodeImportSourceDetails, GitImportWorker, HgImportWorker,
700- get_default_bazaar_branch_store)
701+ BzrImportWorker, BzrSvnImportWorker, CSCVSImportWorker,
702+ CodeImportBranchOpenPolicy, CodeImportSourceDetails, GitImportWorker,
703+ HgImportWorker, get_default_bazaar_branch_store)
704 from lp.codehosting.safe_open import AcceptAnythingPolicy
705 from canonical.launchpad import scripts
706
707@@ -77,6 +77,9 @@
708 elif source_details.rcstype == 'hg':
709 load_optional_plugin('hg')
710 import_worker_cls = HgImportWorker
711+ elif source_details.rcstype == 'bzr':
712+ load_optional_plugin('loom')
713+ import_worker_cls = BzrImportWorker
714 elif source_details.rcstype in ['cvs', 'svn']:
715 import_worker_cls = CSCVSImportWorker
716 else: